@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.
- package/README.md +69 -13
- package/build/.tsbuildinfo +1 -1
- package/build/webhook/create-webhook-only-client.d.ts +35 -0
- package/build/webhook/create-webhook-only-client.d.ts.map +1 -0
- package/build/webhook/create-webhook-only-client.js +36 -0
- package/build/webhook/create-webhook-only-client.js.map +1 -0
- package/build/webhook/handle-webhook-interaction-request.d.ts +28 -2
- package/build/webhook/handle-webhook-interaction-request.d.ts.map +1 -1
- package/build/webhook/handle-webhook-interaction-request.js +34 -1
- package/build/webhook/handle-webhook-interaction-request.js.map +1 -1
- package/build/webhook/index.d.ts +2 -0
- package/build/webhook/index.d.ts.map +1 -1
- package/build/webhook/index.js +5 -2
- package/build/webhook/index.js.map +1 -1
- package/build/webhook/interaction-from-webhook-payload.d.ts +31 -0
- package/build/webhook/interaction-from-webhook-payload.d.ts.map +1 -0
- package/build/webhook/interaction-from-webhook-payload.js +91 -0
- package/build/webhook/interaction-from-webhook-payload.js.map +1 -0
- package/package.json +1 -1
|
@@ -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;
|
|
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;
|
|
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"}
|
package/build/webhook/index.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/build/webhook/index.js
CHANGED
|
@@ -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
|
|
4
|
-
//
|
|
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,
|
|
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.
|
|
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",
|