您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
章节批量编辑增强
// ==UserScript== // @name Bangumi Episodes Batch Edit Improve Plus // @namespace org.binota.scripts.bangumi.bebei // @description 章节批量编辑增强 // @include /^https?:\/\/(bgm\.tv|bangumi\.tv|chii\.in)\/subject\/\d+\/ep(\/edit_batch)?/ // @version 0.1.8 // @grant none // @author BinotaLIU / SilenceAkarin // @license MIT // ==/UserScript== 'use strict'; const $ = selector => document.querySelector(selector); const $a = selector => document.querySelectorAll(selector); const chunk = (input, size) => input.reduce((arr, item, idx) => idx % size === 0 ? [...arr, [item]] : [...arr.slice(0, -1), [...arr.slice(-1)[0], item]], []); const say = str => window.chiiLib.ukagaka.presentSpeech(str); const baseUrl = `${window.location.pathname.match(/^\/subject\/\d+\/ep/).find(() => true)}/edit_batch`; const csrfToken = $('[name=formhash]').value; const revVersion = $('[name=rev_version]')?.value || '0'; const decodeHTMLEntities = text => { const textarea = document.createElement('textarea'); textarea.innerHTML = text; return textarea.value; }; const fetchEpisodesData = async (episodes) => await fetch( baseUrl, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded', }, body: `chkall=on&submit=%E6%89%B9%E9%87%8F%E4%BF%AE%E6%94%B9&formhash=${csrfToken}&${episodes.map(ep => `ep_mod%5B%5D=${ep}`).join('&')}`, } ) .then(res => res.text()) .then(html => { const rawData = ((html || '').match(/<textarea name="ep_list"[^>]+>([\w\W]+)?<\/textarea/) || [null, ''])[1].trim(); return decodeHTMLEntities(rawData); }); const updateEpisodesData = async (episodes, data) => await fetch(baseUrl, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded', }, body: `formhash=${csrfToken}&rev_version=${encodeURIComponent(revVersion)}&editSummary=${encodeURIComponent($('#editSummary').value)}&ep_ids=${episodes.join(',')}&ep_list=${encodeURIComponent(data)}&submit_eps=%E6%94%B9%E5%A5%BD%E4%BA%86` }); const app = async (episodes) => { if (episodes.length <= 20) return; const data = []; const epChunks = chunk(episodes, 20); say('加载章节列表中'); for (const chunk of epChunks) { data.push(await fetchEpisodesData(chunk)); } $('#summary').value = data.join('\n'); $('[name=ep_ids]').value = episodes.join(','); $('[name=edit_ep_batch]').addEventListener('submit', (e) => { e.preventDefault(); const lines = $('#summary').value.trim().split('\n').map(i => i.trim()); if (lines.length !== episodes.length) { return false; } const dataChunks = chunk(lines, 20); (async () => { say('保存资料中……'); for(const i in dataChunks) { await updateEpisodesData(epChunks[i], dataChunks[i].join('\n')); } say('保存完毕'); window.location.href = window.location.pathname.match(/^\/subject\/\d+\/ep/).find(() => true); })(); return false; }); say('章节列表载入完毕'); } const episodes = (window.location.hash.match(/#episodes=((\d+,)*\d+)/) || [null, ''])[1].split(',').filter(i => i.length); if (episodes.length) { app(episodes); return; } // 新增功能:为每个分类添加全选按钮 (感谢Panzerance) function addCategoryCheckAll() { const categories = document.querySelectorAll('li.cat'); // 如果分类少于2个,则不添加全选按钮 if (categories.length < 2) { return; } categories.forEach(catLi => { const checkboxes = []; let current = catLi.nextElementSibling; const categoryName = catLi.textContent.trim(); // 读取分类名称 // 检查是否是Disc后跟数字的分类(如Disc 1, Disc 2等) const isDiscCategory = /^Disc\s+\d+/.test(categoryName); // 收集当前分类下的所有checkbox while (current && !current.matches('li.cat')) { const cb = current.querySelector('input[name="ep_mod[]"]'); if (cb) checkboxes.push(cb); current = current.nextElementSibling; } if (checkboxes.length === 0) return; // 创建全选行 const checkAllLi = document.createElement('li'); checkAllLi.className = 'line_even clearit'; checkAllLi.style.padding = '8px'; // 根据分类类型动态显示"曲目"或"章节" const suffix = isDiscCategory ? '曲目' : '章节'; // 使用读取到的分类名称 checkAllLi.innerHTML = ` <label> <input class="checkbox category-chkall" type="checkbox"> <span class="tip">全选${categoryName}${suffix}</span> </label> `; // 插入到分类的最后一个条目后 const lastLi = checkboxes[checkboxes.length-1].closest('li'); lastLi.insertAdjacentElement('afterend', checkAllLi); // 绑定点击事件 checkAllLi.querySelector('.category-chkall').addEventListener('click', function() { const isChecked = this.checked; checkboxes.forEach(cb => cb.checked = isChecked); updateFormAction(); }); }); } // 优化全选逻辑 const updateFormAction = () => { const activeEps = [...document.querySelectorAll('[name="ep_mod[]"]:checked')].map(i => i.value); document.querySelector('[name=edit_ep_batch]').action = `${baseUrl}#episodes=${activeEps.join(',')}`; }; // 原有全局全选逻辑 $('[name=chkall]').onclick = () => { const checkboxes = [...document.querySelectorAll('[name="ep_mod[]"]')]; const hasUnchecked = checkboxes.some(cb => !cb.checked); checkboxes.forEach(cb => cb.checked = hasUnchecked); updateFormAction(); }; // 初始化分类全选按钮 addCategoryCheckAll(); // 确保选中状态变化时更新表单action $a('[name="ep_mod[]"]').forEach(cb => { cb.addEventListener('click', updateFormAction); });