// ==UserScript== // @name Bangumi Ultimate Enhancer // @namespace https://tampermonkey.net/ // @version 2.4.1 // @description Bangumi 终极增强套件 - 集成Wiki按钮、关联按钮、封面上传、批量关联、批量分集编辑等功能 // @author Bios (improved by Claude) // @match *://bgm.tv/subject/* // @match *://chii.in/subject/* // @match *://bangumi.tv/subject* // @match *://bgm.tv/character/* // @match *://chii.in/character/* // @match *://bangumi.tv/character/* // @match *://bgm.tv/person/* // @match *://chii.in/person/* // @match *://bangumi.tv/person/* // @connect bgm.tv // @grant GM_xmlhttpRequest // @license MIT // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js // @run-at document-idle // @downloadURL none // ==/UserScript== (function() { "use strict"; // 样式注入 function injectStyles() { $('head').append(` `); } /* ==================== Wiki 按钮和关联按钮模块 ======================*/ function initNavButtons() { // 排除特定页面 if (/(edit_detail|edit|add_related|upload_img)/.test(location.pathname)) return; // 获取导航栏 const nav = document.querySelector(".subjectNav .navTabs, .navTabs"); if (!nav) return; // 匹配页面类型和ID const pathMatch = location.pathname.match(/\/(subject|person|character)\/(\d+)/); if (!pathMatch) return; const pageType = pathMatch[1]; // subject, person, or character const pageId = pathMatch[2]; // ID number // 添加Wiki按钮 if (!nav.querySelector(".wiki-button")) { const wikiUrl = pageType === "subject" ? `${location.origin}/${pageType}/${pageId}/edit_detail` : `${location.origin}/${pageType}/${pageId}/edit`; const wikiLi = document.createElement("li"); wikiLi.className = "wiki-button"; wikiLi.innerHTML = `Wiki`; nav.appendChild(wikiLi); } // 添加关联按钮 if (!nav.querySelector(".relate-button")) { const relateUrl = pageType === "subject" ? `${location.origin}/${pageType}/${pageId}/add_related/subject/anime` : `${location.origin}/${pageType}/${pageId}/add_related/anime`; const relateLi = document.createElement("li"); relateLi.className = "relate-button"; relateLi.innerHTML = `关联`; nav.appendChild(relateLi); } } // 监听 URL 变化 function observeURLChanges() { let lastURL = location.href; new MutationObserver(() => { if (location.href !== lastURL) { lastURL = location.href; initNavButtons(); } }).observe(document, { subtree: true, childList: true }); } /* ========= 封面上传模块 ===========*/ async function initCoverUpload() { if (/\/(edit_detail|edit|add_related|upload_img)/.test(location.pathname)) return; const url = window.location.href; let id, type; if (/bangumi\.tv\/subject\/(\d+)/.test(url)) { id = url.match(/bangumi\.tv\/subject\/(\d+)/)[1]; type = "subject"; } else if (/bangumi\.tv\/person\/(\d+)/.test(url)) { id = url.match(/bangumi\.tv\/person\/(\d+)/)[1]; type = "person"; } else if (/bangumi\.tv\/character\/(\d+)/.test(url)) { id = url.match(/bangumi\.tv\/character\/(\d+)/)[1]; type = "character"; } else { return; } // 检查按钮是否已存在,避免重复插入 if (document.querySelector("#coverUploadButton")) return; // 获取导航栏 const nav = document.querySelector(".subjectNav .navTabs") || document.querySelector(".navTabs"); if (!nav) return; // 创建上传按钮 const uploadLi = document.createElement("li"); uploadLi.id = "coverUploadButton"; uploadLi.className = "upload-button"; uploadLi.style.float = "right"; uploadLi.innerHTML = `上传封面`; nav.appendChild(uploadLi); // 创建上传表单容器(初始隐藏) const formContainer = document.createElement("div"); formContainer.id = "coverUploadFormContainer"; formContainer.style.display = "none"; formContainer.style.position = "absolute"; formContainer.style.zIndex = "1000"; formContainer.style.backgroundColor = "#fff"; formContainer.style.border = "1px solid #ddd"; formContainer.style.borderRadius = "4px"; formContainer.style.padding = "10px"; formContainer.style.boxShadow = "0 2px 5px rgba(0,0,0,0.2)"; formContainer.style.width = "240px"; document.body.appendChild(formContainer); let formLoaded = false; let hideTimeout = null; uploadLi.addEventListener("mouseenter", async function () { clearTimeout(hideTimeout); // 定位表单 const buttonRect = uploadLi.getBoundingClientRect(); formContainer.style.top = (buttonRect.bottom + window.scrollY) + "px"; // 让表单的左侧与“封面”按钮的左侧对齐 formContainer.style.left = (buttonRect.left + window.scrollX - 180) + "px"; formContainer.style.display = "block"; if (!formLoaded) { formContainer.innerHTML = "加载中..."; try { const uploadUrl = `https://bangumi.tv/${type}/${id}/upload_img`; const res = await fetch(uploadUrl); const doc = new DOMParser().parseFromString(await res.text(), "text/html"); const form = doc.querySelector("form[enctype='multipart/form-data']"); if (form) { formContainer.innerHTML = ""; const clone = form.cloneNode(true); clone.id = "coverUploadForm"; formContainer.appendChild(clone); formLoaded = true; } else { formContainer.innerHTML = "无法加载上传表单"; } } catch (e) { formContainer.innerHTML = "加载失败"; console.error("上传模块加载失败:", e); } } }); uploadLi.addEventListener("mouseleave", function () { hideTimeout = setTimeout(() => { if (!formContainer.matches(":hover")) { formContainer.style.display = "none"; } }, 200); }); formContainer.addEventListener("mouseenter", function () { clearTimeout(hideTimeout); }); formContainer.addEventListener("mouseleave", function () { hideTimeout = setTimeout(() => { formContainer.style.display = "none"; }, 200); }); } // 监听整个页面的变化,确保按钮不会消失 const observer = new MutationObserver(() => { if (!document.querySelector("#coverUploadButton")) { initCoverUpload(); } }); // 监听整个 body,防止 Bangumi 的 SPA 机制导致按钮被删除 observer.observe(document.body, { childList: true, subtree: true }); // 初次执行 document.addEventListener("DOMContentLoaded", initCoverUpload); setTimeout(initCoverUpload, 1000); /* ==================== 批量分集编辑器功能模块 =====================*/ const BatchEpisodeEditor = { CHUNK_SIZE: 20, BASE_URL: '', CSRF_TOKEN: '', // 初始化方法 init() { if (!this.isEpisodePage()) return; this.BASE_URL = location.pathname.replace(/\/edit_batch$/, ''); this.CSRF_TOKEN = $('[name=formhash]')?.value || ''; if (!this.CSRF_TOKEN) return; this.bindHashChange(); this.upgradeCheckboxes(); // 添加功能标识 const header = document.querySelector('h2.subtitle'); if (header) { const notice = document.createElement('div'); notice.className = 'bgm-enhancer-status'; notice.textContent = '已启用分批编辑功能,支持超过20集的批量编辑'; header.parentNode.insertBefore(notice, header.nextSibling); } }, // 检查是否为分集页面 isEpisodePage() { return /^\/subject\/\d+\/ep(\/edit_batch)?$/.test(location.pathname); }, // 监听hash变化处理批量编辑 bindHashChange() { const processHash = () => { const ids = this.getSelectedIdsFromHash(); if (ids.length > 0) this.handleBatchEdit(ids); }; window.addEventListener('hashchange', processHash); if (location.hash.includes('episodes=')) processHash(); }, // 增强复选框功能 upgradeCheckboxes() { // 动态更新表单action const updateFormAction = () => { const ids = $$('[name="ep_mod[]"]:checked').map(el => el.value); $('form[name="edit_ep_batch"]').action = `${this.BASE_URL}/edit_batch#episodes=${ids.join(',')}`; }; $$('[name="ep_mod[]"]').forEach(el => el.addEventListener('change', updateFormAction) ); // 全选功能 $('[name=chkall]')?.addEventListener('click', () => { $$('[name="ep_mod[]"]').forEach(el => el.checked = true); updateFormAction(); }); }, // 从hash获取选中ID getSelectedIdsFromHash() { const match = location.hash.match(/episodes=([\d,]+)/); return match ? match[1].split(',').filter(Boolean) : []; }, // 批量编辑主逻辑 async handleBatchEdit(episodeIds) { try { // 分块加载数据 const chunks = this.createChunks(episodeIds, this.CHUNK_SIZE); const dataChunks = await this.loadChunkedData(chunks); // 填充表单数据 $('#summary').value = dataChunks.flat().join('\n'); $('[name=ep_ids]').value = episodeIds.join(','); // 增强表单提交 this.upgradeFormSubmit(chunks, episodeIds); window.chiiLib?.ukagaka?.presentSpeech('数据加载完成'); } catch (err) { console.error('批量处理失败:', err); alert('数据加载失败,请刷新重试'); } }, // 分块加载数据 async loadChunkedData(chunks) { window.chiiLib?.ukagaka?.presentSpeech('正在加载分集数据...'); return Promise.all(chunks.map(chunk => this.fetchChunkData(chunk).then(data => data.split('\n')) )); }, // 获取单块数据 async fetchChunkData(episodeIds) { const params = new URLSearchParams(); params.append('chkall', 'on'); params.append('submit', '批量修改'); params.append('formhash', this.CSRF_TOKEN); episodeIds.forEach(id => params.append('ep_mod[]', id)); const res = await fetch(`${this.BASE_URL}/edit_batch`, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: params }); const html = await res.text(); const match = html.match(/]*name="ep_list"[^>]*>([\s\S]*?)<\/textarea>/i); return match?.[1]?.trim() || ''; }, // 增强表单提交处理 upgradeFormSubmit(chunks, originalIds) { const form = $('form[name="edit_ep_batch"]'); if (!form) return; form.onsubmit = async (e) => { e.preventDefault(); // 验证数据完整性 const inputData = $('#summary').value.trim().split('\n'); if (inputData.length !== originalIds.length) { alert(`数据不匹配 (预期 ${originalIds.length} 行,实际 ${inputData.length} 行)`); return; } try { window.chiiLib?.ukagaka?.presentSpeech('正在提交数据...'); await this.saveChunkedData(chunks, inputData); window.chiiLib?.ukagaka?.presentSpeech('保存成功'); location.href = this.BASE_URL; } catch (err) { console.error('保存失败:', err); alert('保存过程中发生错误'); } }; }, // 分块保存数据 async saveChunkedData(chunks, fullData) { const dataChunks = this.createChunks(fullData, this.CHUNK_SIZE); return Promise.all(chunks.map((idChunk, index) => this.saveChunkData(idChunk, dataChunks[index]) )); }, // 保存单块数据 async saveChunkData(episodeIds, chunkData) { const params = new URLSearchParams(); params.append('formhash', this.CSRF_TOKEN); params.append('rev_version', '0'); params.append('editSummary', $('#editSummary')?.value || ''); params.append('ep_ids', episodeIds.join(',')); params.append('ep_list', chunkData.join('\n')); params.append('submit_eps', '改好了'); await fetch(`${this.BASE_URL}/edit_batch`, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: params }); }, // 通用分块方法 createChunks(array, size) { return Array.from( { length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, (i + 1) * size) ); } }; /* ==================== 批量关联 - 用于关联页面 ======================*/ function initBatchRelation() { injectStyles(); // 参数配置 const DELAY_AFTER_CLICK = 250; const DELAY_BETWEEN_ITEMS = 500; const MAX_RETRY_ATTEMPTS = 10; const RETRY_INTERVAL = 100; // 全局变量 let globalItemType = '1'; let currentProcessingIndex = -1; // 根据当前 URL 判断页面类型(支持排除特定路径) function getCurrentPageType() { const 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]) => `${text}`) .join(''); }; switch(pageType) { case 'character': return `类型: ${generateOptions({ '1': '主角', '2': '配角', '3': '客串' })}`; default: return `${ typeof genPrsnStaffList === "function" ? genPrsnStaffList(-1) : '' }`; } } // 针对传入的元素内的下拉框进行设置,并通过递归确保修改成功 function setRelationTypeWithElement($li, item_type) { return new Promise((resolve) => { let attempts = 0; function trySet() { // 确保我们获取的是当前元素内部的select,而不是全局的 let $select = $li.find('select').first(); if ($select.length > 0) { // 先确保下拉框可交互 if ($select.prop('disabled')) { setTimeout(trySet, RETRY_INTERVAL); return; } $select.val(item_type); // 触发 change 事件 const event = new Event('change', { bubbles: true }); $select[0].dispatchEvent(event); setTimeout(() => { if ($select.val() == item_type) { resolve(true); } else if (attempts < MAX_RETRY_ATTEMPTS) { attempts++; setTimeout(trySet, RETRY_INTERVAL); } else { resolve(false); } }, 200); } else if (attempts < MAX_RETRY_ATTEMPTS) { attempts++; setTimeout(trySet, RETRY_INTERVAL); } else { resolve(false); } } trySet(); }); } // 点击项目后利用 MutationObserver 监听新增条目,然后对该条目的下拉框设置类型 function processItem(element, item_type) { return new Promise((resolve) => { // 关联列表容器 const container = document.querySelector('#crtRelateSubjects'); if (!container) { return resolve(false); } // 保存处理前的条目列表 const initialItems = Array.from(container.children); // 绑定 MutationObserver 监听子节点变化 const observer = new MutationObserver((mutations) => { // 获取当前所有条目 const currentItems = Array.from(container.children); // 找出新增的条目(在当前列表中但不在初始列表中的元素) const newItems = currentItems.filter(item => !initialItems.includes(item)); if (newItems.length > 0) { observer.disconnect(); const newItem = newItems[0]; // 获取第一个新增条目 // 确保等待DOM完全渲染 setTimeout(async () => { // 使用新的条目元素直接查找其内部的select const $select = $(newItem).find('select'); if ($select.length > 0) { const success = await setRelationTypeWithElement($(newItem), item_type); resolve(success); } else { resolve(false); } }, DELAY_AFTER_CLICK); } }); observer.observe(container, { childList: true, subtree: true }); // 触发点击 $(element).click(); // 超时防护 setTimeout(() => { observer.disconnect(); resolve(false); }, MAX_RETRY_ATTEMPTS * RETRY_INTERVAL); }); } // 处若搜索结果不唯一且没有完全匹配项则自动选择第一个 function normalizeText(text) { return text.normalize("NFC").replace(/\s+/g, '').replace(/[\u200B-\u200D\uFEFF]/g, '').trim(); } function extractTextFromElement(el) { if (!el) return ''; let text = el.innerText || el.textContent || $(el).text(); // 尝试从 `iframe` 和 `shadowRoot` 获取文本 if (!text.trim()) { if (el.shadowRoot) { text = [...el.shadowRoot.querySelectorAll('*')].map(e => e.textContent).join(''); } let iframe = el.querySelector('iframe'); if (iframe && iframe.contentDocument) { text = iframe.contentDocument.body.textContent; } } return normalizeText(text); } async function processSingleItem(elements, item_type, search_name) { return new Promise(async (resolve) => { if (elements.length === 0) { $('.chitanda_item_not_found').append(search_name + ' '); resolve(false); return; } let elementsArray = elements.toArray(); let normalizedSearchName = normalizeText(search_name); console.log("搜索名(规范化):", normalizedSearchName); // 等待元素加载,避免空文本 await new Promise(res => setTimeout(res, 500)); let selectedElement = elementsArray.find(el => { let normalizedElementText = extractTextFromElement(el); console.log("元素文本(规范化):", normalizedElementText); // 调试用 return normalizedElementText === normalizedSearchName; }); if (!selectedElement) { if (elements.length > 1) { $('.chitanda_item_dupe').append(`${search_name} `); } selectedElement = elements[0]; // 没有完全匹配,取第一个 } resolve(await processItem(selectedElement, item_type)); }); } // 处理下一个项目 async function proceedToNextItem(idx, item_list, item_type, item_num) { if (idx < item_num - 1) { setTimeout(async () => { await ctd_findItemFunc(item_list, item_type, idx + 1); }, DELAY_BETWEEN_ITEMS); } else { setTimeout(() => { $('#subjectList').empty(); $('#subjectList').show(); alert('全部添加完成'); }, DELAY_BETWEEN_ITEMS); } } // 核心查找及处理函数:依次检索每个条目并处理 var ctd_findItemFunc = async function(item_list, item_type, idx) { currentProcessingIndex = idx; item_type = globalItemType; let search_name = item_list[idx].trim(); if (!search_name) { proceedToNextItem(idx, item_list, item_type, item_list.length); return; } var item_num = item_list.length; $('#subjectList').html('正在检索中...'); var search_mod = $('#sbjSearchMod').attr('value'); try { const response = await new Promise((resolve, reject) => { $.ajax({ type: "GET", url: '/json/search-' + search_mod + '/' + encodeURIComponent(search_name), dataType: 'json', success: resolve, error: reject }); }); var html = ''; if ($(response).length > 0) { subjectList = response; for (var i in response) { if ($.inArray(search_mod, enableStaffSbjType) != -1) { html += genSubjectList(response[i], i, 'submitForm'); } else { html += genSubjectList(response[i], i, 'searchResult'); } } $('#subjectList').html(html); $('.chitanda_current_idx').text(idx + 1); $('.chitanda_all_num').text(item_num); await new Promise(resolve => setTimeout(resolve, 400)); // 减少等待时间 var elements = $('#subjectList>li>a.avatar.h'); if (window.location.pathname.includes('/person/') && window.location.pathname.includes('/add_related/character/anime')) { if (elements.length === 0) { $('.chitanda_item_not_found').append(search_name + ' '); } else { $(elements[0]).click(); if (elements.length > 1) { $('.chitanda_item_dupe').append(`${search_name} `); } } $('.chitanda_current_idx').text(idx + 1); if (idx < item_num - 1) { setTimeout(async () => { await ctd_findItemFunc(item_list, item_type, idx + 1); }, DELAY_BETWEEN_ITEMS); } else { setTimeout(() => { $('#subjectList').empty(); $('#subjectList').show(); alert('全部添加完成'); }, DELAY_BETWEEN_ITEMS); } } else { await processSingleItem(elements, item_type, search_name, idx, item_list, item_num); await proceedToNextItem(idx, item_list, item_type, item_num); } } else { $("#robot").fadeIn(300); $("#robot_balloon").html(`没有找到 ${search_name} 的相关结果`); $("#robot").animate({ opacity: 1 }, 500).fadeOut(500); // 减少动画时间 $('.chitanda_item_not_found').append(search_name + ' '); $('#subjectList').html(html); $('.chitanda_current_idx').text(idx + 1); $('.chitanda_all_num').text(item_num); await proceedToNextItem(idx, item_list, item_type, item_num); } } catch (error) { console.error('查询出错:', error); $("#robot").fadeIn(300); $("#robot_balloon").html('通信错误,您是不是重复查询太快了?'); $("#robot").animate({ opacity: 1 }, 500).fadeOut(1000); // 减少动画时间 $('#subjectList').html(''); setTimeout(async () => { if (idx < item_list.length - 1) { await ctd_findItemFunc(item_list, item_type, idx + 1); } else { $('#subjectList').empty(); $('#subjectList').show(); alert('全部添加完成,但部分查询出错'); } }, 1500); // 减少等待时间 } }; // 增强的解析函数:支持多种ID分隔和准确搜索 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}季`); } 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 chitanda_MultiFindItemFunc = async function() { let item_type = '1'; let typeSelector = $('.chitanda_item_type select'); if (typeSelector.length > 0) { item_type = typeSelector.val(); if (item_type == '-999') { alert('请先选择关联类型'); return false; } globalItemType = item_type; } let ctd_item_list = []; const activeTab = $('.tab-panel.active').attr('id'); if (activeTab === 'tab-text') { // 处理文本输入模式 const inputVal = $('#custom_ids').val().trim(); ctd_item_list = getIDsFromText(inputVal); } else if (activeTab === 'tab-range') { // 处理ID范围模式 const startID = $('#id_start').val().trim(); const endID = $('#id_end').val().trim(); ctd_item_list = getIDsFromRange(startID, endID); } if (ctd_item_list.length === 0) { return false; } $('#subjectList').hide(); $('.chitanda_item_not_found').empty(); $('.chitanda_item_dupe').empty(); $('.chitanda_current_idx').text('0'); $('.chitanda_all_num').text(ctd_item_list.length); currentProcessingIndex = -1; await ctd_findItemFunc(ctd_item_list, item_type, 0); }; // 切换标签页 function switchTab(tabId) { $('.tab-nav button').removeClass('active'); $(`.tab-nav button[data-tab="${tabId}"]`).addClass('active'); $('.tab-panel').removeClass('active'); $(`#${tabId}`).addClass('active'); } // 根据页面类型设定 UI 标题 let uiTitle = '人物'; const pageType = getCurrentPageType(); if (pageType === 'character') { uiTitle = '角色'; } else if (pageType === 'subject') { uiTitle = '条目'; } // 创建改进的UI界面 $('.subjectListWrapper').after(` 批量关联助手 自由文本输入 ID范围输入 ~ 批量关联 添加进度:0/0 未找到的${uiTitle}: 存在多结果的${uiTitle}(无最佳匹配结果,将自动选择第一个): `); // 添加关联类型选择器 $('.chitanda_item_type').append(generateTypeSelector()); $('.chitanda_item_type select').prepend('请选择关联类型').val('-999'); // 绑定事件 $('#btn_ctd_multi_search').on('click', chitanda_MultiFindItemFunc); $('.chitanda_item_type select').on('change', function() { globalItemType = $(this).val(); }); $('.tab-nav button').on('click', function() { switchTab($(this).data('tab')); }); } /* 启动所有功能 */ function startEnhancer() { initNavButtons(); observeURLChanges(); initCoverUpload(); initBatchRelation() BatchEpisodeEditor.init(); console.log("Bangumi Ultimate Enhancer 已启动"); } // 在DOM加载完成后启动脚本 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startEnhancer); } else { startEnhancer(); } })();