Gelbooru Respect Tag Blacklist Everywhere

Properly hides image thumbnails that have user-blacklisted tags on Profile pages, Wiki entries, other users' Favorites pages, and the Comments tab.

  1. // ==UserScript==
  2. // @name Gelbooru Respect Tag Blacklist Everywhere
  3. // @namespace http://tampermonkey.net/
  4. // @version 6.3.3
  5. // @description Properly hides image thumbnails that have user-blacklisted tags on Profile pages, Wiki entries, other users' Favorites pages, and the Comments tab.
  6. // @author Xerodusk
  7. // @homepage https://greasyfork.org/en/users/460331-xerodusk
  8. // @license GPL-3.0-or-later
  9. // @match https://gelbooru.com/index.php*page=account*s=profile*
  10. // @match https://gelbooru.com/index.php*page=favorites*
  11. // @match https://gelbooru.com/index.php*page=wiki*s=view*
  12. // @match https://gelbooru.com/index.php*page=comment*s=list*
  13. // @grant none
  14. // @icon https://gelbooru.com/favicon.png
  15. // ==/UserScript==
  16. /* jshint esversion: 6 */
  17.  
  18. /* configuration */
  19.  
  20. // Whether to hide blacklisted image placeholders on the page. You will be prompted about this the first time the script blocks something.
  21. // If true: Will blur out blacklisted images, but not remove them completely (like main gallery pages)
  22. // If false: Will completely remove blacklisted image thumbnails from the page (like search pages)
  23. const removeBlacklistedThumbnailsEntirely = JSON.parse(localStorage.getItem('removeBlacklistedThumbnailsEntirely') || 'false');
  24.  
  25. /*-------------------*/
  26.  
  27. // Get cookie by name
  28. // From https://www.w3schools.com/js/js_cookies.asp
  29. // Modified by me to fix bugs
  30. function getCookie(cname) {
  31. 'use strict';
  32.  
  33. var name = cname + "=";
  34. var cookie = document.cookie.split(';');
  35. var ca = cookie.map(item => decodeURIComponent(item));
  36. for (var i = 0; i < ca.length; i++) {
  37. var c = ca[i];
  38. while (c.charAt(0) == ' ') {
  39. c = c.substring(1);
  40. }
  41. if (c.indexOf(name) == 0) {
  42. return c.substring(name.length, c.length);
  43. }
  44. }
  45. return '';
  46. }
  47.  
  48. // Get tag blacklist as list of strings
  49. function GetBlockedTags() {
  50. 'use strict';
  51.  
  52. // Get blocked tags string from cookie
  53. let blockedTags = getCookie('tag_blacklist');
  54. blockedTags = htmlDecode(blockedTags).replace(/%20/g, ' ');
  55.  
  56. // Split tags into list
  57. blockedTags = blockedTags.split(' ');
  58.  
  59. return blockedTags;
  60. }
  61.  
  62. // Get current user's user ID, if exists
  63. function getUserID() {
  64. 'use strict';
  65.  
  66. // Get user ID from cookie
  67. const userID = window.Cookie.get('user_id');
  68.  
  69. return userID ? parseInt(userID) : -1;
  70. }
  71.  
  72. // Decode encoded characters in tags for proper matching
  73. function htmlDecode(input) {
  74. 'use strict';
  75.  
  76. const tempElem = document.createElement('div');
  77. tempElem.innerHTML = input;
  78. return tempElem.childNodes.length === 0 ? '' : tempElem.childNodes[0].nodeValue;
  79. }
  80.  
  81. // Get tags list for image thumbnail as list of strings
  82. function GetImageTags(imageThumb) {
  83. 'use strict';
  84.  
  85. const tagsString = imageThumb.getElementsByTagName('img')[0].getAttribute('title') || imageThumb.getElementsByTagName('img')[0].getAttribute('alt') || [];
  86. const tagsList = htmlDecode(tagsString).trim().split(' ');
  87.  
  88. return tagsList;
  89. }
  90.  
  91. // Set whether to show notification toast
  92. function setStopShowingToast() {
  93. 'use strict';
  94.  
  95. const storageData = {
  96. value: true,
  97. expiration: (new Date()).getTime() + 1000 * 60 * 60 * 24 * 30, // Setting expires after 30 days
  98. };
  99. localStorage.setItem('doNotShowBlacklistedNotification', JSON.stringify(storageData));
  100. }
  101.  
  102. // Get whether user has opted to stop seeing notification toast
  103. function stopShowingToast() {
  104. 'use strict';
  105.  
  106. // Get data if exists
  107. const storedData = JSON.parse(localStorage.getItem('doNotShowBlacklistedNotification'));
  108. if (!storedData) {
  109. return null;
  110. }
  111.  
  112. // Check if expired
  113. if ((new Date()).getTime() > storedData.expiration) {
  114. localStorage.removeItem('doNotShowBlacklistedNotification');
  115. return null;
  116. }
  117.  
  118. return storedData.value;
  119. }
  120.  
  121. // Create notification toast
  122. function createToast(count) {
  123. 'use strict';
  124.  
  125. // Check whether user has opted to stop seeing these
  126. if (stopShowingToast()) {
  127. return;
  128. }
  129.  
  130. // Create toast
  131. const toast = document.createElement('div');
  132. toast.id = 'blacklist-notification';
  133. toast.classList.add('toast');
  134. const toastTextContainer = document.createElement('div');
  135. toastTextContainer.classList.add('toast-text-container');
  136.  
  137. const firstLine = document.createElement('div');
  138. firstLine.appendChild(document.createTextNode(count + ' blacklisted ' + (count === 1 ? 'image ' : 'images ') + (removeBlacklistedThumbnailsEntirely ? 'removed.' : 'hidden.')));
  139. const hideOptionButton = document.createElement('a');
  140. hideOptionButton.id = 'blacklist-toggle-hide-option';
  141. hideOptionButton.classList.add('toast-action');
  142. hideOptionButton.appendChild(document.createTextNode(removeBlacklistedThumbnailsEntirely ? 'Blur Only' : 'Remove Entirely'));
  143. hideOptionButton.href = 'javascript:void(0)';
  144. hideOptionButton.onclick = () => {
  145. localStorage.setItem('removeBlacklistedThumbnailsEntirely', !removeBlacklistedThumbnailsEntirely);
  146. location.reload();
  147. };
  148. firstLine.appendChild(hideOptionButton);
  149.  
  150. const secondLine = document.createElement('div');
  151. const stopShowingButton = document.createElement('a');
  152. stopShowingButton.classList.add('toast-action');
  153. stopShowingButton.appendChild(document.createTextNode('Do not show this again'));
  154. stopShowingButton.href = 'javascript:void(0)';
  155. stopShowingButton.onclick = () => {
  156. setStopShowingToast();
  157. toast.remove();
  158. };
  159. secondLine.appendChild(stopShowingButton);
  160.  
  161. const closeButtonContainer = document.createElement('div');
  162. closeButtonContainer.id = 'blacklist-toast-close-button-container';
  163. const closeButton = document.createElement('a');
  164. closeButton.id = 'blacklist-toast-close-button';
  165. closeButton.appendChild(document.createTextNode('\u2573'));
  166. closeButton.href = 'javascript:void(0)';
  167. closeButton.onclick = () => toast.remove();
  168. closeButtonContainer.appendChild(closeButton);
  169.  
  170. toastTextContainer.appendChild(firstLine);
  171. toastTextContainer.appendChild(secondLine);
  172. toast.appendChild(toastTextContainer);
  173. toast.appendChild(closeButtonContainer);
  174.  
  175. document.body.appendChild(toast);
  176.  
  177. // Toast styling
  178. const css = document.createElement('style');
  179. css.appendChild(document.createTextNode(`
  180. .toast {
  181. position: fixed;
  182. bottom: 35px;
  183. left: 50%;
  184. transform: translateX(-50%);
  185. box-sizing: border-box;
  186. width: auto;
  187. max-width: 100%;
  188. height: 64px;
  189. border-radius: 32px;
  190. line-height: 1.5em;
  191. background-color: #323232;
  192. padding: 10px 25px;
  193. font-size: 17px;
  194. color: #fff;
  195. display: flex;
  196. align-items: center;
  197. text-align: center;
  198. justify-content: space-between;
  199. cursor: default;
  200. box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
  201. }
  202. .toast.hide {
  203. pointer-events: none;
  204. opacity: 0;
  205. transition: opacity 0.5s cubic-bezier(0, 0, 0.3, 1);
  206. }
  207. .toast .toast-action {
  208. font-weight: 500;
  209. cursor: pointer;
  210. color: #90CAF9;
  211. }
  212. .toast .toast-action:hover,
  213. .toast .toast-action:focus {
  214. color: #42A5F5;
  215. }
  216. #blacklist-toggle-hide-option {
  217. margin-right: 6px;
  218. margin-left: 6px;
  219. }
  220. #blacklist-toast-close-button-container {
  221. block-size: fit-content;
  222. }
  223. #blacklist-toast-close-button {
  224. position: relative;
  225. top: -2px;
  226. color: #ccc;
  227. cursor: pointer;
  228. box-sizing: border-box;
  229. padding: 14px 15px 18px 17px;
  230. width: 48px;
  231. height: 48px;
  232. margin-right: -16px;
  233. border-radius: 24px;
  234. font-size: 16px;
  235. }
  236. #blacklist-toast-close-button:hover,
  237. #blacklist-toast-close-button:focus {
  238. background-color: #757575;
  239. }
  240. `));
  241. document.head.appendChild(css);
  242.  
  243. // Fade out after a bit
  244. setTimeout(() => {
  245. if (toast) {
  246. toast.classList.add('hide');
  247. }
  248. }, 5000);
  249. }
  250.  
  251. // Mark images as blacklisted for profile page
  252. function MarkProfileBlacklistedImages(blockedTags, searchParams) {
  253. 'use strict';
  254.  
  255. // Check if it is your own profile before applying anything
  256. const userID = getUserID();
  257. if (searchParams.has('id') && parseInt(searchParams.get('id')) == userID) {
  258. return;
  259. }
  260.  
  261. // Get all image thumbnails on page
  262. const imageThumbs = [...document.getElementsByClassName('profileThumbnailPadding')];
  263.  
  264. // Apply blacklist to image thumbnails
  265. let count = 0;
  266. imageThumbs.forEach(imageThumb => {
  267. const tags = GetImageTags(imageThumb);
  268. if (tags.some(tag => blockedTags.includes(tag))) {
  269. if (removeBlacklistedThumbnailsEntirely) {
  270. imageThumb.remove();
  271. } else {
  272. imageThumb.classList.add('blacklisted');
  273. }
  274. count++;
  275. }
  276. });
  277.  
  278. // Show notification if any were blocked
  279. if (count) {
  280. createToast(count);
  281. }
  282. }
  283.  
  284. // Mark images as blacklisted for other users' favorites pages
  285. function MarkFavoritesBlacklistedImages(blockedTags, searchParams) {
  286. 'use strict';
  287.  
  288. // Check if it is your own favorites before applying anything
  289. const userID = getUserID();
  290. if (searchParams.has('id') && parseInt(searchParams.get('id')) == userID) {
  291. return;
  292. }
  293.  
  294. // Blacklisted class not already defined on favorites pages due to different framework from the rest of the site
  295. const css = document.createElement('style');
  296.  
  297. css.appendChild(document.createTextNode(`
  298. .blacklisted {
  299. opacity: .2;
  300. filter: blur(10px);
  301. }
  302. `));
  303.  
  304. document.head.appendChild(css);
  305.  
  306. // Get all image thumbnails on page
  307. const imageThumbs = document.querySelectorAll('span.thumb');
  308.  
  309. // Apply blacklist to image thumbnails
  310. let count = 0;
  311. imageThumbs.forEach(imageThumb => {
  312. const tags = GetImageTags(imageThumb);
  313. if (tags.some(tag => blockedTags.includes(tag))) {
  314. if (removeBlacklistedThumbnailsEntirely) {
  315. imageThumb.remove();
  316. } else {
  317. imageThumb.classList.add('blacklisted');
  318. }
  319. count++;
  320. }
  321. });
  322.  
  323. // Show notification if any were blocked
  324. if (count) {
  325. createToast(count);
  326. }
  327. }
  328.  
  329. // Mark images as blacklisted for wiki page
  330. function MarkWikiBlacklistedImages(blockedTags) {
  331. 'use strict';
  332.  
  333. // Get all image thumbnails on page
  334. const imageThumbs = document.querySelectorAll('a[href^="index.php?page=post&s=view&id="]');
  335.  
  336. // Apply blacklist to image thumbnails
  337. let count = 0;
  338. imageThumbs.forEach(imageThumb => {
  339. const tags = GetImageTags(imageThumb);
  340. if (tags.some(tag => blockedTags.includes(tag))) {
  341. if (removeBlacklistedThumbnailsEntirely) {
  342. imageThumb.remove();
  343. } else {
  344. imageThumb.classList.add('blacklisted');
  345. }
  346. count++;
  347. }
  348. });
  349.  
  350. // Show notification if any were blocked
  351. if (count) {
  352. createToast(count);
  353. }
  354. }
  355.  
  356.  
  357. // Mark images as blacklisted for comments tab
  358. function MarkCommentsBlacklistedImages(blockedTags) {
  359. 'use strict';
  360.  
  361. // Blacklisted class not already defined on comments tab due to different framework from the rest of the site
  362. const css = document.createElement('style');
  363.  
  364. css.appendChild(document.createTextNode(`
  365. .blacklisted {
  366. opacity: .2;
  367. filter: blur(10px);
  368. }
  369. `));
  370.  
  371. document.head.appendChild(css);
  372.  
  373. // Get all image thumbnails on page
  374. const imageThumbs = document.querySelectorAll('#comment-list .post .col1 a[href*="page=post&s=view"]');
  375.  
  376. // Apply blacklist to image thumbnails
  377. let count = 0;
  378. imageThumbs.forEach(imageThumb => {
  379. const tags = GetImageTags(imageThumb);
  380. if (tags.some(tag => blockedTags.includes(tag))) {
  381. if (removeBlacklistedThumbnailsEntirely) {
  382. imageThumb.closest('div.post').remove();
  383. } else {
  384. imageThumb.classList.add('blacklisted');
  385. }
  386. count++;
  387. }
  388. });
  389.  
  390. // Show notification if any were blocked
  391. if (count) {
  392. createToast(count);
  393. }
  394. }
  395.  
  396. // Mark images as blacklisted
  397. function MarkBlacklistedImages(blockedTags) {
  398. 'use strict';
  399.  
  400. const searchParams = new URLSearchParams(window.location.search);
  401.  
  402. if (!searchParams.has('s')) {
  403. return false;
  404. }
  405. if (searchParams.get('s') === 'profile') {
  406. MarkProfileBlacklistedImages(blockedTags, searchParams);
  407. } else if (searchParams.get('page') === 'favorites') {
  408. MarkFavoritesBlacklistedImages(blockedTags, searchParams);
  409. } else if (searchParams.get('page') === 'wiki') {
  410. MarkWikiBlacklistedImages(blockedTags);
  411. } else if (searchParams.get('page') === 'comment') {
  412. MarkCommentsBlacklistedImages(blockedTags);
  413. }
  414. }
  415.  
  416. (function() {
  417. 'use strict';
  418.  
  419. const blockedTags = GetBlockedTags();
  420. if (blockedTags) {
  421. MarkBlacklistedImages(blockedTags);
  422. }
  423. })();