@helyx/bot 1.5.4 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +5 -2
  3. package/command-root-catalogue.json +21 -5
  4. package/dist/cloud-connector.js +2 -0
  5. package/dist/cloud-control-dispatcher.js +20 -0
  6. package/dist/command-registration-policy.js +2 -0
  7. package/dist/control-directory-contracts.d.ts +50 -0
  8. package/dist/control-directory-contracts.js +2 -0
  9. package/dist/control-server-schemas.d.ts +3 -3
  10. package/dist/control-server.d.ts +10 -46
  11. package/dist/control-server.js +50 -3
  12. package/dist/creator-invite-service.d.ts +7 -0
  13. package/dist/creator-invite-service.js +17 -0
  14. package/dist/developer-operations-module.js +0 -55
  15. package/dist/diagnostic-command.js +1 -0
  16. package/dist/discord-guild-health.d.ts +2 -2
  17. package/dist/discord-guild-health.js +1 -0
  18. package/dist/events.d.ts +1 -0
  19. package/dist/events.js +2 -0
  20. package/dist/help-credentials.d.ts +25 -0
  21. package/dist/help-credentials.js +149 -0
  22. package/dist/help-module.d.ts +27 -0
  23. package/dist/help-module.js +288 -0
  24. package/dist/help-runtime.d.ts +3 -0
  25. package/dist/help-runtime.js +42 -0
  26. package/dist/help-transport.d.ts +53 -0
  27. package/dist/help-transport.js +267 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +10 -0
  30. package/dist/interactions.js +10 -6
  31. package/dist/process-info-module.d.ts +3 -0
  32. package/dist/process-info-module.js +97 -0
  33. package/dist/server-overview-control.d.ts +35 -0
  34. package/dist/server-overview-control.js +362 -0
  35. package/dist/server-overview-discord.d.ts +33 -0
  36. package/dist/server-overview-discord.js +212 -0
  37. package/dist/server-overview-profile.d.ts +8 -0
  38. package/dist/server-overview-profile.js +62 -0
  39. package/dist/server-overview-routes.d.ts +4 -0
  40. package/dist/server-overview-routes.js +41 -0
  41. package/dist/shard-contracts.d.ts +66 -0
  42. package/dist/shard-contracts.js +23 -0
  43. package/dist/shard-coordinator.d.ts +1 -1
  44. package/dist/shard-router.d.ts +9 -0
  45. package/dist/shard-router.js +6 -0
  46. package/dist/shard-supervisor.d.ts +1 -1
  47. package/dist/shard-supervisor.js +8 -0
  48. package/dist/shard-worker.d.ts +1 -1
  49. package/dist/shard-worker.js +8 -0
  50. package/dist/verification-service.d.ts +4 -0
  51. package/dist/verification-service.js +10 -0
  52. package/documentation/README.md +1 -0
  53. package/documentation/examples/.env.example +29 -85
  54. package/documentation/guides/help-command.md +62 -0
  55. package/documentation/reference/environment.md +7 -6
  56. package/documentation/runbooks/installation-and-upgrades.md +4 -1
  57. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add the built-in private /help command with module selection, reviewed answer choices and local self-host fallback. Keep question selection and its answer in the same private message, show the original question above choices, and use a compact FAQ ID footer. Add opt-in encrypted online Help enrolment and fail closed for unavailable hosted publication state. Integrate authenticated Server Overview reads, bounded Discord profiles and protected invite provenance across direct, Cloud Connect and sharded runtimes.
8
+
9
+ ## 1.6.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Add public `/proc` process information for hosted and self-hosted bots, replacing
14
+ the private `/dev proc` path. Keep the reply private and independent of database
15
+ availability.
16
+
17
+ Add a PebbleHost starter profile with direct Node CLI scripts, flat starter ZIPs,
18
+ compact quoted environment examples and matching guided environment builder
19
+ output. Preserve existing custom settings and encryption keys when rebuilding
20
+ an environment file, and recognize quoted placeholders during setup.
21
+
3
22
  ## 1.5.4
4
23
 
5
24
  ### Patch Changes
package/README.md CHANGED
@@ -164,8 +164,11 @@ paths are read-only:
164
164
 
165
165
  - `/dev shards` reports bounded shard health, including the unsharded
166
166
  compatibility baseline;
167
- - `/dev bot` reports the bot build and bounded installed-module versions;
168
- - `/dev proc` reports safe metrics for the process handling the command.
167
+ - `/dev bot` reports the bot build and bounded installed-module versions.
168
+
169
+ The public `/proc` command reports the same safe process metrics on hosted and
170
+ self-hosted bots, in a server or bot DM. Anyone can run it; replies are ephemeral.
171
+ It works independently of database and module enablement checks.
169
172
 
