@imweapp/openclaw-imwe 2026.4.12-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/index.ts +16 -0
- package/openclaw.plugin.json +58 -0
- package/package.json +73 -0
- package/proto/PbBoxPullProto.proto +43 -0
- package/proto/PbChatAudioContent.proto +23 -0
- package/proto/PbChatDeliverMsg.proto +38 -0
- package/proto/PbChatFileMeta.proto +34 -0
- package/proto/PbChatMsg.proto +93 -0
- package/proto/PbChatRichMediaContent.proto +31 -0
- package/proto/PbChatTextContent.proto +38 -0
- package/proto/PbMarkdownContent.proto +18 -0
- package/proto/PbMsgReadStampContent.proto +11 -0
- package/proto/PbPacket.proto +61 -0
- package/proto/PbSingleChatMsg.proto +60 -0
- package/setup-entry.ts +17 -0
- package/src/accounts.ts +109 -0
- package/src/api-client.ts +740 -0
- package/src/bot-info-cache.ts +49 -0
- package/src/channel.runtime.ts +29 -0
- package/src/channel.ts +456 -0
- package/src/config-schema.ts +26 -0
- package/src/e2ee/api.ts +261 -0
- package/src/e2ee/canonical.ts +59 -0
- package/src/e2ee/errors.ts +103 -0
- package/src/e2ee/index.ts +8 -0
- package/src/e2ee/proper-lockfile.d.ts +61 -0
- package/src/e2ee/service.ts +1273 -0
- package/src/e2ee/store.ts +174 -0
- package/src/e2ee/types.ts +113 -0
- package/src/e2ee/vodozemac.ts +373 -0
- package/src/file-transfer/api.ts +364 -0
- package/src/file-transfer/concurrency.ts +77 -0
- package/src/file-transfer/download.ts +261 -0
- package/src/file-transfer/file-crypto.ts +93 -0
- package/src/file-transfer/index.ts +18 -0
- package/src/file-transfer/scheduler.ts +185 -0
- package/src/file-transfer/types.ts +195 -0
- package/src/file-transfer/upload.ts +656 -0
- package/src/markdown-detect.ts +119 -0
- package/src/media-upload.ts +338 -0
- package/src/media-utils.ts +110 -0
- package/src/monitor.ts +838 -0
- package/src/proto/codec.ts +54 -0
- package/src/proto/inbound.codec.ts +624 -0
- package/src/proto/proto-types.ts +291 -0
- package/src/proto/registry.ts +226 -0
- package/src/proto/send.codec.ts +535 -0
- package/src/recent-message-cache.ts +350 -0
- package/src/send.ts +792 -0
- package/src/setup-core.ts +62 -0
- package/src/types.ts +153 -0
- package/src/vodozemackit/index.ts +297 -0
- package/src/vodozemackit/pkg/vodozemackit_wasm.d.ts +138 -0
- package/src/vodozemackit/pkg/vodozemackit_wasm.js +24 -0
- package/src/vodozemackit/pkg/vodozemackit_wasm_bg.js +1172 -0
- package/src/vodozemackit/pkg/vodozemackit_wasm_bg.wasm +0 -0
- package/src/vodozemackit/pkg/vodozemackit_wasm_bg.wasm.d.ts +109 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* proto-types.ts — Protobuf 消息的 TypeScript 类型接口
|
|
3
|
+
*
|
|
4
|
+
* protobufjs 运行时 decode 返回 Message<{}> 类型,需要通过类型断言
|
|
5
|
+
* 才能访问具体字段。本文件定义与 .proto 字段严格对应的接口,
|
|
6
|
+
* 仅用于 codec 内部的 `as unknown as XxxMsg` 断言,不参与运行时逻辑。
|
|
7
|
+
*
|
|
8
|
+
* 字段名规则:
|
|
9
|
+
* protobufjs 加载 .proto 时,会将 camelCase 字段名原样保留。
|
|
10
|
+
* 例如 PbRequest.reqId → decoded.reqId(不转换)。
|
|
11
|
+
*
|
|
12
|
+
* 维护规则:
|
|
13
|
+
* 当 .proto 文件字段发生变化时,同步更新对应接口。
|
|
14
|
+
* 接口只需包含插件实际访问的字段,不需要穷举所有字段。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
// 传输层(PbPacket.proto)
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/** PbRequest 解码后的字段(插件关注的部分) */
|
|
22
|
+
export interface DecodedPbRequest {
|
|
23
|
+
path: string;
|
|
24
|
+
reqId: string;
|
|
25
|
+
reqStamp: number | bigint;
|
|
26
|
+
/** body 字节,需按 path 二次反序列化 */
|
|
27
|
+
body?: Uint8Array;
|
|
28
|
+
/**
|
|
29
|
+
* 业务字段 / 元信息,命名约定 x-<module>-<key>。
|
|
30
|
+
* 原 imAcctId/boxId/boxSeq 已迁移至此:
|
|
31
|
+
* x-im-acct-id : IM 账号 ID(分身场景传递)
|
|
32
|
+
* x-im-box-id : IM 事件箱 ID
|
|
33
|
+
* x-im-box-seq : IM 事件箱序列号(用于同步)
|
|
34
|
+
*/
|
|
35
|
+
headers: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** PbPacket 解码后的字段 */
|
|
39
|
+
export interface DecodedPbPacket {
|
|
40
|
+
/** PbType 枚举值:0=CLIENT_REQ, 1=SERVER_RSP, 2=SERVER_REQ, 3=CLIENT_RSP */
|
|
41
|
+
type: number;
|
|
42
|
+
request?: DecodedPbRequest;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// 投递层(PbChatDeliverMsg.proto)
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/** PbChatMsgDeliverBody 解码后的字段(插件关注的部分) */
|
|
50
|
+
export interface DecodedDeliverBody {
|
|
51
|
+
convType: number;
|
|
52
|
+
fromId: string;
|
|
53
|
+
toId: string;
|
|
54
|
+
toAcctId: string;
|
|
55
|
+
winId?: string;
|
|
56
|
+
/** 信封类型:1=聊天, 2=操作, 3=支付, 4=服务号通知 */
|
|
57
|
+
envelopeType: number | bigint;
|
|
58
|
+
clientMsgId: string;
|
|
59
|
+
clientStamp: number | bigint;
|
|
60
|
+
serverMsgId: string;
|
|
61
|
+
serverStamp: number | bigint;
|
|
62
|
+
e2eeFlag: boolean;
|
|
63
|
+
/** 发送方设备 e2eeId(e2eeFlag=true 时必填) */
|
|
64
|
+
fromE2eeId?: string;
|
|
65
|
+
/** 接收方设备 e2eeId(e2eeFlag=true 时必填,应 == 本端 local.e2eeId) */
|
|
66
|
+
toE2eeId?: string;
|
|
67
|
+
/** 发送方使用的 Session.sessionId()(e2eeFlag=true 时必填) */
|
|
68
|
+
e2eeSid?: string;
|
|
69
|
+
/** 信封内容字节,需按 envelopeType 二次反序列化 */
|
|
70
|
+
envelope: Uint8Array;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
// 文件元数据层(PbChatFileMeta.proto)
|
|
75
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/** PbChatFileMeta 解码后的字段 */
|
|
78
|
+
export interface DecodedFileMeta {
|
|
79
|
+
url: string;
|
|
80
|
+
key?: Uint8Array;
|
|
81
|
+
iv?: Uint8Array;
|
|
82
|
+
fileType: string;
|
|
83
|
+
digest?: string;
|
|
84
|
+
length?: number | bigint;
|
|
85
|
+
plaintextLength?: number | bigint;
|
|
86
|
+
expireTime?: number | bigint;
|
|
87
|
+
attachmentId?: string;
|
|
88
|
+
/** PbChatFileMetaType 枚举值:0=UNKNOWN, 1=IMAGE, 2=VIDEO, 3=AUDIO, 4=FILE */
|
|
89
|
+
mediaType: number;
|
|
90
|
+
/** 操作凭证(下载回调用) */
|
|
91
|
+
opCreds?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
95
|
+
// 富媒体内容层(PbChatRichMediaContent.proto)
|
|
96
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
/** PbChatRichMediaContent.AttachmentMeta 解码后的字段 */
|
|
99
|
+
export interface DecodedAttachmentMeta {
|
|
100
|
+
attachmentId: string;
|
|
101
|
+
/** PbChatFileMetaType 枚举值 */
|
|
102
|
+
mediaType: number;
|
|
103
|
+
fileMeta?: DecodedFileMeta;
|
|
104
|
+
width?: number | bigint;
|
|
105
|
+
height?: number | bigint;
|
|
106
|
+
blurHash?: string;
|
|
107
|
+
duration?: number | bigint;
|
|
108
|
+
snapshot?: DecodedAttachmentMeta;
|
|
109
|
+
caption?: string;
|
|
110
|
+
fileName?: string;
|
|
111
|
+
fileSize?: number | bigint;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** PbChatRichMediaContent 解码后的字段 */
|
|
115
|
+
export interface DecodedRichMediaContent {
|
|
116
|
+
meta: DecodedAttachmentMeta[];
|
|
117
|
+
caption?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
121
|
+
// 音频内容层(PbChatAudioContent.proto)
|
|
122
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
/** PbChatAudioContent.AttachmentMeta 解码后的字段 */
|
|
125
|
+
export interface DecodedAudioAttachmentMeta {
|
|
126
|
+
attachmentId: string;
|
|
127
|
+
duration: number | bigint;
|
|
128
|
+
fileMeta?: DecodedFileMeta;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** PbChatAudioContent 解码后的字段 */
|
|
132
|
+
export interface DecodedAudioContent {
|
|
133
|
+
meta?: DecodedAudioAttachmentMeta;
|
|
134
|
+
speech?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
// Markdown 内容层(PbMarkdownContent.proto)
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/** PbMarkdownContent 解码后的字段 */
|
|
142
|
+
export interface DecodedMarkdownContent {
|
|
143
|
+
/** 摘要标题(可空) */
|
|
144
|
+
digest: string;
|
|
145
|
+
/** markdown 正文 */
|
|
146
|
+
markdown: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
150
|
+
// 信封层(PbChatMsg.proto)
|
|
151
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/** PbChatMsgEnvelope 解码后的字段 */
|
|
154
|
+
export interface DecodedChatMsgEnvelope {
|
|
155
|
+
senderKey?: Uint8Array;
|
|
156
|
+
autoDeleteType?: number;
|
|
157
|
+
/**
|
|
158
|
+
* oneof envelope 中的文本消息内容(字段3)。
|
|
159
|
+
* 当消息为文本类型时有值,其他类型(音频/图片等)时为 undefined。
|
|
160
|
+
*/
|
|
161
|
+
textContent?: DecodedTextContent;
|
|
162
|
+
/** 图片/视频混合消息(oneof 字段 9) */
|
|
163
|
+
richMediaContent?: DecodedRichMediaContent;
|
|
164
|
+
/** 文件消息(oneof 字段 13,结构同 richMediaContent) */
|
|
165
|
+
fileContent?: DecodedRichMediaContent;
|
|
166
|
+
/** 音频消息(oneof 字段 4) */
|
|
167
|
+
audioContent?: DecodedAudioContent;
|
|
168
|
+
/** Markdown 消息(oneof 字段 16) */
|
|
169
|
+
markdownContent?: DecodedMarkdownContent;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
173
|
+
// 内容层(PbChatTextContent.proto)
|
|
174
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
/** BodyRange 解码后的字段 */
|
|
177
|
+
export interface DecodedBodyRange {
|
|
178
|
+
/** 0=UNKNOWN, 1=AT_USER, 2=LINK */
|
|
179
|
+
key: number;
|
|
180
|
+
/** AT_USER 时为被@者的 acctId */
|
|
181
|
+
payload: string;
|
|
182
|
+
/** 起始位置(UTF-16 单位) */
|
|
183
|
+
start?: number;
|
|
184
|
+
/** 长度(UTF-16 单位) */
|
|
185
|
+
length?: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** PbChatTextContent 解码后的字段 */
|
|
189
|
+
export interface DecodedTextContent {
|
|
190
|
+
text: string;
|
|
191
|
+
bodyRanges: DecodedBodyRange[];
|
|
192
|
+
referenceClientMsgId?: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
196
|
+
// E2EE 单聊装箱层(PbSingleChatMsg.proto)
|
|
197
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
/** PbSingleChatDeviceMsg 解码后的字段(单台对端设备的密文条目) */
|
|
200
|
+
export interface DecodedSingleChatDeviceMsg {
|
|
201
|
+
/** 接收方设备 e2eeId(= 对端 Account.curve25519Key()) */
|
|
202
|
+
toE2eeId: string;
|
|
203
|
+
/** 发送本条消息时使用的 Session.sessionId() */
|
|
204
|
+
e2eeSid: string;
|
|
205
|
+
/** vodozemac EncryptedMessage.body(OlmMessage JSON bytes) */
|
|
206
|
+
envelope: Uint8Array;
|
|
207
|
+
/** 提醒加粗(透传 PbSingleChatMsgReqBody.boldNotify 语义) */
|
|
208
|
+
boldNotify?: boolean;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** PbSingleChatMsgRecipient 解码后的字段(同一对端 imAcctId 的多设备分发) */
|
|
212
|
+
export interface DecodedSingleChatMsgRecipient {
|
|
213
|
+
/** 对端 imAcctId */
|
|
214
|
+
acctId: string;
|
|
215
|
+
/** 该联系人下每台活跃设备一份密文 */
|
|
216
|
+
msgs: DecodedSingleChatDeviceMsg[];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** PbSingleChatMsgRecipients 解码后的字段(所有联系人的设备分发集合) */
|
|
220
|
+
export interface DecodedSingleChatMsgRecipients {
|
|
221
|
+
recipients: DecodedSingleChatMsgRecipient[];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
225
|
+
// 操作信封层(PbChatMsg.proto — PbChatOperationEnvelope)
|
|
226
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* PbMessageDecryptErrorSendContent 解码后的字段。
|
|
230
|
+
*
|
|
231
|
+
* 信令由"解密失败方"发回给"原发送方",字段方向语义为:
|
|
232
|
+
* - e2eeId:信令发送方自己的 e2eeId(= 解密失败消息的接收者设备)
|
|
233
|
+
* - otherE2eeId:失败消息的原发送方 e2eeId(= 信令接收方,应 == 本端 local.e2eeId)
|
|
234
|
+
*/
|
|
235
|
+
export interface DecodedDecryptErrorContent {
|
|
236
|
+
/** 失败的那条消息的 clientMsgId */
|
|
237
|
+
clientMsgId: string;
|
|
238
|
+
/** 信令发送方自己的 e2eeId */
|
|
239
|
+
e2eeId: string;
|
|
240
|
+
/** 失败消息的原发送方 e2eeId */
|
|
241
|
+
otherE2eeId: string;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** PbMsgReadStampSendContent 解码后的字段 */
|
|
245
|
+
export interface DecodedMsgReadStampSendContent {
|
|
246
|
+
/** 已读消息的 clientMsgId 列表 */
|
|
247
|
+
clientMsgIds: string[];
|
|
248
|
+
/** 已读时间戳(Unix 秒) */
|
|
249
|
+
readStamp: number | bigint;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** PbBotThinkingSignal 解码后的字段 */
|
|
253
|
+
export interface DecodedBotThinkingSignal {
|
|
254
|
+
/** 1=begin(开始输入),2=end(结束输入) */
|
|
255
|
+
status: number;
|
|
256
|
+
/** Bot 的 imAcctId */
|
|
257
|
+
botImAcctId: string;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* PbChatOperationEnvelope 解码后的字段。
|
|
262
|
+
*
|
|
263
|
+
* `content` 是 oneof,protobufjs decode 后仅会填充命中的那一个字段,
|
|
264
|
+
* 未命中的字段为 `undefined`。插件目前仅处理 `decryptError`、`botThinking` 与 `msgRead`
|
|
265
|
+
* 三种类型,其余 oneof case 以 `Uint8Array` 占位。
|
|
266
|
+
*/
|
|
267
|
+
export interface DecodedChatOperationEnvelope {
|
|
268
|
+
/** 消息撤回(占位,不解析) */
|
|
269
|
+
msgRecall?: Uint8Array;
|
|
270
|
+
/** 双向删除(占位,不解析) */
|
|
271
|
+
bidirectionalDelete?: Uint8Array;
|
|
272
|
+
/** 双向删除回复(占位,不解析) */
|
|
273
|
+
bidirectionalDeleteAnswer?: Uint8Array;
|
|
274
|
+
/** 群公告(占位,不解析) */
|
|
275
|
+
announcement?: Uint8Array;
|
|
276
|
+
/** 单聊解密失败回执信令(插件处理) */
|
|
277
|
+
decryptError?: DecodedDecryptErrorContent;
|
|
278
|
+
/** 群聊解密失败(占位,不解析) */
|
|
279
|
+
groupDecryptError?: Uint8Array;
|
|
280
|
+
/** 消息已读 */
|
|
281
|
+
msgRead?: DecodedMsgReadStampSendContent;
|
|
282
|
+
/** 消息已送达(占位,不解析) */
|
|
283
|
+
msgArrived?: Uint8Array;
|
|
284
|
+
/** Bot thinking 信号(插件处理) */
|
|
285
|
+
botThinking?: DecodedBotThinkingSignal;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
289
|
+
// 插件自定义(send.proto 已移除,响应走 PbPacket(SERVER_RSP) 解包)
|
|
290
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
291
|
+
// SendResponse 已不再使用,响应体通过 PbMsgRspBody 解析 serverMsgId。
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registry.ts — Protobuf 运行时类型注册表
|
|
3
|
+
*
|
|
4
|
+
* 使用 protobufjs 在运行时直接加载 .proto 文件,无需代码生成步骤。
|
|
5
|
+
* .proto 文件是唯一的结构定义来源,修改后重启进程即生效。
|
|
6
|
+
*
|
|
7
|
+
* 加载的 .proto 文件分两类:
|
|
8
|
+
* 规范文件(与 im_proto 保持一致,camelCase 字段名):
|
|
9
|
+
* PbPacket.proto → PbPacket / PbRequest
|
|
10
|
+
* PbChatDeliverMsg.proto → PbChatMsgDeliverBody
|
|
11
|
+
* PbChatFileMeta.proto → PbChatFileMeta / PbChatFileMetaType
|
|
12
|
+
* PbChatRichMediaContent.proto → PbChatRichMediaContent
|
|
13
|
+
* PbChatAudioContent.proto → PbChatAudioContent
|
|
14
|
+
* PbMarkdownContent.proto → PbMarkdownContent
|
|
15
|
+
* PbChatMsg.proto → PbChatMsgEnvelope / PbChatOperationEnvelope / PbBotThinkingSignal
|
|
16
|
+
* PbChatTextContent.proto → PbChatTextContent / BodyRange
|
|
17
|
+
* PbSingleChatMsg.proto → PbSingleChatMsgReqBody
|
|
18
|
+
* PbBoxPullProto.proto → PbBoxPullReq / PbBoxPullRsp
|
|
19
|
+
* 插件自定义文件(独立定义,package imwe):
|
|
20
|
+
* send.proto → SendResponse
|
|
21
|
+
*
|
|
22
|
+
* 单例模式:首次调用 getRegistry() 时触发文件加载,后续调用直接返回缓存。
|
|
23
|
+
* 并发安全:多个并发调用共享同一个 loadPromise,不会重复加载。
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { load, type Root, type Type } from 'protobufjs';
|
|
27
|
+
import { resolve, dirname } from 'node:path';
|
|
28
|
+
import { fileURLToPath } from 'node:url';
|
|
29
|
+
|
|
30
|
+
// proto 文件所在目录(相对于本文件向上两级到插件根,再进入 proto/)
|
|
31
|
+
const PROTO_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '../../proto');
|
|
32
|
+
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
// 类型注册表接口
|
|
35
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 插件所需的全部 protobuf 类型,按用途分组。
|
|
39
|
+
*
|
|
40
|
+
* 命名规则:
|
|
41
|
+
* - 规范消息(package proto):直接用消息名,如 PbPacket
|
|
42
|
+
* - 插件自定义消息(package imwe):加前缀区分,如 imwe.PollRequest
|
|
43
|
+
*/
|
|
44
|
+
export type ProtoRegistry = {
|
|
45
|
+
// ── 传输层(PbPacket.proto) ──────────────────────────────────────────────
|
|
46
|
+
/** 最外层传输容器,type=CLIENT_REQ(0) 或 SERVER_REQ(2) */
|
|
47
|
+
PbPacket: Type;
|
|
48
|
+
/** 请求体,body 字段按 path 二次反序列化 */
|
|
49
|
+
PbRequest: Type;
|
|
50
|
+
/** 响应体,statusCode=200 表示成功,body 按业务类型反序列化 */
|
|
51
|
+
PbResponse: Type;
|
|
52
|
+
|
|
53
|
+
// ── 投递层(PbChatDeliverMsg.proto) ─────────────────────────────────────
|
|
54
|
+
/** 服务端向客户端投递消息的统一载体,envelope 字段按 envelopeType 二次反序列化 */
|
|
55
|
+
PbChatMsgDeliverBody: Type;
|
|
56
|
+
|
|
57
|
+
// ── 信封层(PbChatMsg.proto) ─────────────────────────────────────────────
|
|
58
|
+
/** 聊天消息信封(envelopeType=1),oneof envelope 包含 13 种消息类型 */
|
|
59
|
+
PbChatMsgEnvelope: Type;
|
|
60
|
+
/**
|
|
61
|
+
* 发送消息响应体(PbResponse.body 按此类型反序列化)。
|
|
62
|
+
* 包含 serverMsgId / serverStamp,用于确认消息已被平台接收。
|
|
63
|
+
*/
|
|
64
|
+
PbMsgRspBody: Type;
|
|
65
|
+
|
|
66
|
+
// ── 操作信封层(PbChatMsg.proto) ────────────────────────────────────────
|
|
67
|
+
/** 操作消息信封(envelopeType=2),oneof content 包含 botThinking 等操作类型 */
|
|
68
|
+
PbChatOperationEnvelope: Type;
|
|
69
|
+
/** Bot thinking 信号,status=1(begin)/2(end) */
|
|
70
|
+
PbBotThinkingSignal: Type;
|
|
71
|
+
/** 单聊解密失败回执信令(PbChatOperationEnvelope.content.decryptError) */
|
|
72
|
+
PbMessageDecryptErrorSendContent: Type;
|
|
73
|
+
/** 消息已读内容(PbChatOperationEnvelope.content.msgRead) */
|
|
74
|
+
PbMsgReadStampSendContent: Type;
|
|
75
|
+
|
|
76
|
+
// ── 内容层(PbChatTextContent.proto) ────────────────────────────────────
|
|
77
|
+
/** 文本消息内容,包含 text / bodyRanges / referenceClientMsgId */
|
|
78
|
+
PbChatTextContent: Type;
|
|
79
|
+
|
|
80
|
+
// ── 内容层(PbMarkdownContent.proto) ────────────────────────────────────
|
|
81
|
+
/** Markdown 消息内容(PbChatMsgEnvelope 字段 16),包含 digest / markdown */
|
|
82
|
+
PbMarkdownContent: Type;
|
|
83
|
+
|
|
84
|
+
// ── 多媒体内容层 ─────────────────────────────────────────────────────────
|
|
85
|
+
/** 图片/视频混合消息内容(PbChatMsgEnvelope 字段 9 和 13) */
|
|
86
|
+
PbChatRichMediaContent: Type;
|
|
87
|
+
/** 文件元数据(含 url、加密信息、文件类型等) */
|
|
88
|
+
PbChatFileMeta: Type;
|
|
89
|
+
/** 音频消息内容(PbChatMsgEnvelope 字段 4) */
|
|
90
|
+
PbChatAudioContent: Type;
|
|
91
|
+
|
|
92
|
+
// ── 发送层(PbSingleChatMsg.proto) ──────────────────────────────────────
|
|
93
|
+
/** 单聊消息请求体,作为 PbRequest.body 的内容 */
|
|
94
|
+
PbSingleChatMsgReqBody: Type;
|
|
95
|
+
/** E2EE 单设备密文条目(e2eeFlag=true 时装箱使用) */
|
|
96
|
+
PbSingleChatDeviceMsg: Type;
|
|
97
|
+
/** E2EE 同一对端 imAcctId 的多设备分发 */
|
|
98
|
+
PbSingleChatMsgRecipient: Type;
|
|
99
|
+
/** E2EE 所有联系人的设备分发集合,序列化后作为 PbSingleChatMsgReqBody.envelope */
|
|
100
|
+
PbSingleChatMsgRecipients: Type;
|
|
101
|
+
|
|
102
|
+
// ── 事件箱拉取(PbBoxPullProto.proto) ───────────────────────────────────
|
|
103
|
+
/** 事件箱拉取请求,POST /api/im/open/bot/pullMessages 的请求体 */
|
|
104
|
+
PbBoxPullReq: Type;
|
|
105
|
+
/** 事件箱拉取响应,POST /api/im/open/bot/pullMessages 的响应体 */
|
|
106
|
+
PbBoxPullRsp: Type;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
// 单例状态
|
|
111
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
/** 已初始化的注册表缓存,null 表示尚未加载 */
|
|
114
|
+
let registry: ProtoRegistry | null = null;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 进行中的加载 Promise,用于防止并发重复加载。
|
|
118
|
+
* 多个并发调用 getRegistry() 时,只有第一个触发 load(),其余等待同一个 Promise。
|
|
119
|
+
*/
|
|
120
|
+
let loadPromise: Promise<ProtoRegistry> | null = null;
|
|
121
|
+
|
|
122
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
// 内部:从已加载的 Root 构建注册表
|
|
124
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 从 protobufjs Root 中按全限定名查找所有需要的类型,构建注册表。
|
|
128
|
+
*
|
|
129
|
+
* 全限定名规则:
|
|
130
|
+
* - 规范文件 package=proto: "proto.MessageName"
|
|
131
|
+
* - 插件自定义 package=imwe: "imwe.MessageName"
|
|
132
|
+
*/
|
|
133
|
+
function buildRegistry(root: Root): ProtoRegistry {
|
|
134
|
+
return {
|
|
135
|
+
// 传输层
|
|
136
|
+
PbPacket: root.lookupType('proto.PbPacket'),
|
|
137
|
+
PbRequest: root.lookupType('proto.PbRequest'),
|
|
138
|
+
PbResponse: root.lookupType('proto.PbResponse'),
|
|
139
|
+
// 投递层
|
|
140
|
+
PbChatMsgDeliverBody: root.lookupType('proto.PbChatMsgDeliverBody'),
|
|
141
|
+
// 信封层
|
|
142
|
+
PbChatMsgEnvelope: root.lookupType('proto.PbChatMsgEnvelope'),
|
|
143
|
+
PbMsgRspBody: root.lookupType('proto.PbMsgRspBody'),
|
|
144
|
+
// 操作信封层
|
|
145
|
+
PbChatOperationEnvelope: root.lookupType('proto.PbChatOperationEnvelope'),
|
|
146
|
+
PbBotThinkingSignal: root.lookupType('proto.PbBotThinkingSignal'),
|
|
147
|
+
PbMessageDecryptErrorSendContent: root.lookupType('proto.PbMessageDecryptErrorSendContent'),
|
|
148
|
+
PbMsgReadStampSendContent: root.lookupType('proto.PbMsgReadStampSendContent'),
|
|
149
|
+
// 内容层
|
|
150
|
+
PbChatTextContent: root.lookupType('proto.PbChatTextContent'),
|
|
151
|
+
PbMarkdownContent: root.lookupType('proto.PbMarkdownContent'),
|
|
152
|
+
// 多媒体内容层
|
|
153
|
+
PbChatRichMediaContent: root.lookupType('proto.PbChatRichMediaContent'),
|
|
154
|
+
PbChatFileMeta: root.lookupType('proto.PbChatFileMeta'),
|
|
155
|
+
PbChatAudioContent: root.lookupType('proto.PbChatAudioContent'),
|
|
156
|
+
// 发送层
|
|
157
|
+
PbSingleChatMsgReqBody: root.lookupType('proto.PbSingleChatMsgReqBody'),
|
|
158
|
+
PbSingleChatDeviceMsg: root.lookupType('proto.PbSingleChatDeviceMsg'),
|
|
159
|
+
PbSingleChatMsgRecipient: root.lookupType('proto.PbSingleChatMsgRecipient'),
|
|
160
|
+
PbSingleChatMsgRecipients: root.lookupType('proto.PbSingleChatMsgRecipients'),
|
|
161
|
+
// 事件箱拉取
|
|
162
|
+
PbBoxPullReq: root.lookupType('proto.PbBoxPullReq'),
|
|
163
|
+
PbBoxPullRsp: root.lookupType('proto.PbBoxPullRsp'),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
168
|
+
// 公开 API
|
|
169
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 获取 Protobuf 类型注册表。
|
|
173
|
+
*
|
|
174
|
+
* 首次调用时从磁盘加载所有 .proto 文件并解析,后续调用直接返回缓存。
|
|
175
|
+
* 加载耗时约几毫秒(本地文件 I/O + protobufjs 解析),仅发生一次。
|
|
176
|
+
*
|
|
177
|
+
* @returns 包含所有插件所需 protobuf 类型的注册表
|
|
178
|
+
* @throws 如果 .proto 文件不存在或语法错误,会抛出 Error
|
|
179
|
+
*/
|
|
180
|
+
export async function getRegistry(): Promise<ProtoRegistry> {
|
|
181
|
+
// 快速路径:已初始化,直接返回
|
|
182
|
+
if (registry) {
|
|
183
|
+
return registry;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 并发安全:复用已有的加载 Promise,避免重复加载
|
|
187
|
+
if (!loadPromise) {
|
|
188
|
+
const protoFiles = [
|
|
189
|
+
// 规范文件(与 im_proto 保持一致)
|
|
190
|
+
resolve(PROTO_DIR, 'PbPacket.proto'),
|
|
191
|
+
resolve(PROTO_DIR, 'PbChatDeliverMsg.proto'),
|
|
192
|
+
resolve(PROTO_DIR, 'PbChatTextContent.proto'), // 需在 PbChatMsg.proto 之前加载(被 import)
|
|
193
|
+
resolve(PROTO_DIR, 'PbChatFileMeta.proto'), // 需在 PbChatRichMediaContent/PbChatAudioContent 之前加载(被 import)
|
|
194
|
+
resolve(PROTO_DIR, 'PbChatRichMediaContent.proto'), // 需在 PbChatMsg.proto 之前加载(被 import)
|
|
195
|
+
resolve(PROTO_DIR, 'PbChatAudioContent.proto'), // 需在 PbChatMsg.proto 之前加载(被 import)
|
|
196
|
+
resolve(PROTO_DIR, 'PbMarkdownContent.proto'), // 需在 PbChatMsg.proto 之前加载(被 import)
|
|
197
|
+
resolve(PROTO_DIR, 'PbMsgReadStampContent.proto'), // 需在 PbChatMsg.proto 之前加载(被 import)
|
|
198
|
+
resolve(PROTO_DIR, 'PbChatMsg.proto'),
|
|
199
|
+
resolve(PROTO_DIR, 'PbSingleChatMsg.proto'),
|
|
200
|
+
resolve(PROTO_DIR, 'PbBoxPullProto.proto'),
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
loadPromise = load(protoFiles)
|
|
204
|
+
.then((root) => {
|
|
205
|
+
const reg = buildRegistry(root);
|
|
206
|
+
registry = reg; // 缓存,后续调用走快速路径
|
|
207
|
+
return reg;
|
|
208
|
+
})
|
|
209
|
+
.catch((err) => {
|
|
210
|
+
// 加载失败时清除 Promise,允许下次重试
|
|
211
|
+
loadPromise = null;
|
|
212
|
+
throw new Error(`imwe proto 加载失败:${String(err)}`);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return loadPromise;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 重置注册表缓存(仅供测试使用)。
|
|
221
|
+
* 生产代码不应调用此函数。
|
|
222
|
+
*/
|
|
223
|
+
export function _resetRegistryForTest(): void {
|
|
224
|
+
registry = null;
|
|
225
|
+
loadPromise = null;
|
|
226
|
+
}
|