@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,37 @@
1
+ import {
2
+ type CommandInteraction,
3
+ type MessageComponentInteraction,
4
+ MessageFlags,
5
+ type ModalSubmitInteraction,
6
+ } from "discord.js";
7
+ import type { Guard } from "@/discord/command/guard";
8
+ import { type ReplyLocaleDeps, replyLocale } from "@/discord/interaction/reply-locale";
9
+ import type { Presenter } from "@/discord/presenter";
10
+
11
+ /**
12
+ * Run a command's or a component's guard, if it has one. The guard gets the
13
+ * reply dependencies, to word its denial in the reply language. On a denial,
14
+ * answer with the guard's own message (ephemeral) and return `false`, so the
15
+ * handler never runs.
16
+ */
17
+ export async function passesGuard<
18
+ I extends CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction,
19
+ >(
20
+ interaction: I,
21
+ guard: Guard<I> | undefined,
22
+ runtime: ReplyLocaleDeps & { readonly presenter: Presenter },
23
+ ): Promise<boolean> {
24
+ if (!guard) {
25
+ return true;
26
+ }
27
+ const result = await guard.check(interaction, runtime);
28
+ if (result.ok) {
29
+ return true;
30
+ }
31
+ const locale = await replyLocale(interaction, runtime);
32
+ await interaction.reply({
33
+ embeds: [runtime.presenter.denial(runtime.translator.resolve(locale, result.message), locale)],
34
+ flags: MessageFlags.Ephemeral,
35
+ });
36
+ return false;
37
+ }
@@ -2,7 +2,7 @@ import type { CommandInteraction } from "discord.js";
2
2
  import type { Guard, GuardResult } from "@/discord/command/guard";
3
3
  import { CORE_MESSAGES } from "@/discord/i18n";
4
4
  import { formatPermissions, missingPermissions, type PermissionBit } from "@/discord/permissions";
5
- import type { LocalizedText } from "@/i18n";
5
+ import type { LocalizedText } from "@/i18n/translator";
6
6
 
