@seedcord/core 0.3.0 → 0.4.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/dist/{Bus-DcuUtOEL.mjs → Bus-BVwmKc-A.mjs} +51 -17
- package/dist/Bus-BVwmKc-A.mjs.map +1 -0
- package/dist/Pluggable-Cf5-WWmW.mjs +280 -0
- package/dist/Pluggable-Cf5-WWmW.mjs.map +1 -0
- package/dist/Pluggable-DFNUIlJM.d.mts +148 -0
- package/dist/{Plugin-Bkeqcjy6.mjs → Plugin-C6oMqWf8.mjs} +32 -7
- package/dist/Plugin-C6oMqWf8.mjs.map +1 -0
- package/dist/Plugin-CG9pWbPF.d.mts +193 -0
- package/dist/{cursor-DKKKUa_7.d.mts → cursor-BcRfmTSo.d.mts} +27 -140
- package/dist/{cursor-Cv1Ui6gZ.mjs → cursor-GBUYz3jk.mjs} +377 -176
- package/dist/cursor-GBUYz3jk.mjs.map +1 -0
- package/dist/{index-uBwh8r7x.d.mts → index-ZzdQafFv.d.mts} +2 -2
- package/dist/index.d.mts +132 -19
- package/dist/index.mjs +186 -19
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +11 -4
- package/dist/internal.index.mjs +8 -455
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +10 -146
- package/dist/node-internal.index.mjs +43 -470
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +2 -0
- package/dist/node.index.mjs +256 -0
- package/dist/node.index.mjs.map +1 -0
- package/dist/{Plugin-B6iRzm3g.d.mts → phases-F8yvPd60.d.mts} +111 -207
- package/dist/plugin.index.d.mts +2 -2
- package/dist/plugin.index.mjs +1 -1
- package/dist/publishDefault-D826XXDQ.mjs +14 -0
- package/dist/publishDefault-D826XXDQ.mjs.map +1 -0
- package/dist/{slashRouteLeaves-De1SxqnX.mjs → slashRouteLeaves-CbYRwqMY.mjs} +10 -2
- package/dist/slashRouteLeaves-CbYRwqMY.mjs.map +1 -0
- package/dist/subscribers-CGGQkUfM.mjs +139 -0
- package/dist/subscribers-CGGQkUfM.mjs.map +1 -0
- package/package.json +14 -8
- package/dist/Bus-DcuUtOEL.mjs.map +0 -1
- package/dist/Plugin-Bkeqcjy6.mjs.map +0 -1
- package/dist/cursor-Cv1Ui6gZ.mjs.map +0 -1
- package/dist/slashRouteLeaves-De1SxqnX.mjs.map +0 -1
- package/dist/subscribers-DuHg-bGT.mjs +0 -3
|
@@ -1,158 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as InteractionMetadataKey, T as InteractionRoutes, g as BuilderComponent, w as InteractionRouteKeys } from "./Bus-BVwmKc-A.mjs";
|
|
2
|
+
import { n as asError, t as PublishDefault } from "./publishDefault-D826XXDQ.mjs";
|
|
2
3
|
import "reflect-metadata";
|
|
3
|
-
import {
|
|
4
|
+
import { TextDisplayBuilder } from "@discordjs/builders";
|
|
4
5
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
5
6
|
import { Logger } from "@seedcord/logger";
|
|
6
|
-
import { routeLeavesOf } from "@seedcord/utils/internal";
|
|
7
7
|
import { ApplicationCommandType, PermissionFlagsBits } from "discord-api-types/v10";
|
|
8
|
-
import { SeedcordError, SeedcordRangeError } from "@seedcord/errors/internal";
|
|
8
|
+
import { SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
|
|
9
9
|
import { prettify, toEpochSeconds } from "@seedcord/utils";
|
|
10
10
|
|
|
11
|
-
//#region src/miscellaneous/guarded.ts
|
|
12
|
-
function accessorStore() {
|
|
13
|
-
return Object.create(null);
|
|
14
|
-
}
|
|
15
|
-
function clearStore(store) {
|
|
16
|
-
for (const key of Object.keys(store)) Reflect.deleteProperty(store, key);
|
|
17
|
-
}
|
|
18
|
-
function guardedAccessor(accessor, storage) {
|
|
19
|
-
return new Proxy(storage, { get(target, key, receiver) {
|
|
20
|
-
if (typeof key !== "string" || Object.hasOwn(target, key)) return Reflect.get(target, key, receiver);
|
|
21
|
-
throw new SeedcordError(SeedcordErrorCode.CoreAccessorUnresolved, [accessor, key]);
|
|
22
|
-
} });
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/commands/CommandInjector.ts
|
|
27
|
-
const commandStorage = accessorStore();
|
|
28
|
-
const userMenuStorage = accessorStore();
|
|
29
|
-
const messageMenuStorage = accessorStore();
|
|
30
|
-
/**
|
|
31
|
-
* The bot's deployed slash commands, keyed by {@link SlashRegistry}. Filled by
|
|
32
|
-
* {@link CommandInjector} after each deploy, and a read before that throws.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* import { Commands } from '@seedcord/gateway';
|
|
37
|
-
*
|
|
38
|
-
* await this.reply(`Try ${Commands['config/set'].mention}`);
|
|
39
|
-
*
|
|
40
|
-
* // a help listing
|
|
41
|
-
* const lines = Object.values(Commands).map((c) => `${c.mention} ${c.description}`);
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
const Commands = guardedAccessor("Commands", commandStorage);
|
|
45
|
-
/**
|
|
46
|
-
* The bot's deployed context-menu commands. Filled by {@link CommandInjector} after each deploy, and a read
|
|
47
|
-
* before that throws.
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```ts
|
|
51
|
-
* import { ContextMenus } from '@seedcord/gateway';
|
|
52
|
-
*
|
|
53
|
-
* const id = ContextMenus.user['View Profile'].id;
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
const ContextMenus = {
|
|
57
|
-
user: guardedAccessor("ContextMenus.user", userMenuStorage),
|
|
58
|
-
message: guardedAccessor("ContextMenus.message", messageMenuStorage)
|
|
59
|
-
};
|
|
60
|
-
const MULTI_GUILD = Symbol("seedcord:commands:multi-guild");
|
|
61
|
-
function commandKey(type, name) {
|
|
62
|
-
return `${type}:${name}`;
|
|
63
|
-
}
|
|
64
|
-
function slashBuildersOf(all) {
|
|
65
|
-
const slash = all.filter((command) => command instanceof SlashCommandBuilder);
|
|
66
|
-
return [...new Set(slash)];
|
|
67
|
-
}
|
|
68
|
-
/** @internal */
|
|
69
|
-
var CommandInjector = class {
|
|
70
|
-
logger = new Logger("Commands", { channel: "bot" });
|
|
71
|
-
inject(deploy, all) {
|
|
72
|
-
this.clear();
|
|
73
|
-
const ids = {
|
|
74
|
-
global: this.indexByKey(deploy.global),
|
|
75
|
-
guild: this.indexGuilds(deploy.guilds),
|
|
76
|
-
warned: /* @__PURE__ */ new Set()
|
|
77
|
-
};
|
|
78
|
-
let clickable = 0;
|
|
79
|
-
for (const command of slashBuildersOf(all)) {
|
|
80
|
-
const id = this.resolveId(ApplicationCommandType.ChatInput, command.name, ids, "falling back to plain text (not clickable)");
|
|
81
|
-
for (const leaf of routeLeavesOf(command.toJSON())) {
|
|
82
|
-
commandStorage[leaf.route] = {
|
|
83
|
-
mention: id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route),
|
|
84
|
-
...id && { id },
|
|
85
|
-
route: leaf.route,
|
|
86
|
-
description: leaf.description
|
|
87
|
-
};
|
|
88
|
-
if (id) clickable++;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const menus = contextMenuLeaves(all);
|
|
92
|
-
this.injectMenus(menus.user, ApplicationCommandType.User, userMenuStorage, ids);
|
|
93
|
-
this.injectMenus(menus.message, ApplicationCommandType.Message, messageMenuStorage, ids);
|
|
94
|
-
this.logger.utils.summary("Linked commands", {
|
|
95
|
-
clickable,
|
|
96
|
-
plain: Object.keys(commandStorage).length - clickable,
|
|
97
|
-
"context menus": menus.user.size + menus.message.size
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
injectMenus(names, kind, storage, ids) {
|
|
101
|
-
for (const name of names) {
|
|
102
|
-
const id = this.resolveId(kind, name, ids, "leaving its id unset");
|
|
103
|
-
storage[name] = {
|
|
104
|
-
...id && { id },
|
|
105
|
-
name,
|
|
106
|
-
kind
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
indexByKey(collection) {
|
|
111
|
-
const map = /* @__PURE__ */ new Map();
|
|
112
|
-
for (const command of collection.values()) map.set(commandKey(command.type, command.name), command.id);
|
|
113
|
-
return map;
|
|
114
|
-
}
|
|
115
|
-
indexGuilds(guilds) {
|
|
116
|
-
const map = /* @__PURE__ */ new Map();
|
|
117
|
-
for (const collection of guilds.values()) for (const command of collection.values()) {
|
|
118
|
-
const key = commandKey(command.type, command.name);
|
|
119
|
-
const existing = map.get(key);
|
|
120
|
-
if (existing === void 0) map.set(key, command.id);
|
|
121
|
-
else if (existing !== command.id) map.set(key, MULTI_GUILD);
|
|
122
|
-
}
|
|
123
|
-
return map;
|
|
124
|
-
}
|
|
125
|
-
resolveId(type, name, ids, consequence) {
|
|
126
|
-
const key = commandKey(type, name);
|
|
127
|
-
const global = ids.global.get(key);
|
|
128
|
-
if (global) return global;
|
|
129
|
-
const guild = ids.guild.get(key);
|
|
130
|
-
if (guild === void 0) return void 0;
|
|
131
|
-
if (guild === MULTI_GUILD) {
|
|
132
|
-
if (!ids.warned.has(key)) {
|
|
133
|
-
this.logger.warn(`${paint.sky.bold(name)} is deployed to multiple guilds, ${consequence}.`);
|
|
134
|
-
ids.warned.add(key);
|
|
135
|
-
}
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
return guild;
|
|
139
|
-
}
|
|
140
|
-
toMention(route, id) {
|
|
141
|
-
return `</${route.replaceAll("/", " ")}:${id}>`;
|
|
142
|
-
}
|
|
143
|
-
toPlain(route) {
|
|
144
|
-
return `/${route.replaceAll("/", " ")}`;
|
|
145
|
-
}
|
|
146
|
-
clear() {
|
|
147
|
-
for (const store of [
|
|
148
|
-
commandStorage,
|
|
149
|
-
userMenuStorage,
|
|
150
|
-
messageMenuStorage
|
|
151
|
-
]) clearStore(store);
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
//#endregion
|
|
156
11
|
//#region src/stops/Notice.ts
|
|
157
12
|
/**
|
|
158
13
|
* Base class for a user-facing refusal or a reported fault.
|
|
@@ -207,8 +62,9 @@ var Notice = class extends Error {
|
|
|
207
62
|
*/
|
|
208
63
|
ephemeral = true;
|
|
209
64
|
/**
|
|
210
|
-
* A short one-line reason. When every arm of an
|
|
211
|
-
*
|
|
65
|
+
* A short one-line reason. When every arm of an {@link or} gate refuses and each one sets this, `or`
|
|
66
|
+
* throws a refusal listing them under `You need to meet at least one of these:`. Write a verb phrase that
|
|
67
|
+
* finishes that line, for example, "be the bot owner".
|
|
212
68
|
*/
|
|
213
69
|
summary;
|
|
214
70
|
constructor(message, options) {
|
|
@@ -328,6 +184,9 @@ function isBounded(field) {
|
|
|
328
184
|
return field.kind === "snowflake" || field.kind === "uuid" || field.kind === "bool" || field.kind === "oneOf";
|
|
329
185
|
}
|
|
330
186
|
function radixOf(field) {
|
|
187
|
+
return kindRadix(field) + (field.nullable === true ? 1n : 0n);
|
|
188
|
+
}
|
|
189
|
+
function kindRadix(field) {
|
|
331
190
|
switch (field.kind) {
|
|
332
191
|
case "snowflake": return 1n << 64n;
|
|
333
192
|
case "uuid": return 1n << 128n;
|
|
@@ -342,6 +201,13 @@ function radixOf(field) {
|
|
|
342
201
|
}
|
|
343
202
|
}
|
|
344
203
|
function boundedToBigint(field, name, value) {
|
|
204
|
+
if (field.nullable === true) {
|
|
205
|
+
if (value === null) return 0n;
|
|
206
|
+
return boundedToBigint({
|
|
207
|
+
...field,
|
|
208
|
+
nullable: false
|
|
209
|
+
}, name, value) + 1n;
|
|
210
|
+
}
|
|
345
211
|
const slot = boundedSlot(field, name, value);
|
|
346
212
|
if (slot < 0n || slot >= radixOf(field)) outOfRange(name, value);
|
|
347
213
|
return slot;
|
|
@@ -372,6 +238,13 @@ function outOfRange(name, value) {
|
|
|
372
238
|
throw new SeedcordRangeError(SeedcordErrorCode.CustomIdValueOutOfRange, [name, String(value)]);
|
|
373
239
|
}
|
|
374
240
|
function bigintToBoundedValue(field, slot) {
|
|
241
|
+
if (field.nullable === true) return slot === 0n ? null : bigintToBoundedValue({
|
|
242
|
+
...field,
|
|
243
|
+
nullable: false
|
|
244
|
+
}, slot - 1n);
|
|
245
|
+
return kindValue(field, slot);
|
|
246
|
+
}
|
|
247
|
+
function kindValue(field, slot) {
|
|
375
248
|
switch (field.kind) {
|
|
376
249
|
case "snowflake": return slot.toString();
|
|
377
250
|
case "uuid": return bigintToUuid(slot);
|
|
@@ -385,7 +258,16 @@ function bigintToUuid(value) {
|
|
|
385
258
|
const hex = value.toString(16).padStart(32, "0");
|
|
386
259
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
387
260
|
}
|
|
261
|
+
const ABSENT = "0";
|
|
262
|
+
const PRESENT = "1";
|
|
388
263
|
function encodeUnboundedToken(field, name, value) {
|
|
264
|
+
if (field.nullable === true) {
|
|
265
|
+
if (value === null) return ABSENT;
|
|
266
|
+
return PRESENT + encodeUnboundedToken({
|
|
267
|
+
...field,
|
|
268
|
+
nullable: false
|
|
269
|
+
}, name, value);
|
|
270
|
+
}
|
|
389
271
|
if (field.kind === "int") {
|
|
390
272
|
if (!Number.isSafeInteger(value)) outOfRange(name, value);
|
|
391
273
|
return bigintToBase64(zigzagEncode(value));
|
|
@@ -393,6 +275,15 @@ function encodeUnboundedToken(field, name, value) {
|
|
|
393
275
|
return escapeToken(value);
|
|
394
276
|
}
|
|
395
277
|
function decodeUnboundedToken(field, piece) {
|
|
278
|
+
if (field.nullable === true) {
|
|
279
|
+
const marker = piece.charAt(0);
|
|
280
|
+
if (marker === ABSENT && piece.length === 1) return null;
|
|
281
|
+
if (marker !== PRESENT) throw new InvalidCustomId(`bad presence marker ${JSON.stringify(marker)}`);
|
|
282
|
+
return decodeUnboundedToken({
|
|
283
|
+
...field,
|
|
284
|
+
nullable: false
|
|
285
|
+
}, piece.slice(1));
|
|
286
|
+
}
|
|
396
287
|
if (field.kind !== "int") return unescapeToken(piece);
|
|
397
288
|
if (piece === "") throw new InvalidCustomId("empty integer token");
|
|
398
289
|
const decoded = zigzagDecode(base64ToBigint(piece));
|
|
@@ -405,6 +296,7 @@ function computeLayoutHash(shape) {
|
|
|
405
296
|
name,
|
|
406
297
|
field.kind,
|
|
407
298
|
isBounded(field),
|
|
299
|
+
field.nullable === true,
|
|
408
300
|
field.kind === "oneOf" ? field.choices ?? [] : null,
|
|
409
301
|
field.kind === "int" ? [field.min ?? null, field.max ?? null] : null
|
|
410
302
|
]));
|
|
@@ -510,14 +402,14 @@ var CustomId = class CustomId {
|
|
|
510
402
|
/** The prefix plus a short hash of the shape, the part of the wire before the colon. */
|
|
511
403
|
routeKey;
|
|
512
404
|
constructor(prefix, shape = {}) {
|
|
513
|
-
if (!prefix || /[:\x1B\x1F]/.test(prefix)) throw new
|
|
405
|
+
if (!prefix || /[:\x1B\x1F]/.test(prefix)) throw new SeedcordTypeError(SeedcordErrorCode.CustomIdInvalidPrefix, [prefix]);
|
|
514
406
|
this.prefix = prefix;
|
|
515
407
|
this.shape = shape;
|
|
516
408
|
this.routeKey = prefix + computeLayoutHash(shape);
|
|
517
409
|
}
|
|
518
410
|
add(name, field) {
|
|
519
|
-
if (/^(?:0|[1-9]\d*)$/.test(name)) throw new
|
|
520
|
-
if (name in this.shape) throw new
|
|
411
|
+
if (/^(?:0|[1-9]\d*)$/.test(name)) throw new SeedcordTypeError(SeedcordErrorCode.CustomIdReservedFieldName, [name]);
|
|
412
|
+
if (name in this.shape) throw new SeedcordTypeError(SeedcordErrorCode.CustomIdDuplicateFieldName, [name]);
|
|
521
413
|
const shape = {
|
|
522
414
|
...this.shape,
|
|
523
415
|
[name]: field
|
|
@@ -532,8 +424,11 @@ var CustomId = class CustomId {
|
|
|
532
424
|
* new CustomId('ban').snowflake('userId');
|
|
533
425
|
* ```
|
|
534
426
|
*/
|
|
535
|
-
snowflake(name) {
|
|
536
|
-
return this.add(name, {
|
|
427
|
+
snowflake(name, opts) {
|
|
428
|
+
return this.add(name, {
|
|
429
|
+
...opts,
|
|
430
|
+
kind: "snowflake"
|
|
431
|
+
});
|
|
537
432
|
}
|
|
538
433
|
/**
|
|
539
434
|
* Add a UUID field, decoded as a lowercase uuid string.
|
|
@@ -543,21 +438,29 @@ var CustomId = class CustomId {
|
|
|
543
438
|
* new CustomId('ticket').uuid('ticketId');
|
|
544
439
|
* ```
|
|
545
440
|
*/
|
|
546
|
-
uuid(name) {
|
|
547
|
-
return this.add(name, {
|
|
441
|
+
uuid(name, opts) {
|
|
442
|
+
return this.add(name, {
|
|
443
|
+
...opts,
|
|
444
|
+
kind: "uuid"
|
|
445
|
+
});
|
|
548
446
|
}
|
|
549
|
-
int(name,
|
|
550
|
-
|
|
447
|
+
int(name, minOrOpts, max, opts) {
|
|
448
|
+
const min = typeof minOrOpts === "number" ? minOrOpts : void 0;
|
|
449
|
+
if (min !== void 0 && max !== void 0 && min > max) throw new SeedcordRangeError(SeedcordErrorCode.CustomIdInvalidBounds, [
|
|
551
450
|
name,
|
|
552
451
|
min,
|
|
553
452
|
max
|
|
554
453
|
]);
|
|
555
|
-
const
|
|
556
|
-
|
|
454
|
+
const options = typeof minOrOpts === "number" ? opts : minOrOpts;
|
|
455
|
+
const bounds = min === void 0 || max === void 0 ? {} : {
|
|
557
456
|
min,
|
|
558
457
|
max
|
|
559
458
|
};
|
|
560
|
-
return this.add(name,
|
|
459
|
+
return this.add(name, {
|
|
460
|
+
...options,
|
|
461
|
+
kind: "int",
|
|
462
|
+
...bounds
|
|
463
|
+
});
|
|
561
464
|
}
|
|
562
465
|
/**
|
|
563
466
|
* Add a boolean flag.
|
|
@@ -567,8 +470,11 @@ var CustomId = class CustomId {
|
|
|
567
470
|
* new CustomId('settings').bool('silent');
|
|
568
471
|
* ```
|
|
569
472
|
*/
|
|
570
|
-
bool(name) {
|
|
571
|
-
return this.add(name, {
|
|
473
|
+
bool(name, opts) {
|
|
474
|
+
return this.add(name, {
|
|
475
|
+
...opts,
|
|
476
|
+
kind: "bool"
|
|
477
|
+
});
|
|
572
478
|
}
|
|
573
479
|
/**
|
|
574
480
|
* Add a field that is one value from a fixed list, decoded as the literal union. No `as const` needed.
|
|
@@ -578,9 +484,10 @@ var CustomId = class CustomId {
|
|
|
578
484
|
* new CustomId('poll').oneOf('choice', ['yes', 'no', 'abstain']);
|
|
579
485
|
* ```
|
|
580
486
|
*/
|
|
581
|
-
oneOf(name, choices) {
|
|
582
|
-
if (choices.length === 0) throw new
|
|
487
|
+
oneOf(name, choices, opts) {
|
|
488
|
+
if (choices.length === 0) throw new SeedcordRangeError(SeedcordErrorCode.CustomIdEmptyChoices, [name]);
|
|
583
489
|
return this.add(name, {
|
|
490
|
+
...opts,
|
|
584
491
|
kind: "oneOf",
|
|
585
492
|
choices
|
|
586
493
|
});
|
|
@@ -594,8 +501,11 @@ var CustomId = class CustomId {
|
|
|
594
501
|
* new CustomId('note').str('message');
|
|
595
502
|
* ```
|
|
596
503
|
*/
|
|
597
|
-
str(name) {
|
|
598
|
-
return this.add(name, {
|
|
504
|
+
str(name, opts) {
|
|
505
|
+
return this.add(name, {
|
|
506
|
+
...opts,
|
|
507
|
+
kind: "string"
|
|
508
|
+
});
|
|
599
509
|
}
|
|
600
510
|
/**
|
|
601
511
|
* Mint a wire string from values. Throws if a value is out of its field's range or the wire is over 100 chars.
|
|
@@ -713,6 +623,285 @@ function contextMenuRouteOf(kind) {
|
|
|
713
623
|
return kind === ApplicationCommandType.User ? "userContextMenu" : "messageContextMenu";
|
|
714
624
|
}
|
|
715
625
|
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region src/reply/ackLegality.ts
|
|
628
|
+
const showModalReason = {
|
|
629
|
+
reason: "this interaction was already acknowledged",
|
|
630
|
+
alternative: "showModal() must be the initial response."
|
|
631
|
+
};
|
|
632
|
+
function deferReason(what) {
|
|
633
|
+
return {
|
|
634
|
+
reason: `this interaction was already acknowledged with ${what}`,
|
|
635
|
+
alternative: "A deferral can only be the initial response."
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
const ILLEGAL = {
|
|
639
|
+
reply: {
|
|
640
|
+
"deferred-reply": {
|
|
641
|
+
reason: "this interaction was already deferred",
|
|
642
|
+
alternative: "Use edit() to fill the deferred reply or followUp() for a new message."
|
|
643
|
+
},
|
|
644
|
+
"deferred-update": {
|
|
645
|
+
reason: "this interaction was already acknowledged with a deferred update",
|
|
646
|
+
alternative: "Use followUp() for a new message."
|
|
647
|
+
},
|
|
648
|
+
replied: {
|
|
649
|
+
reason: "this interaction was already replied to",
|
|
650
|
+
alternative: "Use followUp() for a new message or edit() to rewrite the reply."
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
defer: {
|
|
654
|
+
"deferred-reply": deferReason("a deferred reply"),
|
|
655
|
+
"deferred-update": deferReason("a deferred update"),
|
|
656
|
+
replied: deferReason("a reply")
|
|
657
|
+
},
|
|
658
|
+
deferUpdate: {
|
|
659
|
+
"deferred-reply": deferReason("a deferred reply"),
|
|
660
|
+
"deferred-update": deferReason("a deferred update"),
|
|
661
|
+
replied: deferReason("a reply")
|
|
662
|
+
},
|
|
663
|
+
update: {
|
|
664
|
+
"deferred-reply": {
|
|
665
|
+
reason: "this interaction was deferred as a reply",
|
|
666
|
+
alternative: "The placeholder is yours via edit(). update() rewrites a source message."
|
|
667
|
+
},
|
|
668
|
+
replied: {
|
|
669
|
+
reason: "this interaction was already replied to",
|
|
670
|
+
alternative: "update() rewrites a source message. Use edit() or followUp() instead."
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
followUp: { unacked: {
|
|
674
|
+
reason: "this interaction has not been acknowledged yet",
|
|
675
|
+
alternative: "Ack first with reply() or defer()."
|
|
676
|
+
} },
|
|
677
|
+
edit: { unacked: {
|
|
678
|
+
reason: "nothing has been sent yet",
|
|
679
|
+
alternative: "Send with reply() or defer() first, then edit() rewrites it."
|
|
680
|
+
} },
|
|
681
|
+
delete: { unacked: {
|
|
682
|
+
reason: "nothing has been sent yet",
|
|
683
|
+
alternative: "Acknowledge first with reply() or defer(), then delete() removes it."
|
|
684
|
+
} },
|
|
685
|
+
showModal: {
|
|
686
|
+
"deferred-reply": showModalReason,
|
|
687
|
+
"deferred-update": showModalReason,
|
|
688
|
+
replied: showModalReason
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
const SEND_TARGET = {
|
|
692
|
+
unacked: "reply",
|
|
693
|
+
"deferred-reply": "edit",
|
|
694
|
+
"deferred-update": "followUp",
|
|
695
|
+
replied: "followUp"
|
|
696
|
+
};
|
|
697
|
+
function checkAckLegality(method, state, routeId, ackedBy) {
|
|
698
|
+
if (method === "send") return;
|
|
699
|
+
const illegal = ILLEGAL[method][state];
|
|
700
|
+
if (!illegal) return;
|
|
701
|
+
throw new SeedcordError(SeedcordErrorCode.ReplyIllegalAckState, [
|
|
702
|
+
method,
|
|
703
|
+
illegal.reason,
|
|
704
|
+
illegal.alternative,
|
|
705
|
+
routeId
|
|
706
|
+
], { cause: ackedBy });
|
|
707
|
+
}
|
|
708
|
+
function sendTarget(state) {
|
|
709
|
+
return SEND_TARGET[state];
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
//#endregion
|
|
713
|
+
//#region src/reply/AckTrace.ts
|
|
714
|
+
var AckTrace = class extends Error {
|
|
715
|
+
constructor(method) {
|
|
716
|
+
super(`${method}() acknowledged this interaction`);
|
|
717
|
+
this.name = "AckTrace";
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region src/reply/responseReport.ts
|
|
723
|
+
let replyLogger;
|
|
724
|
+
function logger() {
|
|
725
|
+
replyLogger ??= new Logger("Reply", { channel: "interactions" });
|
|
726
|
+
return replyLogger;
|
|
727
|
+
}
|
|
728
|
+
/** @internal */
|
|
729
|
+
function publishResponse(telemetry, report) {
|
|
730
|
+
const durationMs = performance.now() - report.startedAt;
|
|
731
|
+
const write = {
|
|
732
|
+
routeId: report.routeId,
|
|
733
|
+
interactionId: telemetry.interactionId,
|
|
734
|
+
method: report.method,
|
|
735
|
+
durationMs
|
|
736
|
+
};
|
|
737
|
+
telemetry.bus[PublishDefault]("responseAttempted", report.outcome === "failed" ? {
|
|
738
|
+
...write,
|
|
739
|
+
outcome: "failed",
|
|
740
|
+
messageId: null,
|
|
741
|
+
error: report.error
|
|
742
|
+
} : {
|
|
743
|
+
...write,
|
|
744
|
+
outcome: "sent",
|
|
745
|
+
messageId: report.messageId
|
|
746
|
+
});
|
|
747
|
+
logger().trace(`${paint.sky.bold(report.routeId)} ${report.method} ${report.outcome} ${paint.mute("in")} ${Math.round(durationMs)}ms`);
|
|
748
|
+
}
|
|
749
|
+
async function reportedWrite(telemetry, routeId, method, write) {
|
|
750
|
+
const startedAt = performance.now();
|
|
751
|
+
const result = await attemptWrite(telemetry, routeId, method, startedAt, write);
|
|
752
|
+
publishResponse(telemetry, {
|
|
753
|
+
routeId,
|
|
754
|
+
method,
|
|
755
|
+
startedAt,
|
|
756
|
+
outcome: "sent",
|
|
757
|
+
messageId: null
|
|
758
|
+
});
|
|
759
|
+
return result;
|
|
760
|
+
}
|
|
761
|
+
async function attemptWrite(telemetry, routeId, method, startedAt, write) {
|
|
762
|
+
try {
|
|
763
|
+
return await write();
|
|
764
|
+
} catch (caught) {
|
|
765
|
+
publishResponse(telemetry, {
|
|
766
|
+
routeId,
|
|
767
|
+
method,
|
|
768
|
+
startedAt,
|
|
769
|
+
outcome: "failed",
|
|
770
|
+
error: asError(caught)
|
|
771
|
+
});
|
|
772
|
+
throw caught;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
//#endregion
|
|
777
|
+
//#region src/reply/translateSerialization.ts
|
|
778
|
+
const MAX_DEPTH = 8;
|
|
779
|
+
const OPERATOR_WORD = {
|
|
780
|
+
"<=": "at most",
|
|
781
|
+
">=": "at least",
|
|
782
|
+
"<": "fewer than",
|
|
783
|
+
">": "more than"
|
|
784
|
+
};
|
|
785
|
+
const KIND_WORD = {
|
|
786
|
+
string: "a string",
|
|
787
|
+
number: "a number",
|
|
788
|
+
boolean: "a boolean",
|
|
789
|
+
bigint: "a bigint",
|
|
790
|
+
array: "a list",
|
|
791
|
+
tuple: "a list",
|
|
792
|
+
date: "a date",
|
|
793
|
+
object: "an object"
|
|
794
|
+
};
|
|
795
|
+
function asRecord(value) {
|
|
796
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
797
|
+
}
|
|
798
|
+
function firstLine(message) {
|
|
799
|
+
if (typeof message !== "string") return "unknown validation failure";
|
|
800
|
+
return message.split("\n")[0] ?? message;
|
|
801
|
+
}
|
|
802
|
+
const GIVEN_PREVIEW_LIMIT = 40;
|
|
803
|
+
function gotPhrase(given) {
|
|
804
|
+
if (given === void 0) return "nothing";
|
|
805
|
+
if (given === null) return "null";
|
|
806
|
+
if (typeof given === "string") {
|
|
807
|
+
if (given.length === 0) return "an empty string";
|
|
808
|
+
if (given.length > GIVEN_PREVIEW_LIMIT) return `${given.length} characters`;
|
|
809
|
+
return JSON.stringify(given);
|
|
810
|
+
}
|
|
811
|
+
if (Array.isArray(given)) return given.length === 0 ? "an empty list" : `${given.length} items`;
|
|
812
|
+
if (typeof given === "number" || typeof given === "bigint" || typeof given === "boolean") return String(given);
|
|
813
|
+
if (typeof given === "object") return given.constructor?.name ?? "an object";
|
|
814
|
+
return typeof given;
|
|
815
|
+
}
|
|
816
|
+
function needPhrase(expected, given) {
|
|
817
|
+
const bounds = [...expected.matchAll(/expected(\.length)?\s*(<=|>=|<|>)\s*(\d+)/g)];
|
|
818
|
+
if (bounds.length === 0) return null;
|
|
819
|
+
return bounds.map(([, lengthPart, operator, limit]) => {
|
|
820
|
+
const word = OPERATOR_WORD[operator ?? ""] ?? operator ?? "";
|
|
821
|
+
if (!lengthPart) return `${word} ${limit}`;
|
|
822
|
+
const unit = typeof given === "string" ? "character" : "item";
|
|
823
|
+
return `${word} ${limit} ${Number(limit) === 1 ? unit : `${unit}s`}`;
|
|
824
|
+
}).join(" and ");
|
|
825
|
+
}
|
|
826
|
+
function instanceName(expected) {
|
|
827
|
+
if (typeof expected === "function" && expected.name) return expected.name;
|
|
828
|
+
return "specific class instance";
|
|
829
|
+
}
|
|
830
|
+
function describeLeaf(record) {
|
|
831
|
+
if (typeof record.constraint === "string") {
|
|
832
|
+
const need = typeof record.expected === "string" ? needPhrase(record.expected, record.given) : null;
|
|
833
|
+
if (need) return `need ${need}, got ${gotPhrase(record.given)}`;
|
|
834
|
+
return `${firstLine(record.message)}${"given" in record ? ` (got ${gotPhrase(record.given)})` : ""}`;
|
|
835
|
+
}
|
|
836
|
+
if (typeof record.expected === "function") return `need a ${instanceName(record.expected)}, got ${gotPhrase(record.given)}`;
|
|
837
|
+
if (typeof record.validator === "string") {
|
|
838
|
+
const kind = KIND_WORD[record.validator.replace(/^s\./, "").replace(/\(.*$/, "")];
|
|
839
|
+
if (kind) return `need ${kind}, got ${gotPhrase(record.given)}`;
|
|
840
|
+
return `${firstLine(record.message)}, got ${gotPhrase(record.given)}`;
|
|
841
|
+
}
|
|
842
|
+
return firstLine(record.message);
|
|
843
|
+
}
|
|
844
|
+
function describeUnion(branches, depth) {
|
|
845
|
+
const records = [];
|
|
846
|
+
for (const branch of branches) {
|
|
847
|
+
const record = asRecord(branch);
|
|
848
|
+
if (record) records.push(record);
|
|
849
|
+
}
|
|
850
|
+
const bounded = records.find((record) => typeof record.constraint === "string");
|
|
851
|
+
if (bounded) return describeLeaf(bounded);
|
|
852
|
+
const names = [];
|
|
853
|
+
let allInstances = records.length > 0;
|
|
854
|
+
for (const record of records) if (typeof record.expected === "function") names.push(instanceName(record.expected));
|
|
855
|
+
else allInstances = false;
|
|
856
|
+
if (allInstances) return `need a ${[...new Set(names)].join(" or ")}, got ${gotPhrase(records[0]?.given)}`;
|
|
857
|
+
return detailOf(branches[0], depth + 1);
|
|
858
|
+
}
|
|
859
|
+
function detailOf(value, depth) {
|
|
860
|
+
const record = asRecord(value);
|
|
861
|
+
if (!record || depth >= MAX_DEPTH) return firstLine(record?.message);
|
|
862
|
+
if (Array.isArray(record.errors) && record.errors.length > 0) {
|
|
863
|
+
const errors = record.errors;
|
|
864
|
+
const [first] = errors;
|
|
865
|
+
if (Array.isArray(first)) {
|
|
866
|
+
const [key, inner] = first;
|
|
867
|
+
return `${String(key)}: ${detailOf(inner, depth + 1)}`;
|
|
868
|
+
}
|
|
869
|
+
return describeUnion(errors, depth);
|
|
870
|
+
}
|
|
871
|
+
return describeLeaf(record);
|
|
872
|
+
}
|
|
873
|
+
function translateSerializationError(error, componentClass, index, routeId) {
|
|
874
|
+
const detail = detailOf(error, 0);
|
|
875
|
+
return new SeedcordError(SeedcordErrorCode.ReplyComponentSerialization, [
|
|
876
|
+
componentClass,
|
|
877
|
+
index,
|
|
878
|
+
detail,
|
|
879
|
+
routeId
|
|
880
|
+
], { cause: error });
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
//#endregion
|
|
884
|
+
//#region src/reply/serializeReply.ts
|
|
885
|
+
function serializeReply(response, routeId) {
|
|
886
|
+
if (typeof response === "string") try {
|
|
887
|
+
return { components: [new TextDisplayBuilder().setContent(response).toJSON()] };
|
|
888
|
+
} catch (error) {
|
|
889
|
+
throw translateSerializationError(error, "TextDisplayBuilder", 0, routeId);
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
components: response.components.map((component, index) => {
|
|
893
|
+
try {
|
|
894
|
+
return component.toJSON();
|
|
895
|
+
} catch (error) {
|
|
896
|
+
const name = component.constructor?.name;
|
|
897
|
+
throw translateSerializationError(error, !name || name === "Object" ? "component" : name, index, routeId);
|
|
898
|
+
}
|
|
899
|
+
}),
|
|
900
|
+
...response.allowedMentions && { allowedMentions: response.allowedMentions },
|
|
901
|
+
...response.files && { files: response.files }
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
716
905
|
//#endregion
|
|
717
906
|
//#region src/gates/Gate.ts
|
|
718
907
|
/**
|
|
@@ -807,23 +996,30 @@ var GateNotice = class extends Notice {
|
|
|
807
996
|
var NotOwner = class extends GateNotice {
|
|
808
997
|
constructor(message = "Only the bot owner can use this.") {
|
|
809
998
|
super(message);
|
|
999
|
+
this.summary = "be the bot owner";
|
|
810
1000
|
}
|
|
811
1001
|
};
|
|
812
1002
|
var NotInGuild = class extends GateNotice {
|
|
813
1003
|
constructor(message = "This can only be used in a server.") {
|
|
814
1004
|
super(message);
|
|
1005
|
+
this.summary = "be in a server";
|
|
815
1006
|
}
|
|
816
1007
|
};
|
|
817
1008
|
var NotInDm = class extends GateNotice {
|
|
818
1009
|
constructor(message = "This can only be used in a direct message.") {
|
|
819
1010
|
super(message);
|
|
1011
|
+
this.summary = "be in a direct message";
|
|
820
1012
|
}
|
|
821
1013
|
};
|
|
1014
|
+
function retryAt(resetAt) {
|
|
1015
|
+
return `<t:${toEpochSeconds(resetAt)}:R>`;
|
|
1016
|
+
}
|
|
822
1017
|
var OnCooldown = class extends GateNotice {
|
|
823
1018
|
resetAt;
|
|
824
1019
|
constructor(resetAt, message) {
|
|
825
|
-
super(message ?? `You are doing that too fast. Try again
|
|
1020
|
+
super(message ?? `You are doing that too fast. Try again ${retryAt(resetAt)}.`);
|
|
826
1021
|
this.resetAt = resetAt;
|
|
1022
|
+
this.summary = `try again ${retryAt(resetAt)}`;
|
|
827
1023
|
}
|
|
828
1024
|
};
|
|
829
1025
|
var NotAllowed = class extends Notice {
|
|
@@ -838,16 +1034,17 @@ var NeedsAny = class extends Notice {
|
|
|
838
1034
|
summaries;
|
|
839
1035
|
constructor(summaries) {
|
|
840
1036
|
super("not allowed");
|
|
841
|
-
this.summaries = summaries;
|
|
1037
|
+
this.summaries = [...new Set(summaries)];
|
|
842
1038
|
}
|
|
843
1039
|
render() {
|
|
844
1040
|
const bullets = this.summaries.map((summary) => `• ${summary}`).join("\n");
|
|
845
|
-
return { components: [new NoticeCard(`You need
|
|
1041
|
+
return { components: [new NoticeCard(`You need to meet at least one of these:\n${bullets}`).component] };
|
|
846
1042
|
}
|
|
847
1043
|
};
|
|
848
1044
|
var MissingRole = class extends GateNotice {
|
|
849
1045
|
constructor(message, roleId) {
|
|
850
1046
|
super(message ?? (roleId ? `You need the <@&${roleId}> role to use this.` : "You do not have the required role."));
|
|
1047
|
+
this.summary = roleId ? `hold the <@&${roleId}> role` : "hold the required role";
|
|
851
1048
|
}
|
|
852
1049
|
};
|
|
853
1050
|
var MissingPermissions = class extends Notice {
|
|
@@ -859,6 +1056,8 @@ var MissingPermissions = class extends Notice {
|
|
|
859
1056
|
this.subject = subject;
|
|
860
1057
|
this.missingPerms = missingPerms;
|
|
861
1058
|
this.customLead = message;
|
|
1059
|
+
const names = missingPerms.join(", ");
|
|
1060
|
+
this.summary = subject === null ? `hold ${names}` : `${subject} needs ${names}`;
|
|
862
1061
|
}
|
|
863
1062
|
render() {
|
|
864
1063
|
const bullets = this.missingPerms.map((perm) => `• ${perm}`).join("\n");
|
|
@@ -875,6 +1074,7 @@ var HasDangerousPermissions = class extends Notice {
|
|
|
875
1074
|
this.subject = subject;
|
|
876
1075
|
this.dangerousPerms = dangerousPerms;
|
|
877
1076
|
this.customLead = message;
|
|
1077
|
+
this.summary = `${subject} must not hold ${dangerousPerms.join(", ")}`;
|
|
878
1078
|
}
|
|
879
1079
|
render() {
|
|
880
1080
|
const bullets = this.dangerousPerms.map((perm) => `• ${perm}`).join("\n");
|
|
@@ -1022,8 +1222,9 @@ function RequireRole(roleId, options) {
|
|
|
1022
1222
|
* ```ts
|
|
1023
1223
|
* import { Silence } from '@seedcord/gateway';
|
|
1024
1224
|
*
|
|
1025
|
-
* //
|
|
1026
|
-
*
|
|
1225
|
+
* // a helper under a guildMemberAdd handler, stopping when this guild configured nothing to send
|
|
1226
|
+
* const template = await settings.welcome(member.guild.id);
|
|
1227
|
+
* if (!template) throw new Silence('no welcome message set');
|
|
1027
1228
|
* ```
|
|
1028
1229
|
*/
|
|
1029
1230
|
var Silence = class extends Error {
|
|
@@ -1048,5 +1249,5 @@ function pageCursor(prefix) {
|
|
|
1048
1249
|
}
|
|
1049
1250
|
|
|
1050
1251
|
//#endregion
|
|
1051
|
-
export {
|
|
1052
|
-
//# sourceMappingURL=cursor-
|
|
1252
|
+
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, prefixOf as J, CustomId 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, Notice as X, NoticeCard as Y, 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, decodeFor 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 };
|
|
1253
|
+
//# sourceMappingURL=cursor-GBUYz3jk.mjs.map
|