Greasy Fork

Greasy Fork is available in English.

Licensed (in English)

Show if manga is licensed in English.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Licensed (in English)
// @namespace    http://tampermonkey.net/
// @version      0.6
// @description  Show if manga is licensed in English.
// @author       Santeri Hetekivi
// @match        https://mangadex.org/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=mangadex.org
// @grant        GM.xmlHttpRequest
// @grant        window.onurlchange
// ==/UserScript==

(function() {
    'use strict';
    // ID for result element.
    const ID_RESULT = "licensedInEnglishResult"

    // Colors for different results.
    const COLOR_ERROR = "yellow"
    const COLOR_LICENSED = "red"
    const COLOR_UNLICENSED = "green"

    // Milliseconds to sleep between tries.
    const MS_SLEEP = 1000

    // URL starts.
    // Start for Manga Updates series url.
    const URL_START_MANGAUPDATES = "https://www.mangaupdates.com/series"
    // Start for MangaDex title url.
    const URL_START_MANGADEX = "https://mangadex.org/title/"

    // Querys for elements.
    // Parent to store result element in.
    const QUERY_PARENT = ".title"
    // Query for getting link for Manga Updates.
    const QUERY_LINK_MANGAUPDATES = "a[href^='"+URL_START_MANGAUPDATES+"']"
    // Query for getting elements that can contain TEXT_LICENSED_IN_ENGLISH text.
    const QUERY_MANGAUPDATES_LICENSED_IN_ENGLISH = '[data-cy="info-box-licensed-header"] b'
    const QUERY_TRACK = ".font-bold.mb-2"

    // Texts.
    const TEXT_LICENSED_IN_ENGLISH = "Licensed (in English)"
    const TEXT_LICENSED_POSITIVE = "Yes"
    const TEXT_LICENSED_NEGATIVE = "No"
    const TEXT_TRACK = "Track"
    const TEXT_CONSOLE_START = "Licensed (in English):"

    // Log debug data.
    const logDebug = (...args) => {
        console.debug(TEXT_CONSOLE_START, ...args)
    }

    // Update result element.
    const updateLicensedInEnglishText = (_elementResult, _text, _color = "yellow") => {
        _elementResult.style.color = _color
        _elementResult.textContent = _text;
    }

    // Select node list with given _query.
    const selectNodeList = (_query, _document = document) => {
        const nodeList = _document.querySelectorAll(_query);
        if(!(nodeList instanceof NodeList))
        {
            throw "nodeList for query '"+_query+"' was not instance of NodeList!"
        }
        return nodeList
    }

    // Select single element.
    const selectElement = (_query) => {
        // Get node list.
        const nodeList = selectNodeList(_query);

        // Check that got only single result.
        const lengthNodeList = nodeList.length
        if(lengthNodeList !== 1)
        {
            throw "Found "+lengthNodeList+" nodes for '"+_query+"'!"
        }

        // Check that single element is instance of Element.
        const element = nodeList[0]
        if(!(element instanceof Element))
        {
            throw "Element for query "+_query+" was not instance of Element!"
        }

        // Return gotten element.
        return element
    }

    // Get element for result.
    const getElementResult = () => {
        return document.getElementById(ID_RESULT)
    }

    // Output given _error to console.
    const consoleError = (_error) => {
        console.error(TEXT_CONSOLE_START, _error)
    }

    // Handle given _error.
    const handleError = (_url, _error) => {
        // Output to console.
        consoleError(_error)

        // Get element for result.
        const elementResult = getElementResult()

        // If element result found.
        if(elementResult instanceof Element)
        {
            // Update error to result element.
            updateLicensedInEnglishText(
                elementResult,
                _error,
                COLOR_ERROR
            )
        }

        // If url given
        if(_url)
        {
            // remove url.
            urlLicensedStatus(_url, false, true, null)
        }
    }

    // Check that given _element is instance of Element.
    const checkIsElement = (_element, _name) => {
        if(!(_element instanceof Element))
        {
            throw _name+" was not instance of Element!"
        }
        return _element
    }


    // Get element with TEXT_LICENSED_IN_ENGLISH text.
    const getElementWithText = (
        _query,
        _text,
        _document = document
    ) => {
        // Loop node list.
        const nodeList = selectNodeList(_query, _document)
        let element = null
        for (let i = 0; i < nodeList.length; i++)
        {
            // Get element.
            element = checkIsElement(nodeList[i], "nodeList["+i+"]")
            // If text matches
            if (element.textContent == _text)
            {
                // return element.
                return element
            }
        }

        // No element found.
        return null
    }


    // Get element with TEXT_LICENSED_IN_ENGLISH text.
    const getElementLicensedInEnglish = (_document) => {
        // Get element for text.
        const element = getElementWithText(
            QUERY_MANGAUPDATES_LICENSED_IN_ENGLISH,
            TEXT_LICENSED_IN_ENGLISH,
            _document
        )
        // No element found.
        if (element === null)
        {
            throw "No element with text '"+TEXT_LICENSED_IN_ENGLISH+"' found!"
        }

        // Return element.
        return element
    }

    // Get text answer for licensed in english.
    const getTextLicensedInEnglish = (_document) => {
        // Get text.
        const text = checkIsElement(
            checkIsElement(
                getElementLicensedInEnglish(_document).parentElement ?? null,
                "getElementLicensedInEnglish.parentElement"
            ).nextElementSibling ?? null,
            "getElementLicensedInEnglish.parentElement.nextElementSibling"
        ).textContent.trim()

        // Check gotten text.
        if(
            text !== TEXT_LICENSED_POSITIVE
            &&
            text !== TEXT_LICENSED_NEGATIVE
        )
        {
            throw "Invalid text: "+text
        }

        // Return text.
        return text
    }

    // Handle response.
    const handleResponse = (_url, _response) => {
        // Log debug message.
        logDebug("Handling response...")
        // Get answer for licensed in english.
        logDebug("Getting licensedInEnglish text...")
        const licensedInEnglish = getTextLicensedInEnglish(
            (
                new DOMParser()
            ).parseFromString(
                _response.responseText,
                'text/html'
            )
        )

        // Get licensed.
        logDebug("Getting licensed from text "+licensedInEnglish+"...")
        let licensed = null
        if(licensedInEnglish === TEXT_LICENSED_NEGATIVE)
        {
            licensed = false
        }
        else if(licensedInEnglish === TEXT_LICENSED_POSITIVE)
        {
            licensed = true
        }
        else
        {
            throw "Invalid licensedInEnglish: "+licensedInEnglish
        }

        // Call on success.
        onSuccess(
             checkIsElement(
                getElementResult(),
                "getElementResult"
            ),
            _url,
            licensed
        )
    }

    // Handle success.
    const onSuccess = (_elementResult, _url, _licensed) => {
        // Write debug log.
        logDebug("onSuccess _url: "+_url+" _licensed: "+(_licensed ? "YES" : "NO"))
        // Update result element
        updateLicensedInEnglishText(
            _elementResult,
            (
                _licensed ?
                    TEXT_LICENSED_POSITIVE :
                    TEXT_LICENSED_NEGATIVE
            ),
            (
                _licensed ?
                    COLOR_LICENSED :
                    COLOR_UNLICENSED
            )
        )
        // Set curren url licensed status.
        urlLicensedStatus(_url, true, false, _licensed)
    }

    // Get url for Manga Updates.
    const getURLMangaUpdates = () => {
        // Init urls array
        const urls = []

        // Loop node list.
        const nodeList = selectNodeList(QUERY_LINK_MANGAUPDATES)
        for (let i = 0; i < nodeList.length; i++)
        {
            // Get href
            let href = checkIsElement(nodeList[i], "nodeList["+i+"]").href ?? null
            // If got href as string
            if (typeof href === "string")
            {
                // Trim href.
                href = href.trim()
                // If href not already in urls
                if(!urls.includes(href))
                {
                    // add href to urls.
                    urls.push(href)
                }
            }
        }

        // Check that has only one url.
        const lengthUrls = urls.length
        // If no urls found.
        if(lengthUrls === 0)
        {
            // throw error.
            throw "Manga Updates link not found!"
        }
        // If different amount than 1 links found
        if(lengthUrls !== 1)
        {
            // throw error.
            throw "Found "+lengthUrls+" Manga Updates urls: "+urls.join(",")
        }

        // Return url.
        return urls[0]
    }

    // Get current url.
    const currUrl = () => {
        const href = window.location.href ?? null
        if(
            typeof href !== "string"
            ||
            href.trim() === ""
        )
        {
            throw "Invalid url: "+href
        }
        return href
    }

    // Handle url licensed status.
    const urlLicensedStatus = (_url, _add = false, _remove = false, _licensed = null) => {
        // If _remove given
        if(_remove)
        {
            // and _add given
            if(_add)
            {
                // throw error.
                throw "Both _add and _remove given!"
            }

            // and _licensed given
            if(_licensed !== null)
            {
                // throw error.
                throw "Both _licensed and _remove given!"
            }
        }

        // If
        if(
            // is not MangaDex url
            !_url.startsWith(URL_START_MANGADEX)
            &&
            // and is not removing operation
            !_remove
        )
        {
            throw "Gotten _url was not MangaDex url: "+_url
        }


        // Init running urls.
        if(typeof urlLicensedStatus.data !== "object")
        {
            urlLicensedStatus.data = {}
        }

        // Was included already.
        const includedAlready = _url in urlLicensedStatus.data

        // Init included now to included already.
        let includedNow = includedAlready

        // If adding, not included and not wanted to remove
        if(_add && !includedNow && !_remove)
        {
            // add
            urlLicensedStatus.data[_url] = _licensed
            // and set included.
            includedNow = true
        }

        // If removing and included and does not have status
        if(_remove && includedNow && urlLicensedStatus.data[_url] === null)
        {
            // remove
            delete urlLicensedStatus.data[_url]
            // and update included now.
            includedNow = false
        }

        // If licensed given
        if(_licensed !== null)
        {
            // update status
            urlLicensedStatus.data[_url] = _licensed
            // and add to be included now.
            includedNow = true
        }

        // Debug log currently running urls.
        logDebug("urlLicensedStatus.data", urlLicensedStatus.data)

        // Return status.
        return (
            includedAlready ?
                urlLicensedStatus.data[_url] :
                undefined
        )
    }

    // Get element for track.
    const getElementTrack = () => {
        return getElementWithText(
            QUERY_TRACK,
            TEXT_TRACK
        )
    }

    // Return if needing to try again later.
    const tryAgain = (_licensedStatus) => {
        // If already running try again later.
        if(_licensedStatus === null)
        {
            return true;
        }

        // Get parent elements.
        const parentElementsLength = selectNodeList(QUERY_PARENT).length

        // If
        if(
            // no parent values found
            parentElementsLength === 0
        )
        {
            return true;
        }
        // Throw error if found other than 0 or 1 elements.
        else if(parentElementsLength !== 1)
        {
            throw "Found "+parentElementsLength+" nodes for '"+QUERY_PARENT+"'!"
        }

        // Return that track element is defined.
        return (
            getElementTrack() === null
        )
    }


    // Run logic.
    const run = (_url) => {
        // Log debug message.
        logDebug("Running "+_url+"...")

        // Get licensed status.
        const licensedStatus = urlLicensedStatus(_url, false, null)

        // Init data.
        if(typeof run.data !== "object")
        {
            run.data = {}
        }

        // Init data for given _url.
        if(!(_url in run.data))
        {
            run.data[_url] = {
                counter: 1,
                timeout: null
            }
        }

        // Debug data.
        logDebug("run.data", run.data)
        logDebug("Counter ", run.data[_url].counter)

        // If has active timeout
        if(run.data[_url].timeout !== null)
        {
            // throw error.
            throw "Already has active timeout!"
        }

        // Make sure that document is instance of HTMLDocument.
        if(!(document instanceof HTMLDocument))
        {
            throw "document was not instance of HTMLDocument!"
        }

        // If trying again.
        if(tryAgain(licensedStatus))
        {
            // Log debug message.
            logDebug("Trying again!")

            // Stop if counter is over 10.
            if(10 < run.data[_url].counter)
            {
                throw "Trying again, but counter is: "+run.data[_url].counter
            }

            // Call after timeout.
            run.data[_url].timeout = setTimeout(
                () => {
                    try
                    {
                        // Zero timeout.
                        delete run.data[_url].timeout
                        run.data[_url].timeout = null;
                        // Increment counter.
                        ++run.data[_url].counter
                        // Call run again.
                        run(_url)
                    }
                    catch(_error)
                    {
                        handleError(_url, _error)
                    }
                },
                MS_SLEEP
            );

            // Throw error.
            throw "Trying again in "+MS_SLEEP+" ms!"
        }

        // Get result element.
        let elementResult = getElementResult(ID_RESULT)

        // If no result element found.
        if(!(elementResult instanceof Element))
        {
            // Create result element.
            logDebug("Adding result element...")
            elementResult = document.createElement("p")
            elementResult.id = ID_RESULT
            selectElement(QUERY_PARENT).appendChild(elementResult);
            logDebug("Added result element.")
        }

        // If already has licensed status
        if(typeof licensedStatus === "boolean")
        {
            // just update element.
            onSuccess(
                elementResult,
                _url,
                licensedStatus
            )
            // and return.
            return
        }

        // Update result to loading.
        updateLicensedInEnglishText(
            elementResult,
            "Loading...",
            COLOR_ERROR
        )

        // Get Manga Updates page.
        logDebug("Making GET request...")
        GM.xmlHttpRequest(
            {
                method: "GET",
                url: getURLMangaUpdates(),
                headers: {
                    "Accept": "text/html"
                },
                onload: function(_response) {
                    // Handle response.
                    logDebug("onload")
                    try
                    {
                        handleResponse(_url, _response)
                    }
                    catch(_error)
                    {
                        handleError(_url, _error)
                    }
                },
                onerror: (_response) => {
                    logDebug("onerror")
                    consoleError(_response)
                    handleError(_url, _response.error ?? "Unknown error!")
                },
                ontimeout: (_response) => {
                    logDebug("ontimeout")
                    consoleError(_response)
                    handleError(_url, _response.error ?? "Unknown timeout!")
                },
                onabort: (_response) => {
                    logDebug("onabort")
                    consoleError(_response)
                    handleError(_url, _response.error ?? "Unknown timeout!")
                }
            }
        );
    }

    // After page has fully loaded.
    window.addEventListener(
        'load',
        function()
        {
            // Log debug message.
            logDebug("Page loaded!")
            // Init url.
            let url = null
            try
            {
                // Check that window.onurlchange feature is supported
                // https://www.tampermonkey.net/documentation.php#api:window.onurlchange
                if (window.onurlchange !== null)
                {
                    throw "window.onurlchange feature is not supported!"
                }

                // Add url change event.
                window.addEventListener(
                    'urlchange',
                    (_info) => {
                        // Handle urlchange.
                        logDebug("urlchange")
                        const url = _info.url
                        try
                        {
                            run(url)
                        }
                        catch(_error)
                        {
                            handleError(url, _error)
                        }
                    }
                );

                // Start running.
                logDebug("Start running!")
                url = currUrl();
                run(url)
            }
            catch(_error)
            {
                handleError(url, _error)
            }
        },
        false
    );
})();