@helyx/bot 0.4.30 → 0.4.32

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.
@@ -9,6 +9,7 @@ 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
11
  import { dashboardCategoryLabel } from "./dashboard-category.js";
12
+ import { assertVersion, dashboardActionPermissionId, declaredManagedResourcePermission, emptyPolicy, fromDashboardPolicy, managedResourcePermissionId, permissionPoliciesEqual, permissionPolicyKey, safeOptionalVersion, safeVersion, toDashboardPolicy, } from "./control-permissions.js";
12
13
  const snowflake = z.string().regex(/^\d{17,20}$/u);
13
14
  const moduleId = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/u);
14
15
  const MAX_DISCORD_SETTING_SELECTIONS = 25;
@@ -84,6 +85,7 @@ const managedResourcePageSchema = z
84
85
  .object({
85
86
  items: z.array(managedResourceSummarySchema).max(50),
86
87
  nextCursor: resourceCursor.optional(),
88
+ total: z.number().int().min(0).optional(),
87
89
  })
88
90
  .strict()
89
91
  .refine(hasBoundedManagedResourceBytes);
@@ -129,11 +131,11 @@ export class DiscordGuildResourceDirectory {
129
131
  }
130
132
  async getGuildResources(guildId) {
131
133
  try {
132
- const guild = await this.client.guilds.fetch(guildId);
133
- const [channels, roles] = await Promise.all([
134
- guild.channels.fetch(),
135
- guild.roles.fetch(),
136
- ]);
134
+ const cachedGuild = this.client.guilds.cache.get(guildId);
135
+ const guild = cachedGuild ?? (await this.client.guilds.fetch(guildId));
136
+ const [channels, roles] = cachedGuild?.available === true
137
+ ? [cachedGuild.channels.cache, cachedGuild.roles.cache]
138
+ : await Promise.all([guild.channels.fetch(), guild.roles.fetch()]);
137
139
  return {
138
140
  channels: [...channels.values()]
139
141
  .filter((channel) => channel?.type === ChannelType.GuildText ||
@@ -389,11 +391,25 @@ export class BotControlService {
389
391
  });
390
392
  }
391
393
  async getGuildState(guildId) {
392
- const [resources, installedModules, storedLoggingPolicies, runtime] = await Promise.all([
394
+ const dashboardEntries = this.#dashboardModules.map((dashboardModule) => ({
395
+ dashboardModule,
396
+ module: this.#requireModule(dashboardModule.id),
397
+ }));
398
+ const moduleIds = dashboardEntries.map(({ dashboardModule }) => dashboardModule.id);
399
+ const permissionTargets = dashboardEntries.flatMap(({ dashboardModule, module }) => [
400
+ { moduleId: dashboardModule.id },
401
+ ...dashboardCommands(module).map(({ id }) => ({
402
+ moduleId: dashboardModule.id,
403
+ commandId: id,
404
+ })),
405
+ ]);
406
+ const [resources, installedModules, storedLoggingPolicies, runtime, storedConfigurations, storedPermissions,] = await Promise.all([
393
407
  this.resources.getGuildResources(guildId),
394
408
  new InstallationRepository(this.database).listModules(guildId),
395
409
  new ModuleLoggingPolicyRepository(this.database).listForGuild(guildId),
396
410
  this.#inspectRuntimeCapabilities(),
411
+ this.#readConfigurations(guildId, moduleIds),
412
+ this.#readPermissionPolicies(guildId, permissionTargets),
397
413
  ]);
398
414
  const installed = new Map(installedModules.map((module) => [module.moduleId, module]));
399
415
  const moduleStatuses = {};
@@ -404,24 +420,20 @@ export class BotControlService {
404
420
  `${policy.sourceModuleId}:${policy.eventId}`,
405
421
  policy,
406
422
  ]));
