@went.tf/discord-bot-framework 2.5.0 → 2.7.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.
@@ -0,0 +1,35 @@
1
+ import { Client, ClientOptions } from 'discord.js';
2
+ export interface CreateWebhookOnlyClientOptions extends Omit<ClientOptions, 'intents'> {
3
+ /** No gateway connection is ever opened, so no intents actually apply - defaults to `[]`. */
4
+ intents?: ClientOptions['intents'];
5
+ token: string;
6
+ }
7
+ /**
8
+ * Builds a discord.js `Client` for webhook (HTTP Interactions Endpoint) mode:
9
+ * fully public API (`new Client()` + `Client#rest.setToken()`, both documented),
10
+ * but deliberately **never calls `Client#login()`**, since `login()`
11
+ * unconditionally opens a gateway WebSocket connection
12
+ * (`this.ws.connect()`, no way to opt out) - the entire reason a bot would
13
+ * reach for `./webhook` in the first place.
14
+ *
15
+ * The resulting client's `rest` is fully authenticated (same
16
+ * `rest.setToken()` call `login()` itself makes), so `Interaction#reply()`/
17
+ * `deferReply()`/`editReply()`/etc. work normally - they only ever call
18
+ * `this.client.rest` against `Routes.interactionCallback()`, with no
19
+ * `client.application`/gateway dependency. `client.guilds`/`client.channels`
20
+ * caches stay permanently empty, though - see `interactionFromWebhookPayload`'s
21
+ * doc comment for what that does and doesn't affect.
22
+ *
23
+ * Typed as `Client<true>` (discord.js's "ready" client) rather than the more
24
+ * literally-accurate `Client<false>`, purely so the result can be passed to
25
+ * `interactionFromWebhookPayload`/`dispatch*`/`createInteractionRouter`
26
+ * without a cast at every call site - every discord.js interaction class's
27
+ * own constructor requires `Client<true>` regardless of whether the client
28
+ * actually went through the gateway ready lifecycle. This client never does
29
+ * (there's no gateway connection to become ready on), so anything gated by
30
+ * `Ready` at the type level but genuinely absent at runtime -
31
+ * `client.user`/`client.application` chief among them - will be `null`
32
+ * despite what its type claims; avoid touching those in webhook mode.
33
+ */
34
+ export declare function createWebhookOnlyClient(options: CreateWebhookOnlyClientOptions): Client<true>;
35
+ //# sourceMappingURL=create-webhook-only-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-webhook-only-client.d.ts","sourceRoot":"","sources":["../../src/webhook/create-webhook-only-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,8BAA+B,SAAQ,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC;IACpF,6FAA6F;IAC7F,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,8BAA8B,GAAG,MAAM,CAAC,IAAI,CAAC,CAM7F"}
@@ -0,0 +1,36 @@
1
+ import { Client } from 'discord.js';
2
+ /**
3
+ * Builds a discord.js `Client` for webhook (HTTP Interactions Endpoint) mode:
4
+ * fully public API (`new Client()` + `Client#rest.setToken()`, both documented),
5
+ * but deliberately **never calls `Client#login()`**, since `login()`
6
+ * unconditionally opens a gateway WebSocket connection
7
+ * (`this.ws.connect()`, no way to opt out) - the entire reason a bot would
8
+ * reach for `./webhook` in the first place.
9
+ *
10
+ * The resulting client's `rest` is fully authenticated (same
11
+ * `rest.setToken()` call `login()` itself makes), so `Interaction#reply()`/
12
+ * `deferReply()`/`editReply()`/etc. work normally - they only ever call
13
+ * `this.client.rest` against `Routes.interactionCallback()`, with no
14
+ * `client.application`/gateway dependency. `client.guilds`/`client.channels`
15
+ * caches stay permanently empty, though - see `interactionFromWebhookPayload`'s
16
+ * doc comment for what that does and doesn't affect.
17
+ *
18
+ * Typed as `Client<true>` (discord.js's "ready" client) rather than the more
19
+ * literally-accurate `Client<false>`, purely so the result can be passed to
20
+ * `interactionFromWebhookPayload`/`dispatch*`/`createInteractionRouter`
21
+ * without a cast at every call site - every discord.js interaction class's
22
+ * own constructor requires `Client<true>` regardless of whether the client
23
+ * actually went through the gateway ready lifecycle. This client never does
24
+ * (there's no gateway connection to become ready on), so anything gated by
25
+ * `Ready` at the type level but genuinely absent at runtime -
26
+ * `client.user`/`client.application` chief among them - will be `null`
27
+ * despite what its type claims; avoid touching those in webhook mode.
28
+ */
29
+ export function createWebhookOnlyClient(options) {
30
+ const { token, intents = [], ...clientOptions } = options;
31
+ const client = new Client({ intents, ...clientOptions });
32
+ client.token = token;
33
+ client.rest.setToken(token);
34
+ return client;
35
+ }
36
+ //# sourceMappingURL=create-webhook-only-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-webhook-only-client.js","sourceRoot":"","sources":["../../src/webhook/create-webhook-only-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAiB,MAAM,YAAY,CAAC;AAQnD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAuC;IAC7E,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAC;IAC1D,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,aAAa,EAAE,CAAC,CAAC;IACzD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5B,OAAO,MAAsB,CAAC;AAChC,CAAC"}
@@ -7,12 +7,19 @@ export interface WebhookInteractionRequest {
7
7
  timestamp: string | undefined;
8
8
  /** The raw (unparsed) request body, exactly as received - required for signature verification. */
9
9
  rawBody: string | Buffer;
10
+ /**
11
+ * Request headers, lowercase-keyed (matches Node's `http.IncomingMessage`,
12
+ * Express, Fastify, ... convention). Optional - only used to enrich the
13
+ * diagnostic `debug` log emitted when signature verification fails,
14
+ * verification/dispatch behavior is identical either way.
15
+ */
16
+ headers?: Record<string, string | undefined>;
10
17
  }
