Greasy Fork

来自缓存

Greasy Fork is available in English.

VNDB 跳转

VNDB跳转其他网站(自用)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         VNDB 跳转
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @license      MIT
// @description  VNDB跳转其他网站(自用)
// @author       高龄啊
// @match        https://vndb.org/v*
// @match        https://seiya-saiga.com/game/kouryaku.html?search=*
// @match        https://bbs.kfpromax.com/index.php?search=*
// @icon         https://www.vndb.org/favicon.ico
// @grant        GM_xmlhttpRequest
// @connect      erovoice.us
// @connect      anime-sharing.com
// @connect      asmrconnecting.xyz
// @connect      sunshineboy.top
// @connect      bangumi.tv
// @connect      2dfan.com
// @connect      e-hentai.org
// @connect      bbs.kfpromax.com
// ==/UserScript==

((css) => {
    if (typeof GM_addStyle == "function") {
        GM_addStyle(css);
        return;
    }

    const styleElement = document.createElement("style");
    styleElement.textContent = css;
    document.head.append(styleElement);
})(`
    .rdl-app {
        position: absolute;
        top: 250px; /* 上侧距离 */
        right: 30px; /* 右侧距离 */
        width: 110px; /* 宽度 */
        background-color: #000000; /* 背景色 */
        border-radius: 8px;
        padding: 10px; /* 内边距 */
        z-index: 1000;
    }

    .rdl-list {
        display: flex;
        flex-direction: column;
        gap: 10px;
    }

    .rdl-button {
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 8px 12px;
        border-radius: 4px;
        font-size: 14px;
        font-weight: 500;
        color: #fff;
        background-color: #000000; /* 按钮背景色 */
        border: 1px solid #409eff; /* 边框颜色 */
        cursor: pointer;
        text-decoration: none;
        transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
    }

    .rdl-button:visited {
        color: #3377AA; /* 访问过的链接颜色 */
    }

    .rdl-button:hover {
        background-color: #66b1ff;
        border-color: #66b1ff;
    }

    .rdl-button_green {
        background-color: #67c23a;
        border-color: #67c23a;
    }

    .rdl-button_green:hover {
        background-color: #85ce61;
        border-color: #85ce61;
    }

    .rdl-button_red {
        background-color: #f56c6c;
        border-color: #f56c6c;
    }

    .rdl-button_red:hover {
        background-color: #f89898;
        border-color: #f89898;
    }
`);