7
7
  /**
8
8
  * Generic, reusable guards. They complement — never replace —
@@ -0,0 +1,71 @@
1
+ import type { CommandInteraction } from "discord.js";
2
+ import { sendEmbed } from "@/discord/command/send-embed";
3
+ import type { CommandRuntime } from "@/discord/command/types";
4
+ import { CORE_MESSAGES } from "@/discord/i18n";
5
+ import { replyLocale } from "@/discord/interaction/reply-locale";
6
+ import { BusinessError } from "@/errors/business-error";
7
+ import { errorMessage } from "@/errors/error-message";
8
+ import { createIncidentRef } from "@/errors/incident-ref";
9
+ import { resolveBusinessMessage } from "@/i18n/business-message";
10
+ import type { LocalizedText } from "@/i18n/translator";
11
+
12
+ /** How one command pipeline wants a failed handler reported. */
13
+ export interface CommandFailure {
14
+ readonly error: unknown;
15
+ /** Whether the answer is ephemeral, as the command declared. */
16
+ readonly ephemeral: boolean;
17
+ /** Shown instead of the generic message for an unexpected error. */
18
+ readonly fallbackError?: LocalizedText;
19
+ /** Extra fields identifying the route in the incident log. */
20
+ readonly logFields?: Readonly<Record<string, unknown>>;
21
+ /** The incident log's message. */
22
+ readonly logMessage: string;
23
+ }
24
+
25
+ /**
26
+ * Answer a handler that threw. A {@link BusinessError} is user feedback: its
27
+ * own (translated) message, as a warning or an error by its severity, with no
28
+ * incident minted. Anything else is an incident: logged with a fresh
29
+ * reference, and answered with the fallback message carrying that reference.
30
+ */
31
+ export async function renderCommandFailure(
32
+ interaction: CommandInteraction,
33
+ failure: CommandFailure,
34
+ runtime: CommandRuntime,
35
+ ): Promise<void> {
36
+ const { presenter, logger, translator } = runtime;
37
+ const { error, ephemeral } = failure;
38
+ const locale = await replyLocale(interaction, runtime);
39
+ if (error instanceof BusinessError) {
40
+ const text = resolveBusinessMessage(error, translator, locale);
41
+ const embed =
42
+ error.severity === "warning"
43
+ ? presenter.warning(text, locale)
44
+ : presenter.error(text, locale);
45
+ await sendEmbed(interaction, embed, ephemeral, logger);
46
+ return;
47
+ }
48
+
49
+ const ref = createIncidentRef();
50
+ logger.error(
51
+ {
52
+ command: interaction.commandName,
53
+ ...failure.logFields,
54
+ userId: interaction.user.id,
55
+ ref,
56
+ err: errorMessage(error),
57
+ stack: error instanceof Error ? error.stack : undefined,
58
+ },
59
+ failure.logMessage,
60
+ );
61
+ await sendEmbed(
62
+ interaction,
63
+ presenter.systemError(
64
+ translator.resolve(locale, failure.fallbackError ?? { key: CORE_MESSAGES.genericError }),
65
+ ref,
66
+ locale,
67
+ ),
68
+ ephemeral,
69
+ logger,
70
+ );
71
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The key a subcommand is stored and looked up under. Its own module because
3
+ * dispatch and autocomplete must agree on it: if they ever built it
4
+ * differently, an autocomplete inside a group would silently resolve to no
5
+ * options instead of failing loudly.
6
+ */
7
+ export function routeKey(group: string | null, name: string): string {
8
+ return group ? `${group}/${name}` : name;
9
+ }
@@ -1,4 +1,5 @@
1
- import { type Interaction, REST, Routes } from "discord.js";
1
+ import { type CommandInteraction, type Interaction, REST, Routes } from "discord.js";
2
+ import { sendEmbed } from "@/discord/command/send-embed";
2
3
  import type {
3
4
  CommandRuntime,
4
5
  ContextMenuCommand,
@@ -6,14 +7,30 @@ import type {
6
7
  SlashCommand,
7
8
  } from "@/discord/command/types";
8
9
  import type { InteractionDispatcher } from "@/discord/interaction/interaction-router";
10
+ import type { LocaleResolver } from "@/discord/interaction/locale-resolver";
9
11
  import type { Presenter } from "@/discord/presenter";
10
- import type { Translator } from "@/i18n";
12
+ import { isModuleDisabled } from "@/discord/settings/is-module-disabled";
13
+ import { moduleDisabledEmbed } from "@/discord/settings/module-disabled-embed";
14
+ import type { Translator } from "@/i18n/translator";
11
15
  import type { Logger } from "@/logger";
16
+ import type { ModuleGate } from "@/settings/system/module-gate";
12
17
 
13
18
  export interface CommandRouterDeps {
14
19
  presenter: Presenter;
15
20
  logger: Logger;
16
21
  translator: Translator;
22
+ /**
23
+ * When given, a command registered with a module does not run on a guild
24
+ * where that module is disabled: the member gets the translated "disabled
25
+ * on this server" message instead (FR-036).
26
+ */
27
+ gate?: ModuleGate;
28
+ /**
29
+ * Where every reply of the pipeline reads its language (e.g.
30
+ * `createGuildLocaleResolver`, or the bot's own). Without one, the
31
+ * interaction's own locales are used.
32
+ */
33
+ localeResolver?: LocaleResolver;
17
34
  }
18
35
 
19
36
  /**
@@ -25,6 +42,8 @@ export interface CommandRouterDeps {
25
42
  export class CommandRouter implements InteractionDispatcher {
26
43
  private readonly commands = new Map<string, SlashCommand>();
27
44
  private readonly contextMenuCommands = new Map<string, ContextMenuCommand>();
45
+ /** The module each command was registered for, by command; absent means never gated. */
46
+ private readonly modules = new Map<DeployableCommand, string>();
28
47
  private readonly runtime: CommandRuntime;
29
48
 
30
49
  constructor(private readonly deps: CommandRouterDeps) {
@@ -32,19 +51,27 @@ export class CommandRouter implements InteractionDispatcher {
32
51
  presenter: deps.presenter,
33
52
  logger: deps.logger,
34
53
  translator: deps.translator,
54
+ localeResolver: deps.localeResolver,
35
55
  };
36
56
  }
37
57
 
38
- /** Register a command (last registration of a name wins). */
39
- register(command: SlashCommand): this {
58
+ /**
59
+ * Register a command (last registration of a name wins).
60
+ *
61
+ * @param moduleName - the module the command belongs to, so the gate can
62
+ * skip it on guilds where that module is disabled. Omit it for a command
63
+ * that always runs.
64
+ */
65
+ register(command: SlashCommand, moduleName?: string): this {
40
66
  this.commands.set(command.data.name, command);
67
+ this.remember(command, moduleName);
41
68
  return this;
42
69
  }
43
70
 
44
- /** Register many commands at once. */
45
- registerAll(commands: Iterable<SlashCommand>): this {
71
+ /** Register many commands at once, all of `moduleName` when given. */
72
+ registerAll(commands: Iterable<SlashCommand>, moduleName?: string): this {
46
73
  for (const command of commands) {
47
- this.register(command);
74
+ this.register(command, moduleName);
48
75
  }
49
76
  return this;
50
77
  }
@@ -54,14 +81,15 @@ export class CommandRouter implements InteractionDispatcher {
54
81
  * slash command and a context-menu entry as distinct application command
55
82
  * types, and therefore lets them share a name.
56
83
  */
57
- registerContextMenu(command: ContextMenuCommand): this {
84
+ registerContextMenu(command: ContextMenuCommand, moduleName?: string): this {
58
85
  this.contextMenuCommands.set(command.data.name, command);
86
+ this.remember(command, moduleName);
59
87
  return this;
60
88
  }
61
89
 
62
- registerAllContextMenus(commands: Iterable<ContextMenuCommand>): this {
90
+ registerAllContextMenus(commands: Iterable<ContextMenuCommand>, moduleName?: string): this {
63
91
  for (const command of commands) {
64
- this.registerContextMenu(command);
92
+ this.registerContextMenu(command, moduleName);
65
93
  }
66
94
  return this;
67
95
  }
@@ -78,7 +106,9 @@ export class CommandRouter implements InteractionDispatcher {
78
106
  if (!command) {
79
107
  return false;
80
108
  }
81
- await command.dispatch(interaction, this.runtime);
109
+ if (await this.passesGate(interaction, command)) {
110
+ await command.dispatch(interaction, this.runtime);
111
+ }
82
112
  return true;
83
113
  }
84
114
 
@@ -87,13 +117,44 @@ export class CommandRouter implements InteractionDispatcher {
87
117
  if (!command) {
88
118
  return false;
89
119
  }
90
- await command.dispatch(interaction, this.runtime);
120
+ if (await this.passesGate(interaction, command)) {
121
+ await command.dispatch(interaction, this.runtime);
122
+ }
91
123
  return true;
92
124
  }
93
125
 
94
126
  return false;
95
127
  }
96
128
 
129
+ private remember(command: DeployableCommand, moduleName: string | undefined): void {
130
+ if (moduleName === undefined) {
131
+ this.modules.delete(command);
132
+ } else {
133
+ this.modules.set(command, moduleName);
134
+ }
135
+ }
136
+
137
+ /** Answer the member and return `false` when the command's module is disabled on the guild. */
138
+ private async passesGate(
139
+ interaction: CommandInteraction,
140
+ command: DeployableCommand,
141
+ ): Promise<boolean> {
142
+ const disabled = await isModuleDisabled({
143
+ gate: this.deps.gate,
144
+ moduleName: this.modules.get(command),
145
+ guildId: interaction.guildId,
146
+ });
147
+ if (disabled) {
148
+ await sendEmbed(
149
+ interaction,
150
+ await moduleDisabledEmbed(interaction, this.deps),
151
+ true,
152
+ this.deps.logger,
153
+ );
154
+ }
155
+ return !disabled;
156
+ }
157
+
97
158
  /**
98
159
  * Push every registered command to its declared scope: commands with
99
160
  * `guildIds` go to those guild(s) (instant availability), the rest are
@@ -0,0 +1,42 @@
1
+ import { type CommandInteraction, type EmbedBuilder, MessageFlags } from "discord.js";
2
+ import { errorMessage } from "@/errors/error-message";
3
+ import type { Logger } from "@/logger";
4
+
5
+ /**
6
+ * Deliver an embed regardless of the interaction's lifecycle state, and never
7
+ * throw: a failed delivery (e.g. an expired/unknown interaction, code 10062)
8
+ * must not escape a handler and reach the client's unhandled `error` event,
9
+ * which would crash the process. Visibility on a deferred interaction is fixed
10
+ * at deferReply time, so editReply carries no flags.
11
+ *
12
+ * Typed on `CommandInteraction`, the base shared by chat-input and context-menu
13
+ * commands, so both pipelines answer through this one path.
14
+ */
15
+ export async function sendEmbed(
16
+ interaction: CommandInteraction,
17
+ embed: EmbedBuilder,
18
+ ephemeral: boolean,
19
+ logger: Logger,
20
+ ): Promise<void> {
21
+ const flags = ephemeral ? MessageFlags.Ephemeral : undefined;
22
+ try {
23
+ if (interaction.deferred) {
24
+ await interaction.editReply({ embeds: [embed] });
25
+ return;
26
+ }
27
+ if (interaction.replied) {
28
+ await interaction.followUp({ embeds: [embed], flags });
29
+ return;
30
+ }
31
+ await interaction.reply({ embeds: [embed], flags });
32
+ } catch (error) {
33
+ logger.error(
34
+ {
35
+ commandName: interaction.commandName,
36
+ interactionId: interaction.id,
37
+ err: errorMessage(error),
38
+ },
39
+ "Failed to deliver interaction response",
40
+ );
41
+ }
42
+ }
@@ -3,8 +3,9 @@ import type {
3
3
  ChatInputCommandInteraction,
4
4
  ContextMenuCommandInteraction,
5
5
  } from "discord.js";
6
+ import type { LocaleResolver } from "@/discord/interaction/locale-resolver";
6
7
  import type { Presenter } from "@/discord/presenter";
7
- import type { Translator } from "@/i18n";
8
+ import type { Translator } from "@/i18n/translator";
8
9
  import type { Logger } from "@/logger";
9
10
 
10
11
  /**
@@ -16,6 +17,11 @@ export interface CommandRuntime {
16
17
  readonly presenter: Presenter;
17
18
  readonly logger: Logger;
18
19
  readonly translator: Translator;
20
+ /**
21
+ * Where the pipeline reads the reply language. Without one, it uses the
22
+ * interaction's own locales (`interactionLocale`).
23
+ */
24
+ readonly localeResolver?: LocaleResolver;
19
25
  }
20
26
 
21
27
  /**
@@ -0,0 +1,20 @@
1
+ import type { ComponentAuthorization } from "@/discord/components/component-router";
2
+ import type { PermissionBit } from "@/discord/permissions";
3
+
4
+ /** Any member may click; `because` records why that is acceptable. */
5
+ export function openToAnyone(because: string): ComponentAuthorization {
6
+ return { kind: "anyone", because };
7
+ }
8
+
9
+ /** The router denies the click unless the member holds every permission. */
10
+ export function requiresPermissions(...required: readonly PermissionBit[]): ComponentAuthorization {
11
+ return { kind: "permissions", required };
12
+ }
13
+
14
+ /**
15
+ * The decision needs per-resource state, so the handler makes it. `because`
16
+ * names the rule it applies, so a reviewer can go and check that it does.
17
+ */
18
+ export function checkedByHandler(because: string): ComponentAuthorization {
19
+ return { kind: "handler", because };
20
+ }
@@ -5,12 +5,20 @@ import {
5
5
  MessageFlags,
6
6
  type ModalSubmitInteraction,
7
7
  } from "discord.js";
8
+ import type { Guard } from "@/discord/command/guard";
9
+ import { passesGuard } from "@/discord/command/passes-guard";
8
10
  import { CORE_MESSAGES } from "@/discord/i18n";
9
11
  import type { InteractionDispatcher } from "@/discord/interaction/interaction-router";
12
+ import type { LocaleResolver } from "@/discord/interaction/locale-resolver";
13
+ import { replyLocale } from "@/discord/interaction/reply-locale";
10
14
  import { formatPermissions, missingPermissions, type PermissionBit } from "@/discord/permissions";
11
15
  import type { Presenter } from "@/discord/presenter";
12
- import { type Locale, resolveLocale, type Translator } from "@/i18n";
16
+ import { isModuleDisabled } from "@/discord/settings/is-module-disabled";
17
+ import { moduleDisabledEmbed } from "@/discord/settings/module-disabled-embed";
18
+ import { errorMessage } from "@/errors/error-message";
19
+ import type { Translator } from "@/i18n/translator";
13
20
  import type { Logger } from "@/logger";
21
+ import type { ModuleGate } from "@/settings/system/module-gate";
14
22
 
15
23
  /**
16
24
  * Interactions a {@link ComponentHandler} can be routed: message components
@@ -26,6 +34,8 @@ export interface ComponentRuntime {
26
34
  readonly presenter: Presenter;
27
35
  readonly logger: Logger;
28
36
  readonly translator: Translator;
37
+ /** The bot's reply-language resolver, if it gave one (see `ComponentRouterDeps`). */
38
+ readonly localeResolver?: LocaleResolver;
29
39
  }
30
40
 
31
41
  /**
@@ -54,24 +64,6 @@ export type ComponentAuthorization =
54
64
  */
55
65
  | { readonly kind: "handler"; readonly because: string };
56
66
 
57
- /** Any member may click; `because` records why that is acceptable. */
58
- export function openToAnyone(because: string): ComponentAuthorization {
59
- return { kind: "anyone", because };
60
- }
61
-
62
- /** The router denies the click unless the member holds every permission. */
63
- export function requiresPermissions(...required: readonly PermissionBit[]): ComponentAuthorization {
64
- return { kind: "permissions", required };
65
- }
66
-
67
- /**
68
- * The decision needs per-resource state, so the handler makes it. `because`
69
- * names the rule it applies, so a reviewer can go and check that it does.
70
- */
71
- export function checkedByHandler(because: string): ComponentAuthorization {
72
- return { kind: "handler", because };
73
- }
74
-
75
67
  /**
76
68
  * A persistent component handler, routed by `customId`. It claims an interaction
77
69
  * whose customId equals `customId` or starts with `${customId}:`, so dynamic
@@ -89,18 +81,32 @@ export interface ComponentHandler {
89
81
  readonly customId: string;
90
82
  /** Who may act on this component. Required: see {@link ComponentAuthorization}. */
91
83
  readonly authorize: ComponentAuthorization;
84
+ /**
85
+ * A check run after `authorize` and before the handler, as a command's
86
+ * guard is: on a denial the member gets its message (ephemeral) and the
87
+ * handler never runs. A guard over `BaseInteraction`, such as
88
+ * `requireConfigured`, fits both a command and a component.
89
+ */
90
+ readonly guard?: Guard<RoutableInteraction>;
92
91
  handle(interaction: RoutableInteraction, runtime: ComponentRuntime): Promise<void> | void;
93
92
  }
94
93
 
95
- /** Declare a {@link ComponentHandler}. Identity helper kept for a consistent DSL. */
96
- export function createComponentHandler(handler: ComponentHandler): ComponentHandler {
97
- return handler;
98
- }
99
-
100
94
  export interface ComponentRouterDeps {
101
95
  presenter: Presenter;
102
96
  logger: Logger;
103
97
  translator: Translator;
98
+ /**
99
+ * When given, a handler registered with a module does not run on a guild
100
+ * where that module is disabled: the member gets the translated "disabled
101
+ * on this server" message instead (FR-036).
102
+ */
103
+ gate?: ModuleGate;
104
+ /**
105
+ * Where the router's own replies (denials) read their language, also handed
106
+ * to the handlers through the runtime. Without one, the interaction's own
107
+ * locales are used.
108
+ */
109
+ localeResolver?: LocaleResolver;
104
110
  }
105
111
 
106
112
  function isRoutable(interaction: Interaction): interaction is RoutableInteraction {
@@ -116,6 +122,8 @@ function isRoutable(interaction: Interaction): interaction is RoutableInteractio
116
122
  */
117
123
  export class ComponentRouter implements InteractionDispatcher {
118
124
  private readonly handlers: ComponentHandler[] = [];
125
+ /** The module each handler was registered for; absent means never gated. */
126
+ private readonly modules = new Map<ComponentHandler, string>();
119
127
  private readonly runtime: ComponentRuntime;
120
128
 
121
129
  constructor(private readonly deps: ComponentRouterDeps) {
@@ -123,17 +131,27 @@ export class ComponentRouter implements InteractionDispatcher {
123
131
  presenter: deps.presenter,
124
132
  logger: deps.logger,
125
133
  translator: deps.translator,
134
+ localeResolver: deps.localeResolver,
126
135
  };
127
136
  }
128
137
 
129
- register(handler: ComponentHandler): this {
138
+ /**
139
+ * @param moduleName - the module the handler belongs to, so the gate can
140
+ * skip it on guilds where that module is disabled. Omit it for a handler
141
+ * that always runs.
142
+ */
143
+ register(handler: ComponentHandler, moduleName?: string): this {
130
144
  this.handlers.push(handler);
145
+ if (moduleName !== undefined) {
146
+ this.modules.set(handler, moduleName);
147
+ }
131
148
  return this;
132
149
  }
133
150
 
134
- registerAll(handlers: Iterable<ComponentHandler>): this {
151
+ /** Register many handlers at once, all of `moduleName` when given. */
152
+ registerAll(handlers: Iterable<ComponentHandler>, moduleName?: string): this {
135
153
  for (const handler of handlers) {
136
- this.register(handler);
154
+ this.register(handler, moduleName);
137
155
  }
138
156
  return this;
139
157
  }
@@ -153,18 +171,25 @@ export class ComponentRouter implements InteractionDispatcher {
153
171
  if (!handler) {
154
172
  return false;
155
173
  }
174
+ // First: a disabled module answers the same way whoever clicks.
175
+ if (!(await this.passesGate(interaction, handler))) {
176
+ return true;
177
+ }
156
178
  // Before the handler runs, so a declared permission cannot be bypassed by a
157
179
  // handler that forgot to check it.
158
180
  if (!(await this.passesAuthorization(interaction, handler))) {
159
181
  return true;
160
182
  }
183
+ if (!(await passesGuard(interaction, handler.guard, this.runtime))) {
184
+ return true;
185
+ }
161
186
  try {
162
187
  await handler.handle(interaction, this.runtime);
163
188
  } catch (error) {
164
189
  this.deps.logger.error(
165
190
  {
166
191
  customId: interaction.customId,
167
- err: error instanceof Error ? error.message : String(error),
192
+ err: errorMessage(error),
168
193
  },
169
194
  "Component handler failed",
170
195
  );
@@ -172,6 +197,25 @@ export class ComponentRouter implements InteractionDispatcher {
172
197
  return true;
173
198
  }
174
199
 
200
+ /** Answer the member and return `false` when the handler's module is disabled on the guild. */
201
+ private async passesGate(
202
+ interaction: RoutableInteraction,
203
+ handler: ComponentHandler,
204
+ ): Promise<boolean> {
205
+ const disabled = await isModuleDisabled({
206
+ gate: this.deps.gate,
207
+ moduleName: this.modules.get(handler),
208
+ guildId: interaction.guildId,
209
+ });
210
+ if (disabled) {
211
+ await interaction.reply({
212
+ embeds: [await moduleDisabledEmbed(interaction, this.deps)],
213
+ flags: MessageFlags.Ephemeral,
214
+ });
215
+ }
216
+ return !disabled;
217
+ }
218
+
175
219
  /**
176
220
  * Enforce the handler's declared audience. Only `permissions` is enforceable
177
221
  * here — the other two kinds are statements about a decision made elsewhere,
@@ -193,7 +237,7 @@ export class ComponentRouter implements InteractionDispatcher {
193
237
  return true;
194
238
  }
195
239
 
196
- const locale = this.localeOf(interaction);
240
+ const locale = await replyLocale(interaction, this.deps);
197
241
  const message = this.deps.translator.translate(locale, CORE_MESSAGES.guardPermissionDenied, {
198
242
  missing: formatPermissions(missing),
199
243
  });
@@ -203,11 +247,4 @@ export class ComponentRouter implements InteractionDispatcher {
203
247
  });
204
248
  return false;
205
249
  }
206
-
207
- private localeOf(interaction: RoutableInteraction): Locale {
208
- return resolveLocale(
209
- [interaction.locale, interaction.guildLocale],
210
- this.deps.translator.defaultLocale,
211
- );
212
- }
213
250
  }
@@ -1,6 +1,5 @@
1
1
  export {
2
2
  COLLECTOR_IDLE_MS,
3
- createStateStore,
4
3
  type DisabledInteractiveMessageComponents,
5
4
  type InteractiveMessageCollectorDeps,
6
5
  type InteractiveMessageHandle,
@@ -9,10 +8,13 @@ export {
9
8
  type InteractiveView,
10
9
  mountInteractiveMessageCollector,
11
10
  type SelectComponentContext,
12
- type StateStore,
13
- toMessageEditOptions,
14
11
  } from "@/discord/components/interactive-message/interactive-message-collector";
12
+ export { toMessageEditOptions } from "@/discord/components/interactive-message/message-edit-options";
15
13
  export {
16
14
  type InteractiveMessageOptions,
17
15
  mountInteractiveMessage,
18
16
  } from "@/discord/components/interactive-message/mount-interactive-message";
17
+ export {
18
+ createStateStore,
19
+ type StateStore,
20
+ } from "@/discord/components/interactive-message/state-store";