Greasy Fork

Greasy Fork is available in English.

Bangumi Ultimate Enhancer

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

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

// ==UserScript==
// @name         Bangumi Ultimate Enhancer
// @namespace    https://tampermonkey.net/
// @version      2.5.2
// @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/*
// @exclude      */character/*/add_related/person*
// @exclude      */person/*/add_related/character*
// @connect      bgm.tv
// @icon         https://lain.bgm.tv/pic/icon/l/000/00/01/128.jpg
// @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";

    // 样式注入
    function injectStyles() {
        $('head').append(`
      <style>
      /* 统一色彩变量 - 定义整个样式表使用的全局CSS变量 */
        :root {
            --primary-color: #369CF8;        /* 主要颜色,蓝色 */
            --primary-light: rgba(74, 144, 226, 0.1);  /* 主要颜色的浅色版本,透明度10% */
            --primary-hover: #3A80D2;        /* 鼠标悬停时的主要颜色,深蓝色 */
            --text-primary: #333333;         /* 主要文本颜色,深灰色 */
            --text-secondary: #666666;       /* 次要文本颜色,中灰色 */
            --border-color: #E0E0E0;         /* 边框颜色,浅灰色 */
            --background-light: #F9F9F9;     /* 浅色背景,几乎白色 */
            --shadow-soft: 0 4px 10px rgba(0, 0, 0, 0.05);  /* 柔和阴影效果 */
            --shadow-hover: 0 6px 14px rgba(0, 0, 0, 0.1);  /* 鼠标悬停时的阴影效果,更明显 */
            --success-color: #42B983;        /* 成功状态颜色,绿色 */
            --warning-color: #E6A23C;        /* 警告状态颜色,橙色 */
            --danger-color: #F56C6C;         /* 危险/错误状态颜色,红色 */
            --info-color: #4A90E2;           /* 信息状态颜色,与主要颜色相同的蓝色 */
            --new-color: #F09199;            /* 新增粉色变量 */
            --border-radius: 8px;            /* 统一的边框圆角大小 */
            --transition-normal: all 0.1s cubic-bezier(0.4, 0, 0.2, 1);  /* 标准过渡动画效果 */
        }

        /* 通用按钮美化 - 定义自定义按钮样式 */
        .btnCustom {
            margin: 5px 0;                   /* 上下外边距5px,左右0 */
            background-color: var(--primary-color) !important;  /* 使用主要颜色作为背景,强制覆盖 */
            color: white !important;         /* 文字颜色为白色,强制覆盖 */
            border-radius: var(--border-radius) !important;     /* 使用统一的圆角大小,强制覆盖 */
            padding: 8px 16px !important;    /* 内边距:上下8px,左右16px,强制覆盖 */
            border: none !important;         /* 移除边框,强制覆盖 */
            cursor: pointer !important;      /* 鼠标悬停时显示手指图标,强制覆盖 */
            font-size: 14px;                 /* 文字大小14像素 */
            font-weight: 600;                /* 字体粗细程度,半粗体 */
            text-align: center;              /* 文字居中对齐 */
            display: flex;                   /* 使用弹性布局 */
            justify-content: center;         /* 水平居中对齐 */
            align-items: center;             /* 垂直居中对齐 */
            transition: var(--transition-normal);  /* 应用统一的过渡动画 */
            box-shadow: var(--shadow-soft);  /* 应用柔和阴影效果 */
        }
        .btnCustom:hover {
            background-color: var(--primary-hover) !important;  /* 鼠标悬停时背景色变深,强制覆盖 */
            transform: translateY(-1px);      /* 鼠标悬停时按钮上移1像素,产生悬浮效果 */
            box-shadow: var(--shadow-hover);  /* 鼠标悬停时阴影效果更明显 */
        }

        /* 文本域美化 - 美化多行文本输入框 */
        .enhancer-textarea {
            width: 100%;                     /* 宽度占满父容器 */
            min-height: 80px;                /* 最小高度80像素 */
            max-height: 300px;               /* 最大高度300像素 */
            border: 1px solid var(--border-color);  /* 1像素实线边框,使用统一边框颜色 */
            border-radius: var(--border-radius);    /* 使用统一的圆角大小 */
            padding: 12px;                   /* 四周内边距12像素 */
            margin: 10px 0;                  /* 上下外边距10像素,左右0 */
            resize: vertical;                /* 仅允许垂直方向调整大小 */
            font-size: 14px;                 /* 文字大小14像素 */
            box-sizing: border-box;          /* 盒模型计算方式:边框和内边距包含在宽高中 */
            background: white;               /* 背景色为白色 */
            transition: var(--transition-normal);   /* 应用统一的过渡动画 */
        }
        .enhancer-textarea:focus {
            border-color: var(--primary-color);  /* 获得焦点时边框颜色变为主要颜色 */
            box-shadow: 0 0 0 3px var(--primary-light);  /* 获得焦点时添加主要颜色的浅色阴影 */
            outline: none;                       /* 移除默认的焦点轮廓 */
        }

        /* 数字输入框 - 美化数字类型的输入框 */
        .input-number {
            width: 100px;                    /* 宽度100像素 */
            height: 10px;                    /* 高度10像素 */
            padding: 10px;                   /* 四周内边距10像素 */
            border-radius: var(--border-radius);    /* 使用统一的圆角大小 */
            border: 1px solid var(--border-color);  /* 1像素实线边框,使用统一边框颜色 */
            background: white;               /* 背景色为白色 */
            transition: var(--transition-normal);   /* 应用统一的过渡动画 */
            font-size: 14px;                 /* 文字大小14像素 */
        }
        .input-number:focus {
            border-color: var(--primary-color);     /* 获得焦点时边框颜色变为主要颜色 */
            box-shadow: 0 0 0 3px var(--primary-light);  /* 获得焦点时添加主要颜色的浅色阴影 */
            outline: none;                   /* 移除默认的焦点轮廓 */
        }

        /* Relation 相关样式 - 特定组件的样式定义 */
        .Relation_wrapper {
            width: 280px;                    /* 宽度280像素 */
            margin: 18px 0;                  /* 上下间距18px,左右0 */
            text-align: center;              /* 文字居中对齐 */
            background: white;               /* 背景色为白色 */
            padding: 18px;                   /* 四周内边距18像素 */
            border-radius: var(--border-radius);    /* 使用统一的圆角大小 */
            border: 1px solid var(--border-color);  /* 1像素实线边框,使用统一边框颜色 */
            box-shadow: var(--shadow-soft);  /* 应用柔和阴影效果 */
            transition: var(--transition-normal);   /* 应用统一的过渡动画 */
        }
        .Relation_wrapper:hover {
            box-shadow: var(--shadow-hover); /* 鼠标悬停时阴影效果更明显 */
        }
        .select-label {
            display: flex;                   /* 使用弹性布局 */
            margin-right: auto;              /* 右侧外边距自动(将内容推到左侧) */
            font-weight: 600;                /* 字体粗细程度,半粗体 */
            color: var(--text-secondary);    /* 使用次要文本颜色 */
        }
        .Relation_item_type {
            display: flex;                   /* 使用弹性布局 */
            margin-right: auto;              /* 右侧外边距自动(将内容推到左侧) */
            align-items: center;             /* 垂直居中对齐 */
        }
        .Relation_progress {
            margin: 14px 0;                  /* 上下外边距14像素,左右0 */
            color: var(--primary-color);     /* 使用主要颜色 */
            font-weight: 600;                /* 字体粗细程度,半粗体 */
            font-size: 18px;                 /* 文字大小18像素 */
            text-align: center;              /* 文字居中对齐 */
        }

        /* 状态提示区域 - 用于显示不同状态的提示信息 */
        .Relation_item_not_found,
        .Relation_item_dupe {
            margin-top: 10px;                /* 顶部外边距10像素 */
            padding: 12px;                   /* 四周内边距12像素 */
            min-height: 15px;                /* 最小高度15像素 */
            border-radius: var(--border-radius);   /* 使用统一的圆角大小 */
            font-size: 14px;                 /* 文字大小14像素 */
            transition: var(--transition-normal);  /* 应用统一的过渡动画 */
        }
        .Relation_item_not_found {
            color: var(--danger-color);      /* 文字颜色使用危险/错误状态颜色 */
            background: rgba(245, 108, 108, 0.1);  /* 浅红色背景,10%透明度 */
            border: 1px solid rgba(245, 108, 108, 0.2);  /* 红色边框,20%透明度 */
        }
        .Relation_item_dupe {
            color: var(--info-color);        /* 文字颜色使用信息状态颜色 */
            background: rgba(74, 144, 226, 0.1);   /* 浅蓝色背景,10%透明度 */
            border: 1px solid rgba(74, 144, 226, 0.2);   /* 蓝色边框,20%透明度 */
        }
        .Relation_header {
            font-size: 17px;                 /* 文字大小17像素 */
            margin: 14px 0 8px;              /* 上边距14像素,左右0,下边距8像素 */
            color: var(--text-primary);      /* 使用主要文本颜色 */
            font-weight: 600;                /* 字体粗细程度,半粗体 */
            text-align: left;                /* 内容靠左对齐 */
        }
        .Relation_controls {
            display: flex;                   /* 使用弹性布局 */
            justify-content: flex-end;       /* 内容靠右对齐 */
            align-items: center;             /* 垂直居中对齐 */
            gap: 10px;                       /* 元素之间的间距10像素 */
        }

        /* 布局辅助 - 提供辅助布局的通用类 */
        .flex-row {
            display: flex;                   /* 使用弹性布局 */
            gap: 14px;                       /* 元素之间的间距14像素 */
            align-items: center;             /* 垂直居中对齐 */
        }

        /* 标签页导航 - 定义标签页导航栏样式 */
        .tab-nav {
            display: flex;                   /* 使用弹性布局 */
            justify-content: center;         /* 水平居中对齐 */
            border-bottom: 2px solid var(--border-color);  /* 底部2像素实线边框,使用统一边框颜色 */
            margin-bottom: 18px;             /* 底部外边距18像素 */
        }
        .tab-nav button {
            background: none;                /* 移除背景色 */
            border: none;                    /* 移除边框 */
            padding: 12px 24px;              /* 内边距:上下12px,左右24px */
            cursor: pointer;                 /* 鼠标悬停时显示手指图标 */
            font-size: 14px;                 /* 文字大小14像素 */
            font-weight: 600;                /* 字体粗细程度,半粗体 */
            color: var(--text-secondary);    /* 使用次要文本颜色 */
            border-bottom: 3px solid transparent;  /* 底部3像素透明边框,为激活状态做准备 */
            transition: var(--transition-normal);  /* 应用统一的过渡动画 */
        }
        .tab-nav button.active {
            border-bottom: 3px solid var(--new-color);  /* 当前活动标签底部边框使用主要颜色 */
            color: var(--new-color);     /* 当前活动标签文字颜色使用主要颜色 */
        }
        .tab-nav button:hover {
            color: var(--primary-color);     /* 鼠标悬停时文字颜色使用主要颜色 */
            border-bottom: 3px solid var(--primary-color);  /* 底部2像素实线边框,使用统一边框颜色 */
        }

        /* 标签页内容 - 定义标签页内容区域样式 */
        .tab-panel {
            display: none;                   /* 默认隐藏所有标签页内容 */
        }
        .tab-panel.active {
            display: block;                  /* 显示当前活动的标签页内容 */
            animation: var(--transition-normal); /* 应用统一的过渡动画 */
        }
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(8px); }  /* 动画起始状态:透明度0,向下偏移8像素 */
            to { opacity: 1; transform: translateY(0); }      /* 动画结束状态:透明度1,无偏移 */
        }

        /* 封面上传功能 - 定义封面上传相关元素样式 */
        #coverUploadForm {
            display: flex;                    /* 使用弹性布局 */
            flex-direction: column;           /* 垂直排列子元素 */
            align-items: center;              /* 水平居中对齐 */
        }
        #coverUploadForm input[type="file"] {
            border: 2px dashed var(--border-color);  /* 2像素虚线边框,使用统一边框颜色 */
            border-radius: var(--border-radius);     /* 使用统一的圆角大小 */
            padding: 10px;                    /* 四周内边距10像素 */
            width: 100%;                      /* 宽度占满父容器 */
            background: var(--background-light);     /* 使用浅色背景 */
            box-sizing: border-box;           /* 确保padding不会增加实际宽度 */
            cursor: pointer;                  /* 鼠标悬停时显示手指图标 */
            transition: var(--transition-normal);    /* 应用统一的过渡动画 */
        }
        #coverUploadForm input[type="file"]:hover {
            border-color: var(--primary-color);  /* 鼠标悬停时边框颜色变为主要颜色 */
            background: var(--primary-light);    /* 鼠标悬停时背景色变为主要颜色的浅色版本 */
        }
        #coverUploadForm input[type="submit"] {
            background: var(--primary-color);  /* 背景色使用主要颜色 */
            color: white;                      /* 文字颜色为白色 */
            border: none;                      /* 移除边框 */
            border-radius: 20px;               /* 边框圆角20像素,使按钮更圆润 */
            padding: 8px 16px;                 /* 内边距:上下8px,左右16px */
            margin-top: 8px;                   /* 顶部外边距8像素 */
            cursor: pointer;                   /* 鼠标悬停时显示手指图标 */
            font-weight: 600;                  /* 字体粗细程度,半粗体 */
            transition: var(--transition-normal);    /* 应用统一的过渡动画 */
            box-shadow: var(--shadow-soft);    /* 应用柔和阴影效果 */
            position: relative;                /* 开启相对定位 */
            top: -15px;                        /* 向上移动15px */
        }
        #coverUploadForm input[type="submit"]:hover {
            background: var(--primary-hover);  /* 鼠标悬停时背景色变深 */
            transform: translateY(-1px);       /* 鼠标悬停时按钮上移1像素,产生悬浮效果 */
            box-shadow: var(--shadow-hover);   /* 鼠标悬停时阴影效果更明显 */
        }

        /* 封面上传模态框的整体容器样式 */
        .cover-upload-modal {
            display: none;                   /* 默认隐藏模态框 */
            position: fixed;                 /* 固定定位,相对于视窗 */
            z-index: 1000;                   /* 设置高层级,确保在其他元素上方 */
            background-color: white;          /* 使用白色背景 */
            border: 1px solid var(--border-color);  /* 使用全局定义的边框颜色 */
            border-radius: var(--border-radius);    /* 使用全局定义的圆角大小 */
            padding: 15px;                   /* 内部内容的内边距 */
            box-shadow: var(--shadow-soft);  /* 使用全局定义的柔和阴影 */
            width: 240px;                    /* 固定宽度 */
            max-width: 100%;                 /* 最大宽度不超过父容器 */
            transition: var(--transition-normal);   /* 使用全局定义的过渡动画 */
        }

        /* 鼠标悬停在模态框上时的阴影效果 */
        .cover-upload-modal:hover {
            box-shadow: var(--shadow-hover);  /* 使用全局定义的悬停阴影效果 */
        }

        /* 上传区域的整体布局 */
        .upload-section {
            display: flex;                   /* 使用弹性布局 */
            flex-direction: column;          /* 垂直方向排列子元素 */
        }

        /* URL输入框和按钮的容器 */
        .url-input-container {
            display: flex;                   /* 使用弹性布局 */
            margin-bottom: 10px;             /* 底部外边距,与下一个元素间隔 */
            height: 30px;                    /* 固定高度 */
        }

        /* URL输入框样式 */
        .image-url-input {
            flex-grow: 1;                    /* 允许输入框自动填充剩余空间 */
            padding: 8px;                    /* 内部内边距 */
            margin-right: 10px;              /* 右侧外边距,与按钮间隔 */
            border: 1px solid var(--border-color);  /* 使用全局定义的边框颜色 */
            border-radius: var(--border-radius);    /* 使用全局定义的圆角大小 */
            outline: none;                   /* 移除默认聚焦轮廓 */
            transition: var(--transition-normal);   /* 使用全局定义的过渡动画 */
            font-size: 14px;                 /* 文字大小 */
        }

        /* URL输入框获得焦点时的样式 */
        .image-url-input:focus {
            border-color: var(--primary-color);     /* 边框颜色变为主要颜色 */
            box-shadow: 0 0 0 3px var(--primary-light);  /* 添加主要颜色的浅色阴影 */
        }

        /* 下载按钮样式 */
        .download-url-button {
            padding: 8px 16px;               /* 内部内边距 */
            background-color: var(--new-color);     /* 使用全局定义的新增粉色 */
            color: white;                    /* 白色文字 */
            border: none;                    /* 移除边框 */
            border-radius: var(--border-radius);    /* 使用全局定义的圆角大小 */
            cursor: pointer;                 /* 鼠标悬停显示手型 */
            font-weight: 600;                /* 半粗体文字 */
            transition: var(--transition-normal);   /* 使用全局定义的过渡动画 */
            box-shadow: var(--shadow-soft);  /* 使用全局定义的柔和阴影 */
            display: flex;                   /* 让按钮使用弹性布局 */
            align-items: center;             /* 让文本垂直居中 */
            justify-content: center;         /* 让文本水平居中 */
            height: 100%;                    /* 确保按钮内部填充满 */
        }

        /* 下载按钮鼠标悬停时的样式 */
        .download-url-button:hover {
            background-color: color-mix(in srgb, var(--new-color) 90%, black);  /* 颜色略微变暗 */
            transform: translateY(-1px);     /* 稍微上移,产生悬浮效果 */
            box-shadow: var(--shadow-hover); /* 使用全局定义的悬停阴影 */
        }

        /* 上传表单容器 */
        .upload-form-container {
            margin-top: 10px;                /* 顶部外边距 */
        }

        /* 图片预览容器 */
        .image-preview-container {
            margin-top: 10px;                /* 顶部外边距 */
            display: none;                   /* 默认隐藏 */
            text-align: center;              /* 内容居中 */
        }

        /* 图片预览样式 */
        .image-preview {
            max-width: 100%;                 /* 最大宽度不超过容器 */
            max-height: 300px;               /* 最大高度限制 */
            object-fit: contain;             /* 保持图片比例 */
            border: 1px solid var(--border-color);  /* 使用全局定义的边框颜色 */
            border-radius: var(--border-radius);    /* 使用全局定义的圆角大小 */
        }
      </style>
    `);
    }

    /* ====================
    Wiki 按钮和关联按钮模块
    ======================*/
    function initNavButtons() {
        // 排除特定编辑页面
        const EXCLUDE_PATHS = /(edit_detail|edit|add_related|upload_img)/;
        if (EXCLUDE_PATHS.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, pageId] = pathMatch;
        const origin = location.origin;

        // 按钮配置
        const buttons = [
            {
                className: "wiki-button",
                getText: () => "Wiki",
                getUrl: () => pageType === "subject"
                ? `${origin}/${pageType}/${pageId}/edit_detail`
                : `${origin}/${pageType}/${pageId}/edit`
            },
            {
                className: "relate-button",
                getText: () => "关联",
                getUrl: () => pageType === "subject"
                ? `${origin}/${pageType}/${pageId}/add_related/subject/anime`
                : `${origin}/${pageType}/${pageId}/add_related/anime`
            }
        ];

        // 添加按钮
        buttons.forEach(button => {
            if (!nav.querySelector(`.${button.className}`)) {
                const li = document.createElement("li");
                li.className = button.className;
                li.innerHTML = `<a href="${button.getUrl()}" target="_blank">${button.getText()}</a>`;
                nav.appendChild(li);
            }
        });
    }

    // 监听 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 SUPPORTED_DOMAINS = ['bangumi\\.tv', 'bgm\\.tv', 'chii\\.in'];
        const EXCLUDE_PATHS = /\/(edit_detail|edit|add_related|upload_img)$/;

        // 如果是编辑页面,则不执行
        if (EXCLUDE_PATHS.test(location.pathname)) return;

        // 解析页面类型和 ID
        const url = window.location.href;
        const parseId = (path) => {
            const regex = new RegExp(`(${SUPPORTED_DOMAINS.join('|')})\\/${path}\\/(\\d+)`);
            const match = url.match(regex);
            return match ? { id: match[2], type: path } : null;
        };

        const typeMapping = ['subject', 'person', 'character'];
        const parsedInfo = typeMapping.reduce((result, type) => result || parseId(type), null);
        if (!parsedInfo) return;

        // 避免重复添加按钮
        if (document.querySelector("#coverUploadButton")) return;

        // 获取导航栏(兼容多个模板)
        const nav = document.querySelector(".subjectNav .navTabs") || document.querySelector(".navTabs");
        if (!nav) return;

        // 创建上传按钮(保持原有UI设计)
        const createUploadButton = () => {
            const uploadLi = document.createElement("li");
            uploadLi.id = "coverUploadButton";
            uploadLi.className = "upload-button";
            uploadLi.style.float = "right";
            uploadLi.innerHTML = `<a href="javascript:void(0);" style="padding: 10px 10px 9px;">上传封面</a>`;
            return uploadLi;
        };

        // 创建表单容器,保持原有 UI 设计不变
        const createFormContainer = () => {
            const formContainer = document.createElement("div");
            formContainer.id = "coverUploadFormContainer";
            formContainer.classList.add("cover-upload-modal");
            // 保持原设计布局:URL 上传输入框、异步加载的本地上传表单、预览区域均按原有样式呈现
            formContainer.innerHTML = `
      <div class="upload-section">
        <div class="url-input-container">
          <input
            type="text"
            id="imageUrlInput"
            class="image-url-input"
            placeholder="输入图片 URL"
          >
          <button
            id="downloadUrlButton"
            class="download-url-button"
          >下载</button>
        </div>
        <div id="uploadFormContainer" class="upload-form-container"></div>
        <div id="imagePreviewContainer" class="image-preview-container">
          <img
            id="imagePreview"
            class="image-preview"
            alt="图片预览"
          >
        </div>
      </div>
    `;
            // 设置必要的定位和初始隐藏,保持原来的样式控制由外部CSS决定
            formContainer.style.position = "absolute";
            formContainer.style.zIndex = "9999";
            formContainer.style.display = "none";
            return formContainer;
        };

        const uploadLi = createUploadButton();
        const formContainer = createFormContainer();
        nav.appendChild(uploadLi);
        document.body.appendChild(formContainer);

        let formLoaded = false; // 标记本地上传表单是否加载完毕
        let hideTimeout = null;

        // 图片下载和转换函数
        async function downloadAndConvertImage(imageUrl) {
            try {
                // 尝试提取实际的图片 URL
                let actualImageUrl = imageUrl;

                // 检查是否是 Google 图片重定向链接
                if (imageUrl.includes('google.com/imgres')) {
                    const urlParams = new URL(imageUrl).searchParams;
                    actualImageUrl = urlParams.get('imgurl');
                }

                // 如果没有提取到图片 URL,则使用原始链接
                if (!actualImageUrl) {
                    actualImageUrl = imageUrl;
                }

                // 发送请求下载图片
                const response = await fetch(actualImageUrl);
                const blob = await response.blob();

                // 根据输入图片的类型确定输出格式
                const inputType = blob.type;
                const outputType = inputType.includes('jpeg') || inputType.includes('jpg')
                ? 'image/jpeg'
                : inputType.includes('png')
                ? 'image/png'
                : 'image/jpeg'; // 如果类型未知,默认转换为 JPEG

                // 创建画布用于图像转换
                const img = await createImageBitmap(blob);
                const canvas = document.createElement('canvas');
                canvas.width = img.width;
                canvas.height = img.height;
                const ctx = canvas.getContext('2d');
                ctx.drawImage(img, 0, 0);

                // 将图像转换为指定格式的 Blob
                const convertedBlob = await new Promise(resolve => {
                    canvas.toBlob(resolve, outputType);
                });

                // 根据转换后的格式确定文件扩展名
                const fileExtension = outputType === 'image/png' ? 'png' : 'jpg';
                const convertedFile = new File([convertedBlob], `cover.${fileExtension}`, { type: outputType });

                // 预览转换后的图片
                const previewContainer = document.querySelector("#imagePreviewContainer");
                const previewImage = document.querySelector("#imagePreview");
                previewImage.src = URL.createObjectURL(convertedBlob);
                previewContainer.style.display = "block";

                // 查找文件上传表单
                const fileInput = document.querySelector("#coverUploadForm input[type='file']");
                if (fileInput) {
                    // 创建 DataTransfer 对象并填充文件
                    const dataTransfer = new DataTransfer();
                    dataTransfer.items.add(convertedFile);
                    fileInput.files = dataTransfer.files;

                    // 触发文件上传输入框的 change 事件
                    const event = new Event('change', { bubbles: true });
                    fileInput.dispatchEvent(event);

                    // 显示提交按钮
                    const submitButton = document.querySelector("#coverUploadForm input[type='submit']");
                    if (submitButton) {
                        submitButton.style.display = 'block';
                    }
                } else {
                    alert('未找到文件上传输入框');
                }
            } catch (error) {
                console.error('下载或转换图片时发生错误:', error);
                alert('下载图片失败:' + error.message);
            }
        }

        // 新增的全局点击事件,点击表单容器外区域关闭并取消表单容器
        function setupGlobalClickHandler(container, trigger) {
            document.addEventListener('click', function (event) {
                if (!container.contains(event.target) && !trigger.contains(event.target)) {
                    container.style.display = "none";
                }
            });
        }

        // 预先加载本地上传表单,提升加载速度
        async function preloadLocalUpload() {
            if (formLoaded) return;
            const uploadFormContainer = formContainer.querySelector("#uploadFormContainer");
            uploadFormContainer.innerHTML = "加载中...";
            try {
                const uploadUrl = `https://${window.location.host}/${parsedInfo.type}/${parsedInfo.id}/upload_img`;
                const res = await fetch(uploadUrl);
                const doc = new DOMParser().parseFromString(await res.text(), "text/html");
                const form = doc.querySelector("form[enctype='multipart/form-data']");
                if (form) {
                    form.id = "coverUploadForm";
                    form.style.margin = "0";
                    form.style.padding = "0";
                    uploadFormContainer.innerHTML = form.outerHTML;
                    // 为本地文件上传绑定预览处理事件
                    const fileInput = document.querySelector("#coverUploadForm input[type='file']");
                    fileInput.addEventListener('change', (e) => {
                        const file = e.target.files[0];
                        if (file) {
                            const reader = new FileReader();
                            reader.onload = (ev) => {
                                const previewContainer = formContainer.querySelector("#imagePreviewContainer");
                                const previewImage = formContainer.querySelector("#imagePreview");
                                previewImage.src = ev.target.result;
                                previewContainer.style.display = "block";
                                const submitButton = document.querySelector("#coverUploadForm input[type='submit']");
                                if (submitButton) {
                                    submitButton.style.display = 'block';
                                }
                            };
                            reader.readAsDataURL(file);
                        }
                    });
                    formLoaded = true;
                } else {
                    uploadFormContainer.innerHTML = "无法加载上传表单";
                }
            } catch (e) {
                uploadFormContainer.innerHTML = "加载失败";
                console.error("上传模块加载失败:", e);
            }
        }

        // 事件绑定逻辑,保持原有UI展示行为
        const setupEventHandlers = () => {
            const urlInput = formContainer.querySelector("#imageUrlInput");
            const downloadButton = formContainer.querySelector("#downloadUrlButton");

            // 显示表单函数
            const showForm = () => {
                clearTimeout(hideTimeout);
                const buttonRect = uploadLi.getBoundingClientRect();
                formContainer.style.top = `${buttonRect.bottom + window.scrollY + 5}px`;
                formContainer.style.left = `${buttonRect.left + window.scrollX - 180}px`;
                formContainer.style.display = "block";
            };

            // 延迟隐藏表单逻辑
            const hideForm = () => {
                const previewContainer = formContainer.querySelector("#imagePreviewContainer");
                if (previewContainer.style.display === "block") return;
                hideTimeout = setTimeout(() => {
                    if (!formContainer.matches(":hover")) {
                        formContainer.style.display = "none";
                    }
                }, 200);
            };

            uploadLi.addEventListener("mouseenter", showForm);
            uploadLi.addEventListener("mouseleave", hideForm);
            formContainer.addEventListener("mouseenter", () => clearTimeout(hideTimeout));
            formContainer.addEventListener("mouseleave", hideForm);

            // URL上传部分事件处理
            urlInput.addEventListener('focus', () => {
                urlInput.style.borderColor = '#F4C7CC';
                urlInput.style.boxShadow = '0 0 5px rgba(244, 199, 204, 0.5)';
            });
            urlInput.addEventListener('blur', () => {
                urlInput.style.borderColor = '#ddd';
                urlInput.style.boxShadow = 'none';
            });
            downloadButton.addEventListener('click', () => {
                const imageUrl = urlInput.value.trim();
                if (imageUrl) {
                    downloadAndConvertImage(imageUrl);
                } else {
                    alert('请输入图片 URL');
                }
            });
            urlInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') downloadButton.click();
            });
        };

        // 注册全局点击事件(点击容器外关闭表单)
        setupGlobalClickHandler(formContainer, uploadLi);
        // 预先加载本地上传表单(在页面初始加载时即启动)
        preloadLocalUpload();
        // 事件处理绑定
        setupEventHandlers();
    }

    /* MutationObserver 保证上传按钮始终存在 */
    const observer = new MutationObserver(() => {
        if (!document.querySelector("#coverUploadButton")) {
            initCoverUpload();
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    /* ====================
     批量分集编辑器功能模块
     =====================*/
    const BatchEpisodeEditor = {
        // 常量配置
        CHUNK_SIZE: 20,
        BASE_URL: '',
        CSRF_TOKEN: '',

        // 初始化入口
        init() {
            // 非分集编辑页面直接返回
            if (!this.isEpisodePage()) return;

            // 获取基础URL和CSRF令牌
            this.BASE_URL = location.pathname.replace(/\/edit_batch$/, '');
            this.CSRF_TOKEN = $('[name=formhash]')?.value || '';
            if (!this.CSRF_TOKEN) return;

            // 绑定事件和增强功能
            this.bindHashChange();
            this.upgradeCheckboxes();
            this.addEnhancerNotice();
        },

        // 添加功能增强提示
        addEnhancerNotice() {
            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: () => /^\/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() {
            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({
                chkall: 'on',
                submit: '批量修改',
                formhash: this.CSRF_TOKEN,
                'ep_mod[]': episodeIds
            });

            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({
                formhash: this.CSRF_TOKEN,
                rev_version: '0',
                editSummary: $('#editSummary')?.value || '',
                ep_ids: episodeIds.join(','),
                ep_list: chunkData.join('\n'),
                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)
            );
        }
    };

    /* ====================
    批量关联 - 用于关联条目
    ======================*/
    function initBatchRelation() {
        injectStyles();

        // 参数配置
        const DELAY_AFTER_CLICK = 150;
        const DELAY_BETWEEN_ITEMS = 300;
        const MAX_RETRY_ATTEMPTS = 10;
        const RETRY_INTERVAL = 100;

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

        // 根据当前 URL 判断页面类型(支持排除特定路径)
        function getCurrentPageType() {
            const path = window.location.pathname;
            // 调整正则表达式优先级,先检查更具体的路径
            if (/^\/(?:subject\/\d+\/add_related\/character|character\/\d+\/add_related\/)/.test(path)) {
                return 'character';
            }
            else if (/^\/subject\/\d+\/add_related\//.test(path)) {
                return 'subject';
            } else {
                return 'person';
            }
        }

        // 增强版下拉框生成
        function generateTypeSelector() {
            const pageType = getCurrentPageType();

            // 公共选项生成逻辑
            const generateOptions = (types) => {
                return Object.entries(types)
                    .map(([value, text]) => `<option value="${value}">${text}</option>`)
                    .join('');
            };
            switch(pageType) {
                case 'character':
                    return `<span class="select-label">类型: </span>
                    <select>${generateOptions({
                        '1': '主角',
                        '2': '配角',
                        '3': '客串'
                    })}</select>`;
                default:
                    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) {
                    $('.Relation_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) {
                        $('.Relation_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);
                    $('.Relation_current_idx').text(idx + 1);
                    $('.Relation_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) {
                            $('.Relation_item_not_found').append(search_name + ' ');
                        } else {
                            $(elements[0]).click();
                            if (elements.length > 1) {
                                $('.Relation_item_dupe').append(`${search_name} `);
                            }
                        }

                        $('.Relation_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); // 减少动画时间
                    $('.Relation_item_not_found').append(search_name + ' ');
                    $('#subjectList').html(html);
                    $('.Relation_current_idx').text(idx + 1);
                    $('.Relation_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分隔和准确搜索
        function parsePersonInput(input) {
            input = input.trim();
            // 支持URL格式
            const urlMatch = input.match(/(?:bgm\.tv|bangumi\.tv|chii\.in)\/(?:person|character|subject)\/(\d+)/i);
            if (urlMatch) return urlMatch[1];
            // 提取纯数字ID - 每次只返回一个ID
            const numberMatch = input.match(/^\d+$/);
            if (numberMatch) return numberMatch[0];
            // 支持姓名直接搜索
            if (/^[\u4e00-\u9fa5a-zA-Z0-9\s]+$/.test(input)) {
                return encodeURIComponent(input);
            }
            return input; // 如果无法识别,返回原始输入
        }

        // 从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));
        }

        const numberMap = {
            '0': '零', '1': '一', '2': '二', '3': '三', '4': '四', '5': '五',
            '6': '六', '7': '七', '8': '八', '9': '九', '10': '十',
            'Ⅰ': '一', 'Ⅱ': '二', 'Ⅲ': '三', 'Ⅳ': '四', 'Ⅴ': '五',
            'Ⅵ': '六', 'Ⅶ': '七', 'Ⅷ': '八', 'Ⅸ': '九', 'Ⅹ': '十'
        };

        function normalizeSeasonOrEpisode(text) {
            // 移除可能的空格
            text = text.replace(/\s+/g, '');

            // 处理带数字的情况(包括直接的数字转换)
            const numberMatch = text.match(/(\d+)季$/);
            if (numberMatch) {
                const number = numberMatch[1];
                const chineseNumber = numberMap[number] || number;
                return text.replace(/\d+季$/, `${chineseNumber}季`);
            }

            // 处理原有的罗马数字模式
            const romanMatch = text.match(/[^\d]([ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ])$/);
            if (romanMatch) {
                const romanNumber = romanMatch[1];
                const chineseNumber = numberMap[romanNumber];
                return text.replace(/[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]$/, `${chineseNumber}季`);
            }

            // 新增:处理"标题 数字"格式
            const simpleTitleNumberMatch = text.match(/(.+?)(\d+)$/);
            if (simpleTitleNumberMatch) {
                const title = simpleTitleNumberMatch[1];
                const number = simpleTitleNumberMatch[2];
                const chineseNumber = numberMap[number] || number;
                return `${title}第${chineseNumber}季`;
            }
            return text;
        }

        // 修改 getIDsFromText 函数以支持新的标准化逻辑
        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);
            }

            // 拆分并标准化每个条目
            return input.split(/[,\n\r,、\/|;。.()【】<>!?]+/)
                .map(part => part.trim())
                .filter(part => part.length > 0)
                .map(normalizeSeasonOrEpisode)
                .map(part => {
                // 处理纯数字ID
                const numberMatch = part.match(/\b\d+\b/);
                if (numberMatch) {
                    return "bgm_id=" + numberMatch[0];
                }
                return part;
            })
                .filter(part => part);
        }

        // 批量查找入口函数
        var Relation_MultiFindItemFunc = async function() {
            let item_type = '1';
            let typeSelector = $('.Relation_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();
            $('.Relation_item_not_found').empty();
            $('.Relation_item_dupe').empty();
            $('.Relation_current_idx').text('0');
            $('.Relation_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="Relation_wrapper">
        <h2 style="color: #333333;">批量关联助手</h2>
        <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="Relation_controls" style="margin-top: 10px">
          <span class="Relation_item_type"></span>
          <button id="btn_ctd_multi_search" class="btnCustom">批量关联</button>
        </div>

        <div class="Relation_progress">
          添加进度:<span class="Relation_current_idx">0</span>/<span class="Relation_all_num">0</span>
        </div>

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

        <div class="Relation_header">存在多结果的${uiTitle}(无最佳匹配结果,将自动选择第一个):</div>
        <div class="Relation_item_dupe"></div>
      </div>
    `);
        // 添加关联类型选择器
        $('.Relation_item_type').append(generateTypeSelector());
        $('.Relation_item_type select').prepend('<option value="-999">请选择关联类型</option>').val('-999');

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

    // 启动所有功能
    function startEnhancer() {
        initNavButtons();
        observeURLChanges();
        initCoverUpload();
        initBatchRelation()
        BatchEpisodeEditor.init();
        console.log("Bangumi Ultimate Enhancer 已启动");
    }
    // 在DOM加载完成后启动脚本
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', startEnhancer);
    } else {
        startEnhancer();
    }
})();