@alemonjs/qq-bot 2.1.36 → 2.1.37

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 (53) hide show
  1. package/README.md +66 -1981
  2. package/lib/config.d.ts +2 -0
  3. package/lib/framework-actions.d.ts +75 -0
  4. package/lib/framework-actions.js +620 -0
  5. package/lib/framework-events.d.ts +10 -0
  6. package/lib/framework-events.js +182 -0
  7. package/lib/hook.d.ts +16 -12
  8. package/lib/hook.js +5 -9
  9. package/lib/index.d.ts +5 -1
  10. package/lib/index.websoket.js +6 -2
  11. package/lib/message/AUDIO_EVENT.d.ts +20 -0
  12. package/lib/message/AUDIO_EVENT.js +1 -0
  13. package/lib/message/FORUM_EVENT.d.ts +33 -0
  14. package/lib/message/FORUM_EVENT.js +1 -0
  15. package/lib/message/GUILD_AUDIT.d.ts +14 -0
  16. package/lib/message/GUILD_AUDIT.js +1 -0
  17. package/lib/message/INTERACTION_CREATE.d.ts +13 -20
  18. package/lib/message/MESSAGE_CREATE.d.ts +3 -9
  19. package/lib/message/MESSAGE_DELETE.d.ts +15 -1
  20. package/lib/message/group/C2C_MESSAGE_CREATE.d.ts +3 -13
  21. package/lib/message/group/C2C_MSG_RECEIVE.d.ts +1 -1
  22. package/lib/message/group/C2C_MSG_REJECT.d.ts +1 -1
  23. package/lib/message/group/FRIEND_ADD.d.ts +7 -1
  24. package/lib/message/group/FRIEND_DEL.d.ts +4 -1
  25. package/lib/message/group/GROUP_AT_MESSAGE_CREATE.d.ts +6 -27
  26. package/lib/message/group/GROUP_MEMBER_ADD.d.ts +4 -2
  27. package/lib/message/group/GROUP_MEMBER_REMOVE.d.ts +4 -2
  28. package/lib/message/group/GROUP_MESSAGE_CREATE.d.ts +6 -27
  29. package/lib/message/group/GROUP_MSG_RECEIVE.d.ts +1 -1
  30. package/lib/message/group/GROUP_MSG_REJECT.d.ts +1 -1
  31. package/lib/message/group/MESSAGE_AUDIT_PASS.d.ts +2 -0
  32. package/lib/message/group/MESSAGE_AUDIT_REJECT.d.ts +2 -0
  33. package/lib/message/group/SUBSCRIBE_MESSAGE_STATUS.d.ts +17 -0
  34. package/lib/message/group/SUBSCRIBE_MESSAGE_STATUS.js +1 -0
  35. package/lib/register.d.ts +1 -0
  36. package/lib/register.js +279 -115
  37. package/lib/sdk/api.d.ts +93 -14
  38. package/lib/sdk/api.js +241 -45
  39. package/lib/sdk/client.webhook.js +2 -1
  40. package/lib/sdk/client.websoket.js +2 -1
  41. package/lib/sdk/intents.d.ts +1 -0
  42. package/lib/sdk/intents.js +1 -0
  43. package/lib/sdk/message.d.ts +1 -1
  44. package/lib/sdk/message.group.d.ts +4 -2
  45. package/lib/sdk/message.guild.d.ts +19 -0
  46. package/lib/sdk/non-channel-types.d.ts +138 -0
  47. package/lib/sdk/non-channel-types.js +1 -0
  48. package/lib/sdk/registry.js +2 -2
  49. package/lib/sdk/typing.d.ts +13 -1
  50. package/lib/sends.d.ts +3 -3
  51. package/lib/sends.js +46 -10
  52. package/lib/upload.js +13 -37
  53. package/package.json +7 -4
