Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * 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);
- });
Add Comment
Please, Sign In to add comment