407
- await Promise.all(this.#dashboardModules.map(async (dashboardModule) => {
408
- const module = this.#requireModule(dashboardModule.id);
423
+ for (const { dashboardModule, module } of dashboardEntries) {
409
424
  const installation = installed.get(dashboardModule.id);
410
425
  moduleStatuses[dashboardModule.id] = installation
411
426
  ? installation.enabled
412
427
  ? "enabled"
413
428
  : "disabled"
414
429
  : "unavailable";
415
- const [configuration, permission] = await Promise.all([
416
- this.#configurations.get(guildId, dashboardModule.id),
417
- this.#getPermissionState(guildId, module),
418
- ]);
430
+ const configuration = storedConfigurations.get(dashboardModule.id);
419
431
  if (configuration)
420
432
  configurations[dashboardModule.id] = {
421
433
  version: safeVersion(configuration.version),
422
434
  value: configuration.value,
423
435
  };
424
- permissions[dashboardModule.id] = permission;
436
+ permissions[dashboardModule.id] = this.#buildPermissionState(module, storedPermissions);
425
437
  logging[dashboardModule.id] = dashboardModule.logEvents.map((event) => {
426
438
  const stored = loggingPolicies.get(`${dashboardModule.id}:${event.id}`);
427
439
  return {
@@ -430,7 +442,7 @@ export class BotControlService {
430
442
  version: stored ? safeVersion(stored.version) : 0,
431
443
  };
432
444
  });
433
- }));
445
+ }
434
446
  return {
435
447
  discordCapabilities: {
436
448
  messageContent: runtime.messageContent,
@@ -444,12 +456,17 @@ export class BotControlService {
444
456
  };
445
457
  }
446
458
  async canAccessDashboard(input) {
447
- const actor = await this.resources.resolveActor(input.guildId, input.actorUserId);
448
- return canAccessGuildDashboard(actor, await this.#readGuildAccessPolicy(input.guildId));
459
+ const [actor, policy] = await Promise.all([
460
+ this.resources.resolveActor(input.guildId, input.actorUserId),
461
+ this.#readGuildAccessPolicy(input.guildId),
462
+ ]);
463
+ return canAccessGuildDashboard(actor, policy);
449
464
  }
450
465
  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);
466
+ const [actor, record] = await Promise.all([
467
+ this.resources.resolveActor(input.guildId, input.actorUserId),
468
+ new GuildAccessPolicyRepository(this.database).get(input.guildId),
469
+ ]);
453
470
  const policy = record ?? DEFAULT_GUILD_ACCESS_POLICY;
454
471
  return {
455
472
  administratorRoleIds: [...policy.administratorRoleIds],
@@ -560,9 +577,14 @@ export class BotControlService {
560
577
  }
561
578
  async canAccessManagedResource(input) {
562
579
  try {
563
- const { module } = await this.#prepareManagedResource(input);
580
+ const { module, resource } = this.#prepareManagedResource(input);
564
581
  this.#requireManagedResourceOperation(module, input.resourceType, input.operation);
565
- await this.#authorizeAction(input.guildId, module, input.actorUserId, managedResourcePermissionId(module, input.resourceType, input.operation));
582
+ await Promise.all([
583
+ this.#authorizeAction(input.guildId, module, input.actorUserId, managedResourcePermissionId(module, input.resourceType, input.operation, undefined, declaredManagedResourcePermission(module, input.resourceType, input.operation))),
584
+ resource.availableWhileDisabled
585
+ ? Promise.resolve()
586
+ : this.#requireEnabledManagedResource(input.guildId, input.moduleId),
587
+ ]);
566
588
  return true;
567
589
  }
568
590
  catch (error) {
@@ -840,10 +862,13 @@ export class BotControlService {
840
862
  if (!declared || !action) {
841
863
  throw new BotControlNotFoundError("Dashboard action is not declared by this module");
842
864
  }
843
- if (!(await new InstallationRepository(this.database).isModuleEnabled(input.guildId, input.moduleId))) {
865
+ const [enabled] = await Promise.all([
866
+ new InstallationRepository(this.database).isModuleEnabled(input.guildId, input.moduleId),
867
+ this.#authorizeAction(input.guildId, module, input.actorUserId, dashboardActionPermissionId(module, input.actionId)),
868
+ ]);
869
+ if (!enabled) {
844
870
  throw new BotControlValidationError("Enable this module before running its dashboard actions");
845
871
  }
846
- await this.#authorizeAction(input.guildId, module, input.actorUserId, dashboardActionPermissionId(module, input.actionId));
847
872
  return action.execute({
848
873
  guildId: input.guildId,
849
874
  actorUserId: input.actorUserId,
@@ -854,7 +879,7 @@ export class BotControlService {
854
879
  });
855
880
  }
856
881
  async listManagedResources(input) {
857
- const { module, resource } = await this.#prepareManagedResource(input);
882
+ const { module, resource } = this.#prepareManagedResource(input);
858
883
  this.#requireManagedResourceOperation(module, input.resourceType, "list");
859
884
  this.#validateManagedResourceQuery(module, input.resourceType, input);
860
885
  const result = await resource.list(await this.#managedResourceContext(input, module, "list"), {
@@ -866,7 +891,7 @@ export class BotControlService {
866
891
  return normalizeManagedResourcePage(managedResourcePageSchema.parse(result), managedResourceColumnKeys(module, input.resourceType));
867
892
  }
868
893
  async readManagedResource(input) {
869
- const { module, resource } = await this.#prepareManagedResource(input);
894
+ const { module, resource } = this.#prepareManagedResource(input);
870
895
  this.#requireManagedResourceOperation(module, input.resourceType, "read");
871
896
  const result = await resource.read(await this.#managedResourceContext(input, module, "read"), input.resourceId);
872
897
  if (!result)
@@ -874,7 +899,7 @@ export class BotControlService {
874
899
  return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(result), managedResourceColumnKeys(module, input.resourceType));
875
900
  }
876
901
  async listManagedResourceRelation(input) {
877
- const { module, resource } = await this.#prepareManagedResource(input);
902
+ const { module, resource } = this.#prepareManagedResource(input);
878
903
  this.#requireManagedResourceOperation(module, input.resourceType, "read");
879
904
  const relation = module.manifest.dashboard?.resources
880
905
  ?.find(({ id }) => id === input.resourceType)
@@ -890,12 +915,12 @@ export class BotControlService {
890
915
  })), relation.columns.map(({ key }) => key));
891
916
  }
892
917
  async previewManagedResource(input) {
893
- const { module, resource } = await this.#prepareManagedResource(input);
918
+ const { module, resource } = this.#prepareManagedResource(input);
894
919
  this.#requireManagedResourceOperation(module, input.resourceType, "preview");
895
920
  return resource.preview(await this.#managedResourceContext(input, module, "preview"), input.value);
896
921
  }
897
922
  async createManagedResource(input) {
898
- const { module, resource } = await this.#prepareManagedResource(input);
923
+ const { module, resource } = this.#prepareManagedResource(input);
899
924
  this.#requireManagedResourceOperation(module, input.resourceType, "create");
900
925
  const context = await this.#managedResourceContext(input, module, "create");
901
926
  const declaredIntents = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType)?.creationIntents ?? ["publish"];
@@ -904,7 +929,7 @@ export class BotControlService {
904
929
  return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.create(context, input.value, input.intent)), managedResourceColumnKeys(module, input.resourceType));
905
930
  }
906
931
  async updateManagedResource(input) {
907
- const { module, resource } = await this.#prepareManagedResource(input);
932
+ const { module, resource } = this.#prepareManagedResource(input);
908
933
  this.#requireManagedResourceOperation(module, input.resourceType, "update");
909
934
  return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.update(await this.#managedResourceContext(input, module, "update"), {
910
935
  resourceId: input.resourceId,
@@ -913,7 +938,7 @@ export class BotControlService {
913
938
  })), managedResourceColumnKeys(module, input.resourceType));
914
939
  }
915
940
  async deleteManagedResource(input) {
916
- const { module, resource } = await this.#prepareManagedResource(input);
941
+ const { module, resource } = this.#prepareManagedResource(input);
917
942
  this.#requireManagedResourceOperation(module, input.resourceType, "delete");
918
943
  return managedResourceDeleteResultSchema.parse(await resource.delete(await this.#managedResourceContext(input, module, "delete"), {
919
944
  resourceId: input.resourceId,
@@ -921,7 +946,7 @@ export class BotControlService {
921
946
  }));
922
947
  }
923
948
  async executeManagedResourceAction(input) {
924
- const { module, resource } = await this.#prepareManagedResource(input);
949
+ const { module, resource } = this.#prepareManagedResource(input);
925
950
  const declared = module.manifest.dashboard?.resources
926
951
  ?.find(({ id }) => id === input.resourceType)
927
952
  ?.actions?.find(({ id }) => id === input.actionId);
@@ -941,11 +966,9 @@ export class BotControlService {
941
966
  ? null
942
967
  : normalizeManagedResourceDetail(managedResourceDetailSchema.parse(result), managedResourceColumnKeys(module, input.resourceType));
943
968
  }
944
- async #prepareManagedResource(input) {
969
+ #prepareManagedResource(input) {
945
970
  const module = this.#requireDashboardModule(input.moduleId);
946
971
  const resource = this.#requireManagedResource(module, input.resourceType);
947
- if (!resource.availableWhileDisabled)
948
- await this.#requireEnabledManagedResource(input.guildId, input.moduleId);
949
972
  return { module, resource };
950
973
  }
951
974
  #requireManagedResource(module, resourceType) {
@@ -979,8 +1002,14 @@ export class BotControlService {
979
1002
  if (!(await new InstallationRepository(this.database).isModuleEnabled(guildId, moduleIdValue)))
980
1003
  throw new BotControlValidationError("Enable this module before managing its resources");
981
1004
  }
982
- async #managedResourceContext(input, module, operation, permissionId = managedResourcePermissionId(module, input.resourceType, operation)) {
983
- const actor = await this.#authorizeAction(input.guildId, module, input.actorUserId, permissionId);
1005
+ async #managedResourceContext(input, module, operation, permissionId = managedResourcePermissionId(module, input.resourceType, operation, undefined, declaredManagedResourcePermission(module, input.resourceType, operation))) {
1006
+ const resource = this.#requireManagedResource(module, input.resourceType);
1007
+ const [actor] = await Promise.all([
1008
+ this.#authorizeAction(input.guildId, module, input.actorUserId, permissionId),
1009
+ resource.availableWhileDisabled
1010
+ ? Promise.resolve()
1011
+ : this.#requireEnabledManagedResource(input.guildId, module.manifest.id),
1012
+ ]);
984
1013
  return {
985
1014
  guildId: input.guildId,
986
1015
  actor,
@@ -997,9 +1026,11 @@ export class BotControlService {
997
1026
  };
998
1027
  }
999
1028
  async #authorizeAction(guildId, module, actorUserId, permissionId) {
1000
- const actor = await this.resources.resolveActor(guildId, actorUserId);
1001
- const policy = await this.#permissions.getEffectivePolicy(guildId, module.manifest.id, permissionId);
1002
- const guildAccess = await this.#permissions.getGuildAccessPolicy?.(guildId);
1029
+ const [actor, policy, guildAccess] = await Promise.all([
1030
+ this.resources.resolveActor(guildId, actorUserId),
1031
+ this.#permissions.getEffectivePolicy(guildId, module.manifest.id, permissionId),
1032
+ this.#permissions.getGuildAccessPolicy?.(guildId),
1033
+ ]);
1003
1034
  if (!canUseCommand(actor, policy, guildAccess))
1004
1035
  throw new BotControlForbiddenError();
1005
1036
  return actor;
@@ -1026,12 +1057,50 @@ export class BotControlService {
1026
1057
  }
1027
1058
  async #getPermissionState(guildId, module) {
1028
1059
  const commands = dashboardCommands(module);
1029
- const modulePolicy = await this.#permissions.getPolicy(guildId, module.manifest.id);
1060
+ const policies = await this.#readPermissionPolicies(guildId, [
1061
+ { moduleId: module.manifest.id },
1062
+ ...commands.map(({ id }) => ({
1063
+ moduleId: module.manifest.id,
1064
+ commandId: id,
1065
+ })),
1066
+ ]);
1067
+ return this.#buildPermissionState(module, policies);
1068
+ }
1069
+ async #readConfigurations(guildId, moduleIds) {
1070
+ const values = this.#configurations.getMany
1071
+ ? await this.#configurations.getMany(guildId, moduleIds)
1072
+ : await Promise.all(moduleIds.map((moduleId) => this.#configurations.get(guildId, moduleId)));
1073
+ return new Map(values
1074
+ .filter((value) => value !== null)
1075
+ .map((value) => [value.moduleId, value]));
1076
+ }
1077
+ async #readPermissionPolicies(guildId, targets) {
1078
+ const values = this.#permissions.getPolicies
1079
+ ? await this.#permissions.getPolicies(guildId, targets)
1080
+ : await Promise.all(targets.map(async (target) => ({
1081
+ ...target,
1082
+ ...(await this.#permissions.getPolicy(guildId, target.moduleId, target.commandId)),
1083
+ })));
1084
+ const policies = new Map(values.map((value) => [permissionPolicyKey(value), value]));
1085
+ for (const target of targets)
1086
+ if (!policies.has(permissionPolicyKey(target)))
1087
+ throw new Error("Permission policy bulk response was incomplete");
1088
+ return policies;
1089
+ }
1090
+ #buildPermissionState(module, policies) {
1091
+ const modulePolicy = policies.get(permissionPolicyKey({ moduleId: module.manifest.id }));
1092
+ if (!modulePolicy)
1093
+ throw new Error("Module permission policy was not resolved");
1030
1094
  return {
1031
1095
  version: safeOptionalVersion(modulePolicy.version),
1032
1096
  module: toDashboardPolicy(modulePolicy.policy),
1033
- commands: await Promise.all(commands.map(async (command) => {
1034
- const value = await this.#permissions.getPolicy(guildId, module.manifest.id, command.id);
1097
+ commands: dashboardCommands(module).map((command) => {
1098
+ const value = policies.get(permissionPolicyKey({
1099
+ moduleId: module.manifest.id,
1100
+ commandId: command.id,
1101
+ }));
1102
+ if (!value)
1103
+ throw new Error("Command permission policy was not resolved");
1035
1104
  return {
1036
1105
  commandId: command.id,
1037
1106
  version: safeOptionalVersion(value.version),
@@ -1041,7 +1110,7 @@ export class BotControlService {
1041
1110
  module.defaultPermissions?.commands[command.id] !== undefined),
1042
1111
  policy: toDashboardPolicy(value.policy),
1043
1112
  };
1044
- })),
1113
+ }),
1045
1114
  };
1046
1115
  }
