Remove Blacklisted Images with Sidebar

Adds a sidebar to manage and display blacklisted tags and hides or shows images based on the blacklist on rule34.xxx.

Verzia zo dňa 04.09.2024. Pozri najnovšiu verziu.

  1. // ==UserScript==
  2. // @name Remove Blacklisted Images with Sidebar
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description Adds a sidebar to manage and display blacklisted tags and hides or shows images based on the blacklist on rule34.xxx.
  6. // @author Dramorian
  7. // @match https://rule34.xxx/index.php?page=post*
  8. // @match https://rule34.xxx/index.php?page=favorites*
  9. // @match https://rule34.xxx/index.php?page=comment*
  10. // @exclude https://rule34.xxx/index.php?page=post&s=view*
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=rule34.xxx
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. const debug = true; // Set to false to disable logging
  19.  
  20. function getTagBlacklist() {
  21. const cookieValue = getCookieValue('tag_blacklist');
  22. const blacklist = cookieValue ? decodeBlacklist(cookieValue) : [];
  23.  
  24. if (debug) {
  25. console.log('Retrieved blacklist:', blacklist);
  26. }
  27.  
  28. return blacklist;
  29. }
  30.  
  31. function getCookieValue(name) {
  32. const cookie = document.cookie.split('; ').find(row => row.startsWith(`${name}=`));
  33. return cookie ? cookie.split('=')[1] : null;
  34. }
  35.  
  36. function decodeBlacklist(encodedString) {
  37. return decodeURIComponent(encodedString).split('%20');
  38. }
  39.  
  40. // Function to create and inject the sidebar
  41. function createSidebar() {
  42. const sidebarHTML = `
  43. <div id="blacklist-box">
  44. <div id="sidebar-header">
  45. <h2>Blacklisted</h2>
  46. <button id="toggle-header" aria-label="Toggle Sidebar">
  47. <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-caret-right-fill" viewBox="0 0 16 16" class="toggle-icon">
  48. <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
  49. </svg>
  50. </button>
  51. </div>
  52. <div id="sidebar-content" style="display: none;">
  53. <ul id="blacklist-list" style="list-style: none;"></ul>
  54. </div>
  55. <div id="sidebar-footer">
  56. <button id="disable-all-blacklists">Disable All</button>
  57. <button id="re-enable-all-blacklists" style="display: none;">Re-enable All</button>
  58. </div>
  59. </div>
  60. `;
  61.  
  62. const targetElement = findSidebarInsertionTarget();
  63. if (!targetElement) {
  64. console.error('Suitable element for sidebar insertion not found.');
  65. return;
  66. }
  67.  
  68. insertSidebar(targetElement, sidebarHTML);
  69. updateSidebar();
  70. addSidebarEventListeners();
  71. addCollapsibleFunctionality();
  72. }
  73.  
  74. function findSidebarInsertionTarget() {
  75. return document.querySelector('div.tag-search') || document.querySelector('#content > h1');
  76. }
  77.  
  78. function insertSidebar(targetElement, sidebarHTML) {
  79. const sidebarContainer = document.createElement('div');
  80. sidebarContainer.innerHTML = sidebarHTML;
  81. targetElement.insertAdjacentElement('afterend', sidebarContainer);
  82. if (debug) {
  83. console.log(`Sidebar inserted after ${targetElement.tagName.toLowerCase()} element.`);
  84. }
  85. }
  86.  
  87. function addCollapsibleFunctionality() {
  88. const toggleButton = document.getElementById('toggle-header');
  89. const icon = toggleButton.querySelector('svg');
  90.  
  91. toggleButton.addEventListener('click', () => {
  92. const content = document.getElementById('sidebar-content');
  93. const isCollapsed = content.style.display === 'none';
  94. content.style.display = isCollapsed ? 'block' : 'none';
  95.  
  96. // Apply rotation transform
  97. icon.style.transform = isCollapsed ? 'rotate(90deg)' : 'rotate(0deg)';
  98. icon.style.transition = 'transform 0.25s ease';
  99.  
  100. if (debug) {
  101. console.log('Sidebar toggle button clicked. New display state:', content.style.display);
  102. }
  103. });
  104. }
  105.  
  106. // Function to update the sidebar with current blacklist items
  107. function updateSidebar() {
  108. const blacklist = getTagBlacklist();
  109. const blacklistList = document.getElementById('blacklist-list');
  110. const header = document.querySelector('#blacklist-box h2');
  111.  
  112. blacklistList.innerHTML = '';
  113.  
  114. const detectedTags = blacklist.filter(isTagDetectedOnPage);
  115. const totalHiddenPosts = updateBlacklistList(detectedTags, blacklistList);
  116.  
  117. updateSidebarVisibility(detectedTags.length, header, totalHiddenPosts);
  118. }
  119.  
  120. function updateBlacklistList(detectedTags, blacklistList) {
  121. let totalHiddenPosts = 0;
  122.  
  123. detectedTags.forEach(tag => {
  124. const isDisabled = isTagDisabled(tag);
  125. const hiddenCount = getInitialHiddenPostCount(tag);
  126. totalHiddenPosts += hiddenCount;
  127.  
  128. const listItem = createBlacklistListItem(tag, isDisabled, hiddenCount);
  129. blacklistList.appendChild(listItem);
  130.  
  131. if (debug) console.log(`Added tag to sidebar: ${tag}, Hidden count: ${hiddenCount}`);
  132. });
  133.  
  134. if (debug) console.log('Sidebar updated. Total hidden posts:', totalHiddenPosts);
  135.  
  136. // Re-attach event listeners after updating the sidebar
  137. addCheckboxEventListeners();
  138.  
  139. return totalHiddenPosts;
  140. }
  141.  
  142. function createBlacklistListItem(tag, isDisabled, hiddenCount) {
  143. const listItem = document.createElement('li');
  144. listItem.innerHTML = `
  145. <label>
  146. <input type="checkbox" class="blacklist-checkbox" data-tag="${encodeURIComponent(tag)}" ${isDisabled ? '' : 'checked'}>
  147. ${tag} <span class="count">${hiddenCount}</span>
  148. </label>
  149. `;
  150. return listItem;
  151. }
  152.  
  153. function updateSidebarVisibility(tagCount, header, totalHiddenPosts) {
  154. const sidebar = document.getElementById('blacklist-box');
  155.  
  156. if (tagCount === 0) {
  157. sidebar.style.display = 'none';
  158. header.textContent = 'Blacklisted (0)';
  159. if (debug) console.log('No blacklisted tags detected. Sidebar hidden.');
  160. } else {
  161. sidebar.style.display = '';
  162. header.textContent = `Blacklisted (${totalHiddenPosts})`;
  163. }
  164. }
  165.  
  166. // Function to check if a tag is currently detected on the page
  167. function isTagDetectedOnPage(tag) {
  168. const elements = document.querySelectorAll('img[title], div[id^="p"] > div.col1.thumb > a > img');
  169. return Array.from(elements).some(el => {
  170. const title = el.getAttribute('title');
  171. const isDetected = title && containsExactTag(title, [tag]);
  172. if (debug && isDetected) console.log(`Tag detected on page: ${tag}`);
  173. return isDetected;
  174. });
  175. }
  176.  
  177. // Function to check if a tag is currently disabled
  178. function isTagDisabled(tag) {
  179. const disabledTags = JSON.parse(localStorage.getItem('disabled_tags') || '[]');
  180. const isDisabled = disabledTags.includes(tag);
  181. if (debug) console.log(`Tag ${tag} is ${isDisabled ? 'disabled' : 'enabled'}`);
  182. return isDisabled;
  183. }
  184.  
  185. // Function to toggle the filtering state of a tag
  186. function toggleTag(tag, isEnabled) {
  187. let disabledTags = getDisabledTags();
  188.  
  189. disabledTags = isEnabled ? removeTag(disabledTags, tag) : addTag(disabledTags, tag);
  190.  
  191. if (debug) logTagState(tag, isEnabled);
  192.  
  193. localStorage.setItem('disabled_tags', JSON.stringify(disabledTags));
  194. applyTagFiltering();
  195. }
  196.  
  197. function getDisabledTags() {
  198. return JSON.parse(localStorage.getItem('disabled_tags') || '[]');
  199. }
  200.  
  201. function removeTag(tags, tag) {
  202. return tags.filter(t => t !== tag);
  203. }
  204.  
  205. function addTag(tags, tag) {
  206. if (!tags.includes(tag)) {
  207. tags.push(tag);
  208. }
  209. return tags;
  210. }
  211.  
  212. function logTagState(tag, isEnabled) {
  213. const action = isEnabled ? 'enabled' : 'disabled';
  214. console.log(`Tag ${tag} ${action}`);
  215. }
  216.  
  217. // Function to get the initial count of hidden posts for a given tag
  218. function getInitialHiddenPostCount(tag) {
  219. const favoriteCount = countHiddenPosts('img[title]', tag);
  220. const commentCount = countHiddenPosts('div[id^="p"] > div.col1.thumb > a > img', tag);
  221.  
  222. const totalCount = favoriteCount + commentCount;
  223.  
  224. if (debug) console.log(`Initial hidden post count for tag ${tag}: ${totalCount}`);
  225. return totalCount;
  226. }
  227.  
  228. function countHiddenPosts(selector, tag) {
  229. let count = 0;
  230. const images = document.querySelectorAll(selector);
  231. images.forEach(img => {
  232. const imgTitle = img.getAttribute('title');
  233. if (imgTitle && containsExactTag(imgTitle, [tag])) {
  234. count++;
  235. }
  236. });
  237. return count;
  238. }
  239.  
  240. // Function to check if the title contains any exact blacklist tag
  241. function containsExactTag(title, blacklist) {
  242. return blacklist.some(tag => {
  243. const regex = new RegExp(`\\b${tag}\\b`, 'i');
  244. const contains = regex.test(title);
  245. if (debug && contains) console.log(`Title "${title}" contains tag "${tag}"`);
  246. return contains;
  247. });
  248. }
  249.  
  250. function addSidebarEventListeners() {
  251. const disableAllButton = document.getElementById('disable-all-blacklists');
  252. const reEnableAllButton = document.getElementById('re-enable-all-blacklists');
  253.  
  254. disableAllButton.addEventListener('click', (e) => handleBlacklistToggle(e, true));
  255. reEnableAllButton.addEventListener('click', (e) => handleBlacklistToggle(e, false));
  256.  
  257. // Attach checkbox event listeners
  258. addCheckboxEventListeners();
  259. }
  260.  
  261. function handleBlacklistToggle(event, disable) {
  262. event.preventDefault();
  263.  
  264. if (disable) {
  265. const allTags = getTagBlacklist();
  266. localStorage.setItem('disabled_tags', JSON.stringify(allTags));
  267. if (debug) console.log('Disabled all blacklisted tags');
  268. toggleButtonVisibility('disable-all-blacklists', 're-enable-all-blacklists');
  269. } else {
  270. localStorage.removeItem('disabled_tags');
  271. if (debug) console.log('Re-enabled all blacklisted tags');
  272. toggleButtonVisibility('re-enable-all-blacklists', 'disable-all-blacklists');
  273. }
  274.  
  275. updateSidebar(); // Update the sidebar to reflect the changes
  276. applyTagFiltering(); // Apply filtering immediately
  277. }
  278.  
  279. function toggleButtonVisibility(hiddenButtonId, visibleButtonId) {
  280. document.getElementById(hiddenButtonId).style.display = 'none';
  281. document.getElementById(visibleButtonId).style.display = 'inline';
  282. }
  283.  
  284. // Function to attach event listeners to checkboxes
  285. function addCheckboxEventListeners() {
  286. document.querySelectorAll('#blacklist-list .blacklist-checkbox').forEach(checkbox => {
  287. checkbox.addEventListener('change', function () {
  288. const tag = decodeURIComponent(this.getAttribute('data-tag'));
  289. const isEnabled = this.checked;
  290. toggleTag(tag, isEnabled);
  291. if (debug) console.log(`Checkbox for tag "${tag}" changed to ${isEnabled ? 'enabled' : 'disabled'}`);
  292. });
  293. });
  294. }
  295.  
  296. // Function to apply tag filtering to the page
  297. function applyTagFiltering() {
  298. const disabledTags = JSON.parse(localStorage.getItem('disabled_tags') || '[]');
  299. const allTags = getTagBlacklist();
  300.  
  301. if (debug) console.log('Applying tag filtering. Disabled tags:', disabledTags, 'All tags:', allTags);
  302.  
  303. // Hide posts for tags that are checked (enabled)
  304. hidePosts(allTags.filter(tag => !disabledTags.includes(tag)));
  305.  
  306. // Show posts for tags that are unchecked (disabled)
  307. showPosts(disabledTags);
  308. }
  309.  
  310. // Function to show posts for tags that should be visible
  311. function showPosts(tags) {
  312. tags.forEach(tag => updatePostsVisibility(tag, '', ''));
  313. }
  314.  
  315. // Function to hide posts for tags that should be hidden
  316. function hidePosts(tags) {
  317. if (tags.length === 0) return; // If no tags to hide, exit the function.
  318.  
  319. tags.forEach(tag => updatePostsVisibility(tag, 'none', 'important'));
  320. }
  321.  
  322. // Helper function to update post visibility
  323. function updatePostsVisibility(tag, displayValue, importantValue) {
  324. const elements = document.querySelectorAll(`img[title*="${tag}"], div[id^="p"] > div.col1.thumb > a > img`);
  325. elements.forEach(el => {
  326. const title = el.getAttribute('title');
  327. if (title && (displayValue === '' || containsExactTag(title, [tag]))) {
  328. const parent = el.closest('span') || el.closest('div[id^="p"]');
  329. if (parent) {
  330. parent.style.display = displayValue;
  331. parent.style.setProperty('display', displayValue, importantValue);
  332. if (debug) {
  333. const action = displayValue === 'none' ? 'Hiding' : 'Showing';
  334. console.log(`${action} post for tag "${tag}"`);
  335. }
  336. }
  337. }
  338. });
  339. }
  340.  
  341.  
  342. // Function to remove the effect of the blacklisted-sidebar
  343. // Native "Hidden" button that hides/reveals images
  344. //TODO: think of the better logic
  345. function removeBlacklistedSidebarEffect() {
  346. handleBlacklistCount();
  347. removeSidebarElement();
  348. }
  349.  
  350. function handleBlacklistCount() {
  351. const blacklistCountElement = document.getElementById('blacklist-count');
  352. if (!blacklistCountElement) return;
  353.  
  354. const postCount = parseInt(blacklistCountElement.textContent, 10);
  355.  
  356. if (postCount > 0) {
  357. const hiddenLink = blacklistCountElement.closest('h5').querySelector('a');
  358. if (hiddenLink) {
  359. hiddenLink.click();
  360. if (debug) {
  361. console.log('Clicked on the "Hidden" to remove the blacklisted effect');
  362. }
  363. }
  364. }
  365. }
  366.  
  367. function removeSidebarElement() {
  368. const blacklistSidebar = document.getElementById('blacklisted-sidebar');
  369. if (blacklistSidebar) {
  370. blacklistSidebar.remove();
  371. if (debug) {
  372. console.log('Removed the native blacklisted-sidebar element from the page');
  373. }
  374. }
  375. }
  376.  
  377. removeBlacklistedSidebarEffect();
  378. createSidebar();
  379. applyTagFiltering();
  380. })();