@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,25 @@
1
+ import { type Clock } from "../../clock";
2
+ import type { Guard } from "../../discord/command/guard";
3
+ import type { LocalizedText } from "../../i18n";
4
+ /** How a cooldown window is shared between callers. */
5
+ export type CooldownScope = "user" | "guild" | "global";
6
+ export interface CooldownOptions {
7
+ /** Minimum delay between two accepted invocations, in milliseconds. */
8
+ windowMs: number;
9
+ /** Who shares the window. Defaults to `user`. */
10
+ scope?: CooldownScope;
11
+ /** Denial message; receives the whole seconds left before the next attempt. */
12
+ message?: (secondsLeft: number) => LocalizedText;
13
+ /** Injected for tests; defaults to the system clock. */
14
+ clock?: Clock;
15
+ }
16
+ /**
17
+ * Rate-limit a command per user, per guild or globally.
18
+ *
19
+ * Expired entries are pruned on access rather than by a timer: the map is only
20
+ * read when the command runs, so a background interval would keep the process
21
+ * awake for no benefit. Each guard instance owns its map, so two commands never
22
+ * share a window unless they share the guard.
23
+ */
24
+ export declare function createCooldownGuard(options: CooldownOptions): Guard;
25
+ //# sourceMappingURL=cooldown-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cooldown-guard.d.ts","sourceRoot":"","sources":["../../../src/discord/command/cooldown-guard.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,KAAK,EAAe,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,yBAAyB,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAK5C,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAExD,MAAM,WAAW,eAAe;IAC/B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,aAAa,CAAC;IACjD,wDAAwD;IACxD,KAAK,CAAC,EAAE,KAAK,CAAC;CACd;AAsBD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,KAAK,CAoBnE"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCooldownGuard = createCooldownGuard;
4
+ const clock_1 = require("../../clock");
5
+ const i18n_1 = require("../../discord/i18n");
6
+ const ALLOW = { ok: true };
7
+ const MILLISECONDS_PER_SECOND = 1000;
8
+ function defaultMessage(secondsLeft) {
9
+ return { key: i18n_1.CORE_MESSAGES.cooldownWait, params: { seconds: secondsLeft } };
10
+ }
11
+ /**
12
+ * The key sharing one cooldown window. `global` is a constant so every caller
13
+ * collides on purpose; `guild` falls back to the user outside a guild, so a DM
14
+ * can never bypass a guild-scoped cooldown by having no guild id.
15
+ */
16
+ function cooldownKey(interaction, scope) {
17
+ switch (scope) {
18
+ case "global":
19
+ return `${interaction.commandName}:global`;
20
+ case "guild":
21
+ return `${interaction.commandName}:guild:${interaction.guildId ?? interaction.user.id}`;
22
+ case "user":
23
+ return `${interaction.commandName}:user:${interaction.user.id}`;
24
+ }
25
+ }
26
+ /**
27
+ * Rate-limit a command per user, per guild or globally.
28
+ *
29
+ * Expired entries are pruned on access rather than by a timer: the map is only
30
+ * read when the command runs, so a background interval would keep the process
31
+ * awake for no benefit. Each guard instance owns its map, so two commands never
32
+ * share a window unless they share the guard.
33
+ */
34
+ function createCooldownGuard(options) {
35
+ const { windowMs, scope = "user", message = defaultMessage, clock = clock_1.systemClock } = options;
36
+ const lastUsed = new Map();
37
+ return {
38
+ check(interaction) {
39
+ const now = clock.now().getTime();
40
+ const key = cooldownKey(interaction, scope);
41
+ const previous = lastUsed.get(key);
42
+ if (previous !== undefined && now - previous < windowMs) {
43
+ const secondsLeft = Math.ceil((windowMs - (now - previous)) / MILLISECONDS_PER_SECOND);
44
+ return { ok: false, message: message(secondsLeft) };
45
+ }
46
+ lastUsed.set(key, now);
47
+ pruneExpired(lastUsed, now, windowMs);
48
+ return ALLOW;
49
+ },
50
+ };
51
+ }
52
+ /** Drop entries whose window has elapsed, so the map cannot grow without bound. */
53
+ function pruneExpired(lastUsed, now, windowMs) {
54
+ for (const [key, timestamp] of lastUsed) {
55
+ if (now - timestamp >= windowMs) {
56
+ lastUsed.delete(key);
57
+ }
58
+ }
59
+ }
60
+ //# sourceMappingURL=cooldown-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cooldown-guard.js","sourceRoot":"","sources":["../../../src/discord/command/cooldown-guard.ts"],"names":[],"mappings":";;AAmDA,kDAoBC;AAtED,mCAAkD;AAElD,yCAA+C;AAG/C,MAAM,KAAK,GAAgB,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACxC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAgBrC,SAAS,cAAc,CAAC,WAAmB;IAC1C,OAAO,EAAE,GAAG,EAAE,oBAAa,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,WAA+B,EAAE,KAAoB;IACzE,QAAQ,KAAK,EAAE,CAAC;QACf,KAAK,QAAQ;YACZ,OAAO,GAAG,WAAW,CAAC,WAAW,SAAS,CAAC;QAC5C,KAAK,OAAO;YACX,OAAO,GAAG,WAAW,CAAC,WAAW,UAAU,WAAW,CAAC,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACzF,KAAK,MAAM;YACV,OAAO,GAAG,WAAW,CAAC,WAAW,SAAS,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IAClE,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,mBAAmB,CAAC,OAAwB;IAC3D,MAAM,EAAE,QAAQ,EAAE,KAAK,GAAG,MAAM,EAAE,OAAO,GAAG,cAAc,EAAE,KAAK,GAAG,mBAAW,EAAE,GAAG,OAAO,CAAC;IAC5F,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE3C,OAAO;QACN,KAAK,CAAC,WAA+B;YACpC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YAC5C,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAEnC,IAAI,QAAQ,KAAK,SAAS,IAAI,GAAG,GAAG,QAAQ,GAAG,QAAQ,EAAE,CAAC;gBACzD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,GAAG,GAAG,QAAQ,CAAC,CAAC,GAAG,uBAAuB,CAAC,CAAC;gBACvF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACrD,CAAC;YAED,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACvB,YAAY,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YACtC,OAAO,KAAK,CAAC;QACd,CAAC;KACD,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,SAAS,YAAY,CAAC,QAA6B,EAAE,GAAW,EAAE,QAAgB;IACjF,KAAK,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzC,IAAI,GAAG,GAAG,SAAS,IAAI,QAAQ,EAAE,CAAC;YACjC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;IACF,CAAC;AACF,CAAC"}
@@ -0,0 +1,100 @@
1
+ import { type LocalizationMap } from "discord.js";
2
+ import { type Context } from "../../discord/command/context";
3
+ import type { Guard } from "../../discord/command/guard";
4
+ import type { Options } from "../../discord/command/options";
5
+ import type { SlashCommand } from "../../discord/command/types";
6
+ import { type PermissionBit } from "../../discord/permissions";
7
+ import { type LocalizedText } from "../../i18n";
8
+ export interface SubcommandDef<O extends Options> {
9
+ description: string;
10
+ /** Translated names, keyed by Discord locale (e.g. `{ fr: "ajouter" }`). */
11
+ nameLocalizations?: LocalizationMap;
12
+ /** Translated descriptions, keyed by Discord locale. */
13
+ descriptionLocalizations?: LocalizationMap;
14
+ options?: O;
15
+ guard?: Guard;
16
+ /** Fallback message shown for unexpected (non-business) errors. */
17
+ fallbackError?: LocalizedText;
18
+ /** Defer the reply before running the handler (for work that may exceed 3s). */
19
+ defer?: boolean;
20
+ /** Whether replies (and the defer) are ephemeral. Defaults to true. */
21
+ ephemeral?: boolean;
22
+ handler: (ctx: Context<O>) => Promise<void>;
23
+ }
24
+ /**
25
+ * Type-erased subcommand. `createSubCommand()` captures the per-subcommand option
26
+ * generic `O` inside `invoke`, so a single command can hold subcommands with
27
+ * differing option shapes in one record without leaking `any`.
28
+ */
29
+ export interface CompiledSubcommand {
30
+ description: string;
31
+ nameLocalizations?: LocalizationMap;
32
+ descriptionLocalizations?: LocalizationMap;
33
+ guard?: Guard;
34
+ fallbackError?: LocalizedText;
35
+ defer: boolean;
36
+ ephemeral: boolean;
37
+ options: Options;
38
+ invoke(ctx: Context<Options>): Promise<void>;
39
+ }
40
+ export declare function createSubCommand<O extends Options>(def: SubcommandDef<O>): CompiledSubcommand;
41
+ interface GroupDef {
42
+ description: string;
43
+ nameLocalizations?: LocalizationMap;
44
+ descriptionLocalizations?: LocalizationMap;
45
+ subcommands: Record<string, CompiledSubcommand>;
46
+ }
47
+ /**
48
+ * A command is either *flat* (a single `handler`, like `/ping`) or *grouped*
49
+ * (`subcommands`/`groups`, like `/notes add`). Provide `handler` for the
50
+ * former; `handler` takes precedence over `subcommands`/`groups` if both are set.
51
+ */
52
+ export interface CommandDef<O extends Options = Options> {
53
+ name: string;
54
+ description: string;
55
+ /** Translated names, keyed by Discord locale (e.g. `{ fr: "notes" }`). */
56
+ nameLocalizations?: LocalizationMap;
57
+ /** Translated descriptions, keyed by Discord locale. */
58
+ descriptionLocalizations?: LocalizationMap;
59
+ guard?: Guard;
60
+ /**
61
+ * Permissions a member must hold for Discord to *show* the command. This is a
62
+ * display filter, never a security boundary: a server admin can override it in
63
+ * the Integrations UI, so always pair it with a guard (see `permission-guard.ts`).
64
+ * Only meaningful on the root command — Discord has no per-subcommand equivalent.
65
+ */
66
+ defaultMemberPermissions?: readonly PermissionBit[];
67
+ /** Restrict the command to guild contexts (no DM, no user-app install). */
68
+ guildOnly?: boolean;
69
+ /** Restrict deployment to specific guild(s); omit to deploy globally (all servers). */
70
+ guildIds?: readonly string[];
71
+ /** Flat command: typed options for the single handler. */
72
+ options?: O;
73
+ /** Flat command: defer the reply before running the handler. */
74
+ defer?: boolean;
75
+ /** Flat command: whether replies (and the defer) are ephemeral. Defaults to true. */
76
+ ephemeral?: boolean;
77
+ /** Flat command: fallback message for unexpected errors. */
78
+ fallbackError?: LocalizedText;
79
+ /** Flat command: the single handler invoked when the command runs. */
80
+ handler?: (ctx: Context<O>) => Promise<void>;
81
+ /** Grouped command: named subcommands. */
82
+ subcommands?: Record<string, CompiledSubcommand>;
83
+ /** Grouped command: named subcommand groups. */
84
+ groups?: Record<string, GroupDef>;
85
+ }
86
+ /**
87
+ * The key a subcommand is stored and looked up under. Exported because dispatch
88
+ * and autocomplete must agree on it: if they ever built it differently, an
89
+ * autocomplete inside a group would silently resolve to no options instead of
90
+ * failing loudly.
91
+ */
92
+ export declare function routeKey(group: string | null, name: string): string;
93
+ /**
94
+ * Build a {@link SlashCommand} from a declarative definition: it produces the
95
+ * discord.js builder *and* the routing/guard/error pipeline. The runtime
96
+ * (presenter, logger) is injected per-dispatch by the command router.
97
+ */
98
+ export declare function createCommand<O extends Options = Options>(def: CommandDef<O>): SlashCommand;
99
+ export {};
100
+ //# sourceMappingURL=create-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-command.d.ts","sourceRoot":"","sources":["../../../src/discord/command/create-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSubCommand = createSubCommand;
4
+ exports.routeKey = routeKey;
5
+ exports.createCommand = createCommand;
6
+ const discord_js_1 = require("discord.js");
7
+ const autocomplete_1 = require("../../discord/command/autocomplete");
8
+ const context_1 = require("../../discord/command/context");
9
+ const i18n_1 = require("../../discord/i18n");
10
+ const permissions_1 = require("../../discord/permissions");
11
+ const errors_1 = require("../../errors");
12
+ const i18n_2 = require("../../i18n");
13
+ function createSubCommand(def) {
14
+ return {
15
+ description: def.description,
16
+ nameLocalizations: def.nameLocalizations,
17
+ descriptionLocalizations: def.descriptionLocalizations,
18
+ guard: def.guard,
19
+ fallbackError: def.fallbackError,
20
+ defer: def.defer ?? false,
21
+ ephemeral: def.ephemeral ?? true,
22
+ options: def.options ?? {},
23
+ invoke: (ctx) => def.handler(ctx),
24
+ };
25
+ }
26
+ /** Resolve the reply locale for an interaction handled outside `createContext`. */
27
+ function interactionLocale(interaction, runtime) {
28
+ return (0, i18n_2.resolveLocale)([interaction.locale, interaction.guildLocale], runtime.translator.defaultLocale);
29
+ }
30
+ /**
31
+ * The key a subcommand is stored and looked up under. Exported because dispatch
32
+ * and autocomplete must agree on it: if they ever built it differently, an
33
+ * autocomplete inside a group would silently resolve to no options instead of
34
+ * failing loudly.
35
+ */
36
+ function routeKey(group, name) {
37
+ return group ? `${group}/${name}` : name;
38
+ }
39
+ /**
40
+ * Apply the settings that only exist on the root command: localisations, the
41
+ * visibility filter and the context restriction.
42
+ *
43
+ * `defaultMemberPermissions` is resolved from flags into the single bitfield the
44
+ * API expects. An empty array is meaningful — it means "administrators only" to
45
+ * Discord — so it is passed through rather than skipped.
46
+ */
47
+ function applyRootSettings(builder, def) {
48
+ if (def.nameLocalizations) {
49
+ builder.setNameLocalizations(def.nameLocalizations);
50
+ }
51
+ if (def.descriptionLocalizations) {
52
+ builder.setDescriptionLocalizations(def.descriptionLocalizations);
53
+ }
54
+ if (def.defaultMemberPermissions) {
55
+ builder.setDefaultMemberPermissions((0, permissions_1.resolvePermissions)(def.defaultMemberPermissions));
56
+ }
57
+ if (def.guildOnly) {
58
+ builder.setContexts(discord_js_1.InteractionContextType.Guild);
59
+ }
60
+ }
61
+ function applySubcommand(container, name, def) {
62
+ container.addSubcommand((subBuilder) => {
63
+ subBuilder.setName(name).setDescription(def.description);
64
+ if (def.nameLocalizations) {
65
+ subBuilder.setNameLocalizations(def.nameLocalizations);
66
+ }
67
+ if (def.descriptionLocalizations) {
68
+ subBuilder.setDescriptionLocalizations(def.descriptionLocalizations);
69
+ }
70
+ for (const [optionName, option] of Object.entries(def.options)) {
71
+ option.apply(subBuilder, optionName);
72
+ }
73
+ return subBuilder;
74
+ });
75
+ }
76
+ function readOptions(options, interaction) {
77
+ const values = {};
78
+ for (const [name, option] of Object.entries(options)) {
79
+ values[name] = option.read(interaction, name);
80
+ }
81
+ return values;
82
+ }
83
+ function ephemeralReplyOptions(ephemeral) {
84
+ if (ephemeral) {
85
+ return { flags: discord_js_1.MessageFlags.Ephemeral };
86
+ }
87
+ return {};
88
+ }
89
+ /** Run a guard if present; reply with a denial and return `false` when it fails. */
90
+ async function passesGuard(interaction, guard, runtime) {
91
+ if (!guard) {
92
+ return true;
93
+ }
94
+ const result = await guard.check(interaction);
95
+ if (!result.ok) {
96
+ const locale = interactionLocale(interaction, runtime);
97
+ await interaction.reply({
98
+ embeds: [
99
+ runtime.presenter.denial(runtime.translator.resolve(locale, result.message), locale),
100
+ ],
101
+ flags: discord_js_1.MessageFlags.Ephemeral,
102
+ });
103
+ return false;
104
+ }
105
+ return true;
106
+ }
107
+ /** Read options, optionally defer, build the context and invoke the handler. */
108
+ async function executeRoute(interaction, route, label, runtime) {
109
+ const { presenter, logger, translator } = runtime;
110
+ try {
111
+ const values = readOptions(route.options, interaction);
112
+ if (route.defer) {
113
+ await interaction.deferReply(ephemeralReplyOptions(route.ephemeral));
114
+ }
115
+ const ctx = (0, context_1.createContext)(interaction, values, route.ephemeral, presenter, logger, translator);
116
+ await route.invoke(ctx);
117
+ }
118
+ catch (error) {
119
+ await renderError(interaction, route, label, error, runtime);
120
+ }
121
+ }
122
+ async function dispatchFlat(interaction, root, runtime) {
123
+ if (!(await passesGuard(interaction, root.guard, runtime))) {
124
+ return;
125
+ }
126
+ await executeRoute(interaction, root, interaction.commandName, runtime);
127
+ }
128
+ async function dispatchGrouped(interaction, routes, commandGuard, runtime) {
129
+ const group = interaction.options.getSubcommandGroup(false);
130
+ const name = interaction.options.getSubcommand();
131
+ const label = routeKey(group, name);
132
+ const route = routes.get(label);
133
+ if (!route) {
134
+ const locale = interactionLocale(interaction, runtime);
135
+ await interaction.reply({
136
+ embeds: [
137
+ runtime.presenter.error(runtime.translator.translate(locale, i18n_1.CORE_MESSAGES.unknownSubcommand), locale),
138
+ ],
139
+ flags: discord_js_1.MessageFlags.Ephemeral,
140
+ });
141
+ return;
142
+ }
143
+ if (!(await passesGuard(interaction, route.guard ?? commandGuard, runtime))) {
144
+ return;
145
+ }
146
+ await executeRoute(interaction, route, label, runtime);
147
+ }
148
+ async function renderError(interaction, route, label, error, runtime) {
149
+ const { presenter, logger, translator } = runtime;
150
+ const locale = interactionLocale(interaction, runtime);
151
+ if (error instanceof errors_1.BusinessError) {
152
+ const text = error.translation
153
+ ? translator.translate(locale, error.translation.key, error.translation.params)
154
+ : error.message;
155
+ const embed = error.severity === "warning"
156
+ ? presenter.warning(text, locale)
157
+ : presenter.error(text, locale);
158
+ await (0, context_1.sendEmbed)(interaction, embed, route.ephemeral, logger);
159
+ return;
160
+ }
161
+ const ref = (0, errors_1.createIncidentRef)();
162
+ logger.error({
163
+ command: interaction.commandName,
164
+ subcommand: label,
165
+ userId: interaction.user.id,
166
+ ref,
167
+ err: error instanceof Error ? error.message : String(error),
168
+ stack: error instanceof Error ? error.stack : undefined,
169
+ }, "Command handler failed");
170
+ await (0, context_1.sendEmbed)(interaction, presenter.systemError(translator.resolve(locale, route.fallbackError ?? { key: i18n_1.CORE_MESSAGES.genericError }), ref, locale), route.ephemeral, logger);
171
+ }
172
+ /**
173
+ * Build a {@link SlashCommand} from a declarative definition: it produces the
174
+ * discord.js builder *and* the routing/guard/error pipeline. The runtime
175
+ * (presenter, logger) is injected per-dispatch by the command router.
176
+ */
177
+ function createCommand(def) {
178
+ const builder = new discord_js_1.SlashCommandBuilder().setName(def.name).setDescription(def.description);
179
+ // Configured before the flat/grouped branching below: both return paths share
180
+ // this builder, so root-level settings must not be duplicated per branch.
181
+ applyRootSettings(builder, def);
182
+ if (def.handler) {
183
+ const handler = def.handler;
184
+ const root = {
185
+ description: def.description,
186
+ guard: def.guard,
187
+ fallbackError: def.fallbackError,
188
+ defer: def.defer ?? false,
189
+ ephemeral: def.ephemeral ?? true,
190
+ options: def.options ?? {},
191
+ invoke: (ctx) => handler(ctx),
192
+ };
193
+ for (const [optionName, option] of Object.entries(root.options)) {
194
+ option.apply(builder, optionName);
195
+ }
196
+ return {
197
+ data: builder,
198
+ guildIds: def.guildIds,
199
+ dispatch: (interaction, runtime) => dispatchFlat(interaction, root, runtime),
200
+ autocomplete: hasAutocomplete(root.options)
201
+ ? (interaction) => (0, autocomplete_1.respondWithSuggestions)(interaction, root.options)
202
+ : undefined,
203
+ };
204
+ }
205
+ const routes = new Map();
206
+ if (def.subcommands) {
207
+ for (const [name, subDef] of Object.entries(def.subcommands)) {
208
+ applySubcommand(builder, name, subDef);
209
+ routes.set(name, subDef);
210
+ }
211
+ }
212
+ if (def.groups) {
213
+ for (const [groupName, group] of Object.entries(def.groups)) {
214
+ builder.addSubcommandGroup((groupBuilder) => {
215
+ groupBuilder.setName(groupName).setDescription(group.description);
216
+ if (group.nameLocalizations) {
217
+ groupBuilder.setNameLocalizations(group.nameLocalizations);
218
+ }
219
+ if (group.descriptionLocalizations) {
220
+ groupBuilder.setDescriptionLocalizations(group.descriptionLocalizations);
221
+ }
222
+ for (const [name, subDef] of Object.entries(group.subcommands)) {
223
+ applySubcommand(groupBuilder, name, subDef);
224
+ }
225
+ return groupBuilder;
226
+ });
227
+ for (const [name, subDef] of Object.entries(group.subcommands)) {
228
+ routes.set(routeKey(groupName, name), subDef);
229
+ }
230
+ }
231
+ }
232
+ return {
233
+ data: builder,
234
+ guildIds: def.guildIds,
235
+ dispatch: (interaction, runtime) => dispatchGrouped(interaction, routes, def.guard, runtime),
236
+ autocomplete: [...routes.values()].some((route) => hasAutocomplete(route.options))
237
+ ? (interaction) => (0, autocomplete_1.respondWithSuggestions)(interaction, (0, autocomplete_1.resolveRouteOptions)(interaction, routes, null))
238
+ : undefined,
239
+ };
240
+ }
241
+ /** Whether any option of a route declares a resolver, so the seam stays absent otherwise. */
242
+ function hasAutocomplete(options) {
243
+ return Object.values(options).some((option) => option.autocomplete !== undefined);
244
+ }
245
+ //# sourceMappingURL=create-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-command.js","sourceRoot":"","sources":["../../../src/discord/command/create-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { type ContextMenuCommandInteraction, type LocalizationMap, type Message, type User } from "discord.js";
2
+ import type { Guard } from "../../discord/command/guard";
3
+ import type { ContextMenuCommand } from "../../discord/command/types";
4
+ import { type PermissionBit } from "../../discord/permissions";
5
+ import { type Locale, type LocalizedText, type TranslationParams } from "../../i18n";
6
+ /**
7
+ * Context-menu commands: the entries a user reaches by right-clicking a member
8
+ * ("user" target) or a message ("message" target), under the Apps submenu.
9
+ *
10
+ * They are declared here rather than through `createCommand` because they are a
11
+ * different application command type: no name is typed, no options are parsed —
12
+ * the interaction carries the clicked target instead. Everything else (guards,
13
+ * permissions, error rendering) mirrors the slash-command pipeline so a module
14
+ * writes both the same way.
15
+ */
16
+ /** The target handed to the handler, discriminated by the command's kind. */
17
+ export interface ContextMenuTarget {
18
+ readonly user: User;
19
+ readonly message: Message;
20
+ }
21
+ export interface ContextMenuContext<K extends keyof ContextMenuTarget> {
22
+ readonly interaction: ContextMenuCommandInteraction;
23
+ /** The right-clicked member or message. */
24
+ readonly target: ContextMenuTarget[K];
25
+ /** Reply language: user's client locale → guild locale → configured default. */
26
+ readonly locale: Locale;
27
+ /** Translate a catalog key for this interaction's locale. */
28
+ t(key: string, params?: TranslationParams): string;
29
+ confirm(message: string): Promise<void>;
30
+ error(message: string): Promise<void>;
31
+ deny(message: string): Promise<void>;
32
+ }
33
+ export interface ContextMenuCommandDef<K extends keyof ContextMenuTarget> {
34
+ /** Displayed verbatim in the context menu, so spaces and capitals are allowed. */
35
+ name: string;
36
+ /** Which right-click surface the entry appears on. */
37
+ target: K;
38
+ nameLocalizations?: LocalizationMap;
39
+ guard?: Guard;
40
+ /** Display filter — pair it with a guard, it is not a security boundary. */
41
+ defaultMemberPermissions?: readonly PermissionBit[];
42
+ guildOnly?: boolean;
43
+ guildIds?: readonly string[];
44
+ /** Whether replies are ephemeral. Defaults to true. */
45
+ ephemeral?: boolean;
46
+ defer?: boolean;
47
+ fallbackError?: LocalizedText;
48
+ handler: (ctx: ContextMenuContext<K>) => Promise<void>;
49
+ }
50
+ export declare function createContextMenuCommand<K extends keyof ContextMenuTarget>(def: ContextMenuCommandDef<K>): ContextMenuCommand;
51
+ //# sourceMappingURL=create-context-menu-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-context-menu-command.d.ts","sourceRoot":"","sources":["../../../src/discord/command/create-context-menu-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,6BAA6B,EAGlC,KAAK,eAAe,EACpB,KAAK,OAAO,EAEZ,KAAK,IAAI,EACT,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,KAAK,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElF,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,uBAAuB,CAAC;AAE/E,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAiB,KAAK,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAGhG;;;;;;;;;GASG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,MAAM,iBAAiB;IACpE,QAAQ,CAAC,WAAW,EAAE,6BAA6B,CAAC;IACpD,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IACtC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACnD,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC,SAAS,MAAM,iBAAiB;IACvE,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,MAAM,EAAE,CAAC,CAAC;IACV,iBAAiB,CAAC,EAAE,eAAe,CAAC;IACpC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,4EAA4E;IAC5E,wBAAwB,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,OAAO,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AA6BD,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,MAAM,iBAAiB,EACzE,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAC3B,kBAAkB,CAqBpB"}
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createContextMenuCommand = createContextMenuCommand;
4
+ const discord_js_1 = require("discord.js");
5
+ const errors_1 = require("../../discord/command/errors");
6
+ const i18n_1 = require("../../discord/i18n");
7
+ const permissions_1 = require("../../discord/permissions");
8
+ const errors_2 = require("../../errors");
9
+ const i18n_2 = require("../../i18n");
10
+ const COMMAND_TYPES = {
11
+ user: discord_js_1.ApplicationCommandType.User,
12
+ message: discord_js_1.ApplicationCommandType.Message,
13
+ };
14
+ /**
15
+ * Resolve the clicked target. discord.js exposes it through kind-specific
16
+ * getters, so the narrowing happens once here rather than in every handler.
17
+ *
18
+ * The kind cannot actually mismatch at runtime — Discord routes an interaction
19
+ * to the command it was registered as — but the resolver still asserts it, so a
20
+ * misroute surfaces as a named error instead of an undefined target reaching the
21
+ * handler.
22
+ */
23
+ function resolveTarget(interaction, kind) {
24
+ if (kind === "user" && interaction.isUserContextMenuCommand()) {
25
+ return interaction.targetUser;
26
+ }
27
+ if (kind === "message" && interaction.isMessageContextMenuCommand()) {
28
+ return interaction.targetMessage;
29
+ }
30
+ throw new errors_1.ContextMenuTargetError(interaction.commandName, kind);
31
+ }
32
+ function createContextMenuCommand(def) {
33
+ const ephemeral = def.ephemeral ?? true;
34
+ const builder = new discord_js_1.ContextMenuCommandBuilder()
35
+ .setName(def.name)
36
+ .setType(COMMAND_TYPES[def.target]);
37
+ if (def.nameLocalizations) {
38
+ builder.setNameLocalizations(def.nameLocalizations);
39
+ }
40
+ if (def.defaultMemberPermissions) {
41
+ builder.setDefaultMemberPermissions((0, permissions_1.resolvePermissions)(def.defaultMemberPermissions));
42
+ }
43
+ if (def.guildOnly) {
44
+ builder.setContexts(discord_js_1.InteractionContextType.Guild);
45
+ }
46
+ return {
47
+ data: builder,
48
+ guildIds: def.guildIds,
49
+ dispatch: (interaction, runtime) => dispatch(interaction, def, ephemeral, runtime),
50
+ };
51
+ }
52
+ async function dispatch(interaction, def, ephemeral, runtime) {
53
+ const { presenter, logger, translator } = runtime;
54
+ const locale = (0, i18n_2.resolveLocale)([interaction.locale, interaction.guildLocale], translator.defaultLocale);
55
+ if (def.guard) {
56
+ const result = await def.guard.check(interaction);
57
+ if (!result.ok) {
58
+ await interaction.reply({
59
+ embeds: [presenter.denial(translator.resolve(locale, result.message), locale)],
60
+ flags: discord_js_1.MessageFlags.Ephemeral,
61
+ });
62
+ return;
63
+ }
64
+ }
65
+ try {
66
+ if (def.defer) {
67
+ await interaction.deferReply(ephemeral ? { flags: discord_js_1.MessageFlags.Ephemeral } : {});
68
+ }
69
+ await def.handler({
70
+ interaction,
71
+ target: resolveTarget(interaction, def.target),
72
+ locale,
73
+ t: (key, params) => translator.translate(locale, key, params),
74
+ confirm: (message) => send(interaction, presenter.confirmation(message, locale), ephemeral, logger),
75
+ error: (message) => send(interaction, presenter.error(message, locale), ephemeral, logger),
76
+ deny: (message) => send(interaction, presenter.denial(message, locale), ephemeral, logger),
77
+ });
78
+ }
79
+ catch (error) {
80
+ if (error instanceof errors_2.BusinessError) {
81
+ const text = error.translation
82
+ ? translator.translate(locale, error.translation.key, error.translation.params)
83
+ : error.message;
84
+ const embed = error.severity === "warning"
85
+ ? presenter.warning(text, locale)
86
+ : presenter.error(text, locale);
87
+ await send(interaction, embed, ephemeral, logger);
88
+ return;
89
+ }
90
+ const ref = (0, errors_2.createIncidentRef)();
91
+ logger.error({
92
+ command: interaction.commandName,
93
+ userId: interaction.user.id,
94
+ ref,
95
+ err: error instanceof Error ? error.message : String(error),
96
+ stack: error instanceof Error ? error.stack : undefined,
97
+ }, "Context menu handler failed");
98
+ await send(interaction, presenter.systemError(translator.resolve(locale, def.fallbackError ?? { key: i18n_1.CORE_MESSAGES.genericError }), ref, locale), ephemeral, logger);
99
+ }
100
+ }
101
+ /**
102
+ * Deliver an embed whatever the interaction's lifecycle state, and never throw:
103
+ * a failed delivery (e.g. an expired interaction) must not escape into the
104
+ * client's unhandled `error` event. Mirrors the command pipeline's `sendEmbed`,
105
+ * which is typed for chat-input interactions only.
106
+ */
107
+ async function send(interaction, embed, ephemeral, logger) {
108
+ const flags = ephemeral ? discord_js_1.MessageFlags.Ephemeral : undefined;
109
+ try {
110
+ if (interaction.deferred) {
111
+ await interaction.editReply({ embeds: [embed] });
112
+ return;
113
+ }
114
+ if (interaction.replied) {
115
+ await interaction.followUp({ embeds: [embed], flags });
116
+ return;
117
+ }
118
+ await interaction.reply({ embeds: [embed], flags });
119
+ }
120
+ catch (error) {
121
+ logger.error({
122
+ commandName: interaction.commandName,
123
+ interactionId: interaction.id,
124
+ err: error instanceof Error ? error.message : String(error),
125
+ }, "Failed to deliver interaction response");
126
+ }
127
+ }
128
+ //# sourceMappingURL=create-context-menu-command.js.map