1047
1116
  async #validateDiscordSettings(guildId, module, value) {
@@ -1650,58 +1719,6 @@ function isUnknownMemberError(error) {
1650
1719
  "code" in error &&
1651
1720
  error.code === 10_007);
1652
1721
  }
1653
- function dashboardActionPermissionId(module, actionId) {
1654
- const namespace = modulePermissionNamespace(module);
1655
- return selectDeclaredPermission(module, [
1656
- `${namespace}.${actionId.replaceAll("-", ".")}`,
1657
- `${namespace}.configure`,
1658
- ]);
1659
- }
1660
- function managedResourcePermissionId(module, resourceType, operation, actionId, declaredPermissionId) {
1661
- if (declaredPermissionId)
1662
- return declaredPermissionId;
1663
- const namespace = modulePermissionNamespace(module);
1664
- const resourcePermission = `resource.${resourceType}`;
1665
- const candidates = operation === "create" || operation === "preview"
1666
- ? [`${resourcePermission}.create`, `${namespace}.create`]
1667
- : operation === "update"
1668
- ? [
1669
- `${resourcePermission}.update`,
1670
- `${namespace}.edit`,
1671
- `${namespace}.manage`,
1672
- ]
1673
- : operation === "delete"
1674
- ? [
1675
- `${resourcePermission}.delete`,
1676
- `${namespace}.delete`,
1677
- `${namespace}.cancel`,
1678
- ]
1679
- : operation === "action" && actionId
1680
- ? [
1681
- `${resourcePermission}.${actionId}`,
1682
- `${resourcePermission}.action.${actionId}`,
1683
- `${resourcePermission}.action`,
1684
- `${namespace}.${actionId.replaceAll("-", ".")}`,
1685
- `${namespace}.manage`,
1686
- ]
1687
- : [
1688
- `${resourcePermission}.${operation}`,
1689
- `${namespace}.read`,
1690
- `${namespace}.list`,
1691
- `${namespace}.manage`,
1692
- `${namespace}.entries.read`,
1693
- ];
1694
- return selectDeclaredPermission(module, candidates);
1695
- }
1696
- function modulePermissionNamespace(module) {
1697
- return module.manifest.id.startsWith("helyx.")
1698
- ? module.manifest.id.slice("helyx.".length)
1699
- : module.manifest.id;
1700
- }
1701
- function selectDeclaredPermission(module, candidates) {
1702
- const declared = module.defaultPermissions?.commands ?? {};
1703
- return (candidates.find((candidate) => candidate in declared) ?? candidates[0]);
1704
- }
1705
1722
  function isBoundedManagedResourceValue(value, depth = 0, budget = { keys: 0 }) {
1706
1723
  if (depth > 8)
1707
1724
  return false;
@@ -1766,6 +1783,7 @@ function normalizeManagedResourcePage(value, attributeKeys) {
1766
1783
  updatedAt: item.updatedAt,
1767
1784
  })),
