k2-im 0.1.0

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/dist/i18n.d.ts ADDED
@@ -0,0 +1,1095 @@
1
+ /** IM 领域支持的 5 个展示 locale(与宿主规范 locale 一致)。 */
2
+ type ImLocale = 'en-US' | 'th-TH' | 'vi-VN' | 'zh-CN' | 'zh-TW';
3
+ /** 默认展示语言;缺译 / 未知 locale 一律回退至此(对齐 NUXT_PUBLIC_DEFAULT_LOCALE 默认值)。 */
4
+ declare const DEFAULT_LOCALE: ImLocale;
5
+ /**
6
+ * 把宿主更宽的 locale 联合归一到 ImLocale:精确命中 5 个规范 locale 之一即原样返回,
7
+ * 非法 / 未知输入回退 `DEFAULT_LOCALE`。供宿主 `env.locale`(如 `useI18n().locale`)安全窄化。
8
+ * @example normalizeImLocale('zh-CN') // 'zh-CN'
9
+ * @example normalizeImLocale('fr-FR') // 'zh-CN'(DEFAULT_LOCALE)
10
+ */
11
+ declare function normalizeImLocale(input: string): ImLocale;
12
+
13
+ /** 媒体业务类型:实现据此选对象存储 bucket/host;sticker 对应 app-sticker。 */
14
+ type MediaKind = 'image' | 'video' | 'voice' | 'file' | 'sticker';
15
+
16
+ /** 会话类型。P0 只用 direct / group。 */
17
+ type ConversationType = 'direct' | 'group' | 'channel' | 'supergroup';
18
+ /**
19
+ * 会话复合身份;`conversationId` 只在对应 `conversationType` 的领域内唯一。
20
+ *
21
+ * @template TType 精确会话类型,供 GROUP/CHANNEL sibling 在编译期隔离。
22
+ * @example
23
+ * ```ts
24
+ * const group: ConversationRef<'group'> = {
25
+ * conversationType: 'group',
26
+ * conversationId: 'group-42',
27
+ * }
28
+ * ```
29
+ */
30
+ interface ConversationRef<TType extends ConversationType = ConversationType> {
31
+ conversationType: TType;
32
+ conversationId: string;
33
+ }
34
+ /** 消息内容类型。sticker=表情/贴图(BMessage contentType=7);call=单聊通话记录(contentType=10);transfer=转账(contentType=16);miniProgram=小程序分享卡(contentType=22);redPacket=红包卡(contentType=25);game=小游戏(contentType=27);未知 wire 正文统一归一为 unknown。 */
35
+ type MessageContentType = 'text' | 'system' | 'image' | 'video' | 'voice' | 'file' | 'sticker' | 'contact' | 'miniProgram' | 'game' | 'transfer' | 'call' | 'redPacket' | 'unknown';
36
+ /**
37
+ * 发送态(客户端态):消息从本地创建到服务端确认的生命周期。
38
+ * `uploading` 仅媒体消息使用:文件上传阶段(uploading→pending→sending→sent);
39
+ * 上传期消息只在 DomainState(不进 Outbox),上传成功拿到 URL 后才交 Outbox 走 sending→sent(Outbox 不感知上传,OutboxStatus 无 uploading)。
40
+ */
41
+ type MessageSendStatus = 'pending' | 'uploading' | 'sending' | 'sent' | 'failed';
42
+ /** 送达/已读态(服务端态):消息在对端的回执状态。 */
43
+ type MessageReceipt = 'unread' | 'delivered' | 'read';
44
+ /** 群系统提示事件类型(结构化语义;UI/i18n 层据此渲染文案,**不在 mapper 写死中文**)。 */
45
+ type ImSystemEventType = 'direct_contact_established' | 'direct_contact_required' | 'direct_message_refused' | 'group_info_changed' | 'group_activated' | 'group_member_joined' | 'group_member_invited' | 'group_member_left' | 'group_member_removed' | 'group_master_changed' | 'group_dismissed' | 'group_member_renamed' | 'group_message_pinned' | 'group_message_unpinned' | 'channel_name_changed' | 'channel_avatar_changed' | 'channel_announcement_changed' | 'channel_member_joined' | 'channel_member_invited' | 'channel_member_left' | 'channel_member_removed' | 'channel_owner_transferred' | 'channel_admin_added' | 'channel_admin_removed' | 'channel_dismissed' | 'channel_all_muted' | 'channel_all_unmuted' | 'channel_created' | 'channel_private_chat_disabled' | 'channel_private_chat_enabled' | 'channel_member_renamed' | 'channel_member_muted' | 'channel_member_unmuted' | 'channel_message_pinned' | 'channel_message_unpinned' | 'channel_unknown' | 'red_packet_claimed' | 'red_packet_claimed_thunder' | 'unknown';
46
+ /** 系统提示结构化内容(contentType==='system'):存语义,不把最终中文句子当主数据源(UI/composable 据 eventType + 资料生成文案)。 */
47
+ interface ImSystemMessageBody {
48
+ eventType: ImSystemEventType;
49
+ /**
50
+ * `hidden` 表示该通知只驱动资料/权限等领域副作用,不作为聊天时间线或会话摘要内容展示。
51
+ * 事件仍会进入 DomainState 并广播,不能把它理解成“忽略通知”。
52
+ */
53
+ timelineVisibility?: 'hidden';
54
+ /** 操作者 userId(如踢人者/改名者/群主)。 */
55
+ operatorId?: string;
56
+ /** 被操作成员 userId 列表(如被踢/退群成员;判断是否含自己以触发受限 side-effect)。 */
57
+ targetUserIds?: string[];
58
+ /** 后端 notifyType 原始值(诊断 / unknown 兜底路由)。 */
59
+ rawNotifyType?: number;
60
+ /** 后端 errcode 原始值(仅非默认时存):申请/邀请结果等边界据此判失败(NON_ERR=0x8000 成功,EXCEPT_ERR=0 为默认未设置)。 */
61
+ errcode?: number;
62
+ /** 后端 sContent 原文(未知 event 或需直出时兜底展示)。 */
63
+ fallbackText?: string;
64
+ /** 结构化扩展(群资料变更 name/desc/head_img_url 等,解析 sContent JSON 后填入)。 */
65
+ payload?: Record<string, unknown>;
66
+ /** 可由产品层执行的结构化动作;当前仅允许 direct 非好友提示触发好友申请。 */
67
+ action?: {
68
+ type: 'request-contact';
69
+ userId: string;
70
+ };
71
+ /** 群 Notify 12/13 的结构化置顶 side-effect;仅 coordinator 消费,不作为最终展示文案或会话摘要持久化。 */
72
+ pinnedMessageAction?: ImPinnedMessageAction;
73
+ }
74
+ /**
75
+ * 会话「最后一条」的语义摘要(持久化、经字段级 lastMessageSummaryEqual 参与判等):取代中文预览串作为语义承重。
76
+ * `system` 分支直接持归一后的 ImSystemMessageBody(供 formatLastMessageSummary 复用 formatSystemMessage,selfId 渲染时传入)。
77
+ * EXPRESSION 协议家族按 `imageType` 投影为 `gif` 或静态 `sticker`,供会话列表保留产品差异。
78
+ * 迁移期与 lastMessagePreview 双写并存(阶段 4);判等**禁比对象引用**(否则 hydrate 回读值/重算值每次新建对象即永不相等)。
79
+ */
80
+ type LastMessageSummary = {
81
+ kind: 'text';
82
+ text: string;
83
+ } | {
84
+ kind: 'image';
85
+ } | {
86
+ kind: 'gif';
87
+ } | {
88
+ kind: 'video';
89
+ } | {
90
+ kind: 'file';
91
+ } | {
92
+ kind: 'sticker';
93
+ } | {
94
+ kind: 'contact';
95
+ } | {
96
+ kind: 'miniProgram';
97
+ } | {
98
+ kind: 'game';
99
+ gameType: ImGameType;
100
+ } | {
101
+ kind: 'transfer';
102
+ } | {
103
+ kind: 'call';
104
+ callKind: ImCallKind;
105
+ } | {
106
+ kind: 'redPacket';
107
+ } | {
108
+ kind: 'unknown';
109
+ } | {
110
+ kind: 'voice';
111
+ durationMs?: number;
112
+ } | {
113
+ kind: 'encrypted';
114
+ } | {
115
+ kind: 'recalled';
116
+ by: string;
117
+ } | {
118
+ kind: 'channel-moderation-deleted';
119
+ by: string;
120
+ } | {
121
+ kind: 'system';
122
+ system: ImSystemMessageBody;
123
+ };
124
+ /**
125
+ * 图片消息内容体(contentType==='image')。领域友好类型(string/number),协议 BMessage 的 bytes/bigint 字段在 mapping 层互转。
126
+ * 对齐 Android IMImageMsgBody / BMessage 图片字段(见 outputs/architecture/subagent-read-docs/研究Android图片消息实现.md §B)。
127
+ */
128
+ interface ImImageBody {
129
+ /** 原图地址:接收/已发 = S3 公网直链(BMessage.content);发送乐观占位期 = 本地 objectURL/blobURL。 */
130
+ url: string;
131
+ /** 原图宽(px,BMessage.width)。 */
132
+ width: number;
133
+ /** 原图高(px,BMessage.height)。 */
134
+ height: number;
135
+ /** 缩略图原始字节(内嵌不单传,对应 BMessage.thumbData bytes,mapping 层零转换直传)。UI 层转 blob/dataURL 渲染(im-core 环境无关,不做 base64)。 */
136
+ thumbData?: Uint8Array;
137
+ /** 原图文件 MD5(BMessage.MD5)。 */
138
+ md5?: string;
139
+ /** 图片类型:0=普通,1=gif(BMessage.imageType)。 */
140
+ imageType?: number;
141
+ /** 文件大小(字节;协议 BMessage.fileLength 单位 KB,mapping 层换算)。 */
142
+ size?: number;
143
+ /** 文件解密 TEA key 原始字节(仅密文图片有;接收端解 BContent 后从 BMessage.teaKey 得,用于解下载到的原图文件)。 */
144
+ teaKey?: Uint8Array;
145
+ /** 文件加密类型:0=跟随消息,1=不加密,3=完整 TEA(BMessage.fileEncryptType,见 Android IMFileEncryptType)。 */
146
+ fileEncryptType?: number;
147
+ }
148
+ /**
149
+ * 视频消息内容体(contentType==='video')。领域友好类型(string/number),协议 BMessage 的 bytes/bigint 字段在 mapping 层互转。
150
+ * 对齐 Android IMVideoMsgBody / BMessage 视频字段(见 outputs/architecture/subagent-read-docs/研究Android视频消息实现.md):
151
+ * 视频 URL 进 BMessage.content、封面=本地首帧截图内嵌 thumbData(不单独上传 S3)、duration 单位秒、原文件整体 TEA 加密(对齐图片走 fileEncryptType=3)。
152
+ */
153
+ interface ImVideoBody {
154
+ /** 视频地址:接收/已发 = S3 公网直链(BMessage.content);发送乐观占位期 = 本地 objectURL/blobURL。 */
155
+ url: string;
156
+ /** 视频像素宽(BMessage.width)。Android 发送侧不写 proto,Web 端补充更严谨(Android 收到会读;缺失兜底 0)。 */
157
+ width: number;
158
+ /** 视频像素高(BMessage.height)。 */
159
+ height: number;
160
+ /** 时长(秒,BMessage.duration;proto 单位秒,contentType=3|4 有效)。 */
161
+ duration?: number;
162
+ /** 封面帧原始字节(本地首帧截图,内嵌不单传,对应 BMessage.thumbData bytes)。UI 转 blob 渲染,无需下载即可展示封面。 */
163
+ thumbData?: Uint8Array;
164
+ /** 视频文件 MD5(BMessage.MD5;Android 当前未赋值,保留可选)。 */
165
+ md5?: string;
166
+ /** 文件大小(字节;协议 BMessage.fileLength 单位 KB,mapping 层换算)。 */
167
+ size?: number;
168
+ /** 视频描述(BMessage.describe,可选)。 */
169
+ describe?: string;
170
+ /** 文件解密 TEA key 原始字节(仅密文视频有;接收端解 BContent 后从 BMessage.teaKey 得,用于解下载到的原视频文件)。 */
171
+ teaKey?: Uint8Array;
172
+ /** 文件加密类型:1=不加密,3=完整 TEA(BMessage.fileEncryptType;视频与图片同体系走完整 TEA)。 */
173
+ fileEncryptType?: number;
174
+ }
175
+ /**
176
+ * 文件(附件)消息内容体(contentType==='file')。领域友好类型(string/number),协议 BMessage 的 bytes/bigint 字段在 mapping 层互转。
177
+ * 对齐 Android IMFileMsgBody / BMessage 文件字段(见 outputs/architecture/subagent-read-docs/研究Android文件上传实现.md):
178
+ * 文件 URL 进 BMessage.content、无缩略图/宽高/时长、文件本体走 simple TEA 加密(fileEncryptType=2,仅前 32 字节,大文件性能优 + 跨端一致)。
179
+ */
180
+ interface ImFileBody {
181
+ /** 文件地址:接收/已发 = S3 公网直链(BMessage.content);发送乐观占位期为空字符串(文件不建本地预览 objectURL)。 */
182
+ url: string;
183
+ /** 原始文件名(含扩展名,如 report.pdf;对应 BMessage.fielName——协议字段名是历史 typo,映射层处理)。 */
184
+ fileName: string;
185
+ /** 文件大小(字节;协议 BMessage.fileLength 对 contentType=11 也按 bytes 直写,mapping 层不换算)。 */
186
+ size?: number;
187
+ /** 扩展名(不含点,如 pdf;BMessage.extension):展示图标 / 拼下载文件名用。 */
188
+ extension?: string;
189
+ /** 文件类型归类(0=未知,1=图片,2=视频,3=语音,4=文档;对齐 Android IMFileType,BMessage.fileType):供对端选图标。 */
190
+ fileType?: number;
191
+ /** MIME(如 application/pdf):仅本地/发送端有(协议 BMessage 无 mime 字段),接收端可能缺失需按 extension 兜底;下载时设 blob type / 决定打开方式。 */
192
+ mimeType?: string;
193
+ /** 文件 MD5(BMessage.MD5;Android 文件发送链路未赋值,保留可选)。 */
194
+ md5?: string;
195
+ /** 文件解密 TEA key 原始字节(仅密文文件有;接收端解 BContent 后从 BMessage.teaKey 得,用于解下载到的原文件)。 */
196
+ teaKey?: Uint8Array;
197
+ /** 文件加密类型(1=不加密,2=simple 前32字节 TEA,3=完整 TEA;BMessage.fileEncryptType)。文件消息默认 2(对齐 Android,大文件性能优)。 */
198
+ fileEncryptType?: number;
199
+ }
200
+ /**
201
+ * 语音消息内容体(contentType==='voice')。领域友好类型(string/number),协议 BMessage 的 bytes/bigint 字段在 mapping 层互转。
202
+ * 对齐 Android IMVoiceMsgBody / BMessage 语音字段(见 outputs/architecture/subagent-read-docs/android-voice-message-analysis.md):
203
+ * 语音 URL 进 BMessage.content、文件为 Speex/Ogg(WB 16k mono)、原文件整体 TEA 加密(fileEncryptType=3,与图片/视频同体系)。
204
+ * ⚠ duration 单位=毫秒:Android `VoiceRecorder.getDuration()` 返回 ms 且 `IMVoiceMsgBody.getPbBytes()` 原样写入 BMessage.duration
205
+ *(接收端 `millisecondToInt` 转秒展示);proto 注释「秒」与真实客户端行为冲突,以 Android 源码/真实样本为准(≠ 视频的秒)。
206
+ */
207
+ interface ImVoiceBody {
208
+ /** 语音文件地址:接收/已发 = S3 公网直链(BMessage.content);发送乐观占位期 = 本地 objectURL。 */
209
+ url: string;
210
+ /** 时长(毫秒,BMessage.duration;Android 语音契约为 ms,展示层自行换算秒)。 */
211
+ durationMs: number;
212
+ /** 语音文件 MD5(BMessage.MD5;Android 语音链路未赋值,保留可选)。 */
213
+ md5?: string;
214
+ /** 文件大小(字节;协议 BMessage.fileLength 对 contentType=4 按 proto 注释单位 KB,mapping 层换算。Android 语音不写此字段,仅 Web 补充)。 */
215
+ size?: number;
216
+ /** 文件解密 TEA key 原始字节(仅密文语音有;接收端解 BContent 后从 BMessage.teaKey 得,用于解下载到的语音文件)。 */
217
+ teaKey?: Uint8Array;
218
+ /** 文件加密类型:0=跟随消息,1=不加密,3=完整 TEA(BMessage.fileEncryptType;语音与图片/视频同走完整 TEA)。 */
219
+ fileEncryptType?: number;
220
+ /** 录音振幅波形(0-255):仅发送端本地展示缓存(随消息落盘、刷新后仍可画波形),**不写入 BMessage**(协议无波形字段)。 */
221
+ waveform?: number[];
222
+ }
223
+ /**
224
+ * 表情 / 贴图消息内容体(contentType==='sticker',协议 BMessage.contentType=7)。
225
+ *
226
+ * @remarks 入站兼容全部 EXPRESSION 子类型;本期主动发送 GIF 固定使用
227
+ * `exprType=2`(LINK)与 `imageType=1`(GIF),并上传原始明文字节到 sticker bucket。
228
+ */
229
+ interface ImStickerBody {
230
+ /** 远端 sticker URL;发送上传期间可暂为宿主创建的本地 object URL。 */
231
+ url: string;
232
+ width: number;
233
+ height: number;
234
+ /** 贴图来源类型;2 表示远端链接。 */
235
+ exprType: number;
236
+ /** 图片子类型;1 表示 GIF,0 表示普通静态贴图。 */
237
+ imageType: number;
238
+ /** 原始文件大小(字节;EXPRESSION 的 fileLength 直接使用 bytes)。 */
239
+ size?: number;
240
+ describe?: string;
241
+ contentId?: string;
242
+ packageName?: string;
243
+ /** 部分历史/内置贴图可能携带缩略图;相册 GIF 主动发送不生成。 */
244
+ thumbData?: Uint8Array;
245
+ }
246
+ /** 小游戏类别;wire `gameType=0` 为石头剪刀布,`gameType=1` 为骰子。 */
247
+ type ImGameType = 'rock-paper-scissors' | 'dice';
248
+ /** 石头剪刀布结果值;具体图片含义由产品 renderer 按同一跨端资源表解释。 */
249
+ type ImRockPaperScissorsValue = 1 | 2 | 3;
250
+ /** 骰子点数。 */
251
+ type ImDiceValue = 1 | 2 | 3 | 4 | 5 | 6;
252
+ /**
253
+ * 小游戏消息正文(contentType==='game',协议 BMessage.contentType=27)。
254
+ *
255
+ * @remarks `gameType` 与 `value` 使用判别联合保持值域一致。发送 API 只接收类型,结果在首次发送时生成并写入
256
+ * Outbox;重试、历史与转发均复用该正文,不能再次随机。
257
+ */
258
+ type ImGameBody = {
259
+ gameType: 'rock-paper-scissors';
260
+ value: ImRockPaperScissorsValue;
261
+ } | {
262
+ gameType: 'dice';
263
+ value: ImDiceValue;
264
+ };
265
+ /**
266
+ * 小程序分享卡展示形态;wire `miniProgramShowType` 只在 mapping 层转换。
267
+ *
268
+ * - `default`:0,普通可点卡片
269
+ * - `board-game`:1,棋牌样式且不可点
270
+ * - `board-game-link`:2,棋牌样式且可点
271
+ * - `unknown`:必须同时保留 `rawShowType`,不得猜成可点/不可点
272
+ */
273
+ type ImMiniProgramShowType = 'default' | 'board-game' | 'board-game-link' | 'unknown';
274
+ /**
275
+ * 小程序分享卡片正文(contentType==='miniProgram',协议 BMessage.contentType=22)。
276
+ *
277
+ * @remarks 字段来自 iOS `IMMiniProgramMessageBody` / proto 61-67、80-82、90-93。
278
+ * `currencySource` 对应 proto field 90(无 miniProgram 前缀):`xj` 现金公司、`xy` 信用公司。
279
+ * `subGameName` 保持服务端 JSON 字符串,不在 Core 解析语种表。打开小程序与 X 币/信用 ID 属于宿主,不进本 DTO。
280
+ */
281
+ interface ImMiniProgramBody {
282
+ /** 小程序应用 id(BMessage.miniProgramAppId)。 */
283
+ appId: string;
284
+ host?: string;
285
+ developerId?: string;
286
+ name?: string;
287
+ icon?: string;
288
+ productId?: string;
289
+ isDynamicHost: boolean;
290
+ suffixAddress?: string;
291
+ showType: ImMiniProgramShowType;
292
+ /** 仅 showType='unknown' 时存在,保证未知展示类型可无损往返。 */
293
+ rawShowType?: number;
294
+ description?: string;
295
+ /** 币种公司来源;棋牌点开据此选择信用/现金会员身份。 */
296
+ currencySource?: string;
297
+ currencyType?: string;
298
+ gamePath?: string;
299
+ /** 多语言子游戏名 JSON 字符串,例如 `{"ch":"...","en":"..."}`。 */
300
+ subGameName?: string;
301
+ }
302
+ /** 转账状态。0 待领取 / 1 已收款 / 2 已退还 / 3 已过期(对齐 iOS IMTranferBody,过期 3 以客户端为准)。 */
303
+ type ImTransferStatus = 0 | 1 | 2 | 3;
304
+ /** 转账会话类型。1 个人 / 2 群(BMessage.sessionType)。 */
305
+ type ImTransferSessionType = 1 | 2;
306
+ /**
307
+ * 转账卡片正文(contentType==='transfer',协议 BMessage.contentType=16)。
308
+ *
309
+ * @remarks 字段来自 iOS `IMTranferBody` / proto 34-50。金额为微单位整数的十进制字符串。
310
+ * 钱包 HTTP 记账属于宿主;本 DTO 只承载会话卡片与状态同步。`transferMsgId` 对应 proto `msgId`(field 47),不是 ImMessage.id。
311
+ */
312
+ interface ImTransferBody {
313
+ fromUid: string;
314
+ toUid: string;
315
+ fromUserName?: string;
316
+ toUserName?: string;
317
+ /** 微单位金额(×1_000_000)的十进制字符串。 */
318
+ amount: string;
319
+ currencyType: number;
320
+ status: ImTransferStatus;
321
+ serialNumber: string;
322
+ remark?: string;
323
+ sessionType: ImTransferSessionType;
324
+ transferTime: number;
325
+ receiveTime?: number;
326
+ returnTime?: number;
327
+ isRead?: boolean;
328
+ transferMsgId?: string;
329
+ coinName?: string;
330
+ coinIcon?: string;
331
+ }
332
+ /** 领域通话媒体类型;已从各端 wire 0/1 对调口径归一,宿主禁止再读 raw callType。 */
333
+ type ImCallKind = 'audio' | 'video';
334
+ /**
335
+ * 通话结束结果。Android 跨端同步把未接与取消都写成 callStatus=0,只能归一为 missed。
336
+ * `unknown` 仅表示该端 status 数字无法识别,不得猜成 completed。
337
+ */
338
+ type ImCallOutcome = 'missed' | 'rejected' | 'cancelled' | 'completed' | 'busy' | 'no-permission' | 'interrupted' | 'unknown';
339
+ /**
340
+ * 单聊通话记录正文(contentType==='call',协议 BMessage.contentType=10)。
341
+ *
342
+ * @remarks JSON 在 BMessage.content。Android 本地 / Android 跨端同步 / iOS 三套字段名与 0/1 含义不同,
343
+ * 只在 mapping/call-record-body 归一。记录由各端本地插入,不能当普通聊天发出。
344
+ */
345
+ interface ImCallRecordBody {
346
+ callKind: ImCallKind;
347
+ outcome: ImCallOutcome;
348
+ /** 通话时长;未接通为 0。Android 本地/同步为毫秒,iOS `content` 为秒,入站已换算。 */
349
+ durationMs: number;
350
+ /** 当前账号是否主叫;JSON 无 isCaller 时省略,不得猜。 */
351
+ outgoing?: boolean;
352
+ }
353
+ /**
354
+ * 红包类型。以客户端运行时为准,**不是** proto 注释:
355
+ * 1 拼手气 / 2 普通 / 3 专属 / 4 踩雷。
356
+ */
357
+ type ImRedPacketType = 1 | 2 | 3 | 4;
358
+ /**
359
+ * 红包卡片正文(contentType==='redPacket',协议 BMessage.contentType=25)。
360
+ *
361
+ * @remarks 字段来自 proto 72-79 + 复用 34/35/38/49。领域名 `packetId` 对应 wire `rad_packet_id`。
362
+ * 金额保持 proto string,单位是否微单位未证实,禁止默认套转账 ×1e6。
363
+ * HTTP 详情状态与本地「自己已领」属于宿主投影,不进本 DTO(status 39 / state 53 未裁定)。
364
+ * 本阶段不提供 `sendRedPacket`;出站字段仅用于 codec 往返闭合。
365
+ */
366
+ interface ImRedPacketBody {
367
+ packetId: string;
368
+ packetType: ImRedPacketType;
369
+ amount?: string;
370
+ count?: string;
371
+ showAmount?: boolean;
372
+ thunderRate?: string;
373
+ isThunder?: boolean;
374
+ thunderNumber?: string;
375
+ fromUid?: string;
376
+ toUid?: string;
377
+ currencyType?: number;
378
+ coinName?: string;
379
+ }
380
+ /** 名片类别。`unknown` 必须同时保留 `rawKind`,不得把未知 wire 值猜成个人/群/频道/机器人。 */
381
+ type ImContactKind = 'individual' | 'group' | 'channel' | 'robot' | 'unknown';
382
+ /**
383
+ * 名片消息内容体(contentType==='contact')。字段来自 BMessage contentType=8,但使用领域友好命名:
384
+ * id/icon/name 分别归一为 targetId/avatar/displayName;visitCardKey 归一为 kind,未知值放 rawKind。
385
+ */
386
+ interface ImContactBody {
387
+ /** 被分享对象 id:个人 userId、普通群 groupId、频道 id 或机器人 id。 */
388
+ targetId: string;
389
+ avatar?: string;
390
+ displayName: string;
391
+ username?: string;
392
+ /** Android 合同:0=其他、1=男、2=女;群/频道名片通常为 0。 */
393
+ gender: number;
394
+ kind: ImContactKind;
395
+ /** 仅 kind='unknown' 时存在,保证未知 visitCardKey 可无损往返。 */
396
+ rawKind?: number;
397
+ }
398
+ /**
399
+ * 未知消息正文的稳定领域语义。
400
+ *
401
+ * @remarks 原始 wire contentType 与 parser 异常只属于 `raw` diagnostics,不进入默认产品 DTO,避免 UI 依赖协议数字或异常文案。
402
+ */
403
+ interface UnknownMessageBody {
404
+ /** 未注册 wire 类型与已注册类型解析失败必须可区分,但两者都不能伪装为空文本。 */
405
+ reason: 'unsupported-content-type' | 'malformed-content';
406
+ }
407
+ /** 按 contentType 的结构化内容体。 */
408
+ interface ImMessageBody {
409
+ /** P0 文本内容(明文,解密后的结果)。 */
410
+ text?: string;
411
+ /** contentType==='system' 时的结构化系统提示体(群通知;text 忽略)。 */
412
+ system?: ImSystemMessageBody;
413
+ /** contentType==='image' 时的图片内容体。 */
414
+ image?: ImImageBody;
415
+ /** contentType==='video' 时的视频内容体。 */
416
+ video?: ImVideoBody;
417
+ /** contentType==='voice' 时的语音内容体。 */
418
+ voice?: ImVoiceBody;
419
+ /** contentType==='file' 时的文件(附件)内容体。 */
420
+ file?: ImFileBody;
421
+ /** contentType==='sticker' 时的表情 / GIF 贴图内容体。 */
422
+ sticker?: ImStickerBody;
423
+ /** contentType==='contact' 时的名片内容体。 */
424
+ contact?: ImContactBody;
425
+ /** contentType==='miniProgram' 时的小程序分享卡片。 */
426
+ miniProgram?: ImMiniProgramBody;
427
+ /** contentType==='game' 时的小游戏结果。 */
428
+ game?: ImGameBody;
429
+ /** contentType==='transfer' 时的转账卡片。 */
430
+ transfer?: ImTransferBody;
431
+ /** contentType==='call' 时的单聊通话记录。 */
432
+ call?: ImCallRecordBody;
433
+ /** contentType==='redPacket' 时的红包卡片。 */
434
+ redPacket?: ImRedPacketBody;
435
+ /** contentType==='unknown' 时的稳定未知正文,不含原始 wire type。 */
436
+ unknown?: UnknownMessageBody;
437
+ }
438
+ /**
439
+ * 群 @ 提及范围段(对应 extend.atRange):`userId='0'`=@所有人段。
440
+ * ⚠️ `start`/`end` 坐标**跨端不可信**(实测部分端相对替换前「@昵称␠」串、与正文 `[userId]` token 错位),**展示不使用**;
441
+ * 仅作协议诊断/兼容字段保留。展示按正文 token + atUserIds/atAll 还原(见 resolveMentionSegments),勿把坐标路径接回。
442
+ */
443
+ interface MentionRange {
444
+ userId: string;
445
+ start: number;
446
+ end: number;
447
+ }
448
+ /**
449
+ * 群消息 @ 提及(Android 三写载体的归一解析):`atAll`(MESGrpChat.nNotifyCount=-1)、`atUserIds`(sNotifyUsers 被 @ 成员,
450
+ * 含正文 token 读边界补全)、`ranges`(extend.atRange,可选、仅诊断/兼容——展示**不依赖其坐标**)。正文 `body.text` 保留 `[userId]` token 原样(真相不变);
451
+ * **展示权威来源 = 正文 token + atUserIds/atAll**,由纯函数 `resolveMentionSegments` 还原为高亮段(含 @昵称/@所有人/@我),UI 不自行解析。
452
+ */
453
+ interface ImMessageMention {
454
+ atAll: boolean;
455
+ atUserIds: string[];
456
+ ranges?: MentionRange[];
457
+ /** extend.atUsersName(userId → 发送端携带的昵称):本地查不到成员名时的**末位兜底**(对齐 Android 昵称优先级最后一档,尤其 @自己/非本地缓存成员)。 */
458
+ atUsersName?: Record<string, string>;
459
+ }
460
+ /**
461
+ * 消息失败机器分类(重试 / fail-fast / 统计消费):**不新增值、不放宽为任意字符串**。
462
+ * `unsupported` = 会话类型未接入发送链路(fail-fast,非重试可解)。展示语义另走 `displayCode`(见下)。
463
+ */
464
+ type ImMessageFailureReason = 'timeout' | 'rejected' | 'encrypt_failed' | 'network' | 'unsupported' | 'unknown';
465
+ /**
466
+ * 失败**展示语义**码(强类型枚举,非裸串):供 im-i18n 按 locale 渲染失败气泡文案。
467
+ * 由 `reason` / `SendMediaFailReason` / ACK errcode 穷尽映射而来,与 im-i18n failure catalog 键一一对应(media.* 带 mediaKind 参数)。
468
+ */
469
+ type ImFailureDisplayCode = 'media.empty' | 'media.noCrypto' | 'media.previewFailed' | 'media.encryptFailed' | 'media.uploadFailed' | 'media.cancelled' | 'media.invalidDuration' | 'media.uploadInterrupted' | 'contactRequired' | 'messageRefused' | 'forbiddenOrLeft' | 'contentRejected' | 'groupDismissed' | 'groupUnavailable' | 'channelDismissed' | 'channelMuted' | 'channelNotMember' | 'channelAdminRequired' | 'conversationUnsupported' | 'sendCanceled' | 'sessionLoggedOut' | 'timeout' | 'network' | 'unknown';
470
+ interface ImMessageFailure {
471
+ /** 归一后的错误码(来自 MESChatAck.errcode 或本地失败码,本地码用负值)。 */
472
+ code: number;
473
+ /** 失败机器分类(重试 / fail-fast / 统计消费)。 */
474
+ reason: ImMessageFailureReason;
475
+ /** 失败展示语义码(强类型):UI 经 im-i18n 按 locale 渲染;缺省表示无专门展示码(UI 回退 message)。不参与去重判等。 */
476
+ displayCode?: ImFailureDisplayCode;
477
+ /** 展示模板参数(如未支持会话类型的 type);不参与去重判等。 */
478
+ params?: Record<string, string | number>;
479
+ /** 媒体失败的媒体类型(图片/视频/语音/文件):供 displayCode 为 media.* 时渲染 label。 */
480
+ mediaKind?: MediaKind;
481
+ /** 后端原文 / 诊断兜底文案;仅诊断/展示兜底用,不参与去重判等(见 merge.failureEqual)。 */
482
+ message?: string;
483
+ /**
484
+ * 是否可重试(false=业务级不可重试):如 direct 非好友或 GROUP 不在群(0x8201)、内容为空/违规(0x8202)、群已解散(0x8315)→ UI 隐藏「重发」。
485
+ * 缺省(undefined)视为可重试(向后兼容)。由 code 决定(classifyAckFailure),故不参与去重判等(同 code 必同 retryable)。
486
+ */
487
+ retryable?: boolean;
488
+ }
489
+ interface ImMessageEncryption {
490
+ /** P0 true=消息密文链路, false=明文链路(encrypt=0)。 */
491
+ encrypted: boolean;
492
+ /** 扩展:加密方案标识,默认 'rsa-tea'。 */
493
+ scheme?: 'rsa-tea';
494
+ /** true=密文但解密失败/缺密钥(占位,非真实空正文):供 UI 显式「无法解密」提示,与明文空消息区分。 */
495
+ decryptFailed?: boolean;
496
+ }
497
+ /** 单条消息领域模型(§4.1)。主键为客户端生成的 id(无 serverId)。 */
498
+ interface ImMessage {
499
+ /** P0 客户端生成的稳定消息 ID(UUID),同时作幂等键;刷新后保持。 */
500
+ id: string;
501
+ /** P0 归一化会话 ID。 */
502
+ conversationId: string;
503
+ conversationType: ConversationType;
504
+ /** P0 归一化发送者 ID。 */
505
+ senderId: string;
506
+ contentType: MessageContentType;
507
+ body: ImMessageBody;
508
+ /** P0 统一毫秒 number(本地创建时间,乐观渲染用)。 */
509
+ createdAt: number;
510
+ /** P0 SDK 内部稳定排序键(见 §6.3),宿主只用于排序、不解析语义。 */
511
+ sortKey: string;
512
+ status: MessageSendStatus;
513
+ /** 失败原因(status==='failed' 时)。 */
514
+ failure?: ImMessageFailure;
515
+ /** P0 服务端回传时间(归一为 number)。 */
516
+ serverTime?: number;
517
+ /** P0 默认 'unread'。 */
518
+ receipt: MessageReceipt;
519
+ encryption: ImMessageEncryption;
520
+ /** 群消息 @ 提及(仅群 @ 消息有;解析自 MESGrpChat 明文字段 + extend)。展示走 resolveMentionSegments。 */
521
+ mention?: ImMessageMention;
522
+ /** 扩展:回复。 */
523
+ refMessageId?: string;
524
+ /** 扩展。 */
525
+ edited?: boolean;
526
+ /**
527
+ * 撤回态:撤回者 id(`by`)+ 撤回时间毫秒(`at`)。存在即「已撤回」——正文清空、UI 显示撤回占位气泡(对齐 Android isCancel=1)。
528
+ * 撤回者=自己 → 展示「你撤回了一条消息」;=对端/群成员 → 展示「对方/成员名 撤回了一条消息」。
529
+ */
530
+ recalled?: {
531
+ by: string;
532
+ at: number;
533
+ };
534
+ /**
535
+ * CHANNEL 管理员/社群主全员删除终态。它与发送者本人撤回、`deleteLocal` 完全不同:服务端通过
536
+ * `RADIO_ADMIN_CANCEL_*` 同步给全员,正文不可逆清空,产品统一展示“管理员删除了一条消息”。
537
+ */
538
+ deletedForEveryone?: {
539
+ by: string;
540
+ at: number;
541
+ reason: 'channel-moderation';
542
+ };
543
+ /** 消息表情回应的领域真相;事件日志只留在 Core,SDK 产品 DTO 仅投影聚合结果。 */
544
+ reactions?: ImMessageReactionState;
545
+ /** 扩展位:后端新增语义先落这里,避免污染核心字段。 */
546
+ extensions?: Record<string, unknown>;
547
+ /**
548
+ * 诊断快照(§4.1 诊断口径):后端下发的原始协议字段(已归一为可 JSON 序列化形态)。
549
+ * 默认不填充,仅当映射开启 keepRaw 时附带,便于联调、与后端核对原始下发;不参与任何业务逻辑。
550
+ */
551
+ raw?: ImMessageRaw;
552
+ }
553
+ /** Reaction wire action:0 添加/替换,1 移除。 */
554
+ type ImReactionAction = 0 | 1;
555
+ /** 单条 Reaction 控制事件;它不属于消息时间线。 */
556
+ interface ImReactionEvent {
557
+ id: string;
558
+ conversation: ConversationRef<'direct' | 'group' | 'channel'>;
559
+ parentMessageId: string;
560
+ senderId: string;
561
+ emoji: string;
562
+ action: ImReactionAction;
563
+ createdAt: number;
564
+ }
565
+ /** 父消息上的一个表情聚合。 */
566
+ interface ImReactionAggregate {
567
+ emoji: string;
568
+ count: number;
569
+ mine: boolean;
570
+ latestAt: number;
571
+ participantIds: readonly string[];
572
+ }
573
+ /** Core 持久化状态;`events` 用于去重、乱序和乐观回滚,不进入普通 SDK 产品出口。 */
574
+ interface ImMessageReactionState {
575
+ version: number;
576
+ total: number;
577
+ currentUserEmoji?: string;
578
+ events: readonly ImReactionEvent[];
579
+ aggregates: readonly ImReactionAggregate[];
580
+ }
581
+ /** 置顶消息可展示快照:完整消息可直接复用既有渲染;不支持的内容显式保留部分预览,不伪造空消息。 */
582
+ type ImPinnedMessagePreview = {
583
+ state: 'ready';
584
+ message: ImMessage;
585
+ } | {
586
+ state: 'partial';
587
+ senderId?: string;
588
+ createdAt?: number;
589
+ previewText?: string;
590
+ reason: 'missing-content' | 'unsupported-content';
591
+ };
592
+ /**
593
+ * 单会话当前置顶消息。它与 `ImSession.pinned`(会话列表本地 sticky 偏好)是两套独立语义:
594
+ * 本结构描述服务端 board 的 0/1 条消息置顶真相,主键为 conversationId。
595
+ */
596
+ interface ImPinnedMessage {
597
+ conversationId: string;
598
+ conversationType: Extract<ConversationType, 'group' | 'channel' | 'supergroup'>;
599
+ messageId: string;
600
+ preview: ImPinnedMessagePreview;
601
+ pinnedAt?: number;
602
+ operatorId?: string;
603
+ }
604
+ type ImPinnedMessageActionErrorCode = 'INVALID_BASE64' | 'INVALID_JSON' | 'INVALID_ENVELOPE' | 'INVALID_MESSAGE_CONTENT' | 'CONVERSATION_MISMATCH' | 'INVALID_SNAPSHOT';
605
+ /** Notify 12/13 经 mapper 产出的结构化动作;坏 payload 显式保留错误码,不能等价为 unpin。 */
606
+ type ImPinnedMessageAction = {
607
+ type: 'pin';
608
+ pinnedMessage: ImPinnedMessage;
609
+ } | {
610
+ type: 'unpin';
611
+ } | {
612
+ type: 'invalid';
613
+ code: ImPinnedMessageActionErrorCode;
614
+ };
615
+ /**
616
+ * 后端原始协议字段诊断快照(§4.1)。仅供调试 / 联调 / 与后端核对原始数据,默认不填充。
617
+ * 所有大整数已转 string,保证整体可 JSON.stringify。
618
+ */
619
+ interface ImMessageRaw {
620
+ /** 来源命令字(如 MES_CHAT_DELIVER)。 */
621
+ cmdId?: number;
622
+ /** 协议层 MESChat 关键字段(sFromId/sToId/msgTime 等大整数已转 string)。 */
623
+ chat?: {
624
+ sMsgId: string;
625
+ sFromId: string;
626
+ sToId: string;
627
+ msgType: number;
628
+ encrypt: number;
629
+ msgTime: string;
630
+ extend?: string;
631
+ encryptVersion?: string;
632
+ parentMsgId?: string;
633
+ };
634
+ /** 解码后的内容层 BMessage 关键字段(明文链路)。 */
635
+ content?: {
636
+ contentType: number;
637
+ text?: string;
638
+ };
639
+ /** 内容层无法按预期 codec 解码时的非敏感诊断;不保留原始正文,避免日志或持久化泄露消息内容。 */
640
+ contentDecodeFailure?: {
641
+ codec: 'BMessage';
642
+ byteLength: number;
643
+ };
644
+ /** CHANNEL RadioNotify 诊断字段;17/18/22/23/24 也只保留在这里,不产生 pin/权限副作用。 */
645
+ channelNotify?: {
646
+ notifyType: number;
647
+ historyCursor?: string;
648
+ sequence?: string;
649
+ extend?: string;
650
+ };
651
+ }
652
+
653
+ type EditFailReason = 'not-found' | 'not-own' | 'not-sent' | 'recalled' | 'deleted' | 'expired' | 'unsupported' | 'empty' | 'restricted' | 'unsupported-content' | 'no-key' | 'encrypt-failed' | 'timeout' | 'failed' | 'disconnected';
654
+
655
+ type RecallFailReason = 'not-found' | 'not-own' | 'not-sent' | 'expired' | 'unsupported' | 'timeout' | 'server-expired' | 'failed' | 'disconnected';
656
+
657
+ /**
658
+ * IM 语义文案 catalog 的键、参数和完整性合同。
659
+ * 键描述领域含义而非组件位置,使 Web/H5/SSR 可共享 formatter,宿主布局文案不进入此 schema。
660
+ */
661
+
662
+ /** 系统消息文案(formatSystemMessage 用)。占位符 `{x}` 由 formatSystemMessage 按 body + resolveName 插值。 */
663
+ interface ImSystemCatalog {
664
+ /** 成为联系人后的 direct 建立会话提示。参数:{target}。 */
665
+ directContactEstablished: string;
666
+ /** direct 非好友发送被拒后的提示;“申请添加”由 UI 作为 action 单独渲染。 */
667
+ directContactRequired: string;
668
+ /** direct 消息被对方黑名单策略拒收后的本地系统提示。 */
669
+ directMessageRefused: string;
670
+ /** 自己(operator/target === selfId 时的显示名)。 */
671
+ you: string;
672
+ /** 成员名缺省占位(退群 / 被踢 / 改名 target 缺失时)。 */
673
+ member: string;
674
+ /** 新成员名缺省占位(加群 target 缺失时)。 */
675
+ newMember: string;
676
+ /** 新群主名缺省占位(群主变更 target 缺失时)。 */
677
+ newMaster: string;
678
+ /** 加入群聊。参数:{target}。 */
679
+ groupMemberJoined: string;
680
+ /** 当前账号已经加入普通群。 */
681
+ groupSelfJoined: string;
682
+ /** 当前账号邀请成员加入普通群。参数:{target}。 */
683
+ groupMemberInvitedBySelf: string;
684
+ /** 当前账号作为被邀请者已经加入普通群。 */
685
+ groupSelfInvited: string;
686
+ /** 其他成员邀请目标加入普通群。参数:{operator} {target}。 */
687
+ groupMemberInvitedByOperator: string;
688
+ /** 缺少邀请者时的第三人称加入文案。参数:{target}。 */
689
+ groupMemberInvited: string;
690
+ /** 退出群聊。参数:{target}。 */
691
+ groupMemberLeft: string;
692
+ /** 当前账号主动退出普通群。 */
693
+ groupSelfLeft: string;
694
+ /** 操作者移出成员。参数:{operator} {target}。 */
695
+ groupMemberRemovedByOperator: string;
696
+ /** 当前账号被移出普通群时的固定终态提示。 */
697
+ groupSelfRemoved: string;
698
+ /** 被移出群聊(无操作者)。参数:{target}。 */
699
+ groupMemberRemoved: string;
700
+ /** 修改群昵称。参数:{target}。 */
701
+ groupMemberRenamed: string;
702
+ /** 成为新群主。参数:{target}。 */
703
+ groupMasterChanged: string;
704
+ /** 当前账号成为普通群群主。 */
705
+ groupSelfMasterChanged: string;
706
+ /** 群名称变更。参数:{name}。 */
707
+ groupInfoRenamed: string;
708
+ /** 群资料变更(无新群名)。 */
709
+ groupInfoChanged: string;
710
+ /** 群解散。 */
711
+ groupDismissed: string;
712
+ /** 消息置顶。参数:{operator}。 */
713
+ groupMessagePinned: string;
714
+ /** 移除置顶。参数:{operator}。 */
715
+ groupMessageUnpinned: string;
716
+ channelNameChanged: string;
717
+ channelAvatarChanged: string;
718
+ channelAnnouncementChanged: string;
719
+ channelMemberJoined: string;
720
+ channelSelfJoined: string;
721
+ channelMemberInvited: string;
722
+ channelMemberInvitedBySelf: string;
723
+ channelSelfInvited: string;
724
+ channelMemberInvitedByOperator: string;
725
+ channelMemberLeft: string;
726
+ channelSelfLeft: string;
727
+ /** 当前账号被移出 CHANNEL 时的固定终态提示;独立于 GROUP 文案键维护。 */
728
+ channelSelfRemoved: string;
729
+ channelMemberRemoved: string;
730
+ channelOwnerTransferred: string;
731
+ channelSelfOwnerTransferred: string;
732
+ channelAdminAdded: string;
733
+ channelAdminRemoved: string;
734
+ channelDismissed: string;
735
+ channelAllMuted: string;
736
+ channelAllUnmuted: string;
737
+ channelCreated: string;
738
+ channelPrivateChatDisabled: string;
739
+ channelPrivateChatEnabled: string;
740
+ channelMemberRenamed: string;
741
+ channelMemberMuted: string;
742
+ channelMemberUnmuted: string;
743
+ /** CHANNEL 消息置顶。参数:{operator}。 */
744
+ channelMessagePinned: string;
745
+ /** CHANNEL 移除置顶。参数:{operator}。 */
746
+ channelMessageUnpinned: string;
747
+ channelUnknown: string;
748
+ /** 自己领取了自己发的红包。来源 iOS `redpacket_grabbed_self`。 */
749
+ redPacketClaimedSelf: string;
750
+ /** 他人领取了你的红包。参数:{name}。来源 iOS `redpacket_grabbed_yours`。 */
751
+ redPacketClaimedYours: string;
752
+ /** 你领取了他人的红包。参数:{name}。来源 iOS `redpacket_you_grabbed`。 */
753
+ redPacketYouClaimed: string;
754
+ /** 他人领取了你的红包并踩雷。参数:{name}。来源 iOS `redpacket_grabbed_yours_boom`。 */
755
+ redPacketClaimedYoursThunder: string;
756
+ /** 你领取了他人的红包并踩雷。参数:{recipient} {senderName}。来源 iOS `redpacket_you_grabbed_boom`。 */
757
+ redPacketYouClaimedThunder: string;
758
+ /** 未知事件兜底(仅当无 fallbackText 时用)。 */
759
+ unknown: string;
760
+ }
761
+ /** @提及展示文案(formatMentionAll 用;@所有人 的本地化词,展示按接收端语言、与识别词表分离)。 */
762
+ interface ImMentionCatalog {
763
+ /** @所有人 的本地化词(不含 `@`,由 formatMentionAll 统一拼 `@`)。 */
764
+ all: string;
765
+ }
766
+ /** 媒体占位文案(formatMediaPlaceholder 用;对齐回复摘要 use-message-preview)。 */
767
+ interface ImMediaCatalog {
768
+ image: string;
769
+ video: string;
770
+ voice: string;
771
+ /** 带时长语音。参数:{duration}(已格式化 m:ss / h:mm:ss)。 */
772
+ voiceWithDuration: string;
773
+ file: string;
774
+ /** EXPRESSION + imageType=1。 */
775
+ gif: string;
776
+ /** EXPRESSION 的静态贴图及未知 imageType。 */
777
+ sticker: string;
778
+ contact: string;
779
+ /** 小程序卡片回复摘要,对齐 iOS `min_program_name`。 */
780
+ miniProgram: string;
781
+ /** 转账卡片回复摘要,对齐 iOS `chat_tranfer`。 */
782
+ transfer: string;
783
+ /** 语音通话记录回复摘要。 */
784
+ callAudio: string;
785
+ /** 视频通话记录回复摘要。 */
786
+ callVideo: string;
787
+ /** 红包卡片回复摘要,对齐 iOS `red_envelope` / Android `txt_asset_detail_tab_red_envelope`。 */
788
+ redPacket: string;
789
+ /** 骰子消息的回复摘要。 */
790
+ gameDice: string;
791
+ /** 石头剪刀布消息的回复摘要。 */
792
+ gameRockPaperScissors: string;
793
+ }
794
+ /** 失败气泡文案(formatFailure 用;逐字对齐 core 现有 ImMessageFailure.message,阶段 4b 零回归)。 */
795
+ interface ImFailureCatalog {
796
+ /** 媒体类型名(mediaKind → 文案,供 media.* 模板 {label})。 */
797
+ mediaLabel: Record<MediaKind, string>;
798
+ /** media.empty,参数 {label}。 */
799
+ mediaEmpty: string;
800
+ /** media.noCrypto,参数 {label}。 */
801
+ mediaNoCrypto: string;
802
+ /** media.previewFailed,参数 {label}。 */
803
+ mediaPreviewFailed: string;
804
+ /** media.encryptFailed,参数 {label}。 */
805
+ mediaEncryptFailed: string;
806
+ /** media.uploadFailed,参数 {label}。 */
807
+ mediaUploadFailed: string;
808
+ /** media.cancelled,参数 {label}。 */
809
+ mediaCancelled: string;
810
+ /** media.invalidDuration,参数 {label}。 */
811
+ mediaInvalidDuration: string;
812
+ /** media.uploadInterrupted,参数 {label}。 */
813
+ mediaUploadInterrupted: string;
814
+ /** direct 0x8201:双方已不是好友。 */
815
+ contactRequired: string;
816
+ /** direct 0x8212:消息因对方黑名单策略被拒收。 */
817
+ messageRefused: string;
818
+ /** forbiddenOrLeft(GROUP ACK ERR_CHAT_FORBIDDEN)。 */
819
+ forbiddenOrLeft: string;
820
+ /** contentRejected(ACK ERR_CHAT_UNHEALTHY)。 */
821
+ contentRejected: string;
822
+ /** groupDismissed(ACK ERR_GROUP_DISMISSED)。 */
823
+ groupDismissed: string;
824
+ /** groupUnavailable(ACK ERR_GROUP_NETWORKEXCEPTION)。 */
825
+ groupUnavailable: string;
826
+ /** CHANNEL RADIO ACK 0x8501–0x8504。 */
827
+ channelDismissed: string;
828
+ channelMuted: string;
829
+ channelNotMember: string;
830
+ channelAdminRequired: string;
831
+ /** conversationUnsupported(未接入会话类型),参数 {type}。 */
832
+ conversationUnsupported: string;
833
+ /** sessionLoggedOut(生命周期 logout:退账号时在途消息移出自动复发,可手动重试)。 */
834
+ sessionLoggedOut: string;
835
+ /** sendCanceled(仅本地取消从未进入 transport 的 Outbox queued 项,不表示远端撤回)。 */
836
+ sendCanceled: string;
837
+ /** timeout / network / unknown 通用兜底。 */
838
+ timeout: string;
839
+ network: string;
840
+ unknown: string;
841
+ }
842
+ /** 会话列表预览文案(formatLastMessageSummary 用;media 对齐 session-mapper「[图片]…」;system 复用 formatSystemMessage 带名版)。 */
843
+ interface ImLastMessageCatalog {
844
+ image: string;
845
+ /** GIF 会话摘要,例如 `[GIF]`。 */
846
+ gif: string;
847
+ /** 静态贴图会话摘要,例如 `[贴图]`。 */
848
+ sticker: string;
849
+ video: string;
850
+ voice: string;
851
+ file: string;
852
+ contact: string;
853
+ /** 小程序卡片会话摘要,对齐 iOS `[%@]` + `min_program_name`。 */
854
+ miniProgram: string;
855
+ /** 转账会话摘要,对齐 iOS `[chat_tranfer]`。 */
856
+ transfer: string;
857
+ /** 语音通话记录会话摘要。 */
858
+ callAudio: string;
859
+ /** 视频通话记录会话摘要。 */
860
+ callVideo: string;
861
+ /** 红包会话摘要,对齐 iOS `[%@]` + `red_envelope` / Android `txt_asset_detail_tab_red_envelope`。 */
862
+ redPacket: string;
863
+ /** 小游戏消息的统一会话列表摘要;不在列表中暴露骰子/RPS 的具体类型。 */
864
+ game: string;
865
+ encrypted: string;
866
+ /** 未注册或无法解析的正文。 */
867
+ unknown: string;
868
+ /** 自己撤回。 */
869
+ recalledBySelf: string;
870
+ /** 他人撤回,参数 {name}。 */
871
+ recalledByOther: string;
872
+ /** CHANNEL 管理员全员删除。 */
873
+ channelModerationDeleted: string;
874
+ }
875
+ /**
876
+ * 命令/生命周期「操作提示」文案 key(formatServiceText 用;供 kit services / runtime 产语义码、宿主按 locale 渲染)。
877
+ * 二期收敛:kit 框架无关层不再直接产中文,改产本联合里的码 + 参数(如 {reason}/{label}/{size}),由宿主经 im-i18n 渲染。
878
+ * 动态原始报错(error.message)作为 `{reason}` 参数拼接,不入 catalog(属诊断,不翻译)。
879
+ */
880
+ type ImServiceTextKey = 'common.notConnected' | 'common.selectOrOpenConversation' | 'common.conversationTypeNotReady' | 'common.conversationNotReady' | `recall.${RecallFailReason}` | `edit.${EditFailReason}` | 'localDelete.done' | 'localDelete.notFound' | 'localDelete.inFlight' | 'localDelete.failed' | 'channelDelete.notFound' | 'channelDelete.unsupported' | 'channelDelete.notSent' | 'channelDelete.unavailable' | 'channelDelete.forbidden' | 'channelDelete.timeout' | 'channelDelete.failed' | 'channelDelete.disconnected' | 'directBlock.contactRequired' | 'groupBlock.left' | 'groupBlock.removed' | 'groupBlock.dismissed' | 'groupBlock.unknownRestricted' | 'channelBlock.guest' | 'channelBlock.left' | 'channelBlock.removed' | 'channelBlock.dismissed' | 'channelBlock.muted' | 'channelBlock.allMembersMuted' | 'channelBlock.currentUserMuted' | 'channelBlock.blacklisted' | 'channelBlock.unknownRestricted' | 'channelProfile.loadFailed' | 'channelProfile.notFound' | 'channelProfile.retryAction' | 'channelMembers.title' | 'channelMembers.searchPlaceholder' | 'channelMembers.loading' | 'channelMembers.loadFailed' | 'channelMembers.retryAction' | 'channelMembers.empty' | 'channelMembers.loadingMore' | 'channelMembers.loadMoreFailed' | 'channelMembers.exhausted' | 'channelMembers.loadMoreAction' | 'label.image' | 'label.video' | 'label.file' | 'label.sticker' | 'send.failed' | 'send.retryFailed' | 'send.cancelFailed' | 'send.mediaFailed' | 'send.mediaEmpty' | 'send.mediaCancelled' | 'send.mediaCountLimit' | 'send.mediaTooLarge' | 'send.videoTooLong' | 'send.videoDurationUnavailable' | 'send.voiceEmpty' | 'send.voiceStorageNotConfigured' | 'send.voiceCancelled' | 'send.voiceFailed' | 'media.voiceNotReady' | 'media.voiceLoadEmpty' | 'media.voiceLoadFailed' | 'media.videoOpenEmpty' | 'media.videoLoadFailed' | 'media.imageInvalid' | 'media.imageLoadFailed' | 'media.fileDownloadEmpty' | 'media.fileDownloadFailed' | 'conversation.muteEnableFailed' | 'conversation.muteDisableFailed' | 'read.markReadFailed' | 'pinned.forbidden' | 'pinned.network' | 'pinned.contract' | 'pinned.unsupported' | 'pinned.failed' | 'pinned.previewUnavailable' | 'pinned.locateFailed' | 'pinned.recalled' | 'pinned.deleted' | 'pinned.protected' | 'pinned.pinAction' | 'pinned.unpinAction' | 'pinned.unpinConfirm' | 'pinned.cancelAction' | 'lifecycle.missingLoginState' | 'lifecycle.prepareFailed' | 'lifecycle.missingConnectParams' | 'lifecycle.connectFailed' | 'lifecycle.disposed' | 'forward.emptyInput' | 'forward.allSkipped' | 'forward.partialSkipped' | 'forward.done' | 'forward.failed';
881
+ /** 命令/生命周期操作提示 catalog(key 齐全,模板含 {reason}/{label}/{size} 占位)。 */
882
+ type ImServiceCatalog = Record<ImServiceTextKey, string>;
883
+ /** 星期文案键按 JavaScript `Date#getDay()` 的 Sunday-first 顺序命名。 */
884
+ type ImWeekdayKey = 'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday';
885
+ /** 会话时间与聊天日期条共享的本地化语义文案。 */
886
+ interface ImTimeCatalog {
887
+ today: string;
888
+ yesterday: string;
889
+ weekdays: Readonly<Record<ImWeekdayKey, string>>;
890
+ }
891
+ /** IM 领域文案 catalog 契约(zh-CN 须齐全)。 */
892
+ interface ImCatalog {
893
+ system: ImSystemCatalog;
894
+ mention: ImMentionCatalog;
895
+ media: ImMediaCatalog;
896
+ failure: ImFailureCatalog;
897
+ lastMessage: ImLastMessageCatalog;
898
+ time: ImTimeCatalog;
899
+ service: ImServiceCatalog;
900
+ }
901
+ /** 非默认 locale 的内置 catalog:允许增量提供,缺失键 fallback 回退 DEFAULT_LOCALE。 */
902
+ type ImPartialCatalog = {
903
+ [K in keyof ImCatalog]?: Partial<ImCatalog[K]>;
904
+ };
905
+ /**
906
+ * 宿主可覆盖的运行时展示文案。
907
+ *
908
+ * `mention` 被明确排除:`mention.all` 不只是 UI 标签,Composer 还会把它写入消息正文,
909
+ * 其它客户端必须用同一组协议词识别。允许单个宿主热改会造成发送词与接收词漂移。
910
+ */
911
+ type ImCatalogOverrides = {
912
+ [K in Exclude<keyof ImCatalog, 'mention'>]?: Partial<ImCatalog[K]>;
913
+ };
914
+
915
+ /**
916
+ * IM 语义文案 catalog 的选择、宿主覆盖与默认语言回退。
917
+ *
918
+ * 输入是标准化 `ImLocale`,输出是完整只读 catalog;缺少的键按「宿主覆盖 → SDK 内置译文 → zh-CN 基线」
919
+ * 逐层回退,不访问 Vue i18n、浏览器语言或宿主 Store,因此可在 SSR、Worker 和测试中复用。
920
+ *
921
+ * 三层组合(优先级从高到低):
922
+ * 1) 宿主运行时覆盖(registerImCatalogOverrides 注入)——供内置语言热修普通展示文案,不必等 SDK 发版。
923
+ * 2) SDK 内置分语言译文(PARTIAL_CATALOGS)——开箱即用主体。
924
+ * 3) DEFAULT_LOCALE=zh-CN 基线——保证键恒全。
925
+ */
926
+
927
+ /**
928
+ * 获取指定 locale 的时间语义文案;时间计算与格式化由 Chat Kit 公共工具负责。
929
+ *
930
+ * @example
931
+ * ```ts
932
+ * getImTimeCatalog('zh-CN').yesterday; // "昨天"
933
+ * ```
934
+ */
935
+ declare function getImTimeCatalog(locale: ImLocale): Readonly<ImTimeCatalog>;
936
+ /**
937
+ * 为 SDK 已内置的 locale 注入普通展示文案覆盖(优先级高于内置译文)。
938
+ *
939
+ * 注册时会校验非空值、文案键和模板占位符,并复制输入以隔离调用方后续修改。
940
+ * `mention.all` 不能覆盖,因为它会进入发送正文并参与跨端识别;新增 locale 也必须随 SDK
941
+ * catalog 与 @全体识别合同一起发布,不能复用现有 locale 槽位伪装。
942
+ * 同一 locale 多次调用会累积合并;注入后清对应 locale 缓存,下次 catalogFor 生效。
943
+ * @example registerImCatalogOverrides('en-US', { failure: { network: 'Network error, retry.' } })
944
+ */
945
+ declare function registerImCatalogOverrides(locale: ImLocale, partial: ImCatalogOverrides): void;
946
+ /**
947
+ * 清空当前进程的全部宿主覆盖并失效缓存。
948
+ *
949
+ * 该状态属于应用级文案配置,不属于账号会话;不要在单个用户登出时调用。
950
+ * 仅用于测试隔离,或应用在无并发渲染时整体重载文案管线。
951
+ */
952
+ declare function resetImCatalogOverrides(): void;
953
+
954
+ /**
955
+ * 创建绑定固定 locale 的框架无关 IM 语义 formatter。
956
+ *
957
+ * 该实例只把 Core 的消息、失败和媒体语义转换成文案,不管理响应式 locale,也不包含按钮、Dialog
958
+ * 等宿主 UI 文案。动态语言场景应在调用时传当前 locale,避免旧实例缓存宿主状态。
959
+ */
960
+
961
+ /** 绑定某 locale 的 IM 文案实例(供固定 locale 场景,如 SSR 预渲染 / 测试)。响应式场景用独立 formatXxx(每次传当前 locale)。 */
962
+ interface ImI18n {
963
+ readonly locale: ImLocale;
964
+ formatSystemMessage: (body: ImSystemMessageBody, ctx: {
965
+ resolveName: (uid: string) => string;
966
+ selfId: string;
967
+ }) => string;
968
+ formatLastMessageSummary: (summary: LastMessageSummary, ctx: {
969
+ resolveName: (uid: string) => string;
970
+ selfId: string;
971
+ }) => string;
972
+ formatMediaPlaceholder: (contentType: MessageContentType, opts?: {
973
+ durationMs?: number;
974
+ stickerImageType?: number;
975
+ gameType?: ImGameType;
976
+ }) => string;
977
+ formatMentionAll: () => string;
978
+ formatFailure: (displayCode: ImFailureDisplayCode, opts?: {
979
+ mediaKind?: MediaKind;
980
+ params?: Record<string, string | number>;
981
+ }) => string;
982
+ }
983
+ /**
984
+ * 创建 IM 文案工厂:`createImI18n({ defaultLocale })(locale?)` 得到绑定该 locale 的实例(未传 locale 用 defaultLocale)。
985
+ * defaultLocale 对齐宿主默认显示语言(NUXT_PUBLIC_DEFAULT_LOCALE),缺省 DEFAULT_LOCALE=zh-CN;
986
+ * 缺译回退基准恒为 DEFAULT_LOCALE(保证有全量译文),见 catalogFor。
987
+ */
988
+ declare function createImI18n(opts?: {
989
+ defaultLocale?: ImLocale;
990
+ }): (locale?: ImLocale) => ImI18n;
991
+
992
+ /**
993
+ * 把 Core/SDK 稳定失败分类格式化为用户可读文案。
994
+ * 不展示 raw cause、协议错误体或凭据,未知分类使用明确通用失败文案而不是空字符串。
995
+ */
996
+
997
+ /** formatFailure 上下文:locale + 媒体类型(media.* 的 label)+ 模板参数(如 conversationUnsupported 的 type)。 */
998
+ interface FormatFailureContext {
999
+ locale: ImLocale;
1000
+ mediaKind?: MediaKind;
1001
+ params?: Record<string, string | number>;
1002
+ }
1003
+ /**
1004
+ * 失败展示码 → 气泡文案。media.* 用 mediaKind → label 插值;ACK / 通用码直出;conversationUnsupported 插 params.type。
1005
+ * 逐字对齐 core 现有 ImMessageFailure.message(阶段 4b 零回归)。switch 穷尽 ImFailureDisplayCode(编译期兜底)。
1006
+ */
1007
+ declare function formatFailure(displayCode: ImFailureDisplayCode, ctx: FormatFailureContext): string;
1008
+
1009
+ /**
1010
+ * 格式化会话列表最新消息摘要。
1011
+ * 输入只使用 Core 稳定语义,不解析 raw message body;系统、撤回、媒体等分支由 catalog 统一表达。
1012
+ */
1013
+
1014
+ /** formatLastMessageSummary 上下文(同 formatSystemMessage:locale + 名称解析 + selfId)。 */
1015
+ interface FormatLastMessageContext {
1016
+ locale: ImLocale;
1017
+ resolveName: (uid: string) => string;
1018
+ selfId: string;
1019
+ }
1020
+ /**
1021
+ * 会话列表「最后一条」摘要 → 展示文案。text 直出正文;media 用会话列表占位([图片]…[加密消息],区别于回复摘要 formatMediaPlaceholder 的「照片…」);
1022
+ * system 复用 formatSystemMessage(统一带成员名版,消除会话列表/气泡两套模板,见阶段 4a 决策);recalled 按 by 是否 self 渲染。
1023
+ */
1024
+ declare function formatLastMessageSummary(summary: LastMessageSummary, ctx: FormatLastMessageContext): string;
1025
+
1026
+ /** 把图片、视频、语音、文件等正文类型映射为 locale 对应的稳定媒体占位文案。 */
1027
+
1028
+ /** formatMediaPlaceholder 上下文:locale + 媒体正文中影响占位文案的稳定子类型。 */
1029
+ interface FormatMediaPlaceholderContext {
1030
+ locale: ImLocale;
1031
+ durationMs?: number;
1032
+ /** EXPRESSION 的协议 imageType;1=GIF,其余/缺省按静态贴图展示。 */
1033
+ stickerImageType?: number;
1034
+ /** 小游戏类型;缺失或非法时不把损坏正文伪装成任一种游戏。 */
1035
+ gameType?: ImGameType;
1036
+ /** 通话记录媒体类型;缺失时不把损坏正文伪装成语音或视频通话。 */
1037
+ callKind?: 'audio' | 'video';
1038
+ }
1039
+ /**
1040
+ * 媒体消息 → 单行占位文案(回复摘要 / 会话预览降级用)。对齐 use-message-preview:
1041
+ * image→照片、sticker→GIF/贴图、game→骰子/石头剪刀布、video→视频、file→文件、contact→名片、
1042
+ * miniProgram→小程序、voice→「语音」(durationMs>0 时「语音 m:ss」)。text / system 以及缺少合法 gameType 的损坏正文返回空串。
1043
+ */
1044
+ declare function formatMediaPlaceholder(contentType: MessageContentType, ctx: FormatMediaPlaceholderContext): string;
1045
+
1046
+ /**
1047
+ * 生成 @所有人 的接收端展示文案。
1048
+ * 展示按接收端当前语言(本函数按 locale 取词),与「识别发送端多语言正文」的匹配词表分离;
1049
+ * `@` 前缀在此统一拼接,catalog 只存可翻译的词(如「所有人」/「All」),避免各宿主各存一份。
1050
+ */
1051
+
1052
+ /**
1053
+ * @所有人 展示文案 → `@` + 当前 locale 的本地化词。供宿主注入 resolveTextSegments 的 `atAllDisplay`,
1054
+ * 使收到的 @所有人 段按接收端语言展示(不随发送端原文)。
1055
+ * @example formatMentionAll('en-US') // '@All'
1056
+ */
1057
+ declare function formatMentionAll(locale: ImLocale): string;
1058
+
1059
+ /**
1060
+ * 格式化 Chat Kit service 返回的稳定文本键与参数。
1061
+ * Service 不依赖 locale 或宿主 i18n;只有 Web/H5 最终反馈边界调用本函数生成文案。
1062
+ */
1063
+
1064
+ /** formatServiceText 上下文:locale + 模板参数(如 {reason}=原始报错、{label}=媒体类型名、{size}=字节数)。 */
1065
+ interface FormatServiceTextContext {
1066
+ locale: ImLocale;
1067
+ params?: Record<string, string | number>;
1068
+ }
1069
+ /**
1070
+ * 命令/生命周期「操作提示」码 → 文案(供 kit services / runtime 产码、宿主按 locale 渲染)。
1071
+ * 平铺 key + 通用 `{...}` 插值(不做穷尽 switch,key 联合由 ImServiceTextKey 编译期保证齐全)。
1072
+ * 动态原始报错经 params.reason 拼接,不入 catalog(诊断,不翻译)。
1073
+ */
1074
+ declare function formatServiceText(key: ImServiceTextKey, ctx: FormatServiceTextContext): string;
1075
+
1076
+ /**
1077
+ * 把已归一化系统消息语义格式化为当前 locale 文案。
1078
+ * 不读取协议 notify/raw payload;缺字段使用 catalog 的稳定降级文案,不猜测操作者或目标身份。
1079
+ */
1080
+
1081
+ /** formatSystemMessage 上下文:locale + 名称解析(宿主提供,已按备注>群昵称>昵称解析)+ selfId(判「你」)。 */
1082
+ interface FormatSystemMessageContext {
1083
+ locale: ImLocale;
1084
+ resolveName: (uid: string) => string;
1085
+ selfId: string;
1086
+ }
1087
+ /**
1088
+ * 系统消息(群通知)→ 展示文案:按 eventType 取 catalog 模板 + 插值成员名。对齐宿主 MessageThread 气泡口径
1089
+ *(带成员名、operator/target===selfId → 「你」、target 缺失用缺省占位「新成员/成员/新群主」)。
1090
+ * 已知 eventType 一律走 catalog;仅 unknown 用 fallbackText(后端原文)兜底、无则 catalog.system.unknown(§C.3 fallback 矩阵)。
1091
+ */
1092
+ declare function formatSystemMessage(body: ImSystemMessageBody, ctx: FormatSystemMessageContext): string;
1093
+
1094
+ export { DEFAULT_LOCALE, createImI18n, formatFailure, formatLastMessageSummary, formatMediaPlaceholder, formatMentionAll, formatServiceText, formatSystemMessage, getImTimeCatalog, normalizeImLocale, registerImCatalogOverrides, resetImCatalogOverrides };
1095
+ export type { FormatFailureContext, FormatLastMessageContext, FormatMediaPlaceholderContext, FormatServiceTextContext, FormatSystemMessageContext, ImCatalog, ImCatalogOverrides, ImFailureCatalog, ImI18n, ImLastMessageCatalog, ImLocale, ImMediaCatalog, ImMentionCatalog, ImPartialCatalog, ImServiceCatalog, ImServiceTextKey, ImSystemCatalog, ImTimeCatalog, ImWeekdayKey };