DMHY Comment Block

Block users and keywords in dmhy comment section

安装此脚本
作者推荐脚本

您可能也喜欢动漫花园种子屏蔽助手

安装此脚本
  1. // ==UserScript==
  2. // @name:zh-CN 动漫花园评论区屏蔽助手
  3. // @name DMHY Comment Block
  4. // @namespace https://github.com/xkbkx5904/dmhy-comment-block
  5. // @version 1.0.9
  6. // @description:zh-CN 屏蔽动漫花园评论区的用户和关键词
  7. // @description Block users and keywords in dmhy comment section
  8. // @author xkbkx5904
  9. // @license MIT
  10. // @language zh-CN
  11. // @match *://share.dmhy.org/topics/view/*
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @run-at document-end
  15. // @noframes
  16. // @supportURL https://github.com/xkbkx5904/dmhy-comment-block/issues
  17. // @homepageURL https://github.com/xkbkx5904/dmhy-comment-block
  18. // @icon https://share.dmhy.org/favicon.ico
  19. // @compatible chrome
  20. // @compatible firefox
  21. // @compatible edge
  22. // @require https://cdn.jsdelivr.net/npm/sweetalert2@11
  23. // ==/UserScript==
  24.  
  25. // 用户黑名单列表
  26. let UserBlockList = [];
  27.  
  28. // 缓存常用的 DOM 选择器结果
  29. const SELECTORS = {
  30. COMMENT_TABLE: '#comment_recent',
  31. COMMENT_ROW: 'tr[id^="comment"]',
  32. USERNAME: '.username',
  33. CONTENT: '.comment_con span:last-child'
  34. };
  35.  
  36. // 正则表达式工具类
  37. const RegexUtils = {
  38. isValid(pattern) {
  39. if (!pattern.startsWith('/') || !pattern.endsWith('/')) return true;
  40. try {
  41. new RegExp(pattern.slice(1, -1));
  42. return true;
  43. } catch (e) {
  44. return false;
  45. }
  46. },
  47.  
  48. toRegex(pattern) {
  49. if (pattern.startsWith('/') && pattern.endsWith('/')) {
  50. return new RegExp(pattern.slice(1, -1));
  51. }
  52. return pattern;
  53. },
  54.  
  55. test(pattern, text) {
  56. if (pattern.startsWith('/') && pattern.endsWith('/')) {
  57. try {
  58. const regex = new RegExp(pattern.slice(1, -1));
  59. return regex.test(text);
  60. } catch {
  61. return false;
  62. }
  63. }
  64. return false;
  65. }
  66. };
  67.  
  68. // 黑名单管理类
  69. const BlockListManager = {
  70. addUser(username, commentId = null) {
  71. let userList = UserBlockList.find(item => item.type === 'users');
  72. if (!userList) {
  73. userList = { type: 'users', values: [] };
  74. UserBlockList.push(userList);
  75. }
  76.  
  77. const user = {
  78. username,
  79. userId: username.startsWith('/') ? null : commentId
  80. };
  81.  
  82. userList.values.push(user);
  83. saveBlockList();
  84. handleComments();
  85. },
  86.  
  87. updateUsers(usernames) {
  88. let userList = UserBlockList.find(item => item.type === 'users');
  89. if (!userList) {
  90. userList = { type: 'users', values: [] };
  91. UserBlockList.push(userList);
  92. }
  93.  
  94. userList.values = usernames.map(username => ({
  95. username,
  96. userId: username.startsWith('/') ? null :
  97. userList.values.find(u => u.username === username)?.userId || null
  98. }));
  99. },
  100.  
  101. updateKeywords(keywords) {
  102. let keywordItem = UserBlockList.find(item => item.type === 'keywords');
  103. if (!keywordItem) {
  104. keywordItem = { type: 'keywords', values: [] };
  105. UserBlockList.push(keywordItem);
  106. }
  107. keywordItem.values = keywords.map(RegexUtils.toRegex);
  108. }
  109. };
  110.  
  111. // 从本地存储加载黑名单
  112. function loadBlockList() {
  113. try {
  114. const saved = GM_getValue('dmhy_comment_blocklist', []);
  115. UserBlockList = Array.isArray(saved) ? saved.map(item => {
  116. if (item.type === 'keywords') {
  117. return {
  118. type: 'keywords',
  119. values: item.values.map(k => {
  120. if (typeof k === 'string' && k.startsWith('/') && k.endsWith('/')) {
  121. try {
  122. return new RegExp(k.slice(1, -1));
  123. } catch (e) {
  124. return k;
  125. }
  126. }
  127. return k;
  128. })
  129. };
  130. }
  131. return item;
  132. }) : [];
  133. } catch (err) {
  134. UserBlockList = [];
  135. }
  136. }
  137.  
  138. // 保存黑名单到本地存储
  139. function saveBlockList() {
  140. try {
  141. const listToSave = UserBlockList.map(item => {
  142. if (item.type === 'keywords') {
  143. return {
  144. type: 'keywords',
  145. values: item.values.map(k => {
  146. if (k instanceof RegExp) {
  147. return `/${k.source}/`;
  148. }
  149. return k;
  150. })
  151. };
  152. }
  153. return item;
  154. });
  155. GM_setValue('dmhy_comment_blocklist', listToSave);
  156. } catch (err) {
  157. console.error('保存黑名单失败:', err);
  158. }
  159. }
  160.  
  161. // 处理评论显示
  162. function handleComments() {
  163. const comments = document.querySelectorAll(SELECTORS.COMMENT_ROW);
  164. if (!comments.length) return;
  165.  
  166. // 预先获取黑名单数据
  167. const userList = UserBlockList.find(item => item.type === 'users')?.values || [];
  168. const blockedKeywords = UserBlockList.find(item => item.type === 'keywords')?.values || [];
  169.  
  170. comments.forEach(comment => {
  171. try {
  172. const commentId = comment.id.replace('comment', '');
  173. const usernameEl = comment.querySelector(SELECTORS.USERNAME);
  174. if (!usernameEl) return;
  175.  
  176. const username = usernameEl.textContent.trim();
  177. const content = comment.querySelector(SELECTORS.CONTENT)?.textContent?.trim() || '';
  178.  
  179. // 处理用户名链接
  180. if (!usernameEl.querySelector('a')) {
  181. const userLink = document.createElement('a');
  182. userLink.className = 'user-link';
  183. userLink.style.cssText = 'color:blue;text-decoration:underline;cursor:pointer;';
  184. userLink.textContent = username;
  185. userLink.oncontextmenu = (e) => {
  186. e.preventDefault();
  187. showContextMenu(e, commentId);
  188. return false;
  189. };
  190.  
  191. usernameEl.innerHTML = '';
  192. usernameEl.appendChild(userLink);
  193. }
  194.  
  195. // 重置显示状态并检查是否需要屏蔽
  196. comment.style.removeProperty('display');
  197. if (shouldBlockComment(username, content, commentId, userList, blockedKeywords)) {
  198. comment.style.display = 'none';
  199. }
  200. } catch (err) {
  201. console.error('Error processing comment:', err);
  202. }
  203. });
  204. }
  205.  
  206. // 判断是否需要屏蔽评论
  207. function shouldBlockComment(username, content, commentId, userList, blockedKeywords) {
  208. if (!username) return false;
  209.  
  210. // 检查用户名
  211. const isUserBlocked = userList.some(user => {
  212. // 正则匹配
  213. if (user.username.startsWith('/')) {
  214. return RegexUtils.test(user.username, username);
  215. }
  216. // 普通用户名匹配
  217. const isMatch = user.username === username;
  218. if (isMatch && !user.userId && commentId) {
  219. user.userId = parseInt(commentId);
  220. saveBlockList();
  221. }
  222. return isMatch || (user.userId && user.userId === parseInt(commentId));
  223. });
  224.  
  225. if (isUserBlocked) return true;
  226.  
  227. // 检查关键词
  228. return Boolean(content) && blockedKeywords.some(keyword =>
  229. typeof keyword === 'string'
  230. ? content.toLowerCase().includes(keyword.toLowerCase())
  231. : keyword.test(content)
  232. );
  233. }
  234.  
  235. // 显示右键菜单
  236. function showContextMenu(event, commentId) {
  237. const menu = document.getElementById('dmhy-comment-context-menu');
  238. if (!menu) return;
  239.  
  240. menu.style.position = 'fixed';
  241. menu.style.left = event.clientX + 'px';
  242. menu.style.top = event.clientY + 'px';
  243. menu.style.display = 'block';
  244. const username = event.target.textContent;
  245. const blockUserOption = document.getElementById('block-comment-user');
  246. if (blockUserOption) {
  247. blockUserOption.onclick = function(e) {
  248. e.stopPropagation();
  249. BlockListManager.addUser(username, commentId);
  250. menu.style.display = 'none';
  251. };
  252. }
  253.  
  254. // 改进关闭菜单的逻辑
  255. const closeMenu = (e) => {
  256. if (!menu.contains(e.target)) {
  257. menu.style.display = 'none';
  258. document.removeEventListener('click', closeMenu);
  259. document.removeEventListener('scroll', closeMenu);
  260. }
  261. };
  262.  
  263. window.addEventListener('scroll', closeMenu, { once: true });
  264. setTimeout(() => {
  265. document.addEventListener('click', closeMenu);
  266. }, 0);
  267. }
  268.  
  269. // 添加管理界面
  270. function addBlocklistUI() {
  271. const maxAttempts = 5;
  272. let attempts = 0;
  273.  
  274. function checkAndAddUI() {
  275. const mainBlocklistUI = document.getElementById('dmhy-blocklist-ui');
  276. if (mainBlocklistUI) {
  277. const mainButton = mainBlocklistUI.querySelector('button');
  278. if (mainButton) {
  279. mainButton.textContent = '管理种子黑名单';
  280. }
  281. if (!document.getElementById('show-comment-blocklist')) {
  282. const button = document.createElement('button');
  283. button.id = 'show-comment-blocklist';
  284. button.textContent = '管理评论黑名单';
  285. button.style.marginTop = '5px';
  286. button.style.display = 'block';
  287. mainBlocklistUI.appendChild(button);
  288. button.addEventListener('click', showBlocklistManager);
  289. }
  290. } else {
  291. attempts++;
  292. if (attempts < maxAttempts) {
  293. setTimeout(checkAndAddUI, 200);
  294. } else {
  295. const uiHtml = `
  296. <div id="dmhy-comment-blocklist-ui" style="position:fixed;left:10px;top:10px;z-index:9999;">
  297. <button id="show-comment-blocklist">管理评论黑名单</button>
  298. </div>
  299. `;
  300. document.body.insertAdjacentHTML('beforeend', uiHtml);
  301. document.getElementById('show-comment-blocklist')?.addEventListener('click', showBlocklistManager);
  302. }
  303. }
  304. }
  305.  
  306. checkAndAddUI();
  307. }
  308.  
  309. // 显示黑名单管理界面
  310. function showBlocklistManager() {
  311. const existingManager = document.getElementById('comment-blocklist-manager');
  312. const existingOverlay = document.getElementById('comment-blocklist-overlay');
  313. if (existingManager) existingManager.remove();
  314. if (existingOverlay) existingOverlay.remove();
  315.  
  316. const managerHtml = `
  317. <div id="comment-blocklist-manager" style="position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);
  318. background:white;padding:20px;border:1px solid #ccc;border-radius:5px;z-index:10000;
  319. width:500px;max-height:80vh;overflow-y:auto;">
  320. <h3 style="margin-top:0;">评论区黑名单管理</h3>
  321. <div style="margin-bottom:10px;">
  322. <label>用户黑名单(注意是用户名,用分号分隔):</label><br>
  323. <textarea id="blocked-usernames" style="width:100%;height:60px;margin-top:5px;resize:none;"></textarea>
  324. </div>
  325. <div style="margin-bottom:10px;">
  326. <label>关键词屏蔽(用分号分隔):</label><br>
  327. <textarea id="comment-keywords" style="width:100%;height:60px;margin-top:5px;resize:none;"></textarea>
  328. <div style="color:#666;font-size:12px;margin-top:5px;">
  329. 提示:支持普通文本和正则表达式<br>
  330. - 普通文本直接输入,用分号分隔<br>
  331. - 正则表达式用 / 包裹,例如:/\\d+/<br>
  332. - 示例:文本1;/user\\d+/;文本2
  333. </div>
  334. </div>
  335. <div style="margin-top:10px;text-align:right;">
  336. <button id="save-comment-blocklist">保存</button>
  337. <button id="close-comment-manager">关闭</button>
  338. </div>
  339. </div>
  340. <div id="comment-blocklist-overlay" style="position:fixed;top:0;left:0;right:0;bottom:0;
  341. background:rgba(0,0,0,0.5);z-index:9999;"></div>
  342. `;
  343.  
  344. document.body.insertAdjacentHTML('beforeend', managerHtml);
  345.  
  346. // 填充现有数据
  347. const userList = UserBlockList.find(item => item.type === 'users')?.values || [];
  348. const keywords = UserBlockList.find(item => item.type === 'keywords')?.values || [];
  349.  
  350. document.getElementById('blocked-usernames').value = userList
  351. .map(user => user.username)
  352. .filter(username => username)
  353. .join(';');
  354.  
  355. document.getElementById('comment-keywords').value = keywords.map(k => {
  356. if (k instanceof RegExp) {
  357. return `/${k.source}/`;
  358. }
  359. return k;
  360. }).join(';');
  361.  
  362. // 绑定事件
  363. document.getElementById('close-comment-manager').addEventListener('click', function() {
  364. document.getElementById('comment-blocklist-manager')?.remove();
  365. document.getElementById('comment-blocklist-overlay')?.remove();
  366. });
  367.  
  368. document.getElementById('comment-blocklist-overlay').addEventListener('click', function(e) {
  369. if (e.target === this) {
  370. document.getElementById('comment-blocklist-manager')?.remove();
  371. document.getElementById('comment-blocklist-overlay')?.remove();
  372. }
  373. });
  374.  
  375. // 保存按钮事件
  376. document.getElementById('save-comment-blocklist').addEventListener('click', function() {
  377. const usernames = document.getElementById('blocked-usernames').value
  378. .split(/[;;]/)
  379. .map(name => name.trim())
  380. .filter(Boolean);
  381.  
  382. const keywords = document.getElementById('comment-keywords').value
  383. .split(/[;;]/)
  384. .map(k => k.trim())
  385. .filter(Boolean);
  386.  
  387. // 验证正则表达式
  388. const invalidUsername = usernames.find(name => !RegexUtils.isValid(name));
  389. if (invalidUsername) {
  390. showNotification(`用户名正则表达式错误: ${invalidUsername}`);
  391. return;
  392. }
  393.  
  394. const invalidKeyword = keywords.find(k => !RegexUtils.isValid(k));
  395. if (invalidKeyword) {
  396. showNotification(`关键词正则表达式错误: ${invalidKeyword}`);
  397. return;
  398. }
  399.  
  400. // 更新数据
  401. BlockListManager.updateUsers(usernames);
  402. BlockListManager.updateKeywords(keywords);
  403.  
  404. saveBlockList();
  405. handleComments();
  406. document.getElementById('comment-blocklist-manager')?.remove();
  407. document.getElementById('comment-blocklist-overlay')?.remove();
  408. showNotification('黑名单已更新');
  409. });
  410. }
  411.  
  412. // 添加右键菜单
  413. function addContextMenu() {
  414. const menuHtml = `
  415. <div id="dmhy-comment-context-menu" style="
  416. display: none;
  417. position: fixed;
  418. background: white;
  419. border: 1px solid #ccc;
  420. border-radius: 3px;
  421. padding: 5px;
  422. box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
  423. z-index: 10000;
  424. min-width: 150px;
  425. ">
  426. <div id="block-comment-user" style="
  427. padding: 8px 12px;
  428. cursor: pointer;
  429. white-space: nowrap;
  430. ">
  431. 添加评论用户到黑名单
  432. </div>
  433. </div>
  434. `;
  435. document.body.insertAdjacentHTML('beforeend', menuHtml);
  436.  
  437. const blockUserOption = document.getElementById('block-comment-user');
  438. if (blockUserOption) {
  439. blockUserOption.addEventListener('mouseover', () => {
  440. blockUserOption.style.backgroundColor = '#f0f0f0';
  441. });
  442. blockUserOption.addEventListener('mouseout', () => {
  443. blockUserOption.style.backgroundColor = '';
  444. });
  445. }
  446. }
  447.  
  448. // 显示通知
  449. function showNotification(message) {
  450. const notification = document.createElement('div');
  451. notification.style.cssText = `
  452. position: fixed;
  453. top: 20px;
  454. left: 50%;
  455. transform: translateX(-50%);
  456. background: rgba(0, 0, 0, 0.8);
  457. color: white;
  458. padding: 10px 20px;
  459. border-radius: 4px;
  460. z-index: 10001;
  461. font-size: 14px;
  462. transition: opacity 0.3s;
  463. `;
  464. notification.textContent = message;
  465. document.body.appendChild(notification);
  466.  
  467. setTimeout(() => {
  468. notification.style.opacity = '0';
  469. setTimeout(() => notification.remove(), 300);
  470. }, 2000);
  471. }
  472.  
  473. // 等待评论区加载
  474. function waitForComments() {
  475. return new Promise((resolve) => {
  476. const commentTable = document.querySelector(SELECTORS.COMMENT_TABLE);
  477. if (commentTable?.querySelector(SELECTORS.USERNAME)) {
  478. resolve();
  479. return;
  480. }
  481.  
  482. let attempts = 0;
  483. const maxAttempts = 20;
  484. const interval = setInterval(() => {
  485. const commentTable = document.querySelector(SELECTORS.COMMENT_TABLE);
  486. if (commentTable?.querySelector(SELECTORS.USERNAME)) {
  487. clearInterval(interval);
  488. resolve();
  489. return;
  490. }
  491.  
  492. attempts++;
  493. if (attempts >= maxAttempts) {
  494. clearInterval(interval);
  495. resolve();
  496. }
  497. }, 500);
  498. });
  499. }
  500.  
  501. // 初始化
  502. (function() {
  503. 'use strict';
  504. loadBlockList();
  505. addBlocklistUI();
  506. addContextMenu();
  507.  
  508. waitForComments().then(() => {
  509. handleComments();
  510.  
  511. const commentList = document.querySelector('#comment_list');
  512. if (commentList) {
  513. const observer = new MutationObserver((mutations) => {
  514. for (const mutation of mutations) {
  515. if (mutation.addedNodes.length > 0) {
  516. handleComments();
  517. }
  518. }
  519. });
  520.  
  521. observer.observe(commentList, {
  522. childList: true,
  523. subtree: true
  524. });
  525. }
  526. });
  527. })();