11
18
  export interface WebhookInteractionResponse {
12
19
  status: number;
13
20
  body: APIInteractionResponse | {
14
21
  error: string;
15
- };
22
+ } | Record<string, never>;
16
23
  }
17
24
  export interface HandleWebhookInteractionRequestOptions {
18
25
  /** The bot's Ed25519 application public key, from the Discord developer portal, as a hex string. */
@@ -24,8 +31,18 @@ export interface HandleWebhookInteractionRequestOptions {
24
31
  * Discord as the interaction's initial response (e.g. a `CHANNEL_MESSAGE_WITH_SOURCE`
25
32
  * or a `DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE` to be followed up on later via
26
33
  * `createWebhookInteractionResponder`).
34
+ *
35
+ * If you instead bridge into a real discord.js interaction via
36
+ * `interactionFromWebhookPayload` and dispatch it through
37
+ * `dispatchChatInputCommand`/`createInteractionRouter`, the handler's own
38
+ * `.reply()`/`.deferReply()` call already sends the actual response via
39
+ * REST before this resolves - return nothing (`void`) in that case, and a
40
+ * generic 200 ack is sent back to Discord's original request instead.
41
+ * **This dual response path (a real REST call already made mid-handler,
42
+ * plus a separate bare HTTP ack to the original webhook POST) is unverified
43
+ * against live Discord traffic** - see this module's CLAUDE.md entry.
27
44
  */
28
- onInteraction: (interaction: APIInteraction) => APIInteractionResponse | Promise<APIInteractionResponse>;
45
+ onInteraction: (interaction: APIInteraction) => APIInteractionResponse | void | Promise<APIInteractionResponse | void>;
29
46
  }
