@kodelyth/discord 2026.5.39 → 2026.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/account-inspect-Dqw-enky.js +81 -0
  2. package/dist/account-inspect-api.js +10 -0
  3. package/dist/accounts-B7OBFePq.js +224 -0
  4. package/dist/action-runtime-api.js +2 -0
  5. package/dist/agent-components.runtime-DVY_1VB4.js +4 -0
  6. package/dist/allow-list-B0s7evD7.js +354 -0
  7. package/dist/api-CXAcv9nZ.js +130 -0
  8. package/dist/api.js +23 -0
  9. package/dist/approval-handler.runtime-B9xUAF3n.js +426 -0
  10. package/dist/audit-DoiK49WO.js +24 -0
  11. package/dist/audit-core-BGrq3G7r.js +105 -0
  12. package/dist/channel-U_aeoFwW.js +795 -0
  13. package/dist/channel-actions-BxEBnEuv.js +173 -0
  14. package/dist/channel-actions.runtime-CPtpH-yl.js +263 -0
  15. package/dist/channel-api-BfjklLby.js +21 -0
  16. package/dist/channel-config-api.js +2 -0
  17. package/dist/channel-plugin-api.js +2 -0
  18. package/dist/channel.setup-BUSC0apv.js +337 -0
  19. package/dist/components-luonoe13.js +909 -0
  20. package/dist/config-api-DSYGqaLQ.js +2 -0
  21. package/dist/config-schema-DIqJBGwC.js +357 -0
  22. package/dist/configured-state.js +6 -0
  23. package/dist/contract-api.js +8 -0
  24. package/dist/conversation-identity-DXAm0_Mk.js +270 -0
  25. package/dist/directory-config-CYbuMmPS.js +49 -0
  26. package/dist/directory-contract-api.js +2 -0
  27. package/dist/directory-live-DX4dLRpJ.js +159 -0
  28. package/dist/doctor-bbKSvGVD.js +244 -0
  29. package/dist/doctor-contract-Btjt6NJD.js +383 -0
  30. package/dist/doctor-contract-api.js +2 -0
  31. package/dist/gateway-registry-BKSpa4GB.js +74 -0
  32. package/dist/handle-action.guild-admin-B5BArS2n.js +286 -0
  33. package/dist/inbound-context-WAOqhGlT.js +48 -0
  34. package/dist/inbound-event-delivery-C-1Ji3WP.js +65 -0
  35. package/dist/index.js +26 -0
  36. package/dist/manager.runtime-DXHynKE4.js +2356 -0
  37. package/dist/message-handler-mXzc3tA_.js +381 -0
  38. package/dist/message-handler.preflight-BPD1a347.js +1113 -0
  39. package/dist/message-handler.process-GUa3aV8z.js +1438 -0
  40. package/dist/message-utils-dUbem16p.js +549 -0
  41. package/dist/outbound-adapter-C18OAc1y.js +536 -0
  42. package/dist/pluralkit-D1Q2x0w5.js +22 -0
  43. package/dist/preflight-audio-CZtpWcIm.js +72 -0
  44. package/dist/preflight-audio.runtime-Brx_0_xW.js +7 -0
  45. package/dist/preview-streaming-D_slNIiO.js +8 -0
  46. package/dist/probe-D--Ca4JF.js +139 -0
  47. package/dist/probe.runtime-DQBchZzv.js +2 -0
  48. package/dist/provider-B2-31CIT.js +9565 -0
  49. package/dist/provider-session.runtime-BwzzSsrH.js +6 -0
  50. package/dist/provider.runtime-CP3oHLls.js +2 -0
  51. package/dist/resolve-allowlist-common-CqxPLcJO.js +34 -0
  52. package/dist/resolve-channels-0LX4pUbB.js +265 -0
  53. package/dist/resolve-users-CztOv0Qs.js +120 -0
  54. package/dist/runtime-DUaw66V_.js +1073 -0
  55. package/dist/runtime-api.actions.js +3 -0
  56. package/dist/runtime-api.js +30 -0
  57. package/dist/runtime-api.lookup.js +7 -0
  58. package/dist/runtime-api.monitor-CvVKvEXW.js +5 -0
  59. package/dist/runtime-api.monitor.js +8 -0
  60. package/dist/runtime-api.send.js +6 -0
  61. package/dist/runtime-api.threads.js +6 -0
  62. package/dist/runtime-fC6f4UF2.js +8 -0
  63. package/dist/runtime-setter-api.js +2 -0
  64. package/dist/secret-config-contract-B6WW5V88.js +115 -0
  65. package/dist/secret-contract-api.js +2 -0
  66. package/dist/security-audit-CnyIQKz6.js +120 -0
  67. package/dist/security-audit-contract-api.js +2 -0
  68. package/dist/security-audit.runtime-CQSkjNLu.js +2 -0
  69. package/dist/security-contract-DLvYOgLM.js +26 -0
  70. package/dist/security-contract-api.js +2 -0
  71. package/dist/security-doctor-DepqtNCI.js +18 -0
  72. package/dist/send-DCtPCHGk.js +881 -0
  73. package/dist/send.components-Bcgxvm52.js +474 -0
  74. package/dist/send.outbound-S9t0UuHc.js +330 -0
  75. package/dist/send.receipt-CDn3GBWC.js +3119 -0
  76. package/dist/send.shared-D4iBnAmn.js +669 -0
  77. package/dist/sender-identity-CxCe3_1a.js +43 -0
  78. package/dist/session-contract-Dwhw3RTY.js +6 -0
  79. package/dist/session-key-api.js +2 -0
  80. package/dist/session-key-normalization-CP8dPUid.js +23 -0
  81. package/dist/setup-entry.js +11 -0
  82. package/dist/setup-plugin-api.js +2 -0
  83. package/dist/shared-AIlvuZXt.js +171 -0
  84. package/dist/subagent-hooks-8bK-mgiU.js +120 -0
  85. package/dist/subagent-hooks-api.js +22 -0
  86. package/dist/system-events-Ba1TklaL.js +34 -0
  87. package/dist/target-resolver-BrtFQtoK.js +82 -0
  88. package/dist/targets-DWLLZE2l.js +3 -0
  89. package/dist/test-api.js +45 -0
  90. package/dist/thread-binding-api.js +4 -0
  91. package/dist/thread-bindings-9aKRmZv0.js +255 -0
  92. package/dist/thread-bindings.discord-api-ssGH5wc2.js +244 -0
  93. package/dist/thread-bindings.manager-0YBHGemk.js +534 -0
  94. package/dist/thread-bindings.session-updates-DJZGIwaU.js +54 -0
  95. package/dist/thread-bindings.state-eTFl-PqJ.js +318 -0
  96. package/dist/timeouts-CEwuGaWT.js +52 -0
  97. package/dist/timeouts.js +2 -0
  98. package/dist/typing-BmJKRpCS.js +14 -0
  99. package/package.json +19 -7
  100. package/account-inspect-api.js +0 -7
  101. package/action-runtime-api.js +0 -7
  102. package/api.js +0 -7
  103. package/channel-config-api.js +0 -7
  104. package/channel-plugin-api.js +0 -7
  105. package/configured-state.js +0 -7
  106. package/contract-api.js +0 -7
  107. package/directory-contract-api.js +0 -7
  108. package/doctor-contract-api.js +0 -7
  109. package/index.js +0 -7
  110. package/runtime-api.actions.js +0 -7
  111. package/runtime-api.js +0 -7
  112. package/runtime-api.lookup.js +0 -7
  113. package/runtime-api.monitor.js +0 -7
  114. package/runtime-api.send.js +0 -7
  115. package/runtime-api.threads.js +0 -7
  116. package/runtime-setter-api.js +0 -7
  117. package/secret-contract-api.js +0 -7
  118. package/security-audit-contract-api.js +0 -7
  119. package/security-contract-api.js +0 -7
  120. package/session-key-api.js +0 -7
  121. package/setup-entry.js +0 -7
  122. package/setup-plugin-api.js +0 -7
  123. package/subagent-hooks-api.js +0 -7
  124. package/test-api.js +0 -7
  125. package/thread-binding-api.js +0 -7
  126. package/timeouts.js +0 -7
