@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.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/authz/authorizer.d.ts +45 -0
- package/dist/authz/authorizer.d.ts.map +1 -0
- package/dist/authz/authorizer.js +31 -0
- package/dist/authz/authorizer.js.map +1 -0
- package/dist/authz/require-level-guard.d.ts +23 -0
- package/dist/authz/require-level-guard.d.ts.map +1 -0
- package/dist/authz/require-level-guard.js +37 -0
- package/dist/authz/require-level-guard.js.map +1 -0
- package/dist/clock.d.ts +13 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +13 -0
- package/dist/clock.js.map +1 -0
- package/dist/concurrency/keyed-queue.d.ts +18 -0
- package/dist/concurrency/keyed-queue.d.ts.map +1 -0
- package/dist/concurrency/keyed-queue.js +37 -0
- package/dist/concurrency/keyed-queue.js.map +1 -0
- package/dist/concurrency/run-with-concurrency.d.ts +15 -0
- package/dist/concurrency/run-with-concurrency.d.ts.map +1 -0
- package/dist/concurrency/run-with-concurrency.js +40 -0
- package/dist/concurrency/run-with-concurrency.js.map +1 -0
- package/dist/config/env.d.ts +17 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +49 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/errors.d.ts +14 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +25 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/datetime.d.ts +34 -0
- package/dist/datetime.d.ts.map +1 -0
- package/dist/datetime.js +188 -0
- package/dist/datetime.js.map +1 -0
- package/dist/discord/api-errors.d.ts +20 -0
- package/dist/discord/api-errors.d.ts.map +1 -0
- package/dist/discord/api-errors.js +90 -0
- package/dist/discord/api-errors.js.map +1 -0
- package/dist/discord/channel-exporter.d.ts +42 -0
- package/dist/discord/channel-exporter.d.ts.map +1 -0
- package/dist/discord/channel-exporter.js +3 -0
- package/dist/discord/channel-exporter.js.map +1 -0
- package/dist/discord/command/autocomplete-dispatcher.d.ts +26 -0
- package/dist/discord/command/autocomplete-dispatcher.d.ts.map +1 -0
- package/dist/discord/command/autocomplete-dispatcher.js +41 -0
- package/dist/discord/command/autocomplete-dispatcher.js.map +1 -0
- package/dist/discord/command/autocomplete.d.ts +18 -0
- package/dist/discord/command/autocomplete.d.ts.map +1 -0
- package/dist/discord/command/autocomplete.js +65 -0
- package/dist/discord/command/autocomplete.js.map +1 -0
- package/dist/discord/command/context.d.ts +33 -0
- package/dist/discord/command/context.d.ts.map +1 -0
- package/dist/discord/command/context.js +48 -0
- package/dist/discord/command/context.js.map +1 -0
- package/dist/discord/command/cooldown-guard.d.ts +25 -0
- package/dist/discord/command/cooldown-guard.d.ts.map +1 -0
- package/dist/discord/command/cooldown-guard.js +60 -0
- package/dist/discord/command/cooldown-guard.js.map +1 -0
- package/dist/discord/command/create-command.d.ts +100 -0
- package/dist/discord/command/create-command.d.ts.map +1 -0
- package/dist/discord/command/create-command.js +245 -0
- package/dist/discord/command/create-command.js.map +1 -0
- package/dist/discord/command/create-context-menu-command.d.ts +51 -0
- package/dist/discord/command/create-context-menu-command.d.ts.map +1 -0
- package/dist/discord/command/create-context-menu-command.js +128 -0
- package/dist/discord/command/create-context-menu-command.js.map +1 -0
- package/dist/discord/command/errors.d.ts +19 -0
- package/dist/discord/command/errors.d.ts.map +1 -0
- package/dist/discord/command/errors.js +27 -0
- package/dist/discord/command/errors.js.map +1 -0
- package/dist/discord/command/guard.d.ts +30 -0
- package/dist/discord/command/guard.d.ts.map +1 -0
- package/dist/discord/command/guard.js +34 -0
- package/dist/discord/command/guard.js.map +1 -0
- package/dist/discord/command/localization.d.ts +9 -0
- package/dist/discord/command/localization.d.ts.map +1 -0
- package/dist/discord/command/localization.js +13 -0
- package/dist/discord/command/localization.js.map +1 -0
- package/dist/discord/command/options.d.ts +113 -0
- package/dist/discord/command/options.d.ts.map +1 -0
- package/dist/discord/command/options.js +509 -0
- package/dist/discord/command/options.js.map +1 -0
- package/dist/discord/command/permission-guard.d.ts +21 -0
- package/dist/discord/command/permission-guard.d.ts.map +1 -0
- package/dist/discord/command/permission-guard.js +69 -0
- package/dist/discord/command/permission-guard.js.map +1 -0
- package/dist/discord/command/router.d.ts +48 -0
- package/dist/discord/command/router.d.ts.map +1 -0
- package/dist/discord/command/router.js +118 -0
- package/dist/discord/command/router.js.map +1 -0
- package/dist/discord/command/types.d.ts +51 -0
- package/dist/discord/command/types.d.ts.map +1 -0
- package/dist/discord/command/types.js +3 -0
- package/dist/discord/command/types.js.map +1 -0
- package/dist/discord/components/component-router.d.ts +116 -0
- package/dist/discord/components/component-router.d.ts.map +1 -0
- package/dist/discord/components/component-router.js +123 -0
- package/dist/discord/components/component-router.js.map +1 -0
- package/dist/discord/components/interactive-message/index.d.ts +3 -0
- package/dist/discord/components/interactive-message/index.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/index.js +11 -0
- package/dist/discord/components/interactive-message/index.js.map +1 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts +195 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.js +151 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.js.map +1 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts +30 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.js +44 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.js.map +1 -0
- package/dist/discord/components/paginator/index.d.ts +5 -0
- package/dist/discord/components/paginator/index.d.ts.map +1 -0
- package/dist/discord/components/paginator/index.js +15 -0
- package/dist/discord/components/paginator/index.js.map +1 -0
- package/dist/discord/components/paginator/mount-paginator.d.ts +25 -0
- package/dist/discord/components/paginator/mount-paginator.d.ts.map +1 -0
- package/dist/discord/components/paginator/mount-paginator.js +41 -0
- package/dist/discord/components/paginator/mount-paginator.js.map +1 -0
- package/dist/discord/components/paginator/page-state.d.ts +21 -0
- package/dist/discord/components/paginator/page-state.d.ts.map +1 -0
- package/dist/discord/components/paginator/page-state.js +44 -0
- package/dist/discord/components/paginator/page-state.js.map +1 -0
- package/dist/discord/components/paginator/paginator-buttons.d.ts +17 -0
- package/dist/discord/components/paginator/paginator-buttons.d.ts.map +1 -0
- package/dist/discord/components/paginator/paginator-buttons.js +45 -0
- package/dist/discord/components/paginator/paginator-buttons.js.map +1 -0
- package/dist/discord/components/paginator/paginator-view.d.ts +26 -0
- package/dist/discord/components/paginator/paginator-view.d.ts.map +1 -0
- package/dist/discord/components/paginator/paginator-view.js +28 -0
- package/dist/discord/components/paginator/paginator-view.js.map +1 -0
- package/dist/discord/components/persistent-paginator.d.ts +47 -0
- package/dist/discord/components/persistent-paginator.d.ts.map +1 -0
- package/dist/discord/components/persistent-paginator.js +137 -0
- package/dist/discord/components/persistent-paginator.js.map +1 -0
- package/dist/discord/components/settings-editor/index.d.ts +7 -0
- package/dist/discord/components/settings-editor/index.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/index.js +22 -0
- package/dist/discord/components/settings-editor/index.js.map +1 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts +140 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.js +344 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.js.map +1 -0
- package/dist/discord/components/settings-editor/navigation.d.ts +49 -0
- package/dist/discord/components/settings-editor/navigation.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/navigation.js +39 -0
- package/dist/discord/components/settings-editor/navigation.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts +56 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.js +284 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts +69 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js +353 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts +216 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.js +66 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts +303 -0
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor.view.js +316 -0
- package/dist/discord/components/settings-editor/settings-editor.view.js.map +1 -0
- package/dist/discord/events/create-event.d.ts +5 -0
- package/dist/discord/events/create-event.d.ts.map +1 -0
- package/dist/discord/events/create-event.js +8 -0
- package/dist/discord/events/create-event.js.map +1 -0
- package/dist/discord/events/event-router.d.ts +16 -0
- package/dist/discord/events/event-router.d.ts.map +1 -0
- package/dist/discord/events/event-router.js +51 -0
- package/dist/discord/events/event-router.js.map +1 -0
- package/dist/discord/events/types.d.ts +16 -0
- package/dist/discord/events/types.d.ts.map +1 -0
- package/dist/discord/events/types.js +3 -0
- package/dist/discord/events/types.js.map +1 -0
- package/dist/discord/i18n.d.ts +58 -0
- package/dist/discord/i18n.d.ts.map +1 -0
- package/dist/discord/i18n.js +173 -0
- package/dist/discord/i18n.js.map +1 -0
- package/dist/discord/interaction/button.d.ts +39 -0
- package/dist/discord/interaction/button.d.ts.map +1 -0
- package/dist/discord/interaction/button.js +36 -0
- package/dist/discord/interaction/button.js.map +1 -0
- package/dist/discord/interaction/interaction-router.d.ts +21 -0
- package/dist/discord/interaction/interaction-router.d.ts.map +1 -0
- package/dist/discord/interaction/interaction-router.js +24 -0
- package/dist/discord/interaction/interaction-router.js.map +1 -0
- package/dist/discord/interaction/modal.d.ts +171 -0
- package/dist/discord/interaction/modal.d.ts.map +1 -0
- package/dist/discord/interaction/modal.js +264 -0
- package/dist/discord/interaction/modal.js.map +1 -0
- package/dist/discord/interaction/select-menu.d.ts +104 -0
- package/dist/discord/interaction/select-menu.d.ts.map +1 -0
- package/dist/discord/interaction/select-menu.js +126 -0
- package/dist/discord/interaction/select-menu.js.map +1 -0
- package/dist/discord/permissions.d.ts +32 -0
- package/dist/discord/permissions.d.ts.map +1 -0
- package/dist/discord/permissions.js +40 -0
- package/dist/discord/permissions.js.map +1 -0
- package/dist/discord/placeholder-registry-errors.d.ts +10 -0
- package/dist/discord/placeholder-registry-errors.d.ts.map +1 -0
- package/dist/discord/placeholder-registry-errors.js +18 -0
- package/dist/discord/placeholder-registry-errors.js.map +1 -0
- package/dist/discord/placeholder-registry.d.ts +53 -0
- package/dist/discord/placeholder-registry.d.ts.map +1 -0
- package/dist/discord/placeholder-registry.js +52 -0
- package/dist/discord/placeholder-registry.js.map +1 -0
- package/dist/discord/presenter.d.ts +26 -0
- package/dist/discord/presenter.d.ts.map +1 -0
- package/dist/discord/presenter.js +3 -0
- package/dist/discord/presenter.js.map +1 -0
- package/dist/discord/ui/card.d.ts +114 -0
- package/dist/discord/ui/card.d.ts.map +1 -0
- package/dist/discord/ui/card.js +162 -0
- package/dist/discord/ui/card.js.map +1 -0
- package/dist/discord/ui/color-input-errors.d.ts +12 -0
- package/dist/discord/ui/color-input-errors.d.ts.map +1 -0
- package/dist/discord/ui/color-input-errors.js +21 -0
- package/dist/discord/ui/color-input-errors.js.map +1 -0
- package/dist/discord/ui/color-input.d.ts +37 -0
- package/dist/discord/ui/color-input.d.ts.map +1 -0
- package/dist/discord/ui/color-input.js +166 -0
- package/dist/discord/ui/color-input.js.map +1 -0
- package/dist/discord/ui/colors.d.ts +44 -0
- package/dist/discord/ui/colors.d.ts.map +1 -0
- package/dist/discord/ui/colors.js +45 -0
- package/dist/discord/ui/colors.js.map +1 -0
- package/dist/discord/ui/embed.d.ts +16 -0
- package/dist/discord/ui/embed.d.ts.map +1 -0
- package/dist/discord/ui/embed.js +53 -0
- package/dist/discord/ui/embed.js.map +1 -0
- package/dist/discord/ui/message-attachments.d.ts +40 -0
- package/dist/discord/ui/message-attachments.d.ts.map +1 -0
- package/dist/discord/ui/message-attachments.js +27 -0
- package/dist/discord/ui/message-attachments.js.map +1 -0
- package/dist/errors/business-error.d.ts +41 -0
- package/dist/errors/business-error.d.ts.map +1 -0
- package/dist/errors/business-error.js +39 -0
- package/dist/errors/business-error.js.map +1 -0
- package/dist/errors/describe-error.d.ts +29 -0
- package/dist/errors/describe-error.d.ts.map +1 -0
- package/dist/errors/describe-error.js +49 -0
- package/dist/errors/describe-error.js.map +1 -0
- package/dist/errors/incident-ref.d.ts +7 -0
- package/dist/errors/incident-ref.d.ts.map +1 -0
- package/dist/errors/incident-ref.js +13 -0
- package/dist/errors/incident-ref.js.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +15 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/i18n/business-message.d.ts +14 -0
- package/dist/i18n/business-message.d.ts.map +1 -0
- package/dist/i18n/business-message.js +19 -0
- package/dist/i18n/business-message.js.map +1 -0
- package/dist/i18n/catalog.d.ts +27 -0
- package/dist/i18n/catalog.d.ts.map +1 -0
- package/dist/i18n/catalog.js +25 -0
- package/dist/i18n/catalog.js.map +1 -0
- package/dist/i18n/errors.d.ts +10 -0
- package/dist/i18n/errors.d.ts.map +1 -0
- package/dist/i18n/errors.js +18 -0
- package/dist/i18n/errors.js.map +1 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +16 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/locale.d.ts +22 -0
- package/dist/i18n/locale.d.ts.map +1 -0
- package/dist/i18n/locale.js +40 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/translator.d.ts +35 -0
- package/dist/i18n/translator.d.ts.map +1 -0
- package/dist/i18n/translator.js +40 -0
- package/dist/i18n/translator.js.map +1 -0
- package/dist/logger.d.ts +28 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -0
- package/dist/module/module.d.ts +42 -0
- package/dist/module/module.d.ts.map +1 -0
- package/dist/module/module.js +3 -0
- package/dist/module/module.js.map +1 -0
- package/dist/persistence/database.d.ts +24 -0
- package/dist/persistence/database.d.ts.map +1 -0
- package/dist/persistence/database.js +3 -0
- package/dist/persistence/database.js.map +1 -0
- package/dist/resilience/retry.d.ts +36 -0
- package/dist/resilience/retry.d.ts.map +1 -0
- package/dist/resilience/retry.js +78 -0
- package/dist/resilience/retry.js.map +1 -0
- package/dist/scheduler/errors.d.ts +25 -0
- package/dist/scheduler/errors.d.ts.map +1 -0
- package/dist/scheduler/errors.js +45 -0
- package/dist/scheduler/errors.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +68 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +162 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/package.json +89 -0
- package/src/authz/authorizer.ts +76 -0
- package/src/authz/require-level-guard.ts +42 -0
- package/src/clock.ts +18 -0
- package/src/concurrency/keyed-queue.ts +50 -0
- package/src/concurrency/run-with-concurrency.ts +44 -0
- package/src/config/env.ts +58 -0
- package/src/config/errors.ts +21 -0
- package/src/datetime.ts +221 -0
- package/src/discord/api-errors.ts +91 -0
- package/src/discord/channel-exporter.ts +48 -0
- package/src/discord/command/autocomplete-dispatcher.ts +54 -0
- package/src/discord/command/autocomplete.ts +76 -0
- package/src/discord/command/context.ts +85 -0
- package/src/discord/command/cooldown-guard.ts +81 -0
- package/src/discord/command/create-command.ts +431 -0
- package/src/discord/command/create-context-menu-command.ts +227 -0
- package/src/discord/command/errors.ts +22 -0
- package/src/discord/command/guard.ts +54 -0
- package/src/discord/command/localization.ts +11 -0
- package/src/discord/command/options.ts +847 -0
- package/src/discord/command/permission-guard.ts +74 -0
- package/src/discord/command/router.ts +142 -0
- package/src/discord/command/types.ts +52 -0
- package/src/discord/components/component-router.ts +213 -0
- package/src/discord/components/interactive-message/index.ts +18 -0
- package/src/discord/components/interactive-message/interactive-message-collector.ts +374 -0
- package/src/discord/components/interactive-message/mount-interactive-message.ts +71 -0
- package/src/discord/components/paginator/index.ts +17 -0
- package/src/discord/components/paginator/mount-paginator.ts +69 -0
- package/src/discord/components/paginator/page-state.ts +45 -0
- package/src/discord/components/paginator/paginator-buttons.ts +60 -0
- package/src/discord/components/paginator/paginator-view.ts +61 -0
- package/src/discord/components/persistent-paginator.ts +197 -0
- package/src/discord/components/settings-editor/index.ts +41 -0
- package/src/discord/components/settings-editor/mount-settings-editor.ts +646 -0
- package/src/discord/components/settings-editor/navigation.ts +67 -0
- package/src/discord/components/settings-editor/settings-editor-card.view.ts +344 -0
- package/src/discord/components/settings-editor/settings-editor-field-modal.ts +538 -0
- package/src/discord/components/settings-editor/settings-editor-fields.ts +290 -0
- package/src/discord/components/settings-editor/settings-editor.view.ts +654 -0
- package/src/discord/events/create-event.ts +7 -0
- package/src/discord/events/event-router.ts +53 -0
- package/src/discord/events/types.ts +17 -0
- package/src/discord/i18n.ts +173 -0
- package/src/discord/interaction/button.ts +75 -0
- package/src/discord/interaction/interaction-router.ts +28 -0
- package/src/discord/interaction/modal.ts +505 -0
- package/src/discord/interaction/select-menu.ts +246 -0
- package/src/discord/permissions.ts +57 -0
- package/src/discord/placeholder-registry-errors.ts +11 -0
- package/src/discord/placeholder-registry.ts +77 -0
- package/src/discord/presenter.ts +26 -0
- package/src/discord/ui/card.ts +303 -0
- package/src/discord/ui/color-input-errors.ts +17 -0
- package/src/discord/ui/color-input.ts +179 -0
- package/src/discord/ui/colors.ts +58 -0
- package/src/discord/ui/embed.ts +64 -0
- package/src/discord/ui/message-attachments.ts +57 -0
- package/src/errors/business-error.ts +49 -0
- package/src/errors/describe-error.ts +73 -0
- package/src/errors/incident-ref.ts +10 -0
- package/src/errors/index.ts +12 -0
- package/src/i18n/business-message.ts +23 -0
- package/src/i18n/catalog.ts +41 -0
- package/src/i18n/errors.ts +11 -0
- package/src/i18n/index.ts +11 -0
- package/src/i18n/locale.ts +41 -0
- package/src/i18n/translator.ts +78 -0
- package/src/logger.ts +28 -0
- package/src/module/module.ts +42 -0
- package/src/persistence/database.ts +23 -0
- package/src/resilience/retry.ts +102 -0
- package/src/scheduler/errors.ts +40 -0
- 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";
|