Greasy Fork

Greasy Fork is available in English.

Bangumi Super Enhancer

Bangumi 增强套件

当前为 2025-03-11 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Bangumi Super Enhancer
// @namespace    https://tampermonkey.net/
// @version      2.0
// @description  Bangumi 增强套件
// @author       Bios
// @match        *://bgm.tv/subject/*
// @match        *://chii.in/subject/*
// @match        *://bangumi.tv/subject*
// @match        *://bgm.tv.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() {
        const match = location.pathname.match(/\/subject\/(\d+)/);
        if (!match) return;

        const nav = document.querySelector(".subjectNav .navTabs");
        if (!nav) return;

        const li = document.createElement("li");
        li.innerHTML = `<a href="/subject/${match[1]}/edit_detail" target="_blank">Wiki</a>`;
        nav.appendChild(li);
    }

    /* 封面上传模块 */
    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("提交失败,请减少选中数量!");
        });
    }

    /* 批量关联模块 */
    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 ids = [...new Set($("#custom_ids").val().match(/\d+/g))].map(Number);
            if (ids.length) processBatch(ids);
        });

        document.getElementById("btn_generate")?.addEventListener("click", () => {
            const start = parseInt($("#id_start").val());
            const end = parseInt($("#id_end").val());
            if (start <= end) processBatch(Array.from({length: end - start + 1}, (_, i) => start + i));
        });

        function processBatch(ids) {
            if (!ids.length) return;

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

            $("#subjectList").one("DOMSubtreeModified", function () {
                setTimeout(() => {
                    $("#subjectList .clearit p .avatar").click();
                    const type = $("#relationListContainer select").val();
                    $("#crtRelateSubjects li:not(.old)").slice(0, 10).find("select").val(type);

                    if (ids.length) setTimeout(() => processBatch(ids), 1000);
                }, 1000);
            });
        }
    }

    // 初始化入口
    function init() {
        initWikiButton();
        initCoverUpload();
        enhanceEpisodes();
        initBatchRelation();
    }

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", init);
    } else {
        init();
    }
})();