Skip to content

Commit

Permalink
version 0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
zhb2000 committed Nov 21, 2022
1 parent fb84d43 commit 08ec9e6
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 38 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
# 更新日志
## [0.3.0] - 2022-11-21
### 新增

`MiraiApi` 新增 `bot_list` 方法,用于获取已登录的 QQ 号。

### 修复

修复了 `MiraiApi` 不能多次 `close``connect` 的问题。

### 变更

依赖的 mirai-api-http 版本从 2.5.x 升级至 2.6.x。

由于升级后 mirai-api-http 的接口发生了变更,因此 `MiraiApi` 类的一些方法的参数发生了变化:

- `message_from_id`
- `recall`
- `set_essence`

## [0.2.0] - 2022-09-08
### 新增

Expand Down
58 changes: 39 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LightQ

![PyPI](https://img.shields.io/pypi/v/lightq?logo=pypi&logoColor=white) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/lightq?logo=python&logoColor=white) ![mirai-api-http version](https://img.shields.io/badge/mirai--api--http-v2.5.2-blue) ![PyPI - License](https://img.shields.io/pypi/l/lightq)
![PyPI](https://img.shields.io/pypi/v/lightq?logo=pypi&logoColor=white) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/lightq?logo=python&logoColor=white) ![mirai-api-http version](https://img.shields.io/badge/mirai--api--http-v2.6.2-blue) ![PyPI - License](https://img.shields.io/pypi/l/lightq)

LightQ 是一个基于 [mirai-api-http](https://github.com/project-mirai/mirai-api-http) 的 QQ 机器人框架。

Expand All @@ -25,9 +25,9 @@ pip install .
环境要求:

- Python 3.10
- mirai-api-http 2.5.2
- mirai-api-http 2.6.2

LightQ 需要借助 Web API 调用 Mirai 的功能,因此请先安装并配置好 [Mirai Console Loader](https://github.com/iTXTech/mirai-console-loader)[mirai-api-http](https://github.com/project-mirai/mirai-api-http) 插件:
LightQ 需要借助网络 API 调用 Mirai 的功能,因此请先安装并配置好 [Mirai Console Loader](https://github.com/iTXTech/mirai-console-loader)[mirai-api-http](https://github.com/project-mirai/mirai-api-http) 插件:

1. 安装 [Mirai Console Loader (MCL)](https://github.com/iTXTech/mirai-console-loader)
1. 在 MCL 中配置 QQ 账号和密码,确保能正常登录账号,中途可能需要使用 [TxCaptchaHelper](https://github.com/mzdluo123/TxCaptchaHelper) 应对滑动验证码。
Expand Down Expand Up @@ -61,12 +61,16 @@ if __name__ == '__main__':

`message_handler` 装饰器将 `say_hello` 函数包装为一个 `MessageHandler` 对象,该消息处理器只会响应好友消息 `FriendMessage`。LightQ 还提供了 `event_handler``exception_handler` 装饰器,分别用于创建事件处理器和异常处理器。

`bot.add_all(scan_handlers(__name__))` 的作用是获取当前模块中所有 public 的 handler,并将它们添加到 `bot` 中。注 1:[`__name__` 是 Python 中一个特殊的变量,表示当前模块的全限定名称](https://docs.python.org/zh-cn/3/reference/import.html#name__)。注 2:在 Python 中不以下划线开头的变量为模块的 public 成员,另一种做法是在模块中用 `__all__` 列出所有 public 成员的名字。
`bot.add_all(scan_handlers(__name__))` 的作用是获取当前模块中所有 public 的 handler,并将它们添加到 `bot` 中。

- 注 1:[`__name__` 是 Python 中一个特殊的变量,表示当前模块的全限定名称](https://docs.python.org/zh-cn/3/reference/import.html#name__)
- 注 2:在 Python 中不以下划线开头的变量为模块的 public 成员,另一种做法是在模块中用 `__all__` 列出所有 public 成员的名字。

一个合法的 handler 函数需要返回 `str``MessageChain``None`。Handler 函数既可以是同步函数也可以是异步函数。

```python
from lightq.entities import MessageChain, Plain
from lightq import message_handler
from lightq.entities import FriendMessage, MessageChain, Plain

@message_handler(FriendMessage)
async def say_hello() -> MessageChain: # 一个返回 MessageChain 的异步函数
Expand All @@ -93,7 +97,10 @@ def say_hello() -> str:
上面代码中的 `condition` 函数就是一个过滤器。`lightq.filters` 模块提供了一些现成的过滤器,可以直接使用。让我们再修改一下 `say_hello`,为它设置两个条件:

```python
from lightq import filters
from lightq import RecvContext, filters

def condition(context: RecvContext) -> bool:
return str(context.data.message_chain) == 'Hello'

@message_handler(FriendMessage, filters=[filters.from_user(987654321), condition])
def say_hello() -> str:
Expand All @@ -102,6 +109,7 @@ def say_hello() -> str:
```

### 参数解析
#### 基于类型的参数解析

如果你用过 Spring Boot 之类的 Web 框架,对于参数解析这个概念应该不会陌生。LightQ 框架支持基于类型和基于函数两种参数解析机制。下面这个示例展示了如何使用基于类型的参数解析:

Expand All @@ -115,7 +123,31 @@ def group_message_handler(chain: MessageChain):

注意到 `group_message_handler` 函数带有参数类型注解 `chain: MessageChain`,这个类型注解是不可或缺的。LightQ 框架使用 Python 的内省 (inspect) 机制获取 `chain` 参数的类型,接收到消息后解析出消息链对象,再自动地将消息链对象注入 `chain` 参数中。

LightQ 框架支持自动解析的类型有:`Bot``RecvContext``ExceptionContext``MessageChain``Message` 及其子类、`Event` 及其子类、`Exception` 及其子类。参数解析机制也支持自定义类型,只需让你自己的类型继承 `lightq.framework` 中的 `FromContext` / `FromRecvContext` / `FromExceptionContext` 抽象类并重写对应的方法即可。
参数解析机制的一个重要用途是在 handler 内获取 bot 的引用,并直接调用 bot 对象上的方法:

```python
@event_handler(NudgeEvent)
async def nudge_response(event: NudgeEvent, bot: Bot):
"""谁拍一拍我,我就拍一拍谁"""
if (event.subject.kind == 'Group'
and event.target == bot.bot_id
and event.from_id != bot.bot_id):
await bot.api.send_nudge(event.from_id, event.subject.id, 'Group')
```

LightQ 框架支持自动解析的类型有:

- `Bot`
- `RecvContext`
- `ExceptionContext`
- `MessageChain`
- `Message` 及其子类
- `Event` 及其子类
- `Exception` 及其子类

参数解析机制也支持自定义类型,只需让你自己的类型继承 `lightq.framework` 中的 `FromContext` / `FromRecvContext` / `FromExceptionContext` 抽象类并重写对应的方法即可。

#### 基于函数的参数解析

基于类型的参数解析无法覆盖所有场景,例如:希望从群组消息中解析出群号和发送者的 QQ 号,但二者皆为 `int` 类型,仅凭类型无法区分。此时需要使用基于函数的参数解析,请看如下例子:

Expand All @@ -133,18 +165,6 @@ def group_message_handler(chain: MessageChain, group_id: int, member_id: int):

使用 `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)`),则表示手动指定注入参数。

参数解析机制的一个重要用途是在 handler 内获取 bot 的引用,并直接调用 bot 对象上的方法。详见下述示例:

```python
@event_handler(NudgeEvent)
async def nudge_response(event: NudgeEvent, bot: Bot):
"""谁拍一拍我,我就拍一拍谁"""
if (event.subject.kind == 'Group'
and event.target == bot.bot_id
and event.from_id != bot.bot_id):
await bot.api.send_nudge(event.from_id, event.subject.id, 'Group')
```

本节的示例代码放在 [examples/resolver_example.py](./examples/resolver_example.py) 中。

### 正则表达式
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "lightq"
version = "0.2.1.dev0"
version = "0.3.0"
authors = [{ name = "ZHB", email = "[email protected]" }]
description = "An elegant QQ bot framework based on mirai-api-http."
readme = "README.md"
Expand Down
40 changes: 22 additions & 18 deletions src/lightq/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,17 @@ def session_key(self) -> str | None: return self.__session_key
async def send(self, data: dict[str, Any]) -> dict[str, Any]:
"""
Mirai-api-http 传入格式:
::
{
"syncId": 123, // 消息同步的字段
"command": "sendFriendMessage", // 命令字
"subCommand": null, // 子命令字, 可空
"content": {} // 命令的数据对象, 与通用接口定义相同
}
``syncId`` 由 ``send`` 方法自动生成,无需放在 JSON 中。
```json
{
"syncId": 123, // 消息同步的字段
"command": "sendFriendMessage", // 命令字
"subCommand": null, // 子命令字, 可空
"content": {} // 命令的数据对象, 与通用接口定义相同
}
```
JSON 的 `syncId` 字段由 `send` 方法自动生成,无需传入。
:returns: 若状态码为 0 则将响应的 JSON 返回
:raises:
Expand All @@ -162,13 +164,15 @@ async def send(self, data: dict[str, Any]) -> dict[str, Any]:
async def recv(self) -> Message | Event | SyncMessage | UnsupportedEntity:
"""
Mirai-api-http 推送格式:
::
{
"syncId": "123", // 消息同步的字段
"data": {} // 推送消息内容, 与通用接口定义相同
}
``recv`` 方法返回其中的 ``data`` 部分。
```json
{
"syncId": "123", // 消息同步的字段
"data": {} // 推送消息内容, 与通用接口定义相同
}
```
本方法会返回 JSON 的 `data` 部分。
:raises websockets.exception.WebSocketException: WebSocket 连接被关闭或出错时抛出
"""
Expand Down Expand Up @@ -211,7 +215,7 @@ async def __working_method(self):
await ws.close() # the close method is idempotent

async def connect(self):
"""The ``connect`` method is idempotent."""
"""与 mirai-api-http 建立连接。如果连接已经建立,则什么也不做。"""
if self.__ws is not None:
return
encoded_key = urllib.parse.quote_plus(self.verify_key)
Expand All @@ -229,7 +233,7 @@ def remove_working_task(task):
self.__working_task.add_done_callback(remove_working_task)

async def close(self):
"""The ``close`` method is idempotent."""
"""断开与 mirai-api-http 的连接。如果连接已经断开,则什么也不做。"""
if self.__ws is None:
return
await self.__ws.close()
Expand Down Expand Up @@ -267,10 +271,10 @@ async def send_command(

async def message_from_id(self, message_id: int, friend_or_group_id: int) -> Message | None:
"""
通过 message id 获取消息,当该 message id 没有被缓存或缓存失效时返回 None
通过 message id 获取消息。若该 message id 没有被缓存或缓存失效则返回 `None`。
:param message_id: 获取消息的 message id
:param friend_or_group_id: 好友 QQ 号或群 id
:param friend_or_group_id: 好友 QQ 号或群号
"""
try:
return Message.from_json((await self.send_command('messageFromId', {
Expand Down
1 change: 1 addition & 0 deletions src/lightq/framework/_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ async def run(self):
context = RecvContext(self, data)
background_func = self.__make_background_func(context)
self.create_task(background_func())
await asyncio.sleep(0)
finally:
await self.close()

Expand Down

0 comments on commit 08ec9e6

Please sign in to comment.