♻️ add adapters/cqhttp directory

This commit is contained in:
Artin
2020-11-10 17:34:02 +08:00
parent 09a511acf8
commit c3c2c7935d
2 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,857 @@
"""
CQHTTP (OneBot) v11 协议适配
============================
协议详情请看: `CQHTTP`_ | `OneBot`_
.. _CQHTTP:
http://cqhttp.cc/
.. _OneBot:
https://github.com/howmanybots/onebot
"""
import re
import sys
import asyncio
import httpx
from nonebot.log import logger
from nonebot.config import Config
from nonebot.message import handle_event
from nonebot.typing import Any, Dict, Union, Tuple, Iterable, Optional
from nonebot.exception import NetworkError, ActionFailed, ApiNotAvailable
from nonebot.typing import overrides, Driver, WebSocket, NoReturn
from nonebot.adapters import BaseBot, BaseEvent, BaseMessage, BaseMessageSegment
def log(level: str, message: str):
"""
:说明:
用于打印 CQHTTP 日志。
:参数:
* ``level: str``: 日志等级
* ``message: str``: 日志信息
"""
return logger.opt(colors=True).log(level, "<m>CQHTTP</m> | " + message)
def escape(s: str, *, escape_comma: bool = True) -> str:
"""
:说明:
对字符串进行 CQ 码转义。
:参数:
* ``s: str``: 需要转义的字符串
* ``escape_comma: bool``: 是否转义逗号(``,``)。
"""
s = s.replace("&", "&amp;") \
.replace("[", "&#91;") \
.replace("]", "&#93;")
if escape_comma:
s = s.replace(",", "&#44;")
return s
def unescape(s: str) -> str:
"""
:说明:
对字符串进行 CQ 码去转义。
:参数:
* ``s: str``: 需要转义的字符串
"""
return s.replace("&#44;", ",") \
.replace("&#91;", "[") \
.replace("&#93;", "]") \
.replace("&amp;", "&")
def _b2s(b: Optional[bool]) -> Optional[str]:
"""转换布尔值为字符串。"""
return b if b is None else str(b).lower()
async def _check_reply(bot: "Bot", event: "Event"):
"""
:说明:
检查消息中存在的回复,去除并赋值 ``event.reply``, ``event.to_me``
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
"""
if event.type != "message":
return
try:
index = list(map(lambda x: x.type == "reply",
event.message)).index(True)
except ValueError:
return
msg_seg = event.message[index]
event.reply = await bot.get_msg(message_id=msg_seg.data["id"])
if event.reply["sender"]["user_id"] == event.self_id:
event.to_me = True
del event.message[index]
def _check_at_me(bot: "Bot", event: "Event"):
"""
:说明:
检查消息开头或结尾是否存在 @机器人,去除并赋值 ``event.to_me``
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
"""
if event.type != "message":
return
if event.detail_type == "private":
event.to_me = True
else:
at_me_seg = MessageSegment.at(event.self_id)
# check the first segment
first_msg_seg = event.message[0]
if first_msg_seg == at_me_seg:
event.to_me = True
del event.message[0]
if event.message[0].type == "text":
event.message[0].data["text"] = event.message[0].data[
"text"].lstrip()
if not event.message[0].data["text"]:
del event.message[0]
if event.message[0] == at_me_seg:
del event.message[0]
if event.message[0].type == "text":
event.message[0].data["text"] = event.message[0].data[
"text"].lstrip()
if not event.message[0].data["text"]:
del event.message[0]
if not event.to_me:
# check the last segment
i = -1
last_msg_seg = event.message[i]
if last_msg_seg.type == "text" and \
not last_msg_seg.data["text"].strip() and \
len(event.message) >= 2:
i -= 1
last_msg_seg = event.message[i]
if last_msg_seg == at_me_seg:
event.to_me = True
del event.message[i:]
if not event.message:
event.message.append(MessageSegment.text(""))
def _check_nickname(bot: "Bot", event: "Event"):
"""
:说明:
检查消息开头是否存在,去除并赋值 ``event.to_me``
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
"""
if event.type != "message":
return
first_msg_seg = event.message[0]
if first_msg_seg.type != "text":
return
first_text = first_msg_seg.data["text"]
if bot.config.nickname:
# check if the user is calling me with my nickname
if isinstance(bot.config.nickname, str) or \
not isinstance(bot.config.nickname, Iterable):
nicknames = (bot.config.nickname,)
else:
nicknames = filter(lambda n: n, bot.config.nickname)
nickname_regex = "|".join(nicknames)
m = re.search(rf"^({nickname_regex})([\s,]*|$)", first_text,
re.IGNORECASE)
if m:
nickname = m.group(1)
log("DEBUG", f"User is calling me {nickname}")
event.to_me = True
first_msg_seg.data["text"] = first_text[m.end():]
def _handle_api_result(
result: Optional[Dict[str, Any]]) -> Union[Any, NoReturn]:
"""
:说明:
处理 API 请求返回值。
:参数:
* ``result: Optional[Dict[str, Any]]``: API 返回数据
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``ActionFailed``: API 调用失败
"""
if isinstance(result, dict):
if result.get("status") == "failed":
raise ActionFailed(retcode=result.get("retcode"))
return result.get("data")
class ResultStore:
_seq = 1
_futures: Dict[int, asyncio.Future] = {}
@classmethod
def get_seq(cls) -> int:
s = cls._seq
cls._seq = (cls._seq + 1) % sys.maxsize
return s
@classmethod
def add_result(cls, result: Dict[str, Any]):
if isinstance(result.get("echo"), dict) and \
isinstance(result["echo"].get("seq"), int):
future = cls._futures.get(result["echo"]["seq"])
if future:
future.set_result(result)
@classmethod
async def fetch(cls, seq: int, timeout: Optional[float]) -> Dict[str, Any]:
future = asyncio.get_event_loop().create_future()
cls._futures[seq] = future
try:
return await asyncio.wait_for(future, timeout)
except asyncio.TimeoutError:
raise NetworkError("WebSocket API call timeout") from None
finally:
del cls._futures[seq]
class Bot(BaseBot):
"""
CQHTTP 协议 Bot 适配。继承属性参考 `BaseBot <./#class-basebot>`_ 。
"""
def __init__(self,
driver: Driver,
connection_type: str,
config: Config,
self_id: str,
*,
websocket: Optional[WebSocket] = None):
if connection_type not in ["http", "websocket"]:
raise ValueError("Unsupported connection type")
super().__init__(driver,
connection_type,
config,
self_id,
websocket=websocket)
@property
@overrides(BaseBot)
def type(self) -> str:
"""
- 返回: ``"cqhttp"``
"""
return "cqhttp"
@overrides(BaseBot)
async def handle_message(self, message: dict):
"""
:说明:
调用 `_check_reply <#async-check-reply-bot-event>`_, `_check_at_me <#check-at-me-bot-event>`_, `_check_nickname <#check-nickname-bot-event>`_ 处理事件并转换为 `Event <#class-event>`_
"""
if not message:
return
if "post_type" not in message:
ResultStore.add_result(message)
return
try:
event = Event(message)
# Check whether user is calling me
await _check_reply(self, event)
_check_at_me(self, event)
_check_nickname(self, event)
await handle_event(self, event)
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Failed to handle event. Raw: {message}</bg #f8bbd0></r>"
)
@overrides(BaseBot)
async def call_api(self, api: str, **data) -> Union[Any, NoReturn]:
"""
:说明:
调用 CQHTTP 协议 API
:参数:
* ``api: str``: API 名称
* ``**data: Any``: API 参数
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
if "self_id" in data:
self_id = data.pop("self_id")
if self_id:
bot = self.driver.bots[str(self_id)]
return await bot.call_api(api, **data)
log("DEBUG", f"Calling API <y>{api}</y>")
if self.connection_type == "websocket":
seq = ResultStore.get_seq()
await self.websocket.send({
"action": api,
"params": data,
"echo": {
"seq": seq
}
})
return _handle_api_result(await ResultStore.fetch(
seq, self.config.api_timeout))
elif self.connection_type == "http":
api_root = self.config.api_root.get(self.self_id)
if not api_root:
raise ApiNotAvailable
elif not api_root.endswith("/"):
api_root += "/"
headers = {}
if self.config.access_token is not None:
headers["Authorization"] = "Bearer " + self.config.access_token
try:
async with httpx.AsyncClient(headers=headers) as client:
response = await client.post(
api_root + api,
json=data,
timeout=self.config.api_timeout)
if 200 <= response.status_code < 300:
result = response.json()
return _handle_api_result(result)
raise NetworkError(f"HTTP request received unexpected "
f"status code: {response.status_code}")
except httpx.InvalidURL:
raise NetworkError("API root url invalid")
except httpx.HTTPError:
raise NetworkError("HTTP request failed")
@overrides(BaseBot)
async def send(self,
event: "Event",
message: Union[str, "Message", "MessageSegment"],
at_sender: bool = False,
**kwargs) -> Union[Any, NoReturn]:
"""
:说明:
根据 ``event`` 向触发事件的主体发送消息。
:参数:
* ``event: Event``: Event 对象
* ``message: Union[str, Message, MessageSegment]``: 要发送的消息
* ``at_sender: bool``: 是否 @ 事件主体
* ``**kwargs``: 覆盖默认参数
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``ValueError``: 缺少 ``user_id``, ``group_id``
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
msg = message if isinstance(message, Message) else Message(message)
at_sender = at_sender and bool(event.user_id)
params = {}
if event.user_id:
params["user_id"] = event.user_id
if event.group_id:
params["group_id"] = event.group_id
params.update(kwargs)
if "message_type" not in params:
if "group_id" in params:
params["message_type"] = "group"
elif "user_id" in params:
params["message_type"] = "private"
else:
raise ValueError("Cannot guess message type to reply!")
if at_sender and params["message_type"] != "private":
params["message"] = MessageSegment.at(params["user_id"]) + \
MessageSegment.text(" ") + msg
else:
params["message"] = msg
return await self.send_msg(**params)
class Event(BaseEvent):
"""
CQHTTP 协议 Event 适配。继承属性参考 `BaseEvent <./#class-baseevent>`_ 。
"""
def __init__(self, raw_event: dict):
if "message" in raw_event:
raw_event["message"] = Message(raw_event["message"])
super().__init__(raw_event)
@property
@overrides(BaseEvent)
def id(self) -> Optional[int]:
"""
- 类型: ``Optional[int]``
- 说明: 事件/消息 ID
"""
return self._raw_event.get("message_id") or self._raw_event.get("flag")
@property
@overrides(BaseEvent)
def name(self) -> str:
"""
- 类型: ``str``
- 说明: 事件名称,由类型与 ``.`` 组合而成
"""
n = self.type + "." + self.detail_type
if self.sub_type:
n += "." + self.sub_type
return n
@property
@overrides(BaseEvent)
def self_id(self) -> str:
"""
- 类型: ``str``
- 说明: 机器人自身 ID
"""
return str(self._raw_event["self_id"])
@property
@overrides(BaseEvent)
def time(self) -> int:
"""
- 类型: ``int``
- 说明: 事件发生时间
"""
return self._raw_event["time"]
@property
@overrides(BaseEvent)
def type(self) -> str:
"""
- 类型: ``str``
- 说明: 事件类型
"""
return self._raw_event["post_type"]
@type.setter
@overrides(BaseEvent)
def type(self, value) -> None:
self._raw_event["post_type"] = value
@property
@overrides(BaseEvent)
def detail_type(self) -> str:
"""
- 类型: ``str``
- 说明: 事件详细类型
"""
return self._raw_event[f"{self.type}_type"]
@detail_type.setter
@overrides(BaseEvent)
def detail_type(self, value) -> None:
self._raw_event[f"{self.type}_type"] = value
@property
@overrides(BaseEvent)
def sub_type(self) -> Optional[str]:
"""
- 类型: ``Optional[str]``
- 说明: 事件子类型
"""
return self._raw_event.get("sub_type")
@sub_type.setter
@overrides(BaseEvent)
def sub_type(self, value) -> None:
self._raw_event["sub_type"] = value
@property
@overrides(BaseEvent)
def user_id(self) -> Optional[int]:
"""
- 类型: ``Optional[int]``
- 说明: 事件主体 ID
"""
return self._raw_event.get("user_id")
@user_id.setter
@overrides(BaseEvent)
def user_id(self, value) -> None:
self._raw_event["user_id"] = value
@property
@overrides(BaseEvent)
def group_id(self) -> Optional[int]:
"""
- 类型: ``Optional[int]``
- 说明: 事件主体群 ID
"""
return self._raw_event.get("group_id")
@group_id.setter
@overrides(BaseEvent)
def group_id(self, value) -> None:
self._raw_event["group_id"] = value
@property
@overrides(BaseEvent)
def to_me(self) -> Optional[bool]:
"""
- 类型: ``Optional[bool]``
- 说明: 消息是否与机器人相关
"""
return self._raw_event.get("to_me")
@to_me.setter
@overrides(BaseEvent)
def to_me(self, value) -> None:
self._raw_event["to_me"] = value
@property
@overrides(BaseEvent)
def message(self) -> Optional["Message"]:
"""
- 类型: ``Optional[Message]``
- 说明: 消息内容
"""
return self._raw_event.get("message")
@message.setter
@overrides(BaseEvent)
def message(self, value) -> None:
self._raw_event["message"] = value
@property
@overrides(BaseEvent)
def reply(self) -> Optional[dict]:
"""
- 类型: ``Optional[dict]``
- 说明: 回复消息详情
"""
return self._raw_event.get("reply")
@reply.setter
@overrides(BaseEvent)
def reply(self, value) -> None:
self._raw_event["reply"] = value
@property
@overrides(BaseEvent)
def raw_message(self) -> Optional[str]:
"""
- 类型: ``Optional[str]``
- 说明: 原始消息
"""
return self._raw_event.get("raw_message")
@raw_message.setter
@overrides(BaseEvent)
def raw_message(self, value) -> None:
self._raw_event["raw_message"] = value
@property
@overrides(BaseEvent)
def plain_text(self) -> Optional[str]:
"""
- 类型: ``Optional[str]``
- 说明: 纯文本消息内容
"""
return self.message and self.message.extract_plain_text()
@property
@overrides(BaseEvent)
def sender(self) -> Optional[dict]:
"""
- 类型: ``Optional[dict]``
- 说明: 消息发送者信息
"""
return self._raw_event.get("sender")
@sender.setter
@overrides(BaseEvent)
def sender(self, value) -> None:
self._raw_event["sender"] = value
class MessageSegment(BaseMessageSegment):
"""
CQHTTP 协议 MessageSegment 适配。具体方法参考协议消息段类型或源码。
"""
@overrides(BaseMessageSegment)
def __init__(self, type: str, data: Dict[str, Any]) -> None:
if type == "text":
data["text"] = unescape(data["text"])
super().__init__(type=type, data=data)
@overrides(BaseMessageSegment)
def __str__(self):
type_ = self.type
data = self.data.copy()
# process special types
if type_ == "text":
return escape(
data.get("text", ""), # type: ignore
escape_comma=False)
params = ",".join(
[f"{k}={escape(str(v))}" for k, v in data.items() if v is not None])
return f"[CQ:{type_}{',' if params else ''}{params}]"
@overrides(BaseMessageSegment)
def __add__(self, other) -> "Message":
return Message(self) + other
@staticmethod
def anonymous(ignore_failure: Optional[bool] = None) -> "MessageSegment":
return MessageSegment("anonymous", {"ignore": _b2s(ignore_failure)})
@staticmethod
def at(user_id: Union[int, str]) -> "MessageSegment":
return MessageSegment("at", {"qq": str(user_id)})
@staticmethod
def contact_group(group_id: int) -> "MessageSegment":
return MessageSegment("contact", {"type": "group", "id": str(group_id)})
@staticmethod
def contact_user(user_id: int) -> "MessageSegment":
return MessageSegment("contact", {"type": "qq", "id": str(user_id)})
@staticmethod
def dice() -> "MessageSegment":
return MessageSegment("dice", {})
@staticmethod
def face(id_: int) -> "MessageSegment":
return MessageSegment("face", {"id": str(id_)})
@staticmethod
def forward(id_: str) -> "MessageSegment":
log("WARNING", "Forward Message only can be received!")
return MessageSegment("forward", {"id": id_})
@staticmethod
def image(file: str,
type_: Optional[str] = None,
cache: bool = True,
proxy: bool = True,
timeout: Optional[int] = None) -> "MessageSegment":
return MessageSegment(
"image", {
"file": file,
"type": type_,
"cache": cache,
"proxy": proxy,
"timeout": timeout
})
@staticmethod
def json(data: str) -> "MessageSegment":
return MessageSegment("json", {"data": data})
@staticmethod
def location(latitude: float,
longitude: float,
title: Optional[str] = None,
content: Optional[str] = None) -> "MessageSegment":
return MessageSegment(
"location", {
"lat": str(latitude),
"lon": str(longitude),
"title": title,
"content": content
})
@staticmethod
def music(type_: str, id_: int) -> "MessageSegment":
return MessageSegment("music", {"type": type_, "id": id_})
@staticmethod
def music_custom(url: str,
audio: str,
title: str,
content: Optional[str] = None,
img_url: Optional[str] = None) -> "MessageSegment":
return MessageSegment(
"music", {
"type": "custom",
"url": url,
"audio": audio,
"title": title,
"content": content,
"image": img_url
})
@staticmethod
def node(id_: int) -> "MessageSegment":
return MessageSegment("node", {"id": str(id_)})
@staticmethod
def node_custom(user_id: int, nickname: str,
content: Union[str, "Message"]) -> "MessageSegment":
return MessageSegment("node", {
"user_id": str(user_id),
"nickname": nickname,
"content": content
})
@staticmethod
def poke(type_: str, id_: str) -> "MessageSegment":
return MessageSegment("poke", {"type": type_, "id": id_})
@staticmethod
def record(file: str,
magic: Optional[bool] = None,
cache: Optional[bool] = None,
proxy: Optional[bool] = None,
timeout: Optional[int] = None) -> "MessageSegment":
return MessageSegment("record", {"file": file, "magic": _b2s(magic)})
@staticmethod
def reply(id_: int) -> "MessageSegment":
return MessageSegment("reply", {"id": str(id_)})
@staticmethod
def rps() -> "MessageSegment":
return MessageSegment("rps", {})
@staticmethod
def shake() -> "MessageSegment":
return MessageSegment("shake", {})
@staticmethod
def share(url: str = "",
title: str = "",
content: Optional[str] = None,
img_url: Optional[str] = None) -> "MessageSegment":
return MessageSegment("share", {
"url": url,
"title": title,
"content": content,
"img_url": img_url
})
@staticmethod
def text(text: str) -> "MessageSegment":
return MessageSegment("text", {"text": text})
@staticmethod
def video(file: str,
cache: Optional[bool] = None,
proxy: Optional[bool] = None,
timeout: Optional[int] = None) -> "MessageSegment":
return MessageSegment("video", {
"file": file,
"cache": cache,
"proxy": proxy,
"timeout": timeout
})
@staticmethod
def xml(data: str) -> "MessageSegment":
return MessageSegment("xml", {"data": data})
class Message(BaseMessage):
"""
CQHTTP 协议 Message 适配。
"""
@staticmethod
@overrides(BaseMessage)
def _construct(msg: Union[str, dict, list]) -> Iterable[MessageSegment]:
if isinstance(msg, dict):
yield MessageSegment(msg["type"], msg.get("data") or {})
return
elif isinstance(msg, list):
for seg in msg:
yield MessageSegment(seg["type"], seg.get("data") or {})
return
def _iter_message(msg: str) -> Iterable[Tuple[str, str]]:
text_begin = 0
for cqcode in re.finditer(
r"\[CQ:(?P<type>[a-zA-Z0-9-_.]+)"
r"(?P<params>"
r"(?:,[a-zA-Z0-9-_.]+=?[^,\]]*)*"
r"),?\]", msg):
yield "text", unescape(msg[text_begin:cqcode.pos +
cqcode.start()])
text_begin = cqcode.pos + cqcode.end()
yield cqcode.group("type"), cqcode.group("params").lstrip(",")
yield "text", unescape(msg[text_begin:])
for type_, data in _iter_message(msg):
if type_ == "text":
if data:
# only yield non-empty text segment
yield MessageSegment(type_, {"text": data})
else:
data = {
k: v for k, v in map(
lambda x: x.split("=", maxsplit=1),
filter(lambda x: x, (
x.lstrip() for x in data.split(","))))
}
yield MessageSegment(type_, data)

