@azurioh/discord-kernel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/authz/authorizer.d.ts +45 -0
- package/dist/authz/authorizer.d.ts.map +1 -0
- package/dist/authz/authorizer.js +31 -0
- package/dist/authz/authorizer.js.map +1 -0
- package/dist/authz/require-level-guard.d.ts +23 -0
- package/dist/authz/require-level-guard.d.ts.map +1 -0
- package/dist/authz/require-level-guard.js +37 -0
- package/dist/authz/require-level-guard.js.map +1 -0
- package/dist/clock.d.ts +13 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +13 -0
- package/dist/clock.js.map +1 -0
- package/dist/concurrency/keyed-queue.d.ts +18 -0
- package/dist/concurrency/keyed-queue.d.ts.map +1 -0
- package/dist/concurrency/keyed-queue.js +37 -0
- package/dist/concurrency/keyed-queue.js.map +1 -0
- package/dist/concurrency/run-with-concurrency.d.ts +15 -0
- package/dist/concurrency/run-with-concurrency.d.ts.map +1 -0
- package/dist/concurrency/run-with-concurrency.js +40 -0
- package/dist/concurrency/run-with-concurrency.js.map +1 -0
- package/dist/config/env.d.ts +17 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +49 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/errors.d.ts +14 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +25 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/datetime.d.ts +34 -0
- package/dist/datetime.d.ts.map +1 -0
- package/dist/datetime.js +188 -0
- package/dist/datetime.js.map +1 -0
- package/dist/discord/api-errors.d.ts +20 -0
- package/dist/discord/api-errors.d.ts.map +1 -0
- package/dist/discord/api-errors.js +90 -0
- package/dist/discord/api-errors.js.map +1 -0
- package/dist/discord/channel-exporter.d.ts +42 -0
- package/dist/discord/channel-exporter.d.ts.map +1 -0
- package/dist/discord/channel-exporter.js +3 -0
- package/dist/discord/channel-exporter.js.map +1 -0
- package/dist/discord/command/autocomplete-dispatcher.d.ts +26 -0
- package/dist/discord/command/autocomplete-dispatcher.d.ts.map +1 -0
- package/dist/discord/command/autocomplete-dispatcher.js +41 -0
- package/dist/discord/command/autocomplete-dispatcher.js.map +1 -0
- package/dist/discord/command/autocomplete.d.ts +18 -0
- package/dist/discord/command/autocomplete.d.ts.map +1 -0
- package/dist/discord/command/autocomplete.js +65 -0
- package/dist/discord/command/autocomplete.js.map +1 -0
- package/dist/discord/command/context.d.ts +33 -0
- package/dist/discord/command/context.d.ts.map +1 -0
- package/dist/discord/command/context.js +48 -0
- package/dist/discord/command/context.js.map +1 -0
- package/dist/discord/command/cooldown-guard.d.ts +25 -0
- package/dist/discord/command/cooldown-guard.d.ts.map +1 -0
- package/dist/discord/command/cooldown-guard.js +60 -0
- package/dist/discord/command/cooldown-guard.js.map +1 -0
- package/dist/discord/command/create-command.d.ts +100 -0
- package/dist/discord/command/create-command.d.ts.map +1 -0
- package/dist/discord/command/create-command.js +245 -0
- package/dist/discord/command/create-command.js.map +1 -0
- package/dist/discord/command/create-context-menu-command.d.ts +51 -0
- package/dist/discord/command/create-context-menu-command.d.ts.map +1 -0
- package/dist/discord/command/create-context-menu-command.js +128 -0
- package/dist/discord/command/create-context-menu-command.js.map +1 -0
- package/dist/discord/command/errors.d.ts +19 -0
- package/dist/discord/command/errors.d.ts.map +1 -0
- package/dist/discord/command/errors.js +27 -0
- package/dist/discord/command/errors.js.map +1 -0
- package/dist/discord/command/guard.d.ts +30 -0
- package/dist/discord/command/guard.d.ts.map +1 -0
- package/dist/discord/command/guard.js +34 -0
- package/dist/discord/command/guard.js.map +1 -0
- package/dist/discord/command/localization.d.ts +9 -0
- package/dist/discord/command/localization.d.ts.map +1 -0
- package/dist/discord/command/localization.js +13 -0
- package/dist/discord/command/localization.js.map +1 -0
- package/dist/discord/command/options.d.ts +113 -0
- package/dist/discord/command/options.d.ts.map +1 -0
- package/dist/discord/command/options.js +509 -0
- package/dist/discord/command/options.js.map +1 -0
- package/dist/discord/command/permission-guard.d.ts +21 -0
- package/dist/discord/command/permission-guard.d.ts.map +1 -0
- package/dist/discord/command/permission-guard.js +69 -0
- package/dist/discord/command/permission-guard.js.map +1 -0
- package/dist/discord/command/router.d.ts +48 -0
- package/dist/discord/command/router.d.ts.map +1 -0
- package/dist/discord/command/router.js +118 -0
- package/dist/discord/command/router.js.map +1 -0
- package/dist/discord/command/types.d.ts +51 -0
- package/dist/discord/command/types.d.ts.map +1 -0
- package/dist/discord/command/types.js +3 -0
- package/dist/discord/command/types.js.map +1 -0
- package/dist/discord/components/component-router.d.ts +116 -0
- package/dist/discord/components/component-router.d.ts.map +1 -0
- package/dist/discord/components/component-router.js +123 -0
- package/dist/discord/components/component-router.js.map +1 -0
- package/dist/discord/components/interactive-message/index.d.ts +3 -0
- package/dist/discord/components/interactive-message/index.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/index.js +11 -0
- package/dist/discord/components/interactive-message/index.js.map +1 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts +195 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.js +151 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.js.map +1 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts +30 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.js +44 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.js.map +1 -0
- package/dist/discord/components/paginator/index.d.ts +5 -0
- package/dist/discord/components/paginator/index.d.ts.map +1 -0
- package/dist/discord/components/paginator/index.js +15 -0
- package/dist/discord/components/paginator/index.js.map +1 -0
- package/dist/discord/components/paginator/mount-paginator.d.ts +25 -0
- package/dist/discord/components/paginator/mount-paginator.d.ts.map +1 -0
- package/dist/discord/components/paginator/mount-paginator.js +41 -0
- package/dist/discord/components/paginator/mount-paginator.js.map +1 -0
- package/dist/discord/components/paginator/page-state.d.ts +21 -0
- package/dist/discord/components/paginator/page-state.d.ts.map +1 -0
- package/dist/discord/components/paginator/page-state.js +44 -0
- package/dist/discord/components/paginator/page-state.js.map +1 -0
- package/dist/discord/components/paginator/paginator-buttons.d.ts +17 -0
- package/dist/discord/components/paginator/paginator-buttons.d.ts.map +1 -0
- package/dist/discord/components/paginator/paginator-buttons.js +45 -0
- package/dist/discord/components/paginator/paginator-buttons.js.map +1 -0
- package/dist/discord/components/paginator/paginator-view.d.ts +26 -0
- package/dist/discord/components/paginator/paginator-view.d.ts.map +1 -0
- package/dist/discord/components/paginator/paginator-view.js +28 -0
- package/dist/discord/components/paginator/paginator-view.js.map +1 -0
- package/dist/discord/components/persistent-paginator.d.ts +47 -0
- package/dist/discord/components/persistent-paginator.d.ts.map +1 -0
- package/dist/discord/components/persistent-paginator.js +137 -0
- package/dist/discord/components/persistent-paginator.js.map +1 -0
- package/dist/discord/components/settings-editor/index.d.ts +7 -0
- package/dist/discord/components/settings-editor/index.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/index.js +22 -0
- package/dist/discord/components/settings-editor/index.js.map +1 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts +140 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.js +344 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.js.map +1 -0
- package/dist/discord/components/settings-editor/navigation.d.ts +49 -0
- package/dist/discord/components/settings-editor/navigation.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/navigation.js +39 -0
- package/dist/discord/components/settings-editor/navigation.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts +56 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.js +284 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts +69 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js +353 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts +216 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.js +66 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts +303 -0
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor.view.js +316 -0
- package/dist/discord/components/settings-editor/settings-editor.view.js.map +1 -0
- package/dist/discord/events/create-event.d.ts +5 -0
- package/dist/discord/events/create-event.d.ts.map +1 -0
- package/dist/discord/events/create-event.js +8 -0
- package/dist/discord/events/create-event.js.map +1 -0
- package/dist/discord/events/event-router.d.ts +16 -0
- package/dist/discord/events/event-router.d.ts.map +1 -0
- package/dist/discord/events/event-router.js +51 -0
- package/dist/discord/events/event-router.js.map +1 -0
- package/dist/discord/events/types.d.ts +16 -0
- package/dist/discord/events/types.d.ts.map +1 -0
- package/dist/discord/events/types.js +3 -0
- package/dist/discord/events/types.js.map +1 -0
- package/dist/discord/i18n.d.ts +58 -0
- package/dist/discord/i18n.d.ts.map +1 -0
- package/dist/discord/i18n.js +173 -0
- package/dist/discord/i18n.js.map +1 -0
- package/dist/discord/interaction/button.d.ts +39 -0
- package/dist/discord/interaction/button.d.ts.map +1 -0
- package/dist/discord/interaction/button.js +36 -0
- package/dist/discord/interaction/button.js.map +1 -0
- package/dist/discord/interaction/interaction-router.d.ts +21 -0
- package/dist/discord/interaction/interaction-router.d.ts.map +1 -0
- package/dist/discord/interaction/interaction-router.js +24 -0
- package/dist/discord/interaction/interaction-router.js.map +1 -0
- package/dist/discord/interaction/modal.d.ts +171 -0
- package/dist/discord/interaction/modal.d.ts.map +1 -0
- package/dist/discord/interaction/modal.js +264 -0
- package/dist/discord/interaction/modal.js.map +1 -0
- package/dist/discord/interaction/select-menu.d.ts +104 -0
- package/dist/discord/interaction/select-menu.d.ts.map +1 -0
- package/dist/discord/interaction/select-menu.js +126 -0
- package/dist/discord/interaction/select-menu.js.map +1 -0
- package/dist/discord/permissions.d.ts +32 -0
- package/dist/discord/permissions.d.ts.map +1 -0
- package/dist/discord/permissions.js +40 -0
- package/dist/discord/permissions.js.map +1 -0
- package/dist/discord/placeholder-registry-errors.d.ts +10 -0
- package/dist/discord/placeholder-registry-errors.d.ts.map +1 -0
- package/dist/discord/placeholder-registry-errors.js +18 -0
- package/dist/discord/placeholder-registry-errors.js.map +1 -0
- package/dist/discord/placeholder-registry.d.ts +53 -0
- package/dist/discord/placeholder-registry.d.ts.map +1 -0
- package/dist/discord/placeholder-registry.js +52 -0
- package/dist/discord/placeholder-registry.js.map +1 -0
- package/dist/discord/presenter.d.ts +26 -0
- package/dist/discord/presenter.d.ts.map +1 -0
- package/dist/discord/presenter.js +3 -0
- package/dist/discord/presenter.js.map +1 -0
- package/dist/discord/ui/card.d.ts +114 -0
- package/dist/discord/ui/card.d.ts.map +1 -0
- package/dist/discord/ui/card.js +162 -0
- package/dist/discord/ui/card.js.map +1 -0
- package/dist/discord/ui/color-input-errors.d.ts +12 -0
- package/dist/discord/ui/color-input-errors.d.ts.map +1 -0
- package/dist/discord/ui/color-input-errors.js +21 -0
- package/dist/discord/ui/color-input-errors.js.map +1 -0
- package/dist/discord/ui/color-input.d.ts +37 -0
- package/dist/discord/ui/color-input.d.ts.map +1 -0
- package/dist/discord/ui/color-input.js +166 -0
- package/dist/discord/ui/color-input.js.map +1 -0
- package/dist/discord/ui/colors.d.ts +44 -0
- package/dist/discord/ui/colors.d.ts.map +1 -0
- package/dist/discord/ui/colors.js +45 -0
- package/dist/discord/ui/colors.js.map +1 -0
- package/dist/discord/ui/embed.d.ts +16 -0
- package/dist/discord/ui/embed.d.ts.map +1 -0
- package/dist/discord/ui/embed.js +53 -0
- package/dist/discord/ui/embed.js.map +1 -0
- package/dist/discord/ui/message-attachments.d.ts +40 -0
- package/dist/discord/ui/message-attachments.d.ts.map +1 -0
- package/dist/discord/ui/message-attachments.js +27 -0
- package/dist/discord/ui/message-attachments.js.map +1 -0
- package/dist/errors/business-error.d.ts +41 -0
- package/dist/errors/business-error.d.ts.map +1 -0
- package/dist/errors/business-error.js +39 -0
- package/dist/errors/business-error.js.map +1 -0
- package/dist/errors/describe-error.d.ts +29 -0
- package/dist/errors/describe-error.d.ts.map +1 -0
- package/dist/errors/describe-error.js +49 -0
- package/dist/errors/describe-error.js.map +1 -0
- package/dist/errors/incident-ref.d.ts +7 -0
- package/dist/errors/incident-ref.d.ts.map +1 -0
- package/dist/errors/incident-ref.js +13 -0
- package/dist/errors/incident-ref.js.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +15 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/i18n/business-message.d.ts +14 -0
- package/dist/i18n/business-message.d.ts.map +1 -0
- package/dist/i18n/business-message.js +19 -0
- package/dist/i18n/business-message.js.map +1 -0
- package/dist/i18n/catalog.d.ts +27 -0
- package/dist/i18n/catalog.d.ts.map +1 -0
- package/dist/i18n/catalog.js +25 -0
- package/dist/i18n/catalog.js.map +1 -0
- package/dist/i18n/errors.d.ts +10 -0
- package/dist/i18n/errors.d.ts.map +1 -0
- package/dist/i18n/errors.js +18 -0
- package/dist/i18n/errors.js.map +1 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +16 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/locale.d.ts +22 -0
- package/dist/i18n/locale.d.ts.map +1 -0
- package/dist/i18n/locale.js +40 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/translator.d.ts +35 -0
- package/dist/i18n/translator.d.ts.map +1 -0
- package/dist/i18n/translator.js +40 -0
- package/dist/i18n/translator.js.map +1 -0
- package/dist/logger.d.ts +28 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -0
- package/dist/module/module.d.ts +42 -0
- package/dist/module/module.d.ts.map +1 -0
- package/dist/module/module.js +3 -0
- package/dist/module/module.js.map +1 -0
- package/dist/persistence/database.d.ts +24 -0
- package/dist/persistence/database.d.ts.map +1 -0
- package/dist/persistence/database.js +3 -0
- package/dist/persistence/database.js.map +1 -0
- package/dist/resilience/retry.d.ts +36 -0
- package/dist/resilience/retry.d.ts.map +1 -0
- package/dist/resilience/retry.js +78 -0
- package/dist/resilience/retry.js.map +1 -0
- package/dist/scheduler/errors.d.ts +25 -0
- package/dist/scheduler/errors.d.ts.map +1 -0
- package/dist/scheduler/errors.js +45 -0
- package/dist/scheduler/errors.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +68 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +162 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/package.json +89 -0
- package/src/authz/authorizer.ts +76 -0
- package/src/authz/require-level-guard.ts +42 -0
- package/src/clock.ts +18 -0
- package/src/concurrency/keyed-queue.ts +50 -0
- package/src/concurrency/run-with-concurrency.ts +44 -0
- package/src/config/env.ts +58 -0
- package/src/config/errors.ts +21 -0
- package/src/datetime.ts +221 -0
- package/src/discord/api-errors.ts +91 -0
- package/src/discord/channel-exporter.ts +48 -0
- package/src/discord/command/autocomplete-dispatcher.ts +54 -0
- package/src/discord/command/autocomplete.ts +76 -0
- package/src/discord/command/context.ts +85 -0
- package/src/discord/command/cooldown-guard.ts +81 -0
- package/src/discord/command/create-command.ts +431 -0
- package/src/discord/command/create-context-menu-command.ts +227 -0
- package/src/discord/command/errors.ts +22 -0
- package/src/discord/command/guard.ts +54 -0
- package/src/discord/command/localization.ts +11 -0
- package/src/discord/command/options.ts +847 -0
- package/src/discord/command/permission-guard.ts +74 -0
- package/src/discord/command/router.ts +142 -0
- package/src/discord/command/types.ts +52 -0
- package/src/discord/components/component-router.ts +213 -0
- package/src/discord/components/interactive-message/index.ts +18 -0
- package/src/discord/components/interactive-message/interactive-message-collector.ts +374 -0
- package/src/discord/components/interactive-message/mount-interactive-message.ts +71 -0
- package/src/discord/components/paginator/index.ts +17 -0
- package/src/discord/components/paginator/mount-paginator.ts +69 -0
- package/src/discord/components/paginator/page-state.ts +45 -0
- package/src/discord/components/paginator/paginator-buttons.ts +60 -0
- package/src/discord/components/paginator/paginator-view.ts +61 -0
- package/src/discord/components/persistent-paginator.ts +197 -0
- package/src/discord/components/settings-editor/index.ts +41 -0
- package/src/discord/components/settings-editor/mount-settings-editor.ts +646 -0
- package/src/discord/components/settings-editor/navigation.ts +67 -0
- package/src/discord/components/settings-editor/settings-editor-card.view.ts +344 -0
- package/src/discord/components/settings-editor/settings-editor-field-modal.ts +538 -0
- package/src/discord/components/settings-editor/settings-editor-fields.ts +290 -0
- package/src/discord/components/settings-editor/settings-editor.view.ts +654 -0
- package/src/discord/events/create-event.ts +7 -0
- package/src/discord/events/event-router.ts +53 -0
- package/src/discord/events/types.ts +17 -0
- package/src/discord/i18n.ts +173 -0
- package/src/discord/interaction/button.ts +75 -0
- package/src/discord/interaction/interaction-router.ts +28 -0
- package/src/discord/interaction/modal.ts +505 -0
- package/src/discord/interaction/select-menu.ts +246 -0
- package/src/discord/permissions.ts +57 -0
- package/src/discord/placeholder-registry-errors.ts +11 -0
- package/src/discord/placeholder-registry.ts +77 -0
- package/src/discord/presenter.ts +26 -0
- package/src/discord/ui/card.ts +303 -0
- package/src/discord/ui/color-input-errors.ts +17 -0
- package/src/discord/ui/color-input.ts +179 -0
- package/src/discord/ui/colors.ts +58 -0
- package/src/discord/ui/embed.ts +64 -0
- package/src/discord/ui/message-attachments.ts +57 -0
- package/src/errors/business-error.ts +49 -0
- package/src/errors/describe-error.ts +73 -0
- package/src/errors/incident-ref.ts +10 -0
- package/src/errors/index.ts +12 -0
- package/src/i18n/business-message.ts +23 -0
- package/src/i18n/catalog.ts +41 -0
- package/src/i18n/errors.ts +11 -0
- package/src/i18n/index.ts +11 -0
- package/src/i18n/locale.ts +41 -0
- package/src/i18n/translator.ts +78 -0
- package/src/logger.ts +28 -0
- package/src/module/module.ts +42 -0
- package/src/persistence/database.ts +23 -0
- package/src/resilience/retry.ts +102 -0
- package/src/scheduler/errors.ts +40 -0
- package/src/scheduler/scheduler.ts +248 -0
package/dist/datetime.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.zonedWallClockToUtc = zonedWallClockToUtc;
|
|
4
|
+
exports.isValidWallClock = isValidWallClock;
|
|
5
|
+
exports.dayString = dayString;
|
|
6
|
+
exports.startOfGuildDay = startOfGuildDay;
|
|
7
|
+
exports.endOfGuildDay = endOfGuildDay;
|
|
8
|
+
exports.startOfNextGuildWeek = startOfNextGuildWeek;
|
|
9
|
+
exports.addGuildDays = addGuildDays;
|
|
10
|
+
/**
|
|
11
|
+
* Wall-clock datetime input grammar: `YYYY-MM-DD` with an optional
|
|
12
|
+
* ` HH:MM` / `THH:MM` time (seconds optional). Matches the format documented to
|
|
13
|
+
* users in the slash-command options.
|
|
14
|
+
*/
|
|
15
|
+
const WALL_CLOCK_PATTERN = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?$/;
|
|
16
|
+
function parseWallClock(input) {
|
|
17
|
+
const match = WALL_CLOCK_PATTERN.exec(input.trim());
|
|
18
|
+
if (!match) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const [, year, month, day, hour, minute, second] = match;
|
|
22
|
+
const wallClock = {
|
|
23
|
+
year: Number(year),
|
|
24
|
+
month: Number(month),
|
|
25
|
+
day: Number(day),
|
|
26
|
+
hour: hour ? Number(hour) : 0,
|
|
27
|
+
minute: minute ? Number(minute) : 0,
|
|
28
|
+
second: second ? Number(second) : 0,
|
|
29
|
+
};
|
|
30
|
+
return isRealCalendarInstant(wallClock) ? wallClock : null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reject impossible values the regex alone allows (month 13, day 32, hour 25)
|
|
34
|
+
* by round-tripping through `Date.UTC` and checking nothing overflowed.
|
|
35
|
+
*/
|
|
36
|
+
function isRealCalendarInstant(wallClock) {
|
|
37
|
+
const date = new Date(Date.UTC(wallClock.year, wallClock.month - 1, wallClock.day, wallClock.hour, wallClock.minute, wallClock.second));
|
|
38
|
+
return (date.getUTCFullYear() === wallClock.year &&
|
|
39
|
+
date.getUTCMonth() === wallClock.month - 1 &&
|
|
40
|
+
date.getUTCDate() === wallClock.day &&
|
|
41
|
+
date.getUTCHours() === wallClock.hour &&
|
|
42
|
+
date.getUTCMinutes() === wallClock.minute &&
|
|
43
|
+
date.getUTCSeconds() === wallClock.second);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Offset, in milliseconds, between the given instant and how that instant reads
|
|
47
|
+
* as a wall clock in `timeZone` (positive when the zone is ahead of UTC).
|
|
48
|
+
*/
|
|
49
|
+
function timeZoneOffsetMs(timestamp, timeZone) {
|
|
50
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
51
|
+
timeZone,
|
|
52
|
+
hourCycle: "h23",
|
|
53
|
+
year: "numeric",
|
|
54
|
+
month: "2-digit",
|
|
55
|
+
day: "2-digit",
|
|
56
|
+
hour: "2-digit",
|
|
57
|
+
minute: "2-digit",
|
|
58
|
+
second: "2-digit",
|
|
59
|
+
});
|
|
60
|
+
const parts = new Map();
|
|
61
|
+
for (const { type, value } of formatter.formatToParts(new Date(timestamp))) {
|
|
62
|
+
if (type !== "literal") {
|
|
63
|
+
parts.set(type, Number(value));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const read = (key) => {
|
|
67
|
+
const value = parts.get(key);
|
|
68
|
+
if (value === undefined) {
|
|
69
|
+
throw new RangeError(`Missing "${key}" in formatted parts for time zone "${timeZone}"`);
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
};
|
|
73
|
+
const asUtc = Date.UTC(read("year"), read("month") - 1, read("day"), read("hour"), read("minute"), read("second"));
|
|
74
|
+
return asUtc - timestamp;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Interpret a wall-clock datetime string as local time in `timeZone` and return
|
|
78
|
+
* the matching UTC instant. Discord renders stored timestamps in each viewer's
|
|
79
|
+
* own timezone, so an event entered as "18:00" guild-local must be stored as the
|
|
80
|
+
* equivalent UTC instant — otherwise it drifts by the guild's UTC offset.
|
|
81
|
+
* @throws RangeError When the input does not match the wall-clock grammar.
|
|
82
|
+
*/
|
|
83
|
+
function zonedWallClockToUtc(input, timeZone) {
|
|
84
|
+
const wallClock = parseWallClock(input);
|
|
85
|
+
if (!wallClock) {
|
|
86
|
+
throw new RangeError(`Invalid wall-clock datetime: "${input}"`);
|
|
87
|
+
}
|
|
88
|
+
const utcGuess = Date.UTC(wallClock.year, wallClock.month - 1, wallClock.day, wallClock.hour, wallClock.minute, wallClock.second);
|
|
89
|
+
// Two passes: the offset read at `utcGuess` can be wrong across a DST
|
|
90
|
+
// transition (the guessed instant may land on the far side of the boundary
|
|
91
|
+
// from the target). Re-reading the offset at the first candidate pins the
|
|
92
|
+
// correct one for every time except the ~1h/year that either does not exist
|
|
93
|
+
// (spring-forward gap) or is ambiguous (fall-back overlap), which resolve
|
|
94
|
+
// deterministically to one side.
|
|
95
|
+
const firstOffset = timeZoneOffsetMs(utcGuess, timeZone);
|
|
96
|
+
const secondOffset = timeZoneOffsetMs(utcGuess - firstOffset, timeZone);
|
|
97
|
+
return new Date(utcGuess - secondOffset);
|
|
98
|
+
}
|
|
99
|
+
/** Whether `input` is a valid wall-clock datetime string. */
|
|
100
|
+
function isValidWallClock(input) {
|
|
101
|
+
return parseWallClock(input) !== null;
|
|
102
|
+
}
|
|
103
|
+
/** `YYYY-MM-DD` of a date-only value, read in UTC (matches `Date`-only options). */
|
|
104
|
+
function dayString(date) {
|
|
105
|
+
return date.toISOString().slice(0, 10);
|
|
106
|
+
}
|
|
107
|
+
/** Start of `date`'s calendar day (00:00:00) anchored to `timeZone`. */
|
|
108
|
+
function startOfGuildDay(date, timeZone) {
|
|
109
|
+
return zonedWallClockToUtc(`${dayString(date)} 00:00:00`, timeZone);
|
|
110
|
+
}
|
|
111
|
+
/** End of `date`'s calendar day (23:59:59) anchored to `timeZone`. */
|
|
112
|
+
function endOfGuildDay(date, timeZone) {
|
|
113
|
+
return zonedWallClockToUtc(`${dayString(date)} 23:59:59`, timeZone);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Monday 00:00 (guild-local) of the week following `reference`'s week. Used to
|
|
117
|
+
* anchor "next week"/"week after next" ranges to the guild's own calendar
|
|
118
|
+
* rather than to UTC day boundaries, which can disagree with the guild's local
|
|
119
|
+
* week near midnight around the timezone offset.
|
|
120
|
+
*/
|
|
121
|
+
function startOfNextGuildWeek(reference, timeZone) {
|
|
122
|
+
// Both the weekday *and* the calendar date must be read in `timeZone`, not
|
|
123
|
+
// UTC: near midnight, the guild-local day can already differ from the UTC
|
|
124
|
+
// day, and mixing the two would misplace the coming Monday by a day.
|
|
125
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
126
|
+
timeZone,
|
|
127
|
+
weekday: "short",
|
|
128
|
+
year: "numeric",
|
|
129
|
+
month: "2-digit",
|
|
130
|
+
day: "2-digit",
|
|
131
|
+
});
|
|
132
|
+
const weekdayIndex = {
|
|
133
|
+
Sun: 0,
|
|
134
|
+
Mon: 1,
|
|
135
|
+
Tue: 2,
|
|
136
|
+
Wed: 3,
|
|
137
|
+
Thu: 4,
|
|
138
|
+
Fri: 5,
|
|
139
|
+
Sat: 6,
|
|
140
|
+
};
|
|
141
|
+
const parts = new Map();
|
|
142
|
+
for (const { type, value } of formatter.formatToParts(reference)) {
|
|
143
|
+
if (type !== "literal") {
|
|
144
|
+
parts.set(type, value);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const currentDay = weekdayIndex[parts.get("weekday") ?? ""] ?? 0;
|
|
148
|
+
// ISO week starts on Monday: ahead-days is 0 only when already Monday and we
|
|
149
|
+
// still want *next* week's Monday, hence the `=== 1 ? 7` special-case.
|
|
150
|
+
const daysUntilNextMonday = currentDay === 1 ? 7 : (8 - currentDay) % 7;
|
|
151
|
+
// Advance the *calendar date* (a pure Y-M-D operation, DST-agnostic) before
|
|
152
|
+
// re-anchoring to the timezone — advancing the UTC instant instead would
|
|
153
|
+
// drift by an hour across a DST transition inside the date range.
|
|
154
|
+
const year = Number(parts.get("year"));
|
|
155
|
+
const month = Number(parts.get("month"));
|
|
156
|
+
const day = Number(parts.get("day"));
|
|
157
|
+
const nextMondayUtcCalendarDay = new Date(Date.UTC(year, month - 1, day + daysUntilNextMonday));
|
|
158
|
+
return startOfGuildDay(nextMondayUtcCalendarDay, timeZone);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Add `days` guild-local calendar days to `instant`, re-anchoring to
|
|
162
|
+
* `timeZone`'s midnight. `instant` is assumed to already be a guild-local
|
|
163
|
+
* midnight (e.g. the result of {@link startOfGuildDay} or
|
|
164
|
+
* {@link startOfNextGuildWeek}); adding a fixed millisecond offset instead
|
|
165
|
+
* would drift by the DST delta whenever the interval crosses a transition —
|
|
166
|
+
* this reads the calendar date in `timeZone` first, so the result is always
|
|
167
|
+
* that timezone's actual midnight `days` later.
|
|
168
|
+
*/
|
|
169
|
+
function addGuildDays(instant, days, timeZone) {
|
|
170
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
171
|
+
timeZone,
|
|
172
|
+
year: "numeric",
|
|
173
|
+
month: "2-digit",
|
|
174
|
+
day: "2-digit",
|
|
175
|
+
});
|
|
176
|
+
const parts = new Map();
|
|
177
|
+
for (const { type, value } of formatter.formatToParts(instant)) {
|
|
178
|
+
if (type !== "literal") {
|
|
179
|
+
parts.set(type, value);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const year = Number(parts.get("year"));
|
|
183
|
+
const month = Number(parts.get("month"));
|
|
184
|
+
const day = Number(parts.get("day"));
|
|
185
|
+
const shiftedUtcCalendarDay = new Date(Date.UTC(year, month - 1, day + days));
|
|
186
|
+
return startOfGuildDay(shiftedUtcCalendarDay, timeZone);
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=datetime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datetime.js","sourceRoot":"","sources":["../src/datetime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DiscordAPIError } from "discord.js";
|
|
2
|
+
/**
|
|
3
|
+
* Narrow an unknown rejection to a Discord REST error. Adapters catch `unknown`,
|
|
4
|
+
* so every predicate below starts here rather than trusting a `code` property
|
|
5
|
+
* that any object could carry.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isDiscordAPIError(error: unknown): error is DiscordAPIError;
|
|
8
|
+
/**
|
|
9
|
+
* The targeted guild/channel/member/message/interaction is gone. Treat as a
|
|
10
|
+
* benign outcome: drop the stale reference instead of logging an incident.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isUnknownResource(error: unknown): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The bot lacks a permission or access to the resource. Actionable by a server
|
|
15
|
+
* admin, so it deserves a clear message rather than a generic incident.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isMissingAccess(error: unknown): boolean;
|
|
18
|
+
/** Discord is throttling us: back off and retry rather than surface a failure. */
|
|
19
|
+
export declare function isRateLimited(error: unknown): boolean;
|
|
20
|
+
//# sourceMappingURL=api-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-errors.d.ts","sourceRoot":"","sources":["../../src/discord/api-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAsB,MAAM,YAAY,CAAC;AAkDjE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAE1E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKzD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKvD;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQrD"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isDiscordAPIError = isDiscordAPIError;
|
|
4
|
+
exports.isUnknownResource = isUnknownResource;
|
|
5
|
+
exports.isMissingAccess = isMissingAccess;
|
|
6
|
+
exports.isRateLimited = isRateLimited;
|
|
7
|
+
const discord_js_1 = require("discord.js");
|
|
8
|
+
/**
|
|
9
|
+
* Discord answers a rate limit with an HTTP status, not a JSON error code, so
|
|
10
|
+
* this one cannot come from {@link RESTJSONErrorCodes}.
|
|
11
|
+
*/
|
|
12
|
+
const TOO_MANY_REQUESTS_STATUS = 429;
|
|
13
|
+
/**
|
|
14
|
+
* Codes meaning "the entity you asked about no longer exists". They are benign:
|
|
15
|
+
* a channel was deleted, a member left, a message was removed, an interaction
|
|
16
|
+
* token expired. The right reaction is to clean up local state, never to alert.
|
|
17
|
+
*/
|
|
18
|
+
const UNKNOWN_RESOURCE_CODES = new Set([
|
|
19
|
+
discord_js_1.RESTJSONErrorCodes.UnknownAccount,
|
|
20
|
+
discord_js_1.RESTJSONErrorCodes.UnknownApplication,
|
|
21
|
+
discord_js_1.RESTJSONErrorCodes.UnknownChannel,
|
|
22
|
+
discord_js_1.RESTJSONErrorCodes.UnknownGuild,
|
|
23
|
+
discord_js_1.RESTJSONErrorCodes.UnknownIntegration,
|
|
24
|
+
discord_js_1.RESTJSONErrorCodes.UnknownInvite,
|
|
25
|
+
discord_js_1.RESTJSONErrorCodes.UnknownMember,
|
|
26
|
+
discord_js_1.RESTJSONErrorCodes.UnknownMessage,
|
|
27
|
+
discord_js_1.RESTJSONErrorCodes.UnknownPermissionOverwrite,
|
|
28
|
+
discord_js_1.RESTJSONErrorCodes.UnknownRole,
|
|
29
|
+
discord_js_1.RESTJSONErrorCodes.UnknownToken,
|
|
30
|
+
discord_js_1.RESTJSONErrorCodes.UnknownUser,
|
|
31
|
+
discord_js_1.RESTJSONErrorCodes.UnknownEmoji,
|
|
32
|
+
discord_js_1.RESTJSONErrorCodes.UnknownWebhook,
|
|
33
|
+
discord_js_1.RESTJSONErrorCodes.UnknownBan,
|
|
34
|
+
discord_js_1.RESTJSONErrorCodes.UnknownGuildTemplate,
|
|
35
|
+
discord_js_1.RESTJSONErrorCodes.UnknownSticker,
|
|
36
|
+
discord_js_1.RESTJSONErrorCodes.UnknownInteraction,
|
|
37
|
+
discord_js_1.RESTJSONErrorCodes.UnknownApplicationCommand,
|
|
38
|
+
discord_js_1.RESTJSONErrorCodes.UnknownVoiceState,
|
|
39
|
+
discord_js_1.RESTJSONErrorCodes.UnknownStageInstance,
|
|
40
|
+
]);
|
|
41
|
+
/** Codes meaning the bot is simply not allowed to do this — actionable by an admin. */
|
|
42
|
+
const MISSING_ACCESS_CODES = new Set([
|
|
43
|
+
discord_js_1.RESTJSONErrorCodes.MissingAccess,
|
|
44
|
+
discord_js_1.RESTJSONErrorCodes.MissingPermissions,
|
|
45
|
+
]);
|
|
46
|
+
/** Codes Discord uses for a per-resource throttle, alongside the HTTP 429 status. */
|
|
47
|
+
const RATE_LIMIT_CODES = new Set([
|
|
48
|
+
discord_js_1.RESTJSONErrorCodes.ChannelSendRateLimit,
|
|
49
|
+
discord_js_1.RESTJSONErrorCodes.ServerSendRateLimit,
|
|
50
|
+
discord_js_1.RESTJSONErrorCodes.ServiceResourceIsBeingRateLimited,
|
|
51
|
+
]);
|
|
52
|
+
/**
|
|
53
|
+
* Narrow an unknown rejection to a Discord REST error. Adapters catch `unknown`,
|
|
54
|
+
* so every predicate below starts here rather than trusting a `code` property
|
|
55
|
+
* that any object could carry.
|
|
56
|
+
*/
|
|
57
|
+
function isDiscordAPIError(error) {
|
|
58
|
+
return error instanceof discord_js_1.DiscordAPIError;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The targeted guild/channel/member/message/interaction is gone. Treat as a
|
|
62
|
+
* benign outcome: drop the stale reference instead of logging an incident.
|
|
63
|
+
*/
|
|
64
|
+
function isUnknownResource(error) {
|
|
65
|
+
if (!isDiscordAPIError(error)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return typeof error.code === "number" && UNKNOWN_RESOURCE_CODES.has(error.code);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The bot lacks a permission or access to the resource. Actionable by a server
|
|
72
|
+
* admin, so it deserves a clear message rather than a generic incident.
|
|
73
|
+
*/
|
|
74
|
+
function isMissingAccess(error) {
|
|
75
|
+
if (!isDiscordAPIError(error)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return typeof error.code === "number" && MISSING_ACCESS_CODES.has(error.code);
|
|
79
|
+
}
|
|
80
|
+
/** Discord is throttling us: back off and retry rather than surface a failure. */
|
|
81
|
+
function isRateLimited(error) {
|
|
82
|
+
if (!isDiscordAPIError(error)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (error.status === TOO_MANY_REQUESTS_STATUS) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return typeof error.code === "number" && RATE_LIMIT_CODES.has(error.code);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=api-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-errors.js","sourceRoot":"","sources":["../../src/discord/api-errors.ts"],"names":[],"mappings":";;AAuDA,8CAEC;AAMD,8CAKC;AAMD,0CAKC;AAGD,sCAQC;AA1FD,2CAAiE;AAEjE;;;GAGG;AACH,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAErC;;;;GAIG;AACH,MAAM,sBAAsB,GAAwB,IAAI,GAAG,CAAC;IAC3D,+BAAkB,CAAC,cAAc;IACjC,+BAAkB,CAAC,kBAAkB;IACrC,+BAAkB,CAAC,cAAc;IACjC,+BAAkB,CAAC,YAAY;IAC/B,+BAAkB,CAAC,kBAAkB;IACrC,+BAAkB,CAAC,aAAa;IAChC,+BAAkB,CAAC,aAAa;IAChC,+BAAkB,CAAC,cAAc;IACjC,+BAAkB,CAAC,0BAA0B;IAC7C,+BAAkB,CAAC,WAAW;IAC9B,+BAAkB,CAAC,YAAY;IAC/B,+BAAkB,CAAC,WAAW;IAC9B,+BAAkB,CAAC,YAAY;IAC/B,+BAAkB,CAAC,cAAc;IACjC,+BAAkB,CAAC,UAAU;IAC7B,+BAAkB,CAAC,oBAAoB;IACvC,+BAAkB,CAAC,cAAc;IACjC,+BAAkB,CAAC,kBAAkB;IACrC,+BAAkB,CAAC,yBAAyB;IAC5C,+BAAkB,CAAC,iBAAiB;IACpC,+BAAkB,CAAC,oBAAoB;CACvC,CAAC,CAAC;AAEH,uFAAuF;AACvF,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC;IACzD,+BAAkB,CAAC,aAAa;IAChC,+BAAkB,CAAC,kBAAkB;CACrC,CAAC,CAAC;AAEH,qFAAqF;AACrF,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAC;IACrD,+BAAkB,CAAC,oBAAoB;IACvC,+BAAkB,CAAC,mBAAmB;IACtC,+BAAkB,CAAC,iCAAiC;CACpD,CAAC,CAAC;AAEH;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,KAAc;IAC/C,OAAO,KAAK,YAAY,4BAAe,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,KAAc;IAC/C,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACjF,CAAC;AAED;;;GAGG;AACH,SAAgB,eAAe,CAAC,KAAc;IAC7C,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC/E,CAAC;AAED,kFAAkF;AAClF,SAAgB,aAAa,CAAC,KAAc;IAC3C,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,wBAAwB,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC3E,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port for handing a channel to whatever export/archive pipeline is wired up at
|
|
3
|
+
* the composition root, before the channel is otherwise disposed of.
|
|
4
|
+
*
|
|
5
|
+
* This is the seam that lets one feature use another's capability without
|
|
6
|
+
* importing it: a module that closes channels can ask for the content to be
|
|
7
|
+
* archived first, while knowing nothing about who archives it — or whether
|
|
8
|
+
* anyone does. The composition root decides what implements this, exactly as it
|
|
9
|
+
* does for `Logger` and `DatabaseConnection`.
|
|
10
|
+
*/
|
|
11
|
+
export interface ChannelExportOptions {
|
|
12
|
+
/** A caller-chosen base name (no extension) for the produced archive file. */
|
|
13
|
+
readonly fileName?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Where one export actually ended up.
|
|
17
|
+
*
|
|
18
|
+
* Reported rather than assumed, because a caller usually has to tell the person
|
|
19
|
+
* who triggered the export where to find the result — and because an
|
|
20
|
+
* implementation may have a preferred destination it could not reach. The two
|
|
21
|
+
* destinations are deliberately coarse: `discord`, meaning the archive was
|
|
22
|
+
* posted as a message attachment, and `cloud`, meaning it was uploaded
|
|
23
|
+
* somewhere and only a link came back.
|
|
24
|
+
*/
|
|
25
|
+
export interface ChannelExportOutcome {
|
|
26
|
+
readonly destinationUsed: "discord" | "cloud";
|
|
27
|
+
/** True when cloud delivery was configured, failed, and Discord took over. */
|
|
28
|
+
readonly fellBackFromCloud: boolean;
|
|
29
|
+
readonly cloudUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ChannelExporter {
|
|
32
|
+
/**
|
|
33
|
+
* Export `channelId`'s content and deliver the result.
|
|
34
|
+
*
|
|
35
|
+
* `deliverToChannelId` is where a Discord-delivered archive is posted — also
|
|
36
|
+
* the fallback when cloud delivery is configured but fails. `guildId` is what
|
|
37
|
+
* an implementation resolves that guild's destination preference from, so the
|
|
38
|
+
* caller does not have to know one exists.
|
|
39
|
+
*/
|
|
40
|
+
exportChannel(channelId: string, deliverToChannelId: string, guildId: string, options?: ChannelExportOptions): Promise<ChannelExportOutcome>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=channel-exporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-exporter.d.ts","sourceRoot":"","sources":["../../src/discord/channel-exporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACpC,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,eAAe,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9C,8EAA8E;IAC9E,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC/B;;;;;;;OAOG;IACH,aAAa,CACZ,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,MAAM,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-exporter.js","sourceRoot":"","sources":["../../src/discord/channel-exporter.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Interaction } from "discord.js";
|
|
2
|
+
import type { SlashCommand } from "../../discord/command/types";
|
|
3
|
+
import type { InteractionDispatcher } from "../../discord/interaction/interaction-router";
|
|
4
|
+
import type { Logger } from "../../logger";
|
|
5
|
+
/**
|
|
6
|
+
* The slice of the command registry this dispatcher needs. Depending on a
|
|
7
|
+
* one-method port rather than on `CommandRouter` keeps the two dispatchers
|
|
8
|
+
* decoupled while sharing a single registration — the alternative, a second map
|
|
9
|
+
* of commands, would drift the moment a command is registered in only one.
|
|
10
|
+
*/
|
|
11
|
+
export interface CommandLookup {
|
|
12
|
+
get(name: string): SlashCommand | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Answers option autocomplete requests. It is an {@link InteractionDispatcher}
|
|
16
|
+
* aggregated by the shared {@link InteractionRouter}, never a second
|
|
17
|
+
* `client.on("interactionCreate")` — the whole app keeps exactly one listener.
|
|
18
|
+
*/
|
|
19
|
+
export declare class AutocompleteDispatcher implements InteractionDispatcher {
|
|
20
|
+
private readonly commands;
|
|
21
|
+
private readonly logger;
|
|
22
|
+
constructor(commands: CommandLookup, logger: Logger);
|
|
23
|
+
/** Claim autocomplete interactions; ignore everything else. */
|
|
24
|
+
handle(interaction: Interaction): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=autocomplete-dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete-dispatcher.d.ts","sourceRoot":"","sources":["../../../src/discord/command/autocomplete-dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACtF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC7B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;CAC5C;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,qBAAqB;IAElE,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,MAAM;IAGhC,+DAA+D;IACzD,MAAM,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CA0BxD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AutocompleteDispatcher = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Answers option autocomplete requests. It is an {@link InteractionDispatcher}
|
|
6
|
+
* aggregated by the shared {@link InteractionRouter}, never a second
|
|
7
|
+
* `client.on("interactionCreate")` — the whole app keeps exactly one listener.
|
|
8
|
+
*/
|
|
9
|
+
class AutocompleteDispatcher {
|
|
10
|
+
commands;
|
|
11
|
+
logger;
|
|
12
|
+
constructor(commands, logger) {
|
|
13
|
+
this.commands = commands;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
/** Claim autocomplete interactions; ignore everything else. */
|
|
17
|
+
async handle(interaction) {
|
|
18
|
+
if (!interaction.isAutocomplete()) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const command = this.commands.get(interaction.commandName);
|
|
22
|
+
if (!command?.autocomplete) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
// A failing suggestion must never surface to the user: Discord shows no
|
|
26
|
+
// error for autocomplete, it just leaves the box spinning until it times
|
|
27
|
+
// out. Swallow, log, and let the command reply with an empty list.
|
|
28
|
+
try {
|
|
29
|
+
await command.autocomplete(interaction);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
this.logger.error({
|
|
33
|
+
command: interaction.commandName,
|
|
34
|
+
err: error instanceof Error ? error.message : String(error),
|
|
35
|
+
}, "Autocomplete handler failed");
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.AutocompleteDispatcher = AutocompleteDispatcher;
|
|
41
|
+
//# sourceMappingURL=autocomplete-dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete-dispatcher.js","sourceRoot":"","sources":["../../../src/discord/command/autocomplete-dispatcher.ts"],"names":[],"mappings":";;;AAeA;;;;GAIG;AACH,MAAa,sBAAsB;IAEhB;IACA;IAFlB,YACkB,QAAuB,EACvB,MAAc;QADd,aAAQ,GAAR,QAAQ,CAAe;QACvB,WAAM,GAAN,MAAM,CAAQ;IAC7B,CAAC;IAEJ,+DAA+D;IAC/D,KAAK,CAAC,MAAM,CAAC,WAAwB;QACpC,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,EAAE,CAAC;YACnC,OAAO,KAAK,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAC3D,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC;QACd,CAAC;QAED,wEAAwE;QACxE,yEAAyE;QACzE,mEAAmE;QACnE,IAAI,CAAC;YACJ,MAAM,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CAChB;gBACC,OAAO,EAAE,WAAW,CAAC,WAAW;gBAChC,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC3D,EACD,6BAA6B,CAC7B,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;CACD;AAjCD,wDAiCC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AutocompleteInteraction } from "discord.js";
|
|
2
|
+
import { type CompiledSubcommand } from "../../discord/command/create-command";
|
|
3
|
+
import type { Options } from "../../discord/command/options";
|
|
4
|
+
/**
|
|
5
|
+
* Answer a focused option's autocomplete request.
|
|
6
|
+
*
|
|
7
|
+
* Nothing here is allowed to throw: Discord surfaces no error for a failed
|
|
8
|
+
* autocomplete, it just leaves the input spinning until it times out, so an
|
|
9
|
+
* empty list is the only sane failure mode. The caller logs; the user keeps
|
|
10
|
+
* typing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function respondWithSuggestions(interaction: AutocompleteInteraction, options: Options): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* The options a given autocomplete interaction applies to: the focused option
|
|
15
|
+
* belongs to the invoked subcommand, not to the command as a whole.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveRouteOptions(interaction: AutocompleteInteraction, routes: Map<string, CompiledSubcommand>, flatOptions: Options | null): Options;
|
|
18
|
+
//# sourceMappingURL=autocomplete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../../src/discord/command/autocomplete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,KAAK,kBAAkB,EAAY,MAAM,kCAAkC,CAAC;AACrF,OAAO,KAAK,EAAU,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAWjE;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC3C,WAAW,EAAE,uBAAuB,EACpC,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC,CAsBf;AAWD;;;GAGG;AACH,wBAAgB,mBAAmB,CAClC,WAAW,EAAE,uBAAuB,EACpC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACvC,WAAW,EAAE,OAAO,GAAG,IAAI,GACzB,OAAO,CAUT"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.respondWithSuggestions = respondWithSuggestions;
|
|
4
|
+
exports.resolveRouteOptions = resolveRouteOptions;
|
|
5
|
+
const create_command_1 = require("../../discord/command/create-command");
|
|
6
|
+
/**
|
|
7
|
+
* Serving option autocomplete. Kept beside the DSL because resolving *which*
|
|
8
|
+
* option is focused needs the same route table `createCommand` builds — the
|
|
9
|
+
* dispatcher only decides that an interaction is an autocomplete request.
|
|
10
|
+
*/
|
|
11
|
+
/** Discord refuses more than this many suggestions in one response. */
|
|
12
|
+
const MAX_CHOICES = 25;
|
|
13
|
+
/**
|
|
14
|
+
* Answer a focused option's autocomplete request.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here is allowed to throw: Discord surfaces no error for a failed
|
|
17
|
+
* autocomplete, it just leaves the input spinning until it times out, so an
|
|
18
|
+
* empty list is the only sane failure mode. The caller logs; the user keeps
|
|
19
|
+
* typing.
|
|
20
|
+
*/
|
|
21
|
+
async function respondWithSuggestions(interaction, options) {
|
|
22
|
+
const focused = interaction.options.getFocused(true);
|
|
23
|
+
const option = options[focused.name];
|
|
24
|
+
const resolver = option?.autocomplete;
|
|
25
|
+
if (!resolver) {
|
|
26
|
+
await interaction.respond([]);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// The resolver is the only part that can fail (it usually hits a database).
|
|
30
|
+
// Answering an empty list keeps the picker responsive; rethrowing after the
|
|
31
|
+
// response lets the dispatcher log the cause without leaving the user hanging.
|
|
32
|
+
let choices;
|
|
33
|
+
try {
|
|
34
|
+
choices = await resolver(interaction);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
await interaction.respond([]);
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
await interaction.respond(truncate(choices));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Discord rejects the whole response when it carries more than 25 choices, so a
|
|
44
|
+
* resolver returning more is trimmed rather than failing outright — a partial
|
|
45
|
+
* list is still useful to the user typing.
|
|
46
|
+
*/
|
|
47
|
+
function truncate(choices) {
|
|
48
|
+
return choices.slice(0, MAX_CHOICES);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The options a given autocomplete interaction applies to: the focused option
|
|
52
|
+
* belongs to the invoked subcommand, not to the command as a whole.
|
|
53
|
+
*/
|
|
54
|
+
function resolveRouteOptions(interaction, routes, flatOptions) {
|
|
55
|
+
if (flatOptions) {
|
|
56
|
+
return flatOptions;
|
|
57
|
+
}
|
|
58
|
+
const group = interaction.options.getSubcommandGroup(false);
|
|
59
|
+
const name = interaction.options.getSubcommand(false);
|
|
60
|
+
if (name === null) {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
return routes.get((0, create_command_1.routeKey)(group, name))?.options ?? {};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=autocomplete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete.js","sourceRoot":"","sources":["../../../src/discord/command/autocomplete.ts"],"names":[],"mappings":";;AAqBA,wDAyBC;AAeD,kDAcC;AA1ED,qEAAqF;AAGrF;;;;GAIG;AAEH,uEAAuE;AACvE,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB;;;;;;;GAOG;AACI,KAAK,UAAU,sBAAsB,CAC3C,WAAoC,EACpC,OAAgB;IAEhB,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,MAAM,EAAE,YAAY,CAAC;IAEtC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC9B,OAAO;IACR,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,+EAA+E;IAC/E,IAAI,OAA0B,CAAC;IAC/B,IAAI,CAAC;QACJ,OAAO,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC9B,MAAM,KAAK,CAAC;IACb,CAAC;IAED,MAAM,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,SAAS,QAAQ,CAAC,OAA0B;IAC3C,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,SAAgB,mBAAmB,CAClC,WAAoC,EACpC,MAAuC,EACvC,WAA2B;IAE3B,IAAI,WAAW,EAAE,CAAC;QACjB,OAAO,WAAW,CAAC;IACpB,CAAC;IACD,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACtD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACX,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,IAAA,yBAAQ,EAAC,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ChatInputCommandInteraction, type EmbedBuilder } from "discord.js";
|
|
2
|
+
import type { Options, Values } from "../../discord/command/options";
|
|
3
|
+
import type { Presenter } from "../../discord/presenter";
|
|
4
|
+
import { type Locale, type TranslationParams, type Translator } from "../../i18n";
|
|
5
|
+
import type { Logger } from "../../logger";
|
|
6
|
+
/**
|
|
7
|
+
* The handler-facing surface of a dispatched subcommand: the raw interaction,
|
|
8
|
+
* the parsed & typed options, the locale resolved for this interaction, a
|
|
9
|
+
* translate helper bound to it, and convenience responders that route through
|
|
10
|
+
* the injected {@link Presenter}.
|
|
11
|
+
*/
|
|
12
|
+
export interface Context<O extends Options> {
|
|
13
|
+
readonly interaction: ChatInputCommandInteraction;
|
|
14
|
+
readonly options: Values<O>;
|
|
15
|
+
/** Reply language: user's client locale → guild locale → configured default. */
|
|
16
|
+
readonly locale: Locale;
|
|
17
|
+
/** Translate a catalog key for this interaction's locale. */
|
|
18
|
+
t(key: string, params?: TranslationParams): string;
|
|
19
|
+
reply(embed: EmbedBuilder): Promise<void>;
|
|
20
|
+
confirm(message: string): Promise<void>;
|
|
21
|
+
error(message: string): Promise<void>;
|
|
22
|
+
deny(message: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Deliver an embed regardless of the interaction's lifecycle state, and never
|
|
26
|
+
* throw: a failed delivery (e.g. an expired/unknown interaction, code 10062)
|
|
27
|
+
* must not escape a handler and reach the client's unhandled `error` event,
|
|
28
|
+
* which would crash the process. Visibility on a deferred interaction is fixed
|
|
29
|
+
* at deferReply time, so editReply carries no flags.
|
|
30
|
+
*/
|
|
31
|
+
export declare function sendEmbed(interaction: ChatInputCommandInteraction, embed: EmbedBuilder, ephemeral: boolean, logger: Logger): Promise<void>;
|
|
32
|
+
export declare function createContext<O extends Options>(interaction: ChatInputCommandInteraction, options: Values<O>, ephemeral: boolean, presenter: Presenter, logger: Logger, translator: Translator): Context<O>;
|
|
33
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/discord/command/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,2BAA2B,EAAE,KAAK,YAAY,EAAgB,MAAM,YAAY,CAAC;AAC/F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,KAAK,MAAM,EAAiB,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC7F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,OAAO;IACzC,QAAQ,CAAC,WAAW,EAAE,2BAA2B,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACnD,KAAK,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC9B,WAAW,EAAE,2BAA2B,EACxC,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAC9C,WAAW,EAAE,2BAA2B,EACxC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAClB,SAAS,EAAE,OAAO,EAClB,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACpB,OAAO,CAAC,CAAC,CAAC,CAgBZ"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendEmbed = sendEmbed;
|
|
4
|
+
exports.createContext = createContext;
|
|
5
|
+
const discord_js_1 = require("discord.js");
|
|
6
|
+
const i18n_1 = require("../../i18n");
|
|
7
|
+
/**
|
|
8
|
+
* Deliver an embed regardless of the interaction's lifecycle state, and never
|
|
9
|
+
* throw: a failed delivery (e.g. an expired/unknown interaction, code 10062)
|
|
10
|
+
* must not escape a handler and reach the client's unhandled `error` event,
|
|
11
|
+
* which would crash the process. Visibility on a deferred interaction is fixed
|
|
12
|
+
* at deferReply time, so editReply carries no flags.
|
|
13
|
+
*/
|
|
14
|
+
async function sendEmbed(interaction, embed, ephemeral, logger) {
|
|
15
|
+
const flags = ephemeral ? discord_js_1.MessageFlags.Ephemeral : undefined;
|
|
16
|
+
try {
|
|
17
|
+
if (interaction.deferred) {
|
|
18
|
+
await interaction.editReply({ embeds: [embed] });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (interaction.replied) {
|
|
22
|
+
await interaction.followUp({ embeds: [embed], flags });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await interaction.reply({ embeds: [embed], flags });
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
logger.error({
|
|
29
|
+
commandName: interaction.commandName,
|
|
30
|
+
interactionId: interaction.id,
|
|
31
|
+
err: error instanceof Error ? error.message : String(error),
|
|
32
|
+
}, "Failed to deliver interaction response");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function createContext(interaction, options, ephemeral, presenter, logger, translator) {
|
|
36
|
+
const locale = (0, i18n_1.resolveLocale)([interaction.locale, interaction.guildLocale], translator.defaultLocale);
|
|
37
|
+
return {
|
|
38
|
+
interaction,
|
|
39
|
+
options,
|
|
40
|
+
locale,
|
|
41
|
+
t: (key, params) => translator.translate(locale, key, params),
|
|
42
|
+
reply: (embed) => sendEmbed(interaction, embed, ephemeral, logger),
|
|
43
|
+
confirm: (message) => sendEmbed(interaction, presenter.confirmation(message, locale), ephemeral, logger),
|
|
44
|
+
error: (message) => sendEmbed(interaction, presenter.error(message, locale), ephemeral, logger),
|
|
45
|
+
deny: (message) => sendEmbed(interaction, presenter.denial(message, locale), ephemeral, logger),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/discord/command/context.ts"],"names":[],"mappings":";;AAgCA,8BA2BC;AAED,sCAuBC;AApFD,2CAA+F;AAG/F,iCAA6F;AAsB7F;;;;;;GAMG;AACI,KAAK,UAAU,SAAS,CAC9B,WAAwC,EACxC,KAAmB,EACnB,SAAkB,EAClB,MAAc;IAEd,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,yBAAY,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7D,IAAI,CAAC;QACJ,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;YAC1B,MAAM,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACjD,OAAO;QACR,CAAC;QACD,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACzB,MAAM,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACvD,OAAO;QACR,CAAC;QACD,MAAM,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,CAAC,KAAK,CACX;YACC,WAAW,EAAE,WAAW,CAAC,WAAW;YACpC,aAAa,EAAE,WAAW,CAAC,EAAE;YAC7B,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC3D,EACD,wCAAwC,CACxC,CAAC;IACH,CAAC;AACF,CAAC;AAED,SAAgB,aAAa,CAC5B,WAAwC,EACxC,OAAkB,EAClB,SAAkB,EAClB,SAAoB,EACpB,MAAc,EACd,UAAsB;IAEtB,MAAM,MAAM,GAAG,IAAA,oBAAa,EAC3B,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,WAAW,CAAC,EAC7C,UAAU,CAAC,aAAa,CACxB,CAAC;IACF,OAAO;QACN,WAAW;QACX,OAAO;QACP,MAAM;QACN,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC;QAC7D,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC;QAClE,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CACpB,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC;QACnF,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC;QAC/F,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC;KAC/F,CAAC;AACH,CAAC"}
|