@went.tf/discord-bot-framework 2.4.0 → 2.5.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/README.md +67 -0
- package/build/.tsbuildinfo +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/webhook/create-webhook-interaction-responder.d.ts +33 -0
- package/build/webhook/create-webhook-interaction-responder.d.ts.map +1 -0
- package/build/webhook/create-webhook-interaction-responder.js +25 -0
- package/build/webhook/create-webhook-interaction-responder.js.map +1 -0
- package/build/webhook/handle-webhook-interaction-request.d.ts +41 -0
- package/build/webhook/handle-webhook-interaction-request.d.ts.map +1 -0
- package/build/webhook/handle-webhook-interaction-request.js +45 -0
- package/build/webhook/handle-webhook-interaction-request.js.map +1 -0
- package/build/webhook/index.d.ts +4 -0
- package/build/webhook/index.d.ts.map +1 -0
- package/build/webhook/index.js +8 -0
- package/build/webhook/index.js.map +1 -0
- package/build/webhook/verify-interaction-request.d.ts +22 -0
- package/build/webhook/verify-interaction-request.d.ts.map +1 -0
- package/build/webhook/verify-interaction-request.js +37 -0
- package/build/webhook/verify-interaction-request.js.map +1 -0
- package/package.json +5 -1
package/build/index.d.ts
CHANGED
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -8,5 +8,6 @@ export * from './api-client/index.js';
|
|
|
8
8
|
export * from './interactions/index.js';
|
|
9
9
|
export * from './commands/index.js';
|
|
10
10
|
export * from './client/index.js';
|
|
11
|
+
export * from './webhook/index.js';
|
|
11
12
|
export * from './utils/index.js';
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6CAA6C;AAC7C,sCAAsC;AACtC,wCAAwC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6CAA6C;AAC7C,sCAAsC;AACtC,wCAAwC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { REST } from '@discordjs/rest';
|
|
2
|
+
import { RESTPatchAPIWebhookWithTokenMessageJSONBody, RESTPostAPIWebhookWithTokenJSONBody, RESTPostAPIWebhookWithTokenResult } from 'discord-api-types/v10';
|
|
3
|
+
export interface CreateWebhookInteractionResponderOptions {
|
|
4
|
+
rest: REST;
|
|
5
|
+
applicationId: string;
|
|
6
|
+
/** The responding interaction's own token (`APIInteraction.token`), valid for 15 minutes after receipt. */
|
|
7
|
+
interactionToken: string;
|
|
8
|
+
}
|
|
9
|
+
export interface WebhookInteractionResponder {
|
|
10
|
+
/** `PATCH /webhooks/{application.id}/{interaction.token}/messages/@original` - edits the initial response (including a deferred one). */
|
|
11
|
+
editReply(body: RESTPatchAPIWebhookWithTokenMessageJSONBody): Promise<unknown>;
|
|
12
|
+
/** `POST /webhooks/{application.id}/{interaction.token}` - sends an additional message after the initial response. */
|
|
13
|
+
followUp(body: RESTPostAPIWebhookWithTokenJSONBody): Promise<RESTPostAPIWebhookWithTokenResult>;
|
|
14
|
+
/** `DELETE /webhooks/{application.id}/{interaction.token}/messages/@original` - deletes the initial response. */
|
|
15
|
+
deleteReply(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* REST counterpart to editing/following-up on an interaction once it's
|
|
19
|
+
* already been given its initial response - built on `@discordjs/rest` (an
|
|
20
|
+
* existing peer dependency, same client `createCommandRegistrar` already
|
|
21
|
+
* uses) rather than a live discord.js `Client`, since webhook mode has none.
|
|
22
|
+
*
|
|
23
|
+
* This is *not* a drop-in replacement for discord.js's
|
|
24
|
+
* `Interaction#reply()`/`editReply()`/`options.get*()` surface - it only
|
|
25
|
+
* covers what happens after `handleWebhookInteractionRequest`'s synchronous
|
|
26
|
+
* response (the initial `reply()`/`deferReply()` equivalent is just the
|
|
27
|
+
* `APIInteractionResponse` your `onInteraction` callback returns). Mimicking
|
|
28
|
+
* discord.js's full `Interaction` shape closely enough for existing
|
|
29
|
+
* gateway-based handler code to run unmodified against it is real,
|
|
30
|
+
* unvalidated design work - see CLAUDE.md's `./webhook` design-decision entry.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createWebhookInteractionResponder(options: CreateWebhookInteractionResponderOptions): WebhookInteractionResponder;
|
|
33
|
+
//# sourceMappingURL=create-webhook-interaction-responder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-webhook-interaction-responder.d.ts","sourceRoot":"","sources":["../../src/webhook/create-webhook-interaction-responder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,2CAA2C,EAAE,mCAAmC,EAAE,iCAAiC,EAAU,MAAM,uBAAuB,CAAC;AAEpK,MAAM,WAAW,wCAAwC;IACvD,IAAI,EAAE,IAAI,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,2GAA2G;IAC3G,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,2BAA2B;IAC1C,yIAAyI;IACzI,SAAS,CAAC,IAAI,EAAE,2CAA2C,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/E,sHAAsH;IACtH,QAAQ,CAAC,IAAI,EAAE,mCAAmC,GAAG,OAAO,CAAC,iCAAiC,CAAC,CAAC;IAChG,iHAAiH;IACjH,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,wCAAwC,GAAG,2BAA2B,CAQhI"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Routes } from 'discord-api-types/v10';
|
|
2
|
+
/**
|
|
3
|
+
* REST counterpart to editing/following-up on an interaction once it's
|
|
4
|
+
* already been given its initial response - built on `@discordjs/rest` (an
|
|
5
|
+
* existing peer dependency, same client `createCommandRegistrar` already
|
|
6
|
+
* uses) rather than a live discord.js `Client`, since webhook mode has none.
|
|
7
|
+
*
|
|
8
|
+
* This is *not* a drop-in replacement for discord.js's
|
|
9
|
+
* `Interaction#reply()`/`editReply()`/`options.get*()` surface - it only
|
|
10
|
+
* covers what happens after `handleWebhookInteractionRequest`'s synchronous
|
|
11
|
+
* response (the initial `reply()`/`deferReply()` equivalent is just the
|
|
12
|
+
* `APIInteractionResponse` your `onInteraction` callback returns). Mimicking
|
|
13
|
+
* discord.js's full `Interaction` shape closely enough for existing
|
|
14
|
+
* gateway-based handler code to run unmodified against it is real,
|
|
15
|
+
* unvalidated design work - see CLAUDE.md's `./webhook` design-decision entry.
|
|
16
|
+
*/
|
|
17
|
+
export function createWebhookInteractionResponder(options) {
|
|
18
|
+
const { rest, applicationId, interactionToken } = options;
|
|
19
|
+
return {
|
|
20
|
+
editReply: (body) => rest.patch(Routes.webhookMessage(applicationId, interactionToken, '@original'), { body }),
|
|
21
|
+
followUp: (body) => rest.post(Routes.webhook(applicationId, interactionToken), { body }),
|
|
22
|
+
deleteReply: () => rest.delete(Routes.webhookMessage(applicationId, interactionToken, '@original')),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=create-webhook-interaction-responder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-webhook-interaction-responder.js","sourceRoot":"","sources":["../../src/webhook/create-webhook-interaction-responder.ts"],"names":[],"mappings":"AACA,OAAO,EAAuH,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAkBpK;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iCAAiC,CAAC,OAAiD;IACjG,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC;IAE1D,OAAO;QACL,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,gBAAgB,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;QAC9G,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,gBAAgB,CAAC,EAAE,EAAE,IAAI,EAAE,CAA+C;QACtI,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,gBAAgB,EAAE,WAAW,CAAC,CAAkB;KACrH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { APIInteraction, APIInteractionResponse } from 'discord-api-types/v10';
|
|
2
|
+
import { NestableLogger } from '../logger/types.js';
|
|
3
|
+
export interface WebhookInteractionRequest {
|
|
4
|
+
/** The request's `X-Signature-Ed25519` header. */
|
|
5
|
+
signature: string | undefined;
|
|
6
|
+
/** The request's `X-Signature-Timestamp` header. */
|
|
7
|
+
timestamp: string | undefined;
|
|
8
|
+
/** The raw (unparsed) request body, exactly as received - required for signature verification. */
|
|
9
|
+
rawBody: string | Buffer;
|
|
10
|
+
}
|
|
11
|
+
export interface WebhookInteractionResponse {
|
|
12
|
+
status: number;
|
|
13
|
+
body: APIInteractionResponse | {
|
|
14
|
+
error: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface HandleWebhookInteractionRequestOptions {
|
|
18
|
+
/** The bot's Ed25519 application public key, from the Discord developer portal, as a hex string. */
|
|
19
|
+
publicKey: string;
|
|
20
|
+
logger: NestableLogger;
|
|
21
|
+
/**
|
|
22
|
+
* Called for every interaction that isn't Discord's own PING handshake
|
|
23
|
+
* check. Its return value becomes the HTTP response body sent back to
|
|
24
|
+
* Discord as the interaction's initial response (e.g. a `CHANNEL_MESSAGE_WITH_SOURCE`
|
|
25
|
+
* or a `DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE` to be followed up on later via
|
|
26
|
+
* `createWebhookInteractionResponder`).
|
|
27
|
+
*/
|
|
28
|
+
onInteraction: (interaction: APIInteraction) => APIInteractionResponse | Promise<APIInteractionResponse>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Framework-agnostic core of an HTTP Interactions endpoint: verifies the
|
|
32
|
+
* request's Ed25519 signature, answers Discord's PING (type 1) validation
|
|
33
|
+
* handshake with PONG directly, and otherwise hands the parsed interaction to
|
|
34
|
+
* `onInteraction`. Deliberately takes/returns plain data (raw body + headers
|
|
35
|
+
* in, `{ status, body }` out) instead of an `http.IncomingMessage`/Express
|
|
36
|
+
* `Request`/etc., so it can be wired into any HTTP layer a bot already uses -
|
|
37
|
+
* this package doesn't otherwise depend on one, and picking one here would be
|
|
38
|
+
* a bigger commitment than this function needs to make.
|
|
39
|
+
*/
|
|
40
|
+
export declare function handleWebhookInteractionRequest(request: WebhookInteractionRequest, options: HandleWebhookInteractionRequestOptions): Promise<WebhookInteractionResponse>;
|
|
41
|
+
//# sourceMappingURL=handle-webhook-interaction-request.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { InteractionResponseType, InteractionType } from 'discord-api-types/v10';
|
|
2
|
+
import { verifyInteractionRequest } from './verify-interaction-request.js';
|
|
3
|
+
/**
|
|
4
|
+
* Framework-agnostic core of an HTTP Interactions endpoint: verifies the
|
|
5
|
+
* request's Ed25519 signature, answers Discord's PING (type 1) validation
|
|
6
|
+
* handshake with PONG directly, and otherwise hands the parsed interaction to
|
|
7
|
+
* `onInteraction`. Deliberately takes/returns plain data (raw body + headers
|
|
8
|
+
* in, `{ status, body }` out) instead of an `http.IncomingMessage`/Express
|
|
9
|
+
* `Request`/etc., so it can be wired into any HTTP layer a bot already uses -
|
|
10
|
+
* this package doesn't otherwise depend on one, and picking one here would be
|
|
11
|
+
* a bigger commitment than this function needs to make.
|
|
12
|
+
*/
|
|
13
|
+
export async function handleWebhookInteractionRequest(request, options) {
|
|
14
|
+
const { publicKey, logger, onInteraction } = options;
|
|
15
|
+
const isValid = verifyInteractionRequest({
|
|
16
|
+
publicKey,
|
|
17
|
+
signature: request.signature,
|
|
18
|
+
timestamp: request.timestamp,
|
|
19
|
+
rawBody: request.rawBody,
|
|
20
|
+
});
|
|
21
|
+
if (!isValid) {
|
|
22
|
+
logger.warn('Rejected webhook interaction request with invalid signature');
|
|
23
|
+
return { status: 401, body: { error: 'Invalid request signature' } };
|
|
24
|
+
}
|
|
25
|
+
let interaction;
|
|
26
|
+
try {
|
|
27
|
+
interaction = JSON.parse(request.rawBody.toString());
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
logger.error('Failed to parse webhook interaction request body as JSON', e);
|
|
31
|
+
return { status: 400, body: { error: 'Invalid JSON body' } };
|
|
32
|
+
}
|
|
33
|
+
if (interaction.type === InteractionType.Ping) {
|
|
34
|
+
return { status: 200, body: { type: InteractionResponseType.Pong } };
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const body = await onInteraction(interaction);
|
|
38
|
+
return { status: 200, body };
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
logger.error('Error while handling webhook interaction', e);
|
|
42
|
+
return { status: 500, body: { error: 'Internal error' } };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=handle-webhook-interaction-request.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Experimental: unvalidated against a real bot migration yet. See this
|
|
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.
|
|
5
|
+
export * from './verify-interaction-request.js';
|
|
6
|
+
export * from './handle-webhook-interaction-request.js';
|
|
7
|
+
export * from './create-webhook-interaction-responder.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface VerifyInteractionRequestOptions {
|
|
2
|
+
/** The bot's Ed25519 application public key, from the Discord developer portal, as a hex string. */
|
|
3
|
+
publicKey: string;
|
|
4
|
+
/** The request's `X-Signature-Ed25519` header. */
|
|
5
|
+
signature: string | undefined;
|
|
6
|
+
/** The request's `X-Signature-Timestamp` header. */
|
|
7
|
+
timestamp: string | undefined;
|
|
8
|
+
/** The raw (unparsed) request body, exactly as received - verification fails against a re-serialized body. */
|
|
9
|
+
rawBody: string | Buffer;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Verifies a Discord Interactions-webhook request's Ed25519 signature, per
|
|
13
|
+
* https://discord.com/developers/docs/interactions/overview#setting-up-an-endpoint.
|
|
14
|
+
*
|
|
15
|
+
* Built on Node's native `crypto` module (`verify`/`createPublicKey`, both
|
|
16
|
+
* Ed25519-capable since Node 12) instead of `tweetnacl` or
|
|
17
|
+
* `discord-interactions` - matching this package's standing preference for a
|
|
18
|
+
* native API over a new dependency when one suffices (see the discord-webhook
|
|
19
|
+
* pino transport, and `createHandlerWatcher`'s use of native `fs.watch`).
|
|
20
|
+
*/
|
|
21
|
+
export declare function verifyInteractionRequest(options: VerifyInteractionRequestOptions): boolean;
|
|
22
|
+
//# sourceMappingURL=verify-interaction-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-interaction-request.d.ts","sourceRoot":"","sources":["../../src/webhook/verify-interaction-request.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,+BAA+B;IAC9C,oGAAoG;IACpG,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,oDAAoD;IACpD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,8GAA8G;IAC9G,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,OAAO,CAkB1F"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createPublicKey, verify } from 'node:crypto';
|
|
2
|
+
// DER SPKI wrapper for a raw 32-byte Ed25519 public key
|
|
3
|
+
// (302a300506032b6570032100 = SEQUENCE { SEQUENCE { OID 1.3.101.112 }, BIT STRING (32 bytes) }).
|
|
4
|
+
// Node's `crypto.createPublicKey` has no raw-Ed25519-key input format, only DER/PEM/JWK, and
|
|
5
|
+
// Discord's developer portal only ever gives bots the raw 32-byte hex key - this prefix is the
|
|
6
|
+
// standard, fixed way to wrap one into the DER SPKI shape `createPublicKey` accepts.
|
|
7
|
+
const ed25519SpkiDerPrefix = Buffer.from('302a300506032b6570032100', 'hex');
|
|
8
|
+
/**
|
|
9
|
+
* Verifies a Discord Interactions-webhook request's Ed25519 signature, per
|
|
10
|
+
* https://discord.com/developers/docs/interactions/overview#setting-up-an-endpoint.
|
|
11
|
+
*
|
|
12
|
+
* Built on Node's native `crypto` module (`verify`/`createPublicKey`, both
|
|
13
|
+
* Ed25519-capable since Node 12) instead of `tweetnacl` or
|
|
14
|
+
* `discord-interactions` - matching this package's standing preference for a
|
|
15
|
+
* native API over a new dependency when one suffices (see the discord-webhook
|
|
16
|
+
* pino transport, and `createHandlerWatcher`'s use of native `fs.watch`).
|
|
17
|
+
*/
|
|
18
|
+
export function verifyInteractionRequest(options) {
|
|
19
|
+
const { publicKey, signature, timestamp, rawBody } = options;
|
|
20
|
+
if (!signature || !timestamp) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const key = createPublicKey({
|
|
25
|
+
key: Buffer.concat([ed25519SpkiDerPrefix, Buffer.from(publicKey, 'hex')]),
|
|
26
|
+
format: 'der',
|
|
27
|
+
type: 'spki',
|
|
28
|
+
});
|
|
29
|
+
const message = Buffer.concat([Buffer.from(timestamp, 'utf8'), Buffer.from(rawBody)]);
|
|
30
|
+
return verify(null, message, key, Buffer.from(signature, 'hex'));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Malformed hex in publicKey/signature, wrong-length key, etc. - all just "not valid".
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=verify-interaction-request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-interaction-request.js","sourceRoot":"","sources":["../../src/webhook/verify-interaction-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEtD,wDAAwD;AACxD,iGAAiG;AACjG,6FAA6F;AAC7F,+FAA+F;AAC/F,qFAAqF;AACrF,MAAM,oBAAoB,GAAG,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAC;AAa5E;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAwC;IAC/E,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC7D,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,eAAe,CAAC;YAC1B,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YACzE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,MAAM;SACb,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACtF,OAAO,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,uFAAuF;QACvF,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@went.tf/discord-bot-framework",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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",
|
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
"types": "./build/client/index.d.ts",
|
|
49
49
|
"default": "./build/client/index.js"
|
|
50
50
|
},
|
|
51
|
+
"./webhook": {
|
|
52
|
+
"types": "./build/webhook/index.d.ts",
|
|
53
|
+
"default": "./build/webhook/index.js"
|
|
54
|
+
},
|
|
51
55
|
"./dev": {
|
|
52
56
|
"types": "./build/dev/index.d.ts",
|
|
53
57
|
"default": "./build/dev/index.js"
|