@helyx/bot 0.5.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { parseBotEnvironment } from "@helyx/config";
2
2
  import { StatusHeartbeatReporter } from "@helyx/status-client";
3
3
  import { safeError, ServiceRegistry } from "@helyx/core";
4
- import { checkDatabaseConnection, createDatabasePool, AuditRepository, GuildCommandRepository, InteractionSessionRepository, RateLimitRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
4
+ import { checkDatabaseConnection, createDatabasePool, AuditRepository, GuildCommandRepository, InteractionSessionRepository, PremiumAllocationRepository, RateLimitRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
5
5
  import { createRuntime } from "@helyx/framework";
6
6
  import { createPlatformServices, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, PlatformMemberRoleGrantOwnershipService, PostgresMemberRoleGrantOwnershipStore, } from "@helyx/platform";
7
7
  import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
@@ -30,6 +30,9 @@ import { shardHealthSnapshotSchema, } from "./shard-contracts.js";
30
30
  import { createSupportModule } from "./support-module.js";
31
31
  import { createOAuthModule } from "./oauth-module.js";
32
32
  import { createPrivacyModule } from "./privacy-module.js";
33
+ import { REVIEWED_PREMIUM_MODULE_IDS } from "@helyx/module-manifest";
34
+ import { createPremiumActivationModule } from "./premium-activation-module.js";
35
+ import { DiscordPremiumGuildAuthority } from "./premium-guild-authority.js";
33
36
  import { HttpOAuthService } from "./oauth-service.js";
34
37
  import { DiscordOwnedMessageDeletionService } from "./discord-owned-message-service.js";
35
38
  import { DiscordGuildHealthInspection } from "./discord-guild-health.js";
@@ -45,6 +48,8 @@ import { createLocalScheduledTaskRoute, createScheduledTaskRunner, schedulerOper
45
48
  import { createLocalNumericWriteBehind } from "./numeric-write-behind.js";
46
49
  import { discordRestOptions } from "./discord-rest.js";
47
50
  import { discordBotStatusService } from "./status-producer.js";
51
+ import { assertReviewedPremiumCatalogue, requirePremiumProviderEnvironment, } from "./premium-access.js";
52
+ import { enforcePremiumCatalogue, PREMIUM_CATALOGUE_ENFORCEMENT_INTERVAL_MS, } from "./premium-catalogue-enforcement.js";
48
53
  export { loadConfiguredModulePackages } from "./modules.js";
49
54
  export { resolveModulePackageSpecifiers } from "./module-configuration.js";
50
55
  export * from "./control-server.js";
@@ -83,9 +88,7 @@ export async function startBot(processEnvironment = process.env) {
83
88
  },
84
89
  {
85
90
  key: "creator-connections",
86
- state: environment.HELYX_OAUTH_ENABLED
87
- ? "operational"
88
- : "major_outage",
91
+ state: "operational",
89
92
  reasonCode: environment.HELYX_OAUTH_ENABLED
90
93
  ? null
91
94
  : "feature_disabled",
@@ -95,7 +98,7 @@ export async function startBot(processEnvironment = process.env) {
95
98
  ];
96
99
  },
97
100
  onError(error) {
98
- bootstrapLogger.warn({ event: "status.heartbeat_failed", reason: error.message }, "Status heartbeat could not be delivered");
101
+ bootstrapLogger.warn({ event: "status.heartbeat_failed", error: safeError(error) }, "Status heartbeat could not be delivered");
99
102
  },
100
103
  });
101
104
  reporter.start();
@@ -128,6 +131,7 @@ async function startUnshardedBot(processEnvironment) {
128
131
  : undefined;
129
132
  let databaseClosed = false;
130
133
  let retentionCleanupTimer;
134
+ let premiumCatalogueTimer;
131
135
  async function closeDatabase() {
132
136
  if (databaseClosed)
133
137
  return;
@@ -198,6 +202,15 @@ async function startUnshardedBot(processEnvironment) {
198
202
  })),
199
203
  }, "Module databases ready");
