AO3: [Wrangling] Search my canonicals for illegal characters

automatically runs a fandom-specific tag search over all your assigned fandoms, to find any canonicals with 'illegal' characters such as curly quotes or Chinese pipes

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AO3: [Wrangling] Search my canonicals for illegal characters
// @namespace    https://greasyfork.org/en/users/906106-escctrl
// @version      3.0
// @description  automatically runs a fandom-specific tag search over all your assigned fandoms, to find any canonicals with 'illegal' characters such as curly quotes or Chinese pipes
// @author       escctrl
// @match        https://archiveofourown.org/tags/search
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const COPY_SEARCH_STRING = false;

    const DEBUG = false;

    var found = 0; // counting found results

    var node_ul = document.getElementById('wranglerbuttons') || document.querySelector('#main ul.navigation.actions');
    const node_li = document.createElement('li');

    node_li.id = 'checkillegal';
    node_li.className = 'reindex';
    node_li.innerHTML = "<a href='#'>Check Illegal Characters</a>";
    if (COPY_SEARCH_STRING) node_li.addEventListener("click", copySearchString);
    else node_li.addEventListener("click", startBackgroundCheck);
    node_ul.appendChild(node_li);

    function copySearchString() {
        document.getElementById('tag_search_name').value = "*’* OR *‘* OR *“* OR *”* OR *|* OR *–* OR *—* OR *―* OR *(* OR *)* OR *\\'\\'*";
        var tagtype = document.getElementById('tag_search_status');
        if (tagtype !== null) tagtype.value = 'T'; // if the Smaller Tag Search script is enabled
        else document.getElementById('tag_search_canonical_t').checked = true; // for the plain New Tag Search form
    }

    function startBackgroundCheck() {
        // who am I logged in as?
        const user = document.querySelector('#greeting ul.user.navigation li.dropdown>a.dropdown-toggle[href*="/users/"]').href.match(/\/([A-Za-z0-9_]+)\/?$/i)[1];
        if (DEBUG) console.log("Illegal Characters: Logged in as user "+user);

        window.ao3jail = false;

        // get a list of all my wrangled fandoms
        pageload('https://archiveofourown.org/tag_wranglers/'+user, 'fandoms');
    }

    function retrieveFandomList(me) {
        // find all the assigned fandoms in the table (URL and name)
        const fandomlist = me.querySelectorAll('table tbody tr th a');

        // add a tracker for how many open XHR there will be
        window.openXHR = fandomlist.length;
        if (DEBUG) console.log("Illegal Characters: There are "+window.openXHR+" fandoms assigned, to be checked");
        document.getElementById('checkillegal').firstChild.innerText = "Checking " + window.openXHR + " fandoms";

        fandomlist.forEach( (f, i) => {
            var url = 'https://archiveofourown.org/tags/search?tag_search%5Bname%5D=*%E2%80%99*+OR+*%E2%80%98*+OR+*%E2%80%9C*+OR+*%E2%80%9D*+OR+*%EF%BD%9C*+OR+*%E2%80%93*+OR+*%E2%80%94*+OR+*%E2%80%95*+OR+*%EF%BC%88*+OR+*%EF%BC%89*+OR+*%5C%27%5C%27*&tag_search%5Btype%5D=&tag_search%5Bcanonical%5D=T&tag_search%5Bsort_column%5D=name&tag_search%5Bsort_direction%5D=asc&commit=Search+Tags&tag_search%5Bfandoms%5D=';
            url += encodeURIComponent(f.innerText);

            if (DEBUG) console.log("Illegal Characters: Fandom "+f.innerText+" will be checked in "+3000*i+" second");
            if (DEBUG) url = 'https://archiveofourown.org/tags/search?tag_search%5Bname%5D=*POV*&tag_search%5Btype%5D=&tag_search%5Bcanonical%5D=T&tag_search%5Bsort_column%5D=name&tag_search%5Bsort_direction%5D=asc&commit=Search+Tags&tag_search%5Bfandoms%5D='+ encodeURIComponent(f.innerText);

            // collect the search results for specifically those fandoms
            setTimeout(function() {
                pageload(url, 'search');
            }, 3000*i);
        });
    }

    function retrieveSearchResults(me) {
        window.openXHR--;
        // avoids updating the button by a slower (working) response after a page load was already jailed
        if (!window.ao3jail) document.getElementById('checkillegal').firstChild.innerText = "Checking " + window.openXHR + " fandoms";

        const results = me.querySelectorAll('ol.tag.index li span');
        const fandom = me.querySelector('#tag_search_fandoms').value;

        if (DEBUG) console.log("Illegal Characters: Fandom "+fandom+" has "+results.length+" tags with illegal characters");
        if (DEBUG) console.log("Illegal Characters: There are "+window.openXHR+" fandoms remaining to be checked");

        // adds the found nodelist to the object
        var printtext = "";
        if (results.length > 0) {
            printtext = '<h4 class="heading">'+fandom+'</h4><ol class="tag index group">';
            found = found + results.length;
            for (let n of results.values()) { printtext += '<li>'+n.outerHTML+'</li>'; }
            printtext += '</ol>';

            const node_parent = document.querySelector('#main');
            node_parent.innerHTML += printtext; // appending at the bottom of the list
        }

        if (window.openXHR == 0) {
            if (DEBUG) console.log("Illegal Characters: Last fandom was checked, tallying up the result tags");
            // this was the last open XHR so we can print the total number found!
            printTotals();
        }
    }

    function printTotals() {
        const heading = document.createElement('h3');
        heading.className = "heading"
        heading.innerText = found+' Found';
        const node_parent = document.querySelector('#new_tag_search');
        node_parent.insertAdjacentElement('afterend', heading);
        if (DEBUG) console.log("Illegal Characters: Script completed.");
        document.getElementById('checkillegal').firstChild.innerText = "Check for illegal characters finished";
    }

    function saySorry(me) {
        // this does not count down the checked fandoms so button can never show as "finished" when there were errors

        // user output only if this is the fist page that failed
        if (window.ao3jail == false) {
            // check if we received a retry-after value in the response
            if (DEBUG) console.log("Illegal Characters: 429 retry-after " + me.getResponseHeader('Retry-After'));
            var timeout = parseInt(me.getResponseHeader('Retry-After') || 0);
            timeout = (timeout > 0) ? ' You can try again in '+ Math.ceil(timeout / 60) +' minutes.' : '';
            document.querySelector('#new_tag_search').outerHTML += "<h3 class='heading'>Sorry, the script stopped because it ran into the "+me.statusText+" error."+ timeout +"</h3>";
            document.getElementById('checkillegal').firstChild.innerText = "Check stopped: error";
        }
        window.ao3jail = true;
        // console output for every page that didn't load properly
        console.log("Illegal Characters: The background page load ran into an issue: "+me.status+" "+me.statusText);
    }

    function pageload(url, what) {
        if (DEBUG) console.log("Illegal Characters: Loading page "+url);
        if (window.ao3jail) {
            if (DEBUG) console.log("Illegal Characters: This page load was skipped due to previously encountered errors.");
            return false;
        }
        const xhr = new XMLHttpRequest();
        xhr.onload = () => {
            if (xhr.status==200 && xhr.response != "") {
                if (what == 'fandoms') retrieveFandomList(xhr.response);
                else retrieveSearchResults(xhr.response);
            }
            else saySorry(xhr);
        };
        xhr.onerror = () => { saySorry(xhr); };
        xhr.open("GET", url);
        xhr.responseType = "document";
        xhr.send();
    }

})();