@azurioh/discord-kernel 0.1.0 → 1.0.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 (529) hide show
  1. package/README.md +318 -1
  2. package/dist/authz/require-level-guard.d.ts +1 -1
  3. package/dist/authz/require-level-guard.d.ts.map +1 -1
  4. package/dist/color.d.ts +42 -0
  5. package/dist/color.d.ts.map +1 -0
  6. package/dist/color.js +130 -0
  7. package/dist/color.js.map +1 -0
  8. package/dist/discord/command/all-required.d.ts +8 -0
  9. package/dist/discord/command/all-required.d.ts.map +1 -0
  10. package/dist/discord/command/all-required.js +12 -0
  11. package/dist/discord/command/all-required.js.map +1 -0
  12. package/dist/discord/command/autocomplete-dispatcher.d.ts.map +1 -1
  13. package/dist/discord/command/autocomplete-dispatcher.js +2 -1
  14. package/dist/discord/command/autocomplete-dispatcher.js.map +1 -1
  15. package/dist/discord/command/autocomplete-limits.d.ts +3 -0
  16. package/dist/discord/command/autocomplete-limits.d.ts.map +1 -0
  17. package/dist/discord/command/autocomplete-limits.js +6 -0
  18. package/dist/discord/command/autocomplete-limits.js.map +1 -0
  19. package/dist/discord/command/autocomplete.d.ts +6 -1
  20. package/dist/discord/command/autocomplete.d.ts.map +1 -1
  21. package/dist/discord/command/autocomplete.js +6 -6
  22. package/dist/discord/command/autocomplete.js.map +1 -1
  23. package/dist/discord/command/command-responders.d.ts +26 -0
  24. package/dist/discord/command/command-responders.d.ts.map +1 -0
  25. package/dist/discord/command/command-responders.js +16 -0
  26. package/dist/discord/command/command-responders.js.map +1 -0
  27. package/dist/discord/command/context.d.ts +8 -17
  28. package/dist/discord/command/context.d.ts.map +1 -1
  29. package/dist/discord/command/context.js +8 -37
  30. package/dist/discord/command/context.js.map +1 -1
  31. package/dist/discord/command/cooldown-guard.d.ts +1 -1
  32. package/dist/discord/command/cooldown-guard.d.ts.map +1 -1
  33. package/dist/discord/command/create-command.d.ts +1 -8
  34. package/dist/discord/command/create-command.d.ts.map +1 -1
  35. package/dist/discord/command/create-command.js +17 -65
  36. package/dist/discord/command/create-command.js.map +1 -1
  37. package/dist/discord/command/create-context-menu-command.d.ts +3 -9
  38. package/dist/discord/command/create-context-menu-command.d.ts.map +1 -1
  39. package/dist/discord/command/create-context-menu-command.js +14 -65
  40. package/dist/discord/command/create-context-menu-command.js.map +1 -1
  41. package/dist/discord/command/errors.d.ts +1 -1
  42. package/dist/discord/command/errors.d.ts.map +1 -1
  43. package/dist/discord/command/errors.js +2 -2
  44. package/dist/discord/command/errors.js.map +1 -1
  45. package/dist/discord/command/guard.d.ts +15 -8
  46. package/dist/discord/command/guard.d.ts.map +1 -1
  47. package/dist/discord/command/guard.js +4 -4
  48. package/dist/discord/command/guard.js.map +1 -1
  49. package/dist/discord/command/options.d.ts +1 -10
  50. package/dist/discord/command/options.d.ts.map +1 -1
  51. package/dist/discord/command/options.js +127 -320
  52. package/dist/discord/command/options.js.map +1 -1
  53. package/dist/discord/command/passes-guard.d.ts +14 -0
  54. package/dist/discord/command/passes-guard.d.ts.map +1 -0
  55. package/dist/discord/command/passes-guard.js +27 -0
  56. package/dist/discord/command/passes-guard.js.map +1 -0
  57. package/dist/discord/command/permission-guard.d.ts +1 -1
  58. package/dist/discord/command/permission-guard.d.ts.map +1 -1
  59. package/dist/discord/command/render-command-failure.d.ts +23 -0
  60. package/dist/discord/command/render-command-failure.d.ts.map +1 -0
  61. package/dist/discord/command/render-command-failure.js +40 -0
  62. package/dist/discord/command/render-command-failure.js.map +1 -0
  63. package/dist/discord/command/route-key.d.ts +8 -0
  64. package/dist/discord/command/route-key.d.ts.map +1 -0
  65. package/dist/discord/command/route-key.js +13 -0
  66. package/dist/discord/command/route-key.js.map +1 -0
  67. package/dist/discord/command/router.d.ts +32 -7
  68. package/dist/discord/command/router.d.ts.map +1 -1
  69. package/dist/discord/command/router.js +48 -10
  70. package/dist/discord/command/router.js.map +1 -1
  71. package/dist/discord/command/send-embed.d.ts +14 -0
  72. package/dist/discord/command/send-embed.d.ts.map +1 -0
  73. package/dist/discord/command/send-embed.js +37 -0
  74. package/dist/discord/command/send-embed.js.map +1 -0
  75. package/dist/discord/command/types.d.ts +7 -1
  76. package/dist/discord/command/types.d.ts.map +1 -1
  77. package/dist/discord/components/component-access.d.ts +12 -0
  78. package/dist/discord/components/component-access.d.ts.map +1 -0
  79. package/dist/discord/components/component-access.js +21 -0
  80. package/dist/discord/components/component-access.js.map +1 -0
  81. package/dist/discord/components/component-router.d.ts +37 -15
  82. package/dist/discord/components/component-router.d.ts.map +1 -1
  83. package/dist/discord/components/component-router.js +44 -32
  84. package/dist/discord/components/component-router.js.map +1 -1
  85. package/dist/discord/components/interactive-message/index.d.ts +3 -1
  86. package/dist/discord/components/interactive-message/index.d.ts.map +1 -1
  87. package/dist/discord/components/interactive-message/index.js +5 -3
  88. package/dist/discord/components/interactive-message/index.js.map +1 -1
  89. package/dist/discord/components/interactive-message/interactive-message-collector.d.ts +3 -28
  90. package/dist/discord/components/interactive-message/interactive-message-collector.d.ts.map +1 -1
  91. package/dist/discord/components/interactive-message/interactive-message-collector.js +6 -53
  92. package/dist/discord/components/interactive-message/interactive-message-collector.js.map +1 -1
  93. package/dist/discord/components/interactive-message/message-edit-options.d.ts +18 -0
  94. package/dist/discord/components/interactive-message/message-edit-options.d.ts.map +1 -0
  95. package/dist/discord/components/interactive-message/message-edit-options.js +40 -0
  96. package/dist/discord/components/interactive-message/message-edit-options.js.map +1 -0
  97. package/dist/discord/components/interactive-message/mount-interactive-message.d.ts +2 -1
  98. package/dist/discord/components/interactive-message/mount-interactive-message.d.ts.map +1 -1
  99. package/dist/discord/components/interactive-message/mount-interactive-message.js +4 -2
  100. package/dist/discord/components/interactive-message/mount-interactive-message.js.map +1 -1
  101. package/dist/discord/components/interactive-message/state-store.d.ts +12 -0
  102. package/dist/discord/components/interactive-message/state-store.d.ts.map +1 -0
  103. package/dist/discord/components/interactive-message/state-store.js +14 -0
  104. package/dist/discord/components/interactive-message/state-store.js.map +1 -0
  105. package/dist/discord/components/paginator/mount-paginator.js +7 -5
  106. package/dist/discord/components/paginator/mount-paginator.js.map +1 -1
  107. package/dist/discord/components/paginator/paginator-view.d.ts +1 -1
  108. package/dist/discord/components/paginator/paginator-view.d.ts.map +1 -1
  109. package/dist/discord/components/persistent-paginator.d.ts +1 -1
  110. package/dist/discord/components/persistent-paginator.d.ts.map +1 -1
  111. package/dist/discord/components/persistent-paginator.js +6 -6
  112. package/dist/discord/components/persistent-paginator.js.map +1 -1
  113. package/dist/discord/components/settings-editor/from-declaration-controls.d.ts +67 -0
  114. package/dist/discord/components/settings-editor/from-declaration-controls.d.ts.map +1 -0
  115. package/dist/discord/components/settings-editor/from-declaration-controls.js +245 -0
  116. package/dist/discord/components/settings-editor/from-declaration-controls.js.map +1 -0
  117. package/dist/discord/components/settings-editor/from-declaration-display.d.ts +23 -0
  118. package/dist/discord/components/settings-editor/from-declaration-display.d.ts.map +1 -0
  119. package/dist/discord/components/settings-editor/from-declaration-display.js +114 -0
  120. package/dist/discord/components/settings-editor/from-declaration-display.js.map +1 -0
  121. package/dist/discord/components/settings-editor/from-declaration-layout.d.ts +33 -0
  122. package/dist/discord/components/settings-editor/from-declaration-layout.d.ts.map +1 -0
  123. package/dist/discord/components/settings-editor/from-declaration-layout.js +178 -0
  124. package/dist/discord/components/settings-editor/from-declaration-layout.js.map +1 -0
  125. package/dist/discord/components/settings-editor/from-declaration-search.d.ts +27 -0
  126. package/dist/discord/components/settings-editor/from-declaration-search.d.ts.map +1 -0
  127. package/dist/discord/components/settings-editor/from-declaration-search.js +58 -0
  128. package/dist/discord/components/settings-editor/from-declaration-search.js.map +1 -0
  129. package/dist/discord/components/settings-editor/from-declaration-stored.d.ts +21 -0
  130. package/dist/discord/components/settings-editor/from-declaration-stored.d.ts.map +1 -0
  131. package/dist/discord/components/settings-editor/from-declaration-stored.js +37 -0
  132. package/dist/discord/components/settings-editor/from-declaration-stored.js.map +1 -0
  133. package/dist/discord/components/settings-editor/from-declaration-subject.d.ts +32 -0
  134. package/dist/discord/components/settings-editor/from-declaration-subject.d.ts.map +1 -0
  135. package/dist/discord/components/settings-editor/from-declaration-subject.js +65 -0
  136. package/dist/discord/components/settings-editor/from-declaration-subject.js.map +1 -0
  137. package/dist/discord/components/settings-editor/from-declaration-values.d.ts +57 -0
  138. package/dist/discord/components/settings-editor/from-declaration-values.d.ts.map +1 -0
  139. package/dist/discord/components/settings-editor/from-declaration-values.js +187 -0
  140. package/dist/discord/components/settings-editor/from-declaration-values.js.map +1 -0
  141. package/dist/discord/components/settings-editor/from-declaration.d.ts +39 -0
  142. package/dist/discord/components/settings-editor/from-declaration.d.ts.map +1 -0
  143. package/dist/discord/components/settings-editor/from-declaration.js +132 -0
  144. package/dist/discord/components/settings-editor/from-declaration.js.map +1 -0
  145. package/dist/discord/components/settings-editor/index.d.ts +3 -1
  146. package/dist/discord/components/settings-editor/index.d.ts.map +1 -1
  147. package/dist/discord/components/settings-editor/index.js +6 -3
  148. package/dist/discord/components/settings-editor/index.js.map +1 -1
  149. package/dist/discord/components/settings-editor/mount-settings-editor.d.ts +16 -2
  150. package/dist/discord/components/settings-editor/mount-settings-editor.d.ts.map +1 -1
  151. package/dist/discord/components/settings-editor/mount-settings-editor.js +14 -10
  152. package/dist/discord/components/settings-editor/mount-settings-editor.js.map +1 -1
  153. package/dist/discord/components/settings-editor/navigation.d.ts +20 -0
  154. package/dist/discord/components/settings-editor/navigation.d.ts.map +1 -1
  155. package/dist/discord/components/settings-editor/navigation.js +22 -0
  156. package/dist/discord/components/settings-editor/navigation.js.map +1 -1
  157. package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts +4 -3
  158. package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts.map +1 -1
  159. package/dist/discord/components/settings-editor/settings-editor-card.view.js +60 -34
  160. package/dist/discord/components/settings-editor/settings-editor-card.view.js.map +1 -1
  161. package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts +2 -1
  162. package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts.map +1 -1
  163. package/dist/discord/components/settings-editor/settings-editor-field-modal.js +48 -72
  164. package/dist/discord/components/settings-editor/settings-editor-field-modal.js.map +1 -1
  165. package/dist/discord/components/settings-editor/settings-editor-field-values.d.ts +6 -0
  166. package/dist/discord/components/settings-editor/settings-editor-field-values.d.ts.map +1 -0
  167. package/dist/discord/components/settings-editor/settings-editor-field-values.js +13 -0
  168. package/dist/discord/components/settings-editor/settings-editor-field-values.js.map +1 -0
  169. package/dist/discord/components/settings-editor/settings-editor-fields.d.ts +6 -5
  170. package/dist/discord/components/settings-editor/settings-editor-fields.d.ts.map +1 -1
  171. package/dist/discord/components/settings-editor/settings-editor-fields.js +0 -10
  172. package/dist/discord/components/settings-editor/settings-editor-fields.js.map +1 -1
  173. package/dist/discord/components/settings-editor/settings-editor.view.d.ts +3 -2
  174. package/dist/discord/components/settings-editor/settings-editor.view.d.ts.map +1 -1
  175. package/dist/discord/components/settings-editor/settings-editor.view.js +4 -21
  176. package/dist/discord/components/settings-editor/settings-editor.view.js.map +1 -1
  177. package/dist/discord/events/event-guild-id.d.ts +10 -0
  178. package/dist/discord/events/event-guild-id.d.ts.map +1 -0
  179. package/dist/discord/events/event-guild-id.js +37 -0
  180. package/dist/discord/events/event-guild-id.js.map +1 -0
  181. package/dist/discord/events/event-router.d.ts +17 -3
  182. package/dist/discord/events/event-router.d.ts.map +1 -1
  183. package/dist/discord/events/event-router.js +33 -9
  184. package/dist/discord/events/event-router.js.map +1 -1
  185. package/dist/discord/i18n.d.ts +17 -2
  186. package/dist/discord/i18n.d.ts.map +1 -1
  187. package/dist/discord/i18n.js +43 -6
  188. package/dist/discord/i18n.js.map +1 -1
  189. package/dist/discord/interaction/interaction-locale.d.ts +14 -0
  190. package/dist/discord/interaction/interaction-locale.d.ts.map +1 -0
  191. package/dist/discord/interaction/interaction-locale.js +12 -0
  192. package/dist/discord/interaction/interaction-locale.js.map +1 -0
  193. package/dist/discord/interaction/locale-resolver.d.ts +27 -0
  194. package/dist/discord/interaction/locale-resolver.d.ts.map +1 -0
  195. package/dist/discord/interaction/locale-resolver.js +3 -0
  196. package/dist/discord/interaction/locale-resolver.js.map +1 -0
  197. package/dist/discord/interaction/modal.d.ts.map +1 -1
  198. package/dist/discord/interaction/modal.js +5 -40
  199. package/dist/discord/interaction/modal.js.map +1 -1
  200. package/dist/discord/interaction/reply-locale.d.ts +18 -0
  201. package/dist/discord/interaction/reply-locale.d.ts.map +1 -0
  202. package/dist/discord/interaction/reply-locale.js +25 -0
  203. package/dist/discord/interaction/reply-locale.js.map +1 -0
  204. package/dist/discord/interaction/select-bounds.d.ts +19 -0
  205. package/dist/discord/interaction/select-bounds.d.ts.map +1 -0
  206. package/dist/discord/interaction/select-bounds.js +17 -0
  207. package/dist/discord/interaction/select-bounds.js.map +1 -0
  208. package/dist/discord/interaction/select-menu.d.ts.map +1 -1
  209. package/dist/discord/interaction/select-menu.js +2 -9
  210. package/dist/discord/interaction/select-menu.js.map +1 -1
  211. package/dist/discord/placeholder-registry.d.ts +1 -1
  212. package/dist/discord/placeholder-registry.d.ts.map +1 -1
  213. package/dist/discord/presenter.d.ts +1 -1
  214. package/dist/discord/presenter.d.ts.map +1 -1
  215. package/dist/discord/settings/discord-guild-directory.d.ts +28 -0
  216. package/dist/discord/settings/discord-guild-directory.d.ts.map +1 -0
  217. package/dist/discord/settings/discord-guild-directory.js +171 -0
  218. package/dist/discord/settings/discord-guild-directory.js.map +1 -0
  219. package/dist/discord/settings/guild-locale-resolver.d.ts +25 -0
  220. package/dist/discord/settings/guild-locale-resolver.d.ts.map +1 -0
  221. package/dist/discord/settings/guild-locale-resolver.js +33 -0
  222. package/dist/discord/settings/guild-locale-resolver.js.map +1 -0
  223. package/dist/discord/settings/is-module-disabled.d.ts +16 -0
  224. package/dist/discord/settings/is-module-disabled.d.ts.map +1 -0
  225. package/dist/discord/settings/is-module-disabled.js +20 -0
  226. package/dist/discord/settings/is-module-disabled.js.map +1 -0
  227. package/dist/discord/settings/module-disabled-embed.d.ts +13 -0
  228. package/dist/discord/settings/module-disabled-embed.d.ts.map +1 -0
  229. package/dist/discord/settings/module-disabled-embed.js +16 -0
  230. package/dist/discord/settings/module-disabled-embed.js.map +1 -0
  231. package/dist/discord/settings/require-configured.d.ts +26 -0
  232. package/dist/discord/settings/require-configured.d.ts.map +1 -0
  233. package/dist/discord/settings/require-configured.js +62 -0
  234. package/dist/discord/settings/require-configured.js.map +1 -0
  235. package/dist/discord/ui/color-aliases.d.ts +31 -0
  236. package/dist/discord/ui/color-aliases.d.ts.map +1 -0
  237. package/dist/discord/ui/color-aliases.js +66 -0
  238. package/dist/discord/ui/color-aliases.js.map +1 -0
  239. package/dist/discord/ui/color-input.d.ts +5 -32
  240. package/dist/discord/ui/color-input.d.ts.map +1 -1
  241. package/dist/discord/ui/color-input.js +8 -157
  242. package/dist/discord/ui/color-input.js.map +1 -1
  243. package/dist/discord/ui/colors.d.ts +0 -2
  244. package/dist/discord/ui/colors.d.ts.map +1 -1
  245. package/dist/discord/ui/colors.js +4 -5
  246. package/dist/discord/ui/colors.js.map +1 -1
  247. package/dist/discord/ui/embed.d.ts.map +1 -1
  248. package/dist/discord/ui/embed.js +3 -8
  249. package/dist/discord/ui/embed.js.map +1 -1
  250. package/dist/discord/ui/truncate-text.d.ts +11 -0
  251. package/dist/discord/ui/truncate-text.d.ts.map +1 -0
  252. package/dist/discord/ui/truncate-text.js +19 -0
  253. package/dist/discord/ui/truncate-text.js.map +1 -0
  254. package/dist/errors/error-message.d.ts +9 -0
  255. package/dist/errors/error-message.d.ts.map +1 -0
  256. package/dist/errors/error-message.js +14 -0
  257. package/dist/errors/error-message.js.map +1 -0
  258. package/dist/i18n/business-message.d.ts +1 -1
  259. package/dist/i18n/business-message.d.ts.map +1 -1
  260. package/dist/i18n/catalog.d.ts +9 -0
  261. package/dist/i18n/catalog.d.ts.map +1 -1
  262. package/dist/i18n/catalog.js +4 -0
  263. package/dist/i18n/catalog.js.map +1 -1
  264. package/dist/i18n/locale.d.ts +2 -0
  265. package/dist/i18n/locale.d.ts.map +1 -1
  266. package/dist/i18n/locale.js +3 -1
  267. package/dist/i18n/locale.js.map +1 -1
  268. package/dist/i18n/translator.d.ts +2 -0
  269. package/dist/i18n/translator.d.ts.map +1 -1
  270. package/dist/i18n/translator.js.map +1 -1
  271. package/dist/module/module.d.ts +15 -1
  272. package/dist/module/module.d.ts.map +1 -1
  273. package/dist/resilience/retry.js +2 -2
  274. package/dist/resilience/retry.js.map +1 -1
  275. package/dist/scheduler/scheduler.d.ts.map +1 -1
  276. package/dist/scheduler/scheduler.js +2 -4
  277. package/dist/scheduler/scheduler.js.map +1 -1
  278. package/dist/settings/cache.d.ts +29 -0
  279. package/dist/settings/cache.d.ts.map +1 -0
  280. package/dist/settings/cache.js +47 -0
  281. package/dist/settings/cache.js.map +1 -0
  282. package/dist/settings/choice.d.ts +10 -0
  283. package/dist/settings/choice.d.ts.map +1 -0
  284. package/dist/settings/choice.js +3 -0
  285. package/dist/settings/choice.js.map +1 -0
  286. package/dist/settings/decode-stored.d.ts +20 -0
  287. package/dist/settings/decode-stored.d.ts.map +1 -0
  288. package/dist/settings/decode-stored.js +35 -0
  289. package/dist/settings/decode-stored.js.map +1 -0
  290. package/dist/settings/define-settings.d.ts +52 -0
  291. package/dist/settings/define-settings.d.ts.map +1 -0
  292. package/dist/settings/define-settings.js +141 -0
  293. package/dist/settings/define-settings.js.map +1 -0
  294. package/dist/settings/describe-field.d.ts +19 -0
  295. package/dist/settings/describe-field.d.ts.map +1 -0
  296. package/dist/settings/describe-field.js +131 -0
  297. package/dist/settings/describe-field.js.map +1 -0
  298. package/dist/settings/describe.d.ts +27 -0
  299. package/dist/settings/describe.d.ts.map +1 -0
  300. package/dist/settings/describe.js +62 -0
  301. package/dist/settings/describe.js.map +1 -0
  302. package/dist/settings/duration.d.ts +16 -0
  303. package/dist/settings/duration.d.ts.map +1 -0
  304. package/dist/settings/duration.js +56 -0
  305. package/dist/settings/duration.js.map +1 -0
  306. package/dist/settings/field-order.d.ts +37 -0
  307. package/dist/settings/field-order.d.ts.map +1 -0
  308. package/dist/settings/field-order.js +100 -0
  309. package/dist/settings/field-order.js.map +1 -0
  310. package/dist/settings/field-search.d.ts +10 -0
  311. package/dist/settings/field-search.d.ts.map +1 -0
  312. package/dist/settings/field-search.js +18 -0
  313. package/dist/settings/field-search.js.map +1 -0
  314. package/dist/settings/fields/builders.d.ts +41 -0
  315. package/dist/settings/fields/builders.d.ts.map +1 -0
  316. package/dist/settings/fields/builders.js +83 -0
  317. package/dist/settings/fields/builders.js.map +1 -0
  318. package/dist/settings/fields/field.d.ts +199 -0
  319. package/dist/settings/fields/field.d.ts.map +1 -0
  320. package/dist/settings/fields/field.js +33 -0
  321. package/dist/settings/fields/field.js.map +1 -0
  322. package/dist/settings/fields/zod-schema.d.ts +67 -0
  323. package/dist/settings/fields/zod-schema.d.ts.map +1 -0
  324. package/dist/settings/fields/zod-schema.js +281 -0
  325. package/dist/settings/fields/zod-schema.js.map +1 -0
  326. package/dist/settings/in-memory/in-memory-guild-directory.d.ts +24 -0
  327. package/dist/settings/in-memory/in-memory-guild-directory.d.ts.map +1 -0
  328. package/dist/settings/in-memory/in-memory-guild-directory.js +42 -0
  329. package/dist/settings/in-memory/in-memory-guild-directory.js.map +1 -0
  330. package/dist/settings/in-memory/in-memory-settings-store.d.ts +8 -0
  331. package/dist/settings/in-memory/in-memory-settings-store.d.ts.map +1 -0
  332. package/dist/settings/in-memory/in-memory-settings-store.js +43 -0
  333. package/dist/settings/in-memory/in-memory-settings-store.js.map +1 -0
  334. package/dist/settings/in-memory/in-process-notifier.d.ts +9 -0
  335. package/dist/settings/in-memory/in-process-notifier.d.ts.map +1 -0
  336. package/dist/settings/in-memory/in-process-notifier.js +32 -0
  337. package/dist/settings/in-memory/in-process-notifier.js.map +1 -0
  338. package/dist/settings/index.d.ts +22 -0
  339. package/dist/settings/index.d.ts.map +1 -0
  340. package/dist/settings/index.js +31 -0
  341. package/dist/settings/index.js.map +1 -0
  342. package/dist/settings/is-plain-object.d.ts +6 -0
  343. package/dist/settings/is-plain-object.d.ts.map +1 -0
  344. package/dist/settings/is-plain-object.js +15 -0
  345. package/dist/settings/is-plain-object.js.map +1 -0
  346. package/dist/settings/messages.d.ts +61 -0
  347. package/dist/settings/messages.d.ts.map +1 -0
  348. package/dist/settings/messages.js +155 -0
  349. package/dist/settings/messages.js.map +1 -0
  350. package/dist/settings/migrate.d.ts +35 -0
  351. package/dist/settings/migrate.d.ts.map +1 -0
  352. package/dist/settings/migrate.js +42 -0
  353. package/dist/settings/migrate.js.map +1 -0
  354. package/dist/settings/ports/guild-directory.d.ts +61 -0
  355. package/dist/settings/ports/guild-directory.d.ts.map +1 -0
  356. package/dist/settings/ports/guild-directory.js +3 -0
  357. package/dist/settings/ports/guild-directory.js.map +1 -0
  358. package/dist/settings/ports/settings-changed-notifier.d.ts +20 -0
  359. package/dist/settings/ports/settings-changed-notifier.d.ts.map +1 -0
  360. package/dist/settings/ports/settings-changed-notifier.js +3 -0
  361. package/dist/settings/ports/settings-changed-notifier.js.map +1 -0
  362. package/dist/settings/ports/settings-store.d.ts +76 -0
  363. package/dist/settings/ports/settings-store.d.ts.map +1 -0
  364. package/dist/settings/ports/settings-store.js +3 -0
  365. package/dist/settings/ports/settings-store.js.map +1 -0
  366. package/dist/settings/registry.d.ts +31 -0
  367. package/dist/settings/registry.d.ts.map +1 -0
  368. package/dist/settings/registry.js +102 -0
  369. package/dist/settings/registry.js.map +1 -0
  370. package/dist/settings/settings-declaration-error.d.ts +13 -0
  371. package/dist/settings/settings-declaration-error.d.ts.map +1 -0
  372. package/dist/settings/settings-declaration-error.js +22 -0
  373. package/dist/settings/settings-declaration-error.js.map +1 -0
  374. package/dist/settings/settings-service.d.ts +148 -0
  375. package/dist/settings/settings-service.d.ts.map +1 -0
  376. package/dist/settings/settings-service.js +279 -0
  377. package/dist/settings/settings-service.js.map +1 -0
  378. package/dist/settings/settings-validation-error.d.ts +23 -0
  379. package/dist/settings/settings-validation-error.d.ts.map +1 -0
  380. package/dist/settings/settings-validation-error.js +17 -0
  381. package/dist/settings/settings-validation-error.js.map +1 -0
  382. package/dist/settings/status.d.ts +16 -0
  383. package/dist/settings/status.d.ts.map +1 -0
  384. package/dist/settings/status.js +18 -0
  385. package/dist/settings/status.js.map +1 -0
  386. package/dist/settings/suggest.d.ts +77 -0
  387. package/dist/settings/suggest.d.ts.map +1 -0
  388. package/dist/settings/suggest.js +192 -0
  389. package/dist/settings/suggest.js.map +1 -0
  390. package/dist/settings/system/kernel-settings.d.ts +43 -0
  391. package/dist/settings/system/kernel-settings.d.ts.map +1 -0
  392. package/dist/settings/system/kernel-settings.js +53 -0
  393. package/dist/settings/system/kernel-settings.js.map +1 -0
  394. package/dist/settings/system/module-gate.d.ts +31 -0
  395. package/dist/settings/system/module-gate.d.ts.map +1 -0
  396. package/dist/settings/system/module-gate.js +34 -0
  397. package/dist/settings/system/module-gate.js.map +1 -0
  398. package/dist/settings/testing/index.d.ts +2 -0
  399. package/dist/settings/testing/index.d.ts.map +1 -0
  400. package/dist/settings/testing/index.js +6 -0
  401. package/dist/settings/testing/index.js.map +1 -0
  402. package/dist/settings/testing/settings-store-contract.d.ts +29 -0
  403. package/dist/settings/testing/settings-store-contract.d.ts.map +1 -0
  404. package/dist/settings/testing/settings-store-contract.js +192 -0
  405. package/dist/settings/testing/settings-store-contract.js.map +1 -0
  406. package/dist/settings/timed-search.d.ts +26 -0
  407. package/dist/settings/timed-search.d.ts.map +1 -0
  408. package/dist/settings/timed-search.js +48 -0
  409. package/dist/settings/timed-search.js.map +1 -0
  410. package/dist/settings/types.d.ts +37 -0
  411. package/dist/settings/types.d.ts.map +1 -0
  412. package/dist/settings/types.js +3 -0
  413. package/dist/settings/types.js.map +1 -0
  414. package/dist/settings/validate.d.ts +55 -0
  415. package/dist/settings/validate.d.ts.map +1 -0
  416. package/dist/settings/validate.js +196 -0
  417. package/dist/settings/validate.js.map +1 -0
  418. package/package.json +463 -88
  419. package/src/authz/require-level-guard.ts +1 -1
  420. package/src/color.ts +134 -0
  421. package/src/discord/command/all-required.ts +12 -0
  422. package/src/discord/command/autocomplete-dispatcher.ts +2 -1
  423. package/src/discord/command/autocomplete-limits.ts +2 -0
  424. package/src/discord/command/autocomplete.ts +8 -7
  425. package/src/discord/command/command-responders.ts +39 -0
  426. package/src/discord/command/context.ts +14 -54
  427. package/src/discord/command/cooldown-guard.ts +1 -1
  428. package/src/discord/command/create-command.ts +19 -94
  429. package/src/discord/command/create-context-menu-command.ts +19 -97
  430. package/src/discord/command/errors.ts +1 -1
  431. package/src/discord/command/guard.ts +24 -12
  432. package/src/discord/command/options.ts +202 -412
  433. package/src/discord/command/passes-guard.ts +37 -0
  434. package/src/discord/command/permission-guard.ts +1 -1
  435. package/src/discord/command/render-command-failure.ts +71 -0
  436. package/src/discord/command/route-key.ts +9 -0
  437. package/src/discord/command/router.ts +73 -12
  438. package/src/discord/command/send-embed.ts +42 -0
  439. package/src/discord/command/types.ts +7 -1
  440. package/src/discord/components/component-access.ts +20 -0
  441. package/src/discord/components/component-router.ts +73 -36
  442. package/src/discord/components/interactive-message/index.ts +5 -3
  443. package/src/discord/components/interactive-message/interactive-message-collector.ts +4 -68
  444. package/src/discord/components/interactive-message/message-edit-options.ts +47 -0
  445. package/src/discord/components/interactive-message/mount-interactive-message.ts +5 -3
  446. package/src/discord/components/interactive-message/state-store.ts +20 -0
  447. package/src/discord/components/paginator/mount-paginator.ts +3 -3
  448. package/src/discord/components/paginator/paginator-view.ts +1 -1
  449. package/src/discord/components/persistent-paginator.ts +8 -9
  450. package/src/discord/components/settings-editor/from-declaration-controls.ts +371 -0
  451. package/src/discord/components/settings-editor/from-declaration-display.ts +155 -0
  452. package/src/discord/components/settings-editor/from-declaration-layout.ts +247 -0
  453. package/src/discord/components/settings-editor/from-declaration-search.ts +77 -0
  454. package/src/discord/components/settings-editor/from-declaration-stored.ts +39 -0
  455. package/src/discord/components/settings-editor/from-declaration-subject.ts +110 -0
  456. package/src/discord/components/settings-editor/from-declaration-values.ts +240 -0
  457. package/src/discord/components/settings-editor/from-declaration.ts +218 -0
  458. package/src/discord/components/settings-editor/index.ts +5 -2
  459. package/src/discord/components/settings-editor/mount-settings-editor.ts +28 -10
  460. package/src/discord/components/settings-editor/navigation.ts +41 -0
  461. package/src/discord/components/settings-editor/settings-editor-card.view.ts +79 -36
  462. package/src/discord/components/settings-editor/settings-editor-field-modal.ts +76 -68
  463. package/src/discord/components/settings-editor/settings-editor-field-values.ts +11 -0
  464. package/src/discord/components/settings-editor/settings-editor-fields.ts +6 -11
  465. package/src/discord/components/settings-editor/settings-editor.view.ts +11 -24
  466. package/src/discord/events/event-guild-id.ts +35 -0
  467. package/src/discord/events/event-router.ts +46 -13
  468. package/src/discord/i18n.ts +47 -8
  469. package/src/discord/interaction/interaction-locale.ts +19 -0
  470. package/src/discord/interaction/locale-resolver.ts +28 -0
  471. package/src/discord/interaction/modal.ts +5 -40
  472. package/src/discord/interaction/reply-locale.ts +35 -0
  473. package/src/discord/interaction/select-bounds.ts +33 -0
  474. package/src/discord/interaction/select-menu.ts +2 -9
  475. package/src/discord/placeholder-registry.ts +1 -1
  476. package/src/discord/presenter.ts +1 -1
  477. package/src/discord/settings/discord-guild-directory.ts +214 -0
  478. package/src/discord/settings/guild-locale-resolver.ts +38 -0
  479. package/src/discord/settings/is-module-disabled.ts +22 -0
  480. package/src/discord/settings/module-disabled-embed.ts +19 -0
  481. package/src/discord/settings/require-configured.ts +68 -0
  482. package/src/discord/ui/color-aliases.ts +70 -0
  483. package/src/discord/ui/color-input.ts +8 -173
  484. package/src/discord/ui/colors.ts +2 -3
  485. package/src/discord/ui/embed.ts +3 -9
  486. package/src/discord/ui/truncate-text.ts +15 -0
  487. package/src/errors/error-message.ts +10 -0
  488. package/src/i18n/business-message.ts +1 -1
  489. package/src/i18n/catalog.ts +14 -0
  490. package/src/i18n/locale.ts +3 -0
  491. package/src/i18n/translator.ts +3 -0
  492. package/src/module/module.ts +15 -1
  493. package/src/resilience/retry.ts +1 -1
  494. package/src/scheduler/scheduler.ts +2 -5
  495. package/src/settings/cache.ts +70 -0
  496. package/src/settings/choice.ts +9 -0
  497. package/src/settings/decode-stored.ts +40 -0
  498. package/src/settings/define-settings.ts +214 -0
  499. package/src/settings/describe-field.ts +155 -0
  500. package/src/settings/describe.ts +90 -0
  501. package/src/settings/duration.ts +54 -0
  502. package/src/settings/field-order.ts +128 -0
  503. package/src/settings/field-search.ts +16 -0
  504. package/src/settings/fields/builders.ts +161 -0
  505. package/src/settings/fields/field.ts +205 -0
  506. package/src/settings/fields/zod-schema.ts +300 -0
  507. package/src/settings/in-memory/in-memory-guild-directory.ts +69 -0
  508. package/src/settings/in-memory/in-memory-settings-store.ts +46 -0
  509. package/src/settings/in-memory/in-process-notifier.ts +40 -0
  510. package/src/settings/index.ts +72 -0
  511. package/src/settings/is-plain-object.ts +11 -0
  512. package/src/settings/messages.ts +161 -0
  513. package/src/settings/migrate.ts +58 -0
  514. package/src/settings/ports/guild-directory.ts +48 -0
  515. package/src/settings/ports/settings-changed-notifier.ts +20 -0
  516. package/src/settings/ports/settings-store.ts +74 -0
  517. package/src/settings/registry.ts +137 -0
  518. package/src/settings/settings-declaration-error.ts +16 -0
  519. package/src/settings/settings-service.ts +535 -0
  520. package/src/settings/settings-validation-error.ts +41 -0
  521. package/src/settings/status.ts +25 -0
  522. package/src/settings/suggest.ts +267 -0
  523. package/src/settings/system/kernel-settings.ts +74 -0
  524. package/src/settings/system/module-gate.ts +49 -0
  525. package/src/settings/testing/index.ts +7 -0
  526. package/src/settings/testing/settings-store-contract.ts +278 -0
  527. package/src/settings/timed-search.ts +56 -0
  528. package/src/settings/types.ts +42 -0
  529. package/src/settings/validate.ts +298 -0
