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,399 @@
1
+ // `modal()`, as the files of `src/components/modals/` import it from
2
+ // 'chapterjs': a form that opens in Discord, its fields typed in `run`.
3
+ // https://docs.discord.com/developers/components/using-modal-components
4
+ import { Limits } from '../discord/api.js';
5
+ import { ComponentType, TextInputStyle, } from '../discord/types/component.js';
6
+ import { isDynamic, resolveText, } from './component.js';
7
+ import { checkData, encodeCustomId, } from './custom-id.js';
8
+ import { createFile } from './file.js';
9
+ import { hasDynamicOption, readOptions, resolveOptions, } from './options.js';
10
+ import { readChannelTypes, readPlaceholder, readRange, SELECT_TYPES, } from './select.js';
11
+ /**
12
+ * Declares a form. Export the result as the default export of a file of
13
+ * `src/components/modals/`: the path of the file is what tells the form
14
+ * apart, so you never write an id.
15
+ *
16
+ * ```ts
17
+ * import { modal } from 'chapterjs';
18
+ *
19
+ * export default modal({
20
+ * title: 'Feedback',
21
+ * fields: {
22
+ * text: { type: 'text', label: 'What do you think?', style: 'paragraph' },
23
+ * },
24
+ * async run({ interaction, fields }) {
25
+ * await interaction.reply({ content: `Thanks! You wrote: ${fields.text}`, ephemeral: true });
26
+ * },
27
+ * });
28
+ * ```
29
+ */
30
+ export function modal(config) {
31
+ return createFile('modal', config, {
32
+ asPiece: true,
33
+ pieceKind: 'modal',
34
+ });
35
+ }
36
+ const fail = (message) => {
37
+ throw new TypeError(message);
38
+ };
39
+ const FIELD_TYPES = [
40
+ 'text',
41
+ 'select',
42
+ 'user',
43
+ 'role',
44
+ 'mentionable',
45
+ 'channel',
46
+ 'files',
47
+ 'radio',
48
+ 'checkboxes',
49
+ 'checkbox',
50
+ 'note',
51
+ ];
52
+ /** The keys each kind of field accepts. */
53
+ const FIELD_KEYS = {
54
+ text: ['style', 'placeholder', 'required', 'minLength', 'maxLength', 'value'],
55
+ select: ['options', 'placeholder', 'min', 'max', 'required'],
56
+ user: ['placeholder', 'min', 'max', 'required'],
57
+ role: ['placeholder', 'min', 'max', 'required'],
58
+ mentionable: ['placeholder', 'min', 'max', 'required'],
59
+ channel: ['placeholder', 'min', 'max', 'required', 'channelTypes'],
60
+ files: ['min', 'max', 'required', 'fileTypes'],
61
+ radio: ['options', 'required'],
62
+ checkboxes: ['options', 'min', 'max', 'required'],
63
+ checkbox: ['default'],
64
+ note: ['content'],
65
+ };
66
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
67
+ function checkText(what, value, max, { required = true, min = 1 } = {}) {
68
+ if (value === undefined)
69
+ return required ? fail(`${what} is missing.`) : undefined;
70
+ // A function is checked when the form opens.
71
+ if (isDynamic(value))
72
+ return undefined;
73
+ if (typeof value !== 'string')
74
+ return fail(`${what} is a text, got ${typeof value}.`);
75
+ if (value.length < min || value.length > max) {
76
+ return fail(`${what} is ${value.length} characters long: Discord accepts between ${min} and ${max}.`);
77
+ }
78
+ return value;
79
+ }
80
+ function checkBoolean(what, value, fallback) {
81
+ if (value === undefined)
82
+ return fallback;
83
+ if (typeof value !== 'boolean')
84
+ return fail(`${what} is true or false, got ${JSON.stringify(value) ?? typeof value}.`);
85
+ return value;
86
+ }
87
+ /** Checks one field of a form. */
88
+ export function readField(name, given) {
89
+ const what = `the field "${name}"`;
90
+ if (!/^[A-Za-z_$][\w$]*$/.test(name)) {
91
+ fail(`"${name}" can't be the name of a field: it is how your code reads it (fields.${name}), so use letters, digits and _ only.`);
92
+ }
93
+ if (!isRecord(given)) {
94
+ return fail(`${what} must be an object like { type: 'text', label: '...' }.`);
95
+ }
96
+ const type = given.type;
97
+ if (!FIELD_TYPES.includes(type)) {
98
+ fail(`The type of ${what} is ${JSON.stringify(given.type)}, which does not exist. Types are: ${FIELD_TYPES.join(', ')}.`);
99
+ }
100
+ for (const key of Object.keys(given)) {
101
+ const allowed = [
102
+ 'type',
103
+ ...(type === 'note' ? [] : ['label', 'description']),
104
+ ...FIELD_KEYS[type],
105
+ ];
106
+ if (!allowed.includes(key)) {
107
+ fail(`"${key}" is not something ${what} has (a ${type}). It can have: ${allowed.join(', ')}.`);
108
+ }
109
+ }
110
+ if (type === 'note') {
111
+ checkText(`The content of ${what}`, given.content, Limits.TextDisplayTotal);
112
+ return {
113
+ name,
114
+ field: given,
115
+ options: [],
116
+ required: false,
117
+ min: 0,
118
+ max: 0,
119
+ };
120
+ }
121
+ checkText(`The label of ${what}`, given.label, Limits.LabelText);
122
+ checkText(`The description of ${what}`, given.description, Limits.LabelDescription, { required: false });
123
+ const required = checkBoolean(`"required" of ${what}`, given.required, true);
124
+ let options = [];
125
+ let range = { min: 1, max: 1 };
126
+ switch (type) {
127
+ case 'text': {
128
+ if (given.style !== undefined &&
129
+ given.style !== 'short' &&
130
+ given.style !== 'paragraph') {
131
+ fail(`The style of ${what} is 'short' or 'paragraph', got ${JSON.stringify(given.style)}.`);
132
+ }
133
+ checkText(`The placeholder of ${what}`, given.placeholder, Limits.TextInputPlaceholder, { required: false });
134
+ checkText(`The value of ${what}`, given.value, Limits.TextInputLength, {
135
+ required: false,
136
+ min: 0,
137
+ });
138
+ range = readRange(what, { min: given.minLength, max: given.maxLength }, Limits.TextInputLength, { min: 0, max: Limits.TextInputLength });
139
+ break;
140
+ }
141
+ case 'select':
142
+ options = readOptions(what, given.options);
143
+ readPlaceholder(what, given.placeholder);
144
+ range = readRange(what, given, Limits.SelectValues, { min: 1, max: 1 });
145
+ break;
146
+ case 'user':
147
+ case 'role':
148
+ case 'mentionable':
149
+ case 'channel':
150
+ readPlaceholder(what, given.placeholder);
151
+ range = readRange(what, given, Limits.SelectValues, { min: 1, max: 1 });
152
+ if (type === 'channel')
153
+ readChannelTypes(what, given.channelTypes);
154
+ break;
155
+ case 'files':
156
+ range = readRange(what, given, Limits.FileUploadMax, { min: 1, max: 1 });
157
+ if (given.fileTypes !== undefined) {
158
+ const types = given.fileTypes;
159
+ if (!Array.isArray(types) ||
160
+ types.length === 0 ||
161
+ types.length > Limits.FileUploadTypes ||
162
+ !types.every(one => typeof one === 'string' &&
163
+ /^(image|video|audio|\.[\w-]+)$/.test(one))) {
164
+ fail(`"fileTypes" of ${what} is a list of 'image', 'video', 'audio' or extensions like '.pdf' (${Limits.FileUploadTypes} at most).`);
165
+ }
166
+ }
167
+ break;
168
+ case 'radio':
169
+ options = readOptions(what, given.options, {
170
+ min: Limits.RadioOptionsMin,
171
+ max: Limits.RadioOptions,
172
+ });
173
+ break;
174
+ case 'checkboxes':
175
+ options = readOptions(what, given.options, {
176
+ min: 1,
177
+ max: Limits.CheckboxOptions,
178
+ });
179
+ range = readRange(what, given, Limits.CheckboxOptions, {
180
+ min: 1,
181
+ max: options.length,
182
+ });
183
+ if (range.max > options.length) {
184
+ fail(`"max" of ${what} is ${range.max}, but it only has ${options.length} options.`);
185
+ }
186
+ break;
187
+ case 'checkbox':
188
+ checkBoolean(`"default" of ${what}`, given.default, false);
189
+ break;
190
+ }
191
+ return {
192
+ name,
193
+ field: given,
194
+ options,
195
+ required,
196
+ ...range,
197
+ };
198
+ }
199
+ /** The label and the input of one field, as Discord takes them. */
200
+ function renderField(loaded, prefill, context) {
201
+ const { name, field, required, min, max } = loaded;
202
+ const what = `the field "${name}"`;
203
+ if (field.type === 'note') {
204
+ return {
205
+ type: ComponentType.TextDisplay,
206
+ content: resolveText(`The content of ${what}`, field.content, context, {
207
+ max: Limits.TextDisplayTotal,
208
+ }),
209
+ };
210
+ }
211
+ const placeholder = 'placeholder' in field
212
+ ? resolveText(`The placeholder of ${what}`, field.placeholder, context, {
213
+ max: field.type === 'text'
214
+ ? Limits.TextInputPlaceholder
215
+ : Limits.SelectPlaceholder,
216
+ })
217
+ : undefined;
218
+ const options = resolveOptions(what, loaded.options, context);
219
+ const base = { custom_id: name };
220
+ const picked = Array.isArray(prefill)
221
+ ? prefill
222
+ : prefill !== undefined
223
+ ? [prefill]
224
+ : undefined;
225
+ const withDefaults = (options) => options.map(option => {
226
+ const { default: initial, ...rest } = option;
227
+ const isDefault = picked ? picked.includes(option.value) : initial;
228
+ return isDefault ? { ...rest, default: true } : rest;
229
+ });
230
+ let component;
231
+ switch (field.type) {
232
+ case 'text':
233
+ component = {
234
+ type: ComponentType.TextInput,
235
+ ...base,
236
+ style: field.style === 'paragraph'
237
+ ? TextInputStyle.Paragraph
238
+ : TextInputStyle.Short,
239
+ ...(min !== 0 ? { min_length: min } : {}),
240
+ ...(max !== Limits.TextInputLength ? { max_length: max } : {}),
241
+ ...(required ? {} : { required: false }),
242
+ ...(placeholder !== undefined ? { placeholder } : {}),
243
+ ...((prefill ?? field.value) !== undefined
244
+ ? { value: (prefill ?? field.value) }
245
+ : {}),
246
+ };
247
+ break;
248
+ case 'select':
249
+ component = {
250
+ type: ComponentType.StringSelect,
251
+ ...base,
252
+ options: withDefaults(options),
253
+ ...(placeholder !== undefined ? { placeholder } : {}),
254
+ ...(min !== 1 ? { min_values: min } : {}),
255
+ ...(max !== 1 ? { max_values: max } : {}),
256
+ ...(required ? {} : { required: false }),
257
+ };
258
+ break;
259
+ case 'user':
260
+ case 'role':
261
+ case 'mentionable':
262
+ case 'channel':
263
+ component = {
264
+ type: SELECT_TYPES[field.type],
265
+ ...base,
266
+ ...(placeholder !== undefined ? { placeholder } : {}),
267
+ ...(min !== 1 ? { min_values: min } : {}),
268
+ ...(max !== 1 ? { max_values: max } : {}),
269
+ ...(required ? {} : { required: false }),
270
+ ...(field.type === 'channel' && field.channelTypes
271
+ ? { channel_types: [...field.channelTypes] }
272
+ : {}),
273
+ };
274
+ break;
275
+ case 'files':
276
+ component = {
277
+ type: ComponentType.FileUpload,
278
+ ...base,
279
+ ...(min !== 1 ? { min_values: min } : {}),
280
+ ...(max !== 1 ? { max_values: max } : {}),
281
+ ...(required ? {} : { required: false }),
282
+ ...(field.fileTypes ? { file_types: [...field.fileTypes] } : {}),
283
+ };
284
+ break;
285
+ case 'radio':
286
+ component = {
287
+ type: ComponentType.RadioGroup,
288
+ ...base,
289
+ options: withDefaults(options),
290
+ ...(required ? {} : { required: false }),
291
+ };
292
+ break;
293
+ case 'checkboxes':
294
+ component = {
295
+ type: ComponentType.CheckboxGroup,
296
+ ...base,
297
+ options: withDefaults(options),
298
+ ...(min !== 1 ? { min_values: min } : {}),
299
+ ...(max !== loaded.options.length ? { max_values: max } : {}),
300
+ ...(required ? {} : { required: false }),
301
+ };
302
+ break;
303
+ case 'checkbox': {
304
+ const checked = prefill !== undefined ? prefill === true : field.default === true;
305
+ component = {
306
+ type: ComponentType.Checkbox,
307
+ ...base,
308
+ ...(checked ? { default: true } : {}),
309
+ };
310
+ break;
311
+ }
312
+ }
313
+ const description = resolveText(`The description of ${what}`, field.description, context, { max: Limits.LabelDescription });
314
+ return {
315
+ type: ComponentType.Label,
316
+ label: resolveText(`The label of ${what}`, field.label, context, {
317
+ max: Limits.LabelText,
318
+ }),
319
+ ...(description !== undefined ? { description } : {}),
320
+ component,
321
+ };
322
+ }
323
+ /** Checks what a form is opened with. */
324
+ function readPrefill(loaded, name, given) {
325
+ if (given === undefined)
326
+ return {};
327
+ if (!isRecord(given))
328
+ fail(`The options of ${name} are an object like { values: { ... } }.`);
329
+ const options = given;
330
+ for (const key of Object.keys(options)) {
331
+ if (key !== 'values')
332
+ fail(`"${key}" is not something ${name} takes when opened. It can take: values.`);
333
+ }
334
+ if (options.values === undefined)
335
+ return {};
336
+ if (!isRecord(options.values))
337
+ fail(`"values" of ${name} is an object whose keys are the names of its fields.`);
338
+ const values = options.values;
339
+ for (const [key, value] of Object.entries(values)) {
340
+ const field = loaded.fields.find(one => one.name === key);
341
+ if (!field)
342
+ fail(`"${key}" is not a field of ${name}. Its fields are: ${loaded.fields.map(one => one.name).join(', ')}.`);
343
+ const { type } = field.field;
344
+ const bad = (expected) => fail(`The value given for the field "${key}" of ${name} is ${expected}, got ${JSON.stringify(value) ?? typeof value}.`);
345
+ const known = (picked) => {
346
+ if (!field.options.some(option => option.value === picked)) {
347
+ fail(`${JSON.stringify(picked)} is not an option of the field "${key}" of ${name}. Its options are: ${field.options.map(option => option.value).join(', ')}.`);
348
+ }
349
+ };
350
+ switch (type) {
351
+ case 'text':
352
+ if (typeof value !== 'string' || value.length > Limits.TextInputLength)
353
+ bad(`a text of ${Limits.TextInputLength} characters at most`);
354
+ break;
355
+ case 'select':
356
+ case 'checkboxes':
357
+ if (!Array.isArray(value))
358
+ bad('a list of its options');
359
+ value.forEach(known);
360
+ break;
361
+ case 'radio':
362
+ known(value);
363
+ break;
364
+ case 'checkbox':
365
+ if (typeof value !== 'boolean')
366
+ bad('true or false');
367
+ break;
368
+ default:
369
+ fail(`The field "${key}" of ${name} is a ${type}: it can't be given a value when the form opens.`);
370
+ }
371
+ }
372
+ return values;
373
+ }
374
+ /** The form as Discord takes it, for the data and the values given. */
375
+ export function renderModal(loaded, args) {
376
+ const hasData = Object.keys(loaded.data).length > 0;
377
+ const name = loaded.path.split('/').pop();
378
+ if (args.length > (hasData ? 2 : 1)) {
379
+ fail(`${name} takes ${hasData ? 'its data, then options' : 'options'} at most.`);
380
+ }
381
+ const values = checkData(name, loaded.data, hasData ? args[0] : undefined);
382
+ const prefill = readPrefill(loaded, name, hasData ? args[1] : args[0]);
383
+ const make = (context) => ({
384
+ custom_id: encodeCustomId(loaded.path, loaded.data, values),
385
+ title: resolveText(`The title of ${name}`, loaded.title, context, {
386
+ max: Limits.ModalTitle,
387
+ }),
388
+ components: loaded.fields.map(field => renderField(field, prefill[field.name], context)),
389
+ });
390
+ // Texts computed when the form opens wait for the language of the person.
391
+ const dynamic = isDynamic(loaded.title) ||
392
+ loaded.fields.some(({ field, options }) => Object.values(field).some(isDynamic) || hasDynamicOption(options));
393
+ return {
394
+ kind: 'modal',
395
+ raw: dynamic
396
+ ? t => make({ t, data: values })
397
+ : make({ t: undefined, data: values }),
398
+ };
399
+ }
@@ -0,0 +1,41 @@
1
+ import type { RawSelectOption } from '../discord/types/component.js';
2
+ import type { EmojiInput } from '../structures/message.js';
3
+ import type { DataShape } from './custom-id.js';
4
+ import { type DynamicText, type TextContext } from './component.js';
5
+ /** One option of a menu, with everything Discord shows for it. */
6
+ export interface RichOption<Data extends DataShape = DataShape> {
7
+ /** What the person sees (100 characters at most): a text, or a function of `t` and the data. */
8
+ label: DynamicText<Data>;
9
+ /** What your code receives (100 characters at most). */
10
+ value: string;
11
+ /** A second line under the label (100 characters at most): a text, or a function of `t` and the data. */
12
+ description?: DynamicText<Data>;
13
+ /** An emoji before the label. */
14
+ emoji?: EmojiInput;
15
+ /** Shows the option as picked when the menu appears. */
16
+ default?: boolean;
17
+ }
18
+ /**
19
+ * The options of a menu: a list of texts (each is shown and received as
20
+ * is), an object whose keys are what is shown and values what your code
21
+ * receives, or a list of `RichOption` for descriptions and emojis.
22
+ */
23
+ export type SelectOptions<Data extends DataShape = DataShape> = readonly string[] | Readonly<Record<string, string>> | readonly RichOption<Data>[];
24
+ /** An option as a file declared it, its texts maybe computed when sent. */
25
+ export type LoadedOption = Omit<RawSelectOption, 'label' | 'description'> & {
26
+ label: DynamicText;
27
+ description?: DynamicText;
28
+ };
29
+ /** The options as Discord takes them, their texts computed for who reads. */
30
+ export declare function resolveOptions(what: string, options: readonly LoadedOption[], context: TextContext): RawSelectOption[];
31
+ /** Whether a text of an option is computed when the message is sent. */
32
+ export declare const hasDynamicOption: (options: readonly LoadedOption[]) => boolean;
33
+ /** What `run` receives for one picked option, typed from the declaration. */
34
+ export type OptionValue<Options extends SelectOptions> = Options extends readonly (infer Item)[] ? Item extends string ? Item : Item extends {
35
+ value: infer Value extends string;
36
+ } ? Value : never : Options extends Readonly<Record<string, infer Value extends string>> ? Value : never;
37
+ /** Checks the options of a menu and returns them as Discord takes them. */
38
+ export declare function readOptions(what: string, options: unknown, { min, max }?: {
39
+ min?: number;
40
+ max?: number;
41
+ }): LoadedOption[];
@@ -0,0 +1,101 @@
1
+ // The options of a select menu, a radio group or a checkbox group, as files
2
+ // declare them: a list of texts, an object from what is shown to what the
3
+ // code receives, or a list of objects with a description and an emoji.
4
+ // Written once for the three.
5
+ import { Limits } from '../discord/api.js';
6
+ import { emojiOf } from './instance.js';
7
+ import { isDynamic, resolveText, } from './component.js';
8
+ /** The options as Discord takes them, their texts computed for who reads. */
9
+ export function resolveOptions(what, options, context) {
10
+ return options.map((option, index) => {
11
+ const { label, description, ...rest } = option;
12
+ const resolved = resolveText(`The label of option ${index + 1} of ${what}`, label, context, { max: Limits.SelectOptionText });
13
+ const second = resolveText(`The description of option ${index + 1} of ${what}`, description, context, { max: Limits.SelectOptionText });
14
+ return {
15
+ ...rest,
16
+ label: resolved,
17
+ ...(second !== undefined ? { description: second } : {}),
18
+ };
19
+ });
20
+ }
21
+ /** Whether a text of an option is computed when the message is sent. */
22
+ export const hasDynamicOption = (options) => options.some(option => isDynamic(option.label) || isDynamic(option.description));
23
+ const fail = (message) => {
24
+ throw new TypeError(message);
25
+ };
26
+ /** Checks the options of a menu and returns them as Discord takes them. */
27
+ export function readOptions(what, options, { min = 1, max = Limits.SelectOptions } = {}) {
28
+ const example = `options: ['Pizza', 'Pasta'], options: { 'Shown text': 'value' } or options: [{ label: '...', value: '...', description: '...' }]`;
29
+ let list;
30
+ if (Array.isArray(options)) {
31
+ list = options.map((item, index) => {
32
+ if (typeof item === 'string')
33
+ return { label: item, value: item };
34
+ if (typeof item !== 'object' || item === null) {
35
+ return fail(`Option ${index + 1} of ${what} is a text or an object like { label: '...', value: '...' }, got ${item === null ? 'null' : typeof item}.`);
36
+ }
37
+ const rich = item;
38
+ for (const key of Object.keys(rich)) {
39
+ if (!['label', 'value', 'description', 'emoji', 'default'].includes(key)) {
40
+ fail(`"${key}" is not something an option of ${what} has. It can have: label, value, description, emoji, default.`);
41
+ }
42
+ }
43
+ if ((typeof rich.label !== 'string' && !isDynamic(rich.label)) ||
44
+ typeof rich.value !== 'string') {
45
+ fail(`Option ${index + 1} of ${what} needs a label and a value, both texts.`);
46
+ }
47
+ const option = {
48
+ label: rich.label,
49
+ value: rich.value,
50
+ };
51
+ if (rich.description !== undefined) {
52
+ if (typeof rich.description !== 'string' &&
53
+ !isDynamic(rich.description))
54
+ fail(`The description of option ${index + 1} of ${what} is a text.`);
55
+ option.description = rich.description;
56
+ }
57
+ if (rich.emoji !== undefined)
58
+ option.emoji = emojiOf(rich.emoji, `option ${index + 1} of ${what}`);
59
+ if (rich.default !== undefined) {
60
+ if (typeof rich.default !== 'boolean')
61
+ fail(`"default" of option ${index + 1} of ${what} is true or false.`);
62
+ if (rich.default)
63
+ option.default = true;
64
+ }
65
+ return option;
66
+ });
67
+ }
68
+ else if (typeof options === 'object' && options !== null) {
69
+ list = Object.entries(options).map(([label, value]) => {
70
+ if (typeof value !== 'string') {
71
+ return fail(`The option ${JSON.stringify(label)} of ${what} must be a text (what your code receives), got ${JSON.stringify(value) ?? typeof value}.`);
72
+ }
73
+ return { label, value };
74
+ });
75
+ }
76
+ else {
77
+ return fail(`${what} needs its options: ${example}.`);
78
+ }
79
+ if (list.length < min || list.length > max) {
80
+ fail(`${what} has ${list.length} options: Discord accepts between ${min} and ${max}.`);
81
+ }
82
+ const values = new Set();
83
+ for (const option of list) {
84
+ for (const [field, text] of [
85
+ ['label', option.label],
86
+ ['value', option.value],
87
+ ['description', option.description],
88
+ ]) {
89
+ // A function is checked when the message is sent.
90
+ if (text === undefined || isDynamic(text))
91
+ continue;
92
+ if (text.trim() === '' || text.length > Limits.SelectOptionText) {
93
+ fail(`The ${field} of an option of ${what} is ${text.length} characters long: Discord accepts between 1 and ${Limits.SelectOptionText}.`);
94
+ }
95
+ }
96
+ if (values.has(option.value))
97
+ fail(`${what} has the value ${JSON.stringify(option.value)} twice.`);
98
+ values.add(option.value);
99
+ }
100
+ return list;
101
+ }
@@ -0,0 +1,12 @@
1
+ import { type RawComponent } from '../discord/types/component.js';
2
+ import type { Translator } from '../messages/messages.js';
3
+ export interface RenderedComponents {
4
+ raw: RawComponent[];
5
+ /** Whether the message needs the IS_COMPONENTS_V2 flag. */
6
+ v2: boolean;
7
+ }
8
+ /**
9
+ * The components of a message, as Discord takes them.
10
+ * @see https://docs.discord.com/developers/components/reference#component-object
11
+ */
12
+ export declare function renderComponents(components: readonly unknown[], t?: Translator): RenderedComponents;
@@ -0,0 +1,58 @@
1
+ // Turns the `components` of a message into what Discord receives: rows made
2
+ // by themselves, the whole checked against the limits of a message, and
3
+ // whether the message is built with components only (Components V2).
4
+ import { Limits } from '../discord/api.js';
5
+ import { ComponentType, } from '../discord/types/component.js';
6
+ import { renderedOf, resolve } from './instance.js';
7
+ import { missingTranslator } from '../messages/translate.js';
8
+ import { autoRows } from './layout.js';
9
+ /** Every component of a tree, the nested ones included. */
10
+ function count(components) {
11
+ let total = 0;
12
+ for (const component of components) {
13
+ total += 1;
14
+ if ('components' in component && Array.isArray(component.components)) {
15
+ total += count(component.components);
16
+ }
17
+ if ('accessory' in component)
18
+ total += 1;
19
+ }
20
+ return total;
21
+ }
22
+ /** The characters of every text of a tree. */
23
+ function textLength(components) {
24
+ let total = 0;
25
+ for (const component of components) {
26
+ if (component.type === ComponentType.TextDisplay) {
27
+ total += component.content.length;
28
+ }
29
+ else if ('components' in component &&
30
+ Array.isArray(component.components)) {
31
+ total += textLength(component.components);
32
+ }
33
+ }
34
+ return total;
35
+ }
36
+ /**
37
+ * The components of a message, as Discord takes them.
38
+ * @see https://docs.discord.com/developers/components/reference#component-object
39
+ */
40
+ export function renderComponents(components, t = missingTranslator(null)) {
41
+ if (!Array.isArray(components)) {
42
+ throw new TypeError(`The components of a message are a list, got ${typeof components}.`);
43
+ }
44
+ const pieces = components.map((component, index) => renderedOf(component, `Component ${index + 1} of the message`));
45
+ const rows = autoRows(pieces, 'a message');
46
+ const v2 = rows.some(one => one.kind !== 'row');
47
+ // The texts of a piece may be computed now, for who will read the message.
48
+ const raw = rows.map(one => resolve(one, t).raw);
49
+ const total = count(raw);
50
+ if (total > Limits.MessageComponents) {
51
+ throw new RangeError(`The message has ${total} components once its rows are made: Discord accepts ${Limits.MessageComponents} at most.`);
52
+ }
53
+ const characters = textLength(raw);
54
+ if (characters > Limits.TextDisplayTotal) {
55
+ throw new RangeError(`The texts of the components of the message add up to ${characters} characters: Discord accepts ${Limits.TextDisplayTotal} at most.`);
56
+ }
57
+ return { raw, v2 };
58
+ }
@@ -0,0 +1,18 @@
1
+ import { type RawInteraction } from '../discord/types/interaction.js';
2
+ import { type Reporter } from '../interactions/dispatch.js';
3
+ import type { Context } from '../structures/context.js';
4
+ import type { LoadedComponent } from './convention.js';
5
+ /** A component, with the file it comes from (to report its errors). */
6
+ export interface ComponentEntry {
7
+ file: string;
8
+ component: LoadedComponent;
9
+ }
10
+ export type ComponentRouterOptions = Reporter;
11
+ export declare class ComponentRouter {
12
+ #private;
13
+ constructor(options: ComponentRouterOptions);
14
+ /** Replaces every component (after a load or a reload). */
15
+ set(entries: Iterable<ComponentEntry>): void;
16
+ /** Handles an Interaction Create event, if it is a component or a form. */
17
+ dispatch(ctx: Context, raw: RawInteraction): void;
18
+ }