@azurioh/discord-kernel 0.1.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 (373) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/dist/authz/authorizer.d.ts +45 -0
  4. package/dist/authz/authorizer.d.ts.map +1 -0
  5. package/dist/authz/authorizer.js +31 -0
  6. package/dist/authz/authorizer.js.map +1 -0
  7. package/dist/authz/require-level-guard.d.ts +23 -0
  8. package/dist/authz/require-level-guard.d.ts.map +1 -0
  9. package/dist/authz/require-level-guard.js +37 -0
  10. package/dist/authz/require-level-guard.js.map +1 -0
  11. package/dist/clock.d.ts +13 -0
  12. package/dist/clock.d.ts.map +1 -0
  13. package/dist/clock.js +13 -0
  14. package/dist/clock.js.map +1 -0
  15. package/dist/concurrency/keyed-queue.d.ts +18 -0
  16. package/dist/concurrency/keyed-queue.d.ts.map +1 -0
  17. package/dist/concurrency/keyed-queue.js +37 -0
  18. package/dist/concurrency/keyed-queue.js.map +1 -0
  19. package/dist/concurrency/run-with-concurrency.d.ts +15 -0
  20. package/dist/concurrency/run-with-concurrency.d.ts.map +1 -0
  21. package/dist/concurrency/run-with-concurrency.js +40 -0
  22. package/dist/concurrency/run-with-concurrency.js.map +1 -0
  23. package/dist/config/env.d.ts +17 -0
  24. package/dist/config/env.d.ts.map +1 -0
  25. package/dist/config/env.js +49 -0
  26. package/dist/config/env.js.map +1 -0
  27. package/dist/config/errors.d.ts +14 -0
  28. package/dist/config/errors.d.ts.map +1 -0
  29. package/dist/config/errors.js +25 -0
  30. package/dist/config/errors.js.map +1 -0
  31. package/dist/datetime.d.ts +34 -0
  32. package/dist/datetime.d.ts.map +1 -0
  33. package/dist/datetime.js +188 -0
  34. package/dist/datetime.js.map +1 -0
  35. package/dist/discord/api-errors.d.ts +20 -0
  36. package/dist/discord/api-errors.d.ts.map +1 -0
  37. package/dist/discord/api-errors.js +90 -0
  38. package/dist/discord/api-errors.js.map +1 -0
  39. package/dist/discord/channel-exporter.d.ts +42 -0
  40. package/dist/discord/channel-exporter.d.ts.map +1 -0
  41. package/dist/discord/channel-exporter.js +3 -0
  42. package/dist/discord/channel-exporter.js.map +1 -0
  43. package/dist/discord/command/autocomplete-dispatcher.d.ts +26 -0
  44. package/dist/discord/command/autocomplete-dispatcher.d.ts.map +1 -0
  45. package/dist/discord/command/autocomplete-dispatcher.js +41 -0
  46. package/dist/discord/command/autocomplete-dispatcher.js.map +1 -0
  47. package/dist/discord/command/autocomplete.d.ts +18 -0
  48. package/dist/discord/command/autocomplete.d.ts.map +1 -0
  49. package/dist/discord/command/autocomplete.js +65 -0
  50. package/dist/discord/command/autocomplete.js.map +1 -0
  51. package/dist/discord/command/context.d.ts +33 -0
  52. package/dist/discord/command/context.d.ts.map +1 -0
  53. package/dist/discord/command/context.js +48 -0
  54. package/dist/discord/command/context.js.map +1 -0
  55. package/dist/discord/command/cooldown-guard.d.ts +25 -0
  56. package/dist/discord/command/cooldown-guard.d.ts.map +1 -0
  57. package/dist/discord/command/cooldown-guard.js +60 -0
  58. package/dist/discord/command/cooldown-guard.js.map +1 -0
  59. package/dist/discord/command/create-command.d.ts +100 -0
  60. package/dist/discord/command/create-command.d.ts.map +1 -0
  61. package/dist/discord/command/create-command.js +245 -0
  62. package/dist/discord/command/create-command.js.map +1 -0
  63. package/dist/discord/command/create-context-menu-command.d.ts +51 -0
  64. package/dist/discord/command/create-context-menu-command.d.ts.map +1 -0
  65. package/dist/discord/command/create-context-menu-command.js +128 -0
  66. package/dist/discord/command/create-context-menu-command.js.map +1 -0
  67. package/dist/discord/command/errors.d.ts +19 -0
  68. package/dist/discord/command/errors.d.ts.map +1 -0
  69. package/dist/discord/command/errors.js +27 -0
  70. package/dist/discord/command/errors.js.map +1 -0
  71. package/dist/discord/command/guard.d.ts +30 -0
  72. package/dist/discord/command/guard.d.ts.map +1 -0
  73. package/dist/discord/command/guard.js +34 -0
  74. package/dist/discord/command/guard.js.map +1 -0
  75. package/dist/discord/command/localization.d.ts +9 -0
  76. package/dist/discord/command/localization.d.ts.map +1 -0
  77. package/dist/discord/command/localization.js +13 -0
  78. package/dist/discord/command/localization.js.map +1 -0
  79. package/dist/discord/command/options.d.ts +113 -0
  80. package/dist/discord/command/options.d.ts.map +1 -0
  81. package/dist/discord/command/options.js +509 -0
  82. package/dist/discord/command/options.js.map +1 -0
  83. package/dist/discord/command/permission-guard.d.ts +21 -0
  84. package/dist/discord/command/permission-guard.d.ts.map +1 -0
  85. package/dist/discord/command/permission-guard.js +69 -0
  86. package/dist/discord/command/permission-guard.js.map +1 -0
  87. package/dist/discord/command/router.d.ts +48 -0
  88. package/dist/discord/command/router.d.ts.map +1 -0
  89. package/dist/discord/command/router.js +118 -0
  90. package/dist/discord/command/router.js.map +1 -0
  91. package/dist/discord/command/types.d.ts +51 -0
  92. package/dist/discord/command/types.d.ts.map +1 -0
  93. package/dist/discord/command/types.js +3 -0
  94. package/dist/discord/command/types.js.map +1 -0
  95. package/dist/discord/components/component-router.d.ts +116 -0
  96. package/dist/discord/components/component-router.d.ts.map +1 -0
  97. package/dist/discord/components/component-router.js +123 -0
  98. package/dist/discord/components/component-router.js.map +1 -0
  99. package/dist/discord/components/interactive-message/index.d.ts +3 -0
  100. package/dist/discord/components/interactive-message/index.d.ts.map +1 -0
  101. package/dist/discord/components/interactive-message/index.js +11 -0
  102. package/dist/discord/components/interactive-message/index.js.map +1 -0
  103. package/dist/discord/components/interactive-message/interactive-message-collector.d.ts +195 -0
  104. package/dist/discord/components/interactive-message/interactive-message-collector.d.ts.map +1 -0
  105. package/dist/discord/components/interactive-message/interactive-message-collector.js +151 -0
  106. package/dist/discord/components/interactive-message/interactive-message-collector.js.map +1 -0
  107. package/dist/discord/components/interactive-message/mount-interactive-message.d.ts +30 -0
  108. package/dist/discord/components/interactive-message/mount-interactive-message.d.ts.map +1 -0
  109. package/dist/discord/components/interactive-message/mount-interactive-message.js +44 -0
  110. package/dist/discord/components/interactive-message/mount-interactive-message.js.map +1 -0
  111. package/dist/discord/components/paginator/index.d.ts +5 -0
  112. package/dist/discord/components/paginator/index.d.ts.map +1 -0
  113. package/dist/discord/components/paginator/index.js +15 -0
  114. package/dist/discord/components/paginator/index.js.map +1 -0
  115. package/dist/discord/components/paginator/mount-paginator.d.ts +25 -0
  116. package/dist/discord/components/paginator/mount-paginator.d.ts.map +1 -0
  117. package/dist/discord/components/paginator/mount-paginator.js +41 -0
  118. package/dist/discord/components/paginator/mount-paginator.js.map +1 -0
  119. package/dist/discord/components/paginator/page-state.d.ts +21 -0
  120. package/dist/discord/components/paginator/page-state.d.ts.map +1 -0
  121. package/dist/discord/components/paginator/page-state.js +44 -0
  122. package/dist/discord/components/paginator/page-state.js.map +1 -0
  123. package/dist/discord/components/paginator/paginator-buttons.d.ts +17 -0
  124. package/dist/discord/components/paginator/paginator-buttons.d.ts.map +1 -0
  125. package/dist/discord/components/paginator/paginator-buttons.js +45 -0
  126. package/dist/discord/components/paginator/paginator-buttons.js.map +1 -0
  127. package/dist/discord/components/paginator/paginator-view.d.ts +26 -0
  128. package/dist/discord/components/paginator/paginator-view.d.ts.map +1 -0
  129. package/dist/discord/components/paginator/paginator-view.js +28 -0
  130. package/dist/discord/components/paginator/paginator-view.js.map +1 -0
  131. package/dist/discord/components/persistent-paginator.d.ts +47 -0
  132. package/dist/discord/components/persistent-paginator.d.ts.map +1 -0
  133. package/dist/discord/components/persistent-paginator.js +137 -0
  134. package/dist/discord/components/persistent-paginator.js.map +1 -0
  135. package/dist/discord/components/settings-editor/index.d.ts +7 -0
  136. package/dist/discord/components/settings-editor/index.d.ts.map +1 -0
  137. package/dist/discord/components/settings-editor/index.js +22 -0
  138. package/dist/discord/components/settings-editor/index.js.map +1 -0
  139. package/dist/discord/components/settings-editor/mount-settings-editor.d.ts +140 -0
  140. package/dist/discord/components/settings-editor/mount-settings-editor.d.ts.map +1 -0
  141. package/dist/discord/components/settings-editor/mount-settings-editor.js +344 -0
  142. package/dist/discord/components/settings-editor/mount-settings-editor.js.map +1 -0
  143. package/dist/discord/components/settings-editor/navigation.d.ts +49 -0
  144. package/dist/discord/components/settings-editor/navigation.d.ts.map +1 -0
  145. package/dist/discord/components/settings-editor/navigation.js +39 -0
  146. package/dist/discord/components/settings-editor/navigation.js.map +1 -0
  147. package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts +56 -0
  148. package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts.map +1 -0
  149. package/dist/discord/components/settings-editor/settings-editor-card.view.js +284 -0
  150. package/dist/discord/components/settings-editor/settings-editor-card.view.js.map +1 -0
  151. package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts +69 -0
  152. package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts.map +1 -0
  153. package/dist/discord/components/settings-editor/settings-editor-field-modal.js +353 -0
  154. package/dist/discord/components/settings-editor/settings-editor-field-modal.js.map +1 -0
  155. package/dist/discord/components/settings-editor/settings-editor-fields.d.ts +216 -0
  156. package/dist/discord/components/settings-editor/settings-editor-fields.d.ts.map +1 -0
  157. package/dist/discord/components/settings-editor/settings-editor-fields.js +66 -0
  158. package/dist/discord/components/settings-editor/settings-editor-fields.js.map +1 -0
  159. package/dist/discord/components/settings-editor/settings-editor.view.d.ts +303 -0
  160. package/dist/discord/components/settings-editor/settings-editor.view.d.ts.map +1 -0
  161. package/dist/discord/components/settings-editor/settings-editor.view.js +316 -0
  162. package/dist/discord/components/settings-editor/settings-editor.view.js.map +1 -0
  163. package/dist/discord/events/create-event.d.ts +5 -0
  164. package/dist/discord/events/create-event.d.ts.map +1 -0
  165. package/dist/discord/events/create-event.js +8 -0
  166. package/dist/discord/events/create-event.js.map +1 -0
  167. package/dist/discord/events/event-router.d.ts +16 -0
  168. package/dist/discord/events/event-router.d.ts.map +1 -0
  169. package/dist/discord/events/event-router.js +51 -0
  170. package/dist/discord/events/event-router.js.map +1 -0
  171. package/dist/discord/events/types.d.ts +16 -0
  172. package/dist/discord/events/types.d.ts.map +1 -0
  173. package/dist/discord/events/types.js +3 -0
  174. package/dist/discord/events/types.js.map +1 -0
  175. package/dist/discord/i18n.d.ts +58 -0
  176. package/dist/discord/i18n.d.ts.map +1 -0
  177. package/dist/discord/i18n.js +173 -0
  178. package/dist/discord/i18n.js.map +1 -0
  179. package/dist/discord/interaction/button.d.ts +39 -0
  180. package/dist/discord/interaction/button.d.ts.map +1 -0
  181. package/dist/discord/interaction/button.js +36 -0
  182. package/dist/discord/interaction/button.js.map +1 -0
  183. package/dist/discord/interaction/interaction-router.d.ts +21 -0
  184. package/dist/discord/interaction/interaction-router.d.ts.map +1 -0
  185. package/dist/discord/interaction/interaction-router.js +24 -0
  186. package/dist/discord/interaction/interaction-router.js.map +1 -0
  187. package/dist/discord/interaction/modal.d.ts +171 -0
  188. package/dist/discord/interaction/modal.d.ts.map +1 -0
  189. package/dist/discord/interaction/modal.js +264 -0
  190. package/dist/discord/interaction/modal.js.map +1 -0
  191. package/dist/discord/interaction/select-menu.d.ts +104 -0
  192. package/dist/discord/interaction/select-menu.d.ts.map +1 -0
  193. package/dist/discord/interaction/select-menu.js +126 -0
  194. package/dist/discord/interaction/select-menu.js.map +1 -0
  195. package/dist/discord/permissions.d.ts +32 -0
  196. package/dist/discord/permissions.d.ts.map +1 -0
  197. package/dist/discord/permissions.js +40 -0
  198. package/dist/discord/permissions.js.map +1 -0
  199. package/dist/discord/placeholder-registry-errors.d.ts +10 -0
  200. package/dist/discord/placeholder-registry-errors.d.ts.map +1 -0
  201. package/dist/discord/placeholder-registry-errors.js +18 -0
  202. package/dist/discord/placeholder-registry-errors.js.map +1 -0
  203. package/dist/discord/placeholder-registry.d.ts +53 -0
  204. package/dist/discord/placeholder-registry.d.ts.map +1 -0
  205. package/dist/discord/placeholder-registry.js +52 -0
  206. package/dist/discord/placeholder-registry.js.map +1 -0
  207. package/dist/discord/presenter.d.ts +26 -0
  208. package/dist/discord/presenter.d.ts.map +1 -0
  209. package/dist/discord/presenter.js +3 -0
  210. package/dist/discord/presenter.js.map +1 -0
  211. package/dist/discord/ui/card.d.ts +114 -0
  212. package/dist/discord/ui/card.d.ts.map +1 -0
  213. package/dist/discord/ui/card.js +162 -0
  214. package/dist/discord/ui/card.js.map +1 -0
  215. package/dist/discord/ui/color-input-errors.d.ts +12 -0
  216. package/dist/discord/ui/color-input-errors.d.ts.map +1 -0
  217. package/dist/discord/ui/color-input-errors.js +21 -0
  218. package/dist/discord/ui/color-input-errors.js.map +1 -0
  219. package/dist/discord/ui/color-input.d.ts +37 -0
  220. package/dist/discord/ui/color-input.d.ts.map +1 -0
  221. package/dist/discord/ui/color-input.js +166 -0
  222. package/dist/discord/ui/color-input.js.map +1 -0
  223. package/dist/discord/ui/colors.d.ts +44 -0
  224. package/dist/discord/ui/colors.d.ts.map +1 -0
  225. package/dist/discord/ui/colors.js +45 -0
  226. package/dist/discord/ui/colors.js.map +1 -0
  227. package/dist/discord/ui/embed.d.ts +16 -0
  228. package/dist/discord/ui/embed.d.ts.map +1 -0
  229. package/dist/discord/ui/embed.js +53 -0
  230. package/dist/discord/ui/embed.js.map +1 -0
  231. package/dist/discord/ui/message-attachments.d.ts +40 -0
  232. package/dist/discord/ui/message-attachments.d.ts.map +1 -0
  233. package/dist/discord/ui/message-attachments.js +27 -0
  234. package/dist/discord/ui/message-attachments.js.map +1 -0
  235. package/dist/errors/business-error.d.ts +41 -0
  236. package/dist/errors/business-error.d.ts.map +1 -0
  237. package/dist/errors/business-error.js +39 -0
  238. package/dist/errors/business-error.js.map +1 -0
  239. package/dist/errors/describe-error.d.ts +29 -0
  240. package/dist/errors/describe-error.d.ts.map +1 -0
  241. package/dist/errors/describe-error.js +49 -0
  242. package/dist/errors/describe-error.js.map +1 -0
  243. package/dist/errors/incident-ref.d.ts +7 -0
  244. package/dist/errors/incident-ref.d.ts.map +1 -0
  245. package/dist/errors/incident-ref.js +13 -0
  246. package/dist/errors/incident-ref.js.map +1 -0
  247. package/dist/errors/index.d.ts +4 -0
  248. package/dist/errors/index.d.ts.map +1 -0
  249. package/dist/errors/index.js +15 -0
  250. package/dist/errors/index.js.map +1 -0
  251. package/dist/i18n/business-message.d.ts +14 -0
  252. package/dist/i18n/business-message.d.ts.map +1 -0
  253. package/dist/i18n/business-message.js +19 -0
  254. package/dist/i18n/business-message.js.map +1 -0
  255. package/dist/i18n/catalog.d.ts +27 -0
  256. package/dist/i18n/catalog.d.ts.map +1 -0
  257. package/dist/i18n/catalog.js +25 -0
  258. package/dist/i18n/catalog.js.map +1 -0
  259. package/dist/i18n/errors.d.ts +10 -0
  260. package/dist/i18n/errors.d.ts.map +1 -0
  261. package/dist/i18n/errors.js +18 -0
  262. package/dist/i18n/errors.js.map +1 -0
  263. package/dist/i18n/index.d.ts +6 -0
  264. package/dist/i18n/index.d.ts.map +1 -0
  265. package/dist/i18n/index.js +16 -0
  266. package/dist/i18n/index.js.map +1 -0
  267. package/dist/i18n/locale.d.ts +22 -0
  268. package/dist/i18n/locale.d.ts.map +1 -0
  269. package/dist/i18n/locale.js +40 -0
  270. package/dist/i18n/locale.js.map +1 -0
  271. package/dist/i18n/translator.d.ts +35 -0
  272. package/dist/i18n/translator.d.ts.map +1 -0
  273. package/dist/i18n/translator.js +40 -0
  274. package/dist/i18n/translator.js.map +1 -0
  275. package/dist/logger.d.ts +28 -0
  276. package/dist/logger.d.ts.map +1 -0
  277. package/dist/logger.js +3 -0
  278. package/dist/logger.js.map +1 -0
  279. package/dist/module/module.d.ts +42 -0
  280. package/dist/module/module.d.ts.map +1 -0
  281. package/dist/module/module.js +3 -0
  282. package/dist/module/module.js.map +1 -0
  283. package/dist/persistence/database.d.ts +24 -0
  284. package/dist/persistence/database.d.ts.map +1 -0
  285. package/dist/persistence/database.js +3 -0
  286. package/dist/persistence/database.js.map +1 -0
  287. package/dist/resilience/retry.d.ts +36 -0
  288. package/dist/resilience/retry.d.ts.map +1 -0
  289. package/dist/resilience/retry.js +78 -0
  290. package/dist/resilience/retry.js.map +1 -0
  291. package/dist/scheduler/errors.d.ts +25 -0
  292. package/dist/scheduler/errors.d.ts.map +1 -0
  293. package/dist/scheduler/errors.js +45 -0
  294. package/dist/scheduler/errors.js.map +1 -0
  295. package/dist/scheduler/scheduler.d.ts +68 -0
  296. package/dist/scheduler/scheduler.d.ts.map +1 -0
  297. package/dist/scheduler/scheduler.js +162 -0
  298. package/dist/scheduler/scheduler.js.map +1 -0
  299. package/package.json +89 -0
  300. package/src/authz/authorizer.ts +76 -0
  301. package/src/authz/require-level-guard.ts +42 -0
  302. package/src/clock.ts +18 -0
  303. package/src/concurrency/keyed-queue.ts +50 -0
  304. package/src/concurrency/run-with-concurrency.ts +44 -0
  305. package/src/config/env.ts +58 -0
  306. package/src/config/errors.ts +21 -0
  307. package/src/datetime.ts +221 -0
  308. package/src/discord/api-errors.ts +91 -0
  309. package/src/discord/channel-exporter.ts +48 -0
  310. package/src/discord/command/autocomplete-dispatcher.ts +54 -0
  311. package/src/discord/command/autocomplete.ts +76 -0
  312. package/src/discord/command/context.ts +85 -0
  313. package/src/discord/command/cooldown-guard.ts +81 -0
  314. package/src/discord/command/create-command.ts +431 -0
  315. package/src/discord/command/create-context-menu-command.ts +227 -0
  316. package/src/discord/command/errors.ts +22 -0
  317. package/src/discord/command/guard.ts +54 -0
  318. package/src/discord/command/localization.ts +11 -0
  319. package/src/discord/command/options.ts +847 -0
  320. package/src/discord/command/permission-guard.ts +74 -0
  321. package/src/discord/command/router.ts +142 -0
  322. package/src/discord/command/types.ts +52 -0
  323. package/src/discord/components/component-router.ts +213 -0
  324. package/src/discord/components/interactive-message/index.ts +18 -0
  325. package/src/discord/components/interactive-message/interactive-message-collector.ts +374 -0
  326. package/src/discord/components/interactive-message/mount-interactive-message.ts +71 -0
  327. package/src/discord/components/paginator/index.ts +17 -0
  328. package/src/discord/components/paginator/mount-paginator.ts +69 -0
  329. package/src/discord/components/paginator/page-state.ts +45 -0
  330. package/src/discord/components/paginator/paginator-buttons.ts +60 -0
  331. package/src/discord/components/paginator/paginator-view.ts +61 -0
  332. package/src/discord/components/persistent-paginator.ts +197 -0
  333. package/src/discord/components/settings-editor/index.ts +41 -0
  334. package/src/discord/components/settings-editor/mount-settings-editor.ts +646 -0
  335. package/src/discord/components/settings-editor/navigation.ts +67 -0
  336. package/src/discord/components/settings-editor/settings-editor-card.view.ts +344 -0
  337. package/src/discord/components/settings-editor/settings-editor-field-modal.ts +538 -0
  338. package/src/discord/components/settings-editor/settings-editor-fields.ts +290 -0
  339. package/src/discord/components/settings-editor/settings-editor.view.ts +654 -0
  340. package/src/discord/events/create-event.ts +7 -0
  341. package/src/discord/events/event-router.ts +53 -0
  342. package/src/discord/events/types.ts +17 -0
  343. package/src/discord/i18n.ts +173 -0
  344. package/src/discord/interaction/button.ts +75 -0
  345. package/src/discord/interaction/interaction-router.ts +28 -0
  346. package/src/discord/interaction/modal.ts +505 -0
  347. package/src/discord/interaction/select-menu.ts +246 -0
  348. package/src/discord/permissions.ts +57 -0
  349. package/src/discord/placeholder-registry-errors.ts +11 -0
  350. package/src/discord/placeholder-registry.ts +77 -0
  351. package/src/discord/presenter.ts +26 -0
  352. package/src/discord/ui/card.ts +303 -0
  353. package/src/discord/ui/color-input-errors.ts +17 -0
  354. package/src/discord/ui/color-input.ts +179 -0
  355. package/src/discord/ui/colors.ts +58 -0
  356. package/src/discord/ui/embed.ts +64 -0
  357. package/src/discord/ui/message-attachments.ts +57 -0
  358. package/src/errors/business-error.ts +49 -0
  359. package/src/errors/describe-error.ts +73 -0
  360. package/src/errors/incident-ref.ts +10 -0
  361. package/src/errors/index.ts +12 -0
  362. package/src/i18n/business-message.ts +23 -0
  363. package/src/i18n/catalog.ts +41 -0
  364. package/src/i18n/errors.ts +11 -0
  365. package/src/i18n/index.ts +11 -0
  366. package/src/i18n/locale.ts +41 -0
  367. package/src/i18n/translator.ts +78 -0
  368. package/src/logger.ts +28 -0
  369. package/src/module/module.ts +42 -0
  370. package/src/persistence/database.ts +23 -0
  371. package/src/resilience/retry.ts +102 -0
  372. package/src/scheduler/errors.ts +40 -0
  373. package/src/scheduler/scheduler.ts +248 -0
