@helyx/bot 0.4.11 → 0.4.13
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/CHANGELOG.md +33 -0
- package/dist/control-server.d.ts +38 -3
- package/dist/control-server.js +124 -5
- package/dist/events.d.ts +29 -1
- package/dist/events.js +786 -2
- package/dist/guilds.d.ts +20 -1
- package/dist/guilds.js +149 -9
- package/dist/index.js +21 -5
- package/dist/interactions.js +22 -6
- package/dist/management-module.d.ts +1 -1
- package/dist/shard-contracts.d.ts +324 -0
- package/dist/shard-contracts.js +47 -2
- package/dist/shard-router.d.ts +3 -0
- package/dist/shard-router.js +42 -0
- package/dist/shard-supervisor.d.ts +2 -0
- package/dist/shard-supervisor.js +54 -6
- package/dist/shard-worker.d.ts +6 -0
- package/dist/shard-worker.js +23 -6
- package/dist/verification-service.d.ts +50 -0
- package/dist/verification-service.js +166 -0
- package/package.json +10 -9
package/dist/guilds.d.ts
CHANGED
|
@@ -2,5 +2,24 @@ import { type DatabasePool, type GuildShardOwnership } from "@helyx/database";
|
|
|
2
2
|
import type { ModuleDefinition } from "@helyx/sdk";
|
|
3
3
|
import { type Client } from "discord.js";
|
|
4
4
|
import type { InteractionLogger } from "./interactions.js";
|
|
5
|
-
export
|
|
5
|
+
export interface GuildReconciliationHealth {
|
|
6
|
+
status: "pending" | "running" | "healthy" | "failed" | "stopped";
|
|
7
|
+
lastStartedAt?: string;
|
|
8
|
+
lastSucceededAt?: string;
|
|
9
|
+
durationMs?: number;
|
|
10
|
+
examinedRows: number;
|
|
11
|
+
refreshedGuilds: number;
|
|
12
|
+
removedGuilds: number;
|
|
13
|
+
rejectedFences: number;
|
|
14
|
+
unavailableGuilds: number;
|
|
15
|
+
failureReasonCode?: string | undefined;
|
|
16
|
+
nextScheduledAt?: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
export interface GuildTrackingPreparation {
|
|
19
|
+
(readyClient: Client<true>): Promise<void>;
|
|
20
|
+
reconcile(reason: "ready" | "resume" | "periodic"): Promise<void>;
|
|
21
|
+
health(): GuildReconciliationHealth;
|
|
22
|
+
stop(): void;
|
|
23
|
+
}
|
|
24
|
+
export declare function installGuildTracking(client: Client, database: DatabasePool, modules: readonly ModuleDefinition[], logger: InteractionLogger, enabledByDefaultModuleIds?: ReadonlySet<string>, ownership?: GuildShardOwnership, reconciliationIntervalMs?: number, shardCount?: number): GuildTrackingPreparation;
|
|
6
25
|
//# sourceMappingURL=guilds.d.ts.map
|
package/dist/guilds.js
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { InstallationRepository, } from "@helyx/database";
|
|
2
2
|
import { Events } from "discord.js";
|
|
3
|
-
|
|
3
|
+
const RECONCILIATION_PAGE_SIZE = 250;
|
|
4
|
+
const DEFAULT_RECONCILIATION_INTERVAL_MS = 15 * 60 * 1_000;
|
|
5
|
+
export function installGuildTracking(client, database, modules, logger, enabledByDefaultModuleIds = new Set(), ownership, reconciliationIntervalMs = DEFAULT_RECONCILIATION_INTERVAL_MS, shardCount) {
|
|
6
|
+
if (!Number.isSafeInteger(reconciliationIntervalMs) ||
|
|
7
|
+
reconciliationIntervalMs < 60_000)
|
|
8
|
+
throw new Error("Guild reconciliation interval must be at least one minute");
|
|
4
9
|
const repository = new InstallationRepository(database);
|
|
10
|
+
let prepared = false;
|
|
11
|
+
let stopped = false;
|
|
12
|
+
let reconciliation;
|
|
13
|
+
let timer;
|
|
14
|
+
let health = {
|
|
15
|
+
status: "pending",
|
|
16
|
+
examinedRows: 0,
|
|
17
|
+
refreshedGuilds: 0,
|
|
18
|
+
removedGuilds: 0,
|
|
19
|
+
rejectedFences: 0,
|
|
20
|
+
unavailableGuilds: 0,
|
|
21
|
+
};
|
|
5
22
|
async function syncGuild(guild) {
|
|
6
23
|
const record = await repository.syncGuild({
|
|
7
24
|
guildId: guild.id,
|
|
@@ -39,10 +56,16 @@ export function installGuildTracking(client, database, modules, logger, enabledB
|
|
|
39
56
|
client.on(Events.GuildDelete, (guild) => {
|
|
40
57
|
void repository
|
|
41
58
|
.markGuildRemoved(guild.id, ownership)
|
|
59
|
+
.then((changed) => {
|
|
60
|
+
logger.info({
|
|
61
|
+
event: "guild.removed",
|
|
62
|
+
outcome: changed ? "changed" : "rejected_or_duplicate",
|
|
63
|
+
}, "Discord guild removal observation persisted");
|
|
64
|
+
})
|
|
42
65
|
.catch((error) => {
|
|
43
66
|
logger.error({
|
|
44
67
|
event: "guild.remove_failed",
|
|
45
|
-
|
|
68
|
+
reasonCode: "database_write_failed",
|
|
46
69
|
error: safeError(error),
|
|
47
70
|
}, "Guild removal tracking failed");
|
|
48
71
|
});
|
|
@@ -50,14 +73,131 @@ export function installGuildTracking(client, database, modules, logger, enabledB
|
|
|
50
73
|
client.on(Events.GuildUnavailable, (guild) => {
|
|
51
74
|
logger.warn({ event: "guild.unavailable", guildId: guild.id }, "Discord guild is temporarily unavailable; installation remains active");
|
|
52
75
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
async function runReconciliation(reason) {
|
|
77
|
+
if (stopped || !prepared || !client.isReady())
|
|
78
|
+
return;
|
|
79
|
+
if (reconciliation)
|
|
80
|
+
return reconciliation;
|
|
81
|
+
reconciliation = (async () => {
|
|
82
|
+
const startedAt = Date.now();
|
|
83
|
+
health = {
|
|
84
|
+
...health,
|
|
85
|
+
status: "running",
|
|
86
|
+
lastStartedAt: new Date(startedAt).toISOString(),
|
|
87
|
+
failureReasonCode: undefined,
|
|
88
|
+
nextScheduledAt: undefined,
|
|
89
|
+
};
|
|
90
|
+
let examinedRows = 0;
|
|
91
|
+
let refreshedGuilds = 0;
|
|
92
|
+
let removedGuilds = 0;
|
|
93
|
+
let rejectedFences = 0;
|
|
94
|
+
const observedGuildIds = new Set(client.guilds.cache.keys());
|
|
95
|
+
const unavailableGuilds = [...client.guilds.cache.values()].filter((guild) => !guild.available).length;
|
|
96
|
+
try {
|
|
97
|
+
for (const guild of client.guilds.cache.values()) {
|
|
98
|
+
if (stopped)
|
|
99
|
+
return;
|
|
100
|
+
await syncGuild(guild);
|
|
101
|
+
refreshedGuilds += 1;
|
|
102
|
+
}
|
|
103
|
+
let cursor;
|
|
104
|
+
do {
|
|
105
|
+
if (stopped || !client.isReady())
|
|
106
|
+
return;
|
|
107
|
+
const page = await repository.listActiveGuildIdsForOwnership({
|
|
108
|
+
...(ownership ? { ownership } : {}),
|
|
109
|
+
...(ownership && shardCount !== undefined ? { shardCount } : {}),
|
|
110
|
+
...(cursor ? { cursor } : {}),
|
|
111
|
+
limit: RECONCILIATION_PAGE_SIZE,
|
|
112
|
+
});
|
|
113
|
+
examinedRows += page.guildIds.length;
|
|
114
|
+
if (ownership)
|
|
115
|
+
await repository.adoptGuildsForReconciliation({
|
|
116
|
+
guildIds: page.guildIds,
|
|
117
|
+
ownership,
|
|
118
|
+
shardCount: shardCount,
|
|
119
|
+
});
|
|
120
|
+
const absent = page.guildIds.filter((guildId) => !observedGuildIds.has(guildId));
|
|
121
|
+
const result = await repository.markGuildsRemovedForReconciliation({
|
|
122
|
+
guildIds: absent,
|
|
123
|
+
...(ownership ? { ownership } : {}),
|
|
124
|
+
});
|
|
125
|
+
removedGuilds += result.changed;
|
|
126
|
+
rejectedFences += result.rejectedFence;
|
|
127
|
+
cursor = page.nextCursor;
|
|
128
|
+
} while (cursor);
|
|
129
|
+
const completedAt = Date.now();
|
|
130
|
+
health = {
|
|
131
|
+
status: "healthy",
|
|
132
|
+
lastStartedAt: new Date(startedAt).toISOString(),
|
|
133
|
+
lastSucceededAt: new Date(completedAt).toISOString(),
|
|
134
|
+
durationMs: completedAt - startedAt,
|
|
135
|
+
examinedRows,
|
|
136
|
+
refreshedGuilds,
|
|
137
|
+
removedGuilds,
|
|
138
|
+
rejectedFences,
|
|
139
|
+
unavailableGuilds,
|
|
140
|
+
nextScheduledAt: new Date(completedAt + reconciliationIntervalMs).toISOString(),
|
|
141
|
+
};
|
|
142
|
+
logger.info({
|
|
143
|
+
event: "guild.reconciliation_completed",
|
|
144
|
+
reason,
|
|
145
|
+
durationMs: health.durationMs,
|
|
146
|
+
examinedRows,
|
|
147
|
+
refreshedGuilds,
|
|
148
|
+
removedGuilds,
|
|
149
|
+
rejectedFences,
|
|
150
|
+
unavailableGuilds,
|
|
151
|
+
}, "Discord guild presence reconciliation completed");
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
health = {
|
|
155
|
+
status: "failed",
|
|
156
|
+
lastStartedAt: new Date(startedAt).toISOString(),
|
|
157
|
+
durationMs: Date.now() - startedAt,
|
|
158
|
+
examinedRows,
|
|
159
|
+
refreshedGuilds,
|
|
160
|
+
removedGuilds,
|
|
161
|
+
rejectedFences,
|
|
162
|
+
unavailableGuilds,
|
|
163
|
+
failureReasonCode: "reconciliation_failed",
|
|
164
|
+
nextScheduledAt: new Date(Date.now() + reconciliationIntervalMs).toISOString(),
|
|
165
|
+
};
|
|
166
|
+
logger.error({
|
|
167
|
+
event: "guild.reconciliation_failed",
|
|
168
|
+
reason,
|
|
169
|
+
reasonCode: health.failureReasonCode,
|
|
170
|
+
error: safeError(error),
|
|
171
|
+
}, "Discord guild presence reconciliation failed");
|
|
172
|
+
}
|
|
173
|
+
})().finally(() => {
|
|
174
|
+
reconciliation = undefined;
|
|
175
|
+
});
|
|
176
|
+
return reconciliation;
|
|
177
|
+
}
|
|
178
|
+
const prepare = async (readyClient) => {
|
|
179
|
+
if (stopped || !readyClient.isReady())
|
|
180
|
+
return;
|
|
181
|
+
prepared = true;
|
|
182
|
+
await runReconciliation("ready");
|
|
183
|
+
if (!timer) {
|
|
184
|
+
timer = setInterval(() => void runReconciliation("periodic"), reconciliationIntervalMs);
|
|
185
|
+
timer.unref();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
client.on(Events.ShardResume, () => {
|
|
189
|
+
void runReconciliation("resume");
|
|
190
|
+
});
|
|
191
|
+
prepare.reconcile = runReconciliation;
|
|
192
|
+
prepare.health = () => ({ ...health });
|
|
193
|
+
prepare.stop = () => {
|
|
194
|
+
stopped = true;
|
|
195
|
+
if (timer)
|
|
196
|
+
clearInterval(timer);
|
|
197
|
+
timer = undefined;
|
|
198
|
+
health = { ...health, status: "stopped", nextScheduledAt: undefined };
|
|
60
199
|
};
|
|
200
|
+
return prepare;
|
|
61
201
|
}
|
|
62
202
|
function safeError(error) {
|
|
63
203
|
const value = error instanceof Error ? error : new Error(String(error));
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseBotEnvironment } from "@helyx/config";
|
|
2
2
|
import { safeError, ServiceRegistry } from "@helyx/core";
|
|
3
|
-
import { checkDatabaseConnection, createDatabasePool, InteractionSessionRepository, RateLimitRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
|
|
3
|
+
import { checkDatabaseConnection, createDatabasePool, AuditRepository, InteractionSessionRepository, RateLimitRepository, runCoreMigrations, runConfiguredModuleMigrations, VerificationSetupRepository, } from "@helyx/database";
|
|
4
4
|
import { createRuntime } from "@helyx/framework";
|
|
5
5
|
import { createPlatformServices, PostgresPrivacyProviderExecutor, } from "@helyx/platform";
|
|
6
6
|
import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
@@ -18,9 +18,10 @@ import { loadConfiguredModulePackages, platformServiceOptions, } from "./modules
|
|
|
18
18
|
import { resolveModulePackageSpecifiers } from "./module-configuration.js";
|
|
19
19
|
import { createPermissionsModule } from "./permissions-module.js";
|
|
20
20
|
import { createManagementModule } from "./management-module.js";
|
|
21
|
-
import { DiscordMessageDeliveryService, DiscordMemberEnforcementService, DiscordMemberRoleService, DiscordResourceInspection, installModuleEvents, } from "./events.js";
|
|
21
|
+
import { DiscordMessageDeliveryService, DiscordMemberEnforcementService, DiscordMemberRoleService, DiscordVerificationMemberService, DiscordVerificationSetupService, DiscordResourceInspection, installModuleEvents, } from "./events.js";
|
|
22
22
|
import { DiscordModuleLoggingService } from "./module-logging.js";
|
|
23
23
|
import { BotControlService, createBotControlServer, DiscordGuildResourceDirectory, } from "./control-server.js";
|
|
24
|
+
import { HttpVerificationService } from "./verification-service.js";
|
|
24
25
|
import { startShardSupervisor } from "./shard-supervisor.js";
|
|
25
26
|
import { parseShardWorkerLaunchContract, startShardWorker, } from "./shard-worker.js";
|
|
26
27
|
import { createDeveloperOperationsModule } from "./developer-operations-module.js";
|
|
@@ -87,15 +88,17 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
87
88
|
verifiedMigrationCount: migrationResult.verified.length,
|
|
88
89
|
}, "Core database ready");
|
|
89
90
|
const cleanupExpiredCoreRecords = async () => {
|
|
90
|
-
const [sessions, rateLimits] = await Promise.all([
|
|
91
|
+
const [sessions, rateLimits, verificationAudit] = await Promise.all([
|
|
91
92
|
new InteractionSessionRepository(database).deleteExpired(),
|
|
92
93
|
new RateLimitRepository(database).deleteExpired(),
|
|
94
|
+
new AuditRepository(database).deleteModuleEventsOlderThanMonths("helyx.verification", 12),
|
|
93
95
|
]);
|
|
94
|
-
if (sessions > 0 || rateLimits > 0) {
|
|
96
|
+
if (sessions > 0 || rateLimits > 0 || verificationAudit > 0) {
|
|
95
97
|
bootstrapLogger.info({
|
|
96
98
|
event: "database.retention_cleaned",
|
|
97
99
|
interactionSessions: sessions,
|
|
98
100
|
rateLimitWindows: rateLimits,
|
|
101
|
+
verificationAuditEvents: verificationAudit,
|
|
99
102
|
}, "Expired core records removed");
|
|
100
103
|
}
|
|
101
104
|
};
|
|
@@ -243,6 +246,18 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
243
246
|
})) {
|
|
244
247
|
platformServices.set(name, service);
|
|
245
248
|
}
|
|
249
|
+
if (environment.HELYX_VERIFICATION_ENABLED &&
|
|
250
|
+
environment.HELYX_VERIFICATION_URL &&
|
|
251
|
+
environment.HELYX_VERIFICATION_INTERNAL_SECRET) {
|
|
252
|
+
platformServices.set(HELYX_SERVICE_NAMES.verification, new HttpVerificationService({
|
|
253
|
+
baseUrl: environment.HELYX_VERIFICATION_URL,
|
|
254
|
+
internalSecret: environment.HELYX_VERIFICATION_INTERNAL_SECRET,
|
|
255
|
+
installationId: environment.HELYX_VERIFICATION_INSTALLATION_ID,
|
|
256
|
+
publicSigningKeys: environment.HELYX_VERIFICATION_SIGNING_PUBLIC_KEYS,
|
|
257
|
+
}));
|
|
258
|
+
platformServices.set(HELYX_SERVICE_NAMES.verificationMembers, new DiscordVerificationMemberService(client));
|
|
259
|
+
platformServices.set(HELYX_SERVICE_NAMES.verificationSetup, new DiscordVerificationSetupService(client, new VerificationSetupRepository(database)));
|
|
260
|
+
}
|
|
246
261
|
const numericWriteBehind = createLocalNumericWriteBehind(platformServices, publicModules);
|
|
247
262
|
platformServices.set(HELYX_SERVICE_NAMES.numericWriteBehind, numericWriteBehind);
|
|
248
263
|
platformServices.set(HELYX_SERVICE_NAMES.resourceAuthorization, new CoreResourceAuthorizationService(platformServices.get(HELYX_SERVICE_NAMES.permissions)));
|
|
@@ -266,7 +281,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
266
281
|
...(cloudManagementModule ? [cloudManagementModule.manifest.id] : []),
|
|
267
282
|
...(operationsModule ? [operationsModule.manifest.id] : []),
|
|
268
283
|
]);
|
|
269
|
-
const prepareGuilds = installGuildTracking(client, database, publicModules, bootstrapLogger, enabledCoreModuleIds);
|
|
284
|
+
const prepareGuilds = installGuildTracking(client, database, publicModules, bootstrapLogger, enabledCoreModuleIds, undefined, environment.HELYX_GUILD_RECONCILIATION_INTERVAL_SECONDS * 1_000);
|
|
270
285
|
const commandsReady = installInteractions(client, modules, {
|
|
271
286
|
...(environment.DISCORD_INTERNAL_GUILD_ID
|
|
272
287
|
? { internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID }
|
|
@@ -298,6 +313,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
298
313
|
if (shuttingDown)
|
|
299
314
|
return;
|
|
300
315
|
shuttingDown = true;
|
|
316
|
+
prepareGuilds.stop();
|
|
301
317
|
process.off("SIGINT", onSigint);
|
|
302
318
|
process.off("SIGTERM", onSigterm);
|
|
303
319
|
if (retentionCleanupTimer)
|
package/dist/interactions.js
CHANGED
|
@@ -294,6 +294,7 @@ function addCommandOption(builder, option) {
|
|
|
294
294
|
const channelTypes = {
|
|
295
295
|
guild_text: ChannelType.GuildText,
|
|
296
296
|
guild_announcement: ChannelType.GuildAnnouncement,
|
|
297
|
+
guild_category: ChannelType.GuildCategory,
|
|
297
298
|
};
|
|
298
299
|
builder.addChannelOption((optionBuilder) => {
|
|
299
300
|
optionBuilder
|
|
@@ -376,6 +377,7 @@ function createContext(interaction, services) {
|
|
|
376
377
|
...(ephemeral ? { flags: MessageFlags.Ephemeral } : {}),
|
|
377
378
|
});
|
|
378
379
|
},
|
|
380
|
+
deleteReplyAfter: (milliseconds) => scheduleReplyDeletion(interaction, milliseconds),
|
|
379
381
|
reply: (response) => replyToInteraction(interaction, response),
|
|
380
382
|
...(interaction.isMessageComponent()
|
|
381
383
|
? {
|
|
@@ -490,6 +492,7 @@ function createModalContext(interaction, services) {
|
|
|
490
492
|
...(ephemeral ? { flags: MessageFlags.Ephemeral } : {}),
|
|
491
493
|
});
|
|
492
494
|
},
|
|
495
|
+
deleteReplyAfter: (milliseconds) => scheduleReplyDeletion(interaction, milliseconds),
|
|
493
496
|
reply: (response) => replyToInteraction(interaction, response),
|
|
494
497
|
...(updateResponse ? { updateResponse } : {}),
|
|
495
498
|
getField: (customId) => interaction.fields.getTextInputValue(customId),
|
|
@@ -517,6 +520,16 @@ function createModalContext(interaction, services) {
|
|
|
517
520
|
addChannelMessageReaction: (channelId, messageId, emojiIdentifier) => addChannelMessageReaction(interaction.client, channelId, messageId, emojiIdentifier),
|
|
518
521
|
};
|
|
519
522
|
}
|
|
523
|
+
function scheduleReplyDeletion(interaction, milliseconds) {
|
|
524
|
+
if (!Number.isInteger(milliseconds) ||
|
|
525
|
+
milliseconds < 1_000 ||
|
|
526
|
+
milliseconds > 900_000)
|
|
527
|
+
throw new Error("Reply deletion delay must be between 1 second and 15 minutes");
|
|
528
|
+
const timer = setTimeout(() => {
|
|
529
|
+
void interaction.deleteReply().catch(() => undefined);
|
|
530
|
+
}, milliseconds);
|
|
531
|
+
timer.unref();
|
|
532
|
+
}
|
|
520
533
|
function createSelectContext(interaction, services) {
|
|
521
534
|
return {
|
|
522
535
|
...createContext(interaction, services),
|
|
@@ -739,9 +752,7 @@ function toSelectRows(response) {
|
|
|
739
752
|
.setPlaceholder(select.placeholder)
|
|
740
753
|
.setMinValues(select.minValues ?? 1)
|
|
741
754
|
.setMaxValues(select.maxValues ?? 1)
|
|
742
|
-
.setChannelTypes(...select.channelTypes.map(
|
|
743
|
-
? ChannelType.GuildAnnouncement
|
|
744
|
-
: ChannelType.GuildText))));
|
|
755
|
+
.setChannelTypes(...select.channelTypes.map(discordChannelType))));
|
|
745
756
|
continue;
|
|
746
757
|
}
|
|
747
758
|
if (select.type === "discord-role") {
|
|
@@ -881,9 +892,7 @@ export function toModal(definition) {
|
|
|
881
892
|
.setRequired(field.required ?? false)
|
|
882
893
|
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
883
894
|
.setMaxValues(field.maxValues ?? 1)
|
|
884
|
-
.setChannelTypes(...field.channelTypes.map(
|
|
885
|
-
? ChannelType.GuildAnnouncement
|
|
886
|
-
: ChannelType.GuildText));
|
|
895
|
+
.setChannelTypes(...field.channelTypes.map(discordChannelType));
|
|
887
896
|
if (field.placeholder !== undefined)
|
|
888
897
|
input.setPlaceholder(field.placeholder);
|
|
889
898
|
if (field.defaultValues?.length)
|
|
@@ -894,6 +903,13 @@ export function toModal(definition) {
|
|
|
894
903
|
}
|
|
895
904
|
return modal;
|
|
896
905
|
}
|
|
906
|
+
function discordChannelType(type) {
|
|
907
|
+
if (type === "guild_announcement")
|
|
908
|
+
return ChannelType.GuildAnnouncement;
|
|
909
|
+
if (type === "guild_category")
|
|
910
|
+
return ChannelType.GuildCategory;
|
|
911
|
+
return ChannelType.GuildText;
|
|
912
|
+
}
|
|
897
913
|
async function sendFailure(interaction) {
|
|
898
914
|
if (interaction.isAutocomplete()) {
|
|
899
915
|
await interaction.respond([]).catch(() => undefined);
|
|
@@ -2,7 +2,7 @@ import { type ModuleDefinition } from "@helyx/sdk";
|
|
|
2
2
|
export interface ManagementGuildResources {
|
|
3
3
|
channels: readonly {
|
|
4
4
|
id: string;
|
|
5
|
-
type: "guild_text" | "guild_announcement";
|
|
5
|
+
type: "guild_text" | "guild_announcement" | "guild_category";
|
|
6
6
|
}[];
|
|
7
7
|
roles: readonly {
|
|
8
8
|
id: string;
|