@helyx/bot 0.4.46 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/README.md +4 -4
- package/dist/cloud-management-module.js +1 -5
- package/dist/command-path-contract.d.ts +26 -0
- package/dist/command-path-contract.js +157 -0
- package/dist/command-registration-policy.js +1 -0
- package/dist/control-permissions.js +1 -0
- package/dist/control-server.d.ts +2 -34
- package/dist/control-server.js +31 -43
- package/dist/dashboard-commands.d.ts +1 -0
- package/dist/dashboard-commands.js +6 -35
- package/dist/dashboard-module-projection.d.ts +39 -0
- package/dist/dashboard-module-projection.js +28 -0
- package/dist/discord-analytics.d.ts +30 -0
- package/dist/discord-analytics.js +337 -0
- package/dist/discord-invites.d.ts +24 -0
- package/dist/discord-invites.js +91 -0
- package/dist/events.d.ts +1 -1
- package/dist/events.js +78 -22
- package/dist/guilds.js +1 -4
- package/dist/index.js +49 -8
- package/dist/interactions.d.ts +1 -1
- package/dist/interactions.js +89 -68
- package/dist/management-module.d.ts +1 -1
- package/dist/module-logging.js +1 -4
- package/dist/module-status-control.js +1 -1
- package/dist/modules.d.ts +2 -0
- package/dist/modules.js +27 -4
- package/dist/permissions-module.js +13 -16
- package/dist/premium-access.d.ts +7 -0
- package/dist/premium-access.js +38 -0
- package/dist/premium-activation-module.d.ts +20 -0
- package/dist/premium-activation-module.js +542 -0
- package/dist/premium-catalogue-enforcement.d.ts +5 -0
- package/dist/premium-catalogue-enforcement.js +29 -0
- package/dist/premium-guild-authority.d.ts +12 -0
- package/dist/premium-guild-authority.js +25 -0
- package/dist/scheduled-task-runner.js +14 -1
- package/dist/shard-contracts.js +1 -1
- package/dist/shard-supervisor.js +30 -1
- package/dist/shard-worker.d.ts +2 -0
- package/dist/shard-worker.js +36 -4
- package/documentation/examples/.env.example +2 -2
- package/package.json +6 -6
package/dist/events.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { HELYX_SERVICE_NAMES } from "@helyx/sdk";
|
|
2
|
+
import { safeError } from "@helyx/core";
|
|
2
3
|
import { ChannelType, Events, GatewayIntentBits, GuildMemberFlags, PermissionFlagsBits, RESTJSONErrorCodes, UserFlags, } from "discord.js";
|
|
3
4
|
import { toReplyOptions } from "./interactions.js";
|
|
4
5
|
import { createHash } from "node:crypto";
|
|
5
6
|
import { classifyGuildMessage, unavailableGuildMessageClassification, } from "./message-classification.js";
|
|
6
|
-
|
|
7
|
+
import { canAccessModule } from "./premium-access.js";
|
|
8
|
+
export function availabilityAwareInstallations(services, modulesById) {
|
|
7
9
|
const installations = services.get(HELYX_SERVICE_NAMES.installations);
|
|
8
10
|
const candidate = typeof services.has === "function" &&
|
|
9
11
|
services.has(HELYX_SERVICE_NAMES.moduleAvailability)
|
|
@@ -12,12 +14,15 @@ export function availabilityAwareInstallations(services) {
|
|
|
12
14
|
const availability = typeof candidate?.isAvailable === "function" ? candidate : null;
|
|
13
15
|
return {
|
|
14
16
|
async isModuleEnabled(guildId, moduleId) {
|
|
17
|
+
const module = modulesById?.get(moduleId);
|
|
15
18
|
return ((!availability || (await availability.isAvailable(moduleId))) &&
|
|
16
|
-
installations.isModuleEnabled(guildId, moduleId))
|
|
19
|
+
(await installations.isModuleEnabled(guildId, moduleId)) &&
|
|
20
|
+
(!module || (await canAccessModule(services, guildId, module))));
|
|
17
21
|
},
|
|
18
22
|
};
|
|
19
23
|
}
|
|
20
24
|
export function installModuleEvents(client, modules, logger, services) {
|
|
25
|
+
const modulesById = new Map(modules.map((module) => [module.manifest.id, module]));
|
|
21
26
|
const privilegedMessageContributions = modules.flatMap((module) => (module.events?.privilegedMessageCreate ?? []).map((contribution) => ({
|
|
22
27
|
moduleId: module.manifest.id,
|
|
23
28
|
contribution,
|
|
@@ -122,12 +127,34 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
122
127
|
const guildMessageQueue = guildMessageContributions.length > 0
|
|
123
128
|
? new BoundedGuildEventQueue(logger)
|
|
124
129
|
: null;
|
|
130
|
+
const inviteInspection = memberAddContributions.some(({ moduleId }) => moduleId === "helyx.invite-tracking") &&
|
|
131
|
+
typeof services.has === "function" &&
|
|
132
|
+
services.has(HELYX_SERVICE_NAMES.discordInvites)
|
|
133
|
+
? services.get(HELYX_SERVICE_NAMES.discordInvites)
|
|
134
|
+
: null;
|
|
135
|
+
if (inviteInspection) {
|
|
136
|
+
client.on(Events.ShardDisconnect, (_event, shardId) => {
|
|
137
|
+
for (const guild of client.guilds.cache.values())
|
|
138
|
+
if (guild.shardId === shardId)
|
|
139
|
+
inviteInspection.invalidateContinuity({
|
|
140
|
+
guildId: guild.id,
|
|
141
|
+
reason: "gateway_session_gap",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
client.on(Events.Invalidated, () => {
|
|
145
|
+
for (const guildId of client.guilds.cache.keys())
|
|
146
|
+
inviteInspection.invalidateContinuity({
|
|
147
|
+
guildId,
|
|
148
|
+
reason: "gateway_session_gap",
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
125
152
|
if (privilegedMessageContributions.length > 0) {
|
|
126
153
|
client.on(Events.MessageCreate, (message) => {
|
|
127
154
|
if (!message.guildId || !privilegedEligibilityQueue)
|
|
128
155
|
return;
|
|
129
156
|
privilegedEligibilityQueue.enqueue(message.guildId, async () => {
|
|
130
|
-
const installations = availabilityAwareInstallations(services);
|
|
157
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
131
158
|
const eligible = [];
|
|
132
159
|
for (const item of privilegedMessageContributions) {
|
|
133
160
|
try {
|
|
@@ -231,7 +258,12 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
231
258
|
}
|
|
232
259
|
if (memberAddContributions.length > 0) {
|
|
233
260
|
client.on(Events.GuildMemberAdd, (member) => {
|
|
234
|
-
queue.enqueue(member.guild.id, () => handleMemberAdd(member));
|
|
261
|
+
const accepted = queue.enqueue(member.guild.id, () => handleMemberAdd(member));
|
|
262
|
+
if (!accepted && inviteInspection)
|
|
263
|
+
inviteInspection.invalidateContinuity({
|
|
264
|
+
guildId: member.guild.id,
|
|
265
|
+
reason: "event_queue_gap",
|
|
266
|
+
});
|
|
235
267
|
});
|
|
236
268
|
}
|
|
237
269
|
if (memberRemoveContributions.length > 0) {
|
|
@@ -274,7 +306,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
274
306
|
});
|
|
275
307
|
}
|
|
276
308
|
async function handleResourceLifecycle(contributions, serverId, identifier, resourceId, eventName) {
|
|
277
|
-
const installations = availabilityAwareInstallations(services);
|
|
309
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
278
310
|
for (const { moduleId, contribution } of contributions) {
|
|
279
311
|
try {
|
|
280
312
|
if (!(await installations.isModuleEnabled(serverId, moduleId))) {
|
|
@@ -300,7 +332,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
300
332
|
async function handlePrivilegedMessageCreate(message, contributions) {
|
|
301
333
|
if (!message.guildId || !message.channelId)
|
|
302
334
|
return;
|
|
303
|
-
const installations = availabilityAwareInstallations(services);
|
|
335
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
304
336
|
for (const { moduleId, contribution } of contributions) {
|
|
305
337
|
try {
|
|
306
338
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId)))
|
|
@@ -333,7 +365,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
333
365
|
async function handleGuildMessageCreate(message) {
|
|
334
366
|
if (!message.guildId || !message.channelId)
|
|
335
367
|
return;
|
|
336
|
-
const installations = availabilityAwareInstallations(services);
|
|
368
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
337
369
|
const parentChannelId = message.channel.isThread()
|
|
338
370
|
? (message.channel.parentId ?? undefined)
|
|
339
371
|
: undefined;
|
|
@@ -429,7 +461,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
429
461
|
: reaction.message;
|
|
430
462
|
if (!message.guildId || !message.channelId)
|
|
431
463
|
return;
|
|
432
|
-
const installations = availabilityAwareInstallations(services);
|
|
464
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
433
465
|
const member = await message.guild?.members.fetch(user.id);
|
|
434
466
|
const userRoleIds = member ? [...member.roles.cache.keys()] : [];
|
|
435
467
|
const parentChannelId = messageParentChannelId(message);
|
|
@@ -445,6 +477,14 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
445
477
|
messageId: message.id,
|
|
446
478
|
userId: receivedUser.id,
|
|
447
479
|
userRoleIds,
|
|
480
|
+
userPermissions: member?.permissions
|
|
481
|
+
.toArray()
|
|
482
|
+
.map((permission) => permission
|
|
483
|
+
.replaceAll(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
484
|
+
.toUpperCase()) ?? [],
|
|
485
|
+
isServerOwner: message.guild?.ownerId === user.id,
|
|
486
|
+
isAdministrator: member?.permissions.has(PermissionFlagsBits.Administrator) ??
|
|
487
|
+
false,
|
|
448
488
|
emojiKey: reactionEmojiKey(reaction),
|
|
449
489
|
emojiIdentifier: reactionEmojiIdentifier(reaction),
|
|
450
490
|
reactionCount: Math.max(0, reaction.count ?? 0),
|
|
@@ -486,10 +526,27 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
486
526
|
async function handleMemberAdd(member) {
|
|
487
527
|
if (!member.joinedAt)
|
|
488
528
|
throw new Error("Discord member join timestamp is unavailable");
|
|
489
|
-
const installations = availabilityAwareInstallations(services);
|
|
529
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
530
|
+
const continuityGeneration = inviteInspection
|
|
531
|
+
? inviteInspection.continuityGeneration({ guildId: member.guild.id })
|
|
532
|
+
: `runtime:${client.readyTimestamp ?? 0}`;
|
|
533
|
+
const occurrenceId = createHash("sha256")
|
|
534
|
+
.update("helyx.guild-member-add.v1\0", "utf8")
|
|
535
|
+
.update(member.guild.id, "utf8")
|
|
536
|
+
.update("\0", "utf8")
|
|
537
|
+
.update(member.id, "utf8")
|
|
538
|
+
.update("\0", "utf8")
|
|
539
|
+
.update(member.joinedAt.toISOString(), "utf8")
|
|
540
|
+
.digest("base64url");
|
|
490
541
|
for (const { moduleId, contribution } of memberAddContributions) {
|
|
491
542
|
try {
|
|
492
543
|
if (!(await installations.isModuleEnabled(member.guild.id, moduleId))) {
|
|
544
|
+
if (moduleId === "helyx.invite-tracking" && inviteInspection) {
|
|
545
|
+
inviteInspection.invalidateContinuity({
|
|
546
|
+
guildId: member.guild.id,
|
|
547
|
+
reason: "ownership_gap",
|
|
548
|
+
});
|
|
549
|
+
}
|
|
493
550
|
continue;
|
|
494
551
|
}
|
|
495
552
|
await contribution.execute({
|
|
@@ -500,6 +557,8 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
500
557
|
memberIsBot: member.user.bot,
|
|
501
558
|
accountCreatedAt: member.user.createdAt,
|
|
502
559
|
joinedAt: member.joinedAt,
|
|
560
|
+
occurrenceId,
|
|
561
|
+
continuityGeneration,
|
|
503
562
|
services,
|
|
504
563
|
assignRole: (roleId) => assignMemberRole(member, roleId),
|
|
505
564
|
sendToChannel: (channelId, response) => sendToGuildChannel(member, channelId, response),
|
|
@@ -517,7 +576,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
517
576
|
}
|
|
518
577
|
}
|
|
519
578
|
async function handleMemberRemove(member) {
|
|
520
|
-
const installations = availabilityAwareInstallations(services);
|
|
579
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
521
580
|
for (const { moduleId, contribution } of memberRemoveContributions) {
|
|
522
581
|
try {
|
|
523
582
|
if (!(await installations.isModuleEnabled(member.guild.id, moduleId))) {
|
|
@@ -557,7 +616,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
557
616
|
: reaction.message;
|
|
558
617
|
if (!message.guildId || !message.channelId)
|
|
559
618
|
return;
|
|
560
|
-
const installations = availabilityAwareInstallations(services);
|
|
619
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
561
620
|
const parentChannelId = messageParentChannelId(message);
|
|
562
621
|
for (const { moduleId, contribution } of reactionRemoveContributions) {
|
|
563
622
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId))) {
|
|
@@ -601,7 +660,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
601
660
|
: receivedMessage;
|
|
602
661
|
if (!message.guildId || !message.channelId)
|
|
603
662
|
return;
|
|
604
|
-
const installations = availabilityAwareInstallations(services);
|
|
663
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
605
664
|
for (const { moduleId, contribution } of reactionRemoveAllContributions) {
|
|
606
665
|
try {
|
|
607
666
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId)))
|
|
@@ -631,7 +690,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
631
690
|
const message = reaction.message;
|
|
632
691
|
if (!message.guildId || !message.channelId)
|
|
633
692
|
return;
|
|
634
|
-
const installations = availabilityAwareInstallations(services);
|
|
693
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
635
694
|
for (const { moduleId, contribution } of reactionRemoveEmojiContributions) {
|
|
636
695
|
try {
|
|
637
696
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId)))
|
|
@@ -659,7 +718,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
659
718
|
async function handleMessageUpdate(receivedMessage) {
|
|
660
719
|
if (!receivedMessage.guildId || !receivedMessage.channelId)
|
|
661
720
|
return;
|
|
662
|
-
const installations = availabilityAwareInstallations(services);
|
|
721
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
663
722
|
for (const { moduleId, contribution } of messageUpdateContributions) {
|
|
664
723
|
try {
|
|
665
724
|
if (!(await installations.isModuleEnabled(receivedMessage.guildId, moduleId)))
|
|
@@ -693,7 +752,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
693
752
|
async function handleMessageDelete(message) {
|
|
694
753
|
if (!message.guildId || !message.channelId)
|
|
695
754
|
return;
|
|
696
|
-
const installations = availabilityAwareInstallations(services);
|
|
755
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
697
756
|
for (const { moduleId, contribution } of messageDeleteContributions) {
|
|
698
757
|
try {
|
|
699
758
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId))) {
|
|
@@ -720,7 +779,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
720
779
|
async function handleMessageDeleteBulk(guildId, channelId, messageIds) {
|
|
721
780
|
if (messageIds.length === 0)
|
|
722
781
|
return;
|
|
723
|
-
const installations = availabilityAwareInstallations(services);
|
|
782
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
724
783
|
const boundedIds = messageIds.slice(0, 100);
|
|
725
784
|
for (const { moduleId, contribution } of messageDeleteBulkContributions) {
|
|
726
785
|
try {
|
|
@@ -754,7 +813,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
754
813
|
voiceSessions.delete(key);
|
|
755
814
|
const endedAt = new Date();
|
|
756
815
|
const durationSeconds = Math.max(1, Math.floor((endedAt.getTime() - previous.startedAt.getTime()) / 1_000));
|
|
757
|
-
const installations = availabilityAwareInstallations(services);
|
|
816
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
758
817
|
for (const { moduleId, contribution } of guildVoiceSessionContributions) {
|
|
759
818
|
try {
|
|
760
819
|
if (!(await installations.isModuleEnabled(previous.guildId, moduleId)))
|
|
@@ -822,10 +881,11 @@ class BoundedGuildEventQueue {
|
|
|
822
881
|
enqueue(guildId, run) {
|
|
823
882
|
if (!this.#accepting || this.#pending.length >= 1_000) {
|
|
824
883
|
this.logger.warn({ event: "module.event_dropped", reason: "queue_capacity" }, "Module gateway event queue rejected work");
|
|
825
|
-
return;
|
|
884
|
+
return false;
|
|
826
885
|
}
|
|
827
886
|
this.#pending.push({ guildId, run });
|
|
828
887
|
this.#pump();
|
|
888
|
+
return true;
|
|
829
889
|
}
|
|
830
890
|
queueDepth() {
|
|
831
891
|
return this.#pending.length;
|
|
@@ -2014,8 +2074,4 @@ function isSingleUnicodeEmoji(value) {
|
|
|
2014
2074
|
];
|
|
2015
2075
|
return graphemes.length === 1;
|
|
2016
2076
|
}
|
|
2017
|
-
function safeError(error) {
|
|
2018
|
-
const value = error instanceof Error ? error : new Error(String(error));
|
|
2019
|
-
return { name: value.name, message: value.message };
|
|
2020
|
-
}
|
|
2021
2077
|
//# sourceMappingURL=events.js.map
|
package/dist/guilds.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InstallationRepository, GuildCommandRepository, withTransaction, } from "@helyx/database";
|
|
2
|
+
import { safeError } from "@helyx/core";
|
|
2
3
|
import { Events } from "discord.js";
|
|
3
4
|
const RECONCILIATION_PAGE_SIZE = 250;
|
|
4
5
|
const DEFAULT_RECONCILIATION_INTERVAL_MS = 15 * 60 * 1_000;
|
|
@@ -214,8 +215,4 @@ export function installGuildTracking(client, database, modules, logger, enabledB
|
|
|
214
215
|
};
|
|
215
216
|
return prepare;
|
|
216
217
|
}
|
|
217
|
-
function safeError(error) {
|
|
218
|
-
const value = error instanceof Error ? error : new Error(String(error));
|
|
219
|
-
return { name: value.name, message: value.message };
|
|
220
|
-
}
|
|
221
218
|
//# sourceMappingURL=guilds.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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
|
-
import { createPlatformServices, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, } from "@helyx/platform";
|
|
6
|
+
import { createPlatformServices, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, PlatformMemberRoleGrantOwnershipService, PostgresMemberRoleGrantOwnershipStore, } from "@helyx/platform";
|
|
7
7
|
import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
8
8
|
import { Client, Events, GatewayIntentBits, Partials } from "discord.js";
|
|
9
9
|
import pino from "pino";
|
|
@@ -30,9 +30,14 @@ 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";
|
|
39
|
+
import { DiscordAnalyticsInspection } from "./discord-analytics.js";
|
|
40
|
+
import { DiscordInviteInspection } from "./discord-invites.js";
|
|
36
41
|
import { GuildCommandWorker } from "./guild-command-worker.js";
|
|
37
42
|
import { GuildCommandDiscordClient } from "./guild-command-discord-client.js";
|
|
38
43
|
import { GuildCommandReadService } from "./guild-command-read-service.js";
|
|
@@ -43,6 +48,8 @@ import { createLocalScheduledTaskRoute, createScheduledTaskRunner, schedulerOper
|
|
|
43
48
|
import { createLocalNumericWriteBehind } from "./numeric-write-behind.js";
|
|
44
49
|
import { discordRestOptions } from "./discord-rest.js";
|
|
45
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";
|
|
46
53
|
export { loadConfiguredModulePackages } from "./modules.js";
|
|
47
54
|
export { resolveModulePackageSpecifiers } from "./module-configuration.js";
|
|
48
55
|
export * from "./control-server.js";
|
|
@@ -81,9 +88,7 @@ export async function startBot(processEnvironment = process.env) {
|
|
|
81
88
|
},
|
|
82
89
|
{
|
|
83
90
|
key: "creator-connections",
|
|
84
|
-
state:
|
|
85
|
-
? "operational"
|
|
86
|
-
: "major_outage",
|
|
91
|
+
state: "operational",
|
|
87
92
|
reasonCode: environment.HELYX_OAUTH_ENABLED
|
|
88
93
|
? null
|
|
89
94
|
: "feature_disabled",
|
|
@@ -93,7 +98,7 @@ export async function startBot(processEnvironment = process.env) {
|
|
|
93
98
|
];
|
|
94
99
|
},
|
|
95
100
|
onError(error) {
|
|
96
|
-
bootstrapLogger.warn({ event: "status.heartbeat_failed",
|
|
101
|
+
bootstrapLogger.warn({ event: "status.heartbeat_failed", error: safeError(error) }, "Status heartbeat could not be delivered");
|
|
97
102
|
},
|
|
98
103
|
});
|
|
99
104
|
reporter.start();
|
|
@@ -126,6 +131,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
126
131
|
: undefined;
|
|
127
132
|
let databaseClosed = false;
|
|
128
133
|
let retentionCleanupTimer;
|
|
134
|
+
let premiumCatalogueTimer;
|
|
129
135
|
async function closeDatabase() {
|
|
130
136
|
if (databaseClosed)
|
|
131
137
|
return;
|
|
@@ -196,6 +202,15 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
196
202
|
})),
|
|
197
203
|
}, "Module databases ready");
|
|
198
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();
|
|
199
214
|
const client = new Client({
|
|
200
215
|
rest: discordRestOptions,
|
|
201
216
|
intents: gatewayIntentsForModules(featureModules, environment.HELYX_MESSAGE_CONTENT_ENABLED, environment.HELYX_GUILD_MEMBERS_ENABLED),
|
|
@@ -222,6 +237,16 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
222
237
|
: undefined;
|
|
223
238
|
const supportModule = createSupportModule(cloudConnector);
|
|
224
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
|
+
});
|
|
225
250
|
const oauthModule = environment.HELYX_OAUTH_ENABLED
|
|
226
251
|
? createOAuthModule()
|
|
227
252
|
: undefined;
|
|
@@ -230,6 +255,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
230
255
|
permissionsModule,
|
|
231
256
|
supportModule,
|
|
232
257
|
privacyModule,
|
|
258
|
+
...(premiumActivationModule ? [premiumActivationModule] : []),
|
|
233
259
|
...(oauthModule ? [oauthModule] : []),
|
|
234
260
|
...(cloudManagementModule ? [cloudManagementModule] : []),
|
|
235
261
|
...featureModules,
|
|
@@ -303,6 +329,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
303
329
|
permissionsModule,
|
|
304
330
|
supportModule,
|
|
305
331
|
privacyModule,
|
|
332
|
+
...(premiumActivationModule ? [premiumActivationModule] : []),
|
|
306
333
|
...(oauthModule ? [oauthModule] : []),
|
|
307
334
|
...(cloudManagementModule ? [cloudManagementModule] : []),
|
|
308
335
|
].map(({ manifest }) => manifest),
|
|
@@ -320,8 +347,9 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
320
347
|
];
|
|
321
348
|
const platformServices = new Map();
|
|
322
349
|
const serviceAccess = new ServiceRegistry(platformServices);
|
|
350
|
+
const serviceOptions = platformServiceOptions(environment, publicModules);
|
|
323
351
|
for (const [name, service] of createPlatformServices(database, publicModules, {
|
|
324
|
-
...
|
|
352
|
+
...serviceOptions,
|
|
325
353
|
activationServices: serviceAccess,
|
|
326
354
|
})) {
|
|
327
355
|
platformServices.set(name, service);
|
|
@@ -349,9 +377,14 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
349
377
|
platformServices.set(HELYX_SERVICE_NAMES.discordMessages, new DiscordMessageDeliveryService(client));
|
|
350
378
|
platformServices.set(HELYX_SERVICE_NAMES.discordOwnedMessages, new DiscordOwnedMessageDeletionService(client));
|
|
351
379
|
platformServices.set(HELYX_SERVICE_NAMES.memberEnforcement, new DiscordMemberEnforcementService(client));
|
|
352
|
-
|
|
380
|
+
const memberRoles = new DiscordMemberRoleService(client);
|
|
381
|
+
platformServices.set(HELYX_SERVICE_NAMES.memberRoles, memberRoles);
|
|
382
|
+
platformServices.set(HELYX_SERVICE_NAMES.memberRoleGrants, new PlatformMemberRoleGrantOwnershipService(new PostgresMemberRoleGrantOwnershipStore(database), memberRoles));
|
|
353
383
|
platformServices.set(HELYX_SERVICE_NAMES.discordResources, new DiscordResourceInspection(client));
|
|
354
384
|
platformServices.set(HELYX_SERVICE_NAMES.discordGuildHealth, new DiscordGuildHealthInspection(client, publicModules, serviceAccess));
|
|
385
|
+
platformServices.set(HELYX_SERVICE_NAMES.discordAnalytics, new DiscordAnalyticsInspection(client));
|
|
386
|
+
if (serviceOptions.inviteFingerprintKey)
|
|
387
|
+
platformServices.set(HELYX_SERVICE_NAMES.discordInvites, new DiscordInviteInspection(client, serviceOptions.inviteFingerprintKey));
|
|
355
388
|
if (featureModules.some(({ manifest }) => manifest.id === "helyx.logging")) {
|
|
356
389
|
platformServices.set(HELYX_SERVICE_NAMES.moduleLogging, new DiscordModuleLoggingService(client, database, publicModules, bootstrapLogger));
|
|
357
390
|
}
|
|
@@ -364,6 +397,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
364
397
|
managementModule.manifest.id,
|
|
365
398
|
permissionsModule.manifest.id,
|
|
366
399
|
supportModule.manifest.id,
|
|
400
|
+
...(premiumActivationModule ? [premiumActivationModule.manifest.id] : []),
|
|
367
401
|
...(oauthModule ? [oauthModule.manifest.id] : []),
|
|
368
402
|
...(cloudManagementModule ? [cloudManagementModule.manifest.id] : []),
|
|
369
403
|
...(operationsModule ? [operationsModule.manifest.id] : []),
|
|
@@ -378,6 +412,9 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
378
412
|
prepare: prepareGuilds,
|
|
379
413
|
selfAuthorizingGuildModuleIds: new Set([
|
|
380
414
|
supportModule.manifest.id,
|
|
415
|
+
...(premiumActivationModule
|
|
416
|
+
? [premiumActivationModule.manifest.id]
|
|
417
|
+
: []),
|
|
381
418
|
...(oauthModule ? [oauthModule.manifest.id] : []),
|
|
382
419
|
...(operationsModule ? [operationsModule.manifest.id] : []),
|
|
383
420
|
]),
|
|
@@ -416,6 +453,8 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
416
453
|
process.off("SIGTERM", onSigterm);
|
|
417
454
|
if (retentionCleanupTimer)
|
|
418
455
|
clearInterval(retentionCleanupTimer);
|
|
456
|
+
if (premiumCatalogueTimer)
|
|
457
|
+
clearInterval(premiumCatalogueTimer);
|
|
419
458
|
await scheduledTasks?.stop().catch((error) => {
|
|
420
459
|
process.exitCode = 1;
|
|
421
460
|
bootstrapLogger.error({ event: "scheduler.stop_failed", error: safeError(error) }, "Scheduled task runner shutdown failed");
|
|
@@ -579,6 +618,8 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
579
618
|
catch (error) {
|
|
580
619
|
if (retentionCleanupTimer)
|
|
581
620
|
clearInterval(retentionCleanupTimer);
|
|
621
|
+
if (premiumCatalogueTimer)
|
|
622
|
+
clearInterval(premiumCatalogueTimer);
|
|
582
623
|
await closeDatabase().catch((closeError) => {
|
|
583
624
|
bootstrapLogger.error({ event: "database.close_failed", error: safeError(closeError) }, "Database pool cleanup failed");
|
|
584
625
|
});
|
package/dist/interactions.d.ts
CHANGED
|
@@ -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;
|