您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Bangumi 终极增强套件 - 集成Wiki按钮、关联按钮、封面上传、批量关联、批量分集编辑等功能
当前为
// ==UserScript== // @name Bangumi Ultimate Enhancer // @namespace https://tampermonkey.net/ // @version 2.4 // @description Bangumi 终极增强套件 - 集成Wiki按钮、关联按钮、封面上传、批量关联、批量分集编辑等功能 // @author Bios (improved by Claude) // @match *://bgm.tv/subject/* // @match *://chii.in/subject/* // @match *://bangumi.tv/subject* // @match *://bgm.tv/character/* // @match *://chii.in/character/* // @match *://bangumi.tv/character/* // @match *://bgm.tv/person/* // @match *://chii.in/person/* // @match *://bangumi.tv/person/* // @connect bgm.tv // @grant GM_xmlhttpRequest // @license MIT // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js // @run-at document-idle // ==/UserScript== (function() { "use strict"; // 在页面加载完成后执行 $(document).ready(function() { // 判断当前是否在关联页面 const isRelatedPage = window.location.href.indexOf('/add_related') !== -1; // 判断当前是否在条目/角色/人物主页 const isMainPage = /\/(subject|character|person)\/\d+$/.test(window.location.pathname); // 根据页面类型选择初始化不同功能 if (isRelatedPage) { initBatchRelation(); } else if (isMainPage) { initIDCollector(); } }); // 样式注入 function injectStyles() { $('head').append(` <style> /* 通用按钮美化 */ .btnCustom { margin: 5px 0; background-color: #1E90FF !important; color: white !important; border-radius: 5px !important; padding: 6px 18px !important; border: none !important; cursor: pointer !important; position: relative; top: -4px; left: -8px; font-size: 14px; font-weight: bold; text-align: center; display: flex; justify-content: flex-end; align-items: center; transition: opacity 0.2s, transform 0.2s; } .btnCustom:hover { opacity: 0.9; box-shadow: 3px 6px 12px rgba(0, 0, 0, 0.15); } /* 输入框优化 */ .enhancer-textarea { width: 100%; min-height: 80px; max-height: 300px; border: 1px solid #ccc; border-radius: 12px; padding: 10px; margin: 10px 0; resize: vertical; font-size: 14px; box-sizing: border-box; background: #fdfdfd; transition: all 0.05s ease-in-out; } .enhancer-textarea:focus { border-color: #1E90FF; box-shadow: 0 0 8px rgba(30, 144, 255, 0.3); outline: none; } /* 卡片式面板美化 */ .enhancer-panel { margin: 10px 0; border-radius: 10px; padding: 12px; background: #ffffff; border: 1px solid #e0e0e0; box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.05); } .enhancer-panel h3 { margin-top: 0; margin-bottom: 10px; font-size: 15px; color: #333; } /* 标签和选择框美化 */ .select-label { margin-right: 8px; font-weight: bold; color: #555; } .chitanda_item_type { display: flex; margin-right: auto; align-items: center; margin-bottom: 8px; } /* 主要容器美化 */ .chitanda_wrapper { margin: 15px 0; background: #fff; padding: 15px; border-radius: 10px; border: 1px solid #eaeaea; box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.05); } /* 进度提示美化 */ .chitanda_progress { margin: 12px 0; color: #d346bb; font-weight: bold; font-size: 18px; text-align: center; } /* 信息提示框优化 */ .chitanda_item_not_found, .chitanda_item_dupe { margin-top: 8px; padding: 8px; min-height: 15px; border-radius: 6px; font-size: 14px; } .chitanda_item_not_found { color: #e74c3c; background: #ffebeb; border: 1px solid #e74c3c; } .chitanda_item_dupe { color: #3498db; background: #eaf6ff; border: 1px solid #3498db; } /* 标题优化 */ .chitanda_header { font-size: 16px; margin: 12px 0 6px; color: #333; font-weight: bold; } /* 按钮区域对齐优化 */ .chitanda_controls { display: flex; justify-content: flex-end; align-items: center; } /* 标签导航美化 */ .tab-nav { display: flex; border-bottom: 2px solid #ddd; margin-bottom: 15px; } .tab-nav button { background: none; border: none; padding: 10px 20px; cursor: pointer; font-size: 14px; font-weight: bold; border-bottom: 3px solid transparent; transition: all 0.3s; } .tab-nav button.active { border-bottom: 3px solid #1E90FF; color: #1E90FF; } .tab-nav button:hover { color: #1E90FF; } /* 选项卡面板优化 */ .tab-panel { display: none; } .tab-panel.active { display: block; animation: fadeIn 0.3s ease-in-out; } @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } /* 行内表单优化 */ .flex-row { display: flex; gap: 12px; align-items: center; } /* 输入框美化 */ .input-number { width: 100px; padding: 8px; border-radius: 8px; border: 1px solid #ddd; background: #f9f9f9; transition: all 0.2s; } .input-number:focus { border-color: #1E90FF; box-shadow: 0 0 5px rgba(30, 144, 255, 0.3); outline: none; } </style> `); } /* Wiki 按钮和关联按钮模块 */ function initNavButtons() { // 排除特定页面 if (/(edit_detail|edit|add_related|upload_img)/.test(location.pathname)) return; // 获取导航栏 const nav = document.querySelector(".subjectNav .navTabs, .navTabs"); if (!nav) return; // 匹配页面类型和ID const pathMatch = location.pathname.match(/\/(subject|person|character)\/(\d+)/); if (!pathMatch) return; const pageType = pathMatch[1]; // subject, person, or character const pageId = pathMatch[2]; // ID number // 添加Wiki按钮 if (!nav.querySelector(".wiki-button")) { const wikiUrl = pageType === "subject" ? `${location.origin}/${pageType}/${pageId}/edit_detail` : `${location.origin}/${pageType}/${pageId}/edit`; const wikiLi = document.createElement("li"); wikiLi.className = "wiki-button"; wikiLi.innerHTML = `<a href="${wikiUrl}" target="_blank">Wiki</a>`; nav.appendChild(wikiLi); } // 添加关联按钮 if (!nav.querySelector(".relate-button")) { const relateUrl = pageType === "subject" ? `${location.origin}/${pageType}/${pageId}/add_related/subject/anime` : `${location.origin}/${pageType}/${pageId}/add_related/anime`; const relateLi = document.createElement("li"); relateLi.className = "relate-button"; relateLi.innerHTML = `<a href="${relateUrl}" target="_blank">关联</a>`; nav.appendChild(relateLi); } } // 监听 URL 变化 function observeURLChanges() { let lastURL = location.href; new MutationObserver(() => { if (location.href !== lastURL) { lastURL = location.href; initNavButtons(); } }).observe(document, { subtree: true, childList: true }); } /* 封面上传模块 - 仅在页面无封面时显示 */ async function initCoverUpload() { // 检查当前页面类型 const url = window.location.href; const subjectMatch = url.match(/bangumi\.tv\/subject\/(\d+)/); const personMatch = url.match(/bangumi\.tv\/person\/(\d+)/); const characterMatch = url.match(/bangumi\.tv\/character\/(\d+)/); if (!subjectMatch && !personMatch && !characterMatch) return; // 检查是否已添加上传模块 if (document.querySelector("#coverUploadForm")) return; // 根据页面类型确定ID和类型 let id, type; if (subjectMatch) { id = subjectMatch[1]; type = "subject"; } else if (personMatch) { id = personMatch[1]; type = "person"; } else { id = characterMatch[1]; type = "character"; } // **新逻辑:检查封面是否已存在** let coverExists = false; if (type === "subject") { coverExists = !!document.querySelector("#bangumiInfo img"); } else { coverExists = !!document.querySelector(".infobox_container img") || !!document.querySelector(".subject_sidebar img"); } if (coverExists) { console.log("封面已存在,跳过上传模块"); return; } // 为不同页面类型找到适当的插入位置 let targetElement; if (type === "subject") { targetElement = document.querySelector("#bangumiInfo"); if (!targetElement) return; const links = document.querySelectorAll(".tip_i p a.l"); if (links.length < 2) return; try { const res = await fetch(links[1].href); const doc = new DOMParser().parseFromString(await res.text(), "text/html"); const form = doc.querySelector("#columnInSubjectA .text form"); if (form) { const container = document.createElement("div"); container.className = "enhancer-panel"; const clone = form.parentElement.cloneNode(true); const uploadForm = clone.querySelector("form"); uploadForm.id = "coverUploadForm"; const fileInput = uploadForm.querySelector("input[type=file]"); fileInput.style.width = "100%"; const submitBtn = uploadForm.querySelector("input[type=submit]"); submitBtn.className = "btnCustom"; submitBtn.style.width = "120px"; submitBtn.style.margin = "10px auto 0"; container.appendChild(uploadForm); targetElement.parentNode.insertBefore(container, targetElement); } } catch (e) { console.error("封面加载失败:", e); } } else { targetElement = document.querySelector(".subject_sidebar") || document.querySelector(".infobox_container") || document.querySelector(".infobox"); if (!targetElement) return; try { // 直接获取上传页面 const uploadUrl = `https://bangumi.tv/${type}/${id}/upload_img`; const res = await fetch(uploadUrl); const doc = new DOMParser().parseFromString(await res.text(), "text/html"); const form = doc.querySelector("#columnInSubjectA .text form") || doc.querySelector(".text form") || doc.querySelector("form[enctype='multipart/form-data']"); if (form) { const container = document.createElement("div"); container.className = "enhancer-panel"; const clone = form.parentElement.cloneNode(true); const uploadForm = clone.querySelector("form"); uploadForm.id = "coverUploadForm"; const fileInput = uploadForm.querySelector("input[type=file]"); fileInput.style.width = "100%"; const submitBtn = uploadForm.querySelector("input[type=submit]"); submitBtn.className = "btnCustom"; submitBtn.style.width = "120px"; submitBtn.style.margin = "10px auto 0"; container.appendChild(uploadForm); targetElement.parentNode.insertBefore(container, targetElement); } } catch (e) { console.error("上传模块加载失败:", e); } } } // 批量分集编辑器功能模块 const BatchEpisodeEditor = { CHUNK_SIZE: 20, BASE_URL: '', CSRF_TOKEN: '', // 初始化方法 init() { if (!this.isEpisodePage()) return; this.BASE_URL = location.pathname.replace(/\/edit_batch$/, ''); this.CSRF_TOKEN = $('[name=formhash]')?.value || ''; if (!this.CSRF_TOKEN) return; this.bindHashChange(); this.upgradeCheckboxes(); // 添加功能标识 const header = document.querySelector('h2.subtitle'); if (header) { const notice = document.createElement('div'); notice.className = 'bgm-enhancer-status'; notice.textContent = '已启用分批编辑功能,支持超过20集的批量编辑'; header.parentNode.insertBefore(notice, header.nextSibling); } }, // 检查是否为分集页面 isEpisodePage() { return /^\/subject\/\d+\/ep(\/edit_batch)?$/.test(location.pathname); }, // 监听hash变化处理批量编辑 bindHashChange() { const processHash = () => { const ids = this.getSelectedIdsFromHash(); if (ids.length > 0) this.handleBatchEdit(ids); }; window.addEventListener('hashchange', processHash); if (location.hash.includes('episodes=')) processHash(); }, // 增强复选框功能 upgradeCheckboxes() { // 动态更新表单action const updateFormAction = () => { const ids = $$('[name="ep_mod[]"]:checked').map(el => el.value); $('form[name="edit_ep_batch"]').action = `${this.BASE_URL}/edit_batch#episodes=${ids.join(',')}`; }; $$('[name="ep_mod[]"]').forEach(el => el.addEventListener('change', updateFormAction) ); // 全选功能 $('[name=chkall]')?.addEventListener('click', () => { $$('[name="ep_mod[]"]').forEach(el => el.checked = true); updateFormAction(); }); }, // 从hash获取选中ID getSelectedIdsFromHash() { const match = location.hash.match(/episodes=([\d,]+)/); return match ? match[1].split(',').filter(Boolean) : []; }, // 批量编辑主逻辑 async handleBatchEdit(episodeIds) { try { // 分块加载数据 const chunks = this.createChunks(episodeIds, this.CHUNK_SIZE); const dataChunks = await this.loadChunkedData(chunks); // 填充表单数据 $('#summary').value = dataChunks.flat().join('\n'); $('[name=ep_ids]').value = episodeIds.join(','); // 增强表单提交 this.upgradeFormSubmit(chunks, episodeIds); window.chiiLib?.ukagaka?.presentSpeech('数据加载完成'); } catch (err) { console.error('批量处理失败:', err); alert('数据加载失败,请刷新重试'); } }, // 分块加载数据 async loadChunkedData(chunks) { window.chiiLib?.ukagaka?.presentSpeech('正在加载分集数据...'); return Promise.all(chunks.map(chunk => this.fetchChunkData(chunk).then(data => data.split('\n')) )); }, // 获取单块数据 async fetchChunkData(episodeIds) { const params = new URLSearchParams(); params.append('chkall', 'on'); params.append('submit', '批量修改'); params.append('formhash', this.CSRF_TOKEN); episodeIds.forEach(id => params.append('ep_mod[]', id)); const res = await fetch(`${this.BASE_URL}/edit_batch`, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: params }); const html = await res.text(); const match = html.match(/<textarea [^>]*name="ep_list"[^>]*>([\s\S]*?)<\/textarea>/i); return match?.[1]?.trim() || ''; }, // 增强表单提交处理 upgradeFormSubmit(chunks, originalIds) { const form = $('form[name="edit_ep_batch"]'); if (!form) return; form.onsubmit = async (e) => { e.preventDefault(); // 验证数据完整性 const inputData = $('#summary').value.trim().split('\n'); if (inputData.length !== originalIds.length) { alert(`数据不匹配 (预期 ${originalIds.length} 行,实际 ${inputData.length} 行)`); return; } try { window.chiiLib?.ukagaka?.presentSpeech('正在提交数据...'); await this.saveChunkedData(chunks, inputData); window.chiiLib?.ukagaka?.presentSpeech('保存成功'); location.href = this.BASE_URL; } catch (err) { console.error('保存失败:', err); alert('保存过程中发生错误'); } }; }, // 分块保存数据 async saveChunkedData(chunks, fullData) { const dataChunks = this.createChunks(fullData, this.CHUNK_SIZE); return Promise.all(chunks.map((idChunk, index) => this.saveChunkData(idChunk, dataChunks[index]) )); }, // 保存单块数据 async saveChunkData(episodeIds, chunkData) { const params = new URLSearchParams(); params.append('formhash', this.CSRF_TOKEN); params.append('rev_version', '0'); params.append('editSummary', $('#editSummary')?.value || ''); params.append('ep_ids', episodeIds.join(',')); params.append('ep_list', chunkData.join('\n')); params.append('submit_eps', '改好了'); await fetch(`${this.BASE_URL}/edit_batch`, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: params }); }, // 通用分块方法 createChunks(array, size) { return Array.from( { length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, (i + 1) * size) ); } }; // ID收集器 - 用于条目/角色/人物主页 function initIDCollector() { injectStyles(); // 获取当前页面ID const getPageID = () => { const match = location.pathname.match(/\/(subject|character|person)\/(\d+)/); return match ? parseInt(match[2]) : null; }; const currentID = getPageID(); // 创建界面容器 const container = document.createElement("div"); container.innerHTML = ` <div class="enhancer-panel"> <h3>批量关联助手</h3> <p>当前位置:ID收集 - 点击下方按钮前往关联页面</p> <div style="text-align: center"> <a href="${window.location.pathname}/add_related/subject" class="btnCustom"> 前往批量关联页面 </a> </div> </div> `; // 插入到页面 const targetNode = $("#sbjSearchMod") || $(".main_content"); if (targetNode.length) targetNode.after(container); } // 批量关联功能 - 用于关联页面 function initBatchRelation() { injectStyles(); // 参数配置 const DELAY_AFTER_CLICK = 250; const DELAY_BETWEEN_ITEMS = 500; const MAX_RETRY_ATTEMPTS = 10; const RETRY_INTERVAL = 100; // 全局变量 let globalItemType = '1'; let currentProcessingIndex = -1; // 根据当前 URL 判断页面类型 function getCurrentPageType() { const url = window.location.href; if (url.indexOf('/add_related/character') !== -1) { return 'character'; } else if (url.indexOf('/add_related/subject') !== -1) { return 'subject'; } else { return 'person'; } } // 生成关联类型选择下拉框 function generateTypeSelector() { const pageType = getCurrentPageType(); if (pageType === 'character') { let options = ''; const relationTypes = { '1': '主角', '2': '配角', '3': '客串' }; for (let [value, text] of Object.entries(relationTypes)) { options += `<option value="${value}">${text}</option>`; } return `<span class="select-label">类型: </span><select>${options}</select>`; } else { // 如果有 genPrsnStaffList 函数则调用,否则返回空字符串 return `<span class="select-label"></span>${(typeof genPrsnStaffList === "function") ? genPrsnStaffList(-1) : ''}`; } } // 针对传入的元素内的下拉框进行设置,并通过递归确保修改成功 function setRelationTypeWithElement($li, item_type) { return new Promise((resolve) => { let attempts = 0; function trySet() { // 确保我们获取的是当前元素内部的select,而不是全局的 let $select = $li.find('select').first(); if ($select.length > 0) { // 先确保下拉框可交互 if ($select.prop('disabled')) { setTimeout(trySet, RETRY_INTERVAL); return; } $select.val(item_type); // 触发 change 事件 const event = new Event('change', { bubbles: true }); $select[0].dispatchEvent(event); setTimeout(() => { if ($select.val() == item_type) { resolve(true); } else if (attempts < MAX_RETRY_ATTEMPTS) { attempts++; setTimeout(trySet, RETRY_INTERVAL); } else { resolve(false); } }, 200); } else if (attempts < MAX_RETRY_ATTEMPTS) { attempts++; setTimeout(trySet, RETRY_INTERVAL); } else { resolve(false); } } trySet(); }); } // 点击项目后利用 MutationObserver 监听新增条目,然后对该条目的下拉框设置类型 function processItem(element, item_type) { return new Promise((resolve) => { // 关联列表容器 const container = document.querySelector('#crtRelateSubjects'); if (!container) { return resolve(false); } // 保存处理前的条目列表 const initialItems = Array.from(container.children); // 绑定 MutationObserver 监听子节点变化 const observer = new MutationObserver((mutations) => { // 获取当前所有条目 const currentItems = Array.from(container.children); // 找出新增的条目(在当前列表中但不在初始列表中的元素) const newItems = currentItems.filter(item => !initialItems.includes(item)); if (newItems.length > 0) { observer.disconnect(); const newItem = newItems[0]; // 获取第一个新增条目 // 确保等待DOM完全渲染 setTimeout(async () => { // 使用新的条目元素直接查找其内部的select const $select = $(newItem).find('select'); if ($select.length > 0) { const success = await setRelationTypeWithElement($(newItem), item_type); resolve(success); } else { resolve(false); } }, DELAY_AFTER_CLICK); } }); observer.observe(container, { childList: true, subtree: true }); // 触发点击 $(element).click(); // 超时防护 setTimeout(() => { observer.disconnect(); resolve(false); }, MAX_RETRY_ATTEMPTS * RETRY_INTERVAL); }); } // 处若搜索结果不唯一且没有完全匹配项则自动选择第一个 function normalizeText(text) { return text.normalize("NFC").replace(/\s+/g, '').replace(/[\u200B-\u200D\uFEFF]/g, '').trim(); } function extractTextFromElement(el) { if (!el) return ''; let text = el.innerText || el.textContent || $(el).text(); // 尝试从 `iframe` 和 `shadowRoot` 获取文本 if (!text.trim()) { if (el.shadowRoot) { text = [...el.shadowRoot.querySelectorAll('*')].map(e => e.textContent).join(''); } let iframe = el.querySelector('iframe'); if (iframe && iframe.contentDocument) { text = iframe.contentDocument.body.textContent; } } return normalizeText(text); } async function processSingleItem(elements, item_type, search_name) { return new Promise(async (resolve) => { if (elements.length === 0) { $('.chitanda_item_not_found').append(search_name + ' '); resolve(false); return; } let elementsArray = elements.toArray(); let normalizedSearchName = normalizeText(search_name); console.log("搜索名(规范化):", normalizedSearchName); // 等待元素加载,避免空文本 await new Promise(res => setTimeout(res, 500)); let selectedElement = elementsArray.find(el => { let normalizedElementText = extractTextFromElement(el); console.log("元素文本(规范化):", normalizedElementText); // 调试用 return normalizedElementText === normalizedSearchName; }); if (!selectedElement) { if (elements.length > 1) { $('.chitanda_item_dupe').append(`${search_name} `); } selectedElement = elements[0]; // 没有完全匹配,取第一个 } resolve(await processItem(selectedElement, item_type)); }); } // 处理下一个项目 async function proceedToNextItem(idx, item_list, item_type, item_num) { if (idx < item_num - 1) { setTimeout(async () => { await ctd_findItemFunc(item_list, item_type, idx + 1); }, DELAY_BETWEEN_ITEMS); } else { setTimeout(() => { $('#subjectList').empty(); $('#subjectList').show(); alert('全部添加完成'); }, DELAY_BETWEEN_ITEMS); } } // 核心查找及处理函数:依次检索每个条目并处理 var ctd_findItemFunc = async function(item_list, item_type, idx) { currentProcessingIndex = idx; item_type = globalItemType; let search_name = item_list[idx].trim(); if (!search_name) { proceedToNextItem(idx, item_list, item_type, item_list.length); return; } var item_num = item_list.length; $('#subjectList').html('<tr><td>正在检索中...</td></tr>'); var search_mod = $('#sbjSearchMod').attr('value'); try { const response = await new Promise((resolve, reject) => { $.ajax({ type: "GET", url: '/json/search-' + search_mod + '/' + encodeURIComponent(search_name), dataType: 'json', success: resolve, error: reject }); }); var html = ''; if ($(response).length > 0) { subjectList = response; for (var i in response) { if ($.inArray(search_mod, enableStaffSbjType) != -1) { html += genSubjectList(response[i], i, 'submitForm'); } else { html += genSubjectList(response[i], i, 'searchResult'); } } $('#subjectList').html(html); $('.chitanda_current_idx').text(idx + 1); $('.chitanda_all_num').text(item_num); await new Promise(resolve => setTimeout(resolve, 400)); // 减少等待时间 var elements = $('#subjectList>li>a.avatar.h'); if (window.location.pathname.includes('/person/') && window.location.pathname.includes('/add_related/character/anime')) { if (elements.length === 0) { $('.chitanda_item_not_found').append(search_name + ' '); } else { $(elements[0]).click(); if (elements.length > 1) { $('.chitanda_item_dupe').append(`${search_name} `); } } $('.chitanda_current_idx').text(idx + 1); if (idx < item_num - 1) { setTimeout(async () => { await ctd_findItemFunc(item_list, item_type, idx + 1); }, DELAY_BETWEEN_ITEMS); } else { setTimeout(() => { $('#subjectList').empty(); $('#subjectList').show(); alert('全部添加完成'); }, DELAY_BETWEEN_ITEMS); } } else { await processSingleItem(elements, item_type, search_name, idx, item_list, item_num); await proceedToNextItem(idx, item_list, item_type, item_num); } } else { $("#robot").fadeIn(300); $("#robot_balloon").html(`没有找到 ${search_name} 的相关结果`); $("#robot").animate({ opacity: 1 }, 500).fadeOut(500); // 减少动画时间 $('.chitanda_item_not_found').append(search_name + ' '); $('#subjectList').html(html); $('.chitanda_current_idx').text(idx + 1); $('.chitanda_all_num').text(item_num); await proceedToNextItem(idx, item_list, item_type, item_num); } } catch (error) { console.error('查询出错:', error); $("#robot").fadeIn(300); $("#robot_balloon").html('通信错误,您是不是重复查询太快了?'); $("#robot").animate({ opacity: 1 }, 500).fadeOut(1000); // 减少动画时间 $('#subjectList').html(''); setTimeout(async () => { if (idx < item_list.length - 1) { await ctd_findItemFunc(item_list, item_type, idx + 1); } else { $('#subjectList').empty(); $('#subjectList').show(); alert('全部添加完成,但部分查询出错'); } }, 1500); // 减少等待时间 } }; // 从ID范围中提取ID列表 function getIDsFromRange(start, end) { const startID = parseInt(start, 10); const endID = parseInt(end, 10); if (isNaN(startID) || isNaN(endID) || startID > endID) { alert("ID范围无效"); return []; } return Array.from({ length: endID - startID + 1 }, (_, i) => "bgm_id=" + (startID + i)); } // 从自由文本中提取ID列表 function getIDsFromText(input) { if (!input.trim()) { alert("请输入ID或内容"); return []; } // 先检查是否以 bgm_id= 开头 if (input.startsWith("bgm_id=")) { return input.substring(7) .split(/[,\n\r,、\/|;。.()【】<>!?]+| +/) .map(id => "bgm_id=" + id.trim()) .filter(id => id); } // 识别 URL 形式的 ID const urlPattern = /(bgm\.tv|bangumi\.tv|chii\.in)\/(subject|character|person)\/(\d+)/g; const urlMatches = [...input.matchAll(urlPattern)].map(m => m[3]); if (urlMatches.length > 0) { return urlMatches.map(id => "bgm_id=" + id); } // 识别纯数字 ID const numberPattern = /\b\d+\b/g; const numberMatches = [...input.matchAll(numberPattern)].map(m => m[0]); if (numberMatches.length > 0) { return numberMatches.map(id => "bgm_id=" + id); } // 默认按符号分隔(空格优先级最低) return input.split(/[,\n\r,、\/|;。.()【】<>!?]+/) // 先按主要分隔符拆分 .map(part => part.trim()) // 去除前后空格 .filter(part => part.length > 0) // 过滤掉空字符串 .flatMap(part => { // 保持以下格式不被拆分: // 1. "第3季"、"第2集" // 2. "Ⅱ", "Ⅲ", "Ⅳ"(罗马数字) // 3. "鬼灭之刃 3" 这种名字+数字 return /^(第?\d+|[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]+)$/.test(part) || /\D+\d+$/.test(part) ? [part] : part.split(/ +/); }); } // 批量查找入口函数 var chitanda_MultiFindItemFunc = async function() { let item_type = '1'; let typeSelector = $('.chitanda_item_type select'); if (typeSelector.length > 0) { item_type = typeSelector.val(); if (item_type == '-999') { alert('请先选择关联类型'); return false; } globalItemType = item_type; } let ctd_item_list = []; const activeTab = $('.tab-panel.active').attr('id'); if (activeTab === 'tab-text') { // 处理文本输入模式 const inputVal = $('#custom_ids').val().trim(); ctd_item_list = getIDsFromText(inputVal); } else if (activeTab === 'tab-range') { // 处理ID范围模式 const startID = $('#id_start').val().trim(); const endID = $('#id_end').val().trim(); ctd_item_list = getIDsFromRange(startID, endID); } if (ctd_item_list.length === 0) { return false; } $('#subjectList').hide(); $('.chitanda_item_not_found').empty(); $('.chitanda_item_dupe').empty(); $('.chitanda_current_idx').text('0'); $('.chitanda_all_num').text(ctd_item_list.length); currentProcessingIndex = -1; await ctd_findItemFunc(ctd_item_list, item_type, 0); }; // 切换标签页 function switchTab(tabId) { $('.tab-nav button').removeClass('active'); $(`.tab-nav button[data-tab="${tabId}"]`).addClass('active'); $('.tab-panel').removeClass('active'); $(`#${tabId}`).addClass('active'); } // 根据页面类型设定 UI 标题 let uiTitle = '人物'; const pageType = getCurrentPageType(); if (pageType === 'character') { uiTitle = '角色'; } else if (pageType === 'subject') { uiTitle = '条目'; } // 创建改进的UI界面 $('.subjectListWrapper').after(` <div class="chitanda_wrapper"> <h3>批量关联助手</h3> <div class="tab-nav"> <button data-tab="tab-text" class="active">自由文本输入</button> <button data-tab="tab-range">ID范围输入</button> </div> <div id="tab-text" class="tab-panel active"> <textarea id="custom_ids" class="enhancer-textarea" placeholder="输入ID或网址(支持多种格式:纯数字、bgm_id=xx、网址、文本,可用各类符号(空格优先级最低)分隔)"></textarea> </div> <div id="tab-range" class="tab-panel"> <div class="flex-row" style="justify-content: center"> <input id="id_start" type="number" placeholder="起始ID" class="input-number"> <span style="line-height: 30px">~</span> <input id="id_end" type="number" placeholder="结束ID" class="input-number"> </div> </div> <div class="chitanda_controls" style="margin-top: 10px"> <span class="chitanda_item_type"></span> <button id="btn_ctd_multi_search" class="btnCustom">批量关联</button> </div> <div class="chitanda_progress"> 添加进度:<span class="chitanda_current_idx">0</span>/<span class="chitanda_all_num">0</span> </div> <div class="chitanda_header">未找到的${uiTitle}:</div> <div class="chitanda_item_not_found"></div> <div class="chitanda_header">存在多个结果的${uiTitle}(已自动选择第一个):</div> <div class="chitanda_item_dupe"></div> </div> `); // 添加关联类型选择器 $('.chitanda_item_type').append(generateTypeSelector()); $('.chitanda_item_type select').prepend('<option value="-999">请选择关联类型</option>').val('-999'); // 绑定事件 $('#btn_ctd_multi_search').on('click', chitanda_MultiFindItemFunc); $('.chitanda_item_type select').on('change', function() { globalItemType = $(this).val(); }); $('.tab-nav button').on('click', function() { switchTab($(this).data('tab')); }); } /* 启动所有功能 */ function startEnhancer() { initNavButtons(); observeURLChanges(); initCoverUpload(); BatchEpisodeEditor.init(); console.log("Bangumi Ultimate Enhancer 已启动"); } // 在DOM加载完成后启动脚本 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startEnhancer); } else { startEnhancer(); } })();