Advertisement
Kaanbaltlak

AO3 Blocker - español

Jun 12th, 2024 (edited)
383
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name          AO3 Blocker
  3. // @description   Fork of ao3 savior; blocks works based on certain conditions
  4. // @author        JacenBoy
  5. // @namespace     https://github.com/JacenBoy/ao3-blocker#readme
  6. // @license       Apache-2.0; http://www.apache.org/licenses/LICENSE-2.0
  7. // @match         http*://archiveofourown.org/*
  8. // @version       3.0
  9. // @require       https://openuserjs.org/src/libs/sizzle/GM_config.js
  10. // @require       https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js
  11. // @grant         GM.getValue
  12. // @grant         GM.setValue
  13. // @run-at        document-end
  14. // @downloadURL https://update.greasyfork.org/scripts/409956/AO3%20Blocker.user.js
  15. // @updateURL https://update.greasyfork.org/scripts/409956/AO3%20Blocker.meta.js
  16. // ==/UserScript==
  17.  
  18. /* globals $, GM_config */
  19.  
  20. (function () {
  21.   "use strict";
  22.   window.ao3Blocker = {};
  23.  
  24.   // Initialize GM_config options
  25.   GM_config.init({
  26.     "id": "ao3Blocker",
  27.     "title": "AO3 Blocker",
  28.     "fields": {
  29.       "tagBlacklist": {
  30.         "label": "Lista negra de etiquetas",
  31.         "type": "text",
  32.         "default": ""
  33.       },
  34.       "tagWhitelist": {
  35.         "label": "Lista blanca de etiquetas",
  36.         "type": "text",
  37.         "default": ""
  38.       },
  39.       "tagHighlights": {
  40.         "label": "Etiquetas resaltadas",
  41.         "type": "text",
  42.         "default": ""
  43.       },
  44.       "authorBlacklist": {
  45.         "label": "Lista negra de fickers",
  46.         "type": "text",
  47.         "default": ""
  48.       },
  49.       "titleBlacklist": {
  50.         "label": "Lista negra para títulos",
  51.         "type": "text",
  52.         "default": ""
  53.       },
  54.       "summaryBlacklist": {
  55.         "label": "Lista negra para descripciones",
  56.         "type": "text",
  57.         "default": ""
  58.       },
  59.       "showReasons": {
  60.         "label": "Mostrar el motivo del bloqueo",
  61.         "type": "checkbox",
  62.         "default": true
  63.       },
  64.       "showPlaceholders": {
  65.         "label": "Mostrar placeholder",
  66.         "type": "checkbox",
  67.         "default": true
  68.       },
  69.       "alertOnVisit": {
  70.         "label": "Alertar cuando abres una obra bloqueada",
  71.         "type": "checkbox",
  72.         "default": false
  73.       },
  74.       "debugMode": {
  75.         "label": "Modo Debug",
  76.         "type": "checkbox",
  77.         "default": false
  78.       }
  79.     },
  80.     "events": {
  81.       "save": () => {
  82.         window.ao3Blocker.updated = true;
  83.         alert("Tus cambios han sido guardados.");
  84.       },
  85.       "close": () => {
  86.         if (window.ao3Blocker.updated) location.reload();
  87.       },
  88.       "init": () => {
  89.         // Config is now available
  90.         window.ao3Blocker.config = {
  91.           "showReasons": GM_config.get("showReasons"),
  92.           "showPlaceholders": GM_config.get("showPlaceholders"),
  93.           "alertOnVisit": GM_config.get("alertOnVisit"),
  94.           "authorBlacklist": GM_config.get("authorBlacklist").toLowerCase().split(/,(?:\s)?/g).map(i => i.trim()),
  95.           "titleBlacklist": GM_config.get("titleBlacklist").toLowerCase().split(/,(?:\s)?/g).map(i => i.trim()),
  96.           "tagBlacklist": GM_config.get("tagBlacklist").toLowerCase().split(/,(?:\s)?/g).map(i => i.trim()),
  97.           "tagWhitelist": GM_config.get("tagWhitelist").toLowerCase().split(/,(?:\s)?/g).map(i => i.trim()),
  98.           "tagHighlights": GM_config.get("tagHighlights").toLowerCase().split(/,(?:\s)?/g).map(i => i.trim()),
  99.           "summaryBlacklist": GM_config.get("summaryBlacklist").toLowerCase().split(/,(?:\s)?/g).map(i => i.trim()),
  100.           "debugMode": GM_config.get("debugMode")
  101.         }
  102.  
  103.         addMenu();
  104.         addStyle();
  105.         setTimeout(checkWorks, 10);
  106.       }
  107.     },
  108.     "css": ".config_var {display: grid; grid-template-columns: repeat(2, 0.7fr);}"
  109.   });
  110.  
  111.   // Define the custom styles for the script
  112.   const STYLE = "\n  html body .ao3-blocker-hidden {\n    display: none;\n  }\n  \n  .ao3-blocker-cut {\n    display: none;\n  }\n  \n  .ao3-blocker-cut::after {\n    clear: both;\n    content: '';\n    display: block;\n  }\n  \n  .ao3-blocker-reason {\n    margin-left: 5px;\n  }\n  \n  .ao3-blocker-hide-reasons .ao3-blocker-reason {\n    display: none;\n  }\n  \n  .ao3-blocker-unhide .ao3-blocker-cut {\n    display: block;\n  }\n  \n  .ao3-blocker-fold {\n    align-items: center;\n    display: flex;\n    justify-content: flex-start;\n  }\n  \n  .ao3-blocker-unhide .ao3-blocker-fold {\n    border-bottom: 1px dashed;\n    margin-bottom: 15px;\n    padding-bottom: 5px;\n  }\n  \n  button.ao3-blocker-toggle {\n    margin-left: auto;\n  }\n";
  113.  
  114.   // addMenu() - Add a custom menu to the AO3 menu bar to control our configuration options
  115.   function addMenu() {
  116.     // Define our custom menu and add it to the AO3 menu bar
  117.     const headerMenu = $("ul.primary.navigation.actions");
  118.     const blockerMenu = $("<li class=\"dropdown\"></li>").html("<a>AO3 Blocker</a>");
  119.     headerMenu.find("li.search").before(blockerMenu);
  120.     const dropMenu = $("<ul class=\"menu dropdown-menu\"></ul>");
  121.     blockerMenu.append(dropMenu);
  122.  
  123.     // Add the "Toggle Block Reason" option to the menu
  124.     const reasonButton = $("<li></li>").html(`<a>${window.ao3Blocker.config.showReasons ? "Ocultar" : "Mostrar"} motivo de bloqueo</a>`);
  125.     reasonButton.on("click", () => {
  126.       if (window.ao3Blocker.config.showReasons) {
  127.         GM_config.set("showReasons", false);
  128.       } else {
  129.         GM_config.set("showReasons", true);
  130.       }
  131.       GM_config.save();
  132.       reasonButton.html(`<a>${window.ao3Blocker.config.showReasons ? "Ocultar" : "Mostrar"} motivo de bloqueo</a>`);
  133.     });
  134.     dropMenu.append(reasonButton);
  135.  
  136.     // Add the "Toggle Work Placeholder" option to the menu
  137.     const placeholderButton = $("<li></li>").html(`<a>${window.ao3Blocker.config.showPlaceholders ? "Ocultar" : "Mostrar"} placeholder de obra</a>`);
  138.     placeholderButton.on("click", () => {
  139.       if (window.ao3Blocker.config.showPlaceholders) {
  140.         GM_config.set("showPlaceholders", false);
  141.       } else {
  142.         GM_config.set("showPlaceholders", true);
  143.       }
  144.       GM_config.save();
  145.       placeholderButton.html(`<a>${window.ao3Blocker.config.showPlaceholders ? "Ocultar" : "Mostrar"} placeholder de obra</a>`);
  146.     });
  147.     dropMenu.append(placeholderButton);
  148.  
  149.     // Add the "Toggle Block Alerts" option to the menu
  150.     const alertButton = $("<li></li>").html(`<a>${window.ao3Blocker.config.alertOnVisit ? "No mostrar" : "Mostrar"} aviso de obras bloqueadas</a>`);
  151.     alertButton.on("click", () => {
  152.       if (window.ao3Blocker.config.alertOnVisit) {
  153.         GM_config.set("alertOnVisit", false);
  154.       } else {
  155.         GM_config.set("alertOnVisit", true);
  156.       }
  157.       GM_config.save();
  158.       alertButton.html(`<a>${window.ao3Blocker.config.alertOnVisit ? "No mostrar" : "Mostrar"} aviso de obras bloqueadas</a>`);
  159.     });
  160.     dropMenu.append(alertButton);
  161.  
  162.     // Add an option to show the config dialog
  163.     const settingsButton = $("<li></li>").html("<a>Configuración</a>");
  164.     settingsButton.on("click", () => { GM_config.open(); });
  165.     dropMenu.append(settingsButton);
  166.   }
  167.  
  168.   // Define the CSS namespace. All CSS classes are prefixed with this.
  169.   const CSS_NAMESPACE = "ao3-blocker";
  170.  
  171.   // addStyle() - Apply the custom stylesheet to AO3
  172.   function addStyle() {
  173.     const style = $(`<style class="${CSS_NAMESPACE}"></style>`).html(STYLE);
  174.  
  175.     $("head").append(style);
  176.   }
  177.  
  178.   // getCut(work) - Move standard AO3 work information (tags, summary, etc.) to a custom element for blocked works. This will be hidden by default on blocked works but can be shown if thre user chooses.
  179.   function getCut(work) {
  180.     const cut = $(`<div class="${CSS_NAMESPACE}-cut"></div>`);
  181.  
  182.     $.makeArray(work.children()).forEach((child) => {
  183.       return cut.append(child);
  184.     });
  185.  
  186.     return cut;
  187.   }
  188.  
  189.   // getFold(reason) - Create the work placeholder for blocked works. Optionally, this will show why the work was blocked and give the user the option to unhide it.
  190.   function getFold(reason) {
  191.     const fold = $(`<div class="${CSS_NAMESPACE}-fold"></div>`);
  192.     const note = $(`<span class="${CSS_NAMESPACE}-note"</span>`).text("¡Esta obra está oculta! ");
  193.  
  194.     fold.html(note);
  195.     fold.append(getReasonSpan(reason));
  196.     fold.append(getToggleButton());
  197.  
  198.     return fold;
  199.   }
  200.  
  201.   // getToggleButton() - Create a button that will show or hide the "cut" on blocked works.
  202.   function getToggleButton() {
  203.     const button = $(`<button class="${CSS_NAMESPACE}-toggle"></button>`).text("Unhide");
  204.     const unhideClassFragment = `${CSS_NAMESPACE}-unhide`;
  205.  
  206.     button.on("click", (event) => {
  207.       const work = $(event.target).closest(`.${CSS_NAMESPACE}-work`);
  208.  
  209.       if (work.hasClass(unhideClassFragment)) {
  210.         work.removeClass(unhideClassFragment);
  211.         work.find(`.${CSS_NAMESPACE}-note`).text("Esta obra está oculta.");
  212.         $(event.target).text("Unhide");
  213.       } else {
  214.         work.addClass(unhideClassFragment);
  215.         work.find(`.${CSS_NAMESPACE}-note`).text("ℹ️ Esta obra está oculta.");
  216.         $(event.target).text("Hide");
  217.       }
  218.     });
  219.  
  220.     return button;
  221.   }
  222.  
  223.   // getReasonSpan(reason) - Create the element that holds the block reason information on blocked works.
  224.   function getReasonSpan(reason) {
  225.     const span = $(`<span class="${CSS_NAMESPACE}-reason"></span>`);
  226.  
  227.     let text = undefined;
  228.  
  229.     if (reason.tag) {
  230.       text = `incluye la(s) etiqueta(s): <strong>${reason.tag}</strong>`;
  231.     } else if (reason.author) {
  232.       text = `ficker(s): <strong>${reason.author}</strong>`;
  233.     } else if (reason.title) {
  234.       text = `el título contiene: <strong>${reason.title}</strong>`;
  235.     } else if (reason.summary) {
  236.       text = `la descripción contiene: <strong>${reason.summary}</strong>`;
  237.     }
  238.  
  239.     if (text) {
  240.       span.html(`(Motivo: ${text}.)`);
  241.     }
  242.  
  243.     return span;
  244.   }
  245.  
  246.   // blockWork(work, reason, config) - Replace the standard AO3 work information with the placeholder "fold", and place the "cut" below it, hidden.
  247.   function blockWork(work, reason, config) {
  248.     if (!reason) return;
  249.  
  250.     if (config.showPlaceholders) {
  251.       const fold = getFold(reason);
  252.       const cut = getCut(work);
  253.  
  254.       work.addClass(`${CSS_NAMESPACE}-work`);
  255.       work.html(fold);
  256.       work.append(cut);
  257.  
  258.       if (!config.showReasons) {
  259.         work.addClass(`${CSS_NAMESPACE}-hide-reasons`);
  260.       }
  261.     } else {
  262.       work.addClass(`${CSS_NAMESPACE}-hidden`);
  263.     }
  264.   }
  265.  
  266.   function matchTermsWithWildCard(term0, pattern0) {
  267.     const term = term0.toLowerCase();
  268.     const pattern = pattern0.toLowerCase();
  269.  
  270.     if (term === pattern) return true;
  271.     if (pattern.indexOf("*") === -1) return false;
  272.  
  273.     const lastMatchedIndex = pattern.split("*").filter(Boolean).reduce((prevIndex, chunk) => {
  274.       const matchedIndex = term.indexOf(chunk);
  275.       return prevIndex >= 0 && prevIndex <= matchedIndex ? matchedIndex : -1;
  276.     }, 0);
  277.  
  278.     return lastMatchedIndex >= 0;
  279.   }
  280.  
  281.   function isTagWhitelisted(tags, whitelist) {
  282.     const whitelistLookup = whitelist.reduce((lookup, tag) => {
  283.       lookup[tag.toLowerCase()] = true;
  284.       return lookup;
  285.     }, {});
  286.  
  287.     return tags.some((tag) => {
  288.       return !!whitelistLookup[tag.toLowerCase()];
  289.     });
  290.   }
  291.  
  292.   function findBlacklistedItem(list, blacklist, comparator) {
  293.     let matchingEntry = void 0;
  294.  
  295.     list.some((item) => {
  296.       blacklist.some((entry) => {
  297.         const matched = comparator(item.toLowerCase(), entry.toLowerCase());
  298.  
  299.         if (matched) matchingEntry = entry;
  300.  
  301.         return matched;
  302.       });
  303.     });
  304.  
  305.     return matchingEntry;
  306.   }
  307.  
  308.   function equals(a, b) {
  309.     return a === b;
  310.   }
  311.   function contains(a, b) {
  312.     return a.indexOf(b) !== -1;
  313.   }
  314.  
  315.   function getBlockReason(_ref, _ref2) {
  316.     const _ref$authors = _ref.authors,
  317.       authors = _ref$authors === undefined ? [] : _ref$authors,
  318.       _ref$title = _ref.title,
  319.       title = _ref$title === undefined ? "" : _ref$title,
  320.       _ref$tags = _ref.tags,
  321.       tags = _ref$tags === undefined ? [] : _ref$tags,
  322.       _ref$summary = _ref.summary,
  323.       summary = _ref$summary === undefined ? "" : _ref$summary;
  324.     const _ref2$authorBlacklist = _ref2.authorBlacklist,
  325.       authorBlacklist = _ref2$authorBlacklist === undefined ? [] : _ref2$authorBlacklist,
  326.       _ref2$titleBlacklist = _ref2.titleBlacklist,
  327.       titleBlacklist = _ref2$titleBlacklist === undefined ? [] : _ref2$titleBlacklist,
  328.       _ref2$tagBlacklist = _ref2.tagBlacklist,
  329.       tagBlacklist = _ref2$tagBlacklist === undefined ? [] : _ref2$tagBlacklist,
  330.       _ref2$tagWhitelist = _ref2.tagWhitelist,
  331.       tagWhitelist = _ref2$tagWhitelist === undefined ? [] : _ref2$tagWhitelist,
  332.       _ref2$summaryBlacklis = _ref2.summaryBlacklist,
  333.       summaryBlacklist = _ref2$summaryBlacklis === undefined ? [] : _ref2$summaryBlacklis;
  334.  
  335.  
  336.     if (isTagWhitelisted(tags, tagWhitelist)) {
  337.       return null;
  338.     }
  339.  
  340.     const blockedTag = findBlacklistedItem(tags, tagBlacklist, matchTermsWithWildCard);
  341.     if (blockedTag) {
  342.       return { tag: blockedTag };
  343.     }
  344.  
  345.     const author = findBlacklistedItem(authors, authorBlacklist, equals);
  346.     if (author) {
  347.       return { author: author };
  348.     }
  349.  
  350.     const blockedTitle = findBlacklistedItem([title.toLowerCase()], titleBlacklist, matchTermsWithWildCard);
  351.     if (blockedTitle) {
  352.       return { title: blockedTitle };
  353.     }
  354.  
  355.     const summaryTerm = findBlacklistedItem([summary.toLowerCase()], summaryBlacklist, contains);
  356.     if (summaryTerm) {
  357.       return { summary: summaryTerm };
  358.     }
  359.  
  360.     return null;
  361.   }
  362.  
  363.   const _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; };
  364.  
  365.   function getText(element) {
  366.     return $(element).text().replace(/^\s*|\s*$/g, "");
  367.   }
  368.   function selectTextsIn(root, selector) {
  369.     return $.makeArray($(root).find(selector)).map(getText);
  370.   }
  371.  
  372.   function selectFromWork(container) {
  373.     return _extends({}, selectFromBlurb(container), {
  374.       title: selectTextsIn(container, ".title")[0],
  375.       summary: selectTextsIn(container, ".summary .userstuff")[0]
  376.     });
  377.   }
  378.  
  379.   function selectFromBlurb(blurb) {
  380.     return {
  381.       authors: selectTextsIn(blurb, "a[rel=author]"),
  382.       tags: [].concat(selectTextsIn(blurb, "a.tag"), selectTextsIn(blurb, ".required-tags .text")),
  383.       title: selectTextsIn(blurb, ".header .heading a:first-child")[0],
  384.       summary: selectTextsIn(blurb, "blockquote.summary")[0]
  385.     };
  386.   }
  387.  
  388.   // checkWorks() - Scan all works on the page and block them if they match one of the conditions set by the user.
  389.   function checkWorks() {
  390.     const debugMode = window.ao3Blocker.config.debugMode;
  391.  
  392.     const config = window.ao3Blocker.config;
  393.     // If this is a work page, save the element for future use.
  394.     const workContainer = $("#main.works-show") || $("#main.chapters-show");
  395.     let blocked = 0;
  396.     let total = 0;
  397.  
  398.     if (debugMode) {
  399.       console.groupCollapsed("AO3 BLOCKER");
  400.  
  401.       if (!config) {
  402.         console.warn("Exiting due to missing config.");
  403.         return;
  404.       }
  405.     }
  406.  
  407.     // Loop through all works on the search page and check if they match one of the conditions.
  408.     $.makeArray($("li.blurb")).forEach((blurb) => {
  409.       blurb = $(blurb);
  410.       const blockables = selectFromBlurb(blurb);
  411.       const reason = getBlockReason(blockables, config);
  412.  
  413.       total++;
  414.  
  415.       if (reason) {
  416.         blockWork(blurb, reason, config);
  417.         blocked++;
  418.  
  419.         if (debugMode) {
  420.           console.groupCollapsed(`- blocked ${blurb.attr("id")}`);
  421.           console.log(blurb.html(), reason);
  422.           console.groupEnd();
  423.         }
  424.       } else if (debugMode) {
  425.         console.groupCollapsed(`  skipped ${blurb.attr("id")}`);
  426.         console.log(blurb.html());
  427.         console.groupEnd();
  428.       }
  429.  
  430.       blockables.tags.forEach((tag) => {
  431.         if (config.tagHighlights.includes(tag.toLowerCase())) {
  432.           blurb.css("background-color", "rgba(255,255,0,0.1)");
  433.           if (debugMode) {
  434.             console.groupCollapsed(`? highlighted ${blurb.attr("id")}`);
  435.             console.log(blurb.html());
  436.             console.groupEnd();
  437.           }
  438.         }
  439.       });
  440.     });
  441.  
  442.     // If this is a work page, the work was navigated to from another site (i.e. an external link), and the user had block alerts enabled, show a warning.
  443.     if (config.alertOnVisit && workContainer && document.referrer.indexOf("//archiveofourown.org") === -1) {
  444.  
  445.       const blockables = selectFromWork(workContainer);
  446.       const reason = getBlockReason(blockables, config);
  447.  
  448.       if (reason) {
  449.         blocked++;
  450.         blockWork(workContainer, reason, config);
  451.       }
  452.     }
  453.  
  454.     if (debugMode) {
  455.       console.log(`Se bloquearon ${blocked} de un total de ${total} obras`);
  456.       console.groupEnd();
  457.     }
  458.   }
  459. }());
  460.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement