AO3: [Wrangling] Show fandoms in Tag Search!

Show a tag's fandom, its synned/unsynned status, and what it's synned to in the tag search results!!

目前為 2022-06-18 提交的版本,檢視 最新版本

// ==UserScript==
// @name         AO3: [Wrangling] Show fandoms in Tag Search!
// @description  Show a tag's fandom, its synned/unsynned status, and what it's synned to in the tag search results!!
// @version      2.0.0

// @author       owlwinter
// @namespace    N/A
// @license      MIT license

// @match        *://*.archiveofourown.org/tags/search?*
// @grant        none
// ==/UserScript==


(function() {
    'use strict';

    //So things like getElementsByTagName get a nodelist, not an array
    //This lets us convert the nodelists into an actual array so we can use array functions on it
    //See https://stackoverflow.com/questions/5145032/whats-the-use-of-array-prototype-slice-callarray-0
    const array = a => Array.prototype.slice.call(a, 0)

    //Checks if tag is canonized
    const check_canon = function check_canon(xhr) {
        return xhr.responseXML.documentElement.querySelector("#tag_canonical")?.checked
    }

    //Returns array of a tag's fandoms
    //Does this by peeking at all the "fandoms to remove" on the tag's edit form and putting them into a list
    const check_fandoms = function check_fandoms(xhr) {
        const fandoms = []
        const checks = array(xhr.responseXML.documentElement.querySelectorAll("label > input[name='tag[associations_to_remove][]']")).filter(foo => foo.id.indexOf("parent_Fandom_associations_to_remove") != -1);
        for (const check of checks) {
            const name = check.parentElement.nextElementSibling.innerText
            fandoms.push(name)
        }
        return fandoms
    }

    //Returns true or false if a tag is synned or not
    const check_issynned = function check_issynned(xhr) {
        if (xhr.responseXML.documentElement.querySelector("#tag_syn_string") != null) {
            return xhr.responseXML.documentElement.querySelector("#tag_syn_string").value
        }
        return false
    }

    //Returns what a tag is synned to, or an empty string if its unsynned
    const check_synnedto = function check_synnedto(xhr) {
        if (xhr.responseXML.documentElement.querySelector("#tag_syn_string") != null) {
            return xhr.responseXML.documentElement.querySelector("#tag_syn_string").value
        }
        return ""
    }

    // called for each tag in the search results list
    // `url` is the url of the edit tag page
    // `a` is the link, whose color we will change based on whether the tag is a syn
    // `result` is the span with the "Loading..." in it where we will put the results of our request
    const get_fandoms = function get_fandoms(url, a, result) {
        const xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function xhr_onreadystatechange() {
            if (xhr.readyState == xhr.DONE ) {
                if (xhr.status == 200) {
                    // the "Loading..." text that we show while our script is running is in italics - so clear that
                    result.style.fontStyle = "";

                    // gets the info on the tag
                    const fandoms = check_fandoms(xhr)
                    const synned = check_issynned(xhr)
                    const canon = check_canon(xhr)

                    // figure out the text to display based on whether there were any fandoms or not
                    const text = fandoms.length == 0 ? "Unwrangled" : fandoms.join(", ");
                    result.innerText = " - " + text;

                    // if the tag is synned, leave it alone, otherwise make the tag's text blue
                    if (!synned & !canon) {
                        a.style.color = "#00C";
                    }

                    // hovering over a blue link defaults to a purple background that is just totally unreadable, this class
                    // makes the hover background color white instead
                    if (!synned) {
                        a.classList.toggle("not_synned")
                    }
                } else if (xhr.status == 429) {
                    // ~ao3jail
                    result.innerText = " - Rate limited. Sorry :("
                } else {
                    result.innerText = " - Unexpected error, check the console"
                    console.log(xhr)
                }
            }
        }
        xhr.open("GET", url)
        xhr.responseType = "document"
        xhr.send()
    }

    // called for each tag in the search results list
    // `url` is the url of the edit tag page
    // `a` is the link, whose color we will change based on whether the tag is a syn
    // `result` is the span with the "Loading..." in it where we will put the results of our request
    const get_synnedto = function get_synnedto(url, a, result) {
        const xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function xhr_onreadystatechange() {
            if (xhr.readyState == xhr.DONE ) {
                if (xhr.status == 200) {
                    // the "Loading..." text that we show while our script is running is in italics - so clear that
                    result.style.fontStyle = "";

                    // gets what the tag is synned to
                    const synned = check_issynned(xhr)
                    const synnedto = check_synnedto(xhr)
                    const canon = check_canon(xhr)

                    result.style.fontStyle = "italic";

                    if (canon) {
                        result.innerText = ""
                        return
                    }

                    //If tag is synned, set text to " → Syn", otherwise set text to empty string
                    result.innerText = synned ? " → " + synnedto : ""

                    // if the tag is synned, leave it alone, otherwise make the tag's text blue
                    if (!synned & !canon) {
                        a.style.color = "#00C";
                    }

                    // hovering over a blue link defaults to a purple background that is just totally unreadable, this class
                    // makes the hover background color white instead
                    if (!synned) {
                        a.classList.toggle("not_synned")
                    }
                } else if (xhr.status == 429) {
                    // ~ao3jail
                    result.innerText = " - Rate limited. Sorry :("
                } else {
                    result.innerText = " - Unexpected error, check the console"
                    console.log(xhr)
                }
            }
        }
        xhr.open("GET", url)
        xhr.responseType = "document"
        xhr.send()
    }

    const button = document.createElement("button")
    const button2 = document.createElement("button")
    const buttondiv = document.createElement("div")

    const do_thing = function do_thing(e) {
        e.preventDefault()
        // find each item in the tag search results list
        const search_results = document.querySelector("#main > ol.tag.index.group").getElementsByClassName("tag")
        for (const a of search_results) {
            const span = a.parentElement;
            // this is the span that will hold the result when the request finishes
            const loading = document.createElement("span");
            //Before our request finishes, shows a loading text so user knows something is happening
            loading.innerText = " - Loading..."
            loading.style.fontStyle = "italic"
            span.appendChild(loading);
            // trigger xhr (asynchronous)
            get_fandoms(a.href + "/edit", a, loading);
        }
        // add the unsynned color explanation text to the top of the page, replacing the button
        const div = document.createElement("div")
        div.style.fontSize = "0.875rem"
        div.style.fontStyle = "italic"
        div.appendChild(document.createTextNode(""))
        const span = document.createElement("span")
        span.style.color = "#00C"
        span.innerText = "Blue"
        div.appendChild(span)
        div.appendChild(document.createTextNode(" means tag is not synned"))
        div.style.marginTop = "10px"
        //Removes both buttons after either is clicked
        button.parentElement.parentElement.appendChild(div)
        button.parentElement.parentElement.removeChild(buttondiv)
    }

    const do_thing2 = function do_thing2(e) {
        e.preventDefault()
        // find each item in the tag search results list
        const search_results = document.querySelector("#main > ol.tag.index.group").getElementsByClassName("tag")
        for (const a of search_results) {
            const span = a.parentElement;
            // this is the span that will hold the result when the request finishes
            const loading = document.createElement("span");
            //Before our request finishes, shows a loading text so user knows something is happening
            loading.innerText = " - Loading..."
            loading.style.fontStyle = "italic"
            span.appendChild(loading);
            // trigger xhr (asynchronous)
            get_synnedto(a.href + "/edit", a, loading);
        }
        // add the unsynned color explanation text to the top of the page, replacing the button
        const div = document.createElement("div")
        div.style.fontSize = "0.875rem"
        div.style.fontStyle = "italic"
        div.appendChild(document.createTextNode(""))
        const span = document.createElement("span")
        span.style.color = "#00C"
        span.innerText = "Blue"
        div.appendChild(span)
        div.appendChild(document.createTextNode(" means tag is not synned"))
        div.style.marginTop = "10px"
        //Removes both buttons after either is clicked
        button2.parentElement.parentElement.appendChild(div)
        button2.parentElement.parentElement.removeChild(buttondiv)
    }

    // Adds the load fandom button - since this can get someone rate limited,
    // we definitely don't want to have it happen automatically
    button.innerText = "Load fandoms"
    button.addEventListener("click", do_thing)
    button.style.display = "inline"
    button.style.fontSize = "0.627rem"
    button.style.marginTop = "10px"

    //Adds the load synned to button
    button2.innerText = "See synned to"
    button2.addEventListener("click", do_thing2)
    button2.style.display = "inline"
    button2.style.fontSize = "0.627rem"
    button2.style.marginTop = "10px"
    button2.style.marginLeft = "5px"

    buttondiv.append(button)
    buttondiv.append(button2)

    document.querySelector("#main > h3").append(buttondiv)

    // removes the color background color of the not synned tags are when the user's cursor hovers over them
    // trust me, keeping the same magenta but with the blue text was atrocious
    // comment out the next three lines out at your own risk
    // ......you really don't want to
    // .........don't say i didn't warn you....,
    const style = document.createElement("style")
    style.innerHTML = ".not_synned:hover { background-color: white !important; }"
    document.head.appendChild(style)
})();

QingJ © 2025

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