@@ -0,0 +1,38 @@
1
+ import type { LocaleResolver } from "@/discord/interaction/locale-resolver";
2
+ import { normalizeLocale, resolveLocale } from "@/i18n/locale";
3
+ import type { Translator } from "@/i18n/translator";
4
+ import type { SettingsRegistry } from "@/settings/registry";
5
+ import type { SettingsService } from "@/settings/settings-service";
6
+
7
+ /**
8
+ * The default {@link LocaleResolver} of a bot with settings (FR-038): the
9
+ * member's locale if supported, then the guild's language setting
10
+ * (`registry.kernel`, read through the service's cache), then the guild's
11
+ * Discord locale if supported, then the translator's default.
12
+ *
13
+ * The setting is read only when the member's locale is not supported, and
14
+ * never outside a guild. A failed read rejects: the kernel's
15
+ * reply paths log it and fall back to the interaction's own locales.
16
+ *
17
+ * @param deps.service - reads the guild's kernel settings.
18
+ * @param deps.registry - holds the kernel declaration.
19
+ * @param deps.translator - gives the bot's default language.
20
+ * @returns the resolver to hand to the command and component routers.
21
+ */
22
+ export function createGuildLocaleResolver(deps: {
23
+ service: SettingsService;
24
+ registry: SettingsRegistry;
25
+ translator: Translator;
26
+ }): LocaleResolver {
27
+ const { service, registry, translator } = deps;
28
+ return {
29
+ async resolve({ locale, guildLocale, guildId }) {
30
+ const member = normalizeLocale(locale);
31
+ if (member !== undefined || !guildId) {
32
+ return resolveLocale([member, guildLocale], translator.defaultLocale);
33
+ }
34
+ const setting = (await service.get(registry.kernel, guildId)).locale;
35
+ return resolveLocale([setting, guildLocale], translator.defaultLocale);
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,22 @@
1
+ import type { ModuleGate } from "@/settings/system/module-gate";
2
+
3
+ /**
4
+ * Whether a router must skip a handler because its module is disabled on the
5
+ * guild it runs for. Nothing is gated without a gate, for a handler registered
6
+ * without a module, or outside a guild (FR-036).
7
+ *
8
+ * @param params.gate - the bot's module gate, if the router was given one.
9
+ * @param params.moduleName - the module that registered the handler.
10
+ * @param params.guildId - the guild the handler would run for, `null` outside one.
11
+ */
12
+ export async function isModuleDisabled(params: {
13
+ gate: ModuleGate | undefined;
14
+ moduleName: string | undefined;
15
+ guildId: string | null | undefined;
16
+ }): Promise<boolean> {
17
+ const { gate, moduleName, guildId } = params;
18
+ if (gate === undefined || moduleName === undefined || !guildId) {
19
+ return false;
20
+ }
21
+ return !(await gate.isEnabled(moduleName, guildId));
22
+ }
@@ -0,0 +1,19 @@
1
+ import type { EmbedBuilder } from "discord.js";
2
+ import type { LocaleSubject } from "@/discord/interaction/locale-resolver";
3
+ import { type ReplyLocaleDeps, replyLocale } from "@/discord/interaction/reply-locale";
4
+ import type { Presenter } from "@/discord/presenter";
5
+ import { SETTINGS_MESSAGES } from "@/settings/messages";
6
+
7
+ /**
8
+ * The answer to an interaction with a module disabled on its guild: the
9
+ * translated "disabled on this server" message, as a denial in the reply
10
+ * language the router's `LocaleResolver` gives (or the interaction's own).
11
+ */
12
+ export async function moduleDisabledEmbed(
13
+ interaction: LocaleSubject,
14
+ deps: ReplyLocaleDeps & { readonly presenter: Presenter },
15
+ ): Promise<EmbedBuilder> {
16
+ const locale = await replyLocale(interaction, deps);
17
+ const message = deps.translator.translate(locale, SETTINGS_MESSAGES.moduleDisabled);
18
+ return deps.presenter.denial(message, locale);
19
+ }
@@ -0,0 +1,68 @@
1
+ import { type BaseInteraction, PermissionFlagsBits } from "discord.js";
2
+ import type { Guard, GuardResult } from "@/discord/command/guard";
3
+ import { CORE_MESSAGES } from "@/discord/i18n";
4
+ import { replyLocale } from "@/discord/interaction/reply-locale";
5
+ import type { SettingsDeclaration } from "@/settings/define-settings";
6
+ import { SETTINGS_MESSAGES } from "@/settings/messages";
7
+ import type { SettingsService } from "@/settings/settings-service";
8
+
9
+ const ALLOW: GuardResult = { ok: true };
10
+
11
+ const NOT_CONFIGURED: GuardResult = {
12
+ ok: false,
13
+ message: { key: SETTINGS_MESSAGES.moduleNotConfigured },
14
+ };
15
+
16
+ /**
17
+ * Block a command or a component until its module is configured on the guild
18
+ * (FR-041): until `service.status` lists no missing field, the member gets the
19
+ * translated "not configured" message instead of the handler.
20
+ *
21
+ * - Members allowed to configure the bot (the `ManageGuild` permission, until
22
+ * the permissions feature exists) also read which settings are missing, by
23
+ * their translated labels, in the reply language (the pipeline's
24
+ * `LocaleResolver`). Other members never see them.
25
+ * - The status is read through the service's cache, which every write
26
+ * invalidates: the guard passes as soon as the value is set, no restart.
27
+ * - Outside a guild there is nothing to configure for: denied like
28
+ * `guildOnlyGuard`, without reading any setting.
29
+ *
30
+ * Typed over `BaseInteraction`, so the same guard is a command's `guard` and a
31
+ * component handler's `guard`.
32
+ *
33
+ * @param declaration - the module's settings declaration.
34
+ * @param service - reads the guild's configuration status.
35
+ */
36
+ export function requireConfigured(
37
+ declaration: SettingsDeclaration,
38
+ service: SettingsService,
39
+ ): Guard<BaseInteraction> {
40
+ return {
41
+ async check(interaction, runtime) {
42
+ if (!interaction.guildId) {
43
+ return { ok: false, message: { key: CORE_MESSAGES.guardGuildOnly } };
44
+ }
45
+ const { missing } = await service.status(declaration, interaction.guildId);
46
+ if (missing.length === 0) {
47
+ return ALLOW;
48
+ }
49
+ const canConfigure =
50
+ interaction.memberPermissions?.has(PermissionFlagsBits.ManageGuild) ?? false;
51
+ if (!canConfigure || runtime === undefined) {
52
+ return NOT_CONFIGURED;
53
+ }
54
+ const { translator } = runtime;
55
+ const locale = await replyLocale(interaction, runtime);
56
+ const fields = missing
57
+ .map((key) => translator.translate(locale, declaration.fields[key]?.label ?? key))
58
+ .join(", ");
59
+ return {
60
+ ok: false,
61
+ message: [
62
+ translator.translate(locale, SETTINGS_MESSAGES.moduleNotConfigured),
63
+ translator.translate(locale, SETTINGS_MESSAGES.moduleNotConfiguredMissing, { fields }),
64
+ ].join(" "),
65
+ };
66
+ },
67
+ };
68
+ }
@@ -0,0 +1,70 @@
1
+ import { BASE_COLOR_NAMES, indexColorNames, normalizeColorName } from "@/color";
2
+ import { ColorAliasTooLongError } from "@/discord/ui/color-input-errors";
3
+
4
+ /**
5
+ * The names the kernel ships: the vendor-neutral base colours, `blurple` included.
6
+ *
7
+ * A bot adds its own — the colour it calls by its brand's name — through
8
+ * {@link registerColorAliases}, which is also why this table is not the one
9
+ * lookups read: {@link COLOR_ALIASES} is.
10
+ */
11
+ const BUILT_IN_COLOR_ALIASES: Readonly<Record<string, string>> = {
12
+ ...BASE_COLOR_NAMES,
13
+ };
14
+
15
+ /**
16
+ * Every name a lookup answers to: the kernel's own, plus whatever the bot
17
+ * registered. Exposed for inspection — a screen listing the colours it accepts
18
+ * reads this — never for mutation, which goes through
19
+ * {@link registerColorAliases} so the length check cannot be skipped.
20
+ */
21
+ export const COLOR_ALIASES: Readonly<Record<string, string>> = {
22
+ ...BUILT_IN_COLOR_ALIASES,
23
+ };
24
+
25
+ const COLORS_BY_NORMALIZED_NAME = indexColorNames(COLOR_ALIASES);
26
+
27
+ /** The `#rrggbb` a recognised colour name stands for, `null` for anything else. */
28
+ export function resolveNamedColor(raw: string): string | null {
29
+ return COLORS_BY_NORMALIZED_NAME.get(normalizeColorName(raw)) ?? null;
30
+ }
31
+
32
+ /** `#RRGGBB` — the longest hexadecimal form the parser accepts. */
33
+ const HEX_INPUT_LENGTH = 7;
34
+
35
+ /**
36
+ * What a colour input field may hold.
37
+ *
38
+ * Derived from the built-in table rather than written down, so adding a longer
39
+ * name here cannot leave the field silently truncating it at the client — and
40
+ * fixed rather than recomputed on registration, because field definitions read
41
+ * it once at module load, long before a bot registers anything.
42
+ */
43
+ export const MAX_COLOR_INPUT_LENGTH = Math.max(
44
+ HEX_INPUT_LENGTH,
45
+ ...Object.keys(BUILT_IN_COLOR_ALIASES).map((name) => name.length),
46
+ );
47
+
48
+ /**
49
+ * Teach the parser the colours this bot calls by name — its brand gold, its
50
+ * team colours — from the composition root, before any module is constructed.
51
+ *
52
+ * A name over {@link MAX_COLOR_INPUT_LENGTH} is refused rather than accepted:
53
+ * the input field is already sized, so registering a longer name would let an
54
+ * administrator type a colour the client cuts short, which then reads as the
55
+ * parser rejecting a name it actually knows.
56
+ *
57
+ * @throws ColorAliasTooLongError when a name cannot fit the input field.
58
+ */
59
+ export function registerColorAliases(aliases: Readonly<Record<string, string>>): void {
60
+ for (const [name, hex] of Object.entries(aliases)) {
61
+ if (name.length > MAX_COLOR_INPUT_LENGTH) {
62
+ throw new ColorAliasTooLongError(name, MAX_COLOR_INPUT_LENGTH);
63
+ }
64
+ // Both views are written: the table is what a screen lists, the map is what
65
+ // a lookup reads, and a name present in one but not the other would be a
66
+ // colour the bot advertises and refuses.
67
+ (COLOR_ALIASES as Record<string, string>)[name] = hex;
68
+ COLORS_BY_NORMALIZED_NAME.set(normalizeColorName(name), hex);
69
+ }
70
+ }
@@ -1,179 +1,14 @@
1
- import { ColorAliasTooLongError } from "@/discord/ui/color-input-errors";
2
- import { DISCORD_BLURPLE } from "@/discord/ui/colors";
3
-
4
- /**
5
- * The generic web colours, at the value the name carries everywhere else — an
6
- * administrator typing `orange` gets the orange they would get in CSS, not a
7
- * shade this module invented.
8
- */
9
- const RED = "#ff0000";
10
- const BLUE = "#0000ff";
11
- const GREEN = "#008000";
12
- const YELLOW = "#ffff00";
13
- const ORANGE = "#ffa500";
14
- const PURPLE = "#800080";
15
- const PINK = "#ffc0cb";
16
- const WHITE = "#ffffff";
17
-
18
- /**
19
- * Not `#000000`: Discord reads a zero colour as "no colour set" and renders the
20
- * default grey bar instead, so pure black is the one value an administrator
21
- * could ask for and never see. One step off zero reads as black.
22
- */
23
- const BLACK = "#010101";
24
-
25
- const GREY = "#808080";
26
- const CYAN = "#00ffff";
27
- const BROWN = "#a52a2a";
28
- const SILVER = "#c0c0c0";
29
- const TURQUOISE = "#40e0d0";
30
- const MAGENTA = "#ff00ff";
31
- const NAVY = "#000080";
32
-
33
- /**
34
- * Colour names an administrator may type instead of a hex code, French and
35
- * English side by side.
36
- *
37
- * This table is **input vocabulary, not display text**. Nothing in it is ever
38
- * shown to anyone, and its keys must never be translated at runtime: a catalog
39
- * lookup resolves against the reply locale, which would make `rouge` stop
40
- * working the moment an admin's Discord is set to English — the opposite of the
41
- * point. Both spellings are accepted from everyone, always, which only a flat
42
- * table can promise.
43
- *
44
- * These are the names the kernel ships. A bot adds its own — the colour it
45
- * calls by its brand's name — through {@link registerColorAliases}, which is
46
- * also why this table is not the one lookups read: {@link COLOR_ALIASES} is.
47
- */
48
- const BUILT_IN_COLOR_ALIASES: Readonly<Record<string, string>> = {
49
- red: RED,
50
- rouge: RED,
51
- blue: BLUE,
52
- bleu: BLUE,
53
- green: GREEN,
54
- vert: GREEN,
55
- yellow: YELLOW,
56
- jaune: YELLOW,
57
- orange: ORANGE,
58
- purple: PURPLE,
59
- // French for purple, not the CSS `violet` (a pale pink): an admin typing it
60
- // here is asking for the colour the word means in their own language.
61
- violet: PURPLE,
62
- pink: PINK,
63
- rose: PINK,
64
- white: WHITE,
65
- blanc: WHITE,
66
- black: BLACK,
67
- noir: BLACK,
68
- grey: GREY,
69
- gray: GREY,
70
- gris: GREY,
71
- cyan: CYAN,
72
- brown: BROWN,
73
- marron: BROWN,
74
- silver: SILVER,
75
- argent: SILVER,
76
- argenté: SILVER,
77
- turquoise: TURQUOISE,
78
- magenta: MAGENTA,
79
- navy: NAVY,
80
- marine: NAVY,
81
- blurple: DISCORD_BLURPLE,
82
- };
83
-
84
- /**
85
- * Accents must not be a trap: `dore` and `doré` are the same request, and an
86
- * administrator has no way of knowing which one this file happened to spell.
87
- * Case and surrounding whitespace are folded for the same reason.
88
- */
89
- function normalizeColorName(raw: string): string {
90
- return raw
91
- .trim()
92
- .toLowerCase()
93
- .normalize("NFD")
94
- .replace(/\p{Diacritic}/gu, "");
95
- }
96
-
97
- /**
98
- * Every name a lookup answers to: the kernel's own, plus whatever the bot
99
- * registered. Exposed for inspection — a screen listing the colours it accepts
100
- * reads this — never for mutation, which goes through
101
- * {@link registerColorAliases} so the length check cannot be skipped.
102
- */
103
- export const COLOR_ALIASES: Readonly<Record<string, string>> = {
104
- ...BUILT_IN_COLOR_ALIASES,
105
- };
106
-
107
- const COLORS_BY_NORMALIZED_NAME = new Map(
108
- Object.entries(COLOR_ALIASES).map(([name, hex]) => [normalizeColorName(name), hex]),
109
- );
110
-
111
- /** The `#rrggbb` a recognised colour name stands for, `null` for anything else. */
112
- function resolveNamedColor(raw: string): string | null {
113
- return COLORS_BY_NORMALIZED_NAME.get(normalizeColorName(raw)) ?? null;
114
- }
115
-
116
- /** `#RRGGBB` — the longest hexadecimal form the parser accepts. */
117
- const HEX_INPUT_LENGTH = 7;
118
-
119
- /**
120
- * What a colour input field may hold.
121
- *
122
- * Derived from the built-in table rather than written down, so adding a longer
123
- * name here cannot leave the field silently truncating it at the client — and
124
- * fixed rather than recomputed on registration, because field definitions read
125
- * it once at module load, long before a bot registers anything.
126
- */
127
- export const MAX_COLOR_INPUT_LENGTH = Math.max(
128
- HEX_INPUT_LENGTH,
129
- ...Object.keys(BUILT_IN_COLOR_ALIASES).map((name) => name.length),
130
- );
131
-
132
- /**
133
- * Teach the parser the colours this bot calls by name — its brand gold, its
134
- * team colours — from the composition root, before any module is constructed.
135
- *
136
- * A name over {@link MAX_COLOR_INPUT_LENGTH} is refused rather than accepted:
137
- * the input field is already sized, so registering a longer name would let an
138
- * administrator type a colour the client cuts short, which then reads as the
139
- * parser rejecting a name it actually knows.
140
- *
141
- * @throws ColorAliasTooLongError when a name cannot fit the input field.
142
- */
143
- export function registerColorAliases(aliases: Readonly<Record<string, string>>): void {
144
- for (const [name, hex] of Object.entries(aliases)) {
145
- if (name.length > MAX_COLOR_INPUT_LENGTH) {
146
- throw new ColorAliasTooLongError(name, MAX_COLOR_INPUT_LENGTH);
147
- }
148
- // Both views are written: the table is what a screen lists, the map is what
149
- // a lookup reads, and a name present in one but not the other would be a
150
- // colour the bot advertises and refuses.
151
- (COLOR_ALIASES as Record<string, string>)[name] = hex;
152
- COLORS_BY_NORMALIZED_NAME.set(normalizeColorName(name), hex);
153
- }
154
- }
155
-
156
- /** Discord accepts `#RGB` and `#RRGGBB`, with or without the leading hash. */
157
- const HEX_COLOR_PATTERN = /^#?(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
1
+ import { parseHexColor } from "@/color";
2
+ import { resolveNamedColor } from "@/discord/ui/color-aliases";
158
3
 
159
4
  /**
160
5
  * Normalize a colour input to `#RRGGBB`, from a hexadecimal code or from one of
161
- * the names in {@link COLOR_ALIASES} — an administrator picking a colour should
162
- * not have to look a hex code up. `null` for anything that is neither: the
163
- * refusal an administrator reads belongs to whichever screen asked, so core
164
- * reports that it could not read the value and leaves the wording to the caller.
6
+ * the names in {@link import("@/discord/ui/color-aliases").COLOR_ALIASES} — an
7
+ * administrator picking a colour should not have to look a hex code up. `null`
8
+ * for anything that is neither: the refusal an administrator reads belongs to
9
+ * whichever screen asked, so core reports that it could not read the value and
10
+ * leaves the wording to the caller.
165
11
  */
166
12
  export function parseColorInput(raw: string): string | null {
167
- const value = raw.trim();
168
- const named = resolveNamedColor(value);
169
- if (named !== null) {
170
- return named;
171
- }
172
- if (!HEX_COLOR_PATTERN.test(value)) {
173
- return null;
174
- }
175
- const digits = value.replace("#", "").toLowerCase();
176
- const expanded =
177
- digits.length === 3 ? [...digits].map((digit) => `${digit}${digit}`).join("") : digits;
178
- return `#${expanded}`;
13
+ return resolveNamedColor(raw) ?? parseHexColor(raw);
179
14
  }
@@ -1,3 +1,5 @@
1
+ import { DISCORD_BLURPLE } from "@/color";
2
+
1
3
  /**
2
4
  * The five roles an embed colour can play. A bot restyles by supplying its own
3
5
  * values for these, never by adding a sixth: a role the kernel does not know is
@@ -18,9 +20,6 @@ export function toDiscordColor(hex: string): number {
18
20
  return Number.parseInt(hex.slice(1), 16);
19
21
  }
20
22
 
21
- /** Discord's own brand colour, the fallback for a bot that declares no palette. */
22
- export const DISCORD_BLURPLE = "#5865f2";
23
-
24
23
  /**
25
24
  * What every embed renders as until the composition root says otherwise. Kept
26
25
  * deliberately unbranded: a kernel that shipped one bot's colours would make
@@ -1,4 +1,5 @@
1
1
  import { EmbedBuilder } from "discord.js";
2
+ import { truncateText } from "@/discord/ui/truncate-text";
2
3
 
3
4
  /** Discord hard limit on the number of fields per embed. */
4
5
  const MAX_EMBED_FIELDS = 25;
@@ -35,8 +36,8 @@ export function appendBoundedFields(
35
36
  if (shown >= MAX_EMBED_FIELDS) {
36
37
  break;
37
38
  }
38
- const name = truncate(field.name, MAX_FIELD_NAME);
39
- const value = truncate(field.value, MAX_FIELD_VALUE);
39
+ const name = truncateText(field.name, MAX_FIELD_NAME);
40
+ const value = truncateText(field.value, MAX_FIELD_VALUE);
40
41
  if (usedChars + name.length + value.length > MAX_EMBED_TOTAL - EMBED_TOTAL_SAFETY_MARGIN) {
41
42
  break;
42
43
  }
@@ -51,13 +52,6 @@ export function appendBoundedFields(
51
52
  }
52
53
  }
53
54
 
54
- function truncate(text: string, max: number): string {
55
- if (text.length <= max) {
56
- return text;
57
- }
58
- return `${text.slice(0, max - 1)}…`;
59
- }
60
-
61
55
  /** Build a plain titled embed. */
62
56
  export function buildEmbed(title: string, description: string): EmbedBuilder {
63
57
  return new EmbedBuilder().setTitle(title).setDescription(description);
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `text` cut to at most `max` characters, its last one an ellipsis when it
3
+ * had to be cut: Discord rejects a whole message over one text that is too
4
+ * long, so a value of unknown length is shortened rather than sent as is.
5
+ *
6
+ * @param text - the text to fit.
7
+ * @param max - the most characters the result may hold.
8
+ * @returns `text` itself when it fits, its shortened form otherwise.
9
+ */
10
+ export function truncateText(text: string, max: number): string {
11
+ if (text.length <= max) {
12
+ return text;
13
+ }
14
+ return `${text.slice(0, max - 1)}…`;
15
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The message of an unknown thrown value, for a log field that only needs the
3
+ * one line: an `Error`'s own message, anything else as its string form.
4
+ *
5
+ * {@link import("@/errors/describe-error").describeError} is the fuller record
6
+ * — name, stack and nested causes — for failures worth investigating.
7
+ */
8
+ export function errorMessage(error: unknown): string {
9
+ return error instanceof Error ? error.message : String(error);
10
+ }
@@ -1,4 +1,4 @@
1
- import type { BusinessError } from "@/errors";
1
+ import type { BusinessError } from "@/errors/business-error";
2
2
  import type { Locale } from "@/i18n/locale";
3
3
  import type { Translator } from "@/i18n/translator";
4
4
 
@@ -18,6 +18,15 @@ export interface CatalogEntry {
18
18
  */
19
19
  export type Catalog = Readonly<Record<string, CatalogEntry>>;
20
20
 
21
+ /** The catalog keys a `*_MESSAGES` constant holds. */
22
+ export type MessageKey<M extends Readonly<Record<string, string>>> = M[keyof M];
23
+
24
+ /**
25
+ * A catalog with exactly one entry per key: a key without its entry, or an
26
+ * entry under a key no `*_MESSAGES` constant declares, fails to compile.
27
+ */
28
+ export type KeyedCatalog<K extends string> = Readonly<Record<K, CatalogEntry>>;
29
+
21
30
  /**
22
31
  * Accumulates the core catalog and every module's catalog at boot. Registration
23
32
  * is fail-fast: a duplicate key throws {@link DuplicateTranslationKeyError}
@@ -35,6 +44,11 @@ export class TranslationRegistry {
35
44
  }
36
45
  }
37
46
 
47
+ /** Whether `key` was registered by some catalog (and so has an English source). */
48
+ has(key: string): boolean {
49
+ return this.entries.has(key);
50
+ }
51
+
38
52
  get(key: string): CatalogEntry | undefined {
39
53
  return this.entries.get(key);
40
54
  }
@@ -7,6 +7,9 @@ export const SUPPORTED_LOCALES = ["en", "fr"] as const;
7
7
 
8
8
  export type Locale = (typeof SUPPORTED_LOCALES)[number];
9
9
 
10
+ /** The language every catalog entry provides (`CatalogEntry.en`): the last fallback of any lookup. */
11
+ export const SOURCE_LOCALE: Locale = "en";
12
+
10
13
  /**
11
14
  * Map a raw Discord locale string (`en-US`, `en-GB`, `fr`, `de`…) to a
12
15
  * supported base language. Regional variants collapse to their base tag, so
@@ -22,6 +22,9 @@ export interface Translator {
22
22
  resolve(locale: Locale, text: LocalizedText): string;
23
23
  }
24
24
 
25
+ /** Translates one catalog key into a locale already chosen by the caller. */
26
+ export type TranslateKey = (key: string) => string;
27
+
25
28
  export interface TranslatorOptions {
26
29
  readonly defaultLocale: Locale;
27
30
  readonly logger: Logger;
@@ -1,8 +1,9 @@
1
1
  import type { ContextMenuCommand, SlashCommand } from "@/discord/command/types";
2
2
  import type { ComponentHandler } from "@/discord/components/component-router";
3
3
  import type { DiscordEvent } from "@/discord/events/types";
4
- import type { Catalog } from "@/i18n";
4
+ import type { Catalog } from "@/i18n/catalog";
5
5
  import type { ScheduledJob } from "@/scheduler/scheduler";
6
+ import type { SettingsDeclaration } from "@/settings/define-settings";
6
7
 
7
8
  /**
8
9
  * A self-contained vertical feature slice. A module bundles everything it
@@ -17,6 +18,12 @@ import type { ScheduledJob } from "@/scheduler/scheduler";
17
18
  export interface BotModule {
18
19
  /** Stable identifier, used in logs. */
19
20
  readonly name: string;
21
+ /**
22
+ * Catalog key of the module's name as administrators read it (for example on
23
+ * its enable/disable toggle). Without one, screens show {@link name} as is and
24
+ * the translator logs it as an unknown key.
25
+ */
26
+ readonly label?: string;
20
27
  readonly commands?: readonly SlashCommand[];
21
28
  /** Right-click entries on a member or a message (the Apps submenu). */
22
29
  readonly contextMenuCommands?: readonly ContextMenuCommand[];
@@ -30,6 +37,13 @@ export interface BotModule {
30
37
  * duplicate key across modules fails the boot, never a runtime lookup.
31
38
  */
32
39
  readonly translations?: Catalog;
40
+ /**
41
+ * The module's settings declarations. The composition root collects them into
42
+ * the settings registry at boot, the same way it collects `translations`.
43
+ */
44
+ readonly settings?: readonly SettingsDeclaration[];
45
+ /** Whether the module starts enabled in a guild that never toggled it. Defaults to `true`. */
46
+ readonly defaultEnabled?: boolean;
33
47
  /** One-off initialisation run once at boot, after the client is ready. */
34
48
  setup?(): Promise<void> | void;
35
49
  /**
@@ -1,4 +1,4 @@
1
- import { BusinessError } from "@/errors";
1
+ import { BusinessError } from "@/errors/business-error";
2
2
 
3
3
  const MIN_ATTEMPTS = 1;
4
4
  const DEFAULT_ATTEMPTS = 3;
@@ -1,4 +1,5 @@
1
1
  import cron from "node-cron";
2
+ import { errorMessage } from "@/errors/error-message";
2
3
  import type { Logger } from "@/logger";
3
4
  import {
4
5
  AmbiguousJobScheduleError,
@@ -64,10 +65,6 @@ export interface Scheduler {
64
65
  stop(): void;
65
66
  }
66
67
 
67
- function describeError(error: unknown): string {
68
- return error instanceof Error ? error.message : String(error);
69
- }
70
-
71
68
  /**
72
69
  * A job as it actually arrives at runtime. The exported union already forbids
73
70
  * the invalid combinations, which is exactly why the check below cannot be
@@ -233,7 +230,7 @@ export class CronScheduler implements Scheduler {
233
230
  try {
234
231
  await job.run();
235
232
  } catch (error) {
236
- this.logger.error({ job: job.name, err: describeError(error) }, "Scheduled job failed");
233
+ this.logger.error({ job: job.name, err: errorMessage(error) }, "Scheduled job failed");
237
234
  }
238
235
  }
239
236