chapterjs 0.1.2 → 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.
Files changed (320) 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 +206 -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 +252 -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 +122 -0
  25. package/dist/cli/project.js +452 -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 +346 -0
  31. package/dist/commands/command.js +28 -0
  32. package/dist/commands/convention.d.ts +64 -0
  33. package/dist/commands/convention.js +464 -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 +43 -0
  39. package/dist/commands/tree.js +206 -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 +108 -0
  44. package/dist/components/component.d.ts +140 -0
  45. package/dist/components/component.js +39 -0
  46. package/dist/components/convention.d.ts +35 -0
  47. package/dist/components/convention.js +339 -0
  48. package/dist/components/custom-id.d.ts +72 -0
  49. package/dist/components/custom-id.js +128 -0
  50. package/dist/components/embed.d.ts +28 -0
  51. package/dist/components/embed.js +11 -0
  52. package/dist/components/file.d.ts +30 -0
  53. package/dist/components/file.js +52 -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 +244 -0
  59. package/dist/components/modal.js +399 -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 +18 -0
  65. package/dist/components/router.js +256 -0
  66. package/dist/components/select.d.ts +144 -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/convention.d.ts +25 -0
  155. package/dist/events/convention.js +97 -0
  156. package/dist/events/event.d.ts +8 -0
  157. package/dist/events/event.js +20 -0
  158. package/dist/events/registry.d.ts +706 -0
  159. package/dist/events/registry.js +839 -0
  160. package/dist/events/router.d.ts +45 -0
  161. package/dist/events/router.js +133 -0
  162. package/dist/events/types.d.ts +7 -0
  163. package/dist/events/types.js +48 -0
  164. package/dist/gateway/errors.d.ts +18 -0
  165. package/dist/gateway/errors.js +47 -0
  166. package/dist/gateway/gateway.d.ts +64 -0
  167. package/dist/gateway/gateway.js +117 -0
  168. package/dist/gateway/identify-queue.d.ts +25 -0
  169. package/dist/gateway/identify-queue.js +29 -0
  170. package/dist/gateway/shard.d.ts +94 -0
  171. package/dist/gateway/shard.js +414 -0
  172. package/dist/gateway/state.d.ts +11 -0
  173. package/dist/gateway/state.js +184 -0
  174. package/dist/index.d.ts +55 -18
  175. package/dist/index.js +38 -12
  176. package/dist/interactions/cooldown.d.ts +33 -0
  177. package/dist/interactions/cooldown.js +74 -0
  178. package/dist/interactions/dispatch.d.ts +91 -0
  179. package/dist/interactions/dispatch.js +110 -0
  180. package/dist/loader/generated.d.ts +37 -0
  181. package/dist/loader/generated.js +114 -0
  182. package/dist/loader/hot.d.ts +13 -0
  183. package/dist/loader/hot.js +85 -0
  184. package/dist/loader/loader.d.ts +72 -0
  185. package/dist/loader/loader.js +135 -0
  186. package/dist/loader/locate.d.ts +10 -0
  187. package/dist/loader/locate.js +36 -0
  188. package/dist/loader/watch.d.ts +17 -0
  189. package/dist/loader/watch.js +66 -0
  190. package/dist/main.d.ts +3 -0
  191. package/dist/main.js +6 -0
  192. package/dist/messages/commands.d.ts +23 -0
  193. package/dist/messages/commands.js +123 -0
  194. package/dist/messages/convention.d.ts +89 -0
  195. package/dist/messages/convention.js +347 -0
  196. package/dist/messages/messages.d.ts +219 -0
  197. package/dist/messages/messages.js +30 -0
  198. package/dist/messages/phrases.d.ts +23 -0
  199. package/dist/messages/phrases.js +44 -0
  200. package/dist/messages/translate.d.ts +100 -0
  201. package/dist/messages/translate.js +249 -0
  202. package/dist/presence/convention.d.ts +26 -0
  203. package/dist/presence/convention.js +133 -0
  204. package/dist/presence/presence.d.ts +57 -0
  205. package/dist/presence/presence.js +27 -0
  206. package/dist/rest/errors.d.ts +55 -0
  207. package/dist/rest/errors.js +92 -0
  208. package/dist/rest/rate-limiter.d.ts +46 -0
  209. package/dist/rest/rate-limiter.js +153 -0
  210. package/dist/rest/rest.d.ts +63 -0
  211. package/dist/rest/rest.js +194 -0
  212. package/dist/structures/base.d.ts +48 -0
  213. package/dist/structures/base.js +84 -0
  214. package/dist/structures/channel.d.ts +380 -0
  215. package/dist/structures/channel.js +627 -0
  216. package/dist/structures/context.d.ts +30 -0
  217. package/dist/structures/context.js +7 -0
  218. package/dist/structures/emoji.d.ts +46 -0
  219. package/dist/structures/emoji.js +65 -0
  220. package/dist/structures/entities.d.ts +62 -0
  221. package/dist/structures/entities.js +226 -0
  222. package/dist/structures/guild.d.ts +348 -0
  223. package/dist/structures/guild.js +466 -0
  224. package/dist/structures/interaction.d.ts +197 -0
  225. package/dist/structures/interaction.js +396 -0
  226. package/dist/structures/invite.d.ts +38 -0
  227. package/dist/structures/invite.js +57 -0
  228. package/dist/structures/known.d.ts +28 -0
  229. package/dist/structures/known.js +58 -0
  230. package/dist/structures/member.d.ts +140 -0
  231. package/dist/structures/member.js +260 -0
  232. package/dist/structures/message.d.ts +197 -0
  233. package/dist/structures/message.js +268 -0
  234. package/dist/structures/payload.d.ts +91 -0
  235. package/dist/structures/payload.js +184 -0
  236. package/dist/structures/role.d.ts +69 -0
  237. package/dist/structures/role.js +112 -0
  238. package/dist/structures/user.d.ts +59 -0
  239. package/dist/structures/user.js +94 -0
  240. package/dist/structures/webhook.d.ts +46 -0
  241. package/dist/structures/webhook.js +79 -0
  242. package/dist/tasks/convention.d.ts +26 -0
  243. package/dist/tasks/convention.js +78 -0
  244. package/dist/tasks/schedule.d.ts +54 -0
  245. package/dist/tasks/schedule.js +236 -0
  246. package/dist/tasks/scheduler.d.ts +33 -0
  247. package/dist/tasks/scheduler.js +107 -0
  248. package/dist/tasks/task.d.ts +76 -0
  249. package/dist/tasks/task.js +28 -0
  250. package/dist/util/case.d.ts +13 -0
  251. package/dist/util/case.js +54 -0
  252. package/dist/util/duration.d.ts +8 -0
  253. package/dist/util/duration.js +29 -0
  254. package/dist/util/websocket.d.ts +9 -0
  255. package/dist/util/websocket.js +31 -0
  256. package/dist/voice/connection.d.ts +90 -0
  257. package/dist/voice/connection.js +494 -0
  258. package/dist/voice/dave.d.ts +109 -0
  259. package/dist/voice/dave.js +245 -0
  260. package/dist/voice/opus.d.ts +19 -0
  261. package/dist/voice/opus.js +273 -0
  262. package/dist/voice/session.d.ts +55 -0
  263. package/dist/voice/session.js +405 -0
  264. package/dist/voice/source.d.ts +22 -0
  265. package/dist/voice/source.js +195 -0
  266. package/dist/voice/transport.d.ts +43 -0
  267. package/dist/voice/transport.js +140 -0
  268. package/dist/voice/usage.d.ts +4 -0
  269. package/dist/voice/usage.js +33 -0
  270. package/package.json +26 -7
  271. package/vendor/dave/LICENSE +231 -0
  272. package/vendor/dave/libdave.d.mts +99 -0
  273. package/vendor/dave/libdave.mjs +2 -0
  274. package/vendor/dave/libdave.wasm +0 -0
  275. package/dist/components.d.ts +0 -97
  276. package/dist/components.js +0 -260
  277. package/dist/context.d.ts +0 -72
  278. package/dist/context.js +0 -273
  279. package/dist/discord/client.d.ts +0 -35
  280. package/dist/discord/client.js +0 -66
  281. package/dist/discord/constants.d.ts +0 -49
  282. package/dist/discord/constants.js +0 -61
  283. package/dist/discord/events.d.ts +0 -129
  284. package/dist/discord/events.js +0 -102
  285. package/dist/discord/gateway.d.ts +0 -61
  286. package/dist/discord/gateway.js +0 -320
  287. package/dist/discord/payloads.d.ts +0 -275
  288. package/dist/discord/payloads.js +0 -3
  289. package/dist/discord/presence.d.ts +0 -42
  290. package/dist/discord/presence.js +0 -51
  291. package/dist/discord/rest.d.ts +0 -49
  292. package/dist/discord/rest.js +0 -271
  293. package/dist/discord/types.d.ts +0 -334
  294. package/dist/discord/types.js +0 -4
  295. package/dist/events.d.ts +0 -94
  296. package/dist/events.js +0 -255
  297. package/dist/presence.d.ts +0 -92
  298. package/dist/presence.js +0 -250
  299. package/dist/structures/Channel.d.ts +0 -110
  300. package/dist/structures/Channel.js +0 -171
  301. package/dist/structures/Guild.d.ts +0 -70
  302. package/dist/structures/Guild.js +0 -105
  303. package/dist/structures/Interaction.d.ts +0 -113
  304. package/dist/structures/Interaction.js +0 -274
  305. package/dist/structures/Member.d.ts +0 -70
  306. package/dist/structures/Member.js +0 -124
  307. package/dist/structures/Message.d.ts +0 -87
  308. package/dist/structures/Message.js +0 -182
  309. package/dist/structures/Reaction.d.ts +0 -38
  310. package/dist/structures/Reaction.js +0 -64
  311. package/dist/structures/Role.d.ts +0 -43
  312. package/dist/structures/Role.js +0 -77
  313. package/dist/structures/User.d.ts +0 -41
  314. package/dist/structures/User.js +0 -79
  315. package/dist/structures/utils.d.ts +0 -72
  316. package/dist/structures/utils.js +0 -59
  317. package/dist/utils/errors.d.ts +0 -5
  318. package/dist/utils/errors.js +0 -28
  319. package/dist/utils/modules.d.ts +0 -39
  320. package/dist/utils/modules.js +0 -156
