@seedcord/core 0.3.1 → 0.4.1

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.
Files changed (38) hide show
  1. package/dist/{Bus-BvF4VShN.mjs → Bus-yJGE6Fs2.mjs} +111 -92
  2. package/dist/Bus-yJGE6Fs2.mjs.map +1 -0
  3. package/dist/Pluggable-BI_LQuPm.mjs +280 -0
  4. package/dist/Pluggable-BI_LQuPm.mjs.map +1 -0
  5. package/dist/Pluggable-bFCmT4oB.d.mts +148 -0
  6. package/dist/{Plugin-D9ykXA5g.mjs → Plugin-B0SRHzMX.mjs} +7 -3
  7. package/dist/Plugin-B0SRHzMX.mjs.map +1 -0
  8. package/dist/{Plugin-CQ9IEj5X.d.mts → Plugin-BW2JH2_h.d.mts} +2 -2
  9. package/dist/{cursor-BANzac53.d.mts → cursor-CO_MTgdZ.d.mts} +8 -252
  10. package/dist/{cursor-CuoA9cRh.mjs → cursor-CaT051ii.mjs} +307 -425
  11. package/dist/cursor-CaT051ii.mjs.map +1 -0
  12. package/dist/{index-rt8rZg4d.d.mts → index-CvneCObW.d.mts} +2 -2
  13. package/dist/index.d.mts +78 -19
  14. package/dist/index.mjs +223 -21
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/internal.index.d.mts +13 -6
  17. package/dist/internal.index.mjs +8 -455
  18. package/dist/internal.index.mjs.map +1 -1
  19. package/dist/node-internal.index.d.mts +6 -133
  20. package/dist/node-internal.index.mjs +16 -473
  21. package/dist/node-internal.index.mjs.map +1 -1
  22. package/dist/node.index.d.mts +2 -0
  23. package/dist/node.index.mjs +256 -0
  24. package/dist/node.index.mjs.map +1 -0
  25. package/dist/{phases-hBxMCD2V.d.mts → phases-C5Steaa2.d.mts} +151 -119
  26. package/dist/plugin.index.d.mts +1 -1
  27. package/dist/plugin.index.mjs +1 -1
  28. package/dist/publishDefault-D826XXDQ.mjs +14 -0
  29. package/dist/publishDefault-D826XXDQ.mjs.map +1 -0
  30. package/dist/{slashRouteLeaves-De1SxqnX.mjs → slashRouteLeaves-BQmbFm-S.mjs} +10 -2
  31. package/dist/slashRouteLeaves-BQmbFm-S.mjs.map +1 -0
  32. package/dist/{subscribers-wnMyNYZd.mjs → subscribers-DVa-Pr45.mjs} +3 -3
  33. package/dist/{subscribers-wnMyNYZd.mjs.map → subscribers-DVa-Pr45.mjs.map} +1 -1
  34. package/package.json +19 -12
  35. package/dist/Bus-BvF4VShN.mjs.map +0 -1
  36. package/dist/Plugin-D9ykXA5g.mjs.map +0 -1
  37. package/dist/cursor-CuoA9cRh.mjs.map +0 -1
  38. package/dist/slashRouteLeaves-De1SxqnX.mjs.map +0 -1
@@ -1,9 +1,12 @@
1
- import { b as InteractionRoutes, f as BuilderComponent, v as InteractionMetadataKey, y as InteractionRouteKeys } from "./Bus-BvF4VShN.mjs";
1
+ import { b as InteractionRouteKeys, k as BuilderComponent, x as InteractionRoutes, y as InteractionMetadataKey } from "./Bus-yJGE6Fs2.mjs";
2
+ import { n as asError, t as PublishDefault } from "./publishDefault-D826XXDQ.mjs";
2
3
  import "reflect-metadata";
4
+ import { CustomId, decodeFor } from "@seedcord/custom-id";
3
5
  import { TextDisplayBuilder } from "@discordjs/builders";
4
- import { SeedcordErrorCode } from "@seedcord/errors";
5
6
  import { ApplicationCommandType, PermissionFlagsBits } from "discord-api-types/v10";
6
- import { SeedcordError, SeedcordRangeError } from "@seedcord/errors/internal";
7
+ import { SeedcordErrorCode, paint } from "@seedcord/errors";
8
+ import { SeedcordError } from "@seedcord/errors/internal";
9
+ import { Logger } from "@seedcord/logger";
7
10
  import { prettify, toEpochSeconds } from "@seedcord/utils";
8
11
 
9
12
  //#region src/stops/Notice.ts
