anonbin
bakers tools LITE Sept 29 2020 version
frb
!!!7Gs
…
CKPCGJZej
(153)
6b5920
(258)
2/03/21
5:41 pm EST
info
|
raw
|
download
dl
|
clone
new
paste
|
recent
|
about
// BEGIN Q.JS CODE -- Version 2020-09-29 // MODIFICATIONS BELOW THIS LINE NOT RECOMMENDED! // SEE https://github.com/learnedtocode/qjs // for instructions on modifying this script's behavior without modifying its code! jQuery(function($) { var defaultSettings = { qflair: '', // Examples: REAL, → qcolor: '#99d6ff', youcolor: '#D6D1EA', scrollcolor: 'rgba(255, 255, 255, 0.9)', scrollcolor2: 'rgba(255, 255, 255, 0.6)', // may enlarge scroll bar for visibility scrollminheight: 9, scrollbackcolor: '#333', offbreadcolor: '#ffb', // background for posts in other breads // background for the current post link when showing a quoted post via hover quotehovercolor: '#ebf', scrolltime: 0, // ms // youscrollcolor: (same as 'youcolor' by default), updateDelay: 30, // ms sidenavWidth: 30, // px // Improve reply citing when you select text and click to quote it. // NOTE: This setting also DISABLES the posting box at the top of each thread! improveReplyCiting: false, floodEnabled: false, floodThreshold: 15, // min # posts before beginning fade floodVanish: 25, // max # posts before completed fade/hide floodBehavior: 'fade', // hide, fade fadenametripregex: /^(Anon(ymous)?-.*|.*-!!Hs1Jq13jV6)$/i, fadenametripfloodvalue: -1, // Effective post count for fading, or -1 for auto of floodThreshold+post count strikeThroughNameFags: false, // doesn't work well with other boards! rateHistoryLen: 50, // Data points on chart rateAvgLen: 10, // Number of data points to average for instantaneous rate extraStyles: '', boards: { qrb: { youcolor: '#2a3a4a', youscrollcolor: '#4a6376', offbreadcolor: '#480000', quotehovercolor: '#705', extraStyles: ` .post > div.qjs-controls a { color: #ddaadd; } .body small { color: #339955; } span.qjs-pastebin { color: #c5c8c6; border-color: rgba(197, 200, 198, 0.6); } span.qjs-pastebin a { color: #f33 !important; } `, }, abcu: { // overuses !important in board styles youcolor: '#f3e77d !important', youscrollcolor: '#f3e77d', scrollbackcolor: 'rgba(51, 51, 51, 0.75)', extraStyles: ` .desktop-style div.boardlist:not(.bottom), .boardlist:hover { opacity: 1; background: rgb(195,225,255) !important; color: #cc0000 !important; } .qjs-overlay { color: #cc3333; } `, }, }, }; // If not on an individual thread page (ex. index or catalog) then just exit if (!/^\/[^\/]+\/res\/\d+\.html$/.test(window.location.pathname)) { return; } var qjs_main = window.qjs_main = window.qjs_main || {}; qjs_main.defaultSettings = defaultSettings; // House keeping variables var boardName = window.location.pathname.match(/\/([^\/]+)\//)[1]; var opPostId = window.location.href.match(/\/(\d+)\.html/)[1]; var qposts = []; var currq = -1; var youposts = []; var curryou = -1; var qnavposts = []; var younavposts = []; var postIdsByIndex = {}; var ctx; var borderSz; var minheight; var ratehistory = []; var floodEnabled = getSetting('floodEnabled'); var postCount = 0; var navScaleFactor = 3; var isCyclical = $('.post.op .fa.fa-refresh').length > 0; var isArtificialClick = false; function getSetting(name) { var s = window.qjsSettings; if (s && s.boards && boardName && s.boards[boardName] && name in s.boards[boardName]) { return s.boards[boardName][name]; } if (boardName && defaultSettings.boards[boardName] && name in defaultSettings.boards[boardName]) { return defaultSettings.boards[boardName][name]; } if (s && name in s) { return s[name]; } return defaultSettings[name]; } qjs_main.getSetting = getSetting; // Suggestions to consider for future development // 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? var $allPosts = null; var autoUpdateInterval; var autoUpdateFinishedCount = 0; // Image blacklist and Nope links var imageBlacklist = {}; function loadImageBlacklist() { Object.keys(JSON.parse(localStorage.imageBlacklistHash || "{}")) .forEach(addToImageBlacklist); } loadImageBlacklist(); function saveImageBlacklist() { localStorage.imageBlacklistHash = JSON.stringify(imageBlacklist); } function addToImageBlacklist(md5) { if (md5) { imageBlacklist[md5] = true; } } function findFileParent(img) { // For videos the DOM structure is .files > .file > { .fileinfo, .file } // For images it's .files > .file > { .fileinfo, a } // WTF... var $file = $(img).closest('.file'); var $file2 = $file.parent().closest('.file'); return $file2.length ? $file2 : $file; } function blacklistPostImages(post, undo) { $(post).find('img.post-image').each(function() { var md5 = this.getAttribute('data-md5'); if (undo) { delete imageBlacklist[md5]; findFileParent(this).removeClass('blacklisted'); } else { addToImageBlacklist(md5); findFileParent(this).addClass('blacklisted'); } }); updateBlacklistedCount(post); } function updateBlacklistedCount(post) { var $post = $(post); var blacklistedCount = $post.find('.file.blacklisted').length; // note: may be more than 1 '.file' element var totalCount = $post.find('.fileinfo').length; $post.find('.qjs-blacklist-count').text( blacklistedCount ? ' | ' + blacklistedCount + '/' + totalCount + ' :blacklisted' : '' ); } function addPostControls() { // Must iterate over all posts here, to get the correct index $allPosts.each(function(i, el) { if (!el.classList.contains('qjs-processed-controls')) { $(el).prepend( '<div class="qjs-controls">' + '[#' + i + '] ' + '<a class="qjs-hide" href="#">:hide</a>' + '<span class="qjs-blacklist-count"></span>' + '</div>' ); // see: "Don't scroll to my own new posts" section below postIdsByIndex[i] = el.id.replace(/^(disabled_)/, ''); el.classList.add('qjs-processed-controls'); } }); } function removeBlacklistedImages() { $allPosts.filter(':not(.qjs-processed-images)').each(function() { var blacklistedAny = false; $('img.post-image', this).each(function() { if (imageBlacklist[this.getAttribute('data-md5')]) { blacklistedAny = true; findFileParent(this).addClass('blacklisted'); } }); if (blacklistedAny) { updateBlacklistedCount(this); } this.classList.add('qjs-processed-images'); }); } function addPostNumberSpacesForElement(el, isMention) { var $el = $(el); var postNumber = $el.text(); var pieces = []; var prefix = postNumber.replace(/\d+$/, ''); var numbersOnly = postNumber.substring(prefix.length); if (!numbersOnly) { // may be a link directly to a board like >>>/abcu/ - ignore return; } while (numbersOnly.length % 3 !== 0) numbersOnly = ' ' + numbersOnly; for (var i = 0; i < numbersOnly.length / 3; i++) { pieces.push(numbersOnly.substring(i * 3, i * 3 + 3).trim()); } if (prefix) { pieces[0] = prefix + pieces[0]; } var tagOpen = '<span class="qjs-postnum-part">'; var tagClose = '</span>'; $el.html(tagOpen + pieces.join(tagClose + tagOpen) + tagClose); if (isMention) { $el.addClass('qjs-processed-postnums'); } } function addPostNumberSpaces() { $allPosts.filter(':not(.qjs-processed-postnums)').each(function() { var sels = [ '.post_no:not([id^="post_no"])', '.body-line a[onclick^="highlightReply("]', '.body-line a[href^="/"]', ]; $(this).find(sels.join(', ')).each(function() { addPostNumberSpacesForElement(this); }); this.classList.add('qjs-processed-postnums'); }); // Process quoted posts that appear during an update $allPosts.find('.mentioned > a:not(.qjs-processed-postnums)').each(function() { addPostNumberSpacesForElement(this, true); }); } function fixPostFlags() { $allPosts.filter(':not(.qjs-processed-flags)').each(function() { $('img.flag[src$="/undefined.png"]', this).remove(); this.classList.add('qjs-processed-flags'); }); } function fixAutoUpdates() { if ($allPosts.length < 700) { return; } if (autoUpdateInterval) { return; } if (!$('#auto_update_status').prop('checked')) { return; } var reenableAutoUpdateLimit = 600; function enableAutoUpdateCheck() { autoUpdateInterval = setInterval(function() { if (!$('#auto_update_status').prop('checked')) { var reenablePercent = Math.min( 100, Math.round(100 * autoUpdateFinishedCount / reenableAutoUpdateLimit) ); console.log('re-enabling auto update (' + reenablePercent + '%)'); isArtificialClick = true; $('#auto_update_status').click(); isArtificialClick = false; } if ($allPosts.length >= 751) { autoUpdateFinishedCount++; if (autoUpdateFinishedCount >= reenableAutoUpdateLimit) { clearInterval(autoUpdateInterval); autoUpdateInterval = null; console.log('auto-update check disabled (expired)'); } } }, 600); } enableAutoUpdateCheck(); $('#auto_update_status').on('click', function() { if (isArtificialClick) { return; } if ($(this).prop('checked')) { enableAutoUpdateCheck(); } else { console.log('auto-update check disabled (manual)'); clearInterval(autoUpdateInterval); autoUpdateInterval = null; } }); } $(document).on('click', 'div.qjs-controls a.qjs-hide', function() { var $post = $(this).closest('.post'); if (this.textContent === ':hide') { $post.addClass('qjs-hidden'); blacklistPostImages($post); this.textContent = ($post.find('.file').length ? ':show-text' : ':show'); } else if (this.textContent === ':show-text') { $post.removeClass('qjs-hidden'); this.textContent = ':show'; } else { // ':show' $post.removeClass('qjs-hidden'); blacklistPostImages($post, true); this.textContent = ':hide'; } saveImageBlacklist(); return false; }); $(document).on('mouseover', 'a.hash_unix.hash_u', function() { $(this).closest('.file.blacklisted').addClass('show-temp'); }).on('mouseout', 'a.hash_unix.hash_u', function() { $(this).closest('.file.blacklisted').removeClass('show-temp'); }); // Don't scroll to my own new posts // Relevant 8kun code: // highlightReply(post_response.id); // window.location.hash = post_response.id; // var scroll_reply = $('div.post#reply_' + post_response.id); // if (scroll_reply.length) { // $(window).scrollTop(scroll_reply.offset().top); // } var enableHighlightReply = true; window.highlightReplyOrig = window.highlightReply; window.highlightReply = function(id, event) { if (enableHighlightReply) { return window.highlightReplyOrig.call(this, id, event); } else { enableHighlightReply = true; var $post = $('div.post#reply_' + id); var $anchor = $('#' + id); // Disable scroll_reply in 8kun code $post.attr('id', 'disabled_reply_' + id); // Disable window.location.hash in 8kun code $anchor.attr('id', 'disabled_' + id); setTimeout(function() { // Restore normal function $post.attr('id', 'reply_' + id); $anchor.attr('id', id); }, 0); // Show a notice var $myPost = $('<div class="qjs_my_post">') .data('created', Date.now()) .data('expires', Date.now() + 60000); var $newPostIndicator = $('<a>') .text('>>' + id) .attr('href', '#' + id) .attr('onclick', 'highlightReply(' + JSON.stringify(id) + ', event)') $myPost.append($newPostIndicator); addPostNumberSpacesForElement($newPostIndicator); $myPost.append( $('<span>') .text(' ' + $post.find('time').text().trim().split(' ')[2]) ); $('#qjs_my_posts').append($myPost); fadeMyPosts(); // Don't highlight multiple posts that were just loaded // TODO why does this happen? $('.post.highlighted').removeClass('highlighted'); } }; $(document).ajaxSuccess(function(event, xhr, settings) { if (settings.url === 'https://sys.8kun.top/post.php') { enableHighlightReply = false; } }); function fadeMyPosts() { var $myPosts = $('.qjs_my_post'); if (!$myPosts.length) { return; } $myPosts.each(function() { var $this = $(this); if (Date.now() >= $this.data('expires')) { $this.remove(); } else { var ratio = ( ($this.data('expires') - Date.now()) / ($this.data('expires') - $this.data('created')) ); $this.css('opacity', 0.3 + 0.6 * ratio); } }); setTimeout(fadeMyPosts, 600); } if (getSetting('improveReplyCiting')) { // Fix reply citing: don't reset textarea scroll position or cursor position // To make this simpler, and also fix the quick reply box not appearing: // use the quick reply box only $(window).trigger('cite'); // Disable hide_at_top - relevant 8kun code: // if ($(this).scrollTop() < $origPostForm.offset().top + $origPostForm.height() - 100) { // $postForm.fadeOut(100); // } $('form[name="post"]:first').css({ display: 'block', position: 'absolute', top: '-900px', }); // Fix close-btn behavior $('#quick-reply .close-btn').off('click'); $('#quick-reply .close-btn').on('click', function() { $('#quick-reply #post-form-inner').hide(); }); // Fix [Post a Reply] button behavior $('#link-quick-reply').off('click'); $('#link-quick-reply').on('click', function() { $('#quick-reply #post-form-inner').show(); $('#quick-reply textarea[name=body]').focus(); return false; }); // Hide reply box until it's needed // but only if there are already some replies (i.e. not baking) if ($('.post.reply').length) { $('#quick-reply #post-form-inner').hide(); } window.citeReply = function(id, with_link) { $('#quick-reply #post-form-inner').show(); // TODO what triggers this to break? $('#post-form-outer textarea[name=body]').attr('id', ''); $('#quick-reply textarea[name=body]').attr('id', 'body'); var textarea = document.getElementById('body'); var toInsert = '>>' + id + '\n'; // 8kun code sets this to window.getSelection().toString() var selectedText = sessionStorage.quoteClipboard; if (selectedText) { toInsert += selectedText .split('\n') .map(function(line) { if (line !== '') { return '>' + line + '\n'; } else { // No way to tell the difference between real empty // lines and end of paragraph, so skip them return ''; } }) .join(''); delete sessionStorage.quoteClipboard; } var scrollTop = textarea.scrollTop; var insertPoint = Math.max(textarea.selectionStart, textarea.selectionEnd); if (insertPoint > 0) { insertPoint = textarea.value.indexOf('\n', insertPoint - 1); if (insertPoint === -1) { // Insertion point on last line, which doesn't have a trailing newline toInsert = '\n' + toInsert; insertPoint = textarea.value.length; } else { insertPoint++; } } textarea.value = ( textarea.value.substring(0, insertPoint) + toInsert + textarea.value.substring(insertPoint) ); textarea.selectionStart = textarea.selectionEnd = insertPoint + toInsert.length; textarea.scrollTop = scrollTop; textarea.focus(); }; } // Misc styles $('head').append( `<style> div.boardlist > span.sub > a { display: inline-block; margin: 0 -3px; } span.post-num { margin-left: 0.3em; opacity: 0.6; font-size: 81%; } a[href^="/${boardName}/res/"]:not([href^="/${boardName}/res/${opPostId}.html"]):not([href*="+50.html"]) { background: ${getSetting('offbreadcolor')}; } a.dashed-underline { background: ${getSetting('quotehovercolor')}; } span.qjs-pastebin { font-size: 81%; opacity: 0.75; margin-left: 0.3em; color: #000; border: 1px solid rgb(0, 0, 0, 0.6); border-radius: 3px; padding: 1px 2px; } span.qjs-pastebin a { color: #900 !important; } .post > div.qjs-controls { opacity: 0.75; font-size: 90%; } .post > div.qjs-controls a { color: #306; text-decoration: none; } .post > div.qjs-controls a:hover { color: #c33; } .post.qjs-hidden > .files, .post.qjs-hidden > .body { display: none; } .file.blacklisted { background: #999; padding: 0.3em 0; margin: 0.15em; opacity: 0.6; } .file.blacklisted:not(.show-temp) img { display: none; } .disclaimer-8kun { display: none !important; } .qjs-overlay { position: fixed; top: 36px; right: ${getSetting('sidenavWidth') + 6}px; opacity: 0.45; color: #f60; font-family: sans-serif; text-align: right; font-size: 12px; } #qjs_post_count { font-size: 36px; } #sidenav { cursor: pointer; } .qjs-postnum-part { padding-right: 0.15em; } .qjs-postnum-part:last-child { padding-right: 0; } #qjs-boards-parent { position: relative; } #qjs-boards { position: absolute; top: 1em; left: 0; right: auto; padding: 0.3em; } #qjs-boards > a { display: block; } /* Extra styles */ ${getSetting('extraStyles')} </style>` ); /* Display a replies counter overlay in the top right corner */ $('body').append( '<div id="qjs_overlay_main" class="qjs-overlay">' + '<div id="qjs_post_count" />' + '<div id="qjs_my_posts" />' + '</div>' ); var updatePostCountDelay = 30; function updatePostCount() { postCount = $('#thread_stats_posts').text(); if (postCount) { postCount = +postCount; $('#qjs_post_count').text(postCount); } else { // waiting for 8kun code? setTimeout(updatePostCount, updatePostCountDelay); updatePostCountDelay *= 1.5; } } $(document).on('new_post', function() { setTimeout(updatePostCount, 30); }); updatePostCount(); // 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); }; }(window.jQuery)); // Case insensitive contains selector for finding yous. SO #8746882 $.expr[":"].icontains = jQuery.expr.createPseudo(function (arg) { return function (elem) { return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0; }; }); // Get non-child text. SO #3442394 function immediateText(el) { return el.contents().not(el.children()).text(); } // Scroll to element function myScrollTo(el) { $('html, body').animate({ scrollTop: $(el).offset().top - $('div.boardlist').height() }, getSetting('scrolltime')); } // Scroll to next Q qjs_main.nextq = function() { if(qposts.length > 0) { if(currq < qposts.length-1) { currq++; } myScrollTo($(qposts).get(currq)); } }; // Scroll to last Q qjs_main.lastq = function() { if(qposts.length > 0) { currq = qposts.length - 1; myScrollTo($(qposts).get(currq)); } }; // Scroll to previous Q qjs_main.prevq = function() { if(qposts.length > 0) { if(currq > 0) { currq--; } myScrollTo($(qposts).get(currq)); } }; // Scroll to first Q qjs_main.firstq = function() { if(qposts.length > 0) { currq = 0; myScrollTo($(qposts).get(currq)); } }; // Scroll to next (You) qjs_main.nextyou = function() { if(youposts.length > 0) { if(curryou < youposts.length-1) { curryou++; } myScrollTo($(youposts).get(curryou)); } }; // Scroll to last (You) qjs_main.lastyou = function() { if(youposts.length > 0) { curryou = youposts.length - 1; myScrollTo($(youposts).get(curryou)); } }; // Scroll to previous (You) qjs_main.prevyou = function() { if(youposts.length > 0) { if(curryou > 0) { curryou--; } myScrollTo($(youposts).get(curryou)); } }; // Scroll to first (You) qjs_main.firstyou = function() { if(youposts.length > 0) { curryou = 0; myScrollTo($(youposts).get(curryou)); } }; // Inserts Q navigation links function qnav() { $('div.boardlist').append('<span>[ <a href="javascript:qjs_main.firstq();"><i class="fa fa-step-backward"></i></a> <a href="javascript:qjs_main.prevq();"><i class="fa fa-backward"></i></a> <span style="filter:brightness(70%);">Q</span> <span class="qcount">(?:?)</span> <a href="javascript:qjs_main.nextq();"><i class="fa fa-forward"></i></a> <a href="javascript:qjs_main.lastq();"><i class="fa fa-step-forward"></i></a> ]</span>'); } // Inserts (You) navigation links function younav() { $('div.boardlist').append('<span>[ <a href="javascript:qjs_main.firstyou();"><i class="fa fa-step-backward"></i></a> <a href="javascript:qjs_main.prevyou();"><i class="fa fa-backward"></i></a> <span style="filter:brightness(70%);">(You)</span> <span class="youcount">(?:?)</span> </span><a href="javascript:qjs_main.nextyou();"><i class="fa fa-forward"></i></a> <a href="javascript:qjs_main.lastyou();"><i class="fa fa-step-forward"></i></a> ]</span>'); } // Inserts feature toggle links function togglenav() { $('div.boardlist').append('<span>[ <a href="javascript:qjs_main.toggleFlood();">Post Fading <span class="toggleFloodState">' + (floodEnabled ? 'Off' : 'On') + '</span></a> ]</span>') } // Inserts post rate count/chart 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', '75px'); $(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 (bird's eye) var sideNavKey = null; function sidenav() { var winHeight = $(window).height(); var boardlistHeight = $('div.boardlist').height(); var sideNavKeyNew = [winHeight, boardlistHeight].join(','); if (sideNavKey === sideNavKeyNew) { return; // No size change, so need to do anything now } sideNavKey = sideNavKeyNew; var $nav = $('#sidenav'); if ($nav.length) $nav.remove(); $('body').append('<canvas id="sidenav"></canvas>'); $nav = $('#sidenav'); $nav.css('position', 'fixed'); $nav.css('top', $('div.boardlist').height()); $nav.css('right', 0); $nav.css('width', getSetting('sidenavWidth')); $nav.css('height', $(window).height() - $('div.boardlist').height()); $nav.css('background-color', getSetting('scrollbackcolor')); $('body').css('margin-right', getSetting('sidenavWidth')); ctx = $('#sidenav').get(0).getContext('2d'); var canvasHeight = $(window).height() - $('div.boardlist').height(); ctx.canvas.height = canvasHeight * navScaleFactor; ctx.canvas.width = getSetting('sidenavWidth') * navScaleFactor; borderSz = 1; var $navTooltip = null; var postOffsets = []; // https://stackoverflow.com/a/41956372 function binarySearch(array, pred) { let lo = -1, hi = array.length; while (1 + lo < hi) { const mi = lo + ((hi - lo) >> 1); if (pred(array[mi])) { hi = mi; } else { lo = mi; } } return hi; } function updateTooltip(e) { hoverPostNumber = Math.max(binarySearch(postOffsets, function(o) { return o > e.offsetY; }) - 1, 0); $navTooltip .css('top', Math.min($(window).height() - 18, Math.max(e.clientY - 6, 78)) + 'px') .html('→ #' + hoverPostNumber); } $nav.on('mouseenter', function(e) { var docHeight = $(document).height(); postOffsets = $allPosts.map(function() { return $(this).offset().top / docHeight * canvasHeight; }); $navTooltip = $('<div class="qjs-overlay">') .css('font-weight', 'bold') .css('opacity', '1') .appendTo('body'); updateTooltip(e); }).on('mousemove', function(e) { updateTooltip(e); }).on('mouseleave', function() { $navTooltip.remove(); $navTooltip = null; }).on('click', function(e) { $(window).scrollTop($('#' + postIdsByIndex[hoverPostNumber]).offset().top - 36); }); updateNav(); } // Update nav when scrolling stops $(window).on('scroll', function(e) { updateNavCounts(); updateNavGraphics(); }, getSetting('updateDelay')); // Update nav when resize stops $(window).on('resize', function(e) { setTimeout(sidenav, 300); // why long timeout needed? }, getSetting('updateDelay')); setInterval(sidenav, 3000); // and why is this needed? // but it fixes problems with the post #/offset calculation // Toggle post flooding qjs_main.toggleFlood = function() { if (floodEnabled) { floodEnabled = false; $('.toggleFloodState').text('On'); if(getSetting('floodBehavior') === 'fade') { $('span.poster_id').each(function () { $(this).closest('div.post').css('opacity', 1); $(this).closest('div.post').off('mouseenter mouseleave'); }); } else if(getSetting('floodBehavior') === 'hide') { $(this).closest('div.post').show(); } } else { floodEnabled = true; $('.toggleFloodState').text('Off'); runq(); } }; // Helper to run snippets in the right order function runq() { $allPosts = $('div.post:not(.post-hover):not(.hidden)'); processPastebins(); // Added later (learnedtocode) processQanonbins(); // Added later (learnedtocode) setQPosts(); removeInvalidYous(); setYouPosts(); setFloodPosts(); updateNav(); // Added here later (learnedtocode) addPostControls(); removeBlacklistedImages(); addPostNumberSpaces(); fixAutoUpdates(); fixPostFlags(); } // Try to look up pastebin author(s) function processPastebins() { if (!getSetting('pastebin_key')) return; $allPosts.filter(':not(.qjs-processed-pastebin)').each(function() { if (/pastebin\.com/.test(this.textContent)) { $('p.body-line', this).each(function() { // Pastebin URL length is 29, allow a few more chars for "dough:" etc if (this.textContent.length > 45) return; var binUrl = (this.textContent || '') .match(/(https?:\/\/pastebin\.com\/[a-zA-Z0-9]{8})\b/); if (!binUrl) return; binUrl = binUrl[1]; var binId = binUrl.substring(binUrl.length - 8); var bodyLine = this; function pbDone(data) { var msg; if (data.error) { msg = 'ERROR: ' + data.error; } else if (data.anonymous) { msg = 'anonymous'; } else { msg = ( '<a href="https://pastebin.com/u/' + data.username + '" target="_blank" rel="noopener noreferer">' + '/u/' + data.username + '</a>' ); } $(bodyLine).append( '<span class="qjs-pastebin">' + '<a href="' + binUrl + '" target="_blank" rel="noopener noreferer">' + ':pastebin' + '</a> ' + msg + '</span>' ); } $.ajax({ url: 'https://wearethene.ws/api/dough?v=3&paste_id=' + binId + '&key=' + encodeURIComponent(getSetting('pastebin_key')), success: function(data) { pbDone(data); }, error: function(xhr, textStatus, errorThrown) { pbDone({ error: errorThrown || textStatus || 'unknown', }); }, dataType: 'json', }); }); } this.classList.add('qjs-processed-pastebin'); }); } // Try to look up qanonbin author(s) function processQanonbins() { $allPosts.filter(':not(.qjs-processed-qanonbin)').each(function() { if (/qanonbin\.com/.test(this.textContent)) { $('p.body-line', this).each(function() { // qanonbin URL length is 41, allow a few more chars for "dough:" etc if (!this.textContent || this.textContent.length > 60) return; var binUrl = (this.textContent || '') .match(/(https?:\/\/[a-z0-9\.]*qanonbin\.com\/(paste|clone)\/[a-zA-Z0-9]{9})\b/); if (!binUrl) return; binUrl = binUrl[1]; var binId = binUrl.substring(binUrl.length - 9); var bodyLine = this; function pbDone(data) { var msg; if (data.error) { msg = 'ERROR: ' + data.error; } else { msg = data.info; } $(bodyLine).append( '<span class="qjs-pastebin">' + '<a href="' + binUrl + '" target="_blank" rel="noopener noreferer">' + ':qanonbin' + '</a> ' + msg + '</span>' ); } $.ajax({ url: 'https://www2.qanonbin.com/api/info/' + binId, success: function(data) { pbDone(data); }, error: function(xhr, textStatus, errorThrown) { pbDone({ error: errorThrown || textStatus || 'unknown', }); }, dataType: 'json', }); }); } this.classList.add('qjs-processed-qanonbin'); }); } // Set which posts are Q posts function setQPosts() { $allPosts.filter(':not(.qjs-processed-q)').each(function() { var $this = $(this); if ($this.find('span.trip:contains("!!Hs1Jq13jV6")').length) { qposts.push(this); $this.css('background-color', getSetting('qcolor')); if (getSetting('qflair') !== '') { $this .find('p.intro > label > span.trip') .first() .prepend(getSetting('qflair') + ' '); } } this.classList.add('qjs-processed-q'); }); } // Remove invalid (you)'s function removeInvalidYous() { $allPosts.filter(':not(.qjs-processed-invalidyou)').each(function() { if (/\(You\)/i.test(this.textContent)) { $(this).find('.body :not(small)').contents().filter(function() { return this.nodeType === 3; }).each(function() { var text = this.textContent.replace(/\(You\)/ig, '(.You.)'); if (text !== this.textContent) { this.textContent = text; } }); } this.classList.add('qjs-processed-invalidyou'); }); } // Set which posts are (you) posts function setYouPosts() { $allPosts.filter(':not(.qjs-processed-you)').each(function() { var $this = $(this); if ($this.find('span.own_post, small:icontains("(You)")').length) { youposts.push(this); var youcolor = getSetting('youcolor'); if (/!important/.test(youcolor)) { // Workaround for boards that have badly written styles // https://bugs.jquery.com/ticket/2066 $this.css('cssText', 'background-color: ' + youcolor); } else { $this.css('background-color', youcolor); } } this.classList.add('qjs-processed-you'); }); } // Set flood posts function setFloodPosts() { if (floodEnabled) { var stats = {}; var firstId = null; //console.log("==[ Name Fags ]================================================="); var posts = $('div.post:not(.post-hover)').not('.you'); $(posts).each(function () { var id = $(this).find('p > span.poster_id').first().text(); if(firstId != null) { if (!(id in stats)) { stats[id] = {count: 0, namefag: false, floodcount: 0}; } stats[id].count++; if(!stats[id].namefag) { var name = immediateText($(this).find('p > label span.name').first()); var trip = $(this).find('p > label > span.trip').first().text(); stats[id].namefag = !getSetting('fadenametripregex').test(name+'-'+trip); //if(stats[id].namefag) { // console.log(id + '=' + stats[id].namefag + ', ' + name + ', ' + trip); //} } if(stats[id].namefag) { if(getSetting('fadenametripfloodvalue') < 0) { stats[id].floodcount = getSetting('floodThreshold') + stats[id].count; } else { stats[id].floodcount = getSetting('fadenametripfloodvalue'); } } else { stats[id].floodcount = stats[id].count; } } if (firstId == null) { firstId = id; } }); $.each(stats, function (key, value) { if (key !== firstId) { var ids = $('span.poster_id:contains("' + key + '")'); if (value.floodcount > getSetting('floodThreshold') || value.namefag) { if(getSetting('strikeThroughNameFags') && value.namefag) { $(ids).each(function() { $(this).closest('div.post').find('p > label span.name').first().css('text-decoration','line-through'); }); } if (getSetting('floodBehavior') === 'fade') { var intensity = value.floodcount; if (intensity > getSetting('floodVanish')) { intensity = getSetting('floodVanish'); } intensity = ((getSetting('floodVanish') - getSetting('floodThreshold')) - (intensity - getSetting('floodThreshold'))) / (getSetting('floodVanish') - getSetting('floodThreshold')); if (intensity < 0.1) { intensity = 0.1; } $(ids).each(function () { $(this).closest('div.post').css('opacity', intensity); $(this).closest('div.post').hover(function () { $(this).animate({opacity: 1.0}, getSetting('updateDelay')); }, function () { $(this).animate({opacity: intensity}, getSetting('updateDelay')); }); }); } else if (getSetting('floodBehavior') === 'hide') { if (value.count >= getSetting('floodVanish')) { $(ids).each(function () { $(this).closest('div.post').hide(); }); } } } else { $(ids).each(function () { $(this).closest('div.post').css('opacity', 1.0); }); } } }); } return $.Deferred().resolve(); } // Update navigation function updateNav() { updateNavCounts(); updateNavGraphics(); updateNavPostRate(); } // 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 = getSetting('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 = getSetting('youscrollcolor') || getSetting('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 var heightScale = ctx.canvas.height / $(document).height(), scrollStart = $(window).scrollTop() * heightScale, scrollHeight = $(window).height() * heightScale, scrollMinHeight = getSetting('scrollminheight') * ctx.canvas.height / $(window).height(); if (scrollHeight < scrollMinHeight) { var scrollStart2 = scrollStart - (scrollMinHeight - scrollHeight) / 2; if (scrollStart2 < 0) { scrollStart2 = 0; } else if (scrollStart2 + scrollMinHeight > ctx.canvas.height) { scrollStart2 = ctx.canvas.height - scrollMinHeight; } ctx.fillStyle = getSetting('scrollcolor2'); ctx.fillRect( ctx.canvas.width / 4, scrollStart2, ctx.canvas.width / 2, scrollMinHeight ); } ctx.fillStyle = getSetting('scrollcolor'); ctx.fillRect( ctx.canvas.width / 4, scrollStart, ctx.canvas.width / 2, scrollHeight ); // Add red marker at bottom of >750 posts if($('#thread_stats_posts').text() > 750 && !isCyclical) { var barHeight = (4 * ctx.canvas.height) / ($(window).height() - $('div.boardlist').height()); ctx.fillStyle = '#f66'; ctx.fillRect( 0, ctx.canvas.height - barHeight, ctx.canvas.width, barHeight ); } } } // Updates post rate count/chart function updateNavPostRate() { var posts = $('div.post').not('.post-hover'); var startPost = posts.length - (getSetting('rateHistoryLen') + getSetting('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 - getSetting('rateAvgLen') - 1 >= 0) { var avgend = timehistory[timehistory.length - 1]; var avgstart = timehistory[timehistory.length - getSetting('rateAvgLen') - 1]; ratehistory[ratehistory.length] = getSetting('rateAvgLen') / ((avgend - avgstart) / 60); } else { ratehistory[ratehistory.length] = 0; } } //console.log(ratehistory); if (ratehistory.length) { $('.postRate').text(ratehistory[ratehistory.length-1].toFixed(1)); } if(ratehistory.length > getSetting('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[getSetting('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[getSetting('rateAvgLen')] - minRate)/(maxRate - minRate) * gctx.canvas.height; gctx.moveTo(x, y); for(var i=getSetting('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(); }); } } qjs_main.Q = function() { qnav(); younav(); togglenav(); postratenav(); sidenav(); runq(); // Collapse the boards list in the top bar (function() { var $boards = $('.boardlist:first > .sub[data-description="1"]'); $boards.attr('id', 'qjs-boards-parent'); var links = $boards.html().replace(/^\[\s*|\s*\]$/g, '').split(' / '); $boards.html('[ <a id="qjs-expand-boards" href="#">Boards</a> ]'); $boards.append( $('<div id="qjs-boards" class="boardlist">') .html(links.join('\n')) .hide() ); var $qjsBoards = $('#qjs-boards'); $('#qjs-expand-boards').on('click', function() { if ($qjsBoards.css('display') === 'none') { $qjsBoards.show(); function hideQjsBoardsAgain() { if (isArtificialClick) { return; } $qjsBoards.hide(); $(document).off('click', hideQjsBoardsAgain); } $(document).on('click', hideQjsBoardsAgain); } else { $qjsBoards.hide(); } return false; }); })(); // Scroll back to target post after qjs controls added setTimeout(function() { var hashBackup = document.location.hash; if (hashBackup) { document.location.hash = ''; document.location.hash = hashBackup; $(window).scrollTop($(window).scrollTop() - 60); } }, 0); // 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 in mutationsList) { if (mutationsList[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); }; // Attach snippets to ready/change events // Wait for 8kun code that adds (you)s to run setTimeout(qjs_main.Q, 30); }); // END Q.JS CODE