Kemono助手

提供更好的Kemono使用体验

ของเมื่อวันที่ 26-11-2023 ดู เวอร์ชันล่าสุด

// ==UserScript==
// @name         Kemono助手
// @version      1.1.5
// @description  提供更好的Kemono使用体验
// @author       ZIDOUZI
// @match        https://*.kemono.party/*
// @match        https://*.kemono.su/*
// @icon         https://kemono.su/static/favicon.ico
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @namespace https://greasyfork.org/users/448292
// ==/UserScript==

(async function () {

    const language = navigator.language || navigator.userLanguage;

    let vimMode = GM_getValue('vimMode', false);

    let vimModeId = GM_registerMenuCommand(`[${vimMode ? "✔" : "✖"}]导航键使用vim模式`, vimMode_callback);

    function vimMode_callback() {
        GM_unregisterMenuCommand(vimModeId);
        vimMode = !vimMode;
        GM_setValue('vimMode', vimMode);
        vimModeId = GM_registerMenuCommand(`[${vimMode ? "✔" : "✖"}]导航键使用vim模式`, vimMode_callback);
    }

    const postContent = document.querySelector('.post__content')
    if (postContent) {
        replaceAsync(postContent.innerHTML, /(?<!a href="|<a [^>]+">)(https?:\/\/[^\s<]+)/g, async function (match) {
            let [service, id, post] = await getKemonoUrl(match);
            if (service === null) return `<a href="${match}" target="_blank">${match}</a>`;
            id = id || window.location.href.match(/\/user\/(\d+)/)[1];
            const domain = window.location.href.match(/https:\/\/([^/]+)/)[1];
            const url = `${service}/user/${id}${post ? `/post/${post}` : ""}`;
            return `<a href="https://${domain}/${url}" target="_self">[已替换]${match}</a>`;
        }).then(function (result) {
            postContent.innerHTML = result
                .replace(/<a href="(https:\/\/[^\s<]+)">\1<\/a>\n?(#[^\s<]+)/g, "<a href=\"$1$2\">$1$2</a>")
                .replace(/<a href="(https:\/\/[^\s<]+)">(.*?)<\/a>\n?(#[^\s<]+)/g, "<a href=\"$1$3\">$2</a>")
        })
    }

    const prev = document.querySelector(".post__nav-link.prev");
    if (prev) {
        document.addEventListener("keydown", function (e) {
            if (e.key === "Right" || e.key === "ArrowRight" || vimMode && (e.key === "h" || e.key === "H")) {
                prev.click();
            }
        });
    }

    const next = document.querySelector(".post__nav-link.next");
    if (next) {
        document.addEventListener("keydown", function (e) {
            if (e.key === "Left" || e.key === "ArrowLeft" || vimMode && (e.key === "l" || e.key === "L")) {
                next.click();
            }
        });
    }

    if (language === 'zh-CN') {
        const dms = document.querySelector('.user-header__dms');

        if (dms) dms.innerHTML = '私信'

        const postFlag = document.querySelector('.post__flag');

        if (postFlag) {
            const text = postFlag.querySelector('span:last-child');
            if (text) text.textContent = '标记重新导入';
        }

    }

})();

async function replaceAsync(str, regex, asyncFn) {
    const promises = [];
    str.replace(regex, (match, ...args) => {
        const promise = asyncFn(match, ...args);
        promises.push(promise);
    });
    const data = await Promise.all(promises);
    return str.replace(regex, () => data.shift());
}

async function getKemonoUrl(url) {

    function getFanbox(creatorId) {
        // 同步执行promise
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: `https://api.fanbox.cc/creator.get?creatorId=${creatorId}`,
                headers: {
                    "Content-Type": "application/json",
                    "Accept": "application/json",
                    "Origin": "https://www.fanbox.cc",
                    "Referer": "https://www.fanbox.cc/"
                },
                onload: function (response) {
                    if (response.status === 200) {
                        resolve(JSON.parse(response.responseText))
                    } else {
                        reject({status: response.status, statusText: response.statusText})
                    }
                },
                onerror: function (response) {
                    reject({status: response.status, statusText: response.statusText})
                }
            })
        })
    }

    const pixiv_user = /https:\/\/www\.pixiv\.net\/users\/(\d+)/i;
    const fantia_user = /https:\/\/fantia\.jp\/fanclubs\/(\d+)(\/posts(\S+))?/i;
    const fanbox_user1 = /https:\/\/www\.fanbox\.cc\/@([^/]+)(?:\/posts\/(\d+))?/i;
    const fanbox_user2 = /https:\/\/(.+)\.fanbox\.cc(?:\/posts\/(\d+))?/i;
    const dlsite_user = /https:\/\/www.dlsite.com\/.+?\/profile\/=\/maker_id\/(RG\d+).html/i;
    const patreon_user1 = /https:\/\/www.patreon.com\/user\?u=(\d+)/i;
    const patreon_user2 = /https:\/\/www.patreon.com\/(\w+)/i;
    const patreon_post1 = /https:\/\/www.patreon.com\/posts\/(\d+)/i;
    const patreon_post2 = /https:\/\/www.patreon.com\/posts\/video-download-(\d+)/i;

    let service;
    let id;
    let post = null;

    if (pixiv_user.test(url)) {
        //pixiv artist
        service = "fanbox"
        id = url.match(pixiv_user)[1]
    } else if (fantia_user.test(url)) {
        //fantia
        service = "fantia"
        id = url.match(fantia_user)[1]
    } else if (dlsite_user.test(url)) {
        service = "dlsite"
        id = url.match(dlsite_user)[1]
    } else if (fanbox_user1.test(url) || fanbox_user2.test(url)) {
        //fanbox
        service = "fanbox"
        let matches = fanbox_user1.test(url) ? url.match(fanbox_user1) : url.match(fanbox_user2);
        id = (await getFanbox(matches[1])).body.user.userId.toString()
        post = matches[2]
    } else if (patreon_user1.test(url)) {
        // patreon
        service = "patreon"
        id = url.match(patreon_user1)[1]
    } else if (patreon_post1.test(url)) {
        // patreon post
        service = "patreon"
        post = url.match(patreon_post1)[1]
    } else if (patreon_post2.test(url)) {
        // patreon post
        service = "patreon"
        post = url.match(patreon_post2)[1]
    } else {
        return null;
    }

    return [service, id, post]
}