@@ -60,8 +63,9 @@ var Notice = class extends Error {
60
63
  */
61
64
  ephemeral = true;
62
65
  /**
63
- * A short one-line reason. When every arm of an `or` gate refuses and each refusal sets this, `or`
64
- * lists them as the reply.
66
+ * A short one-line reason. When every arm of an {@link or} gate refuses and each one sets this, `or`
67
+ * throws a refusal listing them under `You need to meet at least one of these:`. Write a verb phrase that
68
+ * finishes that line, for example, "be the bot owner".
65
69
  */
66
70
  summary;
67
71
  constructor(message, options) {
@@ -80,419 +84,6 @@ var NoticeCard = class extends BuilderComponent {
80
84
  }
81
85
  };
82
86
 
83
- //#endregion
84
- //#region src/customId/Errors.ts
85
- /**
86
- * Thrown when a customId was minted by an older version of its shape.
87
- *
88
- * This is normal after the shape changes.
89
- */
90
- var StaleCustomId = class extends Notice {
91
- constructor(prefix) {
92
- super(`Stale customId for "${prefix}".`);
93
- }
94
- render() {
95
- return { components: [new NoticeCard("This button or menu is from an older version. Please run the command again.", "Outdated").component] };
96
- }
97
- };
98
- /**
99
- * Thrown when a customId wire is corrupt or tampered with and cannot be trusted.
100
- *
101
- * It reports because this is an odd case that should not happen.
102
- */
103
- var InvalidCustomId = class extends Notice {
104
- constructor(detail) {
105
- super(`Invalid customId. ${detail}`);
106
- this.report = true;
107
- }
108
- render() {
109
- return { components: [new NoticeCard("Something went wrong. Please try again.").component] };
110
- }
111
- };
112
-
113
- //#endregion
114
- //#region src/customId/codec.ts
115
- const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
116
- const BASE = 64n;
117
- const CHAR_TO_VALUE = new Map([...ALPHABET].map((char, index) => [char, index]));
118
- const DELIMITER = "";
119
- const ESCAPE = "\x1B";
120
- /** @internal */
121
- const HASH_LENGTH = 3;
122
- const SAFE_MAX = BigInt(Number.MAX_SAFE_INTEGER);
123
- const SAFE_MIN = BigInt(Number.MIN_SAFE_INTEGER);
124
- function bigintToBase64(value) {
125
- if (value === 0n) return ALPHABET.charAt(0);
126
- let text = "";
127
- for (let remaining = value; remaining > 0n; remaining /= BASE) text = ALPHABET.charAt(Number(remaining % BASE)) + text;
128
- return text;
129
- }
130
- function base64ToBigint(text) {
131
- let value = 0n;
132
- for (const char of text) {
133
- const digit = CHAR_TO_VALUE.get(char);
134
- if (digit === void 0) throw new InvalidCustomId(`bad character ${JSON.stringify(char)}`);
135
- value = value * BASE + BigInt(digit);
136
- }
137
- return value;
138
- }
139
- function zigzagEncode(value) {
140
- const big = BigInt(value);
141
- return big >= 0n ? big << 1n : (-big << 1n) - 1n;
142
- }
143
- function zigzagDecode(encoded) {
144
- return (encoded & 1n) === 1n ? -(encoded + 1n >> 1n) : encoded >> 1n;
145
- }
146
- function escapeToken(text) {
147
- return text.replaceAll(/[\x1B\x1F]/g, (char) => ESCAPE + char);
148
- }
149
- function unescapeToken(text) {
150
- let out = "";
151
- for (let i = 0; i < text.length; i++) {
152
- if (text.charAt(i) !== ESCAPE) {
153
- out += text.charAt(i);
154
- continue;
155
- }
156
- const next = text.charAt(i + 1);
157
- if (next === "") throw new InvalidCustomId("dangling escape at end of token");
158
- out += next;
159
- i++;
160
- }
161
- return out;
162
- }
163
- function splitTokens(body) {
164
- const pieces = [];
165
- let current = "";
166
- for (let i = 0; i < body.length; i++) {
167
- const char = body.charAt(i);
168
- if (char === ESCAPE) {
169
- current += char + body.charAt(i + 1);
170
- i++;
171
- } else if (char === DELIMITER) {
172
- pieces.push(current);
173
- current = "";
174
- } else current += char;
175
- }
176
- pieces.push(current);
177
- return pieces;
178
- }
179
- function isBounded(field) {
180
- if (field.kind === "int") return field.min !== void 0 && field.max !== void 0;
181
- return field.kind === "snowflake" || field.kind === "uuid" || field.kind === "bool" || field.kind === "oneOf";
182
- }
183
- function radixOf(field) {
184
- switch (field.kind) {
185
- case "snowflake": return 1n << 64n;
186
- case "uuid": return 1n << 128n;
187
- case "bool": return 2n;
188
- case "oneOf":
189
- if (!field.choices?.length) throw new InvalidCustomId("oneOf field has no choices");
190
- return BigInt(field.choices.length);
191
- case "int":
192
- if (field.min === void 0 || field.max === void 0) throw new InvalidCustomId("bounded int field is missing a bound");
193
- return BigInt(field.max) - BigInt(field.min) + 1n;
194
- default: throw new InvalidCustomId(`field kind ${field.kind} has no radix`);
195
- }
196
- }
197
- function boundedToBigint(field, name, value) {
198
- const slot = boundedSlot(field, name, value);
199
- if (slot < 0n || slot >= radixOf(field)) outOfRange(name, value);
200
- return slot;
201
- }
202
- function boundedSlot(field, name, value) {
203
- switch (field.kind) {
204
- case "snowflake":
205
- if (typeof value !== "string" || !/^\d+$/.test(value)) return outOfRange(name, value);
206
- return BigInt(value);
207
- case "uuid": {
208
- if (typeof value !== "string") return outOfRange(name, value);
209
- const hex = value.replaceAll("-", "");
210
- if (!/^[0-9a-fA-F]{32}$/.test(hex)) return outOfRange(name, value);
211
- return BigInt(`0x${hex}`);
212
- }
213
- case "bool": return value ? 1n : 0n;
214
- case "oneOf": {
215
- const index = (field.choices ?? []).indexOf(value);
216
- return index === -1 ? outOfRange(name, value) : BigInt(index);
217
- }
218
- case "int":
219
- if (!Number.isInteger(value)) return outOfRange(name, value);
220
- return BigInt(value - (field.min ?? 0));
221
- default: return outOfRange(name, value);
222
- }
223
- }
224
- function outOfRange(name, value) {
225
- throw new SeedcordRangeError(SeedcordErrorCode.CustomIdValueOutOfRange, [name, String(value)]);
226
- }
227
- function bigintToBoundedValue(field, slot) {
228
- switch (field.kind) {
229
- case "snowflake": return slot.toString();
230
- case "uuid": return bigintToUuid(slot);
231
- case "bool": return slot === 1n;
232
- case "oneOf": return (field.choices ?? [])[Number(slot)];
233
- case "int": return Number(slot) + (field.min ?? 0);
234
- default: throw new InvalidCustomId(`field kind ${field.kind} is not bounded`);
235
- }
236
- }
237
- function bigintToUuid(value) {
238
- const hex = value.toString(16).padStart(32, "0");
239
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
240
- }
241
- function encodeUnboundedToken(field, name, value) {
242
- if (field.kind === "int") {
243
- if (!Number.isSafeInteger(value)) outOfRange(name, value);
244
- return bigintToBase64(zigzagEncode(value));
245
- }
246
- return escapeToken(value);
247
- }
248
- function decodeUnboundedToken(field, piece) {
249
- if (field.kind !== "int") return unescapeToken(piece);
250
- if (piece === "") throw new InvalidCustomId("empty integer token");
251
- const decoded = zigzagDecode(base64ToBigint(piece));
252
- if (decoded > SAFE_MAX || decoded < SAFE_MIN) throw new InvalidCustomId("integer out of safe range");
253
- return Number(decoded);
254
- }
255
- /** @internal */
256
- function computeLayoutHash(shape) {
257
- const signature = JSON.stringify(Object.entries(shape).map(([name, field]) => [
258
- name,
259
- field.kind,
260
- isBounded(field),
261
- field.kind === "oneOf" ? field.choices ?? [] : null,
262
- field.kind === "int" ? [field.min ?? null, field.max ?? null] : null
263
- ]));
264
- const modulus = BASE ** BigInt(3);
265
- let hash = 0n;
266
- for (const char of signature) hash = (hash * 131n + BigInt(char.charCodeAt(0))) % modulus;
267
- let text = "";
268
- for (let i = 0; i < 3; i++) {
269
- text = ALPHABET.charAt(Number(hash % BASE)) + text;
270
- hash /= BASE;
271
- }
272
- return text;
273
- }
274
- /** @internal */
275
- function encodeBody(shape, values) {
276
- const fields = Object.entries(shape);
277
- const pieces = [];
278
- const bounded = fields.filter(([, field]) => isBounded(field));
279
- if (bounded.length > 0) {
280
- let packed = 0n;
281
- for (const [name, field] of bounded) packed = packed * radixOf(field) + boundedToBigint(field, name, values[name]);
282
- pieces.push(bigintToBase64(packed));
283
- }
284
- for (const [name, field] of fields) if (!isBounded(field)) pieces.push(encodeUnboundedToken(field, name, values[name]));
285
- return pieces.join(DELIMITER);
286
- }
287
- function unpackBounded(bounded, blob, result) {
288
- if (blob === void 0 || blob === "") throw new InvalidCustomId("empty packed block");
289
- let packed = base64ToBigint(blob);
290
- for (const [name, field] of [...bounded].reverse()) {
291
- const radix = radixOf(field);
292
- result[name] = bigintToBoundedValue(field, packed % radix);
293
- packed /= radix;
294
- }
295
- if (packed !== 0n) throw new InvalidCustomId("leftover bits after unpacking");
296
- }
297
- /** @internal */
298
- function decodeBody(shape, body) {
299
- const fields = Object.entries(shape);
300
- const bounded = fields.filter(([, field]) => isBounded(field));
301
- const unbounded = fields.filter(([, field]) => !isBounded(field));
302
- const expected = (bounded.length > 0 ? 1 : 0) + unbounded.length;
303
- if (expected === 0) {
304
- if (body !== "") throw new InvalidCustomId(`expected an empty body, got ${JSON.stringify(body)}`);
305
- return {};
306
- }
307
- const pieces = splitTokens(body);
308
- if (pieces.length !== expected) throw new InvalidCustomId(`expected ${expected} piece(s), got ${pieces.length}`);
309
- const result = {};
310
- let cursor = 0;
311
- if (bounded.length > 0) {
312
- unpackBounded(bounded, pieces[cursor], result);
313
- cursor++;
314
- }
315
- for (const [name, field] of unbounded) {
316
- const piece = pieces[cursor];
317
- cursor++;
318
- if (piece === void 0) throw new InvalidCustomId("missing trailing piece");
319
- result[name] = decodeUnboundedToken(field, piece);
320
- }
321
- return result;
322
- }
323
-
324
- //#endregion
325
- //#region src/customId/CustomId.ts
326
- const MAX_WIRE_LENGTH = 100;
327
- function routeKeyOf(wire) {
328
- const colon = wire.indexOf(":");
329
- return colon === -1 ? "" : wire.slice(0, colon);
330
- }
331
- /** Strip the layout hash off the routeKey to recover the stable prefix the controller routes by. @internal */
332
- function prefixOf(wire) {
333
- const key = routeKeyOf(wire);
334
- return key.length <= 3 ? "" : key.slice(0, key.length - 3);
335
- }
336
- /**
337
- * A typed customId. The single source of truth shared by the component that mints it and the handler
338
- * that reads it. This gives you typed reads on the `.customId` field in components. Values are packed
339
- * into a compact wire string, which fits more of them inside Discord's 100-char limit.
340
- *
341
- * @typeParam Prefix - The stable route prefix, e.g. 'approve'.
342
- * @typeParam Shape - The accumulated fields, filled in by the chain.
343
- *
344
- * @example
345
- * ```ts
346
- * const ApproveId = new CustomId('approve')
347
- * .snowflake('userId')
348
- * .oneOf('action', ['approve', 'deny']);
349
- *
350
- * // Set the custom id on a button when creating it.
351
- * new ButtonBuilder().setCustomId(ApproveId.encode({ userId: '123', action: 'approve' }));
352
- *
353
- * // in the handler, userId comes back a string
354
- * const { userId, action } = this.params; // userId: string, action: 'approve' | 'deny'
355
- * await this.event.guild?.members.fetch(userId);
356
- * ```
357
- */
358
- var CustomId = class CustomId {
359
- /** The stable route prefix, e.g. 'approve'. */
360
- prefix;
361
- /** The field definitions accumulated by the chain, keyed by name. */
362
- shape;
363
- /** The prefix plus a short hash of the shape, the part of the wire before the colon. */
364
- routeKey;
365
- constructor(prefix, shape = {}) {
366
- if (!prefix || /[:\x1B\x1F]/.test(prefix)) throw new SeedcordError(SeedcordErrorCode.CustomIdInvalidPrefix, [prefix]);
367
- this.prefix = prefix;
368
- this.shape = shape;
369
- this.routeKey = prefix + computeLayoutHash(shape);
370
- }
371
- add(name, field) {
372
- if (/^(?:0|[1-9]\d*)$/.test(name)) throw new SeedcordError(SeedcordErrorCode.CustomIdReservedFieldName, [name]);
373
- if (name in this.shape) throw new SeedcordError(SeedcordErrorCode.CustomIdDuplicateFieldName, [name]);
374
- const shape = {
375
- ...this.shape,
376
- [name]: field
377
- };
378
- return new CustomId(this.prefix, shape);
379
- }
380
- /**
381
- * Add a Discord ID field, decoded as a string (the `Snowflake` type from discord-api-types).
382
- *
383
- * @example
384
- * ```ts
385
- * new CustomId('ban').snowflake('userId');
386
- * ```
387
- */
388
- snowflake(name) {
389
- return this.add(name, { kind: "snowflake" });
390
- }
391
- /**
392
- * Add a UUID field, decoded as a lowercase uuid string.
393
- *
394
- * @example
395
- * ```ts
396
- * new CustomId('ticket').uuid('ticketId');
397
- * ```
398
- */
399
- uuid(name) {
400
- return this.add(name, { kind: "uuid" });
401
- }
402
- int(name, min, max) {
403
- if (min !== void 0 && max !== void 0 && min > max) throw new SeedcordError(SeedcordErrorCode.CustomIdInvalidBounds, [
404
- name,
405
- min,
406
- max
407
- ]);
408
- const field = min === void 0 || max === void 0 ? { kind: "int" } : {
409
- kind: "int",
410
- min,
411
- max
412
- };
413
- return this.add(name, field);
414
- }
415
- /**
416
- * Add a boolean flag.
417
- *
418
- * @example
419
- * ```ts
420
- * new CustomId('settings').bool('silent');
421
- * ```
422
- */
423
- bool(name) {
424
- return this.add(name, { kind: "bool" });
425
- }
426
- /**
427
- * Add a field that is one value from a fixed list, decoded as the literal union. No `as const` needed.
428
- *
429
- * @example
430
- * ```ts
431
- * new CustomId('poll').oneOf('choice', ['yes', 'no', 'abstain']);
432
- * ```
433
- */
434
- oneOf(name, choices) {
435
- if (choices.length === 0) throw new SeedcordError(SeedcordErrorCode.CustomIdEmptyChoices, [name]);
436
- return this.add(name, {
437
- kind: "oneOf",
438
- choices
439
- });
440
- }
441
- /**
442
- * Add a free short text field. Avoid it where possible. It cannot be packed, which makes it the most
443
- * expensive field on the wire.
444
- *
445
- * @example
446
- * ```ts
447
- * new CustomId('note').str('message');
448
- * ```
449
- */
450
- str(name) {
451
- return this.add(name, { kind: "string" });
452
- }
453
- /**
454
- * Mint a wire string from values. Throws if a value is out of its field's range or the wire is over 100 chars.
455
- *
456
- * @param values - One value per field, typed by the chain.
457
- * @returns The wire string to put on the component's customId.
458
- */
459
- encode(values) {
460
- const wire = `${this.routeKey}:${encodeBody(this.shape, values)}`;
461
- if (wire.length > MAX_WIRE_LENGTH) throw new SeedcordRangeError(SeedcordErrorCode.CustomIdWireTooLong, [wire.length]);
462
- return wire;
463
- }
464
- /**
465
- * Read a wire string back into values.
466
- *
467
- * - This will refuse with a default Notice when the shape changed since the wire was minted.
468
- * - Or refuse with a different default Notice on a corrupt or foreign wire.
469
- *
470
- * @param wire - The customId string from the interaction.
471
- * @returns The decoded values, typed by the chain.
472
- */
473
- decode(wire) {
474
- const key = routeKeyOf(wire);
475
- if (key !== this.routeKey) {
476
- if (prefixOf(wire) === this.prefix) throw new StaleCustomId(this.prefix);
477
- throw new InvalidCustomId(`routeKey ${JSON.stringify(key)} is not ${JSON.stringify(this.routeKey)}`);
478
- }
479
- return decodeBody(this.shape, wire.slice(key.length + 1));
480
- }
481
- /** True if this wire was minted from this customId's prefix, ignoring the shape hash. */
482
- owns(wire) {
483
- return prefixOf(wire) === this.prefix;
484
- }
485
- };
486
- /** @internal */
487
- function decodeFor(defs, wire) {
488
- const match = defs.find((def) => def.owns(wire));
489
- if (!match) throw new InvalidCustomId(`no customId owns ${JSON.stringify(routeKeyOf(wire))}`);
490
- return {
491
- prefix: match.prefix,
492
- params: match.decode(wire)
493
- };
494
- }
495
-
496
87
  //#endregion
497
88
  //#region src/customId/routing.ts
498
89
  /**
@@ -566,6 +157,285 @@ function contextMenuRouteOf(kind) {
566
157
  return kind === ApplicationCommandType.User ? "userContextMenu" : "messageContextMenu";
567
158
  }
568
159
 
160
+ //#endregion
161
+ //#region src/reply/ackLegality.ts
162
+ const showModalReason = {
163
+ reason: "this interaction was already acknowledged",
164
+ alternative: "showModal() must be the initial response."
165
+ };
166
+ function deferReason(what) {
167
+ return {
168
+ reason: `this interaction was already acknowledged with ${what}`,
169
+ alternative: "A deferral can only be the initial response."
170
+ };
171
+ }
172
+ const ILLEGAL = {
173
+ reply: {
174
+ "deferred-reply": {
175
+ reason: "this interaction was already deferred",
176
+ alternative: "Use edit() to fill the deferred reply or followUp() for a new message."
177
+ },
178
+ "deferred-update": {
179
+ reason: "this interaction was already acknowledged with a deferred update",
180
+ alternative: "Use followUp() for a new message."
181
+ },
182
+ replied: {
183
+ reason: "this interaction was already replied to",
184
+ alternative: "Use followUp() for a new message or edit() to rewrite the reply."
185
+ }
186
+ },
187
+ defer: {
188
+ "deferred-reply": deferReason("a deferred reply"),
189
+ "deferred-update": deferReason("a deferred update"),
190
+ replied: deferReason("a reply")
191
+ },
192
+ deferUpdate: {
193
+ "deferred-reply": deferReason("a deferred reply"),
194
+ "deferred-update": deferReason("a deferred update"),
195
+ replied: deferReason("a reply")
196
+ },
197
+ update: {
198
+ "deferred-reply": {
199
+ reason: "this interaction was deferred as a reply",
200
+ alternative: "The placeholder is yours via edit(). update() rewrites a source message."
201
+ },
202
+ replied: {
203
+ reason: "this interaction was already replied to",
204
+ alternative: "update() rewrites a source message. Use edit() or followUp() instead."
205
+ }
206
+ },
207
+ followUp: { unacked: {
208
+ reason: "this interaction has not been acknowledged yet",
209
+ alternative: "Ack first with reply() or defer()."
210
+ } },
211
+ edit: { unacked: {
212
+ reason: "nothing has been sent yet",
213
+ alternative: "Send with reply() or defer() first, then edit() rewrites it."
214
+ } },
215
+ delete: { unacked: {
216
+ reason: "nothing has been sent yet",
217
+ alternative: "Acknowledge first with reply() or defer(), then delete() removes it."
218
+ } },
219
+ showModal: {
220
+ "deferred-reply": showModalReason,
221
+ "deferred-update": showModalReason,
222
+ replied: showModalReason
223
+ }
224
+ };
225
+ const SEND_TARGET = {
226
+ unacked: "reply",
227
+ "deferred-reply": "edit",
228
+ "deferred-update": "followUp",
229
+ replied: "followUp"
230
+ };
231
+ function checkAckLegality(method, state, routeId, ackedBy) {
232
+ if (method === "send") return;
233
+ const illegal = ILLEGAL[method][state];
234
+ if (!illegal) return;
235
+ throw new SeedcordError(SeedcordErrorCode.ReplyIllegalAckState, [
236
+ method,
237
+ illegal.reason,
238
+ illegal.alternative,
239
+ routeId
240
+ ], { cause: ackedBy });
241
+ }
242
+ function sendTarget(state) {
243
+ return SEND_TARGET[state];
244
+ }
245
+
246
+ //#endregion
247
+ //#region src/reply/AckTrace.ts
248
+ var AckTrace = class extends Error {
249
+ constructor(method) {
250
+ super(`${method}() acknowledged this interaction`);
251
+ this.name = "AckTrace";
252
+ }
253
+ };
254
+
255
+ //#endregion
256
+ //#region src/reply/responseReport.ts
257
+ let replyLogger;
258
+ function logger() {
259
+ replyLogger ??= new Logger("Reply", { channel: "interactions" });
260
+ return replyLogger;
261
+ }
262
+ /** @internal */
263
+ function publishResponse(telemetry, report) {
264
+ const durationMs = performance.now() - report.startedAt;
265
+ const write = {
266
+ routeId: report.routeId,
267
+ interactionId: telemetry.interactionId,
268
+ method: report.method,
269
+ durationMs
270
+ };
271
+ telemetry.bus[PublishDefault]("responseAttempted", report.outcome === "failed" ? {
272
+ ...write,
273
+ outcome: "failed",
274
+ messageId: null,
275
+ error: report.error
276
+ } : {
277
+ ...write,
278
+ outcome: "sent",
279
+ messageId: report.messageId
280
+ });
281
+ logger().trace(`${paint.sky.bold(report.routeId)} ${report.method} ${report.outcome} ${paint.mute("in")} ${Math.round(durationMs)}ms`);
282
+ }
283
+ async function reportedWrite(telemetry, routeId, method, write) {
284
+ const startedAt = performance.now();
285
+ const result = await attemptWrite(telemetry, routeId, method, startedAt, write);
286
+ publishResponse(telemetry, {
287
+ routeId,
288
+ method,
289
+ startedAt,
290
+ outcome: "sent",
291
+ messageId: null
292
+ });
293
+ return result;
294
+ }
295
+ async function attemptWrite(telemetry, routeId, method, startedAt, write) {
296
+ try {
297
+ return await write();
298
+ } catch (caught) {
299
+ publishResponse(telemetry, {
300
+ routeId,
301
+ method,
302
+ startedAt,
303
+ outcome: "failed",
304
+ error: asError(caught)
305
+ });
306
+ throw caught;
307
+ }
308
+ }
309
+
310
+ //#endregion
311
+ //#region src/reply/translateSerialization.ts
312
+ const MAX_DEPTH = 8;
313
+ const OPERATOR_WORD = {
314
+ "<=": "at most",
315
+ ">=": "at least",
316
+ "<": "fewer than",
317
+ ">": "more than"
318
+ };
319
+ const KIND_WORD = {
320
+ string: "a string",
321
+ number: "a number",
322
+ boolean: "a boolean",
323
+ bigint: "a bigint",
324
+ array: "a list",
325
+ tuple: "a list",
326
+ date: "a date",
327
+ object: "an object"
328
+ };
329
+ function asRecord(value) {
330
+ return typeof value === "object" && value !== null ? value : null;
331
+ }
332
+ function firstLine(message) {
333
+ if (typeof message !== "string") return "unknown validation failure";
334
+ return message.split("\n")[0] ?? message;
335
+ }
336
+ const GIVEN_PREVIEW_LIMIT = 40;
337
+ function gotPhrase(given) {
338
+ if (given === void 0) return "nothing";
339
+ if (given === null) return "null";
340
+ if (typeof given === "string") {
341
+ if (given.length === 0) return "an empty string";
342
+ if (given.length > GIVEN_PREVIEW_LIMIT) return `${given.length} characters`;
343
+ return JSON.stringify(given);
344
+ }
345
+ if (Array.isArray(given)) return given.length === 0 ? "an empty list" : `${given.length} items`;
346
+ if (typeof given === "number" || typeof given === "bigint" || typeof given === "boolean") return String(given);
347
+ if (typeof given === "object") return given.constructor?.name ?? "an object";
348
+ return typeof given;
349
+ }
350
+ function needPhrase(expected, given) {
351
+ const bounds = [...expected.matchAll(/expected(\.length)?\s*(<=|>=|<|>)\s*(\d+)/g)];
352
+ if (bounds.length === 0) return null;
353
+ return bounds.map(([, lengthPart, operator, limit]) => {
354
+ const word = OPERATOR_WORD[operator ?? ""] ?? operator ?? "";
355
+ if (!lengthPart) return `${word} ${limit}`;
356
+ const unit = typeof given === "string" ? "character" : "item";
357
+ return `${word} ${limit} ${Number(limit) === 1 ? unit : `${unit}s`}`;
358
+ }).join(" and ");
359
+ }
360
+ function instanceName(expected) {
361
+ if (typeof expected === "function" && expected.name) return expected.name;
362
+ return "specific class instance";
363
+ }
364
+ function describeLeaf(record) {
365
+ if (typeof record.constraint === "string") {
366
+ const need = typeof record.expected === "string" ? needPhrase(record.expected, record.given) : null;
367
+ if (need) return `need ${need}, got ${gotPhrase(record.given)}`;
368
+ return `${firstLine(record.message)}${"given" in record ? ` (got ${gotPhrase(record.given)})` : ""}`;
369
+ }
370
+ if (typeof record.expected === "function") return `need a ${instanceName(record.expected)}, got ${gotPhrase(record.given)}`;
371
+ if (typeof record.validator === "string") {
372
+ const kind = KIND_WORD[record.validator.replace(/^s\./, "").replace(/\(.*$/, "")];
373
+ if (kind) return `need ${kind}, got ${gotPhrase(record.given)}`;
374
+ return `${firstLine(record.message)}, got ${gotPhrase(record.given)}`;
375
+ }
376
+ return firstLine(record.message);
377
+ }
378
+ function describeUnion(branches, depth) {
379
+ const records = [];
380
+ for (const branch of branches) {
381
+ const record = asRecord(branch);
382
+ if (record) records.push(record);
383
+ }
384
+ const bounded = records.find((record) => typeof record.constraint === "string");
385
+ if (bounded) return describeLeaf(bounded);
386
+ const names = [];
387
+ let allInstances = records.length > 0;
388
+ for (const record of records) if (typeof record.expected === "function") names.push(instanceName(record.expected));
389
+ else allInstances = false;
390
+ if (allInstances) return `need a ${[...new Set(names)].join(" or ")}, got ${gotPhrase(records[0]?.given)}`;
391
+ return detailOf(branches[0], depth + 1);
392
+ }
393
+ function detailOf(value, depth) {
394
+ const record = asRecord(value);
395
+ if (!record || depth >= MAX_DEPTH) return firstLine(record?.message);
396
+ if (Array.isArray(record.errors) && record.errors.length > 0) {
397
+ const errors = record.errors;
398
+ const [first] = errors;
399
+ if (Array.isArray(first)) {
400
+ const [key, inner] = first;
401
+ return `${String(key)}: ${detailOf(inner, depth + 1)}`;
402
+ }
403
+ return describeUnion(errors, depth);
404
+ }
405
+ return describeLeaf(record);
406
+ }
407
+ function translateSerializationError(error, componentClass, index, routeId) {
408
+ const detail = detailOf(error, 0);
409
+ return new SeedcordError(SeedcordErrorCode.ReplyComponentSerialization, [
410
+ componentClass,
411
+ index,
412
+ detail,
413
+ routeId
414
+ ], { cause: error });
415
+ }
416
+
417
+ //#endregion
418
+ //#region src/reply/serializeReply.ts
419
+ function serializeReply(response, routeId) {
420
+ if (typeof response === "string") try {
421
+ return { components: [new TextDisplayBuilder().setContent(response).toJSON()] };
422
+ } catch (error) {
423
+ throw translateSerializationError(error, "TextDisplayBuilder", 0, routeId);
424
+ }
425
+ return {
426
+ components: response.components.map((component, index) => {
427
+ try {
428
+ return component.toJSON();
429
+ } catch (error) {
430
+ const name = component.constructor?.name;
431
+ throw translateSerializationError(error, !name || name === "Object" ? "component" : name, index, routeId);
432
+ }
433
+ }),
434
+ ...response.allowedMentions && { allowedMentions: response.allowedMentions },
435
+ ...response.files && { files: response.files }
436
+ };
437
+ }
438
+
569
439
  //#endregion
570
440
  //#region src/gates/Gate.ts
571
441
  /**
@@ -660,23 +530,30 @@ var GateNotice = class extends Notice {
660
530
  var NotOwner = class extends GateNotice {
661
531
  constructor(message = "Only the bot owner can use this.") {
662
532
  super(message);
533
+ this.summary = "be the bot owner";
663
534
  }
664
535
  };
665
536
  var NotInGuild = class extends GateNotice {
666
537
  constructor(message = "This can only be used in a server.") {
667
538
  super(message);
539
+ this.summary = "be in a server";
668
540
  }
669
541
  };
670
542
  var NotInDm = class extends GateNotice {
671
543
  constructor(message = "This can only be used in a direct message.") {
672
544
  super(message);
545
+ this.summary = "be in a direct message";
673
546
  }
674
547
  };
548
+ function retryAt(resetAt) {
549
+ return `<t:${toEpochSeconds(resetAt)}:R>`;
550
+ }
675
551
  var OnCooldown = class extends GateNotice {
676
552
  resetAt;
677
553
  constructor(resetAt, message) {
678
- super(message ?? `You are doing that too fast. Try again <t:${toEpochSeconds(resetAt)}:R>.`);
554
+ super(message ?? `You are doing that too fast. Try again ${retryAt(resetAt)}.`);
679
555
  this.resetAt = resetAt;
556
+ this.summary = `try again ${retryAt(resetAt)}`;
680
557
  }
681
558
  };
682
559
  var NotAllowed = class extends Notice {
@@ -691,16 +568,17 @@ var NeedsAny = class extends Notice {
691
568
  summaries;
692
569
  constructor(summaries) {
693
570
  super("not allowed");
694
- this.summaries = summaries;
571
+ this.summaries = [...new Set(summaries)];
695
572
  }
696
573
  render() {
697
574
  const bullets = this.summaries.map((summary) => `• ${summary}`).join("\n");
698
- return { components: [new NoticeCard(`You need any of:\n${bullets}`).component] };
575
+ return { components: [new NoticeCard(`You need to meet at least one of these:\n${bullets}`).component] };
699
576
  }
700
577
  };
701
578
  var MissingRole = class extends GateNotice {
702
579
  constructor(message, roleId) {
703
580
  super(message ?? (roleId ? `You need the <@&${roleId}> role to use this.` : "You do not have the required role."));
581
+ this.summary = roleId ? `hold the <@&${roleId}> role` : "hold the required role";
704
582
  }
705
583
  };
706
584
  var MissingPermissions = class extends Notice {
@@ -712,6 +590,8 @@ var MissingPermissions = class extends Notice {
712
590
  this.subject = subject;
713
591
  this.missingPerms = missingPerms;
714
592
  this.customLead = message;
593
+ const names = missingPerms.join(", ");
594
+ this.summary = subject === null ? `hold ${names}` : `${subject} needs ${names}`;
715
595
  }
716
596
  render() {
717
597
  const bullets = this.missingPerms.map((perm) => `• ${perm}`).join("\n");
@@ -728,6 +608,7 @@ var HasDangerousPermissions = class extends Notice {
728
608
  this.subject = subject;
729
609
  this.dangerousPerms = dangerousPerms;
730
610
  this.customLead = message;
611
+ this.summary = `${subject} must not hold ${dangerousPerms.join(", ")}`;
731
612
  }
732
613
  render() {
733
614
  const bullets = this.dangerousPerms.map((perm) => `• ${perm}`).join("\n");
@@ -875,8 +756,9 @@ function RequireRole(roleId, options) {
875
756
  * ```ts
876
757
  * import { Silence } from '@seedcord/gateway';
877
758
  *
878
- * // before any reply or defer, drop the interaction with no reply and no report
879
- * if (await isBlacklisted(interaction.user.id)) throw new Silence('blacklisted user');
759
+ * // a helper under a guildMemberAdd handler, stopping when this guild configured nothing to send
760
+ * const template = await settings.welcome(member.guild.id);
761
+ * if (!template) throw new Silence('no welcome message set');
880
762
  * ```
881
763
  */
882
764
  var Silence = class extends Error {
@@ -901,5 +783,5 @@ function pageCursor(prefix) {
901
783
  }
902
784
 
903
785
  //#endregion
904
- export { areRoutes as A, prefixOf as B, NotInDm as C, defineEffectGate as D, OnCooldown as E, storeInteractionRoute as F, Notice as H, ComponentDefsKey as I, decodeComponentRoute as L, interactionRoutesOf as M, selectMenuRouteOf as N, defineGate as O, storeComponentRoute as P, CustomId as R, NotAllowed as S, NotOwner as T, NoticeCard as V, GateNotice as _, RequirePermissions as a, MissingRole as b, hasAll as c, pickNotice as d, discardCommits as f, runCommits as g, runCheck as h, RequireBotPermissions as i, contextMenuRouteOf as j, SelectMenuKind as k, missingNames as l, rollbackCommits as m, pageCursor as n, RequireRole as o, markCommits as p, Silence as r, PermissionNames as s, PAGE_MAX as t, presentNames as u, HasDangerousPermissions as v, NotInGuild as w, NeedsAny as x, MissingPermissions as y, decodeFor as z };
905
- //# sourceMappingURL=cursor-CuoA9cRh.mjs.map
786
+ export { translateSerializationError as A, interactionRoutesOf as B, NotInDm as C, defineEffectGate as D, OnCooldown as E, checkAckLegality as F, decodeComponentRoute as G, storeComponentRoute as H, sendTarget as I, NoticeCard as K, SelectMenuKind as L, publishResponse as M, reportedWrite as N, defineGate as O, AckTrace as P, areRoutes as R, NotAllowed as S, NotOwner as T, storeInteractionRoute as U, selectMenuRouteOf as V, ComponentDefsKey as W, GateNotice as _, RequirePermissions as a, MissingRole as b, hasAll as c, pickNotice as d, discardCommits as f, runCommits as g, runCheck as h, RequireBotPermissions as i, attemptWrite as j, serializeReply as k, missingNames as l, rollbackCommits as m, pageCursor as n, RequireRole as o, markCommits as p, Notice as q, Silence as r, PermissionNames as s, PAGE_MAX as t, presentNames as u, HasDangerousPermissions as v, NotInGuild as w, NeedsAny as x, MissingPermissions as y, contextMenuRouteOf as z };
787
+ //# sourceMappingURL=cursor-CaT051ii.mjs.map