群聊敏感词检测插件,支持四种可叠加使用的检测方式 + 群级独立配置。
四种检测方式(均可独立开关)
| 方式 | 说明 |
|---|---|
| 本地词库 | 基于 Trie 树的关键词匹配,支持忽略大小写、模糊匹配(识别"敏 感*词"这类拆字绕过写法),以及零宽字符/全角字符归一化 |
| 外部接口 | 默认内置适配 uapis.cn 的敏感词检测接口,开箱即用;也可切换为通用模式适配其他第三方文本审核 API |
| AI 语义检测 | 调用 AstrBot 已配置好的 LLM 提供商,按可自定义的审核 Prompt 对消息做语义级判断,作为关键词匹配的补充 |
| 图片检测 | 用支持视觉的 LLM 直接识图:判断图片内容本身是否违规,同时把图片里的文字转写出来,交给上面三种文字检测方式再判断一次(专门防止"把敏感词写成图片发"这种规避手段) |
文字部分按 本地词库 → 外部接口 → AI 语义检测 的顺序依次尝试,命中其中任意一种即视为违规;图片部分会先判断图片内容本身,再对转写出的文字复用同一套文字检测顺序。任意一处命中即视为违规,不会重复触发处理。
命中后的处理动作(均可独立开关)
- 撤回消息(目前仅 aiocqhttp 平台实现真正撤回,其他平台会自动跳过撤回但不影响警告/通知)
- 群内警告(@违规者 + 自定义文案)
- 转发通知到指定会话(例如管理员私聊或专门的管理群)
- 自动禁言用户(目前仅 aiocqhttp/OneBot 平台支持真正禁言,需要机器人具备群管理员权限;可配置开关和阶梯时长)
群级独立配置
以上"启用本地/接口/AI/图片检测"和"撤回/警告/通知/禁言"这 9 个开关,都支持在全局默认值基础上为单个群单独覆盖,互不影响。每个群还可以维护一份只在本群生效的专属词库。这些群级设置既可以用群内指令管理,也可以直接在 WebUI 插件配置页里以卡片列表的形式逐群查看和编辑——两者操作的是同一份数据。
访问控制(白名单 / 黑名单)
跟上面"分群配置"是两套完全独立的机制:分群配置决定"这个群该怎么检测",访问控制决定"这个群该不该被处理"。支持白名单和黑名单独立开关,可以只开一个,也可以同时开:
- 都关闭(默认):所有群正常处理
- 只开黑名单:除了黑名单里的群,其余都正常处理
- 只开白名单:只有白名单里的群会被处理,这是纯粹的"仅允许名单"模式
- 都开:白名单优先——只要在白名单里就一定处理,不受黑名单影响;不在白名单里的群一律不处理(不管黑名单怎么设)
用户白名单
用户白名单和群白名单/黑名单是独立机制:群白名单/黑名单决定“这个群该不该被处理”,用户白名单决定“这个用户的发言要不要审查”。启用后,只要发送者用户 ID 命中用户白名单,该用户在所有群里的发言都会完全跳过审查,不触发本地词库、外部接口、AI、图片/转发检测,也不会撤回、警告、通知、禁言或 stop_event。
用户 ID 可通过 /sid 指令获取(返回的 UID 字段)。
批量审核(降低 AI 调用成本,默认关闭)
AI 语义检测可以开启"攒批"模式:不再每条消息单独调用一次模型,而是攒够设定的条数后,一次模型调用同时审核这一批消息,摊薄每次调用里 Prompt 本身的固定开销。代价是:攒批中的消息不再能被实时拦截(无法在命中时阻止消息被其他插件/正常聊天回复处理),只能在批量结果出来后做事后撤回/警告/通知。详见下方"批量审核"专门小节。
astrbot_plugin_sensitivefilter/
metadata.yaml 插件元数据
_conf_schema.json 插件配置 Schema(WebUI 可视化配置,含分群配置卡片)
main.py 插件入口:事件监听、管理指令、命中后的处理动作
word_matcher.py 本地词库匹配(Trie 树)
api_checkers.py 外部接口检测(uapis.cn 适配 + 通用模式)
llm_checker.py AI 语义检测(调用 LLM Provider)
image_checker.py 图片检测(调用支持视觉的 LLM Provider 直接识图)
utils.py 跨模块共享的小工具函数
tests/ 开发期回归测试,不影响插件运行,详见 tests/README.md
main.py 只负责编排(决定先用哪种方式检测、命中后做什么),具体的检测逻辑都下沉到 api_checkers.py / llm_checker.py / image_checker.py / word_matcher.py 四个独立模块里,互不依赖,方便单独测试和替换。群级覆盖配置不再使用独立的 JSON 文件存储,而是直接落在插件配置的 group_overrides 字段里(详见下方"分群配置")。
将本插件文件夹放入 AstrBot/data/plugins/astrbot_plugin_sensitivefilter,在 WebUI 插件管理页重载即可。
出于安全考虑,本插件不预置任何真实的违禁/敏感词,默认词库只有一个示例词 测试敏感词,纯粹用来验证插件能不能正常工作。需要拦截的词请管理员自己通过指令或 WebUI 填进去。
以下指令默认仅管理员可用:
/敏感词 帮助 查看指令列表
/敏感词 添加 <词> 全局词库新增一个词
/敏感词 删除 <词> 全局词库删除一个词
/敏感词 列表 查看全局词库(最多显示前 50 个)
/敏感词 用户白名单 开启|关闭
/敏感词 用户白名单 添加 <用户ID> 该用户发言自动跳过审查
/敏感词 用户白名单 删除 <用户ID> 取消该用户跳过审查
/敏感词 用户白名单 列表 查看用户白名单
/敏感词 本群添加 <词> 仅本群额外生效的专属词
/敏感词 本群删除 <词> 删除本群专属词
/敏感词 设置 <项> <on/off/默认> 为本群单独覆盖某个开关
/敏感词 状态 查看本群当前每一项的生效值及是否被覆盖(含批量队列等待情况)
/敏感词 批量发送 立即发送待审队列;完成后返回审核总数和命中数
/敏感词 白名单 开启|关闭|添加本群|删除本群|列表
/敏感词 黑名单 开启|关闭|添加本群|删除本群|列表
/敏感词 设置 支持的 <项>:总开关、本地、接口、ai、图片、撤回、警告、通知、禁言。
第二个参数填 on/off 表示开启或关闭,填 默认 表示取消本群覆盖、跟随全局配置。
这些指令操作的数据和下方"分群配置"/"访问控制" WebUI 卡片是同一份数据,两种方式可以混用:比如先在群里用指令快速开关一个选项,之后再去 WebUI 里统一查看/批量调整所有群的配置。
插件配置页最上方的"访问控制"区块(对应 whitelist_*/blacklist_* 配置项),决定"这个会话该不该被处理",跟下面的"分群配置"(决定"该怎么处理")是两套完全独立的机制:
- 白名单:开启后,只有白名单 umo 列表里的会话会被检测,其余一律不响应
- 黑名单:开启后,黑名单 umo 列表里的会话会被完全跳过
- 两者都开启时,白名单优先——命中白名单就一定处理,不再看黑名单
群里发送 /敏感词 白名单 添加本群 / /敏感词 黑名单 添加本群 可以快速把当前群加入对应名单。注意"添加本群"只操作列表内容本身,不会自动打开名单的总开关,需要额外用 /敏感词 白名单 开启(或 WebUI 里的开关)才会真正生效;也可以直接在 WebUI 里维护 umo 列表。
插件配置页有一个"分群配置"区块(对应 _conf_schema.json 里的 group_overrides),以卡片列表的形式管理每个群的独立设置,点击"+ 添加条目"即可新增一个群:
- umo(unified_msg_origin):填写该会话的
unified_msg_origin,格式如aiocqhttp:GroupMessage:123456;在群里发送/敏感词 状态可以直接看到当前群的 umo,复制粘贴进来即可 - 插件总开关 / 本地词库检测 / 外部接口检测 / AI 语义检测 / 图片检测 / 命中后撤回 / 命中后警告 / 命中后通知 / 命中后自动禁言:每项都是「跟随全局 / 开启 / 关闭」三选一,默认"跟随全局"表示该群没有单独设置,使用上方全局默认值
- 本群专属敏感词:只在这个群生效的额外词库,会与全局词库叠加检测
未在这个列表里出现的群,所有设置都按全局默认值执行。如果一个群的所有选项都改回"跟随全局"并且专属词库也清空了,下次通过指令修改时插件会自动把这条多余的记录清理掉,避免列表越攒越长。
分群配置里没有"是否处理这个群"的选项,那是上面"访问控制"的职责。两者分开是为了避免"清空分群配置的自定义设置"和"把这个群踢出白名单"这两件不相关的事互相影响。
除了"分群配置"(template_list,单独说明见上),全局默认配置通过 _conf_schema.json 在 WebUI 中可视化编辑,分成七个分组卡片:
- 访问控制:群白名单/黑名单开关及对应的 umo 列表
- 用户白名单:独立维护可跳过审查的用户 ID 列表
- 基础设置:插件总开关、本地词库检测开关、全局词库、大小写/模糊匹配、命中后是否阻止事件继续传播
- 处理动作默认值:撤回/警告/群内警告文案模板/通知/通知接收会话列表/管理员私聊提醒模板,以及自动禁言开关、三档阶梯禁言时长和每日违规次数重置时间
- 外部接口检测:接口类型、地址、密钥、请求方式、请求头、字段路径、超时时间(滑块)
- AI 语义检测:开关、审核使用的模型提供商(点击按钮从已配置的 Provider 中选择,无需手填 ID)、审核 Prompt 模板,以及批量审核相关的开关/批量大小/兜底超时/批量 Prompt(详见下方"批量审核"小节)
- 图片检测:开关、图片审核使用的模型提供商(必须支持视觉)、图片审核 Prompt 模板
群内警告文案模板默认如下,可在 WebUI 自行编辑:
检测到敏感词{recall_status},已禁言处理。
检测到的敏感词:{forbidden_words}
违规次数:第{violation_count}次
群内警告模板可用变量:{forbidden_words}、{original_text}、{masked_text}、{violation_count}、{recall_status}。
管理员私聊/管理群提醒模板默认如下,也可在 WebUI 自行编辑:
🚨 敏感词警报
群聊:{group_id}
用户:{user_name} ({user_id})
违规次数:第{violation_count}次
敏感词:{forbidden_words}
原文:{original_text}
处理:禁言{ban_duration}秒{recall_status}
时间:{timestamp}
管理员提醒模板可用变量:{group_id}、{user_name}、{user_id}、{forbidden_words}、{original_text}、{violation_count}、{ban_duration}、{timestamp}、{recall_status}。另外保留 {masked_text} 作为脱敏文本变量。旧版 {sender}、{word}、{source} 变量仍兼容。
AI 审核 Prompt 模板必须包含 {text} 占位符(用于插入待审核的消息原文);图片审核 Prompt 模板不需要 {text} 占位符(图片本身通过 image_urls 参数单独传给模型,不嵌入 Prompt 文本里)。
自动禁言在“处理动作默认值”分组中配置,并可通过 /敏感词 设置 禁言 on/off/默认 为单个群单独覆盖开关。当前只有 aiocqhttp/OneBot 平台会实际调用 set_group_ban,其他平台会记录日志并跳过禁言,不影响撤回、警告和通知。
启用后,插件会按 umo + 用户 ID 在内存中记录当前统计周期内的违规次数,并按阶梯时长执行禁言:
- 首次违规禁言时长(秒):同一统计周期内第一次触发敏感词的禁言时长,默认 60 秒
- 第二次违规禁言时长(秒):同一统计周期内第二次触发敏感词的禁言时长,默认 300 秒
- 第三次违规禁言时长(秒):同一统计周期内第三次及后续触发敏感词的禁言时长,默认 86400 秒(24 小时)
- 违规次数重置时间(小时):每天几点开始新的统计周期,24 小时制,默认 0 点;例如填 4 表示每天凌晨 4 点重置
以上时长都可在 WebUI 中由用户设置,填 0 表示对应档位只计数、不禁言。违规计数只保存在内存里,插件重载或机器人重启后会重新开始统计。
这些分组对应
_conf_schema.json里的嵌套 object(access_control/user_access_control/basic/actions/api_detection/llm_detection/image_detection),插件代码内部通过统一的_cfg(key)/_set_cfg(key, value)方法读写,不需要在业务逻辑里关心具体的分组路径。
插件内置了两种外部接口模式(由配置项 api_provider 切换):
专门适配 uapis.cn 的「敏感词检测(快速)」接口,开箱即用:
api_url默认已经填好为https://uapis.cn/api/v1/text/profanitycheck,一般不需要改api_key选填:不填走访客额度,填了走账号额度,额度更高更稳定,会通过Authorization: Bearer <api_key>携带。可以在 uapis.cn 控制台 创建密钥- 命中判断逻辑:接口返回
status == "forbidden"时视为命中,命中的具体词语来自响应里的forbidden_words字段,会显示在群内警告文案的"来源"里 - 该接口支持简繁体中文混合检测,词库由 uapis.cn 维护更新,作为本地词库的补充比较合适(本地词库命中速度更快、可自定义;这个接口词库更全、免维护)
只需要把 api_enabled 打开、确认 api_provider 为 uapis_profanitycheck,即可立即生效,不需要额外配置字段路径。
关于额度和费用:uapis.cn 按积分计费,这个接口调用一次消耗 1 积分。不填 api_key 走访客额度,每月 1500 积分,相当于每月最多调用 1500 次;填了走账号额度,每月 3500 积分,最多调用 3500 次。也就是说群消息一多,这个额度很容易就用完,用完之后这条检测路径就会失效(其他检测方式不受影响)。用量大的话建议留意 uapis.cn 控制台的用量。
如果想接入其他任意第三方审核接口,把 api_provider 改成 generic,再配置:
插件会把消息文本放进请求体的一个字段中(字段名可配置,默认 text)发给配置的 api_url,然后从返回的 JSON 中按"点路径"取出一个布尔字段判断是否命中(默认路径 hit),以及一个可选的文本字段作为命中原因(默认路径 reason)。
例如某接口返回 {"result": {"is_violation": true}, "msg": "广告"},则可以把 api_hit_path 配置为 result.is_violation,api_reason_path 配置为 msg。
外部接口检测不支持批量合并发送。 uapis.cn 和通用模式的协议都是"一次只查一段文字、返回一个结果",没有"一次查多条、分别告诉我每条是否命中"的设计,每条消息仍然会逐条触发一次请求。如果觉得调用太频繁/太贵,请用下面"批量审核"小节里的 AI 语义检测批量功能。
AI 语义检测默认逐条即时调用:每条消息单独发一次模型,每次都要带上完整的审核 Prompt,哪怕消息本身只有几个字,固定开销也跑不掉。群消息一多,调用次数和费用涨得很快。开启批量审核后会改成攒够一批消息再一次性发给模型,一次调用顺带审核多条,摊薄这部分固定开销。
代价也很直接:进了批量队列的消息,那次事件处理已经正常结束了(其他插件该跑的都跑了,机器人如果会正常回复可能也回复了),等几秒到几十秒批量结果才出来,所以没法再用 event.stop_event() 拦下来,只能事后撤回/警告/通知。本地词库和外部接口检测不受影响,照样逐条即时检测、即时拦截;图片转写出的文字也始终走即时检测,不进队列,因为它要立刻参与"这张图片算不算违规"的判断。
要求第一时间拦截的群不建议开;更在意调用成本、能接受几秒到几十秒延迟的可以开。
| 配置项 | 说明 |
|---|---|
llm_batch_enabled |
批量审核总开关,默认关闭 |
llm_batch_size |
攒够多少条消息立即触发一次批量调用(默认 10) |
llm_batch_max_wait_minutes |
兜底等待时间(默认 30 分钟):没攒够 llm_batch_size 也没关系,只要队列里最早一条消息等够这个时间,就强制把当前攒到的消息发出去,避免冷门群消息一直攒不满 |
llm_batch_prompt |
批量审核专用 Prompt,需要 {messages} 占位符(替换成带序号的消息列表),跟单条审核用的 llm_prompt 是两套独立模板,格式不同不能混用;模型要返回 {"results": [{"index": 0, "violate": true/false, "reason": "..."}]} 这种数组形式的结果 |
插件关闭/重载前会把队列里还没发出去的消息补做一次检测;但如果等待期间该群已被访问控制排除,或已关闭插件/AI 审核,排队消息会被直接丢弃,不会再送审或处罚。异常崩溃时,队列里的消息也会丢失。
群里发 /敏感词 批量发送 可以不等凑满/超时立即发出去检测。命令会等待本批模型审核完成后回复“共审核 N 条,命中 M 条”;若当前配置跳过、未找到 Provider 或调用失败,也会直接说明原因。/敏感词 状态 会显示当前队列里攒了多少条。
图片检测不接入任何传统 OCR 库或第三方图片审核云服务(这类服务通常需要单独开通账号、走 AK/SK 签名,接入成本远高于 uapis.cn 那种简单接口),而是直接复用 AstrBot 已经配置好的、支持视觉输入的 LLM Provider:
- 用
Comp.Image.convert_to_file_path()(AstrBot 消息组件自带的方法)把消息里的图片统一转换成本地路径(不管原始格式是 URL、本地文件还是 base64,都会被自动处理好) - 调用配置好的视觉 Provider,一次模型调用同时完成两件事:判断图片本身是否违规、把图片里能看到的文字原样转写出来
- 如果图片内容本身被判定违规,直接按"AI 图片审核"为来源处理
- 否则,如果转写出了文字,会把这段文字交给
_check_text()——也就是和普通文字消息完全一样的检测流程(本地词库 → 外部接口 → AI 语义检测)——专门用来抓"把敏感词做成图片发"这种规避手段
image_provider_id 必须显式选一个支持视觉的 Provider,留空就完全不执行图片检测,不会像 AI 语义检测那样退而求其次用"当前会话默认 Provider"——不是所有模型都能读图,选错了不仅检测不到东西,调用还会直接报错。
如果一条消息里有多张图片,会按顺序逐张检测,命中任意一张即停止,不会继续检测后面的图片。QQ 合并转发内成功解析到的图片也使用同一套视觉审核和 OCR 流程;它们不进入 AI 文本批量审核队列,而是即时执行图片审核。
插件的检测逻辑只用了 AstrBot 平台无关的统一事件接口(message_str、Comp.Image、get_group_id()、unified_msg_origin、get_sender_id/name()),不依赖任何平台专属 API。
- aiocqhttp、qq_official、telegram、lark(飞书)、dingtalk(钉钉)、discord 这 6 个平台的适配器在群消息场景下都正确设置了
group_id,检测部分理论上能正常工作。 - 撤回只有 aiocqhttp 支持:AstrBot 框架本身只在 aiocqhttp 适配器里暴露了撤回消息的 API,其他平台命中后只能警告/通知,撤回不了。
- 撤回范围:自动撤回仅支持
aiocqhttp;其他平台命中后仍会警告/通知。处理对象始终是当前发送消息或转发卡的用户。 - QQ 合并转发:仅
aiocqhttp支持,依赖协议端实现get_forward_msg。可审核节点文本和图片,但文件、语音、视频等媒体暂不展开;图片还需要协议端提供视觉 Provider 可读取的url或file。 - 其他平台的转发/引用:飞书引用内容,以及 Slack、钉钉、企业微信未直接携带在事件中的历史内容,当前不保证能够取得或审核。
- 批量审核与调试:批量队列只保存在内存,异常退出会丢失未发送内容。
qq_forward_debug会把转发节点和解析文本写入日志,可能包含敏感内容,应仅临时开启。
欢迎提交 Issue 和 Pull Request。提交前请同步最新 main 分支,并完成以下检查:
ruff check .
ruff format .
python tests/run_all.py请在 PR 描述中说明改动的行为影响;涉及配置项、消息处理或平台适配时,请同步补充相应测试和文档。