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,477 @@
1
+ import { ymbValidationError } from '../errors.js';
2
+ import { READ, schemaOf, ymbSuccess, } from './types.js';
3
+ import { assertImOk, compact, extractGroupMembers, formatFullTime, splitIds, str, toPositiveInt, toUint, unwrapInner, } from './shared.js';
4
+ const SEARCH_TYPE_MASK = {
5
+ type: 1,
6
+ key: 2,
7
+ members: 8,
8
+ precise: 16,
9
+ owners: 32,
10
+ creators: 64,
11
+ create: 256,
12
+ };
13
+ function requireCidUid(cid, uid) {
14
+ const c = String(cid ?? '');
15
+ const u = String(uid ?? '');
16
+ if (!c || !u || c === '0' || u === '0') {
17
+ throw ymbValidationError('self_identity_missing', '该查询需要当前登录人的 cid/uid(非 0),请确认登录态完整');
18
+ }
19
+ return { cid: c, uid: u };
20
+ }
21
+ /** 群 id 必填校验(非 0 数字,按字符串传输)。 */
22
+ function requireGroupId(value) {
23
+ const text = String(value ?? '').trim();
24
+ if (!/^\d+$/.test(text) || text === '0') {
25
+ throw ymbValidationError('id_invalid', `groupId 必须是非 0 数字 ID(收到:${text || '(空)'})`);
26
+ }
27
+ return text;
28
+ }
29
+ function dayRangeShanghai(offsetDay) {
30
+ const now = new Date();
31
+ const tzMs = 8 * 3600 * 1000;
32
+ const local = new Date(now.getTime() + tzMs);
33
+ const dayStart = Date.UTC(local.getUTCFullYear(), local.getUTCMonth(), local.getUTCDate() + offsetDay);
34
+ return { begin: Math.floor((dayStart - tzMs) / 1000), end: Math.floor((dayStart + 86400_000 - tzMs) / 1000) - 1 };
35
+ }
36
+ function maskTypeName(mask) {
37
+ const names = [
38
+ [1, '全员群'], [2, '部门群'], [4, '外部群'], [8, '事项群'], [16, '话题群'],
39
+ ];
40
+ const hit = names.filter(([bit]) => (mask & bit) !== 0).map(([, name]) => name);
41
+ return hit.length ? hit.join('|') : String(mask);
42
+ }
43
+ function stateName(state) {
44
+ if (state === 1)
45
+ return '正常';
46
+ if (state === 0)
47
+ return '已销毁';
48
+ return `状态${state}`;
49
+ }
50
+ /** 群搜索。 */
51
+ const groupSearch = {
52
+ name: 'yimiaoban.group.search',
53
+ resource: 'group',
54
+ method: 'group.search',
55
+ action: 'yimiaoban.group.search',
56
+ summary: '按条件搜索群(cond+mask 全服务端过滤,禁止本地过滤)',
57
+ capability: 'group',
58
+ risk: READ,
59
+ inputSchema: schemaOf({
60
+ name: { type: 'string', description: '群名关键字' },
61
+ precise: { type: 'boolean', description: '精确匹配名称' },
62
+ type: { type: 'integer', description: '群类型筛选(1 普通/6 部门/7 全员等)' },
63
+ members: { type: 'boolean', description: '我所在的群' },
64
+ owner: { type: 'boolean', description: '我是群主的群' },
65
+ creator: { type: 'boolean', description: '我创建的群' },
66
+ today: { type: 'boolean', description: '今天创建的群' },
67
+ yesterday: { type: 'boolean', description: '昨天创建的群' },
68
+ createBegin: { type: 'integer', description: '创建时间下界(unix 秒)' },
69
+ createEnd: { type: 'integer', description: '创建时间上界(unix 秒)' },
70
+ pageSize: { type: 'integer', minimum: 1, maximum: 200, description: '每页大小(默认 100)' },
71
+ }, { description: '至少提供一个搜索条件' }),
72
+ handler: async (args, { host }) => {
73
+ const cond = {};
74
+ let mask = 0;
75
+ if (args.type !== undefined) {
76
+ mask |= SEARCH_TYPE_MASK.type;
77
+ cond.type = toUint(args.type, 'type');
78
+ }
79
+ if (args.name !== undefined && args.name !== '') {
80
+ mask |= SEARCH_TYPE_MASK.key;
81
+ cond.key = String(args.name);
82
+ }
83
+ if (args.members === true) {
84
+ // 成员身份必须是 IM cid + IM uid(cid 来自 teamsCheck 的 imCid,不能用登录 userId 顶替)
85
+ const self = await host.getSelfIdentity();
86
+ const { cid, uid } = requireCidUid(self.cid, self.uid);
87
+ mask |= SEARCH_TYPE_MASK.members;
88
+ cond.members = [{ cid, uid }];
89
+ }
90
+ if (args.precise === true) {
91
+ mask |= SEARCH_TYPE_MASK.precise;
92
+ cond.precise = 1;
93
+ }
94
+ if (args.owner === true) {
95
+ const self = await host.getSelfIdentity();
96
+ mask |= SEARCH_TYPE_MASK.owners;
97
+ cond.owners = [{ cid: self.cid, uid: self.uid }];
98
+ }
99
+ if (args.creator === true) {
100
+ const self = await host.getSelfIdentity();
101
+ mask |= SEARCH_TYPE_MASK.creators;
102
+ cond.creators = [{ cid: self.cid, uid: self.uid }];
103
+ }
104
+ let create;
105
+ if (args.today === true)
106
+ create = dayRangeShanghai(0);
107
+ else if (args.yesterday === true)
108
+ create = dayRangeShanghai(-1);
109
+ else if (args.createBegin !== undefined || args.createEnd !== undefined) {
110
+ create = { begin: Number(args.createBegin ?? 0), end: Number(args.createEnd ?? 0) };
111
+ }
112
+ if (create) {
113
+ mask |= SEARCH_TYPE_MASK.create;
114
+ cond.create = create;
115
+ }
116
+ if (mask === 0)
117
+ throw ymbValidationError('cond_required', '至少提供一个搜索条件(name/type/members/owner/creator/today/yesterday/create*)');
118
+ cond.mask = mask;
119
+ const pageSize = Math.min(toPositiveInt(args.pageSize, 'pageSize') ?? 100, 200);
120
+ const body = { page: { size: pageSize, num: 1 }, cond };
121
+ const res = await host.request({
122
+ method: 'POST',
123
+ path: '/api/em/msg/executeIm/group/searchGroup',
124
+ body,
125
+ operation: 'yimiaoban.group.search',
126
+ });
127
+ assertImOk(res, '搜索群');
128
+ const inner = unwrapInner(res);
129
+ const datas = inner?.datas || [];
130
+ return ymbSuccess('yimiaoban.group.search', {
131
+ total: inner?.total ?? datas.length,
132
+ groups: datas.map((g) => ({
133
+ id: str(g.id),
134
+ name: g.name ?? '',
135
+ type: g.type,
136
+ num: g.num,
137
+ createTime: formatFullTime(g.create),
138
+ })),
139
+ });
140
+ },
141
+ };
142
+ /** 群基础信息(支持多群)。 */
143
+ const groupInfo = {
144
+ name: 'yimiaoban.group.info',
145
+ resource: 'group',
146
+ method: 'group.info',
147
+ action: 'yimiaoban.group.info',
148
+ summary: '按群 id 查基础信息(支持批量;类型位组合 + 状态展示)',
149
+ capability: 'group',
150
+ risk: READ,
151
+ inputSchema: schemaOf({
152
+ groupIds: { type: 'array', items: { type: 'string' }, description: '群 id 列表(逗号串或数组)' },
153
+ groupId: { type: 'string', description: '单群 id(groupIds 的便捷形式)' },
154
+ }, { description: 'groupIds / groupId 至少其一' }),
155
+ handler: async (args, { host }) => {
156
+ let ids = [];
157
+ if (Array.isArray(args.groupIds))
158
+ ids = args.groupIds.map((x) => String(x));
159
+ else if (args.groupIds)
160
+ ids = splitIds(args.groupIds, 'groupIds');
161
+ if (args.groupId)
162
+ ids.push(String(args.groupId));
163
+ ids = [...new Set(ids)];
164
+ if (ids.length === 0)
165
+ throw ymbValidationError('group_ids_required', 'groupIds / groupId 至少其一');
166
+ for (const id of ids) {
167
+ if (!/^\d+$/.test(id) || id === '0')
168
+ throw ymbValidationError('id_invalid', `群 id 必须非 0 数字:${id}`);
169
+ }
170
+ const body = { trans_id: 0, type: 0, mask: 0, group_id: ids, flag: 0 };
171
+ const res = await host.request({
172
+ method: 'POST',
173
+ path: '/api/em/msg/executeIm/group/queryGroupInfo',
174
+ body,
175
+ operation: 'yimiaoban.group.info',
176
+ });
177
+ assertImOk(res, '查询群信息');
178
+ const inner = unwrapInner(res);
179
+ const datas = inner?.datas || [];
180
+ const groups = datas.map((g) => ({
181
+ groupId: str(g.group_id ?? g.id),
182
+ name: g.name ?? '',
183
+ mask: Number(g.mask ?? 0),
184
+ maskName: maskTypeName(Number(g.mask ?? 0)),
185
+ userNum: g.user_num,
186
+ maxNum: g.max_num,
187
+ hostUid: str(g.host_uid ?? g.hostUid ?? ''),
188
+ state: Number(g.state ?? -1),
189
+ stateName: stateName(Number(g.state ?? -1)),
190
+ createTime: formatFullTime(g.create_time ?? g.createTime),
191
+ }));
192
+ return ymbSuccess('yimiaoban.group.info', { total: groups.length, groups });
193
+ },
194
+ };
195
+ /** 群成员列表。 */
196
+ const groupUsers = {
197
+ name: 'yimiaoban.group.users',
198
+ resource: 'group',
199
+ method: 'group.users',
200
+ action: 'yimiaoban.group.users',
201
+ summary: '拉取群成员列表(姓名一律走 hrm,默认 mask 不含 name 位)',
202
+ capability: 'group',
203
+ risk: READ,
204
+ inputSchema: schemaOf({
205
+ groupId: { type: 'string', pattern: '^\\d+$', description: '群 id' },
206
+ mask: { type: 'integer', description: 'member_mask(默认 142=cid|uid|role|add_time)' },
207
+ syncType: { type: 'integer', enum: [0, 1], description: '0 全量(默认)/1 增量' },
208
+ clientUc: { type: 'string', description: '增量游标 server_uc' },
209
+ }, { required: ['groupId'], description: '拉取群成员' }),
210
+ handler: async (args, { host }) => {
211
+ const groupId = requireGroupId(args.groupId);
212
+ const memberMask = Number(args.mask ?? 142);
213
+ const body = compact({
214
+ group_id: groupId,
215
+ type: 0,
216
+ sync_type: Number(args.syncType ?? 0),
217
+ member_mask: memberMask,
218
+ client_uc: args.clientUc !== undefined ? String(args.clientUc) : '0',
219
+ });
220
+ const res = await host.request({
221
+ method: 'POST',
222
+ path: '/api/em/msg/executeIm/group/getGroupUsers',
223
+ body,
224
+ operation: 'yimiaoban.group.users',
225
+ });
226
+ assertImOk(res, '拉取群成员');
227
+ const inner = unwrapInner(res);
228
+ const members = extractGroupMembers(inner);
229
+ const hasRole = (memberMask & 8) !== 0;
230
+ const hasAddTime = (memberMask & 128) !== 0;
231
+ const hasCid = (memberMask & 2) !== 0;
232
+ const hasUid = (memberMask & 4) !== 0;
233
+ const roleNames = { 0: '不可用', 1: '普通成员', 2: '管理员', 3: '群主', 4: '客户', 5: '负责人', 6: '协助人员' };
234
+ const personIds = members.map((m) => String(m?.uid ?? '')).filter((x) => x && x !== '0');
235
+ // 姓名一律走 hrm 解析(不看 member_mask 的 name 位,也不使用 IM 返回的 name 字段)
236
+ const personMap = personIds.length ? await host.resolveUsersByIds([...new Set(personIds)]) : {};
237
+ const rows = members.map((m) => ({
238
+ uid: hasUid ? str(m.uid) : undefined,
239
+ cid: hasCid ? str(m.cid) : undefined,
240
+ name: personMap[str(m.uid)]?.name || str(m.uid),
241
+ role: hasRole ? Number(m.role_type ?? m.role ?? -1) : undefined,
242
+ roleName: hasRole ? roleNames[Number(m.role_type ?? m.role ?? -1)] ?? '-' : undefined,
243
+ addTime: hasAddTime ? formatFullTime(m.add_time ?? m.addTime) : undefined,
244
+ }));
245
+ return ymbSuccess('yimiaoban.group.users', {
246
+ groupId,
247
+ totalNum: inner?.total_num ?? rows.length,
248
+ endFlag: inner?.end_flag,
249
+ serverUc: str(inner?.server_uc ?? ''),
250
+ members: rows,
251
+ });
252
+ },
253
+ };
254
+ /** 批量查群管理员/群主。 */
255
+ const groupAdmins = {
256
+ name: 'yimiaoban.group.admins',
257
+ resource: 'group',
258
+ method: 'group.admins',
259
+ action: 'yimiaoban.group.admins',
260
+ summary: '批量获取群主/管理员(mask 位 1=群主/负责人 2=管理员,默认 3)',
261
+ capability: 'group',
262
+ risk: READ,
263
+ inputSchema: schemaOf({
264
+ groupIds: { type: 'array', items: { type: 'string' }, description: '群 id 列表' },
265
+ mask: { type: 'integer', description: '角色位掩码(默认 3)' },
266
+ }, { required: ['groupIds'], description: '批量查询群管理员' }),
267
+ handler: async (args, { host }) => {
268
+ const ids = Array.isArray(args.groupIds) ? args.groupIds.map((x) => String(x)) : splitIds(args.groupIds, 'groupIds');
269
+ const body = { group_ids: ids, mask: Number(args.mask ?? 3) };
270
+ const res = await host.request({
271
+ method: 'POST',
272
+ path: '/api/em/msg/executeIm/group/getGroupAdminBatch',
273
+ body,
274
+ operation: 'yimiaoban.group.admins',
275
+ });
276
+ assertImOk(res, '查询群管理员');
277
+ const inner = unwrapInner(res);
278
+ const datas = inner?.datas || [];
279
+ const roleNames = { 3: '群主', 2: '管理员' };
280
+ const allUids = new Set();
281
+ for (const g of datas) {
282
+ for (const it of g?.datas || []) {
283
+ if (it?.user?.uid)
284
+ allUids.add(String(it.user.uid));
285
+ }
286
+ }
287
+ const personMap = await host.resolveUsersByIds([...allUids]);
288
+ const groups = datas.map((g) => ({
289
+ groupId: str(g.group_id),
290
+ admins: (g?.datas || []).map((it) => ({
291
+ name: personMap[str(it.user?.uid)]?.name || str(it.user?.uid),
292
+ uid: str(it.user?.uid),
293
+ cid: str(it.user?.cid),
294
+ role: Number(it.role ?? -1),
295
+ roleName: roleNames[Number(it.role ?? -1)] ?? '-',
296
+ })),
297
+ }));
298
+ return ymbSuccess('yimiaoban.group.admins', { total: groups.length, groups });
299
+ },
300
+ };
301
+ /** 批量判断群是否存在。 */
302
+ const groupExist = {
303
+ name: 'yimiaoban.group.exist',
304
+ resource: 'group',
305
+ method: 'group.exist',
306
+ action: 'yimiaoban.group.exist',
307
+ summary: '批量判断群是否存在(ret=0 存在;1209 不存在)',
308
+ capability: 'group',
309
+ risk: READ,
310
+ inputSchema: schemaOf({
311
+ groupIds: { type: 'array', items: { type: 'string' }, description: '群 id 列表' },
312
+ }, { required: ['groupIds'], description: '批量判断群是否存在' }),
313
+ handler: async (args, { host }) => {
314
+ const ids = Array.isArray(args.groupIds) ? args.groupIds.map((x) => String(x)) : splitIds(args.groupIds, 'groupIds');
315
+ for (const id of ids) {
316
+ if (!/^\d+$/.test(id) || id === '0')
317
+ throw ymbValidationError('id_invalid', `群 id 必须非 0 数字:${id || '(空)'}`);
318
+ }
319
+ const body = { groupIds: ids };
320
+ const res = await host.request({
321
+ method: 'POST',
322
+ path: '/api/em/msg/executeIm/group/checkGroupExist',
323
+ body,
324
+ operation: 'yimiaoban.group.exist',
325
+ });
326
+ assertImOk(res, '检查群是否存在');
327
+ const inner = unwrapInner(res);
328
+ const results = inner?.results || [];
329
+ return ymbSuccess('yimiaoban.group.exist', {
330
+ groups: results.map((r) => ({
331
+ groupId: str(r.group_id),
332
+ exists: Number(r.ret) === 0,
333
+ ret: r.ret,
334
+ })),
335
+ });
336
+ },
337
+ };
338
+ /** 检查用户是否在某群内。 */
339
+ const groupUserExist = {
340
+ name: 'yimiaoban.group.userExist',
341
+ resource: 'group',
342
+ method: 'group.userExist',
343
+ action: 'yimiaoban.group.userExist',
344
+ summary: '检查用户是否在某群内(tenantKey 自动注入)',
345
+ capability: 'group',
346
+ risk: READ,
347
+ inputSchema: schemaOf({
348
+ groupId: { type: 'string', pattern: '^\\d+$', description: '群 id' },
349
+ users: { type: 'array', items: { type: 'string' }, description: '待检查 uid 列表' },
350
+ }, { required: ['groupId', 'users'], description: '检查用户是否在群内' }),
351
+ handler: async (args, { host }) => {
352
+ const groupId = requireGroupId(args.groupId);
353
+ const users = Array.isArray(args.users) ? args.users.map((x) => String(x)) : splitIds(args.users, 'users');
354
+ const session = await host.getSession();
355
+ if (!session.tenantKey)
356
+ throw ymbValidationError('tenant_key_missing', '登录态缺少 tenantKey,请重新登录');
357
+ const body = { tenantKey: session.tenantKey, groupId, users };
358
+ const res = await host.request({
359
+ method: 'POST',
360
+ path: '/api/em/msg/executeIm/group/checkGroupUserExist',
361
+ body,
362
+ operation: 'yimiaoban.group.userExist',
363
+ });
364
+ assertImOk(res, '检查群成员');
365
+ const inner = unwrapInner(res);
366
+ const inGroup = Array.isArray(inner?.users) ? inner.users.map((x) => String(x)) : [];
367
+ const inSet = new Set(inGroup);
368
+ const personMap = await host.resolveUsersByIds(users);
369
+ const rows = users.map((uid) => ({
370
+ uid,
371
+ name: personMap[uid]?.name || uid,
372
+ inGroup: inSet.has(uid),
373
+ }));
374
+ return ymbSuccess('yimiaoban.group.userExist', { groupId, total: rows.length, users: rows });
375
+ },
376
+ };
377
+ /** 群公告:获取单条。 */
378
+ const groupAnnounceGet = {
379
+ name: 'yimiaoban.group.announce.get',
380
+ resource: 'group',
381
+ method: 'group.announce.get',
382
+ action: 'yimiaoban.group.announce.get',
383
+ summary: '获取群公告(aid=0/不传=最新公告)',
384
+ capability: 'group',
385
+ risk: READ,
386
+ inputSchema: schemaOf({
387
+ groupId: { type: 'string', pattern: '^\\d+$', description: '群 id' },
388
+ aid: { type: 'string', description: '公告 id(默认 0=最新)' },
389
+ }, { required: ['groupId'], description: '获取群公告' }),
390
+ handler: async (args, { host }) => {
391
+ const body = { groupId: requireGroupId(args.groupId), aid: args.aid !== undefined ? String(args.aid) : '0' };
392
+ const res = await host.request({
393
+ method: 'POST',
394
+ path: '/api/em/msg/executeIm/group/getAnnouce',
395
+ body,
396
+ operation: 'yimiaoban.group.announce.get',
397
+ });
398
+ assertImOk(res, '获取群公告');
399
+ const inner = unwrapInner(res);
400
+ // 公告主体位于 data.data.data(兼容 data.data 直挂 annouce 的形态)
401
+ const data = inner && typeof inner === 'object' && inner.data && typeof inner.data === 'object' ? inner.data : inner;
402
+ if (!data || (data.aid === undefined && data.annouce === undefined)) {
403
+ return ymbSuccess('yimiaoban.group.announce.get', { found: false });
404
+ }
405
+ const personMap = await host.resolveUsersByIds(data?.add_user?.uid ? [String(data.add_user.uid)] : []);
406
+ return ymbSuccess('yimiaoban.group.announce.get', {
407
+ found: true,
408
+ aid: str(data.aid),
409
+ annouce: data.annouce ?? '',
410
+ addUserName: personMap[str(data.add_user?.uid)]?.name || str(data.add_user?.uid),
411
+ time: formatFullTime(data.time),
412
+ updateTime: formatFullTime(data.update_tm ?? data.update_time),
413
+ fmark: Number(data.fmark ?? 0),
414
+ mustRead: Number(data.fmark ?? 0) === 1,
415
+ related: str(data.related ?? ''),
416
+ });
417
+ },
418
+ };
419
+ /** 群公告列表。 */
420
+ const groupAnnounceList = {
421
+ name: 'yimiaoban.group.announce.list',
422
+ resource: 'group',
423
+ method: 'group.announce.list',
424
+ action: 'yimiaoban.group.announce.list',
425
+ summary: '群公告列表(按 id 降序,end_flag=1 还有下一页)',
426
+ capability: 'group',
427
+ risk: READ,
428
+ inputSchema: schemaOf({
429
+ groupId: { type: 'string', pattern: '^\\d+$', description: '群 id' },
430
+ aid: { type: 'string', description: '分页起始公告 id(默认 0)' },
431
+ pageSize: { type: 'integer', description: '分页大小(默认服务端最大)' },
432
+ }, { required: ['groupId'], description: '群公告列表' }),
433
+ handler: async (args, { host }) => {
434
+ const body = compact({
435
+ groupId: requireGroupId(args.groupId),
436
+ aid: args.aid !== undefined ? String(args.aid) : '0',
437
+ pageSize: args.pageSize !== undefined ? toUint(args.pageSize, 'pageSize') : undefined,
438
+ });
439
+ const res = await host.request({
440
+ method: 'POST',
441
+ path: '/api/em/msg/executeIm/group/getAnnouceList',
442
+ body,
443
+ operation: 'yimiaoban.group.announce.list',
444
+ });
445
+ assertImOk(res, '获取公告列表');
446
+ const inner = unwrapInner(res);
447
+ const datas = inner?.datas || inner?.list || [];
448
+ const personIds = datas.map((d) => String(d?.add_user?.uid ?? '')).filter((x) => x && x !== '0');
449
+ const personMap = await host.resolveUsersByIds([...new Set(personIds)]);
450
+ const rows = datas.map((d) => ({
451
+ aid: str(d.aid),
452
+ annouce: d.annouce ?? '',
453
+ deleted: d.annouce === undefined || d.annouce === null || d.annouce === '',
454
+ addUserName: personMap[str(d.add_user?.uid)]?.name || str(d.add_user?.uid),
455
+ time: formatFullTime(d.time),
456
+ updateTime: formatFullTime(d.update_tm ?? d.update_time),
457
+ mustRead: Number(d.fmark ?? 0) === 1,
458
+ related: str(d.related ?? ''),
459
+ }));
460
+ return ymbSuccess('yimiaoban.group.announce.list', {
461
+ total: rows.length,
462
+ endFlag: inner?.end_flag,
463
+ hasMore: Number(inner?.end_flag) === 1,
464
+ announcements: rows,
465
+ });
466
+ },
467
+ };
468
+ export const groupReadOperations = [
469
+ groupSearch,
470
+ groupInfo,
471
+ groupUsers,
472
+ groupAdmins,
473
+ groupExist,
474
+ groupUserExist,
475
+ groupAnnounceGet,
476
+ groupAnnounceList,
477
+ ];