@gakr-gakr/discord 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/account-inspect-api.ts +6 -0
  2. package/action-runtime-api.ts +1 -0
  3. package/api.ts +130 -0
  4. package/autobot.plugin.json +15 -0
  5. package/channel-config-api.ts +1 -0
  6. package/channel-plugin-api.ts +3 -0
  7. package/config-api.ts +4 -0
  8. package/configured-state.ts +6 -0
  9. package/contract-api.ts +21 -0
  10. package/directory-contract-api.ts +4 -0
  11. package/doctor-contract-api.ts +1 -0
  12. package/index.ts +24 -0
  13. package/package.json +79 -0
  14. package/runtime-api.actions.ts +15 -0
  15. package/runtime-api.lookup.ts +22 -0
  16. package/runtime-api.monitor.ts +50 -0
  17. package/runtime-api.send.ts +79 -0
  18. package/runtime-api.threads.ts +31 -0
  19. package/runtime-api.ts +181 -0
  20. package/runtime-setter-api.ts +3 -0
  21. package/secret-contract-api.ts +4 -0
  22. package/security-audit-contract-api.ts +1 -0
  23. package/security-contract-api.ts +4 -0
  24. package/session-key-api.ts +1 -0
  25. package/setup-entry.ts +9 -0
  26. package/setup-plugin-api.ts +3 -0
  27. package/src/account-inspect.ts +131 -0
  28. package/src/accounts.ts +205 -0
  29. package/src/actions/handle-action.guild-admin.ts +421 -0
  30. package/src/actions/handle-action.ts +402 -0
  31. package/src/actions/runtime.guild.ts +446 -0
  32. package/src/actions/runtime.messaging.messages.ts +226 -0
  33. package/src/actions/runtime.messaging.reactions.ts +67 -0
  34. package/src/actions/runtime.messaging.runtime.ts +73 -0
  35. package/src/actions/runtime.messaging.send.ts +336 -0
  36. package/src/actions/runtime.messaging.shared.ts +97 -0
  37. package/src/actions/runtime.messaging.ts +37 -0
  38. package/src/actions/runtime.moderation-shared.ts +48 -0
  39. package/src/actions/runtime.moderation.ts +116 -0
  40. package/src/actions/runtime.presence.ts +117 -0
  41. package/src/actions/runtime.shared.ts +86 -0
  42. package/src/actions/runtime.ts +87 -0
  43. package/src/api.ts +219 -0
  44. package/src/approval-handler.runtime.ts +636 -0
  45. package/src/approval-native.ts +219 -0
  46. package/src/approval-runtime.ts +14 -0
  47. package/src/approval-shared.ts +56 -0
  48. package/src/audit-core.ts +178 -0
  49. package/src/audit.ts +32 -0
  50. package/src/channel-actions.runtime.ts +1 -0
  51. package/src/channel-actions.ts +254 -0
  52. package/src/channel-api.ts +29 -0
  53. package/src/channel.conversation.ts +159 -0
  54. package/src/channel.loaders.ts +50 -0
  55. package/src/channel.runtime.ts +1 -0
  56. package/src/channel.setup.ts +12 -0
  57. package/src/channel.ts +728 -0
  58. package/src/chunk.ts +321 -0
  59. package/src/client.ts +143 -0
  60. package/src/component-custom-id.ts +72 -0
  61. package/src/components-registry.ts +356 -0
  62. package/src/components.builders.ts +410 -0
  63. package/src/components.modal.ts +124 -0
  64. package/src/components.parse.ts +407 -0
  65. package/src/components.ts +54 -0
  66. package/src/components.types.ts +187 -0
  67. package/src/config-schema.ts +6 -0
  68. package/src/config-ui-hints.ts +354 -0
  69. package/src/conversation-identity.ts +58 -0
  70. package/src/delivery-retry.ts +56 -0
  71. package/src/directory-cache.ts +116 -0
  72. package/src/directory-config.ts +58 -0
  73. package/src/directory-live.ts +135 -0
  74. package/src/doctor-contract.ts +477 -0
  75. package/src/doctor-shared.ts +5 -0
  76. package/src/doctor.ts +340 -0
  77. package/src/draft-chunking.ts +43 -0
  78. package/src/draft-stream.ts +162 -0
  79. package/src/error-body.ts +38 -0
  80. package/src/exec-approvals.ts +110 -0
  81. package/src/gateway-logging.ts +67 -0
  82. package/src/group-policy.ts +113 -0
  83. package/src/guilds.ts +29 -0
  84. package/src/inbound-event-delivery.ts +135 -0
  85. package/src/interactive-dispatch.ts +104 -0
  86. package/src/internal/api.commands.ts +51 -0
  87. package/src/internal/api.guild.ts +164 -0
  88. package/src/internal/api.interactions.ts +53 -0
  89. package/src/internal/api.messages.ts +113 -0
  90. package/src/internal/api.reactions.ts +38 -0
  91. package/src/internal/api.ts +61 -0
  92. package/src/internal/api.users.ts +19 -0
  93. package/src/internal/api.webhooks.ts +13 -0
  94. package/src/internal/client.ts +310 -0
  95. package/src/internal/command-deploy.ts +352 -0
  96. package/src/internal/commands.ts +188 -0
  97. package/src/internal/components.base.ts +65 -0
  98. package/src/internal/components.message.ts +279 -0
  99. package/src/internal/components.modal.ts +95 -0
  100. package/src/internal/components.ts +31 -0
  101. package/src/internal/discord.ts +11 -0
  102. package/src/internal/embeds.ts +35 -0
  103. package/src/internal/entity-cache.ts +98 -0
  104. package/src/internal/event-queue.ts +185 -0
  105. package/src/internal/gateway-close-codes.ts +25 -0
  106. package/src/internal/gateway-dispatch.ts +96 -0
  107. package/src/internal/gateway-identify-limiter.ts +26 -0
  108. package/src/internal/gateway-lifecycle.ts +75 -0
  109. package/src/internal/gateway-rate-limit.ts +104 -0
  110. package/src/internal/gateway.ts +479 -0
  111. package/src/internal/interaction-dispatch.ts +162 -0
  112. package/src/internal/interaction-options.ts +98 -0
  113. package/src/internal/interaction-response.ts +53 -0
  114. package/src/internal/interactions.ts +378 -0
  115. package/src/internal/listeners.ts +91 -0
  116. package/src/internal/modal-fields.ts +95 -0
  117. package/src/internal/payload.ts +69 -0
  118. package/src/internal/rest-body.ts +115 -0
  119. package/src/internal/rest-errors.ts +88 -0
  120. package/src/internal/rest-routes.ts +50 -0
  121. package/src/internal/rest-scheduler.ts +557 -0
  122. package/src/internal/rest.ts +322 -0
  123. package/src/internal/schemas.ts +36 -0
  124. package/src/internal/structures.ts +280 -0
  125. package/src/internal/test-builders.test-support.ts +167 -0
  126. package/src/internal/voice.ts +49 -0
  127. package/src/media-detection.ts +28 -0
  128. package/src/mentions.ts +147 -0
  129. package/src/monitor/ack-reactions.ts +70 -0
  130. package/src/monitor/agent-components-auth.ts +7 -0
  131. package/src/monitor/agent-components-context.ts +154 -0
  132. package/src/monitor/agent-components-data.ts +224 -0
  133. package/src/monitor/agent-components-dm-auth.ts +177 -0
  134. package/src/monitor/agent-components-guild-auth.ts +322 -0
  135. package/src/monitor/agent-components-helpers.runtime.ts +3 -0
  136. package/src/monitor/agent-components-helpers.ts +34 -0
  137. package/src/monitor/agent-components-reply.ts +10 -0
  138. package/src/monitor/agent-components.deps.runtime.ts +2 -0
  139. package/src/monitor/agent-components.dispatch.ts +359 -0
  140. package/src/monitor/agent-components.handlers.ts +303 -0
  141. package/src/monitor/agent-components.modal.ts +160 -0
  142. package/src/monitor/agent-components.plugin-interactive.ts +187 -0
  143. package/src/monitor/agent-components.runtime.ts +14 -0
  144. package/src/monitor/agent-components.system-controls.ts +215 -0
  145. package/src/monitor/agent-components.ts +70 -0
  146. package/src/monitor/agent-components.types.ts +58 -0
  147. package/src/monitor/agent-components.wildcard-controls.ts +171 -0
  148. package/src/monitor/allow-list.ts +631 -0
  149. package/src/monitor/auto-presence.ts +356 -0
  150. package/src/monitor/channel-access.ts +102 -0
  151. package/src/monitor/commands.ts +9 -0
  152. package/src/monitor/dm-command-auth.ts +259 -0
  153. package/src/monitor/dm-command-decision.ts +49 -0
  154. package/src/monitor/exec-approvals.ts +161 -0
  155. package/src/monitor/format.ts +45 -0
  156. package/src/monitor/gateway-handle.ts +34 -0
  157. package/src/monitor/gateway-metadata.ts +298 -0
  158. package/src/monitor/gateway-plugin.ts +302 -0
  159. package/src/monitor/gateway-registry.ts +37 -0
  160. package/src/monitor/gateway-supervisor.ts +206 -0
  161. package/src/monitor/inbound-context.ts +95 -0
  162. package/src/monitor/inbound-dedupe.ts +79 -0
  163. package/src/monitor/inbound-job.ts +118 -0
  164. package/src/monitor/listeners.queue.ts +91 -0
  165. package/src/monitor/listeners.reactions.ts +594 -0
  166. package/src/monitor/listeners.ts +150 -0
  167. package/src/monitor/message-channel-info.ts +96 -0
  168. package/src/monitor/message-forwarded.ts +114 -0
  169. package/src/monitor/message-handler.batch-gate.ts +19 -0
  170. package/src/monitor/message-handler.context.ts +492 -0
  171. package/src/monitor/message-handler.dm-preflight.ts +119 -0
  172. package/src/monitor/message-handler.draft-preview.ts +436 -0
  173. package/src/monitor/message-handler.hydration.ts +198 -0
  174. package/src/monitor/message-handler.module-test-helpers.ts +31 -0
  175. package/src/monitor/message-handler.preflight-channel-access.ts +86 -0
  176. package/src/monitor/message-handler.preflight-channel-context.ts +58 -0
  177. package/src/monitor/message-handler.preflight-context.ts +54 -0
  178. package/src/monitor/message-handler.preflight-helpers.ts +164 -0
  179. package/src/monitor/message-handler.preflight-history.ts +23 -0
  180. package/src/monitor/message-handler.preflight-logging.ts +36 -0
  181. package/src/monitor/message-handler.preflight-pluralkit.ts +28 -0
  182. package/src/monitor/message-handler.preflight-runtime.ts +28 -0
  183. package/src/monitor/message-handler.preflight-thread.ts +49 -0
  184. package/src/monitor/message-handler.preflight.ts +822 -0
  185. package/src/monitor/message-handler.preflight.types.ts +115 -0
  186. package/src/monitor/message-handler.process.ts +1033 -0
  187. package/src/monitor/message-handler.routing-preflight.ts +112 -0
  188. package/src/monitor/message-handler.ts +309 -0
  189. package/src/monitor/message-media.ts +536 -0
  190. package/src/monitor/message-run-queue.ts +101 -0
  191. package/src/monitor/message-text.ts +171 -0
  192. package/src/monitor/message-utils.ts +34 -0
  193. package/src/monitor/model-picker-preferences.ts +184 -0
  194. package/src/monitor/model-picker.state.ts +364 -0
  195. package/src/monitor/model-picker.test-utils.ts +26 -0
  196. package/src/monitor/model-picker.ts +38 -0
  197. package/src/monitor/model-picker.view.ts +722 -0
  198. package/src/monitor/native-command-agent-reply.ts +125 -0
  199. package/src/monitor/native-command-arg-ui.ts +233 -0
  200. package/src/monitor/native-command-auth.ts +309 -0
  201. package/src/monitor/native-command-bypass.ts +13 -0
  202. package/src/monitor/native-command-context.ts +109 -0
  203. package/src/monitor/native-command-dispatch.ts +35 -0
  204. package/src/monitor/native-command-model-picker-apply.ts +209 -0
  205. package/src/monitor/native-command-model-picker-interaction.ts +516 -0
  206. package/src/monitor/native-command-model-picker-ui.ts +357 -0
  207. package/src/monitor/native-command-reply.ts +185 -0
  208. package/src/monitor/native-command-route.ts +91 -0
  209. package/src/monitor/native-command-status.ts +76 -0
  210. package/src/monitor/native-command-ui.ts +26 -0
  211. package/src/monitor/native-command-ui.types.ts +20 -0
  212. package/src/monitor/native-command.args.ts +45 -0
  213. package/src/monitor/native-command.options.ts +153 -0
  214. package/src/monitor/native-command.runtime.ts +51 -0
  215. package/src/monitor/native-command.ts +747 -0
  216. package/src/monitor/native-command.types.ts +9 -0
  217. package/src/monitor/native-interaction-channel-context.ts +50 -0
  218. package/src/monitor/preflight-audio.runtime.ts +9 -0
  219. package/src/monitor/preflight-audio.ts +130 -0
  220. package/src/monitor/presence-cache.ts +61 -0
  221. package/src/monitor/presence.ts +50 -0
  222. package/src/monitor/provider-session.runtime.ts +12 -0
  223. package/src/monitor/provider.acp.ts +89 -0
  224. package/src/monitor/provider.allowlist.ts +398 -0
  225. package/src/monitor/provider.cleanup.ts +41 -0
  226. package/src/monitor/provider.commands.ts +129 -0
  227. package/src/monitor/provider.config-log.ts +45 -0
  228. package/src/monitor/provider.deploy-errors.ts +362 -0
  229. package/src/monitor/provider.deploy.ts +221 -0
  230. package/src/monitor/provider.interactions.ts +160 -0
  231. package/src/monitor/provider.lifecycle.ts +562 -0
  232. package/src/monitor/provider.runtime.ts +1 -0
  233. package/src/monitor/provider.startup-log.ts +32 -0
  234. package/src/monitor/provider.startup.ts +323 -0
  235. package/src/monitor/provider.ts +688 -0
  236. package/src/monitor/reply-context.ts +64 -0
  237. package/src/monitor/reply-delivery.ts +216 -0
  238. package/src/monitor/reply-safety.ts +96 -0
  239. package/src/monitor/rest-fetch.ts +97 -0
  240. package/src/monitor/route-resolution.ts +140 -0
  241. package/src/monitor/sender-identity.ts +81 -0
  242. package/src/monitor/startup-status.ts +10 -0
  243. package/src/monitor/status.ts +22 -0
  244. package/src/monitor/system-events.ts +55 -0
  245. package/src/monitor/thread-bindings.config.ts +35 -0
  246. package/src/monitor/thread-bindings.discord-api.ts +310 -0
  247. package/src/monitor/thread-bindings.lifecycle.ts +354 -0
  248. package/src/monitor/thread-bindings.manager.ts +554 -0
  249. package/src/monitor/thread-bindings.messages.ts +6 -0
  250. package/src/monitor/thread-bindings.persona.ts +25 -0
  251. package/src/monitor/thread-bindings.session-adapter.ts +229 -0
  252. package/src/monitor/thread-bindings.session-shared.ts +59 -0
  253. package/src/monitor/thread-bindings.session-updates.ts +35 -0
  254. package/src/monitor/thread-bindings.state.ts +540 -0
  255. package/src/monitor/thread-bindings.ts +48 -0
  256. package/src/monitor/thread-bindings.types.ts +83 -0
  257. package/src/monitor/thread-channel-context.ts +112 -0
  258. package/src/monitor/thread-session-close.ts +63 -0
  259. package/src/monitor/thread-title.ts +181 -0
  260. package/src/monitor/threading.auto-thread.ts +287 -0
  261. package/src/monitor/threading.cache.ts +45 -0
  262. package/src/monitor/threading.starter.ts +288 -0
  263. package/src/monitor/threading.ts +20 -0
  264. package/src/monitor/threading.types.ts +102 -0
  265. package/src/monitor/timeouts.ts +84 -0
  266. package/src/monitor/typing.ts +17 -0
  267. package/src/monitor.gateway.ts +75 -0
  268. package/src/monitor.ts +28 -0
  269. package/src/network-config.ts +79 -0
  270. package/src/normalize.ts +86 -0
  271. package/src/outbound-adapter.ts +327 -0
  272. package/src/outbound-approval.ts +29 -0
  273. package/src/outbound-components.ts +86 -0
  274. package/src/outbound-payload.ts +208 -0
  275. package/src/outbound-send-context.ts +92 -0
  276. package/src/outbound-session-route.ts +72 -0
  277. package/src/pluralkit.ts +58 -0
  278. package/src/preview-streaming.ts +18 -0
  279. package/src/probe.runtime.ts +1 -0
  280. package/src/probe.ts +237 -0
  281. package/src/proxy-fetch.ts +92 -0
  282. package/src/proxy-request-client.ts +21 -0
  283. package/src/recipient-resolution.ts +39 -0
  284. package/src/resolve-allowlist-common.ts +39 -0
  285. package/src/resolve-channels.ts +369 -0
  286. package/src/resolve-users.ts +184 -0
  287. package/src/retry.ts +98 -0
  288. package/src/runtime-api.ts +64 -0
  289. package/src/runtime-config.ts +16 -0
  290. package/src/runtime.ts +23 -0
  291. package/src/secret-config-contract.ts +140 -0
  292. package/src/security-audit.runtime.ts +1 -0
  293. package/src/security-audit.ts +208 -0
  294. package/src/security-contract.ts +47 -0
  295. package/src/security-doctor.ts +20 -0
  296. package/src/security.ts +60 -0
  297. package/src/send-target-parsing.ts +14 -0
  298. package/src/send.channels.ts +139 -0
  299. package/src/send.components.ts +391 -0
  300. package/src/send.emojis-stickers.ts +57 -0
  301. package/src/send.guild.ts +170 -0
  302. package/src/send.message-request.ts +112 -0
  303. package/src/send.messages.ts +229 -0
  304. package/src/send.outbound.ts +459 -0
  305. package/src/send.permissions.ts +283 -0
  306. package/src/send.reactions.ts +155 -0
  307. package/src/send.receipt.ts +69 -0
  308. package/src/send.shared.ts +469 -0
  309. package/src/send.ts +82 -0
  310. package/src/send.types.ts +191 -0
  311. package/src/send.typing.ts +9 -0
  312. package/src/send.voice.ts +140 -0
  313. package/src/send.webhook.ts +137 -0
  314. package/src/session-contract.ts +3 -0
  315. package/src/session-key-normalization.ts +47 -0
  316. package/src/setup-account-state.ts +144 -0
  317. package/src/setup-adapter.ts +14 -0
  318. package/src/setup-core.ts +215 -0
  319. package/src/setup-runtime-helpers.ts +10 -0
  320. package/src/setup-surface.ts +132 -0
  321. package/src/shared-interactive.ts +167 -0
  322. package/src/shared.ts +197 -0
  323. package/src/status-issues.ts +201 -0
  324. package/src/subagent-hooks.ts +232 -0
  325. package/src/target-parsing.ts +70 -0
  326. package/src/target-resolver.ts +129 -0
  327. package/src/targets.ts +12 -0
  328. package/src/token.ts +107 -0
  329. package/src/ui-colors.ts +27 -0
  330. package/src/ui.ts +20 -0
  331. package/src/voice/access.ts +126 -0
  332. package/src/voice/audio.ts +249 -0
  333. package/src/voice/capture-state.ts +120 -0
  334. package/src/voice/command.ts +284 -0
  335. package/src/voice/config.ts +8 -0
  336. package/src/voice/ingress.ts +164 -0
  337. package/src/voice/manager.runtime.ts +14 -0
  338. package/src/voice/manager.ts +1155 -0
  339. package/src/voice/prompt.ts +22 -0
  340. package/src/voice/realtime.ts +1370 -0
  341. package/src/voice/receive-recovery.ts +159 -0
  342. package/src/voice/sanitize.ts +29 -0
  343. package/src/voice/sdk-runtime.ts +14 -0
  344. package/src/voice/segment.ts +160 -0
  345. package/src/voice/session.ts +81 -0
  346. package/src/voice/speaker-context.ts +127 -0
  347. package/src/voice/tts.ts +151 -0
  348. package/src/voice-message.ts +474 -0
  349. package/subagent-hooks-api.ts +27 -0
  350. package/test-api.ts +4 -0
  351. package/thread-binding-api.ts +1 -0
  352. package/timeouts.ts +6 -0
  353. package/tsconfig.json +16 -0
@@ -0,0 +1,469 @@
1
+ import { PollLayoutType } from "discord-api-types/payloads/v10";
2
+ import type { RESTAPIPoll } from "discord-api-types/rest/v10";
3
+ import type { APIChannel } from "discord-api-types/v10";
4
+ import type { AutoBotConfig } from "autobot/plugin-sdk/config-contracts";
5
+ import { buildOutboundMediaLoadOptions } from "autobot/plugin-sdk/media-runtime";
6
+ import { extensionForMime } from "autobot/plugin-sdk/media-runtime";
7
+ import {
8
+ normalizePollDurationHours,
9
+ normalizePollInput,
10
+ type OutboundMediaAccess,
11
+ type PollInput,
12
+ } from "autobot/plugin-sdk/media-runtime";
13
+ import { requireRuntimeConfig } from "autobot/plugin-sdk/plugin-config-runtime";
14
+ import type { ChunkMode } from "autobot/plugin-sdk/reply-chunking";
15
+ import { resolveTextChunksWithFallback } from "autobot/plugin-sdk/reply-payload";
16
+ import type { RetryRunner } from "autobot/plugin-sdk/retry-runtime";
17
+ import { loadWebMedia } from "autobot/plugin-sdk/web-media";
18
+ import { chunkDiscordTextWithMode } from "./chunk.js";
19
+ import { createDiscordClient, resolveDiscordRest, type DiscordClientOpts } from "./client.js";
20
+ import {
21
+ createChannelMessage,
22
+ createUserDmChannel,
23
+ getChannel,
24
+ RequestClient,
25
+ } from "./internal/discord.js";
26
+ import { parseAndResolveRecipient } from "./recipient-resolution.js";
27
+ import { fetchChannelPermissionsDiscord, isThreadChannelType } from "./send.permissions.js";
28
+ import { DiscordSendError } from "./send.types.js";
29
+
30
+ const DISCORD_TEXT_LIMIT = 2000;
31
+ const DISCORD_MAX_STICKERS = 3;
32
+ const DISCORD_POLL_MAX_ANSWERS = 10;
33
+ const DISCORD_POLL_MAX_DURATION_HOURS = 32 * 24;
34
+ const DISCORD_MISSING_PERMISSIONS = 50013;
35
+ const DISCORD_CANNOT_DM = 50007;
36
+
37
+ type DiscordRequest = RetryRunner;
38
+
39
+ export {
40
+ buildDiscordMessagePayload,
41
+ buildDiscordMessageRequest,
42
+ resolveDiscordMessageFlags,
43
+ resolveDiscordSendComponents,
44
+ resolveDiscordSendEmbeds,
45
+ stripUndefinedFields,
46
+ SUPPRESS_EMBEDS_FLAG,
47
+ SUPPRESS_NOTIFICATIONS_FLAG,
48
+ type DiscordSendComponentFactory,
49
+ type DiscordSendComponents,
50
+ type DiscordSendEmbeds,
51
+ } from "./send.message-request.js";
52
+ import {
53
+ buildDiscordMessageRequest,
54
+ resolveDiscordMessageFlags,
55
+ resolveDiscordSendComponents,
56
+ resolveDiscordSendEmbeds,
57
+ type DiscordSendComponents,
58
+ type DiscordSendEmbeds,
59
+ } from "./send.message-request.js";
60
+ type DiscordRecipient =
61
+ | {
62
+ kind: "user";
63
+ id: string;
64
+ }
65
+ | {
66
+ kind: "channel";
67
+ id: string;
68
+ };
69
+
70
+ function normalizeReactionEmoji(raw: string) {
71
+ const trimmed = raw.trim();
72
+ if (!trimmed) {
73
+ throw new Error("emoji required");
74
+ }
75
+ const customMatch = trimmed.match(/^<a?:([^:>]+):(\d+)>$/);
76
+ const identifier = customMatch
77
+ ? `${customMatch[1]}:${customMatch[2]}`
78
+ : trimmed.replace(/[\uFE0E\uFE0F]/g, "");
79
+ return encodeURIComponent(identifier);
80
+ }
81
+
82
+ function normalizeStickerIds(raw: string[]) {
83
+ const ids = raw.map((entry) => entry.trim()).filter(Boolean);
84
+ if (ids.length === 0) {
85
+ throw new Error("At least one sticker id is required");
86
+ }
87
+ if (ids.length > DISCORD_MAX_STICKERS) {
88
+ throw new Error("Discord supports up to 3 stickers per message");
89
+ }
90
+ return ids;
91
+ }
92
+
93
+ function normalizeEmojiName(raw: string, label: string) {
94
+ const name = raw.trim();
95
+ if (!name) {
96
+ throw new Error(`${label} is required`);
97
+ }
98
+ return name;
99
+ }
100
+
101
+ function normalizeDiscordPollInput(input: PollInput): RESTAPIPoll {
102
+ const poll = normalizePollInput(input, {
103
+ maxOptions: DISCORD_POLL_MAX_ANSWERS,
104
+ });
105
+ const duration = normalizePollDurationHours(poll.durationHours, {
106
+ defaultHours: 24,
107
+ maxHours: DISCORD_POLL_MAX_DURATION_HOURS,
108
+ });
109
+ return {
110
+ question: { text: poll.question },
111
+ answers: poll.options.map((answer) => ({ poll_media: { text: answer } })),
112
+ duration,
113
+ allow_multiselect: poll.maxSelections > 1,
114
+ layout_type: PollLayoutType.Default,
115
+ };
116
+ }
117
+
118
+ function getDiscordErrorCode(err: unknown) {
119
+ if (!err || typeof err !== "object") {
120
+ return undefined;
121
+ }
122
+ const candidate =
123
+ "code" in err && err.code !== undefined
124
+ ? err.code
125
+ : "rawError" in err && err.rawError && typeof err.rawError === "object"
126
+ ? (err.rawError as { code?: unknown }).code
127
+ : undefined;
128
+ if (typeof candidate === "number") {
129
+ return candidate;
130
+ }
131
+ if (typeof candidate === "string" && /^\d+$/.test(candidate)) {
132
+ return Number(candidate);
133
+ }
134
+ return undefined;
135
+ }
136
+
137
+ function getDiscordErrorStatus(err: unknown) {
138
+ if (!err || typeof err !== "object") {
139
+ return undefined;
140
+ }
141
+ const candidate =
142
+ "status" in err && err.status !== undefined
143
+ ? err.status
144
+ : "statusCode" in err && err.statusCode !== undefined
145
+ ? err.statusCode
146
+ : undefined;
147
+ if (typeof candidate === "number" && Number.isFinite(candidate)) {
148
+ return candidate;
149
+ }
150
+ if (typeof candidate === "string" && /^\d+$/.test(candidate)) {
151
+ return Number(candidate);
152
+ }
153
+ return undefined;
154
+ }
155
+
156
+ async function buildDiscordSendError(
157
+ err: unknown,
158
+ ctx: {
159
+ channelId: string;
160
+ cfg: AutoBotConfig;
161
+ rest: RequestClient;
162
+ token: string;
163
+ hasMedia: boolean;
164
+ },
165
+ ) {
166
+ if (err instanceof DiscordSendError) {
167
+ return err;
168
+ }
169
+ const code = getDiscordErrorCode(err);
170
+ if (code === DISCORD_CANNOT_DM) {
171
+ return new DiscordSendError(
172
+ `discord dm failed: user blocks dms or privacy settings disallow it (code=${code})`,
173
+ { kind: "dm-blocked", discordCode: code, status: getDiscordErrorStatus(err) },
174
+ );
175
+ }
176
+ if (code !== DISCORD_MISSING_PERMISSIONS) {
177
+ return err;
178
+ }
179
+
180
+ let missing: string[] = [];
181
+ let probedChannelType: number | undefined;
182
+ try {
183
+ const permissions = await fetchChannelPermissionsDiscord(ctx.channelId, {
184
+ rest: ctx.rest,
185
+ token: ctx.token,
186
+ cfg: ctx.cfg,
187
+ });
188
+ probedChannelType = permissions.channelType;
189
+ const current = new Set(permissions.permissions);
190
+ const required = ["ViewChannel", "SendMessages"];
191
+ if (isThreadChannelType(probedChannelType)) {
192
+ required.push("SendMessagesInThreads");
193
+ }
194
+ if (ctx.hasMedia) {
195
+ required.push("AttachFiles");
196
+ }
197
+ missing = required.filter((permission) => !current.has(permission));
198
+ } catch {
199
+ /* ignore permission probe errors */
200
+ }
201
+
202
+ const status = getDiscordErrorStatus(err);
203
+ const apiDetails = [`code=${code}`, status != null ? `status=${status}` : undefined]
204
+ .filter(Boolean)
205
+ .join(" ");
206
+ const probedPermissions = ["ViewChannel", "SendMessages"];
207
+ if (isThreadChannelType(probedChannelType)) {
208
+ probedPermissions.push("SendMessagesInThreads");
209
+ }
210
+ if (ctx.hasMedia) {
211
+ probedPermissions.push("AttachFiles");
212
+ }
213
+ const probeSummary = probedPermissions.join("/");
214
+ const missingLabel = missing.length
215
+ ? `discord missing permissions in channel ${ctx.channelId}: ${missing.join(", ")}`
216
+ : `discord missing permissions in channel ${ctx.channelId}; permission probe did not identify missing ${probeSummary}`;
217
+ return new DiscordSendError(
218
+ `${missingLabel} (${apiDetails}). bot might be blocked by channel/thread overrides, archived thread state, reply target visibility, or app-role position`,
219
+ {
220
+ kind: "missing-permissions",
221
+ channelId: ctx.channelId,
222
+ missingPermissions: missing,
223
+ discordCode: code,
224
+ status,
225
+ },
226
+ );
227
+ }
228
+
229
+ async function resolveChannelId(
230
+ rest: RequestClient,
231
+ recipient: DiscordRecipient,
232
+ request: DiscordRequest,
233
+ ): Promise<{ channelId: string; dm?: boolean }> {
234
+ if (recipient.kind === "channel") {
235
+ return { channelId: recipient.id };
236
+ }
237
+ const dmChannel = (await request(
238
+ () => createUserDmChannel(rest, recipient.id),
239
+ "dm-channel",
240
+ )) as { id: string };
241
+ if (!dmChannel?.id) {
242
+ throw new Error("Failed to create Discord DM channel");
243
+ }
244
+ return { channelId: dmChannel.id, dm: true };
245
+ }
246
+
247
+ async function resolveDiscordTargetChannelId(
248
+ raw: string,
249
+ opts: DiscordClientOpts & { cfg: AutoBotConfig },
250
+ ): Promise<{ channelId: string; dm?: boolean }> {
251
+ const cfg = requireRuntimeConfig(opts.cfg, "Discord target channel resolution");
252
+ const recipient = await parseAndResolveRecipient(raw, cfg, opts.accountId, {
253
+ defaultKind: "channel",
254
+ });
255
+ const { rest, request } = createDiscordClient(opts);
256
+ return await resolveChannelId(rest, recipient, request);
257
+ }
258
+
259
+ export async function resolveDiscordChannelType(
260
+ rest: RequestClient,
261
+ channelId: string,
262
+ ): Promise<number | undefined> {
263
+ try {
264
+ const channel = (await getChannel(rest, channelId)) as APIChannel | undefined;
265
+ return channel?.type;
266
+ } catch {
267
+ return undefined;
268
+ }
269
+ }
270
+
271
+ export function buildDiscordTextChunks(
272
+ text: string,
273
+ opts: { maxLinesPerMessage?: number; chunkMode?: ChunkMode; maxChars?: number } = {},
274
+ ): string[] {
275
+ if (!text) {
276
+ return [];
277
+ }
278
+ const chunks = chunkDiscordTextWithMode(text, {
279
+ maxChars: opts.maxChars ?? DISCORD_TEXT_LIMIT,
280
+ maxLines: opts.maxLinesPerMessage,
281
+ chunkMode: opts.chunkMode,
282
+ });
283
+ return resolveTextChunksWithFallback(text, chunks);
284
+ }
285
+
286
+ export function toDiscordFileBlob(data: Blob | Uint8Array): Blob {
287
+ if (data instanceof Blob) {
288
+ return data;
289
+ }
290
+ const arrayBuffer = new ArrayBuffer(data.byteLength);
291
+ new Uint8Array(arrayBuffer).set(data);
292
+ return new Blob([arrayBuffer]);
293
+ }
294
+
295
+ async function sendDiscordText(
296
+ rest: RequestClient,
297
+ channelId: string,
298
+ text: string,
299
+ replyTo: string | undefined,
300
+ request: DiscordRequest,
301
+ maxLinesPerMessage?: number,
302
+ components?: DiscordSendComponents,
303
+ embeds?: DiscordSendEmbeds,
304
+ chunkMode?: ChunkMode,
305
+ silent?: boolean,
306
+ suppressEmbeds?: boolean,
307
+ maxChars?: number,
308
+ ) {
309
+ if (!text.trim()) {
310
+ throw new Error("Message must be non-empty for Discord sends");
311
+ }
312
+ const chunks = buildDiscordTextChunks(text, { maxLinesPerMessage, chunkMode, maxChars });
313
+ const sendChunk = async (chunk: string, isFirst: boolean) => {
314
+ const chunkComponents = resolveDiscordSendComponents({
315
+ components,
316
+ text: chunk,
317
+ isFirst,
318
+ });
319
+ const chunkEmbeds = resolveDiscordSendEmbeds({ embeds, isFirst });
320
+ const flags = resolveDiscordMessageFlags({
321
+ silent,
322
+ suppressEmbeds: suppressEmbeds && !chunkEmbeds?.length,
323
+ });
324
+ const body = buildDiscordMessageRequest({
325
+ text: chunk,
326
+ components: chunkComponents,
327
+ embeds: chunkEmbeds,
328
+ flags,
329
+ replyTo,
330
+ });
331
+ return (await request(
332
+ () => createChannelMessage<{ id: string; channel_id: string }>(rest, channelId, { body }),
333
+ "text",
334
+ )) as { id: string; channel_id: string };
335
+ };
336
+ if (chunks.length === 1) {
337
+ const result = await sendChunk(chunks[0], true);
338
+ return { ...result, platformMessageIds: result.id ? [result.id] : [] };
339
+ }
340
+ const platformMessageIds: string[] = [];
341
+ let last: { id: string; channel_id: string } | null = null;
342
+ for (const [index, chunk] of chunks.entries()) {
343
+ last = await sendChunk(chunk, index === 0);
344
+ if (last.id) {
345
+ platformMessageIds.push(last.id);
346
+ }
347
+ }
348
+ if (!last) {
349
+ throw new Error("Discord send failed (empty chunk result)");
350
+ }
351
+ return { ...last, platformMessageIds };
352
+ }
353
+
354
+ async function sendDiscordMedia(
355
+ rest: RequestClient,
356
+ channelId: string,
357
+ text: string,
358
+ mediaUrl: string,
359
+ filename: string | undefined,
360
+ mediaAccess: OutboundMediaAccess | undefined,
361
+ mediaLocalRoots: readonly string[] | undefined,
362
+ mediaReadFile: ((filePath: string) => Promise<Buffer>) | undefined,
363
+ maxBytes: number | undefined,
364
+ replyTo: string | undefined,
365
+ request: DiscordRequest,
366
+ maxLinesPerMessage?: number,
367
+ components?: DiscordSendComponents,
368
+ embeds?: DiscordSendEmbeds,
369
+ chunkMode?: ChunkMode,
370
+ silent?: boolean,
371
+ suppressEmbeds?: boolean,
372
+ maxChars?: number,
373
+ ) {
374
+ const media = await loadWebMedia(
375
+ mediaUrl,
376
+ buildOutboundMediaLoadOptions({ maxBytes, mediaAccess, mediaLocalRoots, mediaReadFile }),
377
+ );
378
+ const requestedFileName = filename?.trim();
379
+ const resolvedFileName =
380
+ requestedFileName ||
381
+ media.fileName ||
382
+ (media.contentType ? `upload${extensionForMime(media.contentType) ?? ""}` : "") ||
383
+ "upload";
384
+ const chunks = text
385
+ ? buildDiscordTextChunks(text, { maxLinesPerMessage, chunkMode, maxChars })
386
+ : [];
387
+ const caption = chunks[0] ?? "";
388
+ const fileData = toDiscordFileBlob(media.buffer);
389
+ const captionComponents = resolveDiscordSendComponents({
390
+ components,
391
+ text: caption,
392
+ isFirst: true,
393
+ });
394
+ const captionEmbeds = resolveDiscordSendEmbeds({ embeds, isFirst: true });
395
+ const flags = resolveDiscordMessageFlags({
396
+ silent,
397
+ suppressEmbeds: suppressEmbeds && !captionEmbeds?.length,
398
+ });
399
+ const body = buildDiscordMessageRequest({
400
+ text: caption,
401
+ components: captionComponents,
402
+ embeds: captionEmbeds,
403
+ flags,
404
+ replyTo,
405
+ files: [
406
+ {
407
+ data: fileData,
408
+ name: resolvedFileName,
409
+ },
410
+ ],
411
+ });
412
+ const res = (await request(
413
+ () => createChannelMessage<{ id: string; channel_id: string }>(rest, channelId, { body }),
414
+ "media",
415
+ )) as { id: string; channel_id: string };
416
+ const platformMessageIds = res.id ? [res.id] : [];
417
+ for (const chunk of chunks.slice(1)) {
418
+ if (!chunk.trim()) {
419
+ continue;
420
+ }
421
+ const followup = await sendDiscordText(
422
+ rest,
423
+ channelId,
424
+ chunk,
425
+ replyTo,
426
+ request,
427
+ maxLinesPerMessage,
428
+ undefined,
429
+ undefined,
430
+ chunkMode,
431
+ silent,
432
+ suppressEmbeds,
433
+ maxChars,
434
+ );
435
+ for (const id of followup.platformMessageIds) {
436
+ if (id) {
437
+ platformMessageIds.push(id);
438
+ }
439
+ }
440
+ }
441
+ return { ...res, platformMessageIds };
442
+ }
443
+
444
+ function buildReactionIdentifier(emoji: { id?: string | null; name?: string | null }) {
445
+ if (emoji.id && emoji.name) {
446
+ return `${emoji.name}:${emoji.id}`;
447
+ }
448
+ return emoji.name ?? "";
449
+ }
450
+
451
+ function formatReactionEmoji(emoji: { id?: string | null; name?: string | null }) {
452
+ return buildReactionIdentifier(emoji);
453
+ }
454
+
455
+ export {
456
+ buildDiscordSendError,
457
+ buildReactionIdentifier,
458
+ createDiscordClient,
459
+ formatReactionEmoji,
460
+ normalizeDiscordPollInput,
461
+ normalizeEmojiName,
462
+ normalizeReactionEmoji,
463
+ normalizeStickerIds,
464
+ resolveChannelId,
465
+ resolveDiscordTargetChannelId,
466
+ resolveDiscordRest,
467
+ sendDiscordMedia,
468
+ sendDiscordText,
469
+ };
package/src/send.ts ADDED
@@ -0,0 +1,82 @@
1
+ export {
2
+ createChannelDiscord,
3
+ deleteChannelDiscord,
4
+ editChannelDiscord,
5
+ moveChannelDiscord,
6
+ removeChannelPermissionDiscord,
7
+ setChannelPermissionDiscord,
8
+ } from "./send.channels.js";
9
+ export {
10
+ listGuildEmojisDiscord,
11
+ uploadEmojiDiscord,
12
+ uploadStickerDiscord,
13
+ } from "./send.emojis-stickers.js";
14
+ export {
15
+ addRoleDiscord,
16
+ banMemberDiscord,
17
+ createScheduledEventDiscord,
18
+ resolveEventCoverImage,
19
+ fetchChannelInfoDiscord,
20
+ fetchMemberInfoDiscord,
21
+ fetchRoleInfoDiscord,
22
+ fetchVoiceStatusDiscord,
23
+ kickMemberDiscord,
24
+ listGuildChannelsDiscord,
25
+ listScheduledEventsDiscord,
26
+ removeRoleDiscord,
27
+ timeoutMemberDiscord,
28
+ } from "./send.guild.js";
29
+ export {
30
+ createThreadDiscord,
31
+ deleteMessageDiscord,
32
+ DiscordThreadInitialMessageError,
33
+ editMessageDiscord,
34
+ fetchMessageDiscord,
35
+ listPinsDiscord,
36
+ listThreadsDiscord,
37
+ pinMessageDiscord,
38
+ readMessagesDiscord,
39
+ searchMessagesDiscord,
40
+ unpinMessageDiscord,
41
+ } from "./send.messages.js";
42
+ export { sendMessageDiscord, sendPollDiscord, sendStickerDiscord } from "./send.outbound.js";
43
+ export { sendWebhookMessageDiscord } from "./send.webhook.js";
44
+ export { sendVoiceMessageDiscord } from "./send.voice.js";
45
+ export { sendTypingDiscord } from "./send.typing.js";
46
+ export {
47
+ canViewDiscordGuildChannel,
48
+ fetchChannelPermissionsDiscord,
49
+ hasAllGuildPermissionsDiscord,
50
+ hasAnyGuildPermissionDiscord,
51
+ fetchMemberGuildPermissionsDiscord,
52
+ } from "./send.permissions.js";
53
+ export {
54
+ fetchReactionsDiscord,
55
+ reactMessageDiscord,
56
+ removeOwnReactionsDiscord,
57
+ removeReactionDiscord,
58
+ } from "./send.reactions.js";
59
+ export type {
60
+ DiscordChannelCreate,
61
+ DiscordChannelEdit,
62
+ DiscordChannelMove,
63
+ DiscordChannelPermissionSet,
64
+ DiscordEmojiUpload,
65
+ DiscordMessageEdit,
66
+ DiscordMessageQuery,
67
+ DiscordModerationTarget,
68
+ DiscordReactionRuntimeContext,
69
+ DiscordPermissionsSummary,
70
+ DiscordReactionSummary,
71
+ DiscordReactionUser,
72
+ DiscordReactOpts,
73
+ DiscordRuntimeAccountContext,
74
+ DiscordRoleChange,
75
+ DiscordSearchQuery,
76
+ DiscordSendResult,
77
+ DiscordStickerUpload,
78
+ DiscordThreadCreate,
79
+ DiscordThreadList,
80
+ DiscordTimeoutTarget,
81
+ } from "./send.types.js";
82
+ export { DiscordSendError } from "./send.types.js";