@helyx/bot 1.5.2 → 1.5.3
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 +16 -0
- package/command-root-catalogue.json +452 -0
- package/dist/cached-message-deletion-authority.d.ts +27 -0
- package/dist/cached-message-deletion-authority.js +288 -0
- package/dist/channel-audit-configuration.d.ts +11 -1
- package/dist/channel-audit-configuration.js +28 -6
- package/dist/channel-audit-control.js +42 -2
- package/dist/channel-audit-permissions.js +76 -3
- package/dist/cloud-command-policy-sync.d.ts +26 -0
- package/dist/cloud-command-policy-sync.js +198 -0
- package/dist/cloud-connector.d.ts +6 -1
- package/dist/cloud-connector.js +224 -53
- package/dist/cloud-control-dispatcher.js +18 -0
- package/dist/command-policy-runtime.d.ts +23 -0
- package/dist/command-policy-runtime.js +53 -0
- package/dist/command-root-catalogue.d.ts +5 -0
- package/dist/command-root-catalogue.js +16 -0
- package/dist/control-error-mapping.d.ts +8 -0
- package/dist/control-error-mapping.js +60 -0
- package/dist/control-errors.d.ts +3 -1
- package/dist/control-errors.js +4 -1
- package/dist/control-health-routes.d.ts +10 -0
- package/dist/control-health-routes.js +28 -0
- package/dist/control-server-schemas.d.ts +7 -0
- package/dist/control-server-schemas.js +9 -0
- package/dist/control-server.d.ts +27 -6
- package/dist/control-server.js +103 -122
- package/dist/dashboard-feature-control.d.ts +2 -1
- package/dist/dashboard-feature-control.js +1 -0
- package/dist/dashboard-logging.d.ts +20 -0
- package/dist/dashboard-logging.js +39 -0
- package/dist/dashboard-module-projection.d.ts +1 -1
- package/dist/dashboard-module-projection.js +5 -2
- package/dist/dashboard-preview-import-routes.js +8 -0
- package/dist/database-bootstrap.d.ts +1 -1
- package/dist/database-bootstrap.js +4 -3
- package/dist/discord-channel-audit.js +1 -0
- package/dist/discord-user-identities.d.ts +14 -0
- package/dist/discord-user-identities.js +64 -0
- package/dist/emergency-deletion-dispatcher.d.ts +68 -0
- package/dist/emergency-deletion-dispatcher.js +502 -0
- package/dist/emergency-deletion-rest-transport.d.ts +24 -0
- package/dist/emergency-deletion-rest-transport.js +253 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.js +53 -10
- package/dist/guarded-discord-dispatcher.d.ts +29 -0
- package/dist/guarded-discord-dispatcher.js +204 -0
- package/dist/guild-command-discord-client.d.ts +2 -0
- package/dist/guild-command-discord-client.js +7 -0
- package/dist/guild-command-projection.d.ts +4 -1
- package/dist/guild-command-projection.js +25 -0
- package/dist/guild-command-read-service.d.ts +10 -1
- package/dist/guild-command-read-service.js +37 -0
- package/dist/guild-command-worker.d.ts +19 -4
- package/dist/guild-command-worker.js +356 -26
- package/dist/guild-message-protection-author-roles.d.ts +7 -0
- package/dist/guild-message-protection-author-roles.js +93 -0
- package/dist/guild-message-protection-deletion.d.ts +21 -0
- package/dist/guild-message-protection-deletion.js +138 -0
- package/dist/guild-message-protection-delivery.d.ts +41 -0
- package/dist/guild-message-protection-delivery.js +223 -0
- package/dist/guild-message-update-metadata.d.ts +32 -0
- package/dist/guild-message-update-metadata.js +255 -0
- package/dist/guilds.d.ts +2 -1
- package/dist/guilds.js +11 -1
- package/dist/hosted-server-moderation.d.ts +2 -2
- package/dist/index.js +68 -4
- package/dist/interactions.d.ts +3 -1
- package/dist/interactions.js +77 -12
- package/dist/invite-rollup-maintenance.d.ts +9 -0
- package/dist/invite-rollup-maintenance.js +12 -0
- package/dist/managed-resource-authorization.d.ts +15 -0
- package/dist/managed-resource-authorization.js +37 -0
- package/dist/managed-resource-create-defaults.d.ts +21 -0
- package/dist/managed-resource-create-defaults.js +83 -0
- package/dist/managed-resource-query.d.ts +6 -0
- package/dist/managed-resource-query.js +15 -0
- package/dist/member-role-ids.d.ts +17 -0
- package/dist/member-role-ids.js +66 -0
- package/dist/message-protection-admission.d.ts +60 -0
- package/dist/message-protection-admission.js +279 -0
- package/dist/message-protection-cold-data.d.ts +42 -0
- package/dist/message-protection-cold-data.js +180 -0
- package/dist/message-protection-coordinator.d.ts +73 -0
- package/dist/message-protection-coordinator.js +622 -0
- package/dist/message-protection-settings.d.ts +40 -0
- package/dist/message-protection-settings.js +275 -0
- package/dist/message-protection-summary.d.ts +8 -0
- package/dist/message-protection-summary.js +98 -0
- package/dist/module-availability-control.js +20 -12
- package/dist/module-logging.d.ts +3 -2
- package/dist/module-logging.js +6 -1
- package/dist/modules.js +56 -1
- package/dist/mysql-runtime-persistence.d.ts +1 -0
- package/dist/mysql-runtime-persistence.js +4 -1
- package/dist/nexus-command-recovery-control-routes.d.ts +5 -0
- package/dist/nexus-command-recovery-control-routes.js +48 -0
- package/dist/nexus-command-references.d.ts +6 -0
- package/dist/nexus-command-references.js +33 -0
- package/dist/scheduled-task-runner.js +23 -4
- package/dist/shard-command-startup.d.ts +13 -0
- package/dist/shard-command-startup.js +27 -0
- package/dist/shard-contracts.d.ts +84 -0
- package/dist/shard-contracts.js +33 -0
- package/dist/shard-core-readiness.d.ts +5 -0
- package/dist/shard-core-readiness.js +19 -0
- package/dist/shard-router.d.ts +3 -0
- package/dist/shard-router.js +10 -1
- package/dist/shard-supervisor.d.ts +1 -1
- package/dist/shard-supervisor.js +124 -43
- package/dist/shard-worker.js +25 -15
- package/documentation/README.md +1 -0
- package/documentation/guides/self-hosting.md +54 -0
- package/package.json +18 -17
package/dist/guilds.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { MysqlPool } from "@helyx/database-mysql";
|
|
|
3
3
|
import type { ModuleDefinition } from "@helyx/sdk";
|
|
4
4
|
import { type Client } from "discord.js";
|
|
5
5
|
import type { InteractionLogger } from "./interactions.js";
|
|
6
|
+
import type { MessageProtectionGuildObserver } from "./message-protection-coordinator.js";
|
|
6
7
|
import type { HostedBotInviteGate, HostedGuildOwnerObserver, HostedGuildSafety } from "./hosted-server-moderation.js";
|
|
7
8
|
export interface GuildReconciliationHealth {
|
|
8
9
|
status: "pending" | "running" | "healthy" | "failed" | "stopped";
|
|
@@ -42,6 +43,6 @@ export interface GuildTrackingPersistence extends ReconciliationRepository {
|
|
|
42
43
|
markGuildRemoved(guildId: string, ownership: GuildShardOwnership | undefined, applicationId: string | undefined): Promise<boolean>;
|
|
43
44
|
}
|
|
44
45
|
export declare function createMysqlGuildTrackingPersistence(database: MysqlPool): Promise<GuildTrackingPersistence>;
|
|
45
|
-
export declare function installGuildTracking(client: Client, database: DatabasePool | GuildTrackingPersistence, modules: readonly ModuleDefinition[], logger: InteractionLogger, enabledByDefaultModuleIds?: ReadonlySet<string>, ownership?: GuildShardOwnership, reconciliationIntervalMs?: number, shardCount?: number, applicationId?: string, hostedSafety?: HostedGuildSafety, hostedInviteGate?: HostedBotInviteGate, hostedOwnerObserver?: HostedGuildOwnerObserver | undefined): GuildTrackingPreparation;
|
|
46
|
+
export declare function installGuildTracking(client: Client, database: DatabasePool | GuildTrackingPersistence, modules: readonly ModuleDefinition[], logger: InteractionLogger, enabledByDefaultModuleIds?: ReadonlySet<string>, ownership?: GuildShardOwnership, reconciliationIntervalMs?: number, shardCount?: number, applicationId?: string, hostedSafety?: HostedGuildSafety, hostedInviteGate?: HostedBotInviteGate, hostedOwnerObserver?: HostedGuildOwnerObserver | undefined, protectionObserver?: MessageProtectionGuildObserver): GuildTrackingPreparation;
|
|
46
47
|
export {};
|
|
47
48
|
//# sourceMappingURL=guilds.d.ts.map
|
package/dist/guilds.js
CHANGED
|
@@ -105,7 +105,7 @@ export async function createMysqlGuildTrackingPersistence(database) {
|
|
|
105
105
|
},
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
-
export function installGuildTracking(client, database, modules, logger, enabledByDefaultModuleIds = new Set(), ownership, reconciliationIntervalMs = DEFAULT_RECONCILIATION_INTERVAL_MS, shardCount, applicationId, hostedSafety, hostedInviteGate, hostedOwnerObserver = hostedSafety) {
|
|
108
|
+
export function installGuildTracking(client, database, modules, logger, enabledByDefaultModuleIds = new Set(), ownership, reconciliationIntervalMs = DEFAULT_RECONCILIATION_INTERVAL_MS, shardCount, applicationId, hostedSafety, hostedInviteGate, hostedOwnerObserver = hostedSafety, protectionObserver) {
|
|
109
109
|
if (!Number.isSafeInteger(reconciliationIntervalMs) ||
|
|
110
110
|
reconciliationIntervalMs < 60_000)
|
|
111
111
|
throw new Error("Guild reconciliation interval must be at least one minute");
|
|
@@ -140,6 +140,7 @@ export function installGuildTracking(client, database, modules, logger, enabledB
|
|
|
140
140
|
return operation;
|
|
141
141
|
}
|
|
142
142
|
async function syncGuild(guild, firstObservation = false) {
|
|
143
|
+
const accepted = protectionObserver?.beforeSync(guild);
|
|
143
144
|
const cachedOwner = guild.members?.cache.get(guild.ownerId)?.user ??
|
|
144
145
|
guild.client?.users.cache.get(guild.ownerId);
|
|
145
146
|
await persistence.syncGuild({
|
|
@@ -156,6 +157,7 @@ export function installGuildTracking(client, database, modules, logger, enabledB
|
|
|
156
157
|
});
|
|
157
158
|
if (stopped)
|
|
158
159
|
return;
|
|
160
|
+
accepted?.();
|
|
159
161
|
if (cachedOwner && hostedOwnerObserver)
|
|
160
162
|
await hostedOwnerObserver.observeOwner(guild);
|
|
161
163
|
else
|
|
@@ -260,6 +262,7 @@ export function installGuildTracking(client, database, modules, logger, enabledB
|
|
|
260
262
|
client.on(Events.GuildDelete, (guild) => {
|
|
261
263
|
if (stopped)
|
|
262
264
|
return;
|
|
265
|
+
protectionObserver?.invalidateGuild(guild.id);
|
|
263
266
|
const retry = admissionRetries.get(guild.id);
|
|
264
267
|
if (retry)
|
|
265
268
|
clearTimeout(retry);
|
|
@@ -281,11 +284,13 @@ export function installGuildTracking(client, database, modules, logger, enabledB
|
|
|
281
284
|
});
|
|
282
285
|
});
|
|
283
286
|
client.on(Events.GuildUnavailable, (guild) => {
|
|
287
|
+
protectionObserver?.invalidateGuild(guild.id);
|
|
284
288
|
logger.warn({ event: "guild.unavailable", guildId: guild.id }, "Discord guild is temporarily unavailable; installation remains active");
|
|
285
289
|
});
|
|
286
290
|
async function runReconciliation(reason) {
|
|
287
291
|
if (stopped || !prepared || !client.isReady())
|
|
288
292
|
return;
|
|
293
|
+
protectionObserver?.ready();
|
|
289
294
|
if (reconciliation)
|
|
290
295
|
return reconciliation;
|
|
291
296
|
reconciliation = (async () => {
|
|
@@ -423,12 +428,17 @@ export function installGuildTracking(client, database, modules, logger, enabledB
|
|
|
423
428
|
client.on(Events.ShardResume, () => {
|
|
424
429
|
void runReconciliation("resume");
|
|
425
430
|
});
|
|
431
|
+
if (protectionObserver) {
|
|
432
|
+
client.on(Events.ShardDisconnect, () => protectionObserver.invalidateAll());
|
|
433
|
+
client.on(Events.ShardReconnecting, () => protectionObserver.invalidateAll());
|
|
434
|
+
}
|
|
426
435
|
prepare.reconcile = runReconciliation;
|
|
427
436
|
prepare.health = () => ({ ...health });
|
|
428
437
|
prepare.stop = () => {
|
|
429
438
|
if (stopPromise)
|
|
430
439
|
return stopPromise;
|
|
431
440
|
stopped = true;
|
|
441
|
+
protectionObserver?.invalidateAll();
|
|
432
442
|
if (timer)
|
|
433
443
|
clearInterval(timer);
|
|
434
444
|
timer = undefined;
|
|
@@ -10,9 +10,9 @@ declare const resultSchema: z.ZodObject<{
|
|
|
10
10
|
blacklist: "blacklist";
|
|
11
11
|
}>;
|
|
12
12
|
outcome: z.ZodEnum<{
|
|
13
|
+
failed: "failed";
|
|
13
14
|
completed: "completed";
|
|
14
15
|
requires_review: "requires_review";
|
|
15
|
-
failed: "failed";
|
|
16
16
|
queued: "queued";
|
|
17
17
|
not_currently_installed: "not_currently_installed";
|
|
18
18
|
accepted_for_retry: "accepted_for_retry";
|
|
@@ -45,7 +45,7 @@ export declare class HostedServerModerationClient implements DeveloperServerMode
|
|
|
45
45
|
idempotencyKey: string;
|
|
46
46
|
}): Promise<{
|
|
47
47
|
operationId: string;
|
|
48
|
-
outcome: "
|
|
48
|
+
outcome: "failed" | "completed" | "requires_review" | "queued" | "not_currently_installed" | "accepted_for_retry" | "already_blacklisted";
|
|
49
49
|
paidSlotsReleased: number;
|
|
50
50
|
}>;
|
|
51
51
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { parseBotEnvironment } from "@helyx/config";
|
|
2
2
|
import { StatusHeartbeatReporter } from "@helyx/status-client";
|
|
3
|
+
import { admitStandaloneCommandPolicy, } from "./command-policy-runtime.js";
|
|
3
4
|
import { safeError, ServiceRegistry } from "@helyx/core";
|
|
4
5
|
import { checkDatabaseConnection, createDatabasePool, EckoOperationsRepository, InstallationRepository, inspectConfiguredModuleMigrations, inspectCoreMigrations, inspectCreatorOnboardingMigrations, runCreatorOnboardingMigrations, PremiumAllocationRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
|
|
5
6
|
import { createRuntime } from "@helyx/framework";
|
|
6
7
|
import { assertRepositoryEngineSupported } from "@helyx/database/adapters";
|
|
7
|
-
import { createPlatformServices, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, PlatformMemberRoleGrantOwnershipService, PostgresMemberRoleGrantOwnershipStore, } from "@helyx/platform";
|
|
8
|
-
import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
8
|
+
import { createPlatformServices, createPostgresCommandRestrictionRecovery, createMysqlCommandRestrictionRecovery, createPostgresNexusCommandRecovery, createMysqlNexusCommandRecovery, createPostgresCommandCatalogueRecovery, createMysqlCommandCatalogueRecovery, createPostgresCommandPolicyDelivery, createMysqlCommandPolicyDelivery, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, PlatformMemberRoleGrantOwnershipService, PostgresMemberRoleGrantOwnershipStore, } from "@helyx/platform";
|
|
9
|
+
import { HELYX_SERVICE_NAMES, collectReservedCommandNames, } from "@helyx/sdk";
|
|
9
10
|
import { Client, Events, GatewayIntentBits, Partials } from "discord.js";
|
|
10
11
|
import pino from "pino";
|
|
11
12
|
import { randomUUID } from "node:crypto";
|
|
@@ -15,6 +16,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
15
16
|
import { ClientCloudDiscordDirectory, CloudConnectorController, } from "./cloud-connector.js";
|
|
16
17
|
import { createCloudManagementModule } from "./cloud-management-module.js";
|
|
17
18
|
import { installInteractions } from "./interactions.js";
|
|
19
|
+
import { loadBundledCommandCatalogue } from "./command-root-catalogue.js";
|
|
18
20
|
import { installGuildTracking } from "./guilds.js";
|
|
19
21
|
import { assertConfiguredModuleDatabaseCompatibility, loadConfiguredModulePackages, platformServiceOptions, } from "./modules.js";
|
|
20
22
|
import { resolveModulePackageSpecifiers } from "./module-configuration.js";
|
|
@@ -150,6 +152,8 @@ function assertNoPendingMigrations(scope, pending) {
|
|
|
150
152
|
}
|
|
151
153
|
async function startUnshardedBot(processEnvironment) {
|
|
152
154
|
const environment = parseBotEnvironment(processEnvironment);
|
|
155
|
+
const unshardedSupervisorId = randomUUID();
|
|
156
|
+
let commandPolicyRuntime;
|
|
153
157
|
const mysqlModulePackages = environment.DATABASE_ENGINE === "mysql"
|
|
154
158
|
? await loadConfiguredModulePackages(await resolveModulePackageSpecifiers(environment.HELYX_MODULE_PACKAGES, undefined, undefined, environment.HELYX_MODULE_CATALOGUE_PATH, environment.HELYX_RUNTIME_PROFILE))
|
|
155
159
|
: undefined;
|
|
@@ -157,6 +161,19 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
157
161
|
? await openMysqlBotRuntimePersistence({
|
|
158
162
|
environment,
|
|
159
163
|
modulePackages: mysqlModulePackages,
|
|
164
|
+
...(environment.HELYX_RUNTIME_PROFILE === "self-hosted"
|
|
165
|
+
? {
|
|
166
|
+
onCoreReady: async (pool) => {
|
|
167
|
+
commandPolicyRuntime = await admitStandaloneCommandPolicy(createMysqlCommandPolicyDelivery(pool, {
|
|
168
|
+
applicationId: environment.DISCORD_CLIENT_ID,
|
|
169
|
+
scope: {
|
|
170
|
+
mode: "standalone",
|
|
171
|
+
instanceId: unshardedSupervisorId,
|
|
172
|
+
},
|
|
173
|
+
}));
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
: {}),
|
|
160
177
|
})
|
|
161
178
|
: undefined;
|
|
162
179
|
const database = mysql
|
|
@@ -220,6 +237,11 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
220
237
|
? await inspectRequiredCoreMigrations(database)
|
|
221
238
|
: await runCoreMigrations(database)
|
|
222
239
|
: mysql.database.migrations.core;
|
|
240
|
+
if (database && environment.HELYX_RUNTIME_PROFILE === "self-hosted")
|
|
241
|
+
commandPolicyRuntime = await admitStandaloneCommandPolicy(createPostgresCommandPolicyDelivery(database, {
|
|
242
|
+
applicationId: environment.DISCORD_CLIENT_ID,
|
|
243
|
+
scope: { mode: "standalone", instanceId: unshardedSupervisorId },
|
|
244
|
+
}));
|
|
223
245
|
bootstrapLogger.info({
|
|
224
246
|
event: "database.ready",
|
|
225
247
|
appliedMigrations: migrationResult.applied,
|
|
@@ -302,6 +324,8 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
302
324
|
coreVersion: readBotVersion(),
|
|
303
325
|
logger: bootstrapLogger,
|
|
304
326
|
});
|
|
327
|
+
if (commandPolicyRuntime)
|
|
328
|
+
await cloudConnector.setCommandPolicyDelivery(commandPolicyRuntime);
|
|
305
329
|
const permissionsModule = createPermissionsModule(featureModules);
|
|
306
330
|
const managementModule = createManagementModule(featureModules, guildResources);
|
|
307
331
|
const diagnosticInstallations = mysql
|
|
@@ -363,7 +387,6 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
363
387
|
];
|
|
364
388
|
let scheduledTasks;
|
|
365
389
|
let guildCommandWorker;
|
|
366
|
-
const unshardedSupervisorId = randomUUID();
|
|
367
390
|
const deployedCommit = deploymentReference(processEnvironment);
|
|
368
391
|
const buildHealth = async () => {
|
|
369
392
|
let databaseHealthy = true;
|
|
@@ -454,9 +477,21 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
454
477
|
...publicModules,
|
|
455
478
|
...(operationsModule ? [operationsModule] : []),
|
|
456
479
|
];
|
|
480
|
+
const commandCatalogue = modules.some(({ manifest }) => manifest.id === "helyx.nexus-core")
|
|
481
|
+
? mysql
|
|
482
|
+
? createMysqlCommandCatalogueRecovery(mysql.pool, environment.DISCORD_CLIENT_ID)
|
|
483
|
+
: createPostgresCommandCatalogueRecovery(database, environment.DISCORD_CLIENT_ID)
|
|
484
|
+
: undefined;
|
|
485
|
+
if (commandCatalogue)
|
|
486
|
+
await commandCatalogue.publish(await loadBundledCommandCatalogue());
|
|
457
487
|
const platformServices = new Map();
|
|
458
488
|
const serviceAccess = new ServiceRegistry(platformServices);
|
|
459
|
-
const serviceOptions =
|
|
489
|
+
const serviceOptions = {
|
|
490
|
+
...platformServiceOptions(environment, publicModules),
|
|
491
|
+
...(commandPolicyRuntime
|
|
492
|
+
? { commandPolicyDeliveryScope: commandPolicyRuntime.owner.scope }
|
|
493
|
+
: {}),
|
|
494
|
+
};
|
|
460
495
|
const platformFactory = mysql
|
|
461
496
|
? mysql.createPlatformServices(publicModules, {
|
|
462
497
|
...serviceOptions,
|
|
@@ -575,6 +610,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
575
610
|
: undefined;
|
|
576
611
|
const commandsReady = installInteractions(client, modules, {
|
|
577
612
|
globalCommandMode: environment.HELYX_GLOBAL_COMMAND_MODE,
|
|
613
|
+
resolveGuildCommandName: (input) => guildCommandReads.resolveInteractionCommand(input),
|
|
578
614
|
...(environment.DISCORD_INTERNAL_GUILD_ID
|
|
579
615
|
? { internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID }
|
|
580
616
|
: {}),
|
|
@@ -719,6 +755,17 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
719
755
|
});
|
|
720
756
|
scheduledTasks.start();
|
|
721
757
|
guildCommandWorker = new GuildCommandWorker({
|
|
758
|
+
...(commandPolicyRuntime
|
|
759
|
+
? { commandPolicyDelivery: commandPolicyRuntime.delivery }
|
|
760
|
+
: {}),
|
|
761
|
+
...(commandCatalogue ? { commandCatalogue } : {}),
|
|
762
|
+
...(modules.some(({ manifest }) => manifest.id === "helyx.nexus-core")
|
|
763
|
+
? {
|
|
764
|
+
commandRestrictions: mysql
|
|
765
|
+
? createMysqlCommandRestrictionRecovery(mysql.pool, environment.DISCORD_CLIENT_ID)
|
|
766
|
+
: createPostgresCommandRestrictionRecovery(database, environment.DISCORD_CLIENT_ID),
|
|
767
|
+
}
|
|
768
|
+
: {}),
|
|
722
769
|
...(mysql
|
|
723
770
|
? { repositories: mysql.commandWorker }
|
|
724
771
|
: { database: database }),
|
|
@@ -778,6 +825,22 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
778
825
|
: undefined;
|
|
779
826
|
controlServer = await createBotControlServer({
|
|
780
827
|
service: controlService,
|
|
828
|
+
...(environment.HELYX_RUNTIME_PROFILE === "hosted" &&
|
|
829
|
+
modules.some(({ manifest }) => manifest.id === "helyx.nexus-core")
|
|
830
|
+
? {
|
|
831
|
+
nexusCommandRecovery: mysql
|
|
832
|
+
? createMysqlNexusCommandRecovery(mysql.pool, {
|
|
833
|
+
applicationId: environment.DISCORD_CLIENT_ID,
|
|
834
|
+
persistedCommandCatalogue: true,
|
|
835
|
+
reservedCommandNames: collectReservedCommandNames(modules),
|
|
836
|
+
})
|
|
837
|
+
: createPostgresNexusCommandRecovery(database, {
|
|
838
|
+
applicationId: environment.DISCORD_CLIENT_ID,
|
|
839
|
+
persistedCommandCatalogue: true,
|
|
840
|
+
reservedCommandNames: collectReservedCommandNames(modules),
|
|
841
|
+
}),
|
|
842
|
+
}
|
|
843
|
+
: {}),
|
|
781
844
|
secret: environment.HELYX_BOT_CONTROL_SECRET,
|
|
782
845
|
...(hostedLeaveWorker && hostedOwnerObserver
|
|
783
846
|
? {
|
|
@@ -893,6 +956,7 @@ export function gatewayIntentsForModules(modules, messageContentEnabled = false,
|
|
|
893
956
|
messageContentEnabled) ||
|
|
894
957
|
modules.some((module) => module.events?.guildMessageCreate?.length ||
|
|
895
958
|
module.events?.guildMessageUpdate?.length ||
|
|
959
|
+
module.events?.guildMessageUpdateMetadata?.length ||
|
|
896
960
|
module.events?.messageDelete?.length ||
|
|
897
961
|
module.events?.guildMessageDeleteBulk?.length)) {
|
|
898
962
|
intents.push(GatewayIntentBits.GuildMessages);
|
package/dist/interactions.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CommandAutocompleteContext, CommandAuthorization, CommandContribution, MessageResponse, ModalDefinition, ModuleDefinition, ServiceAccess } from "@helyx/sdk";
|
|
2
2
|
import { type GlobalCommandRegistrationMode } from "./command-registration-policy.js";
|
|
3
3
|
import { ModalBuilder, type AutocompleteInteraction, type ChatInputCommandInteraction, type Client, type InteractionReplyOptions, type InteractionUpdateOptions } from "discord.js";
|
|
4
|
+
import type { GuildCommandInteractionRoute } from "./guild-command-read-service.js";
|
|
4
5
|
export declare const GLOBAL_MODULE_UNAVAILABLE_MESSAGE = "This module is temporarily unavailable for all hosted servers.";
|
|
5
6
|
export declare const AUTOCOMPLETE_RESPONSE_DEADLINE_MS = 2000;
|
|
6
7
|
export declare const DISCORD_DNS_REFRESH_INTERVAL_MS = 30000;
|
|
@@ -16,6 +17,7 @@ export interface CommandRegistration {
|
|
|
16
17
|
synchroniseInternalGuild?: boolean;
|
|
17
18
|
globalCommandMode?: GlobalCommandRegistrationMode;
|
|
18
19
|
selfAuthorizingGuildModuleIds?: ReadonlySet<string>;
|
|
20
|
+
resolveGuildCommandName?(input: GuildCommandInteractionRoute): Promise<string | null>;
|
|
19
21
|
}
|
|
20
22
|
export declare function installInteractions(client: Client, modules: readonly ModuleDefinition[], registration: CommandRegistration, logger: InteractionLogger, services: ServiceAccess): Promise<void>;
|
|
21
23
|
export declare function warmDiscordRestConnection(client: Client<true>, logger: InteractionLogger): Promise<void>;
|
|
@@ -44,5 +46,5 @@ export declare function normalizeDiscordPermission(permission: string): string;
|
|
|
44
46
|
export declare function toReplyOptions(response: MessageResponse): InteractionReplyOptions;
|
|
45
47
|
export declare function toUpdateOptions(response: MessageResponse): InteractionUpdateOptions;
|
|
46
48
|
export declare function toModal(definition: ModalDefinition): ModalBuilder;
|
|
47
|
-
export declare function safeInteractionFailureMessage(error: unknown): string;
|
|
49
|
+
export declare function safeInteractionFailureMessage(error: unknown, nexusRoot?: string | null): string;
|
|
48
50
|
//# sourceMappingURL=interactions.d.ts.map
|
package/dist/interactions.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { lookup as lookupDns } from "node:dns/promises";
|
|
2
|
-
import { HELYX_SERVICE_NAMES, NexusEconomyError, UserFacingInteractionError, } from "@helyx/sdk";
|
|
2
|
+
import { HELYX_SERVICE_NAMES, formatNexusCommandReference, NexusEconomyError, UserFacingInteractionError, } from "@helyx/sdk";
|
|
3
|
+
import { nexusObservedRoot } from "./nexus-command-references.js";
|
|
4
|
+
import { snapshotMemberRoleIds } from "./member-role-ids.js";
|
|
3
5
|
import { canUseCommand, safeError } from "@helyx/core";
|
|
4
6
|
import { commandRegistrationPolicy, contributionRegistrationPolicy, } from "./command-registration-policy.js";
|
|
5
7
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, EmbedBuilder, Events, GuildMember, ApplicationIntegrationType, ChannelType, InteractionContextType, LabelBuilder, MessageFlags, MentionableSelectMenuBuilder, ModalBuilder, RoleSelectMenuBuilder, Routes, StringSelectMenuBuilder, SlashCommandBuilder, TextInputBuilder, TextInputStyle, UserSelectMenuBuilder, TextDisplayBuilder, PermissionFlagsBits, escapeMarkdown, } from "discord.js";
|
|
@@ -92,12 +94,44 @@ export function installInteractions(client, modules, registration, logger, servi
|
|
|
92
94
|
client.on(Events.InteractionCreate, (interaction) => {
|
|
93
95
|
void handleInteraction(interaction);
|
|
94
96
|
});
|
|
97
|
+
function requiresGuildRootResolution(interaction) {
|
|
98
|
+
// Only known non-Nexus application globals are independent of guild root
|
|
99
|
+
// mappings. A newly loaded guild root can collide with an old observation.
|
|
100
|
+
return Boolean(registration.resolveGuildCommandName &&
|
|
101
|
+
(interaction.commandName === "nexus" ||
|
|
102
|
+
!commands.has(interaction.commandName) ||
|
|
103
|
+
interaction.commandGuildId !== null));
|
|
104
|
+
}
|
|
105
|
+
async function resolveRegisteredCommand(interaction) {
|
|
106
|
+
if (!registration.resolveGuildCommandName)
|
|
107
|
+
return commands.get(interaction.commandName);
|
|
108
|
+
try {
|
|
109
|
+
const canonicalName = await registration.resolveGuildCommandName({
|
|
110
|
+
applicationId: interaction.applicationId,
|
|
111
|
+
guildId: interaction.guildId,
|
|
112
|
+
commandGuildId: interaction.commandGuildId,
|
|
113
|
+
commandId: interaction.commandId,
|
|
114
|
+
commandName: interaction.commandName,
|
|
115
|
+
});
|
|
116
|
+
return canonicalName === null ? undefined : commands.get(canonicalName);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
logger.warn({
|
|
120
|
+
event: "interaction.command_route_unavailable",
|
|
121
|
+
...interactionLogContext(interaction),
|
|
122
|
+
error: safeError(error),
|
|
123
|
+
}, "Observed guild command routing is unavailable");
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
95
127
|
async function handleInteraction(interaction) {
|
|
96
128
|
try {
|
|
97
129
|
if (interaction.isAutocomplete()) {
|
|
98
130
|
const response = createAutocompleteResponseController(interaction, logger);
|
|
99
131
|
try {
|
|
100
|
-
const registered =
|
|
132
|
+
const registered = requiresGuildRootResolution(interaction)
|
|
133
|
+
? await resolveRegisteredCommand(interaction)
|
|
134
|
+
: commands.get(interaction.commandName);
|
|
101
135
|
const groupName = registered?.contribution.subcommandGroups?.length
|
|
102
136
|
? interaction.options.getSubcommandGroup(false)
|
|
103
137
|
: null;
|
|
@@ -136,7 +170,9 @@ export function installInteractions(client, modules, registration, logger, servi
|
|
|
136
170
|
return;
|
|
137
171
|
}
|
|
138
172
|
else if (interaction.isChatInputCommand()) {
|
|
139
|
-
const registered =
|
|
173
|
+
const registered = requiresGuildRootResolution(interaction)
|
|
174
|
+
? await resolveRegisteredCommand(interaction)
|
|
175
|
+
: commands.get(interaction.commandName);
|
|
140
176
|
if (!registered) {
|
|
141
177
|
logger.warn({
|
|
142
178
|
event: "interaction.stale_command",
|
|
@@ -277,7 +313,7 @@ export function installInteractions(client, modules, registration, logger, servi
|
|
|
277
313
|
...interactionLogContext(interaction),
|
|
278
314
|
error: safeError(error),
|
|
279
315
|
}, "Module interaction failed");
|
|
280
|
-
await sendFailure(interaction, error);
|
|
316
|
+
await sendFailure(interaction, error, services);
|
|
281
317
|
}
|
|
282
318
|
}
|
|
283
319
|
return registrationReady;
|
|
@@ -580,7 +616,13 @@ function createContext(interaction, services) {
|
|
|
580
616
|
userId: interaction.user.id,
|
|
581
617
|
userDisplayName: interaction.user.globalName ?? interaction.user.username,
|
|
582
618
|
userRoleIds: interaction.member instanceof GuildMember
|
|
583
|
-
?
|
|
619
|
+
? snapshotMemberRoleIds({
|
|
620
|
+
member: interaction.member,
|
|
621
|
+
client: interaction.client,
|
|
622
|
+
guild: interaction.guild,
|
|
623
|
+
guildId: interaction.guildId,
|
|
624
|
+
actorUserId: interaction.user.id,
|
|
625
|
+
})
|
|
584
626
|
: (interaction.member?.roles ?? []),
|
|
585
627
|
...(interaction.member instanceof GuildMember
|
|
586
628
|
? { userHighestRolePosition: interaction.member.roles.highest.position }
|
|
@@ -749,7 +791,13 @@ function createModalContext(interaction, services) {
|
|
|
749
791
|
userId: interaction.user.id,
|
|
750
792
|
userDisplayName: interaction.user.globalName ?? interaction.user.username,
|
|
751
793
|
userRoleIds: interaction.member instanceof GuildMember
|
|
752
|
-
?
|
|
794
|
+
? snapshotMemberRoleIds({
|
|
795
|
+
member: interaction.member,
|
|
796
|
+
client: interaction.client,
|
|
797
|
+
guild: interaction.guild,
|
|
798
|
+
guildId: interaction.guildId,
|
|
799
|
+
actorUserId: interaction.user.id,
|
|
800
|
+
})
|
|
753
801
|
: (interaction.member?.roles ?? []),
|
|
754
802
|
...(interaction.member instanceof GuildMember
|
|
755
803
|
? { userHighestRolePosition: interaction.member.roles.highest.position }
|
|
@@ -1012,7 +1060,13 @@ function permissionActor(interaction) {
|
|
|
1012
1060
|
return {
|
|
1013
1061
|
userId: interaction.user.id,
|
|
1014
1062
|
roleIds: interaction.member instanceof GuildMember
|
|
1015
|
-
?
|
|
1063
|
+
? snapshotMemberRoleIds({
|
|
1064
|
+
member: interaction.member,
|
|
1065
|
+
client: interaction.client,
|
|
1066
|
+
guild: interaction.guild,
|
|
1067
|
+
guildId: interaction.guildId,
|
|
1068
|
+
actorUserId: interaction.user.id,
|
|
1069
|
+
})
|
|
1016
1070
|
: (interaction.member?.roles ?? []),
|
|
1017
1071
|
permissions: interaction.memberPermissions
|
|
1018
1072
|
?.toArray()
|
|
@@ -1346,15 +1400,19 @@ function discordChannelType(type) {
|
|
|
1346
1400
|
return ChannelType.GuildMedia;
|
|
1347
1401
|
return ChannelType.GuildText;
|
|
1348
1402
|
}
|
|
1349
|
-
export function safeInteractionFailureMessage(error) {
|
|
1403
|
+
export function safeInteractionFailureMessage(error, nexusRoot = "nexus") {
|
|
1350
1404
|
if (error instanceof UserFacingInteractionError) {
|
|
1405
|
+
const redeem = formatNexusCommandReference(nexusRoot, "redeem", true);
|
|
1406
|
+
const reopen = redeem
|
|
1407
|
+
? `Run ${redeem} again.`
|
|
1408
|
+
: "Reopen the redeem command from Discord's command picker.";
|
|
1351
1409
|
const messages = {
|
|
1352
1410
|
nexus_exchange_slow: "The Exchange is taking too long to respond. Try again shortly.",
|
|
1353
1411
|
nexus_exchange_item_unavailable: "That Exchange item is not available to you right now.",
|
|
1354
1412
|
nexus_exchange_item_changed: "That Exchange item changed or is no longer available. Start again.",
|
|
1355
1413
|
nexus_exchange_balance_changed: "Your Nexus balance changed after the preview. Start again.",
|
|
1356
|
-
nexus_exchange_form_expired:
|
|
1357
|
-
nexus_exchange_form_replayed:
|
|
1414
|
+
nexus_exchange_form_expired: `This request form expired. ${reopen}`,
|
|
1415
|
+
nexus_exchange_form_replayed: `This request form was already submitted. ${reopen}`,
|
|
1358
1416
|
nexus_exchange_preview_expired: "This redemption preview expired. Start the command again.",
|
|
1359
1417
|
nexus_exchange_invalid_request: "This Exchange request is invalid. Start the command again.",
|
|
1360
1418
|
nexus_exchange_response_required: "This custom request needs a response.",
|
|
@@ -1380,14 +1438,21 @@ export function safeInteractionFailureMessage(error) {
|
|
|
1380
1438
|
}
|
|
1381
1439
|
return "Something went wrong while handling that interaction.";
|
|
1382
1440
|
}
|
|
1383
|
-
async function sendFailure(interaction, error) {
|
|
1441
|
+
async function sendFailure(interaction, error, services) {
|
|
1384
1442
|
if (interaction.isAutocomplete()) {
|
|
1385
1443
|
await interaction.respond([]).catch(() => undefined);
|
|
1386
1444
|
return;
|
|
1387
1445
|
}
|
|
1388
1446
|
if (!interaction.isRepliable())
|
|
1389
1447
|
return;
|
|
1390
|
-
const
|
|
1448
|
+
const needsRoot = error instanceof UserFacingInteractionError &&
|
|
1449
|
+
["nexus_exchange_form_expired", "nexus_exchange_form_replayed"].includes(error.code);
|
|
1450
|
+
const root = needsRoot
|
|
1451
|
+
? interaction.guildId
|
|
1452
|
+
? await nexusObservedRoot(services, interaction.guildId)
|
|
1453
|
+
: null
|
|
1454
|
+
: "nexus";
|
|
1455
|
+
const message = safeInteractionFailureMessage(error, root);
|
|
1391
1456
|
if (interaction.replied || interaction.deferred) {
|
|
1392
1457
|
await interaction.editReply({ content: message }).catch(() => undefined);
|
|
1393
1458
|
return;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ModuleDefinition } from "@helyx/sdk";
|
|
2
|
+
/** Private historical-work exception; never grants the refresh/provider lane. */
|
|
3
|
+
export declare function isInviteRollupMaintenanceTask(task: {
|
|
4
|
+
moduleId: string;
|
|
5
|
+
taskKind: string;
|
|
6
|
+
taskVersion: number;
|
|
7
|
+
delivery: string;
|
|
8
|
+
}, module: ModuleDefinition | undefined): boolean;
|
|
9
|
+
//# sourceMappingURL=invite-rollup-maintenance.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Private historical-work exception; never grants the refresh/provider lane. */
|
|
2
|
+
export function isInviteRollupMaintenanceTask(task, module) {
|
|
3
|
+
return Boolean(task.moduleId === "helyx.invite-tracking" &&
|
|
4
|
+
task.taskKind === "invite-tracking.finalized-rollup" &&
|
|
5
|
+
task.taskVersion === 1 &&
|
|
6
|
+
task.delivery === "one-shot" &&
|
|
7
|
+
module?.manifest.id === task.moduleId &&
|
|
8
|
+
module.scheduledTasks?.some((contribution) => contribution.kind === task.taskKind &&
|
|
9
|
+
contribution.version === task.taskVersion &&
|
|
10
|
+
contribution.delivery === task.delivery));
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=invite-rollup-maintenance.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ManagedResourceContext, ModuleDefinition, ModuleManagedResource, PermissionActor } from "@helyx/sdk";
|
|
2
|
+
import type { ManagedResourceOperation } from "./control-permissions.js";
|
|
3
|
+
/** Explicit, target-bound delegation after the ordinary permission has denied. */
|
|
4
|
+
export declare function authorizeManagedResourceContext(input: {
|
|
5
|
+
module: ModuleDefinition;
|
|
6
|
+
resource: ModuleManagedResource;
|
|
7
|
+
resourceType: string;
|
|
8
|
+
resourceId?: string;
|
|
9
|
+
actionId?: string;
|
|
10
|
+
operation: ManagedResourceOperation;
|
|
11
|
+
permissionId: string;
|
|
12
|
+
authorize(permissionId: string): Promise<PermissionActor>;
|
|
13
|
+
createContext(actor: PermissionActor, scope: "full" | "owner"): ManagedResourceContext;
|
|
14
|
+
}): Promise<ManagedResourceContext>;
|
|
15
|
+
//# sourceMappingURL=managed-resource-authorization.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BotControlForbiddenError } from "./control-errors.js";
|
|
2
|
+
/** Explicit, target-bound delegation after the ordinary permission has denied. */
|
|
3
|
+
export async function authorizeManagedResourceContext(input) {
|
|
4
|
+
let actor;
|
|
5
|
+
try {
|
|
6
|
+
actor = await input.authorize(input.permissionId);
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
if (!(error instanceof BotControlForbiddenError))
|
|
10
|
+
throw error;
|
|
11
|
+
const declaration = input.module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
12
|
+
const ownerPermissionId = input.operation === "update"
|
|
13
|
+
? declaration?.ownerPermissions?.update
|
|
14
|
+
: input.operation === "action"
|
|
15
|
+
? declaration?.actions?.find(({ id }) => id === input.actionId)
|
|
16
|
+
?.ownerPermissionId
|
|
17
|
+
: undefined;
|
|
18
|
+
if (!ownerPermissionId ||
|
|
19
|
+
!input.resourceId ||
|
|
20
|
+
!input.resource.resolveOwnerUserId)
|
|
21
|
+
throw error;
|
|
22
|
+
actor = await input.authorize(ownerPermissionId);
|
|
23
|
+
const context = input.createContext(actor, "owner");
|
|
24
|
+
let ownerUserId;
|
|
25
|
+
try {
|
|
26
|
+
ownerUserId = await input.resource.resolveOwnerUserId(context, input.resourceId);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new BotControlForbiddenError();
|
|
30
|
+
}
|
|
31
|
+
if (!ownerUserId || ownerUserId !== actor.userId)
|
|
32
|
+
throw new BotControlForbiddenError();
|
|
33
|
+
return context;
|
|
34
|
+
}
|
|
35
|
+
return input.createContext(actor, "full");
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=managed-resource-authorization.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ManagedResourceContext, ManagedResourceValue, ModuleDefinition, ModuleManagedResource } from "@helyx/sdk";
|
|
2
|
+
export interface ManagedResourceCreateDefaultsInput {
|
|
3
|
+
guildId: string;
|
|
4
|
+
moduleId: string;
|
|
5
|
+
resourceType: string;
|
|
6
|
+
actorUserId: string;
|
|
7
|
+
correlationId: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ManagedResourceCreateDefaultsControl {
|
|
10
|
+
getManagedResourceCreateDefaults?(input: ManagedResourceCreateDefaultsInput): Promise<{
|
|
11
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export declare function readManagedResourceCreateDefaults(input: {
|
|
15
|
+
module: ModuleDefinition;
|
|
16
|
+
resource: ModuleManagedResource;
|
|
17
|
+
context: ManagedResourceContext;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=managed-resource-create-defaults.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { BotControlNotFoundError, BotControlValidationError, } from "./control-errors.js";
|
|
3
|
+
import { managedResourceObject } from "./control-server-schemas.js";
|
|
4
|
+
import { requireDashboardFeatures, resourceFieldFeatureIds, validateManagedResourceInput, isBoundedManagedResourceValue, } from "./dashboard-feature-control.js";
|
|
5
|
+
const optionalUrl = z
|
|
6
|
+
.string()
|
|
7
|
+
.max(2048)
|
|
8
|
+
.refine((value) => value === "" || /^https?:\/\//u.test(value));
|
|
9
|
+
const embed = z
|
|
10
|
+
.object({
|
|
11
|
+
title: z.string().max(256).optional(),
|
|
12
|
+
description: z.string().max(4000).optional(),
|
|
13
|
+
url: optionalUrl.optional(),
|
|
14
|
+
imageUrl: optionalUrl.optional(),
|
|
15
|
+
thumbnailUrl: optionalUrl.optional(),
|
|
16
|
+
color: z.number().int().min(0).max(0xffffff).optional(),
|
|
17
|
+
timestamp: z.string().max(100).optional(),
|
|
18
|
+
author: z
|
|
19
|
+
.object({
|
|
20
|
+
name: z.string().max(256).optional(),
|
|
21
|
+
url: optionalUrl.optional(),
|
|
22
|
+
iconUrl: optionalUrl.optional(),
|
|
23
|
+
})
|
|
24
|
+
.strict()
|
|
25
|
+
.optional(),
|
|
26
|
+
footer: z
|
|
27
|
+
.object({
|
|
28
|
+
text: z.string().max(2048).optional(),
|
|
29
|
+
iconUrl: optionalUrl.optional(),
|
|
30
|
+
})
|
|
31
|
+
.strict()
|
|
32
|
+
.optional(),
|
|
33
|
+
fields: z
|
|
34
|
+
.array(z
|
|
35
|
+
.object({
|
|
36
|
+
name: z.string().max(256),
|
|
37
|
+
value: z.string().max(1024),
|
|
38
|
+
inline: z.boolean().optional(),
|
|
39
|
+
})
|
|
40
|
+
.strict())
|
|
41
|
+
.max(25)
|
|
42
|
+
.optional(),
|
|
43
|
+
})
|
|
44
|
+
.strict();
|
|
45
|
+
function validatePartialFields(fields, values) {
|
|
46
|
+
for (const [key, value] of Object.entries(values)) {
|
|
47
|
+
const field = fields.find((candidate) => candidate.key === key);
|
|
48
|
+
if (!field)
|
|
49
|
+
throw new BotControlValidationError("Creation defaults contain an undeclared field");
|
|
50
|
+
if (field.type === "list" || field.type === "discord-embeds") {
|
|
51
|
+
if (!Array.isArray(value) ||
|
|
52
|
+
value.length < field.minItems ||
|
|
53
|
+
value.length > field.maxItems)
|
|
54
|
+
throw new BotControlValidationError("Creation defaults contain an invalid list");
|
|
55
|
+
for (const item of value) {
|
|
56
|
+
if (field.type === "discord-embeds") {
|
|
57
|
+
embed.parse(item);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const entry = managedResourceObject.parse(item);
|
|
61
|
+
validateManagedResourceInput(field.fields, entry);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
validateManagedResourceInput([{ ...field, required: false }], {
|
|
66
|
+
[key]: value,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export async function readManagedResourceCreateDefaults(input) {
|
|
71
|
+
const declaration = input.module.manifest.dashboard?.resources?.find(({ id }) => id === input.resource.id);
|
|
72
|
+
if (declaration?.createDefaults !== true || !input.resource.createDefaults)
|
|
73
|
+
throw new BotControlNotFoundError("Creation defaults are not declared by this module");
|
|
74
|
+
await requireDashboardFeatures(input.context.services, input.context.guildId, input.module.manifest.id, [declaration.featureId]);
|
|
75
|
+
const result = await input.resource.createDefaults(input.context);
|
|
76
|
+
if (!isBoundedManagedResourceValue(result))
|
|
77
|
+
throw new BotControlValidationError("Creation defaults contain unsafe or unbounded values");
|
|
78
|
+
const value = managedResourceObject.parse(result);
|
|
79
|
+
validatePartialFields(declaration.fields, value);
|
|
80
|
+
await requireDashboardFeatures(input.context.services, input.context.guildId, input.module.manifest.id, resourceFieldFeatureIds(declaration.fields, value));
|
|
81
|
+
return { value };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=managed-resource-create-defaults.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ModuleDefinition } from "@helyx/sdk";
|
|
2
|
+
export declare function validateManagedResourceQuery(module: ModuleDefinition, resourceType: string, input: {
|
|
3
|
+
search?: string;
|
|
4
|
+
filters?: Readonly<Record<string, string>>;
|
|
5
|
+
}): void;
|
|
6
|
+
//# sourceMappingURL=managed-resource-query.d.ts.map
|