chapterjs 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/asset.d.ts +46 -0
- package/dist/assets/asset.js +86 -0
- package/dist/assets/public.d.ts +19 -0
- package/dist/assets/public.js +120 -0
- package/dist/cache/cache.d.ts +62 -0
- package/dist/cache/cache.js +80 -0
- package/dist/cache/store.d.ts +45 -0
- package/dist/cache/store.js +53 -0
- package/dist/cli/build.d.ts +41 -0
- package/dist/cli/build.js +205 -0
- package/dist/cli/cli.js +108 -25
- package/dist/cli/cluster.d.ts +70 -0
- package/dist/cli/cluster.js +213 -0
- package/dist/cli/dev.d.ts +18 -1
- package/dist/cli/dev.js +253 -198
- package/dist/cli/env.d.ts +31 -0
- package/dist/cli/env.js +93 -0
- package/dist/cli/log.d.ts +13 -0
- package/dist/cli/log.js +17 -0
- package/dist/cli/node-version.d.ts +8 -0
- package/dist/cli/node-version.js +13 -0
- package/dist/cli/preflight.d.ts +36 -0
- package/dist/cli/preflight.js +122 -0
- package/dist/cli/project.d.ts +135 -0
- package/dist/cli/project.js +517 -0
- package/dist/cli/start.d.ts +25 -0
- package/dist/cli/start.js +225 -0
- package/dist/commands/autocomplete.d.ts +13 -0
- package/dist/commands/autocomplete.js +159 -0
- package/dist/commands/command.d.ts +360 -0
- package/dist/commands/command.js +28 -0
- package/dist/commands/declaration.d.ts +77 -0
- package/dist/commands/declaration.js +492 -0
- package/dist/commands/register.d.ts +18 -0
- package/dist/commands/register.js +42 -0
- package/dist/commands/router.d.ts +20 -0
- package/dist/commands/router.js +228 -0
- package/dist/commands/tree.d.ts +47 -0
- package/dist/commands/tree.js +216 -0
- package/dist/commands/twins.d.ts +15 -0
- package/dist/commands/twins.js +78 -0
- package/dist/components/button.d.ts +79 -0
- package/dist/components/button.js +106 -0
- package/dist/components/component.d.ts +140 -0
- package/dist/components/component.js +39 -0
- package/dist/components/custom-id.d.ts +72 -0
- package/dist/components/custom-id.js +128 -0
- package/dist/components/declaration.d.ts +42 -0
- package/dist/components/declaration.js +342 -0
- package/dist/components/declared.d.ts +29 -0
- package/dist/components/declared.js +49 -0
- package/dist/components/embed.d.ts +29 -0
- package/dist/components/embed.js +11 -0
- package/dist/components/instance.d.ts +100 -0
- package/dist/components/instance.js +86 -0
- package/dist/components/layout.d.ts +103 -0
- package/dist/components/layout.js +279 -0
- package/dist/components/modal.d.ts +245 -0
- package/dist/components/modal.js +398 -0
- package/dist/components/options.d.ts +41 -0
- package/dist/components/options.js +101 -0
- package/dist/components/render.d.ts +12 -0
- package/dist/components/render.js +58 -0
- package/dist/components/router.d.ts +19 -0
- package/dist/components/router.js +251 -0
- package/dist/components/select.d.ts +146 -0
- package/dist/components/select.js +179 -0
- package/dist/core/bot.d.ts +99 -0
- package/dist/core/bot.js +174 -0
- package/dist/core/memory.d.ts +66 -0
- package/dist/core/memory.js +115 -0
- package/dist/discord/api.d.ts +112 -0
- package/dist/discord/api.js +115 -0
- package/dist/discord/cdn.d.ts +67 -0
- package/dist/discord/cdn.js +100 -0
- package/dist/discord/codes.d.ts +171 -0
- package/dist/discord/codes.js +421 -0
- package/dist/discord/endpoint.d.ts +56 -0
- package/dist/discord/endpoint.js +53 -0
- package/dist/discord/endpoints.d.ts +1529 -0
- package/dist/discord/endpoints.js +1139 -0
- package/dist/discord/formatting.d.ts +67 -0
- package/dist/discord/formatting.js +87 -0
- package/dist/discord/index.d.ts +10 -0
- package/dist/discord/index.js +13 -0
- package/dist/discord/intents.d.ts +48 -0
- package/dist/discord/intents.js +128 -0
- package/dist/discord/permissions.d.ts +94 -0
- package/dist/discord/permissions.js +185 -0
- package/dist/discord/snowflake.d.ts +21 -0
- package/dist/discord/snowflake.js +36 -0
- package/dist/discord/types/application-command.d.ts +332 -0
- package/dist/discord/types/application-command.js +48 -0
- package/dist/discord/types/application-role-connection-metadata.d.ts +42 -0
- package/dist/discord/types/application-role-connection-metadata.js +22 -0
- package/dist/discord/types/application.d.ts +208 -0
- package/dist/discord/types/application.js +48 -0
- package/dist/discord/types/audit-log.d.ts +255 -0
- package/dist/discord/types/audit-log.js +144 -0
- package/dist/discord/types/auto-moderation.d.ts +167 -0
- package/dist/discord/types/auto-moderation.js +52 -0
- package/dist/discord/types/channel.d.ts +557 -0
- package/dist/discord/types/channel.js +88 -0
- package/dist/discord/types/common.d.ts +54 -0
- package/dist/discord/types/common.js +38 -0
- package/dist/discord/types/component.d.ts +775 -0
- package/dist/discord/types/component.js +76 -0
- package/dist/discord/types/emoji.d.ts +74 -0
- package/dist/discord/types/emoji.js +1 -0
- package/dist/discord/types/entitlement.d.ts +84 -0
- package/dist/discord/types/entitlement.js +22 -0
- package/dist/discord/types/gateway-events.d.ts +1148 -0
- package/dist/discord/types/gateway-events.js +54 -0
- package/dist/discord/types/gateway.d.ts +38 -0
- package/dist/discord/types/gateway.js +1 -0
- package/dist/discord/types/guild-scheduled-event.d.ts +257 -0
- package/dist/discord/types/guild-scheduled-event.js +68 -0
- package/dist/discord/types/guild-template.d.ts +51 -0
- package/dist/discord/types/guild-template.js +1 -0
- package/dist/discord/types/guild.d.ts +910 -0
- package/dist/discord/types/guild.js +153 -0
- package/dist/discord/types/index.d.ts +31 -0
- package/dist/discord/types/index.js +31 -0
- package/dist/discord/types/interaction.d.ts +321 -0
- package/dist/discord/types/interaction.js +47 -0
- package/dist/discord/types/invite.d.ts +158 -0
- package/dist/discord/types/invite.js +39 -0
- package/dist/discord/types/message.d.ts +834 -0
- package/dist/discord/types/message.js +159 -0
- package/dist/discord/types/oauth2.d.ts +22 -0
- package/dist/discord/types/oauth2.js +1 -0
- package/dist/discord/types/permissions.d.ts +183 -0
- package/dist/discord/types/permissions.js +118 -0
- package/dist/discord/types/poll.d.ts +106 -0
- package/dist/discord/types/poll.js +8 -0
- package/dist/discord/types/sku.d.ts +47 -0
- package/dist/discord/types/sku.js +26 -0
- package/dist/discord/types/soundboard.d.ts +71 -0
- package/dist/discord/types/soundboard.js +1 -0
- package/dist/discord/types/stage-instance.d.ts +58 -0
- package/dist/discord/types/stage-instance.js +10 -0
- package/dist/discord/types/sticker.d.ts +115 -0
- package/dist/discord/types/sticker.js +20 -0
- package/dist/discord/types/subscription.d.ts +54 -0
- package/dist/discord/types/subscription.js +12 -0
- package/dist/discord/types/team.d.ts +55 -0
- package/dist/discord/types/team.js +21 -0
- package/dist/discord/types/user.d.ts +273 -0
- package/dist/discord/types/user.js +83 -0
- package/dist/discord/types/voice.d.ts +72 -0
- package/dist/discord/types/voice.js +1 -0
- package/dist/discord/types/webhook.d.ts +184 -0
- package/dist/discord/types/webhook.js +12 -0
- package/dist/events/declaration.d.ts +16 -0
- package/dist/events/declaration.js +80 -0
- package/dist/events/event.d.ts +45 -0
- package/dist/events/event.js +34 -0
- package/dist/events/registry.d.ts +697 -0
- package/dist/events/registry.js +839 -0
- package/dist/events/router.d.ts +46 -0
- package/dist/events/router.js +133 -0
- package/dist/gateway/errors.d.ts +18 -0
- package/dist/gateway/errors.js +47 -0
- package/dist/gateway/gateway.d.ts +64 -0
- package/dist/gateway/gateway.js +117 -0
- package/dist/gateway/identify-queue.d.ts +25 -0
- package/dist/gateway/identify-queue.js +29 -0
- package/dist/gateway/shard.d.ts +94 -0
- package/dist/gateway/shard.js +414 -0
- package/dist/gateway/state.d.ts +11 -0
- package/dist/gateway/state.js +184 -0
- package/dist/index.d.ts +57 -18
- package/dist/index.js +40 -12
- package/dist/interactions/cooldown.d.ts +33 -0
- package/dist/interactions/cooldown.js +74 -0
- package/dist/interactions/dispatch.d.ts +91 -0
- package/dist/interactions/dispatch.js +110 -0
- package/dist/loader/generated.d.ts +10 -0
- package/dist/loader/generated.js +80 -0
- package/dist/loader/hot.d.ts +13 -0
- package/dist/loader/hot.js +85 -0
- package/dist/loader/loader.d.ts +85 -0
- package/dist/loader/loader.js +119 -0
- package/dist/loader/locate.d.ts +10 -0
- package/dist/loader/locate.js +36 -0
- package/dist/loader/watch.d.ts +17 -0
- package/dist/loader/watch.js +66 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +5 -0
- package/dist/messages/commands.d.ts +23 -0
- package/dist/messages/commands.js +142 -0
- package/dist/messages/declaration.d.ts +89 -0
- package/dist/messages/declaration.js +455 -0
- package/dist/messages/messages.d.ts +241 -0
- package/dist/messages/messages.js +31 -0
- package/dist/messages/phrases.d.ts +23 -0
- package/dist/messages/phrases.js +44 -0
- package/dist/messages/translate.d.ts +100 -0
- package/dist/messages/translate.js +249 -0
- package/dist/presence/declaration.d.ts +19 -0
- package/dist/presence/declaration.js +118 -0
- package/dist/presence/presence.d.ts +57 -0
- package/dist/presence/presence.js +27 -0
- package/dist/rest/errors.d.ts +55 -0
- package/dist/rest/errors.js +92 -0
- package/dist/rest/rate-limiter.d.ts +46 -0
- package/dist/rest/rate-limiter.js +153 -0
- package/dist/rest/rest.d.ts +63 -0
- package/dist/rest/rest.js +194 -0
- package/dist/structures/base.d.ts +48 -0
- package/dist/structures/base.js +84 -0
- package/dist/structures/channel.d.ts +380 -0
- package/dist/structures/channel.js +627 -0
- package/dist/structures/context.d.ts +30 -0
- package/dist/structures/context.js +7 -0
- package/dist/structures/emoji.d.ts +46 -0
- package/dist/structures/emoji.js +65 -0
- package/dist/structures/entities.d.ts +62 -0
- package/dist/structures/entities.js +226 -0
- package/dist/structures/guild.d.ts +348 -0
- package/dist/structures/guild.js +466 -0
- package/dist/structures/interaction.d.ts +197 -0
- package/dist/structures/interaction.js +396 -0
- package/dist/structures/invite.d.ts +38 -0
- package/dist/structures/invite.js +57 -0
- package/dist/structures/known.d.ts +28 -0
- package/dist/structures/known.js +58 -0
- package/dist/structures/member.d.ts +140 -0
- package/dist/structures/member.js +260 -0
- package/dist/structures/message.d.ts +197 -0
- package/dist/structures/message.js +268 -0
- package/dist/structures/payload.d.ts +91 -0
- package/dist/structures/payload.js +184 -0
- package/dist/structures/role.d.ts +69 -0
- package/dist/structures/role.js +112 -0
- package/dist/structures/user.d.ts +59 -0
- package/dist/structures/user.js +94 -0
- package/dist/structures/webhook.d.ts +46 -0
- package/dist/structures/webhook.js +79 -0
- package/dist/tasks/declaration.d.ts +16 -0
- package/dist/tasks/declaration.js +56 -0
- package/dist/tasks/schedule.d.ts +54 -0
- package/dist/tasks/schedule.js +236 -0
- package/dist/tasks/scheduler.d.ts +35 -0
- package/dist/tasks/scheduler.js +107 -0
- package/dist/tasks/task.d.ts +75 -0
- package/dist/tasks/task.js +27 -0
- package/dist/util/case.d.ts +13 -0
- package/dist/util/case.js +54 -0
- package/dist/util/duration.d.ts +8 -0
- package/dist/util/duration.js +29 -0
- package/dist/util/websocket.d.ts +9 -0
- package/dist/util/websocket.js +31 -0
- package/dist/voice/connection.d.ts +90 -0
- package/dist/voice/connection.js +494 -0
- package/dist/voice/dave.d.ts +109 -0
- package/dist/voice/dave.js +245 -0
- package/dist/voice/opus.d.ts +19 -0
- package/dist/voice/opus.js +273 -0
- package/dist/voice/session.d.ts +55 -0
- package/dist/voice/session.js +405 -0
- package/dist/voice/source.d.ts +22 -0
- package/dist/voice/source.js +195 -0
- package/dist/voice/transport.d.ts +43 -0
- package/dist/voice/transport.js +140 -0
- package/dist/voice/usage.d.ts +4 -0
- package/dist/voice/usage.js +33 -0
- package/package.json +26 -7
- package/vendor/dave/LICENSE +231 -0
- package/vendor/dave/libdave.d.mts +99 -0
- package/vendor/dave/libdave.mjs +2 -0
- package/vendor/dave/libdave.wasm +0 -0
- package/dist/components.d.ts +0 -97
- package/dist/components.js +0 -260
- package/dist/context.d.ts +0 -72
- package/dist/context.js +0 -273
- package/dist/discord/client.d.ts +0 -35
- package/dist/discord/client.js +0 -66
- package/dist/discord/constants.d.ts +0 -49
- package/dist/discord/constants.js +0 -61
- package/dist/discord/events.d.ts +0 -129
- package/dist/discord/events.js +0 -102
- package/dist/discord/gateway.d.ts +0 -61
- package/dist/discord/gateway.js +0 -320
- package/dist/discord/payloads.d.ts +0 -275
- package/dist/discord/payloads.js +0 -3
- package/dist/discord/presence.d.ts +0 -42
- package/dist/discord/presence.js +0 -51
- package/dist/discord/rest.d.ts +0 -49
- package/dist/discord/rest.js +0 -271
- package/dist/discord/types.d.ts +0 -334
- package/dist/discord/types.js +0 -4
- package/dist/events.d.ts +0 -94
- package/dist/events.js +0 -255
- package/dist/presence.d.ts +0 -92
- package/dist/presence.js +0 -250
- package/dist/structures/Channel.d.ts +0 -110
- package/dist/structures/Channel.js +0 -171
- package/dist/structures/Guild.d.ts +0 -70
- package/dist/structures/Guild.js +0 -105
- package/dist/structures/Interaction.d.ts +0 -113
- package/dist/structures/Interaction.js +0 -274
- package/dist/structures/Member.d.ts +0 -70
- package/dist/structures/Member.js +0 -124
- package/dist/structures/Message.d.ts +0 -87
- package/dist/structures/Message.js +0 -182
- package/dist/structures/Reaction.d.ts +0 -38
- package/dist/structures/Reaction.js +0 -64
- package/dist/structures/Role.d.ts +0 -43
- package/dist/structures/Role.js +0 -77
- package/dist/structures/User.d.ts +0 -41
- package/dist/structures/User.js +0 -79
- package/dist/structures/utils.d.ts +0 -72
- package/dist/structures/utils.js +0 -59
- package/dist/utils/errors.d.ts +0 -5
- package/dist/utils/errors.js +0 -28
- package/dist/utils/modules.d.ts +0 -39
- package/dist/utils/modules.js +0 -156
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// `button()`, as user files import it from 'chapterjs': what the button
|
|
2
|
+
// looks like, what it carries, and what to do when it is clicked.
|
|
3
|
+
// https://docs.discord.com/developers/components/reference#button
|
|
4
|
+
import { Limits } from '../discord/api.js';
|
|
5
|
+
import { ButtonStyle, ComponentType, } from '../discord/types/component.js';
|
|
6
|
+
import { isDynamic, resolveText } from './component.js';
|
|
7
|
+
import { checkData, encodeCustomId, } from './custom-id.js';
|
|
8
|
+
import { createComponent } from './declared.js';
|
|
9
|
+
import { emojiOf } from './instance.js';
|
|
10
|
+
export const BUTTON_STYLES = {
|
|
11
|
+
primary: ButtonStyle.Primary,
|
|
12
|
+
secondary: ButtonStyle.Secondary,
|
|
13
|
+
success: ButtonStyle.Success,
|
|
14
|
+
danger: ButtonStyle.Danger,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Declares a button. Export the result from any file of `src/`: the name
|
|
18
|
+
* of the export is what tells the button apart, so you never write an id.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { button } from 'chapterjs';
|
|
22
|
+
*
|
|
23
|
+
* export const confirm = button({
|
|
24
|
+
* label: 'Confirm',
|
|
25
|
+
* style: 'success',
|
|
26
|
+
* async run({ interaction }) {
|
|
27
|
+
* await interaction.update({ content: 'Confirmed!', components: [] });
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function button(config) {
|
|
33
|
+
return createComponent('button', config, {
|
|
34
|
+
asPiece: true,
|
|
35
|
+
pieceKind: 'button',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const fail = (message) => {
|
|
39
|
+
throw new TypeError(message);
|
|
40
|
+
};
|
|
41
|
+
/** Checks what a message gives to change the look of a button. */
|
|
42
|
+
export function readLook(what, look) {
|
|
43
|
+
if (look === undefined)
|
|
44
|
+
return {};
|
|
45
|
+
if (typeof look !== 'object' || look === null || Array.isArray(look)) {
|
|
46
|
+
fail(`The look of ${what} is an object like { disabled: true }.`);
|
|
47
|
+
}
|
|
48
|
+
const given = look;
|
|
49
|
+
for (const key of Object.keys(given)) {
|
|
50
|
+
if (!['label', 'emoji', 'style', 'disabled'].includes(key)) {
|
|
51
|
+
fail(`"${key}" is not something the look of ${what} has. It can have: label, emoji, style, disabled.`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (given.label !== undefined && !isDynamic(given.label)) {
|
|
55
|
+
if (typeof given.label !== 'string' ||
|
|
56
|
+
given.label.trim() === '' ||
|
|
57
|
+
given.label.length > Limits.ButtonLabel) {
|
|
58
|
+
fail(`The label of ${what} is a text of 1 to ${Limits.ButtonLabel} characters.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (given.style !== undefined &&
|
|
62
|
+
!Object.hasOwn(BUTTON_STYLES, given.style)) {
|
|
63
|
+
fail(`The style of ${what} is 'primary', 'secondary', 'success' or 'danger', got ${JSON.stringify(given.style)}.`);
|
|
64
|
+
}
|
|
65
|
+
if (given.disabled !== undefined && typeof given.disabled !== 'boolean') {
|
|
66
|
+
fail(`"disabled" of ${what} is true or false.`);
|
|
67
|
+
}
|
|
68
|
+
if (given.emoji !== undefined)
|
|
69
|
+
emojiOf(given.emoji, what);
|
|
70
|
+
return given;
|
|
71
|
+
}
|
|
72
|
+
/** The button as Discord takes it, for the data and the look given. */
|
|
73
|
+
export function renderButton(loaded, args) {
|
|
74
|
+
const hasData = Object.keys(loaded.data).length > 0;
|
|
75
|
+
const { name } = loaded;
|
|
76
|
+
if (args.length > (hasData ? 2 : 1)) {
|
|
77
|
+
fail(`${name} takes ${hasData ? 'its data, then a look' : 'a look'} at most: ${hasData ? `${name}({ ... }, { disabled: true })` : `${name}({ disabled: true })`}.`);
|
|
78
|
+
}
|
|
79
|
+
const values = checkData(name, loaded.data, hasData ? args[0] : undefined);
|
|
80
|
+
const look = {
|
|
81
|
+
...loaded.look,
|
|
82
|
+
...readLook(name, hasData ? args[1] : args[0]),
|
|
83
|
+
};
|
|
84
|
+
const base = {
|
|
85
|
+
type: ComponentType.Button,
|
|
86
|
+
style: BUTTON_STYLES[look.style],
|
|
87
|
+
custom_id: encodeCustomId(loaded.name, loaded.data, values),
|
|
88
|
+
};
|
|
89
|
+
if (look.emoji !== undefined)
|
|
90
|
+
base.emoji = emojiOf(look.emoji, name);
|
|
91
|
+
if (look.disabled)
|
|
92
|
+
base.disabled = true;
|
|
93
|
+
const { label } = look;
|
|
94
|
+
if (label === undefined)
|
|
95
|
+
return { kind: 'button', raw: base };
|
|
96
|
+
if (!isDynamic(label))
|
|
97
|
+
return { kind: 'button', raw: { ...base, label } };
|
|
98
|
+
// The label is computed when the message is sent, for who will read it.
|
|
99
|
+
return {
|
|
100
|
+
kind: 'button',
|
|
101
|
+
raw: t => ({
|
|
102
|
+
...base,
|
|
103
|
+
label: resolveText(`The label of ${name}`, label, { t, data: values }, { max: Limits.ButtonLabel }),
|
|
104
|
+
}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { DMChannel, GuildTextBasedChannel } from '../structures/channel.js';
|
|
2
|
+
import type { Guild } from '../structures/guild.js';
|
|
3
|
+
import type { Interaction } from '../structures/interaction.js';
|
|
4
|
+
import type { GuildMember } from '../structures/member.js';
|
|
5
|
+
import type { Message } from '../structures/message.js';
|
|
6
|
+
import type { User } from '../structures/user.js';
|
|
7
|
+
import type { DataShape, DataValuesOf } from './custom-id.js';
|
|
8
|
+
import type { Translator } from '../messages/messages.js';
|
|
9
|
+
/**
|
|
10
|
+
* What a text of a component is computed from when it is a function: `t`
|
|
11
|
+
* in the language of who will read the message, and the data the
|
|
12
|
+
* component carries.
|
|
13
|
+
*/
|
|
14
|
+
export interface TextContext<Data extends DataShape = DataShape> {
|
|
15
|
+
/** The texts of the language files, in the language of who will read the message. */
|
|
16
|
+
t: Translator;
|
|
17
|
+
/** What the component carries, as declared in `data`. */
|
|
18
|
+
data: [DataShape] extends [Data] ? Record<string, string | number | boolean> : DataValuesOf<Data>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A text shown by a component: written as is, or computed when the
|
|
22
|
+
* message is sent, from `t` and the data of the component.
|
|
23
|
+
*/
|
|
24
|
+
export type DynamicText<Data extends DataShape = DataShape> = string | ((context: TextContext<Data>) => string);
|
|
25
|
+
/** Whether a text is computed when the message is sent. */
|
|
26
|
+
export declare const isDynamic: (value: unknown) => value is (context: TextContext) => string;
|
|
27
|
+
/**
|
|
28
|
+
* Checks a text of a component, now when it is written, or when the
|
|
29
|
+
* message is sent when it is a function: `resolve` is then what to check.
|
|
30
|
+
* Returns the text, or `undefined` when there is none and none is needed.
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkDynamicText(what: string, value: unknown, { min, max, required, }: {
|
|
33
|
+
min?: number;
|
|
34
|
+
max: number;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
}): void;
|
|
37
|
+
/** A text given by a file or computed, within the limits of Discord. */
|
|
38
|
+
export declare function checkTextValue(what: string, value: unknown, { min, max }: {
|
|
39
|
+
min?: number;
|
|
40
|
+
max: number;
|
|
41
|
+
}): string;
|
|
42
|
+
/** The text of a component when the message is sent. */
|
|
43
|
+
export declare function resolveText(what: string, value: DynamicText | undefined, context: TextContext, limits: {
|
|
44
|
+
min?: number;
|
|
45
|
+
max: number;
|
|
46
|
+
}): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Where a component can be used: in messages of servers (`'guild'`), in
|
|
49
|
+
* private messages with the bot (`'dm'`), or in both. A component is where
|
|
50
|
+
* the message that holds it is: this says where your code expects it.
|
|
51
|
+
*/
|
|
52
|
+
export type ComponentWhere = 'guild' | 'dm' | 'both';
|
|
53
|
+
/**
|
|
54
|
+
* Who may use a component: `'everyone'` who sees the message (the
|
|
55
|
+
* default), or only the `'author'`, the person who used the command the
|
|
56
|
+
* message answers. Anyone else gets a private refusal.
|
|
57
|
+
*/
|
|
58
|
+
export type ComponentWho = 'everyone' | 'author';
|
|
59
|
+
/** What every interactive component declares, besides what it looks like. */
|
|
60
|
+
export interface InteractiveConfig<Data extends DataShape, Where extends ComponentWhere> {
|
|
61
|
+
/**
|
|
62
|
+
* What the component carries from the message that shows it to the code
|
|
63
|
+
* that runs when it is used: a name and a kind for each value
|
|
64
|
+
* (`{ userId: 'string', page: 'number' }`). Given when the component is
|
|
65
|
+
* put in a message, received typed in `run`. Discord keeps it in the
|
|
66
|
+
* component itself, so it survives restarts.
|
|
67
|
+
*/
|
|
68
|
+
data?: Data;
|
|
69
|
+
/**
|
|
70
|
+
* Where the component can be used: `'guild'` (in servers, the default),
|
|
71
|
+
* `'dm'` (in private messages with the bot) or `'both'`. What `run`
|
|
72
|
+
* receives follows it.
|
|
73
|
+
*/
|
|
74
|
+
where?: Where;
|
|
75
|
+
/**
|
|
76
|
+
* Who may use it: `'everyone'` (the default) or only the `'author'` of
|
|
77
|
+
* the command the message answers.
|
|
78
|
+
*/
|
|
79
|
+
who?: ComponentWho;
|
|
80
|
+
/**
|
|
81
|
+
* Make the answers of the component only visible to who used it. On a
|
|
82
|
+
* message only the person sees (an ephemeral answer), they already are.
|
|
83
|
+
*/
|
|
84
|
+
ephemeral?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/** A server, as an interaction used in one sees it. */
|
|
87
|
+
export type InGuild<I extends Interaction> = I & {
|
|
88
|
+
readonly guildId: string;
|
|
89
|
+
readonly guild: Guild;
|
|
90
|
+
readonly member: GuildMember;
|
|
91
|
+
readonly channel: GuildTextBasedChannel;
|
|
92
|
+
};
|
|
93
|
+
/** A private message, for something that also works in servers. */
|
|
94
|
+
export type InPrivate<I extends Interaction> = I & {
|
|
95
|
+
readonly guildId: null;
|
|
96
|
+
readonly guild: null;
|
|
97
|
+
readonly member: null;
|
|
98
|
+
readonly channel: DMChannel;
|
|
99
|
+
};
|
|
100
|
+
/** A private message, for something that only works there: no server at all. */
|
|
101
|
+
export type InDm<I extends Interaction> = Omit<I, 'guildId' | 'guild' | 'member' | 'channel'> & {
|
|
102
|
+
readonly channel: DMChannel;
|
|
103
|
+
};
|
|
104
|
+
/** What `run` receives about the place, in a server. */
|
|
105
|
+
export interface PlaceInGuild<I extends Interaction> {
|
|
106
|
+
interaction: InGuild<I>;
|
|
107
|
+
/** The server it was used in. */
|
|
108
|
+
guild: Guild;
|
|
109
|
+
/** Who used it, as a member of the server. */
|
|
110
|
+
member: GuildMember;
|
|
111
|
+
/** The channel it was used in. */
|
|
112
|
+
channel: GuildTextBasedChannel;
|
|
113
|
+
}
|
|
114
|
+
/** What `run` receives about the place, in a private message, when it also works in servers. */
|
|
115
|
+
export interface PlaceInPrivate<I extends Interaction> {
|
|
116
|
+
interaction: InPrivate<I>;
|
|
117
|
+
/** No server: it was used in a private message. */
|
|
118
|
+
guild: null;
|
|
119
|
+
/** No member: the person is `user`. */
|
|
120
|
+
member: null;
|
|
121
|
+
/** The private conversation it was used in. */
|
|
122
|
+
channel: DMChannel;
|
|
123
|
+
}
|
|
124
|
+
/** What `run` receives about the place, when it only works in private messages. */
|
|
125
|
+
export interface PlaceInDm<I extends Interaction> {
|
|
126
|
+
interaction: InDm<I>;
|
|
127
|
+
/** The private conversation it was used in. */
|
|
128
|
+
channel: DMChannel;
|
|
129
|
+
}
|
|
130
|
+
/** What `run` receives about where it was used, following `where`. */
|
|
131
|
+
export type PlaceOf<Where extends ComponentWhere, I extends Interaction> = Where extends 'guild' ? PlaceInGuild<I> : Where extends 'dm' ? PlaceInDm<I> : PlaceInGuild<I> | PlaceInPrivate<I>;
|
|
132
|
+
/** What every interactive component gives to its `run`, besides the place. */
|
|
133
|
+
export interface InteractiveContext<Data extends DataShape> {
|
|
134
|
+
/** Who used it. */
|
|
135
|
+
user: User;
|
|
136
|
+
/** The message the component is on. */
|
|
137
|
+
message: Message;
|
|
138
|
+
/** What the component carries, as declared in `data`. */
|
|
139
|
+
data: DataValuesOf<Data>;
|
|
140
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// What every interactive component (button, menu, form) shares: where it
|
|
2
|
+
// works, who may use it, what it carries, and what its `run` receives
|
|
3
|
+
// about the place it was used in.
|
|
4
|
+
/** Whether a text is computed when the message is sent. */
|
|
5
|
+
export const isDynamic = (value) => typeof value === 'function';
|
|
6
|
+
/**
|
|
7
|
+
* Checks a text of a component, now when it is written, or when the
|
|
8
|
+
* message is sent when it is a function: `resolve` is then what to check.
|
|
9
|
+
* Returns the text, or `undefined` when there is none and none is needed.
|
|
10
|
+
*/
|
|
11
|
+
export function checkDynamicText(what, value, { min = 1, max, required = false, }) {
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
if (required)
|
|
14
|
+
throw new TypeError(`${what} is missing.`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (isDynamic(value))
|
|
18
|
+
return;
|
|
19
|
+
checkTextValue(what, value, { min, max });
|
|
20
|
+
}
|
|
21
|
+
/** A text given by a file or computed, within the limits of Discord. */
|
|
22
|
+
export function checkTextValue(what, value, { min = 1, max }) {
|
|
23
|
+
if (typeof value !== 'string') {
|
|
24
|
+
throw new TypeError(`${what} is a text${min === 1 ? ` of 1 to ${max} characters` : ''}, got ${typeof value}.`);
|
|
25
|
+
}
|
|
26
|
+
if (value.length < min ||
|
|
27
|
+
value.length > max ||
|
|
28
|
+
(min > 0 && value.trim() === '')) {
|
|
29
|
+
throw new RangeError(`${what} is ${value.length} characters long: Discord accepts between ${min} and ${max}.`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
/** The text of a component when the message is sent. */
|
|
34
|
+
export function resolveText(what, value, context, limits) {
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
const text = isDynamic(value) ? value(context) : value;
|
|
38
|
+
return checkTextValue(isDynamic(value) ? `${what}, as its function returned it,` : what, text, limits);
|
|
39
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** The kinds of values a component can carry in its `data`. */
|
|
2
|
+
export type DataKind = 'string' | 'number' | 'boolean';
|
|
3
|
+
/**
|
|
4
|
+
* One value a component carries: its kind, or its kind with a default
|
|
5
|
+
* value, which makes it optional where the component is put in a message.
|
|
6
|
+
*/
|
|
7
|
+
export type DataField = DataKind | {
|
|
8
|
+
type: 'string';
|
|
9
|
+
default?: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'number';
|
|
12
|
+
default?: number;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'boolean';
|
|
15
|
+
default?: boolean;
|
|
16
|
+
};
|
|
17
|
+
/** What a component carries: a name for each value, and its kind. */
|
|
18
|
+
export type DataShape = Readonly<Record<string, DataField>>;
|
|
19
|
+
/** The kind of a field, however it was written. */
|
|
20
|
+
type KindOf<Field extends DataField> = Field extends DataKind ? Field : Field extends {
|
|
21
|
+
type: infer Kind;
|
|
22
|
+
} ? Kind : never;
|
|
23
|
+
type ValueOf<Field extends DataField> = KindOf<Field> extends 'string' ? string : KindOf<Field> extends 'number' ? number : boolean;
|
|
24
|
+
/** The names of the fields that have a default value. */
|
|
25
|
+
type WithDefault<Shape extends DataShape> = {
|
|
26
|
+
[Name in keyof Shape]: Shape[Name] extends {
|
|
27
|
+
default: {};
|
|
28
|
+
} ? Name : never;
|
|
29
|
+
}[keyof Shape];
|
|
30
|
+
/**
|
|
31
|
+
* The values of a `data` declaration, typed from it: what `run` and the
|
|
32
|
+
* texts of the component receive. A value with a default is always there.
|
|
33
|
+
*/
|
|
34
|
+
export type DataValuesOf<Shape extends DataShape> = {
|
|
35
|
+
[Name in keyof Shape]: ValueOf<Shape[Name]>;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* What a message gives to a component: the values of its `data`, those
|
|
39
|
+
* with a default value being optional.
|
|
40
|
+
*/
|
|
41
|
+
export type DataInputOf<Shape extends DataShape> = {
|
|
42
|
+
[Name in Exclude<keyof Shape, WithDefault<Shape>>]: ValueOf<Shape[Name]>;
|
|
43
|
+
} & {
|
|
44
|
+
[Name in WithDefault<Shape>]?: ValueOf<Shape[Name]>;
|
|
45
|
+
};
|
|
46
|
+
export declare const DATA_KINDS: readonly DataKind[];
|
|
47
|
+
/** The kind of a field, however it was written. */
|
|
48
|
+
export declare const kindOf: (field: DataField) => DataKind;
|
|
49
|
+
/** The default value of a field, if it has one. */
|
|
50
|
+
export declare const defaultOf: (field: DataField) => string | number | boolean | undefined;
|
|
51
|
+
/** Whether a component can be put in a message without data. */
|
|
52
|
+
export declare const needsNoData: (shape: DataShape) => boolean;
|
|
53
|
+
/** Checks the values a file gives to a component against its `data`. */
|
|
54
|
+
export declare function checkData(what: string, shape: DataShape, values: unknown): Record<string, string | number | boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* The `custom_id` of a component: its name, then its values in the order
|
|
57
|
+
* of the declaration. Discord accepts 100 characters: when the data does
|
|
58
|
+
* not fit, the error says by how much.
|
|
59
|
+
*/
|
|
60
|
+
export declare function encodeCustomId(name: string, shape: DataShape, values: Record<string, string | number | boolean>): string;
|
|
61
|
+
/** The name and the encoded values of a `custom_id` the framework wrote. */
|
|
62
|
+
export declare function decodeCustomId(id: string): {
|
|
63
|
+
name: string;
|
|
64
|
+
parts: string[];
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The values a `custom_id` carries, typed as the component declares them.
|
|
68
|
+
* `null` when they do not match the declaration: the file changed since the
|
|
69
|
+
* message was sent.
|
|
70
|
+
*/
|
|
71
|
+
export declare function readData(shape: DataShape, parts: readonly string[]): Record<string, string | number | boolean> | null;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// The `custom_id` of a component is never written by users: it is the name
|
|
2
|
+
// of the export that declares the component (`ban`), followed by the data
|
|
3
|
+
// the component carries, encoded by the framework. Stateless by design: it
|
|
4
|
+
// survives a restart, and every process of the bot reads it the same way.
|
|
5
|
+
// https://docs.discord.com/developers/components/reference#anatomy-of-a-component-custom-id
|
|
6
|
+
import { Limits } from '../discord/api.js';
|
|
7
|
+
export const DATA_KINDS = ['string', 'number', 'boolean'];
|
|
8
|
+
/** The kind of a field, however it was written. */
|
|
9
|
+
export const kindOf = (field) => typeof field === 'string' ? field : field.type;
|
|
10
|
+
/** The default value of a field, if it has one. */
|
|
11
|
+
export const defaultOf = (field) => typeof field === 'string' ? undefined : field.default;
|
|
12
|
+
/** Whether a component can be put in a message without data. */
|
|
13
|
+
export const needsNoData = (shape) => Object.values(shape).every(field => defaultOf(field) !== undefined);
|
|
14
|
+
/** Fields are separated by `:`; a `:` or a `\` inside a value is escaped. */
|
|
15
|
+
const SEPARATOR = ':';
|
|
16
|
+
const escape = (value) => value.replace(/[\\:]/g, char => `\\${char}`);
|
|
17
|
+
/**
|
|
18
|
+
* Splits on the separators that are not escaped, and unescapes the rest.
|
|
19
|
+
* Written by hand: a regular expression would need lookbehinds that count.
|
|
20
|
+
*/
|
|
21
|
+
function split(encoded) {
|
|
22
|
+
const parts = [];
|
|
23
|
+
let current = '';
|
|
24
|
+
for (let index = 0; index < encoded.length; index++) {
|
|
25
|
+
const char = encoded[index];
|
|
26
|
+
if (char === '\\' && index + 1 < encoded.length) {
|
|
27
|
+
current += encoded[++index];
|
|
28
|
+
}
|
|
29
|
+
else if (char === SEPARATOR) {
|
|
30
|
+
parts.push(current);
|
|
31
|
+
current = '';
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
current += char;
|
|
35
|
+
}
|
|
36
|
+
parts.push(current);
|
|
37
|
+
return parts;
|
|
38
|
+
}
|
|
39
|
+
/** Checks the values a file gives to a component against its `data`. */
|
|
40
|
+
export function checkData(what, shape, values) {
|
|
41
|
+
const names = Object.keys(shape);
|
|
42
|
+
if (names.length === 0) {
|
|
43
|
+
if (values !== undefined) {
|
|
44
|
+
throw new TypeError(`${what} carries no data: it is used as is, without arguments.`);
|
|
45
|
+
}
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
const example = `${what}({ ${names
|
|
49
|
+
.filter(name => defaultOf(shape[name]) === undefined)
|
|
50
|
+
.map(name => `${name}: ${kindOf(shape[name]) === 'string' ? "'...'" : kindOf(shape[name]) === 'number' ? '1' : 'true'}`)
|
|
51
|
+
.join(', ')} })`;
|
|
52
|
+
// Every value has a default: the component is used as is, or with some.
|
|
53
|
+
if (values === undefined && needsNoData(shape))
|
|
54
|
+
values = {};
|
|
55
|
+
if (typeof values !== 'object' || values === null || Array.isArray(values)) {
|
|
56
|
+
throw new TypeError(`${what} needs its data: write ${example}.`);
|
|
57
|
+
}
|
|
58
|
+
const given = values;
|
|
59
|
+
for (const key of Object.keys(given)) {
|
|
60
|
+
if (!Object.hasOwn(shape, key)) {
|
|
61
|
+
throw new TypeError(`"${key}" is not in the data of ${what}. Its data is: ${names.join(', ')}.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const result = {};
|
|
65
|
+
for (const name of names) {
|
|
66
|
+
const fallback = defaultOf(shape[name]);
|
|
67
|
+
const value = given[name] === undefined ? fallback : given[name];
|
|
68
|
+
const kind = kindOf(shape[name]);
|
|
69
|
+
const got = JSON.stringify(value) ?? typeof value;
|
|
70
|
+
if (typeof value !== kind ||
|
|
71
|
+
(kind === 'number' && !Number.isFinite(value))) {
|
|
72
|
+
throw new TypeError(`The data "${name}" of ${what} is a ${kind}, got ${got}.`);
|
|
73
|
+
}
|
|
74
|
+
result[name] = value;
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The `custom_id` of a component: its name, then its values in the order
|
|
80
|
+
* of the declaration. Discord accepts 100 characters: when the data does
|
|
81
|
+
* not fit, the error says by how much.
|
|
82
|
+
*/
|
|
83
|
+
export function encodeCustomId(name, shape, values) {
|
|
84
|
+
const parts = [name];
|
|
85
|
+
for (const field of Object.keys(shape))
|
|
86
|
+
parts.push(escape(String(values[field])));
|
|
87
|
+
const id = parts.join(SEPARATOR);
|
|
88
|
+
if (id.length > Limits.CustomId) {
|
|
89
|
+
throw new RangeError(`The data of ${name} is ${id.length - name.length - 1} characters long once encoded, and Discord leaves ${Limits.CustomId - name.length - 1} for it (its id is ${name}, ${name.length} characters, out of ${Limits.CustomId}). Carry less: an id instead of a name, or a shorter export name.`);
|
|
90
|
+
}
|
|
91
|
+
return id;
|
|
92
|
+
}
|
|
93
|
+
/** The name and the encoded values of a `custom_id` the framework wrote. */
|
|
94
|
+
export function decodeCustomId(id) {
|
|
95
|
+
const [name = '', ...parts] = split(id);
|
|
96
|
+
return { name, parts };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The values a `custom_id` carries, typed as the component declares them.
|
|
100
|
+
* `null` when they do not match the declaration: the file changed since the
|
|
101
|
+
* message was sent.
|
|
102
|
+
*/
|
|
103
|
+
export function readData(shape, parts) {
|
|
104
|
+
const names = Object.keys(shape);
|
|
105
|
+
if (parts.length !== names.length)
|
|
106
|
+
return null;
|
|
107
|
+
const values = {};
|
|
108
|
+
for (const [index, name] of names.entries()) {
|
|
109
|
+
const part = parts[index];
|
|
110
|
+
switch (kindOf(shape[name])) {
|
|
111
|
+
case 'number': {
|
|
112
|
+
const number = Number(part);
|
|
113
|
+
if (part.trim() === '' || !Number.isFinite(number))
|
|
114
|
+
return null;
|
|
115
|
+
values[name] = number;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case 'boolean':
|
|
119
|
+
if (part !== 'true' && part !== 'false')
|
|
120
|
+
return null;
|
|
121
|
+
values[name] = part === 'true';
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
values[name] = part;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return values;
|
|
128
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Declaration } from '../loader/loader.js';
|
|
2
|
+
import { type LoadedButton } from './button.js';
|
|
3
|
+
import { isEmbedDeclaration } from './embed.js';
|
|
4
|
+
import { type ComponentKind } from './declared.js';
|
|
5
|
+
import { type LoadedModal } from './modal.js';
|
|
6
|
+
import { type LoadedSelect } from './select.js';
|
|
7
|
+
/** An embed, checked (when it is static). */
|
|
8
|
+
export interface LoadedEmbed {
|
|
9
|
+
readonly kind: 'embed';
|
|
10
|
+
/** The name of the export that declares it. */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
}
|
|
13
|
+
/** A component, checked: what the router works with. */
|
|
14
|
+
export type LoadedComponent = LoadedButton | LoadedSelect | LoadedModal | LoadedEmbed;
|
|
15
|
+
/** What each kind is called in messages. */
|
|
16
|
+
export declare const WHAT: Record<ComponentKind, string>;
|
|
17
|
+
/**
|
|
18
|
+
* A component (a button, a select menu, a form or an embed), declared
|
|
19
|
+
* anywhere in `src/` with `button()`, `select()`, `modal()` or `embed()`,
|
|
20
|
+
* and exported: the name of the export is its id, so a file may declare
|
|
21
|
+
* several, each under its own export.
|
|
22
|
+
*/
|
|
23
|
+
export declare const componentDeclaration: Declaration<LoadedComponent>;
|
|
24
|
+
/** Two components of the same kind with the same name: the later one is left out. */
|
|
25
|
+
export declare function findDuplicates<T extends {
|
|
26
|
+
file: string;
|
|
27
|
+
export: string;
|
|
28
|
+
component: {
|
|
29
|
+
kind: ComponentKind;
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
|
32
|
+
}>(entries: readonly T[]): {
|
|
33
|
+
valid: T[];
|
|
34
|
+
conflicts: {
|
|
35
|
+
file: string;
|
|
36
|
+
export: string;
|
|
37
|
+
message: string;
|
|
38
|
+
}[];
|
|
39
|
+
};
|
|
40
|
+
/** Whether a declaration is an embed: nothing to route. */
|
|
41
|
+
export declare const isEmbedLoaded: (component: LoadedComponent) => component is LoadedEmbed;
|
|
42
|
+
export { isEmbedDeclaration };
|