Greasy Fork

Greasy Fork is available in English.

Bangumi Ultimate Enhancer

Bangumi 终极增强套件 - 集成Wiki按钮、关联按钮、封面上传、批量关联、批量分集编辑等功能

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

// ==UserScript==
// @name         Bangumi Ultimate Enhancer
// @namespace    https://tampermonkey.net/
// @version      2.3
// @description  Bangumi 终极增强套件 - 集成Wiki按钮、关联按钮、封面上传、批量关联、批量分集编辑等功能
// @author       Bios (improved by Claude)
// @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/*
// @connect      bgm.tv
// @grant        GM_xmlhttpRequest
// @license      MIT
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @run-at       document-idle
// ==/UserScript==

(function() {
    "use strict";

    // 在页面加载完成后执行
    $(document).ready(function() {
        // 判断当前是否在关联页面
        const isRelatedPage = window.location.href.indexOf('/add_related') !== -1;

        // 判断当前是否在条目/角色/人物主页
        const isMainPage = /\/(subject|character|person)\/\d+$/.test(window.location.pathname);

        // 根据页面类型选择初始化不同功能
        if (isRelatedPage) {
            initBatchRelation();
        } else if (isMainPage) {
            initIDCollector();
        }
    });

    // 样式注入
    function injectStyles() {
        $('head').append(`
      <style>
        /* 通用按钮美化 */
        .btnCustom {
        margin: 5px 0;
        background-color: #1E90FF !important;
        color: white !important;
        border-radius: 5px !important;
        padding: 6px 18px !important;
        border: none !important;
        cursor: pointer !important;
        position: relative;
        top: -4px;
        left: -8px;
        font-size: 14px;
        font-weight: bold;
        text-align: center;
        display: flex;
        justify-content: center;
        align-items: center;
        display: inline-block;
        transition: opacity 0.2s, transform 0.2s;
        }

        .btnCustom:hover {
        opacity: 0.9;
        box-shadow: 3px 6px 12px rgba(0, 0, 0, 0.15);
        }

        /* 输入框优化 */
        .enhancer-textarea {
        width: 100%;
        min-height: 80px;
        max-height: 300px;
        border: 1px solid #ccc;
        border-radius: 12px;
        padding: 10px;
        margin: 10px 0;
        resize: vertical;
        font-size: 14px;
        box-sizing: border-box;
        background: #fdfdfd;
        transition: all 0.05s ease-in-out;
        }

        .enhancer-textarea:focus {
        border-color: #1E90FF;
        box-shadow: 0 0 8px rgba(30, 144, 255, 0.3);
        outline: none;
        }

        /* 卡片式面板美化 */
        .enhancer-panel {
        margin: 10px 0;
        border-radius: 10px;
        padding: 12px;
        background: #ffffff;
        border: 1px solid #e0e0e0;
        box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.05);
        }

        .enhancer-panel h3 {
        margin-top: 0;
        margin-bottom: 10px;
        font-size: 15px;
        color: #333;
        }

        /* 标签和选择框美化 */
        .select-label {
        margin-right: 8px;
        font-weight: bold;
        color: #555;
        }

        .chitanda_item_type {
        display: flex;
        margin-right: auto;
        align-items: center;
        margin-bottom: 8px;
        }

        /* 主要容器美化 */
        .chitanda_wrapper {
        margin: 15px 0;
        background: #fff;
        padding: 15px;
        border-radius: 10px;
        border: 1px solid #eaeaea;
        box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.05);
        }

        /* 进度提示美化 */
        .chitanda_progress {
        margin: 12px 0;
        color: #d346bb;
        font-weight: bold;
        font-size: 18px;
        text-align: center;
        }

        /* 信息提示框优化 */
        .chitanda_item_not_found, .chitanda_item_dupe {
        margin-top: 8px;
        padding: 8px;
        min-height: 15px;
        border-radius: 6px;
        font-size: 12px;
        }

        .chitanda_item_not_found {
        color: #e74c3c;
        background: #ffebeb;
        border: 1px solid #e74c3c;
        }

        .chitanda_item_dupe {
        color: #3498db;
        background: #eaf6ff;
        border: 1px solid #3498db;
        }

        /* 标题优化 */
        .chitanda_header {
        font-size: 16px;
        margin: 12px 0 6px;
        color: #333;
        font-weight: bold;
        }

        /* 按钮区域对齐优化 */
        .chitanda_controls {
        display: flex;
        justify-content: flex-end;
        align-items: center;
        }

        /* 标签导航美化 */
        .tab-nav {
        display: flex;
        border-bottom: 2px solid #ddd;
        margin-bottom: 15px;
        }

        .tab-nav button {
        background: none;
        border: none;
        padding: 10px 20px;
        cursor: pointer;
        font-size: 14px;
        font-weight: bold;
        border-bottom: 3px solid transparent;
        transition: all 0.3s;
        }

        .tab-nav button.active {
        border-bottom: 3px solid #1E90FF;
        color: #1E90FF;
        }

        .tab-nav button:hover {
        color: #1E90FF;
        }

        /* 选项卡面板优化 */
        .tab-panel {
        display: none;
        }

        .tab-panel.active {
        display: block;
        animation: fadeIn 0.3s ease-in-out;
        }

        @keyframes fadeIn {
        from { opacity: 0; transform: translateY(5px); }
        to { opacity: 1; transform: translateY(0); }
        }

        /* 行内表单优化 */
        .flex-row {
        display: flex;
        gap: 12px;
        align-items: center;
        }

        /* 输入框美化 */
        .input-number {
        width: 100px;
        padding: 8px;
        border-radius: 8px;
        border: 1px solid #ddd;
        background: #f9f9f9;
        transition: all 0.2s;
        }

        .input-number:focus {
        border-color: #1E90FF;
        box-shadow: 0 0 5px rgba(30, 144, 255, 0.3);
        outline: none;
        }
      </style>
    `);
    }

    /* Wiki 按钮和关联按钮模块 */
    function initNavButtons() {
        // 排除编辑页面
        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) return;
        // 检查Wiki按钮是否已存在
        if (!nav.querySelector(".wiki-button")) {
            const wikiLi = document.createElement("li");
            wikiLi.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) {
                wikiLi.innerHTML = `<a href="${wikiUrl}" target="_blank">Wiki</a>`;
                nav.appendChild(wikiLi);
            }
        }
        // 为主条目添加关联按钮
        if (matchSubject && !nav.querySelector(".relate-button")) {
            const relateLi = document.createElement("li");
            relateLi.className = "relate-button";
            // 关联按钮默认链接到添加动画关联
            const relateUrl = `${location.origin}/subject/${matchSubject[1]}/add_related/subject/anime`;
            relateLi.innerHTML = `<a href="${relateUrl}" target="_blank">关联</a>`;
            nav.appendChild(relateLi);
        }
    }

    // 监听 URL 变化
    function observeURLChanges() {
        let lastURL = location.href;
        new MutationObserver(() => {
            if (location.href !== lastURL) {
                lastURL = location.href;
                initNavButtons();
            }
        }).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);
        }
    }

    /* ------------------------------
      批量分集编辑器功能模块
    ------------------------------ */

    const BatchEpisodeEditor = {
        CHUNK_SIZE: 20,
        BASE_URL: '',
        CSRF_TOKEN: '',

        // 初始化方法
        init() {
            if (!this.isEpisodePage()) return;

            this.BASE_URL = location.pathname.replace(/\/edit_batch$/, '');
            this.CSRF_TOKEN = $('[name=formhash]')?.value || '';

            if (!this.CSRF_TOKEN) return;

            this.bindHashChange();
            this.upgradeCheckboxes();

            // 添加功能标识
            const header = document.querySelector('h2.subtitle');
            if (header) {
                const notice = document.createElement('div');
                notice.className = 'bgm-enhancer-status';
                notice.textContent = '已启用分批编辑功能,支持超过20集的批量编辑';
                header.parentNode.insertBefore(notice, header.nextSibling);
            }
        },

        // 检查是否为分集页面
        isEpisodePage() {
            return /^\/subject\/\d+\/ep(\/edit_batch)?$/.test(location.pathname);
        },

        // 监听hash变化处理批量编辑
        bindHashChange() {
            const processHash = () => {
                const ids = this.getSelectedIdsFromHash();
                if (ids.length > 0) this.handleBatchEdit(ids);
            };

            window.addEventListener('hashchange', processHash);
            if (location.hash.includes('episodes=')) processHash();
        },

        // 增强复选框功能
        upgradeCheckboxes() {
            // 动态更新表单action
            const updateFormAction = () => {
                const ids = $$('[name="ep_mod[]"]:checked').map(el => el.value);
                $('form[name="edit_ep_batch"]').action =
                    `${this.BASE_URL}/edit_batch#episodes=${ids.join(',')}`;
            };

            $$('[name="ep_mod[]"]').forEach(el =>
                                            el.addEventListener('change', updateFormAction)
                                           );

            // 全选功能
            $('[name=chkall]')?.addEventListener('click', () => {
                $$('[name="ep_mod[]"]').forEach(el => el.checked = true);
                updateFormAction();
            });
        },

        // 从hash获取选中ID
        getSelectedIdsFromHash() {
            const match = location.hash.match(/episodes=([\d,]+)/);
            return match ? match[1].split(',').filter(Boolean) : [];
        },

        // 批量编辑主逻辑
        async handleBatchEdit(episodeIds) {
            try {
                // 分块加载数据
                const chunks = this.createChunks(episodeIds, this.CHUNK_SIZE);
                const dataChunks = await this.loadChunkedData(chunks);

                // 填充表单数据
                $('#summary').value = dataChunks.flat().join('\n');
                $('[name=ep_ids]').value = episodeIds.join(',');

                // 增强表单提交
                this.upgradeFormSubmit(chunks, episodeIds);

                window.chiiLib?.ukagaka?.presentSpeech('数据加载完成');
            } catch (err) {
                console.error('批量处理失败:', err);
                alert('数据加载失败,请刷新重试');
            }
        },

        // 分块加载数据
        async loadChunkedData(chunks) {
            window.chiiLib?.ukagaka?.presentSpeech('正在加载分集数据...');
            return Promise.all(chunks.map(chunk =>
                                          this.fetchChunkData(chunk).then(data => data.split('\n'))
                                         ));
        },

        // 获取单块数据
        async fetchChunkData(episodeIds) {
            const params = new URLSearchParams();
            params.append('chkall', 'on');
            params.append('submit', '批量修改');
            params.append('formhash', this.CSRF_TOKEN);
            episodeIds.forEach(id => params.append('ep_mod[]', id));

            const res = await fetch(`${this.BASE_URL}/edit_batch`, {
                method: 'POST',
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                body: params
            });

            const html = await res.text();
            const match = html.match(/<textarea [^>]*name="ep_list"[^>]*>([\s\S]*?)<\/textarea>/i);
            return match?.[1]?.trim() || '';
        },

        // 增强表单提交处理
        upgradeFormSubmit(chunks, originalIds) {
            const form = $('form[name="edit_ep_batch"]');
            if (!form) return;

            form.onsubmit = async (e) => {
                e.preventDefault();

                // 验证数据完整性
                const inputData = $('#summary').value.trim().split('\n');
                if (inputData.length !== originalIds.length) {
                    alert(`数据不匹配 (预期 ${originalIds.length} 行,实际 ${inputData.length} 行)`);
                    return;
                }

                try {
                    window.chiiLib?.ukagaka?.presentSpeech('正在提交数据...');
                    await this.saveChunkedData(chunks, inputData);

                    window.chiiLib?.ukagaka?.presentSpeech('保存成功');
                    location.href = this.BASE_URL;
                } catch (err) {
                    console.error('保存失败:', err);
                    alert('保存过程中发生错误');
                }
            };
        },

        // 分块保存数据
        async saveChunkedData(chunks, fullData) {
            const dataChunks = this.createChunks(fullData, this.CHUNK_SIZE);

            return Promise.all(chunks.map((idChunk, index) =>
                                          this.saveChunkData(idChunk, dataChunks[index])
                                         ));
        },

        // 保存单块数据
        async saveChunkData(episodeIds, chunkData) {
            const params = new URLSearchParams();
            params.append('formhash', this.CSRF_TOKEN);
            params.append('rev_version', '0');
            params.append('editSummary', $('#editSummary')?.value || '');
            params.append('ep_ids', episodeIds.join(','));
            params.append('ep_list', chunkData.join('\n'));
            params.append('submit_eps', '改好了');

            await fetch(`${this.BASE_URL}/edit_batch`, {
                method: 'POST',
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                body: params
            });
        },

        // 通用分块方法
        createChunks(array, size) {
            return Array.from(
                { length: Math.ceil(array.length / size) },
                (_, i) => array.slice(i * size, (i + 1) * size)
            );
        }
    };

    // ID收集器 - 用于条目/角色/人物主页
    function initIDCollector() {
        injectStyles();

        // 获取当前页面ID
        const getPageID = () => {
            const match = location.pathname.match(/\/(subject|character|person)\/(\d+)/);
            return match ? parseInt(match[2]) : null;
        };
        const currentID = getPageID();

        // 创建界面容器
        const container = document.createElement("div");
        container.innerHTML = `
      <div class="enhancer-panel">
        <h3>批量关联助手</h3>
        <p>当前位置:ID收集 - 点击下方按钮前往关联页面</p>
        <div style="text-align: center">
          <a href="${window.location.pathname}/add_related/subject" class="btnCustom">
            前往批量关联页面
          </a>
        </div>
      </div>
    `;

        // 插入到页面
        const targetNode = $("#sbjSearchMod") || $(".main_content");
        if (targetNode.length) targetNode.after(container);
    }

    // 批量关联功能 - 用于关联页面
    function initBatchRelation() {
        injectStyles();

        // 参数配置
        const DELAY_AFTER_CLICK = 250;
        const DELAY_BETWEEN_ITEMS = 500;
        const MAX_RETRY_ATTEMPTS = 10;
        const RETRY_INTERVAL = 100;

        // 全局变量
        let globalItemType = '1';
        let currentProcessingIndex = -1;

        // 根据当前 URL 判断页面类型
        function getCurrentPageType() {
            const url = window.location.href;
            if (url.indexOf('/add_related/character') !== -1) {
                return 'character';
            } else if (url.indexOf('/add_related/subject') !== -1) {
                return 'subject';
            } else {
                return 'person';
            }
        }

        // 生成关联类型选择下拉框
        function generateTypeSelector() {
            const pageType = getCurrentPageType();
            if (pageType === 'character') {
                let options = '';
                const relationTypes = {
                    '1': '主角',
                    '2': '配角',
                    '3': '客串'
                };
                for (let [value, text] of Object.entries(relationTypes)) {
                    options += `<option value="${value}">${text}</option>`;
                }
                return `<span class="select-label">类型: </span><select>${options}</select>`;
            } else {
                // 如果有 genPrsnStaffList 函数则调用,否则返回空字符串
                return `<span class="select-label"></span>${(typeof genPrsnStaffList === "function") ? genPrsnStaffList(-1) : ''}`;
            }
        }

        // 针对传入的元素内的下拉框进行设置,并通过递归确保修改成功
        function setRelationTypeWithElement($li, item_type) {
            return new Promise((resolve) => {
                let attempts = 0;
                function trySet() {
                    // 确保我们获取的是当前元素内部的select,而不是全局的
                    let $select = $li.find('select').first();

                    if ($select.length > 0) {
                        // 先确保下拉框可交互
                        if ($select.prop('disabled')) {
                            setTimeout(trySet, RETRY_INTERVAL);
                            return;
                        }

                        $select.val(item_type);
                        // 触发 change 事件
                        const event = new Event('change', { bubbles: true });
                        $select[0].dispatchEvent(event);

                        setTimeout(() => {
                            if ($select.val() == item_type) {
                                resolve(true);
                            } else if (attempts < MAX_RETRY_ATTEMPTS) {
                                attempts++;
                                setTimeout(trySet, RETRY_INTERVAL);
                            } else {
                                resolve(false);
                            }
                        }, 200);
                    } else if (attempts < MAX_RETRY_ATTEMPTS) {
                        attempts++;
                        setTimeout(trySet, RETRY_INTERVAL);
                    } else {
                        resolve(false);
                    }
                }
                trySet();
            });
        }

        // 点击项目后利用 MutationObserver 监听新增条目,然后对该条目的下拉框设置类型
        function processItem(element, item_type) {
            return new Promise((resolve) => {
                // 关联列表容器
                const container = document.querySelector('#crtRelateSubjects');
                if (!container) {
                    return resolve(false);
                }

                // 保存处理前的条目列表
                const initialItems = Array.from(container.children);

                // 绑定 MutationObserver 监听子节点变化
                const observer = new MutationObserver((mutations) => {
                    // 获取当前所有条目
                    const currentItems = Array.from(container.children);
                    // 找出新增的条目(在当前列表中但不在初始列表中的元素)
                    const newItems = currentItems.filter(item => !initialItems.includes(item));

                    if (newItems.length > 0) {
                        observer.disconnect();
                        const newItem = newItems[0]; // 获取第一个新增条目

                        // 确保等待DOM完全渲染
                        setTimeout(async () => {
                            // 使用新的条目元素直接查找其内部的select
                            const $select = $(newItem).find('select');

                            if ($select.length > 0) {
                                const success = await setRelationTypeWithElement($(newItem), item_type);
                                resolve(success);
                            } else {
                                resolve(false);
                            }
                        }, DELAY_AFTER_CLICK);
                    }
                });

                observer.observe(container, { childList: true, subtree: true });

                // 触发点击
                $(element).click();

                // 超时防护
                setTimeout(() => {
                    observer.disconnect();
                    resolve(false);
                }, MAX_RETRY_ATTEMPTS * RETRY_INTERVAL);
            });
        }

        // 处理单个项目(若搜索结果不唯一则自动选择第一个)
        async function processSingleItem(elements, item_type, search_name, idx, item_list, item_num) {
            return new Promise((resolve) => {
                if (elements.length === 0) {
                    $('.chitanda_item_not_found').append(search_name + ' ');
                    resolve(false);
                    return;
                }
                if (elements.length > 1) {
                    $('.chitanda_item_dupe').append(`${search_name} `);
                    processItem(elements[0], item_type).then((success) => {
                        resolve(success);
                    });
                } else {
                    processItem(elements[0], item_type).then((success) => {
                        resolve(success);
                    });
                }
            });
        }

        // 处理下一个项目
        async function proceedToNextItem(idx, item_list, item_type, item_num) {
            if (idx < item_num - 1) {
                setTimeout(async () => {
                    await ctd_findItemFunc(item_list, item_type, idx + 1);
                }, DELAY_BETWEEN_ITEMS);
            } else {
                setTimeout(() => {
                    $('#subjectList').empty();
                    $('#subjectList').show();
                    alert('全部添加完成');
                }, DELAY_BETWEEN_ITEMS);
            }
        }

        // 核心查找及处理函数:依次检索每个条目并处理
        var ctd_findItemFunc = async function(item_list, item_type, idx) {
            currentProcessingIndex = idx;
            item_type = globalItemType;
            let search_name = item_list[idx].trim();
            if (!search_name) {
                proceedToNextItem(idx, item_list, item_type, item_list.length);
                return;
            }
            var item_num = item_list.length;
            $('#subjectList').html('<tr><td>正在检索中...</td></tr>');
            var search_mod = $('#sbjSearchMod').attr('value');

            try {
                const response = await new Promise((resolve, reject) => {
                    $.ajax({
                        type: "GET",
                        url: '/json/search-' + search_mod + '/' + encodeURIComponent(search_name),
                        dataType: 'json',
                        success: resolve,
                        error: reject
                    });
                });
                var html = '';
                if ($(response).length > 0) {
                    subjectList = response;
                    for (var i in response) {
                        if ($.inArray(search_mod, enableStaffSbjType) != -1) {
                            html += genSubjectList(response[i], i, 'submitForm');
                        } else {
                            html += genSubjectList(response[i], i, 'searchResult');
                        }
                    }
                    $('#subjectList').html(html);
                    $('.chitanda_current_idx').text(idx + 1);
                    $('.chitanda_all_num').text(item_num);
                    await new Promise(resolve => setTimeout(resolve, 400)); // 减少等待时间
                    var elements = $('#subjectList>li>a.avatar.h');
                    await processSingleItem(elements, item_type, search_name, idx, item_list, item_num);
                    await proceedToNextItem(idx, item_list, item_type, item_num);
                } else {
                    $("#robot").fadeIn(300);
                    $("#robot_balloon").html(`没有找到 ${search_name} 的相关结果`);
                    $("#robot").animate({ opacity: 1 }, 500).fadeOut(500); // 减少动画时间
                    $('.chitanda_item_not_found').append(search_name + ' ');
                    $('#subjectList').html(html);
                    $('.chitanda_current_idx').text(idx + 1);
                    $('.chitanda_all_num').text(item_num);
                    await proceedToNextItem(idx, item_list, item_type, item_num);
                }
            } catch (error) {
                console.error('查询出错:', error);
                $("#robot").fadeIn(300);
                $("#robot_balloon").html('通信错误,您是不是重复查询太快了?');
                $("#robot").animate({ opacity: 1 }, 500).fadeOut(1000); // 减少动画时间
                $('#subjectList').html('');
                setTimeout(async () => {
                    if (idx < item_list.length - 1) {
                        await ctd_findItemFunc(item_list, item_type, idx + 1);
                    } else {
                        $('#subjectList').empty();
                        $('#subjectList').show();
                        alert('全部添加完成,但部分查询出错');
                    }
                }, 1500); // 减少等待时间
            }
        };

        // 从ID范围中提取ID列表
        function getIDsFromRange(start, end) {
            const startID = parseInt(start, 10);
            const endID = parseInt(end, 10);
            if (isNaN(startID) || isNaN(endID) || startID > endID) {
                alert("ID范围无效");
                return [];
            }
            return Array.from({ length: endID - startID + 1 }, (_, i) => "bgm_id=" + (startID + i));
        }

        // 从自由文本中提取ID列表
        function getIDsFromText(input) {
            if (input.trim() === "") {
                alert("请输入ID或内容");
                return [];
            }

            // 先检查是否以bgm_id=开头
            if (input.indexOf("bgm_id=") === 0) {
                var idStr = input.substring(7);
                var ids = idStr.split(/[\s,,、\/]+/);
                return ids.map(function(id) {
                    return "bgm_id=" + id.trim();
                }).filter(function(x) { return x; });
            }

            // 检查是否包含纯数字或URL形式的ID
            const urlPattern = /(bgm\.tv|bangumi\.tv|chii\.in)\/(subject|character|person)\/(\d+)/g;
            const numberPattern = /\b\d+\b/g;

            let matches = [];
            let urlMatches = [];
            let match;

            // 提取URL中的ID
            while ((match = urlPattern.exec(input)) !== null) {
                urlMatches.push(match[3]); // ID部分
            }

            // 如果找到URL形式的ID,则优先使用
            if (urlMatches.length > 0) {
                return urlMatches.map(id => "bgm_id=" + id);
            }

            // 否则尝试提取纯数字ID
            while ((match = numberPattern.exec(input)) !== null) {
                matches.push(match[0]);
            }

            if (matches.length > 0) {
                return matches.map(id => "bgm_id=" + id);
            }

            // 如果没有找到任何ID形式,则按正常项目分隔
            return input.split(/['\,、,\/']/)
                .map(function(s) { return s.trim(); })
                .filter(function(s) { return s.length > 0; });
        }

        // 批量查找入口函数
        var chitanda_MultiFindItemFunc = async function() {
            let item_type = '1';
            let typeSelector = $('.chitanda_item_type select');
            if (typeSelector.length > 0) {
                item_type = typeSelector.val();
                if (item_type == '-999') {
                    alert('请先选择关联类型');
                    return false;
                }
                globalItemType = item_type;
            }

            let ctd_item_list = [];
            const activeTab = $('.tab-panel.active').attr('id');

            if (activeTab === 'tab-text') {
                // 处理文本输入模式
                const inputVal = $('#custom_ids').val().trim();
                ctd_item_list = getIDsFromText(inputVal);
            } else if (activeTab === 'tab-range') {
                // 处理ID范围模式
                const startID = $('#id_start').val().trim();
                const endID = $('#id_end').val().trim();
                ctd_item_list = getIDsFromRange(startID, endID);
            }

            if (ctd_item_list.length === 0) {
                return false;
            }

            $('#subjectList').hide();
            $('.chitanda_item_not_found').empty();
            $('.chitanda_item_dupe').empty();
            $('.chitanda_current_idx').text('0');
            $('.chitanda_all_num').text(ctd_item_list.length);
            currentProcessingIndex = -1;
            await ctd_findItemFunc(ctd_item_list, item_type, 0);
        };

        // 切换标签页
        function switchTab(tabId) {
            $('.tab-nav button').removeClass('active');
            $(`.tab-nav button[data-tab="${tabId}"]`).addClass('active');
            $('.tab-panel').removeClass('active');
            $(`#${tabId}`).addClass('active');
        }

        // 根据页面类型设定 UI 标题
        let uiTitle = '人物';
        const pageType = getCurrentPageType();
        if (pageType === 'character') {
            uiTitle = '角色';
        } else if (pageType === 'subject') {
            uiTitle = '条目';
        }

        // 创建改进的UI界面
        $('.subjectListWrapper').after(`
      <div class="chitanda_wrapper">
        <h3>批量关联助手</h3>
        <div class="tab-nav">
          <button data-tab="tab-text" class="active">自由文本输入</button>
          <button data-tab="tab-range">ID范围输入</button>
        </div>

        <div id="tab-text" class="tab-panel active">
          <textarea id="custom_ids" class="enhancer-textarea"
            placeholder="输入ID或网址(支持多种格式:纯数字ID、bgm_id=xxx格式、完整网址、名称文本,可用逗号、空格等分隔)"></textarea>
        </div>

        <div id="tab-range" class="tab-panel">
          <div class="flex-row" style="justify-content: center">
            <input id="id_start" type="number" placeholder="起始ID" class="input-number">
            <span style="line-height: 30px">~</span>
            <input id="id_end" type="number" placeholder="结束ID" class="input-number">
          </div>
        </div>

        <div class="chitanda_controls" style="margin-top: 10px">
          <span class="chitanda_item_type"></span>
          <button id="btn_ctd_multi_search" class="btnCustom">批量关联</button>
        </div>

        <div class="chitanda_progress">
          添加进度:<span class="chitanda_current_idx">0</span>/<span class="chitanda_all_num">0</span>
        </div>

        <div class="chitanda_header">未找到的${uiTitle}:</div>
        <div class="chitanda_item_not_found"></div>

        <div class="chitanda_header">存在多个结果的${uiTitle}(已自动选择第一个):</div>
        <div class="chitanda_item_dupe"></div>
      </div>
    `);

        // 添加关联类型选择器
        $('.chitanda_item_type').append(generateTypeSelector());
        $('.chitanda_item_type select').prepend('<option value="-999">请选择关联类型</option>').val('-999');

        // 绑定事件
        $('#btn_ctd_multi_search').on('click', chitanda_MultiFindItemFunc);
        $('.chitanda_item_type select').on('change', function() {
            globalItemType = $(this).val();
        });
        $('.tab-nav button').on('click', function() {
            switchTab($(this).data('tab'));
        });
    }

    /* 启动所有功能 */
    function startEnhancer() {
        // 启动超级增强器功能
        initNavButtons();
        observeURLChanges();
        initCoverUpload();
        // 启动批量分集编辑器功能
        BatchEpisodeEditor.init();

        console.log("Bangumi Ultimate Enhancer 已启动");
    }

    // 在DOM加载完成后启动脚本
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', startEnhancer);
    } else {
        startEnhancer();
    }
})();