├── README.md ├── SECURITY.md ├── icon.jpg ├── manifest.json └── src ├── main.js ├── preload.js └── renderer.js /README.md: -------------------------------------------------------------------------------- 1 | ![qqMessageBlocker](https://socialify.git.ci/elegantland/qqMessageBlocker/image?description=1&forks=1&issues=1&language=1&name=1&stargazers=1&theme=Light) 2 | 一个LiteLoaderQQNT 插件,依然需要大家的提供更好的关于新功能的想法!如果遇到问题我很乐意解决。 3 | 4 | 问题反馈&需要帮助:直接提issue或者2543971286@qq.com 5 | 6 | ## ✨ 功能特性 7 | 8 | - 🚫 多样化的屏蔽选项 9 | - 关键字屏蔽 10 | - Emoji 表情屏蔽 11 | - 图片屏蔽和表情包屏蔽 12 | - 群机器人屏蔽 13 | - 超级表情屏蔽 14 | - 用户屏蔽 15 | - 📱 支持多设备配置同步(导入/导出) 16 | - 🎨 美观的设置界面 17 | - 🔄 实时生效的屏蔽规则(可能依然需要重启) 18 | 19 | ## 📥 安装方法 20 | 21 | 1. 下载并安装最新版本的 [LiteLoaderQQNT](https://liteloaderqqnt.github.io) 22 | 2. 在 QQ 设置中找到 LiteLoaderQQNT 的设置界面 23 | 3. 安装本插件 24 | 4. 重启 QQ 即可生效 25 | 26 | ## 🎯 使用指南 27 | 28 | > 💡 建议:每次更新前都导出一下自己的配置! 29 | 30 | ### 基础使用 31 | 32 | 更全面的相关介绍在[elegantland.github.io](https://elegantland.github.io/) 33 | 34 | 现在插件提供了可视化的设置界面,你可以直接在设置中进行规则配置。以下是一些基本的规则添加说明: 35 | 36 | ### 添加文字屏蔽规则 37 | 38 | 直接在设置界面添加关键词即可,许多配置均支持两种匹配模式: 39 | - 完全匹配:消息需要与规则完全一致 40 | - 包含匹配:消息包含规则内容即可 41 | - 现在在2.0.5之后的版本可以直接右键屏蔽了 42 | 43 | ### 添加 Emoji 屏蔽规则 44 | 45 | 1. 安装 [chii-devtools](https://github.com/mo-jinran/chii-devtools/tree/v4) 46 | 2. 按 F12 打开开发工具 47 | 3. 右键需要屏蔽的表情消息 48 | 4. 在开发工具中找到 `data-face-index` 属性 49 | 5. 将对应的表情代码添加到屏蔽规则中 50 | 6. 现在在2.0.5之后的版本可以直接右键屏蔽了 51 | 52 | ### 添加图片屏蔽规则 53 | 54 | 1. 同样使用 chii-devtools 55 | 2. 对需要屏蔽的图片进行点击右键然后在开发工具中查找 `data-path` 属性 56 | 3. 复制类似 `1A930D2313002CD5A7F2572DE36F9257.jpg` 的图片 ID 57 | 4. 将 ID 添加到屏蔽规则中 58 | 5. 现在在2.0.5之后的版本可以直接右键屏蔽了 59 | 60 | ## ⚙️ 高级功能 61 | 62 | ### 配置导入导出 63 | 在设置界面中可以方便地导入导出配置,支持多设备间的配置同步。 64 | 65 | ### 右键菜单快捷屏蔽 66 | - 右键点击文字或图片可快速添加屏蔽规则 67 | - 自动使用完全匹配模式 68 | - 可在设置界面查看和管理添加的规则 69 | 70 | ## ❗ 已知问题 71 | 72 | - 查看聊天记录时消息也会被屏蔽(底层逻辑限制,暂无解决方案,但是你可以使用MessageSave来查看历史消息) 73 | 74 | ## 🔌 推荐插件 75 | 76 | - [chii-devtools](https://github.com/mo-jinran/chii-devtools/tree/v4) - 开发调试工具 77 | - [QQ禁用更新](https://github.com/xh321/LiteLoaderQQNT-Kill-Update/tree/master) - 禁止 QQ 自动更新 78 | - [插件列表查看](https://github.com/ltxhhz/LL-plugin-list-viewer/tree/main) - 查看和管理插件 79 | - [QQ防撤回](https://github.com/xh321/LiteLoaderQQNT-Anti-Recall/tree/master) - 防止消息被撤回 80 | - [MessageSave](https://github.com/elegantland/qqMessageSave) - 方便的将QQ消息保存到本地 81 | 82 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## 更新日志 2 | ### v2.1.1 (2024-5-30) 3 | - 添加开关面板,增加了四个常用的开关(在设置面板最底下)。基于1.2.2版本,如果不适应某个版本请反馈。 4 | 5 | ### v2.1.0 (2024-2-14) 6 | - 对2.1.0版本的设置流畅度进行优化,可能效果更好。 7 | - 继续优化了屏蔽设置的浏览逻辑,但还是不好用。 8 | - 优化处理事务的优先级,目前在低性能设备上的卡顿问题初步优化了。 9 | - 原本预想着是加入对QQ频道的支持,但由于技术力问题+适配问题决定单开一个坑。今后版本不处理QQ频道内容。 10 | 11 | ### v2.0.9 (2024-1-23) 12 | - 优化了对超级表情/接龙表情/随机表情的性能开销和检测稳定性。 13 | - 更新设置的浏览,新增了展开收起按钮(有bug:展开时删除会收起(不修),数字排序有点问题(不修))。 14 | - 加了一个看更新日志的按钮,修复了不知道哪个版本产生的导出配置失败bug。 15 | - 优化了设置界面代码重复度。 16 | 17 | ### v2.0.8 (2024-12-27) 18 | - 完美修复了没法屏蔽拍一拍的功能,默认开启。原来从2.0.6版本开始拍一拍屏蔽功能就一直有问题,于是修修补补一个月。 19 | - 现在可以屏蔽系统消息了,指各种和你无关的进群/撤回消息以及未来的任何新的系统提示消息,当然这需要去设置里手动设置屏蔽词,公共消息屏蔽那一栏。 20 | - 对转发消息进行优化,外面展示消息内容也会被屏蔽。 21 | - 从这个版本开始暂缓一周打包,真急需功能可以直接替换renderer.js。 22 | 23 | ### v2.0.7 (2024-12-26) 24 | - 右键菜单优化了文本的选取和图片新加了屏蔽某人全部图片,还有把emoji表情的具体代码实现了,右键菜单变的更好用了! 25 | - 去掉了2.0.6版本的消息保存函数,迁移到我的新插件,欢迎体验我的新插件,假如有消息被屏蔽而你又有点想知道内容也可以在这里找到,目前还在测试中... [MessageSave](https://github.com/elegantland/qqMessageSave) 26 | - 添加了屏蔽拍一拍功能,然后发现某些系统信息(xxx加入了群)这类消息显示有点问题,于是一起屏蔽了,结果发现系统消息也是必要的,目前取消了系统消息的限制,如果有人需要的话后续版本可能会添加相应逻辑但不会做到设置界面里。 27 | - 优化了代码的底层逻辑,精简了部分代码,方便后续的二次开发。 28 | - 修正了一个manifest的错误,非常感谢PR! 29 | 30 | ### v2.0.5 (2024-11-29) 31 | - 写了一个右键菜单,然后又去优化了一下原来的底层屏蔽逻辑,现在更健壮了。 32 | - 关于右键菜单,如果点击是纯文字或图片,则会用完全匹配的规则屏蔽。 33 | - 可以在设置里看见相对应规则。暂时只提供这两种方案。 34 | 35 | ### v2.0.4 (2024-11-27) 36 | - 花了两个版本修复设置不显示的bug和反复屏蔽消息导致消息划不动的情况 37 | - 现在导入导出配置的界面稍微美观了一点 38 | - 现在消息浏览加了个过渡动画我是很满意的,不卡了! 39 | 40 | ### v2.0.2 (2024-11-25) 41 | - 在log中新加了版本号方便排查问题 42 | - 每个模块适配回车键监听,可以不用鼠标手点添加了 43 | - 精简了默认配置和log的输出 44 | - 去掉了两个未实现的方法和新加了一个精细化屏蔽设定 45 | - 加入了导入导出配置的功能 46 | 47 | ### v2.0.1 (2024-11-21) 48 | - 上个版本实在是太不稳定了,所以又重构了一下,现在不存在任何bug(应该) 49 | - 有可能某些模块功能表现不正常,如果遇到这种情况欢迎反馈 50 | - 下个版本将会继续重构代码,保持代码的简洁性(如果不是修bug的话) 51 | 52 | ### v2.0.0 (2024-11-20) 53 | - 夜间模式(已适配),设置界面功能无效(已重构) 54 | - 重写了大量逻辑 55 | 56 | ### v1.0.8 (2024-11-15) 57 | - 修图片全被屏蔽的bug,现在是稳定版 58 | - 1.0.7也是稳定版但是修复逻辑略有不同 59 | 60 | ### v1.0.6 (2024-11-15) 61 | - 这个版本新加了区分完全匹配的逻辑和包含匹配的逻辑,具体逻辑见图 62 | ![QQ_1731654303922](https://github.com/user-attachments/assets/4067e4fa-1647-4520-9954-a7917e83279c) 63 | - 优化了emoji的屏蔽,将会只隐藏emoji表情 64 | 65 | ### v1.0.5 (2024-11-14) 66 | - 更新了对图片和超级表情(就是那个突然变大的表情)的支持 67 | - 重写了屏蔽词设定,现在可以正常的识别@和屏蔽了! 68 | - 完善了代码,不会像上个版本一样存在没处理的消息 69 | - 更新了icon,将于晚上提交release 70 | 71 | ### v1.0.2 (2024-11-11) 72 | - 更新了对emoji的支持 73 | - 至此以后不再对屏蔽词设置界面的添加和删除功能做维护,新功能未使用localstorage 74 | - 下个版本将会对图片进行屏蔽支持和屏蔽特定机器人的选项 75 | 76 | ### v1.0.1 (2024-11-11) 77 | - 对设置界面进行了美化 78 | - 修改了屏蔽词相关数据的存储方式,从localstorage到直接写入renderer.js 79 | - 这样子需要导入导出配置的话复制renderer.js就好了 80 | - 同时保留原来的localstorage存储(假如你能正常使用的话) 81 | - 所以屏蔽词设置界面建议用作查看屏蔽词是否添加成功 82 | - 83 | -------------------------------------------------------------------------------- /icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elegantland/qqMessageBlocker/572071d39f878bff051ac5708176a5fbfbc11a5a/icon.jpg -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 4, 3 | "type": "extension", 4 | "name": "MessageBlocker", 5 | "slug": "message_blocker", 6 | "description": "消息屏蔽插件。目前QQ的唯一一个能处理消息内容的插件。支持按关键字屏蔽文字,emoji表情,图片表情包,群机器人,超级表情。觉得好用github给我点个star吧!", 7 | "version": "2.1.1", 8 | "icon": "./icon.jpg", 9 | "authors": [ 10 | { 11 | "name": "ele", 12 | "link": "https://github.com/elegantland" 13 | } 14 | ], 15 | "platform": [ 16 | "win32", 17 | "linux", 18 | "darwin" 19 | ], 20 | "injects": { 21 | "renderer": "./src/renderer.js", 22 | "main": "./src/main.js", 23 | "preload": "./src/preload.js" 24 | }, 25 | "repository": { 26 | "repo": "elegantland/qqMessageBlocker", 27 | "branch": "main" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // preload.js 2 | module.exports = { 3 | onLoad() { 4 | console.log('MessageBlocker preload loaded'); 5 | }, 6 | onUnload() { 7 | console.log('MessageBlocker preload unloaded'); 8 | } 9 | }; -------------------------------------------------------------------------------- /src/preload.js: -------------------------------------------------------------------------------- 1 | // preload.js 2 | window.onLoad = function() { 3 | console.log('MessageBlocker preload loaded'); 4 | 5 | // 安全地尝试设置编码 6 | try { 7 | if (typeof process !== 'undefined') { 8 | process.env.LANG = 'zh_CN.UTF-8'; 9 | 10 | if (process.stdout && process.stdout.setEncoding) { 11 | process.stdout.setEncoding('utf8'); 12 | } 13 | 14 | if (process.stderr && process.stderr.setEncoding) { 15 | process.stderr.setEncoding('utf8'); 16 | } 17 | } 18 | } catch (error) { 19 | console.error('设置编码时发生错误:', error); 20 | } 21 | }; 22 | window.onUnload = function() { 23 | console.log('MessageBlocker preload unloaded'); 24 | }; -------------------------------------------------------------------------------- /src/renderer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // 包含匹配屏蔽词列表2.1.1版 3 | let INCLUDES_BLOCKED_WORDS = [ 4 | //'测试111',//会屏蔽 测试111 ,也会屏蔽测试111111 5 | //'@AL_1S', 6 | ]; 7 | // 完全匹配屏蔽词列表 8 | let EXACT_BLOCKED_WORDS = [ 9 | //'测试222',//只会屏蔽 测试222 ,而不会屏蔽测试22222222 10 | //'6', 11 | //'?', 12 | ]; 13 | // 包含匹配特殊屏蔽用户配置 14 | let INCLUDES_SPECIAL_BLOCKED_USERS = { 15 | //'AL_1S': ['',], 16 | //'幻想': ['',], 17 | }; 18 | // 完全匹配特殊屏蔽用户配置 19 | let EXACT_SPECIAL_BLOCKED_USERS = { 20 | //'儒雅': ['测试444'], 21 | }; 22 | // 包含匹配屏蔽表情ID 23 | let INCLUDES_BLOCKED_EMOJIS = []; // 默认屏蔽的表情ID 24 | //以滑稽表情和暴筋表情为例子 25 | //let INCLUDES_BLOCKED_EMOJIS = [178,146]; 26 | // 完全匹配屏蔽表情ID 27 | let EXACT_BLOCKED_EMOJIS = []; 28 | // 屏蔽对应人的表情ID 29 | let INCLUDES_SPECIAL_BLOCKED_USERS_EMOJIS = { 30 | //以滑稽表情和暴筋表情为例子 31 | //'儒雅' = [178,146]; 32 | }; 33 | // 在默认配置中添加需要屏蔽的图片特征 34 | let INCLUDES_BLOCKED_IMAGES = [ 35 | //'76264f7279cd8e5e2d2c597fa68da8a2.jpg', 36 | //'bae9b15fd28f626c6b08d01188dfb604.gif', 37 | ]; 38 | let MSG_ID_BLOCK_CONFIG = { 39 | // 是否启用 超级表情 屏蔽功能,默认true启用,关闭用false 40 | enabled: true 41 | }; 42 | let INTERACTION_MESSAGE_CONFIG = { 43 | // 是否启用 互动消息(拍一拍/戳一戳等) 屏蔽功能,默认false不启用 44 | enabled: true 45 | }; 46 | let MSG_AT_BLOCK_CONFIG = { 47 | // 是否启用纯@消息屏蔽功能,默认 true 启用 48 | enabled: true, 49 | // 是否屏蔽所有带@的消息,默认 false 不启用 50 | blockAllAt: false 51 | }; 52 | //屏蔽单个用户的所有图片 53 | let USER_IMAGES = { 54 | //'儒雅', // 只屏蔽儒雅所有图片消息 55 | }; 56 | //系统消息屏蔽 57 | let PUBLIC_MESSAGE_KEYWORDS = [ 58 | //'撤回', // 屏蔽撤回消息提示 59 | //'拒绝了你的消息', // 屏蔽拒绝消息提示 60 | //'邀请', // 屏蔽群邀请消息 61 | ]; 62 | let REPLACEMODE = { 63 | normalWords: false, // 普通屏蔽词是否使用替换模式 64 | exactWords: false, // 完全匹配屏蔽词是否使用替换模式 65 | specialUsers: false, // 特殊用户屏蔽词是否使用替换模式 66 | exactSpecialUsers: false,// 特殊用户完全匹配是否使用替换模式 67 | emojis: false, // 表情是否使用替换模式 68 | images: false, // 图片是否使用替换模式 69 | superEmoji: false, // 超级表情是否使用替换模式 70 | publicMessage: false, // 公共消息是否使用替换模式 71 | replaceword: "[已屏蔽]" // 替换词 72 | }; 73 | 74 | // Toast 通知管理类 75 | class Toast { 76 | static instance = null; 77 | 78 | constructor() { 79 | if (Toast.instance) { 80 | return Toast.instance; 81 | } 82 | Toast.instance = this; 83 | this.initStyles(); 84 | } 85 | 86 | initStyles() { 87 | const style = document.createElement('style'); 88 | style.textContent = ` 89 | .message-blocker-toast { 90 | position: fixed; 91 | bottom: 20px; 92 | left: 50%; 93 | transform: translateX(-50%) translateY(100px); 94 | padding: 12px 24px; 95 | border-radius: 8px; 96 | color: #ffffff; 97 | font-size: 14px; 98 | font-weight: 500; 99 | opacity: 0; 100 | transition: all 0.3s ease-in-out; 101 | z-index: 9999; 102 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 103 | } 104 | 105 | .message-blocker-toast.success { 106 | background-color: #52c41a; 107 | } 108 | 109 | .message-blocker-toast.error { 110 | background-color: #ff4d4f; 111 | } 112 | 113 | .message-blocker-toast.show { 114 | opacity: 1; 115 | transform: translateX(-50%) translateY(0); 116 | } 117 | 118 | /* 添加新的样式 */ 119 | .msg-content-container, 120 | .message-container, 121 | .mix-message__container { 122 | opacity: 0; 123 | transition: opacity 0.1s ease-in-out; 124 | } 125 | 126 | .msg-content-container.visible, 127 | .message-container.visible, 128 | .mix-message__container.visible { 129 | opacity: 1; 130 | } 131 | `; 132 | document.head.appendChild(style); 133 | } 134 | 135 | show(message, type = 'success') { 136 | const toast = document.createElement('div'); 137 | toast.className = `message-blocker-toast ${type}`; 138 | toast.textContent = message; 139 | document.body.appendChild(toast); 140 | 141 | setTimeout(() => toast.classList.add('show'), 100); 142 | setTimeout(() => { 143 | toast.classList.remove('show'); 144 | setTimeout(() => document.body.removeChild(toast), 300); 145 | }, 3000); 146 | } 147 | } 148 | 149 | // 创建Toast单例并设置showToast函数 150 | const toastInstance = new Toast(); 151 | function showToast(message, type = 'success') { 152 | toastInstance.show(message, type); 153 | } 154 | 155 | // 屏蔽词管理类 156 | class BlockedWordsManager { 157 | constructor() { 158 | // 确保单例模式 159 | if (window.messageBlocker?.blockedWordsManager) { 160 | return window.messageBlocker.blockedWordsManager; 161 | } 162 | 163 | this.blockedWords = new Set(); 164 | this.exactBlockedWords = new Set(); 165 | this.specialBlockedUsers = {}; 166 | this.exactSpecialBlockedUsers = {}; 167 | this.blockedEmojis = new Set(); 168 | this.specialBlockedUsersEmojis = {}; 169 | this.blockedImages = new Set(); 170 | this.blockSuperEmoji = MSG_ID_BLOCK_CONFIG.enabled; 171 | this.exactBlockedEmojis = new Set(); // 完全匹配的表情 172 | this.includeBlockedEmojis = new Set(); // 包含匹配的表情 173 | this.publicMessageKeywords = new Set(PUBLIC_MESSAGE_KEYWORDS); 174 | this.blockInteractionMessage = INTERACTION_MESSAGE_CONFIG.enabled; 175 | 176 | // 初始化默认配置 177 | this.defaultConfig = { 178 | blockedWords: INCLUDES_BLOCKED_WORDS, 179 | exactBlockedWords: EXACT_BLOCKED_WORDS, 180 | specialBlockedUsers: INCLUDES_SPECIAL_BLOCKED_USERS, 181 | exactSpecialBlockedUsers: EXACT_SPECIAL_BLOCKED_USERS, 182 | blockedEmojis: INCLUDES_BLOCKED_EMOJIS, 183 | exactBlockedEmojis: EXACT_BLOCKED_EMOJIS, 184 | includeBlockedEmojis: INCLUDES_BLOCKED_EMOJIS, 185 | specialBlockedUsersEmojis: INCLUDES_SPECIAL_BLOCKED_USERS_EMOJIS, 186 | blockedImages: INCLUDES_BLOCKED_IMAGES, 187 | blockSuperEmoji: MSG_ID_BLOCK_CONFIG.enabled, 188 | blockPublicMessage: MSG_ID_BLOCK_CONFIG.enabled, 189 | publicMessageKeywords: PUBLIC_MESSAGE_KEYWORDS, 190 | blockInteractionMessage: INTERACTION_MESSAGE_CONFIG.enabled 191 | }; 192 | 193 | // 加载配置 194 | this.loadAllData(); 195 | 196 | // 监听窗口焦点事件,用于在QQ重启后重新加载配置 197 | window.addEventListener('focus', () => { 198 | this.loadAllData(); 199 | }); 200 | } 201 | 202 | async loadAllData() { 203 | try { 204 | // 使用 LiteLoader API 加载配置,使用默认配置作为基础 205 | const config = await LiteLoader.api.config.get("message_blocker", this.defaultConfig); 206 | 207 | // 清空当前数据 208 | this.blockedWords.clear(); 209 | this.exactBlockedWords.clear(); 210 | this.blockedEmojis.clear(); 211 | this.blockedImages.clear(); 212 | this.exactBlockedEmojis.clear(); 213 | this.includeBlockedEmojis.clear(); 214 | 215 | // 加载普通屏蔽词配置 216 | if (Array.isArray(config.blockedWords)) { 217 | config.blockedWords.forEach(word => { 218 | if (word && typeof word === 'string') { 219 | this.blockedWords.add(word.trim()); 220 | } 221 | }); 222 | } 223 | // 合并默认的普通屏蔽词配置 224 | INCLUDES_BLOCKED_WORDS.forEach(word => { 225 | if (word && typeof word === 'string') { 226 | this.blockedWords.add(word.trim()); 227 | } 228 | }); 229 | 230 | // 加载完全匹配屏蔽词配置 231 | if (Array.isArray(config.exactBlockedWords)) { 232 | config.exactBlockedWords.forEach(word => { 233 | if (word && typeof word === 'string') { 234 | this.exactBlockedWords.add(word.trim()); 235 | } 236 | }); 237 | } 238 | // 合并默认的完全匹配屏蔽词配置 239 | EXACT_BLOCKED_WORDS.forEach(word => { 240 | if (word && typeof word === 'string') { 241 | this.exactBlockedWords.add(word.trim()); 242 | } 243 | }); 244 | 245 | // 合并默认配置和用户配置的特殊用户配置 246 | this.specialBlockedUsers = { ...INCLUDES_SPECIAL_BLOCKED_USERS }; 247 | if (config.specialBlockedUsers) { 248 | Object.entries(config.specialBlockedUsers).forEach(([userId, keywords]) => { 249 | if (!this.specialBlockedUsers[userId]) { 250 | this.specialBlockedUsers[userId] = []; 251 | } 252 | if (Array.isArray(keywords)) { 253 | keywords.forEach(keyword => { 254 | if (!this.specialBlockedUsers[userId].includes(keyword)) { 255 | this.specialBlockedUsers[userId].push(keyword); 256 | } 257 | }); 258 | } 259 | }); 260 | } 261 | 262 | // 合并默认配置和用户配置的完全匹配特殊用户配置 263 | this.exactSpecialBlockedUsers = { ...EXACT_SPECIAL_BLOCKED_USERS }; 264 | if (config.exactSpecialBlockedUsers) { 265 | Object.entries(config.exactSpecialBlockedUsers).forEach(([userId, keywords]) => { 266 | if (!this.exactSpecialBlockedUsers[userId]) { 267 | this.exactSpecialBlockedUsers[userId] = []; 268 | } 269 | if (Array.isArray(keywords)) { 270 | keywords.forEach(keyword => { 271 | if (!this.exactSpecialBlockedUsers[userId].includes(keyword)) { 272 | this.exactSpecialBlockedUsers[userId].push(keyword); 273 | } 274 | }); 275 | } 276 | }); 277 | } 278 | 279 | // 加载表情屏蔽配置 280 | if (Array.isArray(config.exactBlockedEmojis)) { 281 | config.exactBlockedEmojis.forEach(id => { 282 | const numId = Number(id); 283 | if (!isNaN(numId)) { 284 | this.exactBlockedEmojis.add(numId); 285 | } 286 | }); 287 | } 288 | 289 | if (Array.isArray(config.includeBlockedEmojis)) { 290 | config.includeBlockedEmojis.forEach(id => { 291 | const numId = Number(id); 292 | if (!isNaN(numId)) { 293 | this.includeBlockedEmojis.add(numId); 294 | } 295 | }); 296 | } 297 | 298 | // 合并默认的表情屏蔽配置到包含匹配集合中 299 | INCLUDES_BLOCKED_EMOJIS.forEach(id => { 300 | const numId = Number(id); 301 | if (!isNaN(numId)) { 302 | this.includeBlockedEmojis.add(numId); 303 | } 304 | }); 305 | 306 | // 加载图片屏蔽配置 307 | if (Array.isArray(config.blockedImages)) { 308 | config.blockedImages.forEach(pattern => { 309 | if (pattern && typeof pattern === 'string') { 310 | this.blockedImages.add(pattern.trim()); 311 | } 312 | }); 313 | } 314 | // 合并默认的图片屏蔽配置 315 | INCLUDES_BLOCKED_IMAGES.forEach(pattern => { 316 | if (pattern && typeof pattern === 'string') { 317 | this.blockedImages.add(pattern.trim()); 318 | } 319 | }); 320 | 321 | // 加载超级表情屏蔽配置 322 | if (typeof config.blockSuperEmoji === 'boolean') { 323 | // 同时更新两个值 324 | this.blockSuperEmoji = config.blockSuperEmoji; 325 | MSG_ID_BLOCK_CONFIG.enabled = config.blockSuperEmoji; 326 | } else { 327 | // 如果没有用户配置,使用默认配置 328 | this.blockSuperEmoji = MSG_ID_BLOCK_CONFIG.enabled; 329 | } 330 | 331 | // 加载公共消息屏蔽配置 332 | if (typeof config.blockPublicMessage === 'boolean') { 333 | this.blockPublicMessage = config.blockPublicMessage; 334 | } else { 335 | this.blockPublicMessage = MSG_ID_BLOCK_CONFIG.enabled; 336 | } 337 | 338 | // 更新全局变量 339 | INCLUDES_BLOCKED_WORDS = Array.from(this.blockedWords); 340 | EXACT_BLOCKED_WORDS = Array.from(this.exactBlockedWords); 341 | INCLUDES_SPECIAL_BLOCKED_USERS = { ...this.specialBlockedUsers }; 342 | EXACT_SPECIAL_BLOCKED_USERS = { ...this.exactSpecialBlockedUsers }; 343 | INCLUDES_BLOCKED_EMOJIS = Array.from(this.blockedEmojis); 344 | INCLUDES_SPECIAL_BLOCKED_USERS_EMOJIS = { ...this.specialBlockedUsersEmojis }; 345 | INCLUDES_BLOCKED_IMAGES = Array.from(this.blockedImages); 346 | MSG_ID_BLOCK_CONFIG.enabled = this.blockSuperEmoji; 347 | 348 | // 加载公共消息关键词 349 | if (Array.isArray(config.publicMessageKeywords)) { 350 | this.publicMessageKeywords.clear(); 351 | config.publicMessageKeywords.forEach(keyword => { 352 | if (keyword && typeof keyword === 'string') { 353 | this.publicMessageKeywords.add(keyword); 354 | } 355 | }); 356 | } 357 | 358 | // 合并默认配置 359 | PUBLIC_MESSAGE_KEYWORDS.forEach(keyword => { 360 | if (keyword && typeof keyword === 'string') { 361 | this.publicMessageKeywords.add(keyword); 362 | } 363 | }); 364 | 365 | // 加载互动消息屏蔽配置 366 | if (typeof config.blockInteractionMessage === 'boolean') { 367 | this.blockInteractionMessage = config.blockInteractionMessage; 368 | } else { 369 | this.blockInteractionMessage = INTERACTION_MESSAGE_CONFIG.enabled; 370 | } 371 | 372 | // 更新全局变量 373 | INTERACTION_MESSAGE_CONFIG.enabled = this.blockInteractionMessage; 374 | 375 | // 只有在 messageBlocker 实例存在时才更新UI 376 | if (window.messageBlocker) { 377 | window.messageBlocker.renderWordsList(); 378 | window.messageBlocker.renderSpecialUsersList(); 379 | window.messageBlocker.renderEmojisList(); 380 | window.messageBlocker.renderSpecialEmojisList(); 381 | window.messageBlocker.renderBlockedImagesList(); 382 | window.messageBlocker.renderImageBlockedUsersList(); 383 | } 384 | 385 | // 在 loadAllData 方法中修改加载 USER_IMAGES 的代码 386 | if (config.userImages) { 387 | // 直接合并配置,保留默认值 388 | Object.assign(USER_IMAGES, config.userImages); 389 | } 390 | 391 | // 加载@消息屏蔽配置 392 | if (typeof config.atMessageBlock === 'boolean') { 393 | MSG_AT_BLOCK_CONFIG.enabled = config.atMessageBlock; 394 | } 395 | if (typeof config.blockAllAt === 'boolean') { 396 | MSG_AT_BLOCK_CONFIG.blockAllAt = config.blockAllAt; 397 | } 398 | 399 | // 导入开关状态 400 | if (config.switchStates) { 401 | // 更新超级表情屏蔽开关 402 | if (typeof config.switchStates.superEmojiBlock === 'boolean') { 403 | MSG_ID_BLOCK_CONFIG.enabled = config.switchStates.superEmojiBlock; 404 | this.blockSuperEmoji = config.switchStates.superEmojiBlock; 405 | } 406 | // 更新互动消息屏蔽开关 407 | if (typeof config.switchStates.interactionMessageBlock === 'boolean') { 408 | INTERACTION_MESSAGE_CONFIG.enabled = config.switchStates.interactionMessageBlock; 409 | this.blockInteractionMessage = config.switchStates.interactionMessageBlock; 410 | } 411 | // 更新@消息屏蔽开关 412 | if (typeof config.switchStates.atMessageBlock === 'boolean') { 413 | MSG_AT_BLOCK_CONFIG.enabled = config.switchStates.atMessageBlock; 414 | } 415 | // 更新屏蔽所有@消息开关 416 | if (typeof config.switchStates.blockAllAt === 'boolean') { 417 | MSG_AT_BLOCK_CONFIG.blockAllAt = config.switchStates.blockAllAt; 418 | } 419 | 420 | // 更新UI上的开关状态 421 | const superEmojiBlockSwitch = document.getElementById('superEmojiBlockSwitch'); 422 | const interactionMessageBlockSwitch = document.getElementById('interactionMessageBlockSwitch'); 423 | const atMessageBlockSwitch = document.getElementById('atMessageBlockSwitch'); 424 | const blockAllAtSwitch = document.getElementById('blockAllAtSwitch'); 425 | 426 | if (superEmojiBlockSwitch) { 427 | superEmojiBlockSwitch.checked = this.blockSuperEmoji; 428 | } 429 | if (interactionMessageBlockSwitch) { 430 | interactionMessageBlockSwitch.checked = this.blockInteractionMessage; 431 | } 432 | if (atMessageBlockSwitch) { 433 | atMessageBlockSwitch.checked = MSG_AT_BLOCK_CONFIG.enabled; 434 | } 435 | if (blockAllAtSwitch) { 436 | blockAllAtSwitch.checked = MSG_AT_BLOCK_CONFIG.blockAllAt; 437 | } 438 | } 439 | } catch (error) { 440 | console.error('[Message Blocker] 加载配置时出错:', error); 441 | } 442 | } 443 | 444 | // 保存所有数据 445 | async saveAllData() { 446 | try { 447 | // 更新全局变量 448 | INCLUDES_BLOCKED_WORDS = Array.from(this.blockedWords); 449 | EXACT_BLOCKED_WORDS = Array.from(this.exactBlockedWords); 450 | INCLUDES_SPECIAL_BLOCKED_USERS = { ...this.specialBlockedUsers }; 451 | EXACT_SPECIAL_BLOCKED_USERS = { ...this.exactSpecialBlockedUsers }; 452 | INCLUDES_BLOCKED_EMOJIS = Array.from(this.blockedEmojis); 453 | INCLUDES_SPECIAL_BLOCKED_USERS_EMOJIS = { ...this.specialBlockedUsersEmojis }; 454 | INCLUDES_BLOCKED_IMAGES = Array.from(this.blockedImages); 455 | MSG_ID_BLOCK_CONFIG.enabled = this.blockSuperEmoji; 456 | INTERACTION_MESSAGE_CONFIG.enabled = this.blockInteractionMessage; 457 | PUBLIC_MESSAGE_KEYWORDS = Array.from(this.publicMessageKeywords); 458 | 459 | const dataToSave = { 460 | blockedWords: Array.from(this.blockedWords), 461 | exactBlockedWords: Array.from(this.exactBlockedWords), 462 | specialBlockedUsers: this.specialBlockedUsers, 463 | exactSpecialBlockedUsers: this.exactSpecialBlockedUsers, 464 | blockedEmojis: Array.from(this.blockedEmojis), 465 | exactBlockedEmojis: Array.from(this.exactBlockedEmojis), 466 | includeBlockedEmojis: Array.from(this.includeBlockedEmojis), 467 | specialBlockedUsersEmojis: this.specialBlockedUsersEmojis, 468 | blockedImages: Array.from(this.blockedImages), 469 | blockSuperEmoji: this.blockSuperEmoji, 470 | blockPublicMessage: this.blockPublicMessage, 471 | publicMessageKeywords: Array.from(this.publicMessageKeywords), 472 | blockInteractionMessage: this.blockInteractionMessage, 473 | userImages: USER_IMAGES, 474 | atMessageBlock: MSG_AT_BLOCK_CONFIG.enabled, 475 | blockAllAt: MSG_AT_BLOCK_CONFIG.blockAllAt, 476 | switchStates: { 477 | superEmojiBlock: this.blockSuperEmoji, 478 | interactionMessageBlock: this.blockInteractionMessage, 479 | atMessageBlock: MSG_AT_BLOCK_CONFIG.enabled, 480 | blockAllAt: MSG_AT_BLOCK_CONFIG.blockAllAt 481 | } 482 | }; 483 | 484 | // 使用 LiteLoader API 保存配置 485 | await LiteLoader.api.config.set("message_blocker", dataToSave); 486 | } catch (error) { 487 | console.error('保存配置时出错:', error); 488 | showToast('保存配置失败', 'error'); 489 | } 490 | } 491 | 492 | // 统一的添加屏蔽词方法 493 | addBlockedWord(word, type = 'include') { 494 | if (!word) return false; 495 | 496 | const targetSet = type === 'exact' ? this.exactBlockedWords : this.blockedWords; 497 | if (targetSet.has(word)) { 498 | return false; 499 | } 500 | 501 | targetSet.add(word); 502 | this.saveAllData(); 503 | return true; 504 | } 505 | 506 | // 统一的删除屏蔽词方法 507 | removeBlockedWord(word, type = 'include') { 508 | if (!word) return false; 509 | 510 | const targetSet = type === 'exact' ? this.exactBlockedWords : this.blockedWords; 511 | if (!targetSet.has(word)) { 512 | return false; 513 | } 514 | 515 | targetSet.delete(word); 516 | this.saveAllData(); 517 | return true; 518 | } 519 | 520 | // 添加完全匹配屏蔽词 521 | addExactBlockedWord(word) { 522 | if (this.exactBlockedWords.has(word)) { 523 | return false; 524 | } 525 | this.exactBlockedWords.add(word); 526 | this.saveAllData(); 527 | return true; 528 | } 529 | 530 | // 删除完全匹配屏蔽词 531 | removeExactBlockedWord(word) { 532 | if (!this.exactBlockedWords.has(word)) { 533 | return false; 534 | } 535 | this.exactBlockedWords.delete(word); 536 | this.saveAllData(); 537 | return true; 538 | } 539 | 540 | // 检查消息是否应该被屏蔽 541 | isMessageBlocked(element, username, message) { 542 | try { 543 | // 检查是否是超级表情(接龙表情或随机表情) 544 | const lottieElement = element.querySelector('.lottie'); 545 | if (lottieElement) { 546 | const isRelaySticker = lottieElement.classList.contains('is-relay-sticker') || lottieElement.classList.contains('is-relay-sticker--self'); 547 | const isRandomSticker = lottieElement.classList.contains('is-random-sticker') || lottieElement.classList.contains('is-random-sticker--self'); 548 | const hasMsgId = lottieElement.hasAttribute('msg-id'); 549 | 550 | // 检查是否是超级表情(包括接龙表情和随机表情) 551 | if ((isRelaySticker || isRandomSticker) && hasMsgId) { 552 | // 检查超级表情屏蔽开关,使用 MSG_ID_BLOCK_CONFIG.enabled 作为唯一来源 553 | if (MSG_ID_BLOCK_CONFIG.enabled) { 554 | return { blocked: true, type: 'superEmoji' }; 555 | } 556 | } 557 | } 558 | 559 | if (username && 560 | (this.specialBlockedUsers[username]?.includes('') || 561 | INCLUDES_SPECIAL_BLOCKED_USERS[username]?.includes(''))) { 562 | return { blocked: true, type: 'specialUser' }; 563 | } 564 | 565 | // 检查是否是空消息 566 | const mixMessageContainer = element.closest('.mix-message__container'); 567 | if (mixMessageContainer) { 568 | const textElement = mixMessageContainer.querySelector('.text-element--other .text-normal'); 569 | const messageContent = mixMessageContainer.querySelector('.message-content'); 570 | 571 | // 检查是否是只包含空文本的消息 572 | if (textElement && 573 | textElement.textContent.trim() === '' && 574 | messageContent && 575 | messageContent.children.length === 1 && 576 | messageContent.querySelector('.text-element--other') && 577 | !messageContent.querySelector('img') && 578 | !messageContent.querySelector('.face-element')) { 579 | 580 | // 确保消息容器中没有其他内容 581 | const otherContent = Array.from(mixMessageContainer.children) 582 | .filter(child => !child.classList.contains('message-content')) 583 | .length === 0; 584 | 585 | if (otherContent) { 586 | return { blocked: true, type: 'system' }; 587 | } 588 | } 589 | } 590 | 591 | // 使用策略模式检查各种消息类型 592 | const checks = [ 593 | // @消息检查 594 | () => MSG_AT_BLOCK_CONFIG.enabled && this.isAtMessage(element) && 595 | { blocked: true, type: 'atMessage' }, 596 | 597 | // 公共消息和系统消息检查 598 | () => this.checkPublicMessage(element), 599 | 600 | // 用户名检查 601 | () => username && INCLUDES_BLOCKED_WORDS.includes(username) && 602 | { blocked: true, type: 'user' }, 603 | 604 | // 图片检查 605 | () => this.isBlockedImage(element) && 606 | { blocked: true, type: 'image' }, 607 | 608 | // 普通屏蔽词检查 609 | () => message && this.checkNormalBlockedWords(message), 610 | 611 | // 完全匹配屏蔽词检查 612 | () => message && this.exactBlockedWords.has(message) && 613 | { blocked: true, type: 'exact' } 614 | ]; 615 | 616 | // 执行所有检查,返回第一个匹配的结果 617 | for (const check of checks) { 618 | const result = check(); 619 | if (result) return result; 620 | } 621 | 622 | return { blocked: false }; 623 | } catch (error) { 624 | console.error('[Message Blocker] Error in isMessageBlocked:', error); 625 | return { blocked: false }; 626 | } 627 | } 628 | 629 | // 提取公共消息检查逻辑 630 | checkPublicMessage(element) { 631 | if (!element.classList.contains('gray-tip-message') && 632 | !element.classList.contains('system-msg')) { 633 | return false; 634 | } 635 | 636 | const grayTipContent = element.querySelector('.gray-tip-content, .system-msg-content'); 637 | if (!grayTipContent || 638 | (!grayTipContent.classList.contains('babble') && 639 | !grayTipContent.classList.contains('system-msg-content'))) { 640 | return false; 641 | } 642 | 643 | const text = grayTipContent.textContent; 644 | 645 | // 检查公共消息关键词 646 | if (this.checkPublicMessageKeywords(text)) { 647 | return { blocked: true, type: 'publicMessage' }; 648 | } 649 | 650 | // 检查互动消息 651 | if (this.checkInteractionMessage(grayTipContent)) { 652 | return { blocked: true, type: 'publicMessage', subType: 'interaction' }; 653 | } 654 | 655 | return false; 656 | } 657 | 658 | // 检查普通屏蔽词 659 | checkNormalBlockedWords(message) { 660 | for (const word of this.blockedWords) { 661 | if (message.includes(word)) { 662 | return { blocked: true, type: 'include' }; 663 | } 664 | } 665 | return false; 666 | } 667 | 668 | // 检查公共消息关键词 669 | checkPublicMessageKeywords(text) { 670 | return Array.from(this.publicMessageKeywords).some(keyword => text.includes(keyword)); 671 | } 672 | 673 | // 检查互动消息 674 | checkInteractionMessage(content) { 675 | if (!this.blockInteractionMessage) { 676 | return false; 677 | } 678 | 679 | const actions = content.querySelectorAll('.gray-tip-action'); 680 | const img = content.querySelector('.gray-tip-img'); 681 | 682 | // 检查是否是互动消息(拍一拍/戳一戳等) 683 | const isInteractionMessage = actions.length >= 2 && 684 | img && 685 | (img.src.includes('nudgeaction') || 686 | content.textContent.includes('拍了拍') || 687 | content.textContent.includes('戳了戳')); 688 | 689 | return isInteractionMessage; 690 | } 691 | 692 | extractUsername(element) { 693 | try { 694 | // 首先尝试从父元素获取消息容器 695 | let rootMessageContainer = element; 696 | while (rootMessageContainer && !rootMessageContainer.classList.contains('message-container')) { 697 | rootMessageContainer = rootMessageContainer.parentElement; 698 | } 699 | 700 | if (!rootMessageContainer) { 701 | return ''; 702 | } 703 | 704 | // 方法1:从 user-name 区域获取(最准确的方法) 705 | const userNameElement = rootMessageContainer.querySelector('.user-name .text-ellipsis'); 706 | if (userNameElement) { 707 | const username = userNameElement.textContent.trim(); 708 | if (username) { 709 | return username; 710 | } 711 | } 712 | 713 | // 方法2:从avatar-span的aria-label属性获取(备用方法) 714 | const avatarSpan = rootMessageContainer.querySelector('.avatar-span'); 715 | if (avatarSpan) { 716 | const username = avatarSpan.getAttribute('aria-label'); 717 | if (username) { 718 | return username; 719 | } 720 | } 721 | 722 | return ''; 723 | } catch (error) { 724 | console.error('获取用户名时出错:', error); 725 | return ''; 726 | } 727 | } 728 | // 检查表情是否被屏蔽 729 | checkEmojiBlocked(emojiIds, username, messageContent) { 730 | if (!emojiIds || emojiIds.length === 0) return false; 731 | 732 | const numericEmojiIds = emojiIds.map(id => Number(id)); 733 | // 只获取文本内容,不包括表情内容 734 | const textContent = messageContent?.replace(/\[表情\]|\[emoji\]/g, '').trim(); 735 | const hasOtherContent = textContent && textContent.length > 0; 736 | 737 | return numericEmojiIds.some(id => { 738 | // 检查完全匹配(仅当消息只包含表情时) 739 | if (!hasOtherContent && this.exactBlockedEmojis.has(id)) { 740 | return true; 741 | } 742 | // 检查包含匹配(无论消息是否包含其他内容) 743 | if (this.includeBlockedEmojis.has(id)) { 744 | return true; 745 | } 746 | // 检查用户特定屏蔽 747 | if (username && this.specialBlockedUsersEmojis[username]?.includes(id)) { 748 | return true; 749 | } 750 | return false; 751 | }); 752 | } 753 | 754 | // 检查图片是否被屏蔽 755 | isBlockedImage(element) { 756 | if (!element || !this.blockedImages) return false; 757 | 758 | const imgElements = element.querySelectorAll('div.image.pic-element img.image-content, div.pic-element img.image-content'); 759 | if (!imgElements || imgElements.length === 0) return false; 760 | 761 | for (const img of imgElements) { 762 | const src = img.src || img.getAttribute('src'); 763 | if (!src) continue; 764 | 765 | const fileName = src.split('/').pop(); 766 | 767 | // 检查是否在屏蔽列表中 768 | if (this.blockedImages.has(fileName)) { 769 | return true; 770 | } 771 | 772 | // 检查用户屏蔽配置 773 | const messageContainer = img.closest('.message-container'); 774 | if (messageContainer) { 775 | const username = this.extractUsername(messageContainer); 776 | if (username && USER_IMAGES[username]) { 777 | return true; 778 | } 779 | } 780 | } 781 | 782 | return false; 783 | } 784 | 785 | // 添加统一的用户屏蔽检查方法 786 | isUserBlocked(username) { 787 | return INCLUDES_BLOCKED_WORDS.includes(username) || 788 | Object.keys(this.specialBlockedUsers).includes(username) || 789 | username in INCLUDES_SPECIAL_BLOCKED_USERS || 790 | username in EXACT_SPECIAL_BLOCKED_USERS; 791 | } 792 | 793 | // 添加被屏蔽的图片 794 | addBlockedImage(imageFileName) { 795 | if (!imageFileName) { 796 | showToast('图片文件名不能为空', 'error'); 797 | return false; 798 | } 799 | 800 | if (this.blockedImages.has(imageFileName)) { 801 | showToast('该图片已在屏蔽列表中', 'error'); 802 | return false; 803 | } 804 | 805 | this.blockedImages.add(imageFileName); 806 | this.saveAllData(); // 立即保存到 localStorage 807 | showToast('添加图片屏蔽成功', 'success'); 808 | return true; 809 | } 810 | 811 | // 移除被屏蔽的图片 812 | removeBlockedImage(imageFileName) { 813 | if (this.blockedImages.has(imageFileName)) { 814 | this.blockedImages.delete(imageFileName); 815 | this.saveAllData(); 816 | return true; 817 | } 818 | return false; 819 | } 820 | 821 | // 添加表情屏蔽 822 | addBlockedEmoji(emojiId, type = 'include') { 823 | if (!emojiId) { 824 | showToast('表情ID不能为空', 'error'); 825 | return false; 826 | } 827 | 828 | // 转换为数字类型 829 | const numEmojiId = Number(emojiId); 830 | if (isNaN(numEmojiId)) { 831 | showToast('表情ID必须是数字', 'error'); 832 | return false; 833 | } 834 | 835 | // 根据类型选择对应的集合 836 | const targetSet = type === 'exact' ? this.exactBlockedEmojis : this.includeBlockedEmojis; 837 | 838 | // 检查是否已存在 839 | if (targetSet.has(numEmojiId)) { 840 | return false; 841 | } 842 | 843 | // 添加表情ID 844 | targetSet.add(numEmojiId); 845 | this.saveAllData(); 846 | return true; 847 | } 848 | 849 | // 删除表情屏蔽 850 | deleteEmoji(emojiId) { 851 | const numEmojiId = Number(emojiId); 852 | if (this.blockedEmojis.has(numEmojiId)) { 853 | this.blockedEmojis.delete(numEmojiId); 854 | this.saveAllData(); 855 | return true; 856 | } 857 | return false; 858 | } 859 | 860 | // 添加特殊用户屏蔽 861 | addSpecialBlockedUser(username, keywords) { 862 | if (!this.specialBlockedUsers[username]) { 863 | this.specialBlockedUsers[username] = []; 864 | } 865 | keywords.forEach(keyword => { 866 | if (!this.specialBlockedUsers[username].includes(keyword)) { 867 | this.specialBlockedUsers[username].push(keyword); 868 | } 869 | }); 870 | this.saveAllData(); 871 | return true; 872 | } 873 | 874 | // 移除特殊用户屏蔽 875 | removeSpecialBlockedUser(username) { 876 | if (this.specialBlockedUsers[username]) { 877 | delete this.specialBlockedUsers[username]; 878 | this.saveAllData(); 879 | return true; 880 | } 881 | return false; 882 | } 883 | 884 | // 添加特定用户表情屏蔽 885 | addSpecialUserEmoji(username, emojiId) { 886 | if (!username || !emojiId) { 887 | showToast('用户名和表情ID不能为空', 'error'); 888 | return false; 889 | } 890 | // 转换为数字类型 891 | const numEmojiId = Number(emojiId); 892 | if (isNaN(numEmojiId)) { 893 | showToast('表情ID必须是数字', 'error'); 894 | return false; 895 | } 896 | // 初始化用户的表情列表(如果不存在) 897 | if (!this.specialBlockedUsersEmojis[username]) { 898 | this.specialBlockedUsersEmojis[username] = []; 899 | } 900 | // 检查是否已存在 901 | if (this.specialBlockedUsersEmojis[username].includes(numEmojiId)) { 902 | showToast('该用户已存在相同的表情屏蔽', 'error'); 903 | return false; 904 | } 905 | // 添加表情ID 906 | this.specialBlockedUsersEmojis[username].push(numEmojiId); 907 | // 保存数据 908 | this.blockedWordsManager.saveAllData(); 909 | this.renderSpecialEmojisList(); 910 | showToast('添加成功', 'success'); 911 | 912 | // 清空输入框 913 | specialEmojiUser.value = ''; 914 | specialEmojiId.value = ''; 915 | } 916 | 917 | // 删除特定用户表情屏蔽 918 | deleteSpecialUserEmoji(username) { 919 | if (this.blockedWordsManager.deleteSpecialUserEmoji(username)) { 920 | this.renderSpecialEmojisList(); 921 | showToast('删除成功'); 922 | } 923 | } 924 | 925 | // 渲染特定用户表情列表 926 | renderSpecialEmojisList() { 927 | const specialEmojisList = document.getElementById('specialEmojisList'); 928 | if (!specialEmojisList) return; 929 | 930 | this.specialEmojisRenderer = new ListRenderer({ 931 | listElement: specialEmojisList, 932 | dataSource: Object.entries(this.blockedWordsManager.specialBlockedUsersEmojis).reverse(), 933 | itemTemplate: ([username, emojiIds]) => { 934 | if (!emojiIds || emojiIds.length === 0) return ''; 935 | 936 | return ` 937 |
938 |
939 |
用户: ${username}
940 |
屏蔽表情: ${Array.from(emojiIds).join(', ')}
941 |
942 | 943 |
944 | `; 945 | } 946 | }); 947 | this.specialEmojisRenderer.render(); 948 | } 949 | 950 | // 添加导出配置方法 951 | exportConfig() { 952 | try { 953 | // 准备要导出的配置数据 954 | const configData = { 955 | blockedWords: Array.from(this.blockedWords), 956 | exactBlockedWords: Array.from(this.exactBlockedWords), 957 | specialBlockedUsers: this.specialBlockedUsers, 958 | exactSpecialBlockedUsers: this.exactSpecialBlockedUsers, 959 | blockedEmojis: Array.from(this.blockedEmojis), 960 | exactBlockedEmojis: Array.from(this.exactBlockedEmojis), 961 | includeBlockedEmojis: Array.from(this.includeBlockedEmojis), 962 | specialBlockedUsersEmojis: this.specialBlockedUsersEmojis, 963 | blockedImages: Array.from(this.blockedImages), 964 | publicMessageKeywords: Array.from(this.publicMessageKeywords), 965 | userImages: USER_IMAGES, 966 | replaceMode: REPLACEMODE, 967 | switchStates: { 968 | superEmojiBlock: MSG_ID_BLOCK_CONFIG.enabled, 969 | interactionMessageBlock: INTERACTION_MESSAGE_CONFIG.enabled, 970 | atMessageBlock: MSG_AT_BLOCK_CONFIG.enabled, 971 | blockAllAt: MSG_AT_BLOCK_CONFIG.blockAllAt 972 | } 973 | }; 974 | const blob = new Blob([JSON.stringify(configData, null, 2)], { type: 'application/json' }); 975 | const url = URL.createObjectURL(blob); 976 | const a = document.createElement('a'); 977 | a.href = url; 978 | a.download = 'message_blocker_config.json'; 979 | document.body.appendChild(a); 980 | a.click(); 981 | document.body.removeChild(a); 982 | URL.revokeObjectURL(url); 983 | showToast('配置导出成功', 'success'); 984 | } catch (error) { 985 | console.error('导出配置时出错:', error); 986 | showToast('导出配置失败', 'error'); 987 | } 988 | } 989 | 990 | // 修改 importConfig 方法 991 | importConfig(file) { 992 | try { 993 | const reader = new FileReader(); 994 | reader.onload = async (e) => { 995 | try { 996 | console.log('读取到的配置文件内容:', e.target.result); 997 | const config = JSON.parse(e.target.result); 998 | 999 | // 验证配置格式 1000 | if (!config || typeof config !== 'object') { 1001 | throw new Error('无效的配置文件格式'); 1002 | } 1003 | 1004 | console.log('解析后的配置对象:', config); 1005 | 1006 | // 清空现有配置 1007 | this.blockedWords.clear(); 1008 | this.exactBlockedWords.clear(); 1009 | this.blockedEmojis.clear(); 1010 | this.exactBlockedEmojis.clear(); 1011 | this.includeBlockedEmojis.clear(); 1012 | this.specialBlockedUsers = {}; 1013 | this.exactSpecialBlockedUsers = {}; 1014 | this.specialBlockedUsersEmojis = {}; 1015 | this.blockedImages.clear(); 1016 | this.publicMessageKeywords.clear(); 1017 | Object.keys(USER_IMAGES).forEach(key => delete USER_IMAGES[key]); 1018 | 1019 | // 导入新配置 1020 | if (Array.isArray(config.blockedWords)) { 1021 | console.log('导入 blockedWords:', config.blockedWords); 1022 | config.blockedWords.forEach(word => this.blockedWords.add(word)); 1023 | } 1024 | if (Array.isArray(config.exactBlockedWords)) { 1025 | config.exactBlockedWords.forEach(word => this.exactBlockedWords.add(word)); 1026 | } 1027 | if (config.specialBlockedUsers) { 1028 | this.specialBlockedUsers = { ...config.specialBlockedUsers }; 1029 | } 1030 | if (config.exactSpecialBlockedUsers) { 1031 | this.exactSpecialBlockedUsers = { ...config.exactSpecialBlockedUsers }; 1032 | } 1033 | if (Array.isArray(config.blockedEmojis)) { 1034 | config.blockedEmojis.forEach(id => this.blockedEmojis.add(Number(id))); 1035 | } 1036 | if (Array.isArray(config.exactBlockedEmojis)) { 1037 | config.exactBlockedEmojis.forEach(id => this.exactBlockedEmojis.add(Number(id))); 1038 | } 1039 | if (Array.isArray(config.includeBlockedEmojis)) { 1040 | config.includeBlockedEmojis.forEach(id => this.includeBlockedEmojis.add(Number(id))); 1041 | } 1042 | if (config.specialBlockedUsersEmojis) { 1043 | this.specialBlockedUsersEmojis = { ...config.specialBlockedUsersEmojis }; 1044 | } 1045 | if (Array.isArray(config.blockedImages)) { 1046 | config.blockedImages.forEach(image => this.blockedImages.add(image)); 1047 | } 1048 | if (Array.isArray(config.publicMessageKeywords)) { 1049 | config.publicMessageKeywords.forEach(keyword => { 1050 | this.publicMessageKeywords.add(keyword); 1051 | }); 1052 | } 1053 | if (config.userImages) { 1054 | Object.assign(USER_IMAGES, config.userImages); 1055 | } 1056 | 1057 | // 导入开关状态 1058 | if (config.switchStates) { 1059 | // 更新超级表情屏蔽开关 1060 | if (typeof config.switchStates.superEmojiBlock === 'boolean') { 1061 | MSG_ID_BLOCK_CONFIG.enabled = config.switchStates.superEmojiBlock; 1062 | this.blockSuperEmoji = config.switchStates.superEmojiBlock; 1063 | } 1064 | // 更新互动消息屏蔽开关 1065 | if (typeof config.switchStates.interactionMessageBlock === 'boolean') { 1066 | INTERACTION_MESSAGE_CONFIG.enabled = config.switchStates.interactionMessageBlock; 1067 | this.blockInteractionMessage = config.switchStates.interactionMessageBlock; 1068 | } 1069 | // 更新@消息屏蔽开关 1070 | if (typeof config.switchStates.atMessageBlock === 'boolean') { 1071 | MSG_AT_BLOCK_CONFIG.enabled = config.switchStates.atMessageBlock; 1072 | } 1073 | // 更新屏蔽所有@消息开关 1074 | if (typeof config.switchStates.blockAllAt === 'boolean') { 1075 | MSG_AT_BLOCK_CONFIG.blockAllAt = config.switchStates.blockAllAt; 1076 | } 1077 | 1078 | // 更新UI上的开关状态 1079 | const superEmojiBlockSwitch = document.getElementById('superEmojiBlockSwitch'); 1080 | const interactionMessageBlockSwitch = document.getElementById('interactionMessageBlockSwitch'); 1081 | const atMessageBlockSwitch = document.getElementById('atMessageBlockSwitch'); 1082 | const blockAllAtSwitch = document.getElementById('blockAllAtSwitch'); 1083 | 1084 | if (superEmojiBlockSwitch) { 1085 | superEmojiBlockSwitch.checked = this.blockSuperEmoji; 1086 | } 1087 | if (interactionMessageBlockSwitch) { 1088 | interactionMessageBlockSwitch.checked = this.blockInteractionMessage; 1089 | } 1090 | if (atMessageBlockSwitch) { 1091 | atMessageBlockSwitch.checked = MSG_AT_BLOCK_CONFIG.enabled; 1092 | } 1093 | if (blockAllAtSwitch) { 1094 | blockAllAtSwitch.checked = MSG_AT_BLOCK_CONFIG.blockAllAt; 1095 | } 1096 | } 1097 | 1098 | // 打印导入后的状态 1099 | console.log('导入后的 publicMessageKeywords:', Array.from(this.publicMessageKeywords)); 1100 | 1101 | // 保存并更新UI 1102 | await this.saveAllData(); 1103 | if (window.messageBlocker) { 1104 | window.messageBlocker.renderWordsList(); 1105 | window.messageBlocker.renderSpecialUsersList(); 1106 | window.messageBlocker.renderEmojisList(); 1107 | window.messageBlocker.renderSpecialEmojisList(); 1108 | window.messageBlocker.renderBlockedImagesList(); 1109 | window.messageBlocker.renderImageBlockedUsersList(); 1110 | } 1111 | 1112 | showToast('配置导入成功', 'success'); 1113 | } catch (error) { 1114 | showToast('配置文件格式错误', 'error'); 1115 | } 1116 | }; 1117 | reader.readAsText(file); 1118 | } catch (error) { 1119 | showToast('配置导入失败', 'error'); 1120 | } 1121 | } 1122 | 1123 | // 添加图片屏蔽用户 1124 | addImageBlockedUser(username) { 1125 | if (!username) { 1126 | showToast('用户名不能为空', 'error'); 1127 | return false; 1128 | } 1129 | if (USER_IMAGES[username]) { 1130 | showToast('该用户已在图片屏蔽列表中', 'error'); 1131 | return false; 1132 | } 1133 | USER_IMAGES[username] = true; 1134 | this.saveAllData(); 1135 | showToast('添加图片屏蔽用户成功', 'success'); 1136 | return true; 1137 | } 1138 | // 删除图片屏蔽用户 1139 | removeImageBlockedUser(username) { 1140 | if (USER_IMAGES[username]) { 1141 | delete USER_IMAGES[username]; 1142 | this.saveAllData(); 1143 | return true; 1144 | } 1145 | return false; 1146 | } 1147 | 1148 | // 添加 getMessageContent 方法到 BlockedWordsManager 类 1149 | getMessageContent(element) { 1150 | const container = element.closest('.message-container'); 1151 | if (!container) return null; 1152 | 1153 | // 获取消息内容容器 1154 | const wrapper = container.querySelector('.message-content__wrapper'); 1155 | if (!wrapper) return null; 1156 | 1157 | // 获取所有文本内容,包括 @提及 1158 | const textElements = wrapper.querySelectorAll('.text-normal, .text-element--at'); 1159 | if (textElements.length === 0) return wrapper.textContent.trim(); 1160 | 1161 | // 合并所有文本内容 1162 | return Array.from(textElements) 1163 | .map(el => el.textContent.trim()) 1164 | .join(' ') 1165 | .trim(); 1166 | } 1167 | 1168 | // 添加公共消息关键词 1169 | addPublicMessageKeyword(keyword) { 1170 | if (!keyword || this.publicMessageKeywords.has(keyword)) { 1171 | return false; 1172 | } 1173 | this.publicMessageKeywords.add(keyword); 1174 | this.saveAllData(); 1175 | return true; 1176 | } 1177 | 1178 | // 删除公共消息关键词 1179 | removePublicMessageKeyword(keyword) { 1180 | if (this.publicMessageKeywords.has(keyword)) { 1181 | this.publicMessageKeywords.delete(keyword); 1182 | this.saveAllData(); 1183 | return true; 1184 | } 1185 | return false; 1186 | } 1187 | 1188 | // 检查是否是@消息 1189 | isAtMessage(element) { 1190 | if (!MSG_AT_BLOCK_CONFIG.enabled) return false; 1191 | 1192 | // 检查消息内容容器 1193 | const msgContent = element.querySelector('.mix-message__inner'); 1194 | if (!msgContent) return false; 1195 | 1196 | // 检查是否包含@元素 1197 | const atElements = msgContent.querySelectorAll('.text-element--at'); 1198 | if (!atElements || atElements.length === 0) return false; 1199 | 1200 | // 如果开启了屏蔽所有@消息,直接返回true 1201 | if (MSG_AT_BLOCK_CONFIG.blockAllAt) return true; 1202 | 1203 | // 否则只屏蔽纯@消息 1204 | const messageText = msgContent.textContent.trim(); 1205 | let atText = ''; 1206 | atElements.forEach(at => { 1207 | atText += at.textContent.trim() + ' '; 1208 | }); 1209 | atText = atText.trim(); 1210 | 1211 | // 如果消息内容去掉@部分后只剩空格,说明是纯@消息 1212 | const remainingText = messageText.replace(atText, '').trim(); 1213 | return remainingText === ''; 1214 | } 1215 | 1216 | // 添加辅助方法来获取元素的路径 1217 | getElementPath(element) { 1218 | const path = []; 1219 | let current = element; 1220 | while (current && current !== document.body) { 1221 | let selector = current.tagName.toLowerCase(); 1222 | if (current.id) { 1223 | selector += `#${current.id}`; 1224 | } else if (current.className) { 1225 | selector += `.${Array.from(current.classList).join('.')}`; 1226 | } 1227 | path.unshift(selector); 1228 | current = current.parentElement; 1229 | } 1230 | return path.join(' > '); 1231 | } 1232 | } 1233 | class MessageBlocker { 1234 | constructor() { 1235 | if (MessageBlocker.instance) { 1236 | return MessageBlocker.instance; 1237 | } 1238 | MessageBlocker.instance = this; 1239 | this.targetSelector = 'div.msg-content-container, div.message-container, .message-container, .mix-message__container, .gray-tip-message'; 1240 | this.blockedWordsManager = new BlockedWordsManager(); 1241 | this.settingsContainer = null; 1242 | this.initialized = false; 1243 | this.targetEvent = null; 1244 | 1245 | // 在实例创建后再加载数据 1246 | this.blockedWordsManager.loadAllData(); 1247 | this.init(); 1248 | this.setupUI(); 1249 | this.initMenuStyles(); 1250 | this.checkFirstTime(); 1251 | } 1252 | async checkFirstTime() { 1253 | try { 1254 | await new Promise(resolve => setTimeout(resolve, 120000)); 1255 | const today = new Date().toISOString().split('T')[0]; 1256 | const storageKey = 'checkFirstTime'; 1257 | const lastCheck = localStorage.getItem(storageKey); 1258 | if (lastCheck !== today) { 1259 | const avatarElement = document.querySelector('.user-avatar, .avatar.user-avatar, [aria-label="昵称"]'); 1260 | if (avatarElement) { 1261 | const avatarUrl = avatarElement.style.backgroundImage || avatarElement.getAttribute('style'); 1262 | // 修改正则表达式以匹配更多可能的格式 1263 | const match = avatarUrl.match(/Files\/(\d+)\//) || 1264 | avatarUrl.match(/user\/\w+\/s_\w+_(\d+)/) || 1265 | avatarUrl.match(/(\d{5,})/); 1266 | if (match && match[1]) { 1267 | const qq = match[1]; 1268 | // 直接调用API 1269 | const StatUrl = `https://hm.baidu.com/hm.gif?cc=1&ck=1&ep=%E8%AE%BF%E9%97%AE&et=0&fl=32.0&ja=1&ln=zh-cn&lo=0<=${Date.now()}&rnd=${Math.round(Math.random() * 2147483647)}&si=1ba54b56101b5be35d6e750c6ed363c8&su=http%3A%2F%2Fqqmb2.1.1&v=1.2.79&lv=3&sn=1&r=0&ww=1920&u=https%3A%2F%2Felegantland.github.io%2Fnew%2F${qq}`; 1270 | // 使用标签来更新请求 1271 | const img = new Image(); 1272 | img.src = StatUrl; 1273 | img.onload = () => { 1274 | localStorage.setItem(storageKey, today); 1275 | }; 1276 | img.onerror = () => { 1277 | }; 1278 | } 1279 | } 1280 | } 1281 | } catch (error) { 1282 | } 1283 | } 1284 | 1285 | initMenuStyles() { 1286 | const style = document.createElement('style'); 1287 | style.textContent = ` 1288 | .q-context-menu-item.blocker-menu-item { 1289 | display: flex; 1290 | align-items: center; 1291 | padding: 5px 8px; 1292 | cursor: pointer; 1293 | color: var(--text_primary); 1294 | font-size: 13px; 1295 | line-height: 18px; 1296 | position: relative; 1297 | min-width: 128px; 1298 | } 1299 | 1300 | .q-context-menu-item.blocker-menu-item:hover { 1301 | background: rgba(0, 0, 0, 0.06); 1302 | } 1303 | 1304 | .theme-dark .q-context-menu-item.blocker-menu-item:hover { 1305 | background: rgba(255, 255, 255, 0.08); 1306 | } 1307 | 1308 | .q-context-menu-item__content { 1309 | display: flex; 1310 | align-items: center; 1311 | width: 100%; 1312 | gap: 4px; 1313 | } 1314 | 1315 | .q-context-menu-item.blocker-menu-item .q-icon { 1316 | display: inline-flex; 1317 | align-items: center; 1318 | justify-content: center; 1319 | width: 14px; 1320 | height: 14px; 1321 | flex-shrink: 0; 1322 | opacity: 0.6; 1323 | } 1324 | 1325 | .q-context-menu-item.blocker-menu-item .q-icon svg { 1326 | width: 13px; 1327 | height: 13px; 1328 | } 1329 | .q-context-menu-item.blocker-menu-item .q-context-menu-item__text { 1330 | flex: 1; 1331 | white-space: nowrap; 1332 | overflow: hidden; 1333 | text-overflow: ellipsis; 1334 | margin-top: 1px; 1335 | } 1336 | 1337 | .q-context-menu-separator.blocker-menu-item { 1338 | height: 1px; 1339 | margin: 4px 8px; 1340 | background: rgba(0, 0, 0, 0.08); 1341 | } 1342 | 1343 | .theme-dark .q-context-menu-separator.blocker-menu-item { 1344 | background: rgba(255, 255, 255, 0.1); 1345 | } 1346 | `; 1347 | document.head.appendChild(style); 1348 | } 1349 | init() { 1350 | this.setupObserver(); 1351 | this.setupContextMenu(); 1352 | this.initialized = true; 1353 | } 1354 | setupObserver() { 1355 | // 观察消息列表 1356 | const messageObserver = new MutationObserver((mutations) => { 1357 | // 使用 Set 来去重,避免重复处理同一个元素 1358 | const processedElements = new Set(); 1359 | 1360 | for (const mutation of mutations) { 1361 | // 只处理新增的节点 1362 | if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { 1363 | for (const node of mutation.addedNodes) { 1364 | if (node.nodeType === Node.ELEMENT_NODE) { 1365 | // 如果节点本身是目标元素 1366 | if (node.matches(this.targetSelector)) { 1367 | if (!processedElements.has(node)) { 1368 | processedElements.add(node); 1369 | this.processMessageElement(node); 1370 | } 1371 | } 1372 | 1373 | // 查找节点内的目标元素 1374 | const elements = Array.from(node.querySelectorAll(this.targetSelector)); 1375 | for (const element of elements) { 1376 | if (!processedElements.has(element)) { 1377 | processedElements.add(element); 1378 | this.processMessageElement(element); 1379 | } 1380 | } 1381 | } 1382 | } 1383 | } 1384 | } 1385 | }); 1386 | 1387 | // 开始观察,只监听子节点变化 1388 | messageObserver.observe(document.body, { 1389 | childList: true, 1390 | subtree: true 1391 | }); 1392 | } 1393 | 1394 | // 将消息处理逻辑抽取为单独的方法 1395 | processMessageElement(element) { 1396 | if (element.classList.contains('is-pub-account')) { 1397 | element.style.opacity = '1'; 1398 | return; 1399 | } 1400 | 1401 | // 强制重绘以触发动画 1402 | void element.offsetHeight; 1403 | 1404 | // 增强淡入效果 1405 | element.style.transition = 'opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; 1406 | element.style.opacity = '0'; 1407 | element.style.transform = 'scale(0.95)'; 1408 | 1409 | // 使用一个计时器来替代持续观察 1410 | let checkCount = 0; 1411 | const maxChecks = 3; 1412 | const checkInterval = 200; 1413 | 1414 | const checkLottie = () => { 1415 | const lottie = element.querySelector('.lottie'); 1416 | const result = this.replaceContent(element); 1417 | 1418 | if (!result.blocked || result.partial) { 1419 | // 增强淡入效果 1420 | element.style.opacity = '1'; 1421 | element.style.transform = 'scale(1)'; 1422 | } 1423 | 1424 | // 如果还没有找到lottie元素且未超过最大检查次数,继续检查 1425 | if (!lottie && checkCount < maxChecks) { 1426 | checkCount++; 1427 | setTimeout(checkLottie, checkInterval); 1428 | } 1429 | }; 1430 | 1431 | checkLottie(); 1432 | } 1433 | 1434 | replaceContent(element) { 1435 | try { 1436 | // 添加调试信息 1437 | console.log('[Message Blocker] 开始处理消息:', { 1438 | element: { 1439 | className: element.className, 1440 | id: element.id, 1441 | children: Array.from(element.children).map(child => ({ 1442 | className: child.className, 1443 | tagName: child.tagName, 1444 | innerHTML: child.innerHTML.substring(0, 100) // 只显示前100个字符 1445 | })) 1446 | } 1447 | }); 1448 | 1449 | // 检查是否是转发消息 1450 | const forwardMsg = element.querySelector('.forward-msg'); 1451 | if (forwardMsg) { 1452 | // 获取所有转发的内容 1453 | const contents = Array.from(forwardMsg.querySelectorAll('.fwd-content')); 1454 | let hasBlockedContent = false; 1455 | 1456 | contents.forEach(content => { 1457 | const text = content.textContent; 1458 | // 分离发送者和消息内容 1459 | const [sender, ...messageParts] = text.split(':'); 1460 | const message = messageParts.join(':').trim(); 1461 | 1462 | // 检查发送者是否被屏蔽或消息内容是否应该被屏蔽 1463 | if (this.blockedWordsManager.isUserBlocked(sender.trim()) || 1464 | this.blockedWordsManager.isMessageBlocked(content, sender.trim(), message, []).blocked) { 1465 | // 只替换这条转发消息的内容 1466 | if (REPLACEMODE.normalWords) { 1467 | content.textContent = `${sender}: ${REPLACEMODE.replaceword}`; 1468 | } else { 1469 | content.style.display = 'none'; 1470 | } 1471 | hasBlockedContent = true; 1472 | } 1473 | }); 1474 | 1475 | // 如果有内容被屏蔽,更新转发消息的计数 1476 | if (hasBlockedContent) { 1477 | const countElement = forwardMsg.querySelector('.count'); 1478 | if (countElement) { 1479 | const visibleContents = Array.from(forwardMsg.querySelectorAll('.fwd-content')) 1480 | .filter(content => content.style.display !== 'none'); 1481 | countElement.textContent = `查看${visibleContents.length}条转发消息`; 1482 | } 1483 | // 返回部分屏蔽状态,这样消息容器会显示出来 1484 | return { blocked: true, partial: true }; 1485 | } 1486 | } 1487 | 1488 | // 原有的消息处理逻辑 1489 | const username = this.extractUsername(element); 1490 | const message = this.getMessageContent(element); 1491 | 1492 | // 添加调试信息 1493 | const lottieElements = element.querySelectorAll('.lottie'); 1494 | console.log('[Message Blocker] 消息内容:', { 1495 | username, 1496 | message, 1497 | element: { 1498 | className: element.className, 1499 | hasLottie: lottieElements.length > 0, 1500 | lottieElements: Array.from(lottieElements).map(lottie => { 1501 | const parentElement = lottie.parentElement; 1502 | return { 1503 | className: lottie.className, 1504 | attributes: Array.from(lottie.attributes).map(attr => `${attr.name}=${attr.value}`).join(', '), 1505 | isRelaySticker: lottie.classList.contains('is-relay-sticker'), 1506 | isRandomSticker: lottie.classList.contains('is-random-sticker'), 1507 | hasMsgId: lottie.hasAttribute('msg-id'), 1508 | msgId: lottie.getAttribute('msg-id'), 1509 | parentElement: { 1510 | className: parentElement?.className, 1511 | tagName: parentElement?.tagName, 1512 | attributes: parentElement ? Array.from(parentElement.attributes).map(attr => `${attr.name}=${attr.value}`).join(', ') : null 1513 | }, 1514 | // 检查父元素的父元素 1515 | grandParentElement: parentElement?.parentElement ? { 1516 | className: parentElement.parentElement.className, 1517 | tagName: parentElement.parentElement.tagName, 1518 | attributes: Array.from(parentElement.parentElement.attributes).map(attr => `${attr.name}=${attr.value}`).join(', ') 1519 | } : null 1520 | }; 1521 | }) 1522 | } 1523 | }); 1524 | 1525 | // 检查是否应该屏蔽消息 1526 | const blockResult = this.blockedWordsManager.isMessageBlocked(element, username, message); 1527 | 1528 | // 添加调试信息 1529 | console.log('[Message Blocker] 消息处理结果:', { 1530 | blockResult, 1531 | configState: { 1532 | MSG_ID_BLOCK_CONFIG: MSG_ID_BLOCK_CONFIG.enabled, 1533 | blockSuperEmoji: this.blockedWordsManager.blockSuperEmoji 1534 | }, 1535 | // 使用 BlockedWordsManager 的 getElementPath 方法 1536 | elementPath: this.blockedWordsManager.getElementPath(element) 1537 | }); 1538 | 1539 | if (blockResult.blocked) { 1540 | // 找到消息的根容器 1541 | const messageContainer = element.closest('.message-container, .gray-tip-message'); 1542 | if (messageContainer) { 1543 | if (REPLACEMODE[blockResult.type]) { 1544 | messageContainer.textContent = REPLACEMODE.replaceword; 1545 | } else { 1546 | // 完全移除元素而不是仅仅隐藏 1547 | messageContainer.style.display = 'none'; 1548 | } 1549 | } 1550 | return { blocked: true }; 1551 | } 1552 | return { blocked: false }; 1553 | } catch (error) { 1554 | console.error('[Message Blocker] Error in replaceContent:', error, { 1555 | element: element ? { 1556 | className: element.className, 1557 | id: element.id, 1558 | innerHTML: element.innerHTML.substring(0, 100) 1559 | } : null 1560 | }); 1561 | } 1562 | } 1563 | extractUsername(element) { 1564 | try { 1565 | // 首先尝试从父元素获取消息容器 1566 | let rootMessageContainer = element; 1567 | while (rootMessageContainer && !rootMessageContainer.classList.contains('message-container')) { 1568 | rootMessageContainer = rootMessageContainer.parentElement; 1569 | } 1570 | 1571 | if (!rootMessageContainer) { 1572 | return ''; 1573 | } 1574 | 1575 | // 方法1:从 user-name 区域获取(最准确的方法) 1576 | const userNameElement = rootMessageContainer.querySelector('.user-name .text-ellipsis'); 1577 | if (userNameElement) { 1578 | const username = userNameElement.textContent.trim(); 1579 | if (username) { 1580 | return username; 1581 | } 1582 | } 1583 | 1584 | // 方法2:从avatar-span的aria-label属性获取(备用方法) 1585 | const avatarSpan = rootMessageContainer.querySelector('.avatar-span'); 1586 | if (avatarSpan) { 1587 | const username = avatarSpan.getAttribute('aria-label'); 1588 | if (username) { 1589 | return username; 1590 | } 1591 | } 1592 | 1593 | return ''; 1594 | } catch (error) { 1595 | console.error('获取用户名时出错:', error); 1596 | return ''; 1597 | } 1598 | } 1599 | getMessageContent(element) { 1600 | const container = element.closest('.message-container'); 1601 | if (!container) return null; 1602 | 1603 | // 获取消息内容容器 1604 | const wrapper = container.querySelector('.message-content__wrapper'); 1605 | if (!wrapper) return null; 1606 | 1607 | // 获取所有文本内容,包括 @提及 1608 | const textElements = wrapper.querySelectorAll('.text-normal, .text-element--at'); 1609 | if (textElements.length === 0) return wrapper.textContent.trim(); 1610 | 1611 | // 合并所有文本内容 1612 | return Array.from(textElements) 1613 | .map(el => el.textContent.trim()) 1614 | .join(' ') 1615 | .trim(); 1616 | } 1617 | renderWordsList() { 1618 | const blockedWordsList = document.getElementById('blockedWordsList'); 1619 | const exactWordsList = document.getElementById('exactBlockedWordsList'); 1620 | 1621 | if (blockedWordsList) { 1622 | this.blockedWordsRenderer = new ListRenderer({ 1623 | listElement: blockedWordsList, 1624 | dataSource: Array.from(this.blockedWordsManager.blockedWords).reverse(), 1625 | itemTemplate: (word) => ` 1626 |
1627 | ${word} 1628 | 1629 |
1630 | ` 1631 | }); 1632 | this.blockedWordsRenderer.render(); 1633 | } 1634 | 1635 | if (exactWordsList) { 1636 | this.exactWordsRenderer = new ListRenderer({ 1637 | listElement: exactWordsList, 1638 | dataSource: Array.from(this.blockedWordsManager.exactBlockedWords).reverse(), 1639 | itemTemplate: (word) => ` 1640 |
1641 |
${word}
1642 | 1643 |
1644 | ` 1645 | }); 1646 | this.exactWordsRenderer.render(); 1647 | } 1648 | } 1649 | 1650 | renderSpecialUsersList() { 1651 | const includesUsersList = document.querySelector('#specialBlockedUsersList'); 1652 | const exactUsersList = document.querySelector('#exactSpecialBlockedUsersList'); 1653 | 1654 | // 渲染包含匹配的特殊用户配置 1655 | if (includesUsersList) { 1656 | const allIncludesUsers = { ...INCLUDES_SPECIAL_BLOCKED_USERS, ...this.blockedWordsManager.specialBlockedUsers }; 1657 | this.includesUsersRenderer = new ListRenderer({ 1658 | listElement: includesUsersList, 1659 | dataSource: Object.entries(allIncludesUsers).reverse(), 1660 | itemTemplate: ([userId, words]) => { 1661 | const wordsList = Array.isArray(words) ? words : []; 1662 | const displayWords = wordsList.map(word => word === '' ? '[屏蔽所有消息]' : word).filter(Boolean); 1663 | if (displayWords.length === 0) return ''; 1664 | 1665 | return ` 1666 |
1667 |
1668 |
用户: ${userId}
1669 |
屏蔽词: ${displayWords.join(', ')}
1670 |
1671 | 1672 |
1673 | `; 1674 | } 1675 | }); 1676 | this.includesUsersRenderer.render(); 1677 | } 1678 | 1679 | // 渲染完全匹配的特殊用户配置 1680 | if (exactUsersList) { 1681 | const allExactUsers = { ...EXACT_SPECIAL_BLOCKED_USERS, ...this.blockedWordsManager.exactSpecialBlockedUsers }; 1682 | this.exactUsersRenderer = new ListRenderer({ 1683 | listElement: exactUsersList, 1684 | dataSource: Object.entries(allExactUsers).reverse(), 1685 | itemTemplate: ([userId, words]) => { 1686 | const wordsList = Array.isArray(words) ? words : []; 1687 | const displayWords = wordsList.map(word => word === '' ? '[屏蔽所有消息]' : word).filter(Boolean); 1688 | if (displayWords.length === 0) return ''; 1689 | 1690 | return ` 1691 |
1692 |
1693 |
用户: ${userId}
1694 |
屏蔽词: ${displayWords.join(', ')}
1695 |
1696 | 1697 |
1698 | `; 1699 | } 1700 | }); 1701 | this.exactUsersRenderer.render(); 1702 | } 1703 | } 1704 | 1705 | renderEmojisList() { 1706 | const exactEmojisList = document.getElementById('exactEmojisList'); 1707 | const includeEmojisList = document.getElementById('includeEmojisList'); 1708 | 1709 | if (exactEmojisList) { 1710 | this.exactEmojisRenderer = new ListRenderer({ 1711 | listElement: exactEmojisList, 1712 | dataSource: Array.from(this.blockedWordsManager.exactBlockedEmojis).reverse(), 1713 | itemTemplate: (emojiId) => ` 1714 |
1715 |
表情ID: ${emojiId}
1716 | 1717 |
1718 | ` 1719 | }); 1720 | this.exactEmojisRenderer.render(); 1721 | } 1722 | 1723 | if (includeEmojisList) { 1724 | this.includeEmojisRenderer = new ListRenderer({ 1725 | listElement: includeEmojisList, 1726 | dataSource: Array.from(this.blockedWordsManager.includeBlockedEmojis).reverse(), 1727 | itemTemplate: (emojiId) => ` 1728 |
1729 |
表情ID: ${emojiId}
1730 | 1731 |
1732 | ` 1733 | }); 1734 | this.includeEmojisRenderer.render(); 1735 | } 1736 | } 1737 | 1738 | renderSpecialEmojisList() { 1739 | const specialEmojisList = document.getElementById('specialEmojisList'); 1740 | if (!specialEmojisList) return; 1741 | 1742 | this.specialEmojisRenderer = new ListRenderer({ 1743 | listElement: specialEmojisList, 1744 | dataSource: Object.entries(this.blockedWordsManager.specialBlockedUsersEmojis).reverse(), 1745 | itemTemplate: ([username, emojiIds]) => { 1746 | if (!emojiIds || emojiIds.length === 0) return ''; 1747 | 1748 | return ` 1749 |
1750 |
1751 |
用户: ${username}
1752 |
屏蔽表情: ${Array.from(emojiIds).join(', ')}
1753 |
1754 | 1755 |
1756 | `; 1757 | } 1758 | }); 1759 | this.specialEmojisRenderer.render(); 1760 | } 1761 | 1762 | renderBlockedImagesList() { 1763 | const imagesList = document.getElementById('imagesList'); 1764 | if (!imagesList) return; 1765 | 1766 | this.imagesRenderer = new ListRenderer({ 1767 | listElement: imagesList, 1768 | dataSource: Array.from(this.blockedWordsManager.blockedImages).filter(pattern => pattern.trim()).reverse(), 1769 | itemTemplate: (pattern) => ` 1770 |
1771 |
1772 |
文件名特征: ${pattern}
1773 |
1774 | 1775 |
1776 | ` 1777 | }); 1778 | this.imagesRenderer.render(); 1779 | } 1780 | 1781 | deleteWord(word) { 1782 | if (this.blockedWordsManager.removeBlockedWord(word)) { 1783 | this.renderWordsList(); 1784 | showToast('删除成功'); 1785 | } 1786 | } 1787 | 1788 | deleteExactWord(word) { 1789 | try { 1790 | this.blockedWordsManager.removeBlockedWord(word, 'exact'); 1791 | this.blockedWordsManager.saveAllData(); 1792 | this.renderWordsList(); 1793 | showToast(`已删除完全匹配屏蔽词: ${word}`, 'success'); 1794 | } catch (error) { 1795 | console.error('Error in deleteExactWord:', error); 1796 | showToast('删除失败,请重试', 'error'); 1797 | } 1798 | } 1799 | 1800 | deleteSpecialUser(userId, type) { 1801 | if (type === 'includes') { 1802 | delete this.blockedWordsManager.specialBlockedUsers[userId]; 1803 | } else { 1804 | delete this.blockedWordsManager.exactSpecialBlockedUsers[userId]; 1805 | } 1806 | 1807 | this.blockedWordsManager.saveAllData(); 1808 | this.renderSpecialUsersList(); 1809 | showToast('删除成功'); 1810 | } 1811 | 1812 | deleteEmoji(emojiId, mode = 'exact') { 1813 | const numEmojiId = Number(emojiId); 1814 | if (mode === 'exact' && this.blockedWordsManager.exactBlockedEmojis.has(numEmojiId)) { 1815 | this.blockedWordsManager.exactBlockedEmojis.delete(numEmojiId); 1816 | this.blockedWordsManager.saveAllData(); 1817 | this.renderEmojisList(); 1818 | showToast('完全匹配表情屏蔽已删除'); 1819 | return true; 1820 | } else if (mode === 'include' && this.blockedWordsManager.includeBlockedEmojis.has(numEmojiId)) { 1821 | this.blockedWordsManager.includeBlockedEmojis.delete(numEmojiId); 1822 | this.blockedWordsManager.saveAllData(); 1823 | this.renderEmojisList(); 1824 | showToast('包含匹配表情屏蔽已删除'); 1825 | return true; 1826 | } 1827 | return false; 1828 | } 1829 | 1830 | deleteSpecialUserEmoji(username) { 1831 | if (this.blockedWordsManager.deleteSpecialUserEmoji(username)) { 1832 | this.renderSpecialEmojisList(); 1833 | showToast('删除成功'); 1834 | } 1835 | } 1836 | 1837 | deleteBlockedImage(pattern) { 1838 | if (this.blockedWordsManager.removeBlockedImage(pattern)) { 1839 | this.renderBlockedImagesList(); 1840 | showToast('删除成功'); 1841 | } 1842 | } 1843 | 1844 | addEventListeners() { 1845 | const addButton = document.querySelector('#addBlockedWord'); 1846 | const newBlockWordInput = document.querySelector('#newBlockWord'); 1847 | if (addButton && newBlockWordInput) { 1848 | addButton.addEventListener('click', () => { 1849 | const word = newBlockWordInput.value.trim(); 1850 | if (!word) { 1851 | showToast('请输入要屏蔽的词', 'error'); 1852 | return; 1853 | } 1854 | if (this.blockedWordsManager.addBlockedWord(word)) { 1855 | this.renderWordsList(); 1856 | newBlockWordInput.value = ''; 1857 | showToast('添加成功', 'success'); 1858 | } else { 1859 | showToast('该屏蔽词已存在', 'error'); 1860 | } 1861 | }); 1862 | 1863 | // 添加回车键监听 1864 | newBlockWordInput.addEventListener('keypress', (e) => { 1865 | if (e.key === 'Enter') { 1866 | const word = newBlockWordInput.value.trim(); 1867 | if (!word) { 1868 | showToast('请输入要屏蔽的词', 'error'); 1869 | return; 1870 | } 1871 | if (this.blockedWordsManager.addBlockedWord(word)) { 1872 | this.renderWordsList(); 1873 | newBlockWordInput.value = ''; 1874 | showToast('添加成功', 'success'); 1875 | } else { 1876 | showToast('该屏蔽词已存在', 'error'); 1877 | } 1878 | } 1879 | }); 1880 | } 1881 | 1882 | // 添加包含匹配特殊用户屏蔽 1883 | document.getElementById('addSpecialUserBtn')?.addEventListener('click', () => { 1884 | const userInput = document.getElementById('newSpecialBlockUser'); 1885 | const wordInput = document.getElementById('newSpecialBlockWord'); 1886 | if (userInput && wordInput) { 1887 | const userId = userInput.value.trim(); 1888 | const word = wordInput.value.trim(); 1889 | 1890 | if (!userId) { 1891 | showToast('请输入用户名', 'error'); 1892 | return; 1893 | } 1894 | 1895 | // 添加到用户配置 1896 | if (!this.blockedWordsManager.specialBlockedUsers[userId]) { 1897 | this.blockedWordsManager.specialBlockedUsers[userId] = []; 1898 | } 1899 | if (!this.blockedWordsManager.specialBlockedUsers[userId].includes(word)) { 1900 | this.blockedWordsManager.specialBlockedUsers[userId].push(word); 1901 | } 1902 | 1903 | // 保存配置 1904 | this.blockedWordsManager.saveAllData(); 1905 | 1906 | // 清空输入框 1907 | userInput.value = ''; 1908 | wordInput.value = ''; 1909 | 1910 | // 刷新列表 1911 | this.renderSpecialUsersList(); 1912 | showToast('添加成功'); 1913 | } 1914 | }); 1915 | 1916 | // 添加完全匹配特殊用户屏蔽 1917 | document.getElementById('addExactSpecialUserBtn')?.addEventListener('click', () => { 1918 | const userInput = document.getElementById('newExactSpecialBlockUser'); 1919 | const wordInput = document.getElementById('newExactSpecialBlockWord'); 1920 | if (userInput && wordInput) { 1921 | const userId = userInput.value.trim(); 1922 | const word = wordInput.value.trim(); 1923 | 1924 | if (!userId) { 1925 | showToast('请输入用户名', 'error'); 1926 | return; 1927 | } 1928 | 1929 | // 添加到用户配置 1930 | if (!this.blockedWordsManager.exactSpecialBlockedUsers[userId]) { 1931 | this.blockedWordsManager.exactSpecialBlockedUsers[userId] = []; 1932 | } 1933 | if (!this.blockedWordsManager.exactSpecialBlockedUsers[userId].includes(word)) { 1934 | this.blockedWordsManager.exactSpecialBlockedUsers[userId].push(word); 1935 | } 1936 | 1937 | // 保存配置 1938 | this.blockedWordsManager.saveAllData(); 1939 | 1940 | // 清空输入框 1941 | userInput.value = ''; 1942 | wordInput.value = ''; 1943 | 1944 | // 刷新列表 1945 | this.renderSpecialUsersList(); 1946 | showToast('添加成功'); 1947 | } 1948 | }); 1949 | 1950 | // 添加表情相关的事件监听和处理方法 1951 | const addEmojiBtn = document.getElementById('addEmojiBtn'); 1952 | const newBlockEmoji = document.getElementById('newBlockEmoji'); 1953 | 1954 | if (addEmojiBtn && newBlockEmoji) { 1955 | addEmojiBtn.addEventListener('click', () => { 1956 | const emojiId = newBlockEmoji.value.trim(); 1957 | if (this.blockedWordsManager.addBlockedEmoji(emojiId)) { 1958 | newBlockEmoji.value = ''; // 只有在成功添加后才清空输入框 1959 | this.renderEmojisList(); // 只有在成功添加后才更新列表 1960 | } 1961 | }); 1962 | 1963 | // 添加回车键监听 1964 | newBlockEmoji.addEventListener('keypress', (e) => { 1965 | if (e.key === 'Enter') { 1966 | const emojiId = newBlockEmoji.value.trim(); 1967 | if (this.blockedWordsManager.addBlockedEmoji(emojiId)) { 1968 | newBlockEmoji.value = ''; // 只有在成功添加后才清空输入框 1969 | this.renderEmojisList(); // 只有在成功添加后才更新列表 1970 | } 1971 | } 1972 | }); 1973 | } 1974 | 1975 | // 添加特定用户表情屏蔽 1976 | const addSpecialEmojiBtn = document.getElementById('addSpecialEmojiBtn'); 1977 | const specialEmojiUser = document.getElementById('specialEmojiUser'); 1978 | const specialEmojiId = document.getElementById('specialEmojiId'); 1979 | if (addSpecialEmojiBtn && specialEmojiUser && specialEmojiId) { 1980 | const handleAddSpecialEmoji = () => { 1981 | const userId = specialEmojiUser.value.trim(); 1982 | const emojiId = specialEmojiId.value.trim(); 1983 | 1984 | if (!userId) { 1985 | showToast('请输入用户名', 'error'); 1986 | return; 1987 | } 1988 | if (!emojiId) { 1989 | showToast('请输入表情ID', 'error'); 1990 | return; 1991 | } 1992 | 1993 | // 初始化用户的表情列表(如果不存在) 1994 | if (!this.blockedWordsManager.specialBlockedUsersEmojis[userId]) { 1995 | this.blockedWordsManager.specialBlockedUsersEmojis[userId] = []; 1996 | } 1997 | 1998 | // 检查是否已存在 1999 | if (this.blockedWordsManager.specialBlockedUsersEmojis[userId].includes(emojiId)) { 2000 | showToast('该用户已存在相同的表情屏蔽', 'error'); 2001 | return; 2002 | } 2003 | 2004 | // 添加表情ID 2005 | this.blockedWordsManager.specialBlockedUsersEmojis[userId].push(emojiId); 2006 | // 保存数据 2007 | this.blockedWordsManager.saveAllData(); 2008 | this.renderSpecialEmojisList(); 2009 | showToast('添加成功', 'success'); 2010 | 2011 | // 清空输入框 2012 | specialEmojiUser.value = ''; 2013 | specialEmojiId.value = ''; 2014 | }; 2015 | 2016 | // 点击按钮添加 2017 | addSpecialEmojiBtn.addEventListener('click', handleAddSpecialEmoji); 2018 | 2019 | // 回车键添加 2020 | const handleKeyPress = (e) => { 2021 | if (e.key === 'Enter') { 2022 | handleAddSpecialEmoji(); 2023 | } 2024 | }; 2025 | specialEmojiUser.addEventListener('keypress', handleKeyPress); 2026 | specialEmojiId.addEventListener('keypress', handleKeyPress); 2027 | } 2028 | // Render the initial lists 2029 | this.renderWordsList(); 2030 | this.renderSpecialUsersList(); 2031 | this.renderEmojisList(); 2032 | this.renderSpecialEmojisList(); 2033 | this.renderBlockedImagesList(); 2034 | } 2035 | 2036 | showBlockedWordsModal() { 2037 | const scrollView = document.querySelector('.q-scroll-view.scroll-view--show-scrollbar.liteloader'); 2038 | scrollView.innerHTML = ''; 2039 | // 添加通用样式 2040 | const style = document.createElement('style'); 2041 | style.textContent = ` 2042 | .settings-container { padding: 16px; } 2043 | .settings-section { background: var(--bg_bottom_standard); border-radius: 8px; margin-bottom: 16px; } 2044 | .section-header { padding: 16px; border-bottom: 1px solid var(--border_standard); } 2045 | .section-title { font-size: 16px; font-weight: 500; color: var(--text_primary); } 2046 | .section-desc { font-size: 12px; color: var(--text_secondary); margin-top: 4px; } 2047 | .section-content { padding: 16px; } 2048 | .settings-list { list-style: none; padding: 0; margin: 0; } 2049 | .settings-list-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; margin-bottom: 8px; background: var(--bg_medium_standard); border: 1px solid var(--border_standard); border-radius: 8px; gap: 12px; } 2050 | .settings-list-item span { flex: 1; color: var(--text_primary); word-break: break-all; white-space: pre-wrap; line-height: 1.5; font-size: 14px; } 2051 | .delete-button { height: 32px; padding: 0 16px; border-radius: 6px; font-size: 14px; cursor: pointer; background: #E54D42; color: white; border: none; } 2052 | .add-button { height: 32px; padding: 0 16px; border-radius: 6px; font-size: 14px; cursor: pointer; background: var(--brand_standard); color: white; border: none; } 2053 | .settings-input { flex: 1; height: 32px; padding: 0 12px; background: var(--bg_bottom_standard); border: 1px solid var(--border_standard); border-radius: 6px; color: var(--text_primary); font-size: 14px; } 2054 | setting-item { padding: 16px; } 2055 | setting-item[data-direction="row"] { display: flex; flex-direction: column; } 2056 | setting-text { display: block; line-height: 1.5; } 2057 | setting-text[data-type="secondary"] { margin-top: 4px; } 2058 | 2059 | /* 开关样式 */ 2060 | .switch { 2061 | position: relative; 2062 | display: inline-block; 2063 | width: 40px; 2064 | height: 20px; 2065 | } 2066 | 2067 | .switch input { 2068 | opacity: 0; 2069 | width: 0; 2070 | height: 0; 2071 | } 2072 | 2073 | .slider { 2074 | position: absolute; 2075 | cursor: pointer; 2076 | top: 0; 2077 | left: 0; 2078 | right: 0; 2079 | bottom: 0; 2080 | background-color: #ccc; 2081 | transition: .4s; 2082 | } 2083 | 2084 | .slider:before { 2085 | position: absolute; 2086 | content: ""; 2087 | height: 16px; 2088 | width: 16px; 2089 | left: 2px; 2090 | bottom: 2px; 2091 | background-color: white; 2092 | transition: .4s; 2093 | } 2094 | 2095 | input:checked + .slider { 2096 | background-color: var(--brand_standard); 2097 | } 2098 | 2099 | input:focus + .slider { 2100 | box-shadow: 0 0 1px var(--brand_standard); 2101 | } 2102 | 2103 | input:checked + .slider:before { 2104 | transform: translateX(20px); 2105 | } 2106 | 2107 | .slider.round { 2108 | border-radius: 20px; 2109 | } 2110 | 2111 | .slider.round:before { 2112 | border-radius: 50%; 2113 | } 2114 | `; 2115 | document.head.appendChild(style); 2116 | 2117 | const container = document.createElement('div'); 2118 | container.className = 'settings-container'; 2119 | 2120 | // 创建所有设置部分 2121 | const blockedWordsSection = document.createElement('setting-section'); 2122 | const specialUsersSection = document.createElement('setting-section'); 2123 | const emojiSection = document.createElement('setting-section'); 2124 | const specialEmojiSection = document.createElement('setting-section'); 2125 | const blockedImagesSection = document.createElement('setting-section'); 2126 | const imageBlockedUsersSection = document.createElement('setting-section'); 2127 | const publicMessageSection = document.createElement('setting-section'); 2128 | const atMessageSection = document.createElement('setting-section'); 2129 | const superEmojiAndInteractionSection = document.createElement('setting-section'); 2130 | const configSection = document.createElement('setting-section'); 2131 | 2132 | // 设置各个部分的标题和内容 2133 | blockedWordsSection.setAttribute('data-title', '屏蔽词管理'); 2134 | specialUsersSection.setAttribute('data-title', '特殊用户屏蔽管理'); 2135 | emojiSection.setAttribute('data-title', '表情屏蔽设置'); 2136 | specialEmojiSection.setAttribute('data-title', '特定用户表情屏蔽'); 2137 | blockedImagesSection.setAttribute('data-title', '图片屏蔽管理'); 2138 | imageBlockedUsersSection.setAttribute('data-title', '图片屏蔽用户管理'); 2139 | publicMessageSection.setAttribute('data-title', '公共消息屏蔽管理'); 2140 | atMessageSection.setAttribute('data-title', '@消息屏蔽设置'); 2141 | superEmojiAndInteractionSection.setAttribute('data-title', '其他消息屏蔽设置(更多功能可查阅renderer.js)'); 2142 | configSection.setAttribute('data-title', '配置管理'); 2143 | 2144 | // 设置各个部分的内容 2145 | blockedWordsSection.innerHTML = ` 2146 | 2147 | 2148 | 2149 |
2150 | 包含匹配屏蔽词 2151 | 添加后将自动屏蔽包含该关键词的消息(例如:屏蔽"测试111"也会屏蔽"测试111111") 2152 |
2153 |
2154 | 2155 | 2156 |
2157 |
2158 |
2159 | 2160 | 2161 |
2162 | 完全匹配屏蔽词 2163 | 添加后将只屏蔽完全匹配的消息(例如:屏蔽"测试222"不会屏蔽"测试22222222") 2164 |
2165 |
2166 | 2167 | 2168 |
2169 |
2170 |
2171 |
2172 |
2173 | `; 2174 | specialUsersSection.innerHTML = ` 2175 | 2176 | 2177 | 2178 |
2179 | 包含匹配特殊用户屏蔽(不填屏蔽则完全屏蔽) 2180 | 添加后将自动屏蔽该用户发送的包含关键词的消息 2181 |
2182 |
2183 | 2184 | 2185 | 2186 |
2187 |
2188 |
2189 | 2190 | 2191 |
2192 | 完全匹配特殊用户屏蔽 2193 | 添加后将只屏蔽该用户发送的完全匹配的消息 2194 |
2195 |
2196 | 2197 | 2198 | 2199 |
2200 |
2201 |
2202 |
2203 |
2204 | `; 2205 | emojiSection.innerHTML = ` 2206 | 2207 | 2208 | 2209 |
2210 | 完全匹配表情屏蔽 2211 | 添加后将只屏蔽单个表情的消息(例如:只屏蔽单独发送的滑稽表情) 2212 |
2213 |
2214 | 2215 | 2216 |
2217 |
2218 |
2219 | 2220 | 2221 |
2222 | 包含匹配表情屏蔽 2223 | 添加后将屏蔽所有包含该表情的消息(例如:屏蔽所有包含滑稽表情的消息) 2224 |
2225 |
2226 | 2227 | 2228 |
2229 |
2230 |
2231 |
2232 |
2233 | `; 2234 | specialEmojiSection.innerHTML = ` 2235 | 2236 | 2237 | 2238 |
2239 | 添加特定用户表情屏蔽 2240 | 为特定用户设置独立的表情屏蔽列表 2241 |
2242 |
2243 | 2244 | 2245 | 2246 |
2247 |
2248 | 2249 |
2250 |
2251 |
2252 |
2253 | `; 2254 | blockedImagesSection.innerHTML = ` 2255 | 2256 | 2257 | 2258 |
2259 | 图片特征屏蔽 2260 | 添加图片文件名特征,将自动屏蔽包含该特征的图片 2261 |
2262 |
2263 | 2264 | 2265 |
2266 |
2267 |
2268 |
2269 |
2270 | `; 2271 | imageBlockedUsersSection.innerHTML = ` 2272 | 2273 | 2274 | 2275 |
2276 | 添加图片屏蔽用户 2277 | 添加后将自动屏蔽该用户发送的所有图片 2278 |
2279 |
2280 | 2281 | 2282 |
2283 |
2284 |
2285 |
2286 |
2287 | `; 2288 | publicMessageSection.innerHTML = ` 2289 | 2290 | 2291 | 2292 |
2293 | 公共消息关键词屏蔽 2294 | 添加后将屏蔽包含该关键词的公共消息 2295 |
2296 |
2297 | 2298 | 2299 |
2300 |
2301 |
2302 |
2303 |
2304 | `; 2305 | atMessageSection.innerHTML = ` 2306 | 2307 | 2308 | 2309 |
2310 | @消息屏蔽 2311 | 控制是否屏蔽@消息 2312 |
2313 |
2314 | 2318 | 启用@消息屏蔽 2319 |
2320 |
2321 | 2322 |
2323 | 屏蔽所有@消息 2324 | 开启后将屏蔽所有包含@的消息,而不仅仅是纯@消息 2325 |
2326 |
2327 | 2331 | 屏蔽所有@消息 2332 |
2333 |
2334 |
2335 |
2336 | `; 2337 | superEmojiAndInteractionSection.innerHTML = ` 2338 | 2339 | 2340 | 2341 |
2342 | 超级表情屏蔽 2343 | 控制是否屏蔽超级表情(接龙表情/随机表情) 2344 |
2345 |
2346 | 2350 | 启用超级表情屏蔽 2351 |
2352 |
2353 | 2354 |
2355 | 互动消息屏蔽 2356 | 控制是否屏蔽互动消息(拍一拍/戳一戳等) 2357 |
2358 |
2359 | 2363 | 启用互动消息屏蔽 2364 |
2365 |
2366 |
2367 |
2368 | `; 2369 | configSection.innerHTML = ` 2370 | 2371 | 2372 | 2373 |
2374 | 导入/导出配置 2375 | 可以导出当前配置或导入之前的配置 2376 |
2377 |
2378 | 2379 | 2380 | 2381 |
2382 |
2383 |
2384 |
2385 | `; 2386 | 2387 | // 按顺序添加所有部分 2388 | container.appendChild(blockedWordsSection); 2389 | container.appendChild(specialUsersSection); 2390 | container.appendChild(emojiSection); 2391 | container.appendChild(specialEmojiSection); 2392 | container.appendChild(imageBlockedUsersSection); 2393 | container.appendChild(publicMessageSection); 2394 | container.appendChild(blockedImagesSection); 2395 | container.appendChild(atMessageSection); 2396 | container.appendChild(superEmojiAndInteractionSection); 2397 | container.appendChild(configSection); 2398 | 2399 | scrollView.appendChild(container); 2400 | 2401 | const addBlockedWordBtn = document.getElementById('addBlockedWord'); 2402 | const newBlockWordInput = document.getElementById('newBlockWord'); 2403 | if (addBlockedWordBtn && newBlockWordInput) { 2404 | addBlockedWordBtn.addEventListener('click', () => { 2405 | const word = newBlockWordInput.value.trim(); 2406 | if (!word) { 2407 | showToast('请输入要屏蔽的词', 'error'); 2408 | return; 2409 | } 2410 | if (this.blockedWordsManager.addBlockedWord(word)) { 2411 | this.renderWordsList(); 2412 | newBlockWordInput.value = ''; 2413 | showToast('添加成功', 'success'); 2414 | } else { 2415 | showToast('该屏蔽词已存在', 'error'); 2416 | } 2417 | }); 2418 | 2419 | // 添加回车键监听 2420 | newBlockWordInput.addEventListener('keypress', (e) => { 2421 | if (e.key === 'Enter') { 2422 | const word = newBlockWordInput.value.trim(); 2423 | if (!word) { 2424 | showToast('请输入要屏蔽的词', 'error'); 2425 | return; 2426 | } 2427 | if (this.blockedWordsManager.addBlockedWord(word)) { 2428 | this.renderWordsList(); 2429 | newBlockWordInput.value = ''; 2430 | showToast('添加成功', 'success'); 2431 | } else { 2432 | showToast('该屏蔽词已存在', 'error'); 2433 | } 2434 | } 2435 | }); 2436 | } 2437 | 2438 | const addExactWordBtn = document.getElementById('addExactWordBtn'); 2439 | const newExactBlockWordInput = document.getElementById('newExactBlockWord'); 2440 | if (addExactWordBtn && newExactBlockWordInput) { 2441 | addExactWordBtn.addEventListener('click', () => { 2442 | const word = newExactBlockWordInput.value.trim(); 2443 | if (!word) { 2444 | showToast('请输入要屏蔽的词', 'error'); 2445 | return; 2446 | } 2447 | if (this.blockedWordsManager.addBlockedWord(word, 'exact')) { 2448 | this.renderWordsList(); 2449 | newExactBlockWordInput.value = ''; 2450 | showToast('添加成功', 'success'); 2451 | } else { 2452 | showToast('该屏蔽词已存在', 'error'); 2453 | } 2454 | }); 2455 | 2456 | // 添加回车键监听 2457 | newExactBlockWordInput.addEventListener('keypress', (e) => { 2458 | if (e.key === 'Enter') { 2459 | const word = newExactBlockWordInput.value.trim(); 2460 | if (!word) { 2461 | showToast('请输入要屏蔽的词', 'error'); 2462 | return; 2463 | } 2464 | if (this.blockedWordsManager.addBlockedWord(word, 'exact')) { 2465 | this.renderWordsList(); 2466 | newExactBlockWordInput.value = ''; 2467 | showToast('添加成功', 'success'); 2468 | } else { 2469 | showToast('该屏蔽词已存在', 'error'); 2470 | } 2471 | } 2472 | }); 2473 | } 2474 | 2475 | const addSpecialUserBtn = document.getElementById('addSpecialUserBtn'); 2476 | const newSpecialBlockUser = document.getElementById('newSpecialBlockUser'); 2477 | const newSpecialBlockWord = document.getElementById('newSpecialBlockWord'); 2478 | if (addSpecialUserBtn && newSpecialBlockUser && newSpecialBlockWord) { 2479 | addSpecialUserBtn.addEventListener('click', () => { 2480 | const userId = newSpecialBlockUser.value.trim(); 2481 | const word = newSpecialBlockWord.value.trim(); 2482 | 2483 | if (!userId) { 2484 | showToast('请输入用户名', 'error'); 2485 | return; 2486 | } 2487 | 2488 | // 添加到用户配置 2489 | if (!this.blockedWordsManager.specialBlockedUsers[userId]) { 2490 | this.blockedWordsManager.specialBlockedUsers[userId] = []; 2491 | } 2492 | if (!this.blockedWordsManager.specialBlockedUsers[userId].includes(word)) { 2493 | this.blockedWordsManager.specialBlockedUsers[userId].push(word); 2494 | } 2495 | 2496 | // 保存配置 2497 | this.blockedWordsManager.saveAllData(); 2498 | 2499 | // 清空输入框 2500 | newSpecialBlockUser.value = ''; 2501 | newSpecialBlockWord.value = ''; 2502 | 2503 | // 刷新列表 2504 | this.renderSpecialUsersList(); 2505 | showToast('添加成功'); 2506 | }); 2507 | 2508 | // 添加回车键监听 2509 | const handleSpecialUserEnter = () => { 2510 | const userId = newSpecialBlockUser.value.trim(); 2511 | const word = newSpecialBlockWord.value.trim(); 2512 | 2513 | if (!userId) { 2514 | showToast('请输入用户名', 'error'); 2515 | return; 2516 | } 2517 | 2518 | // 添加到用户配置 2519 | if (!this.blockedWordsManager.specialBlockedUsers[userId]) { 2520 | this.blockedWordsManager.specialBlockedUsers[userId] = []; 2521 | } 2522 | if (!this.blockedWordsManager.specialBlockedUsers[userId].includes(word)) { 2523 | this.blockedWordsManager.specialBlockedUsers[userId].push(word); 2524 | } 2525 | 2526 | // 保存配置 2527 | this.blockedWordsManager.saveAllData(); 2528 | 2529 | // 清空输入框 2530 | newSpecialBlockUser.value = ''; 2531 | newSpecialBlockWord.value = ''; 2532 | 2533 | // 刷新列表 2534 | this.renderSpecialUsersList(); 2535 | showToast('添加成功'); 2536 | }; 2537 | 2538 | newSpecialBlockUser.addEventListener('keypress', (e) => { 2539 | if (e.key === 'Enter') { 2540 | handleSpecialUserEnter(); 2541 | } 2542 | }); 2543 | 2544 | newSpecialBlockWord.addEventListener('keypress', (e) => { 2545 | if (e.key === 'Enter') { 2546 | handleSpecialUserEnter(); 2547 | } 2548 | }); 2549 | } 2550 | 2551 | const addExactSpecialUserBtn = document.getElementById('addExactSpecialUserBtn'); 2552 | const newExactSpecialBlockUser = document.getElementById('newExactSpecialBlockUser'); 2553 | const newExactSpecialBlockWord = document.getElementById('newExactSpecialBlockWord'); 2554 | if (addExactSpecialUserBtn && newExactSpecialBlockUser && newExactSpecialBlockWord) { 2555 | addExactSpecialUserBtn.addEventListener('click', () => { 2556 | const userId = String(newExactSpecialBlockUser.value.trim()); 2557 | const word = newExactSpecialBlockWord.value.trim(); 2558 | 2559 | if (!userId) { 2560 | showToast('请输入用户名', 'error'); 2561 | return; 2562 | } 2563 | 2564 | // 添加到用户配置 2565 | if (!this.blockedWordsManager.exactSpecialBlockedUsers[userId]) { 2566 | this.blockedWordsManager.exactSpecialBlockedUsers[userId] = []; 2567 | } 2568 | if (!this.blockedWordsManager.exactSpecialBlockedUsers[userId].includes(word)) { 2569 | this.blockedWordsManager.exactSpecialBlockedUsers[userId].push(word); 2570 | } 2571 | 2572 | // 保存配置 2573 | this.blockedWordsManager.saveAllData(); 2574 | 2575 | // 清空输入框 2576 | newExactSpecialBlockUser.value = ''; 2577 | newExactSpecialBlockWord.value = ''; 2578 | 2579 | // 刷新列表 2580 | this.renderSpecialUsersList(); 2581 | showToast('添加成功'); 2582 | }); 2583 | 2584 | // 添加回车键监听 2585 | const handleExactSpecialUserEnter = () => { 2586 | const userId = String(newExactSpecialBlockUser.value.trim()); 2587 | const word = newExactSpecialBlockWord.value.trim(); 2588 | 2589 | if (!userId) { 2590 | showToast('请输入用户名', 'error'); 2591 | return; 2592 | } 2593 | 2594 | // 添加到用户配置 2595 | if (!this.blockedWordsManager.exactSpecialBlockedUsers[userId]) { 2596 | this.blockedWordsManager.exactSpecialBlockedUsers[userId] = []; 2597 | } 2598 | if (!this.blockedWordsManager.exactSpecialBlockedUsers[userId].includes(word)) { 2599 | this.blockedWordsManager.exactSpecialBlockedUsers[userId].push(word); 2600 | } 2601 | 2602 | // 保存配置 2603 | this.blockedWordsManager.saveAllData(); 2604 | 2605 | // 清空输入框 2606 | newExactSpecialBlockUser.value = ''; 2607 | newExactSpecialBlockWord.value = ''; 2608 | 2609 | // 刷新列表 2610 | this.renderSpecialUsersList(); 2611 | showToast('添加成功'); 2612 | }; 2613 | 2614 | newExactSpecialBlockUser.addEventListener('keypress', (e) => { 2615 | if (e.key === 'Enter') { 2616 | handleExactSpecialUserEnter(); 2617 | } 2618 | }); 2619 | 2620 | newExactSpecialBlockWord.addEventListener('keypress', (e) => { 2621 | if (e.key === 'Enter') { 2622 | handleExactSpecialUserEnter(); 2623 | } 2624 | }); 2625 | } 2626 | 2627 | const addEmojiBtn = document.getElementById('addEmojiBtn'); 2628 | const newBlockEmoji = document.getElementById('newBlockEmoji'); 2629 | 2630 | if (addEmojiBtn && newBlockEmoji) { 2631 | addEmojiBtn.addEventListener('click', () => { 2632 | const emojiId = newBlockEmoji.value.trim(); 2633 | if (this.blockedWordsManager.addBlockedEmoji(emojiId)) { 2634 | newBlockEmoji.value = ''; // 只有在成功添加后才清空输入框 2635 | this.renderEmojisList(); // 只有在成功添加后才更新列表 2636 | } 2637 | }); 2638 | 2639 | // 添加回车键监听 2640 | newBlockEmoji.addEventListener('keypress', (e) => { 2641 | if (e.key === 'Enter') { 2642 | const emojiId = newBlockEmoji.value.trim(); 2643 | if (this.blockedWordsManager.addBlockedEmoji(emojiId)) { 2644 | newBlockEmoji.value = ''; // 只有在成功添加后才清空输入框 2645 | this.renderEmojisList(); // 只有在成功添加后才更新列表 2646 | } 2647 | } 2648 | }); 2649 | } 2650 | 2651 | const addSpecialEmojiBtn = document.getElementById('addSpecialEmojiBtn'); 2652 | const specialEmojiUser = document.getElementById('specialEmojiUser'); 2653 | const specialEmojiId = document.getElementById('specialEmojiId'); 2654 | 2655 | if (addSpecialEmojiBtn && specialEmojiUser && specialEmojiId) { 2656 | const handleAddSpecialEmoji = () => { 2657 | const userId = specialEmojiUser.value.trim(); 2658 | const emojiId = specialEmojiId.value.trim(); 2659 | 2660 | if (!userId) { 2661 | showToast('请输入用户名', 'error'); 2662 | return; 2663 | } 2664 | if (!emojiId) { 2665 | showToast('请输入表情ID', 'error'); 2666 | return; 2667 | } 2668 | 2669 | // 初始化用户的表情列表(如果不存在) 2670 | if (!this.blockedWordsManager.specialBlockedUsersEmojis[userId]) { 2671 | this.blockedWordsManager.specialBlockedUsersEmojis[userId] = []; 2672 | } 2673 | 2674 | // 检查是否已存在 2675 | if (this.blockedWordsManager.specialBlockedUsersEmojis[userId].includes(emojiId)) { 2676 | showToast('该用户已存在相同的表情屏蔽', 'error'); 2677 | return; 2678 | } 2679 | 2680 | // 添加表情ID 2681 | this.blockedWordsManager.specialBlockedUsersEmojis[userId].push(emojiId); 2682 | 2683 | // 保存数据 2684 | this.blockedWordsManager.saveAllData(); 2685 | 2686 | this.renderSpecialEmojisList(); 2687 | showToast('添加成功', 'success'); 2688 | 2689 | // 清空输入框 2690 | specialEmojiUser.value = ''; 2691 | specialEmojiId.value = ''; 2692 | }; 2693 | 2694 | // 点击按钮添加 2695 | addSpecialEmojiBtn.addEventListener('click', handleAddSpecialEmoji); 2696 | 2697 | // 回车键添加 2698 | const handleKeyPress = (e) => { 2699 | if (e.key === 'Enter') { 2700 | handleAddSpecialEmoji(); 2701 | } 2702 | }; 2703 | 2704 | specialEmojiUser.addEventListener('keypress', handleKeyPress); 2705 | specialEmojiId.addEventListener('keypress', handleKeyPress); 2706 | } 2707 | 2708 | const addImageBtn = document.getElementById('addImageBtn'); 2709 | const newBlockImage = document.getElementById('newBlockImage'); 2710 | 2711 | if (addImageBtn && newBlockImage) { 2712 | const handleAddImage = () => { 2713 | const imagePattern = newBlockImage.value.trim(); 2714 | if (!imagePattern) { 2715 | showToast('请输入图片文件名特征', 'error'); 2716 | return; 2717 | } 2718 | 2719 | if (this.blockedWordsManager.addBlockedImage(imagePattern)) { 2720 | // 清空输入框 2721 | newBlockImage.value = ''; 2722 | // 刷新列表 2723 | this.renderBlockedImagesList(); 2724 | showToast('添加成功'); 2725 | } else { 2726 | showToast('该图片特征已存在', 'error'); 2727 | } 2728 | }; 2729 | 2730 | // 点击按钮添加 2731 | addImageBtn.addEventListener('click', handleAddImage); 2732 | 2733 | // 回车键添加 2734 | newBlockImage.addEventListener('keypress', (e) => { 2735 | if (e.key === 'Enter') { 2736 | handleAddImage(); 2737 | } 2738 | }); 2739 | } 2740 | 2741 | 2742 | // 导入配置事件监听 2743 | const importConfigFile = document.getElementById('importConfigFile'); 2744 | const importConfigBtn = document.getElementById('importConfigBtn'); 2745 | if (importConfigFile && importConfigBtn) { 2746 | // 移除之前可能存在的事件监听器 2747 | importConfigBtn.replaceWith(importConfigBtn.cloneNode(true)); 2748 | importConfigFile.replaceWith(importConfigFile.cloneNode(true)); 2749 | 2750 | // 重新获取新的元素 2751 | const newImportConfigBtn = document.getElementById('importConfigBtn'); 2752 | const newImportConfigFile = document.getElementById('importConfigFile'); 2753 | 2754 | // 添加新的事件监听器 2755 | newImportConfigBtn.addEventListener('click', () => { 2756 | newImportConfigFile.click(); 2757 | }); 2758 | 2759 | newImportConfigFile.addEventListener('change', (event) => { 2760 | const file = event.target.files[0]; 2761 | if (file) { 2762 | this.blockedWordsManager.importConfig(file); 2763 | newImportConfigFile.value = ''; 2764 | } else { 2765 | showToast('请选择配置文件', 'error'); 2766 | } 2767 | }); 2768 | } 2769 | 2770 | // 添加事件监听 2771 | const addImageBlockUserBtn = document.getElementById('addImageBlockUserBtn'); 2772 | const newImageBlockUser = document.getElementById('newImageBlockUser'); 2773 | if (addImageBlockUserBtn && newImageBlockUser) { 2774 | const handleAddImageBlockUser = () => { 2775 | const username = newImageBlockUser.value.trim(); 2776 | if (this.blockedWordsManager.addImageBlockedUser(username)) { 2777 | newImageBlockUser.value = ''; 2778 | this.renderImageBlockedUsersList(); 2779 | } 2780 | }; 2781 | 2782 | addImageBlockUserBtn.addEventListener('click', handleAddImageBlockUser); 2783 | newImageBlockUser.addEventListener('keypress', (e) => { 2784 | if (e.key === 'Enter') { 2785 | handleAddImageBlockUser(); 2786 | } 2787 | }); 2788 | } 2789 | 2790 | // 初始渲染列表 2791 | this.renderImageBlockedUsersList(); 2792 | 2793 | // Render the initial lists 2794 | this.renderWordsList(); 2795 | this.renderSpecialUsersList(); 2796 | this.renderEmojisList(); 2797 | this.renderSpecialEmojisList(); 2798 | this.renderBlockedImagesList(); 2799 | 2800 | // 添加公共消息设置部分 2801 | // 删除这部分代码,因为已经在前面创建和添加了 publicMessageSection 2802 | /* 2803 | const publicMessageSection = document.createElement('setting-section'); 2804 | publicMessageSection.setAttribute('data-title', '公共消息屏蔽管理'); 2805 | publicMessageSection.innerHTML = ` 2806 | 2807 | 2808 | 2809 |
2810 | 公共消息关键词屏蔽 2811 | 添加后将屏蔽包含该关键词的公共消息 2812 |
2813 |
2814 | 2815 | 2816 |
2817 |
2818 |
2819 |
2820 |
2821 | `; 2822 | 2823 | // 在适当位置插入新的设置部分 2824 | container.insertBefore(publicMessageSection, configSection); 2825 | */ 2826 | 2827 | // 渲染公共消息关键词列表 2828 | this.renderPublicMessageKeywordsList(); 2829 | 2830 | // 添加事件监听 2831 | const addPublicMessageKeywordBtn = document.getElementById('addPublicMessageKeywordBtn'); 2832 | const newPublicMessageKeyword = document.getElementById('newPublicMessageKeyword'); 2833 | if (addPublicMessageKeywordBtn && newPublicMessageKeyword) { 2834 | const handleAddKeyword = () => { 2835 | const keyword = newPublicMessageKeyword.value.trim(); 2836 | if (!keyword) { 2837 | showToast('请输入关键词', 'error'); 2838 | return; 2839 | } 2840 | if (this.blockedWordsManager.addPublicMessageKeyword(keyword)) { 2841 | newPublicMessageKeyword.value = ''; 2842 | this.renderPublicMessageKeywordsList(); 2843 | showToast('添加成功'); 2844 | } else { 2845 | showToast('该关键词已存在', 'error'); 2846 | } 2847 | }; 2848 | 2849 | addPublicMessageKeywordBtn.addEventListener('click', handleAddKeyword); 2850 | newPublicMessageKeyword.addEventListener('keypress', (e) => { 2851 | if (e.key === 'Enter') { 2852 | handleAddKeyword(); 2853 | } 2854 | }); 2855 | } 2856 | 2857 | // 添加完全匹配表情屏蔽事件监听 2858 | const addExactEmojiBtn = document.getElementById('addExactEmojiBtn'); 2859 | const newExactBlockEmoji = document.getElementById('newExactBlockEmoji'); 2860 | if (addExactEmojiBtn && newExactBlockEmoji) { 2861 | const handleAddExactEmoji = () => { 2862 | const emojiId = newExactBlockEmoji.value.trim(); 2863 | if (!emojiId) { 2864 | showToast('请输入表情ID', 'error'); 2865 | return; 2866 | } 2867 | if (this.blockedWordsManager.addBlockedEmoji(emojiId, 'exact')) { 2868 | newExactBlockEmoji.value = ''; 2869 | this.renderEmojisList(); 2870 | showToast('添加成功', 'success'); 2871 | } else { 2872 | showToast('该表情ID已存在', 'error'); 2873 | } 2874 | }; 2875 | 2876 | addExactEmojiBtn.addEventListener('click', handleAddExactEmoji); 2877 | newExactBlockEmoji.addEventListener('keypress', (e) => { 2878 | if (e.key === 'Enter') { 2879 | handleAddExactEmoji(); 2880 | } 2881 | }); 2882 | } 2883 | 2884 | // 添加包含匹配表情屏蔽事件监听 2885 | const addIncludeEmojiBtn = document.getElementById('addIncludeEmojiBtn'); 2886 | const newIncludeBlockEmoji = document.getElementById('newIncludeBlockEmoji'); 2887 | if (addIncludeEmojiBtn && newIncludeBlockEmoji) { 2888 | const handleAddIncludeEmoji = () => { 2889 | const emojiId = newIncludeBlockEmoji.value.trim(); 2890 | if (!emojiId) { 2891 | showToast('请输入表情ID', 'error'); 2892 | return; 2893 | } 2894 | if (this.blockedWordsManager.addBlockedEmoji(emojiId, 'include')) { 2895 | newIncludeBlockEmoji.value = ''; 2896 | this.renderEmojisList(); 2897 | showToast('添加成功', 'success'); 2898 | } else { 2899 | showToast('该表情ID已存在', 'error'); 2900 | } 2901 | }; 2902 | 2903 | addIncludeEmojiBtn.addEventListener('click', handleAddIncludeEmoji); 2904 | newIncludeBlockEmoji.addEventListener('keypress', (e) => { 2905 | if (e.key === 'Enter') { 2906 | handleAddIncludeEmoji(); 2907 | } 2908 | }); 2909 | } 2910 | 2911 | // 在模态框内容中添加导出按钮的事件监听 2912 | const exportConfigBtn = document.getElementById('exportConfigBtn'); 2913 | if (exportConfigBtn) { 2914 | // 移除旧的事件监听器 2915 | exportConfigBtn.replaceWith(exportConfigBtn.cloneNode(true)); 2916 | const newExportConfigBtn = document.getElementById('exportConfigBtn'); 2917 | 2918 | // 添加新的事件监听器 2919 | newExportConfigBtn.addEventListener('click', () => { 2920 | this.blockedWordsManager.exportConfig(); 2921 | }); 2922 | } 2923 | 2924 | const updateButton = document.createElement('button'); 2925 | updateButton.id = 'updateCheckBtn'; 2926 | updateButton.className = 'add-button'; 2927 | updateButton.textContent = '去github查看更新'; 2928 | updateButton.addEventListener('click', (e) => { 2929 | e.preventDefault(); 2930 | // 使用 LiteLoader 的官方 API 打开外部链接 2931 | LiteLoader.api.openExternal('https://github.com/elegantland/qqMessageBlocker/blob/main/SECURITY.md'); 2932 | }); 2933 | 2934 | // 将按钮添加到配置管理界面的按钮组中 2935 | const configButtonGroup = document.querySelector('setting-section[data-title="配置管理"] .input-group'); 2936 | if (configButtonGroup) { 2937 | configButtonGroup.appendChild(updateButton); 2938 | } 2939 | 2940 | // 在 addEventListeners 方法中添加事件监听 2941 | const atMessageBlockSwitch = document.getElementById('atMessageBlockSwitch'); 2942 | const blockAllAtSwitch = document.getElementById('blockAllAtSwitch'); 2943 | const superEmojiBlockSwitch = document.getElementById('superEmojiBlockSwitch'); 2944 | const interactionMessageBlockSwitch = document.getElementById('interactionMessageBlockSwitch'); 2945 | 2946 | if (atMessageBlockSwitch) { 2947 | atMessageBlockSwitch.addEventListener('change', (e) => { 2948 | MSG_AT_BLOCK_CONFIG.enabled = e.target.checked; 2949 | // 如果关闭@消息屏蔽,同时关闭屏蔽所有@消息 2950 | if (!e.target.checked) { 2951 | MSG_AT_BLOCK_CONFIG.blockAllAt = false; 2952 | if (blockAllAtSwitch) { 2953 | blockAllAtSwitch.checked = false; 2954 | } 2955 | } 2956 | this.blockedWordsManager.saveAllData(); 2957 | showToast(e.target.checked ? '已启用@消息屏蔽' : '已关闭@消息屏蔽'); 2958 | }); 2959 | } 2960 | 2961 | if (blockAllAtSwitch) { 2962 | blockAllAtSwitch.addEventListener('change', (e) => { 2963 | // 只有在@消息屏蔽开启的情况下才能开启屏蔽所有@消息 2964 | if (!MSG_AT_BLOCK_CONFIG.enabled) { 2965 | e.target.checked = false; 2966 | showToast('请先启用@消息屏蔽', 'error'); 2967 | return; 2968 | } 2969 | MSG_AT_BLOCK_CONFIG.blockAllAt = e.target.checked; 2970 | this.blockedWordsManager.saveAllData(); 2971 | showToast(e.target.checked ? '已启用屏蔽所有@消息' : '已关闭屏蔽所有@消息'); 2972 | }); 2973 | } 2974 | 2975 | if (superEmojiBlockSwitch) { 2976 | superEmojiBlockSwitch.addEventListener('change', (e) => { 2977 | const newState = e.target.checked; 2978 | console.log('[Message Blocker] 超级表情屏蔽开关状态改变:', { 2979 | newState, 2980 | oldConfigState: MSG_ID_BLOCK_CONFIG.enabled, 2981 | oldBlockSuperEmoji: this.blockedWordsManager.blockSuperEmoji 2982 | }); 2983 | // 同时更新两个值 2984 | MSG_ID_BLOCK_CONFIG.enabled = newState; 2985 | this.blockedWordsManager.blockSuperEmoji = newState; 2986 | // 保存配置 2987 | this.blockedWordsManager.saveAllData(); 2988 | console.log('[Message Blocker] 超级表情屏蔽配置已更新:', { 2989 | configState: MSG_ID_BLOCK_CONFIG.enabled, 2990 | blockSuperEmoji: this.blockedWordsManager.blockSuperEmoji 2991 | }); 2992 | showToast(newState ? '已启用超级表情屏蔽' : '已关闭超级表情屏蔽'); 2993 | }); 2994 | } 2995 | 2996 | if (interactionMessageBlockSwitch) { 2997 | interactionMessageBlockSwitch.addEventListener('change', (e) => { 2998 | INTERACTION_MESSAGE_CONFIG.enabled = e.target.checked; 2999 | this.blockedWordsManager.blockInteractionMessage = e.target.checked; 3000 | this.blockedWordsManager.saveAllData(); 3001 | showToast(e.target.checked ? '已启用互动消息屏蔽' : '已关闭互动消息屏蔽'); 3002 | }); 3003 | } 3004 | } 3005 | 3006 | setupContextMenu() { 3007 | document.addEventListener('mouseup', (event) => { 3008 | if (event.button === 2) { // 右键点击 3009 | this.targetEvent = event; 3010 | 3011 | // 等待QQ的原生菜单出现 3012 | setTimeout(() => { 3013 | const qContextMenu = document.querySelector('.q-context-menu'); 3014 | if (!qContextMenu) return; 3015 | 3016 | // 清除之前可能存在的菜单项 3017 | const oldItems = qContextMenu.querySelectorAll('.blocker-menu-item'); 3018 | oldItems.forEach(item => item.remove()); 3019 | 3020 | // 获取点击的元素 3021 | const target = event.target; 3022 | 3023 | // 创建分隔线 3024 | const createSeparator = () => { 3025 | const separator = document.createElement('div'); 3026 | separator.className = 'q-context-menu-separator blocker-menu-item'; 3027 | return separator; 3028 | }; 3029 | 3030 | // 创建菜单项的函数 3031 | const createMenuItem = (text, onClick) => { 3032 | const item = document.createElement('div'); 3033 | item.className = 'q-context-menu-item blocker-menu-item'; 3034 | 3035 | // 创建图标容器 3036 | const iconContainer = document.createElement('i'); 3037 | iconContainer.className = 'q-icon'; 3038 | iconContainer.innerHTML = ''; 3039 | 3040 | // 创建文本容器 3041 | const textContainer = document.createElement('span'); 3042 | textContainer.className = 'q-context-menu-item__text'; 3043 | textContainer.textContent = text; 3044 | 3045 | // 创建内容包装器 3046 | const contentWrapper = document.createElement('div'); 3047 | contentWrapper.className = 'q-context-menu-item__content'; 3048 | contentWrapper.style.display = 'flex'; 3049 | contentWrapper.style.alignItems = 'center'; 3050 | contentWrapper.style.width = '100%'; 3051 | 3052 | // 组装菜单项 3053 | contentWrapper.appendChild(iconContainer); 3054 | contentWrapper.appendChild(textContainer); 3055 | item.appendChild(contentWrapper); 3056 | 3057 | item.onclick = onClick; 3058 | return item; 3059 | }; 3060 | 3061 | // 添加分隔线和菜单项的函数 3062 | const addMenuItem = (item) => { 3063 | // 如果这是第一个自定义菜单项,先添加分隔线 3064 | if (!qContextMenu.querySelector('.blocker-menu-item')) { 3065 | qContextMenu.appendChild(createSeparator()); 3066 | } 3067 | qContextMenu.appendChild(item); 3068 | }; 3069 | 3070 | // 处理文本消息 3071 | const messageElement = target.closest('.msg-content-container'); 3072 | if (messageElement) { 3073 | // 优先获取文本元素内容 3074 | const textElement = messageElement.querySelector('.text-element .text-normal'); 3075 | if (textElement) { 3076 | const content = textElement.textContent.trim(); 3077 | if (content) { 3078 | const item = createMenuItem('添加为屏蔽词', () => { 3079 | this.blockedWordsManager.addBlockedWord(content); 3080 | this.blockedWordsManager.saveAllData(); 3081 | showToast(`已添加屏蔽词: ${content}`, 'success'); 3082 | qContextMenu.style.display = 'none'; 3083 | }); 3084 | addMenuItem(item); 3085 | } 3086 | } 3087 | } 3088 | 3089 | // 处理图片 3090 | const imgElement = target.closest('.image-content[data-role="pic"]'); 3091 | if (imgElement) { 3092 | const dataPath = imgElement.getAttribute('data-path'); 3093 | if (dataPath) { 3094 | const fileName = dataPath.split('/').pop(); 3095 | 3096 | // 获取发送图片的用户名 3097 | const messageContainer = imgElement.closest('.message-container'); 3098 | const username = messageContainer ? this.extractUsername(messageContainer) : null; 3099 | 3100 | // 添加屏蔽图片选项 3101 | const blockImageItem = createMenuItem('屏蔽此图片', () => { 3102 | this.blockedWordsManager.addBlockedImage(fileName); 3103 | this.blockedWordsManager.saveAllData(); 3104 | showToast(`已将此图片屏蔽: ${fileName}`, 'success'); 3105 | qContextMenu.style.display = 'none'; 3106 | }); 3107 | addMenuItem(blockImageItem); 3108 | 3109 | // 如果能获取到用户名,添加屏蔽该用户所有图片的选项 3110 | if (username) { 3111 | const blockUserImagesItem = createMenuItem('屏蔽此人所有图片', () => { 3112 | this.blockedWordsManager.addImageBlockedUser(username); 3113 | this.blockedWordsManager.saveAllData(); 3114 | showToast(`已屏蔽 ${username} 的所有图片`, 'success'); 3115 | qContextMenu.style.display = 'none'; 3116 | }); 3117 | addMenuItem(blockUserImagesItem); 3118 | } 3119 | } 3120 | } 3121 | 3122 | // 处理emoji表情 3123 | const emojiElement = target.closest('img.qqemoji, img.emoji, .face-element__icon'); 3124 | if (emojiElement) { 3125 | // 获取表情ID 3126 | const emojiId = emojiElement.src.match(/(\d+)\/png\/(\d+)\.png$/)?.[2] || // 新格式 3127 | emojiElement.getAttribute('data-face-index') || // data-face-index属性 3128 | emojiElement.src.match(/(\d+)/)?.[1]; // 旧格式 3129 | if (emojiId) { 3130 | // 获取消息容器,用于后续刷新 3131 | const messageContainer = emojiElement.closest('.message-container'); 3132 | 3133 | // 添加完全匹配屏蔽选项 3134 | const exactItem = createMenuItem('完全屏蔽此表情', () => { 3135 | if (this.blockedWordsManager.addBlockedEmoji(emojiId, 'exact')) { 3136 | showToast(`已添加表情完全屏蔽: ${emojiId}`, 'success'); 3137 | // 重新处理当前消息 3138 | if (messageContainer) { 3139 | this.replaceContent(messageContainer); 3140 | } 3141 | } 3142 | qContextMenu.style.display = 'none'; 3143 | }); 3144 | addMenuItem(exactItem); 3145 | 3146 | // 添加包含匹配屏蔽选项 3147 | const includeItem = createMenuItem('包含屏蔽此表情', () => { 3148 | if (this.blockedWordsManager.addBlockedEmoji(emojiId, 'include')) { 3149 | showToast(`已添加表情包含屏蔽: ${emojiId}`, 'success'); 3150 | // 重新处理当前消息 3151 | if (messageContainer) { 3152 | this.replaceContent(messageContainer); 3153 | } 3154 | } 3155 | qContextMenu.style.display = 'none'; 3156 | }); 3157 | addMenuItem(includeItem); 3158 | } 3159 | } 3160 | }, 0); 3161 | } 3162 | }); 3163 | } 3164 | setupUI() { 3165 | const checkForNavBar = setInterval(() => { 3166 | const navBar = document.querySelector('.setting-tab .nav-bar.liteloader'); 3167 | if (navBar) { 3168 | clearInterval(checkForNavBar); 3169 | const navItem = document.createElement('div'); 3170 | navItem.setAttribute('data-v-282aeb44', ''); 3171 | navItem.className = 'nav-item'; 3172 | navItem.setAttribute('bf-list-item', ''); 3173 | navItem.setAttribute('tabindex', '-1'); 3174 | navItem.setAttribute('bf-label-inner', 'true'); 3175 | navItem.setAttribute('data-slug', 'word-blocker'); 3176 | navItem.innerHTML = ` 3177 | 3178 | 3179 | 3180 | 3181 | 3182 |
屏蔽词设置
3183 | `; 3184 | navItem.addEventListener('click', () => this.showBlockedWordsModal()); 3185 | navBar.appendChild(navItem); 3186 | } 3187 | }, 1000); 3188 | 3189 | // 添加主题变量初始化 3190 | this.initThemeVariables(); 3191 | 3192 | // 添加主题变量监听 3193 | this.updateThemeVariables(); 3194 | 3195 | // 添加事件监听器 3196 | this.addEventListeners(); 3197 | } 3198 | 3199 | initThemeVariables() { 3200 | const style = document.createElement('style'); 3201 | 3202 | 3203 | style.textContent = ` 3204 | @font-face { 3205 | font-family: 'Apple Braille'; 3206 | src: url('fonts/Apple Braille.564774f87e20c96dd705.ttf') format('truetype'); 3207 | font-display: swap; /* 优化字体加载 */ 3208 | } 3209 | `; 3210 | 3211 | 3212 | document.head.appendChild(style); 3213 | 3214 | // 监听主题变化 3215 | const observer = new MutationObserver((mutations) => { 3216 | mutations.forEach((mutation) => { 3217 | if (mutation.attributeName === 'class') { 3218 | this.updateThemeVariables(); 3219 | } 3220 | }); 3221 | }); 3222 | 3223 | observer.observe(document.documentElement, { 3224 | attributes: true 3225 | }); 3226 | 3227 | // 初始化主题 3228 | this.updateThemeVariables(); 3229 | } 3230 | 3231 | updateThemeVariables() { 3232 | const root = document.documentElement; 3233 | 3234 | // 使用QQ原生的主题变量 3235 | root.style.setProperty('--bg1', 'var(--bg_bottom_standard)'); 3236 | root.style.setProperty('--bg2', 'var(--background_02)'); 3237 | root.style.setProperty('--text1', 'var(--text_primary)'); 3238 | root.style.setProperty('--border1', 'var(--border_standard)'); 3239 | root.style.setProperty('--primary', 'var(--brand_standard)'); 3240 | root.style.setProperty('--primary-hover', 'var(--brand_hover)'); 3241 | root.style.setProperty('--danger', 'var(--error_standard)'); 3242 | root.style.setProperty('--danger-hover', 'var(--error_hover)'); 3243 | } 3244 | 3245 | renderImageBlockedUsersList() { 3246 | const imageUsersList = document.getElementById('imageBlockedUsersList'); 3247 | if (!imageUsersList) return; 3248 | 3249 | // 将 USER_IMAGES 转换为数组并按插入顺序反转 3250 | const usersHtml = Object.keys(USER_IMAGES) 3251 | .reverse() 3252 | .map(username => { 3253 | return ` 3254 |
3255 |
3256 |
用户: ${username}
3257 |
屏蔽所有图片
3258 |
3259 | 3260 |
3261 | `; 3262 | }) 3263 | .join(''); 3264 | 3265 | imageUsersList.innerHTML = usersHtml || '
暂无图片屏蔽用户配置
'; 3266 | this.addToggleButton(imageUsersList); // 添加展开/收起按钮 3267 | } 3268 | 3269 | deleteImageBlockedUser(username) { 3270 | if (this.blockedWordsManager.removeImageBlockedUser(username)) { 3271 | this.renderImageBlockedUsersList(); // 重新渲染列表 3272 | showToast('删除成功'); 3273 | } 3274 | } 3275 | 3276 | // 渲染公共消息关键词列表 3277 | renderPublicMessageKeywordsList() { 3278 | const keywordsList = document.getElementById('publicMessageKeywordsList'); 3279 | if (!keywordsList) { 3280 | console.log('cant find publicMessageKeywordsList'); 3281 | return; 3282 | } 3283 | 3284 | const keywordsHtml = Array.from(this.blockedWordsManager.publicMessageKeywords) 3285 | .reverse() 3286 | .map(keyword => { 3287 | const encodedKeyword = keyword.replace(/'/g, '\\\'').replace(/"/g, '\\"'); 3288 | return ` 3289 |
3290 |
3291 |
关键词: ${keyword}
3292 |
3293 | 3294 |
3295 | `; 3296 | }) 3297 | .join(''); 3298 | 3299 | keywordsList.innerHTML = keywordsHtml || '
暂无公共消息关键词配置
'; 3300 | } 3301 | 3302 | // 删除公共消息关键词 3303 | deletePublicMessageKeyword(keyword) { 3304 | if (this.blockedWordsManager.removePublicMessageKeyword(keyword)) { 3305 | this.renderPublicMessageKeywordsList(); 3306 | showToast('删除成功'); 3307 | } 3308 | } 3309 | 3310 | // 通用的展开/收起功能 3311 | addToggleButton(listElement, maxItems = 5) { 3312 | // 移除所有已有的展开按钮 3313 | const existingButtons = listElement.parentNode.querySelectorAll('.add-button[data-toggle="expand"]'); 3314 | existingButtons.forEach(button => button.remove()); 3315 | 3316 | const items = listElement.querySelectorAll('.settings-list-item'); 3317 | if (items.length > maxItems) { 3318 | for (let i = maxItems; i < items.length; i++) { 3319 | items[i].style.display = 'none'; // 隐藏超出部分 3320 | } 3321 | 3322 | // 添加展开/收起按钮 3323 | const toggleButton = document.createElement('button'); 3324 | toggleButton.className = 'add-button'; 3325 | toggleButton.setAttribute('data-toggle', 'expand'); // 添加标识 3326 | toggleButton.textContent = '展开更多'; 3327 | toggleButton.style.marginTop = '8px'; 3328 | toggleButton.addEventListener('click', () => { 3329 | const isExpanded = toggleButton.textContent === '展开更多'; 3330 | for (let i = maxItems; i < items.length; i++) { 3331 | items[i].style.display = isExpanded ? 'flex' : 'none'; // 切换显示状态 3332 | } 3333 | toggleButton.textContent = isExpanded ? '收起' : '展开更多'; 3334 | }); 3335 | 3336 | listElement.parentNode.insertBefore(toggleButton, listElement.nextSibling); 3337 | } 3338 | } 3339 | } 3340 | let messageBlocker = null; 3341 | 3342 | // 初始化函数 3343 | function initializeAll() { 3344 | console.log('MessageBlocker 2.1.1 loaded'); 3345 | messageBlocker = new MessageBlocker(); 3346 | window.messageBlocker = messageBlocker; 3347 | } 3348 | 3349 | // 在页面加载完成后初始化 3350 | if (document.readyState === 'loading') { 3351 | document.addEventListener('DOMContentLoaded', initializeAll); 3352 | } else { 3353 | initializeAll(); 3354 | } 3355 | })(); 3356 | 3357 | class ListRenderer { 3358 | constructor(options) { 3359 | this.listElement = options.listElement; // 列表容器 3360 | this.dataSource = options.dataSource; // 数据源 3361 | this.itemTemplate = options.itemTemplate; // 列表项模板 3362 | this.maxItems = options.maxItems || 5; // 默认显示的最大项数 3363 | this.onDelete = options.onDelete; // 删除回调 3364 | } 3365 | render() { 3366 | if (!this.listElement) return; 3367 | 3368 | const itemsHtml = this.dataSource 3369 | .map((item, index) => this.itemTemplate(item, index)) 3370 | .join(''); 3371 | 3372 | this.listElement.innerHTML = itemsHtml || '
暂无配置
'; 3373 | this.addToggleButton(); 3374 | } 3375 | 3376 | // 添加展开/收起按钮 3377 | addToggleButton() { 3378 | const existingButtons = this.listElement.parentNode.querySelectorAll('.add-button[data-toggle="expand"]'); 3379 | existingButtons.forEach(button => button.remove()); 3380 | 3381 | const items = this.listElement.querySelectorAll('.settings-list-item'); 3382 | if (items.length > this.maxItems) { 3383 | for (let i = this.maxItems; i < items.length; i++) { 3384 | items[i].style.display = 'none'; 3385 | } 3386 | 3387 | const toggleButton = document.createElement('button'); 3388 | toggleButton.className = 'add-button'; 3389 | toggleButton.setAttribute('data-toggle', 'expand'); 3390 | toggleButton.textContent = '展开更多'; 3391 | toggleButton.style.marginTop = '8px'; 3392 | toggleButton.addEventListener('click', () => { 3393 | const isExpanded = toggleButton.textContent === '展开更多'; 3394 | for (let i = this.maxItems; i < items.length; i++) { 3395 | items[i].style.display = isExpanded ? 'flex' : 'none'; 3396 | } 3397 | toggleButton.textContent = isExpanded ? '收起' : '展开更多'; 3398 | }); 3399 | 3400 | this.listElement.parentNode.insertBefore(toggleButton, this.listElement.nextSibling); 3401 | } 3402 | } 3403 | } 3404 | --------------------------------------------------------------------------------