{"id":18447017,"url":"https://github.com/zhb2000/lightq","last_synced_at":"2026-02-14T13:33:02.796Z","repository":{"id":58572086,"uuid":"523596421","full_name":"zhb2000/lightq","owner":"zhb2000","description":"简洁的 QQ 机器人框架，基于 mirai-api-http","archived":false,"fork":false,"pushed_at":"2023-02-18T16:00:04.000Z","size":185,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-27T18:34:11.761Z","etag":null,"topics":["asyncio","bot","mirai","python","qq","qqbot"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/lightq","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zhb2000.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-11T05:27:03.000Z","updated_at":"2023-02-02T06:01:02.000Z","dependencies_parsed_at":"2024-11-06T07:11:53.794Z","dependency_job_id":"675eb316-321e-4458-8cb0-d6a5f1d3102b","html_url":"https://github.com/zhb2000/lightq","commit_stats":{"total_commits":26,"total_committers":1,"mean_commits":26.0,"dds":0.0,"last_synced_commit":"a3b5d864068829867b19bf60f47cdcc210ecfe4d"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/zhb2000/lightq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhb2000%2Flightq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhb2000%2Flightq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhb2000%2Flightq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhb2000%2Flightq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zhb2000","download_url":"https://codeload.github.com/zhb2000/lightq/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhb2000%2Flightq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29444752,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T12:43:28.304Z","status":"ssl_error","status_checked_at":"2026-02-14T12:43:14.160Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asyncio","bot","mirai","python","qq","qqbot"],"created_at":"2024-11-06T07:11:41.786Z","updated_at":"2026-02-14T13:33:02.779Z","avatar_url":"https://github.com/zhb2000.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LightQ\n\n[![PyPI](https://img.shields.io/pypi/v/lightq?logo=pypi\u0026logoColor=white)](https://pypi.org/project/lightq) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/lightq?logo=python\u0026logoColor=white)](https://www.python.org/downloads/) [![mirai-api-http version](https://img.shields.io/badge/mirai--api--http-v2.6.2-blue)](https://github.com/project-mirai/mirai-api-http) [![PyPI - License](https://img.shields.io/pypi/l/lightq)](https://github.com/zhb2000/lightq/blob/master/LICENSE) [![GitHub Repo stars](https://img.shields.io/github/stars/zhb2000/lightq?style=social)](https://github.com/zhb2000/lightq/stargazers)\n\nLightQ 是一个基于 [mirai-api-http](https://github.com/project-mirai/mirai-api-http) 的 QQ 机器人框架。\n\n# 安装\n\n从 PyPI 安装：\n\n```shell\npip install lightq\n```\n\n从源代码安装：\n\n```shell\ngit clone https://github.com/zhb2000/lightq.git\ncd lightq\npip install .\n```\n\n# 前置条件\n\n环境要求：\n\n- Python 3.10\n- mirai-api-http 2.6.2\n\nLightQ 需要借助网络 API 调用 Mirai 的功能，因此请先安装并配置好  [Mirai Console Loader](https://github.com/iTXTech/mirai-console-loader) 和 [mirai-api-http](https://github.com/project-mirai/mirai-api-http) 插件：\n\n1. 安装 [Mirai Console Loader (MCL)](https://github.com/iTXTech/mirai-console-loader)。\n1. 在 MCL 中配置 QQ 账号和密码，确保能正常登录账号，中途可能需要使用 [TxCaptchaHelper](https://github.com/mzdluo123/TxCaptchaHelper) 应对滑动验证码。\n1. 为 MCL 安装 [mirai-api-http](https://github.com/project-mirai/mirai-api-http) 插件。\n1. 在 mirai-api-http 的配置文件中启用 websocket 适配器。\n\nLightQ 使用 Python 标准库的 [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) 完成异步操作，如果你不熟悉 Python 的协程，可以先看看 Python 文档中[协程与任务](https://docs.python.org/zh-cn/3/library/asyncio-task.html)这一节。\n\n# 简明教程\n## 快速起步\n\n```python\nimport asyncio\nfrom lightq import message_handler, Bot, scan_handlers\nfrom lightq.entities import FriendMessage\n\n@message_handler(FriendMessage)\ndef say_hello() -\u003e str:\n    return 'Hello'\n\nasync def main():\n    bot = Bot(123456789, 'verify-key')  # 请替换为相应的 QQ 号和 verify key\n    bot.add_all(scan_handlers(__name__))\n    await bot.run()\n\nif __name__ == '__main__':\n    asyncio.run(main())\n```\n\n上述代码实现了一个最简单的 QQ 机器人，无论谁给它发消息，它都只会回复 Hello。\n\n`message_handler` 装饰器将 `say_hello` 函数包装为一个 `MessageHandler` 对象，该消息处理器只会响应好友消息 `FriendMessage`。LightQ 还提供了 `event_handler` 和 `exception_handler` 装饰器，分别用于创建事件处理器和异常处理器。\n\n`bot.add_all(scan_handlers(__name__))` 的作用是获取当前模块中所有 public 的 handler，并将它们添加到 `bot` 中。\n\n- 注 1：[`__name__` 是 Python 中一个特殊的变量，表示当前模块的全限定名称](https://docs.python.org/zh-cn/3/reference/import.html#name__)。\n- 注 2：在 Python 中不以下划线开头的变量为模块的 public 成员，另一种做法是在模块中用 `__all__` 列出所有 public 成员的名字。\n\n一个合法的 handler 函数需要返回 `str` 或 `MessageChain` 或 `None`。Handler 函数既可以是同步函数也可以是异步函数。\n\n```python\nfrom lightq import message_handler\nfrom lightq.entities import FriendMessage, MessageChain, Plain\n\n@message_handler(FriendMessage)\nasync def say_hello() -\u003e MessageChain:  # 一个返回 MessageChain 的异步函数\n    await asyncio.sleep(1)\n    return MessageChain([Plain('Hello')])\n```\n\n## 过滤器\n\n如何实现 handler 的有条件执行？需要使用过滤器。我们继续改进之前的 `say_hello`：\n\n```python\nfrom lightq import RecvContext\n\ndef condition(context: RecvContext) -\u003e bool:\n    return str(context.data.message_chain) == 'Hello'\n\n@message_handler(FriendMessage, filters=condition)\ndef say_hello() -\u003e str:\n    \"\"\"当别人对 bot 说 Hello 时回复 Hello\"\"\"\n    return 'Hello'\n```\n\n上面代码中的 `condition` 函数就是一个过滤器。`lightq.filters` 模块提供了一些现成的过滤器，可以直接使用。让我们再修改一下 `say_hello`，为它设置两个条件：\n\n```python\nfrom lightq import RecvContext, filters\n\ndef condition(context: RecvContext) -\u003e bool:\n    return str(context.data.message_chain) == 'Hello'\n\n@message_handler(FriendMessage, filters=[filters.from_user(987654321), condition])\ndef say_hello() -\u003e str:\n    \"\"\"当 QQ 号为 987654321 的用户对 bot 说 Hello 时回复 Hello\"\"\"\n    return 'Hello'\n```\n\n## 参数解析\n### 基于类型的参数解析\n\n如果你用过 Spring Boot 之类的 Web 框架，对于参数解析这个概念应该不会陌生。LightQ 框架支持基于类型和基于函数两种参数解析机制。下面这个示例展示了如何使用基于类型的参数解析：\n\n```python\nfrom lightq.entities import GroupMessage, MessageChain\n\n@message_handler(GroupMessage)\ndef group_message_handler(chain: MessageChain):\n    print(f'收到一条群组消息，内容为：{chain}')\n```\n\n注意到 `group_message_handler` 函数带有参数类型注解 `chain: MessageChain`，这个类型注解是不可或缺的。LightQ 框架使用 Python 的内省 (inspect) 机制获取 `chain` 参数的类型，接收到消息后解析出消息链对象，再自动地将消息链对象注入 `chain` 参数中。\n\n参数解析机制的一个重要用途是在 handler 内获取 bot 的引用，并直接调用 bot 对象上的方法：\n\n```python\n@event_handler(NudgeEvent)\nasync def nudge_response(event: NudgeEvent, bot: Bot):\n    \"\"\"谁拍一拍我，我就拍一拍谁\"\"\"\n    if (event.subject.kind == 'Group'\n        and event.target == bot.bot_id\n        and event.from_id != bot.bot_id):\n        await bot.api.send_nudge(event.from_id, event.subject.id, 'Group')\n```\n\nLightQ 框架支持自动解析的类型有：\n\n- `Bot`\n- `RecvContext`\n- `ExceptionContext`\n- `MessageChain`\n- `Message` 及其子类\n- `Event` 及其子类\n- `Exception` 及其子类\n\n参数解析机制也支持自定义类型，只需让你自己的类型继承 `lightq.framework` 中的 `FromContext` / `FromRecvContext` / `FromExceptionContext` 抽象类并重写对应的方法即可。\n\n### 基于函数的参数解析\n\n基于类型的参数解析无法覆盖所有场景，例如：希望从群组消息中解析出群号和发送者的 QQ 号，但二者皆为 `int` 类型，仅凭类型无法区分。此时需要使用基于函数的参数解析，请看如下例子：\n\n```python\nfrom lightq import resolvers  # resolvers 是一个模块\nfrom lightq import resolve  # resolve 是一个装饰器\n\n@resolve(resolvers.group_id, member_id=resolvers.sender_id)\n@message_handler(GroupMessage)\ndef group_message_handler(chain: MessageChain, group_id: int, member_id: int):\n    print(f'收到一条群组消息，群号 {group_id}，群员 QQ 号 {member_id}，内容为：{chain}')\n```\n\n`resolvers.group_id` 和 `resolvers.sender_id` 是两个类型为 `(RecvContext) -\u003e int` 的函数，分别从 `RecvContext` 对象中解析出发送者的群号和 QQ 号，再配合 `resolve` 装饰器就可以实现参数解析和自动注入的效果。\n\n使用 `resolve` 装饰器时，若以普通方式传参（上面的 `@resolve(resolvers.group_id)`），则根据解析器的 `__name__` 属性注入同名的参数（[Python 函数的 `__name__` 属性默认为该函数的名字](https://docs.python.org/zh-cn/3/library/stdtypes.html#definition.__name__)）；若以命名参数的方式传参（上面的 `@resolve(member_id=resolvers.sender_id)`），则表示手动指定注入参数。\n\n本节的示例代码放在 [examples/resolver_example.py](./examples/resolver_example.py) 中。\n\n## 正则表达式\n\n`lightq.decorators` 模块中有三个很实用的装饰器：`regex_match`、`regex_search`、`regex_fullmatch`，分别对应 Python 标准库中的 `re.match`, `re.search`, `re.fullmatch`，可以通过正则表达式匹配消息的内容。\n\n```python\nimport re\nfrom lightq.decorators import regex_match\n\n@regex_match('(?P\u003cfirst_group\u003e\\w+) (?P\u003csecond_group\u003e\\w+)')\n@message_handler(GroupMessage)\ndef handler(first_group: str, second_group: str, match: re.Match):\n    assert first_group == match['first_group']\n    assert second_group == match['second_group']\n```\n\n正则表达式中形如 `(?P\u003cname\u003e...)` 的是命名组语法。如果消息的内容与正则表达式相匹配，那么将捕获的组按照组的名字注入到 handler 的同名参数中，匹配对象将自动注入到类型为 `re.Match` 的参数中。\n\n正则表达式装饰器可以用来构建 QQ 机器人的指令系统：\n\n```python\n@regex_fullmatch(r'/mute\\s+(?P\u003cmember_id\u003e\\d+)\\s+(?P\u003cduration\u003e\\d+)')\n@resolve(resolvers.group_id)\n@message_handler(GroupMessage)\nasync def mute_command(group_id: int, member_id: str, duration: str, bot: Bot):\n    \"\"\"\n    输入 /mute member_id duration 命令以禁言用户\n\n    :param group_id: 群号\n    :param member_id: 被禁言的用户\n    :param duration: 禁言时长（秒）\n    \"\"\"\n    await bot.api.mute(group_id, int(member_id), int(duration))\n```\n\n`regex_match` 的实现非常简单，其原理是将过滤器和解析器构造出来插入 handler 中，并不需要引入额外的组件。你可以在 [src/lightq/decorators/_regex.py](./src/lightq/decorators/_regex.py) 找到其源代码。\n\n## 设置 handler 的优先级\n\n若不显式地指定 handler 间的优先关系，则机器人遍历各个 handler 的顺序是不确定的，这有时候会带来问题。以下是一个复读机程序，可通过“开始复读”和“停止复读”命令来开关复读功能。\n\n```python\nrepeat_on = False\n\n@regex_fullmatch('(?P\u003coption\u003e开始|停止)复读')\n@message_handler(GroupMessage)\ndef switch(option: str):\n    global repeat_on\n    repeat_on = option == '开始'\n    return f'复读已{option}'\n\n@message_handler(GroupMessage, filters=lambda ctx: repeat_on, after=switch)\ndef repeat(chain: MessageChain) -\u003e MessageChain:\n    return chain\n```\n\n注意到，必须先判断 `switch` 的条件是否满足，然后再判断 `repeat` 的条件是否满足，即 `switch` 的优先级必须高于 `repeat`，否则当用户输入“停止复读”时，程序会继续复读“停止复读”这句话而不是把复读功能关掉。\n\n怎样设置 handler 的优先级呢？有三种方法：\n\n- 使用装饰器的 `after` 参数或 `before` 参数，如上述代码在 `repeat` 的装饰器中设置了 `after=switch`。\n- 直接修改 handler 的 `after` 或 `before` 属性，如 `repeat.after.append(switch)`。\n- 使用 `Bot` 的 `add_order` 方法，如 `bot.add_order(switch, repeat)`。\n\n本节的示例代码放在 [examples/repeater.py](./examples/repeater.py) 中。\n\n## 使用 controller\n\n以上示例中所有的 bot 都是“一问一答”型，而一个具备连续对话能力的 bot 看起来会更加有趣：\n\n```text\n群友 1：/weather\nBot：您想查询哪个城市的天气？\n群友 1：武汉\nBot：武汉的天气为小雨\n\n群友 2：/mute_all\nBot：您确定要开启全员禁言吗？请回复“是”或“否”\n群友 2：no\nBot：请回复“是”或“否”\n群友 2：否\n```\n\n若要实现连续对话功能，就必须保存每个用户的状态。可以将状态保存到全局变量中。如果你不喜欢全局变量这种代码风格，也可以将状态封装到 controller 类中统一管理。Controller 类的编写方法如下：\n\n```python\nclass MyController(lightq.Controller):  # 继承 lightq.Controller 类\n    def __init__(self):\n        self.status = ...  # 将状态作为成员变量封装到 controller 中\n\n    # 过滤器方法\n    def condition(self, context: RecvContext) -\u003e bool: ...\n\n    # 解析器方法\n    def resolver(self, context: RecvContext): ...\n\n    # 用 message_handler 装饰器将 my_handler 方法转换为消息处理器\n    # - 使用 condition 方法作为过滤条件\n    # - 使用 resolver 方法作为参数解析器\n    @resolve(data=resolver)\n    @message_handler(Message, filters=condition)\n    def my_handler(self, data):\n        self.status  # 在方法内部可以通过 self 引用保存的状态\n        ...\n\ncontroller = MyController()\n# 通过 handlers 方法获取所有 public 的 handler\nbot.add_all(controller.handlers())\n# ...\n```\n\n[examples/assistant.py](./examples/assistant.py) 提供了一个完整的 controller 示例，实现了一个支持 `/weather` 和 `/mute_all` 命令的机器人。\n\n此外，你还可以用 `handler_property` 装饰器将属性方法转换为处理器，示例代码见 [examples/assistant_property_style.py](./examples/assistant_property_style.py).\n\n## 其他功能\n### 定时任务、后台任务\n\n相关的函数和方法：\n\n- `asyncio.sleep`：可用于延迟执行等场景。\n- `lightq.utils.sleep_until`：延迟到某个时刻执行，该函数是对 `asyncio.sleep` 的简单封装。\n- `Bot.create_task`：创建后台任务，该方法是对 `asyncio.create_task` 的简单封装。\n- `Bot.create_everyday_task`：创建每日定时任务。\n\n### 日志\n\nLightQ 使用 Python 标准库中的 `logging` 模块来打印日志，可通过 `lightq.logger` 获得 logger 对象。默认的日志打印级别为 INFO。\n\n### 自定义路由\n\nLightQ 默认的路由会根据消息/事件/异常的类型将数据送给指定的 handler。你也可以根据实际场景设计更高效的路由机制。继承 `MessageRouter` / `EventRouter` / `ExceptionRouter` 抽象类（位于 `lightq.framework` 模块中）并重写对应的方法以实现自定义路由机制。\n\n# 未来\n\n（可能是）将来的一些工作：\n\n- 完善文档\n- 支持文件操作\n- 补齐剩余的 API 功能\n- 中间件/钩子函数？\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhb2000%2Flightq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzhb2000%2Flightq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhb2000%2Flightq/lists"}