weaver-work-cli 0.1.4 → 0.1.5

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.
Files changed (87) hide show
  1. package/dist/internal/e10/request-runtime.js +3 -2
  2. package/dist/shortcuts/index.js +4 -0
  3. package/dist/shortcuts/meeting/continuation.js +70 -0
  4. package/dist/shortcuts/meeting/errors.js +63 -0
  5. package/dist/shortcuts/meeting/host.js +167 -0
  6. package/dist/shortcuts/meeting/index.js +123 -0
  7. package/dist/shortcuts/meeting/manifest.js +64 -0
  8. package/dist/shortcuts/meeting/operations/catalogs.js +58 -0
  9. package/dist/shortcuts/meeting/operations/change.js +242 -0
  10. package/dist/shortcuts/meeting/operations/conflicts.js +142 -0
  11. package/dist/shortcuts/meeting/operations/create.js +349 -0
  12. package/dist/shortcuts/meeting/operations/env.js +191 -0
  13. package/dist/shortcuts/meeting/operations/lifecycle.js +139 -0
  14. package/dist/shortcuts/meeting/operations/meetings.js +73 -0
  15. package/dist/shortcuts/meeting/operations/receipts.js +195 -0
  16. package/dist/shortcuts/meeting/operations/registry.js +47 -0
  17. package/dist/shortcuts/meeting/operations/rooms.js +132 -0
  18. package/dist/shortcuts/meeting/operations/shared.js +112 -0
  19. package/dist/shortcuts/meeting/operations/signs.js +41 -0
  20. package/dist/shortcuts/meeting/operations/types.js +25 -0
  21. package/dist/shortcuts/meeting/operations.js +19 -0
  22. package/dist/shortcuts/yimiaoban/continuation.js +70 -0
  23. package/dist/shortcuts/yimiaoban/errors.js +61 -0
  24. package/dist/shortcuts/yimiaoban/host.js +469 -0
  25. package/dist/shortcuts/yimiaoban/index.js +271 -0
  26. package/dist/shortcuts/yimiaoban/manifest.js +28 -0
  27. package/dist/shortcuts/yimiaoban/operations/file-user.js +288 -0
  28. package/dist/shortcuts/yimiaoban/operations/group-read.js +477 -0
  29. package/dist/shortcuts/yimiaoban/operations/group-write.js +591 -0
  30. package/dist/shortcuts/yimiaoban/operations/msg-parse.js +429 -0
  31. package/dist/shortcuts/yimiaoban/operations/msg-sync.js +519 -0
  32. package/dist/shortcuts/yimiaoban/operations/msg-write.js +593 -0
  33. package/dist/shortcuts/yimiaoban/operations/person.js +102 -0
  34. package/dist/shortcuts/yimiaoban/operations/registry.js +38 -0
  35. package/dist/shortcuts/yimiaoban/operations/session.js +437 -0
  36. package/dist/shortcuts/yimiaoban/operations/shared.js +236 -0
  37. package/dist/shortcuts/yimiaoban/operations/types.js +27 -0
  38. package/dist/shortcuts/yimiaoban/operations/write-util.js +78 -0
  39. package/dist/shortcuts/yimiaoban/operations.js +8 -0
  40. package/docs/_catalog.md +6 -0
  41. package/docs/meeting.md +48 -0
  42. package/docs/yimiaoban.md +86 -0
  43. package/package.json +1 -1
  44. package/skill-template/business-info.json +4 -1
  45. package/skill-template/domains/meeting.md +3 -0
  46. package/skill-template/domains/yimiaoban.md +21 -0
  47. package/skills/weaver-e10-calendar/SKILL.md +1 -0
  48. package/skills/weaver-e10-esb/SKILL.md +1 -0
  49. package/skills/weaver-e10-esb/references/source-manifest.json +3 -3
  50. package/skills/weaver-e10-hrm/SKILL.md +1 -0
  51. package/skills/weaver-e10-jiuchuanhui/SKILL.md +1 -0
  52. package/skills/weaver-e10-jucailin/SKILL.md +1 -0
  53. package/skills/weaver-e10-mail/SKILL.md +1 -0
  54. package/skills/weaver-e10-meeting/SKILL.md +106 -0
  55. package/skills/weaver-e10-meeting/product.json +8 -0
  56. package/skills/weaver-e10-meeting/references/meeting-change.md +67 -0
  57. package/skills/weaver-e10-meeting/references/meeting-create.md +52 -0
  58. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +57 -0
  59. package/skills/weaver-e10-meeting/references/meeting-queries.md +61 -0
  60. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +48 -0
  61. package/skills/weaver-e10-meeting/references/meeting-update.md +7 -0
  62. package/skills/weaver-e10-meeting/references/source-manifest.json +50 -0
  63. package/skills/weaver-e10-plan/SKILL.md +1 -0
  64. package/skills/weaver-e10-plan/references/source-manifest.json +3 -3
  65. package/skills/weaver-e10-qiyecheng/SKILL.md +1 -0
  66. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +3 -3
  67. package/skills/weaver-e10-shared/SKILL.md +4 -0
  68. package/skills/weaver-e10-skill-maker/SKILL.md +1 -0
  69. package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
  70. package/skills/weaver-e10-wenshuding/SKILL.md +1 -0
  71. package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
  72. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +5 -5
  73. package/skills/weaver-e10-yimiaoban/SKILL.md +104 -0
  74. package/skills/weaver-e10-yimiaoban/product.json +8 -0
  75. package/skills/weaver-e10-yimiaoban/references/ding-write.md +68 -0
  76. package/skills/weaver-e10-yimiaoban/references/error-codes.md +85 -0
  77. package/skills/weaver-e10-yimiaoban/references/field-resolution.md +46 -0
  78. package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +54 -0
  79. package/skills/weaver-e10-yimiaoban/references/group-read.md +55 -0
  80. package/skills/weaver-e10-yimiaoban/references/group-write.md +72 -0
  81. package/skills/weaver-e10-yimiaoban/references/msg-read.md +59 -0
  82. package/skills/weaver-e10-yimiaoban/references/msg-write.md +64 -0
  83. package/skills/weaver-e10-yimiaoban/references/person.md +44 -0
  84. package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +61 -0
  85. package/skills/weaver-e10-yimiaoban/references/session-sysmsg.md +47 -0
  86. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +485 -0
  87. package/skills/weaver-e10-ziguanjia/SKILL.md +1 -0
