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