@@ -0,0 +1,881 @@
1
+ import { At as getGuildMember, Bt as removeGuildMemberRole, C as RateLimitError, Ct as createGuildBan, Dt as createGuildSticker, E as readRetryAfter, Et as createGuildScheduledEvent, Ft as listGuildRoles, It as listGuildScheduledEvents, Lt as moveGuildChannels, Mt as listGuildActiveThreads, Nt as listGuildChannels, Ot as deleteChannelPermission, Pt as listGuildEmojis, Rt as putChannelPermission, S as DiscordError, St as addGuildMemberRole, T as readDiscordMessage, Tt as createGuildEmoji, Vt as timeoutGuildMember, Wt as __exportAll, _t as listChannelPins, at as deleteOwnMessageReaction, bt as sendChannelTyping, ct as createThread, dt as editChannel, ft as editChannelMessage, gt as listChannelMessages, ht as listChannelArchivedThreads, it as createOwnMessageReaction, jt as getGuildVoiceState, lt as deleteChannel, mt as getChannelMessage, n as createDiscordSendResult, ot as listMessageReactionUsers, pt as getChannel, st as createChannelMessage, ut as deleteChannelMessage, vt as pinChannelMessage, w as readDiscordCode, wt as createGuildChannel, xt as unpinChannelMessage, yt as searchGuildMessages, zt as removeGuildMember } from "./send.receipt-CDn3GBWC.js";
2
+ import { s as resolveDiscordAccount } from "./accounts-B7OBFePq.js";
3
+ import { C as DISCORD_MAX_STICKER_BYTES, D as fetchMemberGuildPermissionsDiscord, E as fetchChannelPermissionsDiscord, F as resolveDiscordClientAccountContext, I as resolveDiscordRest, M as createDiscordClient, O as hasAllGuildPermissionsDiscord, S as DISCORD_MAX_EVENT_COVER_BYTES, T as canViewDiscordGuildChannel, i as formatReactionEmoji, j as parseAndResolveRecipient, k as hasAnyGuildPermissionDiscord, l as resolveChannelId, o as normalizeEmojiName, r as buildReactionIdentifier, s as normalizeReactionEmoji, t as buildDiscordSendError, w as DiscordSendError, x as DISCORD_MAX_EMOJI_BYTES } from "./send.shared-D4iBnAmn.js";
4
+ import { a as rewriteDiscordKnownMentions, n as sendPollDiscord, r as sendStickerDiscord, t as sendMessageDiscord } from "./send.outbound-S9t0UuHc.js";
5
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "klaw/plugin-sdk/string-coerce-runtime";
6
+ import { ChannelType } from "discord-api-types/v10";
7
+ import crypto from "node:crypto";
8
+ import path from "node:path";
9
+ import { writeExternalFileWithinRoot } from "klaw/plugin-sdk/security-runtime";
10
+ import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, extensionForMime, maxBytesForKind, parseFfprobeCodecAndSampleRate, runFfmpeg, runFfprobe, unlinkIfExists } from "klaw/plugin-sdk/media-runtime";
11
+ import { requireRuntimeConfig } from "klaw/plugin-sdk/plugin-config-runtime";
12
+ import { loadWebMediaRaw } from "klaw/plugin-sdk/web-media";
13
+ import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
14
+ import { recordChannelActivity } from "klaw/plugin-sdk/channel-activity-runtime";
15
+ import fs from "node:fs/promises";
16
+ import { resolvePreferredKlawTmpDir, tempWorkspace } from "klaw/plugin-sdk/temp-path";
17
+ import { fetchWithSsrFGuard } from "klaw/plugin-sdk/ssrf-runtime";
18
+ //#region extensions/discord/src/send.channels.ts
19
+ async function createChannelDiscord(payload, opts) {
20
+ const rest = resolveDiscordRest(opts);
21
+ const body = { name: payload.name };
22
+ if (payload.type !== void 0) body.type = payload.type;
23
+ if (payload.parentId) body.parent_id = payload.parentId;
24
+ if (payload.topic) body.topic = payload.topic;
25
+ if (payload.position !== void 0) body.position = payload.position;
26
+ if (payload.nsfw !== void 0) body.nsfw = payload.nsfw;
27
+ return await createGuildChannel(rest, payload.guildId, { body });
28
+ }
29
+ async function editChannelDiscord(payload, opts) {
30
+ const rest = resolveDiscordRest(opts);
31
+ const body = {};
32
+ if (payload.name !== void 0) body.name = payload.name;
33
+ if (payload.topic !== void 0) body.topic = payload.topic;
34
+ if (payload.position !== void 0) body.position = payload.position;
35
+ if (payload.parentId !== void 0) body.parent_id = payload.parentId;
36
+ if (payload.nsfw !== void 0) body.nsfw = payload.nsfw;
37
+ if (payload.rateLimitPerUser !== void 0) body.rate_limit_per_user = payload.rateLimitPerUser;
38
+ if (payload.archived !== void 0) body.archived = payload.archived;
39
+ if (payload.locked !== void 0) body.locked = payload.locked;
40
+ if (payload.autoArchiveDuration !== void 0) body.auto_archive_duration = payload.autoArchiveDuration;
41
+ if (payload.availableTags !== void 0) body.available_tags = payload.availableTags.map((t) => ({
42
+ ...t.id !== void 0 && { id: t.id },
43
+ name: t.name,
44
+ ...t.moderated !== void 0 && { moderated: t.moderated },
45
+ ...t.emoji_id !== void 0 && { emoji_id: t.emoji_id },
46
+ ...t.emoji_name !== void 0 && { emoji_name: t.emoji_name }
47
+ }));
48
+ return await editChannel(rest, payload.channelId, { body });
49
+ }
50
+ async function deleteChannelDiscord(channelId, opts) {
51
+ await deleteChannel(resolveDiscordRest(opts), channelId);
52
+ return {
53
+ ok: true,
54
+ channelId
55
+ };
56
+ }
57
+ async function moveChannelDiscord(payload, opts) {
58
+ const rest = resolveDiscordRest(opts);
59
+ const body = [{
60
+ id: payload.channelId,
61
+ ...payload.parentId !== void 0 && { parent_id: payload.parentId },
62
+ ...payload.position !== void 0 && { position: payload.position }
63
+ }];
64
+ await moveGuildChannels(rest, payload.guildId, { body });
65
+ return { ok: true };
66
+ }
67
+ async function setChannelPermissionDiscord(payload, opts) {
68
+ const rest = resolveDiscordRest(opts);
69
+ const body = { type: payload.targetType };
70
+ if (payload.allow !== void 0) body.allow = payload.allow;
71
+ if (payload.deny !== void 0) body.deny = payload.deny;
72
+ await putChannelPermission(rest, payload.channelId, payload.targetId, { body });
73
+ return { ok: true };
74
+ }
75
+ async function removeChannelPermissionDiscord(channelId, targetId, opts) {
76
+ await deleteChannelPermission(resolveDiscordRest(opts), channelId, targetId);
77
+ return { ok: true };
78
+ }
79
+ //#endregion
80
+ //#region extensions/discord/src/send.emojis-stickers.ts
81
+ async function listGuildEmojisDiscord(guildId, opts) {
82
+ return await listGuildEmojis(resolveDiscordRest(opts), guildId);
83
+ }
84
+ async function uploadEmojiDiscord(payload, opts) {
85
+ const rest = resolveDiscordRest(opts);
86
+ const media = await loadWebMediaRaw(payload.mediaUrl, DISCORD_MAX_EMOJI_BYTES);
87
+ const contentType = normalizeOptionalLowercaseString(media.contentType);
88
+ if (!contentType || ![
89
+ "image/png",
90
+ "image/jpeg",
91
+ "image/jpg",
92
+ "image/gif"
93
+ ].includes(contentType)) throw new Error("Discord emoji uploads require a PNG, JPG, or GIF image");
94
+ const image = `data:${contentType};base64,${media.buffer.toString("base64")}`;
95
+ const roleIds = (payload.roleIds ?? []).map((id) => id.trim()).filter(Boolean);
96
+ return await createGuildEmoji(rest, payload.guildId, { body: {
97
+ name: normalizeEmojiName(payload.name, "Emoji name"),
98
+ image,
99
+ roles: roleIds.length ? roleIds : void 0
100
+ } });
101
+ }
102
+ async function uploadStickerDiscord(payload, opts) {
103
+ const rest = resolveDiscordRest(opts);
104
+ const media = await loadWebMediaRaw(payload.mediaUrl, DISCORD_MAX_STICKER_BYTES);
105
+ const contentType = normalizeOptionalLowercaseString(media.contentType);
106
+ if (!contentType || ![
107
+ "image/png",
108
+ "image/apng",
109
+ "application/json"
110
+ ].includes(contentType)) throw new Error("Discord sticker uploads require a PNG, APNG, or Lottie JSON file");
111
+ return await createGuildSticker(rest, payload.guildId, {
112
+ multipartStyle: "form",
113
+ body: {
114
+ name: normalizeEmojiName(payload.name, "Sticker name"),
115
+ description: normalizeEmojiName(payload.description, "Sticker description"),
116
+ tags: normalizeEmojiName(payload.tags, "Sticker tags"),
117
+ files: [{
118
+ data: media.buffer,
119
+ fieldName: "file",
120
+ name: media.fileName ?? "sticker",
121
+ contentType
122
+ }]
123
+ }
124
+ });
125
+ }
126
+ //#endregion
127
+ //#region extensions/discord/src/send.guild.ts
128
+ async function fetchMemberInfoDiscord(guildId, userId, opts) {
129
+ return await getGuildMember(resolveDiscordRest(opts), guildId, userId);
130
+ }
131
+ async function fetchRoleInfoDiscord(guildId, opts) {
132
+ return await listGuildRoles(resolveDiscordRest(opts), guildId);
133
+ }
134
+ async function addRoleDiscord(payload, opts) {
135
+ await addGuildMemberRole(resolveDiscordRest(opts), payload.guildId, payload.userId, payload.roleId);
136
+ return { ok: true };
137
+ }
138
+ async function removeRoleDiscord(payload, opts) {
139
+ await removeGuildMemberRole(resolveDiscordRest(opts), payload.guildId, payload.userId, payload.roleId);
140
+ return { ok: true };
141
+ }
142
+ async function fetchChannelInfoDiscord(channelId, opts) {
143
+ return await getChannel(resolveDiscordRest(opts), channelId);
144
+ }
145
+ async function listGuildChannelsDiscord(guildId, opts) {
146
+ return await listGuildChannels(resolveDiscordRest(opts), guildId);
147
+ }
148
+ async function fetchVoiceStatusDiscord(guildId, userId, opts) {
149
+ return await getGuildVoiceState(resolveDiscordRest(opts), guildId, userId);
150
+ }
151
+ async function listScheduledEventsDiscord(guildId, opts) {
152
+ return await listGuildScheduledEvents(resolveDiscordRest(opts), guildId);
153
+ }
154
+ const ALLOWED_EVENT_COVER_TYPES = new Set([
155
+ "image/png",
156
+ "image/jpeg",
157
+ "image/jpg",
158
+ "image/gif"
159
+ ]);
160
+ async function resolveEventCoverImage(imageUrl, opts) {
161
+ const media = await loadWebMediaRaw(imageUrl, DISCORD_MAX_EVENT_COVER_BYTES, { localRoots: opts?.localRoots });
162
+ const contentType = normalizeOptionalLowercaseString(media.contentType);
163
+ if (!contentType || !ALLOWED_EVENT_COVER_TYPES.has(contentType)) throw new Error(`Discord event cover images must be PNG, JPG, or GIF (got ${contentType ?? "unknown"})`);
164
+ return `data:${contentType};base64,${media.buffer.toString("base64")}`;
165
+ }
166
+ async function createScheduledEventDiscord(guildId, payload, opts) {
167
+ return await createGuildScheduledEvent(resolveDiscordRest(opts), guildId, payload);
168
+ }
169
+ async function timeoutMemberDiscord(payload, opts) {
170
+ const rest = resolveDiscordRest(opts);
171
+ let until = payload.until;
172
+ if (!until && payload.durationMinutes) {
173
+ const ms = payload.durationMinutes * 60 * 1e3;
174
+ until = new Date(Date.now() + ms).toISOString();
175
+ }
176
+ return await timeoutGuildMember(rest, payload.guildId, payload.userId, {
177
+ body: { communication_disabled_until: until ?? null },
178
+ headers: payload.reason ? { "X-Audit-Log-Reason": encodeURIComponent(payload.reason) } : void 0
179
+ });
180
+ }
181
+ async function kickMemberDiscord(payload, opts) {
182
+ await removeGuildMember(resolveDiscordRest(opts), payload.guildId, payload.userId, { headers: payload.reason ? { "X-Audit-Log-Reason": encodeURIComponent(payload.reason) } : void 0 });
183
+ return { ok: true };
184
+ }
185
+ async function banMemberDiscord(payload, opts) {
186
+ const rest = resolveDiscordRest(opts);
187
+ const deleteMessageDays = typeof payload.deleteMessageDays === "number" && Number.isFinite(payload.deleteMessageDays) ? Math.min(Math.max(Math.floor(payload.deleteMessageDays), 0), 7) : void 0;
188
+ await createGuildBan(rest, payload.guildId, payload.userId, {
189
+ body: deleteMessageDays !== void 0 ? { delete_message_days: deleteMessageDays } : void 0,
190
+ headers: payload.reason ? { "X-Audit-Log-Reason": encodeURIComponent(payload.reason) } : void 0
191
+ });
192
+ return { ok: true };
193
+ }
194
+ //#endregion
195
+ //#region extensions/discord/src/send.messages.ts
196
+ function formatDiscordThreadInitialMessageError(error) {
197
+ return error instanceof Error ? error.message : String(error);
198
+ }
199
+ var DiscordThreadInitialMessageError = class extends Error {
200
+ constructor(thread, error) {
201
+ const initialMessageError = formatDiscordThreadInitialMessageError(error);
202
+ super(`Discord thread was created, but sending the initial message failed: ${initialMessageError}`);
203
+ this.name = "DiscordThreadInitialMessageError";
204
+ this.initialMessageError = initialMessageError;
205
+ this.thread = thread;
206
+ }
207
+ };
208
+ async function readMessagesDiscord(channelId, query, opts) {
209
+ const messageQuery = query ?? {};
210
+ const rest = resolveDiscordRest(opts);
211
+ const limit = typeof messageQuery.limit === "number" && Number.isFinite(messageQuery.limit) ? Math.min(Math.max(Math.floor(messageQuery.limit), 1), 100) : void 0;
212
+ const params = {};
213
+ if (limit) params.limit = limit;
214
+ if (messageQuery.before) params.before = messageQuery.before;
215
+ if (messageQuery.after) params.after = messageQuery.after;
216
+ if (messageQuery.around) params.around = messageQuery.around;
217
+ return await listChannelMessages(rest, channelId, params);
218
+ }
219
+ async function fetchMessageDiscord(channelId, messageId, opts) {
220
+ return await getChannelMessage(resolveDiscordRest(opts), channelId, messageId);
221
+ }
222
+ async function editMessageDiscord(channelId, messageId, payload, opts) {
223
+ return await editChannelMessage(resolveDiscordRest(opts), channelId, messageId, { body: {
224
+ content: payload.content,
225
+ ...payload.flags !== void 0 ? { flags: payload.flags } : {}
226
+ } });
227
+ }
228
+ async function deleteMessageDiscord(channelId, messageId, opts) {
229
+ await deleteChannelMessage(resolveDiscordRest(opts), channelId, messageId);
230
+ return { ok: true };
231
+ }
232
+ async function pinMessageDiscord(channelId, messageId, opts) {
233
+ await pinChannelMessage(resolveDiscordRest(opts), channelId, messageId);
234
+ return { ok: true };
235
+ }
236
+ async function unpinMessageDiscord(channelId, messageId, opts) {
237
+ await unpinChannelMessage(resolveDiscordRest(opts), channelId, messageId);
238
+ return { ok: true };
239
+ }
240
+ async function listPinsDiscord(channelId, opts) {
241
+ return await listChannelPins(resolveDiscordRest(opts), channelId);
242
+ }
243
+ async function createThreadDiscord(channelId, payload, opts) {
244
+ const rest = resolveDiscordRest(opts);
245
+ const body = { name: payload.name };
246
+ if (payload.autoArchiveMinutes) body.auto_archive_duration = payload.autoArchiveMinutes;
247
+ if (!payload.messageId && payload.type !== void 0) body.type = payload.type;
248
+ let channelType;
249
+ if (!payload.messageId) try {
250
+ channelType = (await getChannel(rest, channelId))?.type;
251
+ } catch {
252
+ channelType = void 0;
253
+ }
254
+ const isForumLike = channelType === ChannelType.GuildForum || channelType === ChannelType.GuildMedia;
255
+ if (isForumLike) {
256
+ body.message = { content: payload.content?.trim() ? payload.content : payload.name };
257
+ if (payload.appliedTags?.length) body.applied_tags = payload.appliedTags;
258
+ }
259
+ if (!payload.messageId && !isForumLike && body.type === void 0) body.type = ChannelType.PublicThread;
260
+ const thread = await createThread(rest, channelId, { body }, payload.messageId);
261
+ if (!isForumLike && payload.content?.trim() && "id" in thread) try {
262
+ await createChannelMessage(rest, thread.id, { body: { content: payload.content } });
263
+ } catch (error) {
264
+ throw new DiscordThreadInitialMessageError(thread, error);
265
+ }
266
+ return thread;
267
+ }
268
+ async function listThreadsDiscord(payload, opts) {
269
+ const rest = resolveDiscordRest(opts);
270
+ if (payload.includeArchived) {
271
+ if (!payload.channelId) throw new Error("channelId required to list archived threads");
272
+ const params = {};
273
+ if (payload.before) params.before = payload.before;
274
+ if (payload.limit) params.limit = payload.limit;
275
+ return await listChannelArchivedThreads(rest, payload.channelId, params);
276
+ }
277
+ return await listGuildActiveThreads(rest, payload.guildId);
278
+ }
279
+ async function searchMessagesDiscord(query, opts) {
280
+ const rest = resolveDiscordRest(opts);
281
+ const params = new URLSearchParams();
282
+ params.set("content", query.content);
283
+ if (query.channelIds?.length) for (const channelId of query.channelIds) params.append("channel_id", channelId);
284
+ if (query.authorIds?.length) for (const authorId of query.authorIds) params.append("author_id", authorId);
285
+ if (query.limit) {
286
+ const limit = Math.min(Math.max(Math.floor(query.limit), 1), 25);
287
+ params.set("limit", String(limit));
288
+ }
289
+ return await searchGuildMessages(rest, query.guildId, params);
290
+ }
291
+ //#endregion
292
+ //#region extensions/discord/src/send.webhook.ts
293
+ function resolveWebhookExecutionUrl(params) {
294
+ const baseUrl = new URL(`https://discord.com/api/v10/webhooks/${encodeURIComponent(params.webhookId)}/${encodeURIComponent(params.webhookToken)}`);
295
+ baseUrl.searchParams.set("wait", params.wait === false ? "false" : "true");
296
+ if (params.threadId !== void 0 && params.threadId !== null && params.threadId !== "") baseUrl.searchParams.set("thread_id", String(params.threadId));
297
+ return baseUrl.toString();
298
+ }
299
+ function coerceWebhookErrorBody(raw) {
300
+ if (!raw) return;
301
+ try {
302
+ return JSON.parse(raw);
303
+ } catch {
304
+ return { message: raw.slice(0, 200) };
305
+ }
306
+ }
307
+ async function throwWebhookResponseError(response) {
308
+ const parsed = coerceWebhookErrorBody(await response.text().catch(() => ""));
309
+ if (response.status === 429) throw new RateLimitError(response, {
310
+ message: readDiscordMessage(parsed, "Rate limited"),
311
+ retry_after: readRetryAfter(parsed, response, 1),
312
+ code: readDiscordCode(parsed),
313
+ global: parsed && typeof parsed === "object" && "global" in parsed ? Boolean(parsed.global) : false
314
+ });
315
+ throw new DiscordError(response, parsed);
316
+ }
317
+ async function sendWebhookMessageDiscord(text, opts) {
318
+ const webhookId = normalizeOptionalString(opts.webhookId) ?? "";
319
+ const webhookToken = normalizeOptionalString(opts.webhookToken) ?? "";
320
+ if (!webhookId || !webhookToken) throw new Error("Discord webhook id/token are required");
321
+ const replyTo = normalizeOptionalString(opts.replyTo) ?? "";
322
+ const messageReference = replyTo ? {
323
+ message_id: replyTo,
324
+ fail_if_not_exists: false
325
+ } : void 0;
326
+ const { account, proxyFetch } = resolveDiscordClientAccountContext({
327
+ cfg: opts.cfg,
328
+ accountId: opts.accountId
329
+ });
330
+ const rewrittenText = rewriteDiscordKnownMentions(text, {
331
+ accountId: account.accountId,
332
+ mentionAliases: account.config.mentionAliases
333
+ });
334
+ const response = await (proxyFetch ?? fetch)(resolveWebhookExecutionUrl({
335
+ webhookId,
336
+ webhookToken,
337
+ threadId: opts.threadId,
338
+ wait: opts.wait
339
+ }), {
340
+ method: "POST",
341
+ headers: { "content-type": "application/json" },
342
+ body: JSON.stringify({
343
+ content: rewrittenText,
344
+ username: normalizeOptionalString(opts.username),
345
+ avatar_url: normalizeOptionalString(opts.avatarUrl),
346
+ ...messageReference ? { message_reference: messageReference } : {}
347
+ })
348
+ });
349
+ if (!response.ok) await throwWebhookResponseError(response);
350
+ const payload = await response.json().catch(() => ({}));
351
+ try {
352
+ recordChannelActivity({
353
+ channel: "discord",
354
+ accountId: account.accountId,
355
+ direction: "outbound"
356
+ });
357
+ } catch {}
358
+ return createDiscordSendResult({
359
+ result: payload,
360
+ fallbackChannelId: opts.threadId ? String(opts.threadId) : "",
361
+ kind: "text",
362
+ ...opts.threadId != null ? { threadId: opts.threadId } : {},
363
+ ...replyTo ? { replyToId: replyTo } : {}
364
+ });
365
+ }
366
+ //#endregion
367
+ //#region extensions/discord/src/voice-message.ts
368
+ /**
369
+ * Discord Voice Message Support
370
+ *
371
+ * Implements sending voice messages via Discord's API.
372
+ * Voice messages require:
373
+ * - OGG/Opus format audio
374
+ * - Waveform data (base64 encoded, up to 256 samples, 0-255 values)
375
+ * - Duration in seconds
376
+ * - Message flag 8192 (IS_VOICE_MESSAGE)
377
+ * - No other content (text, embeds, etc.)
378
+ */
379
+ const DISCORD_VOICE_MESSAGE_FLAG = 8192;
380
+ const SUPPRESS_NOTIFICATIONS_FLAG = 4096;
381
+ const WAVEFORM_SAMPLES = 256;
382
+ const DISCORD_OPUS_SAMPLE_RATE_HZ = 48e3;
383
+ const DISCORD_VOICE_UPLOAD_SSRF_POLICY = {
384
+ allowRfc2544BenchmarkRange: true,
385
+ allowIpv6UniqueLocalRange: true
386
+ };
387
+ async function runFfmpegToOutput(params) {
388
+ const rootDir = path.dirname(params.outputPath);
389
+ await fs.mkdir(rootDir, { recursive: true });
390
+ await writeExternalFileWithinRoot({
391
+ rootDir,
392
+ path: path.basename(params.outputPath),
393
+ write: async (tempPath) => {
394
+ await runFfmpeg(params.buildArgs(tempPath));
395
+ }
396
+ });
397
+ }
398
+ function createRateLimitError(response, body, request) {
399
+ return new RateLimitError(response, body, request ?? new Request("https://discord.com/api/v10/channels/voice/messages", { method: "POST" }));
400
+ }
401
+ /**
402
+ * Get audio duration using ffprobe
403
+ */
404
+ async function getAudioDuration(filePath) {
405
+ try {
406
+ const stdout = await runFfprobe([
407
+ "-v",
408
+ "error",
409
+ "-show_entries",
410
+ "format=duration",
411
+ "-of",
412
+ "csv=p=0",
413
+ filePath
414
+ ]);
415
+ const duration = Number.parseFloat(stdout.trim());
416
+ if (Number.isNaN(duration)) throw new Error("Could not parse duration");
417
+ return Math.round(duration * 100) / 100;
418
+ } catch (err) {
419
+ const errMessage = formatErrorMessage(err);
420
+ throw new Error(`Failed to get audio duration: ${errMessage}`, { cause: err });
421
+ }
422
+ }
423
+ /**
424
+ * Generate waveform data from audio file using ffmpeg
425
+ * Returns base64 encoded byte array of amplitude samples (0-255)
426
+ */
427
+ async function generateWaveform(filePath) {
428
+ try {
429
+ return await generateWaveformFromPcm(filePath);
430
+ } catch {
431
+ return generatePlaceholderWaveform();
432
+ }
433
+ }
434
+ /**
435
+ * Generate waveform by extracting raw PCM data and sampling amplitudes
436
+ */
437
+ async function generateWaveformFromPcm(filePath) {
438
+ const tempDir = resolvePreferredKlawTmpDir();
439
+ const tempPcm = path.join(tempDir, `waveform-${crypto.randomUUID()}.raw`);
440
+ try {
441
+ await runFfmpegToOutput({
442
+ outputPath: tempPcm,
443
+ buildArgs: (outputPath) => [
444
+ "-y",
445
+ "-i",
446
+ filePath,
447
+ "-vn",
448
+ "-sn",
449
+ "-dn",
450
+ "-t",
451
+ String(MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS),
452
+ "-f",
453
+ "s16le",
454
+ "-acodec",
455
+ "pcm_s16le",
456
+ "-ac",
457
+ "1",
458
+ "-ar",
459
+ "8000",
460
+ outputPath
461
+ ]
462
+ });
463
+ const pcmData = await fs.readFile(tempPcm);
464
+ const samples = new Int16Array(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength / 2);
465
+ const step = Math.max(1, Math.floor(samples.length / WAVEFORM_SAMPLES));
466
+ const waveform = [];
467
+ for (let i = 0; i < WAVEFORM_SAMPLES && i * step < samples.length; i++) {
468
+ let sum = 0;
469
+ let count = 0;
470
+ for (let j = 0; j < step && i * step + j < samples.length; j++) {
471
+ sum += Math.abs(samples[i * step + j]);
472
+ count++;
473
+ }
474
+ const avg = count > 0 ? sum / count : 0;
475
+ const normalized = Math.min(255, Math.round(avg / 32767 * 255));
476
+ waveform.push(normalized);
477
+ }
478
+ while (waveform.length < WAVEFORM_SAMPLES) waveform.push(0);
479
+ return Buffer.from(waveform).toString("base64");
480
+ } finally {
481
+ await unlinkIfExists(tempPcm);
482
+ }
483
+ }
484
+ /**
485
+ * Generate a placeholder waveform (for when audio processing fails)
486
+ */
487
+ function generatePlaceholderWaveform() {
488
+ const waveform = [];
489
+ for (let i = 0; i < WAVEFORM_SAMPLES; i++) {
490
+ const value = Math.round(128 + 64 * Math.sin(i / WAVEFORM_SAMPLES * Math.PI * 8));
491
+ waveform.push(Math.min(255, Math.max(0, value)));
492
+ }
493
+ return Buffer.from(waveform).toString("base64");
494
+ }
495
+ /**
496
+ * Convert audio file to OGG/Opus format if needed
497
+ * Returns path to the OGG file (may be same as input if already OGG/Opus)
498
+ */
499
+ async function ensureOggOpus(filePath) {
500
+ const trimmed = filePath.trim();
501
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) throw new Error(`Voice message conversion requires a local file path; received a URL/protocol source: ${trimmed}`);
502
+ if (normalizeLowercaseStringOrEmpty(path.extname(filePath)) === ".ogg") try {
503
+ const { codec, sampleRateHz } = parseFfprobeCodecAndSampleRate(await runFfprobe([
504
+ "-v",
505
+ "error",
506
+ "-select_streams",
507
+ "a:0",
508
+ "-show_entries",
509
+ "stream=codec_name,sample_rate",
510
+ "-of",
511
+ "csv=p=0",
512
+ filePath
513
+ ]));
514
+ if (codec === "opus" && sampleRateHz === DISCORD_OPUS_SAMPLE_RATE_HZ) return {
515
+ path: filePath,
516
+ cleanup: false
517
+ };
518
+ } catch {}
519
+ const tempDir = resolvePreferredKlawTmpDir();
520
+ const outputPath = path.join(tempDir, `voice-${crypto.randomUUID()}.ogg`);
521
+ await runFfmpegToOutput({
522
+ outputPath,
523
+ buildArgs: (tempPath) => [
524
+ "-y",
525
+ "-i",
526
+ filePath,
527
+ "-vn",
528
+ "-sn",
529
+ "-dn",
530
+ "-t",
531
+ String(MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS),
532
+ "-ar",
533
+ String(DISCORD_OPUS_SAMPLE_RATE_HZ),
534
+ "-c:a",
535
+ "libopus",
536
+ "-b:a",
537
+ "64k",
538
+ "-f",
539
+ "ogg",
540
+ tempPath
541
+ ]
542
+ });
543
+ return {
544
+ path: outputPath,
545
+ cleanup: true
546
+ };
547
+ }
548
+ /**
549
+ * Get voice message metadata (duration and waveform)
550
+ */
551
+ async function getVoiceMessageMetadata(filePath) {
552
+ const [durationSecs, waveform] = await Promise.all([getAudioDuration(filePath), generateWaveform(filePath)]);
553
+ return {
554
+ durationSecs,
555
+ waveform
556
+ };
557
+ }
558
+ function coerceDiscordErrorBody(raw) {
559
+ if (!raw) return;
560
+ try {
561
+ return JSON.parse(raw);
562
+ } catch {
563
+ return { message: raw.slice(0, 200) };
564
+ }
565
+ }
566
+ async function createVoiceRequestError(response, fallbackMessage) {
567
+ const parsed = coerceDiscordErrorBody(await response.text().catch(() => ""));
568
+ if (response.status === 429) throw createRateLimitError(response, {
569
+ message: readDiscordMessage(parsed, "You are being rate limited."),
570
+ retry_after: readRetryAfter(parsed, response, 1),
571
+ global: parsed && typeof parsed === "object" && "global" in parsed ? Boolean(parsed.global) : false
572
+ });
573
+ return new DiscordError(response, parsed ?? { message: fallbackMessage });
574
+ }
575
+ async function requestVoiceUploadUrl(params) {
576
+ const { response: res, release } = await fetchWithSsrFGuard({
577
+ url: `${params.rest.options?.baseUrl ?? "https://discord.com/api"}/channels/${params.channelId}/attachments`,
578
+ init: {
579
+ method: "POST",
580
+ headers: {
581
+ Authorization: `Bot ${params.botToken}`,
582
+ "Content-Type": "application/json"
583
+ },
584
+ body: JSON.stringify({ files: [{
585
+ filename: params.filename,
586
+ file_size: params.fileSize,
587
+ id: "0"
588
+ }] })
589
+ },
590
+ policy: DISCORD_VOICE_UPLOAD_SSRF_POLICY,
591
+ auditContext: "discord.voice.upload-url"
592
+ });
593
+ try {
594
+ if (!res.ok) throw await createVoiceRequestError(res, "Upload URL request failed");
595
+ return await res.json();
596
+ } finally {
597
+ await release();
598
+ }
599
+ }
600
+ async function uploadVoiceAttachment(params) {
601
+ const { response: uploadResponse, release } = await fetchWithSsrFGuard({
602
+ url: params.uploadUrl,
603
+ init: {
604
+ method: "PUT",
605
+ headers: { "Content-Type": "audio/ogg" },
606
+ body: new Uint8Array(params.audioBuffer)
607
+ },
608
+ policy: DISCORD_VOICE_UPLOAD_SSRF_POLICY,
609
+ auditContext: "discord.voice.attachment-upload"
610
+ });
611
+ try {
612
+ if (!uploadResponse.ok) throw await createVoiceRequestError(uploadResponse, "Failed to upload voice message");
613
+ } finally {
614
+ await release();
615
+ }
616
+ }
617
+ /**
618
+ * Send a voice message to Discord
619
+ *
620
+ * This follows Discord's voice message protocol:
621
+ * 1. Request upload URL from Discord
622
+ * 2. Upload the OGG file to the provided URL
623
+ * 3. Send the message with flag 8192 and attachment metadata
624
+ */
625
+ async function sendDiscordVoiceMessage(rest, channelId, audioBuffer, metadata, replyTo, request, silent, token) {
626
+ const filename = "voice-message.ogg";
627
+ const fileSize = audioBuffer.byteLength;
628
+ const botToken = token;
629
+ if (!botToken) throw new Error("Discord bot token is required for voice message upload");
630
+ const { upload_filename } = await request(async () => {
631
+ const uploadUrlResponse = await requestVoiceUploadUrl({
632
+ rest,
633
+ channelId,
634
+ botToken,
635
+ filename,
636
+ fileSize
637
+ });
638
+ if (!uploadUrlResponse.attachments?.[0]) throw new Error("Failed to get upload URL for voice message");
639
+ const attachment = uploadUrlResponse.attachments[0];
640
+ await uploadVoiceAttachment({
641
+ uploadUrl: attachment.upload_url,
642
+ audioBuffer
643
+ });
644
+ return attachment;
645
+ }, "voice-upload");
646
+ const messagePayload = {
647
+ flags: silent ? DISCORD_VOICE_MESSAGE_FLAG | SUPPRESS_NOTIFICATIONS_FLAG : DISCORD_VOICE_MESSAGE_FLAG,
648
+ attachments: [{
649
+ id: "0",
650
+ filename,
651
+ uploaded_filename: upload_filename,
652
+ duration_secs: metadata.durationSecs,
653
+ waveform: metadata.waveform
654
+ }]
655
+ };
656
+ if (replyTo) messagePayload.message_reference = {
657
+ message_id: replyTo,
658
+ fail_if_not_exists: false
659
+ };
660
+ return await request(() => rest.post(`/channels/${channelId}/messages`, { body: messagePayload }), "voice-message");
661
+ }
662
+ //#endregion
663
+ //#region extensions/discord/src/send.voice.ts
664
+ function toDiscordSendResult(result, fallbackChannelId) {
665
+ return createDiscordSendResult({
666
+ result,
667
+ fallbackChannelId,
668
+ kind: "voice"
669
+ });
670
+ }
671
+ async function materializeVoiceMessageInput(mediaUrl) {
672
+ const media = await loadWebMediaRaw(mediaUrl, maxBytesForKind("audio"));
673
+ const extFromName = media.fileName ? path.extname(media.fileName) : "";
674
+ const extFromMime = media.contentType ? extensionForMime(media.contentType) : "";
675
+ const ext = extFromName || extFromMime || ".bin";
676
+ const workspace = await tempWorkspace({
677
+ rootDir: resolvePreferredKlawTmpDir(),
678
+ prefix: "voice-src-"
679
+ });
680
+ return {
681
+ filePath: await workspace.write(`input${ext}`, media.buffer),
682
+ cleanup: async () => await workspace.cleanup()
683
+ };
684
+ }
685
+ /**
686
+ * Send a voice message to Discord.
687
+ *
688
+ * Voice messages are a special Discord feature that displays audio with a waveform
689
+ * visualization. They require OGG/Opus format and cannot include text content.
690
+ *
691
+ * @param to - Recipient (user ID for DM or channel ID)
692
+ * @param audioPath - Path to local audio file (will be converted to OGG/Opus if needed)
693
+ * @param opts - Send options
694
+ */
695
+ async function sendVoiceMessageDiscord(to, audioPath, opts) {
696
+ const { filePath: localInputPath, cleanup: cleanupLocalInput } = await materializeVoiceMessageInput(audioPath);
697
+ let oggPath = null;
698
+ let oggCleanup = false;
699
+ let token;
700
+ let rest;
701
+ let channelId;
702
+ const cfg = requireRuntimeConfig(opts.cfg, "Discord voice send");
703
+ try {
704
+ const accountInfo = resolveDiscordAccount({
705
+ cfg,
706
+ accountId: opts.accountId
707
+ });
708
+ const client = createDiscordClient({
709
+ ...opts,
710
+ cfg
711
+ });
712
+ token = client.token;
713
+ rest = client.rest;
714
+ const request = client.request;
715
+ const recipient = await parseAndResolveRecipient(to, cfg, opts.accountId);
716
+ channelId = (await resolveChannelId(rest, recipient, request)).channelId;
717
+ const ogg = await ensureOggOpus(localInputPath);
718
+ oggPath = ogg.path;
719
+ oggCleanup = ogg.cleanup;
720
+ const metadata = await getVoiceMessageMetadata(oggPath);
721
+ const audioBuffer = await fs.readFile(oggPath);
722
+ const result = await sendDiscordVoiceMessage(rest, channelId, audioBuffer, metadata, opts.replyTo, request, opts.silent, token);
723
+ recordChannelActivity({
724
+ channel: "discord",
725
+ accountId: accountInfo.accountId,
726
+ direction: "outbound"
727
+ });
728
+ return toDiscordSendResult(result, channelId);
729
+ } catch (err) {
730
+ if (channelId && rest && token) throw await buildDiscordSendError(err, {
731
+ channelId,
732
+ cfg,
733
+ rest,
734
+ token,
735
+ hasMedia: true
736
+ });
737
+ throw err;
738
+ } finally {
739
+ await unlinkIfExists(oggCleanup ? oggPath : null);
740
+ await cleanupLocalInput();
741
+ }
742
+ }
743
+ //#endregion
744
+ //#region extensions/discord/src/send.typing.ts
745
+ async function sendTypingDiscord(channelId, opts) {
746
+ await sendChannelTyping(resolveDiscordRest(opts), channelId);
747
+ return {
748
+ ok: true,
749
+ channelId
750
+ };
751
+ }
752
+ //#endregion
753
+ //#region extensions/discord/src/send.reactions.ts
754
+ function createDiscordReactionRuntimeClient(opts) {
755
+ return createDiscordClient(opts);
756
+ }
757
+ function resolveDiscordReactionClient(opts) {
758
+ if (!opts.cfg) throw new Error("Discord reactions requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.");
759
+ const cfg = requireRuntimeConfig(opts.cfg, "Discord reactions");
760
+ return createDiscordClient({
761
+ ...opts,
762
+ cfg
763
+ });
764
+ }
765
+ function isDiscordReactionRuntimeContext(opts) {
766
+ return Boolean(opts.rest && opts.cfg && opts.accountId);
767
+ }
768
+ async function reactMessageDiscord(channelId, messageId, emoji, opts) {
769
+ const { rest, request } = isDiscordReactionRuntimeContext(opts) ? createDiscordReactionRuntimeClient(opts) : resolveDiscordReactionClient(opts);
770
+ const encoded = normalizeReactionEmoji(emoji);
771
+ await request(() => createOwnMessageReaction(rest, channelId, messageId, encoded), "react");
772
+ return { ok: true };
773
+ }
774
+ async function removeReactionDiscord(channelId, messageId, emoji, opts) {
775
+ const { rest } = isDiscordReactionRuntimeContext(opts) ? createDiscordReactionRuntimeClient(opts) : resolveDiscordReactionClient(opts);
776
+ await deleteOwnMessageReaction(rest, channelId, messageId, normalizeReactionEmoji(emoji));
777
+ return { ok: true };
778
+ }
779
+ async function removeOwnReactionsDiscord(channelId, messageId, opts) {
780
+ const { rest } = isDiscordReactionRuntimeContext(opts) ? createDiscordReactionRuntimeClient(opts) : resolveDiscordReactionClient(opts);
781
+ const message = await getChannelMessage(rest, channelId, messageId);
782
+ const identifiers = /* @__PURE__ */ new Set();
783
+ for (const reaction of message.reactions ?? []) {
784
+ const identifier = buildReactionIdentifier(reaction.emoji);
785
+ if (identifier) identifiers.add(identifier);
786
+ }
787
+ if (identifiers.size === 0) return {
788
+ ok: true,
789
+ removed: []
790
+ };
791
+ const removed = [];
792
+ await Promise.allSettled(Array.from(identifiers, (identifier) => {
793
+ removed.push(identifier);
794
+ return deleteOwnMessageReaction(rest, channelId, messageId, normalizeReactionEmoji(identifier));
795
+ }));
796
+ return {
797
+ ok: true,
798
+ removed
799
+ };
800
+ }
801
+ async function fetchReactionsDiscord(channelId, messageId, opts) {
802
+ const { rest } = isDiscordReactionRuntimeContext(opts) ? createDiscordReactionRuntimeClient(opts) : resolveDiscordReactionClient(opts);
803
+ const reactions = (await getChannelMessage(rest, channelId, messageId)).reactions ?? [];
804
+ if (reactions.length === 0) return [];
805
+ const limit = typeof opts.limit === "number" && Number.isFinite(opts.limit) ? Math.min(Math.max(Math.floor(opts.limit), 1), 100) : 100;
806
+ const summaries = [];
807
+ for (const reaction of reactions) {
808
+ const identifier = buildReactionIdentifier(reaction.emoji);
809
+ if (!identifier) continue;
810
+ const users = await listMessageReactionUsers(rest, channelId, messageId, encodeURIComponent(identifier), { limit });
811
+ summaries.push({
812
+ emoji: {
813
+ id: reaction.emoji.id ?? null,
814
+ name: reaction.emoji.name ?? null,
815
+ raw: formatReactionEmoji(reaction.emoji)
816
+ },
817
+ count: reaction.count,
818
+ users: users.map((user) => ({
819
+ id: user.id,
820
+ username: user.username,
821
+ tag: user.username && user.discriminator ? `${user.username}#${user.discriminator}` : user.username
822
+ }))
823
+ });
824
+ }
825
+ return summaries;
826
+ }
827
+ //#endregion
828
+ //#region extensions/discord/src/send.ts
829
+ var send_exports = /* @__PURE__ */ __exportAll({
830
+ DiscordSendError: () => DiscordSendError,
831
+ DiscordThreadInitialMessageError: () => DiscordThreadInitialMessageError,
832
+ addRoleDiscord: () => addRoleDiscord,
833
+ banMemberDiscord: () => banMemberDiscord,
834
+ canViewDiscordGuildChannel: () => canViewDiscordGuildChannel,
835
+ createChannelDiscord: () => createChannelDiscord,
836
+ createScheduledEventDiscord: () => createScheduledEventDiscord,
837
+ createThreadDiscord: () => createThreadDiscord,
838
+ deleteChannelDiscord: () => deleteChannelDiscord,
839
+ deleteMessageDiscord: () => deleteMessageDiscord,
840
+ editChannelDiscord: () => editChannelDiscord,
841
+ editMessageDiscord: () => editMessageDiscord,
842
+ fetchChannelInfoDiscord: () => fetchChannelInfoDiscord,
843
+ fetchChannelPermissionsDiscord: () => fetchChannelPermissionsDiscord,
844
+ fetchMemberGuildPermissionsDiscord: () => fetchMemberGuildPermissionsDiscord,
845
+ fetchMemberInfoDiscord: () => fetchMemberInfoDiscord,
846
+ fetchMessageDiscord: () => fetchMessageDiscord,
847
+ fetchReactionsDiscord: () => fetchReactionsDiscord,
848
+ fetchRoleInfoDiscord: () => fetchRoleInfoDiscord,
849
+ fetchVoiceStatusDiscord: () => fetchVoiceStatusDiscord,
850
+ hasAllGuildPermissionsDiscord: () => hasAllGuildPermissionsDiscord,
851
+ hasAnyGuildPermissionDiscord: () => hasAnyGuildPermissionDiscord,
852
+ kickMemberDiscord: () => kickMemberDiscord,
853
+ listGuildChannelsDiscord: () => listGuildChannelsDiscord,
854
+ listGuildEmojisDiscord: () => listGuildEmojisDiscord,
855
+ listPinsDiscord: () => listPinsDiscord,
856
+ listScheduledEventsDiscord: () => listScheduledEventsDiscord,
857
+ listThreadsDiscord: () => listThreadsDiscord,
858
+ moveChannelDiscord: () => moveChannelDiscord,
859
+ pinMessageDiscord: () => pinMessageDiscord,
860
+ reactMessageDiscord: () => reactMessageDiscord,
861
+ readMessagesDiscord: () => readMessagesDiscord,
862
+ removeChannelPermissionDiscord: () => removeChannelPermissionDiscord,
863
+ removeOwnReactionsDiscord: () => removeOwnReactionsDiscord,
864
+ removeReactionDiscord: () => removeReactionDiscord,
865
+ removeRoleDiscord: () => removeRoleDiscord,
866
+ resolveEventCoverImage: () => resolveEventCoverImage,
867
+ searchMessagesDiscord: () => searchMessagesDiscord,
868
+ sendMessageDiscord: () => sendMessageDiscord,
869
+ sendPollDiscord: () => sendPollDiscord,
870
+ sendStickerDiscord: () => sendStickerDiscord,
871
+ sendTypingDiscord: () => sendTypingDiscord,
872
+ sendVoiceMessageDiscord: () => sendVoiceMessageDiscord,
873
+ sendWebhookMessageDiscord: () => sendWebhookMessageDiscord,
874
+ setChannelPermissionDiscord: () => setChannelPermissionDiscord,
875
+ timeoutMemberDiscord: () => timeoutMemberDiscord,
876
+ unpinMessageDiscord: () => unpinMessageDiscord,
877
+ uploadEmojiDiscord: () => uploadEmojiDiscord,
878
+ uploadStickerDiscord: () => uploadStickerDiscord
879
+ });
880
+ //#endregion
881
+ export { removeRoleDiscord as A, removeChannelPermissionDiscord as B, fetchChannelInfoDiscord as C, kickMemberDiscord as D, fetchVoiceStatusDiscord as E, uploadStickerDiscord as F, createChannelDiscord as I, deleteChannelDiscord as L, timeoutMemberDiscord as M, listGuildEmojisDiscord as N, listGuildChannelsDiscord as O, uploadEmojiDiscord as P, editChannelDiscord as R, createScheduledEventDiscord as S, fetchRoleInfoDiscord as T, setChannelPermissionDiscord as V, readMessagesDiscord as _, removeReactionDiscord as a, addRoleDiscord as b, sendWebhookMessageDiscord as c, deleteMessageDiscord as d, editMessageDiscord as f, pinMessageDiscord as g, listThreadsDiscord as h, removeOwnReactionsDiscord as i, resolveEventCoverImage as j, listScheduledEventsDiscord as k, DiscordThreadInitialMessageError as l, listPinsDiscord as m, fetchReactionsDiscord as n, sendTypingDiscord as o, fetchMessageDiscord as p, reactMessageDiscord as r, sendVoiceMessageDiscord as s, send_exports as t, createThreadDiscord as u, searchMessagesDiscord as v, fetchMemberInfoDiscord as w, banMemberDiscord as x, unpinMessageDiscord as y, moveChannelDiscord as z };