Package: lightq
LightQLightQ 是一个基于mirai-api-http的 QQ 机器人框架。安装从 PyPI 安装：pipinstalllightq从源代码安装：gitclonehttps://github.com/zhb2000/lightq.gitcdlightq
pipinstall.前置条件环境要求：Python 3.10mirai-api-http 2.6.2LightQ 需要借助网络 API 调用 Mirai 的功能，因此请先安装并配置好Mirai Console Loader和mirai-api-http插件：安装Mirai Console Loader (MCL)。在 MCL 中配置 QQ 账号和密码，确保能正常登录账号，中途可能需要使用TxCaptchaHelper应对滑动验证码。为 MCL 安装mirai-api-http插件。在 mirai-api-http 的配置文件中启用 websocket 适配器。LightQ 使用 Python 标准库的asyncio完成异步操作，如果你不熟悉 Python 的协程，可以先看看 Python 文档中协程与任务这一节。简明教程快速起步importasynciofromlightqimportmessage_handler,Bot,scan_handlersfromlightq.entitiesimportFriendMessage@message_handler(FriendMessage)defsay_hello()->str:return'Hello'asyncdefmain():bot=Bot(123456789,'verify-key')# 请替换为相应的 QQ 号和 verify keybot.add_all(scan_handlers(__name__))awaitbot.run()if__name__=='__main__':asyncio.run(main())上述代码实现了一个最简单的 QQ 机器人，无论谁给它发消息，它都只会回复 Hello。message_handler装饰器将say_hello函数包装为一个MessageHandler对象，该消息处理器只会响应好友消息FriendMessage。LightQ 还提供了event_handler和exception_handler装饰器，分别用于创建事件处理器和异常处理器。bot.add_all(scan_handlers(__name__))的作用是获取当前模块中所有 public 的 handler，并将它们添加到bot中。注 1：__name__是 Python 中一个特殊的变量，表示当前模块的全限定名称。注 2：在 Python 中不以下划线开头的变量为模块的 public 成员，另一种做法是在模块中用__all__列出所有 public 成员的名字。一个合法的 handler 函数需要返回str或MessageChain或None。Handler 函数既可以是同步函数也可以是异步函数。fromlightqimportmessage_handlerfromlightq.entitiesimportFriendMessage,MessageChain,Plain@message_handler(FriendMessage)asyncdefsay_hello()->MessageChain:# 一个返回 MessageChain 的异步函数awaitasyncio.sleep(1)returnMessageChain([Plain('Hello')])过滤器如何实现 handler 的有条件执行？需要使用过滤器。我们继续改进之前的say_hello：fromlightqimportRecvContextdefcondition(context:RecvContext)->bool:returnstr(context.data.message_chain)=='Hello'@message_handler(FriendMessage,filters=condition)defsay_hello()->str:"""当别人对 bot 说 Hello 时回复 Hello"""return'Hello'上面代码中的condition函数就是一个过滤器。lightq.filters模块提供了一些现成的过滤器，可以直接使用。让我们再修改一下say_hello，为它设置两个条件：fromlightqimportRecvContext,filtersdefcondition(context:RecvContext)->bool:returnstr(context.data.message_chain)=='Hello'@message_handler(FriendMessage,filters=[filters.from_user(987654321),condition])defsay_hello()->str:"""当 QQ 号为 987654321 的用户对 bot 说 Hello 时回复 Hello"""return'Hello'参数解析基于类型的参数解析如果你用过 Spring Boot 之类的 Web 框架，对于参数解析这个概念应该不会陌生。LightQ 框架支持基于类型和基于函数两种参数解析机制。下面这个示例展示了如何使用基于类型的参数解析：fromlightq.entitiesimportGroupMessage,MessageChain@message_handler(GroupMessage)defgroup_message_handler(chain:MessageChain):print(f'收到一条群组消息，内容为：{chain}')注意到group_message_handler函数带有参数类型注解chain: MessageChain，这个类型注解是不可或缺的。LightQ 框架使用 Python 的内省 (inspect) 机制获取chain参数的类型，接收到消息后解析出消息链对象，再自动地将消息链对象注入chain参数中。参数解析机制的一个重要用途是在 handler 内获取 bot 的引用，并直接调用 bot 对象上的方法：@event_handler(NudgeEvent)asyncdefnudge_response(event:NudgeEvent,bot:Bot):"""谁拍一拍我，我就拍一拍谁"""if(event.subject.kind=='Group'andevent.target==bot.bot_idandevent.from_id!=bot.bot_id):awaitbot.api.send_nudge(event.from_id,event.subject.id,'Group')LightQ 框架支持自动解析的类型有：BotRecvContextExceptionContextMessageChainMessage及其子类Event及其子类Exception及其子类参数解析机制也支持自定义类型，只需让你自己的类型继承lightq.framework中的FromContext/FromRecvContext/FromExceptionContext抽象类并重写对应的方法即可。基于函数的参数解析基于类型的参数解析无法覆盖所有场景，例如：希望从群组消息中解析出群号和发送者的 QQ 号，但二者皆为int类型，仅凭类型无法区分。此时需要使用基于函数的参数解析，请看如下例子：fromlightqimportresolvers# resolvers 是一个模块fromlightqimportresolve# resolve 是一个装饰器@resolve(resolvers.group_id,member_id=resolvers.sender_id)@message_handler(GroupMessage)defgroup_message_handler(chain:MessageChain,group_id:int,member_id:int):print(f'收到一条群组消息，群号{group_id}，群员 QQ 号{member_id}，内容为：{chain}')resolvers.group_id和resolvers.sender_id是两个类型为(RecvContext) -> int的函数，分别从RecvContext对象中解析出发送者的群号和 QQ 号，再配合resolve装饰器就可以实现参数解析和自动注入的效果。使用resolve装饰器时，若以普通方式传参（上面的@resolve(resolvers.group_id)），则根据解析器的__name__属性注入同名的参数（Python 函数的__name__属性默认为该函数的名字）；若以命名参数的方式传参（上面的@resolve(member_id=resolvers.sender_id)），则表示手动指定注入参数。本节的示例代码放在examples/resolver_example.py中。正则表达式lightq.decorators模块中有三个很实用的装饰器：regex_match、regex_search、regex_fullmatch，分别对应 Python 标准库中的re.match,re.search,re.fullmatch，可以通过正则表达式匹配消息的内容。importrefromlightq.decoratorsimportregex_match@regex_match('(?P<first_group>\w+) (?P<second_group>\w+)')@message_handler(GroupMessage)defhandler(first_group:str,second_group:str,match:re.Match):assertfirst_group==match['first_group']assertsecond_group==match['second_group']正则表达式中形如(?P<name>...)的是命名组语法。如果消息的内容与正则表达式相匹配，那么将捕获的组按照组的名字注入到 handler 的同名参数中，匹配对象将自动注入到类型为re.Match的参数中。正则表达式装饰器可以用来构建 QQ 机器人的指令系统：@regex_fullmatch(r'/mute\s+(?P<member_id>\d+)\s+(?P<duration>\d+)')@resolve(resolvers.group_id)@message_handler(GroupMessage)asyncdefmute_command(group_id:int,member_id:str,duration:str,bot:Bot):"""输入 /mute member_id duration 命令以禁言用户:param group_id: 群号:param member_id: 被禁言的用户:param duration: 禁言时长（秒）"""awaitbot.api.mute(group_id,int(member_id),int(duration))regex_match的实现非常简单，其原理是将过滤器和解析器构造出来插入 handler 中，并不需要引入额外的组件。你可以在src/lightq/decorators/_regex.py找到其源代码。设置 handler 的优先级若不显式地指定 handler 间的优先关系，则机器人遍历各个 handler 的顺序是不确定的，这有时候会带来问题。以下是一个复读机程序，可通过“开始复读”和“停止复读”命令来开关复读功能。repeat_on=False@regex_fullmatch('(?P<option>开始|停止)复读')@message_handler(GroupMessage)defswitch(option:str):globalrepeat_onrepeat_on=option=='开始'returnf'复读已{option}'@message_handler(GroupMessage,filters=lambdactx:repeat_on,after=switch)defrepeat(chain:MessageChain)->MessageChain:returnchain注意到，必须先判断switch的条件是否满足，然后再判断repeat的条件是否满足，即switch的优先级必须高于repeat，否则当用户输入“停止复读”时，程序会继续复读“停止复读”这句话而不是把复读功能关掉。怎样设置 handler 的优先级呢？有三种方法：使用装饰器的after参数或before参数，如上述代码在repeat的装饰器中设置了after=switch。直接修改 handler 的after或before属性，如repeat.after.append(switch)。使用Bot的add_order方法，如bot.add_order(switch, repeat)。本节的示例代码放在examples/repeater.py中。使用 controller以上示例中所有的 bot 都是“一问一答”型，而一个具备连续对话能力的 bot 看起来会更加有趣：群友 1：/weather
Bot：您想查询哪个城市的天气？
群友 1：武汉
Bot：武汉的天气为小雨