@@ -0,0 +1,620 @@
1
+ import { getIdentity } from "./config.js";
2
+ import { C2C_MESSAGE_CREATE, GROUP_AT_MESSAGE_CREATE } from "./sends.js";
3
+ import { normalizeJoinRequest } from "./framework-events.js";
4
+ import { ResultCode, createResult } from "alemonjs";
5
+
6
+ //#region src/framework-actions.ts
7
+ const page = (items, raw) => ({
8
+ Items: items,
9
+ NextCursor: raw.next_cursor,
10
+ HasMore: raw.is_end === void 0 ? Boolean(raw.next_cursor) : !raw.is_end
11
+ });
12
+ const pagination = (params) => ({
13
+ cursor: params?.Cursor ?? params?.After,
14
+ limit: params?.Limit
15
+ });
16
+ const memberIds = (ids) => {
17
+ if (!Array.isArray(ids) || ids.length < 1 || ids.length > 20 || ids.some((id) => typeof id !== "string" || !id)) throw new Error("Expected 1 to 20 non-empty member IDs");
18
+ return ids;
19
+ };
20
+ const nonEmptyIds = (ids, maximum, name) => {
21
+ if (!Array.isArray(ids) || ids.length < 1 || ids.length > maximum || ids.some((id) => typeof id !== "string" || !id)) throw new Error(`Expected 1 to ${maximum} non-empty ${name}`);
22
+ return ids;
23
+ };
24
+ const textLength = (value) => Buffer.byteLength(value, "utf8");
25
+ const menuItem = (item, child = false) => {
26
+ const maximum = child ? 14 : 10;
27
+ if (!item.name || textLength(item.name) > maximum) throw new Error(`Menu item name must be at most ${maximum} bytes`);
28
+ if (child && item.type === "menu") throw new Error("Menu sub-items cannot contain another menu");
29
+ if (item.type === "link" && !item.url.startsWith("https://")) throw new Error("Menu links must use https");
30
+ if (item.type === "menu" && (item.items.length < 1 || item.items.length > 5)) throw new Error("A menu must contain 1 to 5 sub-items");
31
+ return {
32
+ name: item.name,
33
+ type: item.type === "message" ? "send_message" : item.type,
34
+ ..."text" in item && { send_message: item.text },
35
+ ..."url" in item && { link: item.url },
36
+ ..."items" in item && { sub_menu_items: item.items.map((value) => menuItem(value, true)) },
37
+ ..."id" in item && { switch: {
38
+ switch_id: item.id,
39
+ default: item.default
40
+ } }
41
+ };
42
+ };
43
+ const validatePanel = (panel) => {
44
+ if (panel.items.length > 20 || panel.remark && textLength(panel.remark) > 255) throw new Error("Panel supports at most 20 items and a 255-byte remark");
45
+ for (const item of panel.items) {
46
+ if (!item.name || textLength(item.name) > 14 || item.description && textLength(item.description) > 30) throw new Error("Panel item name/description exceeds its limit");
47
+ if (item.type === "link" && (!item.url || !item.url.startsWith("https://"))) throw new Error("Panel link items require an https URL");
48
+ }
49
+ };
50
+ const validatePanelAudience = (scope, audience, userIds, guildIds) => {
51
+ if (!["group", "c2c"].includes(scope)) throw new Error("QQ panel scope must be group or c2c");
52
+ if (Boolean(userIds?.length) && Boolean(guildIds?.length)) throw new Error("A panel audience cannot mix users and groups");
53
+ if (audience === "specific") nonEmptyIds(scope === "group" ? guildIds : userIds, 20, scope === "group" ? "group IDs" : "user IDs");
54
+ else if (userIds?.length || guildIds?.length) throw new Error("Panel audience IDs require audience: specific");
55
+ if (scope === "group" && userIds?.length || scope === "c2c" && guildIds?.length) throw new Error("Panel audience IDs do not match its scope");
56
+ };
57
+ const removal = (r) => ({
58
+ status: r.remove_members_result,
59
+ failedBlacklistIds: r.add_to_member_blacklist_fail_openids ?? []
60
+ });
61
+ const blacklistResult = (r) => ({ failedUserIds: r.fail_openids ?? [] });
62
+ const operation = (value) => value === "remove" ? "del" : "add";
63
+ const mediaType = (value) => ({
64
+ image: 1,
65
+ video: 2,
66
+ audio: 3,
67
+ file: 4
68
+ })[value];
69
+ const mediaReceipt = (r) => ({
70
+ fileId: r.file_info,
71
+ uuid: r.file_uuid,
72
+ ttl: r.ttl,
73
+ messageId: r.id,
74
+ url: r.raw_url
75
+ });
76
+ const receipt = (r) => ({
77
+ id: r.id,
78
+ timestamp: r.timestamp,
79
+ referenceId: r.ext_info?.ref_idx
80
+ });
81
+ const policy = (r) => ({
82
+ id: r.strategy_id,
83
+ guildIds: r.group_openids,
84
+ guildNumbers: r.group_ids,
85
+ enabled: r.is_enable === "on",
86
+ expiresAt: r.expire_at,
87
+ remark: r.remark,
88
+ whitelistCount: r.whitelist_user_count,
89
+ createdAt: r.created_at,
90
+ updatedAt: r.updated_at
91
+ });
92
+ const policyBody = (p) => ({
93
+ group_openids: p.guildIds,
94
+ group_ids: p.guildNumbers,
95
+ is_enable: p.enabled === void 0 ? void 0 : p.enabled ? "on" : "off",
96
+ expire_at: p.expiresAt,
97
+ remark: p.remark
98
+ });
99
+ const fromMenuItem = (item) => ({
100
+ name: item.name,
101
+ ...item.type === "send_message" ? {
102
+ type: "message",
103
+ text: item.send_message
104
+ } : item.type === "switch" ? {
105
+ type: "switch",
106
+ id: item.switch?.switch_id,
107
+ default: item.switch?.default
108
+ } : item.type === "menu" ? {
109
+ type: "menu",
110
+ items: item.sub_menu_items?.map(fromMenuItem) ?? []
111
+ } : {
112
+ type: "link",
113
+ url: item.link
114
+ }
115
+ });
116
+ const panelBody = (panel) => ({
117
+ items: panel.items.map((item) => ({
118
+ name: item.name,
119
+ desc: item.description,
120
+ type: item.type,
121
+ only_admin: item.adminOnly,
122
+ link: item.url
123
+ })),
124
+ remark: panel.remark,
125
+ version: panel.version
126
+ });
127
+ const fromPanel = (r) => {
128
+ if (!["group", "c2c"].includes(r.scope)) throw new Error(`QQ panel scope ${r.scope || "unknown"} is outside the non-channel framework surface`);
129
+ return {
130
+ id: r.panel_id,
131
+ scope: r.scope,
132
+ audience: r.target_type,
133
+ userIds: r.user_openids,
134
+ guildIds: r.group_openids,
135
+ version: r.version,
136
+ createdAt: r.created_at,
137
+ updatedAt: r.updated_at,
138
+ panel: {
139
+ items: (r.panel?.items ?? []).map((item) => ({
140
+ name: item.name,
141
+ description: item.desc,
142
+ type: item.type,
143
+ adminOnly: item.only_admin,
144
+ url: item.link
145
+ })),
146
+ remark: r.panel?.remark,
147
+ version: r.panel?.version
148
+ }
149
+ };
150
+ };
151
+ const member = (r, guildId) => {
152
+ const [IsMaster, UserKey] = getIdentity(r.member_openid);
153
+ return {
154
+ UserId: r.member_openid,
155
+ UserName: r.username,
156
+ UserKey,
157
+ IsMaster,
158
+ IsBot: r.bot ?? false,
159
+ GuildId: guildId,
160
+ Role: r.member_role,
161
+ Roles: r.member_role ? [r.member_role] : [],
162
+ UnionId: r.union_openid,
163
+ JoinedAt: r.joined_at ? Date.parse(r.joined_at) : void 0
164
+ };
165
+ };
166
+ const toQQMessage = (content) => {
167
+ if ([
168
+ content.markdown,
169
+ content.media,
170
+ content.card
171
+ ].filter(Boolean).length > 1) throw new Error("markdown, media and card are mutually exclusive message bodies");
172
+ if (content.keyboard?.templateId && content.keyboard.rows) throw new Error("keyboard templateId and rows are mutually exclusive");
173
+ return {
174
+ msg_type: content.markdown ? 2 : content.media ? 7 : content.card ? 3 : 0,
175
+ content: content.markdown ? void 0 : content.text,
176
+ markdown: content.markdown && {
177
+ content: content.markdown.content,
178
+ custom_template_id: content.markdown.templateId,
179
+ template_id: content.markdown.legacyTemplateId,
180
+ params: content.markdown.params,
181
+ force_verify_image_resource: content.markdown.verifyImages
182
+ },
183
+ media: content.media && { file_info: content.media.fileId },
184
+ ark: content.card && {
185
+ template_id: content.card.templateId,
186
+ kv: content.card.fields.map((field) => ({
187
+ key: field.key,
188
+ value: field.value,
189
+ obj: field.objects?.map((object) => ({ obj_kv: object.fields }))
190
+ }))
191
+ },
192
+ keyboard: content.keyboard && {
193
+ id: content.keyboard.templateId,
194
+ content: content.keyboard.rows && { rows: content.keyboard.rows.map((buttons) => ({ buttons: buttons.map((button) => ({
195
+ id: button.id,
196
+ group_id: button.groupId,
197
+ render_data: {
198
+ label: button.label,
199
+ visited_label: button.visitedLabel ?? button.label,
200
+ style: button.style
201
+ },
202
+ action: {
203
+ type: {
204
+ link: 0,
205
+ callback: 1,
206
+ command: 2
207
+ }[button.action.type],
208
+ data: button.action.data,
209
+ permission: {
210
+ type: {
211
+ users: 0,
212
+ admins: 1,
213
+ everyone: 2,
214
+ roles: 3
215
+ }[button.action.permission?.type ?? "everyone"],
216
+ specify_user_ids: button.action.permission?.userIds,
217
+ specify_role_ids: button.action.permission?.roleIds
218
+ },
219
+ enter: button.action.autoSend,
220
+ reply: button.action.reply,
221
+ unsupport_tips: button.action.hint,
222
+ anchor: button.action.anchor,
223
+ click_limit: button.action.clickLimit,
224
+ modal: button.action.modal && {
225
+ content: button.action.modal.content,
226
+ confirm_text: button.action.modal.confirmText,
227
+ cancel_text: button.action.modal.cancelText
228
+ }
229
+ }
230
+ })) })) }
231
+ }
232
+ };
233
+ };
234
+ /** Translate semantic framework actions. Undefined means a legacy/channel action. */
235
+ const handleFrameworkAction = async (client, data, botId) => {
236
+ const payload = data.payload ?? {};
237
+ const p = payload.params ?? {};
238
+ const event = payload.event ?? {};
239
+ const target = p.target ?? payload.target ?? event.Target ?? (event.SpaceId?.startsWith("GROUP:") ? {
240
+ scope: "group",
241
+ targetId: event.ChannelId || event.GuildId
242
+ } : event.SpaceId?.startsWith("GUILD:") ? {
243
+ scope: "channel",
244
+ targetId: event.ChannelId || event.SpaceId.slice(6)
245
+ } : event.IsPrivate && event.UserId ? {
246
+ scope: "c2c",
247
+ targetId: event.UserId
248
+ } : void 0);
249
+ const groupId = p.guildId ?? payload.GuildId ?? (target?.scope === "group" ? target.targetId : void 0) ?? event.GuildId ?? event.ChannelId;
250
+ const userId = p.userId ?? payload.UserId ?? event.UserId;
251
+ const need = (value, name) => {
252
+ if (!value) throw new Error(`Missing ${name}`);
253
+ return value;
254
+ };
255
+ const requireGroup = () => {
256
+ if (target?.scope !== "group") throw new Error("This operation requires an explicit group target");
257
+ return need(groupId, "guildId");
258
+ };
259
+ const destination = () => {
260
+ if (!target || !["group", "c2c"].includes(target.scope) || !target.targetId) throw new Error("A group or c2c target is required");
261
+ return target;
262
+ };
263
+ const delivery = () => {
264
+ if (p.wakeup && (p.replyId || p.eventId)) throw new Error("wakeup cannot be combined with replyId/eventId");
265
+ if (p.replyId && p.eventId) throw new Error("replyId and eventId are mutually exclusive");
266
+ if (p.wakeup && target?.scope === "group") throw new Error("QQ wakeup messages require a c2c target");
267
+ const origin = event.Target ?? (event.SpaceId?.startsWith("GROUP:") ? {
268
+ scope: "group",
269
+ targetId: event.SpaceId.slice(6)
270
+ } : event.IsPrivate && event.UserId ? {
271
+ scope: "c2c",
272
+ targetId: event.UserId
273
+ } : void 0);
274
+ const sameConversation = (!(origin?.BotId ?? event.BotId) || (origin?.BotId ?? event.BotId) === botId) && (!origin || !target || origin.scope === target.scope && origin.targetId === target.targetId);
275
+ const eventReply = sameConversation && /^(INTERACTION_CREATE|GROUP_ADD_ROBOT|GROUP_MSG_RECEIVE|C2C_MSG_RECEIVE|FRIEND_ADD)/.test(event._tag ?? "");
276
+ const messageReply = sameConversation && /^(GROUP_AT_MESSAGE_CREATE|GROUP_MESSAGE_CREATE|C2C_MESSAGE_CREATE)$/.test(event._tag ?? "");
277
+ return {
278
+ ...p.wakeup ? { is_wakeup: true } : p.eventId ? { event_id: p.eventId } : p.replyId ? { msg_id: p.replyId } : eventReply ? { event_id: event.EventId ?? event.MessageId } : messageReply ? { msg_id: event.MessageId } : {},
279
+ ...p.sequence !== void 0 && { msg_seq: p.sequence },
280
+ ...p.referenceId && { message_reference: { message_id: p.referenceId } }
281
+ };
282
+ };
283
+ const actions = {
284
+ "menu.get": async () => {
285
+ const r = await client.menuGet();
286
+ return {
287
+ version: r.version,
288
+ menu: r.menu && { items: (r.menu.items ?? []).map(fromMenuItem) }
289
+ };
290
+ },
291
+ "menu.set": () => {
292
+ if (!Array.isArray(p.menu?.items) || p.menu.items.length > 10) throw new Error("A QQ menu supports at most 10 items");
293
+ return client.menuPut({ menu: { items: p.menu.items.map(menuItem) } });
294
+ },
295
+ "panel.list": async () => {
296
+ if (!["group", "c2c"].includes(p.scope)) throw new Error("QQ panel scope must be group or c2c");
297
+ const r = await client.panelsList({
298
+ scope: p.scope,
299
+ ...pagination(p.pagination)
300
+ });
301
+ return page(r.records.map(fromPanel), r);
302
+ },
303
+ "panel.create": async () => {
304
+ validatePanel(p.panel);
305
+ validatePanelAudience(p.scope, p.audience, p.userIds, p.guildIds);
306
+ return { id: (await client.panelsCreate({
307
+ scope: p.scope,
308
+ target_type: p.audience,
309
+ user_openids: p.userIds,
310
+ group_openids: p.guildIds,
311
+ panel: panelBody(p.panel)
312
+ })).panel_id };
313
+ },
314
+ "panel.get": async () => fromPanel(await client.panelsGet(need(p.id, "id"))),
315
+ "panel.update": () => {
316
+ validatePanel(p.panel);
317
+ return client.panelsPut(need(p.id, "id"), { panel: panelBody(p.panel) });
318
+ },
319
+ "panel.delete": () => client.panelsDelete(need(p.id, "id")),
320
+ "panel.targets.update": () => {
321
+ if (!p.userIds?.length && !p.guildIds?.length) throw new Error("Update at least one panel audience");
322
+ if (p.userIds) nonEmptyIds(p.userIds, 20, "user IDs");
323
+ if (p.guildIds) nonEmptyIds(p.guildIds, 20, "group IDs");
324
+ return client.panelsTargetPut(need(p.id, "id"), {
325
+ op: operation(p.operation),
326
+ user_openids: p.userIds,
327
+ group_openids: p.guildIds
328
+ });
329
+ },
330
+ "me.share": async () => (await client.generateUrlLink({ callback_data: p.data })).data,
331
+ "connection.gateway": () => client.gateway(),
332
+ "guild.bot.info": async () => {
333
+ const r = await client.groupsBotState(requireGroup());
334
+ return {
335
+ userId: r.member_openid,
336
+ joinedAt: r.joined_at,
337
+ role: r.member_role,
338
+ allowsProactiveMessages: r.allow_proactive_msg,
339
+ receiveMode: r.recv_msg_setting
340
+ };
341
+ },
342
+ "guild.mute.get": async () => {
343
+ const r = await client.groupsRestrictChatSetting(requireGroup());
344
+ const rule = r.global_rule;
345
+ return {
346
+ global: rule && {
347
+ mode: rule.mode,
348
+ schedules: rule.schedule_rules?.map((s) => ({
349
+ id: s.task_id,
350
+ start: s.start_at,
351
+ end: s.end_at,
352
+ enabled: s.enabled
353
+ })),
354
+ recurring: rule.recurring_rules?.map((s) => ({
355
+ id: s.task_id,
356
+ weekdays: s.weekdays,
357
+ start: s.start_time,
358
+ end: s.end_time,
359
+ enabled: s.enabled
360
+ }))
361
+ },
362
+ members: (r.members ?? []).map((m) => ({
363
+ userId: m.member_openid,
364
+ userName: m.username,
365
+ unionId: m.union_openid,
366
+ expiresAt: m.mute_expire_at
367
+ }))
368
+ };
369
+ },
370
+ "member.kick.batch": async () => {
371
+ const r = await client.groupsBatchRemoveMembers(requireGroup(), {
372
+ member_openids: memberIds(p.userIds),
373
+ add_to_member_blacklist: p.blacklist
374
+ });
375
+ return removal(r);
376
+ },
377
+ "member.blacklist.list": async () => {
378
+ const r = await client.groupsMemberBlacklist(requireGroup(), pagination(p.pagination));
379
+ return page(r.users.map((m) => ({
380
+ userId: m.member_openid,
381
+ userName: m.username,
382
+ unionId: m.union_openid,
383
+ isBot: m.bot,
384
+ bannedAt: m.banned_at
385
+ })), r);
386
+ },
387
+ "member.blacklist.update": async () => {
388
+ const r = await client.groupsMemberBlacklistPost(requireGroup(), {
389
+ op: operation(p.operation),
390
+ member_openids: memberIds(p.userIds)
391
+ });
392
+ return blacklistResult(r);
393
+ },
394
+ "member.mute.batch": () => client.groupsRestrictChatSettingPost(requireGroup(), { members: nonEmptyIds(p.members?.map((m) => m.userId), 20, "member IDs").map((userId, index) => {
395
+ const member = p.members[index];
396
+ return {
397
+ op: member.operation === "remove" ? "del" : member.operation,
398
+ member_openid: userId,
399
+ mute_expire_at: member.expiresAt
400
+ };
401
+ }) }),
402
+ "request.guild.list": async () => {
403
+ const id = requireGroup();
404
+ const r = await client.groupsJoinRequestList(id, pagination(p.pagination));
405
+ return page((r.list ?? []).map((item) => normalizeJoinRequest(item, id)), r);
406
+ },
407
+ "request.guild.decide": () => client.groupsApprovalJoinRequest(requireGroup(), need(p.userId, "userId"), {
408
+ op: p.approve ? "approve" : "decline",
409
+ join_request_id: p.requestId,
410
+ reject_reason: p.reason,
411
+ add_to_member_blacklist: p.blacklist
412
+ }),
413
+ "request.policy.list": async () => {
414
+ const r = await client.groupsJoinApprovalStrategies(pagination(p.pagination));
415
+ return page((r.strategies ?? []).map(policy), r);
416
+ },
417
+ "request.policy.create": async () => {
418
+ if (Boolean(p.guildIds) === Boolean(p.guildNumbers)) throw new Error("Provide exactly one of guildIds or guildNumbers");
419
+ const ids = p.guildIds ?? p.guildNumbers;
420
+ if (!Array.isArray(ids) || ids.length < 1 || ids.length > 100) throw new Error("Expected 1 to 100 groups");
421
+ return policy(await client.groupsJoinApprovalStrategyCreate(policyBody(p)));
422
+ },
423
+ "request.policy.update": async () => {
424
+ if (p.guilds && Boolean(p.guilds.ids?.length) === Boolean(p.guilds.numbers?.length)) throw new Error("Policy group updates require exactly one of ids or numbers");
425
+ if (p.guilds?.ids) nonEmptyIds(p.guilds.ids, 100, "group IDs");
426
+ if (p.guilds?.numbers) nonEmptyIds(p.guilds.numbers, 100, "group numbers");
427
+ const r = await client.groupsJoinApprovalStrategyPatch(need(p.id, "id"), {
428
+ ...policyBody(p),
429
+ group_action: p.guilds && {
430
+ op: operation(p.guilds.operation),
431
+ group_openids: p.guilds.ids,
432
+ group_ids: p.guilds.numbers
433
+ }
434
+ });
435
+ return {
436
+ enabled: r.is_enable === void 0 ? void 0 : r.is_enable === "on",
437
+ expiresAt: r.expire_at
438
+ };
439
+ },
440
+ "request.policy.delete": () => client.groupsJoinApprovalStrategyDelete(need(p.id, "id")),
441
+ "request.policy.execute": () => client.groupsJoinApprovalStrategyExecute(need(p.id, "id")),
442
+ "request.policy.whitelist": async () => {
443
+ const r = await client.groupsJoinApprovalStrategyWhitelistUsers(need(p.id, "id"), {
444
+ op: operation(p.operation),
445
+ whitelist_users: nonEmptyIds(p.userIds, 1e4, "whitelist user IDs")
446
+ });
447
+ return {
448
+ id: r.strategy_id,
449
+ whitelistCount: r.whitelist_user_count,
450
+ updatedAt: r.updated_at
451
+ };
452
+ },
453
+ "message.typing": async () => {
454
+ const t = destination();
455
+ if (t.scope !== "c2c") throw new Error("QQ typing is only available in c2c");
456
+ if (p.duration !== void 0 && (!Number.isInteger(p.duration) || p.duration < 1 || p.duration > 60)) throw new Error("QQ typing duration must be an integer from 1 to 60 seconds");
457
+ return receipt(await client.usersOpenMessages(t.targetId, {
458
+ msg_type: 6,
459
+ input_notify: {
460
+ input_type: 1,
461
+ input_second: p.duration ?? 60
462
+ },
463
+ ...delivery()
464
+ }));
465
+ },
466
+ "message.stream": async () => {
467
+ const t = destination();
468
+ if (t.scope !== "c2c") throw new Error("QQ streaming is only available in c2c");
469
+ if (!p.text || !Number.isInteger(p.index) || p.index < 0) throw new Error("QQ streaming requires non-empty text and a non-negative integer index");
470
+ const r = await client.streamMessages(t.targetId, {
471
+ ...delivery(),
472
+ input_mode: p.mode,
473
+ input_state: p.state === "complete" ? 10 : 1,
474
+ content_type: p.contentType,
475
+ content_raw: p.text,
476
+ index: p.index,
477
+ stream_msg_id: p.streamId
478
+ });
479
+ return {
480
+ ...receipt(r),
481
+ remainingLength: r.remain_msg_len
482
+ };
483
+ },
484
+ "media.prepare": async () => {
485
+ const t = destination();
486
+ const body = {
487
+ file_type: mediaType(p.type),
488
+ file_name: p.name,
489
+ file_size: p.size,
490
+ md5: p.hashes.md5,
491
+ sha1: p.hashes.sha1,
492
+ md5_10m: p.hashes.headMd5
493
+ };
494
+ const r = await (t.scope === "group" ? client.groupUploadPrepare(t.targetId, body) : client.usersUploadPrepare(t.targetId, body));
495
+ return {
496
+ id: r.upload_id,
497
+ blockSize: r.block_size,
498
+ parts: r.parts.map((part) => ({
499
+ index: part.index,
500
+ url: part.presigned_url,
501
+ size: part.block_size
502
+ })),
503
+ config: {
504
+ concurrency: r.upload_config.concurrency,
505
+ retryTimeout: r.upload_config.retry_timeout,
506
+ retryDelay: r.upload_config.retry_delay
507
+ }
508
+ };
509
+ },
510
+ "media.part.finish": () => {
511
+ const t = destination();
512
+ const body = {
513
+ upload_id: p.uploadId,
514
+ part_index: p.index,
515
+ block_size: p.size,
516
+ md5: p.md5
517
+ };
518
+ return t.scope === "group" ? client.groupUploadPartFinish(t.targetId, body) : client.usersUploadPartFinish(t.targetId, body);
519
+ },
520
+ "media.complete": async () => {
521
+ const t = destination();
522
+ const body = {
523
+ upload_id: p.uploadId,
524
+ file_type: mediaType(p.type),
525
+ file_name: p.name,
526
+ srv_send_msg: p.send ?? false
527
+ };
528
+ return mediaReceipt(await (t.scope === "group" ? client.postRichMediaByGroup(t.targetId, body) : client.postRichMediaByUser(t.targetId, body)));
529
+ }
530
+ };
531
+ if (target?.scope === "group") Object.assign(actions, {
532
+ "guild.info": async () => {
533
+ const r = await client.groupsInfo(requireGroup());
534
+ return {
535
+ GuildId: r.group_openid,
536
+ GuildName: r.group_name,
537
+ Description: r.group_finger_memo,
538
+ Category: r.group_class_text,
539
+ Tags: r.group_tags,
540
+ MemberCount: r.group_member_num
541
+ };
542
+ },
543
+ "member.info": async () => member(await client.groupsMembersMessage(requireGroup(), need(userId, "userId")), requireGroup()),
544
+ "member.list": async () => {
545
+ const id = requireGroup();
546
+ const r = await client.groupsMembers(id, { cursor: pagination(p).cursor });
547
+ return page(r.members.map((item) => member(item, id)), r);
548
+ },
549
+ "member.kick": async () => removal(await client.groupsBatchRemoveMembers(requireGroup(), { member_openids: [need(userId, "userId")] })),
550
+ "member.ban": async () => removal(await client.groupsBatchRemoveMembers(requireGroup(), {
551
+ member_openids: [need(userId, "userId")],
552
+ add_to_member_blacklist: true
553
+ })),
554
+ "member.unban": async () => blacklistResult(await client.groupsMemberBlacklistPost(requireGroup(), {
555
+ op: "del",
556
+ member_openids: [need(userId, "userId")]
557
+ })),
558
+ "member.mute": () => client.groupsRestrictChatSettingPost(requireGroup(), { members: [{
559
+ member_openid: need(userId, "userId"),
560
+ op: p.duration === 0 ? "del" : "add",
561
+ mute_expire_at: p.duration === 0 ? "" : new Date(Date.now() + p.duration * 1e3).toISOString()
562
+ }] }),
563
+ "guild.mute": () => {
564
+ throw new Error("QQ group API supports member muting, not setting global mute rules");
565
+ }
566
+ });
567
+ if (data.action === "message.send" && p.content) actions["message.send"] = async () => {
568
+ const t = destination();
569
+ const body = {
570
+ ...toQQMessage(p.content),
571
+ ...delivery()
572
+ };
573
+ return receipt(await (t.scope === "group" ? client.groupOpenMessages(t.targetId, body) : client.usersOpenMessages(t.targetId, body)));
574
+ };
575
+ if (data.action === "message.delete" && target && ["group", "c2c"].includes(target.scope)) actions["message.delete"] = () => target.scope === "group" ? client.groupMessageDelete(target.targetId, need(payload.MessageId, "MessageId")) : client.userMessageDelete(target.targetId, need(payload.MessageId, "MessageId"));
576
+ if (data.action === "message.send" && p.format && target && ["group", "c2c"].includes(target.scope)) {
577
+ const requestedBot = target.BotId ?? payload.BotId ?? event.BotId;
578
+ if (requestedBot && requestedBot !== botId) return [createResult(ResultCode.FailParams, "BotId does not match this adapter", null)];
579
+ try {
580
+ const d = delivery();
581
+ const options = {
582
+ ...p,
583
+ replyId: d.msg_id,
584
+ eventId: d.event_id,
585
+ wakeup: d.is_wakeup
586
+ };
587
+ return target.scope === "group" ? await GROUP_AT_MESSAGE_CREATE(client, { ChannelId: target.targetId }, p.format, options) : await C2C_MESSAGE_CREATE(client, { UserId: target.targetId }, p.format, options);
588
+ } catch (error) {
589
+ return [createResult(ResultCode.Fail, data.action, error?.response?.data ?? error?.message ?? error)];
590
+ }
591
+ }
592
+ const action = actions[data.action];
593
+ if (!action) {
594
+ const guildOperation = /^(guild|member|role)\./.test(data.action) || data.action === "me.guilds";
595
+ const hasSourceEvent = Boolean(payload.event && Object.keys(payload.event).length > 0);
596
+ if (guildOperation && (target?.scope === "group" || target?.scope === "c2c" || !target && hasSourceEvent)) return [createResult(target ? ResultCode.Fail : ResultCode.FailParams, target ? `${data.action} is not supported for QQ ${target.scope}` : `${data.action} requires an explicit target scope`, null)];
597
+ if ((/^(permission|reaction|schedule|forum|audio)\./.test(data.action) || /^channel\.(info|update|delete|legacy-announcement\.|api-permission\.request)/.test(data.action) || /^message\.(get|edit|pin|unpin)$/.test(data.action)) && target?.scope !== "channel" && target?.scope !== "direct") return [createResult(target ? ResultCode.Fail : ResultCode.FailParams, target ? `${data.action} is not supported for QQ ${target.scope}` : `${data.action} requires an explicit target scope`, null)];
598
+ return;
599
+ }
600
+ if ((target?.BotId ?? payload.BotId ?? event.BotId) && (target?.BotId ?? payload.BotId ?? event.BotId) !== botId) return [createResult(ResultCode.FailParams, "BotId does not match this adapter", null)];
601
+ try {
602
+ const result = await action();
603
+ if ([
604
+ "member.kick",
605
+ "member.ban",
606
+ "member.kick.batch",
607
+ "member.unban",
608
+ "member.blacklist.update"
609
+ ].includes(data.action)) {
610
+ const outcome = result;
611
+ if (outcome.status !== void 0 && outcome.status !== "success" || outcome.failedBlacklistIds?.length || outcome.failedUserIds?.length) return [createResult(ResultCode.Warn, `${data.action}: some operations failed; inspect result data`, result)];
612
+ }
613
+ return [createResult(ResultCode.Ok, data.action, result)];
614
+ } catch (error) {
615
+ return [createResult(ResultCode.Fail, data.action, error?.response?.data ?? error?.message ?? error)];
616
+ }
617
+ };
618
+
619
+ //#endregion
620
+ export { handleFrameworkAction, toQQMessage };
@@ -0,0 +1,10 @@
1
+ import { EventsEnum, JoinRequestInfo, MessageMediaItem, User } from "alemonjs";
2
+ //#region src/framework-events.d.ts
3
+ /** Preserve scoped identities in every author/mention, not only the top-level sender. */
4
+ declare const normalizeMessageUser: (raw: any, scope: 'group' | 'c2c' | 'channel') => User;
5
+ declare const normalizeJoinRequest: (raw: any, guildId?: any) => JoinRequestInfo;
6
+ declare const normalizeAttachments: (attachments?: any[]) => MessageMediaItem[];
7
+ /** Standard fields expose platform capabilities without business code reading raw QQ data. */
8
+ declare const normalizeFrameworkEvent: (event: EventsEnum, acknowledged?: boolean) => EventsEnum;
9
+ //#endregion
10
+ export { normalizeAttachments, normalizeFrameworkEvent, normalizeJoinRequest, normalizeMessageUser };