botflare 0.1.1 β 0.2.1
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 +25 -98
- package/dist/{bot-icKel3r9.d.mts β bot-CW_jhC5U.d.mts} +77 -19
- package/dist/command-CBeslrzu.mjs.map +1 -1
- package/dist/discord-DEZe1asd.mjs.map +1 -1
- package/dist/gateway.d.mts +26 -12
- package/dist/gateway.mjs +69 -17
- package/dist/gateway.mjs.map +1 -1
- package/dist/index.d.mts +48 -46
- package/dist/index.mjs +0 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/pyyupsk/botflare/main/apps/docs/public/favicon.svg" width="56" height="56" alt="botflare logo" />
|
|
2
|
+
|
|
1
3
|
# botflare
|
|
2
4
|
|
|
3
|
-
[](https://www.npmjs.com/package/botflare)
|
|
6
|
+
[](https://www.npmjs.com/package/botflare)
|
|
7
|
+
[](https://github.com/pyyupsk/botflare/actions/workflows/ci.yml)
|
|
8
|
+
[](https://pyyupsk.github.io/botflare/)
|
|
9
|
+
[](https://www.typescriptlang.org)
|
|
10
|
+
[](https://workers.cloudflare.com)
|
|
11
|
+
[](https://github.com/discord/discord-api-spec)
|
|
12
|
+
[](https://github.com/pyyupsk/botflare/blob/main/LICENSE)
|
|
4
13
|
|
|
5
14
|
Build Discord bots that run on Cloudflare Workers, with types from the official Discord API spec.
|
|
6
15
|
|
|
7
|
-
[
|
|
8
|
-
|
|
9
|
-
botflare is an unofficial library. It is not affiliated with, endorsed by, or sponsored by Discord Inc. or Cloudflare, Inc.
|
|
16
|
+
[π Documentation](https://pyyupsk.github.io/botflare/) Β· [π Getting started](https://pyyupsk.github.io/botflare/guides/getting-started/) Β· [π§© API reference](https://pyyupsk.github.io/botflare/api/) Β· [π€ Example bot](https://github.com/pyyupsk/botflare/tree/main/examples/basic) Β· [π Changelog](https://github.com/pyyupsk/botflare/releases)
|
|
10
17
|
|
|
11
18
|
## Features
|
|
12
19
|
|
|
13
|
-
- Slash commands, subcommands, context
|
|
14
|
-
- Option values typed from the options you declare
|
|
15
|
-
- The Discord Gateway in a Durable Object, so a bot gets
|
|
16
|
-
- `rest` for every Discord route, with path, query, body and result typed from the
|
|
17
|
-
- Cron jobs that post to channels
|
|
18
|
-
- Messages ping nobody unless you allow it, even for `@everyone` in the text
|
|
19
|
-
- `botflare sync` sends your commands to Discord from the command line
|
|
20
|
+
- β‘ Slash commands, subcommands, context menus, buttons, select menus, modals and autocomplete
|
|
21
|
+
- π§ Option values typed from the options you declare. A `choices` option gives only its values
|
|
22
|
+
- π The Discord Gateway in a Durable Object, compressed, so a bot gets events without a public URL
|
|
23
|
+
- π°οΈ `rest` for every Discord route, with path, query, body and result typed from the spec
|
|
24
|
+
- β° Cron jobs that post to channels
|
|
25
|
+
- π Messages ping nobody unless you allow it, even for `@everyone` in the text
|
|
26
|
+
- π οΈ `botflare sync` sends your commands to Discord from the command line
|
|
20
27
|
|
|
21
28
|
## Install
|
|
22
29
|
|
|
@@ -28,7 +35,6 @@ npm install botflare
|
|
|
28
35
|
|
|
29
36
|
```ts
|
|
30
37
|
import { bot, command, option } from 'botflare';
|
|
31
|
-
import { gateway } from 'botflare/gateway';
|
|
32
38
|
|
|
33
39
|
const roll = command(
|
|
34
40
|
{
|
|
@@ -36,103 +42,24 @@ const roll = command(
|
|
|
36
42
|
description: 'Roll a die',
|
|
37
43
|
options: { sides: option('integer', { description: 'Sides on the die', min: 2, max: 100 }) },
|
|
38
44
|
},
|
|
39
|
-
(c) => {
|
|
40
|
-
const sides = c.options.sides ?? 6; // number | undefined
|
|
41
|
-
const rolled = 1 + Math.floor(Math.random() * sides);
|
|
42
|
-
|
|
43
|
-
return c.reply(`You rolled ${rolled}`, {
|
|
44
|
-
buttons: [{ id: `again:${sides}`, label: 'Roll again', emoji: 'π²' }],
|
|
45
|
-
});
|
|
46
|
-
},
|
|
45
|
+
(c) => c.reply(`You rolled ${1 + Math.floor(Math.random() * (c.options.sides ?? 6))}`),
|
|
47
46
|
);
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
commands: [roll],
|
|
51
|
-
buttons: {
|
|
52
|
-
again: (c) => c.update(`You rolled ${1 + Math.floor(Math.random() * Number(c.value))}`),
|
|
53
|
-
},
|
|
54
|
-
gateway: 'GATEWAY',
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
export const Gateway = gateway({ intents: ['Guilds'], bot: app, events: {} });
|
|
58
|
-
|
|
59
|
-
export default app;
|
|
48
|
+
export default bot({ commands: [roll] });
|
|
60
49
|
```
|
|
61
50
|
|
|
62
|
-
Bind the `Gateway` class in `wrangler.jsonc`:
|
|
63
|
-
|
|
64
|
-
```jsonc
|
|
65
|
-
{
|
|
66
|
-
"durable_objects": { "bindings": [{ "name": "GATEWAY", "class_name": "Gateway" }] },
|
|
67
|
-
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["Gateway"] }],
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Then run it:
|
|
72
|
-
|
|
73
51
|
```sh
|
|
74
52
|
npx botflare sync src/index.ts # send the commands to Discord
|
|
75
|
-
npx wrangler
|
|
53
|
+
npx wrangler deploy # then set the Interactions Endpoint URL to the Worker
|
|
76
54
|
```
|
|
77
55
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
## API
|
|
81
|
-
|
|
82
|
-
| Export | Use |
|
|
83
|
-
| ------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
84
|
-
| `bot(config)` | The Worker handler. Checks signatures and answers commands, buttons, menus and modals |
|
|
85
|
-
| `command(config, run)` | A slash command, or a command with `subcommands` |
|
|
86
|
-
| `option(type, config)` | An option of a command, with `choices`, limits or `autocomplete` |
|
|
87
|
-
| `contextMenu('user' \| 'message', β¦)` | An item in the Apps menu of a user or a message |
|
|
88
|
-
| `select(type, config, run)` | A select menu of strings, users, roles, channels or mentionables |
|
|
89
|
-
| `modal(config, run)` | A form in a popup |
|
|
90
|
-
| `gateway(config)` | A Durable Object class that holds the Discord Gateway connection, from `botflare/gateway` |
|
|
91
|
-
| `rest` | Calls any Discord route as the bot and returns `[error, data]` |
|
|
92
|
-
| `mention`, `time` | Mentions and timestamps for message text |
|
|
93
|
-
|
|
94
|
-
A handler answers with one of these:
|
|
95
|
-
|
|
96
|
-
| Answer | What Discord shows |
|
|
97
|
-
| ------------------------------ | --------------------------------------------------------------------------------- |
|
|
98
|
-
| `c.reply(text, options)` | A new message |
|
|
99
|
-
| `c.defer(work, { ephemeral })` | "Thinkingβ¦", then the result of `work`, for work over 3 seconds |
|
|
100
|
-
| `c.update(text, options)` | The message of the button or menu, edited |
|
|
101
|
-
| `c.deferUpdate(work)` | The message of the button or menu as it is, then edited with the result of `work` |
|
|
102
|
-
| `c.modal(form, value)` | A form, with `value` back in `c.value` |
|
|
103
|
-
|
|
104
|
-
Message options are `buttons`, `selects`, `embeds`, `allowedMentions` and `ephemeral`. A command takes `permissions`, `contexts`, `installs` and `locales`. Every config has its description in the editor.
|
|
105
|
-
|
|
106
|
-
## Gateway events
|
|
107
|
-
|
|
108
|
-
```ts
|
|
109
|
-
export const Gateway = gateway({
|
|
110
|
-
intents: ['Guilds', 'GuildMessages', 'MessageContent'],
|
|
111
|
-
bot: app,
|
|
112
|
-
events: {
|
|
113
|
-
MESSAGE_CREATE: (message, c) => message.content === 'hi' && c.send(message.channel_id, 'hello'),
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Turn on privileged intents, such as `MessageContent`, in the Developer Portal first. If the connection is down, a Durable Object alarm opens it again within a minute.
|
|
119
|
-
|
|
120
|
-
## REST
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
import { rest } from 'botflare';
|
|
124
|
-
|
|
125
|
-
const [error, member] = await rest.get('/guilds/{guild_id}/members/{user_id}', {
|
|
126
|
-
path: { guild_id, user_id },
|
|
127
|
-
});
|
|
128
|
-
member?.joined_at; // typed from the Discord API spec
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
On a 429, `rest` waits for `retry_after` and tries again, up to three tries in all.
|
|
56
|
+
The guides cover [answers](https://pyyupsk.github.io/botflare/guides/answers/), [components](https://pyyupsk.github.io/botflare/guides/components/), [gateway events](https://pyyupsk.github.io/botflare/guides/gateway-events/) and [REST](https://pyyupsk.github.io/botflare/guides/rest/). They also cover [cron](https://pyyupsk.github.io/botflare/guides/cron/), [deploy](https://pyyupsk.github.io/botflare/guides/deploy/) and a bot without a public URL.
|
|
132
57
|
|
|
133
58
|
## Why?
|
|
134
59
|
|
|
135
|
-
discord.js and discord.py expect a process that stays on, with a WebSocket and a cache in memory. A Worker runs per request and then stops. botflare
|
|
60
|
+
discord.js and discord.py expect a process that stays on, with a WebSocket and a cache in memory. A Worker runs per request and then stops. botflare answers interactions over HTTP, keeps the gateway connection in a Durable Object, and takes its REST types from the spec that Discord publishes.
|
|
61
|
+
|
|
62
|
+
botflare is an unofficial library. It is not affiliated with, endorsed by, or sponsored by Discord Inc. or Cloudflare, Inc.
|
|
136
63
|
|
|
137
64
|
## License
|
|
138
65
|
|
|
@@ -4,6 +4,7 @@ type Upload = {
|
|
|
4
4
|
data: Blob | ArrayBuffer | Uint8Array<ArrayBuffer> | string;
|
|
5
5
|
description?: string;
|
|
6
6
|
};
|
|
7
|
+
/** @internal */
|
|
7
8
|
type S = {
|
|
8
9
|
AccountResponse: {
|
|
9
10
|
id: string;
|
|
@@ -4606,6 +4607,7 @@ type S = {
|
|
|
4606
4607
|
execute_slack_compatible_webhook_body: S["SlackWebhook"];
|
|
4607
4608
|
execute_slack_compatible_webhook_result: string | null;
|
|
4608
4609
|
};
|
|
4610
|
+
/** @internal */
|
|
4609
4611
|
type Routes = {
|
|
4610
4612
|
"/applications/@me": {
|
|
4611
4613
|
get: {
|
|
@@ -7264,21 +7266,33 @@ type Locales = Partial<Record<`${Locale}`, {
|
|
|
7264
7266
|
name?: string;
|
|
7265
7267
|
description?: string;
|
|
7266
7268
|
}>>;
|
|
7269
|
+
/**
|
|
7270
|
+
* A button on a message. A button with an `id` runs the handler with that id in the `buttons` of
|
|
7271
|
+
* `bot()`, and takes a `style`: `primary` (default), `secondary`, `success` or `danger`. A button
|
|
7272
|
+
* with a `url` opens the link and needs no handler. A button needs a `label`, an `emoji` or both.
|
|
7273
|
+
* An `emoji` is a Unicode emoji, or a custom emoji such as `<:name:id>`.
|
|
7274
|
+
*/
|
|
7267
7275
|
type Button = ({
|
|
7276
|
+
/** The handler name, then an optional `:` and a value, such as `page:2`. */
|
|
7268
7277
|
id: string;
|
|
7278
|
+
/** `primary` (default), `secondary`, `success` or `danger`. */
|
|
7269
7279
|
style?: ButtonStyle;
|
|
7270
7280
|
url?: never;
|
|
7271
7281
|
} | {
|
|
7282
|
+
/** The link the button opens. */
|
|
7272
7283
|
url: string;
|
|
7273
7284
|
id?: never;
|
|
7274
7285
|
style?: never;
|
|
7275
7286
|
}) & ({
|
|
7287
|
+
/** The text on the button. */
|
|
7276
7288
|
label: string;
|
|
7289
|
+
/** A Unicode emoji, or a custom emoji such as `<:name:id>`. */
|
|
7277
7290
|
emoji?: string;
|
|
7278
7291
|
} | {
|
|
7279
7292
|
label?: string;
|
|
7280
7293
|
emoji: string;
|
|
7281
7294
|
}) & {
|
|
7295
|
+
/** `true` greys the button out. */
|
|
7282
7296
|
disabled?: boolean;
|
|
7283
7297
|
};
|
|
7284
7298
|
type ButtonStyle = "primary" | "secondary" | "success" | "danger";
|
|
@@ -7330,20 +7344,39 @@ type SelectMenu = {
|
|
|
7330
7344
|
options?: Record<string, string>;
|
|
7331
7345
|
channelTypes?: ChannelType[];
|
|
7332
7346
|
};
|
|
7347
|
+
/** What a message holds besides its text. */
|
|
7333
7348
|
type MessageOptions = {
|
|
7349
|
+
/** Buttons, five to a row. Pass `[]` to remove the buttons of the message. */
|
|
7334
7350
|
buttons?: Button[];
|
|
7351
|
+
/** Select menus made with `select()`, one to a row, above the buttons. */
|
|
7335
7352
|
selects?: SelectMenu[];
|
|
7353
|
+
/** Embeds, as in the Discord API, such as `{ title, description }`. */
|
|
7336
7354
|
embeds?: S["RichEmbed"][];
|
|
7355
|
+
/**
|
|
7356
|
+
* Who the message pings. By default it pings nobody, even for `@everyone` or `<@id>` in the
|
|
7357
|
+
* text. Pass `{ users: [id] }` or `{ parse: ['roles'] }` to ping.
|
|
7358
|
+
*/
|
|
7337
7359
|
allowedMentions?: S["MessageAllowedMentionsRequest"];
|
|
7360
|
+
/** Files to attach. */
|
|
7338
7361
|
files?: Upload[];
|
|
7339
7362
|
};
|
|
7363
|
+
/** {@link MessageOptions} for the first answer or a follow-up. */
|
|
7340
7364
|
type ReplyOptions = MessageOptions & {
|
|
7365
|
+
/** `true` shows the message to the user who ran the interaction only. */
|
|
7341
7366
|
ephemeral?: boolean;
|
|
7342
7367
|
};
|
|
7368
|
+
/** Calls that send more after the first answer. Each returns an `Error` if Discord refuses. */
|
|
7343
7369
|
type FollowUps = {
|
|
7370
|
+
/**
|
|
7371
|
+
* Runs `work` once Discord has the first answer. Call it before you return the answer. `work`
|
|
7372
|
+
* can call the other follow-up calls.
|
|
7373
|
+
*/
|
|
7344
7374
|
after(work: () => Promise<void>): void;
|
|
7375
|
+
/** Sends a new message. */
|
|
7345
7376
|
followUp(content: string, options?: ReplyOptions): Promise<Error | null>;
|
|
7377
|
+
/** Edits the first answer. */
|
|
7346
7378
|
editReply(content: string, options?: MessageOptions): Promise<Error | null>;
|
|
7379
|
+
/** Deletes the first answer. */
|
|
7347
7380
|
deleteReply(): Promise<Error | null>;
|
|
7348
7381
|
};
|
|
7349
7382
|
type Fields = Record<string, {
|
|
@@ -7366,21 +7399,42 @@ type Modal<F extends Fields = Fields> = {
|
|
|
7366
7399
|
} & FollowUps): Response | Promise<Response>;
|
|
7367
7400
|
};
|
|
7368
7401
|
type Permission = Exclude<keyof typeof PermissionFlagsBits, "ManageEmojisAndStickers">;
|
|
7402
|
+
/** What the handler of a slash command gets. */
|
|
7369
7403
|
type Context<O extends Options> = {
|
|
7404
|
+
/** The interaction as Discord sent it. */
|
|
7370
7405
|
interaction: APIChatInputApplicationCommandInteraction;
|
|
7406
|
+
/** The option values, keyed by name. An option that is not required can be `undefined`. */
|
|
7371
7407
|
options: { [K in keyof O]: O[K] extends Option<keyof OptionValue, infer R, infer V> ? R extends true ? V : V | undefined : never; };
|
|
7408
|
+
/** Answers within 3 seconds. Return what it gives. */
|
|
7372
7409
|
reply: (content: string, options?: ReplyOptions) => Response;
|
|
7410
|
+
/**
|
|
7411
|
+
* Shows "thinkingβ¦" now, then edits the answer to what `work` returns: the text, or
|
|
7412
|
+
* `{ content, buttons, embeds }`. Use it for work that takes longer than 3 seconds.
|
|
7413
|
+
*/
|
|
7373
7414
|
defer: (work: () => Promise<string | ({
|
|
7374
7415
|
content: string;
|
|
7375
7416
|
} & MessageOptions)>, options?: {
|
|
7376
7417
|
ephemeral?: boolean;
|
|
7377
7418
|
}) => Response;
|
|
7419
|
+
/** Opens a modal made with `modal()`. `value` comes back to its handler as `c.value`. */
|
|
7378
7420
|
modal: (modal: Modal, value?: string) => Response;
|
|
7379
7421
|
} & FollowUps;
|
|
7422
|
+
/** Who sees a command or a context menu item, and where. */
|
|
7380
7423
|
type Access = {
|
|
7424
|
+
/**
|
|
7425
|
+
* The name and description in other languages, keyed by Discord locale, such as
|
|
7426
|
+
* `{ th: { name: 'ΰΈΰΈΰΈ’', description: 'ΰΈΰΈΰΈ’ΰΈ₯ΰΈΉΰΈΰΉΰΈΰΉΰΈ²' } }`. The handler still gets the default name.
|
|
7427
|
+
*/
|
|
7381
7428
|
locales?: Locales;
|
|
7429
|
+
/**
|
|
7430
|
+
* Who sees it by default: members with all of these permissions, such as `['ManageGuild']`.
|
|
7431
|
+
* Pass `[]` to show it to admins only. Server admins can change this in Server Settings.
|
|
7432
|
+
* Applies to a top-level command only.
|
|
7433
|
+
*/
|
|
7382
7434
|
permissions?: Permission[];
|
|
7435
|
+
/** Where it shows: `guild`, `bot_dm` or `private_channel`. */
|
|
7383
7436
|
contexts?: ("guild" | "bot_dm" | "private_channel")[];
|
|
7437
|
+
/** How the app must be installed for it: `guild` or `user`. */
|
|
7384
7438
|
installs?: ("guild" | "user")[];
|
|
7385
7439
|
};
|
|
7386
7440
|
type Command<O extends Options = Options> = ({
|
|
@@ -7456,12 +7510,34 @@ type CronContext = {
|
|
|
7456
7510
|
send: Send;
|
|
7457
7511
|
};
|
|
7458
7512
|
type BotConfig = {
|
|
7513
|
+
/** The commands, made with {@link command} or `contextMenu()`. */
|
|
7459
7514
|
commands: (Command | ContextMenu)[];
|
|
7515
|
+
/**
|
|
7516
|
+
* Answers a click, keyed by the button id given to `reply`. An id such as `page:2` runs the
|
|
7517
|
+
* `page` handler with `c.value` set to `'2'`. `c.update` edits the message that holds the button.
|
|
7518
|
+
* Pass `{ buttons: [] }` to remove them. `c.deferUpdate(work)` keeps the message as it is and
|
|
7519
|
+
* edits it with what `work` returns, for a click that takes longer than 3 seconds.
|
|
7520
|
+
*/
|
|
7460
7521
|
buttons?: Record<string, (c: ButtonContext) => Response | Promise<Response>>;
|
|
7522
|
+
/** The select menus the bot answers, made with `select()`. */
|
|
7461
7523
|
selects?: Select<SelectType, unknown>[];
|
|
7524
|
+
/** The modals the bot answers, made with {@link modal}. */
|
|
7462
7525
|
modals?: Modal[];
|
|
7526
|
+
/**
|
|
7527
|
+
* The name of the Durable Object binding that holds the class from `gateway()`. Open the Worker
|
|
7528
|
+
* URL once after the first deploy to start the connection. A GET request or any Cron Trigger
|
|
7529
|
+
* starts it again if it is down.
|
|
7530
|
+
*/
|
|
7463
7531
|
gateway?: string;
|
|
7532
|
+
/**
|
|
7533
|
+
* Gets errors from deferred work. If that work fails, the bot also edits the answer to
|
|
7534
|
+
* "Something went wrong." so it does not stay on "thinkingβ¦".
|
|
7535
|
+
*/
|
|
7464
7536
|
onError?: ErrorHandler;
|
|
7537
|
+
/**
|
|
7538
|
+
* Runs on a Cron Trigger, keyed by the cron expression from your Wrangler config. `c.send` posts
|
|
7539
|
+
* a message to a channel with `DISCORD_TOKEN`, and returns an `Error` if Discord refuses.
|
|
7540
|
+
*/
|
|
7465
7541
|
cron?: Record<string, (c: CronContext) => void | Promise<void>>;
|
|
7466
7542
|
};
|
|
7467
7543
|
/** The Worker handler from {@link bot}. */
|
|
@@ -7482,24 +7558,6 @@ type Bot = {
|
|
|
7482
7558
|
* the hex public key of your application in the Discord Developer Portal.
|
|
7483
7559
|
*
|
|
7484
7560
|
* @param config What the bot answers.
|
|
7485
|
-
* @param config.commands The commands, made with {@link command} or `contextMenu()`.
|
|
7486
|
-
* @param config.modals The modals the bot answers, made with {@link modal}.
|
|
7487
|
-
* @param config.onError Gets errors from deferred work. If that work fails, the bot also edits the
|
|
7488
|
-
* answer to "Something went wrong." so it does not stay on "thinkingβ¦".
|
|
7489
|
-
* @param config.gateway The name of the Durable Object binding that holds the class from `gateway()`.
|
|
7490
|
-
* Open the Worker URL once after the first deploy to start the connection. A GET request or any
|
|
7491
|
-
* Cron Trigger starts it again if it is down.
|
|
7492
|
-
* @param config.cron Runs on a Cron Trigger, keyed by the cron expression from your Wrangler config.
|
|
7493
|
-
* `c.send` posts a message to a channel with `DISCORD_TOKEN`, with optional `buttons` and `embeds`,
|
|
7494
|
-
* and returns an `Error` if Discord refuses.
|
|
7495
|
-
* @param config.buttons Answers a click, keyed by the button id given to `reply`. An id such as
|
|
7496
|
-
* `page:2` runs the `page` handler with `c.value` set to `'2'`. `update` edits the message that
|
|
7497
|
-
* holds the button. Pass `{ buttons: [] }` to remove them.
|
|
7498
|
-
* A button takes a `label`, an `emoji` or both, and a `style`: `primary` (default), `secondary`,
|
|
7499
|
-
* `success` or `danger`. A button with a `url` instead of an `id` opens the link and needs no
|
|
7500
|
-
* handler. `disabled: true` greys a button out. `c.deferUpdate(work)` keeps the message as it is
|
|
7501
|
-
* and edits it with what `work` returns, for a click that takes longer than 3 seconds.
|
|
7502
|
-
* @param config.selects The select menus the bot answers, made with `select()`.
|
|
7503
7561
|
* @returns The Worker handler. Export it as the default export of your Worker. Pass it to
|
|
7504
7562
|
* `gateway({ bot })` to answer interactions that arrive over the gateway too.
|
|
7505
7563
|
* @example
|
|
@@ -7509,4 +7567,4 @@ type Bot = {
|
|
|
7509
7567
|
*/
|
|
7510
7568
|
declare function bot(config: BotConfig): Bot;
|
|
7511
7569
|
export { Locales as C, Suggestion as S, AutocompleteContext as _, Select as a, OptionValue as b, Access as c, ContextMenu as d, MenuContext as f, SelectType as g, Modal as h, ErrorHandler as i, Command as l, Fields as m, bot as n, SelectContext as o, MenuType as p, Send as r, SelectValue as s, Bot as t, Context as u, ChannelType as v, Routes as w, Options as x, Option as y };
|
|
7512
|
-
//# sourceMappingURL=bot-
|
|
7570
|
+
//# sourceMappingURL=bot-CW_jhC5U.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-CBeslrzu.mjs","names":[],"sources":["../src/utils/permissions.ts","../src/utils/command.ts"],"sourcesContent":["import type { PermissionFlagsBits } from 'discord-api-types/v10';\n\nexport type Permission = Exclude<keyof typeof PermissionFlagsBits, 'ManageEmojisAndStickers'>;\n\nconst BIT: Record<Permission, number> = {\n CreateInstantInvite: 0,\n KickMembers: 1,\n BanMembers: 2,\n Administrator: 3,\n ManageChannels: 4,\n ManageGuild: 5,\n AddReactions: 6,\n ViewAuditLog: 7,\n PrioritySpeaker: 8,\n Stream: 9,\n ViewChannel: 10,\n SendMessages: 11,\n SendTTSMessages: 12,\n ManageMessages: 13,\n EmbedLinks: 14,\n AttachFiles: 15,\n ReadMessageHistory: 16,\n MentionEveryone: 17,\n UseExternalEmojis: 18,\n ViewGuildInsights: 19,\n Connect: 20,\n Speak: 21,\n MuteMembers: 22,\n DeafenMembers: 23,\n MoveMembers: 24,\n UseVAD: 25,\n ChangeNickname: 26,\n ManageNicknames: 27,\n ManageRoles: 28,\n ManageWebhooks: 29,\n ManageGuildExpressions: 30,\n UseApplicationCommands: 31,\n RequestToSpeak: 32,\n ManageEvents: 33,\n ManageThreads: 34,\n CreatePublicThreads: 35,\n CreatePrivateThreads: 36,\n UseExternalStickers: 37,\n SendMessagesInThreads: 38,\n UseEmbeddedActivities: 39,\n ModerateMembers: 40,\n ViewCreatorMonetizationAnalytics: 41,\n UseSoundboard: 42,\n CreateGuildExpressions: 43,\n CreateEvents: 44,\n UseExternalSounds: 45,\n SendVoiceMessages: 46,\n SetVoiceChannelStatus: 48,\n SendPolls: 49,\n UseExternalApps: 50,\n PinMessages: 51,\n BypassSlowmode: 52,\n};\n\nexport function permissionBits(names: Permission[]): number {\n return [...new Set(names)].reduce((bits, name) => bits + 2 ** BIT[name], 0);\n}\n","import type {\n APIApplicationCommandInteractionDataOption,\n APIChatInputApplicationCommandInteraction,\n APIMessage,\n APIMessageApplicationCommandInteraction,\n APIUser,\n APIUserApplicationCommandInteraction,\n ApplicationCommandOptionType,\n} from 'discord-api-types/v10';\n\nimport type { Schemas } from '#lib/routes';\nimport { type Locales, localePayload } from '#utils/locale';\nimport type { FollowUps, MessageOptions, ReplyOptions } from '#utils/message';\nimport type { Modal } from '#utils/modal';\nimport {\n type CommandOption,\n type Option,\n type Options,\n type OptionValue,\n optionsPayload,\n} from '#utils/option';\nimport { type Permission, permissionBits } from '#utils/permissions';\n\nexport type Context<O extends Options> = {\n interaction: APIChatInputApplicationCommandInteraction;\n options: {\n [K in keyof O]: O[K] extends Option<keyof OptionValue, infer R, infer V>\n ? R extends true\n ? V\n : V | undefined\n : never;\n };\n reply: (content: string, options?: ReplyOptions) => Response;\n defer: (\n work: () => Promise<string | ({ content: string } & MessageOptions)>,\n options?: { ephemeral?: boolean },\n ) => Response;\n modal: (modal: Modal, value?: string) => Response;\n} & FollowUps;\n\nexport type Access = {\n locales?: Locales;\n permissions?: Permission[];\n contexts?: ('guild' | 'bot_dm' | 'private_channel')[];\n installs?: ('guild' | 'user')[];\n};\n\nconst CONTEXT = { guild: 0, bot_dm: 1, private_channel: 2 } as const;\nconst INSTALL = { guild: 0, user: 1 } as const;\n\nfunction accessPayload(access: Access): Partial<Schemas['ApplicationCommandUpdateRequest']> {\n const fields: Partial<Schemas['ApplicationCommandUpdateRequest']> = localePayload(access.locales);\n if (access.permissions) fields.default_member_permissions = permissionBits(access.permissions);\n if (access.contexts) fields.contexts = access.contexts.map((c) => CONTEXT[c]);\n if (access.installs) fields.integration_types = access.installs.map((i) => INSTALL[i]);\n\n return fields;\n}\n\nexport type Command<O extends Options = Options> =\n | ({\n name: string;\n description: string;\n options: O;\n run(c: Context<O>): Response | Promise<Response>;\n } & Access)\n | ({ name: string; description: string; subcommands: Command[] } & Access);\n\nexport type MenuType = 'user' | 'message';\n\nexport type MenuContext<T extends MenuType> = {\n interaction: T extends 'user'\n ? APIUserApplicationCommandInteraction\n : APIMessageApplicationCommandInteraction;\n target: T extends 'user' ? APIUser : APIMessage;\n reply: Context<Options>['reply'];\n defer: Context<Options>['defer'];\n modal: Context<Options>['modal'];\n} & FollowUps;\n\nexport type ContextMenu<T extends MenuType = MenuType> = {\n menu: T;\n name: string;\n run(c: MenuContext<T>): Response | Promise<Response>;\n} & Access;\n\nconst SUBCOMMAND: ApplicationCommandOptionType.Subcommand = 1;\nconst GROUP: ApplicationCommandOptionType.SubcommandGroup = 2;\n\nfunction childPayload(child: Command): CommandOption {\n const { name, description } = child;\n if (!('subcommands' in child)) {\n return {\n type: SUBCOMMAND,\n name,\n description,\n ...localePayload(child.locales),\n options: optionsPayload(child.options),\n } as CommandOption;\n }\n\n return {\n type: GROUP,\n name,\n description,\n ...localePayload(child.locales),\n options: child.subcommands.map(childPayload),\n } as CommandOption;\n}\n\nexport function payload(\n command: Command | ContextMenu,\n): Schemas['ApplicationCommandUpdateRequest'] {\n const access = accessPayload(command);\n if ('menu' in command) {\n delete access.description_localizations;\n return { type: command.menu === 'user' ? 2 : 3, name: command.name, ...access };\n }\n\n const { name, description } = command;\n const options =\n 'subcommands' in command\n ? command.subcommands.map(childPayload)\n : optionsPayload(command.options);\n\n return { type: 1, name, description, options, ...access };\n}\n\nexport function findCommand(\n commands: Map<string, Command>,\n data: { name: string; options?: APIApplicationCommandInteractionDataOption[] },\n): {\n found: Command | undefined;\n given: APIApplicationCommandInteractionDataOption[];\n path: string;\n} {\n const path = [data.name];\n let found = commands.get(data.name);\n let given = data.options ?? [];\n while (found && 'subcommands' in found) {\n const picked = given[0];\n found = found.subcommands.find((child) => child.name === picked?.name);\n path.push(picked?.name ?? '');\n given = picked && 'options' in picked ? (picked.options ?? []) : [];\n }\n\n return { found, given, path: `/${path.join(' ')}` };\n}\n"],"mappings":";;AAIA,MAAM,MAAkC;CACtC,qBAAqB;CACrB,aAAa;CACb,YAAY;CACZ,eAAe;CACf,gBAAgB;CAChB,aAAa;CACb,cAAc;CACd,cAAc;CACd,iBAAiB;CACjB,QAAQ;CACR,aAAa;CACb,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,YAAY;CACZ,aAAa;CACb,oBAAoB;CACpB,iBAAiB;CACjB,mBAAmB;CACnB,mBAAmB;CACnB,SAAS;CACT,OAAO;CACP,aAAa;CACb,eAAe;CACf,aAAa;CACb,QAAQ;CACR,gBAAgB;CAChB,iBAAiB;CACjB,aAAa;CACb,gBAAgB;CAChB,wBAAwB;CACxB,wBAAwB;CACxB,gBAAgB;CAChB,cAAc;CACd,eAAe;CACf,qBAAqB;CACrB,sBAAsB;CACtB,qBAAqB;CACrB,uBAAuB;CACvB,uBAAuB;CACvB,iBAAiB;CACjB,kCAAkC;CAClC,eAAe;CACf,wBAAwB;CACxB,cAAc;CACd,mBAAmB;CACnB,mBAAmB;CACnB,uBAAuB;CACvB,WAAW;CACX,iBAAiB;CACjB,aAAa;CACb,gBAAgB;AAClB;AAEA,SAAgB,eAAe,OAA6B;CAC1D,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,MAAM,SAAS,OAAO,KAAK,IAAI,OAAO,CAAC;AAC5E;;
|
|
1
|
+
{"version":3,"file":"command-CBeslrzu.mjs","names":[],"sources":["../src/utils/permissions.ts","../src/utils/command.ts"],"sourcesContent":["import type { PermissionFlagsBits } from 'discord-api-types/v10';\n\nexport type Permission = Exclude<keyof typeof PermissionFlagsBits, 'ManageEmojisAndStickers'>;\n\nconst BIT: Record<Permission, number> = {\n CreateInstantInvite: 0,\n KickMembers: 1,\n BanMembers: 2,\n Administrator: 3,\n ManageChannels: 4,\n ManageGuild: 5,\n AddReactions: 6,\n ViewAuditLog: 7,\n PrioritySpeaker: 8,\n Stream: 9,\n ViewChannel: 10,\n SendMessages: 11,\n SendTTSMessages: 12,\n ManageMessages: 13,\n EmbedLinks: 14,\n AttachFiles: 15,\n ReadMessageHistory: 16,\n MentionEveryone: 17,\n UseExternalEmojis: 18,\n ViewGuildInsights: 19,\n Connect: 20,\n Speak: 21,\n MuteMembers: 22,\n DeafenMembers: 23,\n MoveMembers: 24,\n UseVAD: 25,\n ChangeNickname: 26,\n ManageNicknames: 27,\n ManageRoles: 28,\n ManageWebhooks: 29,\n ManageGuildExpressions: 30,\n UseApplicationCommands: 31,\n RequestToSpeak: 32,\n ManageEvents: 33,\n ManageThreads: 34,\n CreatePublicThreads: 35,\n CreatePrivateThreads: 36,\n UseExternalStickers: 37,\n SendMessagesInThreads: 38,\n UseEmbeddedActivities: 39,\n ModerateMembers: 40,\n ViewCreatorMonetizationAnalytics: 41,\n UseSoundboard: 42,\n CreateGuildExpressions: 43,\n CreateEvents: 44,\n UseExternalSounds: 45,\n SendVoiceMessages: 46,\n SetVoiceChannelStatus: 48,\n SendPolls: 49,\n UseExternalApps: 50,\n PinMessages: 51,\n BypassSlowmode: 52,\n};\n\nexport function permissionBits(names: Permission[]): number {\n return [...new Set(names)].reduce((bits, name) => bits + 2 ** BIT[name], 0);\n}\n","import type {\n APIApplicationCommandInteractionDataOption,\n APIChatInputApplicationCommandInteraction,\n APIMessage,\n APIMessageApplicationCommandInteraction,\n APIUser,\n APIUserApplicationCommandInteraction,\n ApplicationCommandOptionType,\n} from 'discord-api-types/v10';\n\nimport type { Schemas } from '#lib/routes';\nimport { type Locales, localePayload } from '#utils/locale';\nimport type { FollowUps, MessageOptions, ReplyOptions } from '#utils/message';\nimport type { Modal } from '#utils/modal';\nimport {\n type CommandOption,\n type Option,\n type Options,\n type OptionValue,\n optionsPayload,\n} from '#utils/option';\nimport { type Permission, permissionBits } from '#utils/permissions';\n\n/** What the handler of a slash command gets. */\nexport type Context<O extends Options> = {\n /** The interaction as Discord sent it. */\n interaction: APIChatInputApplicationCommandInteraction;\n /** The option values, keyed by name. An option that is not required can be `undefined`. */\n options: {\n [K in keyof O]: O[K] extends Option<keyof OptionValue, infer R, infer V>\n ? R extends true\n ? V\n : V | undefined\n : never;\n };\n /** Answers within 3 seconds. Return what it gives. */\n reply: (content: string, options?: ReplyOptions) => Response;\n /**\n * Shows \"thinkingβ¦\" now, then edits the answer to what `work` returns: the text, or\n * `{ content, buttons, embeds }`. Use it for work that takes longer than 3 seconds.\n */\n defer: (\n work: () => Promise<string | ({ content: string } & MessageOptions)>,\n options?: { ephemeral?: boolean },\n ) => Response;\n /** Opens a modal made with `modal()`. `value` comes back to its handler as `c.value`. */\n modal: (modal: Modal, value?: string) => Response;\n} & FollowUps;\n\n/** Who sees a command or a context menu item, and where. */\nexport type Access = {\n /**\n * The name and description in other languages, keyed by Discord locale, such as\n * `{ th: { name: 'ΰΈΰΈΰΈ’', description: 'ΰΈΰΈΰΈ’ΰΈ₯ΰΈΉΰΈΰΉΰΈΰΉΰΈ²' } }`. The handler still gets the default name.\n */\n locales?: Locales;\n /**\n * Who sees it by default: members with all of these permissions, such as `['ManageGuild']`.\n * Pass `[]` to show it to admins only. Server admins can change this in Server Settings.\n * Applies to a top-level command only.\n */\n permissions?: Permission[];\n /** Where it shows: `guild`, `bot_dm` or `private_channel`. */\n contexts?: ('guild' | 'bot_dm' | 'private_channel')[];\n /** How the app must be installed for it: `guild` or `user`. */\n installs?: ('guild' | 'user')[];\n};\n\nconst CONTEXT = { guild: 0, bot_dm: 1, private_channel: 2 } as const;\nconst INSTALL = { guild: 0, user: 1 } as const;\n\nfunction accessPayload(access: Access): Partial<Schemas['ApplicationCommandUpdateRequest']> {\n const fields: Partial<Schemas['ApplicationCommandUpdateRequest']> = localePayload(access.locales);\n if (access.permissions) fields.default_member_permissions = permissionBits(access.permissions);\n if (access.contexts) fields.contexts = access.contexts.map((c) => CONTEXT[c]);\n if (access.installs) fields.integration_types = access.installs.map((i) => INSTALL[i]);\n\n return fields;\n}\n\nexport type Command<O extends Options = Options> =\n | ({\n name: string;\n description: string;\n options: O;\n run(c: Context<O>): Response | Promise<Response>;\n } & Access)\n | ({ name: string; description: string; subcommands: Command[] } & Access);\n\nexport type MenuType = 'user' | 'message';\n\nexport type MenuContext<T extends MenuType> = {\n interaction: T extends 'user'\n ? APIUserApplicationCommandInteraction\n : APIMessageApplicationCommandInteraction;\n target: T extends 'user' ? APIUser : APIMessage;\n reply: Context<Options>['reply'];\n defer: Context<Options>['defer'];\n modal: Context<Options>['modal'];\n} & FollowUps;\n\nexport type ContextMenu<T extends MenuType = MenuType> = {\n menu: T;\n name: string;\n run(c: MenuContext<T>): Response | Promise<Response>;\n} & Access;\n\nconst SUBCOMMAND: ApplicationCommandOptionType.Subcommand = 1;\nconst GROUP: ApplicationCommandOptionType.SubcommandGroup = 2;\n\nfunction childPayload(child: Command): CommandOption {\n const { name, description } = child;\n if (!('subcommands' in child)) {\n return {\n type: SUBCOMMAND,\n name,\n description,\n ...localePayload(child.locales),\n options: optionsPayload(child.options),\n } as CommandOption;\n }\n\n return {\n type: GROUP,\n name,\n description,\n ...localePayload(child.locales),\n options: child.subcommands.map(childPayload),\n } as CommandOption;\n}\n\nexport function payload(\n command: Command | ContextMenu,\n): Schemas['ApplicationCommandUpdateRequest'] {\n const access = accessPayload(command);\n if ('menu' in command) {\n delete access.description_localizations;\n return { type: command.menu === 'user' ? 2 : 3, name: command.name, ...access };\n }\n\n const { name, description } = command;\n const options =\n 'subcommands' in command\n ? command.subcommands.map(childPayload)\n : optionsPayload(command.options);\n\n return { type: 1, name, description, options, ...access };\n}\n\nexport function findCommand(\n commands: Map<string, Command>,\n data: { name: string; options?: APIApplicationCommandInteractionDataOption[] },\n): {\n found: Command | undefined;\n given: APIApplicationCommandInteractionDataOption[];\n path: string;\n} {\n const path = [data.name];\n let found = commands.get(data.name);\n let given = data.options ?? [];\n while (found && 'subcommands' in found) {\n const picked = given[0];\n found = found.subcommands.find((child) => child.name === picked?.name);\n path.push(picked?.name ?? '');\n given = picked && 'options' in picked ? (picked.options ?? []) : [];\n }\n\n return { found, given, path: `/${path.join(' ')}` };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discord-DEZe1asd.mjs","names":[],"sources":["../src/utils/locale.ts","../src/utils/option.ts","../src/utils/upload.ts","../src/lib/rest.ts","../src/utils/button.ts","../src/utils/select.ts","../src/utils/message.ts","../src/lib/discord.ts"],"sourcesContent":["import type { Locale } from 'discord-api-types/v10';\n\nexport type Locales = Partial<Record<`${Locale}`, { name?: string; description?: string }>>;\n\nexport function localePayload(locales: Locales | undefined): {\n name_localizations?: Record<string, string>;\n description_localizations?: Record<string, string>;\n} {\n const names: Record<string, string> = {};\n const descriptions: Record<string, string> = {};\n for (const [locale, text] of Object.entries(locales ?? {})) {\n if (text.name !== undefined) names[locale] = text.name;\n if (text.description !== undefined) descriptions[locale] = text.description;\n }\n\n const fields: ReturnType<typeof localePayload> = {};\n if (Object.keys(names).length > 0) fields.name_localizations = names;\n if (Object.keys(descriptions).length > 0) fields.description_localizations = descriptions;\n\n return fields;\n}\n","import type {\n APIApplicationCommandAutocompleteInteraction,\n APIApplicationCommandInteractionDataOption,\n APIChatInputApplicationCommandInteractionData,\n APIAttachment,\n APIInteractionDataResolvedChannel,\n APIInteractionDataResolvedGuildMember,\n APIRole,\n APIUser,\n ApplicationCommandOptionType,\n} from 'discord-api-types/v10';\n\nimport type { Schemas } from '#lib/routes';\nimport { type Locales, localePayload } from '#utils/locale';\n\nexport type OptionValue = {\n string: string;\n integer: number;\n number: number;\n boolean: boolean;\n user: APIUser & { member?: APIInteractionDataResolvedGuildMember };\n channel: APIInteractionDataResolvedChannel;\n role: APIRole;\n mentionable: (APIUser & { member?: APIInteractionDataResolvedGuildMember }) | APIRole;\n attachment: APIAttachment;\n};\n\nexport type ChannelType =\n | 'text'\n | 'voice'\n | 'category'\n | 'announcement'\n | 'announcement_thread'\n | 'public_thread'\n | 'private_thread'\n | 'stage'\n | 'directory'\n | 'forum';\n\nexport const CHANNEL_TYPES: Record<ChannelType, Schemas['ChannelTypes']> = {\n text: 0,\n voice: 2,\n category: 4,\n announcement: 5,\n announcement_thread: 10,\n public_thread: 11,\n private_thread: 12,\n stage: 13,\n directory: 14,\n forum: 15,\n};\n\nexport type Option<\n T extends keyof OptionValue = keyof OptionValue,\n R extends boolean = boolean,\n V = OptionValue[T],\n> = {\n type: T;\n description: string;\n required: R;\n choices?: Record<string, V>;\n min?: number;\n max?: number;\n minLength?: number;\n maxLength?: number;\n channelTypes?: ChannelType[];\n locales?: Locales;\n autocomplete?: (c: AutocompleteContext) => Suggestion<V>[] | Promise<Suggestion<V>[]>;\n};\n\nexport type AutocompleteContext = {\n interaction: APIApplicationCommandAutocompleteInteraction;\n value: string;\n};\n\nexport type Suggestion<V> = V | { name: string; value: V };\n\nexport type Options = Record<string, Option<keyof OptionValue, boolean, unknown>>;\n\nconst OPTION_TYPE: Record<keyof OptionValue, ApplicationCommandOptionType> = {\n string: 3,\n integer: 4,\n number: 10,\n boolean: 5,\n user: 6,\n channel: 7,\n role: 8,\n mentionable: 9,\n attachment: 11,\n};\n\nconst USER: ApplicationCommandOptionType.User = 6;\nconst CHANNEL: ApplicationCommandOptionType.Channel = 7;\nconst ROLE: ApplicationCommandOptionType.Role = 8;\nconst MENTIONABLE: ApplicationCommandOptionType.Mentionable = 9;\nconst ATTACHMENT: ApplicationCommandOptionType.Attachment = 11;\n\nexport type CommandOption = NonNullable<\n Schemas['ApplicationCommandUpdateRequest']['options']\n>[number];\n\nexport function optionsPayload(options: Options): CommandOption[] {\n return Object.entries(options)\n .map(([name, o]) => {\n const built: Record<string, unknown> = {\n name,\n description: o.description,\n type: OPTION_TYPE[o.type],\n required: o.required,\n };\n\n if (o.choices) {\n built.choices = Object.entries(o.choices).map(([label, value]) => ({ name: label, value }));\n }\n if (o.min !== undefined) built.min_value = o.min;\n if (o.max !== undefined) built.max_value = o.max;\n if (o.minLength !== undefined) built.min_length = o.minLength;\n if (o.maxLength !== undefined) built.max_length = o.maxLength;\n if (o.channelTypes) built.channel_types = o.channelTypes.map((t) => CHANNEL_TYPES[t]);\n if (o.autocomplete) built.autocomplete = true;\n Object.assign(built, localePayload(o.locales));\n\n return built as CommandOption;\n })\n .toSorted((a, b) => Number(b.required) - Number(a.required));\n}\n\nfunction userWithMember(\n resolved: APIChatInputApplicationCommandInteractionData['resolved'],\n id: string,\n): OptionValue['user'] | undefined {\n const user = resolved?.users?.[id];\n if (!user) return undefined;\n\n const member = resolved?.members?.[id];\n\n return member ? { ...user, member } : user;\n}\n\nexport function optionValues(\n options: APIApplicationCommandInteractionDataOption[],\n resolved: APIChatInputApplicationCommandInteractionData['resolved'],\n): Record<string, OptionValue[keyof OptionValue] | undefined> {\n const values: Record<string, OptionValue[keyof OptionValue] | undefined> = {};\n\n for (const o of options) {\n if (!('value' in o)) continue;\n\n const id = String(o.value);\n if (o.type === USER) values[o.name] = userWithMember(resolved, id);\n else if (o.type === CHANNEL) values[o.name] = resolved?.channels?.[id];\n else if (o.type === ROLE) values[o.name] = resolved?.roles?.[id];\n else if (o.type === MENTIONABLE)\n values[o.name] = userWithMember(resolved, id) ?? resolved?.roles?.[id];\n else if (o.type === ATTACHMENT) values[o.name] = resolved?.attachments?.[id];\n else values[o.name] = o.value;\n }\n\n return values;\n}\n","export type Upload = {\n name: string;\n data: Blob | ArrayBuffer | Uint8Array<ArrayBuffer> | string;\n description?: string;\n};\n\nexport function attachmentsOf(\n files: Upload[],\n): { id: string; filename: string; description?: string }[] {\n return files.map((file, i) => ({\n id: String(i),\n filename: file.name,\n ...(file.description !== undefined && { description: file.description }),\n }));\n}\n\nexport function formOf(payload: unknown, files: Upload[]): FormData {\n const form = new FormData();\n form.append('payload_json', JSON.stringify(payload));\n files.forEach((file, i) => {\n const blob = file.data instanceof Blob ? file.data : new Blob([file.data]);\n form.append(`files[${i}]`, blob, file.name);\n });\n\n return form;\n}\n\nexport async function uploadsOf(form: FormData): Promise<{ payload: unknown; files: Upload[] }> {\n const files: Upload[] = [];\n for (const [key, value] of form.entries()) {\n if (key.startsWith('files[') && typeof value !== 'string') {\n files.push({ name: value.name, data: value });\n }\n }\n\n const payload = form.get('payload_json');\n\n return { payload: typeof payload === 'string' ? (JSON.parse(payload) as unknown) : null, files };\n}\n","import type { Routes } from '#lib/routes';\nimport { formOf, type Upload } from '#utils/upload';\n\nconst API = 'https://discord.com/api/v10';\nconst TRIES = 3;\n\ntype Method = 'get' | 'post' | 'put' | 'patch' | 'delete';\n\ntype Init = {\n path?: Record<string, string>;\n query?: object;\n body?: unknown;\n files?: Upload[];\n reason?: string;\n};\n\ntype Route<M extends Method> = {\n [K in keyof Routes]: M extends keyof Routes[K] ? K : never;\n}[keyof Routes];\n\ntype Operation<P extends keyof Routes, M extends Method> =\n Routes[P] extends Record<M, infer O extends { args: unknown[]; result: unknown }> ? O : never;\n\ntype Result<P extends keyof Routes, M extends Method> = Promise<\n [Error, null] | [null, Operation<P, M>['result']]\n>;\n\n/** Calls the Discord REST API. Each method takes a route from the Discord API spec. */\nexport type Rest = {\n [M in Method]: <P extends Route<M>>(path: P, ...args: Operation<P, M>['args']) => Result<P, M>;\n};\n\nexport function callDiscord<M extends Method, P extends Route<M>>(\n token: string | null,\n method: M,\n route: P,\n ...args: Operation<P, M>['args']\n): Result<P, M> {\n return request(token, method, route, args[0] as Init | undefined);\n}\n\nexport function createRest(token: () => unknown): Rest {\n const call =\n (method: Method) =>\n async (route: string, init?: Init): Promise<[Error, null] | [null, unknown]> => {\n const value = token();\n if (typeof value !== 'string') return [new Error('DISCORD_TOKEN is not set'), null];\n\n return request(value, method, route, init);\n };\n\n return {\n get: call('get'),\n post: call('post'),\n put: call('put'),\n patch: call('patch'),\n delete: call('delete'),\n } as Rest;\n}\n\nasync function request<T>(\n token: string | null,\n method: Method,\n route: string,\n init: Init = {},\n): Promise<[Error, null] | [null, T]> {\n const path = route.replace(/\\{(\\w+)\\}/g, (_, name: string) =>\n encodeURIComponent(init.path?.[name] ?? ''),\n );\n\n const query = new URLSearchParams();\n for (const [name, value] of Object.entries(init.query ?? {})) {\n if (value !== undefined) query.set(name, String(value));\n }\n const url = query.size > 0 ? `${API}${path}?${query.toString()}` : `${API}${path}`;\n\n const headers: Record<string, string> = {};\n if (token) headers.Authorization = `Bot ${token}`;\n if (init.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(init.reason);\n let body: string | FormData | undefined;\n if (init.files?.length) {\n body = formOf(init.body, init.files);\n } else if (init.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n body = JSON.stringify(init.body);\n }\n\n const send = async (attempt: number): Promise<[Error, null] | [null, T]> => {\n const res = await fetch(url, { method: method.toUpperCase(), headers, body });\n\n if (res.status === 429 && attempt < TRIES) {\n const { retry_after } = (await res.json()) as { retry_after: number };\n await new Promise((resolve) => setTimeout(resolve, retry_after * 1000));\n return send(attempt + 1);\n }\n\n if (!res.ok) return [new Error(`Discord answered ${res.status}: ${await res.text()}`), null];\n const data: unknown = res.status === 204 ? null : await res.json();\n\n return [null, data as T];\n };\n\n return send(1);\n}\n","import type { Schemas } from '#lib/routes';\n\nexport type Button = (\n | { id: string; style?: ButtonStyle; url?: never }\n | { url: string; id?: never; style?: never }\n) &\n ({ label: string; emoji?: string } | { label?: string; emoji: string }) & { disabled?: boolean };\n\ntype ButtonStyle = 'primary' | 'secondary' | 'success' | 'danger';\n\nconst STYLE: Record<ButtonStyle, Schemas['ButtonStyleTypes']> = {\n primary: 1,\n secondary: 2,\n success: 3,\n danger: 4,\n};\nconst LINK: Schemas['ButtonStyleTypes'] = 5;\nconst CUSTOM_EMOJI = /^<a?:(\\w+):(\\d+)>$/;\n\nfunction emojiOf(text: string): Schemas['ComponentEmojiForRequest'] {\n const custom = CUSTOM_EMOJI.exec(text);\n if (!custom?.[1]) return { name: text };\n\n return { name: custom[1], id: custom[2] };\n}\n\nexport function buttonComponent(button: Button): Schemas['ButtonComponentForMessageRequest'] {\n const component: Schemas['ButtonComponentForMessageRequest'] = {\n type: 2,\n style: 'url' in button ? LINK : STYLE[button.style ?? 'primary'],\n };\n\n if (button.label !== undefined) component.label = button.label;\n if ('url' in button) component.url = button.url;\n else component.custom_id = button.id;\n if (button.emoji) component.emoji = emojiOf(button.emoji);\n if (button.disabled) component.disabled = true;\n\n return component;\n}\n","import type { APIMessageComponentSelectMenuInteraction } from 'discord-api-types/v10';\n\nimport type { Schemas } from '#lib/routes';\nimport { CHANNEL_TYPES, type ChannelType } from '#utils/option';\n\nexport type SelectType = 'string' | 'user' | 'role' | 'channel' | 'mentionable';\n\nexport type SelectMenu = {\n type: SelectType;\n id: string;\n placeholder?: string;\n min?: number;\n max?: number;\n options?: Record<string, string>;\n channelTypes?: ChannelType[];\n};\n\nconst COMPONENT: Record<SelectType, 3 | 5 | 6 | 7 | 8> = {\n string: 3,\n user: 5,\n role: 6,\n mentionable: 7,\n channel: 8,\n};\n\nexport function selectRow(menu: SelectMenu): Schemas['ActionRowComponentForMessageRequest'] {\n const component: Record<string, unknown> = { type: COMPONENT[menu.type], custom_id: menu.id };\n\n if (menu.placeholder !== undefined) component.placeholder = menu.placeholder;\n if (menu.min !== undefined) component.min_values = menu.min;\n if (menu.max !== undefined) component.max_values = menu.max;\n if (menu.options) {\n component.options = Object.entries(menu.options).map(([label, value]) => ({ label, value }));\n }\n if (menu.channelTypes) component.channel_types = menu.channelTypes.map((t) => CHANNEL_TYPES[t]);\n\n return {\n type: 1,\n components: [component as Schemas['ActionRowComponentForMessageRequest']['components'][number]],\n };\n}\n\nexport function selectValues(interaction: APIMessageComponentSelectMenuInteraction): unknown[] {\n const { data } = interaction;\n const resolved = 'resolved' in data ? data.resolved : undefined;\n\n return data.values.map((id) => {\n if (!resolved) return id;\n if ('users' in resolved && resolved.users?.[id]) return resolved.users[id];\n if ('roles' in resolved && resolved.roles?.[id]) return resolved.roles[id];\n if ('channels' in resolved && resolved.channels?.[id]) return resolved.channels[id];\n\n return id;\n });\n}\n","import type { MessageFlags } from 'discord-api-types/v10';\n\nimport type { Schemas } from '#lib/routes';\nimport { type Button, buttonComponent } from '#utils/button';\nimport { type SelectMenu, selectRow } from '#utils/select';\nimport { attachmentsOf, type Upload } from '#utils/upload';\n\nexport type MessageOptions = {\n buttons?: Button[];\n selects?: SelectMenu[];\n embeds?: Schemas['RichEmbed'][];\n allowedMentions?: Schemas['MessageAllowedMentionsRequest'];\n files?: Upload[];\n};\n\nexport type ReplyOptions = MessageOptions & { ephemeral?: boolean };\n\nexport type FollowUps = {\n after(work: () => Promise<void>): void;\n followUp(content: string, options?: ReplyOptions): Promise<Error | null>;\n editReply(content: string, options?: MessageOptions): Promise<Error | null>;\n deleteReply(): Promise<Error | null>;\n};\n\nconst PER_ROW = 5;\nconst EPHEMERAL: MessageFlags.Ephemeral = 64;\nconst NO_PINGS: Schemas['MessageAllowedMentionsRequest'] = { parse: [] };\n\nexport function message(\n content: string,\n options: ReplyOptions = {},\n): Schemas['IncomingWebhookInteractionRequest'] {\n const data: Schemas['IncomingWebhookInteractionRequest'] = {\n content,\n allowed_mentions: options.allowedMentions ?? NO_PINGS,\n };\n if (options.ephemeral) data.flags = EPHEMERAL;\n if (options.embeds) data.embeds = options.embeds;\n if (options.files?.length) data.attachments = attachmentsOf(options.files);\n if (!options.buttons && !options.selects) return data;\n\n const rows = (options.selects ?? []).map(selectRow);\n const buttons = options.buttons ?? [];\n for (let i = 0; i < buttons.length; i += PER_ROW) {\n rows.push({\n type: 1,\n components: buttons.slice(i, i + PER_ROW).map(buttonComponent),\n });\n }\n data.components = rows;\n\n return data;\n}\n","import { callDiscord } from '#lib/rest';\nimport type { Schemas } from '#lib/routes';\nimport type { SyncEnv } from '#utils/env';\nimport { message, type MessageOptions } from '#utils/message';\nimport { type Upload, uploadsOf } from '#utils/upload';\n\nexport async function putCommands(\n env: SyncEnv,\n body: Schemas['ApplicationCommandUpdateRequest'][],\n): Promise<[Error, null] | [null, number]> {\n const [error, saved] = env.guildId\n ? await callDiscord(\n env.token,\n 'put',\n '/applications/{application_id}/guilds/{guild_id}/commands',\n {\n path: { application_id: env.applicationId, guild_id: env.guildId },\n body,\n },\n )\n : await callDiscord(env.token, 'put', '/applications/{application_id}/commands', {\n path: { application_id: env.applicationId },\n body,\n });\n if (error) return [error, null];\n\n return [null, saved?.length ?? 0];\n}\n\nexport async function editOriginal(\n applicationId: string,\n token: string,\n body: Schemas['IncomingWebhookUpdateRequestPartial'],\n files?: Upload[],\n): Promise<Error | null> {\n const [error] = await callDiscord(\n null,\n 'patch',\n '/webhooks/{webhook_id}/{webhook_token}/messages/@original',\n {\n path: { webhook_id: applicationId, webhook_token: token },\n body,\n files,\n },\n );\n\n return error;\n}\n\nexport type Send = (\n channelId: string,\n content: string,\n options?: MessageOptions,\n) => Promise<Error | null>;\n\nexport async function sendMessage(\n token: string,\n channelId: string,\n content: string,\n options: MessageOptions = {},\n): Promise<Error | null> {\n const [error] = await callDiscord(token, 'post', '/channels/{channel_id}/messages', {\n path: { channel_id: channelId },\n body: message(content, {\n buttons: options.buttons,\n selects: options.selects,\n embeds: options.embeds,\n allowedMentions: options.allowedMentions,\n files: options.files,\n }),\n files: options.files,\n });\n\n return error;\n}\n\nexport async function interactionCallback(\n id: string,\n token: string,\n response: Response,\n): Promise<Error | null> {\n const multipart = response.headers.get('content-type')?.startsWith('multipart/form-data');\n const { payload, files } = multipart\n ? await uploadsOf(await response.formData())\n : { payload: JSON.parse(await response.text()) as unknown, files: [] };\n\n const [error] = await callDiscord(\n null,\n 'post',\n '/interactions/{interaction_id}/{interaction_token}/callback',\n {\n path: { interaction_id: id, interaction_token: token },\n body: payload as Schemas['create_interaction_response_body'],\n files,\n },\n );\n\n return error;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"discord-DEZe1asd.mjs","names":[],"sources":["../src/utils/locale.ts","../src/utils/option.ts","../src/utils/upload.ts","../src/lib/rest.ts","../src/utils/button.ts","../src/utils/select.ts","../src/utils/message.ts","../src/lib/discord.ts"],"sourcesContent":["import type { Locale } from 'discord-api-types/v10';\n\nexport type Locales = Partial<Record<`${Locale}`, { name?: string; description?: string }>>;\n\nexport function localePayload(locales: Locales | undefined): {\n name_localizations?: Record<string, string>;\n description_localizations?: Record<string, string>;\n} {\n const names: Record<string, string> = {};\n const descriptions: Record<string, string> = {};\n for (const [locale, text] of Object.entries(locales ?? {})) {\n if (text.name !== undefined) names[locale] = text.name;\n if (text.description !== undefined) descriptions[locale] = text.description;\n }\n\n const fields: ReturnType<typeof localePayload> = {};\n if (Object.keys(names).length > 0) fields.name_localizations = names;\n if (Object.keys(descriptions).length > 0) fields.description_localizations = descriptions;\n\n return fields;\n}\n","import type {\n APIApplicationCommandAutocompleteInteraction,\n APIApplicationCommandInteractionDataOption,\n APIChatInputApplicationCommandInteractionData,\n APIAttachment,\n APIInteractionDataResolvedChannel,\n APIInteractionDataResolvedGuildMember,\n APIRole,\n APIUser,\n ApplicationCommandOptionType,\n} from 'discord-api-types/v10';\n\nimport type { Schemas } from '#lib/routes';\nimport { type Locales, localePayload } from '#utils/locale';\n\nexport type OptionValue = {\n string: string;\n integer: number;\n number: number;\n boolean: boolean;\n user: APIUser & { member?: APIInteractionDataResolvedGuildMember };\n channel: APIInteractionDataResolvedChannel;\n role: APIRole;\n mentionable: (APIUser & { member?: APIInteractionDataResolvedGuildMember }) | APIRole;\n attachment: APIAttachment;\n};\n\nexport type ChannelType =\n | 'text'\n | 'voice'\n | 'category'\n | 'announcement'\n | 'announcement_thread'\n | 'public_thread'\n | 'private_thread'\n | 'stage'\n | 'directory'\n | 'forum';\n\nexport const CHANNEL_TYPES: Record<ChannelType, Schemas['ChannelTypes']> = {\n text: 0,\n voice: 2,\n category: 4,\n announcement: 5,\n announcement_thread: 10,\n public_thread: 11,\n private_thread: 12,\n stage: 13,\n directory: 14,\n forum: 15,\n};\n\nexport type Option<\n T extends keyof OptionValue = keyof OptionValue,\n R extends boolean = boolean,\n V = OptionValue[T],\n> = {\n type: T;\n description: string;\n required: R;\n choices?: Record<string, V>;\n min?: number;\n max?: number;\n minLength?: number;\n maxLength?: number;\n channelTypes?: ChannelType[];\n locales?: Locales;\n autocomplete?: (c: AutocompleteContext) => Suggestion<V>[] | Promise<Suggestion<V>[]>;\n};\n\nexport type AutocompleteContext = {\n interaction: APIApplicationCommandAutocompleteInteraction;\n value: string;\n};\n\nexport type Suggestion<V> = V | { name: string; value: V };\n\nexport type Options = Record<string, Option<keyof OptionValue, boolean, unknown>>;\n\nconst OPTION_TYPE: Record<keyof OptionValue, ApplicationCommandOptionType> = {\n string: 3,\n integer: 4,\n number: 10,\n boolean: 5,\n user: 6,\n channel: 7,\n role: 8,\n mentionable: 9,\n attachment: 11,\n};\n\nconst USER: ApplicationCommandOptionType.User = 6;\nconst CHANNEL: ApplicationCommandOptionType.Channel = 7;\nconst ROLE: ApplicationCommandOptionType.Role = 8;\nconst MENTIONABLE: ApplicationCommandOptionType.Mentionable = 9;\nconst ATTACHMENT: ApplicationCommandOptionType.Attachment = 11;\n\nexport type CommandOption = NonNullable<\n Schemas['ApplicationCommandUpdateRequest']['options']\n>[number];\n\nexport function optionsPayload(options: Options): CommandOption[] {\n return Object.entries(options)\n .map(([name, o]) => {\n const built: Record<string, unknown> = {\n name,\n description: o.description,\n type: OPTION_TYPE[o.type],\n required: o.required,\n };\n\n if (o.choices) {\n built.choices = Object.entries(o.choices).map(([label, value]) => ({ name: label, value }));\n }\n if (o.min !== undefined) built.min_value = o.min;\n if (o.max !== undefined) built.max_value = o.max;\n if (o.minLength !== undefined) built.min_length = o.minLength;\n if (o.maxLength !== undefined) built.max_length = o.maxLength;\n if (o.channelTypes) built.channel_types = o.channelTypes.map((t) => CHANNEL_TYPES[t]);\n if (o.autocomplete) built.autocomplete = true;\n Object.assign(built, localePayload(o.locales));\n\n return built as CommandOption;\n })\n .toSorted((a, b) => Number(b.required) - Number(a.required));\n}\n\nfunction userWithMember(\n resolved: APIChatInputApplicationCommandInteractionData['resolved'],\n id: string,\n): OptionValue['user'] | undefined {\n const user = resolved?.users?.[id];\n if (!user) return undefined;\n\n const member = resolved?.members?.[id];\n\n return member ? { ...user, member } : user;\n}\n\nexport function optionValues(\n options: APIApplicationCommandInteractionDataOption[],\n resolved: APIChatInputApplicationCommandInteractionData['resolved'],\n): Record<string, OptionValue[keyof OptionValue] | undefined> {\n const values: Record<string, OptionValue[keyof OptionValue] | undefined> = {};\n\n for (const o of options) {\n if (!('value' in o)) continue;\n\n const id = String(o.value);\n if (o.type === USER) values[o.name] = userWithMember(resolved, id);\n else if (o.type === CHANNEL) values[o.name] = resolved?.channels?.[id];\n else if (o.type === ROLE) values[o.name] = resolved?.roles?.[id];\n else if (o.type === MENTIONABLE)\n values[o.name] = userWithMember(resolved, id) ?? resolved?.roles?.[id];\n else if (o.type === ATTACHMENT) values[o.name] = resolved?.attachments?.[id];\n else values[o.name] = o.value;\n }\n\n return values;\n}\n","export type Upload = {\n name: string;\n data: Blob | ArrayBuffer | Uint8Array<ArrayBuffer> | string;\n description?: string;\n};\n\nexport function attachmentsOf(\n files: Upload[],\n): { id: string; filename: string; description?: string }[] {\n return files.map((file, i) => ({\n id: String(i),\n filename: file.name,\n ...(file.description !== undefined && { description: file.description }),\n }));\n}\n\nexport function formOf(payload: unknown, files: Upload[]): FormData {\n const form = new FormData();\n form.append('payload_json', JSON.stringify(payload));\n files.forEach((file, i) => {\n const blob = file.data instanceof Blob ? file.data : new Blob([file.data]);\n form.append(`files[${i}]`, blob, file.name);\n });\n\n return form;\n}\n\nexport async function uploadsOf(form: FormData): Promise<{ payload: unknown; files: Upload[] }> {\n const files: Upload[] = [];\n for (const [key, value] of form.entries()) {\n if (key.startsWith('files[') && typeof value !== 'string') {\n files.push({ name: value.name, data: value });\n }\n }\n\n const payload = form.get('payload_json');\n\n return { payload: typeof payload === 'string' ? (JSON.parse(payload) as unknown) : null, files };\n}\n","import type { Routes } from '#lib/routes';\nimport { formOf, type Upload } from '#utils/upload';\n\nconst API = 'https://discord.com/api/v10';\nconst TRIES = 3;\n\ntype Method = 'get' | 'post' | 'put' | 'patch' | 'delete';\n\ntype Init = {\n path?: Record<string, string>;\n query?: object;\n body?: unknown;\n files?: Upload[];\n reason?: string;\n};\n\ntype Route<M extends Method> = {\n [K in keyof Routes]: M extends keyof Routes[K] ? K : never;\n}[keyof Routes];\n\ntype Operation<P extends keyof Routes, M extends Method> =\n Routes[P] extends Record<M, infer O extends { args: unknown[]; result: unknown }> ? O : never;\n\ntype Result<P extends keyof Routes, M extends Method> = Promise<\n [Error, null] | [null, Operation<P, M>['result']]\n>;\n\n/** Calls the Discord REST API. Each method takes a route from the Discord API spec. */\nexport type Rest = {\n [M in Method]: <P extends Route<M>>(path: P, ...args: Operation<P, M>['args']) => Result<P, M>;\n};\n\nexport function callDiscord<M extends Method, P extends Route<M>>(\n token: string | null,\n method: M,\n route: P,\n ...args: Operation<P, M>['args']\n): Result<P, M> {\n return request(token, method, route, args[0] as Init | undefined);\n}\n\nexport function createRest(token: () => unknown): Rest {\n const call =\n (method: Method) =>\n async (route: string, init?: Init): Promise<[Error, null] | [null, unknown]> => {\n const value = token();\n if (typeof value !== 'string') return [new Error('DISCORD_TOKEN is not set'), null];\n\n return request(value, method, route, init);\n };\n\n return {\n get: call('get'),\n post: call('post'),\n put: call('put'),\n patch: call('patch'),\n delete: call('delete'),\n } as Rest;\n}\n\nasync function request<T>(\n token: string | null,\n method: Method,\n route: string,\n init: Init = {},\n): Promise<[Error, null] | [null, T]> {\n const path = route.replace(/\\{(\\w+)\\}/g, (_, name: string) =>\n encodeURIComponent(init.path?.[name] ?? ''),\n );\n\n const query = new URLSearchParams();\n for (const [name, value] of Object.entries(init.query ?? {})) {\n if (value !== undefined) query.set(name, String(value));\n }\n const url = query.size > 0 ? `${API}${path}?${query.toString()}` : `${API}${path}`;\n\n const headers: Record<string, string> = {};\n if (token) headers.Authorization = `Bot ${token}`;\n if (init.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(init.reason);\n let body: string | FormData | undefined;\n if (init.files?.length) {\n body = formOf(init.body, init.files);\n } else if (init.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n body = JSON.stringify(init.body);\n }\n\n const send = async (attempt: number): Promise<[Error, null] | [null, T]> => {\n const res = await fetch(url, { method: method.toUpperCase(), headers, body });\n\n if (res.status === 429 && attempt < TRIES) {\n const { retry_after } = (await res.json()) as { retry_after: number };\n await new Promise((resolve) => setTimeout(resolve, retry_after * 1000));\n return send(attempt + 1);\n }\n\n if (!res.ok) return [new Error(`Discord answered ${res.status}: ${await res.text()}`), null];\n const data: unknown = res.status === 204 ? null : await res.json();\n\n return [null, data as T];\n };\n\n return send(1);\n}\n","import type { Schemas } from '#lib/routes';\n\n/**\n * A button on a message. A button with an `id` runs the handler with that id in the `buttons` of\n * `bot()`, and takes a `style`: `primary` (default), `secondary`, `success` or `danger`. A button\n * with a `url` opens the link and needs no handler. A button needs a `label`, an `emoji` or both.\n * An `emoji` is a Unicode emoji, or a custom emoji such as `<:name:id>`.\n */\nexport type Button = (\n | {\n /** The handler name, then an optional `:` and a value, such as `page:2`. */\n id: string;\n /** `primary` (default), `secondary`, `success` or `danger`. */\n style?: ButtonStyle;\n url?: never;\n }\n | {\n /** The link the button opens. */\n url: string;\n id?: never;\n style?: never;\n }\n) &\n (\n | {\n /** The text on the button. */\n label: string;\n /** A Unicode emoji, or a custom emoji such as `<:name:id>`. */\n emoji?: string;\n }\n | { label?: string; emoji: string }\n ) & {\n /** `true` greys the button out. */\n disabled?: boolean;\n };\n\ntype ButtonStyle = 'primary' | 'secondary' | 'success' | 'danger';\n\nconst STYLE: Record<ButtonStyle, Schemas['ButtonStyleTypes']> = {\n primary: 1,\n secondary: 2,\n success: 3,\n danger: 4,\n};\nconst LINK: Schemas['ButtonStyleTypes'] = 5;\nconst CUSTOM_EMOJI = /^<a?:(\\w+):(\\d+)>$/;\n\nfunction emojiOf(text: string): Schemas['ComponentEmojiForRequest'] {\n const custom = CUSTOM_EMOJI.exec(text);\n if (!custom?.[1]) return { name: text };\n\n return { name: custom[1], id: custom[2] };\n}\n\nexport function buttonComponent(button: Button): Schemas['ButtonComponentForMessageRequest'] {\n const component: Schemas['ButtonComponentForMessageRequest'] = {\n type: 2,\n style: 'url' in button ? LINK : STYLE[button.style ?? 'primary'],\n };\n\n if (button.label !== undefined) component.label = button.label;\n if ('url' in button) component.url = button.url;\n else component.custom_id = button.id;\n if (button.emoji) component.emoji = emojiOf(button.emoji);\n if (button.disabled) component.disabled = true;\n\n return component;\n}\n","import type { APIMessageComponentSelectMenuInteraction } from 'discord-api-types/v10';\n\nimport type { Schemas } from '#lib/routes';\nimport { CHANNEL_TYPES, type ChannelType } from '#utils/option';\n\nexport type SelectType = 'string' | 'user' | 'role' | 'channel' | 'mentionable';\n\nexport type SelectMenu = {\n type: SelectType;\n id: string;\n placeholder?: string;\n min?: number;\n max?: number;\n options?: Record<string, string>;\n channelTypes?: ChannelType[];\n};\n\nconst COMPONENT: Record<SelectType, 3 | 5 | 6 | 7 | 8> = {\n string: 3,\n user: 5,\n role: 6,\n mentionable: 7,\n channel: 8,\n};\n\nexport function selectRow(menu: SelectMenu): Schemas['ActionRowComponentForMessageRequest'] {\n const component: Record<string, unknown> = { type: COMPONENT[menu.type], custom_id: menu.id };\n\n if (menu.placeholder !== undefined) component.placeholder = menu.placeholder;\n if (menu.min !== undefined) component.min_values = menu.min;\n if (menu.max !== undefined) component.max_values = menu.max;\n if (menu.options) {\n component.options = Object.entries(menu.options).map(([label, value]) => ({ label, value }));\n }\n if (menu.channelTypes) component.channel_types = menu.channelTypes.map((t) => CHANNEL_TYPES[t]);\n\n return {\n type: 1,\n components: [component as Schemas['ActionRowComponentForMessageRequest']['components'][number]],\n };\n}\n\nexport function selectValues(interaction: APIMessageComponentSelectMenuInteraction): unknown[] {\n const { data } = interaction;\n const resolved = 'resolved' in data ? data.resolved : undefined;\n\n return data.values.map((id) => {\n if (!resolved) return id;\n if ('users' in resolved && resolved.users?.[id]) return resolved.users[id];\n if ('roles' in resolved && resolved.roles?.[id]) return resolved.roles[id];\n if ('channels' in resolved && resolved.channels?.[id]) return resolved.channels[id];\n\n return id;\n });\n}\n","import type { MessageFlags } from 'discord-api-types/v10';\n\nimport type { Schemas } from '#lib/routes';\nimport { type Button, buttonComponent } from '#utils/button';\nimport { type SelectMenu, selectRow } from '#utils/select';\nimport { attachmentsOf, type Upload } from '#utils/upload';\n\n/** What a message holds besides its text. */\nexport type MessageOptions = {\n /** Buttons, five to a row. Pass `[]` to remove the buttons of the message. */\n buttons?: Button[];\n /** Select menus made with `select()`, one to a row, above the buttons. */\n selects?: SelectMenu[];\n /** Embeds, as in the Discord API, such as `{ title, description }`. */\n embeds?: Schemas['RichEmbed'][];\n /**\n * Who the message pings. By default it pings nobody, even for `@everyone` or `<@id>` in the\n * text. Pass `{ users: [id] }` or `{ parse: ['roles'] }` to ping.\n */\n allowedMentions?: Schemas['MessageAllowedMentionsRequest'];\n /** Files to attach. */\n files?: Upload[];\n};\n\n/** {@link MessageOptions} for the first answer or a follow-up. */\nexport type ReplyOptions = MessageOptions & {\n /** `true` shows the message to the user who ran the interaction only. */\n ephemeral?: boolean;\n};\n\n/** Calls that send more after the first answer. Each returns an `Error` if Discord refuses. */\nexport type FollowUps = {\n /**\n * Runs `work` once Discord has the first answer. Call it before you return the answer. `work`\n * can call the other follow-up calls.\n */\n after(work: () => Promise<void>): void;\n /** Sends a new message. */\n followUp(content: string, options?: ReplyOptions): Promise<Error | null>;\n /** Edits the first answer. */\n editReply(content: string, options?: MessageOptions): Promise<Error | null>;\n /** Deletes the first answer. */\n deleteReply(): Promise<Error | null>;\n};\n\nconst PER_ROW = 5;\nconst EPHEMERAL: MessageFlags.Ephemeral = 64;\nconst NO_PINGS: Schemas['MessageAllowedMentionsRequest'] = { parse: [] };\n\nexport function message(\n content: string,\n options: ReplyOptions = {},\n): Schemas['IncomingWebhookInteractionRequest'] {\n const data: Schemas['IncomingWebhookInteractionRequest'] = {\n content,\n allowed_mentions: options.allowedMentions ?? NO_PINGS,\n };\n if (options.ephemeral) data.flags = EPHEMERAL;\n if (options.embeds) data.embeds = options.embeds;\n if (options.files?.length) data.attachments = attachmentsOf(options.files);\n if (!options.buttons && !options.selects) return data;\n\n const rows = (options.selects ?? []).map(selectRow);\n const buttons = options.buttons ?? [];\n for (let i = 0; i < buttons.length; i += PER_ROW) {\n rows.push({\n type: 1,\n components: buttons.slice(i, i + PER_ROW).map(buttonComponent),\n });\n }\n data.components = rows;\n\n return data;\n}\n","import { callDiscord } from '#lib/rest';\nimport type { Schemas } from '#lib/routes';\nimport type { SyncEnv } from '#utils/env';\nimport { message, type MessageOptions } from '#utils/message';\nimport { type Upload, uploadsOf } from '#utils/upload';\n\nexport async function putCommands(\n env: SyncEnv,\n body: Schemas['ApplicationCommandUpdateRequest'][],\n): Promise<[Error, null] | [null, number]> {\n const [error, saved] = env.guildId\n ? await callDiscord(\n env.token,\n 'put',\n '/applications/{application_id}/guilds/{guild_id}/commands',\n {\n path: { application_id: env.applicationId, guild_id: env.guildId },\n body,\n },\n )\n : await callDiscord(env.token, 'put', '/applications/{application_id}/commands', {\n path: { application_id: env.applicationId },\n body,\n });\n if (error) return [error, null];\n\n return [null, saved?.length ?? 0];\n}\n\nexport async function editOriginal(\n applicationId: string,\n token: string,\n body: Schemas['IncomingWebhookUpdateRequestPartial'],\n files?: Upload[],\n): Promise<Error | null> {\n const [error] = await callDiscord(\n null,\n 'patch',\n '/webhooks/{webhook_id}/{webhook_token}/messages/@original',\n {\n path: { webhook_id: applicationId, webhook_token: token },\n body,\n files,\n },\n );\n\n return error;\n}\n\nexport type Send = (\n channelId: string,\n content: string,\n options?: MessageOptions,\n) => Promise<Error | null>;\n\nexport async function sendMessage(\n token: string,\n channelId: string,\n content: string,\n options: MessageOptions = {},\n): Promise<Error | null> {\n const [error] = await callDiscord(token, 'post', '/channels/{channel_id}/messages', {\n path: { channel_id: channelId },\n body: message(content, {\n buttons: options.buttons,\n selects: options.selects,\n embeds: options.embeds,\n allowedMentions: options.allowedMentions,\n files: options.files,\n }),\n files: options.files,\n });\n\n return error;\n}\n\nexport async function interactionCallback(\n id: string,\n token: string,\n response: Response,\n): Promise<Error | null> {\n const multipart = response.headers.get('content-type')?.startsWith('multipart/form-data');\n const { payload, files } = multipart\n ? await uploadsOf(await response.formData())\n : { payload: JSON.parse(await response.text()) as unknown, files: [] };\n\n const [error] = await callDiscord(\n null,\n 'post',\n '/interactions/{interaction_id}/{interaction_token}/callback',\n {\n path: { interaction_id: id, interaction_token: token },\n body: payload as Schemas['create_interaction_response_body'],\n files,\n },\n );\n\n return error;\n}\n"],"mappings":"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"}
|
package/dist/gateway.d.mts
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
|
-
import { i as ErrorHandler, r as Send, t as Bot } from "./bot-
|
|
1
|
+
import { i as ErrorHandler, r as Send, t as Bot } from "./bot-CW_jhC5U.mjs";
|
|
2
2
|
import { DurableObject } from "cloudflare:workers";
|
|
3
3
|
import { GatewayDispatchPayload, GatewayIntentBits, GatewayPresenceUpdateData } from "discord-api-types/v10";
|
|
4
4
|
type Intent = Exclude<keyof typeof GatewayIntentBits, "GuildBans" | "GuildEmojisAndStickers">;
|
|
5
|
+
/** What a gateway event handler gets besides the event data. */
|
|
6
|
+
export type EventContext = {
|
|
7
|
+
/** Posts a message to a channel, with optional `buttons` and `embeds`. */
|
|
8
|
+
send: Send;
|
|
9
|
+
/**
|
|
10
|
+
* The storage of the Durable Object that holds the connection, such as `storage.kv.get(key)`.
|
|
11
|
+
* The connection keeps its own state under the `session` key, so leave that key alone.
|
|
12
|
+
*/
|
|
13
|
+
storage: DurableObjectStorage;
|
|
14
|
+
};
|
|
5
15
|
type GatewayConfig = {
|
|
16
|
+
/**
|
|
17
|
+
* The names of the gateway intents the bot needs, such as `'GuildMessages'`. Turn on privileged
|
|
18
|
+
* ones, such as `'MessageContent'`, in the Developer Portal first.
|
|
19
|
+
*/
|
|
6
20
|
intents: Intent[];
|
|
21
|
+
/** The status the bot shows while it is connected. */
|
|
7
22
|
presence?: GatewayPresenceUpdateData;
|
|
23
|
+
/**
|
|
24
|
+
* The bot from `bot()`. Its handlers answer the commands, buttons and modals that arrive over the
|
|
25
|
+
* gateway, so the application needs no Interactions Endpoint URL.
|
|
26
|
+
*/
|
|
8
27
|
bot?: Pick<Bot, "answer">;
|
|
28
|
+
/** Gets errors from event handlers and from answering interactions. */
|
|
9
29
|
onError?: ErrorHandler;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
30
|
+
/**
|
|
31
|
+
* A handler per dispatch event name, such as `MESSAGE_CREATE`. `c.send` posts a message to a
|
|
32
|
+
* channel, with optional `buttons` and `embeds`. `c.storage` keeps state between events.
|
|
33
|
+
*/
|
|
34
|
+
events: { [P in GatewayDispatchPayload as P["t"]]?: (data: P["d"], c: EventContext) => unknown; };
|
|
13
35
|
};
|
|
14
36
|
/**
|
|
15
37
|
* Creates a Durable Object class that keeps one connection to the Discord Gateway open and runs your
|
|
@@ -19,14 +41,6 @@ type GatewayConfig = {
|
|
|
19
41
|
* It reads `DISCORD_TOKEN` from the Worker environment.
|
|
20
42
|
*
|
|
21
43
|
* @param config What the bot listens to.
|
|
22
|
-
* @param config.intents The names of the gateway intents the bot needs, such as `'GuildMessages'`.
|
|
23
|
-
* Turn on privileged ones, such as `'MessageContent'`, in the Developer Portal first.
|
|
24
|
-
* @param config.presence The status the bot shows while it is connected.
|
|
25
|
-
* @param config.events A handler per dispatch event name. `c.send` posts a message to a channel,
|
|
26
|
-
* with optional `buttons` and `embeds`.
|
|
27
|
-
* @param config.onError Gets errors from event handlers and from answering interactions.
|
|
28
|
-
* @param config.bot The bot from `bot()`. Its handlers answer the commands, buttons and modals that
|
|
29
|
-
* arrive over the gateway, so the application needs no Interactions Endpoint URL.
|
|
30
44
|
* @returns The Durable Object class.
|
|
31
45
|
* @example
|
|
32
46
|
* import { bot } from 'botflare';
|