@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
@@ -1,20 +1,37 @@
1
1
  import type { Client } from "discord.js";
2
+ import { eventGuildId } from "@/discord/events/event-guild-id";
2
3
  import type { DiscordEvent, EventListener } from "@/discord/events/types";
4
+ import { isModuleDisabled } from "@/discord/settings/is-module-disabled";
5
+ import { errorMessage } from "@/errors/error-message";
3
6
  import type { Logger } from "@/logger";
7
+ import type { ModuleGate } from "@/settings/system/module-gate";
8
+
9
+ /** A registered event and the module it belongs to, if any. */
10
+ interface RegisteredEvent {
11
+ readonly event: DiscordEvent;
12
+ readonly moduleName: string | undefined;
13
+ }
4
14
 
5
15
  /**
6
16
  * Wrap a handler so a thrown error is logged instead of escaping to the
7
17
  * process-level `error` event: a failing handler must never take the bot down.
18
+ * A handler of a module disabled on the event's guild is skipped silently.
8
19
  */
9
- function createEventListener(event: DiscordEvent, logger: Logger): EventListener {
20
+ function createEventListener(params: {
21
+ registered: RegisteredEvent;
22
+ logger: Logger;
23
+ gate: ModuleGate | undefined;
24
+ }): EventListener {
25
+ const { registered, logger, gate } = params;
26
+ const { event, moduleName } = registered;
10
27
  return async (...args) => {
11
28
  try {
29
+ if (await isModuleDisabled({ gate, moduleName, guildId: eventGuildId(args) })) {
30
+ return;
31
+ }
12
32
  await event.execute(...args);
13
33
  } catch (error) {
14
- logger.error(
15
- { event: event.name, err: error instanceof Error ? error.message : String(error) },
16
- "Event handler failed",
17
- );
34
+ logger.error({ event: event.name, err: errorMessage(error) }, "Event handler failed");
18
35
  }
19
36
  };
20
37
  }
@@ -24,25 +41,41 @@ function createEventListener(event: DiscordEvent, logger: Logger): EventListener
24
41
  * the app that calls `client.on`/`client.once`.
25
42
  */
26
43
  export class EventRouter {
27
- private readonly events: DiscordEvent[] = [];
44
+ private readonly events: RegisteredEvent[] = [];
28
45
 
29
- constructor(private readonly logger: Logger) {}
46
+ /**
47
+ * @param logger - where a failing handler is reported.
48
+ * @param gate - when given, a handler registered with a module is skipped,
49
+ * silently, for a guild where that module is disabled (FR-036). Events bound
50
+ * to no guild always run.
51
+ */
52
+ constructor(
53
+ private readonly logger: Logger,
54
+ private readonly gate?: ModuleGate,
55
+ ) {}
30
56
 
31
- register(event: DiscordEvent): this {
32
- this.events.push(event);
57
+ /**
58
+ * @param moduleName - the module the handler belongs to, so the gate can
59
+ * skip it for guilds where that module is disabled. Omit it for a handler
60
+ * that always runs.
61
+ */
62
+ register(event: DiscordEvent, moduleName?: string): this {
63
+ this.events.push({ event, moduleName });
33
64
  return this;
34
65
  }
35
66
 
36
- registerAll(events: Iterable<DiscordEvent>): this {
67
+ /** Register many handlers at once, all of `moduleName` when given. */
68
+ registerAll(events: Iterable<DiscordEvent>, moduleName?: string): this {
37
69
  for (const event of events) {
38
- this.register(event);
70
+ this.register(event, moduleName);
39
71
  }
40
72
  return this;
41
73
  }
42
74
 
43
75
  bind(client: Client): void {
44
- for (const event of this.events) {
45
- const listener = createEventListener(event, this.logger);
76
+ for (const registered of this.events) {
77
+ const { event } = registered;
78
+ const listener = createEventListener({ registered, logger: this.logger, gate: this.gate });
46
79
  if (event.once) {
47
80
  client.once(event.name, listener);
48
81
  } else {
@@ -1,4 +1,4 @@
1
- import type { Catalog } from "@/i18n";
1
+ import type { KeyedCatalog, MessageKey } from "@/i18n/catalog";
2
2
 
3
3
  /** Catalog keys for the framework's own feedback, under the `core.` namespace. */
4
4
  export const CORE_MESSAGES = {
@@ -50,6 +50,21 @@ export const SETTINGS_EDITOR_MESSAGES = {
50
50
  */
51
51
  editFieldButton: "core.settings-editor.edit-field-button",
52
52
  openLevelButton: "core.settings-editor.open-level-button",
53
+ /** The line under an entry whose declaration describes it no further. */
54
+ entryHint: "core.settings-editor.entry-hint",
55
+ /** A card entry's current value, under its hint. Takes `{value}`. */
56
+ currentValue: "core.settings-editor.current-value",
57
+ /** The entry a screen with more entries than one card holds moves the rest behind. */
58
+ moreEntries: "core.settings-editor.more-entries",
59
+ moreEntriesHint: "core.settings-editor.more-entries-hint",
60
+ /**
61
+ * An option whose text is not a catalog key: a search result, already in
62
+ * the reader's language. Takes `{label}` and shows it as is.
63
+ */
64
+ choiceLabel: "core.settings-editor.choice-label",
65
+ /** The free entry beside a searchable setting's list, for a value the list does not offer. */
66
+ otherValue: "core.settings-editor.other-value",
67
+ otherValueHelper: "core.settings-editor.other-value-helper",
53
68
  } as const;
54
69
 
55
70
  /**
@@ -57,7 +72,9 @@ export const SETTINGS_EDITOR_MESSAGES = {
57
72
  * fallbacks and the default cooldown notice. Registered by the composition root
58
73
  * before any module catalog.
59
74
  */
60
- export const CORE_CATALOG: Catalog = {
75
+ export const CORE_CATALOG: KeyedCatalog<
76
+ MessageKey<typeof CORE_MESSAGES> | MessageKey<typeof SETTINGS_EDITOR_MESSAGES>
77
+ > = {
61
78
  [CORE_MESSAGES.presenterSuccessTitle]: { en: "Success", fr: "Succès" },
62
79
  [CORE_MESSAGES.presenterWarningTitle]: { en: "Warning", fr: "Avertissement" },
63
80
  [CORE_MESSAGES.presenterErrorTitle]: { en: "Error", fr: "Erreur" },
@@ -96,7 +113,7 @@ export const CORE_CATALOG: Catalog = {
96
113
  },
97
114
  [SETTINGS_EDITOR_MESSAGES.modalHelper]: {
98
115
  en: "Leave empty to clear your customisation and go back to the default.",
99
- fr: "Laisse vide pour effacer ta personnalisation et revenir à la valeur par défaut.",
116
+ fr: "Laissez vide pour effacer votre personnalisation et revenir à la valeur par défaut.",
100
117
  },
101
118
  [SETTINGS_EDITOR_MESSAGES.modalUploadLabel]: {
102
119
  en: "Upload an image",
@@ -112,11 +129,11 @@ export const CORE_CATALOG: Catalog = {
112
129
  },
113
130
  [SETTINGS_EDITOR_MESSAGES.modalImageHelper]: {
114
131
  en: "An http(s) address. Leave both empty to restore the default.",
115
- fr: "Une adresse http(s). Laisse les deux vides pour revenir au défaut.",
132
+ fr: "Une adresse http(s). Laissez les deux vides pour revenir au défaut.",
116
133
  },
117
134
  [SETTINGS_EDITOR_MESSAGES.modalImageHelperUploaded]: {
118
135
  en: "An image is uploaded; it cannot be shown here. Empty both to restore the default.",
119
- fr: "Une image est envoyée ; impossible de la réafficher. Vide les deux pour le défaut.",
136
+ fr: "Une image est envoyée ; impossible de la réafficher. Videz les deux pour le défaut.",
120
137
  },
121
138
  [SETTINGS_EDITOR_MESSAGES.fieldSaved]: {
122
139
  en: "✅ {field} updated.",
@@ -128,7 +145,7 @@ export const CORE_CATALOG: Catalog = {
128
145
  },
129
146
  [SETTINGS_EDITOR_MESSAGES.fieldUploaded]: {
130
147
  en: "✅ {field} updated with your upload.",
131
- fr: "✅ {field} mis à jour avec ton envoi.",
148
+ fr: "✅ {field} mis à jour avec votre envoi.",
132
149
  },
133
150
  [SETTINGS_EDITOR_MESSAGES.resetButton]: {
134
151
  en: "Reset everything",
@@ -140,7 +157,7 @@ export const CORE_CATALOG: Catalog = {
140
157
  },
141
158
  [SETTINGS_EDITOR_MESSAGES.resetConfirmDescription]: {
142
159
  en: "Every field goes back to its default. An uploaded image is not merely unset — its file is deleted for good and cannot be recovered. The preview below is what you are about to lose.",
143
- fr: "Chaque champ revient à sa valeur par défaut. Une image envoyée n'est pas simplement retirée : le fichier est supprimé définitivement et ne pourra pas être récupéré. L'aperçu ci-dessous est ce que tu es sur le point de perdre.",
160
+ fr: "Chaque champ revient à sa valeur par défaut. Une image envoyée n'est pas simplement retirée : le fichier est supprimé définitivement et ne pourra pas être récupéré. L'aperçu ci-dessous est ce que vous êtes sur le point de perdre.",
144
161
  },
145
162
  [SETTINGS_EDITOR_MESSAGES.resetConfirmButton]: {
146
163
  en: "Reset for good",
@@ -156,7 +173,7 @@ export const CORE_CATALOG: Catalog = {
156
173
  },
157
174
  [SETTINGS_EDITOR_MESSAGES.resetCancelled]: {
158
175
  en: "Nothing was reset. Your customisation is untouched.",
159
- fr: "Rien n'a été réinitialisé. Ta personnalisation est intacte.",
176
+ fr: "Rien n'a été réinitialisé. Votre personnalisation est intacte.",
160
177
  },
161
178
  [SETTINGS_EDITOR_MESSAGES.backButton]: {
162
179
  en: "Back",
@@ -170,4 +187,26 @@ export const CORE_CATALOG: Catalog = {
170
187
  en: "Open",
171
188
  fr: "Ouvrir",
172
189
  },
190
+ [SETTINGS_EDITOR_MESSAGES.entryHint]: {
191
+ en: "Edit this setting.",
192
+ fr: "Modifiez ce paramètre.",
193
+ },
194
+ [SETTINGS_EDITOR_MESSAGES.currentValue]: {
195
+ en: "Current: {value}",
196
+ fr: "Actuel : {value}",
197
+ },
198
+ [SETTINGS_EDITOR_MESSAGES.moreEntries]: {
199
+ en: "More settings",
200
+ fr: "Plus de paramètres",
201
+ },
202
+ [SETTINGS_EDITOR_MESSAGES.moreEntriesHint]: {
203
+ en: "The settings that do not fit on this page.",
204
+ fr: "Les paramètres qui ne tiennent pas sur cette page.",
205
+ },
206
+ [SETTINGS_EDITOR_MESSAGES.choiceLabel]: { en: "{label}", fr: "{label}" },
207
+ [SETTINGS_EDITOR_MESSAGES.otherValue]: { en: "Other value", fr: "Autre valeur" },
208
+ [SETTINGS_EDITOR_MESSAGES.otherValueHelper]: {
209
+ en: "A value the list above does not offer; it replaces the pick. Leave empty to keep the pick.",
210
+ fr: "Une valeur absente de la liste ci-dessus ; elle remplace le choix. Laissez vide pour garder le choix.",
211
+ },
173
212
  };
@@ -0,0 +1,19 @@
1
+ import { type Locale, resolveLocale } from "@/i18n/locale";
2
+ import type { Translator } from "@/i18n/translator";
3
+
4
+ /** The two locales Discord attaches to every interaction. */
5
+ interface LocalizedInteraction {
6
+ readonly locale: string;
7
+ readonly guildLocale: string | null;
8
+ }
9
+
10
+ /**
11
+ * The reply language for one interaction: the member's client locale, then the
12
+ * guild's, then the translator's configured default.
13
+ */
14
+ export function interactionLocale(
15
+ interaction: LocalizedInteraction,
16
+ translator: Translator,
17
+ ): Locale {
18
+ return resolveLocale([interaction.locale, interaction.guildLocale], translator.defaultLocale);
19
+ }
@@ -0,0 +1,28 @@
1
+ import type { Locale } from "@/i18n/locale";
2
+
3
+ /**
4
+ * What a {@link LocaleResolver} knows of the interaction it answers: the
5
+ * member's client locale, and the guild's id and Discord locale (`null` outside
6
+ * a guild). Every discord.js interaction fits as is.
7
+ */
8
+ export interface LocaleSubject {
9
+ readonly locale: string;
10
+ readonly guildLocale: string | null;
11
+ readonly guildId: string | null;
12
+ }
13
+
14
+ /**
15
+ * The port the kernel reads the reply language through, on every path that
16
+ * answers an interaction (commands, guards, failures, components). The bot
17
+ * picks the implementation at composition: `createGuildLocaleResolver` for the
18
+ * guild's language setting (FR-038), or its own. Without one, the kernel uses
19
+ * the interaction's own locales (`interactionLocale`).
20
+ */
21
+ export interface LocaleResolver {
22
+ /**
23
+ * @returns the language to reply in.
24
+ * @throws whatever the implementation's lookup throws; the kernel logs it
25
+ * and falls back to the interaction's own locales.
26
+ */
27
+ resolve(subject: LocaleSubject): Promise<Locale>;
28
+ }
@@ -14,6 +14,7 @@ import {
14
14
  TextInputStyle,
15
15
  UserSelectMenuBuilder,
16
16
  } from "discord.js";
17
+ import { applySelectBounds } from "@/discord/interaction/select-bounds";
17
18
 
18
19
  export type TextFieldStyleName = "short" | "paragraph";
19
20
 
@@ -264,16 +265,7 @@ function createStringSelect(name: string, field: SelectFieldDef): StringSelectMe
264
265
  .setCustomId(name)
265
266
  .setRequired(field.required ?? true)
266
267
  .addOptions(field.options.map(createSelectOption));
267
- if (field.placeholder !== undefined) {
268
- select.setPlaceholder(field.placeholder);
269
- }
270
- if (field.minValues !== undefined) {
271
- select.setMinValues(field.minValues);
272
- }
273
- if (field.maxValues !== undefined) {
274
- select.setMaxValues(field.maxValues);
275
- }
276
- return select;
268
+ return applySelectBounds(select, field);
277
269
  }
278
270
 
279
271
  function createRoleSelect(name: string, field: RoleFieldDef): RoleSelectMenuBuilder {
@@ -281,16 +273,7 @@ function createRoleSelect(name: string, field: RoleFieldDef): RoleSelectMenuBuil
281
273
  if (field.defaultRoleIds !== undefined) {
282
274
  select.setDefaultRoles(...field.defaultRoleIds);
283
275
  }
284
- if (field.placeholder !== undefined) {
285
- select.setPlaceholder(field.placeholder);
286
- }
287
- if (field.minValues !== undefined) {
288
- select.setMinValues(field.minValues);
289
- }
290
- if (field.maxValues !== undefined) {
291
- select.setMaxValues(field.maxValues);
292
- }
293
- return select;
276
+ return applySelectBounds(select, field);
294
277
  }
295
278
 
296
279
  function createUserSelect(name: string, field: UserFieldDef): UserSelectMenuBuilder {
@@ -298,16 +281,7 @@ function createUserSelect(name: string, field: UserFieldDef): UserSelectMenuBuil
298
281
  if (field.defaultUserIds !== undefined) {
299
282
  select.setDefaultUsers(...field.defaultUserIds);
300
283
  }
301
- if (field.placeholder !== undefined) {
302
- select.setPlaceholder(field.placeholder);
303
- }
304
- if (field.minValues !== undefined) {
305
- select.setMinValues(field.minValues);
306
- }
307
- if (field.maxValues !== undefined) {
308
- select.setMaxValues(field.maxValues);
309
- }
310
- return select;
284
+ return applySelectBounds(select, field);
311
285
  }
312
286
 
313
287
  function createChannelSelect(name: string, field: ChannelFieldDef): ChannelSelectMenuBuilder {
@@ -320,16 +294,7 @@ function createChannelSelect(name: string, field: ChannelFieldDef): ChannelSelec
320
294
  if (field.defaultChannelIds !== undefined) {
321
295
  select.setDefaultChannels(...field.defaultChannelIds);
322
296
  }
323
- if (field.placeholder !== undefined) {
324
- select.setPlaceholder(field.placeholder);
325
- }
326
- if (field.minValues !== undefined) {
327
- select.setMinValues(field.minValues);
328
- }
329
- if (field.maxValues !== undefined) {
330
- select.setMaxValues(field.maxValues);
331
- }
332
- return select;
297
+ return applySelectBounds(select, field);
333
298
  }
334
299
 
335
300
  function createLabel(name: string, field: FieldDef): LabelBuilder {
@@ -0,0 +1,35 @@
1
+ import { interactionLocale } from "@/discord/interaction/interaction-locale";
2
+ import type { LocaleResolver, LocaleSubject } from "@/discord/interaction/locale-resolver";
3
+ import { errorMessage } from "@/errors/error-message";
4
+ import type { Locale } from "@/i18n/locale";
5
+ import type { Translator } from "@/i18n/translator";
6
+ import type { Logger } from "@/logger";
7
+
8
+ /** The dependencies every reply path already carries, plus the bot's optional resolver. */
9
+ export interface ReplyLocaleDeps {
10
+ readonly translator: Translator;
11
+ readonly logger: Logger;
12
+ readonly localeResolver?: LocaleResolver;
13
+ }
14
+
15
+ /**
16
+ * The language the kernel answers an interaction in: the bot's
17
+ * {@link LocaleResolver} when it gave one, otherwise the interaction's own
18
+ * locales. A resolver that fails is logged and the interaction's own locales
19
+ * are used, so a store outage never leaves a member without an answer.
20
+ */
21
+ export async function replyLocale(subject: LocaleSubject, deps: ReplyLocaleDeps): Promise<Locale> {
22
+ const { translator, logger, localeResolver } = deps;
23
+ if (localeResolver === undefined) {
24
+ return interactionLocale(subject, translator);
25
+ }
26
+ try {
27
+ return await localeResolver.resolve(subject);
28
+ } catch (error) {
29
+ logger.error(
30
+ { guildId: subject.guildId, err: errorMessage(error) },
31
+ "Could not resolve the reply language; using the interaction's locales",
32
+ );
33
+ return interactionLocale(subject, translator);
34
+ }
35
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What every select menu may declare, whatever it picks from and wherever it
3
+ * is shown (a message or a modal): its hint and how many picks it takes.
4
+ */
5
+ export interface SelectBounds {
6
+ placeholder?: string;
7
+ minValues?: number;
8
+ maxValues?: number;
9
+ }
10
+
11
+ /** The setters every discord.js select menu builder carries for {@link SelectBounds}. */
12
+ interface SelectBoundsBuilder {
13
+ setPlaceholder(placeholder: string): unknown;
14
+ setMinValues(minValues: number): unknown;
15
+ setMaxValues(maxValues: number): unknown;
16
+ }
17
+
18
+ /** Apply the placeholder and pick-count bounds a select declared, leaving unset ones to Discord. */
19
+ export function applySelectBounds<B extends SelectBoundsBuilder>(
20
+ builder: B,
21
+ bounds: SelectBounds,
22
+ ): B {
23
+ if (bounds.placeholder !== undefined) {
24
+ builder.setPlaceholder(bounds.placeholder);
25
+ }
26
+ if (bounds.minValues !== undefined) {
27
+ builder.setMinValues(bounds.minValues);
28
+ }
29
+ if (bounds.maxValues !== undefined) {
30
+ builder.setMaxValues(bounds.maxValues);
31
+ }
32
+ return builder;
33
+ }
@@ -15,6 +15,7 @@ import type {
15
15
  ComponentAuthorization,
16
16
  ComponentHandler,
17
17
  } from "@/discord/components/component-router";
18
+ import { applySelectBounds } from "@/discord/interaction/select-bounds";
18
19
 
19
20
  /** The builders this DSL can produce; the union an action row accepts. */
20
21
  export type AnySelectMenuBuilder =
@@ -109,15 +110,7 @@ function applyBaseOptions<B extends AnySelectMenuBuilder>(
109
110
  forceDisabled: boolean,
110
111
  ): B {
111
112
  builder.setCustomId(def.id);
112
- if (def.placeholder !== undefined) {
113
- builder.setPlaceholder(def.placeholder);
114
- }
115
- if (def.minValues !== undefined) {
116
- builder.setMinValues(def.minValues);
117
- }
118
- if (def.maxValues !== undefined) {
119
- builder.setMaxValues(def.maxValues);
120
- }
113
+ applySelectBounds(builder, def);
121
114
  builder.setDisabled(forceDisabled || (def.disabled ?? false));
122
115
  return builder;
123
116
  }
@@ -1,5 +1,5 @@
1
1
  import { DuplicatePlaceholderTokenError } from "@/discord/placeholder-registry-errors";
2
- import type { Locale } from "@/i18n";
2
+ import type { Locale } from "@/i18n/locale";
3
3
 
4
4
  /**
5
5
  * A token a module offers to text another module's administrator writes,
@@ -1,5 +1,5 @@
1
1
  import type { EmbedBuilder } from "discord.js";
2
- import type { Locale } from "@/i18n";
2
+ import type { Locale } from "@/i18n/locale";
3
3
 
4
4
  /**
5
5
  * Port that renders framework-level responses (errors, denials, confirmations)
@@ -0,0 +1,214 @@
1
+ import { ChannelType, type Client, type Guild } from "discord.js";
2
+ import { isMissingAccess, isUnknownResource } from "@/discord/api-errors";
3
+ import { MAX_AUTOCOMPLETE_CHOICES } from "@/discord/command/autocomplete-limits";
4
+ import type { ChannelKind, GuildDirectory } from "@/settings/ports/guild-directory";
5
+
6
+ /** Every `ChannelType` a guild channel can have: all of them except the DM ones. */
7
+ type GuildChannelType = Exclude<ChannelType, ChannelType.DM | ChannelType.GroupDM>;
8
+
9
+ /**
10
+ * Discord's guild channel types to the kernel's kinds. Both maps are checked
11
+ * for completeness, so a new member on either side is a compile error until
12
+ * it is mapped here.
13
+ */
14
+ const KIND_BY_CHANNEL_TYPE = {
15
+ [ChannelType.GuildText]: "text",
16
+ [ChannelType.GuildVoice]: "voice",
17
+ [ChannelType.GuildCategory]: "category",
18
+ [ChannelType.GuildAnnouncement]: "announcement",
19
+ [ChannelType.AnnouncementThread]: "announcementThread",
20
+ [ChannelType.PublicThread]: "publicThread",
21
+ [ChannelType.PrivateThread]: "privateThread",
22
+ [ChannelType.GuildStageVoice]: "stage",
23
+ [ChannelType.GuildDirectory]: "directory",
24
+ [ChannelType.GuildForum]: "forum",
25
+ [ChannelType.GuildMedia]: "media",
26
+ } as const satisfies Record<GuildChannelType, ChannelKind>;
27
+
28
+ /** The kernel's channel kinds to Discord's guild channel types: the inverse of the map above. */
29
+ export const CHANNEL_TYPE_BY_KIND = {
30
+ text: ChannelType.GuildText,
31
+ voice: ChannelType.GuildVoice,
32
+ category: ChannelType.GuildCategory,
33
+ announcement: ChannelType.GuildAnnouncement,
34
+ announcementThread: ChannelType.AnnouncementThread,
35
+ publicThread: ChannelType.PublicThread,
36
+ privateThread: ChannelType.PrivateThread,
37
+ stage: ChannelType.GuildStageVoice,
38
+ directory: ChannelType.GuildDirectory,
39
+ forum: ChannelType.GuildForum,
40
+ media: ChannelType.GuildMedia,
41
+ } as const satisfies Record<ChannelKind, GuildChannelType>;
42
+
43
+ /** Discord caps autocomplete at this many choices, so no search returns more. */
44
+ const SEARCH_LIMIT = MAX_AUTOCOMPLETE_CHOICES;
45
+
46
+ interface NamedEntity {
47
+ readonly id: string;
48
+ readonly name: string;
49
+ }
50
+
51
+ interface ChannelEntity extends NamedEntity {
52
+ readonly kind: ChannelKind;
53
+ }
54
+
55
+ /** The minimal shape of a guild channel this adapter reads. */
56
+ interface GuildChannelLike {
57
+ readonly id: string;
58
+ readonly name: string;
59
+ readonly type: GuildChannelType;
60
+ }
61
+
62
+ /** The minimal shape of a guild member this adapter reads. */
63
+ interface GuildMemberLike {
64
+ readonly id: string;
65
+ readonly displayName: string;
66
+ }
67
+
68
+ /** Resolve to `null` when Discord says the entity does not exist; rethrow anything else. */
69
+ async function orNullWhenUnknown<T>(lookup: Promise<T | null>): Promise<T | null> {
70
+ try {
71
+ return await lookup;
72
+ } catch (error) {
73
+ if (isUnknownResource(error)) {
74
+ return null;
75
+ }
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ function toChannel(channel: GuildChannelLike): ChannelEntity {
81
+ return { id: channel.id, name: channel.name, kind: KIND_BY_CHANNEL_TYPE[channel.type] };
82
+ }
83
+
84
+ function toMember(member: GuildMemberLike): NamedEntity {
85
+ return { id: member.id, name: member.displayName };
86
+ }
87
+
88
+ function nameStartsWith(params: { name: string; query: string }): boolean {
89
+ return params.name.toLowerCase().startsWith(params.query.toLowerCase());
90
+ }
91
+
92
+ /**
93
+ * Search `cached` first; only when nothing there matches, search what `fetch`
94
+ * returns. discord.js caches what it fetches, so the next lookup is local.
95
+ */
96
+ async function searchCacheThenFetch<T>(params: {
97
+ cached: Iterable<T>;
98
+ fetch: () => Promise<Iterable<T>>;
99
+ matches: (entity: T) => boolean;
100
+ }): Promise<T[]> {
101
+ const { cached, fetch, matches } = params;
102
+ const hits = [...cached].filter(matches);
103
+ if (hits.length > 0) {
104
+ return hits.slice(0, SEARCH_LIMIT);
105
+ }
106
+ return [...(await fetch())].filter(matches).slice(0, SEARCH_LIMIT);
107
+ }
108
+
109
+ /** The guild from the client's cache, else from Discord; `null` when the bot cannot see it. */
110
+ async function findGuild(params: { client: Client; guildId: string }): Promise<Guild | null> {
111
+ const { client, guildId } = params;
112
+ const cached = client.guilds.cache.get(guildId);
113
+ if (cached) {
114
+ return cached;
115
+ }
116
+ try {
117
+ return await client.guilds.fetch(guildId);
118
+ } catch (error) {
119
+ if (isUnknownResource(error) || isMissingAccess(error)) {
120
+ return null;
121
+ }
122
+ throw error;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * {@link GuildDirectory} over a discord.js client. Every lookup reads the
128
+ * client's cache first and asks Discord only on a miss; an entity Discord does
129
+ * not know, or a guild the bot is not in, reads as absent. Search matches a
130
+ * case-insensitive name prefix, except the member search Discord runs itself
131
+ * on a cache miss, and returns at most 25 entries.
132
+ *
133
+ * @param client - the bot's client; its caches are read, never filled by hand.
134
+ * @returns the directory the settings service validates references with.
135
+ */
136
+ export function createDiscordGuildDirectory(client: Client): GuildDirectory {
137
+ return {
138
+ async channel(guildId, id) {
139
+ const guild = await findGuild({ client, guildId });
140
+ if (!guild) {
141
+ return null;
142
+ }
143
+ const channel =
144
+ guild.channels.cache.get(id) ?? (await orNullWhenUnknown(guild.channels.fetch(id)));
145
+ return channel ? toChannel(channel) : null;
146
+ },
147
+
148
+ async role(guildId, id) {
149
+ const guild = await findGuild({ client, guildId });
150
+ if (!guild) {
151
+ return null;
152
+ }
153
+ const role = guild.roles.cache.get(id) ?? (await orNullWhenUnknown(guild.roles.fetch(id)));
154
+ return role ? { id: role.id, name: role.name } : null;
155
+ },
156
+
157
+ async member(guildId, id) {
158
+ const guild = await findGuild({ client, guildId });
159
+ if (!guild) {
160
+ return null;
161
+ }
162
+ const member =
163
+ guild.members.cache.get(id) ?? (await orNullWhenUnknown(guild.members.fetch(id)));
164
+ return member ? toMember(member) : null;
165
+ },
166
+
167
+ async searchChannels(guildId, query, kinds) {
168
+ const guild = await findGuild({ client, guildId });
169
+ if (!guild) {
170
+ return [];
171
+ }
172
+ const types = kinds?.map((kind): GuildChannelType => CHANNEL_TYPE_BY_KIND[kind]);
173
+ const channels = await searchCacheThenFetch<GuildChannelLike>({
174
+ cached: guild.channels.cache.values(),
175
+ fetch: async () => {
176
+ const fetched = await guild.channels.fetch();
177
+ return [...fetched.values()].filter((channel) => channel !== null);
178
+ },
179
+ matches: (channel) =>
180
+ nameStartsWith({ name: channel.name, query }) &&
181
+ (types === undefined || types.includes(channel.type)),
182
+ });
183
+ return channels.map(toChannel);
184
+ },
185
+
186
+ async searchRoles(guildId, query) {
187
+ const guild = await findGuild({ client, guildId });
188
+ if (!guild) {
189
+ return [];
190
+ }
191
+ const roles = await searchCacheThenFetch({
192
+ cached: guild.roles.cache.values(),
193
+ fetch: async () => (await guild.roles.fetch()).values(),
194
+ matches: (role) => nameStartsWith({ name: role.name, query }),
195
+ });
196
+ return roles.map((role) => ({ id: role.id, name: role.name }));
197
+ },
198
+
199
+ async searchMembers(guildId, query) {
200
+ const guild = await findGuild({ client, guildId });
201
+ if (!guild) {
202
+ return [];
203
+ }
204
+ const hits = [...guild.members.cache.values()].filter((member) =>
205
+ nameStartsWith({ name: member.displayName, query }),
206
+ );
207
+ if (hits.length > 0) {
208
+ return hits.slice(0, SEARCH_LIMIT).map(toMember);
209
+ }
210
+ const found = await guild.members.search({ query, limit: SEARCH_LIMIT });
211
+ return [...found.values()].map(toMember);
212
+ },
213
+ };
214
+ }