您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Bangumi 终极增强套件 - 集成Wiki按钮、关联按钮、封面上传、批量关联、批量分集编辑等功能
当前为
// ==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(); } })();