-
-
Notifications
You must be signed in to change notification settings - Fork 640
Open
Labels
enhancementNew feature or requestNew feature or request
Description
希望能解决的问题
Nonebot现在的适配器很多,但是基本上都是各写各的,没有相对的统一结构,甚至存在代码质量的问题(e.g. Bilibili适配器)。
在这份issue中,我们提出了一个通用的适配器规范,其不会影响适配器的平台特性,并且能为用户提供更好的使用体验。
描述所需要的功能
以下规范依据文件名划分
adapter.py
- 对于网络请求,适配器不应当依赖特定的网络层框架/库(e.g. httpx)。
- 适配器的网络请求或 ws 连接应通过驱动器的 Request 进行。若存在所使用的驱动器无法满足平台条件的情况,[1] 条可忽略。
- 基于 [1][2],适配器应在 setup 阶段检查驱动器类型是否符合要求:
def setup(self) -> None: if not isinstance(self.driver, HTTPClientMixin): raise RuntimeError( f"Current driver {self.config.driver} does not support http client requests! " f"{adapter} Adapter need a HTTPClient Driver to work." ) if not isinstance(self.driver, WebSocketClientMixin): raise RuntimeError( f"Current driver {self.config.driver} does not support websocket client! " f"{adapter} Adapter need a WebSocketClient Driver to work." ) self.driver.on_ready(self.prepare_connection) self.driver.on_shutdown(self.close_connection)
- 对于网络请求,若请求时出现异常,适配器应抛出 NetworkError;若请求的响应状态码异常(e.g. 404),适配器应根据情况抛出对应的异常。
exception.py
- 基于[4],适配器应继承
nonebot.exception中的基础异常类型,声明适配器特定异常:import json from typing import Optional from nonebot.drivers import Response from nonebot.exception import AdapterException from nonebot.exception import ActionFailed as BaseActionFailed from nonebot.exception import NetworkError as BaseNetworkError from nonebot.exception import ApiNotAvailable as BaseApiNotAvailable class XXXAdapterException(AdapterException): def __init__(self): super().__init__("xxx") class NetworkError(BaseNetworkError, XXXAdapterException): def __init__(self, msg: Optional[str] = None): super().__init__() self.msg: Optional[str] = msg """错误原因""" def __repr__(self): return f"<NetWorkError message={self.msg}>" def __str__(self): return self.__repr__() class ActionFailed(BaseActionFailed, XXXAdapterException): def __init__(self, response: Response): self.status_code: int = response.status_code self.code: Optional[int] = ... self.message: Optional[str] = ... self.data: Optional[dict] = ... class UnauthorizedException(ActionFailed): pass class RateLimitException(ActionFailed): pass class ApiNotAvailable(BaseApiNotAvailable, XXXAdapterException): pass
event.py
-
Event应存在time字段,表示事件创建的时间。time 为datetime.datetime类型。 -
MessageEvent应存在如下字段:to_me: bool 类型,可选(因为主要是 is_tome 方法)。reply: 一般为 reply 对应的原始消息/原始事件(由 reply 上的 msgid获取);同时也可以为自定义结构(e.g. ob12适配器下的Reply),但是应当挂载一个async def get_origin()方法,以获取原始事件;若平台不存在回复元素,置空即可。message/_message: 适配器对应的 Message 类型。若原事件已存在 message字段并无法转换类型,则使用 _messageoriginal_message: 适配器对应的 Message 类型,并且未经过 check_at_me, check_reply 等处理。message_id: 消息id (有时与事件id等同),用于构造回复消息,撤回消息,编辑消息等操作;若平台不存在消息id,使用 "" 或随机生成即可。
其中
_message,original_message可如下处理:from copy import deepcopy from typing import TYPE_CHECKING from .message import Message class MessageEvent(Event): if TYPE_CHECKING: message: Message original_message: Message def get_message(self): if not hasattr(self, "message"): msg = Message(xxx) setattr(self, "message", msg) setattr(self, "original_message", deepcopy(msg)) return getattr(self, "message")
bot.py
-
适配器应当在 handle_event内执行 check_reply, check_at_me, check_nickname。
- check_reply: 检查消息序列或事件中是否存在 reply 数据,无论是否回复 bot 自己,有则移除 reply 消息段,并将 reply 对应的事件赋值给
reply属性。在此之上,若回复的是 bot 自己的消息,则设 to_me 为真。 - check_at_me:检查消息首部或尾部是否是 @bot_self, 如果是则移除,并且连带@后随的空格一并移除,设 to_me 为真。尾部 at 不强制要求。
- check_nickname: 如果机器人配置了nickname,则检查消息首部是否为昵称,同 check_at_me。
- check_reply: 检查消息序列或事件中是否存在 reply 数据,无论是否回复 bot 自己,有则移除 reply 消息段,并将 reply 对应的事件赋值给
-
适配器应当在 Bot 上编写常用的方法/接口,并写明每个接口的输入类型和输出类型。如果对接协议不存在或未允许扩展api,请将Bot的__getattr__方法明确写为不支持(
def __getattr__(self, item): raise NotImplementError);否则需要编写 bot.pyi 或生成全部的可用方法,而不是完全让用户使用call_api。 -
Bot 应声明自己的 adapter属性为适配器对应的 Adapter 类型:
from typing import TYPE_CHECKING, override from nonebot.adapters import Bot as BaseBot if TYPE_CHECKING: from .adapter import Adapter class Bot(BaseBot): adapter: "Adapter" @override def __init__(self, adapter: "Adapter", self_id: str, **kwargs): ...
message.py
- 适配器应在 message.py 内编写原始数据转为消息序列的方法(e.g. Message.from_guild_message)。
11.1. 适配器应尽最大努力将原始数据格式转为消息序列 (e.g. xml -> json) - 消息段应尽量使用子类消息段+父类消息段静态方法合集:
class MessageSegment(BaseMessageSegment): @staticmethod def text(text: str) -> "Text": return Text("text", {"text": text}) class Text(MessageSegment): @override def __str__(self): return self.data["text"]
MessageSegment.text的 data 必须为{"text": xxx}。- 对于某些平台存在元素包含特殊子元素的情况(例如,kook平台的 KMarkdown 包含 mention, emoji等子元素),适配器应特殊处理,将这些子元素提取为单独的消息段。
utils.py
- 适配器的日志部分应使用 logger_wrapper 返回的 log 进行:
from nonebot.utils import logger_wrapper log = logger_wrapper("XXX Adapter")
README.md
- 适配器的 README 应当写明自己的配置项,至少需要说明自己需要的驱动器类型。
pyproject.toml
- 适配器的最低 python 依赖版本应跟随 nonebot2 要求的最低版本。
GreyElaina, mnixry, he0119, CMHopeSunshine, IllTamer and 2 more5656565566
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request