Greasy Fork

Greasy Fork is available in English.

Bangumi Super Enhancer 222

Bangumi 增强套件

目前为 2025-03-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         Bangumi Super Enhancer 222
// @namespace    https://tampermonkey.net/
// @version      2.2
// @description  Bangumi 增强套件
// @author       Bios
// @match        *://bgm.tv/subject/*
// @match        *://chii.in/subject/*
// @match        *://bangumi.tv/subject*
// @match        *://bgm.tv/character/*
// @match        *://chii.in/character/*
// @match        *://bangumi.tv/character/*
// @match        *://bgm.tv/person/*
// @match        *://chii.in/person/*
// @match        *://bangumi.tv/person/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license      MIT
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// ==/UserScript==

(function () {
    "use strict";

    // 样式增强
    GM_addStyle(`
        .btnCustom {
            margin: 5px 0;
            background-color: #1E90FF !important;
            color: white !important;
            border-radius: 10px !important;
            padding: 5px 15px !important;
            border: none !important;
            cursor: pointer !important;
            transition: opacity 0.2s;
        }

        .btnCustom:hover {
            opacity: 0.8;
        }

        .enhancer-textarea {
            width: 100%;
            min-height: 60px;
            max-height: 300px;
            border: 1px solid #ddd;
            border-radius: 10px;
            padding: 8px;
            margin: 8px 0;
            resize: vertical;
            font-size: 13px;
            box-sizing: border-box;
        }

        .enhancer-panel {
            margin: 15px 0;
            border-radius: 10px;
            padding: 15px;
            background: #f5f5f5;
            border: 1px solid #e0e0e0;
        }

        #coverUploadForm {
            text-align: center;
            padding: 10px;
        }

        #coverUploadForm input[type="file"] {
            margin: 10px auto;
            display: block;
        }
    `);

    /* Wiki 按钮模块 */
    function initWikiButton() {
        // 排除编辑页面
        if (/(edit_detail|edit)$/.test(location.pathname)) return;

        // 排除 add_related 和 upload_img 页面
        if (/add_related|upload_img/.test(location.pathname)) return;

        const matchSubject = location.pathname.match(/\/subject\/(\d+)/);
        const matchPerson = location.pathname.match(/\/person\/(\d+)/);
        const matchCharacter = location.pathname.match(/\/character\/(\d+)/);

        const nav = document.querySelector(".subjectNav .navTabs, .navTabs");
        if (!nav || nav.querySelector(".wiki-button")) return;

        const li = document.createElement("li");
        li.className = "wiki-button";

        let wikiUrl = "";

        if (matchSubject) {
            wikiUrl = `${location.origin}/subject/${matchSubject[1]}/edit_detail`;
        } else if (matchPerson) {
            wikiUrl = `${location.origin}/person/${matchPerson[1]}/edit`;
        } else if (matchCharacter) {
            wikiUrl = `${location.origin}/character/${matchCharacter[1]}/edit`;
        }

        if (wikiUrl) {
            li.innerHTML = `<a href="${wikiUrl}" target="_blank" onclick="this.closest('li').remove()">Wiki</a>`;
            nav.appendChild(li);
        }
    }

    // 监听 URL 变化
    function observeURLChanges() {
        let lastURL = location.href;

        new MutationObserver(() => {
            if (location.href !== lastURL) {
                lastURL = location.href;
                initWikiButton();
            }
        }).observe(document, { subtree: true, childList: true });
    }

    /* 封面上传模块 */
    async function initCoverUpload() {
        if (document.querySelector("img.cover")) return;
        const infoBox = document.querySelector("#bangumiInfo");
        if (!infoBox) return;

        const links = document.querySelectorAll(".tip_i p a.l");
        if (links.length < 2) return;

        try {
            const res = await fetch(links[1].href);
            const doc = new DOMParser().parseFromString(await res.text(), "text/html");
            const form = doc.querySelector("#columnInSubjectA .text form");

            if (form) {
                const container = document.createElement("div");
                container.className = "enhancer-panel";

                const clone = form.parentElement.cloneNode(true);
                const uploadForm = clone.querySelector("form");
                uploadForm.id = "coverUploadForm";

                const fileInput = uploadForm.querySelector("input[type=file]");
                fileInput.style.width = "100%";

                const submitBtn = uploadForm.querySelector("input[type=submit]");
                submitBtn.className = "btnCustom";
                submitBtn.style.width = "120px";
                submitBtn.style.margin = "10px auto 0";

                container.appendChild(uploadForm);
                infoBox.parentNode.insertBefore(container, infoBox);
            }
        } catch (e) {
            console.error("封面加载失败:", e);
        }
    }

    /* 章节批量编辑 */
    function enhanceEpisodes() {
        if (!location.pathname.includes("/ep")) return;

        const formHash = document.querySelector("[name=formhash]")?.value;
        if (!formHash) return;

        document.querySelector("[name=edit_ep_batch]")?.addEventListener("submit", async (e) => {
            e.preventDefault();
            const episodes = [...document.querySelectorAll('[name="ep_mod[]"]:checked')].map(el => el.value);

            let batchSize = 100;
            while (batchSize >= 50 && episodes.length > 0) {
                try {
                    const data = new URLSearchParams();
                    data.append("formhash", formHash);
                    data.append("chkall", "on");
                    data.append("submit", "批量修改");
                    episodes.splice(0, batchSize).forEach(ep => data.append("ep_mod[]", ep));

                    const res = await fetch(location.pathname + "/edit_batch", {
                        method: "POST",
                        headers: { "Content-Type": "application/x-www-form-urlencoded" },
                        body: data
                    });

                    if (!res.ok) throw new Error();
                    alert(`成功提交 ${batchSize} 个章节`);
                    return;
                } catch {
                    batchSize = Math.floor(batchSize / 2);
                }
            }
            alert("提交失败,请减少选中数量!");
        });
    }

    /* 获取当前页面的 ID(即 URL 里的 subject/xxx、character/xxx、person/xxx)*/
    function getCurrentPageID() {
        const match = window.location.pathname.match(/\/(subject|character|person)\/(\d+)/);
        return match ? parseInt(match[2], 10) : null;
    }

    const currentPageID = getCurrentPageID(); /* 获取当前条目 ID */


    /* 批量关联模块 */
    function initBatchRelation() {
        if (!document.getElementById("indexCatBox")) return;

        const panelHTML = `
            <div class="enhancer-panel">
                <textarea id="custom_ids" class="enhancer-textarea"
                    placeholder="输入 ID 或网址(可换行,支持各种分隔字符)"></textarea>
                <div style="text-align: center">
                    <button id="btn_execute" class="btnCustom">自动添加关联</button>
                </div>
            </div>
            <div class="enhancer-panel">
                <div style="display: flex; gap: 10px; justify-content: center">
                    <input id="id_start" type="number" placeholder="起始ID"
                        style="width: 100px; padding: 6px; border-radius: 8px; border: 1px solid #ddd;">
                    <span style="line-height: 30px">~</span>
                    <input id="id_end" type="number" placeholder="结束ID"
                        style="width: 100px; padding: 6px; border-radius: 8px; border: 1px solid #ddd;">
                </div>
                <div style="text-align: center; margin-top: 12px">
                    <button id="btn_generate" class="btnCustom">自动添加关联</button>
                </div>
            </div>
        `;

        const searchMod = document.querySelector("#sbjSearchMod");
        if (searchMod) searchMod.insertAdjacentHTML("afterend", panelHTML);

        document.getElementById("btn_execute")?.addEventListener("click", () => {
            const rawInput = $("#custom_ids").val();
            const ids = extractUniqueIDs(rawInput);
            if (ids.length) processBatch(ids);
        });

        document.getElementById("btn_generate")?.addEventListener("click", () => {
            const start = parseInt($("#id_start").val(), 10);
            const end = parseInt($("#id_end").val(), 10);

            if (isNaN(start) || isNaN(end) || start > end) {
                alert("请输入有效的起始和结束 ID!");
                return;
            }

            const ids = Array.from({ length: end - start + 1 }, (_, i) => start + i);
            processBatch([...new Set(ids)]);
        });

        function extractUniqueIDs(input) {
            const allIDs = input.match(/\d+/g) || [];
            let uniqueIDs = [...new Set(allIDs.map(Number))].filter(id => id > 0);

            if (currentPageID) {
                uniqueIDs = uniqueIDs.filter(id => id !== currentPageID);
            }

            return uniqueIDs;
        }

        function processBatch(ids) {
            if (!ids.length) {
                alert("未找到有效的 ID 或所有 ID 都已被排除!");
                return;
            }

            const batch = ids.splice(0, 10);
            $("#subjectName").val(`bgm_id=${batch.join(",")}`);
            $("#findSubject").click();

            $("#subjectList").one("DOMSubtreeModified", function () {
                setTimeout(() => {
                    $("#subjectList .subjectItem input").each(function () {
                        this.checked = true;
                    });

                    setTimeout(() => {
                        $("#saveSubject").click();
                        alert("所有关联项已成功添加!");
                    }, 300);
                }, 500);
            });
        }
    }

    /* 启动功能 */
    function startEnhancer() {
        initWikiButton();
        observeURLChanges();
        initCoverUpload();
        enhanceEpisodes();
        initBatchRelation();
    }

    startEnhancer();
})();