Greasy Fork

Greasy Fork is available in English.

Bangumi Super Enhancer

Bangumi 增强套件

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Bangumi Super Enhancer
// @namespace    https://tampermonkey.net/
// @version      2.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: 6px 0;  /* 减少外边距 */
            border-radius: 6px;  /* 调小圆角 */
            padding: 5px;  /* 减少内边距 */
            background: #f8f8f8;
            border: 1px solid #e0e0e0;
        }
        #coverUploadForm {
            text-align: center;
            padding: 3px;  /* 减少表单内边距 */
        }
        #coverUploadForm input[type="file"] {
            margin: 3px auto;  /* 缩小文件输入框边距 */
            width: 90%;  /* 调宽输入框减少留白 */
            display: block;
        }
        /* 进一步缩小按钮尺寸 */
        #coverUploadForm input[type="submit"] {
            padding: 4px 8px !important;  /* 更紧凑的按钮尺寸 */
            font-size: 14px !important;  /* 更小的字号 */
            margin: 8px auto 2px !important;  /* 调整按钮间距 */
        }
    `);

    /* 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();

            const subjectList = document.getElementById("subjectList");
            if (!subjectList) {
                alert("未找到关联列表,请检查页面结构。");
                return;
            }

            const observer = new MutationObserver((mutations, observerInstance) => {
                // 当列表中有 li 元素时认为搜索结果已生成
                if ($('#subjectList > li').length > 0) {
                    observerInstance.disconnect();
                    // 延时500ms等待渲染完成
                    setTimeout(function(){
                        var $avatars = $('#subjectList>li>a.avatar.h');
                        if ($avatars.length > 1) {
                            $avatars.each(function(index, element){
                                setTimeout(function(){
                                    $(element).click();
                                    console.log("已点击:" + $(element).text());
                                    // 如有需要,可对关联列表中的条目进行样式修改提醒
                                    $('#crtRelateSubjects li p.title>a').eq(0).css('font-weight', 'bold');
                                }, index * 500);
                            });
                        } else if ($avatars.length === 1) {
                            $avatars.click();
                        }
                        // 根据点击数量延时后点击保存按钮
                        setTimeout(function(){
                            $("#saveSubject").click();
                            alert("所有关联项已成功添加!");
                        }, ($avatars.length > 1 ? $avatars.length * 500 + 300 : 800));
                    }, 500);
                }
            });
            observer.observe(subjectList, { childList: true, subtree: true });
        }
    }

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