番号数据库 + Everything HTML解析 (10.0.0.2:4321)

查询本地数据库与Everything网页(10.0.0.2:4321),显示ℹ️详情、🔍图片搜索、🧭本地文件结果(HTML解析,点击跳转)。数据库无结果时显示i/Y/N添加/备注框等功能。

// ==UserScript==
// @name         番号数据库 + Everything HTML解析 (10.0.0.2:4321)
// @namespace    http://tampermonkey.net/
// @version      3.3
// @description  查询本地数据库与Everything网页(10.0.0.2:4321),显示ℹ️详情、🔍图片搜索、🧭本地文件结果(HTML解析,点击跳转)。数据库无结果时显示i/Y/N添加/备注框等功能。
// @author       Your Name
// @match        https://www.javbus.com/*
// @match        https://thisav.com/*
// @match        https://south-plus.org/*
// @match        https://missav.ws/*
// @match        https://javten.com/*
// @match        https://www.javlibrary.com/*
// @match        https://3xplanet.com/*
// @match        https://njav.tv/*
// @match        https://123av.com/*
// @match        https://mypikpak.com/*
// @match        https://javxspot.com/*
// @match        https://javfree.me/*
// @match        https://sukebei.nyaa.si/*
// @grant        GM_xmlhttpRequest
// @connect      localhost
// @connect      10.0.0.2
// ==/UserScript==