30
47
  /**
31
48
  * Framework-agnostic core of an HTTP Interactions endpoint: verifies the
@@ -36,6 +53,15 @@ export interface HandleWebhookInteractionRequestOptions {
36
53
  * `Request`/etc., so it can be wired into any HTTP layer a bot already uses -
37
54
  * this package doesn't otherwise depend on one, and picking one here would be
38
55
  * a bigger commitment than this function needs to make.
56
+ *
57
+ * On a signature-verification failure, logs a `debug`-level diagnostic
58
+ * (source IP, user-agent, whether the signature/timestamp headers were even
59
+ * present, signature/body length) alongside the existing `warn` - a public
60
+ * webhook endpoint draws routine internet-scanner noise as well as genuine
61
+ * misconfiguration, and this is what tells the two apart after the fact
62
+ * without every consuming bot re-implementing the same wrapper around this
63
+ * call. Requires `request.headers` (optional) to populate the IP/user-agent
64
+ * fields; omit it and those two just come back `undefined`.
39
65
  */
40
66
  export declare function handleWebhookInteractionRequest(request: WebhookInteractionRequest, options: HandleWebhookInteractionRequestOptions): Promise<WebhookInteractionResponse>;
41
67
  //# sourceMappingURL=handle-webhook-interaction-request.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"handle-webhook-interaction-request.d.ts","sourceRoot":"","sources":["../../src/webhook/handle-webhook-interaction-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAA4C,MAAM,uBAAuB,CAAC;AACzH,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGpD,MAAM,WAAW,yBAAyB;IACxC,kDAAkD;IAClD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,oDAAoD;IACpD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,kGAAkG;IAClG,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,sBAAsB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD;AAED,MAAM,WAAW,sCAAsC;IACrD,oGAAoG;IACpG,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;;OAMG;IACH,aAAa,EAAE,CAAC,WAAW,EAAE,cAAc,KAAK,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC1G;AAED;;;;;;;;;GASG;AACH,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,yBAAyB,EAClC,OAAO,EAAE,sCAAsC,GAC9C,OAAO,CAAC,0BAA0B,CAAC,CAiCrC"}
