Guest User

q-posts-user.js

a guest
May 18th, 2020
1,343
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.98 KB | None | 0 0
  1.  
  2. /*
  3. * q.js v2019.12-17
  4. * http://anonsw.github.io/8chjs/
  5. */
  6.  
  7. // Settings
  8. var qflair = ''; // Examples: REAL, →
  9. var qcolor = '#ffc';
  10. var youcolor = '#fcc';
  11. var scrollcolor = 'rgba(153, 153, 153, 0.6)';
  12. var scrollbackcolor = '#333';
  13. var scrolltime = 400; // ms
  14. var updateDelay = 200; // ms
  15. var sidenavWidth = 30; // px
  16.  
  17. var floodEnabled = false;
  18. var floodThreshold = 15; // min # posts before beginning fade
  19. var floodVanish = 30; // max # posts before completed fade/hide
  20. var floodBehavior = 'fade'; // hide, fade
  21. var fadenametripregex = /^(Anon(ymous)?-*|Q -!!Hs1Jq13jV6)$/i;
  22. var fadenametripfloodvalue = -1; // Effective post count for fading; or -1 for auto of floodThreshold+post count
  23.  
  24. var rateHistoryLen = 50; // Data points on chart
  25. var rateAvgLen = 10; // Number of data points to average for instantaneous rate
  26.  
  27. /* House keeping variables */
  28. var qposts = [];
  29. var allqposts = [];
  30. var currq = -1;
  31. var youposts = [];
  32. var curryou = -1;
  33. var qnavposts = [];
  34. var younavposts = [];
  35. var ctx;
  36. var borderSz;
  37. var scrollWd;
  38. var minheight;
  39. var ratehistory = [];
  40.  
  41. // Suggestions from 589388.html#590283
  42. // ...shill detection features, such as
  43. // easily knowing the proportion of posts from a user that don't link.
  44. // I'd want to know any ID that was posting > 1/3 posts targetting noone.
  45.  
  46. // TODO: Behavior for post hover should be to show original post visual before all q.js mods
  47. // TODO: Add flags to turn on/off features
  48. // TODO: Custom-regex -> post color/fade (auto-filter by selecting text and choosing new menu item?)
  49. // Examples: daily reminder, guys, check this out, shill, get out, filtered, tell us more, archive everything
  50. // TODO: Manual shade
  51. // TODO: remove Q trip codes from post content?
  52. // TODO: remove Q from end of post content if not a Q post?
  53. // TODO: recognize all of known Q trip codes? (make to sure to exclude known comps)
  54. // TODO: Links to reset on current Q/(you) post
  55. // TODO: Link to go to latest post (end key doesn't always work, but try capturing that as well?)
  56. // TODO: Keyboard shortcuts for navigation
  57. // TODO: Current/Total overall post navigation
  58. // TODO: Remap end key to always go to end of page
  59. // TODO: Check box for each post to mark as "read", "spam", ?
  60. // TODO: Autocorrect all-caps posts (50% threshold)?
  61. // TODO: Correct broken links but remove referral when clicked?
  62. // TODO: Make flood post fading non-linear to give leniency to posters just passing flood threshold
  63. // TODO: Penalize reposts in flood detection (if id's different, merge?) ?
  64. // TODO: Scorecard of posters ordered by post count (post rate, reply count, ...)?
  65. // TODO: Color/shade posts where there are no references and no question marks
  66. // TODO: If Q or trip used in name field, strike them out or replace with Anonymous?
  67. // TODO: embedded posts in Q posts don't have background-color and inherit Q color, fix?
  68.  
  69. /* Case insensitive contains selector for finding yous. SO #8746882 */
  70. jQuery.expr[":"].icontains = jQuery.expr.createPseudo(function (arg) {
  71. return function (elem) {
  72. return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
  73. };
  74. });
  75.  
  76. /* On scroll stop. SO #9144560 */
  77. (function ($) {
  78. var on = $.fn.on, timer;
  79. $.fn.on = function () {
  80. var args = Array.apply(null, arguments);
  81. var last = args[args.length - 1];
  82.  
  83. if (isNaN(last) || (last === 1 && args.pop())) return on.apply(this, args);
  84.  
  85. var delay = args.pop();
  86. var fn = args.pop();
  87.  
  88. args.push(function () {
  89. var self = this, params = arguments;
  90. clearTimeout(timer);
  91. timer = setTimeout(function () {
  92. fn.apply(self, params);
  93. }, delay);
  94. });
  95.  
  96. return on.apply(this, args);
  97. };
  98. }(this.jQuery || this.Zepto));
  99.  
  100. /* Scroll to element */
  101. function myScrollTo(el) {
  102. $('html, body').animate({
  103. scrollTop: $(el).offset().top - $('div.boardlist').height()
  104. }, scrolltime);
  105. }
  106.  
  107. /* Highlight you references */
  108. function highlightYouRefs() {
  109. $('div.body:icontains("(You)")').each(function() {
  110. $(this).parents('div.post').first().css('background-color',youcolor);
  111. });
  112. return $.Deferred().resolve();
  113. }
  114.  
  115. /* Remove invalid (you)'s */
  116. function removeInvalidYous() {
  117. $('div.body:icontains("(You)")').each(function() {
  118. $(this).find(':not(small)').contents().filter(function() { return this.nodeType == 3 }).each(function() {
  119. this.textContent = this.textContent.replace(/\(+ *You *\)+/ig, "you");
  120. });
  121. });
  122. return $.Deferred().resolve();
  123. }
  124.  
  125. /* Highlight Q posts */
  126. function highlightQ() {
  127. $(allqposts).each(function(idx,val) {
  128. var div = $(val).parents('div.post').first();
  129. if($(div).css('background-color') !== qcolor) {
  130. if(qflair !== "") {
  131. $(val).prepend(qflair + " ");
  132. }
  133. $(div).css('background-color', qcolor);
  134. }
  135. });
  136. return $.Deferred().resolve();
  137. }
  138.  
  139. /* Scroll to next Q */
  140. function nextq() {
  141. if(qposts.length > 0) {
  142. if(currq < qposts.length-1) {
  143. currq++;
  144. }
  145. myScrollTo($(qposts).get(currq));
  146. }
  147. }
  148.  
  149. /* Scroll to last Q */
  150. function lastq() {
  151. if(qposts.length > 0) {
  152. currq = qposts.length - 1;
  153. myScrollTo($(qposts).get(currq));
  154. }
  155. }
  156.  
  157. /* Scroll to previous Q */
  158. function prevq() {
  159. if(qposts.length > 0) {
  160. if(currq > 0) {
  161. currq--;
  162. }
  163. myScrollTo($(qposts).get(currq));
  164. }
  165. }
  166.  
  167. /* Scroll to first Q */
  168. function firstq() {
  169. if(qposts.length > 0) {
  170. currq = 0;
  171. myScrollTo($(qposts).get(currq));
  172. }
  173. }
  174.  
  175. /* Scroll to next (You) */
  176. function nextyou() {
  177. if(youposts.length > 0) {
  178. if(curryou < youposts.length-1) {
  179. curryou++;
  180. }
  181. myScrollTo($(youposts).get(curryou));
  182. }
  183. }
  184.  
  185. /* Scroll to last (You) */
  186. function lastyou() {
  187. if(youposts.length > 0) {
  188. curryou = youposts.length - 1;
  189. myScrollTo($(youposts).get(curryou));
  190. }
  191. }
  192.  
  193. /* Scroll to previous (You) */
  194. function prevyou() {
  195. if(youposts.length > 0) {
  196. if(curryou > 0) {
  197. curryou--;
  198. }
  199. myScrollTo($(youposts).get(curryou));
  200. }
  201. }
  202.  
  203. /* Scroll to first (You) */
  204. function firstyou() {
  205. if(youposts.length > 0) {
  206. curryou = 0;
  207. myScrollTo($(youposts).get(curryou));
  208. }
  209. }
  210.  
  211. /* Inserts Q navigation links */
  212. function qnav() {
  213. $('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>');
  214. }
  215.  
  216. /* Inserts (You) navigation links */
  217. function younav() {
  218. $('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>');
  219. }
  220.  
  221. /* Inserts feature toggle links */
  222. function togglenav() {
  223. $('div.boardlist').append('<span>[ <a href="javascript:toggleFlood();">Turn Post Fading <span class="toggleFloodState">On</span></a> ]</span>')
  224. }
  225.  
  226. function postratenav() {
  227. var height = $('div.boardlist').height() - 1;
  228. $('div.boardlist').append('<span>[ Post Rate: <span class="postRate">0</span> posts/min <canvas class="postRateChart"></canvas>]</span>')
  229. $('.postRate').css('color', $('div.boardlist a').css('color'));
  230. var charts = $('.postRateChart');
  231. $(charts).each(function() {
  232. $(this).css('width', '100px');
  233. $(this).css('height', height);
  234. $(this).css('vertical-align', 'middle');
  235. //$(this).css('border', '1px solid');
  236. //$(this).css('border-color', $('div.boardlist').css('color'));
  237. var gctx = $(this).get(0).getContext('2d');
  238. gctx.canvas.height = 20;
  239. gctx.canvas.width = 100;
  240. });
  241. }
  242.  
  243. /* Inserts side navigation */
  244. function sidenav() {
  245. $('body').append('<canvas id="sidenav"></canvas>');
  246. var nav = $('#sidenav');
  247. $(nav).css('position', 'fixed');
  248. $(nav).css('top', $('div.boardlist').height());
  249. $(nav).css('right', 0);
  250. $(nav).css('width', sidenavWidth);
  251. $(nav).css('height', $(window).height() - $('div.boardlist').height());
  252. $(nav).css('background-color', scrollbackcolor);
  253. $('body').css('margin-right', sidenavWidth);
  254. ctx = $('#sidenav').get(0).getContext('2d');
  255. //ctx.canvas.height = $(document).height() - $('div.boardlist').height();
  256. ctx.canvas.height = 2048;
  257. ctx.canvas.width = sidenavWidth;
  258. borderSz = 1;
  259. scrollWd = ctx.canvas.width / 2;
  260. }
  261.  
  262. /* Update navigation counts */
  263. function updateNavCounts() {
  264. var fontSize = -1;
  265. var lineHeight;
  266.  
  267. if(currq > qposts.length) { currq = qposts.length; }
  268. if(curryou > youposts.length) { curryou = youposts.length; }
  269.  
  270. for(i=0; i<qposts.length; i++) {
  271. var el = $(qposts).get(i);
  272. if(fontSize == -1) {
  273. fontSize = $(el).css('font-size');
  274. lineHeight = Math.floor(parseInt(fontSize.replace('px', '')) * 1.5);
  275. }
  276. if(($(el).offset().top + $(el).height() - 2.25*lineHeight) > $(window).scrollTop()) {
  277. currq = i;
  278. break;
  279. }
  280. }
  281.  
  282. for(i=0; i<youposts.length; i++) {
  283. var el = $(youposts).get(i);
  284. if(fontSize == -1) {
  285. fontSize = $(el).css('font-size');
  286. lineHeight = Math.floor(parseInt(fontSize.replace('px', '')) * 1.5);
  287. }
  288. if(($(el).offset().top + $(el).height() - 2.25*lineHeight) > $(window).scrollTop()) {
  289. curryou = i;
  290. break;
  291. }
  292. }
  293.  
  294. // TODO: check for duplicates and remove from counts
  295. $('.qcount').text("(" + (currq+1) + ":" + qposts.length + ")");
  296. $('.youcount').text("(" + (curryou+1) + ":" + youposts.length + ")");
  297. }
  298.  
  299. /* Update navigation graphics */
  300. function updateNavGraphics() {
  301. var sidenav = $('#sidenav');
  302. if(sidenav.length) {
  303. $(sidenav).css('height', $(window).height() - $('div.boardlist').height());
  304. minheight = ctx.canvas.height / ($(window).height() - $('div.boardlist').height()); // 1px
  305. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  306.  
  307. // Draw nav q posts
  308. qnavposts = [];
  309. ctx.fillStyle = qcolor;
  310. for (i = 0; i < qposts.length; i++) {
  311. // TODO: check if we have already added post, don't draw it again
  312. var el = $(qposts).get(i);
  313. var height = $(el).height() / $(document).height() * ctx.canvas.height;
  314. if(height < minheight) height = minheight;
  315. qnavposts[i] = {
  316. x : borderSz,
  317. y : $(el).offset().top / $(document).height() * ctx.canvas.height,
  318. width : ctx.canvas.width - borderSz*2,
  319. height : height
  320. };
  321. ctx.fillRect(qnavposts[i].x, qnavposts[i].y, qnavposts[i].width, qnavposts[i].height);
  322. }
  323.  
  324. // Draw nav you posts
  325. younavposts = [];
  326. ctx.fillStyle = youcolor;
  327. for (i = 0; i < youposts.length; i++) {
  328. // TODO: check if we have already added post, don't add it again
  329. var el = $(youposts).get(i);
  330. var height = $(el).height() / $(document).height() * ctx.canvas.height;
  331. if(height < minheight) height = minheight;
  332. younavposts[i] = {
  333. x : borderSz,
  334. y : $(el).offset().top / $(document).height() * ctx.canvas.height,
  335. width : ctx.canvas.width - borderSz*2,
  336. height : height
  337. };
  338. ctx.fillRect(younavposts[i].x, younavposts[i].y, younavposts[i].width, younavposts[i].height);
  339. }
  340.  
  341. // Update nav window
  342. ctx.fillStyle = scrollcolor;
  343. ctx.fillRect(
  344. scrollWd / 2,
  345. $(window).scrollTop() / $(document).height() * ctx.canvas.height,
  346. scrollWd,
  347. $(window).height() / $(document).height() * ctx.canvas.height
  348. );
  349.  
  350. // Add red marker at bottom of >750 posts
  351. if($('#thread_stats_posts').text() > 750) {
  352. var barHeight = (4 * 2048) / ($(window).height() - $('div.boardlist').height());
  353. ctx.fillStyle = '#f66';
  354. ctx.fillRect(
  355. 0,
  356. ctx.canvas.height - barHeight,
  357. ctx.canvas.width,
  358. barHeight
  359. );
  360. }
  361. }
  362. }
  363.  
  364. function updateNavPostRate() {
  365. var posts = $('div.post').not('.post-hover');
  366. var startPost = posts.length - (rateHistoryLen + rateAvgLen) + 1;
  367. if(startPost < 1) startPost = 1;
  368. var start = $($($(posts).get(0)).find('.intro time').get(0)).attr('unixtime'); //$('div.post:first .intro time').attr('unixtime');
  369. ratehistory = [];
  370. timehistory = [];
  371. for(var i=startPost; i<posts.length; i++) {
  372. // TODO: check if we have already added post, don't add it again
  373. var step = $($($(posts).get(i)).find('.intro time').get(0)).attr('unixtime'); //$($('div.post .intro time').get(i)).attr('unixtime');
  374. timehistory[timehistory.length] = step;
  375. if(timehistory.length - rateAvgLen - 1 >= 0) {
  376. var avgend = timehistory[timehistory.length - 1];
  377. var avgstart = timehistory[timehistory.length - rateAvgLen - 1];
  378. ratehistory[ratehistory.length] = rateAvgLen / ((avgend - avgstart) / 60);
  379. } else {
  380. ratehistory[ratehistory.length] = 0;
  381. }
  382. }
  383. //console.log(ratehistory);
  384.  
  385. // $('.postRate').text(ratehistory[ratehistory.length-1].toFixed(1));
  386. if (ratehistory.length) {
  387. $('.postRate').text(ratehistory[ratehistory.length-1].toFixed(1));
  388. }
  389. if(ratehistory.length > rateAvgLen) {
  390. var maxRate = Math.max.apply(null, ratehistory);
  391. var minRate = Math.min.apply(null, ratehistory);
  392. //console.log("Max: " + maxRate);
  393. //console.log("Min: " + minRate);
  394. if(minRate > (maxRate - 0.5)) {
  395. minRate = maxRate - 0.5;
  396. maxRate = maxRate + 0.5;
  397. }
  398. if(minRate < 0) {
  399. minRate = 0;
  400. }
  401. var maxTime = timehistory[timehistory.length-1];
  402. var minTime = timehistory[rateAvgLen];
  403. $('.postRateChart').each(function() {
  404. var gctx = $(this).get(0).getContext('2d');
  405. gctx.clearRect(0, 0, gctx.canvas.width, gctx.canvas.height);
  406. gctx.strokeStyle = $('div.boardlist a').css('color');
  407. gctx.beginPath();
  408. var x = 0;
  409. var y = gctx.canvas.height - (ratehistory[rateAvgLen] - minRate)/(maxRate - minRate) * gctx.canvas.height;
  410. gctx.moveTo(x, y);
  411. for(var i=rateAvgLen+1; i<ratehistory.length; i++) {
  412. x = (timehistory[i] - minTime)/(maxTime - minTime) * gctx.canvas.width;
  413. y = gctx.canvas.height - (ratehistory[i] - minRate)/(maxRate - minRate) * gctx.canvas.height;
  414. gctx.lineTo(x, y);
  415. }
  416. gctx.stroke();
  417. gctx.closePath();
  418. });
  419. }
  420. }
  421.  
  422. /* Update navigation */
  423. function updateNav() {
  424. updateNavCounts();
  425. updateNavGraphics();
  426. updateNavPostRate();
  427. }
  428.  
  429. // Update nav when scrolling stops
  430. $(window).on('scroll', function(e) {
  431. updateNavCounts();
  432. updateNavGraphics();
  433. }, updateDelay);
  434.  
  435. // Update nav when resize stops
  436. $(window).on('resize', function(e) {
  437. updateNav();
  438. }, updateDelay);
  439.  
  440. /* Set which posts are Q posts */
  441. function setQPosts() {
  442. qposts = $.map($('div.post span.trip:contains("!!Hs1Jq13jV6"):visible').not('.post-hover'), function(el) {
  443. return $(el).parents('div.post').first();
  444. });
  445. allqposts = $('span.trip:contains("!!mG7VJxZNCI")');
  446. return $.Deferred().resolve();
  447. }
  448.  
  449. /* Set which posts are you posts */
  450. function setYouPosts() {
  451. youposts = $.map($('div.post span.own_post,div.body:icontains("(You)")').not('.post-hover'), function(el) {
  452. return $(el).parents('div.post').first();
  453. });
  454. return $.Deferred().resolve();
  455. }
  456.  
  457. function setFloodPosts() {
  458. if(floodEnabled) {
  459. var stats = {};
  460. var firstId = null;
  461. $('span.poster_id').each(function () {
  462. var id = $(this).text();
  463. if (!(id in stats)) {
  464. stats[id] = {count: 0, namefag: false, floodcount: 0};
  465. }
  466. stats[id].count++;
  467. if(!stats[id].namefag) {
  468. var name = $(this).parents('div').first().find('span.name').text();
  469. var trip = $(this).parents('div').first().find('span.trip').text();
  470. stats[id].namefag = !fadenametripregex.test(name+'-'+trip);
  471. }
  472. if(stats[id].namefag) {
  473. if(fadenametripfloodvalue < 0) {
  474. stats[id].floodcount = floodThreshold + stats[id].count;
  475. } else {
  476. stats[id].floodcount = fadenametripfloodvalue;
  477. }
  478. } else {
  479. stats[id].floodcount = stats[id].count;
  480. }
  481. if (firstId == null) {
  482. firstId = id;
  483. }
  484. });
  485. $.each(stats, function (key, value) {
  486. if (key !== firstId) {
  487. if (value.floodcount > floodThreshold || value.namefag) {
  488. if (floodBehavior === 'fade') {
  489. var intensity = value.floodcount;
  490. if (intensity > floodVanish) {
  491. intensity = floodVanish;
  492. }
  493. intensity = ((floodVanish - floodThreshold) - (intensity - floodThreshold)) / (floodVanish - floodThreshold);
  494. if (intensity < 0.1) {
  495. intensity = 0.1;
  496. }
  497. $('span.poster_id:contains("' + key + '")').each(function () {
  498. $(this).parents('div.post').first().css('opacity', intensity);
  499. $(this).parents('div.post').first().hover(function () {
  500. $(this).animate({opacity: 1.0}, updateDelay);
  501. }, function () {
  502. $(this).animate({opacity: intensity}, updateDelay);
  503. });
  504. });
  505. } else if (floodBehavior === 'hide') {
  506. if (value.count >= floodVanish) {
  507. $('span.poster_id:contains("' + key + '")').each(function () {
  508. $(this).parents('div.post').first().hide();
  509. });
  510. }
  511. }
  512. }
  513. }
  514. });
  515. }
  516. return $.Deferred().resolve();
  517. }
  518.  
  519. function toggleFlood() {
  520. if(floodEnabled) {
  521. floodEnabled = false;
  522. $('.toggleFloodState').text('On');
  523. if(floodBehavior === 'fade') {
  524. $('span.poster_id').each(function () {
  525. $(this).parents('div.post').first().css('opacity', 1);
  526. $(this).parents('div.post').first().off('mouseenter mouseleave');
  527. });
  528. } else if(floodBehavior === 'hide') {
  529. $(this).parents('div.post').first().show();
  530. }
  531. } else {
  532. floodEnabled = true;
  533. $('.toggleFloodState').text('Off');
  534. runq()
  535. }
  536. }
  537.  
  538. /* Helper to run snippets in the right order */
  539. function runq() {
  540. setQPosts()
  541. .done(highlightQ)
  542. .done(removeInvalidYous)
  543. .done(highlightYouRefs)
  544. .done(setYouPosts)
  545. .done(setFloodPosts)
  546. .done(updateNav);
  547. }
  548.  
  549. /* Attach snippets to ready/change events */
  550. $(document).ready(function() {
  551. qnav();
  552. younav();
  553. togglenav();
  554. postratenav();
  555. sidenav();
  556. runq();
  557.  
  558. // Select the node that will be observed for mutations
  559. var targetNode = $('div.thread')[0];
  560.  
  561. // Options for the observer (which mutations to observe)
  562. var config = { childList: true };
  563.  
  564. // Callback function to execute when mutations are observed
  565. var callback = function(mutationsList) {
  566. for(var mutation of mutationsList) {
  567. if (mutation.type == 'childList') {
  568. runq();
  569. break;
  570. }
  571. }
  572. };
  573.  
  574. // Create an observer instance linked to the callback function
  575. var observer = new MutationObserver(callback);
  576.  
  577. // Start observing the target node for configured mutations
  578. observer.observe(targetNode, config);
  579. });
Add Comment
Please, Sign In to add comment