群友 2：/mute_all
Bot：您确定要开启全员禁言吗？请回复“是”或“否”
群友 2：no
Bot：请回复“是”或“否”
群友 2：否若要实现连续对话功能，就必须保存每个用户的状态。可以将状态保存到全局变量中。如果你不喜欢全局变量这种代码风格，也可以将状态封装到 controller 类中统一管理。Controller 类的编写方法如下：classMyController(lightq.Controller):# 继承 lightq.Controller 类def__init__(self):self.status=...# 将状态作为成员变量封装到 controller 中# 过滤器方法defcondition(self,context:RecvContext)->bool:...# 解析器方法defresolver(self,context:RecvContext):...# 用 message_handler 装饰器将 my_handler 方法转换为消息处理器# - 使用 condition 方法作为过滤条件# - 使用 resolver 方法作为参数解析器@resolve(data=resolver)@message_handler(Message,filters=condition)defmy_handler(self,data):self.status# 在方法内部可以通过 self 引用保存的状态...controller=MyController()# 通过 handlers 方法获取所有 public 的 handlerbot.add_all(controller.handlers())# ...examples/assistant.py提供了一个完整的 controller 示例，实现了一个支持/weather和/mute_all命令的机器人。此外，你还可以用handler_property装饰器将属性方法转换为处理器，示例代码见examples/assistant_property_style.py.其他功能定时任务、后台任务相关的函数和方法：asyncio.sleep：可用于延迟执行等场景。lightq.utils.sleep_until：延迟到某个时刻执行，该函数是对asyncio.sleep的简单封装。Bot.create_task：创建后台任务，该方法是对asyncio.create_task的简单封装。Bot.create_everyday_task：创建每日定时任务。日志LightQ 使用 Python 标准库中的logging模块来打印日志，可通过lightq.logger获得 logger 对象。默认的日志打印级别为 INFO。自定义路由LightQ 默认的路由会根据消息/事件/异常的类型将数据送给指定的 handler。你也可以根据实际场景设计更高效的路由机制。继承MessageRouter/EventRouter/ExceptionRouter抽象类（位于lightq.framework模块中）并重写对应的方法以实现自定义路由机制。未来（可能是）将来的一些工作：完善文档支持文件操作补齐剩余的 API 功能中间件/钩子函数？