@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
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
import type { Client } from "discord.js";
|
|
2
|
+
import { eventGuildId } from "@/discord/events/event-guild-id";
|
|
2
3
|
import type { DiscordEvent, EventListener } from "@/discord/events/types";
|
|
4
|
+
import { isModuleDisabled } from "@/discord/settings/is-module-disabled";
|
|
5
|
+
import { errorMessage } from "@/errors/error-message";
|
|
3
6
|
import type { Logger } from "@/logger";
|
|
7
|
+
import type { ModuleGate } from "@/settings/system/module-gate";
|
|
8
|
+
|
|
9
|
+
/** A registered event and the module it belongs to, if any. */
|
|
10
|
+
interface RegisteredEvent {
|
|
11
|
+
readonly event: DiscordEvent;
|
|
12
|
+
readonly moduleName: string | undefined;
|
|
13
|
+
}
|
|
4
14
|
|
|
5
15
|
/**
|
|
6
16
|
* Wrap a handler so a thrown error is logged instead of escaping to the
|
|
7
17
|
* process-level `error` event: a failing handler must never take the bot down.
|
|
18
|
+
* A handler of a module disabled on the event's guild is skipped silently.
|
|
8
19
|
*/
|
|
9
|
-
function createEventListener(
|
|
20
|
+
function createEventListener(params: {
|
|
21
|
+
registered: RegisteredEvent;
|
|
22
|
+
logger: Logger;
|
|
23
|
+
gate: ModuleGate | undefined;
|
|
24
|
+
}): EventListener {
|
|
25
|
+
const { registered, logger, gate } = params;
|
|
26
|
+
const { event, moduleName } = registered;
|
|
10
27
|
return async (...args) => {
|
|
11
28
|
try {
|
|
29
|
+
if (await isModuleDisabled({ gate, moduleName, guildId: eventGuildId(args) })) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
12
32
|
await event.execute(...args);
|
|
13
33
|
} catch (error) {
|
|
14
|
-
logger.error(
|
|
15
|
-
{ event: event.name, err: error instanceof Error ? error.message : String(error) },
|
|
16
|
-
"Event handler failed",
|
|
17
|
-
);
|
|
34
|
+
logger.error({ event: event.name, err: errorMessage(error) }, "Event handler failed");
|
|
18
35
|
}
|
|
19
36
|
};
|
|
20
37
|
}
|
|
@@ -24,25 +41,41 @@ function createEventListener(event: DiscordEvent, logger: Logger): EventListener
|
|
|
24
41
|
* the app that calls `client.on`/`client.once`.
|
|
25
42
|
*/
|
|
26
43
|
export class EventRouter {
|
|
27
|
-
private readonly events:
|
|
44
|
+
private readonly events: RegisteredEvent[] = [];
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
/**
|
|
47
|
+
* @param logger - where a failing handler is reported.
|
|
48
|
+
* @param gate - when given, a handler registered with a module is skipped,
|
|
49
|
+
* silently, for a guild where that module is disabled (FR-036). Events bound
|
|
50
|
+
* to no guild always run.
|
|
51
|
+
*/
|
|
52
|
+
constructor(
|
|
53
|
+
private readonly logger: Logger,
|
|
54
|
+
private readonly gate?: ModuleGate,
|
|
55
|
+
) {}
|
|
30
56
|
|
|
31
|
-
|
|
32
|
-
|
|
57
|
+
/**
|
|
58
|
+
* @param moduleName - the module the handler belongs to, so the gate can
|
|
59
|
+
* skip it for guilds where that module is disabled. Omit it for a handler
|
|
60
|
+
* that always runs.
|
|
61
|
+
*/
|
|
62
|
+
register(event: DiscordEvent, moduleName?: string): this {
|
|
63
|
+
this.events.push({ event, moduleName });
|
|
33
64
|
return this;
|
|
34
65
|
}
|
|
35
66
|
|
|
36
|
-
|
|
67
|
+
/** Register many handlers at once, all of `moduleName` when given. */
|
|
68
|
+
registerAll(events: Iterable<DiscordEvent>, moduleName?: string): this {
|
|
37
69
|
for (const event of events) {
|
|
38
|
-
this.register(event);
|
|
70
|
+
this.register(event, moduleName);
|
|
39
71
|
}
|
|
40
72
|
return this;
|
|
41
73
|
}
|
|
42
74
|
|
|
43
75
|
bind(client: Client): void {
|
|
44
|
-
for (const
|
|
45
|
-
const
|
|
76
|
+
for (const registered of this.events) {
|
|
77
|
+
const { event } = registered;
|
|
78
|
+
const listener = createEventListener({ registered, logger: this.logger, gate: this.gate });
|
|
46
79
|
if (event.once) {
|
|
47
80
|
client.once(event.name, listener);
|
|
48
81
|
} else {
|
package/src/discord/i18n.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { KeyedCatalog, MessageKey } from "@/i18n/catalog";
|
|
2
2
|
|
|
3
3
|
/** Catalog keys for the framework's own feedback, under the `core.` namespace. */
|
|
4
4
|
export const CORE_MESSAGES = {
|
|
@@ -50,6 +50,21 @@ export const SETTINGS_EDITOR_MESSAGES = {
|
|
|
50
50
|
*/
|
|
51
51
|
editFieldButton: "core.settings-editor.edit-field-button",
|
|
52
52
|
openLevelButton: "core.settings-editor.open-level-button",
|
|
53
|
+
/** The line under an entry whose declaration describes it no further. */
|
|
54
|
+
entryHint: "core.settings-editor.entry-hint",
|
|
55
|
+
/** A card entry's current value, under its hint. Takes `{value}`. */
|
|
56
|
+
currentValue: "core.settings-editor.current-value",
|
|
57
|
+
/** The entry a screen with more entries than one card holds moves the rest behind. */
|
|
58
|
+
moreEntries: "core.settings-editor.more-entries",
|
|
59
|
+
moreEntriesHint: "core.settings-editor.more-entries-hint",
|
|
60
|
+
/**
|
|
61
|
+
* An option whose text is not a catalog key: a search result, already in
|
|
62
|
+
* the reader's language. Takes `{label}` and shows it as is.
|
|
63
|
+
*/
|
|
64
|
+
choiceLabel: "core.settings-editor.choice-label",
|
|
65
|
+
/** The free entry beside a searchable setting's list, for a value the list does not offer. */
|
|
66
|
+
otherValue: "core.settings-editor.other-value",
|
|
67
|
+
otherValueHelper: "core.settings-editor.other-value-helper",
|
|
53
68
|
} as const;
|
|
54
69
|
|
|
55
70
|
/**
|
|
@@ -57,7 +72,9 @@ export const SETTINGS_EDITOR_MESSAGES = {
|
|
|
57
72
|
* fallbacks and the default cooldown notice. Registered by the composition root
|
|
58
73
|
* before any module catalog.
|
|
59
74
|
*/
|
|
60
|
-
export const CORE_CATALOG:
|
|
75
|
+
export const CORE_CATALOG: KeyedCatalog<
|
|
76
|
+
MessageKey<typeof CORE_MESSAGES> | MessageKey<typeof SETTINGS_EDITOR_MESSAGES>
|
|
77
|
+
> = {
|
|
61
78
|
[CORE_MESSAGES.presenterSuccessTitle]: { en: "Success", fr: "Succès" },
|
|
62
79
|
[CORE_MESSAGES.presenterWarningTitle]: { en: "Warning", fr: "Avertissement" },
|
|
63
80
|
[CORE_MESSAGES.presenterErrorTitle]: { en: "Error", fr: "Erreur" },
|
|
@@ -96,7 +113,7 @@ export const CORE_CATALOG: Catalog = {
|
|
|
96
113
|
},
|
|
97
114
|
[SETTINGS_EDITOR_MESSAGES.modalHelper]: {
|
|
98
115
|
en: "Leave empty to clear your customisation and go back to the default.",
|
|
99
|
-
fr: "
|
|
116
|
+
fr: "Laissez vide pour effacer votre personnalisation et revenir à la valeur par défaut.",
|
|
100
117
|
},
|
|
101
118
|
[SETTINGS_EDITOR_MESSAGES.modalUploadLabel]: {
|
|
102
119
|
en: "Upload an image",
|
|
@@ -112,11 +129,11 @@ export const CORE_CATALOG: Catalog = {
|
|
|
112
129
|
},
|
|
113
130
|
[SETTINGS_EDITOR_MESSAGES.modalImageHelper]: {
|
|
114
131
|
en: "An http(s) address. Leave both empty to restore the default.",
|
|
115
|
-
fr: "Une adresse http(s).
|
|
132
|
+
fr: "Une adresse http(s). Laissez les deux vides pour revenir au défaut.",
|
|
116
133
|
},
|
|
117
134
|
[SETTINGS_EDITOR_MESSAGES.modalImageHelperUploaded]: {
|
|
118
135
|
en: "An image is uploaded; it cannot be shown here. Empty both to restore the default.",
|
|
119
|
-
fr: "Une image est envoyée ; impossible de la réafficher.
|
|
136
|
+
fr: "Une image est envoyée ; impossible de la réafficher. Videz les deux pour le défaut.",
|
|
120
137
|
},
|
|
121
138
|
[SETTINGS_EDITOR_MESSAGES.fieldSaved]: {
|
|
122
139
|
en: "✅ {field} updated.",
|
|
@@ -128,7 +145,7 @@ export const CORE_CATALOG: Catalog = {
|
|
|
128
145
|
},
|
|
129
146
|
[SETTINGS_EDITOR_MESSAGES.fieldUploaded]: {
|
|
130
147
|
en: "✅ {field} updated with your upload.",
|
|
131
|
-
fr: "✅ {field} mis à jour avec
|
|
148
|
+
fr: "✅ {field} mis à jour avec votre envoi.",
|
|
132
149
|
},
|
|
133
150
|
[SETTINGS_EDITOR_MESSAGES.resetButton]: {
|
|
134
151
|
en: "Reset everything",
|
|
@@ -140,7 +157,7 @@ export const CORE_CATALOG: Catalog = {
|
|
|
140
157
|
},
|
|
141
158
|
[SETTINGS_EDITOR_MESSAGES.resetConfirmDescription]: {
|
|
142
159
|
en: "Every field goes back to its default. An uploaded image is not merely unset — its file is deleted for good and cannot be recovered. The preview below is what you are about to lose.",
|
|
143
|
-
fr: "Chaque champ revient à sa valeur par défaut. Une image envoyée n'est pas simplement retirée : le fichier est supprimé définitivement et ne pourra pas être récupéré. L'aperçu ci-dessous est ce que
|
|
160
|
+
fr: "Chaque champ revient à sa valeur par défaut. Une image envoyée n'est pas simplement retirée : le fichier est supprimé définitivement et ne pourra pas être récupéré. L'aperçu ci-dessous est ce que vous êtes sur le point de perdre.",
|
|
144
161
|
},
|
|
145
162
|
[SETTINGS_EDITOR_MESSAGES.resetConfirmButton]: {
|
|
146
163
|
en: "Reset for good",
|
|
@@ -156,7 +173,7 @@ export const CORE_CATALOG: Catalog = {
|
|
|
156
173
|
},
|
|
157
174
|
[SETTINGS_EDITOR_MESSAGES.resetCancelled]: {
|
|
158
175
|
en: "Nothing was reset. Your customisation is untouched.",
|
|
159
|
-
fr: "Rien n'a été réinitialisé.
|
|
176
|
+
fr: "Rien n'a été réinitialisé. Votre personnalisation est intacte.",
|
|
160
177
|
},
|
|
161
178
|
[SETTINGS_EDITOR_MESSAGES.backButton]: {
|
|
162
179
|
en: "Back",
|
|
@@ -170,4 +187,26 @@ export const CORE_CATALOG: Catalog = {
|
|
|
170
187
|
en: "Open",
|
|
171
188
|
fr: "Ouvrir",
|
|
172
189
|
},
|
|
190
|
+
[SETTINGS_EDITOR_MESSAGES.entryHint]: {
|
|
191
|
+
en: "Edit this setting.",
|
|
192
|
+
fr: "Modifiez ce paramètre.",
|
|
193
|
+
},
|
|
194
|
+
[SETTINGS_EDITOR_MESSAGES.currentValue]: {
|
|
195
|
+
en: "Current: {value}",
|
|
196
|
+
fr: "Actuel : {value}",
|
|
197
|
+
},
|
|
198
|
+
[SETTINGS_EDITOR_MESSAGES.moreEntries]: {
|
|
199
|
+
en: "More settings",
|
|
200
|
+
fr: "Plus de paramètres",
|
|
201
|
+
},
|
|
202
|
+
[SETTINGS_EDITOR_MESSAGES.moreEntriesHint]: {
|
|
203
|
+
en: "The settings that do not fit on this page.",
|
|
204
|
+
fr: "Les paramètres qui ne tiennent pas sur cette page.",
|
|
205
|
+
},
|
|
206
|
+
[SETTINGS_EDITOR_MESSAGES.choiceLabel]: { en: "{label}", fr: "{label}" },
|
|
207
|
+
[SETTINGS_EDITOR_MESSAGES.otherValue]: { en: "Other value", fr: "Autre valeur" },
|
|
208
|
+
[SETTINGS_EDITOR_MESSAGES.otherValueHelper]: {
|
|
209
|
+
en: "A value the list above does not offer; it replaces the pick. Leave empty to keep the pick.",
|
|
210
|
+
fr: "Une valeur absente de la liste ci-dessus ; elle remplace le choix. Laissez vide pour garder le choix.",
|
|
211
|
+
},
|
|
173
212
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Locale, resolveLocale } from "@/i18n/locale";
|
|
2
|
+
import type { Translator } from "@/i18n/translator";
|
|
3
|
+
|
|
4
|
+
/** The two locales Discord attaches to every interaction. */
|
|
5
|
+
interface LocalizedInteraction {
|
|
6
|
+
readonly locale: string;
|
|
7
|
+
readonly guildLocale: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The reply language for one interaction: the member's client locale, then the
|
|
12
|
+
* guild's, then the translator's configured default.
|
|
13
|
+
*/
|
|
14
|
+
export function interactionLocale(
|
|
15
|
+
interaction: LocalizedInteraction,
|
|
16
|
+
translator: Translator,
|
|
17
|
+
): Locale {
|
|
18
|
+
return resolveLocale([interaction.locale, interaction.guildLocale], translator.defaultLocale);
|
|
19
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Locale } from "@/i18n/locale";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a {@link LocaleResolver} knows of the interaction it answers: the
|
|
5
|
+
* member's client locale, and the guild's id and Discord locale (`null` outside
|
|
6
|
+
* a guild). Every discord.js interaction fits as is.
|
|
7
|
+
*/
|
|
8
|
+
export interface LocaleSubject {
|
|
9
|
+
readonly locale: string;
|
|
10
|
+
readonly guildLocale: string | null;
|
|
11
|
+
readonly guildId: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The port the kernel reads the reply language through, on every path that
|
|
16
|
+
* answers an interaction (commands, guards, failures, components). The bot
|
|
17
|
+
* picks the implementation at composition: `createGuildLocaleResolver` for the
|
|
18
|
+
* guild's language setting (FR-038), or its own. Without one, the kernel uses
|
|
19
|
+
* the interaction's own locales (`interactionLocale`).
|
|
20
|
+
*/
|
|
21
|
+
export interface LocaleResolver {
|
|
22
|
+
/**
|
|
23
|
+
* @returns the language to reply in.
|
|
24
|
+
* @throws whatever the implementation's lookup throws; the kernel logs it
|
|
25
|
+
* and falls back to the interaction's own locales.
|
|
26
|
+
*/
|
|
27
|
+
resolve(subject: LocaleSubject): Promise<Locale>;
|
|
28
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
TextInputStyle,
|
|
15
15
|
UserSelectMenuBuilder,
|
|
16
16
|
} from "discord.js";
|
|
17
|
+
import { applySelectBounds } from "@/discord/interaction/select-bounds";
|
|
17
18
|
|
|
18
19
|
export type TextFieldStyleName = "short" | "paragraph";
|
|
19
20
|
|
|
@@ -264,16 +265,7 @@ function createStringSelect(name: string, field: SelectFieldDef): StringSelectMe
|
|
|
264
265
|
.setCustomId(name)
|
|
265
266
|
.setRequired(field.required ?? true)
|
|
266
267
|
.addOptions(field.options.map(createSelectOption));
|
|
267
|
-
|
|
268
|
-
select.setPlaceholder(field.placeholder);
|
|
269
|
-
}
|
|
270
|
-
if (field.minValues !== undefined) {
|
|
271
|
-
select.setMinValues(field.minValues);
|
|
272
|
-
}
|
|
273
|
-
if (field.maxValues !== undefined) {
|
|
274
|
-
select.setMaxValues(field.maxValues);
|
|
275
|
-
}
|
|
276
|
-
return select;
|
|
268
|
+
return applySelectBounds(select, field);
|
|
277
269
|
}
|
|
278
270
|
|
|
279
271
|
function createRoleSelect(name: string, field: RoleFieldDef): RoleSelectMenuBuilder {
|
|
@@ -281,16 +273,7 @@ function createRoleSelect(name: string, field: RoleFieldDef): RoleSelectMenuBuil
|
|
|
281
273
|
if (field.defaultRoleIds !== undefined) {
|
|
282
274
|
select.setDefaultRoles(...field.defaultRoleIds);
|
|
283
275
|
}
|
|
284
|
-
|
|
285
|
-
select.setPlaceholder(field.placeholder);
|
|
286
|
-
}
|
|
287
|
-
if (field.minValues !== undefined) {
|
|
288
|
-
select.setMinValues(field.minValues);
|
|
289
|
-
}
|
|
290
|
-
if (field.maxValues !== undefined) {
|
|
291
|
-
select.setMaxValues(field.maxValues);
|
|
292
|
-
}
|
|
293
|
-
return select;
|
|
276
|
+
return applySelectBounds(select, field);
|
|
294
277
|
}
|
|
295
278
|
|
|
296
279
|
function createUserSelect(name: string, field: UserFieldDef): UserSelectMenuBuilder {
|
|
@@ -298,16 +281,7 @@ function createUserSelect(name: string, field: UserFieldDef): UserSelectMenuBuil
|
|
|
298
281
|
if (field.defaultUserIds !== undefined) {
|
|
299
282
|
select.setDefaultUsers(...field.defaultUserIds);
|
|
300
283
|
}
|
|
301
|
-
|
|
302
|
-
select.setPlaceholder(field.placeholder);
|
|
303
|
-
}
|
|
304
|
-
if (field.minValues !== undefined) {
|
|
305
|
-
select.setMinValues(field.minValues);
|
|
306
|
-
}
|
|
307
|
-
if (field.maxValues !== undefined) {
|
|
308
|
-
select.setMaxValues(field.maxValues);
|
|
309
|
-
}
|
|
310
|
-
return select;
|
|
284
|
+
return applySelectBounds(select, field);
|
|
311
285
|
}
|
|
312
286
|
|
|
313
287
|
function createChannelSelect(name: string, field: ChannelFieldDef): ChannelSelectMenuBuilder {
|
|
@@ -320,16 +294,7 @@ function createChannelSelect(name: string, field: ChannelFieldDef): ChannelSelec
|
|
|
320
294
|
if (field.defaultChannelIds !== undefined) {
|
|
321
295
|
select.setDefaultChannels(...field.defaultChannelIds);
|
|
322
296
|
}
|
|
323
|
-
|
|
324
|
-
select.setPlaceholder(field.placeholder);
|
|
325
|
-
}
|
|
326
|
-
if (field.minValues !== undefined) {
|
|
327
|
-
select.setMinValues(field.minValues);
|
|
328
|
-
}
|
|
329
|
-
if (field.maxValues !== undefined) {
|
|
330
|
-
select.setMaxValues(field.maxValues);
|
|
331
|
-
}
|
|
332
|
-
return select;
|
|
297
|
+
return applySelectBounds(select, field);
|
|
333
298
|
}
|
|
334
299
|
|
|
335
300
|
function createLabel(name: string, field: FieldDef): LabelBuilder {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { interactionLocale } from "@/discord/interaction/interaction-locale";
|
|
2
|
+
import type { LocaleResolver, LocaleSubject } from "@/discord/interaction/locale-resolver";
|
|
3
|
+
import { errorMessage } from "@/errors/error-message";
|
|
4
|
+
import type { Locale } from "@/i18n/locale";
|
|
5
|
+
import type { Translator } from "@/i18n/translator";
|
|
6
|
+
import type { Logger } from "@/logger";
|
|
7
|
+
|
|
8
|
+
/** The dependencies every reply path already carries, plus the bot's optional resolver. */
|
|
9
|
+
export interface ReplyLocaleDeps {
|
|
10
|
+
readonly translator: Translator;
|
|
11
|
+
readonly logger: Logger;
|
|
12
|
+
readonly localeResolver?: LocaleResolver;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The language the kernel answers an interaction in: the bot's
|
|
17
|
+
* {@link LocaleResolver} when it gave one, otherwise the interaction's own
|
|
18
|
+
* locales. A resolver that fails is logged and the interaction's own locales
|
|
19
|
+
* are used, so a store outage never leaves a member without an answer.
|
|
20
|
+
*/
|
|
21
|
+
export async function replyLocale(subject: LocaleSubject, deps: ReplyLocaleDeps): Promise<Locale> {
|
|
22
|
+
const { translator, logger, localeResolver } = deps;
|
|
23
|
+
if (localeResolver === undefined) {
|
|
24
|
+
return interactionLocale(subject, translator);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return await localeResolver.resolve(subject);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
logger.error(
|
|
30
|
+
{ guildId: subject.guildId, err: errorMessage(error) },
|
|
31
|
+
"Could not resolve the reply language; using the interaction's locales",
|
|
32
|
+
);
|
|
33
|
+
return interactionLocale(subject, translator);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every select menu may declare, whatever it picks from and wherever it
|
|
3
|
+
* is shown (a message or a modal): its hint and how many picks it takes.
|
|
4
|
+
*/
|
|
5
|
+
export interface SelectBounds {
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
minValues?: number;
|
|
8
|
+
maxValues?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** The setters every discord.js select menu builder carries for {@link SelectBounds}. */
|
|
12
|
+
interface SelectBoundsBuilder {
|
|
13
|
+
setPlaceholder(placeholder: string): unknown;
|
|
14
|
+
setMinValues(minValues: number): unknown;
|
|
15
|
+
setMaxValues(maxValues: number): unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Apply the placeholder and pick-count bounds a select declared, leaving unset ones to Discord. */
|
|
19
|
+
export function applySelectBounds<B extends SelectBoundsBuilder>(
|
|
20
|
+
builder: B,
|
|
21
|
+
bounds: SelectBounds,
|
|
22
|
+
): B {
|
|
23
|
+
if (bounds.placeholder !== undefined) {
|
|
24
|
+
builder.setPlaceholder(bounds.placeholder);
|
|
25
|
+
}
|
|
26
|
+
if (bounds.minValues !== undefined) {
|
|
27
|
+
builder.setMinValues(bounds.minValues);
|
|
28
|
+
}
|
|
29
|
+
if (bounds.maxValues !== undefined) {
|
|
30
|
+
builder.setMaxValues(bounds.maxValues);
|
|
31
|
+
}
|
|
32
|
+
return builder;
|
|
33
|
+
}
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
ComponentAuthorization,
|
|
16
16
|
ComponentHandler,
|
|
17
17
|
} from "@/discord/components/component-router";
|
|
18
|
+
import { applySelectBounds } from "@/discord/interaction/select-bounds";
|
|
18
19
|
|
|
19
20
|
/** The builders this DSL can produce; the union an action row accepts. */
|
|
20
21
|
export type AnySelectMenuBuilder =
|
|
@@ -109,15 +110,7 @@ function applyBaseOptions<B extends AnySelectMenuBuilder>(
|
|
|
109
110
|
forceDisabled: boolean,
|
|
110
111
|
): B {
|
|
111
112
|
builder.setCustomId(def.id);
|
|
112
|
-
|
|
113
|
-
builder.setPlaceholder(def.placeholder);
|
|
114
|
-
}
|
|
115
|
-
if (def.minValues !== undefined) {
|
|
116
|
-
builder.setMinValues(def.minValues);
|
|
117
|
-
}
|
|
118
|
-
if (def.maxValues !== undefined) {
|
|
119
|
-
builder.setMaxValues(def.maxValues);
|
|
120
|
-
}
|
|
113
|
+
applySelectBounds(builder, def);
|
|
121
114
|
builder.setDisabled(forceDisabled || (def.disabled ?? false));
|
|
122
115
|
return builder;
|
|
123
116
|
}
|
package/src/discord/presenter.ts
CHANGED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { ChannelType, type Client, type Guild } from "discord.js";
|
|
2
|
+
import { isMissingAccess, isUnknownResource } from "@/discord/api-errors";
|
|
3
|
+
import { MAX_AUTOCOMPLETE_CHOICES } from "@/discord/command/autocomplete-limits";
|
|
4
|
+
import type { ChannelKind, GuildDirectory } from "@/settings/ports/guild-directory";
|
|
5
|
+
|
|
6
|
+
/** Every `ChannelType` a guild channel can have: all of them except the DM ones. */
|
|
7
|
+
type GuildChannelType = Exclude<ChannelType, ChannelType.DM | ChannelType.GroupDM>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Discord's guild channel types to the kernel's kinds. Both maps are checked
|
|
11
|
+
* for completeness, so a new member on either side is a compile error until
|
|
12
|
+
* it is mapped here.
|
|
13
|
+
*/
|
|
14
|
+
const KIND_BY_CHANNEL_TYPE = {
|
|
15
|
+
[ChannelType.GuildText]: "text",
|
|
16
|
+
[ChannelType.GuildVoice]: "voice",
|
|
17
|
+
[ChannelType.GuildCategory]: "category",
|
|
18
|
+
[ChannelType.GuildAnnouncement]: "announcement",
|
|
19
|
+
[ChannelType.AnnouncementThread]: "announcementThread",
|
|
20
|
+
[ChannelType.PublicThread]: "publicThread",
|
|
21
|
+
[ChannelType.PrivateThread]: "privateThread",
|
|
22
|
+
[ChannelType.GuildStageVoice]: "stage",
|
|
23
|
+
[ChannelType.GuildDirectory]: "directory",
|
|
24
|
+
[ChannelType.GuildForum]: "forum",
|
|
25
|
+
[ChannelType.GuildMedia]: "media",
|
|
26
|
+
} as const satisfies Record<GuildChannelType, ChannelKind>;
|
|
27
|
+
|
|
28
|
+
/** The kernel's channel kinds to Discord's guild channel types: the inverse of the map above. */
|
|
29
|
+
export const CHANNEL_TYPE_BY_KIND = {
|
|
30
|
+
text: ChannelType.GuildText,
|
|
31
|
+
voice: ChannelType.GuildVoice,
|
|
32
|
+
category: ChannelType.GuildCategory,
|
|
33
|
+
announcement: ChannelType.GuildAnnouncement,
|
|
34
|
+
announcementThread: ChannelType.AnnouncementThread,
|
|
35
|
+
publicThread: ChannelType.PublicThread,
|
|
36
|
+
privateThread: ChannelType.PrivateThread,
|
|
37
|
+
stage: ChannelType.GuildStageVoice,
|
|
38
|
+
directory: ChannelType.GuildDirectory,
|
|
39
|
+
forum: ChannelType.GuildForum,
|
|
40
|
+
media: ChannelType.GuildMedia,
|
|
41
|
+
} as const satisfies Record<ChannelKind, GuildChannelType>;
|
|
42
|
+
|
|
43
|
+
/** Discord caps autocomplete at this many choices, so no search returns more. */
|
|
44
|
+
const SEARCH_LIMIT = MAX_AUTOCOMPLETE_CHOICES;
|
|
45
|
+
|
|
46
|
+
interface NamedEntity {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly name: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface ChannelEntity extends NamedEntity {
|
|
52
|
+
readonly kind: ChannelKind;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The minimal shape of a guild channel this adapter reads. */
|
|
56
|
+
interface GuildChannelLike {
|
|
57
|
+
readonly id: string;
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly type: GuildChannelType;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The minimal shape of a guild member this adapter reads. */
|
|
63
|
+
interface GuildMemberLike {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
readonly displayName: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Resolve to `null` when Discord says the entity does not exist; rethrow anything else. */
|
|
69
|
+
async function orNullWhenUnknown<T>(lookup: Promise<T | null>): Promise<T | null> {
|
|
70
|
+
try {
|
|
71
|
+
return await lookup;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (isUnknownResource(error)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function toChannel(channel: GuildChannelLike): ChannelEntity {
|
|
81
|
+
return { id: channel.id, name: channel.name, kind: KIND_BY_CHANNEL_TYPE[channel.type] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function toMember(member: GuildMemberLike): NamedEntity {
|
|
85
|
+
return { id: member.id, name: member.displayName };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function nameStartsWith(params: { name: string; query: string }): boolean {
|
|
89
|
+
return params.name.toLowerCase().startsWith(params.query.toLowerCase());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Search `cached` first; only when nothing there matches, search what `fetch`
|
|
94
|
+
* returns. discord.js caches what it fetches, so the next lookup is local.
|
|
95
|
+
*/
|
|
96
|
+
async function searchCacheThenFetch<T>(params: {
|
|
97
|
+
cached: Iterable<T>;
|
|
98
|
+
fetch: () => Promise<Iterable<T>>;
|
|
99
|
+
matches: (entity: T) => boolean;
|
|
100
|
+
}): Promise<T[]> {
|
|
101
|
+
const { cached, fetch, matches } = params;
|
|
102
|
+
const hits = [...cached].filter(matches);
|
|
103
|
+
if (hits.length > 0) {
|
|
104
|
+
return hits.slice(0, SEARCH_LIMIT);
|
|
105
|
+
}
|
|
106
|
+
return [...(await fetch())].filter(matches).slice(0, SEARCH_LIMIT);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The guild from the client's cache, else from Discord; `null` when the bot cannot see it. */
|
|
110
|
+
async function findGuild(params: { client: Client; guildId: string }): Promise<Guild | null> {
|
|
111
|
+
const { client, guildId } = params;
|
|
112
|
+
const cached = client.guilds.cache.get(guildId);
|
|
113
|
+
if (cached) {
|
|
114
|
+
return cached;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return await client.guilds.fetch(guildId);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (isUnknownResource(error) || isMissingAccess(error)) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* {@link GuildDirectory} over a discord.js client. Every lookup reads the
|
|
128
|
+
* client's cache first and asks Discord only on a miss; an entity Discord does
|
|
129
|
+
* not know, or a guild the bot is not in, reads as absent. Search matches a
|
|
130
|
+
* case-insensitive name prefix, except the member search Discord runs itself
|
|
131
|
+
* on a cache miss, and returns at most 25 entries.
|
|
132
|
+
*
|
|
133
|
+
* @param client - the bot's client; its caches are read, never filled by hand.
|
|
134
|
+
* @returns the directory the settings service validates references with.
|
|
135
|
+
*/
|
|
136
|
+
export function createDiscordGuildDirectory(client: Client): GuildDirectory {
|
|
137
|
+
return {
|
|
138
|
+
async channel(guildId, id) {
|
|
139
|
+
const guild = await findGuild({ client, guildId });
|
|
140
|
+
if (!guild) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const channel =
|
|
144
|
+
guild.channels.cache.get(id) ?? (await orNullWhenUnknown(guild.channels.fetch(id)));
|
|
145
|
+
return channel ? toChannel(channel) : null;
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
async role(guildId, id) {
|
|
149
|
+
const guild = await findGuild({ client, guildId });
|
|
150
|
+
if (!guild) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const role = guild.roles.cache.get(id) ?? (await orNullWhenUnknown(guild.roles.fetch(id)));
|
|
154
|
+
return role ? { id: role.id, name: role.name } : null;
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
async member(guildId, id) {
|
|
158
|
+
const guild = await findGuild({ client, guildId });
|
|
159
|
+
if (!guild) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const member =
|
|
163
|
+
guild.members.cache.get(id) ?? (await orNullWhenUnknown(guild.members.fetch(id)));
|
|
164
|
+
return member ? toMember(member) : null;
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
async searchChannels(guildId, query, kinds) {
|
|
168
|
+
const guild = await findGuild({ client, guildId });
|
|
169
|
+
if (!guild) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
const types = kinds?.map((kind): GuildChannelType => CHANNEL_TYPE_BY_KIND[kind]);
|
|
173
|
+
const channels = await searchCacheThenFetch<GuildChannelLike>({
|
|
174
|
+
cached: guild.channels.cache.values(),
|
|
175
|
+
fetch: async () => {
|
|
176
|
+
const fetched = await guild.channels.fetch();
|
|
177
|
+
return [...fetched.values()].filter((channel) => channel !== null);
|
|
178
|
+
},
|
|
179
|
+
matches: (channel) =>
|
|
180
|
+
nameStartsWith({ name: channel.name, query }) &&
|
|
181
|
+
(types === undefined || types.includes(channel.type)),
|
|
182
|
+
});
|
|
183
|
+
return channels.map(toChannel);
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
async searchRoles(guildId, query) {
|
|
187
|
+
const guild = await findGuild({ client, guildId });
|
|
188
|
+
if (!guild) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
const roles = await searchCacheThenFetch({
|
|
192
|
+
cached: guild.roles.cache.values(),
|
|
193
|
+
fetch: async () => (await guild.roles.fetch()).values(),
|
|
194
|
+
matches: (role) => nameStartsWith({ name: role.name, query }),
|
|
195
|
+
});
|
|
196
|
+
return roles.map((role) => ({ id: role.id, name: role.name }));
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
async searchMembers(guildId, query) {
|
|
200
|
+
const guild = await findGuild({ client, guildId });
|
|
201
|
+
if (!guild) {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
const hits = [...guild.members.cache.values()].filter((member) =>
|
|
205
|
+
nameStartsWith({ name: member.displayName, query }),
|
|
206
|
+
);
|
|
207
|
+
if (hits.length > 0) {
|
|
208
|
+
return hits.slice(0, SEARCH_LIMIT).map(toMember);
|
|
209
|
+
}
|
|
210
|
+
const found = await guild.members.search({ query, limit: SEARCH_LIMIT });
|
|
211
|
+
return [...found.values()].map(toMember);
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|