170
173
  On the official hosted bot only,
171
174
  `HELYX_HOSTED_MODERATION_ENABLED=true` adds `/dev leave` and `/dev blacklist`.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "revision": 2,
3
+ "revision": 6,
4
4
  "roots": [
5
5
  { "name": "access", "type": 1, "ownerModuleId": "helyx.account-gate" },
6
6
  { "name": "access", "type": 1, "ownerModuleId": "helyx.verification" },
@@ -17,6 +17,7 @@
17
17
  { "name": "games", "type": 1, "ownerModuleId": "helyx.counting" },
18
18
  { "name": "games", "type": 1, "ownerModuleId": "helyx.trivia" },
19
19
  { "name": "giveaway", "type": 1, "ownerModuleId": "helyx.giveaways" },
20
+ { "name": "help", "type": 1, "ownerModuleId": "helyx.help" },
20
21
  { "name": "helyx", "type": 1, "ownerModuleId": "helyx.cloud-connect" },
21
22
  { "name": "helyx", "type": 1, "ownerModuleId": "helyx.management" },
22
23
  { "name": "helyx", "type": 1, "ownerModuleId": "helyx.oauth" },
@@ -26,6 +27,7 @@
26
27
  { "name": "mod", "type": 1, "ownerModuleId": "helyx.moderation" },
27
28
  { "name": "nexus", "type": 1, "ownerModuleId": "helyx.nexus-core" },
28
29
  { "name": "poll", "type": 1, "ownerModuleId": "helyx.polls" },
30
+ { "name": "proc", "type": 1, "ownerModuleId": "helyx.process-info" },
29
31
  { "name": "profile", "type": 1, "ownerModuleId": "helyx.achievements" },
30
32
  { "name": "profile", "type": 1, "ownerModuleId": "helyx.levels" },
31
33
  { "name": "submit", "type": 1, "ownerModuleId": "helyx.feedback" },
@@ -61,6 +63,13 @@
61
63
  "sourcePath": "apps/bot/src/diagnostic-command.ts",
62
64
  "version": "0.1.0"
63
65
  },
66
+ {
67
+ "commands": ["help"],
68
+ "moduleId": "helyx.help",
69
+ "releaseStatus": "live",
70
+ "sourcePath": "apps/bot/src/help-module.ts",
71
+ "version": "0.1.0"
72
+ },
64
73
  {
65
74
  "commands": ["helyx.modules"],
66
75
  "moduleId": "helyx.management",
@@ -96,6 +105,13 @@
96
105
  "sourcePath": "apps/bot/src/privacy-module.ts",
97
106
  "version": "0.1.0"
98
107
  },
108
+ {
109
+ "commands": ["proc"],
110
+ "moduleId": "helyx.process-info",
111
+ "releaseStatus": "live",
112
+ "sourcePath": "apps/bot/src/process-info-module.ts",
113
+ "version": "0.1.0"
114
+ },
99
115
  {
100
116
  "commands": ["helyx.bug"],
101
117
  "moduleId": "helyx.support",
@@ -116,7 +132,7 @@
116
132
  "moduleId": "helyx.account-gate",
117
133
  "packageName": "@helyx/module-account-gate",
118
134
  "releaseStatus": "live",
119
- "version": "2.1.2"
135
+ "version": "2.1.3"
120
136
  },
121
137
  {
122
138
  "commands": [
@@ -127,7 +143,7 @@
127
143
  "moduleId": "helyx.achievements",
128
144
  "packageName": "@helyx/module-achievements",
129
145
  "releaseStatus": "live",
130
- "version": "2.1.3"
146
+ "version": "2.1.4"
131
147
  },
132
148
  {
133
149
  "commands": ["afk"],
@@ -276,7 +292,7 @@
276
292
  "moduleId": "helyx.reactionroles",
277
293
  "packageName": "@helyx/module-reactionroles",
278
294
  "releaseStatus": "live",
279
- "version": "1.1.2"
295
+ "version": "1.1.3"
280
296
  },
281
297
  {
282
298
  "commands": [],
@@ -448,5 +464,5 @@
448
464
  }
449
465
  ]
450
466
  },
451
- "fingerprint": "a33abb53dd9862ee73e43515eae7fa049cd62e8c170473f82bd268878621d55e"
467
+ "fingerprint": "9bd09abbfbaaac0ddb932b6e25911b9a2eb8a3dd5febe92f5862ef903534b2e5"
452
468
  }
