bot 对象与相关接口¶
前面的章节已经说明了如何创建 Bot、添加协议栈、加载插件和启动程序。本篇从管理者的视角补充 bot 对象的特性:它何时完成配置、怎样在运行中的代码里取得它,以及怎样查询和扩展其已注册的组件。
实例名称与配置期¶
创建 bot 时的 name 不只是展示名称,也是当前 Python 进程中 bot 实例的标识。同一时刻不能存在两个同名 bot:
from melobot import Bot
main_bot = Bot("main")
# Bot("main") # 抛出 BotError:同名实例已存在
test_bot = Bot("test")
名称还会用于日志和生命周期 hook 的标识。因此,多 bot 程序应为每个实例提供稳定且不同的名称,不建议依赖默认值 "melobot"。
配置会在首次加载插件时冻结¶
源和适配器必须在 bot 的配置期绑定。一个容易忽略的事实是:首次调用 load_plugin() 就会初始化 bot 的核心组件,不必等到调用 run()。从这一刻开始,add_input()、add_output()、add_io() 和 add_adapter() 都不能再调用。
from melobot import Bot, PluginPlanner
from melobot.protocols.onebot.v11 import OneBotV11Protocol, WSClient
bot = Bot("main")
bot.add_protocol(OneBotV11Protocol(WSClient("ws://127.0.0.1:8080")))
# 此调用会结束配置期
bot.load_plugin(PluginPlanner("1.0.0"))
# bot.add_protocol(...) # 错误:底层源、适配器已不能再绑定
这也是主程序通常采用“先添加所有协议支持,再加载插件”的原因。插件可以在启动后动态加载,但协议组件不支持以这种方式动态增删;若运行时需要启用或停用某项业务功能,应优先将它设计为插件或处理流。
提示
add_input()、add_output()、add_io()、add_adapter() 和 add_protocol() 都会返回 bot 本身,因此可以按配置顺序链式调用。
在当前上下文中获取 bot¶
get_bot() 返回当前执行上下文中的 bot。它适用于不方便通过参数接收 bot 的辅助函数,以及插件加载、生命周期 hook、事件处理流等由 bot 发起的执行过程。
from melobot.bot import get_bot
def get_cache_prefix() -> str:
# 此函数必须在 bot 的执行上下文中调用
return f"{get_bot().name}:cache"
在没有活动 bot 上下文的普通模块代码、独立脚本或自行创建且未继承上下文的线程中调用,会抛出 BotError。因此,不要让这类通用函数在导入时立刻调用 get_bot();将调用放在真正执行时更安全。
melobot.bot 模块还提供动态变量 bot,它等价于在当前时刻调用 get_bot()。例如下面的代码位于由 load_plugin() 加载的插件模块中:
# __plugin__.py
from melobot.bot import bot
@bot.on_started
async def report_started() -> None:
bot.logger.info(f"{bot.name} 已启动")
这里的 bot 在模块被访问时依赖当前上下文,并不是一个可在任意位置使用的全局单例。对于事件处理结点和各种生命周期 hook,若只需要使用 bot,仍然更推荐直接声明 Bot 类型参数,让依赖注入完成获取:
from melobot import Bot, GenericLogger
async def setup(bot: Bot, logger: GenericLogger) -> None:
logger.info(f"正在初始化 {bot.name}")
这种写法具有明确的依赖关系,也更便于测试。自动依赖注入的完整规则可参阅依赖注入与特性。
查询已绑定的适配器¶
查询一个适配器¶
get_adapter() 支持三种查询方式:协议字符串、适配器类型或过滤函数。没有匹配项时返回 None。
from melobot.protocols.onebot.v11 import Adapter
# 按协议标识查询
adapter = bot.get_adapter("OneBot-v11@Meloland")
# 按具体适配器类型查询,类型提示会保留为 Adapter | None
adapter = bot.get_adapter(Adapter)
# 需要按运行时条件选择时使用过滤函数
adapter = bot.get_adapter(filter=lambda a: a.protocol.endswith("@Meloland"))
type 参数存在时会优先按类型或协议查询,filter 仅在不提供 type 时生效。若确实需要一组适配器,使用 get_adapters():
# 获得所有已绑定适配器的一个集合副本
all_adapters = bot.get_adapters()
# 获得满足条件的集合
active_protocols = bot.get_adapters(filter=lambda a: a.protocol.startswith("OneBot"))
在事件处理函数中,若你想要的是产生当前事件的适配器,应直接将对应适配器类型声明为依赖,或读取事件的来源信息;不要按类型重新查询。后两种方式分别可获得精确类型和精确来源,详见源与适配器。
运行时添加处理流¶
插件中的处理流会在插件就绪时自动加入 bot。对于少数确实需要动态启用功能的场景,可使用 add_flows() 直接注册一个或多个 Flow:
from melobot import Bot, on_start_match, send_text
@on_start_match(".health")
async def health_check() -> None:
await send_text("ok")
def enable_health_check(bot: Bot) -> None:
# health_check 此前没有交给 PluginPlanner
bot.add_flows(health_check)
注册后,处理流会参与之后收到的事件分发;已经开始分发的事件不会回头执行新流。此接口不是线程安全的,应在单一事件循环的受控位置调用,例如启动 hook、处理流或插件就绪 hook 中。
处理流一旦无需继续工作,可以调用其 dismiss() 方法停用:
health_check.dismiss()
停用的流不再处理新事件,且不能重新启用。需要恢复同类功能时,创建并注册新的流对象。处理流的优先级、阻断传播和并发规则请参考事件处理流与机制。
等待事件完成分发¶
wait_finish() 是面向适配器扩展等底层场景的异步接口。它等待一个已经交给该 bot 分发器的事件,直到所有能够处理它的流都完成,或事件不再向更低优先级传播。
from melobot import Bot
from melobot.adapter import Event
async def after_dispatch(bot: Bot, event: Event) -> None:
# 前提:event 已经被投入 bot 的分发器
await bot.wait_finish(event)
# 此处再执行依赖于全部处理结果的后续操作
通常而言,插件中的事件处理函数不要使用它:调用者本身就是待完成的处理流之一,在其中等待同一事件会造成死锁。普通业务逻辑应让处理结果沿处理流、会话或插件共享对象传递;只有自定义协议适配器等“事件提交者”才应在提交事件之后等待它结束。
其他查询与通信接口¶
get_plugins() 可返回已加载插件的名称列表,get_share() 可按“插件名 + 共享对象标识”取得跨插件通信对象。后者属于底层接口,动态按名称查找时很有用;常规跨插件调用仍建议使用 mb pinit 生成的带类型提示接口。具体示例和边界条件请参阅共享对象的定义和基本使用。
总结¶
bot 对象既是程序的配置入口,也是运行时组件的统一查询入口。
实践中应牢记三点:先完成协议配置再加载插件;在受控上下文中获取当前 bot;将动态业务功能组织为处理流或插件,而不是尝试在运行期修改协议组件。
下一篇将介绍:日志组件与日志修补。