Greasy Fork

Greasy Fork is available in English.

Tong Li Online: Blurs R-rated Covers

Blurs the covers of R-rated books on Tong Li Online.

当前为 2024-05-12 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name               Tong Li Online: Blurs R-rated Covers
// @name:zh-TW         東立漫遊網:模糊限制級封面
// @description        Blurs the covers of R-rated books on Tong Li Online.
// @description:zh-TW  模糊東立漫遊網限制級書本的封面。
// @icon               https://icons.duckduckgo.com/ip3/www.tongli.com.tw.ico
// @author             Jason Kwok
// @namespace          https://jasonhk.dev/
// @version            1.4.0
// @license            MIT
// @match              https://www.tongli.com.tw/
// @match              https://www.tongli.com.tw/index.aspx
// @match              https://www.tongli.com.tw/BooksLst_Srh.aspx
// @match              https://www.tongli.com.tw/GbooksList.aspx
// @match              https://www.tongli.com.tw/GbooksList.aspx?*
// @match              https://www.tongli.com.tw/search3.aspx
// @match              https://www.tongli.com.tw/Search3.aspx
// @match              https://www.tongli.com.tw/BooksList.aspx
// @match              https://www.tongli.com.tw/BooksList.aspx?*
// @match              https://www.tongli.com.tw/webpagebooks.aspx
// @match              https://www.tongli.com.tw/webpagebooks.aspx?*
// @match              https://www.tongli.com.tw/WebPageBooks.aspx
// @match              https://www.tongli.com.tw/WebPageBooks.aspx?*
// @match              https://www.tongli.com.tw/ThemeBL.aspx
// @match              https://www.tongli.com.tw/ThemeBLBooks.aspx
// @match              https://www.tongli.com.tw/ThemeBLBooks.aspx?*
// @match              https://www.tongli.com.tw/ThemeBeautiful.aspx
// @match              https://www.tongli.com.tw/ThemeBeautiful.aspx?*
// @match              https://www.tongli.com.tw/ThemeGL.aspx
// @match              https://www.tongli.com.tw/ThemeGL.aspx?*
// @match              https://www.tongli.com.tw/NovelIndex/
// @match              https://www.tongli.com.tw/NovelDetail.aspx
// @match              https://www.tongli.com.tw/NovelDetail.aspx?*
// @match              https://www.tongli.com.tw/BooksDetail.aspx
// @match              https://www.tongli.com.tw/BooksDetail.aspx?*
// @run-at             document-end
// @grant              none
// @require            https://update.greasyfork.icu/scripts/483122/1304475/style-shims.js
// @require            https://unpkg.com/[email protected]/uuid-random.min.js
// @require            https://update.greasyfork.icu/scripts/482311/1297431/queue.js
// @supportURL         http://greasyfork.icu/scripts/482312/feedback
// ==/UserScript==

GM.addStyle(`
    .b_package.nsfw img, .owl-item.nsfw > .item img, .comicBox li.nsfw img, .sdBook :is(.hot, .tabbook).nsfw input
    {
        filter: blur(var(--nsfw-blur-radius, 7.5px));
        transition: filter var(--nsfw-transition-duration, 0.3s);
    }

    .b_package.nsfw:hover img, .b_package.nsfw:focus-within img,
    .owl-item.nsfw > .item:hover img, .owl-item.nsfw > .item:focus-within img,
    .comicBox li.nsfw:hover img, .comicBox li.nsfw:focus-within img,
    .sdBook :is(.hot, .tabbook).nsfw:hover input, .sdBook :is(.hot, .tabbook).nsfw:focus-within input
    {
        filter: blur(0px);
    }
`);

const EVENT_KEY = uuid();

