// ==UserScript== // @name 留言审核辅助工具WZ // @namespace http://tampermonkey.net/ // @version 1.2.1 // @description 自动扫描、高亮关键词、跨页检测重复内容 // @author Furnace // @match https://sdxw-admin.iqilu.com/cq-admin/* // @grant none // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/567306/%E7%95%99%E8%A8%80%E5%AE%A1%E6%A0%B8%E8%BE%85%E5%8A%A9%E5%B7%A5%E5%85%B7WZ.user.js // @updateURL https://update.greasyfork.icu/scripts/567306/%E7%95%99%E8%A8%80%E5%AE%A1%E6%A0%B8%E8%BE%85%E5%8A%A9%E5%B7%A5%E5%85%B7WZ.meta.js // ==/UserScript== (function() { 'use strict'; // ================= 核心配置区 ================= // 结合你提供的 iView 结构定制的选择器 const SELECTORS = { row: '.ivu-table-row', content: '.flowline-2', status: '.ivu-badge-status-text' }; // ============================================== const defaultKeywords = "共产党、毛主席、法院、检察院、枉法、公安、派出所、劳动法、贪、集资、强拆、黑恶、黑社会、反动、淫、嫖、生殖、赌、毒品、黄赌毒、上帝、全能神、宗教、伊斯兰、基督、佛、回族、回民、纪委、省长信箱、上访、信访、城投、房改、军人、控告、黑物业、习近平、国补、国家补贴、烂尾、诈骗、换届、选举、黑物业、替百姓说话|驳回\n工资、农民工、欠款、欠薪、12345、供电、回迁、上房、交房、起诉、执行、人才补贴、基本农田、国土资源、盗采|仅管理可见\n已解决|通过"; let commentHashes = JSON.parse(localStorage.getItem('audit_comment_hashes') || '{}'); let scannedIds = JSON.parse(localStorage.getItem('audit_scanned_ids') || '{}'); // 新增:用于记录留言的唯一身份证 let keywordsConfig = localStorage.getItem('audit_keywords') || defaultKeywords; let totalScanned = parseInt(localStorage.getItem('audit_total_scanned') || '0'); function getKeywordsList() { let list = []; // 按行拆分 keywordsConfig.split('\n').forEach(line => { const parts = line.split('|'); const wordsPart = parts[0]; const action = parts[1]?.trim() || '需审核'; // 如果没写操作,默认提示"需审核" if (wordsPart) { // 支持使用中文逗号、英文逗号或顿号来分隔多个关键词 const words = wordsPart.split(/[,,、]/); words.forEach(w => { if (w.trim()) { list.push({ word: w.trim(), action: action }); } }); } }); return list; } function simpleHash(str) { let hash = 0; for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash = hash & hash; } return hash.toString(); } function createFloatingPanel() { if (document.getElementById('audit-panel')) return; const panel = document.createElement('div'); panel.id = 'audit-panel'; panel.style.cssText = ` position: fixed; bottom: 20px; left: 20px; width: 240px; background: #fff; border: 1px solid #dcdee2; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 15px; border-radius: 8px; z-index: 9999; font-size: 14px; color: #515a6e; font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; `; panel.innerHTML = `
审核助手
今日扫描:${totalScanned}
`; document.body.appendChild(panel); document.getElementById('audit-btn-reset').addEventListener('click', () => { if (confirm('确定要清空今天的扫描记录吗?(建议每次审核开始前点击)')) { localStorage.removeItem('audit_comment_hashes'); localStorage.removeItem('audit_scanned_ids'); // 新增:清空已扫描的唯一ID库 localStorage.removeItem('audit_total_scanned'); location.reload(); } }); document.getElementById('audit-btn-settings').addEventListener('click', () => { const area = document.getElementById('audit-settings-area'); const input = document.getElementById('audit-keyword-input'); area.style.display = area.style.display === 'none' ? 'block' : 'none'; input.value = localStorage.getItem('audit_keywords') || defaultKeywords; }); document.getElementById('audit-btn-save').addEventListener('click', () => { localStorage.setItem('audit_keywords', document.getElementById('audit-keyword-input').value); location.reload(); }); } function processComments() { const rows = document.querySelectorAll(SELECTORS.row); if (rows.length === 0) { setTimeout(processComments, 1000); return; } const keywordsList = getKeywordsList(); let newScannedCount = 0; rows.forEach(row => { // 防止单次页面生命周期内的重复遍历 if (row.dataset.audited) return; row.dataset.audited = "true"; const statusEl = row.querySelector(SELECTORS.status); const contentEl = row.querySelector(SELECTORS.content); if (!contentEl) return; const titleEl = contentEl.previousElementSibling; const isProcessed = statusEl && (statusEl.innerText.includes('已审核') || statusEl.innerText.includes('通过') || statusEl.innerText.includes('驳回')); const textContent = contentEl.innerText.trim(); if (!textContent) return; // ===== 核心修改:利用正则直接抓取时间戳,生成双哈希 ===== // 正则匹配类似 "2026-02-24 09:42:15" 的标准时间格式 const timeMatch = row.innerText.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/); const timeStr = timeMatch ? timeMatch[0] : ''; // 唯一身份ID (时间+内容):用于防刷新误判 const rowUniqueId = simpleHash(timeStr + textContent); // 内容查重ID (纯内容):用于检测是不是有人发了同样的话 const textHash = simpleHash(textContent); // ======================================================== const isAlreadyScanned = scannedIds[rowUniqueId]; // 检查这具体的一条是否记录过 // 处理“已审核”状态的豁免 if (isProcessed) { if (!commentHashes[textHash] || commentHashes[textHash].status !== 'processed') { commentHashes[textHash] = { count: 1, status: 'processed' }; } // 即使是已处理的,也把它记入已扫描ID,防止重复计算 if (!isAlreadyScanned) { scannedIds[rowUniqueId] = true; newScannedCount++; } return; } // ===== 计数与查重逻辑 (仅针对之前没见过的“新ID”) ===== if (!isAlreadyScanned) { scannedIds[rowUniqueId] = true; // 登记该 ID newScannedCount++; // 新增扫描量 // 检查内容是否重复 if (commentHashes[textHash]) { if (commentHashes[textHash].status !== 'processed') { commentHashes[textHash].count += 1; } } else { commentHashes[textHash] = { count: 1, status: 'pending' }; } } // ===== UI 渲染:无论是否新扫描,只要库里显示它重复了,就高亮 ===== if (commentHashes[textHash] && commentHashes[textHash].count > 1 && commentHashes[textHash].status !== 'processed') { const cells = row.querySelectorAll('td'); cells.forEach(td => { td.style.backgroundColor = '#fff3cd'; }); if(cells.length === 0) row.style.backgroundColor = '#fff3cd'; if (titleEl) addTag(titleEl, `重复 (共${commentHashes[textHash].count}次)`, '#856404', '#ffe8a1'); } // ===== 提取标题文本,用于关键词匹配 ===== const titleText = titleEl ? titleEl.innerText.trim() : ''; const fullSearchText = titleText + " " + textContent; // 将标题和正文合并作为搜索范围 // ===== 关键词匹配渲染 ===== let matchedKeyword = null; for (let k of keywordsList) { if (fullSearchText.includes(k.word)) { // 改为在合并后的文本中查找 matchedKeyword = k; break; } } if (matchedKeyword) { const cells = row.querySelectorAll('td'); cells.forEach(td => { td.style.backgroundColor = '#f8d7da'; }); if(cells.length === 0) row.style.backgroundColor = '#f8d7da'; let color = matchedKeyword.action === '通过' ? '#19be6b' : (matchedKeyword.action === '驳回' ? '#ed4014' : '#ff9900'); if (titleEl) addTag(titleEl, `建议:${matchedKeyword.action} (${matchedKeyword.word})`, '#fff', color); } }); // 批量保存数据 if (newScannedCount > 0) { localStorage.setItem('audit_comment_hashes', JSON.stringify(commentHashes)); localStorage.setItem('audit_scanned_ids', JSON.stringify(scannedIds)); // 存入唯一ID记录 totalScanned += newScannedCount; localStorage.setItem('audit_total_scanned', totalScanned.toString()); const countEl = document.getElementById('audit-count'); if(countEl) countEl.innerText = totalScanned; } } function addTag(element, text, color, bgColor) { const tag = document.createElement('span'); tag.innerText = text; tag.style.cssText = ` margin-left: 8px; padding: 2px 6px; font-size: 12px; border-radius: 4px; color: ${color}; background-color: ${bgColor}; font-weight: normal; vertical-align: middle; `; // 追加在标题后面 element.appendChild(tag); } // iView 页面可能因为路由切换或接口请求导致 DOM 延迟渲染,这里使用 MutationObserver 监听列表变化 window.addEventListener('load', () => { createFloatingPanel(); processComments(); // 监听 DOM 变化,适配传统的 Ajax 翻页不刷新页面的情况 const observer = new MutationObserver((mutations) => { let shouldProcess = false; for (let mutation of mutations) { if (mutation.addedNodes.length > 0) { shouldProcess = true; break; } } if (shouldProcess) { // 防抖处理,避免频繁触发 clearTimeout(window.processTimer); window.processTimer = setTimeout(processComments, 500); } }); observer.observe(document.body, { childList: true, subtree: true }); }); })();