@@ -0,0 +1,519 @@
1
+ import { ymbValidationError } from '../errors.js';
2
+ import { READ, schemaOf, ymbSuccess, } from './types.js';
3
+ import { assertImOk, compact, formatFullTime, makeMinMaxIdByTm, minMsgId, parseMsg, parseTimeToSec, resolveAuthUid, str, todayRange, unwrapInner, } from './shared.js';
4
+ import { collapseNewlines, collectI18nIds, collectPersonIds, extractFiles, extractImgs, extractVideos, parseContent, senderName, } from './msg-parse.js';
5
+ const MAX_MSG_ID = String((1n << 63n) - 1n);
6
+ const DEFAULT_PAGE_SIZE = 20;
7
+ const MAX_PAGE_SIZE = 50;
8
+ /** 时间/最新/游标/显式 id 计算 start/end。 */
9
+ function resolveStartEnd(args, fallback = {}) {
10
+ // 1) 显式 start/end 最高优先
11
+ if (args.start !== undefined && args.start !== null && args.start !== '') {
12
+ const start = String(args.start);
13
+ const end = args.end !== undefined && args.end !== null && args.end !== '' ? String(args.end) : '0';
14
+ return { start, end };
15
+ }
16
+ if (args.latest === true) {
17
+ return { start: MAX_MSG_ID, end: '0' };
18
+ }
19
+ // 2) 时间范围
20
+ if ((args.from !== undefined && args.from !== null && args.from !== '')
21
+ || (args.to !== undefined && args.to !== null && args.to !== '')) {
22
+ const fromSec = parseTimeToSec(args.from, false);
23
+ const toSec = parseTimeToSec(args.to, true);
24
+ if (toSec > 0) {
25
+ const start = makeMinMaxIdByTm(toSec).max;
26
+ const end = fromSec > 0 ? makeMinMaxIdByTm(fromSec).min : '0';
27
+ return { start, end };
28
+ }
29
+ return { start: MAX_MSG_ID, end: '0' };
30
+ }
31
+ // 3) 默认当天
32
+ const range = todayRange();
33
+ const startTm = fallback.toSec || range.toSec;
34
+ const endTm = fallback.fromSec || range.fromSec;
35
+ if (startTm > 0)
36
+ return { start: makeMinMaxIdByTm(startTm).max, end: makeMinMaxIdByTm(endTm).min };
37
+ return { start: MAX_MSG_ID, end: '0' };
38
+ }
39
+ function pageSizeOf(args) {
40
+ const raw = args.num;
41
+ if (raw === undefined || raw === null || raw === '')
42
+ return DEFAULT_PAGE_SIZE;
43
+ const n = Number(raw);
44
+ if (!Number.isInteger(n) || n <= 0)
45
+ throw ymbValidationError('num_invalid', 'num 必须是正整数');
46
+ return Math.min(n, MAX_PAGE_SIZE);
47
+ }
48
+ /** `actionMsg.code=1303`(无聊天记录)时按成功空列表返回,并说明本次口径。 */
49
+ function emptyPageNote(args) {
50
+ if (args.latest === true)
51
+ return '该会话暂无任何消息记录';
52
+ const hasRange = (args.from !== undefined && args.from !== '') || (args.to !== undefined && args.to !== '');
53
+ if (hasRange)
54
+ return '该时间范围内无消息记录';
55
+ return '当天无消息(如需最新消息请传 latest:true,或指定 from/to 时间范围)';
56
+ }
57
+ /** 单页返回公共字段:`hasMore` + 下一页锚点 + 去重提示。 */
58
+ function pageMeta(messages, args) {
59
+ const hasMore = messages.length >= pageSizeOf(args);
60
+ const nextStart = minMsgId(messages);
61
+ return {
62
+ hasMore,
63
+ ...(hasMore && nextStart
64
+ ? { nextStart, dedupeHint: '下一页 start 传 nextStart;服务端会重复返回该条(边界消息),请按 msgid 去重' }
65
+ : {}),
66
+ };
67
+ }
68
+ /** 富化消息列表:并行取 i18n + 人员姓名,逐条解析 content/sender/media。 */
69
+ async function enrichMessages(rawRows, host, options = {}) {
70
+ const rows = rawRows.filter(Boolean);
71
+ if (rows.length === 0)
72
+ return [];
73
+ const i18nIds = collectI18nIds(rows);
74
+ const personIds = collectPersonIds(rows);
75
+ const fromUids = rows.map((r) => r.from_uid).filter((x) => Boolean(x));
76
+ for (const uid of fromUids)
77
+ personIds.push(uid);
78
+ const [i18nMap, personMap] = await Promise.all([
79
+ host.fetchI18nLabels(i18nIds),
80
+ host.resolveUsersByIds([...new Set(personIds)]),
81
+ ]);
82
+ const nameMap = {};
83
+ for (const [uid, p] of Object.entries(personMap))
84
+ nameMap[uid] = p.name;
85
+ const enriched = [];
86
+ for (const row of rows) {
87
+ const msgObj = parseMsg(row.msg);
88
+ const parsed = parseContent(msgObj, { i18nMap, nameMap });
89
+ const imgs = msgObj ? extractImgs(msgObj) : [];
90
+ const videos = msgObj ? extractVideos(msgObj) : [];
91
+ const files = msgObj ? extractFiles(msgObj) : [];
92
+ const imgFormat = options.imgFormat || 'small';
93
+ const media = [];
94
+ for (const img of imgs) {
95
+ media.push({
96
+ kind: 'img',
97
+ fileId: img.fileId,
98
+ name: img.name,
99
+ w: img.w,
100
+ h: img.h,
101
+ previewUrl: await host.buildPreviewUrl({
102
+ fileId: img.fileId,
103
+ msgid: String(row.ser_msgid ?? ''),
104
+ kind: 'img',
105
+ imgFormat: imgFormat,
106
+ ...(options.groupId ? { groupId: options.groupId } : {}),
107
+ ...(options.user ? { user: options.user } : {}),
108
+ }),
109
+ downloadUrl: await host.buildDownloadUrl({
110
+ fileId: img.fileId,
111
+ msgid: String(row.ser_msgid ?? ''),
112
+ kind: 'img',
113
+ imgFormat: imgFormat,
114
+ ...(options.groupId ? { groupId: options.groupId } : {}),
115
+ ...(options.user ? { user: options.user } : {}),
116
+ }),
117
+ });
118
+ }
119
+ for (const v of videos) {
120
+ media.push({
121
+ kind: 'video',
122
+ fileId: v.fileId,
123
+ name: v.name,
124
+ ext: v.ext,
125
+ previewUrl: await host.buildPreviewUrl({
126
+ fileId: v.fileId,
127
+ msgid: String(row.ser_msgid ?? ''),
128
+ kind: 'video',
129
+ ...(options.groupId ? { groupId: options.groupId } : {}),
130
+ ...(options.user ? { user: options.user } : {}),
131
+ }),
132
+ downloadUrl: await host.buildDownloadUrl({
133
+ fileId: v.fileId,
134
+ msgid: String(row.ser_msgid ?? ''),
135
+ kind: 'video',
136
+ ...(options.groupId ? { groupId: options.groupId } : {}),
137
+ ...(options.user ? { user: options.user } : {}),
138
+ }),
139
+ });
140
+ }
141
+ for (const f of files) {
142
+ media.push({
143
+ kind: 'file',
144
+ fileId: f.fileId,
145
+ name: f.name,
146
+ size: f.size,
147
+ downloadUrl: await host.buildDownloadUrl({
148
+ fileId: f.fileId,
149
+ msgid: String(row.ser_msgid ?? ''),
150
+ kind: 'img',
151
+ imgFormat: imgFormat,
152
+ ...(options.groupId ? { groupId: options.groupId } : {}),
153
+ ...(options.user ? { user: options.user } : {}),
154
+ }),
155
+ });
156
+ }
157
+ const sender = senderName(msgObj || {}, row.from_uid, options.authUid, nameMap);
158
+ enriched.push({
159
+ msgid: str(row.ser_msgid),
160
+ sender,
161
+ from_uid: str(row.from_uid),
162
+ time: formatFullTime(row.msgtime),
163
+ msgtime: row.msgtime,
164
+ type: parsed.type,
165
+ typeName: parsed.typeName,
166
+ title: options.titleFirst ? parsed.title : undefined,
167
+ content: collapseNewlines(parsed.content),
168
+ must: parsed.must,
169
+ fileName: parsed.fileName,
170
+ media,
171
+ ...(msgObj ? { msg: msgObj } : {}),
172
+ });
173
+ }
174
+ return enriched;
175
+ }
176
+ /** 拉单聊消息(默认当天,单页,倒序)。 */
177
+ const msgSyncSingle = {
178
+ name: 'yimiaoban.msg.sync.single',
179
+ resource: 'msg',
180
+ method: 'msg.sync.single',
181
+ action: 'yimiaoban.msg.sync.single',
182
+ summary: '拉取与某人的单聊消息(倒序单页,默认当天;时间范围转消息 id 区间)',
183
+ capability: 'msg',
184
+ risk: READ,
185
+ inputSchema: schemaOf({
186
+ fromUid: { type: 'string', pattern: '^\\d+$', description: '对方 uid(必填)' },
187
+ fromCid: { type: 'string', pattern: '^\\d+$', description: '对方 cid(必填)' },
188
+ num: { type: 'integer', minimum: 1, maximum: 50, description: '单页数量(默认 20,最多 50)' },
189
+ latest: { type: 'boolean', description: '拉最新不限日期' },
190
+ from: { type: 'string', description: '起始时间(YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss 或 unix 秒)' },
191
+ to: { type: 'string', description: '结束时间' },
192
+ start: { type: 'string', description: '显式起始消息 id(最高优先)' },
193
+ end: { type: 'string', description: '显式结束消息 id' },
194
+ imgFormat: { type: 'string', enum: ['small', 'large', 'original'], description: '预览规格(默认 small)' },
195
+ }, { required: ['fromUid', 'fromCid'], description: '拉取单聊消息' }),
196
+ handler: async (args, { host }) => {
197
+ const fromUid = String(args.fromUid);
198
+ const fromCid = String(args.fromCid);
199
+ if (!fromUid || !fromCid || fromUid === '0' || fromCid === '0') {
200
+ throw ymbValidationError('id_invalid', 'fromUid/fromCid 必须非 0');
201
+ }
202
+ const { start, end } = resolveStartEnd(args);
203
+ const body = {
204
+ trans_id: 0,
205
+ dev: 4,
206
+ enc_type: 0,
207
+ datas: [{ from_cid: fromCid, from_uid: fromUid, start, end, max_num: pageSizeOf(args) }],
208
+ };
209
+ const res = await host.request({
210
+ method: 'POST',
211
+ path: '/api/em/msg/executeIm/msg/syncSingleMsg',
212
+ body,
213
+ operation: 'yimiaoban.msg.sync.single',
214
+ });
215
+ const inner = unwrapInner(res);
216
+ const outerDatas = inner?.datas || [];
217
+ const rawRows = [];
218
+ for (const group of outerDatas) {
219
+ const list = group?.datas || group?.data || [];
220
+ if (Array.isArray(list))
221
+ rawRows.push(...list);
222
+ }
223
+ // 1303=无聊天记录:按成功空列表返回(与群聊口径一致),并说明本次口径
224
+ const actCode = Number(res?.data?.actionMsg?.code ?? -1);
225
+ if (actCode !== 1303)
226
+ assertImOk(res, '拉取单聊消息');
227
+ const authUid = await resolveAuthUid(host);
228
+ const messages = await enrichMessages(rawRows, host, {
229
+ authUid,
230
+ user: { cid: fromCid, uid: fromUid },
231
+ imgFormat: args.imgFormat,
232
+ });
233
+ return ymbSuccess('yimiaoban.msg.sync.single', {
234
+ total: messages.length,
235
+ messages,
236
+ range: { from: args.from, to: args.to, start, end },
237
+ ...pageMeta(messages, args),
238
+ ...(actCode === 1303 ? { empty: true, emptyReason: emptyPageNote(args) } : {}),
239
+ });
240
+ },
241
+ };
242
+ /** 拉群聊消息。 */
243
+ const msgSyncGroup = {
244
+ name: 'yimiaoban.msg.sync.group',
245
+ resource: 'msg',
246
+ method: 'msg.sync.group',
247
+ action: 'yimiaoban.msg.sync.group',
248
+ summary: '拉取某群聊天消息(倒序单页,默认当天;type=0 必传)',
249
+ capability: 'msg',
250
+ risk: READ,
251
+ inputSchema: schemaOf({
252
+ groupId: { type: 'string', pattern: '^\\d+$', description: '群 id(必填)' },
253
+ num: { type: 'integer', minimum: 1, maximum: 50, description: '单页数量(默认 20,最多 50)' },
254
+ latest: { type: 'boolean', description: '拉最新不限日期' },
255
+ from: { type: 'string', description: '起始时间' },
256
+ to: { type: 'string', description: '结束时间' },
257
+ startId: { type: 'string', description: '显式起始消息 id(最高优先)' },
258
+ endId: { type: 'string', description: '显式结束消息 id' },
259
+ imgFormat: { type: 'string', enum: ['small', 'large', 'original'], description: '预览规格(默认 small)' },
260
+ }, { required: ['groupId'], description: '拉取群聊消息' }),
261
+ handler: async (args, { host }) => {
262
+ const groupId = String(args.groupId);
263
+ if (!groupId || groupId === '0')
264
+ throw ymbValidationError('id_invalid', 'groupId 必须非 0');
265
+ // 群聊显式 id flag 名为 startId/endId
266
+ const start = args.startId !== undefined && args.startId !== null && args.startId !== ''
267
+ ? String(args.startId)
268
+ : (args.latest === true ? MAX_MSG_ID : resolveStartEnd(args).start);
269
+ const end = args.endId !== undefined && args.endId !== null && args.endId !== ''
270
+ ? String(args.endId)
271
+ : '0';
272
+ const body = {
273
+ trans_id: 0,
274
+ dev: 4,
275
+ type: 0,
276
+ enc_type: 0,
277
+ datas: [{ group_id: groupId, start, end, max_num: pageSizeOf(args) }],
278
+ };
279
+ const res = await host.request({
280
+ method: 'POST',
281
+ path: '/api/em/msg/executeIm/chat/syncGroupMsg',
282
+ body,
283
+ operation: 'yimiaoban.msg.sync.group',
284
+ });
285
+ const inner = unwrapInner(res);
286
+ const outerDatas = inner?.datas || [];
287
+ const rawRows = [];
288
+ for (const group of outerDatas) {
289
+ const list = group?.datas || group?.data || [];
290
+ if (Array.isArray(list))
291
+ rawRows.push(...list);
292
+ }
293
+ const actCode = Number(res?.data?.actionMsg?.code ?? -1);
294
+ if (actCode !== 1303)
295
+ assertImOk(res, '拉取群聊消息');
296
+ const authUid = await resolveAuthUid(host);
297
+ const messages = await enrichMessages(rawRows, host, {
298
+ authUid,
299
+ groupId,
300
+ imgFormat: args.imgFormat,
301
+ });
302
+ return ymbSuccess('yimiaoban.msg.sync.group', {
303
+ groupId,
304
+ total: messages.length,
305
+ messages,
306
+ range: { from: args.from, to: args.to, start, end },
307
+ ...pageMeta(messages, args),
308
+ ...(actCode === 1303 ? { empty: true, emptyReason: emptyPageNote(args) } : {}),
309
+ });
310
+ },
311
+ };
312
+ /** 群置顶消息同步。 */
313
+ const msgTopSync = {
314
+ name: 'yimiaoban.msg.top.sync',
315
+ resource: 'msg',
316
+ method: 'msg.top.sync',
317
+ action: 'yimiaoban.msg.top.sync',
318
+ summary: '同步群置顶消息(摘要 + 消息内容)',
319
+ capability: 'msg',
320
+ risk: READ,
321
+ inputSchema: schemaOf({
322
+ groupId: { type: 'string', pattern: '^\\d+$', description: '群 id(必填)' },
323
+ msgids: { type: 'array', items: { type: 'string' }, description: '仅同步指定消息 id(缺省=全部置顶)' },
324
+ encType: { type: 'integer', enum: [0, 1, 2, 3], description: '加密类型(默认 0,不解密)' },
325
+ }, { required: ['groupId'], description: '同步群置顶消息' }),
326
+ handler: async (args, { host }) => {
327
+ const groupId = String(args.groupId);
328
+ if (!groupId || groupId === '0')
329
+ throw ymbValidationError('id_invalid', 'groupId 必须非 0');
330
+ const body = compact({
331
+ group_id: groupId,
332
+ enc_type: Number(args.encType ?? 0),
333
+ msgids: Array.isArray(args.msgids) && args.msgids.length ? args.msgids.map((x) => String(x)) : undefined,
334
+ });
335
+ const res = await host.request({
336
+ method: 'POST',
337
+ path: '/api/em/msg/executeIm/msg/syncGroupTopMsg',
338
+ body,
339
+ operation: 'yimiaoban.msg.top.sync',
340
+ });
341
+ assertImOk(res, '同步置顶消息');
342
+ const inner = unwrapInner(res);
343
+ const infos = inner?.infos || [];
344
+ const personIds = infos.map((it) => String(it?.user?.uid ?? '')).filter((x) => x && x !== '0');
345
+ const personMap = await host.resolveUsersByIds([...new Set(personIds)]);
346
+ const summary = infos.map((it) => ({
347
+ msgid: str(it.msgid ?? it.ser_msgid),
348
+ operator: personMap[str(it.user?.uid)]?.name || str(it.user?.uid),
349
+ updateTime: formatFullTime(it.update_tm ?? it.time),
350
+ status: Number(it.status ?? 0),
351
+ statusNote: Number(it.status) === 1 ? '无权限/已删除' : '正常',
352
+ }));
353
+ const topRaw = Array.isArray(inner?.datas) ? inner.datas : [];
354
+ const messages = await enrichMessages(topRaw, host, {
355
+ groupId,
356
+ imgFormat: args.imgFormat,
357
+ authUid: await resolveAuthUid(host),
358
+ });
359
+ return ymbSuccess('yimiaoban.msg.top.sync', {
360
+ groupId,
361
+ total: summary.length,
362
+ infos: summary,
363
+ messages,
364
+ });
365
+ },
366
+ };
367
+ /** 单聊已读状态。 */
368
+ const msgReadSingle = {
369
+ name: 'yimiaoban.msg.read.single',
370
+ resource: 'msg',
371
+ method: 'msg.read.single',
372
+ action: 'yimiaoban.msg.read.single',
373
+ summary: '查询单聊消息阅读状态(msgids=自己发出 / recvMsgids=自己接收)',
374
+ capability: 'msg',
375
+ risk: READ,
376
+ inputSchema: schemaOf({
377
+ toUid: { type: 'string', pattern: '^\\d+$', description: '对方 uid' },
378
+ toCid: { type: 'string', pattern: '^\\d+$', description: '对方 cid' },
379
+ msgids: { type: 'array', items: { type: 'string' }, description: '自己发出的消息 id(最多 50)' },
380
+ recvMsgids: { type: 'array', items: { type: 'string' }, description: '自己接收的消息 id(最多 50)' },
381
+ }, { required: ['toUid', 'toCid'], description: '查询单聊阅读状态(msgids/recvMsgids 至少其一)' }),
382
+ handler: async (args, { host }) => {
383
+ const hasSent = Array.isArray(args.msgids) && args.msgids.length > 0;
384
+ const hasRecv = Array.isArray(args.recvMsgids) && args.recvMsgids.length > 0;
385
+ if (!hasSent && !hasRecv)
386
+ throw ymbValidationError('msgids_required', 'msgids / recvMsgids 至少其一');
387
+ const body = compact({
388
+ data: {
389
+ trans_id: 0,
390
+ user: { cid: String(args.toCid), uid: String(args.toUid) },
391
+ msgids: hasSent ? args.msgids.map((x) => String(x)) : undefined,
392
+ recv_msgids: hasRecv ? args.recvMsgids.map((x) => String(x)) : undefined,
393
+ },
394
+ });
395
+ const res = await host.request({
396
+ method: 'POST',
397
+ path: '/api/em/msg/executeIm/msg/getSingleMsgReadStatus',
398
+ body,
399
+ operation: 'yimiaoban.msg.read.single',
400
+ });
401
+ assertImOk(res, '查询单聊阅读状态');
402
+ const inner = unwrapInner(res);
403
+ const datas = inner?.datas || [];
404
+ const rows = Array.isArray(datas)
405
+ ? datas.map((d) => ({
406
+ msgid: str(d.msgid),
407
+ status: Number(d.status),
408
+ statusName: Number(d.status) === 1 ? '已读' : '未读',
409
+ time: formatFullTime(d.time),
410
+ dev: d.dev,
411
+ }))
412
+ : [];
413
+ return ymbSuccess('yimiaoban.msg.read.single', { total: rows.length, items: rows });
414
+ },
415
+ };
416
+ /** 群消息已读状态(未读数)。 */
417
+ const msgReadGroup = {
418
+ name: 'yimiaoban.msg.read.group',
419
+ resource: 'msg',
420
+ method: 'msg.read.group',
421
+ action: 'yimiaoban.msg.read.group',
422
+ summary: '查询群消息阅读状态汇总(每条消息已读/未读情况,原样输出)',
423
+ capability: 'msg',
424
+ risk: READ,
425
+ inputSchema: schemaOf({
426
+ groupId: { type: 'string', pattern: '^\\d+$', description: '群 id' },
427
+ msgids: { type: 'array', items: { type: 'string' }, description: '自己发出的消息 id(最多 50)' },
428
+ recvMsgids: { type: 'array', items: { type: 'string' }, description: '别人发出的消息 id(最多 50)' },
429
+ }, { required: ['groupId'], description: '查询群消息阅读状态' }),
430
+ handler: async (args, { host }) => {
431
+ const hasSent = Array.isArray(args.msgids) && args.msgids.length > 0;
432
+ const hasRecv = Array.isArray(args.recvMsgids) && args.recvMsgids.length > 0;
433
+ if (!hasSent && !hasRecv)
434
+ throw ymbValidationError('msgids_required', 'msgids / recvMsgids 至少其一');
435
+ const body = compact({
436
+ trans_id: 0,
437
+ group_id: String(args.groupId),
438
+ msgids: hasSent ? args.msgids.map((x) => String(x)) : undefined,
439
+ recv_msgids: hasRecv ? args.recvMsgids.map((x) => String(x)) : undefined,
440
+ });
441
+ const res = await host.request({
442
+ method: 'POST',
443
+ path: '/api/em/msg/executeIm/msg/getGroupMsgReadStatus',
444
+ body,
445
+ operation: 'yimiaoban.msg.read.group',
446
+ });
447
+ assertImOk(res, '查询群阅读状态');
448
+ const inner = unwrapInner(res);
449
+ return ymbSuccess('yimiaoban.msg.read.group', {
450
+ groupId: String(args.groupId),
451
+ raw: Array.isArray(inner) ? inner : inner,
452
+ });
453
+ },
454
+ };
455
+ /** 群消息阅读状态详情(谁已读/未读)。 */
456
+ const msgReadGroupDetail = {
457
+ name: 'yimiaoban.msg.read.group.detail',
458
+ resource: 'msg',
459
+ method: 'msg.read.group.detail',
460
+ action: 'yimiaoban.msg.read.group.detail',
461
+ summary: '群消息阅读详情(unread/read count + 每人状态),支持分页',
462
+ capability: 'msg',
463
+ risk: READ,
464
+ inputSchema: schemaOf({
465
+ groupId: { type: 'string', pattern: '^\\d+$', description: '群 id' },
466
+ msgid: { type: 'string', pattern: '^\\d+$', description: '消息 id' },
467
+ pageSize: { type: 'integer', minimum: 1, maximum: 100, description: '分页大小(默认 10)' },
468
+ pageNum: { type: 'integer', minimum: 1, description: '页码(默认 1)' },
469
+ ptype: { type: 'integer', description: 'MsgPageType(分页启用时有效)' },
470
+ }, { required: ['groupId', 'msgid'], description: '群消息阅读详情' }),
471
+ handler: async (args, { host }) => {
472
+ const paging = args.pageSize !== undefined || args.pageNum !== undefined;
473
+ const body = compact({
474
+ trans_id: 0,
475
+ group_id: String(args.groupId),
476
+ msgid: String(args.msgid),
477
+ page: paging ? { size: Number(args.pageSize ?? 10), num: Number(args.pageNum ?? 1) } : undefined,
478
+ ptype: paging && args.ptype !== undefined ? Number(args.ptype) : undefined,
479
+ });
480
+ const res = await host.request({
481
+ method: 'POST',
482
+ path: '/api/em/msg/executeIm/group/syncGroupReadStatus',
483
+ body,
484
+ operation: 'yimiaoban.msg.read.group.detail',
485
+ });
486
+ assertImOk(res, '同步群阅读详情');
487
+ const inner = unwrapInner(res);
488
+ const personIds = (inner?.datas || []).map((d) => String(d?.user?.uid ?? '')).filter((x) => x && x !== '0');
489
+ const personMap = await host.resolveUsersByIds([...new Set(personIds)]);
490
+ const rows = (inner?.datas || []).map((d) => ({
491
+ name: personMap[str(d.user?.uid)]?.name || str(d.user?.uid),
492
+ uid: str(d.user?.uid),
493
+ cid: str(d.user?.cid),
494
+ status: Number(d.status),
495
+ statusName: Number(d.status) === 1 ? '已读' : '未读',
496
+ dev: d.dev,
497
+ time: formatFullTime(d.time),
498
+ }));
499
+ return ymbSuccess('yimiaoban.msg.read.group.detail', {
500
+ groupId: String(args.groupId),
501
+ msgid: String(args.msgid),
502
+ unreadCount: inner?.unread_count,
503
+ readCount: inner?.read_count,
504
+ endFlag: inner?.end_flag,
505
+ hasMore: Number(inner?.end_flag) === 1,
506
+ total: rows.length,
507
+ items: rows,
508
+ });
509
+ },
510
+ };
511
+ export const msgReadOperations = [
512
+ msgSyncSingle,
513
+ msgSyncGroup,
514
+ msgTopSync,
515
+ msgReadSingle,
516
+ msgReadGroup,
517
+ msgReadGroupDetail,
518
+ ];
519
+ export { enrichMessages, resolveStartEnd };