开发背景
开发这个插件的契机是这样的。在一个大群里,内鬼问了chatGPT关于“如何赚钱”的问题时,机器人的回复触发了QQ的莫名其妙的不知道什么玩意的审查机制。1分钟不到的时间群就爆炸了,机器人被口球了一天。具体原因不明,大概是涉诈吧。
对于这样一个大群来说,即使新建群,损失的用户也是不可挽回的,群每炸一次,失去的部分群友就找不回来了。
所以,对于QQ或者说任何一个有审核机制的平台来说,都必须杜绝这种情况的发生,不论哪个插件回复的内容,都必须经过过滤!不然造成的损失可能是无法接受的。
可爱的梦梦和我说,“你可以写个10行插件,叫global-censor”,我想,是这个道理,然后就开始研究。
遇到的问题
要对所有发送的消息进行处理,那最好的办法就是在这些消息被发送之前,对他们进行处理。这样的话,只要监听before-send
事件就可以了。
import { Context, Schema} from 'koishi'
import {} from '@koishijs/censor/lib'
export const using = ["censor"]
export const name = 'global-censor'
export interface Config {}
export const Config: Schema<Config> = Schema.object({})
export function apply(ctx: Context) {
ctx.on("before-send",async (session)=>{
/// DO SOMETHING
},true)
}
我尝试了以下方案:
方案1
直接使用censor服务的transfer转换所有的消息元素——失败!
session.elements = await ctx.censor.transform(session.elements,session)
方案2
套一层上<censor></consor>
然后让解析器自己去做处理——失败!
session.content = "<censor>"+session.content+"</censor>"
失败原因
失败的原因有两个,这里只说一个,剩下的会在其他困难那里描述的。
这是由于,此时的elements不一定就是text类型的,而如果是例如execute类型的话,现在转化是没有意义的。
例如:
<censor><execute>fuck</execute></consor>
当然这个例子比较夸张,不过也不是不可能吧()。就是fuck本身是可以execute的,而且不会返回任何敏感词,但现在这个词被过滤了,下一步就变成了<execute>****</execute>
所以,实际上不会execute任何东西。
再例如:
<censor><execute>foo</execute></consor>
只过滤了文本foo
而实际上foo指令执行出来的东西(例如“fuck”)不会被过滤,相当于过滤了个寂寞。
其他困难
暴力的text-censor
。
const filter = new mint_filter_1.default(words, { transform: 'capital' });
在作过滤之前,直接全变大写了,做完也不还原回去……导致实际上,会出现下面的情况。
<censor><execute>foo</execute></consor>
过滤后变成<execute>FOO</execute>
但实际FOO是不能execute的,不会返回任何结果。
总结
未过滤最后生成的文本
总的来看,监听这个事件时,最终的文本并未生成,不论如何过滤,接下来都会进行其他执行,导致过滤不尽如人意。
我认为解决这个问题有两个方案,
- 调整解析censor时的策略,让其后置,解析完censor内的所有消息元素再
- 添加一个新的事件
befor-final-send
,这个事件中的所有消息元素都不再解析,只当做字符串
未完善的censor生态
这个没什么好说的,如果上面的问题解决了,我去对着text-censor改改也整一个出来,也没什么很大问题。或者去text-censor提pr什么的,都不是关键问题。