async function isNsfw(url, { signal } = {})
{
    try
    {
        const response = await fetch(url, { signal, credentials: "omit" });
        if (response.status === 200)
        {
            const html = await response.text();
            const parser = new DOMParser();
            const page = parser.parseFromString(html, "text/html");

            if (page.getElementById("ContentPlaceHolder1_GradeTxt")?.innerText === "限制級") { return true; }
        }
    }
    catch (e)
    {
        if (typeof e === "string")
        {
            console.debug(e);
        }
        else if ((e instanceof DOMException) && (e.name === "AbortError"))
        {
            console.debug(signal.reason);
        }
        else
        {
            console.error(e);
        }
    }

    return false;
}

const queue = new Queue({ autostart: true, concurrency: 20 });
queue.addEventListener("error", (event) => console.error(event.detail.error));

const pathname = location.pathname.toLowerCase();
if ((pathname === "/bookslst_srh.aspx") || (pathname === "/gbookslist.aspx"))
{
    const serieses = document.querySelectorAll(".b_package");
    for (const series of serieses)
    {
        queue.push(async () =>
        {
            try
            {
                const response = await fetch(series.querySelector("a").href, { credentials: "omit" });
                if (response.status === 200)
                {
                    const html = await response.text();
                    const parser = new DOMParser();
                    const page = parser.parseFromString(html, "text/html");

                    const controller = new AbortController();
                    const signal = controller.signal;

                    const products = page.querySelectorAll(".b_package");
                    for (const product of products)
                    {
                        queue.push(async () =>
                        {
                            if (await isNsfw(product.querySelector("a").href, { signal }))
                            {
                                controller.abort(`Skipping other checks for the series “${series.querySelector(".pk_txt > em").innerText}” since a R-rated book in it was found.`);
                                series.classList.add("nsfw");
                            }
                        });
                    }
                }
            }
            catch (e)
            {
                console.error(e);
            }
        });
    }
}
else if (pathname === "/search3.aspx")
{
    const PageScript = ({ EVENT_KEY }) =>
    {
        const sandbox = document.createElement("iframe");
        sandbox.style.display = "none";
        document.body.appendChild(sandbox);

        sandbox.contentWindow.WebForm_PostBackOptions = WebForm_PostBackOptions;
        sandbox.contentWindow.WebForm_DoPostBackWithOptions = (options) => (new URL(options.actionUrl, location).href);

        const eval = sandbox.contentWindow.eval;
        sandbox.remove();

        window.addEventListener(`${EVENT_KEY}:getProductUrl`, ({ detail }) =>
        {
            const { RETURN_KEY, handler } = JSON.parse(detail);
            window.dispatchEvent(new CustomEvent(RETURN_KEY, { detail: eval(handler) }));
        });
    };

    const scriptWrapper = document.createElement("div");
    scriptWrapper.style.display = "none";
    const shadowRoot = scriptWrapper.attachShadow({ mode: "closed" });
    const script = document.createElement("script");
    script.textContent = `(${PageScript})(${JSON.stringify({ EVENT_KEY })}); //# sourceURL=userscript://page/${encodeURI(GM.info.script.name)}/Bestsellers%20Page.js`;
    shadowRoot.append(script);
    (document.body ?? document.head ?? document.documentElement).append(scriptWrapper);

    const products = document.querySelectorAll(".sdBook :is(.hot, .tabbook)");
    for (const product of products)
    {
        queue.push(async () =>
        {
            const url = await getProductUrl(product.querySelector("input").getAttribute("onclick"));
            if (await isNsfw(url))
            {
                product.classList.add("nsfw");
            }
        });
    }

    function getProductUrl(handler)
    {
        return new Promise((resolve) =>
        {
            const RETURN_KEY = uuid();

            window.addEventListener(RETURN_KEY, ({ detail: url }) =>
            {
                resolve(url);
            }, { once: true });

            window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:getProductUrl`, { detail: JSON.stringify({ RETURN_KEY, handler }) }));
        });
    }
}
else
{
    const products = document.querySelectorAll(".b_package, .owl-item, .comicBox li");
    for (const product of products)
    {
        queue.push(async () =>
        {
            if (await isNsfw(product.querySelector("a").href))
            {
                product.classList.add("nsfw");
            }
        });
    }
}