/* * Bakers Lite JS Code * q.js v2019.12-17 * http://anonsw.github.io/8chjs/ */ // Settings var qflair = ''; // Examples: REAL, → var qcolor = '#ffc'; var youcolor = '#fcc'; var scrollcolor = 'rgba(153, 153, 153, 0.6)'; var scrollbackcolor = '#333'; var scrolltime = 400; // ms var updateDelay = 200; // ms var sidenavWidth = 30; // px var floodEnabled = false; var floodThreshold = 15; // min # posts before beginning fade var floodVanish = 30; // max # posts before completed fade/hide var floodBehavior = 'fade'; // hide, fade var fadenametripregex = /^(Anon(ymous)?-*|Q -!!Hs1Jq13jV6)$/i; var fadenametripfloodvalue = -1; // Effective post count for fading; or -1 for auto of floodThreshold+post count var rateHistoryLen = 50; // Data points on chart var rateAvgLen = 10; // Number of data points to average for instantaneous rate /* House keeping variables */ var qposts = []; var allqposts = []; var currq = -1; var youposts = []; var curryou = -1; var qnavposts = []; var younavposts = []; var ctx; var borderSz; var scrollWd; var minheight; var ratehistory = []; // Suggestions from 589388.html#590283 // ...shill detection features, such as // easily knowing the proportion of posts from a user that don't link. // I'd want to know any ID that was posting > 1/3 posts targetting noone. // TODO: Behavior for post hover should be to show original post visual before all q.js mods // TODO: Add flags to turn on/off features // TODO: Custom-regex -> post color/fade (auto-filter by selecting text and choosing new menu item?) // Examples: daily reminder, guys, check this out, shill, get out, filtered, tell us more, archive everything // TODO: Manual shade // TODO: remove Q trip codes from post content? // TODO: remove Q from end of post content if not a Q post? // TODO: recognize all of known Q trip codes? (make to sure to exclude known comps) // TODO: Links to reset on current Q/(you) post // TODO: Link to go to latest post (end key doesn't always work, but try capturing that as well?) // TODO: Keyboard shortcuts for navigation // TODO: Current/Total overall post navigation // TODO: Remap end key to always go to end of page // TODO: Check box for each post to mark as "read", "spam", ? // TODO: Autocorrect all-caps posts (50% threshold)? // TODO: Correct broken links but remove referral when clicked? // TODO: Make flood post fading non-linear to give leniency to posters just passing flood threshold // TODO: Penalize reposts in flood detection (if id's different, merge?) ? // TODO: Scorecard of posters ordered by post count (post rate, reply count, ...)? // TODO: Color/shade posts where there are no references and no question marks // TODO: If Q or trip used in name field, strike them out or replace with Anonymous? // TODO: embedded posts in Q posts don't have background-color and inherit Q color, fix? /* Case insensitive contains selector for finding yous. SO #8746882 */ jQuery.expr[":"].icontains = jQuery.expr.createPseudo(function (arg) { return function (elem) { return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0; }; }); /* On scroll stop. SO #9144560 */ (function ($) { var on = $.fn.on, timer; $.fn.on = function () { var args = Array.apply(null, arguments); var last = args[args.length - 1]; if (isNaN(last) || (last === 1 && args.pop())) return on.apply(this, args); var delay = args.pop(); var fn = args.pop(); args.push(function () { var self = this, params = arguments; clearTimeout(timer); timer = setTimeout(function () { fn.apply(self, params); }, delay); }); return on.apply(this, args); }; }(this.jQuery || this.Zepto)); /* Scroll to element */ function myScrollTo(el) { $('html, body').animate({ scrollTop: $(el).offset().top - $('div.boardlist').height() }, scrolltime); } /* Highlight you references */ function highlightYouRefs() { $('div.body:icontains("(You)")').each(function() { $(this).parents('div.post').first().css('background-color',youcolor); }); return $.Deferred().resolve(); } /* Remove invalid (you)'s */ function removeInvalidYous() { $('div.body:icontains("(You)")').each(function() { $(this).find(':not(small)').contents().filter(function() { return this.nodeType == 3 }).each(function() { this.textContent = this.textContent.replace(/\(+ *You *\)+/ig, "you"); }); }); return $.Deferred().resolve(); } /* Highlight Q posts */ function highlightQ() { $(allqposts).each(function(idx,val) { var div = $(val).parents('div.post').first(); if($(div).css('background-color') !== qcolor) { if(qflair !== "") { $(val).prepend(qflair + " "); } $(div).css('background-color', qcolor); } }); return $.Deferred().resolve(); } /* Scroll to next Q */ function nextq() { if(qposts.length > 0) { if(currq < qposts.length-1) { currq++; } myScrollTo($(qposts).get(currq)); } } /* Scroll to last Q */ function lastq() { if(qposts.length > 0) { currq = qposts.length - 1; myScrollTo($(qposts).get(currq)); } } /* Scroll to previous Q */ function prevq() { if(qposts.length > 0) { if(currq > 0) { currq--; } myScrollTo($(qposts).get(currq)); } } /* Scroll to first Q */ function firstq() { if(qposts.length > 0) { currq = 0; myScrollTo($(qposts).get(currq)); } } /* Scroll to next (You) */ function nextyou() { if(youposts.length > 0) { if(curryou < youposts.length-1) { curryou++; } myScrollTo($(youposts).get(curryou)); } } /* Scroll to last (You) */ function lastyou() { if(youposts.length > 0) { curryou = youposts.length - 1; myScrollTo($(youposts).get(curryou)); } } /* Scroll to previous (You) */ function prevyou() { if(youposts.length > 0) { if(curryou > 0) { curryou--; } myScrollTo($(youposts).get(curryou)); } } /* Scroll to first (You) */ function firstyou() { if(youposts.length > 0) { curryou = 0; myScrollTo($(youposts).get(curryou)); } } /* Inserts Q navigation links */ function qnav() { $('div.boardlist').append('<span>[ <a href="javascript:firstq();"><i class="fa fa-step-backward"></i></a> <a href="javascript:prevq();"><i class="fa fa-backward"></i></a> <span style="filter:brightness(70%);">Q</span> <span class="qcount">(?:?)</span> <a href="javascript:nextq();"><i class="fa fa-forward"></i></a> <a href="javascript:lastq();"><i class="fa fa-step-forward"></i></a> ]</span>'); } /* Inserts (You) navigation links */ function younav() { $('div.boardlist').append('<span>[ <a href="javascript:firstyou();"><i class="fa fa-step-backward"></i></a> <a href="javascript:prevyou();"><i class="fa fa-backward"></i></a> <span style="filter:brightness(70%);">(You)</span> <span class="youcount">(?:?)</span> </span><a href="javascript:nextyou();"><i class="fa fa-forward"></i></a> <a href="javascript:lastyou();"><i class="fa fa-step-forward"></i></a> ]</span>'); } /* Inserts feature toggle links */ function togglenav() { $('div.boardlist').append('<span>[ <a href="javascript:toggleFlood();">Turn Post Fading <span class="toggleFloodState">On</span></a> ]</span>') } function postratenav() { var height = $('div.boardlist').height() - 1; $('div.boardlist').append('<span>[ Post Rate: <span class="postRate">0</span> posts/min <canvas class="postRateChart"></canvas>]</span>') $('.postRate').css('color', $('div.boardlist a').css('color')); var charts = $('.postRateChart'); $(charts).each(function() { $(this).css('width', '100px'); $(this).css('height', height); $(this).css('vertical-align', 'middle'); //$(this).css('border', '1px solid'); //$(this).css('border-color', $('div.boardlist').css('color')); var gctx = $(this).get(0).getContext('2d'); gctx.canvas.height = 20; gctx.canvas.width = 100; }); } /* Inserts side navigation */ function sidenav() { $('body').append('<canvas id="sidenav"></canvas>'); var nav = $('#sidenav'); $(nav).css('position', 'fixed'); $(nav).css('top', $('div.boardlist').height()); $(nav).css('right', 0); $(nav).css('width', sidenavWidth); $(nav).css('height', $(window).height() - $('div.boardlist').height()); $(nav).css('background-color', scrollbackcolor); $('body').css('margin-right', sidenavWidth); ctx = $('#sidenav').get(0).getContext('2d'); //ctx.canvas.height = $(document).height() - $('div.boardlist').height(); ctx.canvas.height = 2048; ctx.canvas.width = sidenavWidth; borderSz = 1; scrollWd = ctx.canvas.width / 2; } /* Update navigation counts */ function updateNavCounts() { var fontSize = -1; var lineHeight; if(currq > qposts.length) { currq = qposts.length; } if(curryou > youposts.length) { curryou = youposts.length; } for(i=0; i<qposts.length; i++) { var el = $(qposts).get(i); if(fontSize == -1) { fontSize = $(el).css('font-size'); lineHeight = Math.floor(parseInt(fontSize.replace('px', '')) * 1.5); } if(($(el).offset().top + $(el).height() - 2.25*lineHeight) > $(window).scrollTop()) { currq = i; break; } } for(i=0; i<youposts.length; i++) { var el = $(youposts).get(i); if(fontSize == -1) { fontSize = $(el).css('font-size'); lineHeight = Math.floor(parseInt(fontSize.replace('px', '')) * 1.5); } if(($(el).offset().top + $(el).height() - 2.25*lineHeight) > $(window).scrollTop()) { curryou = i; break; } } // TODO: check for duplicates and remove from counts $('.qcount').text("(" + (currq+1) + ":" + qposts.length + ")"); $('.youcount').text("(" + (curryou+1) + ":" + youposts.length + ")"); } /* Update navigation graphics */ function updateNavGraphics() { var sidenav = $('#sidenav'); if(sidenav.length) { $(sidenav).css('height', $(window).height() - $('div.boardlist').height()); minheight = ctx.canvas.height / ($(window).height() - $('div.boardlist').height()); // 1px ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // Draw nav q posts qnavposts = []; ctx.fillStyle = qcolor; for (i = 0; i < qposts.length; i++) { // TODO: check if we have already added post, don't draw it again var el = $(qposts).get(i); var height = $(el).height() / $(document).height() * ctx.canvas.height; if(height < minheight) height = minheight; qnavposts[i] = { x : borderSz, y : $(el).offset().top / $(document).height() * ctx.canvas.height, width : ctx.canvas.width - borderSz*2, height : height }; ctx.fillRect(qnavposts[i].x, qnavposts[i].y, qnavposts[i].width, qnavposts[i].height); } // Draw nav you posts younavposts = []; ctx.fillStyle = youcolor; for (i = 0; i < youposts.length; i++) { // TODO: check if we have already added post, don't add it again var el = $(youposts).get(i); var height = $(el).height() / $(document).height() * ctx.canvas.height; if(height < minheight) height = minheight; younavposts[i] = { x : borderSz, y : $(el).offset().top / $(document).height() * ctx.canvas.height, width : ctx.canvas.width - borderSz*2, height : height }; ctx.fillRect(younavposts[i].x, younavposts[i].y, younavposts[i].width, younavposts[i].height); } // Update nav window ctx.fillStyle = scrollcolor; ctx.fillRect( scrollWd / 2, $(window).scrollTop() / $(document).height() * ctx.canvas.height, scrollWd, $(window).height() / $(document).height() * ctx.canvas.height ); // Add red marker at bottom of >750 posts if($('#thread_stats_posts').text() > 750) { var barHeight = (4 * 2048) / ($(window).height() - $('div.boardlist').height()); ctx.fillStyle = '#f66'; ctx.fillRect( 0, ctx.canvas.height - barHeight, ctx.canvas.width, barHeight ); } } } function updateNavPostRate() { var posts = $('div.post').not('.post-hover'); var startPost = posts.length - (rateHistoryLen + rateAvgLen) + 1; if(startPost < 1) startPost = 1; var start = $($($(posts).get(0)).find('.intro time').get(0)).attr('unixtime'); //$('div.post:first .intro time').attr('unixtime'); ratehistory = []; timehistory = []; for(var i=startPost; i<posts.length; i++) { // TODO: check if we have already added post, don't add it again var step = $($($(posts).get(i)).find('.intro time').get(0)).attr('unixtime'); //$($('div.post .intro time').get(i)).attr('unixtime'); timehistory[timehistory.length] = step; if(timehistory.length - rateAvgLen - 1 >= 0) { var avgend = timehistory[timehistory.length - 1]; var avgstart = timehistory[timehistory.length - rateAvgLen - 1]; ratehistory[ratehistory.length] = rateAvgLen / ((avgend - avgstart) / 60); } else { ratehistory[ratehistory.length] = 0; } } //console.log(ratehistory); // $('.postRate').text(ratehistory[ratehistory.length-1].toFixed(1)); if (ratehistory.length) { $('.postRate').text(ratehistory[ratehistory.length-1].toFixed(1)); } if(ratehistory.length > rateAvgLen) { var maxRate = Math.max.apply(null, ratehistory); var minRate = Math.min.apply(null, ratehistory); //console.log("Max: " + maxRate); //console.log("Min: " + minRate); if(minRate > (maxRate - 0.5)) { minRate = maxRate - 0.5; maxRate = maxRate + 0.5; } if(minRate < 0) { minRate = 0; } var maxTime = timehistory[timehistory.length-1]; var minTime = timehistory[rateAvgLen]; $('.postRateChart').each(function() { var gctx = $(this).get(0).getContext('2d'); gctx.clearRect(0, 0, gctx.canvas.width, gctx.canvas.height); gctx.strokeStyle = $('div.boardlist a').css('color'); gctx.beginPath(); var x = 0; var y = gctx.canvas.height - (ratehistory[rateAvgLen] - minRate)/(maxRate - minRate) * gctx.canvas.height; gctx.moveTo(x, y); for(var i=rateAvgLen+1; i<ratehistory.length; i++) { x = (timehistory[i] - minTime)/(maxTime - minTime) * gctx.canvas.width; y = gctx.canvas.height - (ratehistory[i] - minRate)/(maxRate - minRate) * gctx.canvas.height; gctx.lineTo(x, y); } gctx.stroke(); gctx.closePath(); }); } } /* Update navigation */ function updateNav() { updateNavCounts(); updateNavGraphics(); updateNavPostRate(); } // Update nav when scrolling stops $(window).on('scroll', function(e) { updateNavCounts(); updateNavGraphics(); }, updateDelay); // Update nav when resize stops $(window).on('resize', function(e) { updateNav(); }, updateDelay); /* Set which posts are Q posts */ function setQPosts() { qposts = $.map($('div.post span.trip:contains("!!Hs1Jq13jV6"):visible').not('.post-hover'), function(el) { return $(el).parents('div.post').first(); }); allqposts = $('span.trip:contains("!!mG7VJxZNCI")'); return $.Deferred().resolve(); } /* Set which posts are you posts */ function setYouPosts() { youposts = $.map($('div.post span.own_post,div.body:icontains("(You)")').not('.post-hover'), function(el) { return $(el).parents('div.post').first(); }); return $.Deferred().resolve(); } function setFloodPosts() { if(floodEnabled) { var stats = {}; var firstId = null; $('span.poster_id').each(function () { var id = $(this).text(); if (!(id in stats)) { stats[id] = {count: 0, namefag: false, floodcount: 0}; } stats[id].count++; if(!stats[id].namefag) { var name = $(this).parents('div').first().find('span.name').text(); var trip = $(this).parents('div').first().find('span.trip').text(); stats[id].namefag = !fadenametripregex.test(name+'-'+trip); } if(stats[id].namefag) { if(fadenametripfloodvalue < 0) { stats[id].floodcount = floodThreshold + stats[id].count; } else { stats[id].floodcount = fadenametripfloodvalue; } } else { stats[id].floodcount = stats[id].count; } if (firstId == null) { firstId = id; } }); $.each(stats, function (key, value) { if (key !== firstId) { if (value.floodcount > floodThreshold || value.namefag) { if (floodBehavior === 'fade') { var intensity = value.floodcount; if (intensity > floodVanish) { intensity = floodVanish; } intensity = ((floodVanish - floodThreshold) - (intensity - floodThreshold)) / (floodVanish - floodThreshold); if (intensity < 0.1) { intensity = 0.1; } $('span.poster_id:contains("' + key + '")').each(function () { $(this).parents('div.post').first().css('opacity', intensity); $(this).parents('div.post').first().hover(function () { $(this).animate({opacity: 1.0}, updateDelay); }, function () { $(this).animate({opacity: intensity}, updateDelay); }); }); } else if (floodBehavior === 'hide') { if (value.count >= floodVanish) { $('span.poster_id:contains("' + key + '")').each(function () { $(this).parents('div.post').first().hide(); }); } } } } }); } return $.Deferred().resolve(); } function toggleFlood() { if(floodEnabled) { floodEnabled = false; $('.toggleFloodState').text('On'); if(floodBehavior === 'fade') { $('span.poster_id').each(function () { $(this).parents('div.post').first().css('opacity', 1); $(this).parents('div.post').first().off('mouseenter mouseleave'); }); } else if(floodBehavior === 'hide') { $(this).parents('div.post').first().show(); } } else { floodEnabled = true; $('.toggleFloodState').text('Off'); runq() } } /* Helper to run snippets in the right order */ function runq() { setQPosts() .done(highlightQ) .done(removeInvalidYous) .done(highlightYouRefs) .done(setYouPosts) .done(setFloodPosts) .done(updateNav); } /* Attach snippets to ready/change events */ $(document).ready(function() { qnav(); younav(); togglenav(); postratenav(); sidenav(); runq(); // Select the node that will be observed for mutations var targetNode = $('div.thread')[0]; // Options for the observer (which mutations to observe) var config = { childList: true }; // Callback function to execute when mutations are observed var callback = function(mutationsList) { for(var mutation of mutationsList) { if (mutation.type == 'childList') { runq(); break; } } }; // Create an observer instance linked to the callback function var observer = new MutationObserver(callback); // Start observing the target node for configured mutations observer.observe(targetNode, config); });