chapterjs 0.1.1 → 0.1.4
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 +206 -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 +252 -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 +122 -0
- package/dist/cli/project.js +452 -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 +346 -0
- package/dist/commands/command.js +28 -0
- package/dist/commands/convention.d.ts +64 -0
- package/dist/commands/convention.js +464 -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 +43 -0
- package/dist/commands/tree.js +206 -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 +108 -0
- package/dist/components/component.d.ts +140 -0
- package/dist/components/component.js +39 -0
- package/dist/components/convention.d.ts +35 -0
- package/dist/components/convention.js +339 -0
- package/dist/components/custom-id.d.ts +72 -0
- package/dist/components/custom-id.js +128 -0
- package/dist/components/embed.d.ts +28 -0
- package/dist/components/embed.js +11 -0
- package/dist/components/file.d.ts +30 -0
- package/dist/components/file.js +52 -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 +244 -0
- package/dist/components/modal.js +399 -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 +18 -0
- package/dist/components/router.js +256 -0
- package/dist/components/select.d.ts +144 -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/convention.d.ts +25 -0
- package/dist/events/convention.js +97 -0
- package/dist/events/event.d.ts +8 -0
- package/dist/events/event.js +20 -0
- package/dist/events/registry.d.ts +706 -0
- package/dist/events/registry.js +839 -0
- package/dist/events/router.d.ts +45 -0
- package/dist/events/router.js +133 -0
- package/dist/events/types.d.ts +7 -0
- package/dist/events/types.js +48 -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 +55 -18
- package/dist/index.js +38 -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 +37 -0
- package/dist/loader/generated.js +114 -0
- package/dist/loader/hot.d.ts +13 -0
- package/dist/loader/hot.js +85 -0
- package/dist/loader/loader.d.ts +72 -0
- package/dist/loader/loader.js +135 -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 +3 -0
- package/dist/main.js +6 -0
- package/dist/messages/commands.d.ts +23 -0
- package/dist/messages/commands.js +123 -0
- package/dist/messages/convention.d.ts +89 -0
- package/dist/messages/convention.js +347 -0
- package/dist/messages/messages.d.ts +219 -0
- package/dist/messages/messages.js +30 -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/convention.d.ts +26 -0
- package/dist/presence/convention.js +133 -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/convention.d.ts +26 -0
- package/dist/tasks/convention.js +78 -0
- package/dist/tasks/schedule.d.ts +54 -0
- package/dist/tasks/schedule.js +236 -0
- package/dist/tasks/scheduler.d.ts +33 -0
- package/dist/tasks/scheduler.js +107 -0
- package/dist/tasks/task.d.ts +76 -0
- package/dist/tasks/task.js +28 -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,225 @@
|
|
|
1
|
+
// `chapterjs start`: runs for everyone the bot `chapterjs build` prepared.
|
|
2
|
+
// Nothing is reloaded, commands are registered for every server, and a
|
|
3
|
+
// large bot is spread over several processes by itself.
|
|
4
|
+
import { registerCommands } from '../commands/register.js';
|
|
5
|
+
import { buildCommands } from '../commands/tree.js';
|
|
6
|
+
import { GLOBAL_REQUESTS_PER_SECOND } from '../discord/api.js';
|
|
7
|
+
import { messageOf } from '../loader/locate.js';
|
|
8
|
+
import { GetGatewayBot } from '../discord/endpoints.js';
|
|
9
|
+
import { SessionLimitError } from '../gateway/errors.js';
|
|
10
|
+
import { RestClient } from '../rest/rest.js';
|
|
11
|
+
import { connectToPrimary, planProcesses, REFUSED, runCluster, } from './cluster.js';
|
|
12
|
+
import { loadBundle, readBuild } from './build.js';
|
|
13
|
+
import { readStartEnv } from './env.js';
|
|
14
|
+
import { describeIntents, ensurePrivilegedIntents, fetchApplication, } from './preflight.js';
|
|
15
|
+
import { createProject, explain } from './project.js';
|
|
16
|
+
/** Runs until stopped. Resolves with the exit code of the process. */
|
|
17
|
+
export async function start(options) {
|
|
18
|
+
const { cwd, log, signal } = options;
|
|
19
|
+
const assignment = options.assignment ?? null;
|
|
20
|
+
// 1. What the bot must have before anything is tried.
|
|
21
|
+
const read = await readStartEnv(cwd, options.env);
|
|
22
|
+
if ('problems' in read) {
|
|
23
|
+
for (const problem of read.problems)
|
|
24
|
+
log.error(problem);
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
const { token, devGuildId } = read.env;
|
|
28
|
+
// 2. What `chapterjs build` prepared: production runs that, and only
|
|
29
|
+
// that.
|
|
30
|
+
const built = await readBuild(cwd, options.version);
|
|
31
|
+
if ('problem' in built) {
|
|
32
|
+
if (!assignment)
|
|
33
|
+
log.error(built.problem);
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
if (built.stale && !assignment) {
|
|
37
|
+
log.warn('Your files changed since the last build: the bot runs the build, not your changes. Run "chapterjs build" to put them online.');
|
|
38
|
+
}
|
|
39
|
+
let files;
|
|
40
|
+
try {
|
|
41
|
+
files = await loadBundle(cwd);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// The build ran when it was made: something changed around it since
|
|
45
|
+
// (a package, the version of Node).
|
|
46
|
+
if (!assignment) {
|
|
47
|
+
log.error(`The build of your bot can't run any more: ${messageOf(error)}\nRun "chapterjs build" again: it says what to fix.`);
|
|
48
|
+
}
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
const project = createProject({
|
|
52
|
+
cwd,
|
|
53
|
+
version: options.version,
|
|
54
|
+
log,
|
|
55
|
+
built: files,
|
|
56
|
+
joinsVoice: built.info.voice === true,
|
|
57
|
+
...(options.deferAfter === undefined
|
|
58
|
+
? {}
|
|
59
|
+
: { deferAfter: options.deferAfter }),
|
|
60
|
+
});
|
|
61
|
+
const failures = await project.load();
|
|
62
|
+
if (failures.length > 0) {
|
|
63
|
+
for (const failure of failures)
|
|
64
|
+
project.report(failure);
|
|
65
|
+
if (!assignment) {
|
|
66
|
+
log.error(`${failures.length === 1 ? 'This file' : `These ${failures.length} files`} of the build can't run any more, so the bot was not started. Run "chapterjs build" again: it says what to fix.`);
|
|
67
|
+
}
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
const apiUrl = options.env.CHAPTERJS_API_URL;
|
|
71
|
+
const interval = Number(options.env.CHAPTERJS_IDENTIFY_INTERVAL);
|
|
72
|
+
const identifyInterval = Number.isFinite(interval) && interval > 0 ? interval : undefined;
|
|
73
|
+
const connection = {
|
|
74
|
+
token,
|
|
75
|
+
apiUrl,
|
|
76
|
+
identifyInterval,
|
|
77
|
+
// The dev server belongs to `chapterjs dev`: with the same token, the
|
|
78
|
+
// two never answer the same thing.
|
|
79
|
+
...(devGuildId
|
|
80
|
+
? { guildFilter: (id) => id !== devGuildId }
|
|
81
|
+
: {}),
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
if (assignment)
|
|
85
|
+
return await runShare(project, options, connection, assignment);
|
|
86
|
+
// 3. What Discord must agree with, checked once for every process.
|
|
87
|
+
const rest = new RestClient({
|
|
88
|
+
token,
|
|
89
|
+
version: options.version,
|
|
90
|
+
...(apiUrl ? { baseUrl: apiUrl } : {}),
|
|
91
|
+
});
|
|
92
|
+
const application = await fetchApplication(rest);
|
|
93
|
+
const intents = project.intents();
|
|
94
|
+
await ensurePrivilegedIntents({ rest, log, interactive: false, pollInterval: 0, signal }, application, intents, project.filesNeeding);
|
|
95
|
+
if (await registerCommands({
|
|
96
|
+
rest,
|
|
97
|
+
projectDir: cwd,
|
|
98
|
+
applicationId: application.id,
|
|
99
|
+
commands: buildCommands([...project.commands.values()], {
|
|
100
|
+
guild: false,
|
|
101
|
+
}),
|
|
102
|
+
})) {
|
|
103
|
+
log.success('Commands updated for everyone');
|
|
104
|
+
}
|
|
105
|
+
// 4. How the bot is split: Discord says in how many shards, the size
|
|
106
|
+
// of the machine in how many processes.
|
|
107
|
+
const plan = await rest.request(GetGatewayBot, []);
|
|
108
|
+
const limit = plan.session_start_limit;
|
|
109
|
+
if (limit.remaining < plan.shards) {
|
|
110
|
+
throw new SessionLimitError(plan.shards, limit.remaining, limit.reset_after);
|
|
111
|
+
}
|
|
112
|
+
const processes = planProcesses(plan.shards, options.processes);
|
|
113
|
+
const online = (user, guilds) => {
|
|
114
|
+
const split = [
|
|
115
|
+
...(plan.shards > 1 ? [`${plan.shards} shards`] : []),
|
|
116
|
+
...(processes > 1 ? [`${processes} processes`] : []),
|
|
117
|
+
].join(', ');
|
|
118
|
+
log.success(`Online as ${user} in ${guilds} ${guilds === 1 ? 'server' : 'servers'}${split ? ` (${split})` : ''}`);
|
|
119
|
+
log.info(`Intents computed from your files: ${describeIntents(intents)}`);
|
|
120
|
+
(project.isEmpty() ? log.info : log.success)(project.summary());
|
|
121
|
+
};
|
|
122
|
+
if (processes === 1) {
|
|
123
|
+
return await runAlone(project, options, connection, online);
|
|
124
|
+
}
|
|
125
|
+
const code = await runCluster({
|
|
126
|
+
script: options.script,
|
|
127
|
+
args: options.args,
|
|
128
|
+
env: options.env,
|
|
129
|
+
shards: plan.shards,
|
|
130
|
+
processes,
|
|
131
|
+
maxConcurrency: limit.max_concurrency,
|
|
132
|
+
signal,
|
|
133
|
+
identifyInterval,
|
|
134
|
+
write: options.write,
|
|
135
|
+
onReady: ({ user, guilds }) => online(user, guilds),
|
|
136
|
+
onRestart: (index, why) => log.warn(`Process ${index + 1} of ${processes} stopped by itself (${why}): starting it again. Its servers are back in a moment.`),
|
|
137
|
+
});
|
|
138
|
+
if (code === 0)
|
|
139
|
+
log.success('Disconnected');
|
|
140
|
+
return code;
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (signal.aborted)
|
|
144
|
+
return 0;
|
|
145
|
+
explain(error, log);
|
|
146
|
+
return assignment ? REFUSED : 1;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** Runs a bot until it is stopped or Discord refuses it. */
|
|
150
|
+
async function run(project, connection, stopped, connected, { tasks = true } = {}) {
|
|
151
|
+
let refuse;
|
|
152
|
+
const refused = new Promise(resolve => (refuse = () => resolve('refused')));
|
|
153
|
+
let bot = null;
|
|
154
|
+
const memory = project.watchMemory(() => bot);
|
|
155
|
+
try {
|
|
156
|
+
bot = await project.connect({ ...connection, onFatal: refuse });
|
|
157
|
+
connected(bot);
|
|
158
|
+
project.ready(bot);
|
|
159
|
+
// A task runs once for the whole bot: in one process only.
|
|
160
|
+
if (tasks)
|
|
161
|
+
project.startTasks(() => bot);
|
|
162
|
+
return await Promise.race([
|
|
163
|
+
stopped.then(() => 'stopped'),
|
|
164
|
+
refused,
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
project.stopTasks();
|
|
169
|
+
memory.stop();
|
|
170
|
+
await bot?.close();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** The whole bot in this process. */
|
|
174
|
+
async function runAlone(project, options, connection, online) {
|
|
175
|
+
const { signal, log } = options;
|
|
176
|
+
const stopped = new Promise(resolve => {
|
|
177
|
+
if (signal.aborted)
|
|
178
|
+
resolve();
|
|
179
|
+
else
|
|
180
|
+
signal.addEventListener('abort', () => resolve(), { once: true });
|
|
181
|
+
});
|
|
182
|
+
const result = await run(project, connection, stopped, bot => {
|
|
183
|
+
const user = bot.ctx.cache.users.get(bot.ctx.self.userId);
|
|
184
|
+
online(user.username, bot.ctx.cache.guilds.size);
|
|
185
|
+
});
|
|
186
|
+
if (result === 'refused')
|
|
187
|
+
return 1;
|
|
188
|
+
log.success('Disconnected');
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
/** The share of the bot another process gave this one. */
|
|
192
|
+
async function runShare(project, options, connection, assignment) {
|
|
193
|
+
const primary = connectToPrimary();
|
|
194
|
+
const { signal } = options;
|
|
195
|
+
const stopped = Promise.race([
|
|
196
|
+
primary.stopped,
|
|
197
|
+
new Promise(resolve => {
|
|
198
|
+
if (signal.aborted)
|
|
199
|
+
resolve();
|
|
200
|
+
else
|
|
201
|
+
signal.addEventListener('abort', () => resolve(), { once: true });
|
|
202
|
+
}),
|
|
203
|
+
]);
|
|
204
|
+
const result = await run(project, {
|
|
205
|
+
...connection,
|
|
206
|
+
shards: { ids: assignment.ids, count: assignment.count },
|
|
207
|
+
identifyGate: primary.gate,
|
|
208
|
+
// Discord counts the requests of the whole bot: each process takes
|
|
209
|
+
// its share, so that together they stay under the limit.
|
|
210
|
+
globalLimit: Math.max(1, Math.floor(GLOBAL_REQUESTS_PER_SECOND / assignment.processes)),
|
|
211
|
+
}, stopped, bot => {
|
|
212
|
+
const user = bot.ctx.cache.users.get(bot.ctx.self.userId);
|
|
213
|
+
primary.ready({
|
|
214
|
+
guilds: bot.ctx.cache.guilds.size,
|
|
215
|
+
user: user.username,
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
// The first process of the cluster runs the tasks for all of them.
|
|
219
|
+
{ tasks: assignment.index === 0 });
|
|
220
|
+
if (result === 'refused') {
|
|
221
|
+
primary.refused();
|
|
222
|
+
return REFUSED;
|
|
223
|
+
}
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type RawApplicationCommandInteractionDataOption, type RawInteraction } from '../discord/types/interaction.js';
|
|
2
|
+
import { type Reporter } from '../interactions/dispatch.js';
|
|
3
|
+
import type { Context } from '../structures/context.js';
|
|
4
|
+
import { type CommandEntry } from './tree.js';
|
|
5
|
+
/** How many suggestions Discord shows. */
|
|
6
|
+
export declare const MAX_SUGGESTIONS = 25;
|
|
7
|
+
/**
|
|
8
|
+
* Answers an autocomplete interaction with what the `autocomplete`
|
|
9
|
+
* function of the command returns. Discord shows nothing when the answer
|
|
10
|
+
* is empty, so every path ends with an answer, even when the function
|
|
11
|
+
* fails: the person is never left with a spinner.
|
|
12
|
+
*/
|
|
13
|
+
export declare function suggest(ctx: Context, raw: RawInteraction, entry: CommandEntry | undefined, given: readonly RawApplicationCommandInteractionDataOption[], reporter: Reporter, warnedOnce: Set<string>): void;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Suggestions while the person types in an option: Discord sends an
|
|
2
|
+
// Application Command Autocomplete interaction with what they typed so
|
|
3
|
+
// far, and waits 3 seconds for up to 25 choices. Nothing else can be
|
|
4
|
+
// answered to it: the `autocomplete` function of the file returns the
|
|
5
|
+
// suggestions, and this module checks them and sends them.
|
|
6
|
+
// https://docs.discord.com/developers/interactions/application-commands#autocomplete
|
|
7
|
+
import { CreateInteractionResponse } from '../discord/endpoints.js';
|
|
8
|
+
import { InteractionCallbackType, } from '../discord/types/interaction.js';
|
|
9
|
+
import { authorOf, placeContext, resolvePlace, } from '../interactions/dispatch.js';
|
|
10
|
+
import { translation } from '../messages/translate.js';
|
|
11
|
+
import { DiscordApiError } from '../rest/errors.js';
|
|
12
|
+
import { commandName } from './tree.js';
|
|
13
|
+
/** How many suggestions Discord shows. */
|
|
14
|
+
export const MAX_SUGGESTIONS = 25;
|
|
15
|
+
/** The longest name of a suggestion, and the longest text it may carry. */
|
|
16
|
+
const MAX_LENGTH = 100;
|
|
17
|
+
/** How long Discord waits for the suggestions. */
|
|
18
|
+
const DEADLINE = 3000;
|
|
19
|
+
/** Discord says it when the interaction expired before the answer. */
|
|
20
|
+
const UNKNOWN_INTERACTION = 10062;
|
|
21
|
+
/**
|
|
22
|
+
* Answers an autocomplete interaction with what the `autocomplete`
|
|
23
|
+
* function of the command returns. Discord shows nothing when the answer
|
|
24
|
+
* is empty, so every path ends with an answer, even when the function
|
|
25
|
+
* fails: the person is never left with a spinner.
|
|
26
|
+
*/
|
|
27
|
+
export function suggest(ctx, raw, entry, given, reporter, warnedOnce) {
|
|
28
|
+
const focused = given.find(option => option.focused);
|
|
29
|
+
const started = Date.now();
|
|
30
|
+
const answer = (choices) => {
|
|
31
|
+
ctx.rest
|
|
32
|
+
.request(CreateInteractionResponse, [raw.id, raw.token], {
|
|
33
|
+
body: {
|
|
34
|
+
type: InteractionCallbackType.ApplicationCommandAutocompleteResult,
|
|
35
|
+
data: { choices },
|
|
36
|
+
},
|
|
37
|
+
auth: false,
|
|
38
|
+
})
|
|
39
|
+
.catch((error) => {
|
|
40
|
+
if (!entry || !focused)
|
|
41
|
+
return;
|
|
42
|
+
const name = `${commandName(entry.command.path)} ${focused.name}`;
|
|
43
|
+
if (error instanceof DiscordApiError &&
|
|
44
|
+
error.code === UNKNOWN_INTERACTION) {
|
|
45
|
+
// Too late, or the person typed on and Discord dropped this one.
|
|
46
|
+
if (Date.now() - started < DEADLINE)
|
|
47
|
+
return;
|
|
48
|
+
reporter.onWarning(entry.file, `The suggestions for ${name} took ${((Date.now() - started) / 1000).toFixed(1)} s: Discord waits 3 seconds, then shows none. Make the autocomplete function faster.`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
reporter.onError(entry.file, error);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
// The command, the option or its function is gone: nothing to suggest.
|
|
55
|
+
const fn = focused && entry?.command.autocomplete[focused.name];
|
|
56
|
+
if (!entry || !focused || !fn) {
|
|
57
|
+
answer([]);
|
|
58
|
+
if (entry && focused) {
|
|
59
|
+
// Still registered with suggestions, but the file has no function.
|
|
60
|
+
reporter.onWarning(entry.file, `Discord asked suggestions for ${commandName(entry.command.path)} ${focused.name}, which has none in "autocomplete": the command was registered with an older version of this file.`);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const { command, file } = entry;
|
|
65
|
+
const name = `${commandName(command.path)} ${focused.name}`;
|
|
66
|
+
const option = command.options.find(one => one.name === focused.name);
|
|
67
|
+
const isText = option.type === 'string';
|
|
68
|
+
const author = authorOf(raw);
|
|
69
|
+
if (!author)
|
|
70
|
+
return answer([]);
|
|
71
|
+
const user = ctx.entities.user(author.user);
|
|
72
|
+
const placed = resolvePlace(ctx, raw, user, author.member, command.where, message => reporter.onWarning(file, `${name} ${message}`));
|
|
73
|
+
// Used where the command can't run: no suggestion, the use is refused.
|
|
74
|
+
if (!placed.place)
|
|
75
|
+
return answer([]);
|
|
76
|
+
// What was typed: a text, maybe empty; a number only once it is one.
|
|
77
|
+
const typed = focused.value;
|
|
78
|
+
const value = isText
|
|
79
|
+
? String(typed ?? '')
|
|
80
|
+
: typeof typed === 'number'
|
|
81
|
+
? typed
|
|
82
|
+
: undefined;
|
|
83
|
+
const soFar = {};
|
|
84
|
+
for (const one of given) {
|
|
85
|
+
if (one.value === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
// Ids and numbers arrive as what they are; the focused one may be
|
|
88
|
+
// partial, and stays what the person typed.
|
|
89
|
+
soFar[one.name] = one.name === focused.name ? value : one.value;
|
|
90
|
+
}
|
|
91
|
+
const context = Object.freeze({
|
|
92
|
+
value,
|
|
93
|
+
options: Object.freeze(soFar),
|
|
94
|
+
user,
|
|
95
|
+
locale: raw.locale,
|
|
96
|
+
...placeContext(placed.place, command.where),
|
|
97
|
+
// Suggestions are shown to the person alone: their language.
|
|
98
|
+
...translation(ctx, raw.locale ?? null),
|
|
99
|
+
});
|
|
100
|
+
new Promise(resolve => resolve(fn(context)))
|
|
101
|
+
.then(suggestions => {
|
|
102
|
+
answer(toChoices(suggestions, {
|
|
103
|
+
name,
|
|
104
|
+
isText,
|
|
105
|
+
integer: option.type === 'integer',
|
|
106
|
+
warn: message => {
|
|
107
|
+
// Said once per option: it happens on every keystroke.
|
|
108
|
+
if (warnedOnce.has(`${file} ${name} ${message}`))
|
|
109
|
+
return;
|
|
110
|
+
warnedOnce.add(`${file} ${name} ${message}`);
|
|
111
|
+
reporter.onWarning(file, `${name}: ${message}`);
|
|
112
|
+
},
|
|
113
|
+
}));
|
|
114
|
+
})
|
|
115
|
+
.catch((error) => {
|
|
116
|
+
reporter.onError(file, error);
|
|
117
|
+
answer([]);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* What the function returned, as the choices Discord takes. What can be
|
|
122
|
+
* fixed is fixed and said once (too many, a name too long); what can't
|
|
123
|
+
* (not a list, a value of the wrong kind) is an error, and nothing is
|
|
124
|
+
* suggested.
|
|
125
|
+
*/
|
|
126
|
+
function toChoices(suggestions, { name, isText, integer, warn, }) {
|
|
127
|
+
if (!Array.isArray(suggestions)) {
|
|
128
|
+
throw new TypeError(`The autocomplete function of ${name} must return a list of suggestions, like ['a', 'b'] or [{ name: 'Shown', value: 'a' }]: got ${JSON.stringify(suggestions) ?? typeof suggestions}.`);
|
|
129
|
+
}
|
|
130
|
+
const kept = suggestions.length > MAX_SUGGESTIONS
|
|
131
|
+
? suggestions.slice(0, MAX_SUGGESTIONS)
|
|
132
|
+
: suggestions;
|
|
133
|
+
if (kept !== suggestions) {
|
|
134
|
+
warn(`the autocomplete function returned ${suggestions.length} suggestions, Discord shows ${MAX_SUGGESTIONS} at most: only the first ${MAX_SUGGESTIONS} are sent. Return fewer, the best ones first.`);
|
|
135
|
+
}
|
|
136
|
+
return kept.map(suggestion => {
|
|
137
|
+
const pair = typeof suggestion === 'object' && suggestion !== null
|
|
138
|
+
? suggestion
|
|
139
|
+
: { name: String(suggestion), value: suggestion };
|
|
140
|
+
const { value } = pair;
|
|
141
|
+
if (typeof value !== (isText ? 'string' : 'number')) {
|
|
142
|
+
throw new TypeError(`A suggestion of ${name} has the value ${JSON.stringify(value) ?? typeof value}: the option is a ${isText ? 'text' : 'number'}, so its suggestions must be ${isText ? 'texts' : 'numbers'}.`);
|
|
143
|
+
}
|
|
144
|
+
if (integer && !Number.isInteger(value)) {
|
|
145
|
+
throw new TypeError(`A suggestion of ${name} has the value ${String(value)}, which is not a whole number: the option is an integer.`);
|
|
146
|
+
}
|
|
147
|
+
if (isText && value.length > MAX_LENGTH) {
|
|
148
|
+
throw new TypeError(`A suggestion of ${name} has a value of ${value.length} characters: Discord accepts ${MAX_LENGTH} at most. Suggest a shorter value, like an id, with the text to show as its name.`);
|
|
149
|
+
}
|
|
150
|
+
let shown = typeof pair.name === 'string' && pair.name.length > 0
|
|
151
|
+
? pair.name
|
|
152
|
+
: String(value);
|
|
153
|
+
if (shown.length > MAX_LENGTH) {
|
|
154
|
+
warn(`a suggestion is shown as ${shown.length} characters, Discord shows ${MAX_LENGTH} at most: it is cut. Give it a shorter name.`);
|
|
155
|
+
shown = `${shown.slice(0, MAX_LENGTH - 1)}…`;
|
|
156
|
+
}
|
|
157
|
+
return { name: shown, value: value };
|
|
158
|
+
});
|
|
159
|
+
}
|