1
+ {"version":3,"file":"handle-webhook-interaction-request.d.ts","sourceRoot":"","sources":["../../src/webhook/handle-webhook-interaction-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAA4C,MAAM,uBAAuB,CAAC;AACzH,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGpD,MAAM,WAAW,yBAAyB;IACxC,kDAAkD;IAClD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,oDAAoD;IACpD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,kGAAkG;IAClG,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,sBAAsB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CAC1E;AAED,MAAM,WAAW,sCAAsC;IACrD,oGAAoG;IACpG,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;;;;;;;;;;;;OAgBG;IACH,aAAa,EAAE,CAAC,WAAW,EAAE,cAAc,KAAK,sBAAsB,GAAG,IAAI,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;CACxH;AAmBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,yBAAyB,EAClC,OAAO,EAAE,sCAAsC,GAC9C,OAAO,CAAC,0BAA0B,CAAC,CAyCrC"}
@@ -1,5 +1,21 @@
1
1
  import { InteractionResponseType, InteractionType } from 'discord-api-types/v10';
2
2
  import { verifyInteractionRequest } from './verify-interaction-request.js';
3
+ /**
4
+ * Best-effort source IP for the diagnostic log below - checks the headers a
5
+ * request sitting behind a reverse proxy (Cloudflare, nginx, ...) actually
6
+ * carries the real client IP in, since `req.socket.remoteAddress`-equivalent
7
+ * info is proxy-terminated and not available from headers/body alone. Not
8
+ * used for anything security-sensitive (signature verification never trusts
9
+ * client-supplied IP data) - purely to help a bot operator eyeball whether a
10
+ * batch of rejections looks like internet-scanner noise or something worth a
11
+ * closer look.
12
+ */
13
+ function resolveSourceIp(headers) {
14
+ if (!headers) {
15
+ return undefined;
16
+ }
17
+ return headers['cf-connecting-ip'] ?? headers['x-real-ip'] ?? headers['x-forwarded-for']?.split(',')[0]?.trim();
18
+ }
3
19
  /**
4
20
  * Framework-agnostic core of an HTTP Interactions endpoint: verifies the
5
21
  * request's Ed25519 signature, answers Discord's PING (type 1) validation
@@ -9,6 +25,15 @@ import { verifyInteractionRequest } from './verify-interaction-request.js';
9
25
  * `Request`/etc., so it can be wired into any HTTP layer a bot already uses -
10
26
  * this package doesn't otherwise depend on one, and picking one here would be
11
27
  * a bigger commitment than this function needs to make.
28
+ *
29
+ * On a signature-verification failure, logs a `debug`-level diagnostic
30
+ * (source IP, user-agent, whether the signature/timestamp headers were even
31
+ * present, signature/body length) alongside the existing `warn` - a public
32
+ * webhook endpoint draws routine internet-scanner noise as well as genuine
33
+ * misconfiguration, and this is what tells the two apart after the fact
34
+ * without every consuming bot re-implementing the same wrapper around this
35
+ * call. Requires `request.headers` (optional) to populate the IP/user-agent
36
+ * fields; omit it and those two just come back `undefined`.
12
37
  */
13
38
  export async function handleWebhookInteractionRequest(request, options) {
14
39
  const { publicKey, logger, onInteraction } = options;
@@ -20,6 +45,14 @@ export async function handleWebhookInteractionRequest(request, options) {
20
45
  });
21
46
  if (!isValid) {
22
47
  logger.warn('Rejected webhook interaction request with invalid signature');
48
+ logger.debug('Webhook interaction signature-rejection diagnostics', {
49
+ sourceIp: resolveSourceIp(request.headers),
50
+ userAgent: request.headers?.['user-agent'],
51
+ hasSignatureHeader: request.signature !== undefined,
52
+ hasTimestampHeader: request.timestamp !== undefined,
53
+ signatureLength: request.signature?.length ?? 0,
54
+ bodyLength: Buffer.byteLength(request.rawBody),
55
+ });
23
56
  return { status: 401, body: { error: 'Invalid request signature' } };
24
57
  }
25
58
  let interaction;
@@ -35,7 +68,7 @@ export async function handleWebhookInteractionRequest(request, options) {
35
68
  }
36
69
  try {
37
70
  const body = await onInteraction(interaction);
38
- return { status: 200, body };
71
+ return { status: 200, body: body ?? {} };
39
72
  }
40
73
  catch (e) {
41
74
  logger.error('Error while handling webhook interaction', e);
@@ -1 +1 @@
1
- {"version":3,"file":"handle-webhook-interaction-request.js","sourceRoot":"","sources":["../../src/webhook/handle-webhook-interaction-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0C,uBAAuB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEzH,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AA8B3E;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,OAAkC,EAClC,OAA+C;IAE/C,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;IAErD,MAAM,OAAO,GAAG,wBAAwB,CAAC;QACvC,SAAS;QACT,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CAAC;IACH,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;QAC3E,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,2BAA2B,EAAE,EAAE,CAAC;IACvE,CAAC;IAED,IAAI,WAA2B,CAAC;IAChC,IAAI,CAAC;QACH,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAmB,CAAC;IACzE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,0DAA0D,EAAE,CAAC,CAAC,CAAC;QAC5E,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAC;IAC/D,CAAC;IAED,IAAI,WAAW,CAAC,IAAI,KAAK,eAAe,CAAC,IAAI,EAAE,CAAC;QAC9C,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,uBAAuB,CAAC,IAAI,EAAE,EAAE,CAAC;IACvE,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,CAAC;QAC9C,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAC/B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,0CAA0C,EAAE,CAAC,CAAC,CAAC;QAC5D,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,CAAC;IAC5D,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"handle-webhook-interaction-request.js","sourceRoot":"","sources":["../../src/webhook/handle-webhook-interaction-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0C,uBAAuB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEzH,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AA+C3E;;;;;;;;;GASG;AACH,SAAS,eAAe,CAAC,OAAuD;IAC9E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,OAAO,CAAC,kBAAkB,CAAC,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,iBAAiB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;AAClH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,OAAkC,EAClC,OAA+C;IAE/C,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;IAErD,MAAM,OAAO,GAAG,wBAAwB,CAAC;QACvC,SAAS;QACT,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CAAC;IACH,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;QAC3E,MAAM,CAAC,KAAK,CAAC,qDAAqD,EAAE;YAClE,QAAQ,EAAE,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC;YAC1C,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,YAAY,CAAC;YAC1C,kBAAkB,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS;YACnD,kBAAkB,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS;YACnD,eAAe,EAAE,OAAO,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;YAC/C,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC;SAC/C,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,2BAA2B,EAAE,EAAE,CAAC;IACvE,CAAC;IAED,IAAI,WAA2B,CAAC;IAChC,IAAI,CAAC;QACH,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAmB,CAAC;IACzE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,0DAA0D,EAAE,CAAC,CAAC,CAAC;QAC5E,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAC;IAC/D,CAAC;IAED,IAAI,WAAW,CAAC,IAAI,KAAK,eAAe,CAAC,IAAI,EAAE,CAAC;QAC9C,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,uBAAuB,CAAC,IAAI,EAAE,EAAE,CAAC;IACvE,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,CAAC;QAC9C,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC;IAC3C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,0CAA0C,EAAE,CAAC,CAAC,CAAC;QAC5D,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,CAAC;IAC5D,CAAC;AACH,CAAC"}
@@ -1,4 +1,6 @@
1
1
  export * from './verify-interaction-request.js';
2
2
  export * from './handle-webhook-interaction-request.js';
3
3
  export * from './create-webhook-interaction-responder.js';
4
+ export * from './create-webhook-only-client.js';
5
+ export * from './interaction-from-webhook-payload.js';
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAIA,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAKA,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,iCAAiC,CAAC;AAChD,cAAc,uCAAuC,CAAC"}
@@ -1,8 +1,11 @@
1
1
  // Experimental: unvalidated against a real bot migration yet. See this
2
2
  // module's design-decision entry in CLAUDE.md before relying on it, in
3
- // particular that `createWebhookInteractionResponder` is not a drop-in
4
- // discord.js `Interaction` shim.
3
+ // particular that the initial-response HTTP semantics documented on
4
+ // `handleWebhookInteractionRequest`/`interactionFromWebhookPayload` are
5
+ // unverified against live Discord traffic.
5
6
  export * from './verify-interaction-request.js';
6
7
  export * from './handle-webhook-interaction-request.js';
7
8
  export * from './create-webhook-interaction-responder.js';
9
+ export * from './create-webhook-only-client.js';
10
+ export * from './interaction-from-webhook-payload.js';
8
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,uEAAuE;AACvE,uEAAuE;AACvE,iCAAiC;AACjC,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,uEAAuE;AACvE,oEAAoE;AACpE,wEAAwE;AACxE,2CAA2C;AAC3C,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,iCAAiC,CAAC;AAChD,cAAc,uCAAuC,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { APIInteraction } from 'discord-api-types/v10';
2
+ import { Client, Interaction } from 'discord.js';
3
+ /**
4
+ * Reconstructs a genuine discord.js `Interaction` instance from a raw
5
+ * webhook-delivered interaction payload, so existing gateway-shaped handler
6
+ * code (`interaction.reply()`, `interaction.options.getString()`, etc.) can
7
+ * run completely unmodified in webhook mode - built on the same `client`
8
+ * this instance's `.reply()`/`.editReply()`/etc. will make real REST calls
9
+ * through (see `createWebhookOnlyClient`). Mirrors discord.js's own
10
+ * `InteractionCreateAction.handle()` class-picking switch (which does the
11
+ * same thing for gateway-delivered payloads) exactly, one discord-api-types
12
+ * version at a time - if discord.js adds a new interaction/component type,
13
+ * this needs a matching new case.
14
+ *
15
+ * **Two real gaps versus a gateway-delivered interaction, both from having
16
+ * no populated gateway cache:**
17
+ * - `.guild` is always `null` (`client.guilds.cache` is permanently empty in
18
+ * webhook mode, and unlike `.channel`, nothing pre-caches a partial for
19
+ * it).
20
+ * - `.channel` is `null` unless the caller pre-caches the interaction's
21
+ * inline partial channel data into `client.channels` first - this
22
+ * function does not attempt that itself (discord.js's own equivalent step,
23
+ * `Action#getChannel`, is a private internal not exposed for reuse).
24
+ *
25
+ * `.member` degrades gracefully instead of breaking: it falls back to the
26
+ * raw `APIInteractionGuildMember` POJO instead of a real `GuildMember`
27
+ * class instance when `.guild` is `null`, so plain property reads (`.roles`,
28
+ * `.nick`, ...) keep working - only actual `GuildMember` methods would not.
29
+ */
30
+ export declare function interactionFromWebhookPayload(client: Client<true>, data: APIInteraction): Interaction;
31
+ //# sourceMappingURL=interaction-from-webhook-payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interaction-from-webhook-payload.d.ts","sourceRoot":"","sources":["../../src/webhook/interaction-from-webhook-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA0D,MAAM,uBAAuB,CAAC;AAC/G,OAAO,EAKL,MAAM,EACN,WAAW,EASZ,MAAM,YAAY,CAAC;AAwBpB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,GAAG,WAAW,CAuCrG"}
@@ -0,0 +1,91 @@
1
+ import { ApplicationCommandType, ComponentType, InteractionType } from 'discord-api-types/v10';
2
+ import { AutocompleteInteraction, ButtonInteraction, ChannelSelectMenuInteraction, ChatInputCommandInteraction, MentionableSelectMenuInteraction, MessageContextMenuCommandInteraction, ModalSubmitInteraction, PrimaryEntryPointCommandInteraction, RoleSelectMenuInteraction, StringSelectMenuInteraction, UserContextMenuCommandInteraction, UserSelectMenuInteraction, } from 'discord.js';
3
+ /**
4
+ * Constructs a discord.js interaction class directly against its own real
5
+ * `(client, data)` constructor - checked against every class this module
6
+ * uses (discord.js@14.26.x), each one's constructor is either `protected`
7
+ * (blocks external construction, but not a subclass's `super()` - not used
8
+ * here since it'd be a second, inconsistent mechanism just for those) or, for
9
+ * `ButtonInteraction`/`ModalSubmitInteraction`/the 5 select-menu classes,
10
+ * fully `private` (blocks even subclassing) with a narrower `data` parameter
11
+ * type (`APIMessageButtonInteractionData`, `APIModalSubmitInteraction`,
12
+ * `APIMessage*SelectInteractionData`) that isn't part of discord.js's public
13
+ * export surface to reference by name anyway. Both are pure TypeScript
14
+ * access-control keywords, erased in the compiled `.js` - this is exactly
15
+ * what discord.js's own `InteractionCreateAction.handle()` does at runtime
16
+ * (`new InteractionClass(client, data)`), just reached through a type cast
17
+ * instead of module-internal privilege, since there's no way to reference a
18
+ * type discord.js itself doesn't export.
19
+ */
20
+ function constructInteraction(InteractionClass, client, data) {
21
+ const Ctor = InteractionClass;
22
+ return new Ctor(client, data);
23
+ }
24
+ /**
25
+ * Reconstructs a genuine discord.js `Interaction` instance from a raw
26
+ * webhook-delivered interaction payload, so existing gateway-shaped handler
27
+ * code (`interaction.reply()`, `interaction.options.getString()`, etc.) can
28
+ * run completely unmodified in webhook mode - built on the same `client`
29
+ * this instance's `.reply()`/`.editReply()`/etc. will make real REST calls
30
+ * through (see `createWebhookOnlyClient`). Mirrors discord.js's own
31
+ * `InteractionCreateAction.handle()` class-picking switch (which does the
32
+ * same thing for gateway-delivered payloads) exactly, one discord-api-types
33
+ * version at a time - if discord.js adds a new interaction/component type,
34
+ * this needs a matching new case.
35
+ *
36
+ * **Two real gaps versus a gateway-delivered interaction, both from having
37
+ * no populated gateway cache:**
38
+ * - `.guild` is always `null` (`client.guilds.cache` is permanently empty in
39
+ * webhook mode, and unlike `.channel`, nothing pre-caches a partial for
40
+ * it).
41
+ * - `.channel` is `null` unless the caller pre-caches the interaction's
42
+ * inline partial channel data into `client.channels` first - this
43
+ * function does not attempt that itself (discord.js's own equivalent step,
44
+ * `Action#getChannel`, is a private internal not exposed for reuse).
45
+ *
46
+ * `.member` degrades gracefully instead of breaking: it falls back to the
47
+ * raw `APIInteractionGuildMember` POJO instead of a real `GuildMember`
48
+ * class instance when `.guild` is `null`, so plain property reads (`.roles`,
49
+ * `.nick`, ...) keep working - only actual `GuildMember` methods would not.
50
+ */
51
+ export function interactionFromWebhookPayload(client, data) {
52
+ switch (data.type) {
53
+ case InteractionType.ApplicationCommand:
54
+ switch (data.data.type) {
55
+ case ApplicationCommandType.ChatInput:
56
+ return constructInteraction(ChatInputCommandInteraction, client, data);
57
+ case ApplicationCommandType.User:
58
+ return constructInteraction(UserContextMenuCommandInteraction, client, data);
59
+ case ApplicationCommandType.Message:
60
+ return constructInteraction(MessageContextMenuCommandInteraction, client, data);
61
+ case ApplicationCommandType.PrimaryEntryPoint:
62
+ return constructInteraction(PrimaryEntryPointCommandInteraction, client, data);
63
+ default:
64
+ throw new Error(`Unknown application command interaction type: ${data.data.type}`);
65
+ }
66
+ case InteractionType.MessageComponent:
67
+ switch (data.data.component_type) {
68
+ case ComponentType.Button:
69
+ return constructInteraction(ButtonInteraction, client, data);
70
+ case ComponentType.StringSelect:
71
+ return constructInteraction(StringSelectMenuInteraction, client, data);
72
+ case ComponentType.UserSelect:
73
+ return constructInteraction(UserSelectMenuInteraction, client, data);
74
+ case ComponentType.RoleSelect:
75
+ return constructInteraction(RoleSelectMenuInteraction, client, data);
76
+ case ComponentType.MentionableSelect:
77
+ return constructInteraction(MentionableSelectMenuInteraction, client, data);
78
+ case ComponentType.ChannelSelect:
79
+ return constructInteraction(ChannelSelectMenuInteraction, client, data);
80
+ default:
81
+ throw new Error(`Unknown message component interaction type: ${data.data.component_type}`);
82
+ }
83
+ case InteractionType.ApplicationCommandAutocomplete:
84
+ return constructInteraction(AutocompleteInteraction, client, data);
85
+ case InteractionType.ModalSubmit:
86
+ return constructInteraction(ModalSubmitInteraction, client, data);
87
+ default:
88
+ throw new Error(`Unknown interaction type: ${data.type}`);
89
+ }
90
+ }
91
+ //# sourceMappingURL=interaction-from-webhook-payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interaction-from-webhook-payload.js","sourceRoot":"","sources":["../../src/webhook/interaction-from-webhook-payload.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@went.tf/discord-bot-framework",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Shared framework for building Discord bots with discord.js: logging, env validation, interaction dispatch, API client, and optional Postgres/i18next helpers",
5
5
  "author": "WentTheFox <mail@went.tf> (https://github.com/WentTheFox)",
6
6
  "license": "MIT",