Kemono助手

提供更好的Kemono使用体验

2023/07/13のページです。最新版はこちら。

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         Kemono助手
// @version      1.1.0
// @description  提供更好的Kemono使用体验
// @author       ZIDOUZI
// @match        https://*.kemono.party/*
// @match        https://*.kemono.su/*
// @icon         https://kemono.su/static/favicon.ico
// @grant        GM_xmlhttpRequest
// @namespace https://greasyfork.org/users/448292
// ==/UserScript==

(async function () {
    
    const language = navigator.language || navigator.userLanguage;

    var postContent = document.querySelector('.post__content');

    if (postContent) {
        replaceAsync(postContent.innerHTML, /(?<!a href="|<a [^>]+">)(https?:\/\/[^\s<]+)/g, async function (match) {
            var url = await getKemonoUrl(match);
            return `<a href="${url || match}" target="${url ? "_self" : "_blank"}">${url ? "[已替换]" : ""}${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>")
        })
    }

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

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

    var dms = document.querySelector('.user-header__dms');

    if (language === 'zh-CN' && dms) {
        dms.innerHTML = '私信'
    }

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

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

    var postPublished = document.querySelector('.post__published');

    if (postPublished) {

        fetch('https://kemono.party/api' + window.location.pathname)
            .then(res => res.json())
            .then(res => {
            var edited = new Date(res[0].edited).getTime();

            var editedTime = document.createElement('time');
            editedTime.className = 'timestamp post__edited';
            editedTime.dateTime = edited;
            editedTime.textContent = new Date(edited).toLocaleString();

            postPublished.appendChild(editedTime);

            // insert explain text before edited time
            var editedExplain = document.createElement('span');
            editedExplain.className = 'post__edited-explain';
            editedExplain.textContent = language === 'zh-CN' ? '编辑于:' : 'Edited at:';

            postPublished.insertBefore(editedExplain, editedTime);

            // insert separator
            var separator = document.createElement('span');
            separator.className = 'post__separator';
            separator.textContent = ' ';

            postPublished.insertBefore(separator, editedExplain);

            // insert explain text at first
            var publishedExplain = document.createElement('span');
            publishedExplain.className = 'post__published-explain';
            publishedExplain.textContent = language === 'zh-CN' ? '发布于:' : 'Published at:';

            postPublished.insertBefore(publishedExplain, postPublished.firstChild);
        })
            .catch(err => console.log(err))
    }

})();

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;

    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
        post = matches[2]
    } else if (patreon_user1.test(url)) {
        // patreon
        service = "patreon"
        id = url.match(patreon_user1)[1]
    } else {
        return undefined;
    }

    return post == null ? `https://kemono.party/${service}/user/${id}` : `https://kemono.party/${service}/user/${id}/post/${post}`

}