View File

@ -0,0 +1,989 @@
import asyncio
from nonebot.config import Config
from nonebot.adapters import BaseBot, BaseEvent, BaseMessage, BaseMessageSegment
from nonebot.typing import Any, Dict, List, Union, Driver, Optional, NoReturn, WebSocket, Iterable
def log(level: str, message: str):
...
def escape(s: str, *, escape_comma: bool = ...) -> str:
...
def unescape(s: str) -> str:
...
def _b2s(b: Optional[bool]) -> Optional[str]:
...
async def _check_reply(bot: "Bot", event: "Event"):
...
def _check_at_me(bot: "Bot", event: "Event"):
...
def _check_nickname(bot: "Bot", event: "Event"):
...
def _handle_api_result(
result: Optional[Dict[str, Any]]) -> Union[Any, NoReturn]:
...
class ResultStore:
_seq: int = ...
_futures: Dict[int, asyncio.Future] = ...
@classmethod
def get_seq(cls) -> int:
...
@classmethod
def add_result(cls, result: Dict[str, Any]):
...
@classmethod
async def fetch(cls, seq: int, timeout: Optional[float]) -> Dict[str, Any]:
...
class Bot(BaseBot):
def __init__(self,
driver: Driver,
connection_type: str,
config: Config,
self_id: str,
*,
websocket: WebSocket = None):
...
def type(self) -> str:
...
async def handle_message(self, message: dict):
...
async def call_api(self, api: str, **data) -> Union[Any, NoReturn]:
...
async def send(self, event: "Event", message: Union[str, "Message",
"MessageSegment"],
**kwargs) -> Union[Any, NoReturn]:
...
async def send_private_msg(self,
*,
user_id: int,
message: Union[str, Message],
auto_escape: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
发送私聊消息。
:参数:
* ``user_id``: 对方 QQ 号
* ``message``: 要发送的内容
* ``auto_escape``: 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 ``message`` 字段是字符串时有效
* ``self_id``: 机器人 QQ 号
"""
...
async def send_group_msg(self,
*,
group_id: int,
message: Union[str, Message],
auto_escape: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
发送群消息。
:参数:
* ``group_id``: 群号
* ``message``: 要发送的内容
* ``auto_escape``: 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 ``message`` 字段是字符串时有效
* ``self_id``: 机器人 QQ 号
"""
...
async def send_msg(self,
*,
message_type: Optional[str] = ...,
user_id: Optional[int] = ...,
group_id: Optional[int] = ...,
message: Union[str, Message],
auto_escape: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
发送消息。
:参数:
* ``message_type``: 消息类型,支持 ``private``、``group``,分别对应私聊、群组、讨论组,如不传入,则根据传入的 ``*_id`` 参数判断
* ``user_id``: 对方 QQ 号(消息类型为 ``private`` 时需要)
* ``group_id``: 群号(消息类型为 ``group`` 时需要)
* ``message``: 要发送的内容
* ``auto_escape``: 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 ``message`` 字段是字符串时有效
* ``self_id``: 机器人 QQ 号
"""
...
async def delete_msg(self,
*,
message_id: int,
self_id: Optional[int] = ...) -> None:
"""
:说明:
撤回消息。
:参数:
* ``message_id``: 消息 ID
* ``self_id``: 机器人 QQ 号
"""
...
async def get_msg(self,
*,
message_id: int,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取消息。
:参数:
* ``message_id``: 消息 ID
* ``self_id``: 机器人 QQ 号
"""
...
async def get_forward_msg(self,
*,
id: int,
self_id: Optional[int] = ...) -> None:
"""
:说明:
获取合并转发消息。
:参数:
* ``id``: 合并转发 ID
* ``self_id``: 机器人 QQ 号
"""
...
async def send_like(self,
*,
user_id: int,
times: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
发送好友赞。
:参数:
* ``user_id``: 对方 QQ 号
* ``times``: 赞的次数,每个好友每天最多 10 次
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_kick(self,
*,
group_id: int,
user_id: int,
reject_add_request: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
群组踢人。
:参数:
* ``group_id``: 群号
* ``user_id``: 要踢的 QQ 号
* ``reject_add_request``: 拒绝此人的加群请求
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_ban(self,
*,
group_id: int,
user_id: int,
duration: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
群组单人禁言。
:参数:
* ``group_id``: 群号
* ``user_id``: 要禁言的 QQ 号
* ``duration``: 禁言时长,单位秒,``0`` 表示取消禁言
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_anonymous_ban(self,
*,
group_id: int,
anonymous: Optional[Dict[str, Any]] = ...,
anonymous_flag: Optional[str] = ...,
duration: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
群组匿名用户禁言。
:参数:
* ``group_id``: 群号
* ``anonymous``: 可选,要禁言的匿名用户对象(群消息上报的 ``anonymous`` 字段)
* ``anonymous_flag``: 可选,要禁言的匿名用户的 flag需从群消息上报的数据中获得
* ``duration``: 禁言时长,单位秒,无法取消匿名用户禁言
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_whole_ban(self,
*,
group_id: int,
enable: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
群组全员禁言。
:参数:
* ``group_id``: 群号
* ``enable``: 是否禁言
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_admin(self,
*,
group_id: int,
user_id: int,
enable: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
群组设置管理员。
:参数:
* ``group_id``: 群号
* ``user_id``: 要设置管理员的 QQ 号
* ``enable``: ``True`` 为设置,``False`` 为取消
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_anonymous(self,
*,
group_id: int,
enable: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
群组匿名。
:参数:
* ``group_id``: 群号
* ``enable``: 是否允许匿名聊天
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_card(self,
*,
group_id: int,
user_id: int,
card: str = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
设置群名片(群备注)。
:参数:
* ``group_id``: 群号
* ``user_id``: 要设置的 QQ 号
* ``card``: 群名片内容,不填或空字符串表示删除群名片
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_name(self,
*,
group_id: int,
group_name: str,
self_id: Optional[int] = ...) -> None:
"""
:说明:
设置群名。
:参数:
* ``group_id``: 群号
* ``group_name``: 新群名
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_leave(self,
*,
group_id: int,
is_dismiss: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
退出群组。
:参数:
* ``group_id``: 群号
* ``is_dismiss``: 是否解散,如果登录号是群主,则仅在此项为 True 时能够解散
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_special_title(self,
*,
group_id: int,
user_id: int,
special_title: str = ...,
duration: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
设置群组专属头衔。
:参数:
* ``group_id``: 群号
* ``user_id``: 要设置的 QQ 号
* ``special_title``: 专属头衔,不填或空字符串表示删除专属头衔
* ``duration``: 专属头衔有效期,单位秒,-1 表示永久,不过此项似乎没有效果,可能是只有某些特殊的时间长度有效,有待测试
* ``self_id``: 机器人 QQ 号
"""
...
async def set_friend_add_request(self,
*,
flag: str,
approve: bool = ...,
remark: str = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
处理加好友请求。
:参数:
* ``flag``: 加好友请求的 flag需从上报的数据中获得
* ``approve``: 是否同意请求
* ``remark``: 添加后的好友备注(仅在同意时有效)
* ``self_id``: 机器人 QQ 号
"""
...
async def set_group_add_request(self,
*,
flag: str,
sub_type: str,
approve: bool = ...,
reason: str = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
处理加群请求/邀请。
:参数:
* ``flag``: 加群请求的 flag需从上报的数据中获得
* ``sub_type``: ``add`` 或 ``invite``,请求类型(需要和上报消息中的 ``sub_type`` 字段相符)
* ``approve``: 是否同意请求/邀请
* ``reason``: 拒绝理由(仅在拒绝时有效)
* ``self_id``: 机器人 QQ 号
"""
...
async def get_login_info(self,
*,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取登录号信息。
:参数:
* ``self_id``: 机器人 QQ 号
"""
...
async def get_stranger_info(self,
*,
user_id: int,
no_cache: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取陌生人信息。
:参数:
* ``user_id``: QQ 号
* ``no_cache``: 是否不使用缓存(使用缓存可能更新不及时,但响应更快)
* ``self_id``: 机器人 QQ 号
"""
...
async def get_friend_list(self,
*,
self_id: Optional[int] = ...
) -> List[Dict[str, Any]]:
"""
:说明:
获取好友列表。
:参数:
* ``self_id``: 机器人 QQ 号
"""
...
async def get_group_info(self,
*,
group_id: int,
no_cache: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取群信息。
:参数:
* ``group_id``: 群号
* ``no_cache``: 是否不使用缓存(使用缓存可能更新不及时,但响应更快)
* ``self_id``: 机器人 QQ 号
"""
...
async def get_group_list(self,
*,
self_id: Optional[int] = ...
) -> List[Dict[str, Any]]:
"""
:说明:
获取群列表。
:参数:
* ``self_id``: 机器人 QQ 号
"""
...
async def get_group_member_info(
self,
*,
group_id: int,
user_id: int,
no_cache: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取群成员信息。
:参数:
* ``group_id``: 群号
* ``user_id``: QQ 号
* ``no_cache``: 是否不使用缓存(使用缓存可能更新不及时,但响应更快)
* ``self_id``: 机器人 QQ 号
"""
...
async def get_group_member_list(
self,
*,
group_id: int,
self_id: Optional[int] = ...) -> List[Dict[str, Any]]:
"""
:说明:
获取群成员列表。
:参数:
* ``group_id``: 群号
* ``self_id``: 机器人 QQ 号
"""
...
async def get_group_honor_info(self,
*,
group_id: int,
type: str = ...,
self_id: Optional[int] = ...
) -> Dict[str, Any]:
"""
:说明:
获取群荣誉信息。
:参数:
* ``group_id``: 群号
* ``type``: 要获取的群荣誉类型,可传入 ``talkative`` ``performer`` ``legend`` ``strong_newbie`` ``emotion`` 以分别获取单个类型的群荣誉数据,或传入 ``all`` 获取所有数据
* ``self_id``: 机器人 QQ 号
"""
...
async def get_cookies(self,
*,
domain: str = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取 Cookies。
:参数:
* ``domain``: 需要获取 cookies 的域名
* ``self_id``: 机器人 QQ 号
"""
...
async def get_csrf_token(self,
*,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取 CSRF Token。
:参数:
* ``self_id``: 机器人 QQ 号
"""
...
async def get_credentials(self,
*,
domain: str = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取 QQ 相关接口凭证。
:参数:
* ``domain``: 需要获取 cookies 的域名
* ``self_id``: 机器人 QQ 号
"""
...
async def get_record(self,
*,
file: str,
out_format: str,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取语音。
:参数:
* ``file``: 收到的语音文件名CQ 码的 ``file`` 参数),如 ``0B38145AA44505000B38145AA4450500.silk``
* ``out_format``: 要转换到的格式,目前支持 ``mp3``、``amr``、``wma``、``m4a``、``spx``、``ogg``、``wav``、``flac``
* ``self_id``: 机器人 QQ 号
"""
...
async def get_image(self,
*,
file: str,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取图片。
:参数:
* ``file``: 收到的图片文件名CQ 码的 ``file`` 参数),如 ``6B4DE3DFD1BD271E3297859D41C530F5.jpg``
* ``self_id``: 机器人 QQ 号
"""
...
async def can_send_image(self,
*,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
检查是否可以发送图片。
:参数:
* ``self_id``: 机器人 QQ 号
"""
...
async def can_send_record(self,
*,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
检查是否可以发送语音。
:参数:
* ``self_id``: 机器人 QQ 号
"""
...
async def get_status(self,
*,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取插件运行状态。
:参数:
* ``self_id``: 机器人 QQ 号
"""
...
async def get_version_info(self,
*,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
获取版本信息。
:参数:
* ``self_id``: 机器人 QQ 号
"""
...
async def set_restart(self,
*,
delay: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
重启 OneBot 实现。
:参数:
* ``delay``: 要延迟的毫秒数,如果默认情况下无法重启,可以尝试设置延迟为 2000 左右
* ``self_id``: 机器人 QQ 号
"""
...
async def clean_cache(self, *, self_id: Optional[int] = ...) -> None:
"""
:说明:
清理数据目录。
:参数:
* ``self_id``: 机器人 QQ 号
"""
...
class Event(BaseEvent):
def __init__(self, raw_event: dict):
...
@property
def id(self) -> Optional[int]:
...
@property
def name(self) -> str:
...
@property
def self_id(self) -> str:
...
@property
def time(self) -> int:
...
@property
def type(self) -> str:
...
@type.setter
def type(self, value) -> None:
...
@property
def detail_type(self) -> str:
...
@detail_type.setter
def detail_type(self, value) -> None:
...
@property
def sub_type(self) -> Optional[str]:
...
@sub_type.setter
def sub_type(self, value) -> None:
...
@property
def user_id(self) -> Optional[int]:
...
@user_id.setter
def user_id(self, value) -> None:
...
@property
def group_id(self) -> Optional[int]:
...
@group_id.setter
def group_id(self, value) -> None:
...
@property
def to_me(self) -> Optional[bool]:
...
@to_me.setter
def to_me(self, value) -> None:
...
@property
def message(self) -> Optional["Message"]:
...
@message.setter
def message(self, value) -> None:
...
@property
def reply(self) -> Optional[dict]:
...
@reply.setter
def reply(self, value) -> None:
...
@property
def raw_message(self) -> Optional[str]:
...
@raw_message.setter
def raw_message(self, value) -> None:
...
@property
def plain_text(self) -> Optional[str]:
...
@property
def sender(self) -> Optional[dict]:
...
@sender.setter
def sender(self, value) -> None:
...
class MessageSegment(BaseMessageSegment):
def __init__(self, type: str, data: Dict[str, Any]) -> None:
...
def __str__(self):
...
def __add__(self, other) -> "Message":
...
@staticmethod
def anonymous(ignore_failure: Optional[bool] = ...) -> "MessageSegment":
...
@staticmethod
def at(user_id: Union[int, str]) -> "MessageSegment":
...
@staticmethod
def contact_group(group_id: int) -> "MessageSegment":
...
@staticmethod
def contact_user(user_id: int) -> "MessageSegment":
...
@staticmethod
def dice() -> "MessageSegment":
...
@staticmethod
def face(id_: int) -> "MessageSegment":
...
@staticmethod
def forward(id_: str) -> "MessageSegment":
...
@staticmethod
def image(file: str,
type_: Optional[str] = ...,
cache: bool = ...,
proxy: bool = ...,
timeout: Optional[int] = ...) -> "MessageSegment":
...
@staticmethod
def json(data: str) -> "MessageSegment":
...
@staticmethod
def location(latitude: float,
longitude: float,
title: Optional[str] = ...,
content: Optional[str] = ...) -> "MessageSegment":
...
@staticmethod
def music(type_: str, id_: int) -> "MessageSegment":
...
@staticmethod
def music_custom(url: str,
audio: str,
title: str,
content: Optional[str] = ...,
img_url: Optional[str] = ...) -> "MessageSegment":
...
@staticmethod
def node(id_: int) -> "MessageSegment":
...
@staticmethod
def node_custom(user_id: int, nickname: str,
content: Union[str, "Message"]) -> "MessageSegment":
...
@staticmethod
def poke(type_: str, id_: str) -> "MessageSegment":
...
@staticmethod
def record(file: str,
magic: Optional[bool] = ...,
cache: Optional[bool] = ...,
proxy: Optional[bool] = ...,
timeout: Optional[int] = ...) -> "MessageSegment":
...
@staticmethod
def reply(id_: int) -> "MessageSegment":
...
@staticmethod
def rps() -> "MessageSegment":
...
@staticmethod
def shake() -> "MessageSegment":
...
@staticmethod
def share(url: str = ...,
title: str = ...,
content: Optional[str] = ...,
img_url: Optional[str] = ...) -> "MessageSegment":
...
@staticmethod
def text(text: str) -> "MessageSegment":
...
@staticmethod
def video(file: str,
cache: Optional[bool] = ...,
proxy: Optional[bool] = ...,
timeout: Optional[int] = ...) -> "MessageSegment":
...
@staticmethod
def xml(data: str) -> "MessageSegment":
...
class Message(BaseMessage):
@staticmethod
def _construct(msg: Union[str, dict, list]) -> Iterable[MessageSegment]:
...