(function () {
    'use strict';

    initView();
    unlockTitleCopy();

    function initView() {
        const infoTable = document.querySelector('.vndetails');
        if (!infoTable) return;

        const rdlApp = createElement("div", null, "rdl-app");
        const rdlList = createElement("div", null, "rdl-list");

        const siteItems = [
            { "title": "SunshineBoy", "onlyAsmr": false },
            { "title": "animeShare", "onlyAsmr": false },
            { "title": "bangumi", "onlyAsmr": false },
            { "title": "绯月", "onlyAsmr": false },
            { "title": "青桔网", "onlyAsmr": false },
            { "title": "失落小站", "onlyAsmr": false },
            { "title": "稻荷", "onlyAsmr": false },
            { "title": "2DFan", "onlyAsmr": false },
            { "title": "誠也の部屋", "onlyAsmr": true },
            { "title": "ggbases", "onlyAsmr": false },
            { "title": "nyaa", "onlyAsmr": false },
            { "title": "F95", "onlyAsmr": false },
            { "title": "eHentai", "onlyAsmr": false },
            { "title": "DMM", "onlyAsmr": false }
        ];

        siteItems.forEach(item => {
            const element = createElement("a", item.title, "rdl-button");
            element.target = "_blank";
            rdlList.appendChild(element);
        });

        rdlApp.appendChild(rdlList);
        infoTable.parentNode.insertBefore(rdlApp, infoTable.nextSibling);

        checkExits();
    }

    function createElement(tag, text = null, className = null) {
        const element = document.createElement(tag);
        if (text) element.textContent = text;
        if (className) element.className = className;
        return element;
    }

    function findItem(t) {
        const list = document.getElementsByClassName("rdl-list")[0];
        return Array.from(list.children).find(item => item.textContent === t);
    }

    function getSearchParam(paramName) {
        try {
            const url = new URL(window.location.href);
            const param = url.searchParams.get(paramName);
            return param ? decodeURIComponent(param) : null;
        } catch {
            const regex = new RegExp(`[?&]${paramName}=([^&]+)`);
            const match = window.location.href.match(regex);
            return match ? decodeURIComponent(match[1]) : null;
        }
    }

    function normalizeText(text) {
        return text.trim()
                  .replace(/\s+/g, ' ')
                  .replace(/[!-~]/g, c => String.fromCharCode(c.charCodeAt(0) - 0xFEE0))
                  .toLowerCase();
    }

    function searchAndHighlight(node, keyword) {
        if (!keyword) return;

        const normalizedKeyword = normalizeText(keyword);
        let isFirstMatch = true;

        function processNode(node) {
            if (node.nodeType === Node.TEXT_NODE) {
                const text = node.textContent;
                const normalizedText = normalizeText(text);

                if (normalizedText.includes(normalizedKeyword)) {
                    const span = document.createElement('span');
                    span.textContent = text;
                    span.className = 'keyword-highlight';
                    span.style.backgroundColor = 'yellow';

                    node.replaceWith(span);

                    if (isFirstMatch) {
                        span.scrollIntoView({ behavior: 'smooth', block: 'center' });
                        isFirstMatch = false;
                    }

                    return true;
                }
            } else if (node.nodeType === Node.ELEMENT_NODE) {
                const style = window.getComputedStyle(node);
                if (style.display === 'none' || style.visibility === 'hidden') {
                    return false;
                }

                Array.from(node.childNodes).forEach(child => processNode(child));
            }
            return false;
        }

        processNode(node);
    }

    function init() {
        const keyword = getSearchParam('search');
        console.log('搜索关键词:', keyword);

        if (keyword) {
            if (document.body) {
                searchAndHighlight(document.body, keyword);
            } else {
                document.addEventListener('DOMContentLoaded', () =>
                    searchAndHighlight(document.body, keyword)
                );
            }

            const searchInput = document.querySelector('input[name="keyword"]');
            const searchButton = document.querySelector('input[name="submit"]');

            if (searchInput && searchButton) {
                searchInput.value = keyword;
                searchInput.dispatchEvent(new Event('input', { bubbles: true }));
                searchButton.click();

                console.log(`搜索已触发,关键词: "${keyword}",等待结果加载...`);

                const observer = new MutationObserver(() => {
                    const searchResults = document.querySelector('.search-results');
                    if (searchResults) {
                        console.log('搜索结果已加载:', searchResults);
                        observer.disconnect();
                    }
                });

                observer.observe(document.body, { childList: true, subtree: true });
            } else {
                console.error('未找到搜索框或搜索按钮元素');
            }
        }
    }

    init();

    async function checkExits() {
        const selectors = [
            "body > main > article:nth-child(1) > h2",
            "body > main > article:nth-child(1) > div.vndetails > table > tbody > tr:nth-child(1) > td:nth-child(2) > table > tbody > tr > td:nth-child(2) > span",
            "body > main > article:nth-child(1) > div.vndetails > table > tbody > tr:nth-child(1) > td > details > summary > table > tbody > tr > td:nth-child(2) > span",
            "body > main > article:nth-child(1) > h1" // 更多备选选择器
        ];

        let Titles = null;
        for (const selector of selectors) {
            Titles = document.querySelector(selector);
            if (Titles) break;
        }

        if (Titles) {
            console.log("Titles found:", Titles.innerText);
        } else {
            console.error("No matching element found!");
        }

        setItemLink("绯月", `https://bbs.kfpromax.com/index.php?search=${Titles.innerText}`);
        setItemLink("青桔网", `https://spare.qingju.org/?s=${Titles.innerText}`);
        setItemLink("失落小站", `https://www.shinnku.com/?search=${Titles.innerText}`);
        setItemLink("eHentai", `https://e-hentai.org/?f_search=${Titles.innerText}`);
        setItemLink("DMM", `https://www.dmm.co.jp/search/=/searchstr=${encodeURIComponent(Titles.innerText)}/`);
        setItemLink("animeShare", `https://www.anime-sharing.com/search/3528560/?q=${Titles.innerText}&o=relevance`);
        setItemLink("bangumi", `https://bangumi.tv/subject_search/${Titles.innerText}?cat=4`);
        setItemLink("2DFan", `https://2dfan.com/subjects/search?keyword=${Titles.innerText}`);
        setItemLink("ggbases", `https://www.ggbases.com/search.so?title=${Titles.innerText}`);
        setItemLink("nyaa", `https://nyaa.si/?f=0&c=0_0&q=${Titles.innerText}`);
        setItemLink("F95", `https://f95zone.to/search/040867454/?q=${Titles.innerText}&o=relevance`);
        setItemLink("誠也の部屋", `https://seiya-saiga.com/game/kouryaku.html?search=${Titles.innerText}`);
        setItemLink("稻荷", `https://amoebi.com`);

        const checks = [
            { site: "SunshineBoy", check: SunshineBoyCheck },
            { site: "animeShare", check: animeShareCheck },
        ];

        for (const { site, check } of checks) {
            await checkSite(site, Titles.innerText, check);
        }
    }

    async function checkSite(siteName, Titles, checkFunction) {
        const item = findItem(siteName);
        try {
            await checkFunction(Titles, item);
        } catch (error) {
            console.error(error);
            item.className = "rdl-button rdl-button_red";
        }
    }

    async function SunshineBoyCheck(Titles, item) {
        const url = "https://sunshineboy.top/api/fs/search";
        const postData = JSON.stringify({
            "parent": "/",
            "keywords": Titles,
            "page": 1,
            "scope": 0,
            "per_page": 100,
            "password": ""
        });
        const response = await fetchDataWithPost(url, postData);
        const data = JSON.parse(response.responseText).data;
        const ref = data.content.reduce((acc, contentKey) => {
            if (contentKey.is_dir && contentKey.size < acc.size) {
                const parent = contentKey.parent.replaceAll("/Guest", "");
                return { size: contentKey.size, href: `https://sunshineboy.top${parent}/${contentKey.name}` };
            }
            return acc;
        }, { size: Number.MAX_SAFE_INTEGER, href: "" }).href;
        handleCheckResult(data.total, item, ref, "https://sunshineboy.top");
    }

    async function animeShareCheck(Titles, item) {
        const url = `https://www.anime-sharing.com/search/3528560/?q=${Titles}&o=relevance`;
        const response = await fetchData(url);
        const parser = new DOMParser();
        const doc = parser.parseFromString(response.responseText, 'text/html');
        const voiceIconNodeLength = doc.getElementsByClassName("label label--primary").length;
        handleCheckResult(voiceIconNodeLength > 0, item, url, url);
    }

    function handleCheckResult(condition, item, successLink, failLink) {
        if (condition > 0) {
            item.className = "rdl-button rdl-button_green";
            item.href = successLink;
        } else {
            item.className = "rdl-button rdl-button_red";
            item.href = failLink;
        }
    }

    function fetchData(url) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                onload: (response) => resolve(response),
                onerror: (error) => reject(error),
            });
        });
    }

    async function fetchDataWithPost(url, postData) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "POST",
                url: url,
                data: postData,
                headers: {
                    "Content-Type": "application/json"
                },
                onload: resolve,
                onerror: reject
            });
        });
    }

    function unlockTitleCopy() {
        if (!window.location.href.includes("vndb.org")) return;

        const titleElement1 = document.querySelector("#top_wrapper > ul > li:last-child");
        const titleElement2 = document.querySelector("body > main > article:nth-child(1) > div.vndetails > table > tbody > tr:nth-child(1) > td > details > summary > table > tbody > tr > td:nth-child(2) > span");

        if (titleElement1) {
            titleElement1.style = "user-select:auto";
        }
        if (titleElement2) {
            titleElement2.style = "user-select:auto";
        }
    }

    function setItemLink(Titles, url) {
        const item = findItem(Titles);
        if (item) {
            item.Titles = "rdl-button";
            item.href = url;
        }
    }
})();