Highlight 4chan Posts based on number of replies

Script for highlighting posts based on number of replies. Changes backlink and quotelink font colors.

  1. // ==UserScript==
  2. // @name Highlight 4chan Posts based on number of replies
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.1
  5. // @description Script for highlighting posts based on number of replies. Changes backlink and quotelink font colors.
  6. // @author cheebsisaftfck
  7. // @match http://boards.4chan.org/*
  8. // @match https://boards.4chan.org/*
  9. // @match http://sys.4chan.org/*
  10. // @match https://sys.4chan.org/*
  11. // @match http://www.4chan.org/*
  12. // @match https://www.4chan.org/*
  13. // @match http://boards.4channel.org/*
  14. // @match https://boards.4channel.org/*
  15. // @match http://sys.4channel.org/*
  16. // @match https://sys.4channel.org/*
  17. // @grant none
  18. // @license MIT
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. // Create CSS classes for styling posts based on backlink count
  25. const style = document.createElement('style');
  26. style.textContent = `
  27. .highlight-7plus { background-color: #9E1030FF !important; }
  28. .highlight-4to6 { background-color: #00539CFF !important; }
  29. .backlink-7plus { color: #9CC3D5FF !important; }
  30. .backlink-4to6 { color: #EEA47FFF !important; }
  31. `;
  32. document.head.appendChild(style);
  33.  
  34. // Throttle function to limit how often the MutationObserver callback is executed
  35. function throttle(fn, wait) {
  36. let isCalled = false;
  37. return function(...args) {
  38. if (!isCalled) {
  39. fn.apply(this, args);
  40. isCalled = true;
  41. setTimeout(() => { isCalled = false; }, wait);
  42. }
  43. };
  44. }
  45.  
  46. // Function to reset any existing highlights
  47. function resetHighlights() {
  48. // Remove existing highlight classes from posts and backlinks in one pass
  49. document.querySelectorAll('.highlight-7plus, .highlight-4to6, .backlink-7plus, .backlink-4to6')
  50. .forEach(el => el.classList.remove('highlight-7plus', 'highlight-4to6', 'backlink-7plus', 'backlink-4to6'));
  51. }
  52.  
  53. // Function to highlight posts with 4 or more backlinks and update quote links
  54. function highlightPosts() {
  55. resetHighlights(); // Reset previous highlights
  56.  
  57. // Get all posts with class 'post reply' or 'post op' in a single query
  58. const posts = document.querySelectorAll('div.post');
  59.  
  60. const updates = []; // Store updates to apply later in batch
  61.  
  62. posts.forEach(post => {
  63. const container = post.querySelector('span.container');
  64. if (!container) return;
  65.  
  66. const backlinks = container.querySelectorAll('a.backlink');
  67. const postId = post.id.split('p')[1]; // Extract post ID
  68.  
  69. // Batch the updates to be more efficient
  70. if (backlinks.length >= 7) {
  71. updates.push({element: container, addClass: 'highlight-7plus'});
  72. backlinks.forEach(link => updates.push({element: link, addClass: 'backlink-7plus'}));
  73. } else if (backlinks.length >= 4) {
  74. updates.push({element: container, addClass: 'highlight-4to6'});
  75. backlinks.forEach(link => updates.push({element: link, addClass: 'backlink-4to6'}));
  76. }
  77.  
  78. // Update quote links referring to this post in one go
  79. if (backlinks.length >= 4) {
  80. const quoteLinks = document.querySelectorAll(`a.quotelink[href$="#p${postId}"]`);
  81. quoteLinks.forEach(link => {
  82. updates.push({
  83. element: link,
  84. addClass: backlinks.length >= 7 ? 'highlight-7plus' : 'highlight-4to6'
  85. });
  86. });
  87. }
  88. });
  89.  
  90. // Apply all the updates in one batch to minimize reflows
  91. updates.forEach(update => update.element.classList.add(update.addClass));
  92. }
  93.  
  94. // Efficiently observe DOM changes for dynamically added content
  95. function observeDOM() {
  96. const targetNode = document.body;
  97. const config = { childList: true, subtree: true };
  98.  
  99. const callback = throttle(() => {
  100. highlightPosts(); // Re-run the highlight function when new content is added
  101. }, 2000); // CONFIGURE Throttle here (currently 2 seconds)
  102.  
  103. const observer = new MutationObserver(callback);
  104. observer.observe(targetNode, config);
  105. }
  106.  
  107. // Run the highlight function on page load
  108. window.addEventListener('load', () => {
  109. highlightPosts();
  110. observeDOM(); // Start observing for dynamic content
  111. });
  112.  
  113. })();