ao3 savior

Hide specified works on AO3

  1. // ==UserScript==
  2. // @name ao3 savior
  3. // @description Hide specified works on AO3
  4. // @namespace ao3
  5. // @include http*://archiveofourown.org/*
  6. // @grant none
  7. // @version 1.16
  8. // ==/UserScript==
  9.  
  10.  
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. var STYLE = '\n html body .ao3-savior-hidden.ao3-savior-hidden {\n display: none;\n }\n \n .ao3-savior-cut {\n display: none;\n }\n \n .ao3-savior-cut::after {\n clear: both;\n content: \'\';\n display: block;\n }\n \n .ao3-savior-reason {\n margin-left: 5px;\n }\n \n .ao3-savior-hide-reasons .ao3-savior-reason {\n display: none;\n }\n \n .ao3-savior-unhide .ao3-savior-cut {\n display: block;\n }\n \n .ao3-savior-fold {\n align-items: center;\n display: flex;\n justify-content: flex-start;\n }\n \n .ao3-savior-unhide .ao3-savior-fold {\n border-bottom: 1px dashed;\n margin-bottom: 15px;\n padding-bottom: 5px;\n }\n \n button.ao3-savior-toggle {\n margin-left: auto;\n }\n';
  16.  
  17. function addStyle() {
  18. var style = document.createElement('style');
  19. style.innerHTML = STYLE;
  20. style.className = 'ao3-savior';
  21.  
  22. document.head.appendChild(style);
  23. }
  24.  
  25. var CSS_NAMESPACE = 'ao3-savior';
  26.  
  27. var getCut = function getCut(work) {
  28. var cut = document.createElement('div');
  29.  
  30. cut.className = CSS_NAMESPACE + '-cut';
  31. Array.from(work.childNodes).forEach(function (child) {
  32. return cut.appendChild(child);
  33. });
  34.  
  35. return cut;
  36. };
  37.  
  38. var getFold = function getFold(reason) {
  39. var fold = document.createElement('div');
  40. var note = document.createElement('span');
  41.  
  42. fold.className = CSS_NAMESPACE + '-fold';
  43. note.className = CSS_NAMESPACE + '-note';
  44.  
  45. note.innerHTML = 'This work is hidden!';
  46.  
  47. fold.appendChild(note);
  48. fold.append(' ');
  49. fold.appendChild(getReasonSpan(reason));
  50. fold.appendChild(getToggleButton());
  51.  
  52. return fold;
  53. };
  54.  
  55. var getToggleButton = function getToggleButton() {
  56. var button = document.createElement('button');
  57. var unhideClassFragment = ' ' + CSS_NAMESPACE + '-unhide';
  58.  
  59. button.innerHTML = 'Unhide';
  60. button.className = CSS_NAMESPACE + '-toggle';
  61.  
  62. button.addEventListener('click', function (event) {
  63. var work = event.target.closest('.' + CSS_NAMESPACE + '-work');
  64.  
  65. if (work.className.indexOf(unhideClassFragment) !== -1) {
  66. work.className = work.className.replace(unhideClassFragment, '');
  67. work.querySelector('.' + CSS_NAMESPACE + '-note').innerHTML = 'This work is hidden.';
  68. event.target.innerHTML = 'Unhide';
  69. } else {
  70. work.className += unhideClassFragment;
  71. work.querySelector('.' + CSS_NAMESPACE + '-note').innerHTML = 'ℹ️ This work was hidden.';
  72. event.target.innerHTML = 'Hide';
  73. }
  74. });
  75.  
  76. return button;
  77. };
  78.  
  79. var getReasonSpan = function getReasonSpan(reason) {
  80. var span = document.createElement('span');
  81. var tag = reason.tag,
  82. author = reason.author,
  83. title = reason.title,
  84. summary = reason.summary;
  85.  
  86. var text = void 0;
  87.  
  88. if (tag) {
  89. text = 'tags include <strong>' + tag + '</strong>';
  90. } else if (author) {
  91. text = 'authors include <strong>' + author + '</strong>';
  92. } else if (title) {
  93. text = 'title is <strong>' + title + '</strong>';
  94. } else if (summary) {
  95. text = 'summary includes <strong>' + summary + '</strong>';
  96. }
  97.  
  98. if (text) {
  99. span.innerHTML = '(Reason: ' + text + '.)';
  100. }
  101.  
  102. span.className = CSS_NAMESPACE + '-reason';
  103.  
  104. return span;
  105. };
  106.  
  107. function blockWork(work, reason, config) {
  108. if (!reason) return;
  109.  
  110. var showReasons = config.showReasons,
  111. showPlaceholders = config.showPlaceholders;
  112.  
  113.  
  114. if (showPlaceholders) {
  115. var fold = getFold(reason);
  116. var cut = getCut(work);
  117.  
  118. work.className += ' ' + CSS_NAMESPACE + '-work';
  119. work.innerHTML = '';
  120. work.appendChild(fold);
  121. work.appendChild(cut);
  122.  
  123. if (!showReasons) {
  124. work.className += ' ' + CSS_NAMESPACE + '-hide-reasons';
  125. }
  126. } else {
  127. work.className += ' ' + CSS_NAMESPACE + '-hidden';
  128. }
  129. }
  130.  
  131. function matchTermsWithWildCard(term0, pattern0) {
  132. var term = term0.toLowerCase();
  133. var pattern = pattern0.toLowerCase();
  134.  
  135. if (term === pattern) return true;
  136. if (pattern.indexOf('*') === -1) return false;
  137.  
  138. var lastMatchedIndex = pattern.split('*').filter(Boolean).reduce(function (prevIndex, chunk) {
  139. var matchedIndex = term.indexOf(chunk);
  140. return prevIndex >= 0 && prevIndex <= matchedIndex ? matchedIndex : -1;
  141. }, 0);
  142.  
  143. return lastMatchedIndex >= 0;
  144. }
  145.  
  146. var isItemWhitelisted = function isItemWhitelisted(items, whitelist) {
  147. var whitelistLookup = whitelist.reduce(function (lookup, item) {
  148. lookup[item] = true;
  149. return lookup;
  150. }, {});
  151.  
  152. return items.some(function (item) {
  153. return !!whitelistLookup[item];
  154. });
  155. };
  156.  
  157. var findBlacklistedItem = function findBlacklistedItem(list, blacklist, comparator) {
  158. var matchingEntry = void 0;
  159.  
  160. list.some(function (item) {
  161. blacklist.some(function (entry) {
  162. var matched = comparator(item, entry);
  163.  
  164. if (matched) matchingEntry = entry;
  165.  
  166. return matched;
  167. });
  168. });
  169.  
  170. return matchingEntry;
  171. };
  172.  
  173. var equals = function equals(a, b) {
  174. return a === b;
  175. };
  176. var contains = function contains(a, b) {
  177. return a.indexOf(b) !== -1;
  178. };
  179.  
  180. function getBlockReason(_ref, _ref2) {
  181. var _ref$authors = _ref.authors,
  182. authors = _ref$authors === undefined ? [] : _ref$authors,
  183. _ref$title = _ref.title,
  184. title = _ref$title === undefined ? '' : _ref$title,
  185. _ref$tags = _ref.tags,
  186. tags = _ref$tags === undefined ? [] : _ref$tags,
  187. _ref$summary = _ref.summary,
  188. summary = _ref$summary === undefined ? '' : _ref$summary;
  189. var _ref2$authorBlacklist = _ref2.authorBlacklist,
  190. authorBlacklist = _ref2$authorBlacklist === undefined ? [] : _ref2$authorBlacklist,
  191. _ref2$titleBlacklist = _ref2.titleBlacklist,
  192. titleBlacklist = _ref2$titleBlacklist === undefined ? [] : _ref2$titleBlacklist,
  193. _ref2$tagBlacklist = _ref2.tagBlacklist,
  194. tagBlacklist = _ref2$tagBlacklist === undefined ? [] : _ref2$tagBlacklist,
  195. _ref2$authorWhitelist = _ref2.authorWhitelist,
  196. authorWhitelist = _ref2$authorWhitelist === undefined ? [] : _ref2$authorWhitelist,
  197. _ref2$tagWhitelist = _ref2.tagWhitelist,
  198. tagWhitelist = _ref2$tagWhitelist === undefined ? [] : _ref2$tagWhitelist,
  199. _ref2$summaryBlacklis = _ref2.summaryBlacklist,
  200. summaryBlacklist = _ref2$summaryBlacklis === undefined ? [] : _ref2$summaryBlacklis;
  201.  
  202.  
  203. if (isItemWhitelisted(tags, tagWhitelist)) {
  204. return null;
  205. }
  206.  
  207. if (isItemWhitelisted(authors, authorWhitelist)) {
  208. return null;
  209. }
  210.  
  211. var blockedTag = findBlacklistedItem(tags, tagBlacklist, matchTermsWithWildCard);
  212. if (blockedTag) {
  213. return { tag: blockedTag };
  214. }
  215.  
  216. var author = findBlacklistedItem(authors, authorBlacklist, equals);
  217. if (author) {
  218. return { author: author };
  219. }
  220.  
  221. var blockedTitle = findBlacklistedItem([title], titleBlacklist, matchTermsWithWildCard);
  222. if (blockedTitle) {
  223. return { title: blockedTitle };
  224. }
  225.  
  226. var summaryTerm = findBlacklistedItem([summary], summaryBlacklist, contains);
  227. if (summaryTerm) {
  228. return { summary: summaryTerm };
  229. }
  230.  
  231. return null;
  232. }
  233.  
  234. function isDebug(location) {
  235. return location.hostname === 'localhost' || /\ba3sv-debug\b/.test(location.search);
  236. }
  237.  
  238. var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  239.  
  240. var getText = function getText(element) {
  241. return element.textContent.replace(/^\s*|\s*$/g, '');
  242. };
  243. var selectTextsIn = function selectTextsIn(root, selector) {
  244. return Array.from(root.querySelectorAll(selector)).map(getText);
  245. };
  246.  
  247. function selectFromWork(container) {
  248. return _extends({}, selectFromBlurb(container), {
  249. title: selectTextsIn(container, '.title')[0],
  250. summary: selectTextsIn(container, '.summary .userstuff')[0]
  251. });
  252. }
  253.  
  254. function selectFromBlurb(blurb) {
  255. return {
  256. authors: selectTextsIn(blurb, 'a[rel=author]'),
  257. tags: [].concat(selectTextsIn(blurb, 'a.tag'), selectTextsIn(blurb, '.required-tags .text')),
  258. title: selectTextsIn(blurb, '.header .heading a:first-child')[0],
  259. summary: selectTextsIn(blurb, 'blockquote.summary')[0]
  260. };
  261. }
  262.  
  263. setTimeout(function () {
  264. var debugMode = isDebug(window.location);
  265. var config = window.ao3SaviorConfig;
  266. var workContainer = document.querySelector('#main.works-show') || document.querySelector('#main.chapters-show');
  267. var blocked = 0;
  268. var total = 0;
  269.  
  270. if (debugMode) {
  271. console.groupCollapsed('AO3 SAVIOR');
  272.  
  273. if (!config) {
  274. console.warn('Exiting due to missing config.');
  275. return;
  276. }
  277. }
  278.  
  279. addStyle();
  280.  
  281. Array.from(document.querySelectorAll('li.blurb')).forEach(function (blurb) {
  282. var blockables = selectFromBlurb(blurb);
  283. var reason = getBlockReason(blockables, config);
  284.  
  285. total++;
  286.  
  287. if (reason) {
  288. blockWork(blurb, reason, config);
  289. blocked++;
  290.  
  291. if (debugMode) {
  292. console.groupCollapsed('- blocked ' + blurb.id);
  293. console.log(blurb, reason);
  294. console.groupEnd();
  295. }
  296. } else if (debugMode) {
  297. console.groupCollapsed(' skipped ' + blurb.id);
  298. console.log(blurb);
  299. console.groupEnd();
  300. }
  301. });
  302.  
  303. if (config.alertOnVisit && workContainer && document.referrer.indexOf('//archiveofourown.org') === -1) {
  304.  
  305. var blockables = selectFromWork(workContainer);
  306. var reason = getBlockReason(blockables, config);
  307.  
  308. if (reason) {
  309. blocked++;
  310. blockWork(workContainer, reason, config);
  311. }
  312. }
  313.  
  314. if (debugMode) {
  315. console.log('Blocked ' + blocked + ' out of ' + total + ' works');
  316. console.groupEnd();
  317. }
  318. }, 10);
  319.  
  320. }());

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址