@helyx/bot 0.4.27 → 0.4.29
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 +22 -0
- package/dist/cloud-connector.js +3 -0
- package/dist/cloud-control-dispatcher.js +29 -0
- package/dist/control-server.d.ts +45 -1
- package/dist/control-server.js +213 -18
- package/dist/dashboard-category.d.ts +3 -0
- package/dist/dashboard-category.js +14 -0
- package/dist/interactions.js +125 -92
- package/dist/permissions-module.js +29 -4
- package/dist/resource-authorization.js +2 -1
- package/dist/shard-contracts.d.ts +69 -0
- package/dist/shard-contracts.js +44 -0
- package/dist/shard-router.d.ts +3 -0
- package/dist/shard-router.js +25 -1
- package/dist/supervisor-cloud-directory.d.ts +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.29
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Harden dashboard authentication, privacy execution, control routing, database transactions, and self-host configuration.
|
|
8
|
+
|
|
9
|
+
Add bounded multi-value record reads and use them to batch Polls and Reaction Roles dashboard queries. Improve dashboard presentation, Forms workflows, release safety, and operational tooling across hosted and self-hosted deployments.
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @helyx/control-protocol@0.4.0
|
|
13
|
+
- @helyx/database@0.8.0
|
|
14
|
+
- @helyx/sdk@0.7.0
|
|
15
|
+
- @helyx/platform@0.5.0
|
|
16
|
+
|
|
17
|
+
## 0.4.28
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Add the private testing-stage Forms module contracts and runtime integration,
|
|
22
|
+
and introduce server-configurable Helyx Administrator, Moderator, dashboard
|
|
23
|
+
access, and command-permission policies with fresh Discord authorization.
|
|
24
|
+
|
|
3
25
|
## 0.4.27
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/dist/cloud-connector.js
CHANGED
|
@@ -719,6 +719,9 @@ function connectorCapabilities() {
|
|
|
719
719
|
return [
|
|
720
720
|
"installation.health.read.v1",
|
|
721
721
|
"guild.state.read.v1",
|
|
722
|
+
"guild.dashboard.access.read.v1",
|
|
723
|
+
"guild.access-policy.read.v1",
|
|
724
|
+
"guild.access-policy.write.v1",
|
|
722
725
|
"guild.module.public.read.v1",
|
|
723
726
|
"guild.module.status.write.v1",
|
|
724
727
|
"guild.module.configuration.write.v1",
|
|
@@ -25,6 +25,33 @@ export class CloudControlDispatcher {
|
|
|
25
25
|
result = await this.control.getGuildState(input.guildId);
|
|
26
26
|
break;
|
|
27
27
|
}
|
|
28
|
+
case "guild.dashboard.access.read.v1": {
|
|
29
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
30
|
+
result = {
|
|
31
|
+
allowed: await this.control.canAccessDashboard({
|
|
32
|
+
guildId: input.guildId,
|
|
33
|
+
actorUserId: requiredActor(actorUserId),
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "guild.access-policy.read.v1": {
|
|
39
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
40
|
+
result = await this.control.getGuildAccessPolicy({
|
|
41
|
+
guildId: input.guildId,
|
|
42
|
+
actorUserId: requiredActor(actorUserId),
|
|
43
|
+
});
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case "guild.access-policy.write.v1": {
|
|
47
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
48
|
+
result = await this.control.updateGuildAccessPolicy({
|
|
49
|
+
...input,
|
|
50
|
+
actorUserId: requiredActor(actorUserId),
|
|
51
|
+
correlationId,
|
|
52
|
+
});
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
28
55
|
case "guild.module.public.read.v1": {
|
|
29
56
|
const input = parseOperationInput(request.operation, request.input);
|
|
30
57
|
if (request.actor.kind !== "public")
|
|
@@ -116,6 +143,8 @@ export class CloudControlDispatcher {
|
|
|
116
143
|
resourceId: input.resourceId,
|
|
117
144
|
relationId: input.relationId,
|
|
118
145
|
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
146
|
+
...(input.search === undefined ? {} : { search: input.search }),
|
|
147
|
+
...(input.filters === undefined ? {} : { filters: input.filters }),
|
|
119
148
|
limit: input.limit,
|
|
120
149
|
actorUserId: requiredActor(actorUserId),
|
|
121
150
|
correlationId,
|
package/dist/control-server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DatabasePool, type GuildShardOwnership } from "@helyx/database";
|
|
2
|
-
import { type DashboardActionResult, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type PublicAchievementProjectionPage, type ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
import { type DashboardActionResult, type GuildAccessPolicy, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type PublicAchievementProjectionPage, type ServiceAccess } from "@helyx/sdk";
|
|
3
3
|
import { type ActionReceipt, type SignedAssessmentEnvelope } from "@helyx/verification-contracts";
|
|
4
4
|
import { type Client } from "discord.js";
|
|
5
5
|
import { type FastifyInstance } from "fastify";
|
|
@@ -45,6 +45,7 @@ interface DashboardModuleDefinition {
|
|
|
45
45
|
actions: DashboardActionDefinition[];
|
|
46
46
|
achievementTriggers: NonNullable<ModuleDefinition["manifest"]["contributions"]["achievementTriggers"]>;
|
|
47
47
|
resources: readonly DashboardResourceDefinition[];
|
|
48
|
+
workspace?: NonNullable<ModuleDefinition["manifest"]["dashboard"]>["workspace"];
|
|
48
49
|
}
|
|
49
50
|
interface DashboardLoggingPolicy {
|
|
50
51
|
eventId: string;
|
|
@@ -71,6 +72,7 @@ export interface BotControlGuildResources {
|
|
|
71
72
|
id: string;
|
|
72
73
|
name: string;
|
|
73
74
|
colour: string;
|
|
75
|
+
position: number;
|
|
74
76
|
}>;
|
|
75
77
|
}
|
|
76
78
|
export interface BotPublicModuleProjection {
|
|
@@ -146,6 +148,14 @@ export interface BotControlState {
|
|
|
146
148
|
permissions: Record<string, DashboardPermissionState>;
|
|
147
149
|
logging: Record<string, DashboardLoggingPolicy[]>;
|
|
148
150
|
}
|
|
151
|
+
export interface DashboardGuildAccessState extends GuildAccessPolicy {
|
|
152
|
+
version: number;
|
|
153
|
+
canManage: boolean;
|
|
154
|
+
actorAuthority: "owner" | "administrator" | "moderator" | "member";
|
|
155
|
+
actorRoleIds: string[];
|
|
156
|
+
actorHighestRolePosition: number | null;
|
|
157
|
+
updatedAt: string | null;
|
|
158
|
+
}
|
|
149
159
|
export interface BotControlHandler {
|
|
150
160
|
sendSupportNotification(input: SupportNotificationInput): Promise<void>;
|
|
151
161
|
listGuildPresence(input: {
|
|
@@ -153,6 +163,21 @@ export interface BotControlHandler {
|
|
|
153
163
|
correlationId: string;
|
|
154
164
|
}): Promise<BotGuildPresenceObservation[]>;
|
|
155
165
|
getGuildState(guildId: string): Promise<BotControlState>;
|
|
166
|
+
canAccessDashboard(input: {
|
|
167
|
+
guildId: string;
|
|
168
|
+
actorUserId: string;
|
|
169
|
+
}): Promise<boolean>;
|
|
170
|
+
getGuildAccessPolicy(input: {
|
|
171
|
+
guildId: string;
|
|
172
|
+
actorUserId: string;
|
|
173
|
+
}): Promise<DashboardGuildAccessState>;
|
|
174
|
+
updateGuildAccessPolicy(input: {
|
|
175
|
+
guildId: string;
|
|
176
|
+
actorUserId: string;
|
|
177
|
+
correlationId: string;
|
|
178
|
+
version: number;
|
|
179
|
+
policy: GuildAccessPolicy;
|
|
180
|
+
}): Promise<DashboardGuildAccessState>;
|
|
156
181
|
readPublicModuleProjection(input: {
|
|
157
182
|
guildId: string;
|
|
158
183
|
moduleId: string;
|
|
@@ -235,6 +260,8 @@ export interface BotControlHandler {
|
|
|
235
260
|
relationId: string;
|
|
236
261
|
cursor?: string;
|
|
237
262
|
limit: number;
|
|
263
|
+
search?: string;
|
|
264
|
+
filters?: Readonly<Record<string, string>>;
|
|
238
265
|
actorUserId: string;
|
|
239
266
|
correlationId: string;
|
|
240
267
|
}): Promise<ManagedResourceRelationPage>;
|
|
@@ -354,6 +381,21 @@ export declare class BotControlService implements BotControlHandler {
|
|
|
354
381
|
cursor?: string;
|
|
355
382
|
}): Promise<PrivacyErasePage>;
|
|
356
383
|
getGuildState(guildId: string): Promise<BotControlState>;
|
|
384
|
+
canAccessDashboard(input: {
|
|
385
|
+
guildId: string;
|
|
386
|
+
actorUserId: string;
|
|
387
|
+
}): Promise<boolean>;
|
|
388
|
+
getGuildAccessPolicy(input: {
|
|
389
|
+
guildId: string;
|
|
390
|
+
actorUserId: string;
|
|
391
|
+
}): Promise<DashboardGuildAccessState>;
|
|
392
|
+
updateGuildAccessPolicy(input: {
|
|
393
|
+
guildId: string;
|
|
394
|
+
actorUserId: string;
|
|
395
|
+
correlationId: string;
|
|
396
|
+
version: number;
|
|
397
|
+
policy: GuildAccessPolicy;
|
|
398
|
+
}): Promise<DashboardGuildAccessState>;
|
|
357
399
|
readPublicModuleProjection(input: {
|
|
358
400
|
guildId: string;
|
|
359
401
|
moduleId: string;
|
|
@@ -436,6 +478,8 @@ export declare class BotControlService implements BotControlHandler {
|
|
|
436
478
|
relationId: string;
|
|
437
479
|
cursor?: string;
|
|
438
480
|
limit: number;
|
|
481
|
+
search?: string;
|
|
482
|
+
filters?: Readonly<Record<string, string>>;
|
|
439
483
|
actorUserId: string;
|
|
440
484
|
correlationId: string;
|
|
441
485
|
}): Promise<ManagedResourceRelationPage>;
|
package/dist/control-server.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { AuditRepository, InstallationRepository, ModuleLoggingPolicyRepository, OptimisticConcurrencyError, PermissionPolicyRepository, withTransaction, } from "@helyx/database";
|
|
2
|
-
import { DashboardActionValidationError, HELYX_SERVICE_NAMES, ModuleStateValidationError, } from "@helyx/sdk";
|
|
1
|
+
import { AuditRepository, GuildAccessPolicyRepository, InstallationRepository, ModuleLoggingPolicyRepository, OptimisticConcurrencyError, PermissionPolicyRepository, withTransaction, } from "@helyx/database";
|
|
2
|
+
import { DashboardActionValidationError, DEFAULT_GUILD_ACCESS_POLICY, HELYX_SERVICE_NAMES, ModuleStateValidationError, } from "@helyx/sdk";
|
|
3
3
|
import { actionReceiptSchema, signedAssessmentEnvelopeSchema, } from "@helyx/verification-contracts";
|
|
4
|
-
import { canUseCommand } from "@helyx/core";
|
|
4
|
+
import { canAccessGuildDashboard, canMutatePermissionPolicy, canUseCommand, permissionMutationDenialMessage, resolveHelyxAuthority, } from "@helyx/core";
|
|
5
5
|
import { ChannelType, PermissionFlagsBits } from "discord.js";
|
|
6
6
|
import Fastify, {} from "fastify";
|
|
7
7
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
8
8
|
import { z, ZodError } from "zod";
|
|
9
9
|
import { addChannelMessageReaction as addDiscordChannelMessageReaction, deleteChannelMessage as deleteDiscordChannelMessage, editChannelMessage as editDiscordChannelMessage, normalizeDiscordPermission, sendToChannel as sendDiscordMessageToChannel, } from "./interactions.js";
|
|
10
10
|
import { SHARD_HEALTH_STALE_MS, } from "./shard-contracts.js";
|
|
11
|
+
import { dashboardCategoryLabel } from "./dashboard-category.js";
|
|
11
12
|
const snowflake = z.string().regex(/^\d{17,20}$/u);
|
|
12
13
|
const moduleId = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/u);
|
|
13
14
|
const MAX_DISCORD_SETTING_SELECTIONS = 25;
|
|
@@ -41,6 +42,15 @@ const permissionState = z.object({
|
|
|
41
42
|
}))
|
|
42
43
|
.max(100),
|
|
43
44
|
});
|
|
45
|
+
const guildAccessPolicy = z
|
|
46
|
+
.object({
|
|
47
|
+
administratorRoleIds: z.array(snowflake).max(25),
|
|
48
|
+
moderatorRoleIds: z.array(snowflake).max(25),
|
|
49
|
+
dashboardModeratorsEnabled: z.boolean(),
|
|
50
|
+
dashboardAllowedRoleIds: z.array(snowflake).max(25),
|
|
51
|
+
dashboardDeniedRoleIds: z.array(snowflake).max(25),
|
|
52
|
+
})
|
|
53
|
+
.strict();
|
|
44
54
|
const mutationContext = z.object({
|
|
45
55
|
actorUserId: snowflake,
|
|
46
56
|
correlationId: z.uuid(),
|
|
@@ -146,6 +156,7 @@ export class DiscordGuildResourceDirectory {
|
|
|
146
156
|
id: role.id,
|
|
147
157
|
name: role.name,
|
|
148
158
|
colour: role.hexColor,
|
|
159
|
+
position: role.position,
|
|
149
160
|
})),
|
|
150
161
|
};
|
|
151
162
|
}
|
|
@@ -181,7 +192,13 @@ export class DiscordGuildResourceDirectory {
|
|
|
181
192
|
async resolveActor(guildId, actorUserId) {
|
|
182
193
|
try {
|
|
183
194
|
const guild = await this.client.guilds.fetch(guildId);
|
|
184
|
-
|
|
195
|
+
// Dashboard authorization must reflect current Discord roles and
|
|
196
|
+
// permissions even when a member-update gateway event was missed.
|
|
197
|
+
const member = await guild.members.fetch({
|
|
198
|
+
user: actorUserId,
|
|
199
|
+
force: true,
|
|
200
|
+
cache: true,
|
|
201
|
+
});
|
|
185
202
|
return {
|
|
186
203
|
userId: member.id,
|
|
187
204
|
roleIds: [...member.roles.cache.keys()],
|
|
@@ -190,6 +207,7 @@ export class DiscordGuildResourceDirectory {
|
|
|
190
207
|
.map(normalizeDiscordPermission),
|
|
191
208
|
isServerOwner: guild.ownerId === member.id,
|
|
192
209
|
isAdministrator: member.permissions.has(PermissionFlagsBits.Administrator),
|
|
210
|
+
highestRolePosition: member.roles.highest.position,
|
|
193
211
|
};
|
|
194
212
|
}
|
|
195
213
|
catch (error) {
|
|
@@ -425,6 +443,88 @@ export class BotControlService {
|
|
|
425
443
|
logging,
|
|
426
444
|
};
|
|
427
445
|
}
|
|
446
|
+
async canAccessDashboard(input) {
|
|
447
|
+
const actor = await this.resources.resolveActor(input.guildId, input.actorUserId);
|
|
448
|
+
return canAccessGuildDashboard(actor, await this.#readGuildAccessPolicy(input.guildId));
|
|
449
|
+
}
|
|
450
|
+
async getGuildAccessPolicy(input) {
|
|
451
|
+
const actor = await this.resources.resolveActor(input.guildId, input.actorUserId);
|
|
452
|
+
const record = await new GuildAccessPolicyRepository(this.database).get(input.guildId);
|
|
453
|
+
const policy = record ?? DEFAULT_GUILD_ACCESS_POLICY;
|
|
454
|
+
return {
|
|
455
|
+
administratorRoleIds: [...policy.administratorRoleIds],
|
|
456
|
+
moderatorRoleIds: [...policy.moderatorRoleIds],
|
|
457
|
+
dashboardModeratorsEnabled: policy.dashboardModeratorsEnabled,
|
|
458
|
+
dashboardAllowedRoleIds: [...policy.dashboardAllowedRoleIds],
|
|
459
|
+
dashboardDeniedRoleIds: [...policy.dashboardDeniedRoleIds],
|
|
460
|
+
version: record ? safeVersion(record.version) : 0,
|
|
461
|
+
canManage: actor.isServerOwner || actor.isAdministrator,
|
|
462
|
+
actorAuthority: resolveHelyxAuthority(actor, policy),
|
|
463
|
+
actorRoleIds: [...actor.roleIds],
|
|
464
|
+
actorHighestRolePosition: actor.highestRolePosition ?? null,
|
|
465
|
+
updatedAt: record?.updatedAt.toISOString() ?? null,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
async updateGuildAccessPolicy(input) {
|
|
469
|
+
const actor = await this.resources.resolveActor(input.guildId, input.actorUserId);
|
|
470
|
+
if (!actor.isServerOwner && !actor.isAdministrator)
|
|
471
|
+
throw new BotControlForbiddenError("Only the server owner or a Discord Administrator can change dashboard access");
|
|
472
|
+
const resources = await this.resources.getGuildResources(input.guildId);
|
|
473
|
+
const availableRoles = new Set(resources.roles.map(({ id }) => id));
|
|
474
|
+
const roleIds = [
|
|
475
|
+
...input.policy.administratorRoleIds,
|
|
476
|
+
...input.policy.moderatorRoleIds,
|
|
477
|
+
...input.policy.dashboardAllowedRoleIds,
|
|
478
|
+
...input.policy.dashboardDeniedRoleIds,
|
|
479
|
+
];
|
|
480
|
+
if (roleIds.some((roleId) => !availableRoles.has(roleId)))
|
|
481
|
+
throw new BotControlValidationError("Dashboard access contains a Discord role that is no longer available");
|
|
482
|
+
const saved = await withTransaction(this.database, async (connection) => {
|
|
483
|
+
const record = await new GuildAccessPolicyRepository(connection).save({
|
|
484
|
+
guildId: input.guildId,
|
|
485
|
+
administratorRoleIds: [...input.policy.administratorRoleIds],
|
|
486
|
+
moderatorRoleIds: [...input.policy.moderatorRoleIds],
|
|
487
|
+
dashboardModeratorsEnabled: input.policy.dashboardModeratorsEnabled,
|
|
488
|
+
dashboardAllowedRoleIds: [...input.policy.dashboardAllowedRoleIds],
|
|
489
|
+
dashboardDeniedRoleIds: [...input.policy.dashboardDeniedRoleIds],
|
|
490
|
+
expectedVersion: input.version === 0 ? null : BigInt(input.version),
|
|
491
|
+
updatedBy: input.actorUserId,
|
|
492
|
+
});
|
|
493
|
+
await new AuditRepository(connection).append({
|
|
494
|
+
guildId: input.guildId,
|
|
495
|
+
actorUserId: input.actorUserId,
|
|
496
|
+
action: "guild.access.updated",
|
|
497
|
+
source: "dashboard",
|
|
498
|
+
correlationId: input.correlationId,
|
|
499
|
+
afterState: {
|
|
500
|
+
version: record.version.toString(),
|
|
501
|
+
administratorRoleCount: record.administratorRoleIds.length,
|
|
502
|
+
moderatorRoleCount: record.moderatorRoleIds.length,
|
|
503
|
+
dashboardAllowedRoleCount: record.dashboardAllowedRoleIds.length,
|
|
504
|
+
dashboardDeniedRoleCount: record.dashboardDeniedRoleIds.length,
|
|
505
|
+
dashboardModeratorsEnabled: record.dashboardModeratorsEnabled,
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
return record;
|
|
509
|
+
});
|
|
510
|
+
return {
|
|
511
|
+
administratorRoleIds: [...saved.administratorRoleIds],
|
|
512
|
+
moderatorRoleIds: [...saved.moderatorRoleIds],
|
|
513
|
+
dashboardModeratorsEnabled: saved.dashboardModeratorsEnabled,
|
|
514
|
+
dashboardAllowedRoleIds: [...saved.dashboardAllowedRoleIds],
|
|
515
|
+
dashboardDeniedRoleIds: [...saved.dashboardDeniedRoleIds],
|
|
516
|
+
version: safeVersion(saved.version),
|
|
517
|
+
canManage: true,
|
|
518
|
+
actorAuthority: resolveHelyxAuthority(actor, saved),
|
|
519
|
+
actorRoleIds: [...actor.roleIds],
|
|
520
|
+
actorHighestRolePosition: actor.highestRolePosition ?? null,
|
|
521
|
+
updatedAt: saved.updatedAt.toISOString(),
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
async #readGuildAccessPolicy(guildId) {
|
|
525
|
+
return ((await new GuildAccessPolicyRepository(this.database).get(guildId)) ??
|
|
526
|
+
DEFAULT_GUILD_ACCESS_POLICY);
|
|
527
|
+
}
|
|
428
528
|
async readPublicModuleProjection(input) {
|
|
429
529
|
const module = this.#requireModule(input.moduleId);
|
|
430
530
|
if (!module.publicProjection)
|
|
@@ -583,6 +683,26 @@ export class BotControlService {
|
|
|
583
683
|
])));
|
|
584
684
|
for (const command of commands)
|
|
585
685
|
assertVersion(currentCommands.get(command.id).version, supplied.get(command.id).version);
|
|
686
|
+
const [actor, guildAccess, resources] = await Promise.all([
|
|
687
|
+
this.resources.resolveActor(input.guildId, input.actorUserId),
|
|
688
|
+
this.#permissions.getGuildAccessPolicy?.(input.guildId) ??
|
|
689
|
+
Promise.resolve(DEFAULT_GUILD_ACCESS_POLICY),
|
|
690
|
+
this.resources.getGuildResources(input.guildId),
|
|
691
|
+
]);
|
|
692
|
+
const rolePositions = Object.fromEntries(resources.roles.map((role) => [role.id, role.position]));
|
|
693
|
+
const requestedModulePolicy = fromDashboardPolicy(input.state.module, currentModule.policy);
|
|
694
|
+
if (!permissionPoliciesEqual(currentModule.policy, requestedModulePolicy))
|
|
695
|
+
assertPermissionMutationAllowed(canMutatePermissionPolicy(actor, currentModule.policy, requestedModulePolicy, guildAccess, rolePositions));
|
|
696
|
+
for (const command of commands) {
|
|
697
|
+
const requested = supplied.get(command.id);
|
|
698
|
+
const current = currentCommands.get(command.id);
|
|
699
|
+
const target = requested.enabled
|
|
700
|
+
? fromDashboardPolicy(requested.policy, current.policy)
|
|
701
|
+
: requestedModulePolicy;
|
|
702
|
+
const enabledChanged = requested.enabled === current.inherited;
|
|
703
|
+
if (enabledChanged || !permissionPoliciesEqual(current.policy, target))
|
|
704
|
+
assertPermissionMutationAllowed(canMutatePermissionPolicy(actor, current.policy, target, guildAccess, rolePositions));
|
|
705
|
+
}
|
|
586
706
|
await withTransaction(this.database, async (connection) => {
|
|
587
707
|
const policies = new PermissionPolicyRepository(connection);
|
|
588
708
|
const audit = new AuditRepository(connection);
|
|
@@ -765,6 +885,8 @@ export class BotControlService {
|
|
|
765
885
|
return normalizeManagedResourceRelationPage(managedResourceRelationPageSchema.parse(await resource.listRelation(context, input.resourceId, input.relationId, {
|
|
766
886
|
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
767
887
|
limit: input.limit,
|
|
888
|
+
...(input.search === undefined ? {} : { search: input.search }),
|
|
889
|
+
...(input.filters === undefined ? {} : { filters: input.filters }),
|
|
768
890
|
})), relation.columns.map(({ key }) => key));
|
|
769
891
|
}
|
|
770
892
|
async previewManagedResource(input) {
|
|
@@ -877,7 +999,8 @@ export class BotControlService {
|
|
|
877
999
|
async #authorizeAction(guildId, module, actorUserId, permissionId) {
|
|
878
1000
|
const actor = await this.resources.resolveActor(guildId, actorUserId);
|
|
879
1001
|
const policy = await this.#permissions.getEffectivePolicy(guildId, module.manifest.id, permissionId);
|
|
880
|
-
|
|
1002
|
+
const guildAccess = await this.#permissions.getGuildAccessPolicy?.(guildId);
|
|
1003
|
+
if (!canUseCommand(actor, policy, guildAccess))
|
|
881
1004
|
throw new BotControlForbiddenError();
|
|
882
1005
|
return actor;
|
|
883
1006
|
}
|
|
@@ -1139,6 +1262,35 @@ export async function createBotControlServer(input) {
|
|
|
1139
1262
|
const { guildId } = z.object({ guildId: snowflake }).parse(request.params);
|
|
1140
1263
|
return input.service.getGuildState(guildId);
|
|
1141
1264
|
});
|
|
1265
|
+
server.post("/v1/guilds/:guildId/dashboard-access", async (request) => {
|
|
1266
|
+
const { guildId } = z.object({ guildId: snowflake }).parse(request.params);
|
|
1267
|
+
const { actorUserId } = z
|
|
1268
|
+
.object({ actorUserId: snowflake })
|
|
1269
|
+
.strict()
|
|
1270
|
+
.parse(request.body);
|
|
1271
|
+
return {
|
|
1272
|
+
allowed: await input.service.canAccessDashboard({
|
|
1273
|
+
guildId,
|
|
1274
|
+
actorUserId,
|
|
1275
|
+
}),
|
|
1276
|
+
};
|
|
1277
|
+
});
|
|
1278
|
+
server.get("/v1/guilds/:guildId/access-policy", async (request) => {
|
|
1279
|
+
const { guildId } = z.object({ guildId: snowflake }).parse(request.params);
|
|
1280
|
+
const { actorUserId } = z
|
|
1281
|
+
.object({ actorUserId: snowflake })
|
|
1282
|
+
.strict()
|
|
1283
|
+
.parse(request.query);
|
|
1284
|
+
return input.service.getGuildAccessPolicy({ guildId, actorUserId });
|
|
1285
|
+
});
|
|
1286
|
+
server.put("/v1/guilds/:guildId/access-policy", async (request) => {
|
|
1287
|
+
const { guildId } = z.object({ guildId: snowflake }).parse(request.params);
|
|
1288
|
+
const body = mutationContext
|
|
1289
|
+
.extend({ version: z.number().int().min(0), policy: guildAccessPolicy })
|
|
1290
|
+
.strict()
|
|
1291
|
+
.parse(request.body);
|
|
1292
|
+
return input.service.updateGuildAccessPolicy({ guildId, ...body });
|
|
1293
|
+
});
|
|
1142
1294
|
server.get("/v1/guilds/:guildId/modules/:moduleId/public-projection", async (request) => {
|
|
1143
1295
|
const params = z
|
|
1144
1296
|
.object({ guildId: snowflake, moduleId })
|
|
@@ -1272,6 +1424,21 @@ export async function createBotControlServer(input) {
|
|
|
1272
1424
|
.object({
|
|
1273
1425
|
cursor: resourceCursor.optional(),
|
|
1274
1426
|
limit: z.coerce.number().int().min(1).max(50).default(50),
|
|
1427
|
+
search: z.string().trim().min(1).max(200).optional(),
|
|
1428
|
+
filters: z
|
|
1429
|
+
.preprocess((value) => {
|
|
1430
|
+
if (typeof value !== "string")
|
|
1431
|
+
return value;
|
|
1432
|
+
if (value.length > 2_048)
|
|
1433
|
+
return value;
|
|
1434
|
+
try {
|
|
1435
|
+
return JSON.parse(value);
|
|
1436
|
+
}
|
|
1437
|
+
catch {
|
|
1438
|
+
return value;
|
|
1439
|
+
}
|
|
1440
|
+
}, managedResourceFilters)
|
|
1441
|
+
.optional(),
|
|
1275
1442
|
actorUserId: snowflake,
|
|
1276
1443
|
correlationId: z.uuid(),
|
|
1277
1444
|
})
|
|
@@ -1281,6 +1448,8 @@ export async function createBotControlServer(input) {
|
|
|
1281
1448
|
...params,
|
|
1282
1449
|
...(query.cursor === undefined ? {} : { cursor: query.cursor }),
|
|
1283
1450
|
limit: query.limit,
|
|
1451
|
+
...(query.search === undefined ? {} : { search: query.search }),
|
|
1452
|
+
...(query.filters === undefined ? {} : { filters: query.filters }),
|
|
1284
1453
|
actorUserId: query.actorUserId,
|
|
1285
1454
|
correlationId: query.correlationId,
|
|
1286
1455
|
});
|
|
@@ -1657,7 +1826,7 @@ function mapDashboardModule(module) {
|
|
|
1657
1826
|
name: module.manifest.name,
|
|
1658
1827
|
version: module.manifest.version,
|
|
1659
1828
|
summary: module.manifest.description,
|
|
1660
|
-
category:
|
|
1829
|
+
category: dashboardCategoryLabel(dashboard.category),
|
|
1661
1830
|
icon: dashboard.icon,
|
|
1662
1831
|
availability: "available",
|
|
1663
1832
|
releaseStatus: module.manifest.releaseStatus,
|
|
@@ -1668,6 +1837,9 @@ function mapDashboardModule(module) {
|
|
|
1668
1837
|
actions: (dashboard.actions ?? []).map((action) => ({ ...action })),
|
|
1669
1838
|
achievementTriggers: (module.manifest.contributions?.achievementTriggers ?? []).map((trigger) => structuredClone(trigger)),
|
|
1670
1839
|
resources: (dashboard.resources ?? []).map((resource) => structuredClone(resource)),
|
|
1840
|
+
workspace: dashboard.workspace
|
|
1841
|
+
? structuredClone(dashboard.workspace)
|
|
1842
|
+
: undefined,
|
|
1671
1843
|
};
|
|
1672
1844
|
}
|
|
1673
1845
|
function dashboardCommands(module) {
|
|
@@ -1702,14 +1874,18 @@ function dashboardCommands(module) {
|
|
|
1702
1874
|
function toDashboardPolicy(policy) {
|
|
1703
1875
|
const preset = policy.everyone
|
|
1704
1876
|
? "everyone"
|
|
1705
|
-
: policy.
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
? "
|
|
1712
|
-
: "
|
|
1877
|
+
: policy.accessLevel === "administrator"
|
|
1878
|
+
? "administrators"
|
|
1879
|
+
: policy.accessLevel === "moderator"
|
|
1880
|
+
? "moderators"
|
|
1881
|
+
: policy.allowedRoleIds.length > 0 &&
|
|
1882
|
+
policy.requiredPermissions.length === 0
|
|
1883
|
+
? "custom"
|
|
1884
|
+
: policy.requiredPermissions.includes("ADMINISTRATOR")
|
|
1885
|
+
? "administrators"
|
|
1886
|
+
: policy.requiredPermissions.includes("MANAGE_GUILD")
|
|
1887
|
+
? "moderators"
|
|
1888
|
+
: "custom";
|
|
1713
1889
|
return {
|
|
1714
1890
|
preset,
|
|
1715
1891
|
allowedRoleIds: [...policy.allowedRoleIds],
|
|
@@ -1719,11 +1895,12 @@ function toDashboardPolicy(policy) {
|
|
|
1719
1895
|
function fromDashboardPolicy(input, current) {
|
|
1720
1896
|
return {
|
|
1721
1897
|
everyone: input.preset === "everyone",
|
|
1722
|
-
|
|
1723
|
-
?
|
|
1898
|
+
...(input.preset === "administrators"
|
|
1899
|
+
? { accessLevel: "administrator" }
|
|
1724
1900
|
: input.preset === "moderators"
|
|
1725
|
-
?
|
|
1726
|
-
:
|
|
1901
|
+
? { accessLevel: "moderator" }
|
|
1902
|
+
: {}),
|
|
1903
|
+
requiredPermissions: [],
|
|
1727
1904
|
allowedRoleIds: input.preset === "custom" ? input.allowedRoleIds : [],
|
|
1728
1905
|
deniedRoleIds: input.deniedRoleIds,
|
|
1729
1906
|
allowedUserIds: [...current.allowedUserIds],
|
|
@@ -1740,6 +1917,24 @@ function emptyPolicy() {
|
|
|
1740
1917
|
deniedUserIds: [],
|
|
1741
1918
|
};
|
|
1742
1919
|
}
|
|
1920
|
+
function permissionPoliciesEqual(left, right) {
|
|
1921
|
+
return (left.everyone === right.everyone &&
|
|
1922
|
+
left.accessLevel === right.accessLevel &&
|
|
1923
|
+
sameStrings(left.requiredPermissions, right.requiredPermissions) &&
|
|
1924
|
+
sameStrings(left.allowedRoleIds, right.allowedRoleIds) &&
|
|
1925
|
+
sameStrings(left.deniedRoleIds, right.deniedRoleIds) &&
|
|
1926
|
+
sameStrings(left.allowedUserIds, right.allowedUserIds) &&
|
|
1927
|
+
sameStrings(left.deniedUserIds, right.deniedUserIds));
|
|
1928
|
+
}
|
|
1929
|
+
function sameStrings(left, right) {
|
|
1930
|
+
return (left.length === right.length &&
|
|
1931
|
+
left.every((value, index) => value === right[index]));
|
|
1932
|
+
}
|
|
1933
|
+
function assertPermissionMutationAllowed(decision) {
|
|
1934
|
+
if (decision.allowed)
|
|
1935
|
+
return;
|
|
1936
|
+
throw new BotControlForbiddenError(permissionMutationDenialMessage(decision.reason));
|
|
1937
|
+
}
|
|
1743
1938
|
function safeVersion(version) {
|
|
1744
1939
|
const value = Number(version);
|
|
1745
1940
|
if (!Number.isSafeInteger(value) || value < 1)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const categoryLabels = {
|
|
2
|
+
community: "Community",
|
|
3
|
+
safety: "Safety",
|
|
4
|
+
support: "Support & Staff",
|
|
5
|
+
automation: "Automation",
|
|
6
|
+
engagement: "Engagement",
|
|
7
|
+
gaming: "Gaming",
|
|
8
|
+
insights: "Insights",
|
|
9
|
+
integrations: "Integrations",
|
|
10
|
+
};
|
|
11
|
+
export function dashboardCategoryLabel(category) {
|
|
12
|
+
return categoryLabels[category];
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=dashboard-category.js.map
|
package/dist/interactions.js
CHANGED
|
@@ -372,6 +372,9 @@ function createContext(interaction, services) {
|
|
|
372
372
|
userRoleIds: interaction.member instanceof GuildMember
|
|
373
373
|
? [...interaction.member.roles.cache.keys()]
|
|
374
374
|
: (interaction.member?.roles ?? []),
|
|
375
|
+
...(interaction.member instanceof GuildMember
|
|
376
|
+
? { userHighestRolePosition: interaction.member.roles.highest.position }
|
|
377
|
+
: {}),
|
|
375
378
|
userPermissions: interaction.memberPermissions
|
|
376
379
|
?.toArray()
|
|
377
380
|
.map(normalizeDiscordPermission) ?? [],
|
|
@@ -491,6 +494,9 @@ function createModalContext(interaction, services) {
|
|
|
491
494
|
userRoleIds: interaction.member instanceof GuildMember
|
|
492
495
|
? [...interaction.member.roles.cache.keys()]
|
|
493
496
|
: (interaction.member?.roles ?? []),
|
|
497
|
+
...(interaction.member instanceof GuildMember
|
|
498
|
+
? { userHighestRolePosition: interaction.member.roles.highest.position }
|
|
499
|
+
: {}),
|
|
494
500
|
userPermissions: interaction.memberPermissions
|
|
495
501
|
?.toArray()
|
|
496
502
|
.map(normalizeDiscordPermission) ?? [],
|
|
@@ -525,6 +531,12 @@ function createModalContext(interaction, services) {
|
|
|
525
531
|
roleIds: [...(selected?.roles.keys() ?? [])],
|
|
526
532
|
};
|
|
527
533
|
},
|
|
534
|
+
getSelectedRolePositions: (customId) => {
|
|
535
|
+
const roles = interaction.fields.getSelectedRoles(customId, false);
|
|
536
|
+
return Object.fromEntries([...(roles?.values() ?? [])]
|
|
537
|
+
.filter((role) => role !== null)
|
|
538
|
+
.map((role) => [role.id, role.position]));
|
|
539
|
+
},
|
|
528
540
|
getSelectedChannelIds: (customId) => [
|
|
529
541
|
...(interaction.fields.getSelectedChannels(customId, false)?.keys() ??
|
|
530
542
|
[]),
|
|
@@ -624,7 +636,8 @@ export async function authorizeCommand(interaction, moduleId, commandId, rateLim
|
|
|
624
636
|
const permissions = services.get(HELYX_SERVICE_NAMES.permissions);
|
|
625
637
|
const policy = await permissions.getEffectivePolicy(interaction.guildId, moduleId, commandId);
|
|
626
638
|
const actor = permissionActor(interaction);
|
|
627
|
-
|
|
639
|
+
const guildAccess = await permissions.getGuildAccessPolicy?.(interaction.guildId);
|
|
640
|
+
if (!canUseCommand(actor, policy, guildAccess)) {
|
|
628
641
|
await interaction.reply({
|
|
629
642
|
content: "You do not have permission to use this command.",
|
|
630
643
|
flags: MessageFlags.Ephemeral,
|
|
@@ -662,7 +675,8 @@ async function authorizeAutocomplete(interaction, moduleId, commandId, services,
|
|
|
662
675
|
return true;
|
|
663
676
|
const permissions = services.get(HELYX_SERVICE_NAMES.permissions);
|
|
664
677
|
const policy = await permissions.getEffectivePolicy(interaction.guildId, moduleId, commandId);
|
|
665
|
-
|
|
678
|
+
const guildAccess = await permissions.getGuildAccessPolicy?.(interaction.guildId);
|
|
679
|
+
return canUseCommand(permissionActor(interaction), policy, guildAccess);
|
|
666
680
|
}
|
|
667
681
|
function permissionActor(interaction) {
|
|
668
682
|
return {
|
|
@@ -676,6 +690,9 @@ function permissionActor(interaction) {
|
|
|
676
690
|
isServerOwner: interaction.guild?.ownerId === interaction.user.id,
|
|
677
691
|
isAdministrator: interaction.memberPermissions?.has(PermissionFlagsBits.Administrator) ??
|
|
678
692
|
false,
|
|
693
|
+
...(interaction.member instanceof GuildMember
|
|
694
|
+
? { highestRolePosition: interaction.member.roles.highest.position }
|
|
695
|
+
: {}),
|
|
679
696
|
};
|
|
680
697
|
}
|
|
681
698
|
export function normalizeDiscordPermission(permission) {
|
|
@@ -824,100 +841,116 @@ export function toModal(definition) {
|
|
|
824
841
|
const modal = new ModalBuilder()
|
|
825
842
|
.setCustomId(definition.customId)
|
|
826
843
|
.setTitle(definition.title);
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
if (field.maxLength !== undefined)
|
|
844
|
-
input.setMaxLength(field.maxLength);
|
|
845
|
-
if (field.placeholder !== undefined)
|
|
846
|
-
input.setPlaceholder(field.placeholder);
|
|
847
|
-
if (field.value !== undefined)
|
|
848
|
-
input.setValue(field.value);
|
|
849
|
-
label.setTextInputComponent(input);
|
|
850
|
-
}
|
|
851
|
-
else if (field.type === "string-select") {
|
|
852
|
-
const input = new StringSelectMenuBuilder()
|
|
853
|
-
.setCustomId(field.customId)
|
|
854
|
-
.setRequired(field.required ?? false)
|
|
855
|
-
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
856
|
-
.setMaxValues(field.maxValues ?? 1)
|
|
857
|
-
.addOptions(field.options.map((option) => ({ ...option })));
|
|
858
|
-
if (field.placeholder !== undefined)
|
|
859
|
-
input.setPlaceholder(field.placeholder);
|
|
860
|
-
label.setStringSelectMenuComponent(input);
|
|
861
|
-
}
|
|
862
|
-
else if (field.type === "discord-user") {
|
|
863
|
-
const input = new UserSelectMenuBuilder()
|
|
864
|
-
.setCustomId(field.customId)
|
|
865
|
-
.setRequired(field.required ?? false)
|
|
866
|
-
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
867
|
-
.setMaxValues(field.maxValues ?? 1);
|
|
868
|
-
if (field.placeholder !== undefined)
|
|
869
|
-
input.setPlaceholder(field.placeholder);
|
|
870
|
-
if (field.defaultValues?.length)
|
|
871
|
-
input.setDefaultUsers(...field.defaultValues);
|
|
872
|
-
label.setUserSelectMenuComponent(input);
|
|
873
|
-
}
|
|
874
|
-
else if (field.type === "discord-role") {
|
|
875
|
-
const input = new RoleSelectMenuBuilder()
|
|
876
|
-
.setCustomId(field.customId)
|
|
877
|
-
.setRequired(field.required ?? false)
|
|
878
|
-
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
879
|
-
.setMaxValues(field.maxValues ?? 1);
|
|
880
|
-
if (field.placeholder !== undefined)
|
|
881
|
-
input.setPlaceholder(field.placeholder);
|
|
882
|
-
if (field.defaultValues?.length)
|
|
883
|
-
input.setDefaultRoles(...field.defaultValues);
|
|
884
|
-
label.setRoleSelectMenuComponent(input);
|
|
885
|
-
}
|
|
886
|
-
else if (field.type === "discord-mentionable") {
|
|
887
|
-
const input = new MentionableSelectMenuBuilder()
|
|
888
|
-
.setCustomId(field.customId)
|
|
889
|
-
.setRequired(field.required ?? false)
|
|
890
|
-
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
891
|
-
.setMaxValues(field.maxValues ?? 1);
|
|
892
|
-
if (field.placeholder !== undefined)
|
|
893
|
-
input.setPlaceholder(field.placeholder);
|
|
894
|
-
if (field.defaultValues?.length) {
|
|
895
|
-
input.addDefaultUsers(...field.defaultValues
|
|
896
|
-
.filter((value) => value.type === "user")
|
|
897
|
-
.map((value) => value.id));
|
|
898
|
-
input.addDefaultRoles(...field.defaultValues
|
|
899
|
-
.filter((value) => value.type === "role")
|
|
900
|
-
.map((value) => value.id));
|
|
901
|
-
}
|
|
902
|
-
label.setMentionableSelectMenuComponent(input);
|
|
903
|
-
}
|
|
904
|
-
else if (field.type === "discord-channel") {
|
|
905
|
-
const input = new ChannelSelectMenuBuilder()
|
|
906
|
-
.setCustomId(field.customId)
|
|
907
|
-
.setRequired(field.required ?? false)
|
|
908
|
-
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
909
|
-
.setMaxValues(field.maxValues ?? 1)
|
|
910
|
-
.setChannelTypes(...field.channelTypes.map(discordChannelType));
|
|
911
|
-
if (field.placeholder !== undefined)
|
|
912
|
-
input.setPlaceholder(field.placeholder);
|
|
913
|
-
if (field.defaultValues?.length)
|
|
914
|
-
input.setDefaultChannels(...field.defaultValues);
|
|
915
|
-
label.setChannelSelectMenuComponent(input);
|
|
844
|
+
if (definition.items !== undefined &&
|
|
845
|
+
((definition.text?.length ?? 0) > 0 || definition.fields.length > 0))
|
|
846
|
+
throw new Error("Ordered modal items cannot be combined with legacy text or fields");
|
|
847
|
+
const items = definition.items ?? [
|
|
848
|
+
...(definition.text ?? []).map((text) => ({
|
|
849
|
+
type: "text-display",
|
|
850
|
+
...text,
|
|
851
|
+
})),
|
|
852
|
+
...definition.fields,
|
|
853
|
+
];
|
|
854
|
+
if (items.length > 5)
|
|
855
|
+
throw new Error("Discord modals support at most five components");
|
|
856
|
+
for (const item of items) {
|
|
857
|
+
if (item.type !== "text-display") {
|
|
858
|
+
modal.addLabelComponents(toModalLabel(item));
|
|
859
|
+
continue;
|
|
916
860
|
}
|
|
917
|
-
modal.
|
|
861
|
+
modal.addTextDisplayComponents(new TextDisplayBuilder().setContent(item.markdown === false ? escapeMarkdown(item.content) : item.content));
|
|
918
862
|
}
|
|
919
863
|
return modal;
|
|
920
864
|
}
|
|
865
|
+
function toModalLabel(field) {
|
|
866
|
+
const label = new LabelBuilder().setLabel(field.label);
|
|
867
|
+
if (field.description !== undefined)
|
|
868
|
+
label.setDescription(field.description);
|
|
869
|
+
if (field.type === undefined || field.type === "text") {
|
|
870
|
+
const input = new TextInputBuilder()
|
|
871
|
+
.setCustomId(field.customId)
|
|
872
|
+
.setStyle(field.style === "paragraph"
|
|
873
|
+
? TextInputStyle.Paragraph
|
|
874
|
+
: TextInputStyle.Short)
|
|
875
|
+
.setRequired(field.required ?? false);
|
|
876
|
+
if (field.minLength !== undefined)
|
|
877
|
+
input.setMinLength(field.minLength);
|
|
878
|
+
if (field.maxLength !== undefined)
|
|
879
|
+
input.setMaxLength(field.maxLength);
|
|
880
|
+
if (field.placeholder !== undefined)
|
|
881
|
+
input.setPlaceholder(field.placeholder);
|
|
882
|
+
if (field.value !== undefined)
|
|
883
|
+
input.setValue(field.value);
|
|
884
|
+
label.setTextInputComponent(input);
|
|
885
|
+
}
|
|
886
|
+
else if (field.type === "string-select") {
|
|
887
|
+
const input = new StringSelectMenuBuilder()
|
|
888
|
+
.setCustomId(field.customId)
|
|
889
|
+
.setRequired(field.required ?? false)
|
|
890
|
+
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
891
|
+
.setMaxValues(field.maxValues ?? 1)
|
|
892
|
+
.addOptions(field.options.map((option) => ({ ...option })));
|
|
893
|
+
if (field.placeholder !== undefined)
|
|
894
|
+
input.setPlaceholder(field.placeholder);
|
|
895
|
+
label.setStringSelectMenuComponent(input);
|
|
896
|
+
}
|
|
897
|
+
else if (field.type === "discord-user") {
|
|
898
|
+
const input = new UserSelectMenuBuilder()
|
|
899
|
+
.setCustomId(field.customId)
|
|
900
|
+
.setRequired(field.required ?? false)
|
|
901
|
+
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
902
|
+
.setMaxValues(field.maxValues ?? 1);
|
|
903
|
+
if (field.placeholder !== undefined)
|
|
904
|
+
input.setPlaceholder(field.placeholder);
|
|
905
|
+
if (field.defaultValues?.length)
|
|
906
|
+
input.setDefaultUsers(...field.defaultValues);
|
|
907
|
+
label.setUserSelectMenuComponent(input);
|
|
908
|
+
}
|
|
909
|
+
else if (field.type === "discord-role") {
|
|
910
|
+
const input = new RoleSelectMenuBuilder()
|
|
911
|
+
.setCustomId(field.customId)
|
|
912
|
+
.setRequired(field.required ?? false)
|
|
913
|
+
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
914
|
+
.setMaxValues(field.maxValues ?? 1);
|
|
915
|
+
if (field.placeholder !== undefined)
|
|
916
|
+
input.setPlaceholder(field.placeholder);
|
|
917
|
+
if (field.defaultValues?.length)
|
|
918
|
+
input.setDefaultRoles(...field.defaultValues);
|
|
919
|
+
label.setRoleSelectMenuComponent(input);
|
|
920
|
+
}
|
|
921
|
+
else if (field.type === "discord-mentionable") {
|
|
922
|
+
const input = new MentionableSelectMenuBuilder()
|
|
923
|
+
.setCustomId(field.customId)
|
|
924
|
+
.setRequired(field.required ?? false)
|
|
925
|
+
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
926
|
+
.setMaxValues(field.maxValues ?? 1);
|
|
927
|
+
if (field.placeholder !== undefined)
|
|
928
|
+
input.setPlaceholder(field.placeholder);
|
|
929
|
+
if (field.defaultValues?.length) {
|
|
930
|
+
input.addDefaultUsers(...field.defaultValues
|
|
931
|
+
.filter((value) => value.type === "user")
|
|
932
|
+
.map((value) => value.id));
|
|
933
|
+
input.addDefaultRoles(...field.defaultValues
|
|
934
|
+
.filter((value) => value.type === "role")
|
|
935
|
+
.map((value) => value.id));
|
|
936
|
+
}
|
|
937
|
+
label.setMentionableSelectMenuComponent(input);
|
|
938
|
+
}
|
|
939
|
+
else if (field.type === "discord-channel") {
|
|
940
|
+
const input = new ChannelSelectMenuBuilder()
|
|
941
|
+
.setCustomId(field.customId)
|
|
942
|
+
.setRequired(field.required ?? false)
|
|
943
|
+
.setMinValues(field.minValues ?? (field.required ? 1 : 0))
|
|
944
|
+
.setMaxValues(field.maxValues ?? 1)
|
|
945
|
+
.setChannelTypes(...field.channelTypes.map(discordChannelType));
|
|
946
|
+
if (field.placeholder !== undefined)
|
|
947
|
+
input.setPlaceholder(field.placeholder);
|
|
948
|
+
if (field.defaultValues?.length)
|
|
949
|
+
input.setDefaultChannels(...field.defaultValues);
|
|
950
|
+
label.setChannelSelectMenuComponent(input);
|
|
951
|
+
}
|
|
952
|
+
return label;
|
|
953
|
+
}
|
|
921
954
|
function discordChannelType(type) {
|
|
922
955
|
if (type === "guild_announcement")
|
|
923
956
|
return ChannelType.GuildAnnouncement;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineModule, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
2
2
|
import { PermissionFlagsBits } from "discord.js";
|
|
3
|
+
import { canMutatePermissionPolicy, permissionMutationDenialMessage, } from "@helyx/core";
|
|
3
4
|
const MODULE_SELECT_ID = "helyx.permissions.module";
|
|
4
5
|
const TARGET_SELECT_ID = "helyx.permissions.target";
|
|
5
6
|
const MODULE_PREVIOUS_ID = "helyx.permissions.module.previous";
|
|
@@ -243,9 +244,32 @@ export function createPermissionsModule(configurableModules) {
|
|
|
243
244
|
allowedUserIds: [...allowed.userIds],
|
|
244
245
|
deniedUserIds: [...denied.userIds],
|
|
245
246
|
};
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
const permissionService = context.services.get(HELYX_SERVICE_NAMES.permissions);
|
|
248
|
+
const current = await permissionService.getPolicy(context.serverId, moduleId, commandId);
|
|
249
|
+
const guildAccess = (await permissionService.getGuildAccessPolicy?.(context.serverId)) ?? {
|
|
250
|
+
administratorRoleIds: [],
|
|
251
|
+
moderatorRoleIds: [],
|
|
252
|
+
dashboardModeratorsEnabled: false,
|
|
253
|
+
dashboardAllowedRoleIds: [],
|
|
254
|
+
dashboardDeniedRoleIds: [],
|
|
255
|
+
};
|
|
256
|
+
const rolePositions = {
|
|
257
|
+
...(context.getSelectedRolePositions?.(ALLOWED_MENTIONABLES_FIELD_ID) ?? {}),
|
|
258
|
+
...(context.getSelectedRolePositions?.(DENIED_MENTIONABLES_FIELD_ID) ?? {}),
|
|
259
|
+
};
|
|
260
|
+
const decision = canMutatePermissionPolicy({
|
|
261
|
+
userId: context.userId,
|
|
262
|
+
roleIds: context.userRoleIds,
|
|
263
|
+
permissions: context.userPermissions ?? [],
|
|
264
|
+
isServerOwner: context.isServerOwner ?? false,
|
|
265
|
+
isAdministrator: context.isAdministrator,
|
|
266
|
+
...(context.userHighestRolePosition === undefined
|
|
267
|
+
? {}
|
|
268
|
+
: { highestRolePosition: context.userHighestRolePosition }),
|
|
269
|
+
}, current.policy, policy, guildAccess, rolePositions);
|
|
270
|
+
if (!decision.allowed)
|
|
271
|
+
throw new Error(permissionMutationDenialMessage(decision.reason));
|
|
272
|
+
await permissionService.setPolicy({
|
|
249
273
|
guildId: context.serverId,
|
|
250
274
|
moduleId,
|
|
251
275
|
...(commandId ? { commandId } : {}),
|
|
@@ -326,7 +350,8 @@ export function createPermissionsModule(configurableModules) {
|
|
|
326
350
|
function restrictedPolicy() {
|
|
327
351
|
return {
|
|
328
352
|
everyone: false,
|
|
329
|
-
|
|
353
|
+
accessLevel: "moderator",
|
|
354
|
+
requiredPermissions: [],
|
|
330
355
|
allowedRoleIds: [],
|
|
331
356
|
deniedRoleIds: [],
|
|
332
357
|
allowedUserIds: [],
|
|
@@ -6,7 +6,8 @@ export class CoreResourceAuthorizationService {
|
|
|
6
6
|
}
|
|
7
7
|
async canUse(input) {
|
|
8
8
|
const policy = await this.#permissions.getEffectivePolicy(input.guildId, input.moduleId, input.permissionId);
|
|
9
|
-
|
|
9
|
+
const guildAccess = await this.#permissions.getGuildAccessPolicy?.(input.guildId);
|
|
10
|
+
return canUseCommand(input.actor, policy, guildAccess);
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
//# sourceMappingURL=resource-authorization.js.map
|
|
@@ -169,6 +169,27 @@ export type ShardHealthSnapshot = z.infer<typeof shardHealthSnapshotSchema>;
|
|
|
169
169
|
export declare const shardControlOperationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
170
170
|
kind: z.ZodLiteral<"guild.state.get">;
|
|
171
171
|
guildId: z.ZodString;
|
|
172
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
173
|
+
kind: z.ZodLiteral<"guild.dashboard.access">;
|
|
174
|
+
guildId: z.ZodString;
|
|
175
|
+
actorUserId: z.ZodString;
|
|
176
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
177
|
+
kind: z.ZodLiteral<"guild.access-policy.get">;
|
|
178
|
+
guildId: z.ZodString;
|
|
179
|
+
actorUserId: z.ZodString;
|
|
180
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
181
|
+
actorUserId: z.ZodString;
|
|
182
|
+
correlationId: z.ZodString;
|
|
183
|
+
kind: z.ZodLiteral<"guild.access-policy.update">;
|
|
184
|
+
guildId: z.ZodString;
|
|
185
|
+
version: z.ZodNumber;
|
|
186
|
+
policy: z.ZodObject<{
|
|
187
|
+
administratorRoleIds: z.ZodArray<z.ZodString>;
|
|
188
|
+
moderatorRoleIds: z.ZodArray<z.ZodString>;
|
|
189
|
+
dashboardModeratorsEnabled: z.ZodBoolean;
|
|
190
|
+
dashboardAllowedRoleIds: z.ZodArray<z.ZodString>;
|
|
191
|
+
dashboardDeniedRoleIds: z.ZodArray<z.ZodString>;
|
|
192
|
+
}, z.core.$strict>;
|
|
172
193
|
}, z.core.$strict>, z.ZodObject<{
|
|
173
194
|
kind: z.ZodLiteral<"guild.module.public.read">;
|
|
174
195
|
guildId: z.ZodString;
|
|
@@ -285,6 +306,8 @@ export declare const shardControlOperationSchema: z.ZodDiscriminatedUnion<[z.Zod
|
|
|
285
306
|
relationId: z.ZodString;
|
|
286
307
|
cursor: z.ZodOptional<z.ZodString>;
|
|
287
308
|
limit: z.ZodNumber;
|
|
309
|
+
search: z.ZodOptional<z.ZodString>;
|
|
310
|
+
filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
288
311
|
}, z.core.$strict>, z.ZodObject<{
|
|
289
312
|
actorUserId: z.ZodString;
|
|
290
313
|
correlationId: z.ZodString;
|
|
@@ -485,6 +508,27 @@ export declare const shardControlRequestSchema: z.ZodObject<{
|
|
|
485
508
|
operation: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
486
509
|
kind: z.ZodLiteral<"guild.state.get">;
|
|
487
510
|
guildId: z.ZodString;
|
|
511
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
512
|
+
kind: z.ZodLiteral<"guild.dashboard.access">;
|
|
513
|
+
guildId: z.ZodString;
|
|
514
|
+
actorUserId: z.ZodString;
|
|
515
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
516
|
+
kind: z.ZodLiteral<"guild.access-policy.get">;
|
|
517
|
+
guildId: z.ZodString;
|
|
518
|
+
actorUserId: z.ZodString;
|
|
519
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
520
|
+
actorUserId: z.ZodString;
|
|
521
|
+
correlationId: z.ZodString;
|
|
522
|
+
kind: z.ZodLiteral<"guild.access-policy.update">;
|
|
523
|
+
guildId: z.ZodString;
|
|
524
|
+
version: z.ZodNumber;
|
|
525
|
+
policy: z.ZodObject<{
|
|
526
|
+
administratorRoleIds: z.ZodArray<z.ZodString>;
|
|
527
|
+
moderatorRoleIds: z.ZodArray<z.ZodString>;
|
|
528
|
+
dashboardModeratorsEnabled: z.ZodBoolean;
|
|
529
|
+
dashboardAllowedRoleIds: z.ZodArray<z.ZodString>;
|
|
530
|
+
dashboardDeniedRoleIds: z.ZodArray<z.ZodString>;
|
|
531
|
+
}, z.core.$strict>;
|
|
488
532
|
}, z.core.$strict>, z.ZodObject<{
|
|
489
533
|
kind: z.ZodLiteral<"guild.module.public.read">;
|
|
490
534
|
guildId: z.ZodString;
|
|
@@ -601,6 +645,8 @@ export declare const shardControlRequestSchema: z.ZodObject<{
|
|
|
601
645
|
relationId: z.ZodString;
|
|
602
646
|
cursor: z.ZodOptional<z.ZodString>;
|
|
603
647
|
limit: z.ZodNumber;
|
|
648
|
+
search: z.ZodOptional<z.ZodString>;
|
|
649
|
+
filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
604
650
|
}, z.core.$strict>, z.ZodObject<{
|
|
605
651
|
actorUserId: z.ZodString;
|
|
606
652
|
correlationId: z.ZodString;
|
|
@@ -1306,6 +1352,27 @@ export declare const supervisorMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
1306
1352
|
operation: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
1307
1353
|
kind: z.ZodLiteral<"guild.state.get">;
|
|
1308
1354
|
guildId: z.ZodString;
|
|
1355
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1356
|
+
kind: z.ZodLiteral<"guild.dashboard.access">;
|
|
1357
|
+
guildId: z.ZodString;
|
|
1358
|
+
actorUserId: z.ZodString;
|
|
1359
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1360
|
+
kind: z.ZodLiteral<"guild.access-policy.get">;
|
|
1361
|
+
guildId: z.ZodString;
|
|
1362
|
+
actorUserId: z.ZodString;
|
|
1363
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1364
|
+
actorUserId: z.ZodString;
|
|
1365
|
+
correlationId: z.ZodString;
|
|
1366
|
+
kind: z.ZodLiteral<"guild.access-policy.update">;
|
|
1367
|
+
guildId: z.ZodString;
|
|
1368
|
+
version: z.ZodNumber;
|
|
1369
|
+
policy: z.ZodObject<{
|
|
1370
|
+
administratorRoleIds: z.ZodArray<z.ZodString>;
|
|
1371
|
+
moderatorRoleIds: z.ZodArray<z.ZodString>;
|
|
1372
|
+
dashboardModeratorsEnabled: z.ZodBoolean;
|
|
1373
|
+
dashboardAllowedRoleIds: z.ZodArray<z.ZodString>;
|
|
1374
|
+
dashboardDeniedRoleIds: z.ZodArray<z.ZodString>;
|
|
1375
|
+
}, z.core.$strict>;
|
|
1309
1376
|
}, z.core.$strict>, z.ZodObject<{
|
|
1310
1377
|
kind: z.ZodLiteral<"guild.module.public.read">;
|
|
1311
1378
|
guildId: z.ZodString;
|
|
@@ -1422,6 +1489,8 @@ export declare const supervisorMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
1422
1489
|
relationId: z.ZodString;
|
|
1423
1490
|
cursor: z.ZodOptional<z.ZodString>;
|
|
1424
1491
|
limit: z.ZodNumber;
|
|
1492
|
+
search: z.ZodOptional<z.ZodString>;
|
|
1493
|
+
filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
1425
1494
|
}, z.core.$strict>, z.ZodObject<{
|
|
1426
1495
|
actorUserId: z.ZodString;
|
|
1427
1496
|
correlationId: z.ZodString;
|
package/dist/shard-contracts.js
CHANGED
|
@@ -46,6 +46,15 @@ const permissionState = z
|
|
|
46
46
|
.max(100),
|
|
47
47
|
})
|
|
48
48
|
.strict();
|
|
49
|
+
const guildAccessPolicy = z
|
|
50
|
+
.object({
|
|
51
|
+
administratorRoleIds: z.array(snowflake).max(25),
|
|
52
|
+
moderatorRoleIds: z.array(snowflake).max(25),
|
|
53
|
+
dashboardModeratorsEnabled: z.boolean(),
|
|
54
|
+
dashboardAllowedRoleIds: z.array(snowflake).max(25),
|
|
55
|
+
dashboardDeniedRoleIds: z.array(snowflake).max(25),
|
|
56
|
+
})
|
|
57
|
+
.strict();
|
|
49
58
|
const configurationValue = z.record(z.string().min(1).max(100), z.union([
|
|
50
59
|
z.null(),
|
|
51
60
|
z.string().max(4_000),
|
|
@@ -222,6 +231,29 @@ export const shardHealthSnapshotSchema = z
|
|
|
222
231
|
});
|
|
223
232
|
export const shardControlOperationSchema = z.discriminatedUnion("kind", [
|
|
224
233
|
z.object({ kind: z.literal("guild.state.get"), guildId: snowflake }).strict(),
|
|
234
|
+
z
|
|
235
|
+
.object({
|
|
236
|
+
kind: z.literal("guild.dashboard.access"),
|
|
237
|
+
guildId: snowflake,
|
|
238
|
+
actorUserId: snowflake,
|
|
239
|
+
})
|
|
240
|
+
.strict(),
|
|
241
|
+
z
|
|
242
|
+
.object({
|
|
243
|
+
kind: z.literal("guild.access-policy.get"),
|
|
244
|
+
guildId: snowflake,
|
|
245
|
+
actorUserId: snowflake,
|
|
246
|
+
})
|
|
247
|
+
.strict(),
|
|
248
|
+
z
|
|
249
|
+
.object({
|
|
250
|
+
kind: z.literal("guild.access-policy.update"),
|
|
251
|
+
guildId: snowflake,
|
|
252
|
+
version: z.number().int().min(0),
|
|
253
|
+
policy: guildAccessPolicy,
|
|
254
|
+
...mutationContext.shape,
|
|
255
|
+
})
|
|
256
|
+
.strict(),
|
|
225
257
|
z
|
|
226
258
|
.object({
|
|
227
259
|
kind: z.literal("guild.module.public.read"),
|
|
@@ -330,6 +362,8 @@ export const shardControlOperationSchema = z.discriminatedUnion("kind", [
|
|
|
330
362
|
relationId: resourceType,
|
|
331
363
|
cursor: resourceCursor.optional(),
|
|
332
364
|
limit: z.number().int().min(1).max(50),
|
|
365
|
+
search: z.string().trim().min(1).max(200).optional(),
|
|
366
|
+
filters: managedResourceFilters.optional(),
|
|
333
367
|
...mutationContext.shape,
|
|
334
368
|
})
|
|
335
369
|
.strict(),
|
|
@@ -911,6 +945,12 @@ export async function executeShardControlOperation(handler, operation) {
|
|
|
911
945
|
});
|
|
912
946
|
case "guild.state.get":
|
|
913
947
|
return handler.getGuildState(operation.guildId);
|
|
948
|
+
case "guild.dashboard.access":
|
|
949
|
+
return handler.canAccessDashboard(operation);
|
|
950
|
+
case "guild.access-policy.get":
|
|
951
|
+
return handler.getGuildAccessPolicy(operation);
|
|
952
|
+
case "guild.access-policy.update":
|
|
953
|
+
return handler.updateGuildAccessPolicy(operation);
|
|
914
954
|
case "guild.module.public.read":
|
|
915
955
|
return handler.readPublicModuleProjection({
|
|
916
956
|
guildId: operation.guildId,
|
|
@@ -1008,6 +1048,10 @@ export async function executeShardControlOperation(handler, operation) {
|
|
|
1008
1048
|
resourceId: operation.resourceId,
|
|
1009
1049
|
relationId: operation.relationId,
|
|
1010
1050
|
...(operation.cursor === undefined ? {} : { cursor: operation.cursor }),
|
|
1051
|
+
...(operation.search === undefined ? {} : { search: operation.search }),
|
|
1052
|
+
...(operation.filters === undefined
|
|
1053
|
+
? {}
|
|
1054
|
+
: { filters: operation.filters }),
|
|
1011
1055
|
limit: operation.limit,
|
|
1012
1056
|
actorUserId: operation.actorUserId,
|
|
1013
1057
|
correlationId: operation.correlationId,
|
package/dist/shard-router.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ export declare class ShardControlRouter implements BotControlHandler {
|
|
|
16
16
|
notificationGuildId?: string;
|
|
17
17
|
});
|
|
18
18
|
getGuildState(guildId: string): ReturnType<BotControlHandler["getGuildState"]>;
|
|
19
|
+
canAccessDashboard(input: Parameters<BotControlHandler["canAccessDashboard"]>[0]): ReturnType<BotControlHandler["canAccessDashboard"]>;
|
|
20
|
+
getGuildAccessPolicy(input: Parameters<BotControlHandler["getGuildAccessPolicy"]>[0]): ReturnType<BotControlHandler["getGuildAccessPolicy"]>;
|
|
21
|
+
updateGuildAccessPolicy(input: Parameters<BotControlHandler["updateGuildAccessPolicy"]>[0]): ReturnType<BotControlHandler["updateGuildAccessPolicy"]>;
|
|
19
22
|
readPublicModuleProjection(input: Parameters<BotControlHandler["readPublicModuleProjection"]>[0]): ReturnType<BotControlHandler["readPublicModuleProjection"]>;
|
|
20
23
|
canAccessManagedResource(input: Parameters<BotControlHandler["canAccessManagedResource"]>[0]): ReturnType<BotControlHandler["canAccessManagedResource"]>;
|
|
21
24
|
listGuildPresence(input: Parameters<BotControlHandler["listGuildPresence"]>[0]): ReturnType<BotControlHandler["listGuildPresence"]>;
|
package/dist/shard-router.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { BotControlTransportError, } from "./control-server.js";
|
|
3
3
|
import { SHARD_CONTROL_TIMEOUT_MS, controlOperationGuildId, guildIdToShardId, shardControlOperationSchema, shardControlResponseSchema, } from "./shard-contracts.js";
|
|
4
|
-
|
|
4
|
+
// Cross-region/public Redis connections used by local staging routinely take
|
|
5
|
+
// just under one second. Privacy operations validate the leader lease several
|
|
6
|
+
// times in succession (provider discovery plus execution), so a one-second
|
|
7
|
+
// deadline turned healthy leases into deterministic 503s. Keep the check
|
|
8
|
+
// bounded while allowing normal network variance.
|
|
9
|
+
const LEASE_VALIDATION_TIMEOUT_MS = 3_000;
|
|
5
10
|
export class ShardControlRouter {
|
|
6
11
|
input;
|
|
7
12
|
constructor(input) {
|
|
@@ -10,6 +15,25 @@ export class ShardControlRouter {
|
|
|
10
15
|
getGuildState(guildId) {
|
|
11
16
|
return this.#route({ kind: "guild.state.get", guildId });
|
|
12
17
|
}
|
|
18
|
+
canAccessDashboard(input) {
|
|
19
|
+
return this.#route({ kind: "guild.dashboard.access", ...input });
|
|
20
|
+
}
|
|
21
|
+
getGuildAccessPolicy(input) {
|
|
22
|
+
return this.#route({ kind: "guild.access-policy.get", ...input });
|
|
23
|
+
}
|
|
24
|
+
updateGuildAccessPolicy(input) {
|
|
25
|
+
return this.#route({
|
|
26
|
+
kind: "guild.access-policy.update",
|
|
27
|
+
...input,
|
|
28
|
+
policy: {
|
|
29
|
+
administratorRoleIds: [...input.policy.administratorRoleIds],
|
|
30
|
+
moderatorRoleIds: [...input.policy.moderatorRoleIds],
|
|
31
|
+
dashboardModeratorsEnabled: input.policy.dashboardModeratorsEnabled,
|
|
32
|
+
dashboardAllowedRoleIds: [...input.policy.dashboardAllowedRoleIds],
|
|
33
|
+
dashboardDeniedRoleIds: [...input.policy.dashboardDeniedRoleIds],
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
13
37
|
readPublicModuleProjection(input) {
|
|
14
38
|
return this.#route({ kind: "guild.module.public.read", ...input });
|
|
15
39
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DatabasePool } from "@helyx/database";
|
|
2
2
|
import type { CloudDiscordDirectory, CloudDiscordSnapshot } from "./cloud-connector.js";
|
|
3
|
-
export declare const CLOUD_HELLO_GUILD_LIMIT =
|
|
3
|
+
export declare const CLOUD_HELLO_GUILD_LIMIT = 1000;
|
|
4
4
|
export declare class SupervisorCloudDiscordDirectory implements CloudDiscordDirectory {
|
|
5
5
|
#private;
|
|
6
6
|
private readonly database;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helyx/bot",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.29",
|
|
4
4
|
"description": "Production Discord runtime for the modular Helyx platform.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"helyx",
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@helyx/config": ">=0.5.0 <0.6.0",
|
|
52
|
-
"@helyx/control-protocol": ">=0.
|
|
52
|
+
"@helyx/control-protocol": ">=0.4.0 <0.5.0",
|
|
53
53
|
"@helyx/core": ">=0.1.0 <0.2.0",
|
|
54
|
-
"@helyx/database": ">=0.
|
|
54
|
+
"@helyx/database": ">=0.8.0 <0.9.0",
|
|
55
55
|
"@helyx/framework": ">=0.1.0 <0.2.0",
|
|
56
56
|
"@helyx/module-manifest": ">=0.6.0 <0.7.0",
|
|
57
|
-
"@helyx/platform": ">=0.
|
|
58
|
-
"@helyx/sdk": ">=0.
|
|
57
|
+
"@helyx/platform": ">=0.5.0 <0.6.0",
|
|
58
|
+
"@helyx/sdk": ">=0.7.0 <0.8.0",
|
|
59
59
|
"@helyx/verification-contracts": ">=0.3.0 <0.4.0",
|
|
60
60
|
"discord.js": "14.27.0",
|
|
61
61
|
"fastify": "5.10.0",
|