@helyx/bot 0.4.44 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ import { canUseCommand } from "@helyx/core";
4
4
  import { commandRegistrationPolicy, contributionRegistrationPolicy, } from "./command-registration-policy.js";
5
5
  import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, EmbedBuilder, Events, GuildMember, ApplicationIntegrationType, ChannelType, InteractionContextType, LabelBuilder, MessageFlags, MentionableSelectMenuBuilder, ModalBuilder, RoleSelectMenuBuilder, Routes, StringSelectMenuBuilder, SlashCommandBuilder, TextInputBuilder, TextInputStyle, UserSelectMenuBuilder, TextDisplayBuilder, PermissionFlagsBits, escapeMarkdown, } from "discord.js";
6
6
  import { collectCommandRegistrations, } from "./shared-commands.js";
7
+ import { resolveDirectCommandPath } from "./command-path-contract.js";
7
8
  const buttonStyles = {
8
9
  primary: ButtonStyle.Primary,
9
10
  secondary: ButtonStyle.Secondary,
@@ -108,10 +109,7 @@ export function installInteractions(client, modules, registration, logger, servi
108
109
  if (!registered ||
109
110
  !subcommand?.autocomplete ||
110
111
  !ownerModuleId ||
111
- !(await authorizeAutocomplete(interaction, ownerModuleId, subcommand.permissionId ??
112
- [registered.contribution.name, groupName, subcommand.name]
113
- .filter(Boolean)
114
- .join("."), services, registration.selfAuthorizingGuildModuleIds?.has(ownerModuleId) ??
112
+ !(await authorizeAutocomplete(interaction, ownerModuleId, resolveDirectCommandPath(registered.contribution, subcommand, groupName).permissionId, services, registration.selfAuthorizingGuildModuleIds?.has(ownerModuleId) ??
115
113
  false, subcommand.authorization === "resource-scoped", isInstallationIndependentModule(modulesById, ownerModuleId), subcommand.authorization))) {
116
114
  await response.respond([]);
117
115
  return;
@@ -175,12 +173,7 @@ export function installInteractions(client, modules, registration, logger, servi
175
173
  });
176
174
  return;
177
175
  }
178
- const commandId = subcommandName
179
- ? [command.name, groupName, subcommandName]
180
- .filter(Boolean)
181
- .join(".")
182
- : command.name;
183
- const permissionId = subcommand?.permissionId ?? command.permissionId ?? commandId;
176
+ const { permissionId } = resolveDirectCommandPath(command, subcommand, groupName);
184
177
  const owner = resolveCommandOwner(registered, subcommandName, groupName);
185
178
  const ownerModuleId = owner?.moduleId ?? moduleId;
186
179
  const installationIndependentCommand = isInstallationIndependentModule(modulesById, ownerModuleId);
@@ -515,6 +508,8 @@ function addCommandOption(builder, option) {
515
508
  guild_text: ChannelType.GuildText,
516
509
  guild_announcement: ChannelType.GuildAnnouncement,
517
510
  guild_category: ChannelType.GuildCategory,
511
+ guild_forum: ChannelType.GuildForum,
512
+ guild_media: ChannelType.GuildMedia,
518
513
  };
519
514
  builder.addChannelOption((optionBuilder) => {
520
515
  optionBuilder
@@ -1003,7 +998,7 @@ export function normalizeDiscordPermission(permission) {
1003
998
  return permission.replaceAll(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
1004
999
  }
1005
1000
  export function toReplyOptions(response) {
1006
- const allowedMentions = toAllowedUserMentions(response.allowedUserMentionIds);
1001
+ const allowedMentions = toAllowedMentions(response.allowedUserMentionIds, response.allowedRoleMentionIds);
1007
1002
  const selectRows = toSelectRows(response);
1008
1003
  if (response.componentsV2) {
1009
1004
  if (response.content !== undefined) {
@@ -1053,27 +1048,37 @@ export function toReplyOptions(response) {
1053
1048
  ],
1054
1049
  };
1055
1050
  }
1056
- const embed = response.embed
1057
- ? new EmbedBuilder()
1058
- .setTitle(response.embed.title)
1059
- .setDescription(response.embed.description)
1060
- .setColor(response.embed.color ?? 0x7c3aed)
1061
- : undefined;
1062
- if (embed && response.embed?.footer)
1063
- embed.setFooter({ text: response.embed.footer });
1064
- if (embed && response.embed?.url)
1065
- embed.setURL(response.embed.url);
1066
- if (embed && response.embed?.author)
1067
- embed.setAuthor({
1068
- name: response.embed.author.name,
1069
- ...(response.embed.author.iconUrl
1070
- ? { iconURL: response.embed.author.iconUrl }
1071
- : {}),
1072
- });
1073
- if (embed && response.embed?.timestamp)
1074
- embed.setTimestamp(new Date(response.embed.timestamp));
1075
- if (embed && response.embed?.imageUrl)
1076
- embed.setImage(response.embed.imageUrl);
1051
+ if (response.embed && response.embeds)
1052
+ throw new Error("Message responses cannot use both embed and embeds");
1053
+ const embeds = (response.embeds ?? (response.embed ? [response.embed] : [])).map((value) => {
1054
+ const embed = new EmbedBuilder().setColor(value.color ?? 0x7c3aed);
1055
+ if (value.title)
1056
+ embed.setTitle(value.title);
1057
+ if (value.description)
1058
+ embed.setDescription(value.description);
1059
+ if (value.footer)
1060
+ embed.setFooter({
1061
+ text: value.footer,
1062
+ ...(value.footerIconUrl ? { iconURL: value.footerIconUrl } : {}),
1063
+ });
1064
+ if (value.url)
1065
+ embed.setURL(value.url);
1066
+ if (value.author)
1067
+ embed.setAuthor({
1068
+ name: value.author.name,
1069
+ ...(value.author.url ? { url: value.author.url } : {}),
1070
+ ...(value.author.iconUrl ? { iconURL: value.author.iconUrl } : {}),
1071
+ });
1072
+ if (value.timestamp)
1073
+ embed.setTimestamp(new Date(value.timestamp));
1074
+ if (value.imageUrl)
1075
+ embed.setImage(value.imageUrl);
1076
+ if (value.thumbnailUrl)
1077
+ embed.setThumbnail(value.thumbnailUrl);
1078
+ if (value.fields?.length)
1079
+ embed.addFields(...value.fields);
1080
+ return embed;
1081
+ });
1077
1082
  const components = response.buttons?.length
1078
1083
  ? [
1079
1084
  new ActionRowBuilder().addComponents(response.buttons.map((button) => {
@@ -1096,15 +1101,20 @@ export function toReplyOptions(response) {
1096
1101
  : [];
1097
1102
  components.push(...selectRows);
1098
1103
  return {
1099
- ...(response.ephemeral || response.suppressEmbeds
1104
+ ...(response.ephemeral ||
1105
+ response.suppressEmbeds ||
1106
+ response.suppressNotifications
1100
1107
  ? {
1101
1108
  flags: (response.ephemeral ? MessageFlags.Ephemeral : 0) |
1102
- (response.suppressEmbeds ? MessageFlags.SuppressEmbeds : 0),
1109
+ (response.suppressEmbeds ? MessageFlags.SuppressEmbeds : 0) |
1110
+ (response.suppressNotifications
1111
+ ? MessageFlags.SuppressNotifications
1112
+ : 0),
1103
1113
  }
1104
1114
  : {}),
1105
1115
  ...(allowedMentions ? { allowedMentions } : {}),
1106
1116
  ...(response.content === undefined ? {} : { content: response.content }),
1107
- ...(embed ? { embeds: [embed] } : {}),
1117
+ ...(embeds.length > 0 ? { embeds } : {}),
1108
1118
  components,
1109
1119
  };
1110
1120
  }
@@ -1154,15 +1164,25 @@ export function toUpdateOptions(response) {
1154
1164
  ephemeral: false,
1155
1165
  });
1156
1166
  }
1157
- function toAllowedUserMentions(userIds) {
1158
- if (userIds === undefined)
1167
+ function toAllowedMentions(userIds, roleIds) {
1168
+ if (userIds === undefined && roleIds === undefined)
1159
1169
  return undefined;
1160
- if (userIds.length > 100 ||
1161
- new Set(userIds).size !== userIds.length ||
1162
- userIds.some((userId) => !/^\d{17,20}$/u.test(userId))) {
1163
- throw new Error("Allowed user mentions must be unique Discord snowflakes");
1170
+ validateAllowedMentionIds(userIds, "user");
1171
+ validateAllowedMentionIds(roleIds, "role");
1172
+ return {
1173
+ parse: [],
1174
+ ...(userIds === undefined ? {} : { users: [...userIds] }),
1175
+ ...(roleIds === undefined ? {} : { roles: [...roleIds] }),
1176
+ };
1177
+ }
1178
+ function validateAllowedMentionIds(ids, label) {
1179
+ if (ids === undefined)
1180
+ return;
1181
+ if (ids.length > 100 ||
1182
+ new Set(ids).size !== ids.length ||
1183
+ ids.some((id) => !/^\d{17,20}$/u.test(id))) {
1184
+ throw new Error(`Allowed ${label} mentions must be unique Discord snowflakes`);
1164
1185
  }
1165
- return { parse: [], users: [...userIds] };
1166
1186
  }
1167
1187
  function toButtonEmoji(emojiIdentifier) {
1168
1188
  const custom = /^([A-Za-z0-9_]{2,32}):(\d{17,20})$/u.exec(emojiIdentifier);
@@ -1289,6 +1309,10 @@ function discordChannelType(type) {
1289
1309
  return ChannelType.GuildAnnouncement;
1290
1310
  if (type === "guild_category")
1291
1311
  return ChannelType.GuildCategory;
1312
+ if (type === "guild_forum")
1313
+ return ChannelType.GuildForum;
1314
+ if (type === "guild_media")
1315
+ return ChannelType.GuildMedia;
1292
1316
  return ChannelType.GuildText;
1293
1317
  }
1294
1318
  export function safeInteractionFailureMessage(error) {
@@ -2,7 +2,7 @@ import { type ModuleDefinition } from "@helyx/sdk";
2
2
  export interface ManagementGuildResources {
3
3
  channels: readonly {
4
4
  id: string;
5
- type: "guild_text" | "guild_announcement" | "guild_category";
5
+ type: "guild_text" | "guild_announcement" | "guild_category" | "guild_forum" | "guild_media";
6
6
  }[];
7
7
  roles: readonly {
8
8
  id: string;
package/dist/modules.d.ts CHANGED
@@ -20,8 +20,10 @@ export declare function deriveSecureRandomSelectionKey(encodedDataEncryptionKey:
20
20
  export declare function platformServiceOptions(environment: PlatformServiceEnvironment, modules: readonly ModuleDefinition[]): {
21
21
  applicationId?: string;
22
22
  secureRandomSelectionKey?: Uint8Array;
23
+ inviteFingerprintKey?: Uint8Array;
23
24
  dashboardBaseUrl?: string;
24
25
  };
26
+ export declare function deriveInviteFingerprintKey(selectionKey: Uint8Array): Uint8Array;
25
27
  export declare function loadConfiguredModules(specifiers: readonly string[], importer?: ModuleImporter): Promise<ModuleDefinition[]>;
26
28
  export declare function loadConfiguredModulePackages(specifiers: readonly string[], importer?: ModuleImporter, resolvePackageJson?: PackageJsonResolver, readPackageJson?: PackageJsonReader): Promise<ConfiguredModulePackage[]>;
27
29
  export declare function validateContributionParity(definition: ModuleDefinition): void;
package/dist/modules.js CHANGED
@@ -4,10 +4,13 @@ import { createHmac } from "node:crypto";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { createRequire } from "node:module";
6
6
  import { dirname } from "node:path";
7
+ import { assertCommandPathContract } from "./command-path-contract.js";
7
8
  const officialModulePackage = /^@helyx\/module-[a-z0-9][a-z0-9-]*$/u;
8
9
  const giveawaysModuleId = "helyx.giveaways";
10
+ const inviteTrackingModuleId = "helyx.invite-tracking";
9
11
  export const secureRandomSelectionWorkerKeyName = "HELYX_INTERNAL_SECURE_RANDOM_SELECTION_KEY";
10
12
  const secureRandomSelectionKeyContext = "helyx.secure-random-selection.worker.v1";
13
+ const inviteFingerprintKeyContext = "helyx.discord-invite-fingerprint.v1";
11
14
  function decodeSecureRandomSelectionKey(encodedKey, variableName) {
12
15
  const key = Buffer.from(encodedKey, "base64");
13
16
  if (key.byteLength !== 32) {
@@ -30,25 +33,38 @@ export function platformServiceOptions(environment, modules) {
30
33
  : {};
31
34
  const internalSelectionKey = environment.HELYX_INTERNAL_SECURE_RANDOM_SELECTION_KEY;
32
35
  if (internalSelectionKey) {
36
+ const selectionKey = decodeSecureRandomSelectionKey(internalSelectionKey, secureRandomSelectionWorkerKeyName);
33
37
  return {
34
38
  ...application,
35
39
  ...urls,
36
- secureRandomSelectionKey: decodeSecureRandomSelectionKey(internalSelectionKey, secureRandomSelectionWorkerKeyName),
40
+ secureRandomSelectionKey: selectionKey,
41
+ inviteFingerprintKey: deriveInviteFingerprintKey(selectionKey),
37
42
  };
38
43
  }
39
44
  const encodedKey = environment.HELYX_DATA_ENCRYPTION_KEY;
40
45
  if (!encodedKey) {
41
- if (modules.some(({ manifest }) => manifest.id === giveawaysModuleId)) {
42
- throw new Error("HELYX_DATA_ENCRYPTION_KEY is required when Giveaways is loaded");
46
+ const keyRequired = modules.some(({ manifest }) => manifest.id === giveawaysModuleId ||
47
+ manifest.id === inviteTrackingModuleId);
48
+ if (keyRequired) {
49
+ throw new Error("HELYX_DATA_ENCRYPTION_KEY is required when Giveaways or Invite Tracking is loaded");
43
50
  }
44
51
  return { ...application, ...urls };
45
52
  }
53
+ const selectionKey = deriveSecureRandomSelectionKey(encodedKey);
46
54
  return {
47
55
  ...application,
48
56
  ...urls,
49
- secureRandomSelectionKey: deriveSecureRandomSelectionKey(encodedKey),
57
+ secureRandomSelectionKey: selectionKey,
58
+ inviteFingerprintKey: deriveInviteFingerprintKey(selectionKey),
50
59
  };
51
60
  }
61
+ export function deriveInviteFingerprintKey(selectionKey) {
62
+ if (selectionKey.byteLength !== 32)
63
+ throw new Error("Secure random selection key must contain exactly 32 bytes");
64
+ return createHmac("sha256", selectionKey)
65
+ .update(inviteFingerprintKeyContext, "utf8")
66
+ .digest();
67
+ }
52
68
  function isModuleDefinition(value) {
53
69
  if (typeof value !== "object" || value === null)
54
70
  return false;
@@ -165,13 +181,20 @@ export function validateContributionParity(definition) {
165
181
  ...(definition.events?.guildEmojiUpdate ?? []),
166
182
  ].map(({ type }) => type);
167
183
  const executableLogs = (definition.logging?.events ?? []).map(({ id }) => id);
184
+ const executableSchedulableActions = [
185
+ ...new Set((definition.schedulableActions ?? []).map(({ id }) => id)),
186
+ ];
168
187
  const executableDashboardActions = (definition.dashboardActions ?? []).map(({ id }) => id);
169
188
  const executableManagedResources = (definition.managedResources ?? []).map(({ id }) => id);
170
189
  assertSameContributionIds(definition.manifest.contributions.commands, executableCommands, "commands");
171
190
  assertSameContributionIds(definition.manifest.contributions.events, executableEvents, "events");
172
191
  assertSameContributionIds(definition.manifest.contributions.logs ?? [], executableLogs, "logs");
192
+ assertSameContributionIds(definition.manifest.contributions.schedulableActions ?? [], executableSchedulableActions, "schedulable actions");
173
193
  assertSameContributionIds(definition.manifest.dashboard?.actions?.map(({ id }) => id) ?? [], executableDashboardActions, "dashboard actions");
174
194
  assertSameContributionIds(definition.manifest.dashboard?.resources?.map(({ id }) => id) ?? [], executableManagedResources, "dashboard resources");
195
+ if (definition.manifest.manifestVersion === "0.4") {
196
+ assertCommandPathContract(definition);
197
+ }
175
198
  validatePrivacyParity(definition);
176
199
  }
177
200
  function validatePrivacyParity(definition) {
@@ -2,6 +2,7 @@ import { defineModule, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
2
2
  import { isCustomerSelectableModule } from "@helyx/module-manifest";
3
3
  import { PermissionFlagsBits } from "discord.js";
4
4
  import { canMutatePermissionPolicy, permissionMutationDenialMessage, } from "@helyx/core";
5
+ import { collectCommandPermissionTargets } from "./command-path-contract.js";
5
6
  import { INTERNAL_MODULE_MANIFEST_FIELDS } from "./internal-module-manifest.js";
6
7
  const MODULE_SELECT_ID = "helyx.permissions.module";
7
8
  const TARGET_SELECT_ID = "helyx.permissions.target";
@@ -129,7 +130,8 @@ export function createPermissionsModule(configurableModules) {
129
130
  if (!module || !target)
130
131
  throw new Error("Permission target expired");
131
132
  const commandId = target === "__module__" ? undefined : target;
132
- if (commandId && !commandTargets(module).includes(commandId)) {
133
+ if (commandId &&
134
+ !commandTargets(module).some(({ id }) => id === commandId)) {
133
135
  throw new Error("Selected command is unavailable");
134
136
  }
135
137
  const current = await context.services
@@ -437,17 +439,12 @@ function restrictedPolicy() {
437
439
  };
438
440
  }
439
441
  function commandTargets(module) {
440
- const targets = (module.interactions?.commands ?? []).flatMap((command) => {
441
- const nested = [
442
- ...(command.subcommands ?? []).map((subcommand) => `${command.name}.${subcommand.name}`),
443
- ...(command.subcommandGroups ?? []).flatMap((group) => group.subcommands.map((subcommand) => `${command.name}.${group.name}.${subcommand.name}`)),
444
- ];
445
- if (nested.length > 0)
446
- return nested;
447
- return [command.name];
448
- });
449
- targets.push(...(module.sharedCommands ?? []).map((command) => command.permissionId ?? `${command.family}.${command.path}`), ...(module.nexusCommands ?? []).map((command) => `nexus.${command.path}`));
450
- return [...new Set(targets)];
442
+ return [
443
+ ...new Map(collectCommandPermissionTargets(module).map(({ id, name }) => [
444
+ id,
445
+ { id, label: name },
446
+ ])).values(),
447
+ ];
451
448
  }
452
449
  function modulePickerResponse(modules, requestedPage) {
453
450
  const pages = pageCount(modules.length, MODULE_PAGE_SIZE);
@@ -483,7 +480,7 @@ function targetPickerResponse(module, targets, requestedPage) {
483
480
  ephemeral: true,
484
481
  embed: {
485
482
  title: `${module.manifest.name} permissions`,
486
- description: "Choose the whole module or one command. Command policies override the module policy." +
483
+ description: "Choose the whole module, a command, or a declared command operation. Target policies override the module policy." +
487
484
  (pages > 1 ? ` Page ${String(page + 1)} of ${String(pages)}.` : ""),
488
485
  color: VIOLET,
489
486
  },
@@ -495,11 +492,11 @@ function targetPickerResponse(module, targets, requestedPage) {
495
492
  {
496
493
  label: "Entire module",
497
494
  value: "__module__",
498
- description: "Default policy for every command",
495
+ description: "Default policy for every command and operation",
499
496
  },
500
497
  ...visible.map((target) => ({
501
- label: `/${target.replaceAll(".", " ")}`,
502
- value: target,
498
+ label: target.label,
499
+ value: target.id,
503
500
  })),
504
501
  ],
505
502
  },
@@ -4,7 +4,7 @@ export const SHARD_CONTRACT_VERSION = 1;
4
4
  // Guild dashboard state includes the bounded module catalogue as well as the
5
5
  // guild's Discord resources. Keep this aligned with the cloud-control message
6
6
  // ceiling so hosted and self-hosted transports accept the same valid state.
7
- export const SHARD_CONTROL_MAX_BYTES = 256 * 1_024;
7
+ export const SHARD_CONTROL_MAX_BYTES = 768 * 1_024;
8
8
  export const SHARD_CONTROL_TIMEOUT_MS = 30_000;
9
9
  export const SHARD_HEALTH_STALE_MS = 30_000;
10
10
  export const SHARD_LEASE_HEARTBEAT_TIMEOUT_MS = 20_000;
@@ -2,7 +2,7 @@ import { parseBotEnvironment } from "@helyx/config";
2
2
  import { safeError, ServiceRegistry } from "@helyx/core";
3
3
  import { checkDatabaseConnection, createDatabasePool } from "@helyx/database";
4
4
  import { createRuntime } from "@helyx/framework";
5
- import { createPlatformServices } from "@helyx/platform";
5
+ import { createPlatformServices, PlatformMemberRoleGrantOwnershipService, PostgresMemberRoleGrantOwnershipStore, } from "@helyx/platform";
6
6
  import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
7
7
  import { Client, Events, Partials } from "discord.js";
8
8
  import { monitorEventLoopDelay } from "node:perf_hooks";
@@ -21,6 +21,8 @@ import { createPermissionsModule } from "./permissions-module.js";
21
21
  import { WorkerCloudConnectorProxy } from "./shard-cloud-proxy.js";
22
22
  import { DiscordOwnedMessageDeletionService } from "./discord-owned-message-service.js";
23
23
  import { DiscordGuildHealthInspection } from "./discord-guild-health.js";
24
+ import { DiscordAnalyticsInspection } from "./discord-analytics.js";
25
+ import { DiscordInviteInspection } from "./discord-invites.js";
24
26
  import { CoreResourceAuthorizationService } from "./resource-authorization.js";
25
27
  import { PostgresTriviaDataProvider } from "./trivia-data-provider.js";
26
28
  import { createDeveloperOperationsModule, WorkerShardHealthProvider, } from "./developer-operations-module.js";
@@ -181,8 +183,9 @@ export async function startShardWorker(processEnvironment = process.env, identit
181
183
  ];
182
184
  const platformServices = new Map();
183
185
  const serviceAccess = new ServiceRegistry(platformServices);
186
+ const serviceOptions = platformServiceOptions(environment, publicModules);
184
187
  for (const [name, service] of createPlatformServices(database, publicModules, {
185
- ...platformServiceOptions(environment, publicModules),
188
+ ...serviceOptions,
186
189
  activationServices: serviceAccess,
187
190
  })) {
188
191
  platformServices.set(name, service);
@@ -213,9 +216,14 @@ export async function startShardWorker(processEnvironment = process.env, identit
213
216
  platformServices.set(HELYX_SERVICE_NAMES.discordMessages, new DiscordMessageDeliveryService(client));
214
217
  platformServices.set(HELYX_SERVICE_NAMES.discordOwnedMessages, new DiscordOwnedMessageDeletionService(client));
215
218
  platformServices.set(HELYX_SERVICE_NAMES.memberEnforcement, new DiscordMemberEnforcementService(client));
216
- platformServices.set(HELYX_SERVICE_NAMES.memberRoles, new DiscordMemberRoleService(client));
219
+ const memberRoles = new DiscordMemberRoleService(client);
220
+ platformServices.set(HELYX_SERVICE_NAMES.memberRoles, memberRoles);
221
+ platformServices.set(HELYX_SERVICE_NAMES.memberRoleGrants, new PlatformMemberRoleGrantOwnershipService(new PostgresMemberRoleGrantOwnershipStore(database), memberRoles));
217
222
  platformServices.set(HELYX_SERVICE_NAMES.discordResources, new DiscordResourceInspection(client));
218
223
  platformServices.set(HELYX_SERVICE_NAMES.discordGuildHealth, new DiscordGuildHealthInspection(client, publicModules, serviceAccess));
224
+ platformServices.set(HELYX_SERVICE_NAMES.discordAnalytics, new DiscordAnalyticsInspection(client));
225
+ if (serviceOptions.inviteFingerprintKey)
226
+ platformServices.set(HELYX_SERVICE_NAMES.discordInvites, new DiscordInviteInspection(client, serviceOptions.inviteFingerprintKey));
219
227
  if (featureModules.some(({ manifest }) => manifest.id === "helyx.logging"))
220
228
  platformServices.set(HELYX_SERVICE_NAMES.moduleLogging, new DiscordModuleLoggingService(client, database, publicModules, logger));
221
229
  const scheduledTaskRoute = createLocalScheduledTaskRoute({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helyx/bot",
3
- "version": "0.4.44",
3
+ "version": "0.5.0",
4
4
  "description": "Production Discord runtime for the modular Helyx platform.",
5
5
  "keywords": [
6
6
  "helyx",
@@ -53,11 +53,11 @@
53
53
  "@helyx/config": ">=0.5.0 <0.6.0",
54
54
  "@helyx/control-protocol": ">=0.4.0 <0.5.0",
55
55
  "@helyx/core": ">=0.1.0 <0.2.0",
56
- "@helyx/database": ">=0.9.0 <0.10.0",
56
+ "@helyx/database": ">=0.10.0",
57
57
  "@helyx/framework": ">=0.1.0 <0.2.0",
58
58
  "@helyx/module-manifest": ">=0.8.0 <0.9.0",
59
- "@helyx/platform": ">=0.6.2 <0.7.0",
60
- "@helyx/sdk": ">=0.9.0 <0.10.0",
59
+ "@helyx/platform": ">=0.7.0",
60
+ "@helyx/sdk": ">=0.10.0",
61
61
  "@helyx/status-client": ">=0.1.0 <0.2.0",
62
62
  "@helyx/verification-contracts": ">=0.3.0 <0.4.0",
63
63
  "discord.js": "14.27.0",