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,517 @@
1
+ // What `chapterjs dev` and `chapterjs start` share: the files of a project,
2
+ // loaded and routed, and a bot that runs them. Each command adds what makes
3
+ // it different (reloading for one, every server and several processes for
4
+ // the other).
5
+ import { stat } from 'node:fs/promises';
6
+ import { join } from 'node:path';
7
+ import { setPublicDir } from '../assets/asset.js';
8
+ import { listPublic, publicDeclarations } from '../assets/public.js';
9
+ import { MissingForEvent } from '../events/registry.js';
10
+ import { sourcesJoinVoice } from '../voice/usage.js';
11
+ import { writeGenerated } from '../loader/generated.js';
12
+ import { commandDeclaration, scanCommands } from '../commands/declaration.js';
13
+ import { CommandRouter } from '../commands/router.js';
14
+ import { findConflicts } from '../commands/tree.js';
15
+ import { componentDeclaration, findDuplicates, } from '../components/declaration.js';
16
+ import { ComponentRouter } from '../components/router.js';
17
+ import { taskDeclaration } from '../tasks/declaration.js';
18
+ import { DEFAULT_PRESENCE, presenceDeclaration, } from '../presence/declaration.js';
19
+ import { assembleMessages, commandsDeclarations, languageDeclaration, languageDefaultsDeclaration, messagesDeclarations, scanLanguages, } from '../messages/declaration.js';
20
+ import { translation } from '../messages/translate.js';
21
+ import { translateCommand, unknownCommands } from '../messages/commands.js';
22
+ import { TimerScheduler, } from '../tasks/scheduler.js';
23
+ import { createBot } from '../core/bot.js';
24
+ import { watchMemory } from '../core/memory.js';
25
+ import { GatewayIntent } from '../discord/intents.js';
26
+ import { eventDeclaration } from '../events/declaration.js';
27
+ import { EventRouter, intentsFor, intentsOf, limitsFor, } from '../events/router.js';
28
+ import { GatewayFatalError, SessionLimitError } from '../gateway/errors.js';
29
+ import { loadSources, readDeclarations, siteName, } from '../loader/loader.js';
30
+ import { locate, messageOf } from '../loader/locate.js';
31
+ import { DiscordApiError, DiscordUnavailableError, InvalidTokenError, } from '../rest/errors.js';
32
+ import { PreflightFailure } from './preflight.js';
33
+ /**
34
+ * Writes the types of a project (`.chapterjs/`): what the project adds to
35
+ * 'chapterjs', like the files of `public/`, its languages and its
36
+ * commands. With the project as it loaded (`info`), a language only
37
+ * offers the commands without `description`, `t` is typed from the
38
+ * default language and `default: true` is refused in every language that
39
+ * says it when another one does; without it (`sync`, which runs nothing),
40
+ * the commands and the languages are found by reading the files, every
41
+ * command is offered, and the first language or the one that says
42
+ * `default: true` types `t`.
43
+ * @returns how many files were written
44
+ */
45
+ export async function writeTypes(cwd, info) {
46
+ const [files, found, names] = await Promise.all([
47
+ listPublic(cwd),
48
+ info ? null : scanLanguages(cwd),
49
+ info ? null : scanCommands(cwd),
50
+ ]);
51
+ const languages = info ? info.languages : found.languages;
52
+ const defaultLocale = info ? info.defaultLocale : found.defaultLocale;
53
+ const commands = info
54
+ ? info.commands
55
+ : names.map(name => ({ name, described: false }));
56
+ return writeGenerated(cwd, publicDeclarations(files) +
57
+ messagesDeclarations(languages, defaultLocale) +
58
+ languageDefaultsDeclaration(info?.languageDefaults ?? []) +
59
+ commandsDeclarations(commands));
60
+ }
61
+ /** The kinds of declarations a project is made of: one per feature. */
62
+ export const DECLARATIONS = [
63
+ eventDeclaration,
64
+ commandDeclaration,
65
+ componentDeclaration,
66
+ taskDeclaration,
67
+ presenceDeclaration,
68
+ languageDeclaration,
69
+ ];
70
+ /** Whether the folder is a project: it has a `src` folder. */
71
+ export async function hasSources(cwd, log) {
72
+ if ((await stat(join(cwd, 'src')).catch(() => null))?.isDirectory()) {
73
+ return true;
74
+ }
75
+ log.error('There is no src folder here.\nRun this command in the folder of your bot (the one with package.json), or create a project with "pnpm create chapter".');
76
+ return false;
77
+ }
78
+ /** The intents that only bring what happens in private messages. */
79
+ const PRIVATE_INTENTS = GatewayIntent.DirectMessages |
80
+ GatewayIntent.DirectMessageReactions |
81
+ GatewayIntent.DirectMessageTyping |
82
+ GatewayIntent.DirectMessagePolls;
83
+ const megabytes = (bytes) => `${Math.round(bytes / 1024 / 1024)} MB`;
84
+ /** What tells one declaration from another: its file and its export. */
85
+ const keyOf = (site) => `${site.file}#${site.export}`;
86
+ /**
87
+ * Two declarations with the same name, where the name is the export's
88
+ * (a task): the later one is left out, with a message saying so.
89
+ */
90
+ function sameNames(entries, nameOf, what) {
91
+ const seen = new Map();
92
+ const conflicts = [];
93
+ const valid = [...entries]
94
+ .sort((a, b) => a.file === b.file
95
+ ? a.export < b.export
96
+ ? -1
97
+ : 1
98
+ : a.file < b.file
99
+ ? -1
100
+ : 1)
101
+ .filter(entry => {
102
+ const first = seen.get(nameOf(entry));
103
+ if (!first) {
104
+ seen.set(nameOf(entry), entry);
105
+ return true;
106
+ }
107
+ conflicts.push({
108
+ file: entry.file,
109
+ export: entry.export,
110
+ message: `There is already a ${what} named ${nameOf(entry)}, in ${first.file}: the name of the export is the name of the ${what}, so rename one of them.`,
111
+ });
112
+ return false;
113
+ });
114
+ return { valid, conflicts };
115
+ }
116
+ export function createProject(options) {
117
+ const { cwd, log } = options;
118
+ // Where `asset()` reads the files of the project from.
119
+ setPublicDir(cwd);
120
+ const report = (failure) => {
121
+ const where = locate(failure.error, cwd);
122
+ log.error(`${where ? `${where.file}:${where.line}` : siteName(failure)} ${messageOf(failure.error)}`);
123
+ };
124
+ const router = new EventRouter((file, error) => report({ file, error }), (event, error) => {
125
+ const missing = error instanceof MissingForEvent ? error : null;
126
+ const what = missing?.what === 'user'
127
+ ? 'the user who did it'
128
+ : 'the channel it happened in';
129
+ log.warn(`A ${event} event was not given to your files: Discord did not let the bot read ${what} (${messageOf(missing ? missing.cause : error)}).`);
130
+ });
131
+ const reporter = {
132
+ onError: (file, error) => report({ file, error }),
133
+ onWarning: (file, message) => log.warn(`${file} ${message}`),
134
+ ...(options.deferAfter === undefined
135
+ ? {}
136
+ : { deferAfter: options.deferAfter }),
137
+ };
138
+ const commandRouter = new CommandRouter(reporter);
139
+ const componentRouter = new ComponentRouter(reporter);
140
+ const scheduler = new TimerScheduler(reporter);
141
+ let events = new Map();
142
+ let commands = new Map();
143
+ let components = new Map();
144
+ let tasks = new Map();
145
+ let presence = null;
146
+ /** The files the presence was declared in, to keep it when they break. */
147
+ let presenceFiles = new Set();
148
+ // Joining voice needs the bot's own voice state, sent with an intent.
149
+ let voice = options.joinsVoice ?? false;
150
+ const neededIntents = () => intentsFor(events.values()) | (voice ? GatewayIntent.GuildVoiceStates : 0);
151
+ let messages = null;
152
+ /** The last version of each language that loaded. */
153
+ let languages = new Map();
154
+ let languageDefaults = [];
155
+ /** The bot running the files now, to give it what a reload changes. */
156
+ let running = null;
157
+ /** The presence each bot was given last, to send only what changed. */
158
+ const given = new WeakMap();
159
+ let lost = false;
160
+ const count = (size, one, many) => `${size} ${size === 1 ? one : many}`;
161
+ return {
162
+ get events() {
163
+ return events;
164
+ },
165
+ get commands() {
166
+ return commands;
167
+ },
168
+ get components() {
169
+ return components;
170
+ },
171
+ get tasks() {
172
+ return tasks;
173
+ },
174
+ get presence() {
175
+ return presence;
176
+ },
177
+ get messages() {
178
+ return messages;
179
+ },
180
+ typesInfo: () => ({
181
+ languages: [...languages.values()].map(entry => ({
182
+ file: entry.file,
183
+ export: entry.export,
184
+ name: entry.name,
185
+ locale: entry.language.locale,
186
+ })),
187
+ ...(messages ? { defaultLocale: messages.default } : {}),
188
+ languageDefaults,
189
+ commands: [...commands.values()].map(({ command }) => ({
190
+ name: command.path.join(' '),
191
+ described: command.described,
192
+ })),
193
+ }),
194
+ report,
195
+ async load() {
196
+ const { built } = options;
197
+ if (options.joinsVoice === undefined) {
198
+ voice = await sourcesJoinVoice(cwd);
199
+ }
200
+ // Every file of src/ runs (or ran, in a build); what each exports is
201
+ // then read, kind by kind.
202
+ const sources = built ? { modules: built, failed: [] } : await loadSources(cwd);
203
+ const read = (declaration) => readDeclarations(sources.modules, declaration);
204
+ const [eventItems, commandItems, componentItems, taskItems, presenceItems, languageItems,] = [
205
+ read(eventDeclaration),
206
+ read(commandDeclaration),
207
+ read(componentDeclaration),
208
+ read(taskDeclaration),
209
+ read(presenceDeclaration),
210
+ read(languageDeclaration),
211
+ ];
212
+ const broken = new Set(sources.failed.map(({ file }) => file));
213
+ /**
214
+ * The next version of a kind: what loaded now, plus the last good
215
+ * version of what failed (an export that can't be read, or a file
216
+ * that can't run at all).
217
+ */
218
+ const next = (previous, items, make) => {
219
+ const result = new Map();
220
+ for (const item of items.loaded)
221
+ result.set(keyOf(item), make(item));
222
+ const failedKeys = new Set(items.failed.map(({ file, export: exported }) => keyOf({ file, export: exported ?? 'default' })));
223
+ for (const [key, entry] of previous) {
224
+ if (failedKeys.has(key) || broken.has(entry.file)) {
225
+ result.set(key, entry);
226
+ }
227
+ }
228
+ return result;
229
+ };
230
+ events = next(events, eventItems, item => ({
231
+ file: item.file,
232
+ export: item.export,
233
+ event: item.value,
234
+ }));
235
+ router.set(events.values());
236
+ const { valid, conflicts } = findConflicts([
237
+ ...next(commands, commandItems, item => ({
238
+ file: item.file,
239
+ export: item.export,
240
+ command: item.value,
241
+ })).values(),
242
+ ]);
243
+ commands = new Map(valid.map(entry => [keyOf(entry), entry]));
244
+ const unique = findDuplicates([
245
+ ...next(components, componentItems, item => ({
246
+ file: item.file,
247
+ export: item.export,
248
+ component: item.value,
249
+ })).values(),
250
+ ]);
251
+ components = new Map(unique.valid.map(entry => [keyOf(entry), entry]));
252
+ componentRouter.set(components.values());
253
+ const sameTask = sameNames([
254
+ ...next(tasks, taskItems, item => ({
255
+ file: item.file,
256
+ export: item.export,
257
+ task: item.value,
258
+ })).values(),
259
+ ], entry => entry.task.name, 'task');
260
+ tasks = new Map(sameTask.valid.map(entry => [keyOf(entry), entry]));
261
+ scheduler.set(tasks.values());
262
+ // One presence. A second one is left out.
263
+ const [first, ...extra] = presenceItems.loaded;
264
+ if (first)
265
+ presence = first.value;
266
+ else if (presenceItems.failed.length === 0 &&
267
+ ![...broken].some(file => presenceFiles.has(file))) {
268
+ presence = null;
269
+ }
270
+ presenceFiles = new Set(presenceItems.loaded.map(({ file }) => file));
271
+ const twice = extra.map(item => ({
272
+ file: item.file,
273
+ export: item.export,
274
+ error: new TypeError(`There are two presences: the one of ${siteName(first)} is used, keep only one.`),
275
+ }));
276
+ const nextLanguages = next(languages, languageItems, item => ({
277
+ file: item.file,
278
+ export: item.export,
279
+ name: item.name,
280
+ language: item.value,
281
+ }));
282
+ // What the files say now, before a declaration is put back to its
283
+ // last good version: two languages saying default: true are both
284
+ // underlined.
285
+ languageDefaults = [...nextLanguages.values()]
286
+ .filter(({ language }) => language.isDefault)
287
+ .map(({ language }) => language.locale);
288
+ const previousLanguages = languages;
289
+ languages = nextLanguages;
290
+ let assembled = assembleMessages([...languages.values()]);
291
+ // A language that no longer matches the others keeps its last version
292
+ // that did, like a declaration that no longer loads.
293
+ const restored = assembled.failed.filter(failure => {
294
+ const key = keyOf({
295
+ file: failure.file,
296
+ export: failure.export ?? 'default',
297
+ });
298
+ const previous = previousLanguages.get(key);
299
+ return previous !== undefined && previous !== languages.get(key);
300
+ });
301
+ if (restored.length > 0) {
302
+ for (const failure of restored) {
303
+ const key = keyOf({
304
+ file: failure.file,
305
+ export: failure.export ?? 'default',
306
+ });
307
+ languages.set(key, previousLanguages.get(key));
308
+ }
309
+ const again = assembleMessages([...languages.values()]);
310
+ assembled = {
311
+ messages: again.messages,
312
+ failed: [
313
+ ...assembled.failed,
314
+ ...again.failed.filter(({ file }) => !assembled.failed.some(one => one.file === file)),
315
+ ],
316
+ };
317
+ }
318
+ messages = assembled.messages;
319
+ if (running)
320
+ running.ctx.messages = messages;
321
+ // A command has its texts in its file, or in the language files: the
322
+ // languages give them to the ones without, and a command that can't
323
+ // be described is left out.
324
+ const badTexts = [...assembled.failed];
325
+ if (messages) {
326
+ const names = new Set([...commands.values()].map(entry => entry.command.path.join(' ')));
327
+ for (const { file, path } of unknownCommands(messages, names)) {
328
+ badTexts.push({
329
+ file,
330
+ error: new TypeError(`"commands" translates "${path}", which is not a command of this project${names.size > 0 ? ` (its commands are: ${[...names].join(', ')})` : ''}. The key is the name of the command, like 'ping' or 'mod ban'.`),
331
+ });
332
+ }
333
+ }
334
+ for (const [key, entry] of commands) {
335
+ const { command, failed } = translateCommand(siteName(entry), entry.command, messages);
336
+ badTexts.push(...failed);
337
+ if (command)
338
+ commands.set(key, { ...entry, command });
339
+ else
340
+ commands.delete(key);
341
+ }
342
+ commandRouter.set(commands.values());
343
+ return [
344
+ ...sources.failed,
345
+ ...eventItems.failed,
346
+ ...commandItems.failed,
347
+ ...componentItems.failed,
348
+ ...taskItems.failed,
349
+ ...presenceItems.failed,
350
+ ...languageItems.failed,
351
+ ...badTexts,
352
+ ...twice,
353
+ ...[...conflicts, ...unique.conflicts, ...sameTask.conflicts].map(({ file, export: exported, message }) => ({
354
+ file,
355
+ export: exported,
356
+ error: new TypeError(message),
357
+ })),
358
+ ];
359
+ },
360
+ summary() {
361
+ if (events.size === 0 &&
362
+ commands.size === 0 &&
363
+ components.size === 0 &&
364
+ tasks.size === 0 &&
365
+ presence === null &&
366
+ messages === null) {
367
+ return "Nothing to run yet: export a command or an event from a file of src/, like export default command({ name: 'ping', ... })";
368
+ }
369
+ return `${[
370
+ ...(commands.size > 0
371
+ ? [count(commands.size, 'command', 'commands')]
372
+ : []),
373
+ ...(events.size > 0 ? [count(events.size, 'event', 'events')] : []),
374
+ ...(components.size > 0
375
+ ? [count(components.size, 'component', 'components')]
376
+ : []),
377
+ ...(tasks.size > 0 ? [count(tasks.size, 'task', 'tasks')] : []),
378
+ ...(presence ? ['a presence'] : []),
379
+ ...(messages
380
+ ? [
381
+ `messages in ${count(messages.locales.size, 'language', 'languages')}`,
382
+ ]
383
+ : []),
384
+ ].join(', ')} loaded`;
385
+ },
386
+ isEmpty: () => events.size === 0 &&
387
+ commands.size === 0 &&
388
+ components.size === 0 &&
389
+ tasks.size === 0 &&
390
+ presence === null &&
391
+ messages === null,
392
+ filesNeeding: intent => [...events.values()]
393
+ .filter(({ event }) => (intentsOf(event) & GatewayIntent[intent]) !== 0)
394
+ .map(({ file }) => file),
395
+ intents: (privateEvents = true) => neededIntents() & (privateEvents ? ~0 : ~PRIVATE_INTENTS),
396
+ async connect(connection) {
397
+ const created = createBot({
398
+ token: connection.token,
399
+ version: options.version,
400
+ intents: neededIntents() &
401
+ (connection.privateEvents === false ? ~PRIVATE_INTENTS : ~0),
402
+ guildFilter: connection.guildFilter,
403
+ privateEvents: connection.privateEvents,
404
+ presence: presence?.raw,
405
+ shards: connection.shards,
406
+ identifyGate: connection.identifyGate,
407
+ ...(connection.identifyInterval === undefined
408
+ ? {}
409
+ : { gateway: { identifyInterval: connection.identifyInterval } }),
410
+ // Only what the files of the project use is remembered.
411
+ cache: { limits: limitsFor(events.values()) },
412
+ // A local fake of Discord (tests) serves voice without TLS too.
413
+ voice: { secure: !connection.apiUrl?.startsWith('http://') },
414
+ rest: {
415
+ ...(connection.apiUrl ? { baseUrl: connection.apiUrl } : {}),
416
+ ...(connection.globalLimit === undefined
417
+ ? {}
418
+ : { globalLimit: connection.globalLimit }),
419
+ },
420
+ beforeDispatch: (event, data) => router.before(created.ctx, event, data),
421
+ onDispatch(event, data, info) {
422
+ router.dispatch(created.ctx, event, data, {
423
+ joined: info.joined,
424
+ before: info.before,
425
+ });
426
+ if (event === 'INTERACTION_CREATE') {
427
+ const interaction = data;
428
+ commandRouter.dispatch(created.ctx, interaction);
429
+ componentRouter.dispatch(created.ctx, interaction);
430
+ }
431
+ },
432
+ onEvent(event) {
433
+ if (event.type === 'disconnected' && !lost) {
434
+ lost = true;
435
+ log.warn('Connection to Discord lost, reconnecting...');
436
+ }
437
+ else if (event.type === 'ready' && lost) {
438
+ lost = false;
439
+ log.reload('Reconnected to Discord');
440
+ }
441
+ else if (event.type === 'fatal') {
442
+ log.error(event.error.message);
443
+ connection.onFatal(created);
444
+ }
445
+ else if (event.type === 'stateError') {
446
+ log.warn(`An event of Discord (${event.event}) could not be read: ${messageOf(event.error)}`);
447
+ }
448
+ else if (event.type === 'voiceWarning') {
449
+ log.warn(`Voice: ${event.message}`);
450
+ }
451
+ },
452
+ });
453
+ given.set(created, presence?.key ?? '');
454
+ created.ctx.messages = messages;
455
+ running = created;
456
+ await created.connect();
457
+ return created;
458
+ },
459
+ applyPresence(bot) {
460
+ const key = presence?.key ?? '';
461
+ if (given.get(bot) === key)
462
+ return false;
463
+ given.set(bot, key);
464
+ bot.setPresence(presence?.raw ?? DEFAULT_PRESENCE);
465
+ return true;
466
+ },
467
+ ready(bot) {
468
+ const user = bot.ctx.cache.users.get(bot.ctx.self.userId);
469
+ router.emit('ready', {
470
+ user,
471
+ guilds: bot.ctx.cache.guilds,
472
+ ...translation(bot.ctx, null),
473
+ });
474
+ },
475
+ startTasks(current) {
476
+ scheduler.start(() => {
477
+ const bot = current();
478
+ if (!bot?.ctx.self)
479
+ return null;
480
+ const user = bot.ctx.cache.users.get(bot.ctx.self.userId);
481
+ return user
482
+ ? {
483
+ user,
484
+ guilds: bot.ctx.cache.guilds,
485
+ ...translation(bot.ctx, null),
486
+ }
487
+ : null;
488
+ });
489
+ },
490
+ stopTasks: () => scheduler.stop(),
491
+ // The bot looks after its own memory: it remembers less before memory
492
+ // is full, and only speaks up when it can't give up anything more.
493
+ watchMemory: current => watchMemory({
494
+ cache: () => current()?.ctx.cache,
495
+ onTightened: ({ members, forgotten }, { used, limit }) => log.info(`Memory was getting full (${megabytes(used)} of ${megabytes(limit)}): the bot now remembers ${members} members per server at most, and forgot ${forgotten} members and users it had not seen for the longest. Nothing changes for your code.`),
496
+ onRestored: ({ members }) => log.info(`Memory is fine again: the bot remembers up to ${members} members per server again.`),
497
+ onFull: ({ used, limit }, freed) => log.warn(`The bot uses ${megabytes(used)} of the ${megabytes(limit)} of memory it can use, and already remembers as little as it can: ${current()?.ctx.cache.guilds.size ?? 0} servers and ${current()?.ctx.cache.channels.size ?? 0} channels, which it needs. Remembering less freed ${megabytes(freed)}. If your own code keeps things that only grow (a list, a Map), that is what fills it: look there first. Otherwise give the bot more memory: set NODE_OPTIONS=--max-old-space-size=4096 (in MB) on a machine that has that much.`),
498
+ }),
499
+ };
500
+ }
501
+ /** Says what went wrong in words the developer can act on. */
502
+ export function explain(error, log) {
503
+ if (error instanceof PreflightFailure)
504
+ return;
505
+ if (error instanceof InvalidTokenError ||
506
+ error instanceof GatewayFatalError ||
507
+ error instanceof SessionLimitError ||
508
+ error instanceof DiscordUnavailableError) {
509
+ log.error(error.message);
510
+ }
511
+ else if (error instanceof DiscordApiError) {
512
+ log.error(`Discord refused a request the framework needs to start: ${error.message}`);
513
+ }
514
+ else {
515
+ log.error(`Something unexpected happened: ${messageOf(error)}\nThis is probably a bug in ChapterJS: please report it at https://github.com/chapterjs/chapterjs/issues`);
516
+ }
517
+ }
@@ -0,0 +1,25 @@
1
+ import { type Assignment } from './cluster.js';
2
+ import type { Log } from './log.js';
3
+ export interface StartOptions {
4
+ /** The folder of the project. */
5
+ cwd: string;
6
+ env: Record<string, string | undefined>;
7
+ /** The version of the framework. */
8
+ version: string;
9
+ log: Log;
10
+ /** Aborted when the bot is asked to stop (Ctrl+C, the host). */
11
+ signal: AbortSignal;
12
+ /** Shows a line printed by another process of the bot. */
13
+ write: (line: string) => void;
14
+ /** How many processes to use, when the developer says so. */
15
+ processes?: number | undefined;
16
+ /** The share of this process, when another one started it. */
17
+ assignment?: Assignment | null;
18
+ /** How the other processes are started: this command again. */
19
+ script: string;
20
+ args: readonly string[];
21
+ /** Only tests change this. */
22
+ deferAfter?: number;
23
+ }
24
+ /** Runs until stopped. Resolves with the exit code of the process. */
25
+ export declare function start(options: StartOptions): Promise<number>;