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,342 @@
|
|
|
1
|
+
// What a file declares with `button()`, `select()`, `modal()` and
|
|
2
|
+
// `embed()`, checked when it loads against the limits of Discord, so a
|
|
3
|
+
// mistake is explained before Discord refuses it; the component is then
|
|
4
|
+
// bound to the name of its export, which is its id and what makes its
|
|
5
|
+
// instances.
|
|
6
|
+
import { Limits } from '../discord/api.js';
|
|
7
|
+
import { checkEmbed } from '../structures/payload.js';
|
|
8
|
+
import { isDynamic } from './component.js';
|
|
9
|
+
import { readLook, renderButton, } from './button.js';
|
|
10
|
+
import { DATA_KINDS } from './custom-id.js';
|
|
11
|
+
import { isEmbedDeclaration } from './embed.js';
|
|
12
|
+
import { bindComponent, componentStateOf, } from './declared.js';
|
|
13
|
+
import { readField, renderModal } from './modal.js';
|
|
14
|
+
import { readOptions } from './options.js';
|
|
15
|
+
import { readChannelTypes, readPlaceholder, readRange, renderSelect, SELECT_TYPES, } from './select.js';
|
|
16
|
+
const fail = (message) => {
|
|
17
|
+
throw new TypeError(message);
|
|
18
|
+
};
|
|
19
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
20
|
+
/**
|
|
21
|
+
* What the name of a component can hold: the name of an export (or of a
|
|
22
|
+
* file, for a default export), as long as a custom_id can carry it beside
|
|
23
|
+
* the data: no `:` (what separates the data), and room left for the data.
|
|
24
|
+
*/
|
|
25
|
+
const NAME = /^[^:\s]+$/;
|
|
26
|
+
function checkKeys(what, value, allowed) {
|
|
27
|
+
for (const key of Object.keys(value)) {
|
|
28
|
+
if (!allowed.includes(key)) {
|
|
29
|
+
fail(`"${key}" is not something ${what} has. It can have: ${allowed.join(', ')}.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function checkBoolean(what, value) {
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return false;
|
|
36
|
+
if (typeof value !== 'boolean')
|
|
37
|
+
return fail(`${what} is true or false, got ${JSON.stringify(value) ?? typeof value}.`);
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
const PLACES = ['guild', 'dm', 'both'];
|
|
41
|
+
function checkWhere(what, value) {
|
|
42
|
+
if (value === undefined)
|
|
43
|
+
return 'guild';
|
|
44
|
+
if (!PLACES.includes(value)) {
|
|
45
|
+
return fail(`"where" says where ${what} can be used: 'guild' (in servers, which is the default), 'dm' (in private messages with the bot) or 'both'. Got ${JSON.stringify(value) ?? typeof value}.`);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function checkWho(what, value) {
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
return 'everyone';
|
|
52
|
+
if (value !== 'everyone' && value !== 'author') {
|
|
53
|
+
return fail(`"who" says who may use ${what}: 'everyone' (the default) or 'author' (only who used the command the message answers). Got ${JSON.stringify(value) ?? typeof value}.`);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function checkDataShape(what, value) {
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return Object.freeze({});
|
|
60
|
+
if (!isRecord(value)) {
|
|
61
|
+
return fail(`"data" of ${what} is an object from a name to a kind: data: { userId: 'string', page: 'number' }.`);
|
|
62
|
+
}
|
|
63
|
+
const kinds = DATA_KINDS.map(one => `'${one}'`).join(', ');
|
|
64
|
+
for (const [name, field] of Object.entries(value)) {
|
|
65
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name)) {
|
|
66
|
+
fail(`"${name}" can't be the name of a data of ${what}: it is how your code reads it (data.${name}), so use letters, digits and _ only.`);
|
|
67
|
+
}
|
|
68
|
+
// A kind, or a kind with a default value.
|
|
69
|
+
if (isRecord(field)) {
|
|
70
|
+
for (const key of Object.keys(field)) {
|
|
71
|
+
if (key !== 'type' && key !== 'default') {
|
|
72
|
+
fail(`"${key}" is not something the data "${name}" of ${what} has. It can have: type, default.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!DATA_KINDS.includes(field.type)) {
|
|
76
|
+
fail(`The data "${name}" of ${what} has the type ${JSON.stringify(field.type) ?? typeof field.type}, which does not exist. Kinds are: ${kinds}.`);
|
|
77
|
+
}
|
|
78
|
+
if (field.default !== undefined &&
|
|
79
|
+
(typeof field.default !== field.type ||
|
|
80
|
+
(field.type === 'number' && !Number.isFinite(field.default)))) {
|
|
81
|
+
fail(`The default value of the data "${name}" of ${what} must be a ${field.type}, got ${JSON.stringify(field.default) ?? typeof field.default}.`);
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!DATA_KINDS.includes(field)) {
|
|
86
|
+
fail(`The data "${name}" of ${what} has the kind ${JSON.stringify(field) ?? typeof field}, which does not exist. Kinds are: ${kinds}, or { type: 'number', default: 1 } for a value with a default.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return Object.freeze({ ...value });
|
|
90
|
+
}
|
|
91
|
+
function checkRun(what, value, example) {
|
|
92
|
+
if (typeof value !== 'function') {
|
|
93
|
+
fail(`${what} has no "run": the function to run when someone uses it, like ${example}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function readButton(name, config) {
|
|
97
|
+
if (!isRecord(config)) {
|
|
98
|
+
return fail(`button() needs an object: button({ label: '...', run({ interaction }) { ... } })`);
|
|
99
|
+
}
|
|
100
|
+
const what = 'this button';
|
|
101
|
+
checkKeys('a button', config, [
|
|
102
|
+
'label',
|
|
103
|
+
'emoji',
|
|
104
|
+
'style',
|
|
105
|
+
'disabled',
|
|
106
|
+
'data',
|
|
107
|
+
'where',
|
|
108
|
+
'who',
|
|
109
|
+
'ephemeral',
|
|
110
|
+
'run',
|
|
111
|
+
]);
|
|
112
|
+
const look = readLook(what, {
|
|
113
|
+
...(config.label !== undefined ? { label: config.label } : {}),
|
|
114
|
+
...(config.emoji !== undefined ? { emoji: config.emoji } : {}),
|
|
115
|
+
...(config.style !== undefined ? { style: config.style } : {}),
|
|
116
|
+
...(config.disabled !== undefined ? { disabled: config.disabled } : {}),
|
|
117
|
+
});
|
|
118
|
+
if (look.label === undefined && look.emoji === undefined) {
|
|
119
|
+
fail('A button needs a label or an emoji.');
|
|
120
|
+
}
|
|
121
|
+
checkRun(what, config.run, `async run({ interaction }) { await interaction.update('Done!'); }`);
|
|
122
|
+
return Object.freeze({
|
|
123
|
+
kind: 'button',
|
|
124
|
+
name,
|
|
125
|
+
look: {
|
|
126
|
+
style: look.style ?? 'primary',
|
|
127
|
+
disabled: look.disabled ?? false,
|
|
128
|
+
...(look.label !== undefined ? { label: look.label } : {}),
|
|
129
|
+
...(look.emoji !== undefined ? { emoji: look.emoji } : {}),
|
|
130
|
+
},
|
|
131
|
+
data: checkDataShape(what, config.data),
|
|
132
|
+
where: checkWhere(what, config.where),
|
|
133
|
+
who: checkWho(what, config.who),
|
|
134
|
+
ephemeral: checkBoolean('"ephemeral"', config.ephemeral),
|
|
135
|
+
run: config.run,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function readSelect(name, config) {
|
|
139
|
+
if (!isRecord(config)) {
|
|
140
|
+
return fail(`select() needs an object: select({ options: ['a', 'b'], run({ interaction, value }) { ... } })`);
|
|
141
|
+
}
|
|
142
|
+
const what = 'this select menu';
|
|
143
|
+
const type = (config.type ?? 'string');
|
|
144
|
+
if (!Object.hasOwn(SELECT_TYPES, type)) {
|
|
145
|
+
fail(`The type of ${what} is ${JSON.stringify(config.type)}, which does not exist. Types are: ${Object.keys(SELECT_TYPES)
|
|
146
|
+
.map(one => `'${one}'`)
|
|
147
|
+
.join(', ')}.`);
|
|
148
|
+
}
|
|
149
|
+
checkKeys('a select menu', config, [
|
|
150
|
+
'type',
|
|
151
|
+
'options',
|
|
152
|
+
'placeholder',
|
|
153
|
+
'min',
|
|
154
|
+
'max',
|
|
155
|
+
'disabled',
|
|
156
|
+
'channelTypes',
|
|
157
|
+
'data',
|
|
158
|
+
'where',
|
|
159
|
+
'who',
|
|
160
|
+
'ephemeral',
|
|
161
|
+
'run',
|
|
162
|
+
]);
|
|
163
|
+
if (type !== 'string' && config.options !== undefined) {
|
|
164
|
+
fail(`${what} lists ${type}s: Discord fills it, so it takes no "options".`);
|
|
165
|
+
}
|
|
166
|
+
if (type !== 'channel' && config.channelTypes !== undefined) {
|
|
167
|
+
fail(`"channelTypes" is only for a menu of channels (type: 'channel').`);
|
|
168
|
+
}
|
|
169
|
+
const options = type === 'string' ? readOptions(what, config.options) : [];
|
|
170
|
+
const placeholder = readPlaceholder(what, config.placeholder);
|
|
171
|
+
checkRun(what, config.run, `async run({ interaction, values }) { ... }`);
|
|
172
|
+
return Object.freeze({
|
|
173
|
+
kind: 'select',
|
|
174
|
+
name,
|
|
175
|
+
type: type,
|
|
176
|
+
options: Object.freeze(options),
|
|
177
|
+
channelTypes: readChannelTypes(what, config.channelTypes),
|
|
178
|
+
look: {
|
|
179
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
180
|
+
...(checkBoolean('"disabled"', config.disabled)
|
|
181
|
+
? { disabled: true }
|
|
182
|
+
: {}),
|
|
183
|
+
},
|
|
184
|
+
...readRange(what, config, Limits.SelectValues, { min: 1, max: 1 }),
|
|
185
|
+
data: checkDataShape(what, config.data),
|
|
186
|
+
where: checkWhere(what, config.where),
|
|
187
|
+
who: checkWho(what, config.who),
|
|
188
|
+
ephemeral: checkBoolean('"ephemeral"', config.ephemeral),
|
|
189
|
+
run: config.run,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function readModal(name, config) {
|
|
193
|
+
if (!isRecord(config)) {
|
|
194
|
+
return fail(`modal() needs an object: modal({ title: '...', fields: { ... }, run({ interaction, fields }) { ... } })`);
|
|
195
|
+
}
|
|
196
|
+
const what = 'this form';
|
|
197
|
+
checkKeys('a form', config, [
|
|
198
|
+
'title',
|
|
199
|
+
'fields',
|
|
200
|
+
'data',
|
|
201
|
+
'where',
|
|
202
|
+
'ephemeral',
|
|
203
|
+
'run',
|
|
204
|
+
]);
|
|
205
|
+
if (!isDynamic(config.title) &&
|
|
206
|
+
(typeof config.title !== 'string' ||
|
|
207
|
+
config.title.trim() === '' ||
|
|
208
|
+
config.title.length > Limits.ModalTitle)) {
|
|
209
|
+
fail(`The title of ${what} is a text of 1 to ${Limits.ModalTitle} characters.`);
|
|
210
|
+
}
|
|
211
|
+
if (!isRecord(config.fields)) {
|
|
212
|
+
fail(`"fields" of ${what} is an object whose keys are the names of the fields: fields: { reason: { type: 'text', label: 'Reason' } }`);
|
|
213
|
+
}
|
|
214
|
+
const fields = Object.entries(config.fields).map(([name, field]) => readField(name, field));
|
|
215
|
+
const inputs = fields.filter(field => field.field.type !== 'note');
|
|
216
|
+
if (fields.length === 0 || fields.length > Limits.ModalFields) {
|
|
217
|
+
fail(`${what} has ${fields.length} fields: Discord accepts between 1 and ${Limits.ModalFields}.`);
|
|
218
|
+
}
|
|
219
|
+
if (inputs.length === 0) {
|
|
220
|
+
fail(`${what} has nothing to fill in: add a field like { type: 'text', label: '...' }.`);
|
|
221
|
+
}
|
|
222
|
+
checkRun(what, config.run, `async run({ interaction, fields }) { ... }`);
|
|
223
|
+
return Object.freeze({
|
|
224
|
+
kind: 'modal',
|
|
225
|
+
name,
|
|
226
|
+
title: config.title,
|
|
227
|
+
fields: Object.freeze(fields),
|
|
228
|
+
data: checkDataShape(what, config.data),
|
|
229
|
+
where: checkWhere(what, config.where),
|
|
230
|
+
ephemeral: checkBoolean('"ephemeral"', config.ephemeral),
|
|
231
|
+
run: config.run,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
/** One component, by the kind it was declared with. */
|
|
235
|
+
function readOne(state, declared, { name, file, export: exported }) {
|
|
236
|
+
const what = `the ${WHAT[state.kind]} ${name}`;
|
|
237
|
+
if (!NAME.test(name)) {
|
|
238
|
+
fail(`"${name}" can't be the name of a ${WHAT[state.kind]}: the name of the export is its id, and an id has no space and no ":". Rename the export.`);
|
|
239
|
+
}
|
|
240
|
+
if (name.length > Limits.CustomId - 20) {
|
|
241
|
+
fail(`The name of ${what} is ${name.length} characters long: Discord gives ${Limits.CustomId} characters to the id of a component and its data together. Rename the export${exported === 'default' ? ` or the file ${file}` : ''} with something shorter.`);
|
|
242
|
+
}
|
|
243
|
+
switch (state.kind) {
|
|
244
|
+
case 'button': {
|
|
245
|
+
const loaded = readButton(name, state.config);
|
|
246
|
+
bindComponent(declared, {
|
|
247
|
+
name,
|
|
248
|
+
render: args => renderButton(loaded, args),
|
|
249
|
+
});
|
|
250
|
+
return loaded;
|
|
251
|
+
}
|
|
252
|
+
case 'select': {
|
|
253
|
+
const loaded = readSelect(name, state.config);
|
|
254
|
+
bindComponent(declared, {
|
|
255
|
+
name,
|
|
256
|
+
render: args => renderSelect(loaded, args),
|
|
257
|
+
});
|
|
258
|
+
return loaded;
|
|
259
|
+
}
|
|
260
|
+
case 'modal': {
|
|
261
|
+
const loaded = readModal(name, state.config);
|
|
262
|
+
bindComponent(declared, {
|
|
263
|
+
name,
|
|
264
|
+
render: args => renderModal(loaded, args),
|
|
265
|
+
});
|
|
266
|
+
return loaded;
|
|
267
|
+
}
|
|
268
|
+
default: {
|
|
269
|
+
const { config } = state;
|
|
270
|
+
if (typeof config !== 'function') {
|
|
271
|
+
if (!isRecord(config)) {
|
|
272
|
+
fail(`embed() takes the embed itself ({ title: '...' }) or a function that returns one.`);
|
|
273
|
+
}
|
|
274
|
+
checkEmbed(config, 'this embed');
|
|
275
|
+
}
|
|
276
|
+
bindComponent(declared, {
|
|
277
|
+
name,
|
|
278
|
+
render: args => {
|
|
279
|
+
if (typeof config === 'function')
|
|
280
|
+
return config(...args);
|
|
281
|
+
if (args.length > 0)
|
|
282
|
+
fail(`${name} always looks the same: it takes no arguments.`);
|
|
283
|
+
return config;
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
return Object.freeze({ kind: 'embed', name });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/** What each kind is called in messages. */
|
|
291
|
+
export const WHAT = {
|
|
292
|
+
button: 'button',
|
|
293
|
+
select: 'select menu',
|
|
294
|
+
modal: 'form',
|
|
295
|
+
embed: 'embed',
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* A component (a button, a select menu, a form or an embed), declared
|
|
299
|
+
* anywhere in `src/` with `button()`, `select()`, `modal()` or `embed()`,
|
|
300
|
+
* and exported: the name of the export is its id, so a file may declare
|
|
301
|
+
* several, each under its own export.
|
|
302
|
+
*/
|
|
303
|
+
export const componentDeclaration = {
|
|
304
|
+
one: 'component',
|
|
305
|
+
many: 'components',
|
|
306
|
+
is: value => componentStateOf(value) !== undefined,
|
|
307
|
+
list: false,
|
|
308
|
+
read(value, site) {
|
|
309
|
+
return readOne(componentStateOf(value), value, site);
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
/** Two components of the same kind with the same name: the later one is left out. */
|
|
313
|
+
export function findDuplicates(entries) {
|
|
314
|
+
const seen = new Map();
|
|
315
|
+
const conflicts = [];
|
|
316
|
+
const valid = [...entries]
|
|
317
|
+
.sort((a, b) => a.file === b.file
|
|
318
|
+
? a.export < b.export
|
|
319
|
+
? -1
|
|
320
|
+
: 1
|
|
321
|
+
: a.file < b.file
|
|
322
|
+
? -1
|
|
323
|
+
: 1)
|
|
324
|
+
.filter(entry => {
|
|
325
|
+
const key = `${entry.component.kind}:${entry.component.name}`;
|
|
326
|
+
const first = seen.get(key);
|
|
327
|
+
if (!first) {
|
|
328
|
+
seen.set(key, entry);
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
conflicts.push({
|
|
332
|
+
file: entry.file,
|
|
333
|
+
export: entry.export,
|
|
334
|
+
message: `There is already a ${WHAT[entry.component.kind]} named ${entry.component.name}, in ${first.file}: the name of the export is what tells a ${WHAT[entry.component.kind]} apart, so rename one of them.`,
|
|
335
|
+
});
|
|
336
|
+
return false;
|
|
337
|
+
});
|
|
338
|
+
return { valid, conflicts };
|
|
339
|
+
}
|
|
340
|
+
/** Whether a declaration is an embed: nothing to route. */
|
|
341
|
+
export const isEmbedLoaded = (component) => component.kind === 'embed';
|
|
342
|
+
export { isEmbedDeclaration };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type PieceKind, type Rendered } from './instance.js';
|
|
2
|
+
/** The kinds of components a file declares. */
|
|
3
|
+
export type ComponentKind = 'button' | 'select' | 'modal' | 'embed';
|
|
4
|
+
/** A declared component as the loader knows it: what it declared, and once loaded, how to make instances. */
|
|
5
|
+
export interface ComponentState {
|
|
6
|
+
readonly kind: ComponentKind;
|
|
7
|
+
readonly config: unknown;
|
|
8
|
+
/** What made it, for messages: `button()`. */
|
|
9
|
+
readonly made: string;
|
|
10
|
+
bound?: {
|
|
11
|
+
/** The name of the export that declares it: its id. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Makes an instance from what the component was called with. */
|
|
14
|
+
render: (args: readonly unknown[]) => Rendered | object;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** The state of a declared component, if the value is one. */
|
|
18
|
+
export declare function componentStateOf(value: unknown): ComponentState | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Makes a declared component. `asPiece` says whether it stands for its
|
|
21
|
+
* instance by itself when it carries no data (a button, a menu, a modal:
|
|
22
|
+
* not an embed, which is only a function).
|
|
23
|
+
*/
|
|
24
|
+
export declare function createComponent(kind: ComponentKind, config: unknown, { asPiece, pieceKind }: {
|
|
25
|
+
asPiece: boolean;
|
|
26
|
+
pieceKind?: PieceKind;
|
|
27
|
+
}): (...args: unknown[]) => unknown;
|
|
28
|
+
/** Binds a declared component to its name and to what makes its instances. */
|
|
29
|
+
export declare function bindComponent(declared: unknown, bound: NonNullable<ComponentState['bound']>): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// What `button()`, `select()`, `modal()` and `embed()` return: a component,
|
|
2
|
+
// declared in a file of the project. It is a function (called with the
|
|
3
|
+
// data the component carries) that the loader recognises, checks, and binds
|
|
4
|
+
// to the name of its export: that name is the custom_id. Until then it can't
|
|
5
|
+
// make anything, which is how a component the framework never loaded is
|
|
6
|
+
// refused.
|
|
7
|
+
import { piece, pieceFunction, } from './instance.js';
|
|
8
|
+
const STATE = Symbol.for('chapterjs.component.declared');
|
|
9
|
+
/** The state of a declared component, if the value is one. */
|
|
10
|
+
export function componentStateOf(value) {
|
|
11
|
+
if (typeof value !== 'function')
|
|
12
|
+
return undefined;
|
|
13
|
+
return value[STATE];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Makes a declared component. `asPiece` says whether it stands for its
|
|
17
|
+
* instance by itself when it carries no data (a button, a menu, a modal:
|
|
18
|
+
* not an embed, which is only a function).
|
|
19
|
+
*/
|
|
20
|
+
export function createComponent(kind, config, { asPiece, pieceKind }) {
|
|
21
|
+
const state = { kind, config, made: `${kind}()` };
|
|
22
|
+
const notBound = () => {
|
|
23
|
+
throw new Error(`This ${kind} was not loaded by ChapterJS: a ${kind} is exported from a file of src/ (export const name = ${kind}({ ... })), and used once the bot started.`);
|
|
24
|
+
};
|
|
25
|
+
const declared = (...args) => {
|
|
26
|
+
if (!state.bound)
|
|
27
|
+
return notBound();
|
|
28
|
+
const rendered = state.bound.render(args);
|
|
29
|
+
return pieceKind
|
|
30
|
+
? piece(pieceKind, rendered.raw)
|
|
31
|
+
: rendered;
|
|
32
|
+
};
|
|
33
|
+
Object.defineProperty(declared, STATE, { value: state });
|
|
34
|
+
Object.defineProperty(declared, 'config', {
|
|
35
|
+
value: config,
|
|
36
|
+
enumerable: true,
|
|
37
|
+
});
|
|
38
|
+
if (asPiece) {
|
|
39
|
+
pieceFunction(declared, () => state.bound ? state.bound.render([]) : notBound());
|
|
40
|
+
}
|
|
41
|
+
return declared;
|
|
42
|
+
}
|
|
43
|
+
/** Binds a declared component to its name and to what makes its instances. */
|
|
44
|
+
export function bindComponent(declared, bound) {
|
|
45
|
+
const state = componentStateOf(declared);
|
|
46
|
+
if (!state)
|
|
47
|
+
throw new TypeError('Not a component.');
|
|
48
|
+
state.bound = bound;
|
|
49
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Embed } from '../structures/payload.js';
|
|
2
|
+
/**
|
|
3
|
+
* What `embed()` returns: a declaration the framework finds in the exports
|
|
4
|
+
* of a file. Import it where you send a message: `embeds: [welcome({
|
|
5
|
+
* member })]`, or `embeds: [rules]` for an embed that always looks the
|
|
6
|
+
* same.
|
|
7
|
+
*/
|
|
8
|
+
export type EmbedDeclaration<Args extends readonly unknown[] = []> = {
|
|
9
|
+
/** What the file gave to `embed()`, not checked yet. */
|
|
10
|
+
readonly config: unknown;
|
|
11
|
+
} & ((...args: Args) => Embed);
|
|
12
|
+
/**
|
|
13
|
+
* Declares an embed. Export the result from any file of `src/`. Give it
|
|
14
|
+
* the embed itself when it always looks the same, or a function of what it
|
|
15
|
+
* shows:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { embed, type GuildMember } from 'chapterjs';
|
|
19
|
+
*
|
|
20
|
+
* export const welcome = embed((member: GuildMember) => ({
|
|
21
|
+
* title: `Welcome ${member.displayName}!`,
|
|
22
|
+
* color: 0x57f287,
|
|
23
|
+
* }));
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function embed(definition: Embed): EmbedDeclaration<[]>;
|
|
27
|
+
export declare function embed<const Args extends readonly unknown[]>(definition: (...args: Args) => Embed): EmbedDeclaration<Args>;
|
|
28
|
+
/** Whether a value is a declared embed: a function to call for the embed. */
|
|
29
|
+
export declare const isEmbedDeclaration: (value: unknown) => value is EmbedDeclaration<readonly unknown[]>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// `embed()`, as user files import it from 'chapterjs': an embed written
|
|
2
|
+
// once, used in every message that needs it.
|
|
3
|
+
// Static, or a function of what it shows.
|
|
4
|
+
import { createComponent, componentStateOf } from './declared.js';
|
|
5
|
+
export function embed(definition) {
|
|
6
|
+
return createComponent('embed', definition, {
|
|
7
|
+
asPiece: false,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
/** Whether a value is a declared embed: a function to call for the embed. */
|
|
11
|
+
export const isEmbedDeclaration = (value) => componentStateOf(value)?.kind === 'embed';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { RawActionRow, RawButton, RawContainer, RawFileComponent, RawMediaGallery, RawSection, RawSelectMenu, RawSeparator, RawTextDisplay, RawThumbnail } from '../discord/types/component.js';
|
|
2
|
+
import type { RawEmoji } from '../discord/types/emoji.js';
|
|
3
|
+
import type { RawInteractionCallbackModalData } from '../discord/types/interaction.js';
|
|
4
|
+
import type { EmojiInput } from '../structures/message.js';
|
|
5
|
+
import type { Translator } from '../messages/messages.js';
|
|
6
|
+
declare const brand: unique symbol;
|
|
7
|
+
/** A piece of a message made by ChapterJS: a button, a menu, a layout. */
|
|
8
|
+
export interface Piece<Kind extends string> {
|
|
9
|
+
/** What kind of piece it is. Only the framework makes pieces. */
|
|
10
|
+
readonly [brand]: Kind;
|
|
11
|
+
}
|
|
12
|
+
/** A button: declared with `button()`, or made with `linkButton()` or `premiumButton()`. */
|
|
13
|
+
export type ButtonComponent = Piece<'button'>;
|
|
14
|
+
/** A select menu declared with `select()`. */
|
|
15
|
+
export type SelectComponent = Piece<'select'>;
|
|
16
|
+
/** A row of up to 5 buttons, or of one menu: `row()`. */
|
|
17
|
+
export type ActionRowComponent = Piece<'row'>;
|
|
18
|
+
/** A text: `text()`. */
|
|
19
|
+
export type TextComponent = Piece<'text'>;
|
|
20
|
+
/** Texts with a button or a thumbnail beside them: `section()`. */
|
|
21
|
+
export type SectionComponent = Piece<'section'>;
|
|
22
|
+
/** A small image beside a section: `thumbnail()`. */
|
|
23
|
+
export type ThumbnailComponent = Piece<'thumbnail'>;
|
|
24
|
+
/** Up to 10 images or videos: `gallery()`. */
|
|
25
|
+
export type GalleryComponent = Piece<'gallery'>;
|
|
26
|
+
/** An attached file shown in the message: `file()`. */
|
|
27
|
+
export type FileComponent = Piece<'file'>;
|
|
28
|
+
/** Space, with or without a line: `separator()`. */
|
|
29
|
+
export type SeparatorComponent = Piece<'separator'>;
|
|
30
|
+
/** A box around other pieces, with an optional color: `container()`. */
|
|
31
|
+
export type ContainerComponent = Piece<'container'>;
|
|
32
|
+
/** A form to open with `interaction.showModal()`: declared with `modal()`. */
|
|
33
|
+
export type ModalComponent = Piece<'modal'>;
|
|
34
|
+
/** What a container can hold. */
|
|
35
|
+
export type ContainerChild = ButtonComponent | SelectComponent | ActionRowComponent | TextComponent | SectionComponent | GalleryComponent | FileComponent | SeparatorComponent;
|
|
36
|
+
/**
|
|
37
|
+
* What the `components` of a message take. Buttons and menus are put in
|
|
38
|
+
* rows by the framework (5 buttons per row, a menu alone in its row);
|
|
39
|
+
* `row()` is there to choose. As soon as a message has a text, a section,
|
|
40
|
+
* a gallery, a file, a separator or a container, it is built with
|
|
41
|
+
* components only: Discord then takes no `content` and no `embeds`.
|
|
42
|
+
* @see https://docs.discord.com/developers/components/reference
|
|
43
|
+
*/
|
|
44
|
+
export type MessageComponent = ContainerChild | ContainerComponent;
|
|
45
|
+
export type PieceKind = 'button' | 'select' | 'row' | 'text' | 'section' | 'thumbnail' | 'gallery' | 'file' | 'separator' | 'container' | 'modal';
|
|
46
|
+
interface RawOf {
|
|
47
|
+
button: RawButton;
|
|
48
|
+
select: RawSelectMenu;
|
|
49
|
+
row: RawActionRow;
|
|
50
|
+
text: RawTextDisplay;
|
|
51
|
+
section: RawSection;
|
|
52
|
+
thumbnail: RawThumbnail;
|
|
53
|
+
gallery: RawMediaGallery;
|
|
54
|
+
file: RawFileComponent;
|
|
55
|
+
separator: RawSeparator;
|
|
56
|
+
container: RawContainer;
|
|
57
|
+
modal: RawInteractionCallbackModalData;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* What a piece sends: known when the piece is made, or computed when the
|
|
61
|
+
* message is sent, with `t` in the language of who will read it (a piece
|
|
62
|
+
* whose texts are functions).
|
|
63
|
+
*/
|
|
64
|
+
export type Deferred<R> = R | ((t: Translator) => R);
|
|
65
|
+
/** A piece as the framework sees it. */
|
|
66
|
+
export interface Rendered<Kind extends PieceKind = PieceKind> {
|
|
67
|
+
kind: Kind;
|
|
68
|
+
raw: Deferred<RawOf[Kind]>;
|
|
69
|
+
}
|
|
70
|
+
/** A piece with what it sends, known: after `resolve()`. */
|
|
71
|
+
export interface Resolved<Kind extends PieceKind = PieceKind> {
|
|
72
|
+
kind: Kind;
|
|
73
|
+
raw: RawOf[Kind];
|
|
74
|
+
}
|
|
75
|
+
/** What a piece sends, for the language of who will read it. */
|
|
76
|
+
export declare function resolve<Kind extends PieceKind>(rendered: Rendered<Kind>, t: Translator): Resolved<Kind>;
|
|
77
|
+
/**
|
|
78
|
+
* What a piece made of others sends: known now when every child is, else
|
|
79
|
+
* computed when the message is sent, from what the children send then.
|
|
80
|
+
*/
|
|
81
|
+
export declare function compose<R>(children: readonly Rendered[], build: (raws: readonly unknown[]) => R): Deferred<R>;
|
|
82
|
+
/** Makes a piece. `raw` is frozen: a piece never changes once made. */
|
|
83
|
+
export declare function piece<Kind extends PieceKind>(kind: Kind, raw: Deferred<RawOf[Kind]>): Piece<Kind>;
|
|
84
|
+
/**
|
|
85
|
+
* Lets a function stand for a piece: the file of a button without data is
|
|
86
|
+
* a function (called with a look) and also the button itself. `render`
|
|
87
|
+
* is asked for the piece when the message is built.
|
|
88
|
+
*/
|
|
89
|
+
export declare function pieceFunction<F extends object>(target: F, render: () => Rendered): F;
|
|
90
|
+
/** Whether a value is a piece made by the framework. */
|
|
91
|
+
export declare function isPiece(value: unknown): value is Piece<PieceKind>;
|
|
92
|
+
/** What a piece is and how it is sent. Throws on anything else. */
|
|
93
|
+
export declare function renderedOf(value: unknown, where: string): Rendered;
|
|
94
|
+
/**
|
|
95
|
+
* An emoji as a component takes it: a standard one (`'👍'`), a custom one as
|
|
96
|
+
* Discord writes it (`'<:name:id>'`, `'<a:name:id>'`) or one of
|
|
97
|
+
* `guild.emojis`.
|
|
98
|
+
*/
|
|
99
|
+
export declare function emojiOf(input: EmojiInput, what: string): Partial<RawEmoji>;
|
|
100
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// What goes in the `components` of a message: pieces made by the framework
|
|
2
|
+
// (a button declared with `button()`, a `row()`, a `container()`...),
|
|
3
|
+
// never raw JSON. Each piece knows the Discord shape it is sent as; the
|
|
4
|
+
// brand is what lets a message refuse anything else, so that no custom_id
|
|
5
|
+
// is ever written by hand.
|
|
6
|
+
/** What a piece sends, for the language of who will read it. */
|
|
7
|
+
export function resolve(rendered, t) {
|
|
8
|
+
const { kind, raw } = rendered;
|
|
9
|
+
return { kind, raw: typeof raw === 'function' ? raw(t) : raw };
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* What a piece made of others sends: known now when every child is, else
|
|
13
|
+
* computed when the message is sent, from what the children send then.
|
|
14
|
+
*/
|
|
15
|
+
export function compose(children, build) {
|
|
16
|
+
if (children.every(child => typeof child.raw !== 'function')) {
|
|
17
|
+
return build(children.map(child => child.raw));
|
|
18
|
+
}
|
|
19
|
+
return (t) => build(children.map(child => resolve(child, t).raw));
|
|
20
|
+
}
|
|
21
|
+
const INSTANCE = Symbol.for('chapterjs.component');
|
|
22
|
+
/** Makes a piece. `raw` is frozen: a piece never changes once made. */
|
|
23
|
+
export function piece(kind, raw) {
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
[INSTANCE]: true,
|
|
26
|
+
kind,
|
|
27
|
+
raw,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Lets a function stand for a piece: the file of a button without data is
|
|
32
|
+
* a function (called with a look) and also the button itself. `render`
|
|
33
|
+
* is asked for the piece when the message is built.
|
|
34
|
+
*/
|
|
35
|
+
export function pieceFunction(target, render) {
|
|
36
|
+
Object.defineProperty(target, INSTANCE, { value: true });
|
|
37
|
+
Object.defineProperty(target, 'kind', { get: () => render().kind });
|
|
38
|
+
Object.defineProperty(target, 'raw', { get: () => render().raw });
|
|
39
|
+
return target;
|
|
40
|
+
}
|
|
41
|
+
/** Whether a value is a piece made by the framework. */
|
|
42
|
+
export function isPiece(value) {
|
|
43
|
+
return ((typeof value === 'object' || typeof value === 'function') &&
|
|
44
|
+
value !== null &&
|
|
45
|
+
value[INSTANCE] === true);
|
|
46
|
+
}
|
|
47
|
+
/** What a piece is and how it is sent. Throws on anything else. */
|
|
48
|
+
export function renderedOf(value, where) {
|
|
49
|
+
if (!isPiece(value)) {
|
|
50
|
+
throw new TypeError(`${where} is not a component made by ChapterJS, got ${value === null ? 'null' : typeof value === 'object' && 'custom_id' in value ? 'an object with a custom_id' : typeof value}. Use the buttons, menus and forms declared with button(), select() and modal(), and row(), text(), section(), container()... from 'chapterjs'. Components are never written as JSON.`);
|
|
51
|
+
}
|
|
52
|
+
const { kind, raw } = value;
|
|
53
|
+
return { kind, raw };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* An emoji as a component takes it: a standard one (`'👍'`), a custom one as
|
|
57
|
+
* Discord writes it (`'<:name:id>'`, `'<a:name:id>'`) or one of
|
|
58
|
+
* `guild.emojis`.
|
|
59
|
+
*/
|
|
60
|
+
export function emojiOf(input, what) {
|
|
61
|
+
if (typeof input !== 'string') {
|
|
62
|
+
const id = input.id ?? null;
|
|
63
|
+
const name = input.name ?? null;
|
|
64
|
+
if (!id && !name) {
|
|
65
|
+
throw new TypeError(`The emoji of ${what} has no name and no id.`);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...(id ? { id } : {}),
|
|
69
|
+
...(name ? { name } : {}),
|
|
70
|
+
...(input.animated ? { animated: true } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const text = input.trim();
|
|
74
|
+
const custom = /^<(a?):(\w+):(\d+)>$/.exec(text);
|
|
75
|
+
if (custom) {
|
|
76
|
+
return {
|
|
77
|
+
id: custom[3],
|
|
78
|
+
name: custom[2],
|
|
79
|
+
...(custom[1] ? { animated: true } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (text === '') {
|
|
83
|
+
throw new TypeError(`The emoji of ${what} is empty: pass one like "👍", or a custom one like "<:name:id>".`);
|
|
84
|
+
}
|
|
85
|
+
return { name: text };
|
|
86
|
+
}
|