mirror of
https://github.com/snowy-yang/qqbotsdk.git
synced 2026-10-21 12:24:49 +00:00
- 新增 panel 域:指令面板(斜线指令)增删改查/关联对象 + /v2/menu 全局自定义菜单 - 新增 group 域:群信息/成员/机器人群内状态、入群申请列表与审批、群黑名单、 群禁言状态、入群自动审批策略(增删改查/执行/白名单) - v2 补充:流式单聊消息、群/单聊大文件分片上传(预上传+分片确认)、分享链接 - guild 补充:子频道增删改、机器人频道列表、身份组 CRUD 与成员授予/收回、 成员列表/踢出、全员与批量禁言、接口权限、消息频率设置 - channel 补充:子频道用户/身份组权限、频道私信(建会话/发信/撤回) - 新增 audio 域(播放控制、上/下麦)与 forum 域(帖子增删查) - 事件补齐 GROUP_MEMBER_ADD/REMOVE、GROUP_JOIN_REQUEST、SUBSCRIBE_MESSAGE_STATUS; Intent 与 intents.toml 补 GROUP_MEMBER_EVENT (1<<24) 分组(默认 false)
104 lines
2.4 KiB
Python
104 lines
2.4 KiB
Python
"""协议模型:Opcode、payload 及各事件 d 字段的 TypedDict、Intent 位掩码。
|
|
|
|
Payload 里 `NOT_SET` 哨兵标记"外发时应省略"的字段,由
|
|
WebsocketConnecter.send_helper 统一过滤。
|
|
"""
|
|
|
|
from enum import IntEnum, IntFlag
|
|
from typing import Any, Literal, TypedDict
|
|
|
|
|
|
class MissingType:
|
|
def __repr__(self) -> Literal["<NotSet>"]:
|
|
return "<NotSet>"
|
|
|
|
def __bool__(self) -> Literal[False]:
|
|
return False
|
|
|
|
|
|
NOT_SET = MissingType()
|
|
type NoneType = MissingType | None
|
|
|
|
type DispatchData = Any
|
|
|
|
|
|
class Opcode(IntEnum):
|
|
DISPATCH = 0
|
|
HEARTBEAT = 1
|
|
IDENTIFY = 2
|
|
RESUME = 6
|
|
RECONNECT = 7
|
|
INVALID_SESSION = 9
|
|
HELLO = 10
|
|
HEARTBEAT_ACK = 11
|
|
VALIDATION = 13
|
|
|
|
|
|
class Payload(TypedDict, total=False):
|
|
id: str | NoneType
|
|
# 线上 payload 反序列化出来是 int,这里按实际类型声明
|
|
op: int | NoneType
|
|
d: "HelloData | IdentifyData | ResumeData | ValidationData | DispatchData | int | NoneType"
|
|
s: int | NoneType
|
|
t: str | NoneType
|
|
|
|
|
|
class HelloData(TypedDict):
|
|
heartbeat_interval: int # ms
|
|
|
|
|
|
class IdentifyData(TypedDict):
|
|
token: str
|
|
intents: int
|
|
shard: tuple[int, int]
|
|
properties: dict[str, str]
|
|
|
|
|
|
class ResumeData(TypedDict):
|
|
token: str
|
|
session_id: str
|
|
seq: int
|
|
|
|
|
|
class ReadyData(TypedDict, total=False):
|
|
version: int
|
|
session_id: str
|
|
user: dict[str, Any]
|
|
shard: tuple[int, int]
|
|
intents: int
|
|
|
|
|
|
class ValidationData(TypedDict):
|
|
plain_token: str
|
|
event_ts: str
|
|
|
|
|
|
class Intent(IntFlag):
|
|
GUILDS = 1 << 0
|
|
GUILD_MEMBERS = 1 << 1
|
|
GUILD_MESSAGES = 1 << 9
|
|
GUILD_MESSAGE_REACTIONS = 1 << 10
|
|
DIRECT_MESSAGE = 1 << 12
|
|
GROUP_MEMBER_EVENT = 1 << 24
|
|
GROUP_AND_C2C_EVENT = 1 << 25
|
|
INTERACTION = 1 << 26
|
|
MESSAGE_AUDIT = 1 << 27
|
|
FORUMS_EVENT = 1 << 28
|
|
AUDIO_ACTION = 1 << 29
|
|
PUBLIC_GUILD_MESSAGES = 1 << 30
|
|
|
|
|
|
def payload_of(op: Opcode, d: Any = NOT_SET) -> Payload:
|
|
return {"id": NOT_SET, "op": op, "d": d, "s": NOT_SET, "t": NOT_SET}
|
|
|
|
|
|
def error_parts(data: Any) -> tuple[Any, Any]:
|
|
"""从错误响应体取 (code, message);非 dict 响应整体作 message。
|
|
|
|
QQ 的失败响应 HTTP 可能仍是 200 且 Content-Type 不规范,
|
|
ApiError/TokenError 统一用它暴露真实原因。
|
|
"""
|
|
if isinstance(data, dict):
|
|
return data.get("code"), data.get("message")
|
|
return None, data
|