Advertisement
Guest User

Untitled

a guest
Apr 8th, 2025
151
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.97 KB | None | 0 0
  1. // ==UserScript==
  2. // @name /bag/ filter
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.7.8-fx
  5. // @description Hides non whitelisted images. Images are whitelisted after 15 minutes of the post not being deleted. Excludes GIF, MP4, and WEBM files.
  6. // @author Anonymous
  7. // @match https://boards.4chan.org/vg*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. // Check for a thread.
  15. if (!/^bag\/|\/bag\/|Blue Archive|BIue Archive/.test(document?.querySelector('.postInfo.desktop .subject')?.textContent?.trim() ?? '')) return;
  16.  
  17. const ADD_TO_WHITELIST_TIMEOUT_MS = 15 * 60 * 1000;
  18. const MD5_WHITELIST_KEY = 'bagFilter';
  19. const IS_DISABLED_KEY = 'bagFilterDisabled';
  20. const FILTERED_CSS_CLASS = 'bag-filter-filtered';
  21. const FILTER_STYLES = `
  22. .fileThumb img:not(.full-image).${FILTERED_CSS_CLASS} {
  23. width: 50px !important;
  24. height: 50px !important;
  25. opacity: 0 !important;
  26. }
  27.  
  28. .fileThumb:hover img:not(.full-image).${FILTERED_CSS_CLASS} {
  29. width: auto !important;
  30. height: auto !important;
  31. opacity: 1 !important;
  32. z-index: 100;
  33. }`;
  34.  
  35. const fileTypeRegex = /\.(gif|mp4|webm)$/i;
  36.  
  37. function isExcludedFileType(post) {
  38. const fileText = post.querySelector('.fileText-original, .fileText');
  39. if (fileText) {
  40. const fileLink = fileText.querySelector('a');
  41. if (fileLink && fileLink.href) {
  42. return fileTypeRegex.test(fileLink.href);
  43. }
  44. }
  45. return false;
  46. }
  47.  
  48. const styleSheet = document.createElement('style');
  49. document.head.appendChild(styleSheet);
  50.  
  51. const opLinks = document.querySelector('.opContainer .fileText');
  52. const toggleLink = document.createElement('a');
  53. toggleLink.href = '#';
  54. opLinks.appendChild(document.createTextNode(' '));
  55. opLinks.appendChild(toggleLink);
  56. toggleLink.onclick = () => {
  57. window.localStorage.setItem(IS_DISABLED_KEY, String(!isDisabled()));
  58. updateToggle();
  59. };
  60.  
  61. function isDisabled() {
  62. return window.localStorage.getItem(IS_DISABLED_KEY) === 'true';
  63. }
  64.  
  65. function updateToggle() {
  66. if (isDisabled()) {
  67. toggleLink.textContent = 'enable bag filter';
  68. styleSheet.textContent = '';
  69. } else {
  70. toggleLink.textContent = 'disable bag filter';
  71. styleSheet.textContent = FILTER_STYLES;
  72. }
  73. }
  74.  
  75. const md5Whitelist = new Set(JSON.parse(window.localStorage.getItem(MD5_WHITELIST_KEY) || '[]'));
  76.  
  77. function update() {
  78. if (isDisabled()) {
  79. return;
  80. }
  81. const addToWhitelistPostTime = Date.now() - ADD_TO_WHITELIST_TIMEOUT_MS;
  82. const posts = document.querySelectorAll('.replyContainer:has(.fileThumb)');
  83. let md5WhitelistChanged = false;
  84. for (const post of posts) {
  85.  
  86. if (isExcludedFileType(post)) {
  87. continue;
  88. }
  89.  
  90. const postTime = Number(post.querySelector('.dateTime').dataset.utc) * 1000;
  91. const thumbnail = post.querySelector('img:not(.full-image)');
  92. if (!thumbnail) continue;
  93.  
  94. const md5 = thumbnail.dataset.md5;
  95. const otherFiltersMatch = imageSizeMatch(post);
  96.  
  97. if (post.hasAttribute('hidden')) {
  98. if (md5Whitelist.has(md5)) {
  99. md5Whitelist.delete(md5);
  100. md5WhitelistChanged = true;
  101. }
  102. } else if (postTime < addToWhitelistPostTime && !post.classList.contains('deleted-post')) {
  103. if (!md5Whitelist.has(md5) && otherFiltersMatch) {
  104. md5Whitelist.add(md5);
  105. md5WhitelistChanged = true;
  106. }
  107. }
  108. if (md5Whitelist.has(md5) || !otherFiltersMatch) {
  109. thumbnail.classList.remove(FILTERED_CSS_CLASS);
  110. } else {
  111. thumbnail.classList.add(FILTERED_CSS_CLASS);
  112. }
  113. }
  114. if (md5WhitelistChanged) {
  115. window.localStorage.setItem(MD5_WHITELIST_KEY, JSON.stringify([...md5Whitelist]));
  116. }
  117. }
  118.  
  119. function imageSizeMatch(post) {
  120. const fileText = post.querySelector('.fileText-original, .fileText')?.textContent;
  121. if (fileText) {
  122. const matchInKB = fileText.match(/\((\d+(?:\.\d+)?)\s*KB/i);
  123. if (matchInKB) {
  124. const sizeInKB = parseFloat(matchInKB[1]);
  125. return sizeInKB >= 20;
  126. }
  127. const matchInMB = fileText.match(/\((\d+(?:\.\d+)?)\s*MB/i);
  128. if (matchInMB) {
  129. const sizeInMB = parseFloat(matchInMB[1]);
  130. return sizeInMB >= 1;
  131. }
  132. }
  133. return false;
  134. }
  135.  
  136. // Handle the shift + right-click event.
  137. function handleRightClick(event) {
  138. // Check if Shift key is pressed.
  139. if (!event.shiftKey) return;
  140. // Prevent the default context menu.
  141. event.preventDefault();
  142.  
  143. const thumbnail = event.currentTarget; // Use currentTarget to get the bound image
  144. const md5 = thumbnail.dataset.md5;
  145. if (md5 && !md5Whitelist.has(md5)) {
  146. md5Whitelist.add(md5);
  147. window.localStorage.setItem(MD5_WHITELIST_KEY, JSON.stringify([...md5Whitelist]));
  148. thumbnail.classList.remove(FILTERED_CSS_CLASS); // Immediately show the image
  149. }
  150. }
  151.  
  152. // Additional mousedown handler for Firefox.
  153. function mousedownHandler(event) {
  154. if (event.button === 2 && event.shiftKey) {
  155. // Prevent default and handle the event as a right-click.
  156. event.preventDefault();
  157. handleRightClick(event);
  158. }
  159. }
  160.  
  161. // Attach right-click and mousedown listeners to all image thumbnails.
  162. function addRightClickListeners() {
  163. const images = document.querySelectorAll('.replyContainer .fileThumb img:not(.full-image)');
  164. images.forEach(image => {
  165. // Skip if parent post has excluded file type
  166. const post = image.closest('.replyContainer');
  167. if (post && isExcludedFileType(post)) {
  168. return;
  169. }
  170.  
  171. // Remove any existing listeners to avoid duplication.
  172. image.removeEventListener('contextmenu', handleRightClick);
  173. image.removeEventListener('mousedown', mousedownHandler);
  174. // Add listeners for both contextmenu and mousedown events.
  175. image.addEventListener('contextmenu', handleRightClick);
  176. image.addEventListener('mousedown', mousedownHandler);
  177. });
  178. }
  179.  
  180. // Observe for dynamic content loading (e.g., infinite scrolling).
  181. const observer = new MutationObserver(() => {
  182. update();
  183. addRightClickListeners();
  184. });
  185.  
  186. observer.observe(document.body, {childList: true, subtree: true});
  187.  
  188. // Initial run.
  189. updateToggle();
  190. update();
  191. addRightClickListeners();
  192. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement