@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,92 @@
1
+ import { isIP } from "node:net";
2
+ import type { AutoBotConfig } from "autobot/plugin-sdk/config-contracts";
3
+ import { makeProxyFetch } from "autobot/plugin-sdk/fetch-runtime";
4
+ import { danger } from "autobot/plugin-sdk/runtime-env";
5
+ import type { RuntimeEnv } from "autobot/plugin-sdk/runtime-env";
6
+ import { normalizeLowercaseStringOrEmpty } from "autobot/plugin-sdk/string-coerce-runtime";
7
+ import type { ResolvedDiscordAccount } from "./accounts.js";
8
+
9
+ function resolveDiscordProxyUrl(
10
+ account: Pick<ResolvedDiscordAccount, "config">,
11
+ cfg: AutoBotConfig,
12
+ ): string | undefined {
13
+ const accountProxy = account.config.proxy?.trim();
14
+ if (accountProxy) {
15
+ return accountProxy;
16
+ }
17
+ const channelProxy = cfg?.channels?.discord?.proxy;
18
+ if (typeof channelProxy !== "string") {
19
+ return undefined;
20
+ }
21
+ const trimmed = channelProxy.trim();
22
+ return trimmed || undefined;
23
+ }
24
+
25
+ function resolveDiscordProxyFetchByUrl(
26
+ proxyUrl: string | undefined,
27
+ runtime?: Pick<RuntimeEnv, "error">,
28
+ ): typeof fetch | undefined {
29
+ return withValidatedDiscordProxy(proxyUrl, runtime, (proxy) => makeProxyFetch(proxy));
30
+ }
31
+
32
+ export function resolveDiscordProxyFetchForAccount(
33
+ account: Pick<ResolvedDiscordAccount, "config">,
34
+ cfg: AutoBotConfig,
35
+ runtime?: Pick<RuntimeEnv, "error">,
36
+ ): typeof fetch | undefined {
37
+ return resolveDiscordProxyFetchByUrl(resolveDiscordProxyUrl(account, cfg), runtime);
38
+ }
39
+
40
+ export function withValidatedDiscordProxy<T>(
41
+ proxyUrl: string | undefined,
42
+ runtime: Pick<RuntimeEnv, "error"> | undefined,
43
+ createValue: (proxyUrl: string) => T,
44
+ ): T | undefined {
45
+ const proxy = proxyUrl?.trim();
46
+ if (!proxy) {
47
+ return undefined;
48
+ }
49
+ try {
50
+ validateDiscordProxyUrl(proxy);
51
+ return createValue(proxy);
52
+ } catch (err) {
53
+ runtime?.error?.(danger(`discord: invalid rest proxy: ${String(err)}`));
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ export function validateDiscordProxyUrl(proxyUrl: string): string {
59
+ let parsed: URL;
60
+ try {
61
+ parsed = new URL(proxyUrl);
62
+ } catch {
63
+ throw new Error("Proxy URL must be a valid http or https URL");
64
+ }
65
+ if (!["http:", "https:"].includes(parsed.protocol)) {
66
+ throw new Error("Proxy URL must use http or https");
67
+ }
68
+ if (!isLoopbackProxyHostname(parsed.hostname)) {
69
+ throw new Error("Proxy URL must target a loopback host");
70
+ }
71
+ return proxyUrl;
72
+ }
73
+
74
+ function isLoopbackProxyHostname(hostname: string): boolean {
75
+ const normalized = normalizeLowercaseStringOrEmpty(hostname);
76
+ if (!normalized) {
77
+ return false;
78
+ }
79
+ const bracketless =
80
+ normalized.startsWith("[") && normalized.endsWith("]") ? normalized.slice(1, -1) : normalized;
81
+ if (bracketless === "localhost") {
82
+ return true;
83
+ }
84
+ const ipFamily = isIP(bracketless);
85
+ if (ipFamily === 4) {
86
+ return bracketless.startsWith("127.");
87
+ }
88
+ if (ipFamily === 6) {
89
+ return bracketless === "::1" || bracketless === "0:0:0:0:0:0:0:1";
90
+ }
91
+ return false;
92
+ }
@@ -0,0 +1,21 @@
1
+ import { RequestClient, type RequestClientOptions } from "./internal/discord.js";
2
+
3
+ type ProxyRequestClientOptions = RequestClientOptions;
4
+
5
+ export const DISCORD_REST_TIMEOUT_MS = 15_000;
6
+
7
+ export function createDiscordRequestClient(
8
+ token: string,
9
+ options?: ProxyRequestClientOptions,
10
+ ): RequestClient {
11
+ if (!options?.fetch) {
12
+ return new RequestClient(token, options);
13
+ }
14
+ return new RequestClient(token, {
15
+ runtimeProfile: "persistent",
16
+ maxQueueSize: 1000,
17
+ timeout: DISCORD_REST_TIMEOUT_MS,
18
+ ...options,
19
+ fetch: options.fetch,
20
+ });
21
+ }
@@ -0,0 +1,39 @@
1
+ import type { AutoBotConfig } from "autobot/plugin-sdk/config-contracts";
2
+ import { requireRuntimeConfig } from "autobot/plugin-sdk/plugin-config-runtime";
3
+ import { resolveDiscordAccount } from "./accounts.js";
4
+ import { parseAndResolveDiscordTarget } from "./target-resolver.js";
5
+ import type { DiscordTargetParseOptions } from "./targets.js";
6
+
7
+ type DiscordRecipient =
8
+ | {
9
+ kind: "user";
10
+ id: string;
11
+ }
12
+ | {
13
+ kind: "channel";
14
+ id: string;
15
+ };
16
+
17
+ export async function parseAndResolveRecipient(
18
+ raw: string,
19
+ cfg: AutoBotConfig,
20
+ accountId?: string,
21
+ parseOptions: DiscordTargetParseOptions = {},
22
+ ): Promise<DiscordRecipient> {
23
+ if (!cfg) {
24
+ throw new Error(
25
+ "Discord recipient resolution requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.",
26
+ );
27
+ }
28
+ const resolvedCfg = requireRuntimeConfig(cfg, "Discord recipient resolution");
29
+ const accountInfo = resolveDiscordAccount({ cfg: resolvedCfg, accountId });
30
+ const resolved = await parseAndResolveDiscordTarget(
31
+ raw,
32
+ {
33
+ cfg: resolvedCfg,
34
+ accountId: accountInfo.accountId,
35
+ },
36
+ parseOptions,
37
+ );
38
+ return { kind: resolved.kind, id: resolved.id };
39
+ }
@@ -0,0 +1,39 @@
1
+ import type { DiscordGuildSummary } from "./guilds.js";
2
+ import { normalizeDiscordSlug } from "./monitor/allow-list.js";
3
+ import { normalizeDiscordToken } from "./token.js";
4
+
5
+ export function resolveDiscordAllowlistToken(token: string): string | undefined {
6
+ return normalizeDiscordToken(token, "channels.discord.token");
7
+ }
8
+
9
+ export function buildDiscordUnresolvedResults<T extends { input: string; resolved: boolean }>(
10
+ entries: string[],
11
+ buildResult: (input: string) => T,
12
+ ): T[] {
13
+ return entries.map((input) => buildResult(input));
14
+ }
15
+
16
+ export function findDiscordGuildByName(
17
+ guilds: DiscordGuildSummary[],
18
+ input: string,
19
+ ): DiscordGuildSummary | undefined {
20
+ const slug = normalizeDiscordSlug(input);
21
+ if (!slug) {
22
+ return undefined;
23
+ }
24
+ return guilds.find((guild) => guild.slug === slug);
25
+ }
26
+
27
+ export function filterDiscordGuilds(
28
+ guilds: DiscordGuildSummary[],
29
+ params: { guildId?: string; guildName?: string },
30
+ ): DiscordGuildSummary[] {
31
+ if (params.guildId) {
32
+ return guilds.filter((guild) => guild.id === params.guildId);
33
+ }
34
+ if (params.guildName) {
35
+ const match = findDiscordGuildByName(guilds, params.guildName);
36
+ return match ? [match] : [];
37
+ }
38
+ return guilds;
39
+ }
@@ -0,0 +1,369 @@
1
+ import { DiscordApiError, fetchDiscord } from "./api.js";
2
+ import { listGuilds } from "./guilds.js";
3
+ import { normalizeDiscordSlug } from "./monitor/allow-list.js";
4
+ import {
5
+ buildDiscordUnresolvedResults,
6
+ filterDiscordGuilds,
7
+ resolveDiscordAllowlistToken,
8
+ } from "./resolve-allowlist-common.js";
9
+
10
+ type DiscordChannelSummary = {
11
+ id: string;
12
+ name: string;
13
+ guildId: string;
14
+ type?: number;
15
+ archived?: boolean;
16
+ };
17
+
18
+ type DiscordChannelPayload = {
19
+ id?: string;
20
+ name?: string;
21
+ type?: number;
22
+ guild_id?: string;
23
+ thread_metadata?: { archived?: boolean };
24
+ };
25
+
26
+ export type DiscordChannelResolution = {
27
+ input: string;
28
+ resolved: boolean;
29
+ guildId?: string;
30
+ guildName?: string;
31
+ channelId?: string;
32
+ channelName?: string;
33
+ archived?: boolean;
34
+ note?: string;
35
+ };
36
+
37
+ function parseDiscordChannelInput(raw: string): {
38
+ guild?: string;
39
+ channel?: string;
40
+ channelId?: string;
41
+ guildId?: string;
42
+ guildOnly?: boolean;
43
+ } {
44
+ const trimmed = raw.trim();
45
+ if (!trimmed) {
46
+ return {};
47
+ }
48
+ const mention = trimmed.match(/^<#(\d+)>$/);
49
+ if (mention) {
50
+ return { channelId: mention[1] };
51
+ }
52
+ const channelPrefix = trimmed.match(/^(?:channel:|discord:)?(\d+)$/i);
53
+ if (channelPrefix) {
54
+ return { channelId: channelPrefix[1] };
55
+ }
56
+ const guildPrefix = trimmed.match(/^(?:guild:|server:)?(\d+)$/i);
57
+ if (guildPrefix && !trimmed.includes("/") && !trimmed.includes("#")) {
58
+ return { guildId: guildPrefix[1], guildOnly: true };
59
+ }
60
+ const split = trimmed.includes("/") ? trimmed.split("/") : trimmed.split("#");
61
+ if (split.length >= 2) {
62
+ const guild = split[0]?.trim();
63
+ const channel = split.slice(1).join("#").trim();
64
+ if (!channel) {
65
+ return guild ? { guild: guild.trim(), guildOnly: true } : {};
66
+ }
67
+ if (guild && /^\d+$/.test(guild)) {
68
+ if (/^\d+$/.test(channel)) {
69
+ return { guildId: guild, channelId: channel };
70
+ }
71
+ return { guildId: guild, channel };
72
+ }
73
+ return { guild, channel };
74
+ }
75
+ return { guild: trimmed, guildOnly: true };
76
+ }
77
+
78
+ async function listGuildChannels(
79
+ token: string,
80
+ fetcher: typeof fetch,
81
+ guildId: string,
82
+ ): Promise<DiscordChannelSummary[]> {
83
+ const raw = await fetchDiscord<DiscordChannelPayload[]>(
84
+ `/guilds/${guildId}/channels`,
85
+ token,
86
+ fetcher,
87
+ );
88
+ return raw
89
+ .map((channel) => {
90
+ const archived = channel.thread_metadata?.archived;
91
+ return {
92
+ id: typeof channel.id === "string" ? channel.id : "",
93
+ name: typeof channel.name === "string" ? channel.name : "",
94
+ guildId,
95
+ type: channel.type,
96
+ archived,
97
+ };
98
+ })
99
+ .filter((channel) => Boolean(channel.id) && Boolean(channel.name));
100
+ }
101
+
102
+ type FetchChannelResult =
103
+ | { status: "found"; channel: DiscordChannelSummary }
104
+ | { status: "not-found" }
105
+ | { status: "forbidden" }
106
+ | { status: "invalid" };
107
+
108
+ async function fetchChannel(
109
+ token: string,
110
+ fetcher: typeof fetch,
111
+ channelId: string,
112
+ ): Promise<FetchChannelResult> {
113
+ let raw: DiscordChannelPayload;
114
+ try {
115
+ raw = await fetchDiscord<DiscordChannelPayload>(`/channels/${channelId}`, token, fetcher);
116
+ } catch (err) {
117
+ if (err instanceof DiscordApiError && err.status === 403) {
118
+ return { status: "forbidden" };
119
+ }
120
+ if (err instanceof DiscordApiError && err.status === 404) {
121
+ return { status: "not-found" };
122
+ }
123
+ throw err;
124
+ }
125
+ if (!raw || typeof raw.guild_id !== "string" || typeof raw.id !== "string") {
126
+ return { status: "invalid" };
127
+ }
128
+ return {
129
+ status: "found",
130
+ channel: {
131
+ id: raw.id,
132
+ name: typeof raw.name === "string" ? raw.name : "",
133
+ guildId: raw.guild_id,
134
+ type: raw.type,
135
+ },
136
+ };
137
+ }
138
+
139
+ function preferActiveMatch(candidates: DiscordChannelSummary[]): DiscordChannelSummary | undefined {
140
+ if (candidates.length === 0) {
141
+ return undefined;
142
+ }
143
+ const scored = candidates.map((channel) => {
144
+ const isThread = channel.type === 11 || channel.type === 12;
145
+ const archived = Boolean(channel.archived);
146
+ const score = (archived ? 0 : 2) + (isThread ? 0 : 1);
147
+ return { channel, score };
148
+ });
149
+ scored.sort((a, b) => b.score - a.score);
150
+ return scored[0]?.channel ?? candidates[0];
151
+ }
152
+
153
+ export async function resolveDiscordChannelAllowlist(params: {
154
+ token: string;
155
+ entries: string[];
156
+ fetcher?: typeof fetch;
157
+ }): Promise<DiscordChannelResolution[]> {
158
+ const token = resolveDiscordAllowlistToken(params.token);
159
+ if (!token) {
160
+ return buildDiscordUnresolvedResults(params.entries, (input) => ({
161
+ input,
162
+ resolved: false,
163
+ }));
164
+ }
165
+ const fetcher = params.fetcher ?? fetch;
166
+ const guilds = await listGuilds(token, fetcher);
167
+ const channelsByGuild = new Map<string, Promise<DiscordChannelSummary[]>>();
168
+ const getChannels = (guildId: string) => {
169
+ const existing = channelsByGuild.get(guildId);
170
+ if (existing) {
171
+ return existing;
172
+ }
173
+ const promise = listGuildChannels(token, fetcher, guildId);
174
+ channelsByGuild.set(guildId, promise);
175
+ return promise;
176
+ };
177
+
178
+ const results: DiscordChannelResolution[] = [];
179
+
180
+ for (const input of params.entries) {
181
+ const parsed = parseDiscordChannelInput(input);
182
+ if (parsed.guildOnly) {
183
+ const guild = filterDiscordGuilds(guilds, {
184
+ guildId: parsed.guildId,
185
+ guildName: parsed.guild,
186
+ })[0];
187
+ if (guild) {
188
+ results.push({
189
+ input,
190
+ resolved: true,
191
+ guildId: guild.id,
192
+ guildName: guild.name,
193
+ });
194
+ } else {
195
+ results.push({
196
+ input,
197
+ resolved: false,
198
+ guildId: parsed.guildId,
199
+ guildName: parsed.guild,
200
+ });
201
+ }
202
+ continue;
203
+ }
204
+
205
+ if (parsed.channelId) {
206
+ const channelId = parsed.channelId;
207
+ const result = await fetchChannel(token, fetcher, channelId);
208
+ if (result.status === "found") {
209
+ const channel = result.channel;
210
+ if (parsed.guildId && parsed.guildId !== channel.guildId) {
211
+ const expectedGuild = guilds.find((entry) => entry.id === parsed.guildId);
212
+ const actualGuild = guilds.find((entry) => entry.id === channel.guildId);
213
+ results.push({
214
+ input,
215
+ resolved: false,
216
+ guildId: parsed.guildId,
217
+ guildName: expectedGuild?.name,
218
+ channelId,
219
+ channelName: channel.name,
220
+ note: actualGuild?.name
221
+ ? `channel belongs to guild ${actualGuild.name}`
222
+ : "channel belongs to a different guild",
223
+ });
224
+ continue;
225
+ }
226
+ const guild = guilds.find((entry) => entry.id === channel.guildId);
227
+ results.push({
228
+ input,
229
+ resolved: true,
230
+ guildId: channel.guildId,
231
+ guildName: guild?.name,
232
+ channelId: channel.id,
233
+ channelName: channel.name,
234
+ archived: channel.archived,
235
+ });
236
+ continue;
237
+ }
238
+
239
+ if (result.status === "not-found" && parsed.guildId) {
240
+ const guild = guilds.find((entry) => entry.id === parsed.guildId);
241
+ if (guild) {
242
+ const channels = await getChannels(guild.id);
243
+ const matches = channels.filter(
244
+ (channel) => normalizeDiscordSlug(channel.name) === normalizeDiscordSlug(channelId),
245
+ );
246
+ const match = preferActiveMatch(matches);
247
+ if (match) {
248
+ results.push({
249
+ input,
250
+ resolved: true,
251
+ guildId: guild.id,
252
+ guildName: guild.name,
253
+ channelId: match.id,
254
+ channelName: match.name,
255
+ archived: match.archived,
256
+ });
257
+ continue;
258
+ }
259
+ }
260
+ }
261
+
262
+ results.push({
263
+ input,
264
+ resolved: false,
265
+ guildId: parsed.guildId,
266
+ channelId,
267
+ });
268
+ continue;
269
+ }
270
+
271
+ if (parsed.guildId || parsed.guild) {
272
+ const guild = filterDiscordGuilds(guilds, {
273
+ guildId: parsed.guildId,
274
+ guildName: parsed.guild,
275
+ })[0];
276
+ const channelQuery = parsed.channel?.trim();
277
+ if (!guild || !channelQuery) {
278
+ results.push({
279
+ input,
280
+ resolved: false,
281
+ guildId: parsed.guildId,
282
+ guildName: parsed.guild,
283
+ channelName: channelQuery ?? parsed.channel,
284
+ });
285
+ continue;
286
+ }
287
+ const channels = await getChannels(guild.id);
288
+ const normalizedChannelQuery = normalizeDiscordSlug(channelQuery);
289
+ const isNumericId = /^\d+$/.test(channelQuery);
290
+ let matches = channels.filter((channel) =>
291
+ isNumericId
292
+ ? channel.id === channelQuery
293
+ : normalizeDiscordSlug(channel.name) === normalizedChannelQuery,
294
+ );
295
+ if (isNumericId && matches.length === 0) {
296
+ matches = channels.filter(
297
+ (channel) => normalizeDiscordSlug(channel.name) === normalizedChannelQuery,
298
+ );
299
+ }
300
+ const match = preferActiveMatch(matches);
301
+ if (match) {
302
+ results.push({
303
+ input,
304
+ resolved: true,
305
+ guildId: guild.id,
306
+ guildName: guild.name,
307
+ channelId: match.id,
308
+ channelName: match.name,
309
+ archived: match.archived,
310
+ });
311
+ } else {
312
+ results.push({
313
+ input,
314
+ resolved: false,
315
+ guildId: guild.id,
316
+ guildName: guild.name,
317
+ channelName: parsed.channel,
318
+ note: `channel not found in guild ${guild.name}`,
319
+ });
320
+ }
321
+ continue;
322
+ }
323
+
324
+ const channelName = input.trim().replace(/^#/, "");
325
+ if (!channelName) {
326
+ results.push({
327
+ input,
328
+ resolved: false,
329
+ channelName: channelName,
330
+ });
331
+ continue;
332
+ }
333
+ const candidates: DiscordChannelSummary[] = [];
334
+ for (const guild of guilds) {
335
+ const channels = await getChannels(guild.id);
336
+ for (const channel of channels) {
337
+ if (normalizeDiscordSlug(channel.name) === normalizeDiscordSlug(channelName)) {
338
+ candidates.push(channel);
339
+ }
340
+ }
341
+ }
342
+ const match = preferActiveMatch(candidates);
343
+ if (match) {
344
+ const guild = guilds.find((entry) => entry.id === match.guildId);
345
+ results.push({
346
+ input,
347
+ resolved: true,
348
+ guildId: match.guildId,
349
+ guildName: guild?.name,
350
+ channelId: match.id,
351
+ channelName: match.name,
352
+ archived: match.archived,
353
+ note:
354
+ candidates.length > 1 && guild?.name
355
+ ? `matched multiple; chose ${guild.name}`
356
+ : undefined,
357
+ });
358
+ continue;
359
+ }
360
+
361
+ results.push({
362
+ input,
363
+ resolved: false,
364
+ channelName: channelName,
365
+ });
366
+ }
367
+
368
+ return results;
369
+ }