@@ -0,0 +1,452 @@
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 { eventTypedFolders } from '../events/types.js';
10
+ import { MissingForEvent } from '../events/registry.js';
11
+ import { sourcesJoinVoice } from '../voice/usage.js';
12
+ import { writeGenerated } from '../loader/generated.js';
13
+ import { commandsConvention } from '../commands/convention.js';
14
+ import { CommandRouter } from '../commands/router.js';
15
+ import { findConflicts } from '../commands/tree.js';
16
+ import { componentsConvention, findDuplicates, } from '../components/convention.js';
17
+ import { ComponentRouter } from '../components/router.js';
18
+ import { tasksConvention } from '../tasks/convention.js';
19
+ import { DEFAULT_PRESENCE, presenceConvention, } from '../presence/convention.js';
20
+ import { assembleMessages, commandsDeclarations, languagesConvention, languageTypedFolders, messagesDeclarations, } from '../messages/convention.js';
21
+ import { translation } from '../messages/translate.js';
22
+ import { translateCommand, unknownCommands } from '../messages/commands.js';
23
+ import { TimerScheduler, } from '../tasks/scheduler.js';
24
+ import { createBot } from '../core/bot.js';
25
+ import { watchMemory } from '../core/memory.js';
26
+ import { GatewayIntent } from '../discord/intents.js';
27
+ import { eventsConvention } from '../events/convention.js';
28
+ import { EventRouter, intentsFor, intentsOf, limitsFor, } from '../events/router.js';
29
+ import { GatewayFatalError, SessionLimitError } from '../gateway/errors.js';
30
+ import { listFolder, loadBuilt, loadFolder, } from '../loader/loader.js';
31
+ import { locate, messageOf } from '../loader/locate.js';
32
+ import { DiscordApiError, DiscordUnavailableError, InvalidTokenError, } from '../rest/errors.js';
33
+ import { PreflightFailure } from './preflight.js';
34
+ /**
35
+ * Writes the types of a project (`.chapterjs/`): one project per typed
36
+ * folder (every event, every language file), and what every file gets,
37
+ * like the files of `public/`. With the commands and the languages as
38
+ * they loaded, a language file only offers the ones without `description`
39
+ * in their file, `t` is typed from the default language and `default:
40
+ * true` is refused in every file when another one says it (`defaults`,
41
+ * the files that do); without them (`sync`, which runs nothing),
42
+ * every command is offered and the first language file types `t`.
43
+ * @returns how many files were written
44
+ */
45
+ export async function writeTypes(cwd, commands, messages, defaults = []) {
46
+ const [files, languageFiles, commandFiles] = await Promise.all([
47
+ listPublic(cwd),
48
+ listFolder(cwd, languagesConvention),
49
+ listFolder(cwd, commandsConvention),
50
+ ]);
51
+ return writeGenerated(cwd, [...eventTypedFolders(), ...languageTypedFolders(languageFiles, defaults)], publicDeclarations(files) +
52
+ messagesDeclarations(languageFiles, messages ? messages.files.get(messages.default) : undefined),
53
+ // Only language files use it, and they are in the main project.
54
+ commandsDeclarations(commandFiles.map(({ file }) => ({
55
+ file,
56
+ described: commands?.get(file)?.command.described ?? false,
57
+ }))));
58
+ }
59
+ /** The conventional folders of a project: one per feature. */
60
+ export const CONVENTIONS = [
61
+ eventsConvention,
62
+ commandsConvention,
63
+ componentsConvention,
64
+ tasksConvention,
65
+ presenceConvention,
66
+ languagesConvention,
67
+ ];
68
+ /** Whether the folder is a project: it has a `src` folder. */
69
+ export async function hasSources(cwd, log) {
70
+ if ((await stat(join(cwd, 'src')).catch(() => null))?.isDirectory()) {
71
+ return true;
72
+ }
73
+ 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".');
74
+ return false;
75
+ }
76
+ /** The intents that only bring what happens in private messages. */
77
+ const PRIVATE_INTENTS = GatewayIntent.DirectMessages |
78
+ GatewayIntent.DirectMessageReactions |
79
+ GatewayIntent.DirectMessageTyping |
80
+ GatewayIntent.DirectMessagePolls;
81
+ const megabytes = (bytes) => `${Math.round(bytes / 1024 / 1024)} MB`;
82
+ export function createProject(options) {
83
+ const { cwd, log } = options;
84
+ // Where `asset()` reads the files of the project from.
85
+ setPublicDir(cwd);
86
+ const report = ({ file, error }) => {
87
+ const where = locate(error, cwd);
88
+ log.error(`${where ? `${where.file}:${where.line}` : file} ${messageOf(error)}`);
89
+ };
90
+ const router = new EventRouter((file, error) => report({ file, error }), (event, error) => {
91
+ const missing = error instanceof MissingForEvent ? error : null;
92
+ const what = missing?.what === 'user'
93
+ ? 'the user who did it'
94
+ : 'the channel it happened in';
95
+ log.warn(`A ${event} event was not given to your files: Discord did not let the bot read ${what} (${messageOf(missing ? missing.cause : error)}).`);
96
+ });
97
+ const reporter = {
98
+ onError: (file, error) => report({ file, error }),
99
+ onWarning: (file, message) => log.warn(`${file} ${message}`),
100
+ ...(options.deferAfter === undefined
101
+ ? {}
102
+ : { deferAfter: options.deferAfter }),
103
+ };
104
+ const commandRouter = new CommandRouter(reporter);
105
+ const componentRouter = new ComponentRouter(reporter);
106
+ const scheduler = new TimerScheduler(reporter);
107
+ let events = new Map();
108
+ let commands = new Map();
109
+ let components = new Map();
110
+ let tasks = new Map();
111
+ let presence = null;
112
+ // Joining voice needs the bot's own voice state, sent with an intent.
113
+ let voice = options.joinsVoice ?? false;
114
+ const neededIntents = () => intentsFor(events.values()) | (voice ? GatewayIntent.GuildVoiceStates : 0);
115
+ let messages = null;
116
+ /** The last version of each language file that loaded. */
117
+ let languages = new Map();
118
+ let languageDefaults = [];
119
+ /** The bot running the files now, to give it what a reload changes. */
120
+ let running = null;
121
+ /** The presence each bot was given last, to send only what changed. */
122
+ const given = new WeakMap();
123
+ let lost = false;
124
+ const count = (size, one, many) => `${size} ${size === 1 ? one : many}`;
125
+ return {
126
+ get events() {
127
+ return events;
128
+ },
129
+ get commands() {
130
+ return commands;
131
+ },
132
+ get components() {
133
+ return components;
134
+ },
135
+ get tasks() {
136
+ return tasks;
137
+ },
138
+ get presence() {
139
+ return presence;
140
+ },
141
+ get messages() {
142
+ return messages;
143
+ },
144
+ get languageDefaults() {
145
+ return languageDefaults;
146
+ },
147
+ report,
148
+ async load() {
149
+ const { built } = options;
150
+ if (options.joinsVoice === undefined) {
151
+ voice = await sourcesJoinVoice(cwd);
152
+ }
153
+ const [eventFiles, commandFiles, componentFiles, taskFiles, presenceFiles, messageFiles,] = await Promise.all(built
154
+ ? [
155
+ loadBuilt(eventsConvention, built),
156
+ loadBuilt(commandsConvention, built),
157
+ loadBuilt(componentsConvention, built),
158
+ loadBuilt(tasksConvention, built),
159
+ loadBuilt(presenceConvention, built),
160
+ loadBuilt(languagesConvention, built),
161
+ ]
162
+ : [
163
+ loadFolder(cwd, eventsConvention),
164
+ loadFolder(cwd, commandsConvention),
165
+ loadFolder(cwd, componentsConvention),
166
+ loadFolder(cwd, tasksConvention),
167
+ loadFolder(cwd, presenceConvention),
168
+ loadFolder(cwd, languagesConvention),
169
+ ]);
170
+ const nextEvents = new Map();
171
+ for (const { file, value } of eventFiles.loaded) {
172
+ nextEvents.set(file, { file, event: value });
173
+ }
174
+ for (const { file } of eventFiles.failed) {
175
+ const previous = events.get(file);
176
+ if (previous)
177
+ nextEvents.set(file, previous);
178
+ }
179
+ events = nextEvents;
180
+ router.set(events.values());
181
+ const entries = commandFiles.loaded.map(({ file, value }) => ({ file, command: value }));
182
+ for (const { file } of commandFiles.failed) {
183
+ const previous = commands.get(file);
184
+ if (previous)
185
+ entries.push(previous);
186
+ }
187
+ const { valid, conflicts } = findConflicts(entries);
188
+ commands = new Map(valid.map(entry => [entry.file, entry]));
189
+ const pieces = componentFiles.loaded.map(({ file, value }) => ({ file, component: value }));
190
+ for (const { file } of componentFiles.failed) {
191
+ const previous = components.get(file);
192
+ if (previous)
193
+ pieces.push(previous);
194
+ }
195
+ const unique = findDuplicates(pieces);
196
+ components = new Map(unique.valid.map(entry => [entry.file, entry]));
197
+ componentRouter.set(components.values());
198
+ const nextTasks = new Map();
199
+ for (const { file, value } of taskFiles.loaded) {
200
+ nextTasks.set(file, { file, task: value });
201
+ }
202
+ for (const { file } of taskFiles.failed) {
203
+ const previous = tasks.get(file);
204
+ if (previous)
205
+ nextTasks.set(file, previous);
206
+ }
207
+ tasks = nextTasks;
208
+ scheduler.set(tasks.values());
209
+ // One presence file. A second one (another extension) is left out.
210
+ const [first, ...extra] = presenceFiles.loaded;
211
+ if (first)
212
+ presence = first.value;
213
+ else if (presenceFiles.failed.length === 0)
214
+ presence = null;
215
+ const twice = extra.map(({ file }) => ({
216
+ file,
217
+ error: new TypeError(`There are two presence files: ${first.file} is used, keep only one.`),
218
+ }));
219
+ const nextLanguages = new Map();
220
+ for (const { file, value } of messageFiles.loaded) {
221
+ nextLanguages.set(file, { file, language: value });
222
+ }
223
+ for (const { file } of messageFiles.failed) {
224
+ const previous = languages.get(file);
225
+ if (previous)
226
+ nextLanguages.set(file, previous);
227
+ }
228
+ // What the files say now, before a file is put back to its last good
229
+ // version: two files saying default: true are both underlined.
230
+ languageDefaults = [...nextLanguages.values()]
231
+ .filter(({ language }) => language.isDefault)
232
+ .map(({ file }) => file);
233
+ const previousLanguages = languages;
234
+ languages = nextLanguages;
235
+ let assembled = assembleMessages([...languages.values()]);
236
+ // A language that no longer matches the others keeps its last version
237
+ // that did, like a file that no longer loads.
238
+ const restored = assembled.failed.filter(({ file }) => {
239
+ const previous = previousLanguages.get(file);
240
+ return previous !== undefined && previous !== languages.get(file);
241
+ });
242
+ if (restored.length > 0) {
243
+ for (const { file } of restored) {
244
+ languages.set(file, previousLanguages.get(file));
245
+ }
246
+ const again = assembleMessages([...languages.values()]);
247
+ assembled = {
248
+ messages: again.messages,
249
+ failed: [
250
+ ...assembled.failed,
251
+ ...again.failed.filter(({ file }) => !assembled.failed.some(one => one.file === file)),
252
+ ],
253
+ };
254
+ }
255
+ messages = assembled.messages;
256
+ if (running)
257
+ running.ctx.messages = messages;
258
+ // A command has its texts in its file, or in the language files: the
259
+ // languages give them to the ones without, and a command that can't
260
+ // be described is left out.
261
+ const badTexts = [...assembled.failed];
262
+ if (messages) {
263
+ const paths = new Set([...commands.values()].map(entry => entry.command.path.join('/')));
264
+ for (const { file, path } of unknownCommands(messages, paths)) {
265
+ badTexts.push({
266
+ file,
267
+ error: new TypeError(`"commands" translates "${path}", which is not a command of this project${paths.size > 0 ? ` (its commands are: ${[...paths].join(', ')})` : ''}. The key is the path of the command file, like 'ping' or 'mod/ban'.`),
268
+ });
269
+ }
270
+ }
271
+ for (const [file, entry] of commands) {
272
+ const { command, failed } = translateCommand(file, entry.command, messages);
273
+ badTexts.push(...failed);
274
+ if (command)
275
+ commands.set(file, { ...entry, command });
276
+ else
277
+ commands.delete(file);
278
+ }
279
+ commandRouter.set(commands.values());
280
+ return [
281
+ ...eventFiles.failed,
282
+ ...commandFiles.failed,
283
+ ...componentFiles.failed,
284
+ ...taskFiles.failed,
285
+ ...presenceFiles.failed,
286
+ ...messageFiles.failed,
287
+ ...badTexts,
288
+ ...twice,
289
+ ...[...conflicts, ...unique.conflicts].map(({ file, message }) => ({
290
+ file,
291
+ error: new TypeError(message),
292
+ })),
293
+ ];
294
+ },
295
+ summary() {
296
+ if (events.size === 0 &&
297
+ commands.size === 0 &&
298
+ components.size === 0 &&
299
+ tasks.size === 0 &&
300
+ presence === null &&
301
+ messages === null) {
302
+ return 'Nothing to run yet: add a file in src/commands/ or in a folder like src/events/messageCreate/';
303
+ }
304
+ return `${[
305
+ ...(commands.size > 0
306
+ ? [count(commands.size, 'command', 'commands')]
307
+ : []),
308
+ ...(events.size > 0 ? [count(events.size, 'event', 'events')] : []),
309
+ ...(components.size > 0
310
+ ? [count(components.size, 'component', 'components')]
311
+ : []),
312
+ ...(tasks.size > 0 ? [count(tasks.size, 'task', 'tasks')] : []),
313
+ ...(presence ? ['a presence'] : []),
314
+ ...(messages
315
+ ? [
316
+ `messages in ${count(messages.locales.size, 'language', 'languages')}`,
317
+ ]
318
+ : []),
319
+ ].join(', ')} loaded`;
320
+ },
321
+ isEmpty: () => events.size === 0 &&
322
+ commands.size === 0 &&
323
+ components.size === 0 &&
324
+ tasks.size === 0 &&
325
+ presence === null &&
326
+ messages === null,
327
+ filesNeeding: intent => [...events.values()]
328
+ .filter(({ event }) => (intentsOf(event) & GatewayIntent[intent]) !== 0)
329
+ .map(({ file }) => file),
330
+ intents: (privateEvents = true) => neededIntents() & (privateEvents ? ~0 : ~PRIVATE_INTENTS),
331
+ async connect(connection) {
332
+ const created = createBot({
333
+ token: connection.token,
334
+ version: options.version,
335
+ intents: neededIntents() &
336
+ (connection.privateEvents === false ? ~PRIVATE_INTENTS : ~0),
337
+ guildFilter: connection.guildFilter,
338
+ privateEvents: connection.privateEvents,
339
+ presence: presence?.raw,
340
+ shards: connection.shards,
341
+ identifyGate: connection.identifyGate,
342
+ ...(connection.identifyInterval === undefined
343
+ ? {}
344
+ : { gateway: { identifyInterval: connection.identifyInterval } }),
345
+ // Only what the files of the project use is remembered.
346
+ cache: { limits: limitsFor(events.values()) },
347
+ // A local fake of Discord (tests) serves voice without TLS too.
348
+ voice: { secure: !connection.apiUrl?.startsWith('http://') },
349
+ rest: {
350
+ ...(connection.apiUrl ? { baseUrl: connection.apiUrl } : {}),
351
+ ...(connection.globalLimit === undefined
352
+ ? {}
353
+ : { globalLimit: connection.globalLimit }),
354
+ },
355
+ beforeDispatch: (event, data) => router.before(created.ctx, event, data),
356
+ onDispatch(event, data, info) {
357
+ router.dispatch(created.ctx, event, data, {
358
+ joined: info.joined,
359
+ before: info.before,
360
+ });
361
+ if (event === 'INTERACTION_CREATE') {
362
+ const interaction = data;
363
+ commandRouter.dispatch(created.ctx, interaction);
364
+ componentRouter.dispatch(created.ctx, interaction);
365
+ }
366
+ },
367
+ onEvent(event) {
368
+ if (event.type === 'disconnected' && !lost) {
369
+ lost = true;
370
+ log.warn('Connection to Discord lost, reconnecting...');
371
+ }
372
+ else if (event.type === 'ready' && lost) {
373
+ lost = false;
374
+ log.reload('Reconnected to Discord');
375
+ }
376
+ else if (event.type === 'fatal') {
377
+ log.error(event.error.message);
378
+ connection.onFatal(created);
379
+ }
380
+ else if (event.type === 'stateError') {
381
+ log.warn(`An event of Discord (${event.event}) could not be read: ${messageOf(event.error)}`);
382
+ }
383
+ else if (event.type === 'voiceWarning') {
384
+ log.warn(`Voice: ${event.message}`);
385
+ }
386
+ },
387
+ });
388
+ given.set(created, presence?.key ?? '');
389
+ created.ctx.messages = messages;
390
+ running = created;
391
+ await created.connect();
392
+ return created;
393
+ },
394
+ applyPresence(bot) {
395
+ const key = presence?.key ?? '';
396
+ if (given.get(bot) === key)
397
+ return false;
398
+ given.set(bot, key);
399
+ bot.setPresence(presence?.raw ?? DEFAULT_PRESENCE);
400
+ return true;
401
+ },
402
+ ready(bot) {
403
+ const user = bot.ctx.cache.users.get(bot.ctx.self.userId);
404
+ router.emit('ready', {
405
+ user,
406
+ guilds: bot.ctx.cache.guilds,
407
+ ...translation(bot.ctx, null),
408
+ });
409
+ },
410
+ startTasks(current) {
411
+ scheduler.start(() => {
412
+ const bot = current();
413
+ if (!bot?.ctx.self)
414
+ return null;
415
+ const user = bot.ctx.cache.users.get(bot.ctx.self.userId);
416
+ return user
417
+ ? {
418
+ user,
419
+ guilds: bot.ctx.cache.guilds,
420
+ ...translation(bot.ctx, null),
421
+ }
422
+ : null;
423
+ });
424
+ },
425
+ stopTasks: () => scheduler.stop(),
426
+ // The bot looks after its own memory: it remembers less before memory
427
+ // is full, and only speaks up when it can't give up anything more.
428
+ watchMemory: current => watchMemory({
429
+ cache: () => current()?.ctx.cache,
430
+ 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.`),
431
+ onRestored: ({ members }) => log.info(`Memory is fine again: the bot remembers up to ${members} members per server again.`),
432
+ 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.`),
433
+ }),
434
+ };
435
+ }
436
+ /** Says what went wrong in words the developer can act on. */
437
+ export function explain(error, log) {
438
+ if (error instanceof PreflightFailure)
439
+ return;
440
+ if (error instanceof InvalidTokenError ||
441
+ error instanceof GatewayFatalError ||
442
+ error instanceof SessionLimitError ||
443
+ error instanceof DiscordUnavailableError) {
444
+ log.error(error.message);
445
+ }
446
+ else if (error instanceof DiscordApiError) {
447
+ log.error(`Discord refused a request the framework needs to start: ${error.message}`);
448
+ }
449
+ else {
450
+ log.error(`Something unexpected happened: ${messageOf(error)}\nThis is probably a bug in ChapterJS: please report it at https://github.com/chapterjs/chapterjs/issues`);
451
+ }
452
+ }
@@ -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>;