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 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
- [![NPM version](https://img.shields.io/npm/v/botflare?color=f38020&label=)](https://www.npmjs.com/package/botflare)
5
+ [![NPM version](https://img.shields.io/npm/v/botflare?color=c6f432&label=npm&logo=npm&logoColor=white)](https://www.npmjs.com/package/botflare)
6
+ [![Install size](https://img.shields.io/npm/unpacked-size/botflare?color=c6f432&label=size)](https://www.npmjs.com/package/botflare)
7
+ [![CI](https://github.com/pyyupsk/botflare/actions/workflows/ci.yml/badge.svg)](https://github.com/pyyupsk/botflare/actions/workflows/ci.yml)
8
+ [![Docs](https://github.com/pyyupsk/botflare/actions/workflows/docs.yml/badge.svg)](https://pyyupsk.github.io/botflare/)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
10
+ [![Cloudflare Workers](https://img.shields.io/badge/Cloudflare-Workers-f38020?logo=cloudflareworkers&logoColor=white)](https://workers.cloudflare.com)
11
+ [![Discord API](https://img.shields.io/badge/Discord-API%20spec-5865f2?logo=discord&logoColor=white)](https://github.com/discord/discord-api-spec)
12
+ [![License](https://img.shields.io/github/license/pyyupsk/botflare?color=c6f432)](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
- [πŸ‘‰ Example bot](https://github.com/pyyupsk/botflare/tree/main/examples/basic) Β· [πŸ‘‰ Discord API spec](https://github.com/discord/discord-api-spec)
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 menu commands, buttons, select menus, modals and autocomplete
14
- - Option values typed from the options you declare: a `choices` option gives only its values
15
- - The Discord Gateway in a Durable Object, so a bot gets commands and events without a public URL
16
- - `rest` for every Discord route, with path, query, body and result typed from the Discord API spec
17
- - Cron jobs that post to channels, and `onError` for work that runs after Discord has its answer
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
- const app = bot({
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 dev # open the local URL once to start the gateway
53
+ npx wrangler deploy # then set the Interactions Endpoint URL to the Worker
76
54
  ```
77
55
 
78
- `botflare sync` reads `DISCORD_TOKEN`, `DISCORD_APPLICATION_ID` and, for one test server, `DISCORD_GUILD_ID`. The Worker reads `DISCORD_TOKEN`, and `DISCORD_PUBLIC_KEY` for interactions over HTTP.
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 is built for that model from the start: it answers interactions over HTTP, keeps the gateway connection in a Durable Object, and takes its REST types from the spec that Discord publishes. When Discord adds a route, one script updates the types.
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-icKel3r9.d.mts.map
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":"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"}
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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"}
@@ -1,15 +1,37 @@
1
- import { i as ErrorHandler, r as Send, t as Bot } from "./bot-icKel3r9.mjs";
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
- events: { [P in GatewayDispatchPayload as P["t"]]?: (data: P["d"], c: {
11
- send: Send;
12
- }) => unknown; };
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';