您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动生成Bangumi编辑摘要
// ==UserScript== // @name bangumi 自动生成编辑摘要 // @namespace https://bgm.tv/group/topic/433505 // @version 0.3 // @description 自动生成Bangumi编辑摘要 // @author You // @match https://bgm.tv/subject/*/edit_detail // @match https://bgm.tv/character/*/edit // @match https://bgm.tv/person/*/edit // @match https://bangumi.tv/subject/*/edit_detail // @match https://bangumi.tv/character/*/edit // @match https://bangumi.tv/person/*/edit // @match https://chii.in/subject/*/edit_detail // @match https://chii.in/character/*/edit // @match https://chii.in/person/*/edit // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; // 存储初始值 let initialTitle, initialPlatform, initialPros, initialSeries, initialWcode, initialSummary, initialNsfw, initialTags; const editSummaryInput = document.querySelector('#editSummary'); const titleInput = document.querySelector('[name="subject_title"], [name="crt_name"]'); const getPlatform = () => document.querySelector(`[for="${[...document.querySelectorAll('input[name=platform]')].find(i => i.checked)?.id}"]`)?.textContent; const getPros = () => [...document.querySelectorAll('[name^=prsn_pro]')].filter(c => c.checked).map(c => document.querySelector(`[for=${c.id}]`).textContent); const seriesCheckbox = document.querySelector('#subjectSeries'); const summaryTextarea = document.querySelector('#subject_summary, #crt_summary'); const nsfwCheckbox = document.querySelector('input[name="subject_nsfw"]'); const tagsInput = document.querySelector('input#tags'); const lockedSummary = localStorage.getItem('lockedEditSummary'); if (lockedSummary) editSummaryInput.value = lockedSummary; // 初始化函数 function init() { // 获取初始值 initialTitle = titleInput.value; initialPlatform = getPlatform(); initialPros = getPros(); initialSeries = seriesCheckbox?.checked; initialWcode = getWcode(); initialSummary = summaryTextarea.value; initialNsfw = nsfwCheckbox?.checked; initialTags = tagsInput?.value; editSummaryInput.style.width = '60%'; editSummaryInput.after(newGenBtn(), newLockButton(), document.createElement('br'), newAutoGenBtn()); } // 添加自动生成按钮 function newAutoGenBtn() { const autoGenBtn = document.createElement('input'); autoGenBtn.type = 'checkbox'; autoGenBtn.id = 'autoGenEditSummary'; autoGenBtn.style.marginRight = '5px'; const autoGenLabel = document.createElement('label'); autoGenLabel.htmlFor = 'autoGenEditSummary'; autoGenLabel.textContent = '提交时自动生成'; autoGenBtn.onchange = function () { localStorage.setItem('autoGenEditSummary', this.checked); }; autoGenBtn.checked = localStorage.getItem('autoGenEditSummary') === 'true'; autoGenLabel.prepend(autoGenBtn); return autoGenLabel; } // 添加生成按钮 function newGenBtn() { const genBtn = document.createElement('button'); genBtn.type = 'button'; genBtn.textContent = '生成摘要'; genBtn.onclick = generateEditSummary; return genBtn; } // 添加锁定按钮 function newLockButton() { let isLocked = lockedSummary; const lockBtn = document.createElement('button'); lockBtn.type = 'button'; lockBtn.textContent = isLocked ? '🔒' : '🔓'; lockBtn.title = isLocked ? '编辑摘要已锁定' : '编辑摘要未锁定'; lockBtn.style.marginLeft = '5px'; lockBtn.style.cursor = 'pointer'; lockBtn.style.background = 'none'; lockBtn.style.border = 'none'; lockBtn.style.fontSize = '16px'; lockBtn.onclick = function () { isLocked = !isLocked; this.textContent = isLocked ? '🔒' : '🔓'; this.title = isLocked ? '编辑摘要已锁定' : '编辑摘要未锁定'; localStorage.setItem('lockedEditSummary', isLocked ? editSummaryInput.value : ''); }; return lockBtn; } // 劫持提交按钮 const submitBtn = document.querySelector('input.inputBtn[name="submit"]'); submitBtn.addEventListener('click', () => { localStorage.getItem('autoGenEditSummary') === 'true' && generateEditSummary(); }, { capture: false }); // 生成编辑摘要 function generateEditSummary() { const newWcode = getWcode(); const newSummary = summaryTextarea.value; const newTags = tagsInput?.value; const changes = []; // 分析标题变化 if (initialTitle !== titleInput.value) { changes.push(`修改标题(${initialTitle} → ${titleInput.value})`); } // 分析类型变化 const newPlatform = getPlatform(); if (initialPlatform !== newPlatform) { changes.push(`修改类型(${initialPlatform} → ${newPlatform})`); } // 分析人物职业变化 const newPros = getPros(); const proChanges = analyzeArrChanges(initialPros, newPros, '职业'); if (proChanges.length > 0) changes.push(...proChanges); // 分析系列变化 if (initialSeries !== seriesCheckbox?.checked) { changes.push(seriesCheckbox?.checked ? '标记为系列' : '取消系列标记'); } // 分析wcode变化 const wcodeChanges = analyzeWcodeChanges(initialWcode, newWcode); if (wcodeChanges.length > 0) { changes.push(...wcodeChanges); } // 分析summary变化 if (initialSummary !== newSummary) { changes.push(`${initialSummary ? '修改' : '添加'}简介`); } // 分析nsfw变化 if (initialNsfw !== nsfwCheckbox?.checked) { changes.push(nsfwCheckbox?.checked ? '标记为受限内容' : '取消受限内容标记'); } // 分析tags变化 const tagsToArr = tags => tags ? tags.split(/\s+/).filter(t => t) : []; const tagChanges = analyzeArrChanges(tagsToArr(initialTags), tagsToArr(newTags), '标签'); if (tagChanges.length > 0) changes.push(...tagChanges); // 更新编辑摘要输入框 const editSummaryInput = document.querySelector('input#editSummary'); if (editSummaryInput && changes.length > 0) { if (!editSummaryInput.dataset.userModified || editSummaryInput.value === '') { editSummaryInput.value = changes.join(';'); } } else if (editSummaryInput) { editSummaryInput.value = '空编辑'; } } // 分析wcode变化 function analyzeWcodeChanges(oldWcode, newWcode) { // 解析wcode为对象 const oldData = parseWcode(oldWcode); const newData = parseWcode(newWcode); const getMultiData = data => Object.fromEntries(Object.entries(data).filter(([, v]) => typeof v === 'object')); const oldMultiData = getMultiData(oldData); const newMultiData = getMultiData(newData); const multiKeyChanges = []; for (const key in oldMultiData) { if (key in newMultiData) { const subChanges = genFieldChanges(oldMultiData[key], newMultiData[key]); multiKeyChanges.push(...subChanges.map(change => `${key}${change}`)); delete oldData[key]; delete newData[key]; } } return genFieldChanges(oldData, newData).concat(multiKeyChanges); } function genFieldChanges(oldData, newData) { const changes = []; for (const key in oldData) { const movedTo = getMovedTo(oldData, newData, key); if (movedTo) { changes.push(`${key} → ${movedTo}`); continue; } if (!(key in newData)) { changes.push(`删除${key}${ oldData[key] ? `(${oldData[key]})`: ''}`); } } for (const key in newData) { if (!(key in oldData)) { changes.push(`添加${key}`); } } for (const key in oldData) { if (key in newData) { const oldValue = oldData[key]; const newValue = newData[key]; if (oldValue !== newValue) { changes.push(`修改${key}(${oldValue} → ${newValue})`); } } } // 若是移动的字段,不再重复记录删除和添加 const moves = changes.filter(c => c.includes('→')).map(c => c.split('→').map(s => s.trim())); return changes.filter(c => c.includes('→') && !c.includes('修改') || !moves.some(([from, to]) => c.startsWith(`删除${from}`) || c === `添加${to}` || c.startsWith(`修改${from}`) || c.startsWith(`修改${to}`) ) ); } function getMovedTo(oldData, newData, key) { const oldValue = oldData[key]; if (!oldValue) return null; for (const newKey in newData) { if (newKey !== key && newData[newKey] === oldValue && oldValue !== oldData[newKey]) { return newKey; } } return null; } // 解析wcode为对象 function parseWcode(wcode) { const result = {}; const lines = wcode.split('\n').map(l => l.trim().replace(/^[|\[]/, '').replace(/]$/, '')) .filter(l => !['', '{{', '}}'].includes(l)); let currentKey = null; let inMultiValue = false; for (const line of lines) { // 处理多值字段开始 if (line.endsWith('={')) { currentKey = line.replace('={', '').trim(); inMultiValue = true; continue; } // 处理多值字段结束 if (inMultiValue && line === '}') { if (currentKey) { currentKey = null; inMultiValue = false; } continue; } // 处理多值字段内容 if (inMultiValue) { const [subKey, ...subValueParts] = line.split('|'); const subValue = subValueParts.join('|').trim(); if (subKey.trim()) { if (!result[currentKey]) result[currentKey] = {}; if (subValue) { result[currentKey][subKey.trim()] = subValue; } else { // 纯值子段 result[currentKey][subKey.trim()] = null; } continue; } } // 处理普通字段 if (line.includes('=')) { const [key, ...valueParts] = line.split('='); const value = valueParts.join('=').trim(); if (key.trim() && value) result[key.trim()] = value; } } return result; } // 分析数组变化 function analyzeArrChanges(oldArr, newArr, label) { const changes = []; const oldSet = new Set(oldArr); const newSet = new Set(newArr); // 检查新增的标签 const addedItems = []; for (const item of newArr) { if (!oldSet.has(item)) { addedItems.push(item); } } if (addedItems.length) { changes.push(`添加${label}${addedItems.join('、')}`); } // 检查删除的标签 const removedItems = []; for (const item of oldArr) { if (!newSet.has(item)) { removedItems.push(item); } } if (removedItems.length) { changes.push(`删除${label}${removedItems.join('、')}`); } return changes; } // 页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })(); function getWcode() { if (nowmode === 'wcode') { return document.getElementById('subject_infobox').value; } else if (nowmode === 'normal') { info = new Array(); ids = new Object(); props = new Object(); input_num = $("#infobox_normal input.id").length; ids = $("#infobox_normal input.id"); props = $("#infobox_normal input.prop"); for (i = 0; i < input_num; i++) { id = $(ids).get(i); prop = $(props).get(i); if ($(id).hasClass('multiKey')) { multiKey = $(id).val(); info[multiKey] = new Object(); var subKey = 0; i++; id = $(ids).get(i); prop = $(props).get(i); while (($(id).hasClass('multiSubKey') || $(prop).hasClass('multiSubVal')) && i < input_num) { if (isNaN($(id).val())) { info[multiKey][subKey] = { key: $(id).val(), value: $(prop).val() }; } else { info[multiKey][subKey] = $(prop).val(); } subKey++; i++; id = $(ids).get(i); prop = $(props).get(i); } i--; } else if ($.trim($(id).val()) != "") { info[$(id).val()] = $(prop).val(); } } return WCODEDump(info); } }