Greasy Fork

Greasy Fork is available in English.

Bangumi Ultimate Enhancer

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

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Bangumi Ultimate Enhancer
// @namespace    https://tampermonkey.net/
// @version      2.5
// @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;                /* 字体粗细程度,半粗体 */
        }
        .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,无偏移 */
        }

        /* 封面上传功能 - 定义封面上传相关元素样式 */
        .cover-upload-button a {
            color: var(--primary-color) !important; /* 链接颜色使用主要颜色,强制覆盖 */
            font-weight: 600;                 /* 字体粗细程度,半粗体 */
            transition: var(--transition-normal);   /* 应用统一的过渡动画 */
            text-decoration: none;            /* 移除下划线 */
        }
        .cover-upload-button a:hover {
            color: var(--primary-hover) !important; /* 鼠标悬停时链接颜色变深,强制覆盖 */
            text-decoration: underline;       /* 添加下划线 */
        }
        #coverUploadFormContainer {
            background: white;                /* 背景色为白色 */
            border: 1px solid var(--border-color);  /* 1像素实线边框,使用统一边框颜色 */
            border-radius: var(--border-radius);    /* 使用统一的圆角大小 */
            box-shadow: var(--shadow-soft);   /* 应用柔和阴影效果 */
            padding: 15px;                    /* 四周内边距15像素 */
            width: 240px;                     /* 宽度240像素 */
            max-width: 100%;                  /* 确保不超过父容器宽度 */
            transition: var(--transition-normal);   /* 应用统一的过渡动画 */
        }
        #coverUploadFormContainer:hover {
            box-shadow: var(--shadow-hover);  /* 鼠标悬停时阴影效果更明显 */
        }
        #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 20px;                 /* 内边距:上下8px,左右20px */
            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;

        // 图片下载和转换函数(保持原有逻辑)
        const downloadAndConvertImage = async (imageUrl) => {
            try {
                const response = await fetch(imageUrl);
                const blob = await response.blob();
                // 创建 JPG 文件
                const jpgBlob = new Blob([blob], { type: 'image/jpeg' });
                const jpgFile = new File([jpgBlob], 'cover.jpg', { type: 'image/jpeg' });

                // 显示预览
                const previewContainer = formContainer.querySelector("#imagePreviewContainer");
                const previewImage = formContainer.querySelector("#imagePreview");
                previewImage.src = URL.createObjectURL(jpgBlob);
                previewContainer.style.display = "block";

                // 查找上传表单中的文件输入
                const fileInput = document.querySelector("#coverUploadForm input[type='file']");
                if (fileInput) {
                    const dataTransfer = new DataTransfer();
                    dataTransfer.items.add(jpgFile);
                    fileInput.files = dataTransfer.files;
                    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>批量关联助手</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();
    }
})();