@@ -0,0 +1,74 @@
1
+ import type { CommandInteraction } from "discord.js";
2
+ import type { Guard, GuardResult } from "@/discord/command/guard";
3
+ import { CORE_MESSAGES } from "@/discord/i18n";
4
+ import { formatPermissions, missingPermissions, type PermissionBit } from "@/discord/permissions";
5
+ import type { LocalizedText } from "@/i18n";
6
+
7
+ /**
8
+ * Generic, reusable guards. They complement — never replace —
9
+ * `defaultMemberPermissions` on a command: that field only *hides* the command
10
+ * in the Discord client, and a server admin can override it in the Integrations
11
+ * UI. The guard is the enforcement that actually runs server-side.
12
+ */
13
+
14
+ const ALLOW: GuardResult = { ok: true };
15
+
16
+ /**
17
+ * Deny unless the member holds every permission in `required`.
18
+ *
19
+ * `interaction.memberPermissions` is `null` outside a guild; that is a denial,
20
+ * since an unknown permission set cannot be treated as a granted one.
21
+ */
22
+ export function createPermissionGuard(
23
+ required: readonly PermissionBit[],
24
+ denyMessage?: LocalizedText,
25
+ ): Guard {
26
+ return {
27
+ check(interaction: CommandInteraction): GuardResult {
28
+ const missing = missingPermissions(interaction.memberPermissions, required);
29
+ if (missing.length === 0) {
30
+ return ALLOW;
31
+ }
32
+ return {
33
+ ok: false,
34
+ message: denyMessage ?? {
35
+ key: CORE_MESSAGES.guardPermissionDenied,
36
+ params: { missing: formatPermissions(missing) },
37
+ },
38
+ };
39
+ },
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Deny unless the member holds one of `roleIds`.
45
+ *
46
+ * Outside a guild there is no member and therefore no role: deny. The member's
47
+ * role cache is authoritative here because `interactionCreate` always carries a
48
+ * fully resolved member object for guild interactions.
49
+ */
50
+ export function createRoleGuard(roleIds: readonly string[], denyMessage?: LocalizedText): Guard {
51
+ return {
52
+ check(interaction: CommandInteraction): GuardResult {
53
+ const member = interaction.member;
54
+ if (!member || !interaction.inCachedGuild()) {
55
+ return { ok: false, message: denyMessage ?? { key: CORE_MESSAGES.guardGuildOnly } };
56
+ }
57
+ const held = interaction.member.roles.cache;
58
+ if (roleIds.some((roleId) => held.has(roleId))) {
59
+ return ALLOW;
60
+ }
61
+ return { ok: false, message: denyMessage ?? { key: CORE_MESSAGES.guardRoleDenied } };
62
+ },
63
+ };
64
+ }
65
+
66
+ /** Deny outside a guild, so a handler can rely on the guild context existing. */
67
+ export const guildOnlyGuard: Guard = {
68
+ check(interaction: CommandInteraction): GuardResult {
69
+ if (interaction.inGuild()) {
70
+ return ALLOW;
71
+ }
72
+ return { ok: false, message: { key: CORE_MESSAGES.guardGuildOnly } };
73
+ },
74
+ };
@@ -0,0 +1,142 @@
1
+ import { type Interaction, REST, Routes } from "discord.js";
2
+ import type {
3
+ CommandRuntime,
4
+ ContextMenuCommand,
5
+ DeployableCommand,
6
+ SlashCommand,
7
+ } from "@/discord/command/types";
8
+ import type { InteractionDispatcher } from "@/discord/interaction/interaction-router";
9
+ import type { Presenter } from "@/discord/presenter";
10
+ import type { Translator } from "@/i18n";
11
+ import type { Logger } from "@/logger";
12
+
13
+ export interface CommandRouterDeps {
14
+ presenter: Presenter;
15
+ logger: Logger;
16
+ translator: Translator;
17
+ }
18
+
19
+ /**
20
+ * Owns the registered slash commands and dispatches chat-input interactions to
21
+ * them, injecting the shared {@link CommandRuntime}. Also registers the command
22
+ * definitions with Discord's API. It does not attach its own listener — it is an
23
+ * {@link InteractionDispatcher} driven by the shared {@link InteractionRouter}.
24
+ */
25
+ export class CommandRouter implements InteractionDispatcher {
26
+ private readonly commands = new Map<string, SlashCommand>();
27
+ private readonly contextMenuCommands = new Map<string, ContextMenuCommand>();
28
+ private readonly runtime: CommandRuntime;
29
+
30
+ constructor(private readonly deps: CommandRouterDeps) {
31
+ this.runtime = {
32
+ presenter: deps.presenter,
33
+ logger: deps.logger,
34
+ translator: deps.translator,
35
+ };
36
+ }
37
+
38
+ /** Register a command (last registration of a name wins). */
39
+ register(command: SlashCommand): this {
40
+ this.commands.set(command.data.name, command);
41
+ return this;
42
+ }
43
+
44
+ /** Register many commands at once. */
45
+ registerAll(commands: Iterable<SlashCommand>): this {
46
+ for (const command of commands) {
47
+ this.register(command);
48
+ }
49
+ return this;
50
+ }
51
+
52
+ /**
53
+ * Register a context-menu entry. Kept in its own map because Discord treats a
54
+ * slash command and a context-menu entry as distinct application command
55
+ * types, and therefore lets them share a name.
56
+ */
57
+ registerContextMenu(command: ContextMenuCommand): this {
58
+ this.contextMenuCommands.set(command.data.name, command);
59
+ return this;
60
+ }
61
+
62
+ registerAllContextMenus(commands: Iterable<ContextMenuCommand>): this {
63
+ for (const command of commands) {
64
+ this.registerContextMenu(command);
65
+ }
66
+ return this;
67
+ }
68
+
69
+ /** Look up a registered slash command, for dispatchers that share this registry. */
70
+ get(name: string): SlashCommand | undefined {
71
+ return this.commands.get(name);
72
+ }
73
+
74
+ /** Claim and dispatch chat-input and context-menu commands; ignore everything else. */
75
+ async handle(interaction: Interaction): Promise<boolean> {
76
+ if (interaction.isChatInputCommand()) {
77
+ const command = this.commands.get(interaction.commandName);
78
+ if (!command) {
79
+ return false;
80
+ }
81
+ await command.dispatch(interaction, this.runtime);
82
+ return true;
83
+ }
84
+
85
+ if (interaction.isContextMenuCommand()) {
86
+ const command = this.contextMenuCommands.get(interaction.commandName);
87
+ if (!command) {
88
+ return false;
89
+ }
90
+ await command.dispatch(interaction, this.runtime);
91
+ return true;
92
+ }
93
+
94
+ return false;
95
+ }
96
+
97
+ /**
98
+ * Push every registered command to its declared scope: commands with
99
+ * `guildIds` go to those guild(s) (instant availability), the rest are
100
+ * deployed globally (available on every server, slower to propagate). The
101
+ * global scope is always synced — even when empty — so stale global commands
102
+ * are pruned. Returns the number of commands deployed.
103
+ */
104
+ async deployCommands(token: string, clientId: string): Promise<number> {
105
+ const rest = new REST({ version: "10" }).setToken(token);
106
+ const global: unknown[] = [];
107
+ const byGuild = new Map<string, unknown[]>();
108
+ let deployed = 0;
109
+
110
+ const all: DeployableCommand[] = [
111
+ ...this.commands.values(),
112
+ ...this.contextMenuCommands.values(),
113
+ ];
114
+
115
+ for (const command of all) {
116
+ const body = command.data.toJSON ? command.data.toJSON() : null;
117
+ if (body === null) {
118
+ continue;
119
+ }
120
+ deployed += 1;
121
+ if (command.guildIds && command.guildIds.length > 0) {
122
+ for (const guildId of command.guildIds) {
123
+ const bucket = byGuild.get(guildId) ?? [];
124
+ bucket.push(body);
125
+ byGuild.set(guildId, bucket);
126
+ }
127
+ } else {
128
+ global.push(body);
129
+ }
130
+ }
131
+
132
+ await rest.put(Routes.applicationCommands(clientId), { body: global });
133
+ this.deps.logger.info({ count: global.length }, "Global slash commands deployed");
134
+
135
+ for (const [guildId, body] of byGuild) {
136
+ await rest.put(Routes.applicationGuildCommands(clientId, guildId), { body });
137
+ this.deps.logger.info({ guildId, count: body.length }, "Guild slash commands deployed");
138
+ }
139
+
140
+ return deployed;
141
+ }
142
+ }
@@ -0,0 +1,52 @@
1
+ import type {
2
+ AutocompleteInteraction,
3
+ ChatInputCommandInteraction,
4
+ ContextMenuCommandInteraction,
5
+ } from "discord.js";
6
+ import type { Presenter } from "@/discord/presenter";
7
+ import type { Translator } from "@/i18n";
8
+ import type { Logger } from "@/logger";
9
+
10
+ /**
11
+ * Per-request dependencies injected into the command pipeline by the router.
12
+ * Passing these at dispatch time (rather than importing singletons) keeps the
13
+ * framework free of global state and trivially testable.
14
+ */
15
+ export interface CommandRuntime {
16
+ readonly presenter: Presenter;
17
+ readonly logger: Logger;
18
+ readonly translator: Translator;
19
+ }
20
+
21
+ /**
22
+ * The framework's slash-command contract. `data` is a discord.js builder (or
23
+ * anything with `.toJSON()`); `dispatch` routes an interaction using the
24
+ * supplied runtime. `guildIds` restricts deployment to specific guild(s); when
25
+ * omitted the command is deployed globally (available on every server).
26
+ */
27
+ export interface SlashCommand {
28
+ readonly data: { name: string; toJSON?: () => unknown };
29
+ readonly guildIds?: readonly string[];
30
+ dispatch(interaction: ChatInputCommandInteraction, runtime: CommandRuntime): Promise<void>;
31
+ /**
32
+ * Answer an autocomplete request for one of this command's options. Present
33
+ * only when at least one option declares a resolver, so the dispatcher can
34
+ * skip commands that never autocomplete.
35
+ */
36
+ autocomplete?(interaction: AutocompleteInteraction): Promise<void>;
37
+ }
38
+
39
+ /**
40
+ * A context-menu (a.k.a. application) command: the entry a user gets by
41
+ * right-clicking a member or a message. It shares deployment and permissions
42
+ * with a slash command, but carries no name to type and no options — only the
43
+ * target the user clicked.
44
+ */
45
+ export interface ContextMenuCommand {
46
+ readonly data: { name: string; toJSON?: () => unknown };
47
+ readonly guildIds?: readonly string[];
48
+ dispatch(interaction: ContextMenuCommandInteraction, runtime: CommandRuntime): Promise<void>;
49
+ }
50
+
51
+ /** Anything the command router can register and deploy to Discord. */
52
+ export type DeployableCommand = SlashCommand | ContextMenuCommand;
@@ -0,0 +1,213 @@
1
+ import {
2
+ type AnySelectMenuInteraction,
3
+ type ButtonInteraction,
4
+ type Interaction,
5
+ MessageFlags,
6
+ type ModalSubmitInteraction,
7
+ } from "discord.js";
8
+ import { CORE_MESSAGES } from "@/discord/i18n";
9
+ import type { InteractionDispatcher } from "@/discord/interaction/interaction-router";
10
+ import { formatPermissions, missingPermissions, type PermissionBit } from "@/discord/permissions";
11
+ import type { Presenter } from "@/discord/presenter";
12
+ import { type Locale, resolveLocale, type Translator } from "@/i18n";
13
+ import type { Logger } from "@/logger";
14
+
15
+ /**
16
+ * Interactions a {@link ComponentHandler} can be routed: message components
17
+ * (buttons, any select menu) and modal submissions.
18
+ */
19
+ export type RoutableInteraction =
20
+ | ButtonInteraction
21
+ | AnySelectMenuInteraction
22
+ | ModalSubmitInteraction;
23
+
24
+ /** Per-dispatch dependencies, injected by the router (mirrors `CommandRuntime`). */
25
+ export interface ComponentRuntime {
26
+ readonly presenter: Presenter;
27
+ readonly logger: Logger;
28
+ readonly translator: Translator;
29
+ }
30
+
31
+ /**
32
+ * Who may act on a component.
33
+ *
34
+ * A command declares its audience through `defaultMemberPermissions` + a
35
+ * `Guard`, and `Guard` is typed on `CommandInteraction` — deliberately, so a
36
+ * guard cannot be attached to a button. That leaves a component with no
37
+ * framework-level place to state who may click it, and the only thing standing
38
+ * between a member and a destructive button is whether whoever wrote the
39
+ * handler remembered to check. Every existing handler does; nothing makes the
40
+ * next one.
41
+ *
42
+ * So the answer is required by the type. There is no default and no optional
43
+ * field: a handler that says nothing does not compile, and "anyone may click
44
+ * this" has to be written down and justified rather than reached by omission.
45
+ */
46
+ export type ComponentAuthorization =
47
+ /** Any member may click. `because` is why that is safe, not decoration. */
48
+ | { readonly kind: "anyone"; readonly because: string }
49
+ /** The router denies unless the member holds every listed permission. */
50
+ | { readonly kind: "permissions"; readonly required: readonly PermissionBit[] }
51
+ /**
52
+ * The handler decides for itself, because the answer depends on state the
53
+ * router cannot see — whether this member opened *this* ticket, say.
54
+ */
55
+ | { readonly kind: "handler"; readonly because: string };
56
+
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
+ /**
76
+ * A persistent component handler, routed by `customId`. It claims an interaction
77
+ * whose customId equals `customId` or starts with `${customId}:`, so dynamic
78
+ * state can be appended after a colon (e.g. handler `role` claims `role:add:123`).
79
+ *
80
+ * Best practice — handler (this) vs collector ({@link import("@/discord/components/paginator").mountPaginator}):
81
+ * use a **handler** for *persistent* components that must keep working forever and
82
+ * survive a restart (role pickers, ticket/verify buttons); the bot has no
83
+ * in-memory closure after a restart, so encode state in the customId (or refetch
84
+ * it). Use a **collector** for *transient*, single-message, owner-scoped flows
85
+ * with a lifetime (pagination, a confirm dialog) — its state lives in memory and
86
+ * dies on idle.
87
+ */
88
+ export interface ComponentHandler {
89
+ readonly customId: string;
90
+ /** Who may act on this component. Required: see {@link ComponentAuthorization}. */
91
+ readonly authorize: ComponentAuthorization;
92
+ handle(interaction: RoutableInteraction, runtime: ComponentRuntime): Promise<void> | void;
93
+ }
94
+
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
+ export interface ComponentRouterDeps {
101
+ presenter: Presenter;
102
+ logger: Logger;
103
+ translator: Translator;
104
+ }
105
+
106
+ function isRoutable(interaction: Interaction): interaction is RoutableInteraction {
107
+ return interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit();
108
+ }
109
+
110
+ /**
111
+ * Routes persistent component/modal interactions to registered handlers by
112
+ * `customId`, injecting the shared {@link ComponentRuntime}. It does not attach
113
+ * its own listener — it is an {@link InteractionDispatcher} driven by the shared
114
+ * {@link import("@/discord/interaction/interaction-router").InteractionRouter},
115
+ * so commands and components share a single `interactionCreate` listener.
116
+ */
117
+ export class ComponentRouter implements InteractionDispatcher {
118
+ private readonly handlers: ComponentHandler[] = [];
119
+ private readonly runtime: ComponentRuntime;
120
+
121
+ constructor(private readonly deps: ComponentRouterDeps) {
122
+ this.runtime = {
123
+ presenter: deps.presenter,
124
+ logger: deps.logger,
125
+ translator: deps.translator,
126
+ };
127
+ }
128
+
129
+ register(handler: ComponentHandler): this {
130
+ this.handlers.push(handler);
131
+ return this;
132
+ }
133
+
134
+ registerAll(handlers: Iterable<ComponentHandler>): this {
135
+ for (const handler of handlers) {
136
+ this.register(handler);
137
+ }
138
+ return this;
139
+ }
140
+
141
+ private match(customId: string): ComponentHandler | undefined {
142
+ return this.handlers.find(
143
+ (handler) => customId === handler.customId || customId.startsWith(`${handler.customId}:`),
144
+ );
145
+ }
146
+
147
+ /** Claim and route component/modal interactions by `customId`; ignore the rest. */
148
+ async handle(interaction: Interaction): Promise<boolean> {
149
+ if (!isRoutable(interaction)) {
150
+ return false;
151
+ }
152
+ const handler = this.match(interaction.customId);
153
+ if (!handler) {
154
+ return false;
155
+ }
156
+ // Before the handler runs, so a declared permission cannot be bypassed by a
157
+ // handler that forgot to check it.
158
+ if (!(await this.passesAuthorization(interaction, handler))) {
159
+ return true;
160
+ }
161
+ try {
162
+ await handler.handle(interaction, this.runtime);
163
+ } catch (error) {
164
+ this.deps.logger.error(
165
+ {
166
+ customId: interaction.customId,
167
+ err: error instanceof Error ? error.message : String(error),
168
+ },
169
+ "Component handler failed",
170
+ );
171
+ }
172
+ return true;
173
+ }
174
+
175
+ /**
176
+ * Enforce the handler's declared audience. Only `permissions` is enforceable
177
+ * here — the other two kinds are statements about a decision made elsewhere,
178
+ * and their value is that the handler had to state one.
179
+ *
180
+ * `memberPermissions` is `null` outside a guild; that is a denial, since an
181
+ * unknown permission set must never be read as a granted one — the same rule
182
+ * `createPermissionGuard` follows for commands.
183
+ */
184
+ private async passesAuthorization(
185
+ interaction: RoutableInteraction,
186
+ handler: ComponentHandler,
187
+ ): Promise<boolean> {
188
+ if (handler.authorize.kind !== "permissions") {
189
+ return true;
190
+ }
191
+ const missing = missingPermissions(interaction.memberPermissions, handler.authorize.required);
192
+ if (missing.length === 0) {
193
+ return true;
194
+ }
195
+
196
+ const locale = this.localeOf(interaction);
197
+ const message = this.deps.translator.translate(locale, CORE_MESSAGES.guardPermissionDenied, {
198
+ missing: formatPermissions(missing),
199
+ });
200
+ await interaction.reply({
201
+ embeds: [this.deps.presenter.denial(message, locale)],
202
+ flags: MessageFlags.Ephemeral,
203
+ });
204
+ return false;
205
+ }
206
+
207
+ private localeOf(interaction: RoutableInteraction): Locale {
208
+ return resolveLocale(
209
+ [interaction.locale, interaction.guildLocale],
210
+ this.deps.translator.defaultLocale,
211
+ );
212
+ }
213
+ }
@@ -0,0 +1,18 @@
1
+ export {
2
+ COLLECTOR_IDLE_MS,
3
+ createStateStore,
4
+ type DisabledInteractiveMessageComponents,
5
+ type InteractiveMessageCollectorDeps,
6
+ type InteractiveMessageHandle,
7
+ type InteractiveMessagePayload,
8
+ type InteractiveSelect,
9
+ type InteractiveView,
10
+ mountInteractiveMessageCollector,
11
+ type SelectComponentContext,
12
+ type StateStore,
13
+ toMessageEditOptions,
14
+ } from "@/discord/components/interactive-message/interactive-message-collector";
15
+ export {
16
+ type InteractiveMessageOptions,
17
+ mountInteractiveMessage,
18
+ } from "@/discord/components/interactive-message/mount-interactive-message";