// ==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 // @downloadURL none // ==/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 = $("
").html("AO3 Blocker"); headerMenu.find("li.search").before(blockerMenu); var dropMenu = $(" "); blockerMenu.append(dropMenu); var reasonButton = $("").html(`${GM_config.get("showReasons") ? "Hide" : "Show"} Block Reason`); reasonButton.on("click", () => { if (GM_config.get("showReasons")) { GM_config.set("showReasons", false); } else { GM_config.set("showReasons", true); } GM_config.save(); reasonButton.html(`${GM_config.get("showReasons") ? "Hide" : "Show"} Block Reason`); }); dropMenu.append(reasonButton); var placeholderButton = $("").html(`${GM_config.get("showPlaceholders") ? "Hide" : "Show"} Work Placeholder`); placeholderButton.on("click", () => { if (GM_config.get("showPlaceholders")) { GM_config.set("showPlaceholders", false); } else { GM_config.set("showPlaceholders", true); } GM_config.save(); placeholderButton.html(`${GM_config.get("showPlaceholders") ? "Hide" : "Show"} Work Placeholder`); }); dropMenu.append(placeholderButton); var alertButton = $("").html(`${GM_config.get("alertOnVisit") ? "Don't Show" : "Show"} Blocked Work Alerts`); alertButton.on("click", () => { if (GM_config.get("alertOnVisit")) { GM_config.set("alertOnVisit", false); } else { GM_config.set("alertOnVisit", true); } GM_config.save(); alertButton.html(`${GM_config.get("alertOnVisit") ? "Don't Show" : "Show"} Blocked Work Alerts`); }); dropMenu.append(alertButton); var settingsButton = $("").html("All Settings"); settingsButton.on("click", () => {GM_config.open();}); dropMenu.append(settingsButton); } function addStyle() { var style = document.createElement("style"); var 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 " + tag + ""; } else if (author) { text = "authors include " + author + ""; } else if (title) { text = "title is " + title + ""; } else if (summary) { text = "summary includes " + summary + ""; } 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); }());