@helyx/bot 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- 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.js +2 -0
- package/dist/cloud-control-dispatcher.js +22 -0
- package/dist/command-registration-policy.js +1 -0
- package/dist/control-errors.d.ts +8 -0
- package/dist/control-errors.js +24 -0
- package/dist/control-server-schemas.d.ts +2 -2
- package/dist/control-server.d.ts +4 -1
- package/dist/control-server.js +18 -25
- package/dist/diagnostic-command.d.ts +76 -0
- package/dist/diagnostic-command.js +1962 -0
- 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-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/hosted-server-moderation.d.ts +2 -2
- package/dist/index.js +30 -1
- package/dist/shard-contracts.d.ts +87 -0
- package/dist/shard-contracts.js +39 -0
- package/dist/shard-router.d.ts +2 -0
- package/dist/shard-router.js +12 -0
- package/dist/shard-supervisor.js +7 -0
- package/dist/shard-worker.d.ts +1 -0
- package/dist/shard-worker.js +38 -1
- package/documentation/reference/environment.md +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Client } from "discord.js";
|
|
2
|
+
import type { ChannelAuditInput, ChannelAuditModuleId, ChannelAuditReport } from "@helyx/sdk";
|
|
3
|
+
/** Fresh guild roles/channels and the bot member only. Never reads messages or enumerates members. */
|
|
4
|
+
export declare function inspectDiscordChannelAudit(client: Client, input: {
|
|
5
|
+
guildId: string;
|
|
6
|
+
moduleId: ChannelAuditModuleId;
|
|
7
|
+
assumptions: ChannelAuditInput;
|
|
8
|
+
}): Promise<ChannelAuditReport>;
|
|
9
|
+
//# sourceMappingURL=discord-channel-audit.d.ts.map
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { PermissionFlagsBits } from "discord.js";
|
|
2
|
+
import { channelAuditInputSchema, channelAuditReportSchema, } from "@helyx/control-protocol/channel-audit";
|
|
3
|
+
import { buildChannelAuditReport, } from "./channel-audit-permissions.js";
|
|
4
|
+
import { dangerousRolePermissions } from "./discord-role-safety.js";
|
|
5
|
+
async function readBefore(read, deadline) {
|
|
6
|
+
const remaining = deadline - Date.now();
|
|
7
|
+
if (remaining <= 0)
|
|
8
|
+
throw new Error("Channel audit read deadline reached");
|
|
9
|
+
let timer;
|
|
10
|
+
try {
|
|
11
|
+
return await Promise.race([
|
|
12
|
+
read(),
|
|
13
|
+
new Promise((_, reject) => {
|
|
14
|
+
timer = setTimeout(() => reject(new Error("Channel audit read deadline reached")), remaining);
|
|
15
|
+
timer.unref();
|
|
16
|
+
}),
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
if (timer)
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Fresh guild roles/channels and the bot member only. Never reads messages or enumerates members. */
|
|
25
|
+
export async function inspectDiscordChannelAudit(client, input) {
|
|
26
|
+
const assumptions = channelAuditInputSchema.parse(input.assumptions);
|
|
27
|
+
const deadline = Date.now() + 8_500;
|
|
28
|
+
const snapshot = {
|
|
29
|
+
guildId: input.guildId,
|
|
30
|
+
roles: [],
|
|
31
|
+
rolesComplete: false,
|
|
32
|
+
channels: [],
|
|
33
|
+
fetchedChannels: 0,
|
|
34
|
+
totalChannelsKnown: false,
|
|
35
|
+
fetchLimitations: [],
|
|
36
|
+
};
|
|
37
|
+
const guild = await readBefore(() => client.guilds.fetch(input.guildId), deadline).catch(() => null);
|
|
38
|
+
if (!guild) {
|
|
39
|
+
snapshot.fetchLimitations.push("The current Discord server could not be fetched; channel and role coverage is incomplete.");
|
|
40
|
+
return channelAuditReportSchema.parse(buildChannelAuditReport(snapshot, input.moduleId, assumptions, new Date().toISOString()));
|
|
41
|
+
}
|
|
42
|
+
if (guild.id !== input.guildId)
|
|
43
|
+
throw new Error("Discord returned a different server for this inspection");
|
|
44
|
+
const [rolesResult, channelsResult, memberResult] = await Promise.allSettled([
|
|
45
|
+
readBefore(() => guild.roles.fetch(), deadline),
|
|
46
|
+
readBefore(() => guild.channels.fetch(), deadline),
|
|
47
|
+
readBefore(() => guild.members.fetchMe({ force: true }), deadline),
|
|
48
|
+
]);
|
|
49
|
+
const me = memberResult.status === "fulfilled" ? memberResult.value : null;
|
|
50
|
+
if (me && me.guild.id !== guild.id)
|
|
51
|
+
throw new Error("Discord bot member belongs to a different server");
|
|
52
|
+
if (!me)
|
|
53
|
+
snapshot.fetchLimitations.push("Helyx's current server member could not be fetched; bot publishing and assignment checks may be unknown.");
|
|
54
|
+
if (rolesResult.status === "fulfilled") {
|
|
55
|
+
snapshot.rolesComplete = true;
|
|
56
|
+
const canManage = me?.permissions.has(PermissionFlagsBits.ManageRoles) ?? false;
|
|
57
|
+
snapshot.roles = [...rolesResult.value.values()].map((role) => {
|
|
58
|
+
if (role.guild.id !== guild.id)
|
|
59
|
+
throw new Error("Discord role belongs to a different server");
|
|
60
|
+
const reason = role.id === guild.id
|
|
61
|
+
? "The everyone role cannot be assigned."
|
|
62
|
+
: role.managed
|
|
63
|
+
? "This role is managed by Discord or an integration and cannot be assigned by Helyx."
|
|
64
|
+
: !me
|
|
65
|
+
? undefined
|
|
66
|
+
: !canManage
|
|
67
|
+
? "Helyx lacks Manage Roles in this server."
|
|
68
|
+
: role.comparePositionTo(me.roles.highest) >= 0
|
|
69
|
+
? "The selected role is at or above Helyx's highest role."
|
|
70
|
+
: undefined;
|
|
71
|
+
const dangerous = dangerousRolePermissions(role.permissions.bitfield);
|
|
72
|
+
return {
|
|
73
|
+
id: role.id,
|
|
74
|
+
permissions: role.permissions.bitfield,
|
|
75
|
+
managed: role.managed,
|
|
76
|
+
assignable: reason ? false : me ? true : null,
|
|
77
|
+
...(reason ? { assignmentReason: reason } : {}),
|
|
78
|
+
...(dangerous.length
|
|
79
|
+
? {
|
|
80
|
+
assignmentPolicyReason: `Helyx's role-safety policy blocks managing roles with ${dangerous.join(", ")}. Discord hierarchy alone does not make this role eligible.`,
|
|
81
|
+
}
|
|
82
|
+
: {}),
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else
|
|
87
|
+
snapshot.fetchLimitations.push("The current Discord role list could not be fetched; missing roles are unknown, not confirmed deleted.");
|
|
88
|
+
const observedIds = new Set();
|
|
89
|
+
if (channelsResult.status === "fulfilled") {
|
|
90
|
+
snapshot.fetchedChannels = channelsResult.value.size;
|
|
91
|
+
for (const channel of channelsResult.value.values()) {
|
|
92
|
+
if (!channel)
|
|
93
|
+
continue;
|
|
94
|
+
if (channel.guild.id !== guild.id)
|
|
95
|
+
throw new Error("Discord channel belongs to a different server");
|
|
96
|
+
observedIds.add(channel.id);
|
|
97
|
+
const flags = "flags" in channel ? BigInt(channel.flags.bitfield) : 0n;
|
|
98
|
+
const obfuscated = (flags & 131072n) !== 0n || channel.name === "___hidden___";
|
|
99
|
+
const overwrites = "permissionOverwrites" in channel
|
|
100
|
+
? [...channel.permissionOverwrites.cache.values()].map((overwrite) => ({
|
|
101
|
+
id: overwrite.id,
|
|
102
|
+
type: overwrite.type,
|
|
103
|
+
allow: overwrite.allow.bitfield,
|
|
104
|
+
deny: overwrite.deny.bitfield,
|
|
105
|
+
}))
|
|
106
|
+
: [];
|
|
107
|
+
const botPermissions = !obfuscated && me
|
|
108
|
+
? (channel.permissionsFor(me)?.bitfield ?? null)
|
|
109
|
+
: null;
|
|
110
|
+
snapshot.channels.push({
|
|
111
|
+
id: channel.id,
|
|
112
|
+
name: obfuscated ? "Unknown channel" : channel.name,
|
|
113
|
+
type: channel.type,
|
|
114
|
+
obfuscated,
|
|
115
|
+
overwrites: obfuscated ? [] : overwrites,
|
|
116
|
+
botPermissions,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
snapshot.totalChannelsKnown =
|
|
120
|
+
me?.permissions.has(PermissionFlagsBits.Administrator) ?? false;
|
|
121
|
+
}
|
|
122
|
+
else
|
|
123
|
+
snapshot.fetchLimitations.push("Discord's channel list could not be fetched; cached metadata is not used as fresh permission evidence.");
|
|
124
|
+
// Bulk lists can omit invisible channels. Resolve only explicitly selected
|
|
125
|
+
// missing IDs; a permission failure or deadline remains unknown.
|
|
126
|
+
const selected = new Set([
|
|
127
|
+
assumptions.entryChannelId,
|
|
128
|
+
...assumptions.allowedChannelIds,
|
|
129
|
+
...(assumptions.reviewChannelId ? [assumptions.reviewChannelId] : []),
|
|
130
|
+
]);
|
|
131
|
+
snapshot.invalidSelectedChannels = [];
|
|
132
|
+
for (const id of selected) {
|
|
133
|
+
if (observedIds.has(id))
|
|
134
|
+
continue;
|
|
135
|
+
try {
|
|
136
|
+
const channel = await readBefore(() => guild.channels.fetch(id, { force: true, cache: false }), deadline);
|
|
137
|
+
if (!channel)
|
|
138
|
+
continue;
|
|
139
|
+
if (channel.guild.id !== guild.id) {
|
|
140
|
+
snapshot.invalidSelectedChannels.push({ id, reason: "wrong_guild" });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
observedIds.add(id);
|
|
144
|
+
const obfuscated = (BigInt("flags" in channel ? channel.flags.bitfield : 0) & 131072n) !==
|
|
145
|
+
0n || channel.name === "___hidden___";
|
|
146
|
+
snapshot.channels.push({
|
|
147
|
+
id,
|
|
148
|
+
name: obfuscated ? "Unknown channel" : channel.name,
|
|
149
|
+
type: channel.type,
|
|
150
|
+
obfuscated,
|
|
151
|
+
overwrites: !obfuscated && "permissionOverwrites" in channel
|
|
152
|
+
? [...channel.permissionOverwrites.cache.values()].map((overwrite) => ({
|
|
153
|
+
id: overwrite.id,
|
|
154
|
+
type: overwrite.type,
|
|
155
|
+
allow: overwrite.allow.bitfield,
|
|
156
|
+
deny: overwrite.deny.bitfield,
|
|
157
|
+
}))
|
|
158
|
+
: [],
|
|
159
|
+
botPermissions: !obfuscated && me
|
|
160
|
+
? (channel.permissionsFor(me)?.bitfield ?? null)
|
|
161
|
+
: null,
|
|
162
|
+
});
|
|
163
|
+
snapshot.fetchedChannels += 1;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const code = error && typeof error === "object" && "code" in error
|
|
167
|
+
? error.code
|
|
168
|
+
: undefined;
|
|
169
|
+
if (code === 10003 || code === "GuildChannelUnowned")
|
|
170
|
+
snapshot.invalidSelectedChannels.push({
|
|
171
|
+
id,
|
|
172
|
+
reason: code === 10003 ? "not_found" : "wrong_guild",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Cache-only entries establish known inventory, never current permissions or deletion.
|
|
177
|
+
for (const channel of guild.channels.cache.values()) {
|
|
178
|
+
if (observedIds.has(channel.id))
|
|
179
|
+
continue;
|
|
180
|
+
snapshot.channels.push(unknownChannel(channel));
|
|
181
|
+
snapshot.totalChannelsKnown = false;
|
|
182
|
+
}
|
|
183
|
+
return channelAuditReportSchema.parse(buildChannelAuditReport(snapshot, input.moduleId, assumptions, new Date().toISOString()));
|
|
184
|
+
}
|
|
185
|
+
function unknownChannel(channel) {
|
|
186
|
+
return {
|
|
187
|
+
id: channel.id,
|
|
188
|
+
name: "Unknown channel",
|
|
189
|
+
type: channel.type,
|
|
190
|
+
obfuscated: true,
|
|
191
|
+
overwrites: [],
|
|
192
|
+
botPermissions: null,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=discord-channel-audit.js.map
|
|
@@ -163,8 +163,8 @@ export declare class DiscordGuildHealthInspection implements DiscordGuildHealthI
|
|
|
163
163
|
resourceValidationValid: boolean | null;
|
|
164
164
|
activationValid: boolean | null;
|
|
165
165
|
catalogueMatches: boolean;
|
|
166
|
-
requiredCapabilities: readonly ("
|
|
167
|
-
missingCapabilities: ("
|
|
166
|
+
requiredCapabilities: readonly ("message-content" | "guild-members")[];
|
|
167
|
+
missingCapabilities: ("message-content" | "guild-members")[];
|
|
168
168
|
missingPermissions: string[];
|
|
169
169
|
healthStatus: "healthy" | "degraded" | "unhealthy";
|
|
170
170
|
healthMessage: string | null;
|
|
@@ -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);
|
|
@@ -11,8 +11,8 @@ declare const resultSchema: z.ZodObject<{
|
|
|
11
11
|
}>;
|
|
12
12
|
outcome: z.ZodEnum<{
|
|
13
13
|
completed: "completed";
|
|
14
|
-
failed: "failed";
|
|
15
14
|
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: "completed" | "
|
|
48
|
+
outcome: "completed" | "requires_review" | "failed" | "queued" | "not_currently_installed" | "accepted_for_retry" | "already_blacklisted";
|
|
49
49
|
paidSlotsReleased: number;
|
|
50
50
|
}>;
|
|
51
51
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseBotEnvironment } from "@helyx/config";
|
|
2
2
|
import { StatusHeartbeatReporter } from "@helyx/status-client";
|
|
3
3
|
import { safeError, ServiceRegistry } from "@helyx/core";
|
|
4
|
-
import { checkDatabaseConnection, createDatabasePool, EckoOperationsRepository, inspectConfiguredModuleMigrations, inspectCoreMigrations, inspectCreatorOnboardingMigrations, runCreatorOnboardingMigrations, PremiumAllocationRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
|
|
4
|
+
import { checkDatabaseConnection, createDatabasePool, EckoOperationsRepository, InstallationRepository, inspectConfiguredModuleMigrations, inspectCoreMigrations, inspectCreatorOnboardingMigrations, runCreatorOnboardingMigrations, PremiumAllocationRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
|
|
5
5
|
import { createRuntime } from "@helyx/framework";
|
|
6
6
|
import { assertRepositoryEngineSupported } from "@helyx/database/adapters";
|
|
7
7
|
import { createPlatformServices, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, PlatformMemberRoleGrantOwnershipService, PostgresMemberRoleGrantOwnershipStore, } from "@helyx/platform";
|
|
@@ -20,6 +20,7 @@ import { assertConfiguredModuleDatabaseCompatibility, loadConfiguredModulePackag
|
|
|
20
20
|
import { resolveModulePackageSpecifiers } from "./module-configuration.js";
|
|
21
21
|
import { createPermissionsModule } from "./permissions-module.js";
|
|
22
22
|
import { createManagementModule } from "./management-module.js";
|
|
23
|
+
import { createDiagnosticModule, createMysqlDiagnosticLock, createPostgresDiagnosticLock, } from "./diagnostic-command.js";
|
|
23
24
|
import { DiscordMessageDeliveryService, DiscordMemberRoleService, DiscordVerificationMemberService, DiscordResourceInspection, installModuleEvents, } from "./events.js";
|
|
24
25
|
import { DiscordModerationMemberEnforcementAdapter } from "./discord-member-enforcement-service.js";
|
|
25
26
|
import { DiscordModuleLoggingService } from "./module-logging.js";
|
|
@@ -303,6 +304,30 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
303
304
|
});
|
|
304
305
|
const permissionsModule = createPermissionsModule(featureModules);
|
|
305
306
|
const managementModule = createManagementModule(featureModules, guildResources);
|
|
307
|
+
const diagnosticInstallations = mysql
|
|
308
|
+
? new (await import("@helyx/database-mysql")).InstallationRepository(mysql.pool)
|
|
309
|
+
: new InstallationRepository(database);
|
|
310
|
+
const diagnosticModule = createDiagnosticModule({
|
|
311
|
+
client,
|
|
312
|
+
modules: featureModules,
|
|
313
|
+
runExclusive: mysql
|
|
314
|
+
? createMysqlDiagnosticLock(mysql.pool)
|
|
315
|
+
: createPostgresDiagnosticLock(database),
|
|
316
|
+
assertGuildAvailable: async (guildId) => {
|
|
317
|
+
if (shuttingDown ||
|
|
318
|
+
!(await diagnosticInstallations.getActiveGuild(guildId)))
|
|
319
|
+
throw new Error("The server installation is not available");
|
|
320
|
+
},
|
|
321
|
+
listEnabledModules: (guildId) => diagnosticInstallations.listEnabledModules(guildId),
|
|
322
|
+
commandStatus: {
|
|
323
|
+
get: (guildId) => {
|
|
324
|
+
return guildCommandReads.getStatus(guildId);
|
|
325
|
+
},
|
|
326
|
+
refresh: (input) => {
|
|
327
|
+
return guildCommandReads.refreshStatus(input);
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
});
|
|
306
331
|
const cloudManagementModule = environment.HELYX_CLOUD_CONNECT_ENABLED
|
|
307
332
|
? createCloudManagementModule({
|
|
308
333
|
connector: cloudConnector,
|
|
@@ -327,6 +352,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
327
352
|
: undefined;
|
|
328
353
|
const publicModules = [
|
|
329
354
|
managementModule,
|
|
355
|
+
diagnosticModule,
|
|
330
356
|
permissionsModule,
|
|
331
357
|
supportModule,
|
|
332
358
|
privacyModule,
|
|
@@ -405,6 +431,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
405
431
|
...(deployedCommit ? { deploymentReference: deployedCommit } : {}),
|
|
406
432
|
coreFunctions: [
|
|
407
433
|
managementModule,
|
|
434
|
+
diagnosticModule,
|
|
408
435
|
permissionsModule,
|
|
409
436
|
supportModule,
|
|
410
437
|
privacyModule,
|
|
@@ -534,6 +561,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
534
561
|
});
|
|
535
562
|
const enabledCoreModuleIds = new Set([
|
|
536
563
|
managementModule.manifest.id,
|
|
564
|
+
diagnosticModule.manifest.id,
|
|
537
565
|
permissionsModule.manifest.id,
|
|
538
566
|
supportModule.manifest.id,
|
|
539
567
|
...(premiumActivationModule ? [premiumActivationModule.manifest.id] : []),
|
|
@@ -555,6 +583,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
555
583
|
synchroniseInternalGuild: false,
|
|
556
584
|
prepare: prepareGuilds,
|
|
557
585
|
selfAuthorizingGuildModuleIds: new Set([
|
|
586
|
+
diagnosticModule.manifest.id,
|
|
558
587
|
supportModule.manifest.id,
|
|
559
588
|
...(premiumActivationModule
|
|
560
589
|
? [premiumActivationModule.manifest.id]
|
|
@@ -286,6 +286,35 @@ export declare const shardControlOperationSchema: z.ZodDiscriminatedUnion<[z.Zod
|
|
|
286
286
|
moduleIds: z.ZodArray<z.ZodString>;
|
|
287
287
|
expectedRevision: z.ZodString;
|
|
288
288
|
idempotencyKey: z.ZodString;
|
|
289
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
290
|
+
actorUserId: z.ZodString;
|
|
291
|
+
correlationId: z.ZodString;
|
|
292
|
+
kind: z.ZodLiteral<"guild.module.channel-audit.config.read">;
|
|
293
|
+
guildId: z.ZodString;
|
|
294
|
+
moduleId: z.ZodEnum<{
|
|
295
|
+
"helyx.account-gate": "helyx.account-gate";
|
|
296
|
+
"helyx.verification": "helyx.verification";
|
|
297
|
+
}>;
|
|
298
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
299
|
+
actorUserId: z.ZodString;
|
|
300
|
+
correlationId: z.ZodString;
|
|
301
|
+
kind: z.ZodLiteral<"guild.module.channel-audit.check">;
|
|
302
|
+
guildId: z.ZodString;
|
|
303
|
+
moduleId: z.ZodEnum<{
|
|
304
|
+
"helyx.account-gate": "helyx.account-gate";
|
|
305
|
+
"helyx.verification": "helyx.verification";
|
|
306
|
+
}>;
|
|
307
|
+
assumptions: z.ZodObject<{
|
|
308
|
+
entryChannelId: z.ZodString;
|
|
309
|
+
beforeRoleIds: z.ZodArray<z.ZodString>;
|
|
310
|
+
afterRoleId: z.ZodString;
|
|
311
|
+
entrance: z.ZodEnum<{
|
|
312
|
+
public: "public";
|
|
313
|
+
restricted: "restricted";
|
|
314
|
+
}>;
|
|
315
|
+
allowedChannelIds: z.ZodArray<z.ZodString>;
|
|
316
|
+
reviewChannelId: z.ZodOptional<z.ZodString>;
|
|
317
|
+
}, z.core.$strict>;
|
|
289
318
|
}, z.core.$strict>, z.ZodObject<{
|
|
290
319
|
actorUserId: z.ZodString;
|
|
291
320
|
correlationId: z.ZodString;
|
|
@@ -742,6 +771,35 @@ export declare const shardControlRequestSchema: z.ZodObject<{
|
|
|
742
771
|
moduleIds: z.ZodArray<z.ZodString>;
|
|
743
772
|
expectedRevision: z.ZodString;
|
|
744
773
|
idempotencyKey: z.ZodString;
|
|
774
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
775
|
+
actorUserId: z.ZodString;
|
|
776
|
+
correlationId: z.ZodString;
|
|
777
|
+
kind: z.ZodLiteral<"guild.module.channel-audit.config.read">;
|
|
778
|
+
guildId: z.ZodString;
|
|
779
|
+
moduleId: z.ZodEnum<{
|
|
780
|
+
"helyx.account-gate": "helyx.account-gate";
|
|
781
|
+
"helyx.verification": "helyx.verification";
|
|
782
|
+
}>;
|
|
783
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
784
|
+
actorUserId: z.ZodString;
|
|
785
|
+
correlationId: z.ZodString;
|
|
786
|
+
kind: z.ZodLiteral<"guild.module.channel-audit.check">;
|
|
787
|
+
guildId: z.ZodString;
|
|
788
|
+
moduleId: z.ZodEnum<{
|
|
789
|
+
"helyx.account-gate": "helyx.account-gate";
|
|
790
|
+
"helyx.verification": "helyx.verification";
|
|
791
|
+
}>;
|
|
792
|
+
assumptions: z.ZodObject<{
|
|
793
|
+
entryChannelId: z.ZodString;
|
|
794
|
+
beforeRoleIds: z.ZodArray<z.ZodString>;
|
|
795
|
+
afterRoleId: z.ZodString;
|
|
796
|
+
entrance: z.ZodEnum<{
|
|
797
|
+
public: "public";
|
|
798
|
+
restricted: "restricted";
|
|
799
|
+
}>;
|
|
800
|
+
allowedChannelIds: z.ZodArray<z.ZodString>;
|
|
801
|
+
reviewChannelId: z.ZodOptional<z.ZodString>;
|
|
802
|
+
}, z.core.$strict>;
|
|
745
803
|
}, z.core.$strict>, z.ZodObject<{
|
|
746
804
|
actorUserId: z.ZodString;
|
|
747
805
|
correlationId: z.ZodString;
|
|
@@ -1703,6 +1761,35 @@ export declare const supervisorMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
1703
1761
|
moduleIds: z.ZodArray<z.ZodString>;
|
|
1704
1762
|
expectedRevision: z.ZodString;
|
|
1705
1763
|
idempotencyKey: z.ZodString;
|
|
1764
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1765
|
+
actorUserId: z.ZodString;
|
|
1766
|
+
correlationId: z.ZodString;
|
|
1767
|
+
kind: z.ZodLiteral<"guild.module.channel-audit.config.read">;
|
|
1768
|
+
guildId: z.ZodString;
|
|
1769
|
+
moduleId: z.ZodEnum<{
|
|
1770
|
+
"helyx.account-gate": "helyx.account-gate";
|
|
1771
|
+
"helyx.verification": "helyx.verification";
|
|
1772
|
+
}>;
|
|
1773
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1774
|
+
actorUserId: z.ZodString;
|
|
1775
|
+
correlationId: z.ZodString;
|
|
1776
|
+
kind: z.ZodLiteral<"guild.module.channel-audit.check">;
|
|
1777
|
+
guildId: z.ZodString;
|
|
1778
|
+
moduleId: z.ZodEnum<{
|
|
1779
|
+
"helyx.account-gate": "helyx.account-gate";
|
|
1780
|
+
"helyx.verification": "helyx.verification";
|
|
1781
|
+
}>;
|
|
1782
|
+
assumptions: z.ZodObject<{
|
|
1783
|
+
entryChannelId: z.ZodString;
|
|
1784
|
+
beforeRoleIds: z.ZodArray<z.ZodString>;
|
|
1785
|
+
afterRoleId: z.ZodString;
|
|
1786
|
+
entrance: z.ZodEnum<{
|
|
1787
|
+
public: "public";
|
|
1788
|
+
restricted: "restricted";
|
|
1789
|
+
}>;
|
|
1790
|
+
allowedChannelIds: z.ZodArray<z.ZodString>;
|
|
1791
|
+
reviewChannelId: z.ZodOptional<z.ZodString>;
|
|
1792
|
+
}, z.core.$strict>;
|
|
1706
1793
|
}, z.core.$strict>, z.ZodObject<{
|
|
1707
1794
|
actorUserId: z.ZodString;
|
|
1708
1795
|
correlationId: z.ZodString;
|
package/dist/shard-contracts.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { channelAuditModuleIdSchema, channelAuditInputSchema, channelAuditConfigurationSchema, channelAuditReportSchema, } from "@helyx/control-protocol/channel-audit";
|
|
2
3
|
import { CLOUD_MAX_RESOURCE_IMPORT_BYTES, creatorOnboardingRequestSchema, } from "@helyx/control-protocol";
|
|
3
4
|
import { AUTOMOD_SCANNER_LIMITS } from "@helyx/sdk";
|
|
4
5
|
import { signedAssessmentEnvelopeSchema } from "@helyx/verification-contracts";
|
|
@@ -355,6 +356,23 @@ export const shardControlOperationSchema = z.discriminatedUnion("kind", [
|
|
|
355
356
|
...mutationContext.shape,
|
|
356
357
|
})
|
|
357
358
|
.strict(),
|
|
359
|
+
z
|
|
360
|
+
.object({
|
|
361
|
+
kind: z.literal("guild.module.channel-audit.config.read"),
|
|
362
|
+
guildId: snowflake,
|
|
363
|
+
moduleId: channelAuditModuleIdSchema,
|
|
364
|
+
...mutationContext.shape,
|
|
365
|
+
})
|
|
366
|
+
.strict(),
|
|
367
|
+
z
|
|
368
|
+
.object({
|
|
369
|
+
kind: z.literal("guild.module.channel-audit.check"),
|
|
370
|
+
guildId: snowflake,
|
|
371
|
+
moduleId: channelAuditModuleIdSchema,
|
|
372
|
+
assumptions: channelAuditInputSchema,
|
|
373
|
+
...mutationContext.shape,
|
|
374
|
+
})
|
|
375
|
+
.strict(),
|
|
358
376
|
z
|
|
359
377
|
.object({
|
|
360
378
|
kind: z.literal("guild.module.configuration.update"),
|
|
@@ -1117,6 +1135,27 @@ export async function executeShardControlOperation(handler, operation) {
|
|
|
1117
1135
|
correlationId: operation.correlationId,
|
|
1118
1136
|
});
|
|
1119
1137
|
}
|
|
1138
|
+
case "guild.module.channel-audit.config.read": {
|
|
1139
|
+
if (!handler.readChannelAuditConfiguration)
|
|
1140
|
+
throw new Error("Channel audit is not supported by this shard version");
|
|
1141
|
+
return channelAuditConfigurationSchema.parse(await handler.readChannelAuditConfiguration({
|
|
1142
|
+
guildId: operation.guildId,
|
|
1143
|
+
moduleId: operation.moduleId,
|
|
1144
|
+
actorUserId: operation.actorUserId,
|
|
1145
|
+
correlationId: operation.correlationId,
|
|
1146
|
+
}));
|
|
1147
|
+
}
|
|
1148
|
+
case "guild.module.channel-audit.check": {
|
|
1149
|
+
if (!handler.checkChannelAudit)
|
|
1150
|
+
throw new Error("Channel audit is not supported by this shard version");
|
|
1151
|
+
return channelAuditReportSchema.parse(await handler.checkChannelAudit({
|
|
1152
|
+
guildId: operation.guildId,
|
|
1153
|
+
moduleId: operation.moduleId,
|
|
1154
|
+
actorUserId: operation.actorUserId,
|
|
1155
|
+
correlationId: operation.correlationId,
|
|
1156
|
+
assumptions: operation.assumptions,
|
|
1157
|
+
}));
|
|
1158
|
+
}
|
|
1120
1159
|
case "guild.module.configuration.update": {
|
|
1121
1160
|
return handler.updateConfiguration({
|
|
1122
1161
|
guildId: operation.guildId,
|
package/dist/shard-router.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export declare class ShardControlRouter implements BotControlHandler {
|
|
|
48
48
|
listGuildPresence(input: Parameters<BotControlHandler["listGuildPresence"]>[0]): ReturnType<BotControlHandler["listGuildPresence"]>;
|
|
49
49
|
setModuleStatus(input: Parameters<BotControlHandler["setModuleStatus"]>[0]): ReturnType<BotControlHandler["setModuleStatus"]>;
|
|
50
50
|
completeFirstTimeCommandSetup(input: Parameters<BotControlHandler["completeFirstTimeCommandSetup"]>[0]): ReturnType<BotControlHandler["completeFirstTimeCommandSetup"]>;
|
|
51
|
+
readChannelAuditConfiguration(input: Parameters<NonNullable<BotControlHandler["readChannelAuditConfiguration"]>>[0]): ReturnType<NonNullable<BotControlHandler["readChannelAuditConfiguration"]>>;
|
|
52
|
+
checkChannelAudit(input: Parameters<NonNullable<BotControlHandler["checkChannelAudit"]>>[0]): ReturnType<NonNullable<BotControlHandler["checkChannelAudit"]>>;
|
|
51
53
|
updateConfiguration(input: Parameters<BotControlHandler["updateConfiguration"]>[0]): ReturnType<BotControlHandler["updateConfiguration"]>;
|
|
52
54
|
previewConfiguration(input: Parameters<BotControlHandler["previewConfiguration"]>[0]): ReturnType<BotControlHandler["previewConfiguration"]>;
|
|
53
55
|
updatePermissions(input: Parameters<BotControlHandler["updatePermissions"]>[0]): ReturnType<BotControlHandler["updatePermissions"]>;
|
package/dist/shard-router.js
CHANGED
|
@@ -117,6 +117,18 @@ export class ShardControlRouter {
|
|
|
117
117
|
expectedRevision: input.expectedRevision.toString(),
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
|
+
readChannelAuditConfiguration(input) {
|
|
121
|
+
return this.#route(shardControlOperationSchema.parse({
|
|
122
|
+
kind: "guild.module.channel-audit.config.read",
|
|
123
|
+
...input,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
checkChannelAudit(input) {
|
|
127
|
+
return this.#route(shardControlOperationSchema.parse({
|
|
128
|
+
kind: "guild.module.channel-audit.check",
|
|
129
|
+
...input,
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
120
132
|
updateConfiguration(input) {
|
|
121
133
|
return this.#route(shardControlOperationSchema.parse({
|
|
122
134
|
kind: "guild.module.configuration.update",
|
package/dist/shard-supervisor.js
CHANGED
|
@@ -13,6 +13,7 @@ import { CloudConnectorController, } from "./cloud-connector.js";
|
|
|
13
13
|
import { createCloudManagementModule } from "./cloud-management-module.js";
|
|
14
14
|
import { BotControlService, createBotControlServer, mapBotControlError, } from "./control-server.js";
|
|
15
15
|
import { createManagementModule } from "./management-module.js";
|
|
16
|
+
import { createDiagnosticModule } from "./diagnostic-command.js";
|
|
16
17
|
import { buildApplicationCommandDefinitions } from "./interactions.js";
|
|
17
18
|
import { resolveModulePackageSpecifiers } from "./module-configuration.js";
|
|
18
19
|
import { deriveSecureRandomSelectionKey, loadConfiguredModulePackages, platformServiceOptions, secureRandomSelectionWorkerKeyName, } from "./modules.js";
|
|
@@ -217,6 +218,10 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
217
218
|
};
|
|
218
219
|
const permissionsModule = createPermissionsModule(featureModules);
|
|
219
220
|
const managementModule = createManagementModule(featureModules, unavailableResources);
|
|
221
|
+
// The supervisor registers metadata; guild checks and repairs run in workers.
|
|
222
|
+
const diagnosticModule = createDiagnosticModule({
|
|
223
|
+
modules: featureModules,
|
|
224
|
+
});
|
|
220
225
|
const cloudManagementModule = environment.HELYX_CLOUD_CONNECT_ENABLED
|
|
221
226
|
? createCloudManagementModule({
|
|
222
227
|
connector: cloudConnector,
|
|
@@ -242,6 +247,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
242
247
|
: undefined;
|
|
243
248
|
const publicModules = [
|
|
244
249
|
managementModule,
|
|
250
|
+
diagnosticModule,
|
|
245
251
|
permissionsModule,
|
|
246
252
|
supportModule,
|
|
247
253
|
privacyModule,
|
|
@@ -265,6 +271,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
265
271
|
: {}),
|
|
266
272
|
coreFunctions: [
|
|
267
273
|
managementModule,
|
|
274
|
+
diagnosticModule,
|
|
268
275
|
permissionsModule,
|
|
269
276
|
supportModule,
|
|
270
277
|
privacyModule,
|
package/dist/shard-worker.d.ts
CHANGED
|
@@ -102,6 +102,7 @@ export declare function shardWorkerCommandRegistration(input: {
|
|
|
102
102
|
prepare: NonNullable<CommandRegistration["prepare"]>;
|
|
103
103
|
operationsModuleId?: string;
|
|
104
104
|
supportModuleId: string;
|
|
105
|
+
diagnosticModuleId?: string;
|
|
105
106
|
premiumActivationModuleId?: string;
|
|
106
107
|
oauthModuleId?: string;
|
|
107
108
|
}): CommandRegistration;
|