mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-20 13:47:15 +08:00
plugin: add banwords plugin
This commit is contained in:
1
plugins/banwords/.gitignore
vendored
Normal file
1
plugins/banwords/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
banwords.txt
|
||||||
9
plugins/banwords/README.md
Normal file
9
plugins/banwords/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
### 说明
|
||||||
|
简易的敏感词插件,暂不支持分词,请自行导入词库到插件文件夹中的`banwords.txt`,每行一个词,一个参考词库是[1](https://github.com/cjh0613/tencent-sensitive-words/blob/main/sensitive_words_lines.txt)。
|
||||||
|
|
||||||
|
`config.json`中能够填写默认的处理行为,目前行为有:
|
||||||
|
- `ignore` : 无视这条消息。
|
||||||
|
- `replace` : 将消息中的敏感词替换成"*",并回复违规。
|
||||||
|
|
||||||
|
### 致谢
|
||||||
|
搜索功能实现来自https://github.com/toolgood/ToolGood.Words
|
||||||
250
plugins/banwords/WordsSearch.py
Normal file
250
plugins/banwords/WordsSearch.py
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# ToolGood.Words.WordsSearch.py
|
||||||
|
# 2020, Lin Zhijun, https://github.com/toolgood/ToolGood.Words
|
||||||
|
# Licensed under the Apache License 2.0
|
||||||
|
# 更新日志
|
||||||
|
# 2020.04.06 第一次提交
|
||||||
|
# 2020.05.16 修改,支持大于0xffff的字符
|
||||||
|
|
||||||
|
__all__ = ['WordsSearch']
|
||||||
|
__author__ = 'Lin Zhijun'
|
||||||
|
__date__ = '2020.05.16'
|
||||||
|
|
||||||
|
class TrieNode():
|
||||||
|
def __init__(self):
|
||||||
|
self.Index = 0
|
||||||
|
self.Index = 0
|
||||||
|
self.Layer = 0
|
||||||
|
self.End = False
|
||||||
|
self.Char = ''
|
||||||
|
self.Results = []
|
||||||
|
self.m_values = {}
|
||||||
|
self.Failure = None
|
||||||
|
self.Parent = None
|
||||||
|
|
||||||
|
def Add(self,c):
|
||||||
|
if c in self.m_values :
|
||||||
|
return self.m_values[c]
|
||||||
|
node = TrieNode()
|
||||||
|
node.Parent = self
|
||||||
|
node.Char = c
|
||||||
|
self.m_values[c] = node
|
||||||
|
return node
|
||||||
|
|
||||||
|
def SetResults(self,index):
|
||||||
|
if (self.End == False):
|
||||||
|
self.End = True
|
||||||
|
self.Results.append(index)
|
||||||
|
|
||||||
|
class TrieNode2():
|
||||||
|
def __init__(self):
|
||||||
|
self.End = False
|
||||||
|
self.Results = []
|
||||||
|
self.m_values = {}
|
||||||
|
self.minflag = 0xffff
|
||||||
|
self.maxflag = 0
|
||||||
|
|
||||||
|
def Add(self,c,node3):
|
||||||
|
if (self.minflag > c):
|
||||||
|
self.minflag = c
|
||||||
|
if (self.maxflag < c):
|
||||||
|
self.maxflag = c
|
||||||
|
self.m_values[c] = node3
|
||||||
|
|
||||||
|
def SetResults(self,index):
|
||||||
|
if (self.End == False) :
|
||||||
|
self.End = True
|
||||||
|
if (index in self.Results )==False :
|
||||||
|
self.Results.append(index)
|
||||||
|
|
||||||
|
def HasKey(self,c):
|
||||||
|
return c in self.m_values
|
||||||
|
|
||||||
|
|
||||||
|
def TryGetValue(self,c):
|
||||||
|
if (self.minflag <= c and self.maxflag >= c):
|
||||||
|
if c in self.m_values:
|
||||||
|
return self.m_values[c]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class WordsSearch():
|
||||||
|
def __init__(self):
|
||||||
|
self._first = {}
|
||||||
|
self._keywords = []
|
||||||
|
self._indexs=[]
|
||||||
|
|
||||||
|
def SetKeywords(self,keywords):
|
||||||
|
self._keywords = keywords
|
||||||
|
self._indexs=[]
|
||||||
|
for i in range(len(keywords)):
|
||||||
|
self._indexs.append(i)
|
||||||
|
|
||||||
|
root = TrieNode()
|
||||||
|
allNodeLayer={}
|
||||||
|
|
||||||
|
for i in range(len(self._keywords)): # for (i = 0; i < _keywords.length; i++)
|
||||||
|
p = self._keywords[i]
|
||||||
|
nd = root
|
||||||
|
for j in range(len(p)): # for (j = 0; j < p.length; j++)
|
||||||
|
nd = nd.Add(ord(p[j]))
|
||||||
|
if (nd.Layer == 0):
|
||||||
|
nd.Layer = j + 1
|
||||||
|
if nd.Layer in allNodeLayer:
|
||||||
|
allNodeLayer[nd.Layer].append(nd)
|
||||||
|
else:
|
||||||
|
allNodeLayer[nd.Layer]=[]
|
||||||
|
allNodeLayer[nd.Layer].append(nd)
|
||||||
|
nd.SetResults(i)
|
||||||
|
|
||||||
|
|
||||||
|
allNode = []
|
||||||
|
allNode.append(root)
|
||||||
|
for key in allNodeLayer.keys():
|
||||||
|
for nd in allNodeLayer[key]:
|
||||||
|
allNode.append(nd)
|
||||||
|
allNodeLayer=None
|
||||||
|
|
||||||
|
for i in range(len(allNode)): # for (i = 0; i < allNode.length; i++)
|
||||||
|
if i==0 :
|
||||||
|
continue
|
||||||
|
nd=allNode[i]
|
||||||
|
nd.Index = i
|
||||||
|
r = nd.Parent.Failure
|
||||||
|
c = nd.Char
|
||||||
|
while (r != None and (c in r.m_values)==False):
|
||||||
|
r = r.Failure
|
||||||
|
if (r == None):
|
||||||
|
nd.Failure = root
|
||||||
|
else:
|
||||||
|
nd.Failure = r.m_values[c]
|
||||||
|
for key2 in nd.Failure.Results :
|
||||||
|
nd.SetResults(key2)
|
||||||
|
root.Failure = root
|
||||||
|
|
||||||
|
allNode2 = []
|
||||||
|
for i in range(len(allNode)): # for (i = 0; i < allNode.length; i++)
|
||||||
|
allNode2.append( TrieNode2())
|
||||||
|
|
||||||
|
for i in range(len(allNode2)): # for (i = 0; i < allNode2.length; i++)
|
||||||
|
oldNode = allNode[i]
|
||||||
|
newNode = allNode2[i]
|
||||||
|
|
||||||
|
for key in oldNode.m_values :
|
||||||
|
index = oldNode.m_values[key].Index
|
||||||
|
newNode.Add(key, allNode2[index])
|
||||||
|
|
||||||
|
for index in range(len(oldNode.Results)): # for (index = 0; index < oldNode.Results.length; index++)
|
||||||
|
item = oldNode.Results[index]
|
||||||
|
newNode.SetResults(item)
|
||||||
|
|
||||||
|
oldNode=oldNode.Failure
|
||||||
|
while oldNode != root:
|
||||||
|
for key in oldNode.m_values :
|
||||||
|
if (newNode.HasKey(key) == False):
|
||||||
|
index = oldNode.m_values[key].Index
|
||||||
|
newNode.Add(key, allNode2[index])
|
||||||
|
for index in range(len(oldNode.Results)):
|
||||||
|
item = oldNode.Results[index]
|
||||||
|
newNode.SetResults(item)
|
||||||
|
oldNode=oldNode.Failure
|
||||||
|
allNode = None
|
||||||
|
root = None
|
||||||
|
|
||||||
|
# first = []
|
||||||
|
# for index in range(65535):# for (index = 0; index < 0xffff; index++)
|
||||||
|
# first.append(None)
|
||||||
|
|
||||||
|
# for key in allNode2[0].m_values :
|
||||||
|
# first[key] = allNode2[0].m_values[key]
|
||||||
|
|
||||||
|
self._first = allNode2[0]
|
||||||
|
|
||||||
|
|
||||||
|
def FindFirst(self,text):
|
||||||
|
ptr = None
|
||||||
|
for index in range(len(text)): # for (index = 0; index < text.length; index++)
|
||||||
|
t =ord(text[index]) # text.charCodeAt(index)
|
||||||
|
tn = None
|
||||||
|
if (ptr == None):
|
||||||
|
tn = self._first.TryGetValue(t)
|
||||||
|
else:
|
||||||
|
tn = ptr.TryGetValue(t)
|
||||||
|
if (tn==None):
|
||||||
|
tn = self._first.TryGetValue(t)
|
||||||
|
|
||||||
|
|
||||||
|
if (tn != None):
|
||||||
|
if (tn.End):
|
||||||
|
item = tn.Results[0]
|
||||||
|
keyword = self._keywords[item]
|
||||||
|
return { "Keyword": keyword, "Success": True, "End": index, "Start": index + 1 - len(keyword), "Index": self._indexs[item] }
|
||||||
|
ptr = tn
|
||||||
|
return None
|
||||||
|
|
||||||
|
def FindAll(self,text):
|
||||||
|
ptr = None
|
||||||
|
list = []
|
||||||
|
|
||||||
|
for index in range(len(text)): # for (index = 0; index < text.length; index++)
|
||||||
|
t =ord(text[index]) # text.charCodeAt(index)
|
||||||
|
tn = None
|
||||||
|
if (ptr == None):
|
||||||
|
tn = self._first.TryGetValue(t)
|
||||||
|
else:
|
||||||
|
tn = ptr.TryGetValue(t)
|
||||||
|
if (tn==None):
|
||||||
|
tn = self._first.TryGetValue(t)
|
||||||
|
|
||||||
|
|
||||||
|
if (tn != None):
|
||||||
|
if (tn.End):
|
||||||
|
for j in range(len(tn.Results)): # for (j = 0; j < tn.Results.length; j++)
|
||||||
|
item = tn.Results[j]
|
||||||
|
keyword = self._keywords[item]
|
||||||
|
list.append({ "Keyword": keyword, "Success": True, "End": index, "Start": index + 1 - len(keyword), "Index": self._indexs[item] })
|
||||||
|
ptr = tn
|
||||||
|
return list
|
||||||
|
|
||||||
|
|
||||||
|
def ContainsAny(self,text):
|
||||||
|
ptr = None
|
||||||
|
for index in range(len(text)): # for (index = 0; index < text.length; index++)
|
||||||
|
t =ord(text[index]) # text.charCodeAt(index)
|
||||||
|
tn = None
|
||||||
|
if (ptr == None):
|
||||||
|
tn = self._first.TryGetValue(t)
|
||||||
|
else:
|
||||||
|
tn = ptr.TryGetValue(t)
|
||||||
|
if (tn==None):
|
||||||
|
tn = self._first.TryGetValue(t)
|
||||||
|
|
||||||
|
if (tn != None):
|
||||||
|
if (tn.End):
|
||||||
|
return True
|
||||||
|
ptr = tn
|
||||||
|
return False
|
||||||
|
|
||||||
|
def Replace(self,text, replaceChar = '*'):
|
||||||
|
result = list(text)
|
||||||
|
|
||||||
|
ptr = None
|
||||||
|
for i in range(len(text)): # for (i = 0; i < text.length; i++)
|
||||||
|
t =ord(text[i]) # text.charCodeAt(index)
|
||||||
|
tn = None
|
||||||
|
if (ptr == None):
|
||||||
|
tn = self._first.TryGetValue(t)
|
||||||
|
else:
|
||||||
|
tn = ptr.TryGetValue(t)
|
||||||
|
if (tn==None):
|
||||||
|
tn = self._first.TryGetValue(t)
|
||||||
|
|
||||||
|
if (tn != None):
|
||||||
|
if (tn.End):
|
||||||
|
maxLength = len( self._keywords[tn.Results[0]])
|
||||||
|
start = i + 1 - maxLength
|
||||||
|
for j in range(start,i+1): # for (j = start; j <= i; j++)
|
||||||
|
result[j] = replaceChar
|
||||||
|
ptr = tn
|
||||||
|
return ''.join(result)
|
||||||
0
plugins/banwords/__init__.py
Normal file
0
plugins/banwords/__init__.py
Normal file
63
plugins/banwords/banwords.py
Normal file
63
plugins/banwords/banwords.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# encoding:utf-8
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from bridge.context import ContextType
|
||||||
|
from bridge.reply import Reply, ReplyType
|
||||||
|
import plugins
|
||||||
|
from plugins import *
|
||||||
|
from common.log import logger
|
||||||
|
from .WordsSearch import WordsSearch
|
||||||
|
|
||||||
|
|
||||||
|
@plugins.register(name="Banwords", desc="判断消息中是否有敏感词、决定是否回复。", version="1.0", author="lanvent", desire_priority= 100)
|
||||||
|
class Banwords(Plugin):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
try:
|
||||||
|
curdir=os.path.dirname(__file__)
|
||||||
|
config_path=os.path.join(curdir,"config.json")
|
||||||
|
conf=None
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
conf={"action":"ignore"}
|
||||||
|
with open(config_path,"w") as f:
|
||||||
|
json.dump(conf,f,indent=4)
|
||||||
|
else:
|
||||||
|
with open(config_path,"r") as f:
|
||||||
|
conf=json.load(f)
|
||||||
|
self.searchr = WordsSearch()
|
||||||
|
self.action = conf["action"]
|
||||||
|
banwords_path = os.path.join(curdir,"banwords.txt")
|
||||||
|
with open(banwords_path, 'r', encoding='utf-8') as f:
|
||||||
|
words=[]
|
||||||
|
for line in f:
|
||||||
|
word = line.strip()
|
||||||
|
if word:
|
||||||
|
words.append(word)
|
||||||
|
self.searchr.SetKeywords(words)
|
||||||
|
self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
|
||||||
|
logger.info("[Banwords] inited")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Banwords init failed: %s" % e)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def on_handle_context(self, e_context: EventContext):
|
||||||
|
|
||||||
|
if e_context['context'].type not in [ContextType.TEXT,ContextType.IMAGE_CREATE]:
|
||||||
|
return
|
||||||
|
|
||||||
|
content = e_context['context'].content
|
||||||
|
logger.debug("[Banwords] on_handle_context. content: %s" % content)
|
||||||
|
if self.action == "ignore":
|
||||||
|
f = self.searchr.FindFirst(content)
|
||||||
|
if f:
|
||||||
|
logger.info("Banwords: %s" % f["Keyword"])
|
||||||
|
e_context.action = EventAction.BREAK_PASS
|
||||||
|
return
|
||||||
|
elif self.action == "replace":
|
||||||
|
if self.searchr.ContainsAny(content):
|
||||||
|
reply = Reply(ReplyType.INFO, "发言中包含敏感词,请重试: \n"+self.searchr.Replace(content))
|
||||||
|
e_context['reply'] = reply
|
||||||
|
e_context.action = EventAction.BREAK_PASS
|
||||||
|
return
|
||||||
3
plugins/banwords/banwords.txt.template
Normal file
3
plugins/banwords/banwords.txt.template
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
nipples
|
||||||
|
pennis
|
||||||
|
法轮功
|
||||||
3
plugins/banwords/config.json.template
Normal file
3
plugins/banwords/config.json.template
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"action": "ignore"
|
||||||
|
}
|
||||||
4
plugins/godcmd/config.json.template
Normal file
4
plugins/godcmd/config.json.template
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"password": "",
|
||||||
|
"admin_users": []
|
||||||
|
}
|
||||||
@@ -273,9 +273,13 @@ class Godcmd(Plugin):
|
|||||||
|
|
||||||
if isadmin:
|
if isadmin:
|
||||||
return False,"管理员账号无需认证"
|
return False,"管理员账号无需认证"
|
||||||
|
|
||||||
|
if len(self.password) == 0:
|
||||||
|
return False,"未设置口令,无法认证"
|
||||||
|
|
||||||
if len(args) != 1:
|
if len(args) != 1:
|
||||||
return False,"请提供口令"
|
return False,"请提供口令"
|
||||||
|
|
||||||
password = args[0]
|
password = args[0]
|
||||||
if password == self.password:
|
if password == self.password:
|
||||||
self.admin_users.append(userid)
|
self.admin_users.append(userid)
|
||||||
|
|||||||
Reference in New Issue
Block a user