@helyx/bot 1.3.1 → 1.5.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/CHANGELOG.md +100 -28
- package/README.md +65 -11
- package/dist/channel-audit-configuration.d.ts +7 -0
- package/dist/channel-audit-configuration.js +40 -0
- package/dist/channel-audit-control.d.ts +14 -0
- package/dist/channel-audit-control.js +61 -0
- package/dist/channel-audit-permissions.d.ts +56 -0
- package/dist/channel-audit-permissions.js +417 -0
- package/dist/channel-audit-routes.d.ts +4 -0
- package/dist/channel-audit-routes.js +42 -0
- package/dist/cloud-connector.d.ts +11 -4
- package/dist/cloud-connector.js +54 -15
- package/dist/cloud-control-dispatcher.d.ts +8 -3
- package/dist/cloud-control-dispatcher.js +84 -4
- package/dist/control-errors.d.ts +12 -0
- package/dist/control-errors.js +32 -0
- package/dist/control-server-schemas.d.ts +84 -0
- package/dist/control-server-schemas.js +101 -0
- package/dist/control-server.d.ts +25 -8
- package/dist/control-server.js +101 -152
- package/dist/core-retention.d.ts +26 -0
- package/dist/core-retention.js +49 -0
- package/dist/creator-guild-join-service.d.ts +25 -0
- package/dist/creator-guild-join-service.js +218 -0
- package/dist/creator-invite-service.d.ts +37 -0
- package/dist/creator-invite-service.js +686 -0
- package/dist/creator-onboarding-control.d.ts +12 -0
- package/dist/creator-onboarding-control.js +65 -0
- package/dist/creator-onboarding-platform.d.ts +20 -0
- package/dist/creator-onboarding-platform.js +58 -0
- package/dist/dashboard-action-control.d.ts +2 -2
- package/dist/dashboard-action-control.js +2 -3
- package/dist/database-bootstrap.d.ts +36 -0
- package/dist/database-bootstrap.js +101 -0
- package/dist/developer-operations-module.d.ts +1 -0
- package/dist/developer-operations-module.js +34 -16
- package/dist/discord-channel-audit.d.ts +9 -0
- package/dist/discord-channel-audit.js +195 -0
- package/dist/discord-guild-health.d.ts +2 -2
- package/dist/discord-owned-message-service.d.ts +1 -0
- package/dist/discord-owned-message-service.js +3 -0
- package/dist/discord-role-safety.d.ts +4 -0
- package/dist/discord-role-safety.js +30 -0
- package/dist/events.d.ts +2 -0
- package/dist/events.js +5 -20
- package/dist/guild-command-read-service.d.ts +8 -3
- package/dist/guild-command-read-service.js +14 -7
- package/dist/guild-command-release.d.ts +15 -8
- package/dist/guild-command-release.js +10 -3
- package/dist/guild-command-worker.d.ts +17 -4
- package/dist/guild-command-worker.js +17 -6
- package/dist/guilds.d.ts +24 -3
- package/dist/guilds.js +185 -64
- package/dist/hosted-server-moderation.js +10 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +200 -82
- package/dist/interactions.js +16 -3
- package/dist/module-configuration.d.ts +1 -1
- package/dist/module-configuration.js +5 -2
- package/dist/module-logging.d.ts +8 -3
- package/dist/module-logging.js +13 -7
- package/dist/modules.d.ts +1 -0
- package/dist/modules.js +8 -1
- package/dist/mysql-runtime-persistence.d.ts +46 -0
- package/dist/mysql-runtime-persistence.js +91 -0
- package/dist/oauth-module.js +2 -2
- package/dist/oauth-service.d.ts +7 -0
- package/dist/oauth-service.js +3 -0
- package/dist/scheduled-task-runner.d.ts +9 -85
- package/dist/scheduled-task-runner.js +1 -1
- package/dist/shard-contracts.d.ts +222 -0
- package/dist/shard-contracts.js +52 -1
- package/dist/shard-router.d.ts +4 -1
- package/dist/shard-router.js +19 -0
- package/dist/shard-supervisor.js +43 -9
- package/dist/shard-worker.d.ts +5 -2
- package/dist/shard-worker.js +41 -5
- package/dist/supervisor-cloud-directory.d.ts +9 -3
- package/dist/supervisor-cloud-directory.js +8 -12
- package/dist/trivia-data-provider.d.ts +11 -0
- package/dist/trivia-data-provider.js +46 -26
- package/dist/verification-service.d.ts +2 -2
- package/dist/verification-service.js +6 -3
- package/documentation/README.md +5 -3
- package/documentation/examples/.env.example +15 -11
- package/documentation/guides/getting-started.md +29 -12
- package/documentation/guides/privacy-and-support.md +6 -5
- package/documentation/guides/self-hosting.md +16 -11
- package/documentation/modules/auto-moderation.md +7 -5
- package/documentation/modules/building-a-module.md +48 -2
- package/documentation/modules/moderation.md +5 -5
- package/documentation/modules/publishing-a-module.md +6 -4
- package/documentation/modules/testing-a-module.md +39 -4
- package/documentation/reference/environment.md +43 -9
- package/documentation/runbooks/backup-and-recovery.md +26 -7
- package/documentation/runbooks/installation-and-upgrades.md +97 -27
- package/package.json +20 -16
|
@@ -6,6 +6,9 @@ export class DiscordOwnedMessageDeletionService {
|
|
|
6
6
|
constructor(client) {
|
|
7
7
|
this.client = client;
|
|
8
8
|
}
|
|
9
|
+
isReady() {
|
|
10
|
+
return this.client.isReady();
|
|
11
|
+
}
|
|
9
12
|
async addOwnedMessageReaction(input) {
|
|
10
13
|
const emoji = prepareEmojiIdentifier(input.emojiIdentifier);
|
|
11
14
|
const message = await this.#fetchOwnedMessage(input);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The existing Helyx role-grant policy, shared by delivery and inspection. */
|
|
2
|
+
export declare const dangerousRolePermissionNames: Set<string>;
|
|
3
|
+
export declare function dangerousRolePermissions(permissions: bigint): string[];
|
|
4
|
+
//# sourceMappingURL=discord-role-safety.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PermissionsBitField } from "discord.js";
|
|
2
|
+
/** The existing Helyx role-grant policy, shared by delivery and inspection. */
|
|
3
|
+
export const dangerousRolePermissionNames = new Set([
|
|
4
|
+
"ADMINISTRATOR",
|
|
5
|
+
"BAN_MEMBERS",
|
|
6
|
+
"DEAFEN_MEMBERS",
|
|
7
|
+
"KICK_MEMBERS",
|
|
8
|
+
"MANAGE_CHANNELS",
|
|
9
|
+
"MANAGE_EVENTS",
|
|
10
|
+
"MANAGE_GUILD",
|
|
11
|
+
"MANAGE_GUILD_EXPRESSIONS",
|
|
12
|
+
"MANAGE_MESSAGES",
|
|
13
|
+
"MANAGE_NICKNAMES",
|
|
14
|
+
"MANAGE_ROLES",
|
|
15
|
+
"MANAGE_THREADS",
|
|
16
|
+
"MANAGE_WEBHOOKS",
|
|
17
|
+
"MENTION_EVERYONE",
|
|
18
|
+
"MODERATE_MEMBERS",
|
|
19
|
+
"MOVE_MEMBERS",
|
|
20
|
+
"MUTE_MEMBERS",
|
|
21
|
+
"VIEW_AUDIT_LOG",
|
|
22
|
+
]);
|
|
23
|
+
export function dangerousRolePermissions(permissions) {
|
|
24
|
+
return new PermissionsBitField(permissions)
|
|
25
|
+
.toArray()
|
|
26
|
+
.map((permission) => permission.replaceAll(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase())
|
|
27
|
+
.filter((permission) => dangerousRolePermissionNames.has(permission))
|
|
28
|
+
.sort();
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=discord-role-safety.js.map
|
package/dist/events.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DiscordChannelAudienceInspection, DiscordChannelInspection, DiscordEmojiInspection, DiscordMessageInspection, DiscordGuildCapabilityInspection, DiscordGuildChannelAudienceInspection, DiscordResourceInspectionService, DiscordRoleInspection, DiscordMessageService, InstallationService, MemberEnforcementService, MemberRoleService, ModuleDefinition, ServiceAccess, VerificationMemberService } from "@helyx/sdk";
|
|
2
|
+
import { inspectDiscordChannelAudit } from "./discord-channel-audit.js";
|
|
2
3
|
import { type Client, type MessageReaction, type PartialMessageReaction } from "discord.js";
|
|
3
4
|
import { type InteractionLogger } from "./interactions.js";
|
|
4
5
|
import type { TransientViolationEvidenceStore } from "./transient-violation-evidence.js";
|
|
@@ -80,6 +81,7 @@ export declare class DiscordResourceInspection implements DiscordResourceInspect
|
|
|
80
81
|
#private;
|
|
81
82
|
private readonly client;
|
|
82
83
|
constructor(client: Client);
|
|
84
|
+
inspectChannelAudit(input: Parameters<typeof inspectDiscordChannelAudit>[1]): Promise<import("@helyx/sdk").ChannelAuditReport>;
|
|
83
85
|
inspectGuildCapabilities(input: {
|
|
84
86
|
guildId: string;
|
|
85
87
|
}): Promise<DiscordGuildCapabilityInspection>;
|
package/dist/events.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { HELYX_SERVICE_NAMES } from "@helyx/sdk";
|
|
2
|
+
import { inspectDiscordChannelAudit } from "./discord-channel-audit.js";
|
|
3
|
+
import { dangerousRolePermissionNames } from "./discord-role-safety.js";
|
|
2
4
|
import { safeError } from "@helyx/core";
|
|
3
5
|
import { ChannelType, Events, GatewayIntentBits, GuildMemberFlags, PermissionFlagsBits, RESTJSONErrorCodes, UserFlags, } from "discord.js";
|
|
4
6
|
import { toReplyOptions } from "./interactions.js";
|
|
@@ -1272,26 +1274,6 @@ export function reactionCacheKey(emojiIdentifier) {
|
|
|
1272
1274
|
const discordSnowflake = /^\d{17,20}$/u;
|
|
1273
1275
|
const moduleIdentifier = /^helyx\.[a-z0-9][a-z0-9-]*$/u;
|
|
1274
1276
|
const customEmojiIdentifier = /^(?:<a?:([A-Za-z0-9_]{2,32}):(\d{17,20})>|([A-Za-z0-9_]{2,32}):(\d{17,20}))$/u;
|
|
1275
|
-
const dangerousRolePermissionNames = new Set([
|
|
1276
|
-
"ADMINISTRATOR",
|
|
1277
|
-
"BAN_MEMBERS",
|
|
1278
|
-
"DEAFEN_MEMBERS",
|
|
1279
|
-
"KICK_MEMBERS",
|
|
1280
|
-
"MANAGE_CHANNELS",
|
|
1281
|
-
"MANAGE_EVENTS",
|
|
1282
|
-
"MANAGE_GUILD",
|
|
1283
|
-
"MANAGE_GUILD_EXPRESSIONS",
|
|
1284
|
-
"MANAGE_MESSAGES",
|
|
1285
|
-
"MANAGE_NICKNAMES",
|
|
1286
|
-
"MANAGE_ROLES",
|
|
1287
|
-
"MANAGE_THREADS",
|
|
1288
|
-
"MANAGE_WEBHOOKS",
|
|
1289
|
-
"MENTION_EVERYONE",
|
|
1290
|
-
"MODERATE_MEMBERS",
|
|
1291
|
-
"MOVE_MEMBERS",
|
|
1292
|
-
"MUTE_MEMBERS",
|
|
1293
|
-
"VIEW_AUDIT_LOG",
|
|
1294
|
-
]);
|
|
1295
1277
|
const directMessageAttemptTimeoutMs = 5_000;
|
|
1296
1278
|
const guildChannelAudienceInspectionLimit = 500;
|
|
1297
1279
|
export class DiscordMessageDeliveryService {
|
|
@@ -1696,6 +1678,9 @@ export class DiscordResourceInspection {
|
|
|
1696
1678
|
constructor(client) {
|
|
1697
1679
|
this.client = client;
|
|
1698
1680
|
}
|
|
1681
|
+
inspectChannelAudit(input) {
|
|
1682
|
+
return inspectDiscordChannelAudit(this.client, input);
|
|
1683
|
+
}
|
|
1699
1684
|
async inspectGuildCapabilities(input) {
|
|
1700
1685
|
validateSnowflake(input.guildId, "guild");
|
|
1701
1686
|
const guild = await this.client.guilds.fetch(input.guildId);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type GuildCommandBudgetClass, type GuildCommandHealth, type GuildCommandHistoryRecord, type GuildCommandRequestRecord, type GuildCommandRequestSource, type GuildCommandStateRecord, type GuildCommandVisibilityProfile } from "@helyx/database";
|
|
1
|
+
import { GuildCommandRepository, InstallationRepository, type GuildCommandBudgetClass, type GuildCommandHealth, type GuildCommandHistoryRecord, type GuildCommandRequestRecord, type GuildCommandRequestSource, type GuildCommandStateRecord, type GuildCommandVisibilityProfile } from "@helyx/database";
|
|
2
|
+
import { type GuildCommandRepository as MysqlGuildCommandRepository, type InstallationRepository as MysqlInstallationRepository } from "@helyx/database-mysql";
|
|
2
3
|
import type { ModuleDefinition } from "@helyx/sdk";
|
|
3
4
|
import type { Pool } from "pg";
|
|
4
5
|
import type { GuildCommandWorkerHealth } from "./guild-command-worker.js";
|
|
@@ -78,9 +79,13 @@ export interface GuildCommandReadHandler {
|
|
|
78
79
|
scheduledGuildCount: number;
|
|
79
80
|
}>;
|
|
80
81
|
}
|
|
82
|
+
type ReadCommandMethod = "getState" | "getActiveRequest" | "getCapacity" | "ensureState" | "schedule" | "recordStatusRefresh" | "changeVisibilityProfile" | "setMigrationHold" | "listHistory" | "getHealth";
|
|
83
|
+
export interface GuildCommandReadRepositories {
|
|
84
|
+
commands: Pick<GuildCommandRepository, ReadCommandMethod> & Pick<MysqlGuildCommandRepository, ReadCommandMethod>;
|
|
85
|
+
installations: Pick<InstallationRepository, "listActiveGuildIdsWithEnabledModule"> & Pick<MysqlInstallationRepository, "listActiveGuildIdsWithEnabledModule">;
|
|
86
|
+
}
|
|
81
87
|
export declare class GuildCommandReadService implements GuildCommandReadHandler {
|
|
82
88
|
#private;
|
|
83
|
-
private readonly database;
|
|
84
89
|
private readonly applicationId;
|
|
85
90
|
private readonly workerHealth?;
|
|
86
91
|
private readonly discordToken?;
|
|
@@ -88,7 +93,7 @@ export declare class GuildCommandReadService implements GuildCommandReadHandler
|
|
|
88
93
|
private readonly writeGuildIds;
|
|
89
94
|
private readonly writeAllGuilds;
|
|
90
95
|
private readonly modules;
|
|
91
|
-
constructor(
|
|
96
|
+
constructor(databaseOrRepositories: Pool | GuildCommandReadRepositories, applicationId: string, workerHealth?: (() => Promise<GuildCommandWorkerHealth> | undefined) | undefined, discordToken?: string | undefined, guildCommandWritesEnabled?: boolean, writeGuildIds?: readonly string[], writeAllGuilds?: boolean, modules?: readonly ModuleDefinition[]);
|
|
92
97
|
previewFirstTimeSetup(moduleIds: string[]): Promise<{
|
|
93
98
|
selectedModuleIds: string[];
|
|
94
99
|
rootCount: number;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { GuildCommandRepository, GuildCommandRevisionConflictError, InstallationRepository, } from "@helyx/database";
|
|
2
|
+
import { GuildCommandRevisionConflictError as MysqlGuildCommandRevisionConflictError, } from "@helyx/database-mysql";
|
|
2
3
|
import { isCustomerSelectableModule } from "@helyx/module-manifest";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
4
5
|
import { REST, Routes } from "discord.js";
|
|
5
6
|
import { guildCommandDefinitionHash, projectGuildCommands, } from "./guild-command-projection.js";
|
|
6
7
|
export class GuildCommandReadService {
|
|
7
|
-
database;
|
|
8
8
|
applicationId;
|
|
9
9
|
workerHealth;
|
|
10
10
|
discordToken;
|
|
@@ -13,8 +13,8 @@ export class GuildCommandReadService {
|
|
|
13
13
|
writeAllGuilds;
|
|
14
14
|
modules;
|
|
15
15
|
#repository;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
#installations;
|
|
17
|
+
constructor(databaseOrRepositories, applicationId, workerHealth, discordToken, guildCommandWritesEnabled = false, writeGuildIds = [], writeAllGuilds = false, modules = []) {
|
|
18
18
|
this.applicationId = applicationId;
|
|
19
19
|
this.workerHealth = workerHealth;
|
|
20
20
|
this.discordToken = discordToken;
|
|
@@ -22,7 +22,14 @@ export class GuildCommandReadService {
|
|
|
22
22
|
this.writeGuildIds = writeGuildIds;
|
|
23
23
|
this.writeAllGuilds = writeAllGuilds;
|
|
24
24
|
this.modules = modules;
|
|
25
|
-
|
|
25
|
+
const repositories = "commands" in databaseOrRepositories
|
|
26
|
+
? databaseOrRepositories
|
|
27
|
+
: {
|
|
28
|
+
commands: new GuildCommandRepository(databaseOrRepositories),
|
|
29
|
+
installations: new InstallationRepository(databaseOrRepositories),
|
|
30
|
+
};
|
|
31
|
+
this.#repository = repositories.commands;
|
|
32
|
+
this.#installations = repositories.installations;
|
|
26
33
|
}
|
|
27
34
|
previewFirstTimeSetup(moduleIds) {
|
|
28
35
|
const requested = new Set(moduleIds);
|
|
@@ -97,11 +104,10 @@ export class GuildCommandReadService {
|
|
|
97
104
|
return this.getStatus(input.guildId);
|
|
98
105
|
}
|
|
99
106
|
async reconcileModuleAvailability(input) {
|
|
100
|
-
const installations = new InstallationRepository(this.database);
|
|
101
107
|
let cursor;
|
|
102
108
|
let scheduledGuildCount = 0;
|
|
103
109
|
do {
|
|
104
|
-
const page = await installations.listActiveGuildIdsWithEnabledModule({
|
|
110
|
+
const page = await this.#installations.listActiveGuildIdsWithEnabledModule({
|
|
105
111
|
moduleId: input.moduleId,
|
|
106
112
|
...(cursor ? { cursor } : {}),
|
|
107
113
|
limit: 500,
|
|
@@ -152,7 +158,8 @@ export class GuildCommandReadService {
|
|
|
152
158
|
});
|
|
153
159
|
}
|
|
154
160
|
catch (error) {
|
|
155
|
-
if (!(error instanceof GuildCommandRevisionConflictError)
|
|
161
|
+
if (!(error instanceof GuildCommandRevisionConflictError) &&
|
|
162
|
+
!(error instanceof MysqlGuildCommandRevisionConflictError))
|
|
156
163
|
throw error;
|
|
157
164
|
}
|
|
158
165
|
return this.getStatus(input.guildId);
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { GuildCommandRepository, InstallationRepository, RuntimeStateRepository, type DatabasePool } from "@helyx/database";
|
|
2
|
-
type
|
|
3
|
-
type
|
|
4
|
-
type
|
|
5
|
-
|
|
2
|
+
import type { GuildCommandRepository as MysqlGuildCommandRepository, InstallationRepository as MysqlInstallationRepository, RuntimeStateRepository as MysqlRuntimeStateRepository } from "@helyx/database-mysql";
|
|
3
|
+
type ReleaseInstallationRepository = Pick<InstallationRepository, "listActiveGuilds"> & Pick<MysqlInstallationRepository, "listActiveGuilds">;
|
|
4
|
+
type ReleaseCommandRepository = Pick<GuildCommandRepository, "getState" | "schedule"> & Pick<MysqlGuildCommandRepository, "getState" | "schedule">;
|
|
5
|
+
type ReleaseRuntimeRepository = Pick<RuntimeStateRepository, "get" | "compareAndSet"> & Pick<MysqlRuntimeStateRepository, "get" | "compareAndSet">;
|
|
6
|
+
type ReleaseRepositories = {
|
|
7
|
+
installations: ReleaseInstallationRepository;
|
|
8
|
+
commands: ReleaseCommandRepository;
|
|
9
|
+
runtime: ReleaseRuntimeRepository;
|
|
10
|
+
};
|
|
11
|
+
type ReleasePersistence = ({
|
|
6
12
|
database: DatabasePool;
|
|
13
|
+
} & Partial<ReleaseRepositories>) | ({
|
|
14
|
+
database?: never;
|
|
15
|
+
} & ReleaseRepositories);
|
|
16
|
+
export declare function scheduleGuildCommandRelease(input: {
|
|
7
17
|
applicationId: string;
|
|
8
18
|
releaseId: string;
|
|
9
19
|
staggerMs?: number;
|
|
10
|
-
|
|
11
|
-
commands?: ReleaseCommandRepository;
|
|
12
|
-
runtime?: ReleaseRuntimeRepository;
|
|
13
|
-
}): Promise<{
|
|
20
|
+
} & ReleasePersistence): Promise<{
|
|
14
21
|
scheduled: number;
|
|
15
22
|
skippedIncomplete: number;
|
|
16
23
|
replay: boolean;
|
|
@@ -6,9 +6,16 @@ export async function scheduleGuildCommandRelease(input) {
|
|
|
6
6
|
if (!RELEASE_ID.test(input.releaseId))
|
|
7
7
|
throw new Error("Guild command release ID is invalid");
|
|
8
8
|
const markerKey = `guild-commands:release:${input.applicationId}:${input.releaseId}`;
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
9
|
+
const database = input.database;
|
|
10
|
+
const installations = input.installations ??
|
|
11
|
+
(database ? new InstallationRepository(database) : undefined);
|
|
12
|
+
const commands = input.commands ??
|
|
13
|
+
(database ? new GuildCommandRepository(database) : undefined);
|
|
14
|
+
const runtime = input.runtime ??
|
|
15
|
+
(database ? new RuntimeStateRepository(database) : undefined);
|
|
16
|
+
if (!installations || !commands || !runtime) {
|
|
17
|
+
throw new Error("Guild command release requires a database or all three repositories");
|
|
18
|
+
}
|
|
12
19
|
if (await runtime.get(markerKey))
|
|
13
20
|
return { scheduled: 0, skippedIncomplete: 0, replay: true };
|
|
14
21
|
const guilds = await installations.listActiveGuilds();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { GuildCommandRepository, type GuildCommandHealth, type GuildCommandRequestRecord } from "@helyx/database";
|
|
1
|
+
import { GuildCommandRepository, InstallationRepository, ModuleAvailabilityRepository, type GuildCommandHealth, type GuildCommandRequestRecord } from "@helyx/database";
|
|
2
|
+
import { type GuildCommandRepository as MysqlGuildCommandRepository, type InstallationRepository as MysqlInstallationRepository, type ModuleAvailabilityRepository as MysqlModuleAvailabilityRepository } from "@helyx/database-mysql";
|
|
2
3
|
import type { Logger, ModuleDefinition } from "@helyx/sdk";
|
|
3
4
|
import type { Pool } from "pg";
|
|
4
5
|
import { type GuildCommandDiff, type GuildCommandProjection } from "./guild-command-projection.js";
|
|
@@ -9,10 +10,22 @@ export interface GuildCommandWorkerHealth extends GuildCommandHealth {
|
|
|
9
10
|
workerRunning: boolean;
|
|
10
11
|
activeCount: number;
|
|
11
12
|
}
|
|
13
|
+
type WorkerCommandRepository = Pick<GuildCommandRepository, "claimDue" | "getHealth" | "heartbeat" | "getState" | "completeDryRun" | "defer" | "markBlocked" | "markFailed" | "completeLive" | "isLeaseCurrent" | "reserveCreations" | "settleCreation"> & Pick<MysqlGuildCommandRepository, "claimDue" | "getHealth" | "heartbeat" | "getState" | "completeDryRun" | "defer" | "markBlocked" | "markFailed" | "completeLive" | "isLeaseCurrent" | "reserveCreations" | "settleCreation">;
|
|
14
|
+
export interface GuildCommandWorkerRepositories {
|
|
15
|
+
commands: WorkerCommandRepository;
|
|
16
|
+
installations: Pick<InstallationRepository, "listEnabledModules"> & Pick<MysqlInstallationRepository, "listEnabledModules">;
|
|
17
|
+
availability: Pick<ModuleAvailabilityRepository, "list"> & Pick<MysqlModuleAvailabilityRepository, "list">;
|
|
18
|
+
}
|
|
19
|
+
type WorkerPersistence = {
|
|
20
|
+
database: Pool;
|
|
21
|
+
repositories?: never;
|
|
22
|
+
} | {
|
|
23
|
+
database?: never;
|
|
24
|
+
repositories: GuildCommandWorkerRepositories;
|
|
25
|
+
};
|
|
12
26
|
export declare class GuildCommandWorker {
|
|
13
27
|
#private;
|
|
14
28
|
constructor(input: {
|
|
15
|
-
database: Pool;
|
|
16
29
|
modules: readonly ModuleDefinition[];
|
|
17
30
|
logger: Logger;
|
|
18
31
|
workerId?: string;
|
|
@@ -24,13 +37,13 @@ export declare class GuildCommandWorker {
|
|
|
24
37
|
claimLimit?: number;
|
|
25
38
|
concurrency?: number;
|
|
26
39
|
stopTimeoutMs?: number;
|
|
27
|
-
});
|
|
40
|
+
} & WorkerPersistence);
|
|
28
41
|
start(): void;
|
|
29
42
|
runOnce(): Promise<void>;
|
|
30
43
|
stop(): Promise<void>;
|
|
31
44
|
getHealth(): Promise<GuildCommandWorkerHealth>;
|
|
32
45
|
}
|
|
33
|
-
type LiveRepository = Pick<GuildCommandRepository, "completeLive" | "isLeaseCurrent" | "reserveCreations" | "settleCreation">;
|
|
46
|
+
type LiveRepository = Pick<GuildCommandRepository, "completeLive" | "isLeaseCurrent" | "reserveCreations" | "settleCreation"> & Pick<MysqlGuildCommandRepository, "completeLive" | "isLeaseCurrent" | "reserveCreations" | "settleCreation">;
|
|
34
47
|
export declare function reconcileLiveGuildCommands(input: {
|
|
35
48
|
repository: LiveRepository;
|
|
36
49
|
discord: Pick<GuildCommandDiscordClient, "observe" | "replace">;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GuildCommandBudgetExceededError, GuildCommandRepository, InstallationRepository, ModuleAvailabilityRepository, } from "@helyx/database";
|
|
2
|
+
import { GuildCommandBudgetExceededError as MysqlGuildCommandBudgetExceededError, } from "@helyx/database-mysql";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { diffDryRunGuildCommands, diffGuildCommands, projectGuildCommands, } from "./guild-command-projection.js";
|
|
4
5
|
const DEFAULT_POLL_INTERVAL_MS = 1_000;
|
|
@@ -8,7 +9,8 @@ const DEFAULT_CONCURRENCY = 2;
|
|
|
8
9
|
const DEFAULT_STOP_TIMEOUT_MS = 5_000;
|
|
9
10
|
export class GuildCommandWorker {
|
|
10
11
|
#repository;
|
|
11
|
-
#
|
|
12
|
+
#installations;
|
|
13
|
+
#availability;
|
|
12
14
|
#modules;
|
|
13
15
|
#logger;
|
|
14
16
|
#workerId;
|
|
@@ -28,8 +30,16 @@ export class GuildCommandWorker {
|
|
|
28
30
|
constructor(input) {
|
|
29
31
|
if (input.fencingToken < 1n)
|
|
30
32
|
throw new Error("Guild command worker fencing token must be positive");
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
const repositories = input.database
|
|
34
|
+
? {
|
|
35
|
+
commands: new GuildCommandRepository(input.database),
|
|
36
|
+
installations: new InstallationRepository(input.database),
|
|
37
|
+
availability: new ModuleAvailabilityRepository(input.database),
|
|
38
|
+
}
|
|
39
|
+
: input.repositories;
|
|
40
|
+
this.#repository = repositories.commands;
|
|
41
|
+
this.#installations = repositories.installations;
|
|
42
|
+
this.#availability = repositories.availability;
|
|
33
43
|
this.#modules = input.modules;
|
|
34
44
|
this.#logger = input.logger;
|
|
35
45
|
this.#workerId = input.workerId ?? `guild-command:${randomUUID()}`;
|
|
@@ -145,8 +155,8 @@ export class GuildCommandWorker {
|
|
|
145
155
|
heartbeat.unref();
|
|
146
156
|
try {
|
|
147
157
|
const [enabled, availability, state] = await Promise.all([
|
|
148
|
-
|
|
149
|
-
|
|
158
|
+
this.#installations.listEnabledModules(request.guildId),
|
|
159
|
+
this.#availability.list(),
|
|
150
160
|
this.#repository.getState(request.applicationId, request.guildId),
|
|
151
161
|
]);
|
|
152
162
|
if (!state || state.desiredRevision !== request.requestedRevision) {
|
|
@@ -310,7 +320,8 @@ export async function reconcileLiveGuildCommands(input) {
|
|
|
310
320
|
})).ledgerId;
|
|
311
321
|
}
|
|
312
322
|
catch (error) {
|
|
313
|
-
if (error instanceof GuildCommandBudgetExceededError
|
|
323
|
+
if (error instanceof GuildCommandBudgetExceededError ||
|
|
324
|
+
error instanceof MysqlGuildCommandBudgetExceededError)
|
|
314
325
|
throw new GuildCommandLiveError("creation_budget_exhausted");
|
|
315
326
|
throw error;
|
|
316
327
|
}
|
package/dist/guilds.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type DatabasePool, type GuildShardOwnership } from "@helyx/database";
|
|
1
|
+
import { InstallationRepository, type DatabasePool, type GuildShardOwnership } from "@helyx/database";
|
|
2
|
+
import type { MysqlPool } from "@helyx/database-mysql";
|
|
2
3
|
import type { ModuleDefinition } from "@helyx/sdk";
|
|
3
4
|
import { type Client } from "discord.js";
|
|
4
5
|
import type { InteractionLogger } from "./interactions.js";
|
|
@@ -20,7 +21,27 @@ export interface GuildTrackingPreparation {
|
|
|
20
21
|
(readyClient: Client<true>): Promise<void>;
|
|
21
22
|
reconcile(reason: "ready" | "resume" | "periodic"): Promise<void>;
|
|
22
23
|
health(): GuildReconciliationHealth;
|
|
23
|
-
stop(): void
|
|
24
|
+
stop(): Promise<void>;
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
+
interface GuildObservation {
|
|
27
|
+
guildId: string;
|
|
28
|
+
ownerUserId: string;
|
|
29
|
+
ownerUsername?: string;
|
|
30
|
+
name: string;
|
|
31
|
+
modules: readonly ModuleDefinition[];
|
|
32
|
+
enabledByDefaultModuleIds: ReadonlySet<string>;
|
|
33
|
+
firstObservation: boolean;
|
|
34
|
+
applicationId?: string;
|
|
35
|
+
ownership?: GuildShardOwnership;
|
|
36
|
+
continueObservation?: () => boolean;
|
|
37
|
+
}
|
|
38
|
+
type ReconciliationRepository = Pick<InstallationRepository, "listActiveGuildIdsForOwnership" | "adoptGuildsForReconciliation" | "markGuildsRemovedForReconciliation">;
|
|
39
|
+
export interface GuildTrackingPersistence extends ReconciliationRepository {
|
|
40
|
+
readonly engine: "postgresql" | "mysql";
|
|
41
|
+
syncGuild(input: GuildObservation): Promise<void>;
|
|
42
|
+
markGuildRemoved(guildId: string, ownership: GuildShardOwnership | undefined, applicationId: string | undefined): Promise<boolean>;
|
|
43
|
+
}
|
|
44
|
+
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 {};
|
|
26
47
|
//# sourceMappingURL=guilds.d.ts.map
|