您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
检测bangumi发布/修改内容中含有的敏感词,并进行单个或批量替换,同时支持自定义预设,可自动/手动更新词库
当前为
// ==UserScript== // @name Bangumi 敏感词检测+替换 // @namespace https://gf.qytechs.cn/zh-CN/users/1386262-zintop // @version 1.3.1 // @description 检测bangumi发布/修改内容中含有的敏感词,并进行单个或批量替换,同时支持自定义预设,可自动/手动更新词库 // @author zintop // @license MIT // @include /^https?:\/\/(bgm\.tv|bangumi\.tv|chii\.in)\/(.*(group\/topic\/.+\/edit|group\/.+\/settings|group\/.+\/new_topic|blog\/create|blog\/.+\/edit|subject\/.+\/topic\/new|subject\/topic\/.+\/edit|user\/.+\/timeline|settings|index\/create|index\/.+\/edit|anime\/list\/.+)|subject\/\d+\/?$)/ // @grant none // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY = 'sensitive_panel_settings'; const REMOTE_JSON = 'https://raw.githubusercontent.com/zintop/bangumi-sensitive-words/refs/heads/main/bangumi-sensitive-words.json'; let SENSITIVE_WORDS = []; let lastUpdate = ''; let detectedWords = new Set(); let regexPresets = JSON.parse(localStorage.getItem('sensitive_regex_presets') || '[]'); let panelFirstShowDone = false; function $(s) { return document.querySelector(s); } function savePanelSettings(panel) { const s = { left: panel.style.left, top: panel.style.top, width: panel.style.width, height: panel.style.height, opacity: panel.style.opacity }; localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } function loadPanelSettings(panel) { const s = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); if (s.left) panel.style.left = s.left; if (s.top) panel.style.top = s.top; if (s.width) panel.style.width = s.width; if (s.height) panel.style.height = s.height; if (s.opacity) panel.style.opacity = s.opacity; } async function fetchRemoteWords() { try { const res = await fetch(REMOTE_JSON + '?_=' + Date.now()); const json = await res.json(); if (Array.isArray(json)) { SENSITIVE_WORDS = json; lastUpdate = new Date().toLocaleString(); const el = $('#sensitive-last-update'); if(el) el.textContent = `词库更新时间:${lastUpdate}`; runDetection(); } } catch (e) { console.error('敏感词库更新失败', e); } } // ====== 浮窗适配关灯模式 ====== function applyTheme() { const theme = document.documentElement.getAttribute("data-theme"); const bg = theme === "dark" ? "#444" : "#fff"; const panel = $('#sensitive-panel'); if (panel) panel.style.background = bg; document.querySelectorAll('.sensitive-dialog').forEach(d => d.style.background = bg); } function observeThemeChange() { const observer = new MutationObserver(() => applyTheme()); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); } function createUI() { const panel = document.createElement('div'); panel.id = 'sensitive-panel'; panel.style.cssText = ` position: fixed; top:80px; left:320px; width:280px; max-height:80vh; overflow-y:auto; z-index:99999; border:1px solid #f99; font-size:13px; font-family:sans-serif; border-radius:8px; box-shadow:0 2px 6px rgba(0,0,0,0.15); resize:both; overflow:hidden auto; opacity:1; display:none; `; loadPanelSettings(panel); panel.innerHTML = ` <div id="sensitive-header" style="background:#f99;color:#fff;padding:5px;cursor:move;">敏感词检测</div> <div id="sensitive-status" style="padding:5px;"><strong>✅ 没有检测到敏感词</strong></div> <div id="sensitive-last-update" style="padding:5px; font-size:11px; color:#666;">词库更新时间:${lastUpdate}</div> <div id="sensitive-word-list" style="padding:5px;"></div> <div style="padding:5px;"> <button id="replace-all">全部替换</button> <button id="replace-stars">全部替换为**</button> <button id="add-preset" style="margin-left:4px;">添加预设</button> <button id="update-words" style="margin-left:4px;">手动更新词库</button> </div> <div id="preset-list" style="padding:5px;"></div> `; document.body.appendChild(panel); applyTheme(); observeThemeChange(); const header = $('#sensitive-header'); let offsetX=0, offsetY=0, isDown=false; header.addEventListener('mousedown', e => { isDown=true; offsetX=e.clientX-panel.offsetLeft; offsetY=e.clientY-panel.offsetTop; e.preventDefault(); }); document.addEventListener('mouseup', ()=>{isDown=false;}); document.addEventListener('mousemove', e=>{ if(!isDown) return; panel.style.left=`${e.clientX-offsetX}px`; panel.style.top=`${e.clientY-offsetY}px`; savePanelSettings(panel); }); const uname = document.querySelector('.avatar')?.getAttribute('href')?.split('/').pop(); if(!uname) return; const dock = document.querySelector('#dock ul>li.first'); if(dock){ const li = document.createElement('li'); li.innerHTML = `<a href="javascript:void(0);" id="toggleSensitiveBtn">敏感词🔍</a><p></p>`; dock.after(li); $('#toggleSensitiveBtn').addEventListener('click', ()=>{ panel.style.display = panel.style.display==='none'?'block':'none'; }); } $('#replace-all').onclick = () => { Array.from(detectedWords).forEach(w=>{ const r=prompt(`将 "${w}" 替换为:`); if(r!=null) replaceWordInInputs(w,r); }); runDetection(); }; $('#replace-stars').onclick = () => { detectedWords.forEach(w=>replaceWordInInputs(w,'*'.repeat(w.length))); runDetection(); }; $('#add-preset').onclick = showPresetDialog; $('#update-words').onclick = fetchRemoteWords; renderPresets(); } function updateToggleButtonText(){ const btn = $('#toggleSensitiveBtn'); if(!btn) return; btn.textContent = detectedWords.size>0 ? '敏感词⚠️' : '敏感词🔍'; const panel = $('#sensitive-panel'); if(detectedWords.size>0 && !panelFirstShowDone){ panel.style.display='block'; panelFirstShowDone=true; } } function showPresetDialog(editIdx){ const isEdit = typeof editIdx==='number'; const existing = isEdit ? regexPresets[editIdx] : null; const dialog = document.createElement('div'); dialog.className = 'sensitive-dialog'; dialog.style.cssText = `position: fixed; top: 20%; left: 50%; transform: translateX(-50%); padding: 20px; z-index: 100000; border: 1px solid #ccc; box-shadow: 0 2px 8px rgba(0,0,0,0.3); max-height: 70vh; overflow-y: auto;`; dialog.innerHTML = ` <h3>${isEdit?'编辑':'添加'}预设</h3> <div id="preset-items"> ${existing?existing.rules.map(r=>`<div><input placeholder="指定内容" value="${r.pattern}"> → <input placeholder="替换为" value="${r.replace}"></div>`).join(''):'<div><input placeholder="指定内容"> → <input placeholder="替换为"></div>'} </div> <button id="add-rule">添加规则</button> <br><br> <input id="preset-name" placeholder="预设名称(可选)" value="${existing?existing.name:''}"><br><br> <button id="save-preset">保存</button> <button id="cancel-preset">取消</button> `; document.body.appendChild(dialog); applyTheme(); // 预设弹窗适配关灯模式 $('#add-rule').onclick=()=>{$('#preset-items').appendChild(document.createElement('div')).innerHTML='<input placeholder="指定内容"> → <input placeholder="替换为">';}; $('#cancel-preset').onclick=()=>dialog.remove(); $('#save-preset').onclick=()=>{ const name=$('#preset-name').value.trim()||`预设${regexPresets.length+1}`; const rules=Array.from(dialog.querySelectorAll('#preset-items > div')).map(div=>{ const inputs=div.querySelectorAll('input'); return {pattern:inputs[0].value.trim(),replace:inputs[1].value}; }).filter(r=>r.pattern.length>0); if(rules.length===0){alert('请至少添加一个有效的预设规则');return;} if(isEdit) regexPresets[editIdx]={name,rules}; else regexPresets.push({name,rules}); localStorage.setItem('sensitive_regex_presets',JSON.stringify(regexPresets)); dialog.remove(); renderPresets(); runDetection(); }; } function renderPresets(){ const container=$('#preset-list'); container.innerHTML=''; regexPresets.forEach((preset,i)=>{ const div=document.createElement('div'); div.style.marginBottom='8px'; div.style.border='1px solid #ddd'; div.style.padding='6px'; div.style.borderRadius='4px'; div.innerHTML=`<b>${preset.name}</b> <button class="btn-load" data-i="${i}">加载</button> <button class="btn-edit" data-i="${i}">编辑</button> <button class="btn-delete" data-i="${i}">删除</button>`; container.appendChild(div); }); container.querySelectorAll('.btn-load').forEach(btn=>{ btn.onclick=()=>{ const preset=regexPresets[btn.dataset.i]; preset.rules.forEach(rule=>replaceWordInInputs(rule.pattern,rule.replace)); runDetection(); }; }); container.querySelectorAll('.btn-edit').forEach(btn=>{ btn.onclick=()=>showPresetDialog(Number(btn.dataset.i)); }); container.querySelectorAll('.btn-delete').forEach(btn=>{ btn.onclick=()=>{ if(confirm('确定删除此预设?')){ regexPresets.splice(Number(btn.dataset.i),1); localStorage.setItem('sensitive_regex_presets',JSON.stringify(regexPresets)); renderPresets(); runDetection(); } }; }); } function replaceWordInInputs(word,replacement){ const inputs=Array.from(document.querySelectorAll('textarea,input[type=text],input[type=search],input:not([type])')).filter(el=>el.offsetParent!==null); inputs.forEach(input=>{ if(input.value.includes(word)){ input.value=input.value.split(word).join(replacement); input.dispatchEvent(new Event('input',{bubbles:true})); } }); } function hookInputEvents(){ const inputs=Array.from(document.querySelectorAll('textarea,input[type=text],input[type=search],input:not([type])')).filter(el=>el.offsetParent!==null); inputs.forEach(input=>input.addEventListener('input',()=>runDetection())); } function runDetection(customRules){ const list=$('#sensitive-word-list'); const status=$('#sensitive-status'); detectedWords.clear(); list.innerHTML=''; const inputs=Array.from(document.querySelectorAll('textarea,input[type=text],input[type=search],input:not([type])')).filter(el=>el.offsetParent!==null); const text=inputs.map(i=>i.value).join('\n'); SENSITIVE_WORDS.forEach(w=>{if(text.includes(w)) detectedWords.add(w);}); const rules=customRules||regexPresets.flatMap(p=>p.rules); rules.forEach(({pattern})=>{ let reg; try{reg=new RegExp(pattern,'gi');}catch{return;} let match; while((match=reg.exec(text))!==null) detectedWords.add(match[0]); }); if(detectedWords.size===0) status.innerHTML='<strong>✅ 没有检测到敏感词</strong>'; else status.innerHTML=`<strong style="color:red">⚠️ 检测到${detectedWords.size}个敏感词</strong>`; detectedWords.forEach(w=>{ const div=document.createElement('div'); div.style.marginBottom='4px'; div.style.wordBreak='break-word'; div.innerHTML=`<strong>${w}</strong> <button class="btn-replace">替换</button>`; const btn=div.querySelector('.btn-replace'); btn.onclick=()=>{ const r=prompt(`将“${w}”替换为:`); if(r!=null){replaceWordInInputs(w,r); runDetection();} }; list.appendChild(div); }); updateToggleButtonText(); } function init(){ createUI(); fetchRemoteWords(); runDetection(); hookInputEvents(); } window.addEventListener('load',init); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址