Greasy Fork

AO3 Blocker

Fork of ao3 savior; blocks works based on certain conditions

当前为 2020-08-25 提交的版本,查看 最新版本

// ==UserScript==
// @name          AO3 Blocker
// @description   Fork of ao3 savior; blocks works based on certain conditions
// @author        JacenBoy
// @namespace     https://github.com/JacenBoy/ao3-blocker#readme
// @license       Apache-2.0; http://www.apache.org/licenses/LICENSE-2.0
// @include       http*://archiveofourown.org/*
// @version       1.0
// @require       https://openuserjs.org/src/libs/sizzle/GM_config.js
// @require       https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js
// @grant         GM_getValue
// @grant         GM_setValue
// @run-at        document-end
// ==/UserScript==



(function () {
  "use strict";

  GM_config.init({
    "id": "ao3Blocker",
    "title": "AO3 Blocker",
    "fields": {
      "tagBlacklist": {
        "label": "Tag Blacklist",
        "type": "text",
        "default": ""
      },
      "tagWhitelist": {
        "label": "Tag Whitelist",
        "type": "text",
        "default": ""
      },
      "authorBlacklist": {
        "label": "Author Blacklist",
        "type": "text",
        "default": ""
      },
      "titleBlacklist": {
        "label": "Title Blacklist",
        "type": "text",
        "default": ""
      },
      "summaryBlacklist": {
        "label": "Summary Blacklist",
        "type": "text",
        "default": ""
      },
      "showReasons": {
        "label": "Show Block Reason",
        "type": "checkbox",
        "default": true
      },
      "showPlaceholders": {
        "label": "Show Work Placeholder",
        "type": "checkbox",
        "default": true
      },
      "alertOnVisit": {
        "label": "Alert When Opening Blocked Work",
        "type": "checkbox",
        "default": false
      }
    }
  });

  var 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";

  function addMenu() {
    var headerMenu = $("ul.primary.navigation.actions");
    var blockerMenu = $("<li class=\"dropdown\"></li>").html("<a>AO3 Blocker</a>");
    headerMenu.find("li.search").before(blockerMenu);
    var dropMenu = $("<ul class=\"menu dropdown-menu\"></ul>");
    blockerMenu.append(dropMenu);

    var reasonButton = $("<li></li>").html(`<a>${GM_config.get("showReasons") ? "Hide" : "Show"} Block Reason</a>`);
    reasonButton.on("click", () => {
      if (GM_config.get("showReasons")) {
        GM_config.set("showReasons", false);
      } else {
        GM_config.set("showReasons", true);
      }
      GM_config.save();
      reasonButton.html(`<a>${GM_config.get("showReasons") ? "Hide" : "Show"} Block Reason</a>`);
    });
    dropMenu.append(reasonButton);

    var placeholderButton = $("<li></li>").html(`<a>${GM_config.get("showPlaceholders") ? "Hide" : "Show"} Work Placeholder</a>`);
    placeholderButton.on("click", () => {
      if (GM_config.get("showPlaceholders")) {
        GM_config.set("showPlaceholders", false);
      } else {
        GM_config.set("showPlaceholders", true);
      }
      GM_config.save();
      placeholderButton.html(`<a>${GM_config.get("showPlaceholders") ? "Hide" : "Show"} Work Placeholder</a>`);
    });
    dropMenu.append(placeholderButton);

    var alertButton = $("<li></li>").html(`<a>${GM_config.get("alertOnVisit") ? "Don't Show" : "Show"} Blocked Work Alerts</a>`);
    alertButton.on("click", () => {
      if (GM_config.get("alertOnVisit")) {
        GM_config.set("alertOnVisit", false);
      } else {
        GM_config.set("alertOnVisit", true);
      }
      GM_config.save();
      alertButton.html(`<a>${GM_config.get("alertOnVisit") ? "Don't Show" : "Show"} Blocked Work Alerts</a>`);
    });
    dropMenu.append(alertButton);

    var settingsButton = $("<li></li>").html("<a>All Settings</a>");
    settingsButton.on("click", () => {GM_config.open();});
    dropMenu.append(settingsButton);
  }

  function addStyle() {
    var style = document.createElement("style");
    var style = $("<style class=\"ao3-blocker\"></style>").html(STYLE);

    $("head").append(style);
  }

  var CSS_NAMESPACE = "ao3-blocker";

  var getCut = function getCut(work) {
    var cut = document.createElement("div");

    cut.className = CSS_NAMESPACE + "-cut";
    Array.from(work.childNodes).forEach(function (child) {
      return cut.appendChild(child);
    });

    return cut;
  };

  var getFold = function getFold(reason) {
    var fold = document.createElement("div");
    var note = document.createElement("span");

    fold.className = CSS_NAMESPACE + "-fold";
    note.className = CSS_NAMESPACE + "-note";

    note.innerHTML = "This work is hidden!";

    fold.appendChild(note);
    fold.append(" ");
    fold.appendChild(getReasonSpan(reason));
    fold.appendChild(getToggleButton());

    return fold;
  };

  var getToggleButton = function getToggleButton() {
    var button = document.createElement("button");
    var unhideClassFragment = " " + CSS_NAMESPACE + "-unhide";

    button.innerHTML = "Unhide";
    button.className = CSS_NAMESPACE + "-toggle";

    button.addEventListener("click", function (event) {
      var work = event.target.closest("." + CSS_NAMESPACE + "-work");

      if (work.className.indexOf(unhideClassFragment) !== -1) {
        work.className = work.className.replace(unhideClassFragment, "");
        work.querySelector("." + CSS_NAMESPACE + "-note").innerHTML = "This work is hidden.";
        event.target.innerHTML = "Unhide";
      } else {
        work.className += unhideClassFragment;
        work.querySelector("." + CSS_NAMESPACE + "-note").innerHTML = "ℹ️ This work was hidden.";
        event.target.innerHTML = "Hide";
      }
    });

    return button;
  };

  var getReasonSpan = function getReasonSpan(reason) {
    var span = document.createElement("span");
    var tag = reason.tag,
      author = reason.author,
      title = reason.title,
      summary = reason.summary;

    var text = void 0;

    if (tag) {
      text = "tags include <strong>" + tag + "</strong>";
    } else if (author) {
      text = "authors include <strong>" + author + "</strong>";
    } else if (title) {
      text = "title is <strong>" + title + "</strong>";
    } else if (summary) {
      text = "summary includes <strong>" + summary + "</strong>";
    }

    if (text) {
      span.innerHTML = "(Reason: " + text + ".)";
    }

    span.className = CSS_NAMESPACE + "-reason";

    return span;
  };

  function blockWork(work, reason, config) {
    if (!reason) return;

    var showReasons = config.showReasons,
      showPlaceholders = config.showPlaceholders;


    if (showPlaceholders) {
      var fold = getFold(reason);
      var cut = getCut(work);

      work.className += " " + CSS_NAMESPACE + "-work";
      work.innerHTML = "";
      work.appendChild(fold);
      work.appendChild(cut);

      if (!showReasons) {
        work.className += " " + CSS_NAMESPACE + "-hide-reasons";
      }
    } else {
      work.className += " " + CSS_NAMESPACE + "-hidden";
    }
  }

  function matchTermsWithWildCard(term0, pattern0) {
    var term = term0.toLowerCase();
    var pattern = pattern0.toLowerCase();

    if (term === pattern) return true;
    if (pattern.indexOf("*") === -1) return false;

    var lastMatchedIndex = pattern.split("*").filter(Boolean).reduce(function (prevIndex, chunk) {
      var matchedIndex = term.indexOf(chunk);
      return prevIndex >= 0 && prevIndex <= matchedIndex ? matchedIndex : -1;
    }, 0);

    return lastMatchedIndex >= 0;
  }

  var isTagWhitelisted = function isTagWhitelisted(tags, whitelist) {
    var whitelistLookup = whitelist.reduce(function (lookup, tag) {
      lookup[tag.toLowerCase()] = true;
      return lookup;
    }, {});

    return tags.some(function (tag) {
      return !!whitelistLookup[tag.toLowerCase()];
    });
  };

  var findBlacklistedItem = function findBlacklistedItem(list, blacklist, comparator) {
    var matchingEntry = void 0;

    list.some(function (item) {
      blacklist.some(function (entry) {
        var matched = comparator(item.toLowerCase(), entry.toLowerCase());

        if (matched) matchingEntry = entry;

        return matched;
      });
    });

    return matchingEntry;
  };

  var equals = function equals(a, b) {
    return a === b;
  };
  var contains = function contains(a, b) {
    return a.indexOf(b) !== -1;
  };

  function getBlockReason(_ref, _ref2) {
    var _ref$authors = _ref.authors,
      authors = _ref$authors === undefined ? [] : _ref$authors,
      _ref$title = _ref.title,
      title = _ref$title === undefined ? "" : _ref$title,
      _ref$tags = _ref.tags,
      tags = _ref$tags === undefined ? [] : _ref$tags,
      _ref$summary = _ref.summary,
      summary = _ref$summary === undefined ? "" : _ref$summary;
    var _ref2$authorBlacklist = _ref2.authorBlacklist,
      authorBlacklist = _ref2$authorBlacklist === undefined ? [] : _ref2$authorBlacklist,
      _ref2$titleBlacklist = _ref2.titleBlacklist,
      titleBlacklist = _ref2$titleBlacklist === undefined ? [] : _ref2$titleBlacklist,
      _ref2$tagBlacklist = _ref2.tagBlacklist,
      tagBlacklist = _ref2$tagBlacklist === undefined ? [] : _ref2$tagBlacklist,
      _ref2$tagWhitelist = _ref2.tagWhitelist,
      tagWhitelist = _ref2$tagWhitelist === undefined ? [] : _ref2$tagWhitelist,
      _ref2$summaryBlacklis = _ref2.summaryBlacklist,
      summaryBlacklist = _ref2$summaryBlacklis === undefined ? [] : _ref2$summaryBlacklis;


    if (isTagWhitelisted(tags, tagWhitelist)) {
      return null;
    }

    var blockedTag = findBlacklistedItem(tags, tagBlacklist, matchTermsWithWildCard);
    if (blockedTag) {
      return { tag: blockedTag };
    }

    var author = findBlacklistedItem(authors, authorBlacklist, equals);
    if (author) {
      return { author: author };
    }

    var blockedTitle = findBlacklistedItem([title.toLowerCase()], titleBlacklist, matchTermsWithWildCard);
    if (blockedTitle) {
      return { title: blockedTitle };
    }

    var summaryTerm = findBlacklistedItem([summary.toLowerCase()], summaryBlacklist, contains);
    if (summaryTerm) {
      return { summary: summaryTerm };
    }

    return null;
  }

  function isDebug(location) {
    return location.hostname === "localhost" || /\ba3sv-debug\b/.test(location.search);
  }

  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; };

  var getText = function getText(element) {
    return element.textContent.replace(/^\s*|\s*$/g, "");
  };
  var selectTextsIn = function selectTextsIn(root, selector) {
    return Array.from(root.querySelectorAll(selector)).map(getText);
  };

  function selectFromWork(container) {
    return _extends({}, selectFromBlurb(container), {
      title: selectTextsIn(container, ".title")[0],
      summary: selectTextsIn(container, ".summary .userstuff")[0]
    });
  }

  function selectFromBlurb(blurb) {
    return {
      authors: selectTextsIn(blurb, "a[rel=author]"),
      tags: [].concat(selectTextsIn(blurb, "a.tag"), selectTextsIn(blurb, ".required-tags .text")),
      title: selectTextsIn(blurb, ".header .heading a:first-child")[0],
      summary: selectTextsIn(blurb, "blockquote.summary")[0]
    };
  }

  setTimeout(function () {
    var debugMode = isDebug(window.location);
    var config = {
      "showReasons": GM_config.get("showReasons"),
      "showPlaceholders": GM_config.get("showPlaceholders"),
      "alertOnVisit": GM_config.get("alertOnVisit"),
      "authorBlacklist": GM_config.get("authorBlacklist").split(/,(?:\s)?/g).map(i=>i.trim()),
      "titleBlacklist": GM_config.get("titleBlacklist").split(/,(?:\s)?/g).map(i=>i.trim()),
      "tagBlacklist": GM_config.get("tagBlacklist").split(/,(?:\s)?/g).map(i=>i.trim()),
      "tagWhitelist": GM_config.get("tagWhitelist").split(/,(?:\s)?/g).map(i=>i.trim()),
      "summaryBlacklist": GM_config.get("summaryBlacklist").split(/,(?:\s)?/g).map(i=>i.trim())
    };
    var workContainer = document.querySelector("#main.works-show") || document.querySelector("#main.chapters-show");
    var blocked = 0;
    var total = 0;

    if (debugMode) {
      console.groupCollapsed("AO3 BLOCKER");

      if (!config) {
        console.warn("Exiting due to missing config.");
        return;
      }
    }

    addMenu();

    addStyle();

    Array.from(document.querySelectorAll("li.blurb")).forEach(function (blurb) {
      var blockables = selectFromBlurb(blurb);
      var reason = getBlockReason(blockables, config);

      total++;

      if (reason) {
        blockWork(blurb, reason, config);
        blocked++;

        if (debugMode) {
          console.groupCollapsed("- blocked " + blurb.id);
          console.log(blurb, reason);
          console.groupEnd();
        }
      } else if (debugMode) {
        console.groupCollapsed("  skipped " + blurb.id);
        console.log(blurb);
        console.groupEnd();
      }
    });

    if (config.alertOnVisit && workContainer && document.referrer.indexOf("//archiveofourown.org") === -1) {

      var blockables = selectFromWork(workContainer);
      var reason = getBlockReason(blockables, config);

      if (reason) {
        blocked++;
        blockWork(workContainer, reason, config);
      }
    }

    if (debugMode) {
      console.log("Blocked " + blocked + " out of " + total + " works");
      console.groupEnd();
    }
  }, 10);

}());