Files
qqbotsdk/src/qqbotsdk/model.py
T
snowy-yang 15e1f5e303 feat: OpenAPI 全量对齐——对照新旧两版官方文档补齐全部接口与事件
- 新增 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)
2026-09-23 17:46:37 +08:00

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