@@ -904,6 +904,8 @@ function connectorCapabilities() {
904
904
  "installation.health.read.v1",
905
905
  "guild.state.read.v1",
906
906
  "guild.user-identities.read.v1",
907
+ "guild.overview.read.v1",
908
+ "guild.overview.invite.revoke.v1",
907
909
  "guild.dashboard.access.read.v1",
908
910
  "guild.access-policy.read.v1",
909
911
  "guild.access-policy.write.v1",
@@ -18,6 +18,26 @@ export class CloudControlDispatcher {
18
18
  : undefined;
19
19
  let result;
20
20
  switch (request.operation) {
21
+ case "guild.overview.read.v1": {
22
+ const input = parseOperationInput(request.operation, request.input);
23
+ if (!this.control.readServerOverview)
24
+ throw new Error("Overview is unavailable");
25
+ result = await this.control.readServerOverview({
26
+ ...input,
27
+ actorUserId: requiredActor(actorUserId),
28
+ });
29
+ break;
30
+ }
31
+ case "guild.overview.invite.revoke.v1": {
32
+ const input = parseOperationInput(request.operation, request.input);
33
+ if (!this.control.revokeServerOverviewInvite)
34
+ throw new Error("Invite management is unavailable");
35
+ result = await this.control.revokeServerOverviewInvite({
36
+ ...input,
37
+ actorUserId: requiredActor(actorUserId),
38
+ });
39
+ break;
40
+ }
21
41
  case "installation.health.read.v1":
22
42
  parseOperationInput(request.operation, request.input);
23
43
  result = {
@@ -6,6 +6,8 @@ const platformGlobalModuleIds = new Set([
6
6
  "helyx.oauth",
7
7
  "helyx.permissions",
8
8
  "helyx.privacy",
9
+ "helyx.process-info",
10
+ "helyx.help",
9
11
  "helyx.premium-activation",
10
12
  ]);
11
13
  const deploymentModuleIds = new Set(["helyx.cloud-connect"]);
@@ -0,0 +1,50 @@
1
+ import type { DiscordUserIdentityControl } from "@helyx/control-protocol/discord-user-identities";
2
+ import type { MessageResponse, PermissionActor, PublicAchievementProjectionPage } from "@helyx/sdk";
3
+ import type { OverviewDiscordDirectory } from "./server-overview-discord.js";
4
+ export interface BotControlGuildResources {
5
+ botGuildPermissions?: string[];
6
+ channels: Array<{
7
+ id: string;
8
+ name: string;
9
+ type: "guild_text" | "guild_announcement" | "guild_category" | "guild_forum" | "guild_media";
10
+ }>;
11
+ roles: Array<{
12
+ id: string;
13
+ name: string;
14
+ colour: string;
15
+ position: number;
16
+ }>;
17
+ }
18
+ export interface BotPublicModuleProjection {
19
+ server: {
20
+ id: string;
21
+ name: string;
22
+ memberCount: number;
23
+ };
24
+ projection: PublicAchievementProjectionPage;
25
+ }
26
+ export type BotGuildPresenceState = "installed" | "not_installed" | "unknown" | "unavailable";
27
+ export interface BotGuildPresenceObservation {
28
+ guildId: string;
29
+ state: BotGuildPresenceState;
30
+ observedAt: string;
31
+ }
32
+ export interface BotControlGuildResourceDirectory extends DiscordUserIdentityControl {
33
+ overview?: OverviewDiscordDirectory;
34
+ listGuildPresence(guildIds: readonly string[]): Promise<BotGuildPresenceObservation[]>;
35
+ getGuildResources(guildId: string): Promise<BotControlGuildResources>;
36
+ getGuildPublicSummary(guildId: string): Promise<{
37
+ id: string;
38
+ name: string;
39
+ memberCount: number;
40
+ }>;
41
+ sendToChannel(channelId: string, response: MessageResponse): Promise<{
42
+ messageId: string;
43
+ }>;
44
+ deleteChannelMessage(channelId: string, messageId: string): Promise<void>;
45
+ editChannelMessage(channelId: string, messageId: string, response: MessageResponse): Promise<void>;
46
+ addChannelMessageReaction(channelId: string, messageId: string, emojiIdentifier: string): Promise<void>;
47
+ resolveActor(guildId: string, actorUserId: string): Promise<PermissionActor>;
48
+ sendPrivateSupportNotification?(guildId: string, channelId: string, response: MessageResponse, notificationId: string): Promise<void>;
49
+ }
50
+ //# sourceMappingURL=control-directory-contracts.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=control-directory-contracts.js.map
@@ -8,9 +8,9 @@ export declare const permissionState: z.ZodObject<{
8
8
  module: z.ZodObject<{
9
9
  preset: z.ZodEnum<{
10
10
  everyone: "everyone";
11
+ custom: "custom";
11
12
  moderators: "moderators";
12
13
  administrators: "administrators";
13
- custom: "custom";
14
14
  }>;
15
15
  allowedRoleIds: z.ZodArray<z.ZodString>;
16
16
  deniedRoleIds: z.ZodArray<z.ZodString>;
@@ -22,9 +22,9 @@ export declare const permissionState: z.ZodObject<{
22
22
  policy: z.ZodObject<{
23
23
  preset: z.ZodEnum<{
24
24
  everyone: "everyone";
25
+ custom: "custom";
25
26
  moderators: "moderators";
26
27
  administrators: "administrators";
27
- custom: "custom";
28
28
  }>;
29
29
  allowedRoleIds: z.ZodArray<z.ZodString>;
30
30
  deniedRoleIds: z.ZodArray<z.ZodString>;
@@ -59,8 +59,8 @@ export declare const managedResourceDetailSchema: z.ZodObject<{
59
59
  availableActionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
60
60
  notices: z.ZodOptional<z.ZodArray<z.ZodObject<{
61
61
  kind: z.ZodEnum<{
62
- info: "info";
63
62
  warning: "warning";
63
+ info: "info";
64
64
  }>;
65
65
  message: z.ZodString;
66
66
  }, z.core.$strict>>>;
@@ -1,9 +1,13 @@
1
+ import type { BotControlGuildResources, BotPublicModuleProjection, BotGuildPresenceObservation, BotControlGuildResourceDirectory } from "./control-directory-contracts.js";
2
+ export type { BotControlGuildResources, BotPublicModuleProjection, BotGuildPresenceState, BotGuildPresenceObservation, BotControlGuildResourceDirectory, } from "./control-directory-contracts.js";
1
3
  import { type DiscordUserIdentityControl, type DiscordUserIdentityRequest } from "@helyx/control-protocol/discord-user-identities";
4
+ import type { ServerOverviewControl } from "@helyx/control-protocol";
5
+ import { type OverviewDiscordDirectory } from "./server-overview-discord.js";
2
6
  import { AuditRepository, GuildAccessPolicyRepository, InstallationRepository, ModuleLoggingPolicyRepository, PermissionPolicyRepository, type DatabasePool, type GuildShardOwnership } from "@helyx/database";
3
7
  import type { MysqlPool } from "@helyx/database-mysql";
4
8
  import type { CreatorOnboardingRequest, CreatorOnboardingResponse } from "@helyx/sdk";
5
9
  export { mapBotControlError, safeBotControlFieldError, } from "./control-error-mapping.js";
6
- import { type DashboardActionInspection, type DashboardActionResult, type FeatureAccessDecision, type GuildAccessPolicy, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type OAuthConsumerCleanupResult, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type PublicAchievementProjectionPage, type ServiceAccess } from "@helyx/sdk";
10
+ import { type DashboardActionInspection, type DashboardActionResult, type FeatureAccessDecision, type GuildAccessPolicy, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type OAuthConsumerCleanupResult, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type ServiceAccess } from "@helyx/sdk";
7
11
  import { type ActionReceipt, type SignedAssessmentEnvelope } from "@helyx/verification-contracts";
8
12
  import { type Client } from "discord.js";
9
13
  import { type FastifyInstance } from "fastify";
@@ -29,53 +33,10 @@ interface DashboardLoggingPolicy {
29
33
  enabled: boolean;
30
34
  version: number;
31
35
  }
32
- export interface BotControlGuildResources {
33
- channels: Array<{
34
- id: string;
35
- name: string;
36
- type: "guild_text" | "guild_announcement" | "guild_category" | "guild_forum" | "guild_media";
37
- }>;
38
- roles: Array<{
39
- id: string;
40
- name: string;
41
- colour: string;
42
- position: number;
43
- }>;
44
- }
45
- export interface BotPublicModuleProjection {
46
- server: {
47
- id: string;
48
- name: string;
49
- memberCount: number;
50
- };
51
- projection: PublicAchievementProjectionPage;
52
- }
53
- export type BotGuildPresenceState = "installed" | "not_installed" | "unknown" | "unavailable";
54
- export interface BotGuildPresenceObservation {
55
- guildId: string;
56
- state: BotGuildPresenceState;
57
- observedAt: string;
58
- }
59
- export interface BotControlGuildResourceDirectory extends DiscordUserIdentityControl {
60
- listGuildPresence(guildIds: readonly string[]): Promise<BotGuildPresenceObservation[]>;
61
- getGuildResources(guildId: string): Promise<BotControlGuildResources>;
62
- getGuildPublicSummary(guildId: string): Promise<{
63
- id: string;
64
- name: string;
65
- memberCount: number;
66
- }>;
67
- sendToChannel(channelId: string, response: MessageResponse): Promise<{
68
- messageId: string;
69
- }>;
70
- deleteChannelMessage(channelId: string, messageId: string): Promise<void>;
71
- editChannelMessage(channelId: string, messageId: string, response: MessageResponse): Promise<void>;
72
- addChannelMessageReaction(channelId: string, messageId: string, emojiIdentifier: string): Promise<void>;
73
- resolveActor(guildId: string, actorUserId: string): Promise<PermissionActor>;
74
- sendPrivateSupportNotification?(guildId: string, channelId: string, response: MessageResponse, notificationId: string): Promise<void>;
75
- }
76
36
  export declare class DiscordGuildResourceDirectory implements BotControlGuildResourceDirectory {
77
37
  #private;
78
38
  private readonly client;
39
+ readonly overview: OverviewDiscordDirectory;
79
40
  constructor(client: Client);
80
41
  resolveDiscordUserIdentities(input: DiscordUserIdentityRequest): Promise<{
81
42
  guildId: string;
@@ -110,6 +71,7 @@ export interface SupportNotificationInput {
110
71
  source: "dashboard_bug" | "dashboard_support" | "dashboard_security" | "discord_command";
111
72
  }
112
73
  export interface BotControlState {
74
+ botGuildPermissions?: string[];
113
75
  discordCapabilities?: {
114
76
  messageContent: boolean;
115
77
  };
@@ -134,7 +96,7 @@ export interface DashboardGuildAccessState extends GuildAccessPolicy {
134
96
  actorHighestRolePosition: number | null;
135
97
  updatedAt: string | null;
136
98
  }
137
- export interface BotControlHandler extends FirstTimeCommandSetupHandler, DashboardActionHandler, ManagedResourceCreateDefaultsControl, ChannelAuditControl, DiscordUserIdentityControl {
99
+ export interface BotControlHandler extends ServerOverviewControl, FirstTimeCommandSetupHandler, DashboardActionHandler, ManagedResourceCreateDefaultsControl, ChannelAuditControl, DiscordUserIdentityControl {
138
100
  creatorOnboarding?(input: CreatorOnboardingRequest): Promise<CreatorOnboardingResponse>;
139
101
  cleanupOAuthConsumer(input: {
140
102
  consumerBindingId: string;
@@ -363,6 +325,8 @@ export declare class BotControlService implements BotControlHandler {
363
325
  private readonly supportNotifications?;
364
326
  private readonly presenceOwnership?;
365
327
  private readonly presenceShardCount?;
328
+ readonly readServerOverview: NonNullable<ServerOverviewControl["readServerOverview"]>;
329
+ readonly revokeServerOverviewInvite: NonNullable<ServerOverviewControl["revokeServerOverviewInvite"]>;
366
330
  readonly readChannelAuditConfiguration: Required<ChannelAuditControl>["readChannelAuditConfiguration"];
367
331
  readonly checkChannelAudit: Required<ChannelAuditControl>["checkChannelAudit"];
368
332
  constructor(databaseOrPersistence: DatabasePool | BotControlPersistence, modules: readonly ModuleDefinition[], resources: BotControlGuildResourceDirectory, services: ServiceAccess, privacyExecutor?: PrivacyProviderExecutor, supportNotifications?: {
@@ -1,4 +1,8 @@
1
+ import { overviewReadContext } from "./server-overview-profile.js";
1
2
  import { discordUserIdentityRequestSchema, } from "@helyx/control-protocol/discord-user-identities";
3
+ import { createServerOverviewControl } from "./server-overview-control.js";
4
+ import { createOverviewDiscordDirectory, } from "./server-overview-discord.js";
5
+ import { registerServerOverviewControlRoutes } from "./server-overview-routes.js";
2
6
  import { createDiscordUserIdentityLookup, } from "./discord-user-identities.js";
3
7
  import { AuditRepository, GuildAccessPolicyRepository, InstallationRepository, ModuleLoggingPolicyRepository, OptimisticConcurrencyError, PermissionPolicyRepository, withTransaction, } from "@helyx/database";
4
8
  import { safeError } from "@helyx/core";
@@ -44,9 +48,11 @@ import { validateDiscordUserSettingSelections, validateDiscordSettingSelections,
44
48
  import { configurationValue, guildAccessPolicy, managedResourceDeleteResultSchema, managedResourceDetailSchema, managedResourceFilters, managedResourceObject, managedResourcePageSchema, managedResourceRelationPageSchema, moduleId, mutationContext, permissionState, resourceActionId, resourceCursor, resourceId, snowflake, } from "./control-server-schemas.js";
45
49
  export class DiscordGuildResourceDirectory {
46
50
  client;
51
+ overview;
47
52
  #identities;
48
53
  constructor(client) {
49
54
  this.client = client;
55
+ this.overview = createOverviewDiscordDirectory(client);
50
56
  this.#identities = createDiscordUserIdentityLookup(client);
51
57
  }
52
58
  resolveDiscordUserIdentities(input) {
@@ -107,6 +113,9 @@ export class DiscordGuildResourceDirectory {
107
113
  colour: role.hexColor,
108
114
  position: role.position,
109
115
  })),
116
+ ...(guild.members?.me
117
+ ? { botGuildPermissions: guild.members.me.permissions.toArray() }
118
+ : {}),
110
119
  };
111
120
  }
112
121
  catch (error) {
@@ -242,6 +251,9 @@ export class BotControlService {
242
251
  #services;
243
252
  #privacyProviders;
244
253
  #persistence;
254
+ #overview;
255
+ readServerOverview;
256
+ revokeServerOverviewInvite;
245
257
  readChannelAuditConfiguration;
246
258
  checkChannelAudit;
247
259
  constructor(databaseOrPersistence, modules, resources, services, privacyExecutor, supportNotifications, presenceOwnership, presenceShardCount, statusModules) {
@@ -276,6 +288,33 @@ export class BotControlService {
276
288
  });
277
289
  this.readChannelAuditConfiguration = audit.readChannelAuditConfiguration;
278
290
  this.checkChannelAudit = audit.checkChannelAudit;
291
+ this.#overview = createServerOverviewControl({
292
+ modules: this.#modules,
293
+ services: this.#services,
294
+ permissions: this.#permissions,
295
+ directory: resources.overview,
296
+ auditInviteRevoke: async (input) => {
297
+ await this.#persistence.repositories.audit.append({
298
+ guildId: input.guildId,
299
+ actorUserId: input.actorUserId,
300
+ action: `server.invite.${input.outcome}`,
301
+ source: "dashboard",
302
+ correlationId: input.correlationId,
303
+ targetType: "discord-invite",
304
+ targetId: createHash("sha256")
305
+ .update(`${input.guildId}:${input.code}`)
306
+ .digest("hex"),
307
+ });
308
+ },
309
+ resolveActor: (guildId, actorUserId) => resources.resolveActor(guildId, actorUserId),
310
+ readGuildPolicy: (guildId) => this.#readGuildAccessPolicy(guildId),
311
+ listModules: (guildId) => this.#persistence.repositories.installations.listModules(guildId),
312
+ requireAccessible: (guildId, moduleId) => this.#requireModuleAccessible(guildId, moduleId),
313
+ context: (guildId, actor) => overviewReadContext(guildId, actor, this.#services),
314
+ });
315
+ this.readServerOverview = this.#overview.readServerOverview.bind(this.#overview);
316
+ this.revokeServerOverviewInvite =
317
+ this.#overview.revokeServerOverviewInvite.bind(this.#overview);
279
318
  }
280
319
  async creatorOnboarding(input) {
281
320
  return executeCreatorOnboarding(input, {
@@ -994,7 +1033,9 @@ export class BotControlService {
994
1033
  const declaredIntents = declaration?.creationIntents ?? ["publish"];
995
1034
  if (!declaredIntents.includes(input.intent))
996
1035
  throw new DashboardActionValidationError("This creation intent is not declared by the module.");
997
- return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.create(context, input.value, input.intent)), managedResourceColumnKeys(module, input.resourceType));
1036
+ const result = normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.create(context, input.value, input.intent)), managedResourceColumnKeys(module, input.resourceType));
1037
+ this.#overview.invalidate(input.guildId);
1038
+ return result;
998
1039
  }
999
1040
  async updateManagedResource(input) {
1000
1041
  await this.#requireModuleAccessible(input.guildId, input.moduleId);
@@ -1005,11 +1046,13 @@ export class BotControlService {
1005
1046
  declaration?.featureId,
1006
1047
  ...resourceFieldFeatureIds(declaration?.fields ?? [], input.value),
1007
1048
  ]);
1008
- return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.update(await this.#managedResourceContext(input, module, "update"), {
1049
+ const result = normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.update(await this.#managedResourceContext(input, module, "update"), {
1009
1050
  resourceId: input.resourceId,
1010
1051
  expectedRevision: input.expectedRevision,
1011
1052
  value: input.value,
1012
1053
  })), managedResourceColumnKeys(module, input.resourceType));
1054
+ this.#overview.invalidate(input.guildId);
1055
+ return result;
1013
1056
  }
1014
1057
  async deleteManagedResource(input) {
1015
1058
  await this.#requireModuleAccessible(input.guildId, input.moduleId);
@@ -1017,10 +1060,12 @@ export class BotControlService {
1017
1060
  this.#requireManagedResourceOperation(module, input.resourceType, "delete");
1018
1061
  const declaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
1019
1062
  await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [declaration?.featureId]);
1020
- return managedResourceDeleteResultSchema.parse(await resource.delete(await this.#managedResourceContext(input, module, "delete"), {
1063
+ const result = managedResourceDeleteResultSchema.parse(await resource.delete(await this.#managedResourceContext(input, module, "delete"), {
1021
1064
  resourceId: input.resourceId,
1022
1065
  expectedRevision: input.expectedRevision,
1023
1066
  }));
1067
+ this.#overview.invalidate(input.guildId);
1068
+ return result;
1024
1069
  }
1025
1070
  async executeManagedResourceAction(input) {
1026
1071
  await this.#requireModuleAccessible(input.guildId, input.moduleId);
@@ -1046,6 +1091,7 @@ export class BotControlService {
1046
1091
  expectedRevision: input.expectedRevision,
1047
1092
  value: input.value,
1048
1093
  });
1094
+ this.#overview.invalidate(input.guildId);
1049
1095
  return result === null
1050
1096
  ? null
1051
1097
  : normalizeManagedResourceDetail(managedResourceDetailSchema.parse(result), managedResourceColumnKeys(module, input.resourceType));
@@ -1523,6 +1569,7 @@ export async function createBotControlServer(input) {
1523
1569
  return input.service.setModuleStatus({ ...params, ...body });
1524
1570
  });
1525
1571
  registerDashboardActionRoutes(server, input.service);
1572
+ registerServerOverviewControlRoutes(server, input.service);
1526
1573
  registerChannelAuditRoutes(server, input.service);
1527
1574
  server.get("/v1/guilds/:guildId/modules/:moduleId/resources/:resourceType", async (request) => {
1528
1575
  const params = z
@@ -8,6 +8,13 @@ export declare class DiscordCreatorInviteService implements CreatorInviteService
8
8
  private readonly repository;
9
9
  private readonly services;
10
10
  private readonly module;
11
+ identifyInvites(input: {
12
+ guildId: string;
13
+ codes: readonly string[];
14
+ }): Promise<readonly {
15
+ code: string;
16
+ reference: string;
17
+ }[]>;
11
18
  constructor(client: Client, repository: CreatorInviteRepository, services: ServiceAccess, module: ModuleDefinition | undefined, encryption: string | {
12
19
  inviteEncryptionKey: string;
13
20
  });
@@ -16,6 +16,23 @@ export class DiscordCreatorInviteService {
16
16
  repository;
17
17
  services;
18
18
  module;
19
+ async identifyInvites(input) {
20
+ validateId(input.guildId);
21
+ if (input.codes.length > 15 ||
22
+ input.codes.some((code) => !/^[A-Za-z0-9_-]{1,100}$/u.test(code)))
23
+ throw new CreatorInviteOperationError("unavailable");
24
+ this.#ownedGuild(input.guildId);
25
+ if (!(await this.repository.isInstalled()))
26
+ return [];
27
+ const requested = new Set(input.codes);
28
+ const found = [];
29
+ for (const record of await this.repository.listOwnedForGuild(input.guildId)) {
30
+ const code = this.#decrypt(record);
31
+ if (requested.has(code))
32
+ found.push({ code, reference: record.reference });
33
+ }
34
+ return found;
35
+ }
19
36
  #key;
20
37
  #rest = null;
21
38
  #cleanupCursor;
@@ -27,7 +27,6 @@ export function createDeveloperOperationsModule(input) {
27
27
  "dev.shards",
28
28
  "dev.bot",
29
29
  "dev.modules",
30
- "dev.proc",
31
30
  "dev.trivia-data",
32
31
  "dev.leave",
33
32
  "dev.blacklist",
@@ -166,41 +165,6 @@ export function createDeveloperOperationsModule(input) {
166
165
  });
167
166
  },
168
167
  },
169
- {
170
- name: "proc",
171
- permissionId: "dev.proc",
172
- description: "Show safe process resource and uptime information",
173
- acknowledgement: "immediate-response",
174
- authorization: "self-authorized",
175
- async execute(context) {
176
- if (!(await authorizeDeveloperCommand(context)))
177
- return;
178
- const memory = process.memoryUsage();
179
- const cpu = process.cpuUsage();
180
- await context.reply({
181
- ephemeral: true,
182
- componentsV2: {
183
- accentColor: 0x2563eb,
184
- text: [
185
- {
186
- content: [
187
- "## Process information",
188
- `Runtime: ${input.processLabel}`,
189
- `Uptime: ${formatDuration(process.uptime())}`,
190
- `RSS memory: ${formatBytes(memory.rss)}`,
191
- `Heap: ${formatBytes(memory.heapUsed)} used / ${formatBytes(memory.heapTotal)} allocated`,
192
- `External memory: ${formatBytes(memory.external)}`,
193
- `CPU time: ${formatCpu(cpu.user)} user / ${formatCpu(cpu.system)} system`,
194
- `Node: ${safeInlineCode(process.version)}`,
195
- `Platform: ${safeInlineCode(`${process.platform}/${process.arch}`)}`,
196
- `PID: ${process.pid}`,
197
- ].join("\n"),
198
- },
199
- ],
200
- },
201
- });
202
- },
203
- },
204
168
  {
205
169
  name: "trivia-data",
206
170
  permissionId: "dev.trivia-data",
@@ -411,25 +375,6 @@ function categoryLabel(category) {
411
375
  .join(" ")
412
376
  .replace("And", "and");
413
377
  }
414
- function formatBytes(value) {
415
- return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
416
- }
417
- function formatCpu(microseconds) {
418
- return `${(microseconds / 1_000_000).toFixed(2)}s`;
419
- }
420
- function formatDuration(seconds) {
421
- const total = Math.max(0, Math.floor(seconds));
422
- const days = Math.floor(total / 86_400);
423
- const hours = Math.floor((total % 86_400) / 3_600);
424
- const minutes = Math.floor((total % 3_600) / 60);
425
- const remainder = total % 60;
426
- return [
427
- ...(days > 0 ? [`${days}d`] : []),
428
- ...(days > 0 || hours > 0 ? [`${hours}h`] : []),
429
- `${minutes}m`,
430
- `${remainder}s`,
431
- ].join(" ");
432
- }
433
378
  export class WorkerShardHealthProvider {
434
379
  #pending = new Map();
435
380
  getHealth() {
@@ -22,6 +22,7 @@ const CORE_MODULE_IDS = new Set([
22
22
  "helyx.support",
23
23
  "helyx.oauth",
24
24
  "helyx.privacy",
25
+ "helyx.process-info",
25
26
  "helyx.cloud-connect",
26
27
  "helyx.premium-activation",
27
28
  "helyx.developer-operations",
@@ -163,8 +163,8 @@ export declare class DiscordGuildHealthInspection implements DiscordGuildHealthI
163
163
  resourceValidationValid: boolean | null;
164
164
  activationValid: boolean | null;
165
165
  catalogueMatches: boolean;
166
- requiredCapabilities: readonly ("message-content" | "guild-members")[];
167
- missingCapabilities: ("message-content" | "guild-members")[];
166
+ requiredCapabilities: readonly ("guild-members" | "message-content")[];
167
+ missingCapabilities: ("guild-members" | "message-content")[];
168
168
  missingPermissions: string[];
169
169
  healthStatus: "healthy" | "degraded" | "unhealthy";
170
170
  healthMessage: string | null;
@@ -8,6 +8,7 @@ const INTERNAL_HELYX_MODULE_IDS = new Set([
8
8
  "helyx.management",
9
9
  "helyx.permissions",
10
10
  "helyx.support",
11
+ "helyx.process-info",
11
12
  ]);
12
13
  export function moduleCatalogueMatches(packagedManifest, executableManifest) {
13
14
  return (packagedManifest !== null &&
package/dist/events.d.ts CHANGED
@@ -113,6 +113,7 @@ export declare class DiscordResourceInspection implements DiscordResourceInspect
113
113
  userId: string;
114
114
  displayName: string | null;
115
115
  avatarUrl?: string;
116
+ isBot?: boolean;
116
117
  }[]>;
117
118
  searchMembers(input: {
118
119
  guildId: string;
package/dist/events.js CHANGED
@@ -1802,6 +1802,7 @@ export class DiscordResourceInspection {
1802
1802
  const member = members.get(userId) ?? null;
1803
1803
  const cachedUser = this.client.users?.cache?.get(userId);
1804
1804
  const avatarUrl = memberAvatarUrl(member);
1805
+ const isBot = member?.user?.bot ?? cachedUser?.bot;
1805
1806
  return {
1806
1807
  userId,
1807
1808
  displayName: member?.displayName ??
@@ -1809,6 +1810,7 @@ export class DiscordResourceInspection {
1809
1810
  cachedUser?.username ??
1810
1811
  null,
1811
1812
  ...(avatarUrl ? { avatarUrl } : {}),
1813
+ ...(typeof isBot === "boolean" ? { isBot } : {}),
1812
1814
  };
1813
1815
  });
1814
1816
  }