Greasy Fork

Greasy Fork is available in English.

Bangumi Ultimate Enhancer

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Bangumi Ultimate Enhancer
// @namespace    https://tampermonkey.net/
// @version      2.4
// @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: flex-end;
        align-items: center;
        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: 14px;
        }

        .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|add_related|upload_img)/.test(location.pathname)) return;

        // 获取导航栏
        const nav = document.querySelector(".subjectNav .navTabs, .navTabs");
        if (!nav) return;

        // 匹配页面类型和ID
        const pathMatch = location.pathname.match(/\/(subject|person|character)\/(\d+)/);
        if (!pathMatch) return;

        const pageType = pathMatch[1];  // subject, person, or character
        const pageId = pathMatch[2];    // ID number

        // 添加Wiki按钮
        if (!nav.querySelector(".wiki-button")) {
            const wikiUrl = pageType === "subject"
            ? `${location.origin}/${pageType}/${pageId}/edit_detail`
            : `${location.origin}/${pageType}/${pageId}/edit`;

            const wikiLi = document.createElement("li");
            wikiLi.className = "wiki-button";
            wikiLi.innerHTML = `<a href="${wikiUrl}" target="_blank">Wiki</a>`;
            nav.appendChild(wikiLi);
        }

        // 添加关联按钮
        if (!nav.querySelector(".relate-button")) {
            const relateUrl = pageType === "subject"
            ? `${location.origin}/${pageType}/${pageId}/add_related/subject/anime`
            : `${location.origin}/${pageType}/${pageId}/add_related/anime`;

            const relateLi = document.createElement("li");
            relateLi.className = "relate-button";
            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() {
        // 检查当前页面类型
        const url = window.location.href;
        const subjectMatch = url.match(/bangumi\.tv\/subject\/(\d+)/);
        const personMatch = url.match(/bangumi\.tv\/person\/(\d+)/);
        const characterMatch = url.match(/bangumi\.tv\/character\/(\d+)/);

        if (!subjectMatch && !personMatch && !characterMatch) return;

        // 检查是否已添加上传模块
        if (document.querySelector("#coverUploadForm")) return;

        // 根据页面类型确定ID和类型
        let id, type;
        if (subjectMatch) {
            id = subjectMatch[1];
            type = "subject";
        } else if (personMatch) {
            id = personMatch[1];
            type = "person";
        } else {
            id = characterMatch[1];
            type = "character";
        }

        // **新逻辑:检查封面是否已存在**
        let coverExists = false;
        if (type === "subject") {
            coverExists = !!document.querySelector("#bangumiInfo img");
        } else {
            coverExists = !!document.querySelector(".infobox_container img") ||
                !!document.querySelector(".subject_sidebar img");
        }

        if (coverExists) {
            console.log("封面已存在,跳过上传模块");
            return;
        }

        // 为不同页面类型找到适当的插入位置
        let targetElement;
        if (type === "subject") {
            targetElement = document.querySelector("#bangumiInfo");
            if (!targetElement) 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);
                    targetElement.parentNode.insertBefore(container, targetElement);
                }
            } catch (e) {
                console.error("封面加载失败:", e);
            }
        } else {
            targetElement = document.querySelector(".subject_sidebar") ||
                document.querySelector(".infobox_container") ||
                document.querySelector(".infobox");

            if (!targetElement) return;

            try {
                // 直接获取上传页面
                const uploadUrl = `https://bangumi.tv/${type}/${id}/upload_img`;
                const res = await fetch(uploadUrl);
                const doc = new DOMParser().parseFromString(await res.text(), "text/html");

                const form = doc.querySelector("#columnInSubjectA .text form") ||
                      doc.querySelector(".text form") ||
                      doc.querySelector("form[enctype='multipart/form-data']");

                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);
                    targetElement.parentNode.insertBefore(container, targetElement);
                }
            } 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);
            });
        }

        // 处若搜索结果不唯一且没有完全匹配项则自动选择第一个
        function normalizeText(text) {
            return text.normalize("NFC").replace(/\s+/g, '').replace(/[\u200B-\u200D\uFEFF]/g, '').trim();
        }

        function extractTextFromElement(el) {
            if (!el) return '';

            let text = el.innerText || el.textContent || $(el).text();

            // 尝试从 `iframe` 和 `shadowRoot` 获取文本
            if (!text.trim()) {
                if (el.shadowRoot) {
                    text = [...el.shadowRoot.querySelectorAll('*')].map(e => e.textContent).join('');
                }
                let iframe = el.querySelector('iframe');
                if (iframe && iframe.contentDocument) {
                    text = iframe.contentDocument.body.textContent;
                }
            }

            return normalizeText(text);
        }

        async function processSingleItem(elements, item_type, search_name) {
            return new Promise(async (resolve) => {
                if (elements.length === 0) {
                    $('.chitanda_item_not_found').append(search_name + ' ');
                    resolve(false);
                    return;
                }

                let elementsArray = elements.toArray();
                let normalizedSearchName = normalizeText(search_name);

                console.log("搜索名(规范化):", normalizedSearchName);

                // 等待元素加载,避免空文本
                await new Promise(res => setTimeout(res, 500));

                let selectedElement = elementsArray.find(el => {
                    let normalizedElementText = extractTextFromElement(el);
                    console.log("元素文本(规范化):", normalizedElementText); // 调试用
                    return normalizedElementText === normalizedSearchName;
                });

                if (!selectedElement) {
                    if (elements.length > 1) {
                        $('.chitanda_item_dupe').append(`${search_name} `);
                    }
                    selectedElement = elements[0]; // 没有完全匹配,取第一个
                }

                resolve(await processItem(selectedElement, item_type));
            });
        }

        // 处理下一个项目
        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');
                    if (window.location.pathname.includes('/person/') && window.location.pathname.includes('/add_related/character/anime')) {
                        if (elements.length === 0) {
                            $('.chitanda_item_not_found').append(search_name + ' ');
                        } else {
                            $(elements[0]).click();
                            if (elements.length > 1) {
                                $('.chitanda_item_dupe').append(`${search_name} `);
                            }
                        }

                        $('.chitanda_current_idx').text(idx + 1);

                        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);
                        }
                    } else {
                        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.startsWith("bgm_id=")) {
                return input.substring(7)
                    .split(/[,\n\r,、\/|;。.()【】<>!?]+| +/)
                    .map(id => "bgm_id=" + id.trim())
                    .filter(id => id);
            }

            // 识别 URL 形式的 ID
            const urlPattern = /(bgm\.tv|bangumi\.tv|chii\.in)\/(subject|character|person)\/(\d+)/g;
            const urlMatches = [...input.matchAll(urlPattern)].map(m => m[3]);

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

            // 识别纯数字 ID
            const numberPattern = /\b\d+\b/g;
            const numberMatches = [...input.matchAll(numberPattern)].map(m => m[0]);

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

            // 默认按符号分隔(空格优先级最低)
            return input.split(/[,\n\r,、\/|;。.()【】<>!?]+/)  // 先按主要分隔符拆分
                .map(part => part.trim())  // 去除前后空格
                .filter(part => part.length > 0)  // 过滤掉空字符串
                .flatMap(part => {
                // 保持以下格式不被拆分:
                // 1. "第3季"、"第2集"
                // 2. "Ⅱ", "Ⅲ", "Ⅳ"(罗马数字)
                // 3. "鬼灭之刃 3" 这种名字+数字
                return /^(第?\d+|[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]+)$/.test(part) || /\D+\d+$/.test(part) ? [part] : part.split(/ +/);
            });

        }

        // 批量查找入口函数
        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或网址(支持多种格式:纯数字、bgm_id=xx、网址、文本,可用各类符号(空格优先级最低)分隔)"></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();
    }
})();