@hogsend/plugin-discord 0.25.0 → 0.27.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hogsend/plugin-discord",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@hogsend/engine": "^0.25.0"
26
+ "@hogsend/engine": "^0.27.0"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "discord.js": ">=14.0.0"
@@ -1,4 +1,8 @@
1
1
  export { type PostToIngressArgs, postToIngress } from "./ingress.js";
2
+ export {
3
+ LINK_VERIFY_COMMANDS,
4
+ registerSlashCommands,
5
+ } from "./register-commands.js";
2
6
  export { createDiscordRuntime } from "./runtime.js";
3
7
  export {
4
8
  createDiscordGatewayWorker,
@@ -0,0 +1,68 @@
1
+ import { DISCORD_API_BASE } from "../constants.js";
2
+
3
+ /**
4
+ * The slash commands the native `/link` → `/verify` identify loop needs.
5
+ * `/link` carries no options (it opens a private modal that collects the
6
+ * email); `/verify` takes the emailed code (STRING option type = 3).
7
+ */
8
+ export const LINK_VERIFY_COMMANDS = [
9
+ {
10
+ name: "link",
11
+ description: "Link your email to your Discord account",
12
+ },
13
+ {
14
+ name: "verify",
15
+ description: "Verify a code we emailed you (fallback)",
16
+ options: [
17
+ {
18
+ name: "code",
19
+ description: "The code from your email",
20
+ type: 3,
21
+ required: true,
22
+ },
23
+ ],
24
+ },
25
+ ] as const;
26
+
27
+ /**
28
+ * Idempotently register `/link` + `/verify` GLOBALLY (a PUT replaces the full
29
+ * command set), so they appear in EVERY guild the bot is in — the
30
+ * one-bot-many-guilds story (a guild-scoped PUT would only register in a single
31
+ * guild and silently break multi-guild). The inline gateway runtime calls this
32
+ * once the socket is ready, eliminating the forgotten manual
33
+ * `discord:register-commands` step; idempotent, so re-running on each lease
34
+ * acquisition (and after a token rotation) is safe.
35
+ *
36
+ * Best-effort: a non-2xx logs the HTTP STATUS ONLY (never the bot token, which
37
+ * Discord's error body can echo) and never throws — the socket is already up.
38
+ */
39
+ export async function registerSlashCommands(args: {
40
+ botToken: string;
41
+ applicationId: string;
42
+ }): Promise<void> {
43
+ try {
44
+ const res = await fetch(
45
+ `${DISCORD_API_BASE}/applications/${args.applicationId}/commands`,
46
+ {
47
+ method: "PUT",
48
+ headers: {
49
+ "Content-Type": "application/json",
50
+ Authorization: `Bot ${args.botToken}`,
51
+ },
52
+ body: JSON.stringify(LINK_VERIFY_COMMANDS),
53
+ },
54
+ );
55
+ if (!res.ok) {
56
+ console.error(
57
+ `discord slash-command registration failed (${res.status})`,
58
+ );
59
+ } else {
60
+ console.log("discord slash-commands registered (/link, /verify)");
61
+ }
62
+ } catch (err) {
63
+ console.error(
64
+ "discord slash-command registration error:",
65
+ err instanceof Error ? err.message : String(err),
66
+ );
67
+ }
68
+ }
@@ -1,6 +1,7 @@
1
1
  import { DISCORD_INTENTS } from "../constants.js";
2
2
  import { DiscordEvents } from "../events.js";
3
3
  import { type PostToIngressResult, postToIngress } from "./ingress.js";
4
+ import { registerSlashCommands } from "./register-commands.js";
4
5
 
5
6
  /**
6
7
  * The long-lived Discord Gateway worker. It is its OWN entrypoint / Railway
@@ -171,6 +172,21 @@ export function createDiscordGatewayWorker(
171
172
  });
172
173
  c.once("ready", () => {
173
174
  console.log("discord gateway worker connected");
175
+ // Idempotently (re)register /link + /verify so the forgotten manual
176
+ // `discord:register-commands` step is gone and the commands self-heal
177
+ // after a token rotation. App id comes from the ready client; GLOBAL
178
+ // registration so they appear in every guild the bot is in.
179
+ const appId = c.application?.id;
180
+ if (appId) {
181
+ void registerSlashCommands({
182
+ botToken: config.botToken,
183
+ applicationId: appId,
184
+ });
185
+ } else {
186
+ console.warn(
187
+ "discord: no application id on ready; skipping command registration",
188
+ );
189
+ }
174
190
  });
175
191
 
176
192
  // Rejects on a bad token or disallowed (un-toggled) privileged intents —