@seedcord/core 0.10.0 → 0.11.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 +27 -9
- package/dist/CoordinatedShutdown-H5YjZn8A.d.mts +78 -0
- package/dist/CoordinatedStartup-CGnJrqcl.mjs +168 -0
- package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +1 -0
- package/dist/{HmrModuleHandler-DOAtah3j.mjs → HmrModuleHandler-8ZC-UuZh.mjs} +9 -22
- package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +1 -0
- package/dist/PluginHost-B8kxLOeb.mjs +245 -0
- package/dist/PluginHost-B8kxLOeb.mjs.map +1 -0
- package/dist/{Plugin-Dhx_ivqj.d.mts → PluginHost-DB4j8unu.d.mts} +110 -17
- package/dist/PluginLifecycle-AhQ46NIr.mjs +158 -0
- package/dist/PluginLifecycle-AhQ46NIr.mjs.map +1 -0
- package/dist/ServerHost-CtmAhEXn.mjs +168 -0
- package/dist/ServerHost-CtmAhEXn.mjs.map +1 -0
- package/dist/ServerHost-DYeh0KTk.d.mts +40 -0
- package/dist/bindViteEnv-BgFewlZ0.d.mts +11 -0
- package/dist/bindViteEnv-DWq5rftJ.mjs +88 -0
- package/dist/bindViteEnv-DWq5rftJ.mjs.map +1 -0
- package/dist/env-internal.index.d.mts +2 -0
- package/dist/env-internal.index.mjs +3 -0
- package/dist/hmr.index.d.mts +0 -1
- package/dist/hmr.index.mjs +1 -1
- package/dist/{cursor-BTU5bGoo.d.mts → index-Du71Xx69.d.mts} +253 -6
- package/dist/index.d.mts +19 -84
- package/dist/index.mjs +17 -37
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +186 -22
- package/dist/internal.index.mjs +714 -19
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +25 -70
- package/dist/node-internal.index.mjs +83 -297
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +3 -2
- package/dist/node.index.mjs +53 -200
- package/dist/node.index.mjs.map +1 -1
- package/dist/{phases-D_zQBXga.d.mts → phases-FTM69J3a.d.mts} +4 -4
- package/dist/plugin.index.d.mts +2 -2
- package/dist/plugin.index.mjs +2 -2
- package/dist/publishDefault-D3dAyzZ9.mjs +7 -0
- package/dist/publishDefault-D3dAyzZ9.mjs.map +1 -0
- package/dist/subscribers-Ce3SWy23.mjs +1913 -0
- package/dist/subscribers-Ce3SWy23.mjs.map +1 -0
- package/package.json +24 -17
- package/dist/Bus-BxPa-Uvp.mjs +0 -866
- package/dist/Bus-BxPa-Uvp.mjs.map +0 -1
- package/dist/HmrModuleHandler-DOAtah3j.mjs.map +0 -1
- package/dist/Pluggable-CwKbLui3.d.mts +0 -162
- package/dist/Pluggable-afy2H0Dr.mjs +0 -339
- package/dist/Pluggable-afy2H0Dr.mjs.map +0 -1
- package/dist/Plugin-CmNW5n6a.mjs +0 -122
- package/dist/Plugin-CmNW5n6a.mjs.map +0 -1
- package/dist/contextMenuLeaves-COzDYa6c.d.mts +0 -17
- package/dist/cursor-CgNBnJfu.mjs +0 -802
- package/dist/cursor-CgNBnJfu.mjs.map +0 -1
- package/dist/devChannel-CQU7hq7Q.mjs +0 -14
- package/dist/devChannel-CQU7hq7Q.mjs.map +0 -1
- package/dist/index-B_-EjyvS.d.mts +0 -135
- package/dist/publishDefault-BuSZPbjT.mjs +0 -14
- package/dist/publishDefault-BuSZPbjT.mjs.map +0 -1
- package/dist/slashRouteLeaves-k1hJnXDO.mjs +0 -31
- package/dist/slashRouteLeaves-k1hJnXDO.mjs.map +0 -1
- package/dist/subscribers-xGy7a7xR.mjs +0 -167
- package/dist/subscribers-xGy7a7xR.mjs.map +0 -1
- package/dist/types-BG0BimVr.d.mts +0 -13
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cursor-CgNBnJfu.mjs","names":[],"sources":["../src/stops/Notice.ts","../src/stops/NoticeCard.ts","../src/customId/routing.ts","../src/decorators/interactionRoutes.ts","../src/decorators/middleware.ts","../src/reply/ackLegality.ts","../src/reply/AckTrace.ts","../src/reply/responseReport.ts","../src/reply/translateSerialization.ts","../src/reply/serializeReply.ts","../src/gates/Gate.ts","../src/notices/index.ts","../src/gates/effects.ts","../src/gates/catalog/options.ts","../src/permissions/bits.ts","../src/gates/catalog/permissions.ts","../src/stops/Silence.ts","../src/pagination/cursor.ts"],"sourcesContent":["import type { RenderContext, ReplyResponse } from '@seedcord/types';\n\n/**\n * Base class for a user-facing refusal or a reported fault.\n *\n * Throw a `Notice` to stop a handler and reply to the user. The framework catches it at the controller\n * boundary and renders {@link Notice.render}, which always decides what the user sees. With `report`\n * false that render is all that happens. With `report` true the framework also logs the fault and\n * publishes it to the `handledException` bus. A raw, non-Notice throw shows the generic message.\n *\n * @example\n * ```ts\n * import { Notice, BuilderComponent, type RenderContext, type ReplyResponse } from '@seedcord/gateway';\n * import { TextDisplayBuilder } from '@discordjs/builders';\n *\n * // reading `.component` applies the configured bot color to the container accent\n * class TooPoorCard extends BuilderComponent<'container'> {\n * constructor(balance: number) {\n * super('container');\n * this.instance.addTextDisplayComponents(\n * new TextDisplayBuilder().setContent(`### Insufficient balance\\nYou need more than ${balance} coins.`)\n * );\n * }\n * }\n *\n * class TooPoor extends Notice {\n * constructor(private readonly balance: number) {\n * super(`balance ${balance} is below the cost`);\n * }\n *\n * render(_ctx: RenderContext): ReplyResponse {\n * return { components: [new TooPoorCard(this.balance).component] };\n * }\n * }\n *\n * // in a handler, throwing stops the handler and replies with render(ctx)\n * if (wallet.balance < cost) throw new TooPoor(wallet.balance);\n * ```\n */\nexport abstract class Notice extends Error {\n /**\n * Whether this denial is a reported fault. True also logs it and publishes it to the `handledException`\n * bus. The user always sees {@link Notice.render} either way.\n *\n * @defaultValue `false`\n */\n public report = false;\n\n /**\n * Whether the reply is ephemeral, seen only by the invoking user. Set it false for a refusal the\n * whole channel should see.\n *\n * @defaultValue `true`\n */\n public ephemeral = true;\n\n /**\n * A short one-line reason. When every arm of an {@link or} gate refuses and each one sets this, `or`\n * throws a refusal listing them under `You need to meet at least one of these:`. Write a verb phrase that\n * finishes that line, for example, \"be the bot owner\".\n */\n public summary?: string;\n\n protected constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n\n // Error sets name to 'Error', so stamp the concrete subclass name for logs and the fault report\n this.name = new.target.name;\n Error.captureStackTrace(this, this.constructor);\n }\n\n /**\n * Builds what the user sees. Called fresh each time the denial is shown, so the builders are new\n * and the bot color resolves at render time.\n */\n public abstract render(ctx: RenderContext): ReplyResponse;\n}\n","import { TextDisplayBuilder } from '@discordjs/builders';\n\nimport { BuilderComponent } from '#components/Component';\n\n// built fresh inside a Notice's render to back its ComponentsV2 reply\n// discord renders the ### line as an h3 with the description below\nexport class NoticeCard extends BuilderComponent<'container'> {\n public constructor(description: string, title = 'Cannot Proceed') {\n super('container');\n this.instance.addTextDisplayComponents(new TextDisplayBuilder().setContent(`### ${title}\\n${description}`));\n }\n}\n","import 'reflect-metadata';\n\nimport { decodeFor } from '@seedcord/custom-id';\nimport { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { ComponentDefsBrand } from '#src/decorators/brands';\nimport type { AnyCustomId, DecodedParams } from '@seedcord/custom-id';\nimport type { Promisable } from 'type-fest';\n\n/**\n * route decorators store a handler's customId defs here, so the component base can decode\n * against them at runtime.\n *\n * @internal\n */\n// storeComponentRoute writes this beside the route keys in metadataKeys.ts\nexport const ComponentDefsKey = Symbol.for('seedcord:custom-id:component-defs');\n\n/**\n * the phantom a component handler base carries. a route decorator constrains its argument to this, so\n * passing different defs to the decorator and the handler's generic is a compile error. never set at runtime.\n *\n * @internal\n */\nexport interface HasComponentDefs<Defs extends readonly AnyCustomId[]> {\n /** @internal */\n readonly [ComponentDefsBrand]?: Defs;\n}\n\n/** @internal */\nexport interface DecodedComponentRoute {\n prefix: string;\n params: Record<string, unknown>;\n}\n\n/**\n * throws `CustomIdHandlerRouteMissing` when the class has no stored defs. a drifted or corrupt wire\n * throws `StaleCustomId` or `InvalidCustomId`.\n *\n * @internal\n */\nexport function decodeComponentRoute(handlerClass: { name: string }, wire: string): DecodedComponentRoute {\n const defs = Reflect.getMetadata(ComponentDefsKey, handlerClass) as readonly AnyCustomId[] | undefined;\n if (!defs) throw new SeedcordError(SeedcordErrorCode.CustomIdHandlerRouteMissing, [handlerClass.name]);\n return decodeFor(defs, wire);\n}\n\n/** @internal */\nexport type SingleParams<Defs extends readonly AnyCustomId[]> = Defs extends readonly [infer One extends AnyCustomId]\n ? DecodedParams<One['shape']>\n : never;\n\n/** @internal */\nexport type MatchArms<Defs extends readonly AnyCustomId[], Ret> = {\n [Def in Defs[number] as Def['prefix']]: (params: DecodedParams<Def['shape']>) => Promisable<Ret>;\n};\n","import 'reflect-metadata';\n\nimport { ApplicationCommandType } from 'discord-api-types/v10';\n\nimport { ComponentDefsKey } from '#customId/routing';\nimport { InteractionMetadataKey, InteractionRouteKeys, InteractionKind } from '#src/metadataKeys';\n\nimport type { ContextMenuKind } from '#registries/ContextMenuRegistry';\nimport type { AnyCustomId } from '@seedcord/custom-id';\n\n// loose on purpose, each transport's typed decorators pass their asserted ctors straight in\nexport type RoutableConstructor = new (...args: never[]) => unknown;\n\nexport function areRoutes(routes: unknown): routes is string[] {\n return Array.isArray(routes) && routes.every((route) => typeof route === 'string');\n}\n\n// the build's manifest emitter reads the same pairs\nexport function interactionRoutesOf(constructor: RoutableConstructor): [InteractionKind, string[]][] {\n const pairs: [InteractionKind, string[]][] = [];\n for (const route of Object.values(InteractionKind)) {\n const meta: unknown = Reflect.getMetadata(InteractionRouteKeys[route], constructor);\n if (areRoutes(meta)) pairs.push([route, meta]);\n }\n return pairs;\n}\n\nexport function storeInteractionRoute(\n route: InteractionKind,\n routes: string | readonly string[],\n constructor: RoutableConstructor\n): void {\n const key = InteractionRouteKeys[route];\n const saved: unknown = Reflect.getMetadata(key, constructor);\n const existing = areRoutes(saved) ? saved : [];\n const toStore = typeof routes === 'string' ? [routes] : routes;\n Reflect.defineMetadata(key, [...existing, ...toStore], constructor);\n Reflect.defineMetadata(InteractionMetadataKey, true, constructor);\n}\n\nexport function storeComponentRoute(\n route: InteractionKind,\n defs: readonly AnyCustomId[],\n constructor: RoutableConstructor\n): void {\n storeInteractionRoute(\n route,\n defs.map((def) => def.prefix),\n constructor\n );\n Reflect.defineMetadata(ComponentDefsKey, defs, constructor);\n}\n\nexport function contextMenuRouteOf(kind: ContextMenuKind): InteractionKind {\n return kind === ApplicationCommandType.User ? InteractionKind.UserContextMenu : InteractionKind.MessageContextMenu;\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordTypeError } from '@seedcord/errors/internal';\n\nimport { InteractionMiddlewareMetadataKey } from '#src/metadataKeys';\n\nimport type { MiddlewareKind } from '#src/metadataKeys';\nimport type { MiddlewareKindsBrand } from './brands';\nimport type { Constructor, NonEmptyTuple } from 'type-fest';\n\n/** @internal */\nexport type AnyMiddlewareCtor = Constructor<unknown, never[]>;\n\n// core cannot import the transport's middleware base. the kinds arrive on a phantom brand.\ntype KindsOf<TCtor extends AnyMiddlewareCtor> =\n InstanceType<TCtor> extends { [MiddlewareKindsBrand]?: infer Kind extends MiddlewareKind } ? Kind : never;\n\ntype AssertMiddlewareKinds<Kinds extends MiddlewareKind, TCtor extends AnyMiddlewareCtor> = [Kinds] extends [\n KindsOf<TCtor>\n]\n ? [KindsOf<TCtor>] extends [Kinds]\n ? TCtor\n : Constructor<\n ['the middleware generic declares a kind that RegisterInteractionMiddleware omits', KindsOf<TCtor>]\n >\n : Constructor<['RegisterInteractionMiddleware lists a kind that the middleware generic omits', Kinds]>;\n\n/** Registration options for an interaction middleware. */\nexport interface InteractionMiddlewareOptions<Kinds extends NonEmptyTuple<MiddlewareKind>> {\n /**\n * Restrict this middleware to certain interaction kinds. The middleware's generic must list the same\n * kinds, or applying the decorator is a compile error. Omit it to run on every repliable kind.\n */\n readonly kinds?: Kinds;\n /**\n * Lower runs earlier. Two middleware sharing a priority run in registration order.\n *\n * @defaultValue 0\n */\n readonly priority?: number;\n}\n\n/** @internal */\nexport interface InteractionMiddlewareMetadata {\n readonly priority: number;\n readonly kinds?: readonly MiddlewareKind[];\n}\n\n/**\n * Registers an interaction middleware. It runs after the dispatcher builds the handler and before its\n * gates, over that handler's reply surface.\n *\n * @param options - The kinds this middleware runs on and its ordering.\n * @decorator\n *\n * @example\n * ```ts\n * \\@RegisterInteractionMiddleware({ kinds: [InteractionKind.Button], priority: 10 })\n * class Audit extends InteractionMiddleware<InteractionKind.Button> {\n * async execute() {\n * this.logger.info(this.dispatch.routeId);\n * }\n * }\n * ```\n *\n * @throws A **SeedcordTypeError** If `priority` is not a finite number.\n */\nexport function RegisterInteractionMiddleware<\n const Kinds extends NonEmptyTuple<MiddlewareKind> = NonEmptyTuple<MiddlewareKind>\n>(options: InteractionMiddlewareOptions<Kinds> = {}) {\n return function <TCtor extends AnyMiddlewareCtor>(ctor: AssertMiddlewareKinds<Kinds[number], TCtor>): void {\n const priority = Number(options.priority ?? 0);\n if (!Number.isFinite(priority))\n throw new SeedcordTypeError(SeedcordErrorCode.DecoratorInvalidMiddlewarePriority);\n if (options.kinds?.length === 0)\n throw new SeedcordTypeError(SeedcordErrorCode.DecoratorEmptyMiddlewareFilter, ['kinds']);\n\n const metadata: InteractionMiddlewareMetadata = {\n priority,\n ...(options.kinds && { kinds: options.kinds })\n };\n\n Reflect.defineMetadata(InteractionMiddlewareMetadataKey, metadata, ctor);\n };\n}\n\n/** @internal */\nexport function interactionMiddlewareMetaOf(constructor: AnyMiddlewareCtor): InteractionMiddlewareMetadata | undefined {\n const saved: unknown = Reflect.getMetadata(InteractionMiddlewareMetadataKey, constructor);\n return saved as InteractionMiddlewareMetadata | undefined;\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { AckTrace } from './AckTrace';\n\nexport type AckState = 'unacked' | 'deferred-reply' | 'deferred-update' | 'replied';\n\nexport type ReplyMethod =\n 'reply' | 'defer' | 'deferUpdate' | 'update' | 'followUp' | 'edit' | 'delete' | 'send' | 'showModal';\n\ninterface Illegal {\n // spliced into the message after \"was called when\"\n readonly reason: string;\n readonly alternative: string;\n}\n\nconst showModalReason: Illegal = {\n reason: 'this interaction was already acknowledged',\n alternative: 'showModal() must be the initial response.'\n};\n\nfunction deferReason(what: string): Illegal {\n return {\n reason: `this interaction was already acknowledged with ${what}`,\n alternative: 'A deferral can only be the initial response.'\n };\n}\n\n// a missing entry means legal in that state\nconst ILLEGAL: Record<Exclude<ReplyMethod, 'send'>, Partial<Record<AckState, Illegal>>> = {\n reply: {\n 'deferred-reply': {\n reason: 'this interaction was already deferred',\n alternative: 'Use edit() to fill the deferred reply or followUp() for a new message.'\n },\n 'deferred-update': {\n reason: 'this interaction was already acknowledged with a deferred update',\n alternative: 'Use followUp() for a new message.'\n },\n replied: {\n reason: 'this interaction was already replied to',\n alternative: 'Use followUp() for a new message or edit() to rewrite the reply.'\n }\n },\n defer: {\n 'deferred-reply': deferReason('a deferred reply'),\n 'deferred-update': deferReason('a deferred update'),\n replied: deferReason('a reply')\n },\n deferUpdate: {\n 'deferred-reply': deferReason('a deferred reply'),\n 'deferred-update': deferReason('a deferred update'),\n replied: deferReason('a reply')\n },\n update: {\n 'deferred-reply': {\n reason: 'this interaction was deferred as a reply',\n alternative: 'The placeholder is yours via edit(). update() rewrites a source message.'\n },\n replied: {\n reason: 'this interaction was already replied to',\n alternative: 'update() rewrites a source message. Use edit() or followUp() instead.'\n }\n },\n followUp: {\n unacked: {\n reason: 'this interaction has not been acknowledged yet',\n alternative: 'Ack first with reply() or defer().'\n }\n },\n edit: {\n unacked: {\n reason: 'nothing has been sent yet',\n alternative: 'Send with reply() or defer() first, then edit() rewrites it.'\n }\n },\n delete: {\n unacked: {\n reason: 'nothing has been sent yet',\n alternative: 'Acknowledge first with reply() or defer(), then delete() removes it.'\n }\n },\n showModal: {\n 'deferred-reply': showModalReason,\n 'deferred-update': showModalReason,\n replied: showModalReason\n }\n};\n\nconst SEND_TARGET: Record<AckState, 'reply' | 'edit' | 'followUp'> = {\n unacked: 'reply',\n 'deferred-reply': 'edit',\n 'deferred-update': 'followUp',\n replied: 'followUp'\n};\n\n// throws before any API call, and ackedBy becomes the throw cause with its stack pointing at the first ack\nexport function checkAckLegality(method: ReplyMethod, state: AckState, routeId: string, ackedBy?: AckTrace): void {\n if (method === 'send') return;\n const illegal = ILLEGAL[method][state];\n if (!illegal) return;\n throw new SeedcordError(\n SeedcordErrorCode.ReplyIllegalAckState,\n [method, illegal.reason, illegal.alternative, routeId],\n { cause: ackedBy }\n );\n}\n\nexport function sendTarget(state: AckState): 'reply' | 'edit' | 'followUp' {\n return SEND_TARGET[state];\n}\n","import type { ReplyMethod } from './ackLegality';\n\n// the first ack's stack, passed as the throw cause so a double-ack points to both lines\nexport class AckTrace extends Error {\n public constructor(method: ReplyMethod) {\n super(`${method}() acknowledged this interaction`);\n this.name = 'AckTrace';\n }\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\n\nimport { asError } from '#stops/asError';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { DispatchContext } from '#src/dispatch/DispatchContext';\nimport type { Bus } from '#subscribers/Bus';\nimport type { ReplyMethod } from './ackLegality';\n\nlet replyLogger: Logger | undefined;\nfunction logger(): Logger {\n replyLogger ??= new Logger('Reply', { channel: 'interactions' });\n return replyLogger;\n}\n\nexport interface ReplyTelemetry {\n readonly bus: Bus;\n readonly dispatch: DispatchContext;\n readonly interactionId: string;\n}\n\n// the reply verbs plus the autocomplete choices callback, which runs outside the ack state machine\nexport type WriteMethod = ReplyMethod | 'respond';\n\ninterface WriteStart {\n readonly method: WriteMethod;\n // performance.now() captured before the write began\n readonly startedAt: number;\n}\n\ninterface SentReport extends WriteStart {\n readonly outcome: 'sent';\n readonly messageId: string | null;\n}\n\ninterface FailedReport extends WriteStart {\n readonly outcome: 'failed';\n readonly error: Error;\n}\n\nexport type ResponseReport = SentReport | FailedReport;\n\n/** @internal */\nexport function publishResponse(telemetry: ReplyTelemetry, report: ResponseReport): void {\n const durationMs = performance.now() - report.startedAt;\n const { routeId } = telemetry.dispatch;\n const write = {\n dispatchId: telemetry.dispatch.id,\n routeId,\n interactionId: telemetry.interactionId,\n method: report.method,\n durationMs\n };\n telemetry.bus[PublishDefault](\n 'responseAttempted',\n report.outcome === 'failed'\n ? { ...write, outcome: 'failed', messageId: null, error: report.error }\n : { ...write, outcome: 'sent', messageId: report.messageId }\n );\n\n // after publish so a throwing sink doesn't reach the publish above\n logger().trace(\n `${paint.sky.bold(routeId)} ${report.method} ${report.outcome} ${paint.mute('in')} ${Math.round(durationMs)}ms`\n );\n}\n\nexport async function reportedWrite<Result>(\n telemetry: ReplyTelemetry,\n method: WriteMethod,\n write: () => Promise<Result>\n): Promise<Result> {\n const startedAt = performance.now();\n const result = await attemptWrite(telemetry, method, startedAt, write);\n publishResponse(telemetry, { method, startedAt, outcome: 'sent', messageId: null });\n return result;\n}\n\n// publishes the failed arm when write() throws, since the caller's success report sits after the write and a throw skips it\nexport async function attemptWrite<Result>(\n telemetry: ReplyTelemetry,\n method: WriteMethod,\n startedAt: number,\n write: () => Promise<Result>\n): Promise<Result> {\n try {\n return await write();\n } catch (caught) {\n const error = asError(caught);\n publishResponse(telemetry, { method, startedAt, outcome: 'failed', error });\n // rethrown raw\n throw caught;\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// shapeshift aggregate errors nest, so the cap bounds the recursion\nconst MAX_DEPTH = 8;\n\ninterface ShapeshiftLike {\n readonly message?: unknown;\n readonly errors?: unknown;\n readonly constraint?: unknown;\n readonly validator?: unknown;\n readonly expected?: unknown;\n readonly given?: unknown;\n}\n\nconst OPERATOR_WORD: Record<string, string> = {\n '<=': 'at most',\n '>=': 'at least',\n '<': 'fewer than',\n '>': 'more than'\n};\n\nconst KIND_WORD: Record<string, string> = {\n string: 'a string',\n number: 'a number',\n boolean: 'a boolean',\n bigint: 'a bigint',\n array: 'a list',\n tuple: 'a list',\n date: 'a date',\n object: 'an object'\n};\n\nfunction asRecord(value: unknown): ShapeshiftLike | null {\n return typeof value === 'object' && value !== null ? value : null;\n}\n\nfunction firstLine(message: unknown): string {\n if (typeof message !== 'string') return 'unknown validation failure';\n return message.split('\\n')[0] ?? message;\n}\n\n// strings over this limit render as a character count\nconst GIVEN_PREVIEW_LIMIT = 40;\n\nfunction gotPhrase(given: unknown): string {\n if (given === undefined) return 'nothing';\n if (given === null) return 'null';\n if (typeof given === 'string') {\n if (given.length === 0) return 'an empty string';\n if (given.length > GIVEN_PREVIEW_LIMIT) return `${given.length} characters`;\n return JSON.stringify(given);\n }\n if (Array.isArray(given)) return given.length === 0 ? 'an empty list' : `${given.length} items`;\n if (typeof given === 'number' || typeof given === 'bigint' || typeof given === 'boolean') return String(given);\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a null-prototype value has no constructor at runtime\n if (typeof given === 'object') return given.constructor?.name ?? 'an object';\n return typeof given;\n}\n\n// shapeshift constraint strings take the form 'expected.length >= 1' or 'expected <= 16777215'\nfunction needPhrase(expected: string, given: unknown): string | null {\n const bounds = [...expected.matchAll(/expected(\\.length)?\\s*(<=|>=|<|>)\\s*(\\d+)/g)];\n if (bounds.length === 0) return null;\n const phrases = bounds.map(([, lengthPart, operator, limit]) => {\n const word = OPERATOR_WORD[operator ?? ''] ?? operator ?? '';\n if (!lengthPart) return `${word} ${limit}`;\n const unit = typeof given === 'string' ? 'character' : 'item';\n return `${word} ${limit} ${Number(limit) === 1 ? unit : `${unit}s`}`;\n });\n return phrases.join(' and ');\n}\n\nfunction instanceName(expected: unknown): string {\n if (typeof expected === 'function' && expected.name) return expected.name;\n return 'specific class instance';\n}\n\nfunction describeLeaf(record: ShapeshiftLike): string {\n if (typeof record.constraint === 'string') {\n const need = typeof record.expected === 'string' ? needPhrase(record.expected, record.given) : null;\n if (need) return `need ${need}, got ${gotPhrase(record.given)}`;\n return `${firstLine(record.message)}${'given' in record ? ` (got ${gotPhrase(record.given)})` : ''}`;\n }\n if (typeof record.expected === 'function') {\n return `need a ${instanceName(record.expected)}, got ${gotPhrase(record.given)}`;\n }\n if (typeof record.validator === 'string') {\n const kind = KIND_WORD[record.validator.replace(/^s\\./, '').replace(/\\(.*$/, '')];\n if (kind) return `need ${kind}, got ${gotPhrase(record.given)}`;\n return `${firstLine(record.message)}, got ${gotPhrase(record.given)}`;\n }\n return firstLine(record.message);\n}\n\nfunction describeUnion(branches: unknown[], depth: number): string {\n const records: ShapeshiftLike[] = [];\n for (const branch of branches) {\n const record = asRecord(branch);\n if (record) records.push(record);\n }\n const bounded = records.find((record) => typeof record.constraint === 'string');\n if (bounded) return describeLeaf(bounded);\n\n const names: string[] = [];\n let allInstances = records.length > 0;\n for (const record of records) {\n if (typeof record.expected === 'function') names.push(instanceName(record.expected));\n else allInstances = false;\n }\n if (allInstances) {\n return `need a ${[...new Set(names)].join(' or ')}, got ${gotPhrase(records[0]?.given)}`;\n }\n return detailOf(branches[0], depth + 1);\n}\n\n// shapeshift nests errors as [key, inner] tuples\nfunction detailOf(value: unknown, depth: number): string {\n const record = asRecord(value);\n if (!record || depth >= MAX_DEPTH) return firstLine(record?.message);\n\n if (Array.isArray(record.errors) && record.errors.length > 0) {\n const errors = record.errors as unknown[];\n const [first] = errors;\n if (Array.isArray(first)) {\n const [key, inner] = first as [unknown, unknown];\n return `${String(key)}: ${detailOf(inner, depth + 1)}`;\n }\n return describeUnion(errors, depth);\n }\n\n return describeLeaf(record);\n}\n\n// a shapeshift throw becomes an actionable message with the cause kept, while anything else contributes its own message as the detail\nexport function translateSerializationError(\n error: unknown,\n componentClass: string,\n index: number,\n routeId: string\n): SeedcordError<SeedcordErrorCode.ReplyComponentSerialization> {\n const detail = detailOf(error, 0);\n return new SeedcordError(SeedcordErrorCode.ReplyComponentSerialization, [componentClass, index, detail, routeId], {\n cause: error\n });\n}\n","import { TextDisplayBuilder } from '@discordjs/builders';\n\nimport { translateSerializationError } from './translateSerialization';\n\nimport type { ReplyResponse } from '@seedcord/types';\nimport type { APIMessageTopLevelComponent } from 'discord-api-types/v10';\n\nexport interface SerializedReply<TNative = never> {\n readonly components: APIMessageTopLevelComponent[];\n readonly allowedMentions?: ReplyResponse['allowedMentions'];\n readonly files?: ReplyResponse<TNative>['files'];\n}\n\nexport function serializeReply<TNative>(\n response: ReplyResponse<TNative> | string,\n routeId: string\n): SerializedReply<TNative> {\n if (typeof response === 'string') {\n try {\n return { components: [new TextDisplayBuilder().setContent(response).toJSON()] };\n } catch (error) {\n throw translateSerializationError(error, 'TextDisplayBuilder', 0, routeId);\n }\n }\n\n const components = response.components.map((component, index) => {\n try {\n return component.toJSON();\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a null-prototype value has no constructor at runtime\n const name = component.constructor?.name;\n throw translateSerializationError(error, !name || name === 'Object' ? 'component' : name, index, routeId);\n }\n });\n\n return {\n components,\n ...(response.allowedMentions && { allowedMentions: response.allowedMentions }),\n ...(response.files && { files: response.files })\n };\n}\n","import type { CoreBase } from '#interfaces/CoreBase';\nimport type { DispatchContext } from '#src/dispatch/DispatchContext';\n\n/**\n * The transport-agnostic gate context, the scalar identity of the acting user resolvable from any\n * transport's payload. Every field except `core` is nullable, since an event or DM may carry no\n * guild or member. Each transport extends it with its own arm. For example, the gateway adds the djs\n * `user`/`guild`/`member` objects and the live interaction or event payload.\n *\n * @example\n * ```ts\n * // a gate reading only the base fields fits any handler on any transport\n * const NeedsGuild = defineGate('NeedsGuild', (ctx: GateContextBase) => {\n * if (!ctx.guildId) throw new NotInGuild();\n * });\n * ```\n */\nexport interface GateContextBase {\n /** Gates read the config and the rate limiter through it. */\n readonly core: CoreBase;\n /** The acting user's id, or null on an event that carries none. */\n readonly userId: string | null;\n /** The guild id, or null outside a guild. */\n readonly guildId: string | null;\n /** The channel id, or null when the source carries none. */\n readonly channelId: string | null;\n /**\n * The member's role ids without the everyone role, the shape a raw interaction payload\n * contains. Empty outside a guild or when the source contains none.\n */\n readonly memberRoleIds: readonly string[];\n /**\n * The member's permission bits, or null outside a guild. On an interaction these are the\n * channel-scoped permissions the payload contains, and on a gateway event they're the member's guild-level ones.\n */\n readonly memberPermissions: bigint | null;\n /**\n * The bot's permissions in the invoked channel, the wire `app_permissions` value. Null on gateway events.\n */\n readonly appPermissions: bigint | null;\n /**\n * The handler's own route, as `kind:route` (`slash:daily`, `button:confirm`). A handler registered\n * on several reports the one this dispatch matched. Null when the handler declares none, which\n * covers a plain event handler, the unhandled default, and a gate run outside a handler.\n */\n readonly declaredRoute: string | null;\n /**\n * The bag for this dispatch, the same instance the handler and its middleware hold. Every middleware\n * runs before the first gate. A gate therefore reads what a middleware wrote.\n */\n readonly dispatch: DispatchContext;\n}\n\n/**\n * The context the guild-scoped permission gates require. It adds the base permission sets computed from\n * roles alone, which only the gateway transport can provide from its cache. The http transport's context\n * does not define these fields.\n *\n * @example\n * ```ts\n * // in:'guild' returns a Gate<GuildPermissionsContext>, which fits only a gateway handler\n * \\@Gated(RequirePermissions([PermissionFlagsBits.BanMembers], { in: 'guild' }))\n * ```\n */\nexport interface GuildPermissionsContext extends GateContextBase {\n /**\n * The member's base permission set, roles only, matching djs `GuildMember.permissions`. Null when the\n * member is unavailable.\n */\n readonly memberGuildPermissions: bigint | null;\n /**\n * The bot's base permission set from the transport's cache. Null when the cached member is unavailable.\n */\n readonly appGuildPermissions: bigint | null;\n}\n\n// phantom brand rejects a bare check function or plain object where a Gate is expected\ndeclare const GateBrand: unique symbol;\n\n/**\n * A precondition attached to a handler. It refuses by throwing a `Notice` (a reply) or a `Silence`\n * (a drop with no reply), and passes by returning. `Ctx` is the context it requires, so a gate that reads\n * a field absent on a handler's context is rejected at compile time. `Name` is captured as a literal so\n * a mismatch error can name the gate. Build one with {@link defineGate} or {@link defineEffectGate}.\n *\n * @typeParam Ctx - The context the gate requires. A field missing from it is a compile error.\n * @typeParam Name - The gate's literal name, named in a mismatch error.\n *\n * @example\n * ```ts\n * // a factory returning a typed, agnostic gate\n * function MyGate(): Gate<GateContextBase, 'MyGate'> {\n * return defineGate('MyGate', (ctx) => {\n * if (shouldRefuse(ctx)) throw new MyNotice();\n * });\n * }\n * ```\n *\n * @see {@link defineGate}\n */\nexport interface Gate<Ctx extends GateContextBase = GateContextBase, Name extends string = string> {\n /** The gate's name, surfaced in a mismatch error and joined by the combinators. */\n readonly name: Name;\n /** Reads the context and refuses by throwing a Notice or a Silence, or passes by resolving. */\n check(ctx: Ctx): Promise<void>;\n\n /** @internal */\n readonly [GateBrand]: true;\n}\n\n/**\n * A gate that carries a side effect, split so the effect fires only once the gate is the one that\n * let the request through. `check` peeks and refuses, while `commit` applies the effect after the whole\n * gate set passes. {@link Cooldown} is the catalog gate that returns one. Build one with\n * {@link defineEffectGate}.\n *\n * @typeParam Ctx - The context both `check` and `commit` require. A field missing from it fails to compile.\n * @typeParam Name - The gate's literal name, shown when a mismatch error names the gate.\n *\n * @example\n * ```ts\n * // a factory returning a typed effect gate\n * function MyEffectGate(): EffectGate<GateContextBase, 'MyEffectGate'> {\n * return defineEffectGate(\n * 'MyEffectGate',\n * (ctx) => {\n * if (shouldRefuse(ctx)) throw new MyNotice();\n * },\n * (ctx) => {\n * applyMyEffect(ctx);\n * }\n * );\n * }\n * ```\n *\n * @see {@link defineEffectGate}\n */\nexport interface EffectGate<Ctx extends GateContextBase = GateContextBase, Name extends string = string> extends Gate<\n Ctx,\n Name\n> {\n /** Applies the side effect, run only after the whole gate set passes. */\n commit(ctx: Ctx): Promise<void>;\n}\n\n/**\n * Builds a {@link Gate} from a check. The check refuses by throwing a Notice or a Silence and passes\n * by returning. The required context is inferred from `ctx`, so annotate it as narrowly as the fields\n * the check reads. No annotation defaults to {@link GateContextBase}, an agnostic gate that fits every\n * handler.\n *\n * @typeParam Name - The gate's name, captured as a literal for mismatch errors.\n * @typeParam Ctx - The context the check reads, inferred from the `ctx` annotation.\n *\n * @param name - The gate's name, used in mismatch errors.\n * @param fn - The check, which refuses by throwing a Notice or a Silence and passes by returning.\n *\n * @see {@link defineEffectGate}\n * @see the `@Gated` decorator from your transport package\n *\n * @example\n * ```ts\n * // a gate factory that takes its own option and closes over it\n * function MinRoles(min: number): Gate<GateContextBase, 'MinRoles'> {\n * return defineGate('MinRoles', (ctx) => {\n * if (ctx.memberRoleIds.length < min) throw new NotEnoughRoles(min);\n * });\n * }\n * ```\n *\n * @example\n * ```ts\n * // agnostic gate (no ctx annotation): fits any handler\n * const Owner = defineGate('owner', (ctx) => {\n * if (!ctx.userId) throw new NotOwner();\n * });\n * ```\n */\nexport function defineGate<const Name extends string, Ctx extends GateContextBase = GateContextBase>(\n name: Name,\n fn: (ctx: Ctx) => void | Promise<void>\n): Gate<Ctx, Name> {\n return { name, check: async (ctx) => fn(ctx) } as Gate<Ctx, Name>;\n}\n\n/**\n * Builds an {@link EffectGate} from a `check` and a `commit`. `check` peeks and refuses by throwing, and\n * `commit` applies the side effect, running only once the whole gate set passes so a later refusal never\n * commits. In an `or`, a refusing arm's queued commit is rolled back. This is how {@link Cooldown} peeks\n * in `check` and charges the slot in `commit`.\n *\n * @typeParam Name - The gate's literal name, used to name a mismatch error.\n * @typeParam Ctx - The context both `check` and `commit` read, inferred from the `ctx` annotation.\n *\n * @param name - The gate's name, used in mismatch errors.\n * @param check - Peeks and refuses by throwing, without applying the side effect.\n * @param commit - Applies the side effect, running only after the whole gate set passes.\n *\n * @see {@link defineGate}\n * @see the `@Gated` decorator from your transport package\n * @see {@link Cooldown}\n *\n * @example\n * ```ts\n * // an effect-gate factory that takes its own limit and closes over it\n * function UsesPerDay(max: number): EffectGate<GateContextBase, 'UsesPerDay'> {\n * return defineEffectGate(\n * 'UsesPerDay',\n * (ctx) => {\n * // peek and refuse by throwing. do not mutate yet\n * if (usedToday(ctx.userId) >= max) throw new OutOfUsesNotice(max);\n * },\n * (ctx) => {\n * // runs only after the whole gate set passes\n * recordUse(ctx.userId);\n * }\n * );\n * }\n * ```\n */\nexport function defineEffectGate<const Name extends string, Ctx extends GateContextBase = GateContextBase>(\n name: Name,\n check: (ctx: Ctx) => void | Promise<void>,\n commit: (ctx: Ctx) => void | Promise<void>\n): EffectGate<Ctx, Name> {\n return {\n name,\n check: async (ctx) => check(ctx),\n commit: async (ctx) => commit(ctx)\n } as EffectGate<Ctx, Name>;\n}\n\n/**\n * The context a {@link Gate} requires.\n *\n * @typeParam TGate - The gate type to read the required context from.\n *\n * @example\n * ```ts\n * type OwnerCtx = RequiredOf<ReturnType<typeof OwnerOnly>>; // GateContextBase\n * ```\n */\nexport type RequiredOf<TGate> = TGate extends Gate<infer Ctx> ? Ctx : never;\n","import { toEpochSeconds } from '@seedcord/utils';\n\nimport { Notice } from '#stops/Notice';\nimport { NoticeCard } from '#stops/NoticeCard';\n\nimport type { EpochMs, ReplyResponse } from '@seedcord/types';\n\nexport abstract class GateNotice extends Notice {\n public render(): ReplyResponse {\n return { components: [new NoticeCard(this.message).component] };\n }\n}\n\nexport class NotOwner extends GateNotice {\n public constructor(message = 'Only the bot owner can use this.') {\n super(message);\n this.summary = 'be the bot owner';\n }\n}\n\nexport class NotInGuild extends GateNotice {\n public constructor(message = 'This can only be used in a server.') {\n super(message);\n this.summary = 'be in a server';\n }\n}\n\nexport class NotInDm extends GateNotice {\n public constructor(message = 'This can only be used in a direct message.') {\n super(message);\n this.summary = 'be in a direct message';\n }\n}\n\nfunction retryAt(resetAt: EpochMs): string {\n return `<t:${toEpochSeconds(resetAt)}:R>`;\n}\n\nexport class OnCooldown extends GateNotice {\n public constructor(\n public readonly resetAt: EpochMs,\n message?: string\n ) {\n super(message ?? `You are doing that too fast. Try again ${retryAt(resetAt)}.`);\n this.summary = `try again ${retryAt(resetAt)}`;\n }\n}\n\nexport class NotAllowed extends Notice {\n public constructor() {\n super('not allowed');\n }\n\n public render(): ReplyResponse {\n return { components: [new NoticeCard('You are not allowed to use this command.').component] };\n }\n}\n\nexport class NeedsAny extends Notice {\n private readonly summaries: readonly string[];\n\n public constructor(summaries: readonly string[]) {\n super('not allowed');\n // two arms can refuse with the same summary\n this.summaries = [...new Set(summaries)];\n }\n\n public render(): ReplyResponse {\n const bullets = this.summaries.map((summary) => `• ${summary}`).join('\\n');\n return { components: [new NoticeCard(`You need to meet at least one of these:\\n${bullets}`).component] };\n }\n}\n\nexport class MissingRole extends GateNotice {\n public constructor(message: string | undefined, roleId: string | null) {\n super(\n message ?? (roleId ? `You need the <@&${roleId}> role to use this.` : 'You do not have the required role.')\n );\n this.summary = roleId ? `hold the <@&${roleId}> role` : 'hold the required role';\n }\n}\n\nexport class MissingPermissions extends Notice {\n private readonly customLead: string | undefined;\n\n public constructor(\n message: string | undefined,\n private readonly subject: string | null,\n private readonly missingPerms: readonly string[]\n ) {\n super(message ?? 'A required permission is missing.');\n this.customLead = message;\n const names = missingPerms.join(', ');\n this.summary = subject === null ? `hold ${names}` : `${subject} needs ${names}`;\n }\n\n public render(): ReplyResponse {\n const bullets = this.missingPerms.map((perm) => `• ${perm}`).join('\\n');\n const lead =\n this.customLead ??\n `${this.subject === null ? 'You are' : `${this.subject} is`} missing the following permission entries:`;\n return {\n components: [new NoticeCard(`${lead}\\n\\n${bullets}`).component]\n };\n }\n}\n\nexport class HasDangerousPermissions extends Notice {\n private readonly customLead: string | undefined;\n\n public constructor(\n message: string | undefined,\n private readonly subject: string,\n private readonly dangerousPerms: readonly string[]\n ) {\n super(message ?? 'A dangerous permission is enabled.');\n this.customLead = message;\n this.summary = `${subject} must not hold ${dangerousPerms.join(', ')}`;\n }\n\n public render(): ReplyResponse {\n const bullets = this.dangerousPerms.map((perm) => `• ${perm}`).join('\\n');\n const lead =\n this.customLead ?? `${this.subject} has the following permission entries that must not be enabled:`;\n return { components: [new NoticeCard(`${lead}\\n\\n${bullets}`).component] };\n }\n}\n","import type { EffectGate, Gate, GateContextBase } from './Gate';\n\ntype CommitFn = () => Promise<void>;\n\n// commits wait here until the whole gate set passes, one queue per request. the key is the context\n// itself and the map is weak, so an entry goes when its request does\nconst commitQueues = new WeakMap<GateContextBase, CommitFn[]>();\n\nfunction isEffectGate(gate: Gate<GateContextBase>): gate is EffectGate<GateContextBase> {\n return typeof (gate as Partial<EffectGate<GateContextBase>>).commit === 'function';\n}\n\n// a refused effect gate never commits. combinators call this too\nexport async function runCheck(gate: Gate<GateContextBase>, ctx: GateContextBase): Promise<void> {\n await gate.check(ctx);\n if (!isEffectGate(gate)) return;\n\n let queue = commitQueues.get(ctx);\n if (!queue) {\n queue = [];\n commitQueues.set(ctx, queue);\n }\n queue.push(() => gate.commit(ctx));\n}\n\nexport async function runCommits(ctx: GateContextBase): Promise<void> {\n const queue = commitQueues.get(ctx);\n if (!queue) return;\n for (const commit of queue) await commit();\n}\n\n// the runner calls this from a finally, refused run or not\nexport function discardCommits(ctx: GateContextBase): void {\n commitQueues.delete(ctx);\n}\n\n// an `or` arm takes this mark before it runs\nexport function markCommits(ctx: GateContextBase): number {\n return commitQueues.get(ctx)?.length ?? 0;\n}\n\n// and rolls back to it when the arm refuses, leaving no commit behind\nexport function rollbackCommits(ctx: GateContextBase, mark: number): void {\n const queue = commitQueues.get(ctx);\n if (queue && queue.length > mark) queue.length = mark;\n}\n","import type { Notice } from '#stops/Notice';\n\n/**\n * The override a catalog gate accepts. A one-line `message` reword or a full `notice` replacement. Accepted by\n * the agnostic catalog gates and, nested per refusal, by {@link RequireRole}. {@link RequirePermissions} and\n * {@link RequireBotPermissions} take `RequirePermissionsOptions` instead.\n *\n * @example\n * ```ts\n * // reword the default refusal\n * OwnerOnly({ message: 'Bot owners only.' });\n * ```\n *\n * @example\n * ```ts\n * // replace the refusal entirely, for example a translated Notice\n * GuildOnly({ notice: new MyTranslatedNotice() });\n * ```\n */\nexport interface GateNoticeOptions {\n /** Reword the default refusal, keeping its embed styling. */\n message?: string;\n /** Replace the default refusal Notice entirely, for full control or a translated copy. */\n notice?: Notice;\n}\n\nexport function pickNotice(options: GateNoticeOptions | undefined, makeDefault: (message?: string) => Notice): Notice {\n return options?.notice ?? makeDefault(options?.message);\n}\n","import { prettify } from '@seedcord/utils';\nimport { PermissionFlagsBits } from 'discord-api-types/v10';\n\n/**\n * The permission flag bits a caller or the bot must hold. Pass the `PermissionFlagsBits` values from\n * `discord-api-types/v10` (or `discord.js`).\n */\nexport type PermissionScope = readonly bigint[];\n\n// permission bit to its prettified human-readable name\nexport const PermissionNames = new Map<bigint, string>(\n Object.entries(PermissionFlagsBits).map(([key, bit]) => [bit, prettify(key)])\n);\n\nconst ADMINISTRATOR = PermissionFlagsBits.Administrator;\n\nfunction nameOf(bit: bigint): string {\n return PermissionNames.get(bit) ?? String(bit);\n}\n\n// discord treats the administrator bit as granting every permission\nfunction holds(held: bigint, bit: bigint): boolean {\n return (held & ADMINISTRATOR) === ADMINISTRATOR || (held & bit) === bit;\n}\n\n/** @internal */\nexport function hasAll(held: bigint, scope: PermissionScope): boolean {\n return scope.every((bit) => holds(held, bit));\n}\n\n/** @internal only reached once hasAll failed, so the administrator bit is absent */\nexport function missingNames(held: bigint, scope: PermissionScope): string[] {\n return scope.reduce<string[]>((names, bit) => {\n if ((held & bit) !== bit) names.push(nameOf(bit));\n return names;\n }, []);\n}\n\n/** @internal */\nexport function presentNames(held: bigint, scope: PermissionScope): string[] {\n return scope.reduce<string[]>((names, bit) => {\n if (holds(held, bit)) names.push(nameOf(bit));\n return names;\n }, []);\n}\n","import { defineGate } from '#gates/Gate';\nimport { MissingPermissions, MissingRole, NotInGuild } from '#notices/index';\nimport { hasAll, missingNames } from '#src/permissions/bits';\n\nimport { pickNotice } from './options';\n\nimport type { Gate, GateContextBase, GuildPermissionsContext } from '#gates/Gate';\nimport type { PermissionScope } from '#src/permissions/bits';\nimport type { GateNoticeOptions } from './options';\n\nexport type { PermissionScope } from '#src/permissions/bits';\nexport { PermissionNames } from '#src/permissions/bits';\n\ninterface PermCheckKind {\n unresolved: string;\n subject: string | null;\n}\n\nconst MEMBER_CHECK: PermCheckKind = {\n unresolved: 'Your server member data could not be resolved. Try again.',\n subject: null\n};\nconst BOT_CHECK: PermCheckKind = {\n unresolved: \"The bot's permissions could not be resolved. Try again.\",\n subject: 'The bot'\n};\n\nfunction refusePermissions(\n held: bigint | null,\n scope: PermissionScope,\n options: RequirePermissionsOptions | undefined,\n guildId: string | null,\n kind: PermCheckKind\n): void {\n if (held === null) {\n throw pickNotice(\n options?.notInGuild,\n (message) => new NotInGuild(message ?? (guildId ? kind.unresolved : undefined))\n );\n }\n if (hasAll(held, scope)) return;\n throw pickNotice(\n options?.missing,\n (message) => new MissingPermissions(message, kind.subject, missingNames(held, scope))\n );\n}\n\n/**\n * Options for {@link RequirePermissions} and {@link RequireBotPermissions}.\n */\nexport interface RequirePermissionsOptions {\n /**\n * The set the check reads. `'channel'` reads the effective channel set the payload carries, what\n * Discord enforces in the invoked channel. `'guild'` reads the base set from roles, available on the\n * gateway transport only.\n *\n * @defaultValue `'channel'`\n */\n in?: 'channel' | 'guild';\n /** Reword or replace the refusal shown when the set is unavailable (outside a guild, or unresolved inside one). */\n notInGuild?: GateNoticeOptions;\n /** Reword or replace the refusal shown when a permission is missing. */\n missing?: GateNoticeOptions;\n}\n\n/**\n * Options for {@link RequireRole}.\n */\nexport interface RequireRoleOptions {\n /** Reword or replace the refusal shown outside a guild. */\n notInGuild?: GateNoticeOptions;\n /** Reword or replace the refusal shown when the caller lacks the role. */\n missingRole?: GateNoticeOptions;\n}\n\n/**\n * Requires the caller to hold every permission in `scope`. Refuses outside a guild first, then when a\n * permission is missing. The Administrator bit passes any scope.\n *\n * The default `'channel'` scope reads the effective channel set the payload carries, what Discord enforces\n * in the invoked channel. The `'guild'` scope reads the base set from roles and requires a\n * {@link GuildPermissionsContext}, so it fits a gateway handler only.\n *\n * @param scope - The permission flag bits the caller must all hold.\n * @param options - Pick the scope with `in`, and override each refusal with `notInGuild` or `missing`.\n *\n * @see the `@Gated` decorator from your transport package\n *\n * @example\n * ```ts\n * import { PermissionFlagsBits } from 'discord-api-types/v10';\n *\n * \\@Gated(RequirePermissions([PermissionFlagsBits.BanMembers]))\n * \\@SlashRoute('ban')\n * class BanHandler extends SlashHandler<'ban'> {\n * async execute() {\n * // ...\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * // the base set from roles, on a gateway handler\n * RequirePermissions([PermissionFlagsBits.BanMembers], { in: 'guild' });\n * ```\n */\nexport function RequirePermissions(\n scope: PermissionScope,\n options: RequirePermissionsOptions & { in: 'guild' }\n): Gate<GuildPermissionsContext, 'RequirePermissions'>;\nexport function RequirePermissions(\n scope: PermissionScope,\n options?: RequirePermissionsOptions & { in?: 'channel' }\n): Gate<GateContextBase, 'RequirePermissions'>;\nexport function RequirePermissions(\n scope: PermissionScope,\n options?: RequirePermissionsOptions\n): Gate<GuildPermissionsContext, 'RequirePermissions'> | Gate<GateContextBase, 'RequirePermissions'> {\n if (options?.in === 'guild') {\n return defineGate('RequirePermissions', (ctx: GuildPermissionsContext) => {\n refusePermissions(ctx.memberGuildPermissions, scope, options, ctx.guildId, MEMBER_CHECK);\n });\n }\n return defineGate('RequirePermissions', (ctx: GateContextBase) => {\n refusePermissions(ctx.memberPermissions, scope, options, ctx.guildId, MEMBER_CHECK);\n });\n}\n\n/**\n * Requires the bot to hold every permission in `scope`. Refuses outside a guild first, then when a\n * permission is missing. The Administrator bit passes any scope.\n *\n * The default `'channel'` scope reads the bot's effective channel set (wire `app_permissions`), what\n * Discord enforces in the invoked channel. The `'guild'` scope reads the bot's base set from roles and\n * requires a {@link GuildPermissionsContext}, fitting a gateway handler only. `app_permissions` is\n * absent on gateway events, the channel scope refuses there as unresolved, use `in: 'guild'` on an\n * event handler.\n *\n * @param scope - The permission flag bits the bot must all hold.\n * @param options - Pick the scope with `in`, and override each refusal with `notInGuild` or `missing`.\n *\n * @see the `@Gated` decorator from your transport package\n *\n * @example\n * ```ts\n * import { PermissionFlagsBits } from 'discord-api-types/v10';\n *\n * \\@Gated(RequireBotPermissions([PermissionFlagsBits.ManageMessages]))\n * \\@SlashRoute('purge')\n * class PurgeHandler extends SlashHandler<'purge'> {\n * async execute() {\n * // ...\n * }\n * }\n * ```\n */\nexport function RequireBotPermissions(\n scope: PermissionScope,\n options: RequirePermissionsOptions & { in: 'guild' }\n): Gate<GuildPermissionsContext, 'RequireBotPermissions'>;\nexport function RequireBotPermissions(\n scope: PermissionScope,\n options?: RequirePermissionsOptions & { in?: 'channel' }\n): Gate<GateContextBase, 'RequireBotPermissions'>;\nexport function RequireBotPermissions(\n scope: PermissionScope,\n options?: RequirePermissionsOptions\n): Gate<GuildPermissionsContext, 'RequireBotPermissions'> | Gate<GateContextBase, 'RequireBotPermissions'> {\n if (options?.in === 'guild') {\n return defineGate('RequireBotPermissions', (ctx: GuildPermissionsContext) => {\n refusePermissions(ctx.appGuildPermissions, scope, options, ctx.guildId, BOT_CHECK);\n });\n }\n return defineGate('RequireBotPermissions', (ctx: GateContextBase) => {\n refusePermissions(ctx.appPermissions, scope, options, ctx.guildId, BOT_CHECK);\n });\n}\n\n/**\n * Requires the caller to hold `roleId`, read from the member's role ids. Refuses outside a guild first,\n * then when the role is missing. Roles are guild-global, so there is no scope dimension.\n *\n * @param roleId - The role snowflake the caller must hold.\n * @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-role one with `missingRole`.\n *\n * @see the `@Gated` decorator from your transport package\n *\n * @example\n * ```ts\n * \\@Gated(RequireRole('123456789012345678'))\n * \\@SlashRoute('vip')\n * class VipHandler extends SlashHandler<'vip'> {\n * async execute() {\n * // ...\n * }\n * }\n * ```\n */\nexport function RequireRole(roleId: string, options?: RequireRoleOptions): Gate<GateContextBase, 'RequireRole'> {\n return defineGate('RequireRole', (ctx) => {\n if (!ctx.guildId) throw pickNotice(options?.notInGuild, (message) => new NotInGuild(message));\n if (ctx.memberRoleIds.includes(roleId)) return;\n throw pickNotice(options?.missingRole, (message) => new MissingRole(message, roleId));\n });\n}\n","/**\n * Throw to stop a handler with no reply and no report.\n *\n * The framework boundary catches `Silence` before {@link Notice}, makes zero Discord calls, and stops.\n * Ideally you'd only throw this in `EventHandlers` (or `Gates` for those), because it doesn't make sense to\n * leave the user with no reply for an interaction.\n *\n * @example\n * ```ts\n * import { Silence } from '@seedcord/gateway';\n *\n * // a helper under a guildMemberAdd handler, stopping when this guild configured nothing to send\n * const template = await settings.welcome(member.guild.id);\n * if (!template) throw new Silence('no welcome message set');\n * ```\n */\nexport class Silence extends Error {\n /**\n * @param reason - Optional note written only to a debug log, never shown to the user or reported.\n */\n public constructor(public readonly reason?: string) {\n super(reason ?? 'Silence');\n\n Error.captureStackTrace(this, this.constructor);\n }\n}\n","import { CustomId } from '@seedcord/custom-id';\n\nimport type { CustomIdField } from '@seedcord/custom-id';\n\n// Included in the layout hash so it is fixed per prefix. Reducing it marks every active button as StaleCustomId.\n// @internal\nexport const PAGE_MAX = 1_000_000;\n\n// Discord rejects a message with duplicate custom_ids, and two controls can target the same page (first and\n// prev both hit 0).\n// @internal\nconst SLOT_MAX = 4;\n\n/** Page cursor for a paginator. */\nexport type PageCursor<Prefix extends string> = CustomId<\n Prefix,\n { page: CustomIdField<number>; slot: CustomIdField<number> }\n>;\n\n/** Build the page cursor for a paginator. */\nexport function pageCursor<Prefix extends string>(prefix: Prefix): PageCursor<Prefix> {\n return new CustomId(prefix).int('page', 0, PAGE_MAX).int('slot', 0, SLOT_MAX);\n}\n"],"mappings":"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"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
//#region src/hmr/devChannel.ts
|
|
2
|
-
let channel;
|
|
3
|
-
/** @internal */
|
|
4
|
-
function setDevChannel(next) {
|
|
5
|
-
channel = next;
|
|
6
|
-
}
|
|
7
|
-
/** @internal */
|
|
8
|
-
function getDevChannel() {
|
|
9
|
-
return channel;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
//#endregion
|
|
13
|
-
export { setDevChannel as n, getDevChannel as t };
|
|
14
|
-
//# sourceMappingURL=devChannel-CQU7hq7Q.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devChannel-CQU7hq7Q.mjs","names":[],"sources":["../src/hmr/devChannel.ts"],"sourcesContent":["import type { DevChannel, SeedcordCliEvents, SeedcordFrameworkEvents } from '@seedcord/types/internal';\n\n// one module instance per process, the HmrManager write and every framework read share it\nlet channel: DevChannel<SeedcordFrameworkEvents, SeedcordCliEvents> | undefined;\n\n/** @internal */\nexport function setDevChannel(next: DevChannel<SeedcordFrameworkEvents, SeedcordCliEvents> | undefined): void {\n channel = next;\n}\n\n/** @internal */\nexport function getDevChannel(): DevChannel<SeedcordFrameworkEvents, SeedcordCliEvents> | undefined {\n return channel;\n}\n"],"mappings":";AAGA,IAAI;;AAGJ,SAAgB,cAAc,MAAgF;CAC1G,UAAU;AACd;;AAGA,SAAgB,gBAAoF;CAChG,OAAO;AACX"}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { l as Subscriber, r as CoreBase, y as SubscriptionKey } from "./phases-D_zQBXga.mjs";
|
|
2
|
-
import "reflect-metadata";
|
|
3
|
-
import { APIMessageTopLevelComponent } from "discord-api-types/v10";
|
|
4
|
-
import { EventFrequency, ReplyFile } from "@seedcord/types";
|
|
5
|
-
import { Constructor } from "type-fest";
|
|
6
|
-
//#region src/subscribers/decorators/Subscribe.d.ts
|
|
7
|
-
/**
|
|
8
|
-
* Options accepted by the `@Subscribe` decorator.
|
|
9
|
-
*/
|
|
10
|
-
interface SubscribeOptions {
|
|
11
|
-
/**
|
|
12
|
-
* Frequency: `'once'` or `'on'`.
|
|
13
|
-
*
|
|
14
|
-
* @defaultValue `'on'`
|
|
15
|
-
*/
|
|
16
|
-
readonly frequency?: EventFrequency | undefined;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Registers a subscriber handler class with a specific subscriber event.
|
|
20
|
-
*
|
|
21
|
-
* Associates the decorated class with a subscriber event type for automatic
|
|
22
|
-
* registration and execution when the subscriber is emitted.
|
|
23
|
-
*
|
|
24
|
-
* @typeParam TSubscriber - The subscriber event name to register for
|
|
25
|
-
* @param subscriber - The subscriber event name to register for
|
|
26
|
-
* @param options - Options to configure the subscriber handler registration.
|
|
27
|
-
* @decorator
|
|
28
|
-
* @example
|
|
29
|
-
* ```typescript
|
|
30
|
-
* \@Subscribe('userJoin')
|
|
31
|
-
* class WelcomeHandler extends Subscriber<'userJoin'> {
|
|
32
|
-
* async execute() {
|
|
33
|
-
* }
|
|
34
|
-
* }
|
|
35
|
-
* ```
|
|
36
|
-
* @example
|
|
37
|
-
* ```ts
|
|
38
|
-
* \@Subscribe('userJoin', { frequency: 'once' })
|
|
39
|
-
* // or
|
|
40
|
-
* \@Subscribe('userJoin')
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
declare function Subscribe<TSubscriber extends SubscriptionKey>(subscriber: TSubscriber, options?: SubscribeOptions): <HandlerCtor extends Constructor<Subscriber<TSubscriber, CoreBase>>>(constructor: HandlerCtor) => void;
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/subscribers/bases/WebhookSender.d.ts
|
|
46
|
-
/** A file attached to a webhook report. */
|
|
47
|
-
interface WebhookFile extends ReplyFile {
|
|
48
|
-
readonly description: string;
|
|
49
|
-
}
|
|
50
|
-
interface WebhookSendOptions {
|
|
51
|
-
flags: number;
|
|
52
|
-
username: string;
|
|
53
|
-
avatarUrl?: string | undefined;
|
|
54
|
-
components: readonly {
|
|
55
|
-
toJSON(): APIMessageTopLevelComponent;
|
|
56
|
-
}[];
|
|
57
|
-
files?: readonly WebhookFile[] | undefined;
|
|
58
|
-
}
|
|
59
|
-
declare class WebhookSender {
|
|
60
|
-
private readonly rest;
|
|
61
|
-
private readonly route;
|
|
62
|
-
constructor(url: string);
|
|
63
|
-
verify(): Promise<'ok' | 'missing' | 'unreachable'>;
|
|
64
|
-
send(options: WebhookSendOptions): Promise<void>;
|
|
65
|
-
}
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/subscribers/bases/WebhookLog.d.ts
|
|
68
|
-
/**
|
|
69
|
-
* The content a reporter returns from {@link WebhookLog.report}.
|
|
70
|
-
*/
|
|
71
|
-
interface WebhookReport {
|
|
72
|
-
/** Shown as the webhook message author. Defaults to the reporter's class name. */
|
|
73
|
-
username?: string;
|
|
74
|
-
/** Shown as the webhook message avatar. Defaults to the webhook's own. */
|
|
75
|
-
avatarUrl?: string;
|
|
76
|
-
components: readonly {
|
|
77
|
-
toJSON(): APIMessageTopLevelComponent;
|
|
78
|
-
}[];
|
|
79
|
-
files?: readonly WebhookFile[] | undefined;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Base class for subscribers that deliver their event to a Discord webhook.
|
|
83
|
-
*
|
|
84
|
-
* Declare the url's environment variable with `@WebhookUrl` and implement {@link report}. The base
|
|
85
|
-
* resolves the url, validates it, reuses one sender per url, and sends. Registration skips a reporter
|
|
86
|
-
* whose variable is unset and warns at boot.
|
|
87
|
-
*
|
|
88
|
-
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
89
|
-
* @typeParam TCore - The transport's Core
|
|
90
|
-
*/
|
|
91
|
-
declare abstract class WebhookLog<KeyOfSubscribers extends SubscriptionKey, TCore extends CoreBase> extends Subscriber<KeyOfSubscribers, TCore> {
|
|
92
|
-
private static readonly senders;
|
|
93
|
-
/**
|
|
94
|
-
* Builds the message for one published event.
|
|
95
|
-
*
|
|
96
|
-
* @param suppressed - How many events were dropped since the last card
|
|
97
|
-
*/
|
|
98
|
-
abstract report(suppressed: number): WebhookReport | Promise<WebhookReport>;
|
|
99
|
-
/**
|
|
100
|
-
* Events sharing this key collapse into one card per minute. Return `null` to send every event.
|
|
101
|
-
*/
|
|
102
|
-
protected throttleKey(): string | null;
|
|
103
|
-
execute(): Promise<void>;
|
|
104
|
-
/** @internal */
|
|
105
|
-
static senderFor(url: string): WebhookSender;
|
|
106
|
-
/** @internal */
|
|
107
|
-
static envKeyOf(ctor: NewableFunction): string;
|
|
108
|
-
/** @internal Returns null when the env var is unset, throws when set but malformed. */
|
|
109
|
-
static urlOf(envKey: string): string | null;
|
|
110
|
-
}
|
|
111
|
-
//#endregion
|
|
112
|
-
//#region src/subscribers/decorators/WebhookUrl.d.ts
|
|
113
|
-
/**
|
|
114
|
-
* Declares the environment variable a {@link WebhookLog} reporter reads its webhook url from.
|
|
115
|
-
*
|
|
116
|
-
* An unset variable disables the reporter with a boot warning. A set but malformed value throws
|
|
117
|
-
* at boot.
|
|
118
|
-
*
|
|
119
|
-
* @param envKey - The environment variable name holding the webhook url
|
|
120
|
-
* @decorator
|
|
121
|
-
* @example
|
|
122
|
-
* ```ts
|
|
123
|
-
* \@Subscribe('memberBanned')
|
|
124
|
-
* \@WebhookUrl('BAN_LOG_WEBHOOK_URL')
|
|
125
|
-
* class BanLog extends WebhookLog<'memberBanned'> {
|
|
126
|
-
* report() {
|
|
127
|
-
* return { components: [new BanCard(this.data).component] };
|
|
128
|
-
* }
|
|
129
|
-
* }
|
|
130
|
-
* ```
|
|
131
|
-
*/
|
|
132
|
-
declare function WebhookUrl(envKey: string): <HandlerCtor extends Constructor<WebhookLog<SubscriptionKey, CoreBase>>>(constructor: HandlerCtor) => void;
|
|
133
|
-
//#endregion
|
|
134
|
-
export { Subscribe as a, WebhookFile as i, WebhookLog as n, SubscribeOptions as o, WebhookReport as r, WebhookUrl as t };
|
|
135
|
-
//# sourceMappingURL=index-B_-EjyvS.d.mts.map
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
//#region src/stops/asError.ts
|
|
2
|
-
/** @internal */
|
|
3
|
-
function asError(caught) {
|
|
4
|
-
return Error.isError(caught) ? caught : new Error(String(caught), { cause: caught });
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
//#endregion
|
|
8
|
-
//#region src/subscribers/publishDefault.ts
|
|
9
|
-
/** @internal PublishableKey excludes this key so publish() never emits it externally. */
|
|
10
|
-
const PublishDefault = Symbol("seedcord:bus:publish-default");
|
|
11
|
-
|
|
12
|
-
//#endregion
|
|
13
|
-
export { asError as n, PublishDefault as t };
|
|
14
|
-
//# sourceMappingURL=publishDefault-BuSZPbjT.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"publishDefault-BuSZPbjT.mjs","names":[],"sources":["../src/stops/asError.ts","../src/subscribers/publishDefault.ts"],"sourcesContent":["// dropping a non-Error throw would leave the user with no fault card, and every report payload declares an Error\n/** @internal */\nexport function asError(caught: unknown): Error {\n return Error.isError(caught) ? caught : new Error(String(caught), { cause: caught });\n}\n","/** @internal PublishableKey excludes this key so publish() never emits it externally. */\nexport const PublishDefault = Symbol('seedcord:bus:publish-default');\n"],"mappings":";;AAEA,SAAgB,QAAQ,QAAwB;CAC5C,OAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,IAAI,MAAM,OAAO,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC;AACvF;;;;;ACHA,MAAa,iBAAiB,OAAO,8BAA8B"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { M as isBuilderComponentClass, g as CommandMetadataKey } from "./Bus-BxPa-Uvp.mjs";
|
|
2
|
-
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
3
|
-
import { routeLeavesOf } from "@seedcord/utils/internal";
|
|
4
|
-
|
|
5
|
-
//#region src/commands/isCommandClass.ts
|
|
6
|
-
/** @internal */
|
|
7
|
-
function isCommandClass(value) {
|
|
8
|
-
return isBuilderComponentClass(value) && Reflect.hasMetadata(CommandMetadataKey, value);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region src/commands/slashRouteLeaves.ts
|
|
13
|
-
/**
|
|
14
|
-
* dedupes route keys since the same builder appears once per guild.
|
|
15
|
-
* these come from the same {@link routeLeavesOf} that `seedcord codegen` reads, so the boot-time
|
|
16
|
-
* check compares against the generated registry's keys.
|
|
17
|
-
*
|
|
18
|
-
* @internal
|
|
19
|
-
*/
|
|
20
|
-
function slashRouteLeaves(commands) {
|
|
21
|
-
const leaves = /* @__PURE__ */ new Set();
|
|
22
|
-
for (const command of commands) {
|
|
23
|
-
if (!(command instanceof SlashCommandBuilder)) continue;
|
|
24
|
-
for (const leaf of routeLeavesOf(command.toJSON())) leaves.add(leaf.route);
|
|
25
|
-
}
|
|
26
|
-
return leaves;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
//#endregion
|
|
30
|
-
export { isCommandClass as n, slashRouteLeaves as t };
|
|
31
|
-
//# sourceMappingURL=slashRouteLeaves-k1hJnXDO.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"slashRouteLeaves-k1hJnXDO.mjs","names":[],"sources":["../src/commands/isCommandClass.ts","../src/commands/slashRouteLeaves.ts"],"sourcesContent":["import { isBuilderComponentClass } from '#components/Component';\nimport { CommandMetadataKey } from '#src/metadataKeys';\n\nimport type { BuilderComponent } from '#components/Component';\nimport type { Constructor } from 'type-fest';\n\n/** @internal */\nexport type CommandCtor = Constructor<BuilderComponent<'command' | 'context_menu'>, never[]>;\n\n// the cli's codegen scan reads this too\n/** @internal */\nexport function isCommandClass(value: unknown): value is CommandCtor {\n return isBuilderComponentClass(value) && Reflect.hasMetadata(CommandMetadataKey, value);\n}\n","import { SlashCommandBuilder } from '@discordjs/builders';\nimport { routeLeavesOf } from '@seedcord/utils/internal';\n\nimport type { ContextMenuCommandBuilder } from '@discordjs/builders';\n\n/**\n * dedupes route keys since the same builder appears once per guild.\n * these come from the same {@link routeLeavesOf} that `seedcord codegen` reads, so the boot-time\n * check compares against the generated registry's keys.\n *\n * @internal\n */\nexport function slashRouteLeaves(commands: Iterable<SlashCommandBuilder | ContextMenuCommandBuilder>): Set<string> {\n const leaves = new Set<string>();\n for (const command of commands) {\n if (!(command instanceof SlashCommandBuilder)) continue;\n for (const leaf of routeLeavesOf(command.toJSON())) leaves.add(leaf.route);\n }\n return leaves;\n}\n"],"mappings":";;;;;;AAWA,SAAgB,eAAe,OAAsC;CACjE,OAAO,wBAAwB,KAAK,KAAK,QAAQ,YAAY,oBAAoB,KAAK;AAC1F;;;;;;;;;;;ACDA,SAAgB,iBAAiB,UAAkF;CAC/G,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,WAAW,UAAU;EAC5B,IAAI,EAAE,mBAAmB,sBAAsB;EAC/C,KAAK,MAAM,QAAQ,cAAc,QAAQ,OAAO,CAAC,GAAG,OAAO,IAAI,KAAK,KAAK;CAC7E;CACA,OAAO;AACX"}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { D as clearStore, E as accessorStore, O as guardedAccessor, g as CommandMetadataKey, k as contextMenuLeaves } from "./Bus-BxPa-Uvp.mjs";
|
|
2
|
-
import "reflect-metadata";
|
|
3
|
-
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
4
|
-
import { ApplicationCommandType } from "discord-api-types/v10";
|
|
5
|
-
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
6
|
-
import { SeedcordError, SeedcordTypeError } from "@seedcord/errors/internal";
|
|
7
|
-
import { Logger } from "@seedcord/logger";
|
|
8
|
-
import { routeLeavesOf } from "@seedcord/utils/internal";
|
|
9
|
-
|
|
10
|
-
//#region src/commands/CommandInjector.ts
|
|
11
|
-
const commandStorage = accessorStore();
|
|
12
|
-
const userMenuStorage = accessorStore();
|
|
13
|
-
const messageMenuStorage = accessorStore();
|
|
14
|
-
/**
|
|
15
|
-
* The bot's deployed slash commands, keyed by {@link SlashRegistry}. Filled by
|
|
16
|
-
* {@link CommandInjector} after each deploy, and a read before that throws.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* import { Commands } from '@seedcord/gateway';
|
|
21
|
-
*
|
|
22
|
-
* await this.reply(`Try ${Commands['config/set'].mention}`);
|
|
23
|
-
*
|
|
24
|
-
* // a help listing
|
|
25
|
-
* const lines = Object.values(Commands).map((c) => `${c.mention} ${c.description}`);
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
const Commands = guardedAccessor("Commands", commandStorage);
|
|
29
|
-
/**
|
|
30
|
-
* The bot's deployed context-menu commands. Filled by {@link CommandInjector} after each deploy, and a read
|
|
31
|
-
* before that throws.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* import { ContextMenus } from '@seedcord/gateway';
|
|
36
|
-
*
|
|
37
|
-
* const id = ContextMenus.user['View Profile'].id;
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
const ContextMenus = {
|
|
41
|
-
user: guardedAccessor("ContextMenus.user", userMenuStorage),
|
|
42
|
-
message: guardedAccessor("ContextMenus.message", messageMenuStorage)
|
|
43
|
-
};
|
|
44
|
-
const MULTI_GUILD = Symbol("seedcord:commands:multi-guild");
|
|
45
|
-
function commandKey(type, name) {
|
|
46
|
-
return `${type}:${name}`;
|
|
47
|
-
}
|
|
48
|
-
function slashBuildersOf(all) {
|
|
49
|
-
const slash = all.filter((command) => command instanceof SlashCommandBuilder);
|
|
50
|
-
return [...new Set(slash)];
|
|
51
|
-
}
|
|
52
|
-
/** @internal */
|
|
53
|
-
var CommandInjector = class {
|
|
54
|
-
logger = new Logger("Commands", { channel: "bot" });
|
|
55
|
-
inject(deploy, all) {
|
|
56
|
-
this.clear();
|
|
57
|
-
const ids = {
|
|
58
|
-
global: this.indexByKey(deploy.global),
|
|
59
|
-
guild: this.indexGuilds(deploy.guilds),
|
|
60
|
-
warned: /* @__PURE__ */ new Set()
|
|
61
|
-
};
|
|
62
|
-
let clickable = 0;
|
|
63
|
-
for (const command of slashBuildersOf(all)) {
|
|
64
|
-
const id = this.resolveId(ApplicationCommandType.ChatInput, command.name, ids, "falling back to plain text (not clickable)");
|
|
65
|
-
for (const leaf of routeLeavesOf(command.toJSON())) {
|
|
66
|
-
commandStorage[leaf.route] = {
|
|
67
|
-
mention: id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route),
|
|
68
|
-
...id && { id },
|
|
69
|
-
route: leaf.route,
|
|
70
|
-
description: leaf.description
|
|
71
|
-
};
|
|
72
|
-
if (id) clickable++;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const menus = contextMenuLeaves(all);
|
|
76
|
-
this.injectMenus(menus.user, ApplicationCommandType.User, userMenuStorage, ids);
|
|
77
|
-
this.injectMenus(menus.message, ApplicationCommandType.Message, messageMenuStorage, ids);
|
|
78
|
-
this.logger.utils.summary("Linked commands", {
|
|
79
|
-
clickable,
|
|
80
|
-
plain: Object.keys(commandStorage).length - clickable,
|
|
81
|
-
"context menus": menus.user.size + menus.message.size
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
injectMenus(names, kind, storage, ids) {
|
|
85
|
-
for (const name of names) {
|
|
86
|
-
const id = this.resolveId(kind, name, ids, "leaving its id unset");
|
|
87
|
-
storage[name] = {
|
|
88
|
-
...id && { id },
|
|
89
|
-
name,
|
|
90
|
-
kind
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
indexByKey(collection) {
|
|
95
|
-
const map = /* @__PURE__ */ new Map();
|
|
96
|
-
for (const command of collection.values()) map.set(commandKey(command.type, command.name), command.id);
|
|
97
|
-
return map;
|
|
98
|
-
}
|
|
99
|
-
indexGuilds(guilds) {
|
|
100
|
-
const map = /* @__PURE__ */ new Map();
|
|
101
|
-
for (const collection of guilds.values()) for (const command of collection.values()) {
|
|
102
|
-
const key = commandKey(command.type, command.name);
|
|
103
|
-
const existing = map.get(key);
|
|
104
|
-
if (existing === void 0) map.set(key, command.id);
|
|
105
|
-
else if (existing !== command.id) map.set(key, MULTI_GUILD);
|
|
106
|
-
}
|
|
107
|
-
return map;
|
|
108
|
-
}
|
|
109
|
-
resolveId(type, name, ids, consequence) {
|
|
110
|
-
const key = commandKey(type, name);
|
|
111
|
-
const global = ids.global.get(key);
|
|
112
|
-
if (global) return global;
|
|
113
|
-
const guild = ids.guild.get(key);
|
|
114
|
-
if (guild === void 0) return void 0;
|
|
115
|
-
if (guild === MULTI_GUILD) {
|
|
116
|
-
if (!ids.warned.has(key)) {
|
|
117
|
-
this.logger.warn(`${paint.sky.bold(name)} is deployed to multiple guilds, ${consequence}.`);
|
|
118
|
-
ids.warned.add(key);
|
|
119
|
-
}
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
return guild;
|
|
123
|
-
}
|
|
124
|
-
toMention(route, id) {
|
|
125
|
-
return `</${route.replaceAll("/", " ")}:${id}>`;
|
|
126
|
-
}
|
|
127
|
-
toPlain(route) {
|
|
128
|
-
return `/${route.replaceAll("/", " ")}`;
|
|
129
|
-
}
|
|
130
|
-
clear() {
|
|
131
|
-
for (const store of [
|
|
132
|
-
commandStorage,
|
|
133
|
-
userMenuStorage,
|
|
134
|
-
messageMenuStorage
|
|
135
|
-
]) clearStore(store);
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
//#endregion
|
|
140
|
-
//#region src/decorators/Command.ts
|
|
141
|
-
const ConfigGuilds = "config";
|
|
142
|
-
function RegisterCommand(scope = "config", guilds = []) {
|
|
143
|
-
return (ctor) => {
|
|
144
|
-
const existingMeta = Reflect.getOwnMetadata(CommandMetadataKey, ctor);
|
|
145
|
-
if (existingMeta) throw new SeedcordError(SeedcordErrorCode.DecoratorCommandAlreadyRegistered, [
|
|
146
|
-
ctor.name,
|
|
147
|
-
decoratorCall(existingMeta.scope),
|
|
148
|
-
decoratorCall(scope)
|
|
149
|
-
]);
|
|
150
|
-
if (scope === "global" && guilds.length > 0) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorCommandGlobalWithGuilds);
|
|
151
|
-
if (scope === "guild" && (!Array.isArray(guilds) || guilds.length === 0)) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds);
|
|
152
|
-
const meta = scope === "guild" ? {
|
|
153
|
-
scope,
|
|
154
|
-
guilds
|
|
155
|
-
} : { scope };
|
|
156
|
-
Reflect.defineMetadata(CommandMetadataKey, meta, ctor);
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
function decoratorCall(scope) {
|
|
160
|
-
if (scope === "global") return "@RegisterCommand('global')";
|
|
161
|
-
if (scope === "guild") return "@RegisterCommand('guild', [...])";
|
|
162
|
-
return "@RegisterCommand()";
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
//#endregion
|
|
166
|
-
export { ContextMenus as a, Commands as i, RegisterCommand as n, CommandInjector as r, ConfigGuilds as t };
|
|
167
|
-
//# sourceMappingURL=subscribers-xGy7a7xR.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"subscribers-xGy7a7xR.mjs","names":[],"sources":["../src/commands/CommandInjector.ts","../src/decorators/Command.ts"],"sourcesContent":["import { SlashCommandBuilder } from '@discordjs/builders';\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { routeLeavesOf } from '@seedcord/utils/internal';\nimport { ApplicationCommandType } from 'discord-api-types/v10';\n\nimport { contextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport { accessorStore, clearStore, guardedAccessor } from '#src/miscellaneous/guarded';\n\nimport type {\n ContextMenuKind,\n MessageContextMenuRegistry,\n UserContextMenuRegistry\n} from '#registries/ContextMenuRegistry';\nimport type { SlashRegistry } from '#registries/SlashRegistry';\nimport type { CommandBuilder, DeployResult } from '#src/commands/types';\nimport type { APIApplicationCommand, Snowflake } from 'discord-api-types/v10';\n\nconst commandStorage = accessorStore<CommandInfo>();\nconst userMenuStorage = accessorStore<ContextMenuInfo>();\nconst messageMenuStorage = accessorStore<ContextMenuInfo>();\n\n/** What {@link Commands} resolves to for one slash route. */\nexport interface CommandInfo {\n /** The clickable `</config set:id>` mention. A route whose id did not resolve falls back to `/config set`. */\n readonly mention: string;\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The full route, e.g. `config/set`. */\n readonly route: string;\n /** The route's own description, taken from the subcommand for a nested route. */\n readonly description: string;\n}\n\n/** Each registered slash route mapped to its deployed command. */\nexport type InjectedCommandMap = {\n [K in keyof SlashRegistry]: CommandInfo;\n};\n\n/**\n * The bot's deployed slash commands, keyed by {@link SlashRegistry}. Filled by\n * {@link CommandInjector} after each deploy, and a read before that throws.\n *\n * @example\n * ```ts\n * import { Commands } from '@seedcord/gateway';\n *\n * await this.reply(`Try ${Commands['config/set'].mention}`);\n *\n * // a help listing\n * const lines = Object.values(Commands).map((c) => `${c.mention} ${c.description}`);\n * ```\n */\nexport const Commands = guardedAccessor('Commands', commandStorage) as InjectedCommandMap;\n\n/** What {@link ContextMenus} resolves to for one context-menu command. */\nexport interface ContextMenuInfo {\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The name shown in Discord's right-click menu. */\n readonly name: string;\n readonly kind: ContextMenuKind;\n}\n\n/** Deployed context-menu commands, split by kind because Discord scopes name uniqueness per command type. */\nexport interface InjectedContextMenuMap {\n readonly user: { [K in keyof UserContextMenuRegistry]: ContextMenuInfo };\n readonly message: { [K in keyof MessageContextMenuRegistry]: ContextMenuInfo };\n}\n\n/**\n * The bot's deployed context-menu commands. Filled by {@link CommandInjector} after each deploy, and a read\n * before that throws.\n *\n * @example\n * ```ts\n * import { ContextMenus } from '@seedcord/gateway';\n *\n * const id = ContextMenus.user['View Profile'].id;\n * ```\n */\nexport const ContextMenus = {\n user: guardedAccessor('ContextMenus.user', userMenuStorage),\n message: guardedAccessor('ContextMenus.message', messageMenuStorage)\n} as InjectedContextMenuMap;\n\nconst MULTI_GUILD = Symbol('seedcord:commands:multi-guild');\n\n// Discord scopes name uniqueness per type, and a context-menu name may be all lowercase like a slash name\nfunction commandKey(type: ApplicationCommandType, name: string): string {\n return `${type}:${name}`;\n}\n\nfunction slashBuildersOf(all: readonly CommandBuilder[]): SlashCommandBuilder[] {\n const slash = all.filter((command): command is SlashCommandBuilder => command instanceof SlashCommandBuilder);\n // a guild command in N guilds is the same builder pushed once per guild\n return [...new Set(slash)];\n}\n\ninterface DeployedIds {\n readonly global: Map<string, Snowflake>;\n readonly guild: Map<string, Snowflake | typeof MULTI_GUILD>;\n readonly warned: Set<string>;\n}\n\n/** @internal */\nexport class CommandInjector {\n private readonly logger = new Logger('Commands', { channel: 'bot' });\n\n public inject(deploy: DeployResult, all: readonly CommandBuilder[]): void {\n this.clear();\n\n const ids: DeployedIds = {\n global: this.indexByKey(deploy.global),\n guild: this.indexGuilds(deploy.guilds),\n warned: new Set<string>()\n };\n let clickable = 0;\n\n for (const command of slashBuildersOf(all)) {\n const id = this.resolveId(\n ApplicationCommandType.ChatInput,\n command.name,\n ids,\n 'falling back to plain text (not clickable)'\n );\n for (const leaf of routeLeavesOf(command.toJSON())) {\n commandStorage[leaf.route] = {\n mention: id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route),\n ...(id && { id }),\n route: leaf.route,\n description: leaf.description\n };\n if (id) clickable++;\n }\n }\n\n const menus = contextMenuLeaves(all);\n this.injectMenus(menus.user, ApplicationCommandType.User, userMenuStorage, ids);\n this.injectMenus(menus.message, ApplicationCommandType.Message, messageMenuStorage, ids);\n\n this.logger.utils.summary('Linked commands', {\n clickable,\n plain: Object.keys(commandStorage).length - clickable,\n 'context menus': menus.user.size + menus.message.size\n });\n }\n\n private injectMenus(\n names: ReadonlySet<string>,\n kind: ContextMenuKind,\n storage: Record<string, ContextMenuInfo>,\n ids: DeployedIds\n ): void {\n for (const name of names) {\n const id = this.resolveId(kind, name, ids, 'leaving its id unset');\n storage[name] = { ...(id && { id }), name, kind };\n }\n }\n\n private indexByKey(collection: Map<Snowflake, APIApplicationCommand>): Map<string, Snowflake> {\n const map = new Map<string, Snowflake>();\n for (const command of collection.values()) map.set(commandKey(command.type, command.name), command.id);\n return map;\n }\n\n private indexGuilds(\n guilds: Map<Snowflake, Map<Snowflake, APIApplicationCommand>>\n ): Map<string, Snowflake | typeof MULTI_GUILD> {\n const map = new Map<string, Snowflake | typeof MULTI_GUILD>();\n for (const collection of guilds.values()) {\n for (const command of collection.values()) {\n const key = commandKey(command.type, command.name);\n const existing = map.get(key);\n // Discord assigns a separate id per guild, so two ids for one name means no single mention\n if (existing === undefined) map.set(key, command.id);\n else if (existing !== command.id) map.set(key, MULTI_GUILD);\n }\n }\n return map;\n }\n\n private resolveId(\n type: ApplicationCommandType,\n name: string,\n ids: DeployedIds,\n consequence: string\n ): Snowflake | undefined {\n const key = commandKey(type, name);\n const global = ids.global.get(key);\n if (global) return global;\n\n const guild = ids.guild.get(key);\n if (guild === undefined) return undefined;\n if (guild === MULTI_GUILD) {\n if (!ids.warned.has(key)) {\n this.logger.warn(`${paint.sky.bold(name)} is deployed to multiple guilds, ${consequence}.`);\n ids.warned.add(key);\n }\n return undefined;\n }\n return guild;\n }\n\n private toMention(route: string, id: Snowflake): string {\n return `</${route.replaceAll('/', ' ')}:${id}>`;\n }\n\n private toPlain(route: string): string {\n return `/${route.replaceAll('/', ' ')}`;\n }\n\n private clear(): void {\n for (const store of [commandStorage, userMenuStorage, messageMenuStorage]) clearStore(store);\n }\n}\n","import 'reflect-metadata';\n\nimport { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\n\nimport { CommandMetadataKey } from '#src/metadataKeys';\n\nimport type { BuilderComponent } from '#components/Component';\nimport type { Constructor, LiteralUnion } from 'type-fest';\n\ntype CommandCtor = Constructor<BuilderComponent<'command' | 'context_menu'>>;\n\nexport const ConfigGuilds = 'config';\n\n/** A guild id, or `'config'` for every id in `commands.guilds`. */\nexport type GuildTarget = LiteralUnion<typeof ConfigGuilds, string>;\n\ninterface GlobalMeta {\n scope: 'global';\n}\n\ninterface GuildMeta {\n scope: 'guild';\n guilds: GuildTarget[];\n}\n\ninterface ConfigMeta {\n scope: 'config';\n}\n\nexport type CommandMeta = GlobalMeta | GuildMeta | ConfigMeta;\n\ntype CommandScope = CommandMeta['scope'];\n\n/**\n * Registers a command in the guilds listed under `commands.guilds`. With no ids listed there, the\n * command deploys globally.\n *\n * @decorator\n * @example\n * ```typescript\n * \\@RegisterCommand()\n * class PingCommand extends BuilderComponent<'command'> {} // or BuilderComponent<'context_menu'>\n * ```\n */\nexport function RegisterCommand(): (ctor: CommandCtor) => void;\n\n/**\n * Registers a command globally, ignoring `commands.guilds`.\n *\n * @decorator\n * @example\n * ```typescript\n * \\@RegisterCommand('global')\n * class PingCommand extends BuilderComponent<'command'> {} // or BuilderComponent<'context_menu'>\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/unified-signatures -- merging these puts two different deploy targets under one doc block\nexport function RegisterCommand(scope: 'global'): (ctor: CommandCtor) => void;\n\n/**\n * Registers a command in the guilds you pass. These ids replace `commands.guilds`. Include\n * `'config'` among them to keep that list too.\n *\n * @decorator\n * @example\n * ```typescript\n * \\@RegisterCommand('guild', ['config', '123456789'])\n * class AdminCommand extends BuilderComponent<'command'> {} // or BuilderComponent<'context_menu'>\n * ```\n */\nexport function RegisterCommand(scope: 'guild', guilds: GuildTarget[]): (ctor: CommandCtor) => void;\n\nexport function RegisterCommand(scope: CommandScope = 'config', guilds: GuildTarget[] = []) {\n return (ctor: CommandCtor): void => {\n const existingMeta = Reflect.getOwnMetadata(CommandMetadataKey, ctor) as CommandMeta | undefined;\n if (existingMeta) {\n throw new SeedcordError(SeedcordErrorCode.DecoratorCommandAlreadyRegistered, [\n ctor.name,\n decoratorCall(existingMeta.scope),\n decoratorCall(scope)\n ]);\n }\n\n if (scope === 'global' && guilds.length > 0) {\n throw new SeedcordTypeError(SeedcordErrorCode.DecoratorCommandGlobalWithGuilds);\n }\n\n if (scope === 'guild' && (!Array.isArray(guilds) || guilds.length === 0)) {\n throw new SeedcordTypeError(SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds);\n }\n\n const meta: CommandMeta = scope === 'guild' ? { scope, guilds } : { scope };\n Reflect.defineMetadata(CommandMetadataKey, meta, ctor);\n };\n}\n\nfunction decoratorCall(scope: CommandScope): string {\n if (scope === 'global') return \"@RegisterCommand('global')\";\n if (scope === 'guild') return \"@RegisterCommand('guild', [...])\";\n\n return '@RegisterCommand()';\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ContextMenuCommandBuilder, SlashCommandBuilder } from "@discordjs/builders";
|
|
2
|
-
import { APIApplicationCommand } from "discord-api-types/v10";
|
|
3
|
-
//#region src/commands/types.d.ts
|
|
4
|
-
/** @internal */
|
|
5
|
-
type CommandBuilder = SlashCommandBuilder | ContextMenuCommandBuilder;
|
|
6
|
-
/** discord returns these from each scope's deploy, keyed by command id. @internal */
|
|
7
|
-
interface DeployResult {
|
|
8
|
-
global: Map<string, APIApplicationCommand>;
|
|
9
|
-
guilds: Map<string, Map<string, APIApplicationCommand>>;
|
|
10
|
-
}
|
|
11
|
-
//#endregion
|
|
12
|
-
export { DeployResult as n, CommandBuilder as t };
|
|
13
|
-
//# sourceMappingURL=types-BG0BimVr.d.mts.map
|