200
204
  const featureModules = modulePackages.map(({ definition }) => definition);
205
+ assertReviewedPremiumCatalogue(featureModules);
206
+ await enforcePremiumCatalogue(database, bootstrapLogger);
207
+ premiumCatalogueTimer = setInterval(() => void enforcePremiumCatalogue(database, bootstrapLogger).catch((error) => {
208
+ bootstrapLogger.warn({
209
+ event: "premium.catalogue_enforcement_failed",
210
+ error: safeError(error),
211
+ }, "Premium module catalogue enforcement failed");
212
+ }), PREMIUM_CATALOGUE_ENFORCEMENT_INTERVAL_MS);
213
+ premiumCatalogueTimer.unref();
201
214
  const client = new Client({
202
215
  rest: discordRestOptions,
203
216
  intents: gatewayIntentsForModules(featureModules, environment.HELYX_MESSAGE_CONTENT_ENABLED, environment.HELYX_GUILD_MEMBERS_ENABLED),
@@ -224,6 +237,16 @@ async function startUnshardedBot(processEnvironment) {
224
237
  : undefined;
225
238
  const supportModule = createSupportModule(cloudConnector);
226
239
  const privacyModule = createPrivacyModule();
240
+ const premiumEnvironment = requirePremiumProviderEnvironment(featureModules, environment.STRIPE_ENVIRONMENT);
241
+ const premiumActivationModule = !premiumEnvironment
242
+ ? undefined
243
+ : createPremiumActivationModule({
244
+ allocations: new PremiumAllocationRepository(database),
245
+ authority: new DiscordPremiumGuildAuthority(client),
246
+ provider: "stripe",
247
+ providerEnvironment: premiumEnvironment,
248
+ reviewedPremiumModuleIds: REVIEWED_PREMIUM_MODULE_IDS,
249
+ });
227
250
  const oauthModule = environment.HELYX_OAUTH_ENABLED
228
251
  ? createOAuthModule()
229
252
  : undefined;
@@ -232,6 +255,7 @@ async function startUnshardedBot(processEnvironment) {
232
255
  permissionsModule,
233
256
  supportModule,
234
257
  privacyModule,
258
+ ...(premiumActivationModule ? [premiumActivationModule] : []),
235
259
  ...(oauthModule ? [oauthModule] : []),
236
260
  ...(cloudManagementModule ? [cloudManagementModule] : []),
237
261
  ...featureModules,
@@ -305,6 +329,7 @@ async function startUnshardedBot(processEnvironment) {
305
329
  permissionsModule,
306
330
  supportModule,
307
331
  privacyModule,
332
+ ...(premiumActivationModule ? [premiumActivationModule] : []),
308
333
  ...(oauthModule ? [oauthModule] : []),
309
334
  ...(cloudManagementModule ? [cloudManagementModule] : []),
310
335
  ].map(({ manifest }) => manifest),
@@ -372,6 +397,7 @@ async function startUnshardedBot(processEnvironment) {
372
397
  managementModule.manifest.id,
373
398
  permissionsModule.manifest.id,
374
399
  supportModule.manifest.id,
400
+ ...(premiumActivationModule ? [premiumActivationModule.manifest.id] : []),
375
401
  ...(oauthModule ? [oauthModule.manifest.id] : []),
376
402
  ...(cloudManagementModule ? [cloudManagementModule.manifest.id] : []),
377
403
  ...(operationsModule ? [operationsModule.manifest.id] : []),
@@ -386,6 +412,9 @@ async function startUnshardedBot(processEnvironment) {
386
412
  prepare: prepareGuilds,
387
413
  selfAuthorizingGuildModuleIds: new Set([
388
414
  supportModule.manifest.id,
415
+ ...(premiumActivationModule
416
+ ? [premiumActivationModule.manifest.id]
417
+ : []),
389
418
  ...(oauthModule ? [oauthModule.manifest.id] : []),
390
419
  ...(operationsModule ? [operationsModule.manifest.id] : []),
391
420
  ]),
@@ -424,6 +453,8 @@ async function startUnshardedBot(processEnvironment) {
424
453
  process.off("SIGTERM", onSigterm);
425
454
  if (retentionCleanupTimer)
426
455
  clearInterval(retentionCleanupTimer);
456
+ if (premiumCatalogueTimer)
457
+ clearInterval(premiumCatalogueTimer);
427
458
  await scheduledTasks?.stop().catch((error) => {
428
459
  process.exitCode = 1;
429
460
  bootstrapLogger.error({ event: "scheduler.stop_failed", error: safeError(error) }, "Scheduled task runner shutdown failed");
@@ -587,6 +618,8 @@ async function startUnshardedBot(processEnvironment) {
587
618
  catch (error) {
588
619
  if (retentionCleanupTimer)
589
620
  clearInterval(retentionCleanupTimer);
621
+ if (premiumCatalogueTimer)
622
+ clearInterval(premiumCatalogueTimer);
590
623
  await closeDatabase().catch((closeError) => {
591
624
  bootstrapLogger.error({ event: "database.close_failed", error: safeError(closeError) }, "Database pool cleanup failed");
592
625
  });
@@ -39,7 +39,7 @@ export declare function sendToChannel(client: Client, channelId: string, respons
39
39
  export declare function deleteChannelMessage(client: Client, channelId: string, messageId: string): Promise<void>;
40
40
  export declare function editChannelMessage(client: Client, channelId: string, messageId: string, response: MessageResponse): Promise<void>;
41
41
  export declare function addChannelMessageReaction(client: Client, channelId: string, messageId: string, emojiIdentifier: string): Promise<void>;
42
- export declare function authorizeCommand(interaction: ChatInputCommandInteraction, moduleId: string, commandId: string, rateLimit: CommandContribution["rateLimit"], services: ServiceAccess, selfAuthorizingGuildCommand: boolean, resourceScopedCommand?: boolean, requiredModuleName?: string, installationIndependentCommand?: boolean, authorization?: CommandAuthorization): Promise<boolean>;
42
+ export declare function authorizeCommand(interaction: ChatInputCommandInteraction, moduleId: string, commandId: string, rateLimit: CommandContribution["rateLimit"], services: ServiceAccess, selfAuthorizingGuildCommand: boolean, resourceScopedCommand?: boolean, requiredModuleName?: string, installationIndependentCommand?: boolean, authorization?: CommandAuthorization, module?: ModuleDefinition): Promise<boolean>;
43
43
  export declare function normalizeDiscordPermission(permission: string): string;
44
44
  export declare function toReplyOptions(response: MessageResponse): InteractionReplyOptions;
45
45
  export declare function toUpdateOptions(response: MessageResponse): InteractionUpdateOptions;
@@ -1,10 +1,11 @@
1
1
  import { lookup as lookupDns } from "node:dns/promises";
2
2
  import { HELYX_SERVICE_NAMES, NexusEconomyError, UserFacingInteractionError, } from "@helyx/sdk";
3
- import { canUseCommand } from "@helyx/core";
3
+ import { canUseCommand, safeError } 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
7
  import { resolveDirectCommandPath } from "./command-path-contract.js";
8
+ import { canAccessModule, PREMIUM_MODULE_UNAVAILABLE_MESSAGE, } from "./premium-access.js";
8
9
  const buttonStyles = {
9
10
  primary: ButtonStyle.Primary,
10
11
  secondary: ButtonStyle.Secondary,
@@ -110,7 +111,7 @@ export function installInteractions(client, modules, registration, logger, servi
110
111
  !subcommand?.autocomplete ||
111
112
  !ownerModuleId ||
112
113
  !(await authorizeAutocomplete(interaction, ownerModuleId, resolveDirectCommandPath(registered.contribution, subcommand, groupName).permissionId, services, registration.selfAuthorizingGuildModuleIds?.has(ownerModuleId) ??
113
- false, subcommand.authorization === "resource-scoped", isInstallationIndependentModule(modulesById, ownerModuleId), subcommand.authorization))) {
114
+ false, subcommand.authorization === "resource-scoped", isInstallationIndependentModule(modulesById, ownerModuleId), subcommand.authorization, requiredCommandModule(modulesById, ownerModuleId)))) {
114
115
  await response.respond([]);
115
116
  return;
116
117
  }
@@ -190,7 +191,7 @@ export function installInteractions(client, modules, registration, logger, servi
190
191
  await interaction.deferReply({ flags: MessageFlags.Ephemeral });
191
192
  }
192
193
  if (await authorizeCommand(interaction, ownerModuleId, permissionId, rateLimit, services, registration.selfAuthorizingGuildModuleIds?.has(ownerModuleId) ??
193
- false, subcommand?.authorization === "resource-scoped", owner?.moduleName, installationIndependentCommand, authorization)) {
194
+ false, subcommand?.authorization === "resource-scoped", owner?.moduleName, installationIndependentCommand, authorization, requiredCommandModule(modulesById, ownerModuleId))) {
194
195
  const context = createCommandContext(interaction, services);
195
196
  await execute(context);
196
197
  }
@@ -217,12 +218,7 @@ export function installInteractions(client, modules, registration, logger, servi
217
218
  }
218
219
  if (button &&
219
220
  context &&
220
- button.contribution.authorization === "public") {
221
- await button.contribution.execute(context);
222
- }
223
- else if (button &&
224
- context &&
225
- (await authorizeComponent(interaction, button.moduleId, services, undefined, isInstallationIndependentModule(modulesById, button.moduleId)))) {
221
+ (await authorizeComponent(interaction, button.moduleId, services, undefined, isInstallationIndependentModule(modulesById, button.moduleId), modulesById.get(button.moduleId)))) {
226
222
  await button.contribution.execute(context);
227
223
  }
228
224
  }
@@ -239,7 +235,7 @@ export function installInteractions(client, modules, registration, logger, servi
239
235
  await interaction.deferReply({ flags: MessageFlags.Ephemeral });
240
236
  }
241
237
  if (modal &&
242
- (await authorizeComponent(interaction, modal.moduleId, services, undefined, isInstallationIndependentModule(modulesById, modal.moduleId)))) {
238
+ (await authorizeComponent(interaction, modal.moduleId, services, undefined, isInstallationIndependentModule(modulesById, modal.moduleId), modulesById.get(modal.moduleId)))) {
243
239
  await modal.contribution.execute(createModalContext(interaction, services));
244
240
  }
245
241
  }
@@ -264,7 +260,7 @@ export function installInteractions(client, modules, registration, logger, servi
264
260
  }
265
261
  if (select &&
266
262
  context &&
267
- (await authorizeComponent(interaction, select.moduleId, services, undefined, isInstallationIndependentModule(modulesById, select.moduleId)))) {
263
+ (await authorizeComponent(interaction, select.moduleId, services, undefined, isInstallationIndependentModule(modulesById, select.moduleId), modulesById.get(select.moduleId)))) {
268
264
  await select.contribution.execute(context);
269
265
  }
270
266
  }
@@ -857,19 +853,31 @@ export async function addChannelMessageReaction(client, channelId, messageId, em
857
853
  const message = await channel.messages.fetch(messageId);
858
854
  await message.react(emojiIdentifier);
859
855
  }
860
- async function authorizeComponent(interaction, moduleId, services, requiredModuleName, installationIndependent = false) {
856
+ async function authorizeComponent(interaction, moduleId, services, requiredModuleName, installationIndependent = false, module) {
861
857
  if (installationIndependent)
862
858
  return true;
863
- if (!(await moduleIsGloballyAvailable(services, moduleId))) {
859
+ const installations = interaction.guildId
860
+ ? services.get(HELYX_SERVICE_NAMES.installations)
861
+ : undefined;
862
+ const [globallyAvailable, moduleEnabled, premiumAccessible] = await Promise.all([
863
+ moduleIsGloballyAvailable(services, moduleId),
864
+ installations
865
+ ? installations.isModuleEnabled(interaction.guildId, moduleId)
866
+ : Promise.resolve(true),
867
+ interaction.guildId && module
868
+ ? canAccessModule(services, interaction.guildId, module)
869
+ : Promise.resolve(true),
870
+ ]);
871
+ if (!globallyAvailable) {
864
872
  await replyEphemeralText(interaction, GLOBAL_MODULE_UNAVAILABLE_MESSAGE);
865
873
  return false;
866
874
  }
867
- if (!interaction.guildId)
868
- return true;
869
- const installations = services.get(HELYX_SERVICE_NAMES.installations);
870
- if (await installations.isModuleEnabled(interaction.guildId, moduleId)) {
871
- return true;
875
+ if (!premiumAccessible) {
876
+ await replyEphemeralText(interaction, PREMIUM_MODULE_UNAVAILABLE_MESSAGE);
877
+ return false;
872
878
  }
879
+ if (moduleEnabled)
880
+ return true;
873
881
  await replyEphemeralText(interaction, requiredModuleName
874
882
  ? `This command needs ${requiredModuleName}. Ask a server administrator to enable it in the Helyx dashboard.`
875
883
  : "This module is disabled in this server. Ask a server administrator to enable it with `/helyx` or in the Helyx dashboard.");
@@ -882,22 +890,29 @@ async function replyEphemeralText(interaction, content) {
882
890
  }
883
891
  await interaction.reply({ content, flags: MessageFlags.Ephemeral });
884
892
  }
885
- export async function authorizeCommand(interaction, moduleId, commandId, rateLimit, services, selfAuthorizingGuildCommand, resourceScopedCommand = false, requiredModuleName, installationIndependentCommand = false, authorization) {
893
+ export async function authorizeCommand(interaction, moduleId, commandId, rateLimit, services, selfAuthorizingGuildCommand, resourceScopedCommand = false, requiredModuleName, installationIndependentCommand = false, authorization, module) {
886
894
  const bypassPlatformAuthorization = authorization === "public" || authorization === "self-authorized";
887
895
  if (!installationIndependentCommand && !selfAuthorizingGuildCommand) {
888
896
  const installations = interaction.guildId
889
897
  ? services.get(HELYX_SERVICE_NAMES.installations)
890
898
  : undefined;
891
- const [globallyAvailable, moduleEnabled] = await Promise.all([
899
+ const [globallyAvailable, moduleEnabled, premiumAccessible] = await Promise.all([
892
900
  moduleIsGloballyAvailable(services, moduleId),
893
901
  installations && !selfAuthorizingGuildCommand
894
902
  ? installations.isModuleEnabled(interaction.guildId, moduleId)
895
903
  : Promise.resolve(true),
904
+ interaction.guildId && module
905
+ ? canAccessModule(services, interaction.guildId, module)
906
+ : Promise.resolve(true),
896
907
  ]);
897
908
  if (!globallyAvailable) {
898
909
  await replyEphemeralText(interaction, GLOBAL_MODULE_UNAVAILABLE_MESSAGE);
899
910
  return false;
900
911
  }
912
+ if (!premiumAccessible) {
913
+ await replyEphemeralText(interaction, PREMIUM_MODULE_UNAVAILABLE_MESSAGE);
914
+ return false;
915
+ }
901
916
  if (!moduleEnabled) {
902
917
  await replyEphemeralText(interaction, requiredModuleName
903
918
  ? `This command needs ${requiredModuleName}. Ask a server administrator to enable it in the Helyx dashboard.`
@@ -939,19 +954,22 @@ export async function authorizeCommand(interaction, moduleId, commandId, rateLim
939
954
  }
940
955
  return true;
941
956
  }
942
- async function authorizeAutocomplete(interaction, moduleId, commandId, services, selfAuthorizingGuildCommand, resourceScopedCommand, installationIndependentCommand = false, authorization) {
957
+ async function authorizeAutocomplete(interaction, moduleId, commandId, services, selfAuthorizingGuildCommand, resourceScopedCommand, installationIndependentCommand = false, authorization, module) {
943
958
  const bypassPlatformAuthorization = authorization === "public" || authorization === "self-authorized";
944
959
  if (!installationIndependentCommand && !selfAuthorizingGuildCommand) {
945
960
  const installations = interaction.guildId
946
961
  ? services.get(HELYX_SERVICE_NAMES.installations)
947
962
  : undefined;
948
- const [globallyAvailable, moduleEnabled] = await Promise.all([
963
+ const [globallyAvailable, moduleEnabled, premiumAccessible] = await Promise.all([
949
964
  moduleIsGloballyAvailable(services, moduleId),
950
965
  installations && !selfAuthorizingGuildCommand
951
966
  ? installations.isModuleEnabled(interaction.guildId, moduleId)
952
967
  : Promise.resolve(true),
968
+ interaction.guildId && module
969
+ ? canAccessModule(services, interaction.guildId, module)
970
+ : Promise.resolve(true),
953
971
  ]);
954
- if (!globallyAvailable || !moduleEnabled)
972
+ if (!globallyAvailable || !moduleEnabled || !premiumAccessible)
955
973
  return false;
956
974
  }
957
975
  if (bypassPlatformAuthorization ||
@@ -1365,17 +1383,6 @@ async function sendFailure(interaction, error) {
1365
1383
  .reply({ content: message, flags: MessageFlags.Ephemeral })
1366
1384
  .catch(() => undefined);
1367
1385
  }
1368
- function safeError(error) {
1369
- const value = error instanceof Error ? error : new Error(String(error));
1370
- return {
1371
- name: value.name,
1372
- message: value.message,
1373
- ...("code" in value &&
1374
- (typeof value.code === "string" || typeof value.code === "number")
1375
- ? { code: value.code }
1376
- : {}),
1377
- };
1378
- }
1379
1386
  function interactionLogContext(interaction) {
1380
1387
  return {
1381
1388
  interactionId: interaction.id,
@@ -1,4 +1,5 @@
1
1
  import { InstallationRepository, ModuleConfigurationRepository, ModuleLoggingPolicyRepository, } from "@helyx/database";
2
+ import { safeError } from "@helyx/core";
2
3
  import { toReplyOptions } from "./interactions.js";
3
4
  import { createHash } from "node:crypto";
4
5
  const LOGGING_MODULE_ID = "helyx.logging";
@@ -108,8 +109,4 @@ function normalizeText(value, maximum, label) {
108
109
  }
109
110
  return normalized;
110
111
  }
111
- function safeError(error) {
112
- const value = error instanceof Error ? error : new Error(String(error));
113
- return { name: value.name, message: value.message };
114
- }
115
112
  //# sourceMappingURL=module-logging.js.map
@@ -13,7 +13,7 @@ export async function mutateDashboardModuleStatus(input) {
13
13
  input.services.has(HELYX_SERVICE_NAMES.numericWriteBehind)
14
14
  ? input.services.get(HELYX_SERVICE_NAMES.numericWriteBehind)
15
15
  : null;
16
- if (writeBehind)
16
+ if (change.enabled)
17
17
  await input.requireModuleAvailable();
18
18
  let mutationResult;
19
19
  const setEnabled = async () => {
@@ -0,0 +1,7 @@
1
+ import { type ModuleDefinition, type ServiceAccess } from "@helyx/sdk";
2
+ export declare const PREMIUM_MODULE_UNAVAILABLE_MESSAGE = "This Premium module is not assigned to this server. A Premium subscriber can assign a slot in Billing or with `/activate`.";
3
+ export declare function moduleRequiresPremium(module: ModuleDefinition): boolean;
4
+ export declare function requirePremiumProviderEnvironment(modules: readonly ModuleDefinition[], environment: "sandbox" | "live" | undefined): "sandbox" | "live" | undefined;
5
+ export declare function canAccessModule(services: ServiceAccess, guildId: string, module: ModuleDefinition): Promise<boolean>;
6
+ export declare function assertReviewedPremiumCatalogue(modules: readonly ModuleDefinition[]): void;
7
+ //# sourceMappingURL=premium-access.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { getModuleTier, HELYX_PREMIUM_ENTITLEMENT_CAPABILITY, isReviewedPremiumModuleId, REVIEWED_PREMIUM_MODULE_IDS, } from "@helyx/module-manifest";
2
+ import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
3
+ export const PREMIUM_MODULE_UNAVAILABLE_MESSAGE = "This Premium module is not assigned to this server. A Premium subscriber can assign a slot in Billing or with `/activate`.";
4
+ export function moduleRequiresPremium(module) {
5
+ return getModuleTier(module.manifest) === "premium";
6
+ }
7
+ export function requirePremiumProviderEnvironment(modules, environment) {
8
+ if (!modules.some(moduleRequiresPremium))
9
+ return undefined;
10
+ if (!environment)
11
+ throw new Error("STRIPE_ENVIRONMENT is required when a reviewed Premium module is loaded");
12
+ return environment;
13
+ }
14
+ export async function canAccessModule(services, guildId, module) {
15
+ if (!moduleRequiresPremium(module))
16
+ return true;
17
+ if (!isReviewedPremiumModuleId(module.manifest.id))
18
+ return false;
19
+ if (typeof services.has === "function" &&
20
+ !services.has(HELYX_SERVICE_NAMES.entitlements))
21
+ return false;
22
+ const entitlements = services.get(HELYX_SERVICE_NAMES.entitlements);
23
+ return entitlements.has(guildId, HELYX_PREMIUM_ENTITLEMENT_CAPABILITY);
24
+ }
25
+ export function assertReviewedPremiumCatalogue(modules) {
26
+ const byId = new Map(modules.map((module) => [module.manifest.id, module]));
27
+ for (const module of modules) {
28
+ if (moduleRequiresPremium(module) &&
29
+ !isReviewedPremiumModuleId(module.manifest.id))
30
+ throw new Error(`Premium module has not passed catalogue review: ${module.manifest.id}`);
31
+ }
32
+ for (const moduleId of REVIEWED_PREMIUM_MODULE_IDS) {
33
+ const module = byId.get(moduleId);
34
+ if (module && !moduleRequiresPremium(module))
35
+ throw new Error(`Reviewed Premium module is missing its Premium manifest tier: ${moduleId}`);
36
+ }
37
+ }
38
+ //# sourceMappingURL=premium-access.js.map
@@ -0,0 +1,20 @@
1
+ import type { BillingEnvironment, PremiumAllocationRepository } from "@helyx/database";
2
+ import { type ModuleDefinition } from "@helyx/sdk";
3
+ type AllocationService = Pick<PremiumAllocationRepository, "getAccountAllocation" | "inspectTarget" | "assignLowestAvailable" | "moveSlot" | "releaseSlot" | "auditDenied">;
4
+ export interface PremiumGuildAuthority {
5
+ canManageGuild(input: {
6
+ guildId: string;
7
+ userId: string;
8
+ }): Promise<boolean>;
9
+ }
10
+ export interface PremiumActivationModuleOptions {
11
+ allocations: AllocationService;
12
+ authority: PremiumGuildAuthority;
13
+ provider: string;
14
+ providerEnvironment: BillingEnvironment;
15
+ reviewedPremiumModuleIds: readonly string[];
16
+ now?: () => Date;
17
+ }
18
+ export declare function createPremiumActivationModule(options: PremiumActivationModuleOptions): ModuleDefinition;
19
+ export {};
20
+ //# sourceMappingURL=premium-activation-module.d.ts.map