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.
Files changed (318) hide show
  1. package/dist/assets/asset.d.ts +46 -0
  2. package/dist/assets/asset.js +86 -0
  3. package/dist/assets/public.d.ts +19 -0
  4. package/dist/assets/public.js +120 -0
  5. package/dist/cache/cache.d.ts +62 -0
  6. package/dist/cache/cache.js +80 -0
  7. package/dist/cache/store.d.ts +45 -0
  8. package/dist/cache/store.js +53 -0
  9. package/dist/cli/build.d.ts +41 -0
  10. package/dist/cli/build.js +205 -0
  11. package/dist/cli/cli.js +108 -25
  12. package/dist/cli/cluster.d.ts +70 -0
  13. package/dist/cli/cluster.js +213 -0
  14. package/dist/cli/dev.d.ts +18 -1
  15. package/dist/cli/dev.js +253 -198
  16. package/dist/cli/env.d.ts +31 -0
  17. package/dist/cli/env.js +93 -0
  18. package/dist/cli/log.d.ts +13 -0
  19. package/dist/cli/log.js +17 -0
  20. package/dist/cli/node-version.d.ts +8 -0
  21. package/dist/cli/node-version.js +13 -0
  22. package/dist/cli/preflight.d.ts +36 -0
  23. package/dist/cli/preflight.js +122 -0
  24. package/dist/cli/project.d.ts +135 -0
  25. package/dist/cli/project.js +517 -0
  26. package/dist/cli/start.d.ts +25 -0
  27. package/dist/cli/start.js +225 -0
  28. package/dist/commands/autocomplete.d.ts +13 -0
  29. package/dist/commands/autocomplete.js +159 -0
  30. package/dist/commands/command.d.ts +360 -0
  31. package/dist/commands/command.js +28 -0
  32. package/dist/commands/declaration.d.ts +77 -0
  33. package/dist/commands/declaration.js +492 -0
  34. package/dist/commands/register.d.ts +18 -0
  35. package/dist/commands/register.js +42 -0
  36. package/dist/commands/router.d.ts +20 -0
  37. package/dist/commands/router.js +228 -0
  38. package/dist/commands/tree.d.ts +47 -0
  39. package/dist/commands/tree.js +216 -0
  40. package/dist/commands/twins.d.ts +15 -0
  41. package/dist/commands/twins.js +78 -0
  42. package/dist/components/button.d.ts +79 -0
  43. package/dist/components/button.js +106 -0
  44. package/dist/components/component.d.ts +140 -0
  45. package/dist/components/component.js +39 -0
  46. package/dist/components/custom-id.d.ts +72 -0
  47. package/dist/components/custom-id.js +128 -0
  48. package/dist/components/declaration.d.ts +42 -0
  49. package/dist/components/declaration.js +342 -0
  50. package/dist/components/declared.d.ts +29 -0
  51. package/dist/components/declared.js +49 -0
  52. package/dist/components/embed.d.ts +29 -0
  53. package/dist/components/embed.js +11 -0
  54. package/dist/components/instance.d.ts +100 -0
  55. package/dist/components/instance.js +86 -0
  56. package/dist/components/layout.d.ts +103 -0
  57. package/dist/components/layout.js +279 -0
  58. package/dist/components/modal.d.ts +245 -0
  59. package/dist/components/modal.js +398 -0
  60. package/dist/components/options.d.ts +41 -0
  61. package/dist/components/options.js +101 -0
  62. package/dist/components/render.d.ts +12 -0
  63. package/dist/components/render.js +58 -0
  64. package/dist/components/router.d.ts +19 -0
  65. package/dist/components/router.js +251 -0
  66. package/dist/components/select.d.ts +146 -0
  67. package/dist/components/select.js +179 -0
  68. package/dist/core/bot.d.ts +99 -0
  69. package/dist/core/bot.js +174 -0
  70. package/dist/core/memory.d.ts +66 -0
  71. package/dist/core/memory.js +115 -0
  72. package/dist/discord/api.d.ts +112 -0
  73. package/dist/discord/api.js +115 -0
  74. package/dist/discord/cdn.d.ts +67 -0
  75. package/dist/discord/cdn.js +100 -0
  76. package/dist/discord/codes.d.ts +171 -0
  77. package/dist/discord/codes.js +421 -0
  78. package/dist/discord/endpoint.d.ts +56 -0
  79. package/dist/discord/endpoint.js +53 -0
  80. package/dist/discord/endpoints.d.ts +1529 -0
  81. package/dist/discord/endpoints.js +1139 -0
  82. package/dist/discord/formatting.d.ts +67 -0
  83. package/dist/discord/formatting.js +87 -0
  84. package/dist/discord/index.d.ts +10 -0
  85. package/dist/discord/index.js +13 -0
  86. package/dist/discord/intents.d.ts +48 -0
  87. package/dist/discord/intents.js +128 -0
  88. package/dist/discord/permissions.d.ts +94 -0
  89. package/dist/discord/permissions.js +185 -0
  90. package/dist/discord/snowflake.d.ts +21 -0
  91. package/dist/discord/snowflake.js +36 -0
  92. package/dist/discord/types/application-command.d.ts +332 -0
  93. package/dist/discord/types/application-command.js +48 -0
  94. package/dist/discord/types/application-role-connection-metadata.d.ts +42 -0
  95. package/dist/discord/types/application-role-connection-metadata.js +22 -0
  96. package/dist/discord/types/application.d.ts +208 -0
  97. package/dist/discord/types/application.js +48 -0
  98. package/dist/discord/types/audit-log.d.ts +255 -0
  99. package/dist/discord/types/audit-log.js +144 -0
  100. package/dist/discord/types/auto-moderation.d.ts +167 -0
  101. package/dist/discord/types/auto-moderation.js +52 -0
  102. package/dist/discord/types/channel.d.ts +557 -0
  103. package/dist/discord/types/channel.js +88 -0
  104. package/dist/discord/types/common.d.ts +54 -0
  105. package/dist/discord/types/common.js +38 -0
  106. package/dist/discord/types/component.d.ts +775 -0
  107. package/dist/discord/types/component.js +76 -0
  108. package/dist/discord/types/emoji.d.ts +74 -0
  109. package/dist/discord/types/emoji.js +1 -0
  110. package/dist/discord/types/entitlement.d.ts +84 -0
  111. package/dist/discord/types/entitlement.js +22 -0
  112. package/dist/discord/types/gateway-events.d.ts +1148 -0
  113. package/dist/discord/types/gateway-events.js +54 -0
  114. package/dist/discord/types/gateway.d.ts +38 -0
  115. package/dist/discord/types/gateway.js +1 -0
  116. package/dist/discord/types/guild-scheduled-event.d.ts +257 -0
  117. package/dist/discord/types/guild-scheduled-event.js +68 -0
  118. package/dist/discord/types/guild-template.d.ts +51 -0
  119. package/dist/discord/types/guild-template.js +1 -0
  120. package/dist/discord/types/guild.d.ts +910 -0
  121. package/dist/discord/types/guild.js +153 -0
  122. package/dist/discord/types/index.d.ts +31 -0
  123. package/dist/discord/types/index.js +31 -0
  124. package/dist/discord/types/interaction.d.ts +321 -0
  125. package/dist/discord/types/interaction.js +47 -0
  126. package/dist/discord/types/invite.d.ts +158 -0
  127. package/dist/discord/types/invite.js +39 -0
  128. package/dist/discord/types/message.d.ts +834 -0
  129. package/dist/discord/types/message.js +159 -0
  130. package/dist/discord/types/oauth2.d.ts +22 -0
  131. package/dist/discord/types/oauth2.js +1 -0
  132. package/dist/discord/types/permissions.d.ts +183 -0
  133. package/dist/discord/types/permissions.js +118 -0
  134. package/dist/discord/types/poll.d.ts +106 -0
  135. package/dist/discord/types/poll.js +8 -0
  136. package/dist/discord/types/sku.d.ts +47 -0
  137. package/dist/discord/types/sku.js +26 -0
  138. package/dist/discord/types/soundboard.d.ts +71 -0
  139. package/dist/discord/types/soundboard.js +1 -0
  140. package/dist/discord/types/stage-instance.d.ts +58 -0
  141. package/dist/discord/types/stage-instance.js +10 -0
  142. package/dist/discord/types/sticker.d.ts +115 -0
  143. package/dist/discord/types/sticker.js +20 -0
  144. package/dist/discord/types/subscription.d.ts +54 -0
  145. package/dist/discord/types/subscription.js +12 -0
  146. package/dist/discord/types/team.d.ts +55 -0
  147. package/dist/discord/types/team.js +21 -0
  148. package/dist/discord/types/user.d.ts +273 -0
  149. package/dist/discord/types/user.js +83 -0
  150. package/dist/discord/types/voice.d.ts +72 -0
  151. package/dist/discord/types/voice.js +1 -0
  152. package/dist/discord/types/webhook.d.ts +184 -0
  153. package/dist/discord/types/webhook.js +12 -0
  154. package/dist/events/declaration.d.ts +16 -0
  155. package/dist/events/declaration.js +80 -0
  156. package/dist/events/event.d.ts +45 -0
  157. package/dist/events/event.js +34 -0
  158. package/dist/events/registry.d.ts +697 -0
  159. package/dist/events/registry.js +839 -0
  160. package/dist/events/router.d.ts +46 -0
  161. package/dist/events/router.js +133 -0
  162. package/dist/gateway/errors.d.ts +18 -0
  163. package/dist/gateway/errors.js +47 -0
  164. package/dist/gateway/gateway.d.ts +64 -0
  165. package/dist/gateway/gateway.js +117 -0
  166. package/dist/gateway/identify-queue.d.ts +25 -0
  167. package/dist/gateway/identify-queue.js +29 -0
  168. package/dist/gateway/shard.d.ts +94 -0
  169. package/dist/gateway/shard.js +414 -0
  170. package/dist/gateway/state.d.ts +11 -0
  171. package/dist/gateway/state.js +184 -0
  172. package/dist/index.d.ts +57 -18
  173. package/dist/index.js +40 -12
  174. package/dist/interactions/cooldown.d.ts +33 -0
  175. package/dist/interactions/cooldown.js +74 -0
  176. package/dist/interactions/dispatch.d.ts +91 -0
  177. package/dist/interactions/dispatch.js +110 -0
  178. package/dist/loader/generated.d.ts +10 -0
  179. package/dist/loader/generated.js +80 -0
  180. package/dist/loader/hot.d.ts +13 -0
  181. package/dist/loader/hot.js +85 -0
  182. package/dist/loader/loader.d.ts +85 -0
  183. package/dist/loader/loader.js +119 -0
  184. package/dist/loader/locate.d.ts +10 -0
  185. package/dist/loader/locate.js +36 -0
  186. package/dist/loader/watch.d.ts +17 -0
  187. package/dist/loader/watch.js +66 -0
  188. package/dist/main.d.ts +2 -0
  189. package/dist/main.js +5 -0
  190. package/dist/messages/commands.d.ts +23 -0
  191. package/dist/messages/commands.js +142 -0
  192. package/dist/messages/declaration.d.ts +89 -0
  193. package/dist/messages/declaration.js +455 -0
  194. package/dist/messages/messages.d.ts +241 -0
  195. package/dist/messages/messages.js +31 -0
  196. package/dist/messages/phrases.d.ts +23 -0
  197. package/dist/messages/phrases.js +44 -0
  198. package/dist/messages/translate.d.ts +100 -0
  199. package/dist/messages/translate.js +249 -0
  200. package/dist/presence/declaration.d.ts +19 -0
  201. package/dist/presence/declaration.js +118 -0
  202. package/dist/presence/presence.d.ts +57 -0
  203. package/dist/presence/presence.js +27 -0
  204. package/dist/rest/errors.d.ts +55 -0
  205. package/dist/rest/errors.js +92 -0
  206. package/dist/rest/rate-limiter.d.ts +46 -0
  207. package/dist/rest/rate-limiter.js +153 -0
  208. package/dist/rest/rest.d.ts +63 -0
  209. package/dist/rest/rest.js +194 -0
  210. package/dist/structures/base.d.ts +48 -0
  211. package/dist/structures/base.js +84 -0
  212. package/dist/structures/channel.d.ts +380 -0
  213. package/dist/structures/channel.js +627 -0
  214. package/dist/structures/context.d.ts +30 -0
  215. package/dist/structures/context.js +7 -0
  216. package/dist/structures/emoji.d.ts +46 -0
  217. package/dist/structures/emoji.js +65 -0
  218. package/dist/structures/entities.d.ts +62 -0
  219. package/dist/structures/entities.js +226 -0
  220. package/dist/structures/guild.d.ts +348 -0
  221. package/dist/structures/guild.js +466 -0
  222. package/dist/structures/interaction.d.ts +197 -0
  223. package/dist/structures/interaction.js +396 -0
  224. package/dist/structures/invite.d.ts +38 -0
  225. package/dist/structures/invite.js +57 -0
  226. package/dist/structures/known.d.ts +28 -0
  227. package/dist/structures/known.js +58 -0
  228. package/dist/structures/member.d.ts +140 -0
  229. package/dist/structures/member.js +260 -0
  230. package/dist/structures/message.d.ts +197 -0
  231. package/dist/structures/message.js +268 -0
  232. package/dist/structures/payload.d.ts +91 -0
  233. package/dist/structures/payload.js +184 -0
  234. package/dist/structures/role.d.ts +69 -0
  235. package/dist/structures/role.js +112 -0
  236. package/dist/structures/user.d.ts +59 -0
  237. package/dist/structures/user.js +94 -0
  238. package/dist/structures/webhook.d.ts +46 -0
  239. package/dist/structures/webhook.js +79 -0
  240. package/dist/tasks/declaration.d.ts +16 -0
  241. package/dist/tasks/declaration.js +56 -0
  242. package/dist/tasks/schedule.d.ts +54 -0
  243. package/dist/tasks/schedule.js +236 -0
  244. package/dist/tasks/scheduler.d.ts +35 -0
  245. package/dist/tasks/scheduler.js +107 -0
  246. package/dist/tasks/task.d.ts +75 -0
  247. package/dist/tasks/task.js +27 -0
  248. package/dist/util/case.d.ts +13 -0
  249. package/dist/util/case.js +54 -0
  250. package/dist/util/duration.d.ts +8 -0
  251. package/dist/util/duration.js +29 -0
  252. package/dist/util/websocket.d.ts +9 -0
  253. package/dist/util/websocket.js +31 -0
  254. package/dist/voice/connection.d.ts +90 -0
  255. package/dist/voice/connection.js +494 -0
  256. package/dist/voice/dave.d.ts +109 -0
  257. package/dist/voice/dave.js +245 -0
  258. package/dist/voice/opus.d.ts +19 -0
  259. package/dist/voice/opus.js +273 -0
  260. package/dist/voice/session.d.ts +55 -0
  261. package/dist/voice/session.js +405 -0
  262. package/dist/voice/source.d.ts +22 -0
  263. package/dist/voice/source.js +195 -0
  264. package/dist/voice/transport.d.ts +43 -0
  265. package/dist/voice/transport.js +140 -0
  266. package/dist/voice/usage.d.ts +4 -0
  267. package/dist/voice/usage.js +33 -0
  268. package/package.json +26 -7
  269. package/vendor/dave/LICENSE +231 -0
  270. package/vendor/dave/libdave.d.mts +99 -0
  271. package/vendor/dave/libdave.mjs +2 -0
  272. package/vendor/dave/libdave.wasm +0 -0
  273. package/dist/components.d.ts +0 -97
  274. package/dist/components.js +0 -260
  275. package/dist/context.d.ts +0 -72
  276. package/dist/context.js +0 -273
  277. package/dist/discord/client.d.ts +0 -35
  278. package/dist/discord/client.js +0 -66
  279. package/dist/discord/constants.d.ts +0 -49
  280. package/dist/discord/constants.js +0 -61
  281. package/dist/discord/events.d.ts +0 -129
  282. package/dist/discord/events.js +0 -102
  283. package/dist/discord/gateway.d.ts +0 -61
  284. package/dist/discord/gateway.js +0 -320
  285. package/dist/discord/payloads.d.ts +0 -275
  286. package/dist/discord/payloads.js +0 -3
  287. package/dist/discord/presence.d.ts +0 -42
  288. package/dist/discord/presence.js +0 -51
  289. package/dist/discord/rest.d.ts +0 -49
  290. package/dist/discord/rest.js +0 -271
  291. package/dist/discord/types.d.ts +0 -334
  292. package/dist/discord/types.js +0 -4
  293. package/dist/events.d.ts +0 -94
  294. package/dist/events.js +0 -255
  295. package/dist/presence.d.ts +0 -92
  296. package/dist/presence.js +0 -250
  297. package/dist/structures/Channel.d.ts +0 -110
  298. package/dist/structures/Channel.js +0 -171
  299. package/dist/structures/Guild.d.ts +0 -70
  300. package/dist/structures/Guild.js +0 -105
  301. package/dist/structures/Interaction.d.ts +0 -113
  302. package/dist/structures/Interaction.js +0 -274
  303. package/dist/structures/Member.d.ts +0 -70
  304. package/dist/structures/Member.js +0 -124
  305. package/dist/structures/Message.d.ts +0 -87
  306. package/dist/structures/Message.js +0 -182
  307. package/dist/structures/Reaction.d.ts +0 -38
  308. package/dist/structures/Reaction.js +0 -64
  309. package/dist/structures/Role.d.ts +0 -43
  310. package/dist/structures/Role.js +0 -77
  311. package/dist/structures/User.d.ts +0 -41
  312. package/dist/structures/User.js +0 -79
  313. package/dist/structures/utils.d.ts +0 -72
  314. package/dist/structures/utils.js +0 -59
  315. package/dist/utils/errors.d.ts +0 -5
  316. package/dist/utils/errors.js +0 -28
  317. package/dist/utils/modules.d.ts +0 -39
  318. 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
+ }