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,256 @@
1
+ // Runs the component someone used: reads the id the framework wrote in it,
2
+ // finds its file, turns what Discord sent into what `run` receives, and
3
+ // makes sure the person always gets an answer.
4
+ import { ComponentType, } from '../discord/types/component.js';
5
+ import { InteractionType, } from '../discord/types/interaction.js';
6
+ import { MessageFlags } from '../discord/types/message.js';
7
+ import { authorOf, placeContext, refuse, resolvePlace, runInteraction, } from '../interactions/dispatch.js';
8
+ import { audienceLocale, translation } from '../messages/translate.js';
9
+ import { ComponentInteraction, ModalInteraction, } from '../structures/interaction.js';
10
+ import { remember } from '../structures/known.js';
11
+ import { toCamelCase } from '../util/case.js';
12
+ import { decodeCustomId, readData } from './custom-id.js';
13
+ import { SELECT_TYPES } from './select.js';
14
+ /** What a component is called, for the person and the developer. */
15
+ const WHAT = {
16
+ button: 'button',
17
+ select: 'menu',
18
+ modal: 'form',
19
+ embed: 'embed',
20
+ };
21
+ export class ComponentRouter {
22
+ #components = new Map();
23
+ #options;
24
+ constructor(options) {
25
+ this.#options = options;
26
+ }
27
+ /** Replaces every component (after a load or a reload). */
28
+ set(entries) {
29
+ const components = new Map();
30
+ for (const entry of entries) {
31
+ if (entry.component.kind !== 'embed') {
32
+ components.set(entry.component.path, entry);
33
+ }
34
+ }
35
+ this.#components = components;
36
+ }
37
+ /** Handles an Interaction Create event, if it is a component or a form. */
38
+ dispatch(ctx, raw) {
39
+ const isComponent = raw.type === InteractionType.MessageComponent;
40
+ const isModal = raw.type === InteractionType.ModalSubmit;
41
+ if (!isComponent && !isModal)
42
+ return;
43
+ const data = raw.data;
44
+ if (!data || typeof data.custom_id !== 'string')
45
+ return;
46
+ const author = authorOf(raw);
47
+ if (!author)
48
+ return;
49
+ const user = ctx.entities.user(author.user);
50
+ const { user: _user, member: _member, message: rawMessage, ...rest } = raw;
51
+ const message = rawMessage
52
+ ? ctx.entities.message(rawMessage, raw.guild_id)
53
+ : null;
54
+ // Only the person sees an ephemeral message: what answers a component
55
+ // on it (an update of that message, a reply) stays with them, whatever
56
+ // the file says, so `t` speaks their language.
57
+ // https://docs.discord.com/developers/resources/message#message-object-message-flags
58
+ const isPrivate = ((rawMessage?.flags ?? 0) &
59
+ MessageFlags.Ephemeral) !==
60
+ 0;
61
+ const ephemeralOf = (component) => isPrivate || ('ephemeral' in component && component.ephemeral);
62
+ const { path, parts } = decodeCustomId(data.custom_id);
63
+ const entry = this.#components.get(path);
64
+ const what = isModal
65
+ ? 'form'
66
+ : data.component_type ===
67
+ ComponentType.Button
68
+ ? 'button'
69
+ : 'menu';
70
+ const interaction = isModal
71
+ ? new ModalInteraction(ctx, rest, user, {
72
+ ephemeral: entry ? ephemeralOf(entry.component) : isPrivate,
73
+ message,
74
+ })
75
+ : new ComponentInteraction(ctx, rest, user, {
76
+ ephemeral: entry ? ephemeralOf(entry.component) : isPrivate,
77
+ message: message,
78
+ });
79
+ if (isComponent && !message)
80
+ return;
81
+ // Embeds are never in the map: what is left has a `run`.
82
+ if (!entry ||
83
+ entry.component.kind === 'embed' ||
84
+ (isModal
85
+ ? entry.component.kind !== 'modal'
86
+ : entry.component.kind === 'modal') ||
87
+ (entry.component.kind === 'button' &&
88
+ data.component_type !==
89
+ ComponentType.Button) ||
90
+ (entry.component.kind === 'select' &&
91
+ SELECT_TYPES[entry.component.type] !==
92
+ data.component_type)) {
93
+ // Sent before the file was renamed, removed or changed of kind.
94
+ refuse(interaction, 'gone', { what });
95
+ return;
96
+ }
97
+ const { component, file } = entry;
98
+ const values = readData(component.data, parts);
99
+ if (!values) {
100
+ // The data of the file changed since the message was sent.
101
+ refuse(interaction, 'outdated', { what });
102
+ return;
103
+ }
104
+ const name = component.path;
105
+ const placed = resolvePlace(ctx, raw, user, author.member, component.where, warning => this.#options.onWarning(file, `${name} ${warning}`));
106
+ if (!placed.place) {
107
+ refuse(interaction, placed.refusal, { what });
108
+ return;
109
+ }
110
+ const { place } = placed;
111
+ if (component.kind !== 'modal' &&
112
+ component.who === 'author' &&
113
+ rawMessage?.interaction_metadata?.user.id !== user.id) {
114
+ refuse(interaction, 'authorOnly', { what });
115
+ return;
116
+ }
117
+ remember(interaction, {
118
+ guild: place.guild ?? undefined,
119
+ member: place.member ?? undefined,
120
+ channel: place.channel,
121
+ });
122
+ let specific;
123
+ try {
124
+ specific =
125
+ component.kind === 'modal'
126
+ ? {
127
+ fields: this.#readFields(ctx, raw, component.fields, data),
128
+ }
129
+ : component.kind === 'select'
130
+ ? this.#readValues(ctx, raw, component.type, data)
131
+ : {};
132
+ }
133
+ catch (error) {
134
+ this.#options.onError(file, error);
135
+ refuse(interaction, 'failed', { what });
136
+ return;
137
+ }
138
+ const context = Object.freeze({
139
+ interaction,
140
+ user,
141
+ message,
142
+ data: Object.freeze(values),
143
+ ...specific,
144
+ ...placeContext(place, component.where),
145
+ // `t` speaks the language of who will read the answer.
146
+ ...translation(ctx, audienceLocale({
147
+ person: raw.locale,
148
+ guild: place.guild,
149
+ ephemeral: ephemeralOf(component),
150
+ })),
151
+ });
152
+ runInteraction({
153
+ interaction,
154
+ file,
155
+ name,
156
+ what,
157
+ reporter: this.#options,
158
+ // Nothing changes on screen while a component is answered; a form
159
+ // sent is like a command used.
160
+ defer: () => interaction instanceof ComponentInteraction
161
+ ? interaction.deferUpdate()
162
+ : interaction.defer(),
163
+ unanswered: `finished without answering: the person sees "This interaction failed". Call interaction.update() or interaction.reply() in run${component.kind === 'modal' ? '' : ', or interaction.deferUpdate() to change nothing'}.`,
164
+ run: () => component.run(context),
165
+ });
166
+ }
167
+ /** What the person picked in a menu, as structures where it applies. */
168
+ #readValues(ctx, raw, type, data) {
169
+ const values = resolveValues(ctx, raw, type, data.values ?? [], data.resolved ?? {});
170
+ return { values, value: values[0] };
171
+ }
172
+ /** What the person filled in, field by field. */
173
+ #readFields(ctx, raw, fields, data) {
174
+ const sent = new Map();
175
+ for (const top of data.components ?? []) {
176
+ if (top.type === ComponentType.Label && top.component?.custom_id) {
177
+ sent.set(top.component.custom_id, top.component);
178
+ }
179
+ }
180
+ const resolved = data.resolved ?? {};
181
+ const result = {};
182
+ for (const { name, field, required } of fields) {
183
+ if (field.type === 'note')
184
+ continue;
185
+ const input = sent.get(name);
186
+ switch (field.type) {
187
+ case 'text': {
188
+ const text = input && 'value' in input ? input.value : '';
189
+ result[name] = text === '' && !required ? undefined : text;
190
+ break;
191
+ }
192
+ case 'radio': {
193
+ const value = input && 'value' in input ? input.value : null;
194
+ result[name] = value === null || value === '' ? undefined : value;
195
+ break;
196
+ }
197
+ case 'checkbox':
198
+ result[name] =
199
+ input && 'value' in input ? input.value === true : false;
200
+ break;
201
+ case 'select':
202
+ case 'checkboxes':
203
+ result[name] =
204
+ input && 'values' in input ? [...input.values] : [];
205
+ break;
206
+ case 'files':
207
+ result[name] = (input && 'values' in input ? input.values : [])
208
+ .map(id => {
209
+ const attachment = resolved.attachments?.[id];
210
+ return attachment ? toCamelCase(attachment) : undefined;
211
+ })
212
+ .filter(one => one !== undefined);
213
+ break;
214
+ default:
215
+ result[name] = resolveValues(ctx, raw, field.type, input && 'values' in input ? input.values : [], resolved);
216
+ }
217
+ }
218
+ return result;
219
+ }
220
+ }
221
+ /** The things picked in a menu of the server, as structures. */
222
+ function resolveValues(ctx, raw, type, ids, resolved) {
223
+ if (type === 'string')
224
+ return [...ids];
225
+ const guildId = raw.guild_id;
226
+ const userOf = (id) => {
227
+ const rawUser = resolved.users?.[id];
228
+ if (!rawUser)
229
+ return undefined;
230
+ const member = resolved.members?.[id];
231
+ if (member && guildId) {
232
+ ctx.entities.member(guildId, member, rawUser);
233
+ }
234
+ return ctx.entities.user(rawUser);
235
+ };
236
+ const roleOf = (id) => {
237
+ const role = resolved.roles?.[id];
238
+ return role && guildId ? ctx.entities.role(guildId, role) : undefined;
239
+ };
240
+ const channelOf = (id) => {
241
+ const partial = resolved.channels?.[id];
242
+ return (ctx.cache.channels.get(id) ??
243
+ (partial
244
+ ? ctx.entities.channel(partial, guildId)
245
+ : undefined));
246
+ };
247
+ return ids
248
+ .map(id => type === 'user'
249
+ ? userOf(id)
250
+ : type === 'role'
251
+ ? roleOf(id)
252
+ : type === 'channel'
253
+ ? channelOf(id)
254
+ : (userOf(id) ?? roleOf(id)))
255
+ .filter(one => one !== undefined);
256
+ }
@@ -0,0 +1,144 @@
1
+ import type { Snowflake } from '../discord/types/common.js';
2
+ import { ChannelType } from '../discord/types/channel.js';
3
+ import { type RawSelectMenu } from '../discord/types/component.js';
4
+ import type { Channel } from '../structures/channel.js';
5
+ import type { ComponentInteraction } from '../structures/interaction.js';
6
+ import type { Role } from '../structures/role.js';
7
+ import type { User } from '../structures/user.js';
8
+ import type { TranslationContext } from '../messages/messages.js';
9
+ import { type DynamicText } from './component.js';
10
+ import type { ComponentWhere, InteractiveConfig, InteractiveContext, PlaceOf } from './component.js';
11
+ import { type DataShape, type DataInputOf } from './custom-id.js';
12
+ import type { Rendered, SelectComponent } from './instance.js';
13
+ import { type LoadedOption, type OptionValue, type SelectOptions } from './options.js';
14
+ /** The kinds of menus: texts you declare, or things of the server. */
15
+ export type SelectType = 'string' | 'user' | 'role' | 'mentionable' | 'channel';
16
+ /** The kinds of menus whose options are things of the server. */
17
+ export type EntitySelectType = Exclude<SelectType, 'string'>;
18
+ export declare const SELECT_TYPES: Record<SelectType, RawSelectMenu['type']>;
19
+ /** What `run` receives for one picked thing, by kind of menu. */
20
+ interface EntityValues {
21
+ user: User;
22
+ role: Role;
23
+ mentionable: User | Role;
24
+ channel: Channel;
25
+ }
26
+ /** What a select menu looks like. */
27
+ export interface SelectLook<Data extends DataShape = DataShape> {
28
+ /**
29
+ * The text shown when nothing is picked (150 characters at most):
30
+ * written as is, or a function of `t` and the data, run when the
31
+ * message is sent.
32
+ */
33
+ placeholder?: DynamicText<Data>;
34
+ /** Shows the menu greyed out, impossible to use. */
35
+ disabled?: boolean;
36
+ }
37
+ /** What a message can change when it shows a menu. */
38
+ export interface SelectInstanceOptions extends SelectLook {
39
+ /**
40
+ * What is picked when the menu appears: the values of the options for a
41
+ * menu of texts, the ids (or the things themselves) for the others.
42
+ */
43
+ defaults?: readonly (string | {
44
+ id: Snowflake;
45
+ })[];
46
+ }
47
+ /** What every select file declares, whatever its kind. */
48
+ interface SelectConfigBase<Data extends DataShape, Where extends ComponentWhere> extends SelectLook<Data>, InteractiveConfig<Data, Where> {
49
+ /** How many things must be picked at least (0 to 25). 1 by default. */
50
+ min?: number;
51
+ /** How many things can be picked at most (1 to 25). 1 by default. */
52
+ max?: number;
53
+ }
54
+ /** What `run` receives when something is picked. */
55
+ export type SelectContext<Picked, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> = InteractiveContext<Data> & TranslationContext & {
56
+ /** What was picked, in the order of the menu. */
57
+ values: Picked[];
58
+ /** The first thing picked; `undefined` when nothing was (a menu with `min: 0`). */
59
+ value: Picked | undefined;
60
+ } & PlaceOf<Where, ComponentInteraction>;
61
+ /** A menu of texts you declare. */
62
+ export interface StringSelectConfig<Options extends SelectOptions, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> extends SelectConfigBase<Data, Where> {
63
+ /** The kind of menu: texts you declare, which is the default. */
64
+ type?: 'string';
65
+ /**
66
+ * The options (25 at most): a list of texts, an object from what is
67
+ * shown to what your code receives, or a list of `RichOption`.
68
+ */
69
+ options: Options;
70
+ /** What to do when something is picked. */
71
+ run: (context: SelectContext<OptionValue<Options>, Data, Where>) => unknown;
72
+ }
73
+ /** A menu of users, roles, users and roles, or channels of the server. */
74
+ export interface EntitySelectConfig<Type extends EntitySelectType, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> extends SelectConfigBase<Data, Where> {
75
+ /** The kind of menu: what Discord lists. */
76
+ type: Type;
77
+ /** For a menu of channels: the kinds of channels listed. All by default. */
78
+ channelTypes?: readonly ChannelType[];
79
+ /** What to do when something is picked. */
80
+ run: (context: SelectContext<EntityValues[Type], Data, Where>) => unknown;
81
+ }
82
+ /** What a select file gives to `select()`: a menu of texts, or of things of the server. */
83
+ export type SelectConfig<Options extends SelectOptions = SelectOptions, Type extends EntitySelectType = EntitySelectType, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> = StringSelectConfig<Options, Data, Where> | EntitySelectConfig<Type, Data, Where>;
84
+ /**
85
+ * What `select()` returns: the default export of a select file. Import it
86
+ * where you send a message, and call it with the data it carries; a menu
87
+ * without data is used as is, or called with what to show.
88
+ */
89
+ export type SelectFile<Data extends DataShape = {}> = {
90
+ /** What the file gave to `select()`, not checked yet. */
91
+ readonly config: unknown;
92
+ } & ({} extends Data ? ((options?: SelectInstanceOptions) => SelectComponent) & SelectComponent : {} extends DataInputOf<Data> ? ((data?: DataInputOf<Data>, options?: SelectInstanceOptions) => SelectComponent) & SelectComponent : (data: DataInputOf<Data>, options?: SelectInstanceOptions) => SelectComponent);
93
+ /**
94
+ * Declares a select menu. Export the result as the default export of a
95
+ * file of `src/components/selects/`: the path of the file is what tells
96
+ * the menu apart, so you never write an id.
97
+ *
98
+ * ```ts
99
+ * import { select } from 'chapterjs';
100
+ *
101
+ * export default select({
102
+ * placeholder: 'Pick a color',
103
+ * options: { Red: 'red', Blue: 'blue' },
104
+ * async run({ interaction, value }) {
105
+ * await interaction.update({ content: `You picked ${value}.`, components: [] });
106
+ * },
107
+ * });
108
+ * ```
109
+ */
110
+ export declare function select<const Options extends SelectOptions, const Data extends DataShape = {}, const Where extends ComponentWhere = 'guild'>(config: StringSelectConfig<Options, Data, Where>): SelectFile<Data>;
111
+ export declare function select<const Type extends EntitySelectType, const Data extends DataShape = {}, const Where extends ComponentWhere = 'guild'>(config: EntitySelectConfig<Type, Data, Where>): SelectFile<Data>;
112
+ /** A select file, checked. */
113
+ export interface LoadedSelect {
114
+ readonly kind: 'select';
115
+ readonly path: string;
116
+ readonly type: SelectType;
117
+ /** The options of a menu of texts, their texts maybe computed when sent. */
118
+ readonly options: readonly LoadedOption[];
119
+ readonly channelTypes: readonly ChannelType[] | undefined;
120
+ readonly look: SelectLook;
121
+ readonly min: number;
122
+ readonly max: number;
123
+ readonly data: DataShape;
124
+ readonly where: ComponentWhere;
125
+ readonly who: 'everyone' | 'author';
126
+ readonly ephemeral: boolean;
127
+ readonly run: (context: SelectContext<unknown, DataShape, ComponentWhere>) => unknown;
128
+ }
129
+ /** Checks `min` and `max` of a menu, a group of checkboxes or a file upload. */
130
+ export declare function readRange(what: string, given: {
131
+ min?: unknown;
132
+ max?: unknown;
133
+ }, limit: number, defaults: {
134
+ min: number;
135
+ max: number;
136
+ }): {
137
+ min: number;
138
+ max: number;
139
+ };
140
+ export declare function readPlaceholder(what: string, value: unknown): DynamicText | undefined;
141
+ export declare function readChannelTypes(what: string, value: unknown): ChannelType[] | undefined;
142
+ /** The menu as Discord takes it, for the data and the options given. */
143
+ export declare function renderSelect(loaded: LoadedSelect, args: readonly unknown[]): Rendered<'select'>;
144
+ export {};
@@ -0,0 +1,179 @@
1
+ // `select()`, as the files of `src/components/selects/` import it from
2
+ // 'chapterjs': a menu of texts you declare, or of users, roles, channels
3
+ // of the server, and what to do with what is picked.
4
+ // https://docs.discord.com/developers/components/reference#string-select
5
+ import { Limits } from '../discord/api.js';
6
+ import { ChannelType } from '../discord/types/channel.js';
7
+ import { ComponentType, } from '../discord/types/component.js';
8
+ import { isDynamic, resolveText, } from './component.js';
9
+ import { checkData, encodeCustomId, } from './custom-id.js';
10
+ import { createFile } from './file.js';
11
+ import { hasDynamicOption, resolveOptions, } from './options.js';
12
+ export const SELECT_TYPES = {
13
+ string: ComponentType.StringSelect,
14
+ user: ComponentType.UserSelect,
15
+ role: ComponentType.RoleSelect,
16
+ mentionable: ComponentType.MentionableSelect,
17
+ channel: ComponentType.ChannelSelect,
18
+ };
19
+ export function select(config) {
20
+ return createFile('select', config, {
21
+ asPiece: true,
22
+ pieceKind: 'select',
23
+ });
24
+ }
25
+ const fail = (message) => {
26
+ throw new TypeError(message);
27
+ };
28
+ /** Checks `min` and `max` of a menu, a group of checkboxes or a file upload. */
29
+ export function readRange(what, given, limit, defaults) {
30
+ const read = (key, fallback, low) => {
31
+ const value = given[key];
32
+ if (value === undefined)
33
+ return fallback;
34
+ if (!Number.isInteger(value) ||
35
+ value < low ||
36
+ value > limit) {
37
+ return fail(`"${key}" of ${what} is a whole number between ${low} and ${limit}, got ${JSON.stringify(value) ?? typeof value}.`);
38
+ }
39
+ return value;
40
+ };
41
+ const min = read('min', defaults.min, 0);
42
+ const max = read('max', defaults.max, 1);
43
+ if (min > max) {
44
+ fail(`${what} can never be used: its "min" (${min}) is greater than its "max" (${max}).`);
45
+ }
46
+ return { min, max };
47
+ }
48
+ export function readPlaceholder(what, value) {
49
+ if (value === undefined)
50
+ return undefined;
51
+ // A function is checked when the message is sent.
52
+ if (isDynamic(value))
53
+ return value;
54
+ if (typeof value !== 'string' ||
55
+ value.trim() === '' ||
56
+ value.length > Limits.SelectPlaceholder) {
57
+ return fail(`The placeholder of ${what} is a text of 1 to ${Limits.SelectPlaceholder} characters.`);
58
+ }
59
+ return value;
60
+ }
61
+ export function readChannelTypes(what, value) {
62
+ if (value === undefined)
63
+ return undefined;
64
+ const known = Object.values(ChannelType);
65
+ if (!Array.isArray(value) ||
66
+ value.length === 0 ||
67
+ !value.every(one => known.includes(one))) {
68
+ return fail(`"channelTypes" of ${what} is a list of ChannelType values, like [ChannelType.GuildText].`);
69
+ }
70
+ return value;
71
+ }
72
+ /** Checks what a message gives to change a menu. */
73
+ function readInstanceOptions(loaded, name, given) {
74
+ if (given === undefined)
75
+ return { look: {}, defaults: undefined };
76
+ if (typeof given !== 'object' || given === null || Array.isArray(given)) {
77
+ fail(`The options of ${name} are an object like { placeholder: '...', defaults: [...] }.`);
78
+ }
79
+ const options = given;
80
+ for (const key of Object.keys(options)) {
81
+ if (!['placeholder', 'disabled', 'defaults'].includes(key)) {
82
+ fail(`"${key}" is not something ${name} takes when shown. It can take: placeholder, disabled, defaults.`);
83
+ }
84
+ }
85
+ const look = {};
86
+ const placeholder = readPlaceholder(name, options.placeholder);
87
+ if (placeholder !== undefined)
88
+ look.placeholder = placeholder;
89
+ if (options.disabled !== undefined) {
90
+ if (typeof options.disabled !== 'boolean')
91
+ fail(`"disabled" of ${name} is true or false.`);
92
+ look.disabled = options.disabled;
93
+ }
94
+ if (options.defaults === undefined)
95
+ return { look, defaults: undefined };
96
+ if (!Array.isArray(options.defaults)) {
97
+ fail(`"defaults" of ${name} is a list of what is picked when the menu appears.`);
98
+ }
99
+ const ids = options.defaults.map(item => {
100
+ const id = typeof item === 'string' ? item : item?.id;
101
+ if (typeof id !== 'string') {
102
+ return fail(`A default of ${name} is a text, or something with an id, got ${JSON.stringify(item) ?? typeof item}.`);
103
+ }
104
+ return id;
105
+ });
106
+ if (ids.length > loaded.max) {
107
+ fail(`${name} has ${ids.length} defaults, but ${loaded.max} can be picked at most.`);
108
+ }
109
+ if (loaded.type === 'string') {
110
+ for (const value of ids) {
111
+ if (!loaded.options.some(option => option.value === value)) {
112
+ fail(`${JSON.stringify(value)} is not an option of ${name}. Its options are: ${loaded.options.map(option => option.value).join(', ')}.`);
113
+ }
114
+ }
115
+ return { look, defaults: ids };
116
+ }
117
+ // Discord wants to know what each default is; a mentionable may be either,
118
+ // and a role id is also a guild id for @everyone: users first.
119
+ const kind = loaded.type === 'mentionable' ? 'user' : loaded.type;
120
+ return { look, defaults: ids.map(id => ({ id, type: kind })) };
121
+ }
122
+ /** The menu as Discord takes it, for the data and the options given. */
123
+ export function renderSelect(loaded, args) {
124
+ const hasData = Object.keys(loaded.data).length > 0;
125
+ const name = loaded.path.split('/').pop();
126
+ if (args.length > (hasData ? 2 : 1)) {
127
+ fail(`${name} takes ${hasData ? 'its data, then options' : 'options'} at most.`);
128
+ }
129
+ const values = checkData(name, loaded.data, hasData ? args[0] : undefined);
130
+ const { look, defaults } = readInstanceOptions(loaded, name, hasData ? args[1] : args[0]);
131
+ const merged = { ...loaded.look, ...look };
132
+ const dynamic = isDynamic(merged.placeholder) || hasDynamicOption(loaded.options);
133
+ const make = (context) => {
134
+ const placeholder = resolveText(`The placeholder of ${name}`, merged.placeholder, context, { max: Limits.SelectPlaceholder });
135
+ const base = {
136
+ custom_id: encodeCustomId(loaded.path, loaded.data, values),
137
+ ...(placeholder !== undefined ? { placeholder } : {}),
138
+ ...(loaded.min !== 1 ? { min_values: loaded.min } : {}),
139
+ ...(loaded.max !== 1 ? { max_values: loaded.max } : {}),
140
+ ...(merged.disabled ? { disabled: true } : {}),
141
+ };
142
+ if (loaded.type !== 'string') {
143
+ return {
144
+ type: SELECT_TYPES[loaded.type],
145
+ ...base,
146
+ ...(loaded.channelTypes
147
+ ? { channel_types: [...loaded.channelTypes] }
148
+ : {}),
149
+ ...(defaults
150
+ ? { default_values: defaults }
151
+ : {}),
152
+ };
153
+ }
154
+ const picked = defaults;
155
+ return {
156
+ type: ComponentType.StringSelect,
157
+ ...base,
158
+ options: resolveOptions(name, loaded.options, context)
159
+ .map(option => picked
160
+ ? {
161
+ ...option,
162
+ ...(picked.includes(option.value)
163
+ ? { default: true }
164
+ : { default: undefined }),
165
+ }
166
+ : option)
167
+ .map(option => option.default === undefined
168
+ ? (({ default: _d, ...rest }) => rest)(option)
169
+ : option),
170
+ };
171
+ };
172
+ // Texts computed when the message is sent wait for who will read it.
173
+ return {
174
+ kind: 'select',
175
+ raw: dynamic
176
+ ? t => make({ t, data: values })
177
+ : make({ t: undefined, data: values }),
178
+ };
179
+ }
@@ -0,0 +1,99 @@
1
+ import { type CacheOptions } from '../cache/cache.js';
2
+ import type { Snowflake } from '../discord/types/common.js';
3
+ import type { GatewayDispatchEventName, GatewayDispatchEvents, RawGatewayPresenceUpdate } from '../discord/types/gateway-events.js';
4
+ import { Gateway, type GatewayOptions } from '../gateway/gateway.js';
5
+ import type { ShardEvent } from '../gateway/shard.js';
6
+ import { type RestOptions } from '../rest/rest.js';
7
+ import type { Context } from '../structures/context.js';
8
+ /** What a dispatched event is, besides its data. */
9
+ export interface DispatchInfo {
10
+ shardId: number;
11
+ /**
12
+ * For Guild Create: `true` when the bot just joined the server, `false`
13
+ * when it was already in it and Discord is only sending its data (after
14
+ * connecting, or after an outage).
15
+ */
16
+ joined: boolean;
17
+ /** What `beforeDispatch` returned for this event. */
18
+ before: unknown;
19
+ }
20
+ export type BotEvent = ShardEvent
21
+ /** Every server the bot is in has sent its data (or took too long). */
22
+ | {
23
+ type: 'guildsReady';
24
+ }
25
+ /** Applying an event to the cache failed: the event is still delivered. */
26
+ | {
27
+ type: 'stateError';
28
+ event: GatewayDispatchEventName;
29
+ error: unknown;
30
+ }
31
+ /** Something went wrong with a voice connection, which goes on or ends. */
32
+ | {
33
+ type: 'voiceWarning';
34
+ message: string;
35
+ };
36
+ export interface BotOptions {
37
+ token: string;
38
+ /** The version of the framework. */
39
+ version: string;
40
+ intents: number;
41
+ presence?: RawGatewayPresenceUpdate | undefined;
42
+ cache?: CacheOptions;
43
+ shards?: GatewayOptions['shards'];
44
+ identifyGate?: GatewayOptions['identifyGate'];
45
+ /** Called for every event, once the cache reflects it. */
46
+ onDispatch?: <E extends GatewayDispatchEventName>(event: E, data: GatewayDispatchEvents[E], info: DispatchInfo) => void;
47
+ /**
48
+ * Called for every event before the cache reflects it: the last moment
49
+ * to read what the event removes. What it returns is given back to
50
+ * `onDispatch`.
51
+ */
52
+ beforeDispatch?: <E extends GatewayDispatchEventName>(event: E, data: GatewayDispatchEvents[E]) => unknown;
53
+ onEvent?: (event: BotEvent) => void;
54
+ /**
55
+ * The servers the bot pays attention to. Events of the others are dropped
56
+ * before anything else, as if the bot was not in them: this is how a dev
57
+ * bot only sees its dev server. What belongs to no server is decided by
58
+ * `privateEvents`.
59
+ */
60
+ guildFilter?: (guildId: Snowflake) => boolean;
61
+ /**
62
+ * Whether what happens outside servers (private messages, commands used
63
+ * there) is for this bot. Default: true. Discord sends it to every
64
+ * process of the bot that runs its first shard: when a dev bot and a
65
+ * production bot share a token, only one may answer.
66
+ */
67
+ privateEvents?: boolean;
68
+ /**
69
+ * How long to wait for the data of every server after connecting, in
70
+ * milliseconds, before going on without the ones that did not come
71
+ * (an outage of Discord). Default: 15000.
72
+ */
73
+ guildsTimeout?: number;
74
+ /** Only tests change these. */
75
+ rest?: Partial<RestOptions>;
76
+ gateway?: Pick<GatewayOptions, 'backoff' | 'identifyInterval'>;
77
+ voice?: {
78
+ secure?: boolean;
79
+ };
80
+ }
81
+ export interface Bot {
82
+ readonly ctx: Context;
83
+ readonly gateway: Gateway;
84
+ /**
85
+ * Connects to Discord, and resolves once every shard is ready and every
86
+ * server has sent its data.
87
+ */
88
+ connect(): Promise<void>;
89
+ /** Disconnects cleanly: the bot goes offline right away. */
90
+ close(): Promise<void>;
91
+ /**
92
+ * Changes what the bot shows under its name (its status and activity)
93
+ * on every shard of this process. Discord's limit on presence updates is
94
+ * respected: when it is reached, the latest presence is sent as soon as
95
+ * it allows.
96
+ */
97
+ setPresence(presence: RawGatewayPresenceUpdate): void;
98
+ }
99
+ export declare function createBot(options: BotOptions): Bot;