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,251 @@
|
|
|
1
|
+
// Runs the component someone used: reads the id the framework wrote in it,
|
|
2
|
+
// finds its declaration, turns what Discord sent into what `run` receives,
|
|
3
|
+
// and makes sure the person always gets an answer.
|
|
4
|
+
import { ComponentType, } from '../discord/types/component.js';
|
|
5
|
+
import { InteractionType, } from '../discord/types/interaction.js';
|
|
6
|
+
import { MessageFlags } from '../discord/types/message.js';
|
|
7
|
+
import { authorOf, placeContext, refuse, resolvePlace, runInteraction, } from '../interactions/dispatch.js';
|
|
8
|
+
import { audienceLocale, translation } from '../messages/translate.js';
|
|
9
|
+
import { ComponentInteraction, ModalInteraction, } from '../structures/interaction.js';
|
|
10
|
+
import { remember } from '../structures/known.js';
|
|
11
|
+
import { toCamelCase } from '../util/case.js';
|
|
12
|
+
import { decodeCustomId, readData } from './custom-id.js';
|
|
13
|
+
import { SELECT_TYPES } from './select.js';
|
|
14
|
+
/** What a component is called, for the person and the developer. */
|
|
15
|
+
const WHAT_OF = {
|
|
16
|
+
button: 'button',
|
|
17
|
+
select: 'menu',
|
|
18
|
+
modal: 'form',
|
|
19
|
+
};
|
|
20
|
+
export class ComponentRouter {
|
|
21
|
+
#components = new Map();
|
|
22
|
+
#options;
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.#options = options;
|
|
25
|
+
}
|
|
26
|
+
/** Replaces every component (after a load or a reload). */
|
|
27
|
+
set(entries) {
|
|
28
|
+
const components = new Map();
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (entry.component.kind !== 'embed') {
|
|
31
|
+
components.set(`${entry.component.kind}:${entry.component.name}`, entry);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
this.#components = components;
|
|
35
|
+
}
|
|
36
|
+
/** Handles an Interaction Create event, if it is a component or a form. */
|
|
37
|
+
dispatch(ctx, raw) {
|
|
38
|
+
const isComponent = raw.type === InteractionType.MessageComponent;
|
|
39
|
+
const isModal = raw.type === InteractionType.ModalSubmit;
|
|
40
|
+
if (!isComponent && !isModal)
|
|
41
|
+
return;
|
|
42
|
+
const data = raw.data;
|
|
43
|
+
if (!data || typeof data.custom_id !== 'string')
|
|
44
|
+
return;
|
|
45
|
+
const author = authorOf(raw);
|
|
46
|
+
if (!author)
|
|
47
|
+
return;
|
|
48
|
+
const user = ctx.entities.user(author.user);
|
|
49
|
+
const { user: _user, member: _member, message: rawMessage, ...rest } = raw;
|
|
50
|
+
const message = rawMessage
|
|
51
|
+
? ctx.entities.message(rawMessage, raw.guild_id)
|
|
52
|
+
: null;
|
|
53
|
+
// Only the person sees an ephemeral message: what answers a component
|
|
54
|
+
// on it (an update of that message, a reply) stays with them, whatever
|
|
55
|
+
// the file says, so `t` speaks their language.
|
|
56
|
+
// https://docs.discord.com/developers/resources/message#message-object-message-flags
|
|
57
|
+
const isPrivate = ((rawMessage?.flags ?? 0) &
|
|
58
|
+
MessageFlags.Ephemeral) !==
|
|
59
|
+
0;
|
|
60
|
+
const ephemeralOf = (component) => isPrivate || ('ephemeral' in component && component.ephemeral);
|
|
61
|
+
const { name, parts } = decodeCustomId(data.custom_id);
|
|
62
|
+
// A button, a menu and a form may share a name: what was used says
|
|
63
|
+
// which one is meant.
|
|
64
|
+
const kind = isModal
|
|
65
|
+
? 'modal'
|
|
66
|
+
: data.component_type ===
|
|
67
|
+
ComponentType.Button
|
|
68
|
+
? 'button'
|
|
69
|
+
: 'select';
|
|
70
|
+
const entry = this.#components.get(`${kind}:${name}`);
|
|
71
|
+
const what = WHAT_OF[kind];
|
|
72
|
+
const interaction = isModal
|
|
73
|
+
? new ModalInteraction(ctx, rest, user, {
|
|
74
|
+
ephemeral: entry ? ephemeralOf(entry.component) : isPrivate,
|
|
75
|
+
message,
|
|
76
|
+
})
|
|
77
|
+
: new ComponentInteraction(ctx, rest, user, {
|
|
78
|
+
ephemeral: entry ? ephemeralOf(entry.component) : isPrivate,
|
|
79
|
+
message: message,
|
|
80
|
+
});
|
|
81
|
+
if (isComponent && !message)
|
|
82
|
+
return;
|
|
83
|
+
// Embeds are never in the map: what is left has a `run`.
|
|
84
|
+
if (!entry ||
|
|
85
|
+
entry.component.kind === 'embed' ||
|
|
86
|
+
(entry.component.kind === 'select' &&
|
|
87
|
+
SELECT_TYPES[entry.component.type] !==
|
|
88
|
+
data.component_type)) {
|
|
89
|
+
// Sent before the export was renamed, removed or changed of kind.
|
|
90
|
+
refuse(interaction, 'gone', { what });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const { component, file } = entry;
|
|
94
|
+
const values = readData(component.data, parts);
|
|
95
|
+
if (!values) {
|
|
96
|
+
// The data of the component changed since the message was sent.
|
|
97
|
+
refuse(interaction, 'outdated', { what });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const placed = resolvePlace(ctx, raw, user, author.member, component.where, warning => this.#options.onWarning(file, `${name} ${warning}`));
|
|
101
|
+
if (!placed.place) {
|
|
102
|
+
refuse(interaction, placed.refusal, { what });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const { place } = placed;
|
|
106
|
+
if (component.kind !== 'modal' &&
|
|
107
|
+
component.who === 'author' &&
|
|
108
|
+
rawMessage?.interaction_metadata?.user.id !== user.id) {
|
|
109
|
+
refuse(interaction, 'authorOnly', { what });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
remember(interaction, {
|
|
113
|
+
guild: place.guild ?? undefined,
|
|
114
|
+
member: place.member ?? undefined,
|
|
115
|
+
channel: place.channel,
|
|
116
|
+
});
|
|
117
|
+
let specific;
|
|
118
|
+
try {
|
|
119
|
+
specific =
|
|
120
|
+
component.kind === 'modal'
|
|
121
|
+
? {
|
|
122
|
+
fields: this.#readFields(ctx, raw, component.fields, data),
|
|
123
|
+
}
|
|
124
|
+
: component.kind === 'select'
|
|
125
|
+
? this.#readValues(ctx, raw, component.type, data)
|
|
126
|
+
: {};
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this.#options.onError(file, error);
|
|
130
|
+
refuse(interaction, 'failed', { what });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const context = Object.freeze({
|
|
134
|
+
interaction,
|
|
135
|
+
user,
|
|
136
|
+
message,
|
|
137
|
+
data: Object.freeze(values),
|
|
138
|
+
...specific,
|
|
139
|
+
...placeContext(place, component.where),
|
|
140
|
+
// `t` speaks the language of who will read the answer.
|
|
141
|
+
...translation(ctx, audienceLocale({
|
|
142
|
+
person: raw.locale,
|
|
143
|
+
guild: place.guild,
|
|
144
|
+
ephemeral: ephemeralOf(component),
|
|
145
|
+
})),
|
|
146
|
+
});
|
|
147
|
+
runInteraction({
|
|
148
|
+
interaction,
|
|
149
|
+
file,
|
|
150
|
+
name,
|
|
151
|
+
what,
|
|
152
|
+
reporter: this.#options,
|
|
153
|
+
// Nothing changes on screen while a component is answered; a form
|
|
154
|
+
// sent is like a command used.
|
|
155
|
+
defer: () => interaction instanceof ComponentInteraction
|
|
156
|
+
? interaction.deferUpdate()
|
|
157
|
+
: interaction.defer(),
|
|
158
|
+
unanswered: `finished without answering: the person sees "This interaction failed". Call interaction.update() or interaction.reply() in run${component.kind === 'modal' ? '' : ', or interaction.deferUpdate() to change nothing'}.`,
|
|
159
|
+
run: () => component.run(context),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/** What the person picked in a menu, as structures where it applies. */
|
|
163
|
+
#readValues(ctx, raw, type, data) {
|
|
164
|
+
const values = resolveValues(ctx, raw, type, data.values ?? [], data.resolved ?? {});
|
|
165
|
+
return { values, value: values[0] };
|
|
166
|
+
}
|
|
167
|
+
/** What the person filled in, field by field. */
|
|
168
|
+
#readFields(ctx, raw, fields, data) {
|
|
169
|
+
const sent = new Map();
|
|
170
|
+
for (const top of data.components ?? []) {
|
|
171
|
+
if (top.type === ComponentType.Label && top.component?.custom_id) {
|
|
172
|
+
sent.set(top.component.custom_id, top.component);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const resolved = data.resolved ?? {};
|
|
176
|
+
const result = {};
|
|
177
|
+
for (const { name, field, required } of fields) {
|
|
178
|
+
if (field.type === 'note')
|
|
179
|
+
continue;
|
|
180
|
+
const input = sent.get(name);
|
|
181
|
+
switch (field.type) {
|
|
182
|
+
case 'text': {
|
|
183
|
+
const text = input && 'value' in input ? input.value : '';
|
|
184
|
+
result[name] = text === '' && !required ? undefined : text;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case 'radio': {
|
|
188
|
+
const value = input && 'value' in input ? input.value : null;
|
|
189
|
+
result[name] = value === null || value === '' ? undefined : value;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
case 'checkbox':
|
|
193
|
+
result[name] =
|
|
194
|
+
input && 'value' in input ? input.value === true : false;
|
|
195
|
+
break;
|
|
196
|
+
case 'select':
|
|
197
|
+
case 'checkboxes':
|
|
198
|
+
result[name] =
|
|
199
|
+
input && 'values' in input ? [...input.values] : [];
|
|
200
|
+
break;
|
|
201
|
+
case 'files':
|
|
202
|
+
result[name] = (input && 'values' in input ? input.values : [])
|
|
203
|
+
.map(id => {
|
|
204
|
+
const attachment = resolved.attachments?.[id];
|
|
205
|
+
return attachment ? toCamelCase(attachment) : undefined;
|
|
206
|
+
})
|
|
207
|
+
.filter(one => one !== undefined);
|
|
208
|
+
break;
|
|
209
|
+
default:
|
|
210
|
+
result[name] = resolveValues(ctx, raw, field.type, input && 'values' in input ? input.values : [], resolved);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/** The things picked in a menu of the server, as structures. */
|
|
217
|
+
function resolveValues(ctx, raw, type, ids, resolved) {
|
|
218
|
+
if (type === 'string')
|
|
219
|
+
return [...ids];
|
|
220
|
+
const guildId = raw.guild_id;
|
|
221
|
+
const userOf = (id) => {
|
|
222
|
+
const rawUser = resolved.users?.[id];
|
|
223
|
+
if (!rawUser)
|
|
224
|
+
return undefined;
|
|
225
|
+
const member = resolved.members?.[id];
|
|
226
|
+
if (member && guildId) {
|
|
227
|
+
ctx.entities.member(guildId, member, rawUser);
|
|
228
|
+
}
|
|
229
|
+
return ctx.entities.user(rawUser);
|
|
230
|
+
};
|
|
231
|
+
const roleOf = (id) => {
|
|
232
|
+
const role = resolved.roles?.[id];
|
|
233
|
+
return role && guildId ? ctx.entities.role(guildId, role) : undefined;
|
|
234
|
+
};
|
|
235
|
+
const channelOf = (id) => {
|
|
236
|
+
const partial = resolved.channels?.[id];
|
|
237
|
+
return (ctx.cache.channels.get(id) ??
|
|
238
|
+
(partial
|
|
239
|
+
? ctx.entities.channel(partial, guildId)
|
|
240
|
+
: undefined));
|
|
241
|
+
};
|
|
242
|
+
return ids
|
|
243
|
+
.map(id => type === 'user'
|
|
244
|
+
? userOf(id)
|
|
245
|
+
: type === 'role'
|
|
246
|
+
? roleOf(id)
|
|
247
|
+
: type === 'channel'
|
|
248
|
+
? channelOf(id)
|
|
249
|
+
: (userOf(id) ?? roleOf(id)))
|
|
250
|
+
.filter(one => one !== undefined);
|
|
251
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { Snowflake } from '../discord/types/common.js';
|
|
2
|
+
import { ChannelType } from '../discord/types/channel.js';
|
|
3
|
+
import { type RawSelectMenu } from '../discord/types/component.js';
|
|
4
|
+
import type { Channel } from '../structures/channel.js';
|
|
5
|
+
import type { ComponentInteraction } from '../structures/interaction.js';
|
|
6
|
+
import type { Role } from '../structures/role.js';
|
|
7
|
+
import type { User } from '../structures/user.js';
|
|
8
|
+
import type { TranslationContext } from '../messages/messages.js';
|
|
9
|
+
import { type DynamicText } from './component.js';
|
|
10
|
+
import type { ComponentWhere, InteractiveConfig, InteractiveContext, PlaceOf } from './component.js';
|
|
11
|
+
import { type DataShape, type DataInputOf } from './custom-id.js';
|
|
12
|
+
import type { Rendered, SelectComponent } from './instance.js';
|
|
13
|
+
import { type LoadedOption, type OptionValue, type SelectOptions } from './options.js';
|
|
14
|
+
/** The kinds of menus: texts you declare, or things of the server. */
|
|
15
|
+
export type SelectType = 'string' | 'user' | 'role' | 'mentionable' | 'channel';
|
|
16
|
+
/** The kinds of menus whose options are things of the server. */
|
|
17
|
+
export type EntitySelectType = Exclude<SelectType, 'string'>;
|
|
18
|
+
export declare const SELECT_TYPES: Record<SelectType, RawSelectMenu['type']>;
|
|
19
|
+
/** What `run` receives for one picked thing, by kind of menu. */
|
|
20
|
+
interface EntityValues {
|
|
21
|
+
user: User;
|
|
22
|
+
role: Role;
|
|
23
|
+
mentionable: User | Role;
|
|
24
|
+
channel: Channel;
|
|
25
|
+
}
|
|
26
|
+
/** What a select menu looks like. */
|
|
27
|
+
export interface SelectLook<Data extends DataShape = DataShape> {
|
|
28
|
+
/**
|
|
29
|
+
* The text shown when nothing is picked (150 characters at most):
|
|
30
|
+
* written as is, or a function of `t` and the data, run when the
|
|
31
|
+
* message is sent.
|
|
32
|
+
*/
|
|
33
|
+
placeholder?: DynamicText<Data>;
|
|
34
|
+
/** Shows the menu greyed out, impossible to use. */
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** What a message can change when it shows a menu. */
|
|
38
|
+
export interface SelectInstanceOptions extends SelectLook {
|
|
39
|
+
/**
|
|
40
|
+
* What is picked when the menu appears: the values of the options for a
|
|
41
|
+
* menu of texts, the ids (or the things themselves) for the others.
|
|
42
|
+
*/
|
|
43
|
+
defaults?: readonly (string | {
|
|
44
|
+
id: Snowflake;
|
|
45
|
+
})[];
|
|
46
|
+
}
|
|
47
|
+
/** What every select file declares, whatever its kind. */
|
|
48
|
+
interface SelectConfigBase<Data extends DataShape, Where extends ComponentWhere> extends SelectLook<Data>, InteractiveConfig<Data, Where> {
|
|
49
|
+
/** How many things must be picked at least (0 to 25). 1 by default. */
|
|
50
|
+
min?: number;
|
|
51
|
+
/** How many things can be picked at most (1 to 25). 1 by default. */
|
|
52
|
+
max?: number;
|
|
53
|
+
}
|
|
54
|
+
/** What `run` receives when something is picked. */
|
|
55
|
+
export type SelectContext<Picked, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> = InteractiveContext<Data> & TranslationContext & {
|
|
56
|
+
/** What was picked, in the order of the menu. */
|
|
57
|
+
values: Picked[];
|
|
58
|
+
/** The first thing picked; `undefined` when nothing was (a menu with `min: 0`). */
|
|
59
|
+
value: Picked | undefined;
|
|
60
|
+
} & PlaceOf<Where, ComponentInteraction>;
|
|
61
|
+
/** A menu of texts you declare. */
|
|
62
|
+
export interface StringSelectConfig<Options extends SelectOptions, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> extends SelectConfigBase<Data, Where> {
|
|
63
|
+
/** The kind of menu: texts you declare, which is the default. */
|
|
64
|
+
type?: 'string';
|
|
65
|
+
/**
|
|
66
|
+
* The options (25 at most): a list of texts, an object from what is
|
|
67
|
+
* shown to what your code receives, or a list of `RichOption`.
|
|
68
|
+
*/
|
|
69
|
+
options: Options;
|
|
70
|
+
/** What to do when something is picked. */
|
|
71
|
+
run: (context: SelectContext<OptionValue<Options>, Data, Where>) => unknown;
|
|
72
|
+
}
|
|
73
|
+
/** A menu of users, roles, users and roles, or channels of the server. */
|
|
74
|
+
export interface EntitySelectConfig<Type extends EntitySelectType, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> extends SelectConfigBase<Data, Where> {
|
|
75
|
+
/** The kind of menu: what Discord lists. */
|
|
76
|
+
type: Type;
|
|
77
|
+
/** For a menu of channels: the kinds of channels listed. All by default. */
|
|
78
|
+
channelTypes?: readonly ChannelType[];
|
|
79
|
+
/** What to do when something is picked. */
|
|
80
|
+
run: (context: SelectContext<EntityValues[Type], Data, Where>) => unknown;
|
|
81
|
+
}
|
|
82
|
+
/** What a select file gives to `select()`: a menu of texts, or of things of the server. */
|
|
83
|
+
export type SelectConfig<Options extends SelectOptions = SelectOptions, Type extends EntitySelectType = EntitySelectType, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> = StringSelectConfig<Options, Data, Where> | EntitySelectConfig<Type, Data, Where>;
|
|
84
|
+
/**
|
|
85
|
+
* What `select()` returns: a declaration the framework finds in the exports
|
|
86
|
+
* of a file. Import it where you send a message, and call it with the data
|
|
87
|
+
* it carries; a menu without data is used as is, or called with what to
|
|
88
|
+
* show.
|
|
89
|
+
*/
|
|
90
|
+
export type SelectDeclaration<Data extends DataShape = {}> = {
|
|
91
|
+
/** What the file gave to `select()`, not checked yet. */
|
|
92
|
+
readonly config: unknown;
|
|
93
|
+
} & ({} extends Data ? ((options?: SelectInstanceOptions) => SelectComponent) & SelectComponent : {} extends DataInputOf<Data> ? ((data?: DataInputOf<Data>, options?: SelectInstanceOptions) => SelectComponent) & SelectComponent : (data: DataInputOf<Data>, options?: SelectInstanceOptions) => SelectComponent);
|
|
94
|
+
/**
|
|
95
|
+
* Declares a select menu. Export the result from any file of `src/`: the
|
|
96
|
+
* name of the export is what tells the menu apart, so you never write an
|
|
97
|
+
* id.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* import { select } from 'chapterjs';
|
|
101
|
+
*
|
|
102
|
+
* export const color = select({
|
|
103
|
+
* placeholder: 'Pick a color',
|
|
104
|
+
* options: { Red: 'red', Blue: 'blue' },
|
|
105
|
+
* async run({ interaction, value }) {
|
|
106
|
+
* await interaction.update({ content: `You picked ${value}.`, components: [] });
|
|
107
|
+
* },
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export declare function select<const Options extends SelectOptions, const Data extends DataShape = {}, const Where extends ComponentWhere = 'guild'>(config: StringSelectConfig<Options, Data, Where>): SelectDeclaration<Data>;
|
|
112
|
+
export declare function select<const Type extends EntitySelectType, const Data extends DataShape = {}, const Where extends ComponentWhere = 'guild'>(config: EntitySelectConfig<Type, Data, Where>): SelectDeclaration<Data>;
|
|
113
|
+
/** A select menu, checked. */
|
|
114
|
+
export interface LoadedSelect {
|
|
115
|
+
readonly kind: 'select';
|
|
116
|
+
/** The name of the export that declares it: its id for Discord. */
|
|
117
|
+
readonly name: string;
|
|
118
|
+
readonly type: SelectType;
|
|
119
|
+
/** The options of a menu of texts, their texts maybe computed when sent. */
|
|
120
|
+
readonly options: readonly LoadedOption[];
|
|
121
|
+
readonly channelTypes: readonly ChannelType[] | undefined;
|
|
122
|
+
readonly look: SelectLook;
|
|
123
|
+
readonly min: number;
|
|
124
|
+
readonly max: number;
|
|
125
|
+
readonly data: DataShape;
|
|
126
|
+
readonly where: ComponentWhere;
|
|
127
|
+
readonly who: 'everyone' | 'author';
|
|
128
|
+
readonly ephemeral: boolean;
|
|
129
|
+
readonly run: (context: SelectContext<unknown, DataShape, ComponentWhere>) => unknown;
|
|
130
|
+
}
|
|
131
|
+
/** Checks `min` and `max` of a menu, a group of checkboxes or a file upload. */
|
|
132
|
+
export declare function readRange(what: string, given: {
|
|
133
|
+
min?: unknown;
|
|
134
|
+
max?: unknown;
|
|
135
|
+
}, limit: number, defaults: {
|
|
136
|
+
min: number;
|
|
137
|
+
max: number;
|
|
138
|
+
}): {
|
|
139
|
+
min: number;
|
|
140
|
+
max: number;
|
|
141
|
+
};
|
|
142
|
+
export declare function readPlaceholder(what: string, value: unknown): DynamicText | undefined;
|
|
143
|
+
export declare function readChannelTypes(what: string, value: unknown): ChannelType[] | undefined;
|
|
144
|
+
/** The menu as Discord takes it, for the data and the options given. */
|
|
145
|
+
export declare function renderSelect(loaded: LoadedSelect, args: readonly unknown[]): Rendered<'select'>;
|
|
146
|
+
export {};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// `select()`, as the files of `src/components/selects/` import it from
|
|
2
|
+
// 'chapterjs': a menu of texts you declare, or of users, roles, channels
|
|
3
|
+
// of the server, and what to do with what is picked.
|
|
4
|
+
// https://docs.discord.com/developers/components/reference#string-select
|
|
5
|
+
import { Limits } from '../discord/api.js';
|
|
6
|
+
import { ChannelType } from '../discord/types/channel.js';
|
|
7
|
+
import { ComponentType, } from '../discord/types/component.js';
|
|
8
|
+
import { isDynamic, resolveText, } from './component.js';
|
|
9
|
+
import { checkData, encodeCustomId, } from './custom-id.js';
|
|
10
|
+
import { createComponent } from './declared.js';
|
|
11
|
+
import { hasDynamicOption, resolveOptions, } from './options.js';
|
|
12
|
+
export const SELECT_TYPES = {
|
|
13
|
+
string: ComponentType.StringSelect,
|
|
14
|
+
user: ComponentType.UserSelect,
|
|
15
|
+
role: ComponentType.RoleSelect,
|
|
16
|
+
mentionable: ComponentType.MentionableSelect,
|
|
17
|
+
channel: ComponentType.ChannelSelect,
|
|
18
|
+
};
|
|
19
|
+
export function select(config) {
|
|
20
|
+
return createComponent('select', config, {
|
|
21
|
+
asPiece: true,
|
|
22
|
+
pieceKind: 'select',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const fail = (message) => {
|
|
26
|
+
throw new TypeError(message);
|
|
27
|
+
};
|
|
28
|
+
/** Checks `min` and `max` of a menu, a group of checkboxes or a file upload. */
|
|
29
|
+
export function readRange(what, given, limit, defaults) {
|
|
30
|
+
const read = (key, fallback, low) => {
|
|
31
|
+
const value = given[key];
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
return fallback;
|
|
34
|
+
if (!Number.isInteger(value) ||
|
|
35
|
+
value < low ||
|
|
36
|
+
value > limit) {
|
|
37
|
+
return fail(`"${key}" of ${what} is a whole number between ${low} and ${limit}, got ${JSON.stringify(value) ?? typeof value}.`);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
};
|
|
41
|
+
const min = read('min', defaults.min, 0);
|
|
42
|
+
const max = read('max', defaults.max, 1);
|
|
43
|
+
if (min > max) {
|
|
44
|
+
fail(`${what} can never be used: its "min" (${min}) is greater than its "max" (${max}).`);
|
|
45
|
+
}
|
|
46
|
+
return { min, max };
|
|
47
|
+
}
|
|
48
|
+
export function readPlaceholder(what, value) {
|
|
49
|
+
if (value === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
// A function is checked when the message is sent.
|
|
52
|
+
if (isDynamic(value))
|
|
53
|
+
return value;
|
|
54
|
+
if (typeof value !== 'string' ||
|
|
55
|
+
value.trim() === '' ||
|
|
56
|
+
value.length > Limits.SelectPlaceholder) {
|
|
57
|
+
return fail(`The placeholder of ${what} is a text of 1 to ${Limits.SelectPlaceholder} characters.`);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
export function readChannelTypes(what, value) {
|
|
62
|
+
if (value === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
const known = Object.values(ChannelType);
|
|
65
|
+
if (!Array.isArray(value) ||
|
|
66
|
+
value.length === 0 ||
|
|
67
|
+
!value.every(one => known.includes(one))) {
|
|
68
|
+
return fail(`"channelTypes" of ${what} is a list of ChannelType values, like [ChannelType.GuildText].`);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
/** Checks what a message gives to change a menu. */
|
|
73
|
+
function readInstanceOptions(loaded, name, given) {
|
|
74
|
+
if (given === undefined)
|
|
75
|
+
return { look: {}, defaults: undefined };
|
|
76
|
+
if (typeof given !== 'object' || given === null || Array.isArray(given)) {
|
|
77
|
+
fail(`The options of ${name} are an object like { placeholder: '...', defaults: [...] }.`);
|
|
78
|
+
}
|
|
79
|
+
const options = given;
|
|
80
|
+
for (const key of Object.keys(options)) {
|
|
81
|
+
if (!['placeholder', 'disabled', 'defaults'].includes(key)) {
|
|
82
|
+
fail(`"${key}" is not something ${name} takes when shown. It can take: placeholder, disabled, defaults.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const look = {};
|
|
86
|
+
const placeholder = readPlaceholder(name, options.placeholder);
|
|
87
|
+
if (placeholder !== undefined)
|
|
88
|
+
look.placeholder = placeholder;
|
|
89
|
+
if (options.disabled !== undefined) {
|
|
90
|
+
if (typeof options.disabled !== 'boolean')
|
|
91
|
+
fail(`"disabled" of ${name} is true or false.`);
|
|
92
|
+
look.disabled = options.disabled;
|
|
93
|
+
}
|
|
94
|
+
if (options.defaults === undefined)
|
|
95
|
+
return { look, defaults: undefined };
|
|
96
|
+
if (!Array.isArray(options.defaults)) {
|
|
97
|
+
fail(`"defaults" of ${name} is a list of what is picked when the menu appears.`);
|
|
98
|
+
}
|
|
99
|
+
const ids = options.defaults.map(item => {
|
|
100
|
+
const id = typeof item === 'string' ? item : item?.id;
|
|
101
|
+
if (typeof id !== 'string') {
|
|
102
|
+
return fail(`A default of ${name} is a text, or something with an id, got ${JSON.stringify(item) ?? typeof item}.`);
|
|
103
|
+
}
|
|
104
|
+
return id;
|
|
105
|
+
});
|
|
106
|
+
if (ids.length > loaded.max) {
|
|
107
|
+
fail(`${name} has ${ids.length} defaults, but ${loaded.max} can be picked at most.`);
|
|
108
|
+
}
|
|
109
|
+
if (loaded.type === 'string') {
|
|
110
|
+
for (const value of ids) {
|
|
111
|
+
if (!loaded.options.some(option => option.value === value)) {
|
|
112
|
+
fail(`${JSON.stringify(value)} is not an option of ${name}. Its options are: ${loaded.options.map(option => option.value).join(', ')}.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { look, defaults: ids };
|
|
116
|
+
}
|
|
117
|
+
// Discord wants to know what each default is; a mentionable may be either,
|
|
118
|
+
// and a role id is also a guild id for @everyone: users first.
|
|
119
|
+
const kind = loaded.type === 'mentionable' ? 'user' : loaded.type;
|
|
120
|
+
return { look, defaults: ids.map(id => ({ id, type: kind })) };
|
|
121
|
+
}
|
|
122
|
+
/** The menu as Discord takes it, for the data and the options given. */
|
|
123
|
+
export function renderSelect(loaded, args) {
|
|
124
|
+
const hasData = Object.keys(loaded.data).length > 0;
|
|
125
|
+
const { name } = loaded;
|
|
126
|
+
if (args.length > (hasData ? 2 : 1)) {
|
|
127
|
+
fail(`${name} takes ${hasData ? 'its data, then options' : 'options'} at most.`);
|
|
128
|
+
}
|
|
129
|
+
const values = checkData(name, loaded.data, hasData ? args[0] : undefined);
|
|
130
|
+
const { look, defaults } = readInstanceOptions(loaded, name, hasData ? args[1] : args[0]);
|
|
131
|
+
const merged = { ...loaded.look, ...look };
|
|
132
|
+
const dynamic = isDynamic(merged.placeholder) || hasDynamicOption(loaded.options);
|
|
133
|
+
const make = (context) => {
|
|
134
|
+
const placeholder = resolveText(`The placeholder of ${name}`, merged.placeholder, context, { max: Limits.SelectPlaceholder });
|
|
135
|
+
const base = {
|
|
136
|
+
custom_id: encodeCustomId(loaded.name, loaded.data, values),
|
|
137
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
138
|
+
...(loaded.min !== 1 ? { min_values: loaded.min } : {}),
|
|
139
|
+
...(loaded.max !== 1 ? { max_values: loaded.max } : {}),
|
|
140
|
+
...(merged.disabled ? { disabled: true } : {}),
|
|
141
|
+
};
|
|
142
|
+
if (loaded.type !== 'string') {
|
|
143
|
+
return {
|
|
144
|
+
type: SELECT_TYPES[loaded.type],
|
|
145
|
+
...base,
|
|
146
|
+
...(loaded.channelTypes
|
|
147
|
+
? { channel_types: [...loaded.channelTypes] }
|
|
148
|
+
: {}),
|
|
149
|
+
...(defaults
|
|
150
|
+
? { default_values: defaults }
|
|
151
|
+
: {}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const picked = defaults;
|
|
155
|
+
return {
|
|
156
|
+
type: ComponentType.StringSelect,
|
|
157
|
+
...base,
|
|
158
|
+
options: resolveOptions(name, loaded.options, context)
|
|
159
|
+
.map(option => picked
|
|
160
|
+
? {
|
|
161
|
+
...option,
|
|
162
|
+
...(picked.includes(option.value)
|
|
163
|
+
? { default: true }
|
|
164
|
+
: { default: undefined }),
|
|
165
|
+
}
|
|
166
|
+
: option)
|
|
167
|
+
.map(option => option.default === undefined
|
|
168
|
+
? (({ default: _d, ...rest }) => rest)(option)
|
|
169
|
+
: option),
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
// Texts computed when the message is sent wait for who will read it.
|
|
173
|
+
return {
|
|
174
|
+
kind: 'select',
|
|
175
|
+
raw: dynamic
|
|
176
|
+
? t => make({ t, data: values })
|
|
177
|
+
: make({ t: undefined, data: values }),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type CacheOptions } from '../cache/cache.js';
|
|
2
|
+
import type { Snowflake } from '../discord/types/common.js';
|
|
3
|
+
import type { GatewayDispatchEventName, GatewayDispatchEvents, RawGatewayPresenceUpdate } from '../discord/types/gateway-events.js';
|
|
4
|
+
import { Gateway, type GatewayOptions } from '../gateway/gateway.js';
|
|
5
|
+
import type { ShardEvent } from '../gateway/shard.js';
|
|
6
|
+
import { type RestOptions } from '../rest/rest.js';
|
|
7
|
+
import type { Context } from '../structures/context.js';
|
|
8
|
+
/** What a dispatched event is, besides its data. */
|
|
9
|
+
export interface DispatchInfo {
|
|
10
|
+
shardId: number;
|
|
11
|
+
/**
|
|
12
|
+
* For Guild Create: `true` when the bot just joined the server, `false`
|
|
13
|
+
* when it was already in it and Discord is only sending its data (after
|
|
14
|
+
* connecting, or after an outage).
|
|
15
|
+
*/
|
|
16
|
+
joined: boolean;
|
|
17
|
+
/** What `beforeDispatch` returned for this event. */
|
|
18
|
+
before: unknown;
|
|
19
|
+
}
|
|
20
|
+
export type BotEvent = ShardEvent
|
|
21
|
+
/** Every server the bot is in has sent its data (or took too long). */
|
|
22
|
+
| {
|
|
23
|
+
type: 'guildsReady';
|
|
24
|
+
}
|
|
25
|
+
/** Applying an event to the cache failed: the event is still delivered. */
|
|
26
|
+
| {
|
|
27
|
+
type: 'stateError';
|
|
28
|
+
event: GatewayDispatchEventName;
|
|
29
|
+
error: unknown;
|
|
30
|
+
}
|
|
31
|
+
/** Something went wrong with a voice connection, which goes on or ends. */
|
|
32
|
+
| {
|
|
33
|
+
type: 'voiceWarning';
|
|
34
|
+
message: string;
|
|
35
|
+
};
|
|
36
|
+
export interface BotOptions {
|
|
37
|
+
token: string;
|
|
38
|
+
/** The version of the framework. */
|
|
39
|
+
version: string;
|
|
40
|
+
intents: number;
|
|
41
|
+
presence?: RawGatewayPresenceUpdate | undefined;
|
|
42
|
+
cache?: CacheOptions;
|
|
43
|
+
shards?: GatewayOptions['shards'];
|
|
44
|
+
identifyGate?: GatewayOptions['identifyGate'];
|
|
45
|
+
/** Called for every event, once the cache reflects it. */
|
|
46
|
+
onDispatch?: <E extends GatewayDispatchEventName>(event: E, data: GatewayDispatchEvents[E], info: DispatchInfo) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Called for every event before the cache reflects it: the last moment
|
|
49
|
+
* to read what the event removes. What it returns is given back to
|
|
50
|
+
* `onDispatch`.
|
|
51
|
+
*/
|
|
52
|
+
beforeDispatch?: <E extends GatewayDispatchEventName>(event: E, data: GatewayDispatchEvents[E]) => unknown;
|
|
53
|
+
onEvent?: (event: BotEvent) => void;
|
|
54
|
+
/**
|
|
55
|
+
* The servers the bot pays attention to. Events of the others are dropped
|
|
56
|
+
* before anything else, as if the bot was not in them: this is how a dev
|
|
57
|
+
* bot only sees its dev server. What belongs to no server is decided by
|
|
58
|
+
* `privateEvents`.
|
|
59
|
+
*/
|
|
60
|
+
guildFilter?: (guildId: Snowflake) => boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Whether what happens outside servers (private messages, commands used
|
|
63
|
+
* there) is for this bot. Default: true. Discord sends it to every
|
|
64
|
+
* process of the bot that runs its first shard: when a dev bot and a
|
|
65
|
+
* production bot share a token, only one may answer.
|
|
66
|
+
*/
|
|
67
|
+
privateEvents?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* How long to wait for the data of every server after connecting, in
|
|
70
|
+
* milliseconds, before going on without the ones that did not come
|
|
71
|
+
* (an outage of Discord). Default: 15000.
|
|
72
|
+
*/
|
|
73
|
+
guildsTimeout?: number;
|
|
74
|
+
/** Only tests change these. */
|
|
75
|
+
rest?: Partial<RestOptions>;
|
|
76
|
+
gateway?: Pick<GatewayOptions, 'backoff' | 'identifyInterval'>;
|
|
77
|
+
voice?: {
|
|
78
|
+
secure?: boolean;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export interface Bot {
|
|
82
|
+
readonly ctx: Context;
|
|
83
|
+
readonly gateway: Gateway;
|
|
84
|
+
/**
|
|
85
|
+
* Connects to Discord, and resolves once every shard is ready and every
|
|
86
|
+
* server has sent its data.
|
|
87
|
+
*/
|
|
88
|
+
connect(): Promise<void>;
|
|
89
|
+
/** Disconnects cleanly: the bot goes offline right away. */
|
|
90
|
+
close(): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Changes what the bot shows under its name (its status and activity)
|
|
93
|
+
* on every shard of this process. Discord's limit on presence updates is
|
|
94
|
+
* respected: when it is reached, the latest presence is sent as soon as
|
|
95
|
+
* it allows.
|
|
96
|
+
*/
|
|
97
|
+
setPresence(presence: RawGatewayPresenceUpdate): void;
|
|
98
|
+
}
|
|
99
|
+
export declare function createBot(options: BotOptions): Bot;
|