(function () {
    'use strict';

    const EVERYTHING_BASE = "http://10.0.0.2:4321";

    const regex = /\b\d{7}\b/g;
    const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
    const processedNodes = new Set();

    while (walker.nextNode()) {
        const node = walker.currentNode;
        if (processedNodes.has(node)) continue;
        processedNodes.add(node);

        if (regex.test(node.nodeValue)) {
            const codes = node.nodeValue.match(regex);
            codes.forEach(code => queryDatabase(code, node));
        }
    }

    // ---------------- 数据库查询 ----------------
    function queryDatabase(code, textNode) {
        const apiUrl = `http://localhost:5002/check-code?code=${code}`;
        GM_xmlhttpRequest({
            method: "GET",
            url: apiUrl,
            onload: res => {
                try {
                    const result = JSON.parse(res.responseText);
                    if (result.exists) {
                        const remark = result.data.remark || "无备注";
                        const worth = result.data.worth_watching == 0 ? "不值得观看" : "值得观看";
                        const text = result.data.ready_to_exe === 1
                            ? `待处理中, ${remark}`
                            : `备注: ${remark},${worth}`;
                        showResult(code, textNode, text, true);
                    } else {
                        showResult(code, textNode, `${code}未找到`, false);
                    }
                } catch {
                    showResult(code, textNode, "解析错误", false);
                }
            },
            onerror: () => showResult(code, textNode, "查询失败", false)
        });
    }

    // ---------------- 结果显示 ----------------
    function showResult(code, textNode, text, hasData) {
        const resultSpan = document.createElement("span");
        resultSpan.style.marginLeft = "5px";
        resultSpan.style.color =
            text.includes("待处理中") ? "orange" :
            text.includes("未找到") ? "red" : "green";

        const fontSize = "10px";
        const buttonContainer = document.createElement("span");
        buttonContainer.style.marginLeft = "5px";

        // 🔍 javstore 搜索按钮
        const searchButton = createButton("🔍", fontSize, () =>
            window.open(`https://img.javstore.net/search/images/?q=${code}`, "_blank")
        );
        searchButton.title = "在 javstore.net 搜索图片";

        // ℹ️ 详情(点击展开/关闭)
        const detailButton = createButton("ℹ️", fontSize);
        detailButton.title = "点击查看详情";
        let detailBox = null;
        let isOpen = false;
        detailButton.addEventListener("click", () => {
            if (isOpen) {
                if (detailBox) detailBox.remove();
                isOpen = false;
                return;
            }
            isOpen = true;
            fetchDetails(code, detailButton, box => {
                detailBox = box;
                document.body.appendChild(box);
            });
        });

        // 🧭 Everything 查询按钮
        const evButton = createButton("🧭", fontSize, () => fetchEverythingHTML(code, evButton));
        evButton.title = "在 Everything (10.0.0.2:4321) 中查询本地文件";

        buttonContainer.appendChild(searchButton);
        buttonContainer.appendChild(detailButton);
        buttonContainer.appendChild(evButton);

        // 无数据库数据时额外按钮
        if (!hasData) {
            const remarkInput = document.createElement("input");
            remarkInput.type = "text";
            remarkInput.placeholder = "备注";
            remarkInput.style.width = "45px";
            remarkInput.style.fontSize = fontSize;
            remarkInput.style.marginLeft = "5px";

            const addY = createButton("Y", fontSize, () =>
                addCode(code, 1, 1, remarkInput.value.trim(), addY)
            );
            const addN = createButton("N", fontSize, () =>
                addCode(code, 0, 0, remarkInput.value.trim() || "UG", addN)
            );

            const imgButton = createButton("i", fontSize, e => {
                e.preventDefault();
                e.stopPropagation();
                let i = 0;
                const symbols = ["+", "-", "+", "-"];
                const intervalId = setInterval(() => {
                    imgButton.textContent = symbols[i++ % symbols.length];
                }, 300);
                fetchImage(code, imgButton, intervalId);
            });

            buttonContainer.appendChild(imgButton);
            buttonContainer.appendChild(addY);
            buttonContainer.appendChild(addN);
            buttonContainer.appendChild(remarkInput);
        }

        resultSpan.textContent = `[${text}] `;
        resultSpan.appendChild(buttonContainer);
        textNode.parentNode.insertBefore(resultSpan, textNode.nextSibling);
    }

    // ---------------- 公共函数 ----------------
    function createButton(text, size, onClick) {
        const b = document.createElement("button");
        b.textContent = text;
        b.style.fontSize = size;
        b.style.marginLeft = "5px";
        b.style.cursor = "pointer";
        if (onClick) b.addEventListener("click", onClick);
        return b;
    }

    // ---------------- 添加数据库 ----------------
    function addCode(code, ready, worth, remark, btn) {
        const apiUrl = `http://localhost:5002/add-code`;
        btn.textContent = "添加中...";
        btn.disabled = true;
        GM_xmlhttpRequest({
            method: "POST",
            url: apiUrl,
            headers: { "Content-Type": "application/json" },
            data: JSON.stringify({ code, ready_to_exe: ready, worth_watching: worth, remark }),
            onload: res => {
                try {
                    const r = JSON.parse(res.responseText);
                    if (r.success) {
                        const parent = btn.parentNode.parentNode;
                        parent.textContent = `[已添加]`;
                        parent.style.color = "green";
                    } else fail();
                } catch { fail(); }
                function fail() {
                    btn.textContent = "添加失败";
                    btn.style.color = "red";
                }
            },
            onerror: () => {
                btn.textContent = "添加失败";
                btn.style.color = "red";
            }
        });
    }

    // ---------------- 获取图片 ----------------
    function fetchImage(code, btn, id) {
        const apiUrl = `http://localhost:5002/get-image?code=${code}`;
        GM_xmlhttpRequest({
            method: "GET",
            url: apiUrl,
            onload: res => {
                clearInterval(id);
                btn.textContent = "i";
                try {
                    const r = JSON.parse(res.responseText);
                    if (r.image_url) window.open(r.image_url, "_blank");
                    else btn.textContent = "无图";
                } catch { btn.textContent = "错误"; }
            },
            onerror: () => {
                clearInterval(id);
                btn.textContent = "失败";
            }
        });
    }

    // ---------------- 展开详情框 ----------------
    function fetchDetails(code, button, callback) {
        const apiUrl = `http://localhost:5002/check-code?code=${code}`;
        GM_xmlhttpRequest({
            method: "GET",
            url: apiUrl,
            onload: function (res) {
                try {
                    const r = JSON.parse(res.responseText);
                    if (!r.exists || !r.data) return;
                    const d = r.data;
                    const fields = [
                        { k: "actor", n: "演员" },
                        { k: "author", n: "作者" },
                        { k: "downloadable", n: "可下载" },
                        { k: "rating", n: "评分" },
                        { k: "screenshot_link", n: "截图链接" },
                        { k: "storage_location", n: "存储路径" }
                    ];
                    const box = document.createElement("div");
                    box.className = "detail-box";
                    box.style.position = "absolute";
                    box.style.background = "rgba(0,0,0,0.85)";
                    box.style.color = "#fff";
                    box.style.padding = "8px 10px";
                    box.style.borderRadius = "8px";
                    box.style.fontSize = "12px";
                    box.style.maxWidth = "280px";
                    box.style.whiteSpace = "pre-wrap";
                    box.style.zIndex = 9999;
                    box.style.lineHeight = "1.5";

                    let content = "";
                    fields.forEach(f => {
                        if (d[f.k]) content += `${f.n}: ${d[f.k]}\n`;
                    });
                    box.textContent = content || "无详细数据";

                    const rect = button.getBoundingClientRect();
                    box.style.top = `${rect.top + window.scrollY + 20}px`;
                    box.style.left = `${rect.left + window.scrollX}px`;
                    callback(box);
                } catch (e) {
                    console.error("详情加载错误:", e);
                }
            }
        });
    }

    // ---------------- Everything HTML 解析 ----------------
    function fetchEverythingHTML(keyword, button) {
        const apiUrl = `${EVERYTHING_BASE}/?search=${encodeURIComponent(keyword)}&count=20`;

        GM_xmlhttpRequest({
            method: "GET",
            url: apiUrl,
            onload: res => {
                const htmlText = res.responseText;
                const parser = new DOMParser();
                const doc = parser.parseFromString(htmlText, "text/html");

                // 支持 trdata1 / trdata2 两种交替行
                const links = [
                    ...doc.querySelectorAll("tr.trdata1 td.file span nobr a"),
                    ...doc.querySelectorAll("tr.trdata2 td.file span nobr a")
                ];

                if (links.length === 0) {
                    showTooltip(button, "未找到结果");
                    return;
                }

                const box = document.createElement("div");
                box.className = "ev-html-box";
                box.style.position = "absolute";
                box.style.background = "rgba(0,0,0,0.85)";
                box.style.color = "#fff";
                box.style.padding = "8px";
                box.style.borderRadius = "6px";
                box.style.fontSize = "12px";
                box.style.maxWidth = "420px";
                box.style.maxHeight = "250px";
                box.style.overflowY = "auto";
                box.style.zIndex = 9999;

                links.forEach(a => {
                    const div = document.createElement("div");
                    div.textContent = a.textContent.trim();
                    div.style.cursor = "pointer";
                    div.style.marginBottom = "4px";
                    div.style.whiteSpace = "nowrap";
                    div.addEventListener("click", () => {
                        const href = a.getAttribute("href");
                        const url = href.startsWith("http") ? href : EVERYTHING_BASE + href;
                        window.open(url, "_blank");
                    });
                    box.appendChild(div);
                });

                const rect = button.getBoundingClientRect();
                box.style.top = `${rect.top + window.scrollY + 20}px`;
                box.style.left = `${rect.left + window.scrollX}px`;

                document.body.appendChild(box);

                const closeHandler = () => {
                    box.remove();
                    button.removeEventListener("click", closeHandler);
                };
                button.addEventListener("click", closeHandler, { once: true });
            },
            onerror: () =>
                showTooltip(button, "无法连接 Everything,请确认 http://10.0.0.2:4321 可访问。")
        });
    }

    // ---------------- 简易提示 ----------------
    function showTooltip(button, message) {
        const tip = document.createElement("div");
        tip.textContent = message;
        tip.style.position = "absolute";
        tip.style.background = "#222";
        tip.style.color = "#fff";
        tip.style.padding = "5px 8px";
        tip.style.borderRadius = "5px";
        tip.style.fontSize = "12px";
        tip.style.zIndex = 9999;

        const rect = button.getBoundingClientRect();
        tip.style.top = `${rect.top + window.scrollY + 20}px`;
        tip.style.left = `${rect.left + window.scrollX}px`;

        document.body.appendChild(tip);
        setTimeout(() => tip.remove(), 2000);
    }

})();