1768
1785
  ...(value.nextCursor === undefined ? {} : { nextCursor: value.nextCursor }),
1786
+ ...(value.total === undefined ? {} : { total: value.total }),
1769
1787
  };
1770
1788
  }
1771
1789
  function normalizeManagedResourceRelationPage(value, valueKeys) {
@@ -1871,81 +1889,9 @@ function dashboardCommands(module) {
1871
1889
  ...new Map(commands.map((command) => [command.id, command])).values(),
1872
1890
  ];
1873
1891
  }
1874
- function toDashboardPolicy(policy) {
1875
- const preset = policy.everyone
1876
- ? "everyone"
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";
1889
- return {
1890
- preset,
1891
- allowedRoleIds: [...policy.allowedRoleIds],
1892
- deniedRoleIds: [...policy.deniedRoleIds],
1893
- };
1894
- }
1895
- function fromDashboardPolicy(input, current) {
1896
- return {
1897
- everyone: input.preset === "everyone",
1898
- ...(input.preset === "administrators"
1899
- ? { accessLevel: "administrator" }
1900
- : input.preset === "moderators"
1901
- ? { accessLevel: "moderator" }
1902
- : {}),
1903
- requiredPermissions: [],
1904
- allowedRoleIds: input.preset === "custom" ? input.allowedRoleIds : [],
1905
- deniedRoleIds: input.deniedRoleIds,
1906
- allowedUserIds: [...current.allowedUserIds],
1907
- deniedUserIds: [...current.deniedUserIds],
1908
- };
1909
- }
1910
- function emptyPolicy() {
1911
- return {
1912
- everyone: false,
1913
- requiredPermissions: [],
1914
- allowedRoleIds: [],
1915
- deniedRoleIds: [],
1916
- allowedUserIds: [],
1917
- deniedUserIds: [],
1918
- };
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
1892
  function assertPermissionMutationAllowed(decision) {
1934
1893
  if (decision.allowed)
1935
1894
  return;
1936
1895
  throw new BotControlForbiddenError(permissionMutationDenialMessage(decision.reason));
1937
1896
  }
1938
- function safeVersion(version) {
1939
- const value = Number(version);
1940
- if (!Number.isSafeInteger(value) || value < 1)
1941
- throw new Error("Configuration version exceeded the supported range");
1942
- return value;
1943
- }
1944
- function safeOptionalVersion(version) {
1945
- return version === null ? 0 : safeVersion(version);
1946
- }
1947
- function assertVersion(actual, requested) {
1948
- if (safeOptionalVersion(actual) !== requested)
1949
- throw new OptimisticConcurrencyError("Permission policy");
1950
- }
1951
1897
  //# sourceMappingURL=control-server.js.map
@@ -0,0 +1,174 @@
1
+ import type { DiscordGuildHealthInspectionService, DiscordGuildHealthUnavailable, ModuleDefinition, ServiceAccess } from "@helyx/sdk";
2
+ import { type Client } from "discord.js";
3
+ export declare function moduleCatalogueMatches(packagedManifest: unknown, executableManifest: unknown): boolean;
4
+ export declare function missingRequiredPermissions(grantedPermissions: readonly string[], requiredPermissions: readonly string[]): string[];
5
+ export declare function hasRequiredModuleConfiguration(exposesConfiguration: boolean, savedConfiguration: unknown): boolean;
6
+ export declare function isHealthInspectableModule(moduleId: string): boolean;
7
+ export declare class DiscordGuildHealthInspection implements DiscordGuildHealthInspectionService {
8
+ #private;
9
+ private readonly client;
10
+ private readonly modules;
11
+ private readonly services?;
12
+ constructor(client: Client, modules?: readonly ModuleDefinition[], services?: ServiceAccess | undefined);
13
+ inspectOverview(input: {
14
+ guildId: string;
15
+ }): Promise<DiscordGuildHealthUnavailable> | Promise<{
16
+ available: true;
17
+ guildId: string;
18
+ name: string;
19
+ ownerId: string;
20
+ createdAt: Date;
21
+ memberCount: number;
22
+ verificationLevel: string;
23
+ explicitContentFilter: string;
24
+ defaultMessageNotifications: string;
25
+ mfaLevel: string;
26
+ premiumTier: number;
27
+ features: ("ANIMATED_BANNER" | "ANIMATED_ICON" | "APPLICATION_COMMAND_PERMISSIONS_V2" | "AUTO_MODERATION" | "BANNER" | "COMMUNITY" | "CREATOR_MONETIZABLE_PROVISIONAL" | "CREATOR_STORE_PAGE" | "DEVELOPER_SUPPORT_SERVER" | "DISCOVERABLE" | "FEATURABLE" | "HAS_DIRECTORY_ENTRY" | "HUB" | "INVITES_DISABLED" | "INVITE_SPLASH" | "LINKED_TO_HUB" | "MEMBER_VERIFICATION_GATE_ENABLED" | "MORE_SOUNDBOARD" | "MONETIZATION_ENABLED" | "MORE_STICKERS" | "NEWS" | "PARTNERED" | "PREVIEW_ENABLED" | "PRIVATE_THREADS" | "RAID_ALERTS_DISABLED" | "RELAY_ENABLED" | "ROLE_ICONS" | "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" | "ROLE_SUBSCRIPTIONS_ENABLED" | "SOUNDBOARD" | "TICKETED_EVENTS_ENABLED" | "VANITY_URL" | "VERIFIED" | "VIP_REGIONS" | "WELCOME_SCREEN_ENABLED" | "GUILD_TAGS" | "ENHANCED_ROLE_COLORS" | "GUESTS_ENABLED" | "PIN_PERMISSION_MIGRATION_COMPLETE")[];
28
+ botPermissions: ("CreateInstantInvite" | "KickMembers" | "BanMembers" | "Administrator" | "ManageChannels" | "ManageGuild" | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker" | "Stream" | "ViewChannel" | "SendMessages" | "SendTTSMessages" | "ManageMessages" | "EmbedLinks" | "AttachFiles" | "ReadMessageHistory" | "MentionEveryone" | "UseExternalEmojis" | "ViewGuildInsights" | "Connect" | "Speak" | "MuteMembers" | "DeafenMembers" | "MoveMembers" | "UseVAD" | "ChangeNickname" | "ManageNicknames" | "ManageRoles" | "ManageWebhooks" | "ManageEmojisAndStickers" | "ManageGuildExpressions" | "UseApplicationCommands" | "RequestToSpeak" | "ManageEvents" | "ManageThreads" | "CreatePublicThreads" | "CreatePrivateThreads" | "UseExternalStickers" | "SendMessagesInThreads" | "UseEmbeddedActivities" | "ModerateMembers" | "ViewCreatorMonetizationAnalytics" | "UseSoundboard" | "CreateGuildExpressions" | "CreateEvents" | "UseExternalSounds" | "SendVoiceMessages" | "SetVoiceChannelStatus" | "SendPolls" | "UseExternalApps" | "PinMessages" | "BypassSlowmode")[];
29
+ channelCount: number;
30
+ roleCount: number;
31
+ emojiCount: number;
32
+ stickerCount: number;
33
+ rulesChannelId: string | null;
34
+ publicUpdatesChannelId: string | null;
35
+ safetyAlertsChannelId: string | null;
36
+ invitesDisabledUntil: Date | null;
37
+ dmsDisabledUntil: Date | null;
38
+ botHighestRolePosition: number;
39
+ }>;
40
+ listChannels(input: {
41
+ guildId: string;
42
+ }): Promise<DiscordGuildHealthUnavailable> | Promise<{
43
+ available: true;
44
+ items: {
45
+ channelId: string;
46
+ name: string;
47
+ type: string;
48
+ parentId: string | null;
49
+ ageRestricted: boolean;
50
+ everyonePermissions: ("CreateInstantInvite" | "KickMembers" | "BanMembers" | "Administrator" | "ManageChannels" | "ManageGuild" | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker" | "Stream" | "ViewChannel" | "SendMessages" | "SendTTSMessages" | "ManageMessages" | "EmbedLinks" | "AttachFiles" | "ReadMessageHistory" | "MentionEveryone" | "UseExternalEmojis" | "ViewGuildInsights" | "Connect" | "Speak" | "MuteMembers" | "DeafenMembers" | "MoveMembers" | "UseVAD" | "ChangeNickname" | "ManageNicknames" | "ManageRoles" | "ManageWebhooks" | "ManageEmojisAndStickers" | "ManageGuildExpressions" | "UseApplicationCommands" | "RequestToSpeak" | "ManageEvents" | "ManageThreads" | "CreatePublicThreads" | "CreatePrivateThreads" | "UseExternalStickers" | "SendMessagesInThreads" | "UseEmbeddedActivities" | "ModerateMembers" | "ViewCreatorMonetizationAnalytics" | "UseSoundboard" | "CreateGuildExpressions" | "CreateEvents" | "UseExternalSounds" | "SendVoiceMessages" | "SetVoiceChannelStatus" | "SendPolls" | "UseExternalApps" | "PinMessages" | "BypassSlowmode")[];
51
+ botPermissions: ("CreateInstantInvite" | "KickMembers" | "BanMembers" | "Administrator" | "ManageChannels" | "ManageGuild" | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker" | "Stream" | "ViewChannel" | "SendMessages" | "SendTTSMessages" | "ManageMessages" | "EmbedLinks" | "AttachFiles" | "ReadMessageHistory" | "MentionEveryone" | "UseExternalEmojis" | "ViewGuildInsights" | "Connect" | "Speak" | "MuteMembers" | "DeafenMembers" | "MoveMembers" | "UseVAD" | "ChangeNickname" | "ManageNicknames" | "ManageRoles" | "ManageWebhooks" | "ManageEmojisAndStickers" | "ManageGuildExpressions" | "UseApplicationCommands" | "RequestToSpeak" | "ManageEvents" | "ManageThreads" | "CreatePublicThreads" | "CreatePrivateThreads" | "UseExternalStickers" | "SendMessagesInThreads" | "UseEmbeddedActivities" | "ModerateMembers" | "ViewCreatorMonetizationAnalytics" | "UseSoundboard" | "CreateGuildExpressions" | "CreateEvents" | "UseExternalSounds" | "SendVoiceMessages" | "SetVoiceChannelStatus" | "SendPolls" | "UseExternalApps" | "PinMessages" | "BypassSlowmode")[];
52
+ permissionsLocked: boolean | null;
53
+ }[];
54
+ }>;
55
+ listRoles(input: {
56
+ guildId: string;
57
+ }): Promise<{
58
+ available: true;
59
+ items: {
60
+ roleId: string;
61
+ name: string;
62
+ position: number;
63
+ managed: boolean;
64
+ memberCount: number;
65
+ permissions: ("CreateInstantInvite" | "KickMembers" | "BanMembers" | "Administrator" | "ManageChannels" | "ManageGuild" | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker" | "Stream" | "ViewChannel" | "SendMessages" | "SendTTSMessages" | "ManageMessages" | "EmbedLinks" | "AttachFiles" | "ReadMessageHistory" | "MentionEveryone" | "UseExternalEmojis" | "ViewGuildInsights" | "Connect" | "Speak" | "MuteMembers" | "DeafenMembers" | "MoveMembers" | "UseVAD" | "ChangeNickname" | "ManageNicknames" | "ManageRoles" | "ManageWebhooks" | "ManageEmojisAndStickers" | "ManageGuildExpressions" | "UseApplicationCommands" | "RequestToSpeak" | "ManageEvents" | "ManageThreads" | "CreatePublicThreads" | "CreatePrivateThreads" | "UseExternalStickers" | "SendMessagesInThreads" | "UseEmbeddedActivities" | "ModerateMembers" | "ViewCreatorMonetizationAnalytics" | "UseSoundboard" | "CreateGuildExpressions" | "CreateEvents" | "UseExternalSounds" | "SendVoiceMessages" | "SetVoiceChannelStatus" | "SendPolls" | "UseExternalApps" | "PinMessages" | "BypassSlowmode")[];
66
+ }[];
67
+ }>;
68
+ listMembers(input: {
69
+ guildId: string;
70
+ after?: string;
71
+ limit: number;
72
+ }): Promise<DiscordGuildHealthUnavailable | {
73
+ nextAfter?: string;
74
+ available: true;
75
+ items: {
76
+ userId: string;
77
+ bot: boolean;
78
+ system: boolean;
79
+ createdAt: Date;
80
+ joinedAt: Date | null;
81
+ timedOutUntil: Date | null;
82
+ publicFlags: ("Staff" | "Partner" | "Hypesquad" | "BugHunterLevel1" | "MFASMS" | "PremiumPromoDismissed" | "HypeSquadOnlineHouse1" | "HypeSquadOnlineHouse2" | "HypeSquadOnlineHouse3" | "PremiumEarlySupporter" | "TeamPseudoUser" | "HasUnreadUrgentMessages" | "BugHunterLevel2" | "VerifiedBot" | "VerifiedDeveloper" | "CertifiedModerator" | "BotHTTPInteractions" | "Spammer" | "DisablePremium" | "ActiveDeveloper" | "Quarantined" | "Collaborator" | "RestrictedCollaborator")[];
83
+ roleIds: string[];
84
+ }[];
85
+ }>;
86
+ listInvites(input: {
87
+ guildId: string;
88
+ }): Promise<DiscordGuildHealthUnavailable | {
89
+ available: true;
90
+ items: {
91
+ code: string;
92
+ channelId: string | null;
93
+ channelName: string | null;
94
+ channelExists: boolean;
95
+ inviterUserId: string | null;
96
+ createdAt: Date | null;
97
+ expiresAt: Date | null;
98
+ maxUses: number;
99
+ uses: number;
100
+ temporary: boolean;
101
+ }[];
102
+ }>;
103
+ listIntegrations(input: {
104
+ guildId: string;
105
+ }): Promise<DiscordGuildHealthUnavailable | {
106
+ available: true;
107
+ items: {
108
+ integrationId: string;
109
+ name: string;
110
+ type: import("discord.js").IntegrationType;
111
+ enabled: boolean;
112
+ syncing: boolean | null;
113
+ roleId: string | null;
114
+ roleExists: boolean;
115
+ }[];
116
+ }>;
117
+ listWebhooks(input: {
118
+ guildId: string;
119
+ }): Promise<DiscordGuildHealthUnavailable | {
120
+ available: true;
121
+ items: {
122
+ webhookId: string;
123
+ name: string;
124
+ channelId: string;
125
+ ownerUserId: string | null;
126
+ applicationId: null;
127
+ channelName: string | null;
128
+ }[];
129
+ }>;
130
+ listAutoModerationRules(input: {
131
+ guildId: string;
132
+ }): Promise<DiscordGuildHealthUnavailable | {
133
+ available: true;
134
+ items: {
135
+ ruleId: string;
136
+ name: string;
137
+ enabled: boolean;
138
+ triggerType: string;
139
+ eventType: string;
140
+ actionCount: number;
141
+ }[];
142
+ }>;
143
+ inspectAuditSummary(input: {
144
+ guildId: string;
145
+ }): Promise<DiscordGuildHealthUnavailable | {
146
+ available: true;
147
+ inspected: number;
148
+ highImpactActionsLastHour: number;
149
+ }>;
150
+ inspectHelyxModules(input: {
151
+ guildId: string;
152
+ batchSize?: number;
153
+ }): Promise<DiscordGuildHealthUnavailable | {
154
+ available: true;
155
+ items: {
156
+ moduleId: string;
157
+ name: string;
158
+ version: string;
159
+ enabled: boolean;
160
+ configured: boolean;
161
+ configurationValid: boolean;
162
+ resourceValidationValid: boolean | null;
163
+ activationValid: boolean | null;
164
+ catalogueMatches: boolean;
165
+ requiredCapabilities: readonly ("message-content" | "guild-members")[];
166
+ missingCapabilities: ("message-content" | "guild-members")[];
167
+ missingPermissions: string[];
168
+ healthStatus: "healthy" | "degraded" | "unhealthy";
169
+ healthMessage: string | null;
170
+ diagnosticsAvailable: boolean;
171
+ }[];
172
+ }>;
173
+ }
174
+ //# sourceMappingURL=discord-guild-health.d.ts.map