Greasy Fork is available in English.
检测bangumi发布/修改内容中含有的敏感词,并对其进行单个替换或批量替换,同时支持自定义预设
当前为
// ==UserScript==
// @name bangumi 敏感词替换+自定义预设
// @namespace http://greasyfork.icu/zh-CN/users/1386262-zintop
// @version 1.1.0
// @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|subject\/.+|settings|index\/create|index\/.+\/edit|anime\/list\/.+).*/
// @grant none
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'sensitive_panel_settings';
const SENSITIVE_WORDS = [
"白粉","办证","辦證","毕业证","畢業證","冰毒","步枪","步槍","春药","春藥","大发","大發",
"大麻","代开","代開","代考","贷款","貸款","发票","發票","海洛因","妓女","精神病","可卡因",
"批发","批發","皮肤病","皮膚病","嫖娼","窃听器","竊聽器","上门服务","上門服務","商铺","商鋪",
"手枪","手槍","铁枪","鐵槍","钢枪","鋼槍","特殊服务","特殊服務","騰訊","香烟","香煙","学位证",
"學位證","摇头丸","搖頭丸","医院","醫院","隐形眼镜","聊天记录","援交","找小姐","找小妹","作弊",
"v信","迷药","电动车","早泄","毒枭","春节","当场死亡","烟草","假钞","罂粟","牛皮癣","甲状腺",
"安乐死","香艳","医疗政策","服务中心","习近平","李克强","支那","前列腺","迷魂药","迷情粉",
"迷藥","麻醉药","肛门","麻果","麻古","假币","私人侦探","提现","借腹生子","代孕","客服电话",
"刻章","套牌车","麻将机","走私","财税务","医疗","安眠药","weed","干扰器","快3","快三","住宿费"
];
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;
}
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; background:#E9E8E8; 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-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>
</div>
<div id="preset-list" style="padding:5px;"></div>
`;
document.body.appendChild(panel);
// 拖动
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;
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.style.cssText = `position: fixed; top: 20%; left: 50%; transform: translateX(-50%);
background: #E9E8E8; 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);
$('#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 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 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 init(){ createUI(); runDetection(); hookInputEvents(); }
window.addEventListener('load',init);
})();