引入
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
在此记录简单的插件编写规则,避免踩坑。
插件编写
1. 创建插件
1.1 创建
建议使用nb脚手架(nb-cli)创建插件,终端中输入:
nb plugin
按照指引新建插件即可,创建成功后得到插件包文件
plugin_name
├── __init__.py
├── config.py
1.2 初始化调整
此时,直接运行机器人会报错。需要调整__init__.py文件中的内容。
global_config = get_driver().config
config = Config.parse_obj(global_config) # 需要改动的地方
- 若需要插件配置
global_config = nonebot.get_driver().config
plugin_config = Config(**global_config.dict()) # 注意改成这样,具体作用在配置插件中说明
-
若不需要
删除上述需要注意的那一行,global_config视情况删除
1.3 推荐的插件结构:
plugin_name
├── __init__.py
├── config.py (按需使用)
├── data_source.py (按需创建,一般在该文件中编写数据获取函数)
└── model.py (按需创建,一般在该文件中编写数据库模型)
2. 插件配置
config.py
在该文件中使用 pydantic 定义插件所需要的配置项以及类型。
from pydantic import BaseSettings
class Config(BaseSettings):
# 写入插件需要的配置,类型,默认值
name: str
level: int = 5
foo: str = "bar"
class Config:
# 忽略传入的其他配置
extra = "ignore"
此时,__init__.py 中的
plugin_config = Config(**global_config.dict())
会将.evn文件中的填写配置传入插件配置。
3. 插件触发
3.1 创建事件响应器 Matcher
用于定义该插件在什么情况下被触发。
官方例子:
weather = on_command("weather", rule=to_me(), aliases={"天气", "天气预报"}, priority=5)
常用两种消息事件响应器:
on_message():消息事件响应器,参考onon_command():消息事件的命令响应器,参考 on_command
更多官方事件响应器,参考插件matcher
3.1.1 匹配规则
当事件传递时,在 Matcher 运行前进行规则检查
3.1.1.1 Rule
官方的Rule大部分的可以在封装的事件响应器直接使用,仅对to_me()进行介绍
to_me()判断成功,只需要满足以下任意一条条件:
-
直接在消息开头@机器人
-
直接在消息开头叫机器人的昵称(.env中定义的nickname)
-
私聊
3.1.1.1.1 自定义Rule
简单的例子:
def checker():
async def _checker(bot: Bot, event: Event, state: T_State) -> bool:
if True: #在这个位置写入你的判断代码
return True #记住,返回值一定要是个bool类型的值!
return Rule(_checker)
更多参考自定义Rule
3.1.1.2 Permission
常用两种
-
SUPERUSER匹配任意超级用户消息类型事件
-
USER(*user, perm=None)event的session_id在user白名单内且满足 perm参数:
-
*user: str: 白名单 -
perm: Optional[Permission]: 需要同时满足的权限
-
更多参考Permission
3.1.1.3 注意
在这里需要强调的是,Permission 与 Rule 的表现并不相同, Rule 只会在初次响应时生效,在余下的对话中并没有限制事件;
但是 Permission 会持续生效,在连续对话中会一直对事件主体加以限制。
因此,在群聊消息中需要注意使用 Permission
3.2 事件处理
创建事件响应器后,需要对响应的事件进行处理。
weather = on_command("weather", rule=to_me(), aliases={"天气", "天气预报"}, priority=5)
weather 这个变量作为一个容器对事件响应器进行了包裹,而想要对事件进行处理需要使用装饰器。
一个事件响应器可以有多个事件处理器对其进行处理,且为顺序执行。
NoneBot提供三种事件处理器:
xxx.handle()xxx.reveive()xxx.got(key, prompt, args_parser)
参考官方文档
注意V2.0.0b1新语法,参考事件处理流程
xxx.handle()
最基础的处理器装饰器,收到信息后立即处理此消息,不会等待进一步的输入,也不会响应后续输入。 例如:
test = on_command("test")
@test.handle()
async def test_handle(bot: Bot, event: Event, state: T_State):
print('我收到了命令“/test”')
xxx.receive()
运行到此修饰器时,会先暂停对该事件的处理,当再次收到用户输入后才对新的输入后进行处理(类似在开头运行了input())
常见于对后续数据的接收等,一般不会放在第一个使用,放第一个等同于xxx.handle()
test = on_command("test")
@test.handle()
async def test_handle(bot: Bot, event: Event, state: T_State):
print('我收到了第一条消息')
@test.recive()
async def test_handle(bot: Bot, event: Event, state: T_State):
print('我收到了第二条消息')
@test.recive()
async def test_handle(bot: Bot, event: Event, state: T_State):
print('我收到了第三条消息')
xxx.got(key, prompt=None, args_parser=None)
运行到此修饰器时,会先检查这个事件的state,是否带有key字段
有则等同于xxx.handle(),开始执行该处理器内容
无则向用户发送prompt,接着类似xxx.recive(),等待输入,输入完成后进行args_parser处理
,然后执行该处理器内容,最后执行下一个修饰器(如果有的话)
注意:
- prompt可以是
Message。 - 如果在后续需要提取这个消息的信息,可用
state[key]进行提取。 - args_parser是参数解析函数,不填则使用默认解析,参考文档
# V2a语法
test = on_command("test")
@test.handle()
async def test_handle(bot: Bot, event: Event, state: T_State):
cmd = str(event.get_message()).strip() #获取把命令头部分去除后的消息内容,也就是命令的具体内容。
if cmd: #如果命令的具体内容不为空,就将cmd存入state。
state['cmd'] = cmd
@test.got('cmd', '请输入命令的具体参数')
async def test_handle(bot: Bot, event: Event, state: T_State):
print('我已获取到完整的命令,开始执行')
# v2b 注意新语法!
async def handle_first_receive(matcher: Matcher, args: Message = CommandArg()):
plain_text = args.extract_plain_text()
if plain_text:
matcher.set_arg("cmd", args) # 等同于 state['cmd'] = plain_text (注意args是Message)
事件处理器参数
常用的参数有:bot: Bot, event: Event, state: T_State, matcher: Matcher
在事件处理器函数中视情况使用
注意V2.0.0b1新语法,参考获取上下文信息
bot
最常用的方法为call_api,可以直接调用gocq的API以实现nb2不原生支持的功能。
简单例子
await bot.call_api('send_msg',**{
'user_id' : user_id,
'message' : message
})
event
上报事件对象,可以获取到上报的所有信息,也是我们最常用的获取信息的途径。
session_id = event.get_session_id()
#群聊中:gruop_群号_qq号
#私聊中:qq号
qq_id = event.get_user_id() # 只获取qq号
此方法用于获取事件消息内容,包括但不限于文字、Unicode表情、QQ表情、图片等。 其中QQ表情、图片等将会用CQ码进行表示,可以参考gocq官方文档进行解析。
注意,on_command()时,传入的消息将自动去除“命令头”,仅保留后面的内容
msg = event.get_message()
其中,msg为用户的输入。 同时此方法也有其他的方法对消息进行处理,例如仅保留文字部分:
msg = event.get_message().extract_plain_text
# 或者
msg = event.get_get_plaintext()
state
状态字典,可以存储任意的信息,其中还包含一些特殊的值以获取 NoneBot 内部处理时的一些信息,如:
state["current_key"]: 存储当前 got 获取的参数名
state["_prefix"], state["_suffix"]: 存储当前 TRIE 匹配的前缀/后缀,可以通过该值获取用户命令的原始命令
事件处理方法
当我们使用注册一个名为xxx的事件响应器后,可以使用这些处理方法。
xxx.send()
向用户发送一条Message类型的消息,例如:
await xxx.send('你好')
await xxx.send(Message('[CQ:face,id=123]'))
xxx.pause()
这个函数用于结束当前事件处理函数,强制接收一条新的消息,然后运行下一个处理函数。
放弃本处理函数接下来的操作,强制获取新的消息用于下一个处理函数
await xxx.pause('请输入下一条信息')
xxx.reject()
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后重新运行当前处理函数
此方法常用于与receive和got等配合,在用户输入不正确的时候重新要求输入。
注意避免无限reject(),否则只能等消息等待超时(可在.env中修改超时时间)
await xxx.reject('您输入的xxx信息不符合格式,请重新输入')
xxx.finish()
发送一条消息给当前交互用户,并结束当前事件响应器
此方法用于一个事件的结束,即使此后还有未启用的处理器也将停止。 不会影响事件的继续传播。
await xxx.finish('事件已经处理完毕')
4.插件加载
插件加载尤其要注意加载顺序(但存在相互调用关系时)
# 使用手动加载单个插件的形式
nonebot.load_plugin('nonebot_plugin_apscheduler')
nonebot.load_plugin('src.plugins.xmuer_search')
nonebot.load_plugin('src.plugins.xmuer_search')
# 不要使用 load_plugins
# 或者手动配置 load_from_toml