您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Bangumi 增强套件 - 集成Wiki按钮、关联按钮、封面上传、批量关联、批量分集编辑等功能
当前为
// ==UserScript== // @name Bangumi Ultimate Enhancer // @namespace https://tampermonkey.net/ // @version 2.2.4 // @description Bangumi 增强套件 - 集成Wiki按钮、关联按钮、封面上传、批量关联、批量分集编辑等功能 // @author Bios & Anonymous (Merged 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/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @license MIT // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js // ==/UserScript== (function () { "use strict"; // 样式增强 GM_addStyle(` .btnCustom { margin: 5px 0; background-color: #1E90FF !important; color: white !important; border-radius: 10px !important; padding: 5px 15px !important; border: none !important; cursor: pointer !important; transition: opacity 0.2s; } .btnCustom:hover { opacity: 0.8; } .enhancer-textarea { width: 100%; min-height: 60px; max-height: 300px; border: 1px solid #ddd; border-radius: 10px; padding: 8px; margin: 8px 0; resize: vertical; font-size: 13px; box-sizing: border-box; } .enhancer-panel { margin: 6px 0; /* 减少外边距 */ border-radius: 6px; /* 调小圆角 */ padding: 5px; /* 减少内边距 */ background: #f8f8f8; border: 1px solid #e0e0e0; } #coverUploadForm { text-align: center; padding: 3px; /* 减少表单内边距 */ } #coverUploadForm input[type="file"] { margin: 3px auto; /* 缩小文件输入框边距 */ width: 90%; /* 调宽输入框减少留白 */ display: block; } /* 进一步缩小按钮尺寸 */ #coverUploadForm input[type="submit"] { padding: 4px 8px !important; /* 更紧凑的按钮尺寸 */ font-size: 14px !important; /* 更小的字号 */ margin: 8px auto 2px !important; /* 调整按钮间距 */ } .bgm-enhancer-status { background: #e6f4ff; border-radius: 4px; padding: 8px; margin: 8px 0; border-left: 3px solid #1E90FF; font-size: 13px; color: #333; } `); // 通用工具函数 const $ = selector => document.querySelector(selector); const $$ = selector => Array.from(document.querySelectorAll(selector)); // 获取当前页面的 ID(即 URL 里的 subject/xxx、character/xxx、person/xxx) function getCurrentPageID() { const match = window.location.pathname.match(/\/(subject|character|person)\/(\d+)/); return match ? parseInt(match[2], 10) : null; } const currentPageID = getCurrentPageID(); /* ------------------------------ 超级增强器功能模块 ------------------------------ */ /* Wiki 按钮和关联按钮模块 */ function initNavButtons() { // 排除编辑页面 if (/(edit_detail|edit)$/.test(location.pathname)) return; // 排除 add_related 和 upload_img 页面 if (/add_related|upload_img/.test(location.pathname)) return; const matchSubject = location.pathname.match(/\/subject\/(\d+)/); const matchPerson = location.pathname.match(/\/person\/(\d+)/); const matchCharacter = location.pathname.match(/\/character\/(\d+)/); const nav = document.querySelector(".subjectNav .navTabs, .navTabs"); if (!nav) return; // 检查Wiki按钮是否已存在 if (!nav.querySelector(".wiki-button")) { const wikiLi = document.createElement("li"); wikiLi.className = "wiki-button"; let wikiUrl = ""; if (matchSubject) { wikiUrl = `${location.origin}/subject/${matchSubject[1]}/edit_detail`; } else if (matchPerson) { wikiUrl = `${location.origin}/person/${matchPerson[1]}/edit`; } else if (matchCharacter) { wikiUrl = `${location.origin}/character/${matchCharacter[1]}/edit`; } if (wikiUrl) { wikiLi.innerHTML = `<a href="${wikiUrl}" target="_blank">Wiki</a>`; nav.appendChild(wikiLi); } } // 为主条目添加关联按钮 if (matchSubject && !nav.querySelector(".relate-button")) { const relateLi = document.createElement("li"); relateLi.className = "relate-button"; // 关联按钮默认链接到添加动画关联 const relateUrl = `${location.origin}/subject/${matchSubject[1]}/add_related/subject/anime`; 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() { if (document.querySelector("img.cover")) return; const infoBox = document.querySelector("#bangumiInfo"); if (!infoBox) 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); infoBox.parentNode.insertBefore(container, infoBox); } } catch (e) { console.error("封面加载失败:", e); } } /* 批量关联模块 */ function initBatchRelation() { if (!document.getElementById("indexCatBox")) return; const panelHTML = ` <div class="enhancer-panel"> <textarea id="custom_ids" class="enhancer-textarea" placeholder="输入 ID 或网址(可换行,支持各种分隔字符)"></textarea> <div style="text-align: center"> <button id="btn_execute" class="btnCustom">自动添加关联</button> </div> </div> <div class="enhancer-panel"> <div style="display: flex; gap: 10px; justify-content: center"> <input id="id_start" type="number" placeholder="起始ID" style="width: 100px; padding: 6px; border-radius: 8px; border: 1px solid #ddd;"> <span style="line-height: 30px">~</span> <input id="id_end" type="number" placeholder="结束ID" style="width: 100px; padding: 6px; border-radius: 8px; border: 1px solid #ddd;"> </div> <div style="text-align: center; margin-top: 12px"> <button id="btn_generate" class="btnCustom">自动添加关联</button> </div> </div> `; const searchMod = document.querySelector("#sbjSearchMod"); if (searchMod) searchMod.insertAdjacentHTML("afterend", panelHTML); document.getElementById("btn_execute")?.addEventListener("click", () => { const rawInput = document.getElementById("custom_ids").value; const ids = extractUniqueIDs(rawInput); if (ids.length) processBatch(ids); }); document.getElementById("btn_generate")?.addEventListener("click", () => { const start = parseInt(document.getElementById("id_start").value, 10); const end = parseInt(document.getElementById("id_end").value, 10); if (isNaN(start) || isNaN(end) || start > end) { alert("请输入有效的起始和结束 ID!"); return; } const ids = Array.from({ length: end - start + 1 }, (_, i) => start + i); processBatch([...new Set(ids)]); }); function extractUniqueIDs(input) { const allIDs = input.match(/\d+/g) || []; let uniqueIDs = [...new Set(allIDs.map(Number))].filter(id => id > 0); if (currentPageID) { uniqueIDs = uniqueIDs.filter(id => id !== currentPageID); } return uniqueIDs; } function processBatch(ids) { if (!ids.length) { alert("未找到有效的 ID 或所有 ID 都已被排除!"); return; } const batch = ids.splice(0, 10); $("#subjectName").value = `bgm_id=${batch.join(",")}`; $("#findSubject").click(); const subjectList = document.getElementById("subjectList"); if (!subjectList) { alert("未找到关联列表,请检查页面结构。"); return; } const observer = new MutationObserver((mutations, observerInstance) => { // 当列表中有 li 元素时认为搜索结果已生成 if (document.querySelectorAll('#subjectList > li').length > 0) { observerInstance.disconnect(); // 延时500ms等待渲染完成 setTimeout(function(){ var $avatars = document.querySelectorAll('#subjectList>li>a.avatar.h'); if ($avatars.length > 1) { $avatars.forEach(function(element, index){ setTimeout(function(){ element.click(); console.log("已点击:" + element.textContent); // 如有需要,可对关联列表中的条目进行样式修改提醒 document.querySelectorAll('#crtRelateSubjects li p.title>a')[0].style.fontWeight = 'bold'; }, index * 500); }); } else if ($avatars.length === 1) { $avatars[0].click(); } // 根据点击数量延时后点击保存按钮 setTimeout(function(){ $("#saveSubject").click(); alert("所有关联项已成功添加!"); }, ($avatars.length > 1 ? $avatars.length * 500 + 300 : 800)); }, 500); } }); observer.observe(subjectList, { childList: true, subtree: true }); } } /* ------------------------------ 批量分集编辑器功能模块 ------------------------------ */ 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) ); } }; /* 启动所有功能 */ function startEnhancer() { // 启动超级增强器功能 initNavButtons(); observeURLChanges(); initCoverUpload(); initBatchRelation(); // 启动批量分集编辑器功能 BatchEpisodeEditor.init(); console.log("Bangumi Ultimate Enhancer 已启动"); } // 在DOM加载完成后启动脚本 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startEnhancer); } else { startEnhancer(); } })();