@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,116 @@
1
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import {
3
+ type ActionGate,
4
+ jsonResult,
5
+ readStringParam,
6
+ type DiscordActionConfig,
7
+ type AutoBotConfig,
8
+ } from "../runtime-api.js";
9
+ import {
10
+ banMemberDiscord,
11
+ hasAnyGuildPermissionDiscord,
12
+ kickMemberDiscord,
13
+ timeoutMemberDiscord,
14
+ } from "../send.js";
15
+ import {
16
+ isDiscordModerationAction,
17
+ readDiscordModerationCommand,
18
+ requiredGuildPermissionForModerationAction,
19
+ } from "./runtime.moderation-shared.js";
20
+ import { createDiscordActionOptions } from "./runtime.shared.js";
21
+
22
+ export const discordModerationActionRuntime = {
23
+ banMemberDiscord,
24
+ hasAnyGuildPermissionDiscord,
25
+ kickMemberDiscord,
26
+ timeoutMemberDiscord,
27
+ };
28
+
29
+ async function verifySenderModerationPermission(params: {
30
+ guildId: string;
31
+ senderUserId?: string;
32
+ requiredPermission: bigint;
33
+ accountId?: string;
34
+ cfg: AutoBotConfig;
35
+ }) {
36
+ // CLI/manual flows may not have sender context; enforce only when present.
37
+ if (!params.senderUserId) {
38
+ return;
39
+ }
40
+ const hasPermission = await discordModerationActionRuntime.hasAnyGuildPermissionDiscord(
41
+ params.guildId,
42
+ params.senderUserId,
43
+ [params.requiredPermission],
44
+ createDiscordActionOptions({ cfg: params.cfg, accountId: params.accountId }),
45
+ );
46
+ if (!hasPermission) {
47
+ throw new Error("Sender does not have required permissions for this moderation action.");
48
+ }
49
+ }
50
+
51
+ export async function handleDiscordModerationAction(
52
+ action: string,
53
+ params: Record<string, unknown>,
54
+ isActionEnabled: ActionGate<DiscordActionConfig>,
55
+ cfg: AutoBotConfig,
56
+ ): Promise<AgentToolResult<unknown>> {
57
+ if (!isDiscordModerationAction(action)) {
58
+ throw new Error(`Unknown action: ${action}`);
59
+ }
60
+ if (!isActionEnabled("moderation", false)) {
61
+ throw new Error("Discord moderation is disabled.");
62
+ }
63
+ if (!cfg) {
64
+ throw new Error("Discord moderation actions require a resolved runtime config.");
65
+ }
66
+ const accountId = readStringParam(params, "accountId");
67
+ const command = readDiscordModerationCommand(action, params);
68
+ const senderUserId = readStringParam(params, "senderUserId");
69
+ const withOpts = () => createDiscordActionOptions({ cfg, accountId });
70
+ await verifySenderModerationPermission({
71
+ guildId: command.guildId,
72
+ senderUserId,
73
+ requiredPermission: requiredGuildPermissionForModerationAction(command.action),
74
+ accountId,
75
+ cfg,
76
+ });
77
+ switch (command.action) {
78
+ case "timeout": {
79
+ const member = await discordModerationActionRuntime.timeoutMemberDiscord(
80
+ {
81
+ guildId: command.guildId,
82
+ userId: command.userId,
83
+ durationMinutes: command.durationMinutes,
84
+ until: command.until,
85
+ reason: command.reason,
86
+ },
87
+ withOpts(),
88
+ );
89
+ return jsonResult({ ok: true, member });
90
+ }
91
+ case "kick": {
92
+ await discordModerationActionRuntime.kickMemberDiscord(
93
+ {
94
+ guildId: command.guildId,
95
+ userId: command.userId,
96
+ reason: command.reason,
97
+ },
98
+ withOpts(),
99
+ );
100
+ return jsonResult({ ok: true });
101
+ }
102
+ case "ban": {
103
+ await discordModerationActionRuntime.banMemberDiscord(
104
+ {
105
+ guildId: command.guildId,
106
+ userId: command.userId,
107
+ reason: command.reason,
108
+ deleteMessageDays: command.deleteMessageDays,
109
+ },
110
+ withOpts(),
111
+ );
112
+ return jsonResult({ ok: true });
113
+ }
114
+ }
115
+ throw new Error("Unsupported Discord moderation action");
116
+ }
@@ -0,0 +1,117 @@
1
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import { normalizeLowercaseStringOrEmpty } from "autobot/plugin-sdk/string-coerce-runtime";
3
+ import type { Activity, UpdatePresenceData } from "../internal/gateway.js";
4
+ import { getGateway } from "../monitor/gateway-registry.js";
5
+ import {
6
+ type ActionGate,
7
+ jsonResult,
8
+ readStringParam,
9
+ type DiscordActionConfig,
10
+ } from "../runtime-api.js";
11
+
12
+ const ACTIVITY_TYPE_MAP: Record<string, number> = {
13
+ playing: 0,
14
+ streaming: 1,
15
+ listening: 2,
16
+ watching: 3,
17
+ custom: 4,
18
+ competing: 5,
19
+ };
20
+
21
+ const VALID_STATUSES = new Set(["online", "dnd", "idle", "invisible"]);
22
+
23
+ export async function handleDiscordPresenceAction(
24
+ action: string,
25
+ params: Record<string, unknown>,
26
+ isActionEnabled: ActionGate<DiscordActionConfig>,
27
+ ): Promise<AgentToolResult<unknown>> {
28
+ if (action !== "setPresence") {
29
+ throw new Error(`Unknown presence action: ${action}`);
30
+ }
31
+
32
+ if (!isActionEnabled("presence", false)) {
33
+ throw new Error("Discord presence changes are disabled.");
34
+ }
35
+
36
+ const accountId = readStringParam(params, "accountId");
37
+ const gateway = getGateway(accountId);
38
+ if (!gateway) {
39
+ throw new Error(
40
+ `Discord gateway not available${accountId ? ` for account "${accountId}"` : ""}. The bot may not be connected.`,
41
+ );
42
+ }
43
+ if (!gateway.isConnected) {
44
+ throw new Error(
45
+ `Discord gateway is not connected${accountId ? ` for account "${accountId}"` : ""}.`,
46
+ );
47
+ }
48
+
49
+ const statusRaw = readStringParam(params, "status") ?? "online";
50
+ if (!VALID_STATUSES.has(statusRaw)) {
51
+ throw new Error(
52
+ `Invalid status "${statusRaw}". Must be one of: ${[...VALID_STATUSES].join(", ")}`,
53
+ );
54
+ }
55
+ const status = statusRaw as UpdatePresenceData["status"];
56
+
57
+ const activityTypeRaw = readStringParam(params, "activityType");
58
+ const activityName = readStringParam(params, "activityName");
59
+
60
+ const activities: Activity[] = [];
61
+
62
+ if (activityTypeRaw || activityName) {
63
+ if (!activityTypeRaw) {
64
+ throw new Error(
65
+ "activityType is required when activityName is provided. " +
66
+ `Valid types: ${Object.keys(ACTIVITY_TYPE_MAP).join(", ")}`,
67
+ );
68
+ }
69
+ const typeNum = ACTIVITY_TYPE_MAP[normalizeLowercaseStringOrEmpty(activityTypeRaw)];
70
+ if (typeNum === undefined) {
71
+ throw new Error(
72
+ `Invalid activityType "${activityTypeRaw}". Must be one of: ${Object.keys(ACTIVITY_TYPE_MAP).join(", ")}`,
73
+ );
74
+ }
75
+
76
+ const activity: Activity = {
77
+ name: activityName ?? "",
78
+ type: typeNum,
79
+ };
80
+
81
+ // Streaming URL (Twitch/YouTube). May not render for bots but is the correct payload shape.
82
+ if (typeNum === 1) {
83
+ const url = readStringParam(params, "activityUrl");
84
+ if (url) {
85
+ activity.url = url;
86
+ }
87
+ }
88
+
89
+ const state = readStringParam(params, "activityState");
90
+ if (state) {
91
+ activity.state = state;
92
+ }
93
+
94
+ activities.push(activity);
95
+ }
96
+
97
+ const presenceData: UpdatePresenceData = {
98
+ since: null,
99
+ activities,
100
+ status,
101
+ afk: false,
102
+ };
103
+
104
+ gateway.updatePresence(presenceData);
105
+
106
+ return jsonResult({
107
+ ok: true,
108
+ status,
109
+ activities: activities.map((a) =>
110
+ Object.assign(
111
+ { type: a.type, name: a.name },
112
+ a.url ? { url: a.url } : {},
113
+ a.state ? { state: a.state } : {},
114
+ ),
115
+ ),
116
+ });
117
+ }
@@ -0,0 +1,86 @@
1
+ import { parseAvailableTags, readNumberParam, readStringParam } from "../runtime-api.js";
2
+ import type { AutoBotConfig } from "../runtime-api.js";
3
+ import type {
4
+ DiscordChannelCreate,
5
+ DiscordChannelEdit,
6
+ DiscordChannelMove,
7
+ } from "../send.types.js";
8
+
9
+ export function readDiscordParentIdParam(
10
+ params: Record<string, unknown>,
11
+ ): string | null | undefined {
12
+ if (params.clearParent === true) {
13
+ return null;
14
+ }
15
+ if (params.parentId === null) {
16
+ return null;
17
+ }
18
+ return readStringParam(params, "parentId");
19
+ }
20
+
21
+ function readDiscordBooleanParam(
22
+ params: Record<string, unknown>,
23
+ key: string,
24
+ ): boolean | undefined {
25
+ return typeof params[key] === "boolean" ? params[key] : undefined;
26
+ }
27
+
28
+ export function createDiscordActionOptions<
29
+ T extends Record<string, unknown> = Record<string, never>,
30
+ >(params: {
31
+ cfg: AutoBotConfig;
32
+ accountId?: string;
33
+ extra?: T;
34
+ }): { cfg: AutoBotConfig; accountId?: string } & T {
35
+ return {
36
+ cfg: params.cfg,
37
+ ...(params.accountId ? { accountId: params.accountId } : {}),
38
+ ...(params.extra ?? ({} as T)),
39
+ };
40
+ }
41
+
42
+ export function readDiscordChannelCreateParams(
43
+ params: Record<string, unknown>,
44
+ ): DiscordChannelCreate {
45
+ const parentId = readDiscordParentIdParam(params);
46
+ return {
47
+ guildId: readStringParam(params, "guildId", { required: true }),
48
+ name: readStringParam(params, "name", { required: true }),
49
+ type:
50
+ readNumberParam(params, "channelType", { integer: true }) ??
51
+ readNumberParam(params, "type", { integer: true }) ??
52
+ undefined,
53
+ parentId: parentId ?? undefined,
54
+ topic: readStringParam(params, "topic") ?? undefined,
55
+ position: readNumberParam(params, "position", { integer: true }) ?? undefined,
56
+ nsfw: readDiscordBooleanParam(params, "nsfw"),
57
+ };
58
+ }
59
+
60
+ export function readDiscordChannelEditParams(params: Record<string, unknown>): DiscordChannelEdit {
61
+ const parentId = readDiscordParentIdParam(params);
62
+ return {
63
+ channelId: readStringParam(params, "channelId", { required: true }),
64
+ name: readStringParam(params, "name") ?? undefined,
65
+ topic: readStringParam(params, "topic") ?? undefined,
66
+ position: readNumberParam(params, "position", { integer: true }) ?? undefined,
67
+ parentId: parentId === undefined ? undefined : parentId,
68
+ nsfw: readDiscordBooleanParam(params, "nsfw"),
69
+ rateLimitPerUser: readNumberParam(params, "rateLimitPerUser", { integer: true }) ?? undefined,
70
+ archived: readDiscordBooleanParam(params, "archived"),
71
+ locked: readDiscordBooleanParam(params, "locked"),
72
+ autoArchiveDuration:
73
+ readNumberParam(params, "autoArchiveDuration", { integer: true }) ?? undefined,
74
+ availableTags: parseAvailableTags(params.availableTags),
75
+ };
76
+ }
77
+
78
+ export function readDiscordChannelMoveParams(params: Record<string, unknown>): DiscordChannelMove {
79
+ const parentId = readDiscordParentIdParam(params);
80
+ return {
81
+ guildId: readStringParam(params, "guildId", { required: true }),
82
+ channelId: readStringParam(params, "channelId", { required: true }),
83
+ parentId: parentId === undefined ? undefined : parentId,
84
+ position: readNumberParam(params, "position", { integer: true }) ?? undefined,
85
+ };
86
+ }
@@ -0,0 +1,87 @@
1
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import { createDiscordActionGate } from "../accounts.js";
3
+ import { readStringParam, type AutoBotConfig } from "../runtime-api.js";
4
+ import { handleDiscordGuildAction } from "./runtime.guild.js";
5
+ import { handleDiscordMessagingAction } from "./runtime.messaging.js";
6
+ import { handleDiscordModerationAction } from "./runtime.moderation.js";
7
+ import { handleDiscordPresenceAction } from "./runtime.presence.js";
8
+
9
+ const messagingActions = new Set([
10
+ "react",
11
+ "reactions",
12
+ "sticker",
13
+ "poll",
14
+ "permissions",
15
+ "fetchMessage",
16
+ "readMessages",
17
+ "sendMessage",
18
+ "editMessage",
19
+ "deleteMessage",
20
+ "threadCreate",
21
+ "threadList",
22
+ "threadReply",
23
+ "pinMessage",
24
+ "unpinMessage",
25
+ "listPins",
26
+ "searchMessages",
27
+ ]);
28
+
29
+ const guildActions = new Set([
30
+ "memberInfo",
31
+ "roleInfo",
32
+ "emojiList",
33
+ "emojiUpload",
34
+ "stickerUpload",
35
+ "roleAdd",
36
+ "roleRemove",
37
+ "channelInfo",
38
+ "channelList",
39
+ "voiceStatus",
40
+ "eventList",
41
+ "eventCreate",
42
+ "channelCreate",
43
+ "channelEdit",
44
+ "channelDelete",
45
+ "channelMove",
46
+ "categoryCreate",
47
+ "categoryEdit",
48
+ "categoryDelete",
49
+ "channelPermissionSet",
50
+ "channelPermissionRemove",
51
+ ]);
52
+
53
+ const moderationActions = new Set(["timeout", "kick", "ban"]);
54
+
55
+ const presenceActions = new Set(["setPresence"]);
56
+
57
+ export async function handleDiscordAction(
58
+ params: Record<string, unknown>,
59
+ cfg: AutoBotConfig,
60
+ options?: {
61
+ mediaAccess?: {
62
+ localRoots?: readonly string[];
63
+ readFile?: (filePath: string) => Promise<Buffer>;
64
+ workspaceDir?: string;
65
+ };
66
+ mediaLocalRoots?: readonly string[];
67
+ mediaReadFile?: (filePath: string) => Promise<Buffer>;
68
+ },
69
+ ): Promise<AgentToolResult<unknown>> {
70
+ const action = readStringParam(params, "action", { required: true });
71
+ const accountId = readStringParam(params, "accountId");
72
+ const isActionEnabled = createDiscordActionGate({ cfg, accountId });
73
+
74
+ if (messagingActions.has(action)) {
75
+ return await handleDiscordMessagingAction(action, params, isActionEnabled, cfg, options);
76
+ }
77
+ if (guildActions.has(action)) {
78
+ return await handleDiscordGuildAction(action, params, isActionEnabled, cfg, options);
79
+ }
80
+ if (moderationActions.has(action)) {
81
+ return await handleDiscordModerationAction(action, params, isActionEnabled, cfg);
82
+ }
83
+ if (presenceActions.has(action)) {
84
+ return await handleDiscordPresenceAction(action, params, isActionEnabled);
85
+ }
86
+ throw new Error(`Unknown action: ${action}`);
87
+ }
package/src/api.ts ADDED
@@ -0,0 +1,219 @@
1
+ import { resolveFetch } from "autobot/plugin-sdk/fetch-runtime";
2
+ import {
3
+ resolveRetryConfig,
4
+ retryAsync,
5
+ type RetryConfig,
6
+ } from "autobot/plugin-sdk/retry-runtime";
7
+ import { isDiscordHtmlResponseBody, summarizeDiscordResponseBody } from "./error-body.js";
8
+
9
+ const DISCORD_API_BASE = "https://discord.com/api/v10";
10
+ const DISCORD_API_RETRY_DEFAULTS = {
11
+ attempts: 3,
12
+ minDelayMs: 500,
13
+ maxDelayMs: 5 * 60_000,
14
+ jitter: 0.1,
15
+ };
16
+ const DISCORD_API_429_FALLBACK_RETRY_AFTER_SECONDS = 60;
17
+
18
+ type DiscordApiErrorPayload = {
19
+ message?: string;
20
+ retry_after?: number;
21
+ code?: number;
22
+ global?: boolean;
23
+ };
24
+
25
+ function parseDiscordApiErrorPayload(text: string): DiscordApiErrorPayload | null {
26
+ const trimmed = text.trim();
27
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
28
+ return null;
29
+ }
30
+ try {
31
+ const payload = JSON.parse(trimmed);
32
+ if (payload && typeof payload === "object") {
33
+ return payload as DiscordApiErrorPayload;
34
+ }
35
+ } catch {
36
+ return null;
37
+ }
38
+ return null;
39
+ }
40
+
41
+ function parseRetryAfterSeconds(text: string, response: Response): number | undefined {
42
+ const payload = parseDiscordApiErrorPayload(text);
43
+ const retryAfter =
44
+ payload && typeof payload.retry_after === "number" && Number.isFinite(payload.retry_after)
45
+ ? payload.retry_after
46
+ : undefined;
47
+ if (retryAfter !== undefined) {
48
+ return retryAfter;
49
+ }
50
+ const header = response.headers.get("Retry-After");
51
+ if (!header) {
52
+ return undefined;
53
+ }
54
+ const parsed = Number(header);
55
+ if (Number.isFinite(parsed) && parsed >= 0) {
56
+ return parsed;
57
+ }
58
+ const retryAt = Date.parse(header);
59
+ if (!Number.isFinite(retryAt)) {
60
+ return undefined;
61
+ }
62
+ return Math.max(0, (retryAt - Date.now()) / 1000);
63
+ }
64
+
65
+ function formatRetryAfterSeconds(value: number | undefined): string | undefined {
66
+ if (value === undefined || !Number.isFinite(value) || value < 0) {
67
+ return undefined;
68
+ }
69
+ const rounded = value < 10 ? value.toFixed(1) : Math.round(value).toString();
70
+ return `${rounded}s`;
71
+ }
72
+
73
+ function formatDiscordApiErrorText(text: string, response: Response): string | undefined {
74
+ const trimmed = text.trim();
75
+ if (!trimmed) {
76
+ return undefined;
77
+ }
78
+ const payload = parseDiscordApiErrorPayload(trimmed);
79
+ if (!payload) {
80
+ const looksJson = trimmed.startsWith("{") && trimmed.endsWith("}");
81
+ if (looksJson) {
82
+ return "unknown error";
83
+ }
84
+ const summary = summarizeDiscordResponseBody(trimmed);
85
+ if (isDiscordHtmlResponseBody(trimmed, response.headers.get("content-type"))) {
86
+ if (!summary) {
87
+ return response.status === 429 ? "rate limited by Discord upstream" : undefined;
88
+ }
89
+ return response.status === 429 ? `rate limited by Discord upstream: ${summary}` : summary;
90
+ }
91
+ return summary;
92
+ }
93
+ const message =
94
+ typeof payload.message === "string" && payload.message.trim()
95
+ ? payload.message.trim()
96
+ : "unknown error";
97
+ const retryAfter = formatRetryAfterSeconds(
98
+ typeof payload.retry_after === "number" ? payload.retry_after : undefined,
99
+ );
100
+ return retryAfter ? `${message} (retry after ${retryAfter})` : message;
101
+ }
102
+
103
+ export class DiscordApiError extends Error {
104
+ status: number;
105
+ retryAfter?: number;
106
+
107
+ constructor(message: string, status: number, retryAfter?: number) {
108
+ super(message);
109
+ this.status = status;
110
+ this.retryAfter = retryAfter;
111
+ }
112
+ }
113
+
114
+ function getDiscordApiRetryAfterMs(
115
+ err: unknown,
116
+ retryConfig: Required<RetryConfig>,
117
+ ): number | undefined {
118
+ if (!(err instanceof DiscordApiError) || typeof err.retryAfter !== "number") {
119
+ return undefined;
120
+ }
121
+ return Math.min(Math.max(0, err.retryAfter * 1000), retryConfig.maxDelayMs);
122
+ }
123
+
124
+ type DiscordFetchOptions = {
125
+ retry?: RetryConfig;
126
+ label?: string;
127
+ };
128
+
129
+ type DiscordApiRequestOptions = DiscordFetchOptions & {
130
+ body?: unknown;
131
+ fetcher?: typeof fetch;
132
+ headers?: Record<string, string>;
133
+ method?: string;
134
+ signal?: AbortSignal;
135
+ timeoutMs?: number;
136
+ };
137
+
138
+ function normalizeDiscordRequestBody(body: unknown, headers: Headers): BodyInit | null | undefined {
139
+ if (body === undefined) {
140
+ return undefined;
141
+ }
142
+ if (
143
+ typeof body === "string" ||
144
+ body instanceof Blob ||
145
+ body instanceof FormData ||
146
+ body instanceof URLSearchParams ||
147
+ body instanceof ArrayBuffer
148
+ ) {
149
+ return body;
150
+ }
151
+ headers.set("Content-Type", headers.get("Content-Type") ?? "application/json");
152
+ return JSON.stringify(body);
153
+ }
154
+
155
+ function resolveDiscordRequestSignal(options: DiscordApiRequestOptions) {
156
+ if (options.signal || typeof options.timeoutMs !== "number") {
157
+ return options.signal;
158
+ }
159
+ return AbortSignal.timeout(options.timeoutMs);
160
+ }
161
+
162
+ export async function requestDiscord<T>(
163
+ path: string,
164
+ token: string,
165
+ options?: DiscordApiRequestOptions,
166
+ ): Promise<T> {
167
+ const fetchImpl = resolveFetch(options?.fetcher ?? fetch);
168
+ if (!fetchImpl) {
169
+ throw new Error("fetch is not available");
170
+ }
171
+
172
+ const retryConfig = resolveRetryConfig(DISCORD_API_RETRY_DEFAULTS, options?.retry);
173
+ return retryAsync(
174
+ async () => {
175
+ const headers = new Headers(options?.headers);
176
+ headers.set("Authorization", `Bot ${token}`);
177
+ const body = normalizeDiscordRequestBody(options?.body, headers);
178
+ const res = await fetchImpl(`${DISCORD_API_BASE}${path}`, {
179
+ method: options?.method ?? (body === undefined ? "GET" : "POST"),
180
+ headers,
181
+ body,
182
+ signal: resolveDiscordRequestSignal(options ?? {}),
183
+ });
184
+ const text = await res.text().catch(() => "");
185
+ if (!res.ok) {
186
+ const detail = formatDiscordApiErrorText(text, res);
187
+ const suffix = detail ? `: ${detail}` : "";
188
+ const retryAfter =
189
+ res.status === 429
190
+ ? (parseRetryAfterSeconds(text, res) ?? DISCORD_API_429_FALLBACK_RETRY_AFTER_SECONDS)
191
+ : undefined;
192
+ throw new DiscordApiError(
193
+ `Discord API ${path} failed (${res.status})${suffix}`,
194
+ res.status,
195
+ retryAfter,
196
+ );
197
+ }
198
+ if (!text.trim()) {
199
+ return undefined as T;
200
+ }
201
+ return JSON.parse(text) as T;
202
+ },
203
+ {
204
+ ...retryConfig,
205
+ label: options?.label ?? path,
206
+ shouldRetry: (err) => err instanceof DiscordApiError && err.status === 429,
207
+ retryAfterMs: (err) => getDiscordApiRetryAfterMs(err, retryConfig),
208
+ },
209
+ );
210
+ }
211
+
212
+ export async function fetchDiscord<T>(
213
+ path: string,
214
+ token: string,
215
+ fetcher: typeof fetch = fetch,
216
+ options?: DiscordFetchOptions,
217
+ ): Promise<T> {
218
+ return await requestDiscord<T>(path, token, { ...options, fetcher, method: "GET" });
219
+ }