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,46 @@
1
+ import { type CacheLimits } from '../cache/cache.js';
2
+ import type { GatewayDispatchEventName, GatewayDispatchEvents } from '../discord/types/gateway-events.js';
3
+ import type { Context } from '../structures/context.js';
4
+ import type { EventHandler } from './declaration.js';
5
+ import { type EventContexts, type EventName } from './registry.js';
6
+ /** A handler, with the file and the export it comes from (to report its errors). */
7
+ export interface LoadedEvent {
8
+ file: string;
9
+ export: string;
10
+ event: EventHandler;
11
+ }
12
+ /**
13
+ * The intents the bot needs: Guilds, which keeps the cache of servers,
14
+ * roles and channels up to date, plus the ones of the events listened to
15
+ * and of the options their files turned on.
16
+ */
17
+ export declare function intentsFor(events: Iterable<LoadedEvent>): number;
18
+ /**
19
+ * How much the bot remembers for a set of files: the default, raised by
20
+ * the events that need more to give all they can.
21
+ */
22
+ export declare function limitsFor(events: Iterable<LoadedEvent>): CacheLimits;
23
+ /** The intents one file needs, given the options it passed. */
24
+ export declare function intentsOf(event: EventHandler): number;
25
+ export declare class EventRouter {
26
+ #private;
27
+ /**
28
+ * `onError`: a handler threw. `onSkipped`: an event could not be given
29
+ * to its handlers, because Discord refused what they are promised.
30
+ */
31
+ constructor(onError: (file: string, error: unknown) => void, onSkipped?: (event: EventName, error: unknown) => void);
32
+ /** Replaces every handler (after a load or a reload). */
33
+ set(events: Iterable<LoadedEvent>): void;
34
+ /**
35
+ * Reads what the event is about to remove from the cache. Returns what
36
+ * `dispatch` needs back; nothing is read for events nobody listens to.
37
+ */
38
+ before<E extends GatewayDispatchEventName>(ctx: Context, event: E, data: GatewayDispatchEvents[E]): unknown[] | undefined;
39
+ /** Runs the handlers of a gateway event, once the cache reflects it. */
40
+ dispatch<E extends GatewayDispatchEventName>(ctx: Context, event: E, data: GatewayDispatchEvents[E], extra: {
41
+ joined: boolean;
42
+ before: unknown[] | undefined;
43
+ }): void;
44
+ /** Runs the handlers of an event with what they receive. */
45
+ emit<Name extends EventName>(name: Name, context: EventContexts[Name]): void;
46
+ }
@@ -0,0 +1,133 @@
1
+ // Delivers gateway events to the handlers of the project.
2
+ import { DEFAULT_CACHE_LIMITS } from '../cache/cache.js';
3
+ import { GatewayIntent } from '../discord/intents.js';
4
+ import { audienceLocale, translation } from '../messages/translate.js';
5
+ import { EVENTS } from './registry.js';
6
+ /**
7
+ * The intents the bot needs: Guilds, which keeps the cache of servers,
8
+ * roles and channels up to date, plus the ones of the events listened to
9
+ * and of the options their files turned on.
10
+ */
11
+ export function intentsFor(events) {
12
+ let intents = GatewayIntent.Guilds;
13
+ for (const { event } of events)
14
+ intents |= intentsOf(event);
15
+ return intents;
16
+ }
17
+ /**
18
+ * How much the bot remembers for a set of files: the default, raised by
19
+ * the events that need more to give all they can.
20
+ */
21
+ export function limitsFor(events) {
22
+ const limits = { ...DEFAULT_CACHE_LIMITS };
23
+ for (const { event } of events) {
24
+ const needed = EVENTS[event.name].remembers ?? {};
25
+ for (const kind of Object.keys(needed)) {
26
+ limits[kind] = Math.max(limits[kind], needed[kind]);
27
+ }
28
+ }
29
+ return limits;
30
+ }
31
+ /** The intents one file needs, given the options it passed. */
32
+ export function intentsOf(event) {
33
+ const { intents } = EVENTS[event.name];
34
+ return typeof intents === 'function'
35
+ ? intents(event.options)
36
+ : intents;
37
+ }
38
+ /** For each gateway event, the events it may turn into. */
39
+ const BY_GATEWAY_EVENT = new Map();
40
+ for (const [name, definition] of Object.entries(EVENTS)) {
41
+ for (const source of definition.sources) {
42
+ const list = BY_GATEWAY_EVENT.get(source.on) ?? [];
43
+ list.push({ name: name, ...source });
44
+ BY_GATEWAY_EVENT.set(source.on, list);
45
+ }
46
+ }
47
+ export class EventRouter {
48
+ #handlers = new Map();
49
+ #onError;
50
+ #onSkipped;
51
+ /**
52
+ * `onError`: a handler threw. `onSkipped`: an event could not be given
53
+ * to its handlers, because Discord refused what they are promised.
54
+ */
55
+ constructor(onError, onSkipped = () => { }) {
56
+ this.#onError = onError;
57
+ this.#onSkipped = onSkipped;
58
+ }
59
+ /** Replaces every handler (after a load or a reload). */
60
+ set(events) {
61
+ const handlers = new Map();
62
+ for (const loaded of events) {
63
+ const list = handlers.get(loaded.event.name) ?? [];
64
+ list.push(loaded);
65
+ handlers.set(loaded.event.name, list);
66
+ }
67
+ this.#handlers = handlers;
68
+ }
69
+ /**
70
+ * Reads what the event is about to remove from the cache. Returns what
71
+ * `dispatch` needs back; nothing is read for events nobody listens to.
72
+ */
73
+ before(ctx, event, data) {
74
+ const sources = BY_GATEWAY_EVENT.get(event);
75
+ if (!sources)
76
+ return undefined;
77
+ return sources.map(source => source.before && this.#handlers.has(source.name)
78
+ ? source.before(ctx, data)
79
+ : undefined);
80
+ }
81
+ /** Runs the handlers of a gateway event, once the cache reflects it. */
82
+ dispatch(ctx, event, data, extra) {
83
+ const sources = BY_GATEWAY_EVENT.get(event);
84
+ if (!sources)
85
+ return;
86
+ sources.forEach((source, index) => {
87
+ if (!this.#handlers.has(source.name))
88
+ return;
89
+ const deliver = (prepared) => {
90
+ const built = source.build(ctx, data, {
91
+ joined: extra.joined,
92
+ before: extra.before?.[index],
93
+ prepared,
94
+ });
95
+ if (!built)
96
+ return;
97
+ // One gateway event can be several occurrences (messages deleted
98
+ // together, emojis changed at once): each is delivered.
99
+ const occurrences = Array.isArray(built)
100
+ ? built
101
+ : [built];
102
+ const guildOf = EVENTS[source.name].guildOf;
103
+ for (const context of occurrences) {
104
+ // `t` speaks the language of the server the event happened in.
105
+ const guild = guildOf?.(context);
106
+ this.emit(source.name, {
107
+ ...context,
108
+ ...translation(ctx, audienceLocale({ guild })),
109
+ });
110
+ }
111
+ };
112
+ const preparing = source.prepare?.(ctx, data);
113
+ if (!preparing)
114
+ return deliver();
115
+ preparing.then(deliver, (error) => this.#onSkipped(source.name, error));
116
+ });
117
+ }
118
+ /** Runs the handlers of an event with what they receive. */
119
+ emit(name, context) {
120
+ const handlers = this.#handlers.get(name);
121
+ if (!handlers)
122
+ return;
123
+ // Handlers can't change what the others receive.
124
+ const frozen = Object.freeze({ ...context });
125
+ const accepts = EVENTS[name].accepts;
126
+ for (const { file, event } of handlers) {
127
+ if (accepts && !accepts(frozen, event.options))
128
+ continue;
129
+ // One handler failing never stops the others, nor the bot.
130
+ new Promise(resolve => resolve(event.handler(frozen))).catch((error) => this.#onError(file, error));
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Discord closed the connection for a reason that reconnecting can't fix:
3
+ * something has to change before the bot can connect.
4
+ * @see https://docs.discord.com/developers/topics/opcodes-and-status-codes#gateway-gateway-close-event-codes
5
+ */
6
+ export declare class GatewayFatalError extends Error {
7
+ readonly name = "GatewayFatalError";
8
+ /** The close code Discord gave. */
9
+ readonly code: number;
10
+ constructor(code: number, intents: number);
11
+ }
12
+ /** The bot started too many sessions today: Discord would reset its token. */
13
+ export declare class SessionLimitError extends Error {
14
+ readonly name = "SessionLimitError";
15
+ /** When the bot can connect again. */
16
+ readonly resetAt: Date;
17
+ constructor(needed: number, remaining: number, resetAfter: number);
18
+ }
@@ -0,0 +1,47 @@
1
+ import { GatewayCloseCode } from '../discord/codes.js';
2
+ import { intentNames, PRIVILEGED_INTENTS } from '../discord/intents.js';
3
+ /**
4
+ * Discord closed the connection for a reason that reconnecting can't fix:
5
+ * something has to change before the bot can connect.
6
+ * @see https://docs.discord.com/developers/topics/opcodes-and-status-codes#gateway-gateway-close-event-codes
7
+ */
8
+ export class GatewayFatalError extends Error {
9
+ name = 'GatewayFatalError';
10
+ /** The close code Discord gave. */
11
+ code;
12
+ constructor(code, intents) {
13
+ super(explain(code, intents));
14
+ this.code = code;
15
+ }
16
+ }
17
+ function explain(code, intents) {
18
+ switch (code) {
19
+ case GatewayCloseCode.AuthenticationFailed:
20
+ return 'Discord refused the bot token. Copy it again from the Developer Portal (your application → Bot → Reset Token) into BOT_TOKEN in your .env file.';
21
+ case GatewayCloseCode.DisallowedIntents: {
22
+ const privileged = intentNames(intents).filter(name => PRIVILEGED_INTENTS.includes(name));
23
+ return `Discord refused the connection: the bot uses privileged intents (${privileged.join(', ') || 'none found'}) that are not enabled. Enable them in the Developer Portal: your application → Bot → Privileged Gateway Intents.`;
24
+ }
25
+ case GatewayCloseCode.InvalidIntents:
26
+ return 'Discord refused the intents sent by the framework. This is a bug in ChapterJS: please report it.';
27
+ case GatewayCloseCode.ShardingRequired:
28
+ return 'The bot is in too many servers for a single connection: it must use more shards.';
29
+ case GatewayCloseCode.InvalidShard:
30
+ return 'Discord refused the shard of this connection: the number of shards is not the one Discord expects for this bot.';
31
+ case GatewayCloseCode.InvalidApiVersion:
32
+ return 'Discord no longer accepts the gateway version this version of ChapterJS speaks: update ChapterJS.';
33
+ default:
34
+ return `Discord closed the connection for good (close code ${code}).`;
35
+ }
36
+ }
37
+ /** The bot started too many sessions today: Discord would reset its token. */
38
+ export class SessionLimitError extends Error {
39
+ name = 'SessionLimitError';
40
+ /** When the bot can connect again. */
41
+ resetAt;
42
+ constructor(needed, remaining, resetAfter) {
43
+ const resetAt = new Date(Date.now() + resetAfter);
44
+ super(`The bot needs to start ${needed} session(s) but Discord only allows ${remaining} more until ${resetAt.toLocaleString()}. Going over that limit makes Discord reset the bot token, so the bot is not started. This usually means it was restarted too many times: wait, then start it again.`);
45
+ this.resetAt = resetAt;
46
+ }
47
+ }
@@ -0,0 +1,64 @@
1
+ import type { GatewayOpcode } from '../discord/codes.js';
2
+ import type { Snowflake } from '../discord/types/common.js';
3
+ import type { RawGatewayPresenceUpdate } from '../discord/types/gateway-events.js';
4
+ import type { Rest } from '../rest/rest.js';
5
+ import { type IdentifyGate } from './identify-queue.js';
6
+ import { Shard, type ShardEvent, type ShardOptions } from './shard.js';
7
+ export interface GatewayOptions {
8
+ rest: Rest;
9
+ token: string;
10
+ intents: number;
11
+ presence?: RawGatewayPresenceUpdate | undefined;
12
+ /**
13
+ * Which shards this process runs, out of how many. By default: all of
14
+ * them, as many as Discord recommends. A layer that spreads shards over
15
+ * several processes gives each process its share.
16
+ */
17
+ shards?: {
18
+ ids: readonly number[];
19
+ count: number;
20
+ };
21
+ /**
22
+ * Decides when a shard may identify. By default: a queue for the shards
23
+ * of this process. Processes sharing a bot must share one gate.
24
+ */
25
+ identifyGate?: IdentifyGate;
26
+ onDispatch: ShardOptions['onDispatch'];
27
+ onEvent?: (event: ShardEvent) => void;
28
+ /** Only tests change these. */
29
+ backoff?: ShardOptions['backoff'];
30
+ identifyInterval?: number;
31
+ }
32
+ /**
33
+ * The shard that receives the events of a server:
34
+ * `(guild_id >> 22) % num_shards`.
35
+ * @see https://docs.discord.com/developers/events/gateway#sharding-sharding-formula
36
+ */
37
+ export declare function shardIdFor(guildId: Snowflake, shardCount: number): number;
38
+ export declare class Gateway {
39
+ #private;
40
+ constructor(options: GatewayOptions);
41
+ /** How many shards the bot is split into, across every process. */
42
+ get shardCount(): number;
43
+ /** The shards this process runs. */
44
+ get shards(): ReadonlyMap<number, Shard>;
45
+ /** The average heartbeat latency of the shards, in ms; `null` at first. */
46
+ get ping(): number | null;
47
+ /**
48
+ * Connects every shard of this process, and resolves once they are all
49
+ * ready. Rejects (and closes them all) if Discord refuses the connection.
50
+ */
51
+ connect(): Promise<void>;
52
+ /**
53
+ * Changes the presence of the bot on every shard of this process: sent
54
+ * now when Discord's limit allows it, and carried by every session
55
+ * started later.
56
+ */
57
+ setPresence(presence: RawGatewayPresenceUpdate): void;
58
+ /** The shard of this process that handles a server, if it runs here. */
59
+ shardFor(guildId: Snowflake): Shard | undefined;
60
+ /** Sends an event to Discord through one shard. */
61
+ send(shardId: number, op: GatewayOpcode, data: unknown): Promise<void>;
62
+ /** Closes every connection: the bot goes offline right away. */
63
+ close(): Promise<void>;
64
+ }
@@ -0,0 +1,117 @@
1
+ // Every connection of the bot to the Discord gateway: how many shards,
2
+ // when each one may identify, and one place to listen to them all.
3
+ // https://docs.discord.com/developers/events/gateway#sharding
4
+ import { GetGatewayBot } from '../discord/endpoints.js';
5
+ import { SessionLimitError } from './errors.js';
6
+ import { IdentifyQueue } from './identify-queue.js';
7
+ import { Shard } from './shard.js';
8
+ /**
9
+ * The shard that receives the events of a server:
10
+ * `(guild_id >> 22) % num_shards`.
11
+ * @see https://docs.discord.com/developers/events/gateway#sharding-sharding-formula
12
+ */
13
+ export function shardIdFor(guildId, shardCount) {
14
+ return Number((BigInt(guildId) >> 22n) % BigInt(shardCount));
15
+ }
16
+ export class Gateway {
17
+ #options;
18
+ #shards = new Map();
19
+ #shardCount = 0;
20
+ #started = false;
21
+ /** The presence every session starts with. */
22
+ #presence;
23
+ constructor(options) {
24
+ this.#options = options;
25
+ this.#presence = options.presence;
26
+ }
27
+ /** How many shards the bot is split into, across every process. */
28
+ get shardCount() {
29
+ return this.#shardCount;
30
+ }
31
+ /** The shards this process runs. */
32
+ get shards() {
33
+ return this.#shards;
34
+ }
35
+ /** The average heartbeat latency of the shards, in ms; `null` at first. */
36
+ get ping() {
37
+ const pings = [...this.#shards.values()]
38
+ .map(shard => shard.ping)
39
+ .filter(ping => ping !== null);
40
+ if (pings.length === 0)
41
+ return null;
42
+ return Math.round(pings.reduce((sum, ping) => sum + ping, 0) / pings.length);
43
+ }
44
+ /**
45
+ * Connects every shard of this process, and resolves once they are all
46
+ * ready. Rejects (and closes them all) if Discord refuses the connection.
47
+ */
48
+ async connect() {
49
+ if (this.#started)
50
+ throw new Error('The gateway was already started.');
51
+ this.#started = true;
52
+ const options = this.#options;
53
+ // Not cached: the recommended number of shards changes as the bot grows.
54
+ const info = await options.rest.request(GetGatewayBot, []);
55
+ const count = options.shards?.count ?? info.shards;
56
+ const ids = options.shards?.ids ?? Array.from({ length: count }, (_, id) => id);
57
+ for (const id of ids) {
58
+ if (!Number.isInteger(id) || id < 0 || id >= count) {
59
+ throw new RangeError(`Shard ${id} does not exist: with ${count} shard(s), ids go from 0 to ${count - 1}.`);
60
+ }
61
+ }
62
+ const limit = info.session_start_limit;
63
+ if (limit.remaining < ids.length) {
64
+ throw new SessionLimitError(ids.length, limit.remaining, limit.reset_after);
65
+ }
66
+ this.#shardCount = count;
67
+ const identifyGate = options.identifyGate ??
68
+ new IdentifyQueue(limit.max_concurrency, options.identifyInterval);
69
+ for (const id of ids) {
70
+ this.#shards.set(id, new Shard({
71
+ id,
72
+ count,
73
+ token: options.token,
74
+ intents: options.intents,
75
+ url: info.url,
76
+ presence: this.#presence,
77
+ identifyGate,
78
+ onDispatch: options.onDispatch,
79
+ onEvent: options.onEvent,
80
+ backoff: options.backoff,
81
+ }));
82
+ }
83
+ try {
84
+ await Promise.all([...this.#shards.values()].map(shard => shard.connect()));
85
+ }
86
+ catch (error) {
87
+ await this.close();
88
+ throw error;
89
+ }
90
+ }
91
+ /**
92
+ * Changes the presence of the bot on every shard of this process: sent
93
+ * now when Discord's limit allows it, and carried by every session
94
+ * started later.
95
+ */
96
+ setPresence(presence) {
97
+ this.#presence = presence;
98
+ for (const shard of this.#shards.values())
99
+ shard.setPresence(presence);
100
+ }
101
+ /** The shard of this process that handles a server, if it runs here. */
102
+ shardFor(guildId) {
103
+ return this.#shards.get(shardIdFor(guildId, this.#shardCount));
104
+ }
105
+ /** Sends an event to Discord through one shard. */
106
+ async send(shardId, op, data) {
107
+ const shard = this.#shards.get(shardId);
108
+ if (!shard) {
109
+ throw new RangeError(`Shard ${shardId} does not run in this process.`);
110
+ }
111
+ await shard.send(op, data);
112
+ }
113
+ /** Closes every connection: the bot goes offline right away. */
114
+ async close() {
115
+ await Promise.all([...this.#shards.values()].map(shard => shard.close()));
116
+ }
117
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Decides when a shard may identify. Behind an interface because shards of
3
+ * the same bot may run in several processes, which then have to share one
4
+ * gate.
5
+ */
6
+ export interface IdentifyGate {
7
+ /** Resolves when the shard may send its Identify. */
8
+ wait(shardId: number): Promise<void>;
9
+ }
10
+ /**
11
+ * How long Discord counts concurrent identifies for, in milliseconds.
12
+ * @see https://docs.discord.com/developers/events/gateway#rate-limiting
13
+ */
14
+ export declare const IDENTIFY_INTERVAL = 5000;
15
+ /**
16
+ * The default gate, for shards of one process. Shards are put in buckets by
17
+ * `shard_id % max_concurrency`; a bucket lets one shard identify every 5
18
+ * seconds, and different buckets don't wait for each other.
19
+ * @see https://docs.discord.com/developers/events/gateway#sharding-max-concurrency
20
+ */
21
+ export declare class IdentifyQueue implements IdentifyGate {
22
+ #private;
23
+ constructor(maxConcurrency: number, interval?: number);
24
+ wait(shardId: number): Promise<void>;
25
+ }
@@ -0,0 +1,29 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises';
2
+ /**
3
+ * How long Discord counts concurrent identifies for, in milliseconds.
4
+ * @see https://docs.discord.com/developers/events/gateway#rate-limiting
5
+ */
6
+ export const IDENTIFY_INTERVAL = 5000;
7
+ /**
8
+ * The default gate, for shards of one process. Shards are put in buckets by
9
+ * `shard_id % max_concurrency`; a bucket lets one shard identify every 5
10
+ * seconds, and different buckets don't wait for each other.
11
+ * @see https://docs.discord.com/developers/events/gateway#sharding-max-concurrency
12
+ */
13
+ export class IdentifyQueue {
14
+ #maxConcurrency;
15
+ #interval;
16
+ /** For each bucket: when its last waiting shard will have identified. */
17
+ #tails = new Map();
18
+ constructor(maxConcurrency, interval = IDENTIFY_INTERVAL) {
19
+ this.#maxConcurrency = Math.max(1, Math.floor(maxConcurrency));
20
+ this.#interval = interval;
21
+ }
22
+ wait(shardId) {
23
+ const key = shardId % this.#maxConcurrency;
24
+ const turn = this.#tails.get(key) ?? Promise.resolve();
25
+ // The next shard of the bucket goes one interval after this one.
26
+ this.#tails.set(key, turn.then(() => sleep(this.#interval)).then(() => undefined));
27
+ return turn;
28
+ }
29
+ }
@@ -0,0 +1,94 @@
1
+ import { GatewayOpcode } from '../discord/codes.js';
2
+ import type { GatewayDispatchEventName, GatewayDispatchEvents, RawGatewayPresenceUpdate } from '../discord/types/gateway-events.js';
3
+ import type { IdentifyGate } from './identify-queue.js';
4
+ export type ShardStatus =
5
+ /** Not started yet. */
6
+ 'idle'
7
+ /** Opening the WebSocket, or waiting for Hello. */
8
+ | 'connecting'
9
+ /** Waiting for its turn to identify, or for Ready. */
10
+ | 'identifying'
11
+ /** Waiting for Discord to replay the events missed while disconnected. */
12
+ | 'resuming'
13
+ /** Connected: events are flowing. */
14
+ | 'ready'
15
+ /** Disconnected, about to try again. */
16
+ | 'reconnecting'
17
+ /** Closed for good: by the bot, or by a fatal error. */
18
+ | 'closed';
19
+ /** What happens to a shard, for whoever shows it to the developer. */
20
+ export type ShardEvent = {
21
+ type: 'ready';
22
+ shardId: number;
23
+ resumed: boolean;
24
+ } | {
25
+ type: 'disconnected';
26
+ shardId: number;
27
+ /** The close code, when the connection was closed with one. */
28
+ code: number | null;
29
+ /** Whether the session can be resumed without losing events. */
30
+ resumable: boolean;
31
+ /** In how many milliseconds the shard tries again. */
32
+ retryIn: number;
33
+ } | {
34
+ type: 'fatal';
35
+ shardId: number;
36
+ error: Error;
37
+ };
38
+ export interface ShardOptions {
39
+ /** `[shard_id, num_shards]` of the Identify payload. */
40
+ id: number;
41
+ count: number;
42
+ token: string;
43
+ intents: number;
44
+ /** The WSS URL given by Get Gateway Bot. */
45
+ url: string;
46
+ /** The presence the bot has as soon as it is connected. */
47
+ presence?: RawGatewayPresenceUpdate | undefined;
48
+ /** Decides when this shard may identify. */
49
+ identifyGate: IdentifyGate;
50
+ /** Called for every event Discord dispatches, in order. */
51
+ onDispatch: <E extends GatewayDispatchEventName>(event: E, data: GatewayDispatchEvents[E], shardId: number) => void;
52
+ onEvent?: (event: ShardEvent) => void;
53
+ /** Milliseconds to wait before the nth try in a row. Only tests change it. */
54
+ backoff?: (attempt: number) => number;
55
+ /** The window of the presence update limit, in ms. Only tests change it. */
56
+ presenceWindow?: number | undefined;
57
+ }
58
+ export declare class Shard {
59
+ #private;
60
+ readonly id: number;
61
+ constructor(options: ShardOptions);
62
+ get status(): ShardStatus;
63
+ /**
64
+ * How long Discord took to acknowledge the last heartbeat, in
65
+ * milliseconds; `null` before the first one.
66
+ */
67
+ get ping(): number | null;
68
+ /**
69
+ * Connects, and resolves once Discord said the shard is ready. Rejects if
70
+ * Discord refuses the connection for good. After that the shard stays
71
+ * connected by itself.
72
+ */
73
+ connect(): Promise<void>;
74
+ /**
75
+ * Sends an event to Discord, once the shard is ready and the send limit
76
+ * allows it.
77
+ * @see https://docs.discord.com/developers/events/gateway-events#send-events
78
+ */
79
+ send(op: GatewayOpcode, data: unknown): Promise<void>;
80
+ /**
81
+ * Changes the presence of the bot on this shard. It is sent right away
82
+ * when the shard is ready and Discord's limit allows it, otherwise as
83
+ * soon as one of them does (only the latest presence is sent then); a
84
+ * session started later identifies with it.
85
+ * @see https://docs.discord.com/developers/events/gateway-events#update-presence
86
+ */
87
+ setPresence(presence: RawGatewayPresenceUpdate): void;
88
+ /**
89
+ * Closes the connection for good. The session is ended, so the bot
90
+ * appears offline right away instead of staying online as a ghost.
91
+ * @see https://docs.discord.com/developers/events/gateway#initiating-a-disconnect
92
+ */
93
+ close(): Promise<void>;
94
+ }