mirror of
https://github.com/nonebot/nonebot2.git
synced 2024-11-27 18:45:05 +08:00
Add filter support
This commit is contained in:
parent
7c5d7630ea
commit
af431f17b1
34
README.md
34
README.md
@ -1,7 +1,35 @@
|
||||
# QQBot
|
||||
|
||||
此 QQBot 非彼 QQBot,不是对 SmartQQ 的封装,而是基于开源的 [sjdy521/Mojo-Webqq](https://github.com/sjdy521/Mojo-Webqq) 实现的处理命令的逻辑。
|
||||
此 QQBot 非彼 QQBot,不是对 SmartQQ 的封装,而是基于开源的 [sjdy521/Mojo-Webqq](https://github.com/sjdy521/Mojo-Webqq) 实现的对消息的自动处理程序,支持自定义插件。
|
||||
|
||||
现在基本框架已经完成,不过还有部分基础性的命令没有实现,待完成之后,再来进行命令的扩充。
|
||||
## 如何部署
|
||||
|
||||
由于还没有完成,代码的各个部分、程序的功能等可能会变动比较频繁,此 README 先不详细写。目前可以参考 [编写命令](Write_Command.md) 来了解如何编写命令,因为命令是本程序的重要内容,所以这个文档会更新比较及时。
|
||||
推荐使用 Docker 部署,因为基本可以一键开启,如果你想手动运行,也可以参考第二个小标题「手动部署」。
|
||||
|
||||
### 使用 Docker
|
||||
|
||||
本仓库根目录下的 `docker-compose.yml` 即为 Docker Compose 的配置文件,直接跑就行。如果你想对镜像进行修改,可以自行更改 Dockerfile 来构建或者继承已经构建好的镜像。
|
||||
|
||||
### 手动运行
|
||||
|
||||
首先需要运行 sjdy521/Mojo-Webqq,具体见它的 GitHub 仓库的使用教程。然后运行:
|
||||
|
||||
```sh
|
||||
pip install -r requirements.txt
|
||||
python app.py
|
||||
```
|
||||
|
||||
注意要求 Python 3.x。
|
||||
|
||||
## 插件
|
||||
|
||||
程序支持两种插件形式,一种是过滤器/Filter,一种是命令/Command。
|
||||
|
||||
本质上程序主体是一个 web app,接受 sjdy521/Mojo-Webqq 的 POST 请求,从而收到消息。收到消息后,首先运行过滤器,按照优先级从大到小顺序运行 `filters` 目录中的 `.py` 文件中指定的过滤器函数,函数返回非 False 即表示不拦截消息,从而消息继续传给下一个过滤器,如果返回了 False,则消息不再进行后续处理,而直接抛弃。过滤器运行完之后,会开始按照命令执行,首先根据命令的开始标志判断有没有消息中有没有指定命令,如果指定了,则执行指定的命令,如果没指定,则看当前用户有没有开启交互式会话,如果开启了会话,则执行会话指定的命令,否则,使用默认的 fallback 命令。
|
||||
|
||||
过滤器和命令的使用场景区别:
|
||||
|
||||
- 过滤器:可用于消息的后台日志、频率控制、关键词分析,一般在使用者无意识的情况下进行;
|
||||
- 命令:使用者有意识地想要使用某个给定的命令的功能。
|
||||
|
||||
关于过滤器和命令的细节,请参考 [编写过滤器](Write_Filter.md) 和 [编写命令](Write_Command.md)。
|
||||
|
24
Write_Filter.md
Normal file
24
Write_Filter.md
Normal file
@ -0,0 +1,24 @@
|
||||
# 编写过滤器
|
||||
|
||||
编写过滤器比较简单,只需要调用 `filter.py` 中的 `add_filter` 函数,传入过滤器函数和优先级,即可。
|
||||
|
||||
比如我们需要做一个消息拦截器,当匹配到消息中有不文明词汇,就发送一条警告,并拦截消息不让后续过滤器和命令处理,代码可能如下:
|
||||
|
||||
```python
|
||||
from filter import add_filter
|
||||
from commands import core
|
||||
|
||||
|
||||
def _interceptor(ctx_msg):
|
||||
if 'xxx' in ctx_msg.get('content', ''):
|
||||
core.echo('请不要说脏话', ctx_msg)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
add_filter(_interceptor, 100)
|
||||
```
|
||||
|
||||
一般建议优先级设置为 0~100 之间。
|
||||
|
||||
过滤器函数返回 True 表示让消息继续传递,返回 False 表示拦截消息。由于很多情况下可能不需要拦截,因此为了方便起见,将不返回值的情况(返回 None)作为不拦截处理,因此只要返回结果 is not False 就表示不拦截。
|
18
app.py
18
app.py
@ -10,6 +10,7 @@ from config import config
|
||||
from command import hub as cmdhub
|
||||
from command import CommandNotExistsError, CommandScopeError, CommandPermissionError
|
||||
from apiclient import client as api
|
||||
from filter import apply_filters
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@ -29,11 +30,13 @@ def _send_text(text, ctx_msg):
|
||||
@app.route('/', methods=['POST'])
|
||||
def _index():
|
||||
ctx_msg = request.json
|
||||
source = get_source(ctx_msg)
|
||||
try:
|
||||
if ctx_msg.get('msg_class') != 'recv':
|
||||
raise SkipException
|
||||
if not apply_filters(ctx_msg):
|
||||
raise SkipException
|
||||
content = ctx_msg.get('content', '')
|
||||
source = get_source(ctx_msg)
|
||||
if content.startswith('@'):
|
||||
my_group_nick = ctx_msg.get('receiver')
|
||||
if not my_group_nick:
|
||||
@ -91,6 +94,16 @@ def _index():
|
||||
return '', 204
|
||||
|
||||
|
||||
def _load_filters():
|
||||
filter_mod_files = filter(
|
||||
lambda filename: filename.endswith('.py') and not filename.startswith('_'),
|
||||
os.listdir(get_filters_dir())
|
||||
)
|
||||
command_mods = [os.path.splitext(file)[0] for file in filter_mod_files]
|
||||
for mod_name in command_mods:
|
||||
importlib.import_module('filters.' + mod_name)
|
||||
|
||||
|
||||
def _load_commands():
|
||||
command_mod_files = filter(
|
||||
lambda filename: filename.endswith('.py') and not filename.startswith('_'),
|
||||
@ -106,5 +119,6 @@ def _load_commands():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_load_filters()
|
||||
_load_commands()
|
||||
app.run(host=os.environ.get('HOST'), port=os.environ.get('PORT'))
|
||||
app.run(host=os.environ.get('HOST', '0.0.0.0'), port=os.environ.get('PORT', '8080'))
|
||||
|
14
filter.py
Normal file
14
filter.py
Normal file
@ -0,0 +1,14 @@
|
||||
_filters = []
|
||||
|
||||
|
||||
def apply_filters(ctx_msg):
|
||||
filters = sorted(_filters, key=lambda x: x[0], reverse=True)
|
||||
for f in filters:
|
||||
r = f[1](ctx_msg)
|
||||
if r is False:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def add_filter(func, priority):
|
||||
_filters.append((priority, func))
|
39
filters/frequency_limiter.py
Normal file
39
filters/frequency_limiter.py
Normal file
@ -0,0 +1,39 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from cachetools import TTLCache as TTLDict
|
||||
|
||||
from filter import add_filter
|
||||
from little_shit import get_target
|
||||
from commands import core
|
||||
|
||||
_freq_count = TTLDict(maxsize=10000, ttl=2 * 60 * 60)
|
||||
_max_message_count_per_hour = 150
|
||||
|
||||
|
||||
def _limiter(ctx_msg):
|
||||
target = get_target(ctx_msg)
|
||||
if target not in _freq_count:
|
||||
# First message of this target in 2 hours (_freq_count's ttl)
|
||||
_freq_count[target] = (0, datetime.now())
|
||||
|
||||
count, last_check_dt = _freq_count[target]
|
||||
now_dt = datetime.now()
|
||||
delta = now_dt - last_check_dt
|
||||
|
||||
if delta >= timedelta(hours=1):
|
||||
count = 0
|
||||
last_check_dt = now_dt
|
||||
|
||||
if count >= _max_message_count_per_hour:
|
||||
# Too many messages in this hour
|
||||
core.echo('我们聊天太频繁啦,休息一会儿再聊吧~', ctx_msg)
|
||||
count = -1
|
||||
|
||||
if count >= 0:
|
||||
count += 1
|
||||
|
||||
_freq_count[target] = (count, last_check_dt)
|
||||
return count >= 0
|
||||
|
||||
|
||||
add_filter(_limiter, 100)
|
10
filters/message_logger.py
Normal file
10
filters/message_logger.py
Normal file
@ -0,0 +1,10 @@
|
||||
from filter import add_filter
|
||||
|
||||
|
||||
def _log_message(ctx_msg):
|
||||
print(ctx_msg.get('sender', '')
|
||||
+ (('@' + ctx_msg.get('group')) if ctx_msg.get('type') == 'group_message' else '')
|
||||
+ ': ' + ctx_msg.get('content'))
|
||||
|
||||
|
||||
add_filter(_log_message, 1000)
|
@ -16,6 +16,10 @@ def get_root_dir():
|
||||
return os.path.split(os.path.realpath(__file__))[0]
|
||||
|
||||
|
||||
def get_filters_dir():
|
||||
return _mkdir_if_not_exists_and_return_path(os.path.join(get_root_dir(), 'filters'))
|
||||
|
||||
|
||||
def get_commands_dir():
|
||||
return _mkdir_if_not_exists_and_return_path(os.path.join(get_root_dir(), 'commands'))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user