您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
检测bangumi发布/修改内容中含有的敏感词,并对其进行单个替换或批量替换,同时支持自定义预设,不局限于敏感词列表
// ==UserScript== // @name bangumi 敏感词替换+自定义预设 // @namespace https://greasyfork.org/zh-CN/users/1386262-zintop // @version 1.0.3 // @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).*/ // @grant none // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY = 'sensitive_panel_settings'; const SENSITIVE_WORDS = [ "白粉", "办证", "辦證", "毕业证", "畢業證", "冰毒", "步枪", "步槍", "春药", "春藥", "大发", "大發", "大麻", "代开", "代開", "代考", "贷款", "貸款", "发票", "發票", "海洛因", "妓女", "精神病", "可卡因", "批发", "批發", "皮肤病", "皮膚病", "嫖娼", "窃听器", "竊聽器", "上门服务", "上門服務", "商铺", "商鋪", "手枪", "手槍", "铁枪", "鐵槍", "钢枪", "鋼槍", "特殊服务", "特殊服務", "騰訊", "香烟", "香煙", "学位证", "學位證", "摇头丸", "搖頭丸", "医院", "醫院", "隐形眼镜", "聊天记录", "援交", "找小姐", "找小妹", "作弊", "v信", "迷药", "电动车", "早泄", "毒枭", "春节", "当场死亡", "烟草", "假钞", "罂粟", "牛皮癣", "甲状腺", "安乐死", "香艳", "医疗政策", "服务中心", "习近平", "李克强", "支那", "前列腺", "迷魂药", "迷情粉", "迷藥", "麻醉药", "肛门", "麻果", "麻古", "假币", "私人侦探", "提现", "借腹生子", "代孕", "客服电话", "刻章", "套牌车", "麻将机", "走私", "财税务" ]; let detectedWords = new Set(); let regexPresets = JSON.parse(localStorage.getItem('sensitive_regex_presets') || '[]'); 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; padding: 0; 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; `; setTimeout(() => loadPanelSettings(panel), 0); panel.innerHTML = ` <div id="sensitive-header" style="background:#f99;color:#fff;padding:5px;cursor:move;"> 敏感词检测 </div> <div id="sensitive-body" style="padding:10px;"> <div id="sensitive-status"><strong>✅ 没有检测到敏感词</strong></div> <div id="sensitive-word-list" style="margin:10px 0;"></div> <div style="margin-bottom:10px;"> <button id="btn-replace-all">全部替换</button> <button id="btn-replace-star">全部替换为**</button> </div> <hr> <button id="btn-add-preset">添加预设</button> <div id="preset-list" style="margin-top:10px;"></div> <hr> <div>透明度: <input type="range" id="opacity-slider" min="0.2" max="1" step="0.05" value="1"> </div> </div> `; document.body.appendChild(panel); // 拖动 let isDragging = false, offsetX, offsetY; const header = panel.querySelector('#sensitive-header'); header.onmousedown = function (e) { isDragging = true; offsetX = e.clientX - panel.offsetLeft; offsetY = e.clientY - panel.offsetTop; document.onmousemove = function (e) { if (isDragging) { panel.style.left = (e.clientX - offsetX) + 'px'; panel.style.top = (e.clientY - offsetY) + 'px'; panel.style.right = 'auto'; } }; document.onmouseup = () => { if (isDragging) { isDragging = false; savePanelSettings(panel); } }; }; panel.onmouseup = () => savePanelSettings(panel); // 透明度 $('#opacity-slider').oninput = (e) => { panel.style.opacity = e.target.value; savePanelSettings(panel); }; // 全部替换 $('#btn-replace-all').onclick = () => { const arr = Array.from(detectedWords); (function next(i) { if (i >= arr.length) return; const w = arr[i]; const r = prompt(`将“${w}”替换为:`); if (r != null) { replaceWordInInputs(w, r); } next(i + 1); })(0); detectedWords.clear(); updatePanel(); }; // 全部替换为星号 $('#btn-replace-star').onclick = () => { detectedWords.forEach(w => { replaceWordInInputs(w, '*'.repeat(w.length)); }); detectedWords.clear(); updatePanel(); }; // 添加预设 $('#btn-add-preset').onclick = showPresetDialog; renderPresets(); } 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 = () => { const div = document.createElement('div'); div.innerHTML = `<input placeholder="指定内容"> → <input placeholder="替换为">`; $('#preset-items').appendChild(div); }; $('#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 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); let 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 (e) { 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 line = document.createElement('div'); line.style.marginBottom = '4px'; line.style.wordBreak = 'break-word'; line.innerHTML = `<strong>${w}</strong> <button data-word="${w}" class="btn-replace">替换</button>`; list.appendChild(line); }); list.querySelectorAll('.btn-replace').forEach(btn => { btn.onclick = () => { const w = btn.dataset.word; const r = prompt(`将“${w}”替换为:`); if (r != null) { replaceWordInInputs(w, r); detectedWords.delete(w); updatePanel(); } }; }); } } 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 updatePanel() { runDetection(); } function $(s) { return document.querySelector(s); } 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); })();