@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/CHANGELOG.md +57 -0
- package/README.md +4 -4
- package/dist/cloud-management-module.js +1 -5
- package/dist/command-registration-policy.js +1 -0
- package/dist/control-server.d.ts +1 -33
- package/dist/control-server.js +23 -41
- package/dist/dashboard-commands.d.ts +1 -0
- package/dist/dashboard-commands.js +1 -0
- package/dist/dashboard-module-projection.d.ts +39 -0
- package/dist/dashboard-module-projection.js +28 -0
- package/dist/events.d.ts +1 -1
- package/dist/events.js +29 -20
- package/dist/guilds.js +1 -4
- package/dist/index.js +38 -5
- package/dist/interactions.d.ts +1 -1
- package/dist/interactions.js +41 -34
- package/dist/module-logging.js +1 -4
- package/dist/module-status-control.js +1 -1
- 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-supervisor.js +30 -1
- package/dist/shard-worker.d.ts +2 -0
- package/dist/shard-worker.js +25 -1
- package/documentation/examples/.env.example +2 -2
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Update Fastify to the patched 5.12.3 release used by hosted and self-hosted runtime distributions.
|
|
8
|
+
|
|
9
|
+
## 0.6.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Allow Premium slots to be released, reassigned and moved immediately without a
|
|
14
|
+
transfer cooldown, while preserving atomic entitlement projection and
|
|
15
|
+
final-source Premium module disablement.
|
|
16
|
+
|
|
17
|
+
Add fail-closed Premium quantity incident controls, recoverable allocation
|
|
18
|
+
management for removed servers, overlapping Stripe webhook-secret rotation and
|
|
19
|
+
restricted exception diagnostics. Complete the production environment
|
|
20
|
+
contract for every API and bot schema field.
|
|
21
|
+
|
|
22
|
+
- Updated dependencies
|
|
23
|
+
- @helyx/config@1.0.0
|
|
24
|
+
- @helyx/database@1.0.0
|
|
25
|
+
|
|
26
|
+
## 0.6.0
|
|
27
|
+
|
|
28
|
+
### Minor Changes
|
|
29
|
+
|
|
30
|
+
- 1dfbf5b: Add the reviewed Premium module catalogue and enforce its entitlement across
|
|
31
|
+
hosted command, interaction, event, scheduled-task, configuration and dashboard
|
|
32
|
+
mutation paths. Add account-owned Premium slot allocation, the `/activate`
|
|
33
|
+
Discord flow, final-source-loss repair and optional safe billing lifecycle
|
|
34
|
+
notifications. Add audited, privacy-aware Admin Premium overrides and
|
|
35
|
+
deterministic entitlement convergence for inactive subscriptions and quantity
|
|
36
|
+
reductions, while keeping billing and Premium-only commands out of generated
|
|
37
|
+
self-host projects.
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- Updated dependencies [1dfbf5b]
|
|
42
|
+
- @helyx/config@0.7.0
|
|
43
|
+
- @helyx/database@0.12.0
|
|
44
|
+
- @helyx/module-manifest@0.9.0
|
|
45
|
+
- @helyx/platform@0.8.0
|
|
46
|
+
|
|
47
|
+
## 0.5.1
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- 579f2c4: Keep transport-public components behind module availability and enablement,
|
|
52
|
+
and classify dashboard permission operations separately from Discord commands.
|
|
53
|
+
- Updated dependencies [579f2c4]
|
|
54
|
+
- Updated dependencies [579f2c4]
|
|
55
|
+
- Updated dependencies [579f2c4]
|
|
56
|
+
- @helyx/sdk@0.11.0
|
|
57
|
+
- @helyx/config@0.6.0
|
|
58
|
+
- @helyx/database@0.11.0
|
|
59
|
+
|
|
3
60
|
## 0.5.0
|
|
4
61
|
|
|
5
62
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -45,10 +45,10 @@ bot-specific `HELYX_STATUS_HEARTBEAT_SECRET`,
|
|
|
45
45
|
`CF_ACCESS_CLIENT_ID`/`CF_ACCESS_CLIENT_SECRET` pair. Never reuse another
|
|
46
46
|
producer's Access identity or heartbeat secret.
|
|
47
47
|
|
|
48
|
-
Giveaways also
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
Giveaways and Invite Tracking also require the existing
|
|
49
|
+
`HELYX_DATA_ENCRYPTION_KEY`. Core uses it for secure weighted-selection
|
|
50
|
+
checkpoints and stable invite-member fingerprints without exposing the key to
|
|
51
|
+
a module. Keep it stable across deployments.
|
|
52
52
|
|
|
53
53
|
Trivia uses questions already imported into PostgreSQL. Multiple Choice and
|
|
54
54
|
Guess both run entirely from the saved question catalogue, and the bot runtime
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineModule, } from "@helyx/sdk";
|
|
2
|
+
import { safeError } from "@helyx/core";
|
|
2
3
|
import { Events } from "discord.js";
|
|
3
4
|
import { ClientCloudDiscordDirectory, CloudConnectorUserError, } from "./cloud-connector.js";
|
|
4
5
|
import { INTERNAL_MODULE_MANIFEST_FIELDS } from "./internal-module-manifest.js";
|
|
@@ -622,9 +623,4 @@ function restrictedPolicy() {
|
|
|
622
623
|
deniedUserIds: [],
|
|
623
624
|
};
|
|
624
625
|
}
|
|
625
|
-
function safeError(error) {
|
|
626
|
-
return error instanceof Error
|
|
627
|
-
? { name: error.name, message: error.message }
|
|
628
|
-
: { name: "Error", message: String(error) };
|
|
629
|
-
}
|
|
630
626
|
//# sourceMappingURL=cloud-management-module.js.map
|
|
@@ -5,6 +5,7 @@ const platformGlobalModuleIds = new Set([
|
|
|
5
5
|
"helyx.oauth",
|
|
6
6
|
"helyx.permissions",
|
|
7
7
|
"helyx.privacy",
|
|
8
|
+
"helyx.premium-activation",
|
|
8
9
|
]);
|
|
9
10
|
const deploymentModuleIds = new Set(["helyx.cloud-connect"]);
|
|
10
11
|
const internalGuildModuleIds = new Set(["helyx.developer-operations"]);
|
package/dist/control-server.d.ts
CHANGED
|
@@ -7,41 +7,9 @@ import { type BotModuleStatusMutationResult } from "./module-status-control.js";
|
|
|
7
7
|
import { type FirstTimeCommandSetupHandler } from "./first-time-command-setup.js";
|
|
8
8
|
import { type ShardHealthSnapshot } from "./shard-contracts.js";
|
|
9
9
|
import { type DashboardPermissionState } from "./control-permissions.js";
|
|
10
|
-
import { type
|
|
10
|
+
import { type DashboardModuleDefinition } from "./dashboard-module-projection.js";
|
|
11
11
|
import { type AvailabilityModuleCatalogue } from "./module-availability-control.js";
|
|
12
12
|
import { registerGuildCommandControlRoutes } from "./guild-command-control-routes.js";
|
|
13
|
-
interface DashboardLogEventDefinition {
|
|
14
|
-
id: string;
|
|
15
|
-
name: string;
|
|
16
|
-
description: string;
|
|
17
|
-
defaultEnabled: boolean;
|
|
18
|
-
}
|
|
19
|
-
interface DashboardActionDefinition {
|
|
20
|
-
id: string;
|
|
21
|
-
label: string;
|
|
22
|
-
description: string;
|
|
23
|
-
buttonLabel: string;
|
|
24
|
-
style: "primary" | "secondary" | "danger";
|
|
25
|
-
}
|
|
26
|
-
type DashboardResourceDefinition = NonNullable<NonNullable<ModuleDefinition["manifest"]["dashboard"]>["resources"]>[number];
|
|
27
|
-
interface DashboardModuleDefinition {
|
|
28
|
-
id: string;
|
|
29
|
-
name: string;
|
|
30
|
-
version: string;
|
|
31
|
-
summary: string;
|
|
32
|
-
category: string;
|
|
33
|
-
icon: string;
|
|
34
|
-
availability: "available";
|
|
35
|
-
releaseStatus: "planned" | "testing" | "beta" | "live";
|
|
36
|
-
requiredPermissions: string[];
|
|
37
|
-
settings: readonly unknown[];
|
|
38
|
-
commands: DashboardCommandDefinition[];
|
|
39
|
-
logEvents: DashboardLogEventDefinition[];
|
|
40
|
-
actions: DashboardActionDefinition[];
|
|
41
|
-
achievementTriggers: NonNullable<ModuleDefinition["manifest"]["contributions"]["achievementTriggers"]>;
|
|
42
|
-
resources: readonly DashboardResourceDefinition[];
|
|
43
|
-
workspace?: NonNullable<ModuleDefinition["manifest"]["dashboard"]>["workspace"];
|
|
44
|
-
}
|
|
45
13
|
interface DashboardLoggingPolicy {
|
|
46
14
|
eventId: string;
|
|
47
15
|
enabled: boolean;
|
package/dist/control-server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AuditRepository, GuildAccessPolicyRepository, GuildCommandActiveLeaseError, GuildCommandRevisionConflictError, GuildCommandSetupConflictError, InstallationRepository, ModuleLoggingPolicyRepository, OptimisticConcurrencyError, PermissionPolicyRepository, withTransaction, } from "@helyx/database";
|
|
2
|
+
import { safeError } from "@helyx/core";
|
|
2
3
|
import { DashboardActionValidationError, DEFAULT_GUILD_ACCESS_POLICY, HELYX_SERVICE_NAMES, ModuleStateValidationError, } from "@helyx/sdk";
|
|
3
4
|
import { actionReceiptSchema, signedAssessmentEnvelopeSchema, } from "@helyx/verification-contracts";
|
|
4
5
|
import { canAccessGuildDashboard, canMutatePermissionPolicy, canUseCommand, resolveHelyxAuthority, } from "@helyx/core";
|
|
@@ -8,12 +9,13 @@ import { createHash, timingSafeEqual } from "node:crypto";
|
|
|
8
9
|
import { z, ZodError } from "zod";
|
|
9
10
|
import { addChannelMessageReaction as addDiscordChannelMessageReaction, deleteChannelMessage as deleteDiscordChannelMessage, editChannelMessage as editDiscordChannelMessage, normalizeDiscordPermission, sendToChannel as sendDiscordMessageToChannel, } from "./interactions.js";
|
|
10
11
|
import { mutateDashboardModuleStatus, } from "./module-status-control.js";
|
|
12
|
+
import { canAccessModule, PREMIUM_MODULE_UNAVAILABLE_MESSAGE, } from "./premium-access.js";
|
|
11
13
|
import { completeFirstTimeCommandSetup, } from "./first-time-command-setup.js";
|
|
12
14
|
import { SHARD_HEALTH_STALE_MS, } from "./shard-contracts.js";
|
|
13
|
-
import { dashboardCategoryLabel } from "./dashboard-category.js";
|
|
14
15
|
import { assertVersion, dashboardActionPermissionId, declaredManagedResourcePermission, fromDashboardPolicy, managedResourcePermissionId, permissionPoliciesEqual, permissionPolicyKey, safeOptionalVersion, safeVersion, toDashboardPolicy, } from "./control-permissions.js";
|
|
15
16
|
import { assertPermissionMutationAllowed, BotControlForbiddenError, } from "./control-errors.js";
|
|
16
|
-
import { dashboardCommands
|
|
17
|
+
import { dashboardCommands } from "./dashboard-commands.js";
|
|
18
|
+
import { mapDashboardModule, } from "./dashboard-module-projection.js";
|
|
17
19
|
import { availabilityCatalogueDefinitions, availabilityCatalogue, dashboardModuleStatus, disabledModuleSummary, moduleAvailabilityService, moduleAvailabilityStates, statusCatalogueDefinitions, } from "./module-availability-control.js";
|
|
18
20
|
import { registerGuildCommandControlRoutes } from "./guild-command-control-routes.js";
|
|
19
21
|
import { createPrivacyProviderRegistry, describePrivacyProviders, } from "./privacy-provider-registry.js";
|
|
@@ -633,13 +635,13 @@ export class BotControlService {
|
|
|
633
635
|
dashboardModuleIds: this.#dashboardModules.map(({ id }) => id),
|
|
634
636
|
installations: this.#installations,
|
|
635
637
|
services: this.#services,
|
|
636
|
-
requireModuleAvailable: () => this.#
|
|
638
|
+
requireModuleAvailable: () => this.#requireModuleAccessible(input.guildId, input.moduleId),
|
|
637
639
|
});
|
|
638
640
|
}
|
|
639
641
|
async completeFirstTimeCommandSetup(input) {
|
|
640
642
|
for (const moduleIdValue of new Set(input.moduleIds)) {
|
|
641
643
|
this.#requireDashboardModule(moduleIdValue);
|
|
642
|
-
await this.#
|
|
644
|
+
await this.#requireModuleAccessible(input.guildId, moduleIdValue);
|
|
643
645
|
}
|
|
644
646
|
return completeFirstTimeCommandSetup({
|
|
645
647
|
installations: this.#installations,
|
|
@@ -649,7 +651,7 @@ export class BotControlService {
|
|
|
649
651
|
}
|
|
650
652
|
async updateConfiguration(input) {
|
|
651
653
|
const module = this.#requireDashboardModule(input.moduleId);
|
|
652
|
-
await this.#
|
|
654
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
653
655
|
await this.#validateDiscordSettings(input.guildId, module, input.value);
|
|
654
656
|
if (module.configuration?.validateUpdate) {
|
|
655
657
|
try {
|
|
@@ -683,7 +685,7 @@ export class BotControlService {
|
|
|
683
685
|
}
|
|
684
686
|
async updatePermissions(input) {
|
|
685
687
|
const module = this.#requireDashboardModule(input.moduleId);
|
|
686
|
-
await this.#
|
|
688
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
687
689
|
const commands = dashboardCommands(module);
|
|
688
690
|
const supplied = new Map(input.state.commands.map((command) => [command.commandId, command]));
|
|
689
691
|
if (supplied.size !== input.state.commands.length ||
|
|
@@ -808,8 +810,8 @@ export class BotControlService {
|
|
|
808
810
|
}
|
|
809
811
|
async updateLoggingPolicy(input) {
|
|
810
812
|
await Promise.all([
|
|
811
|
-
this.#
|
|
812
|
-
this.#
|
|
813
|
+
this.#requireModuleAccessible(input.guildId, input.moduleId),
|
|
814
|
+
this.#requireModuleAccessible(input.guildId, "helyx.logging"),
|
|
813
815
|
]);
|
|
814
816
|
if (!this.#modules.has("helyx.logging")) {
|
|
815
817
|
throw new BotControlNotFoundError("Logging is not installed");
|
|
@@ -855,7 +857,7 @@ export class BotControlService {
|
|
|
855
857
|
}
|
|
856
858
|
async executeDashboardAction(input) {
|
|
857
859
|
const module = this.#requireDashboardModule(input.moduleId);
|
|
858
|
-
await this.#
|
|
860
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
859
861
|
const declared = module.manifest.dashboard?.actions?.find(({ id }) => id === input.actionId);
|
|
860
862
|
const action = module.dashboardActions?.find(({ id }) => id === input.actionId);
|
|
861
863
|
if (!declared || !action) {
|
|
@@ -914,13 +916,13 @@ export class BotControlService {
|
|
|
914
916
|
})), relation.columns.map(({ key }) => key));
|
|
915
917
|
}
|
|
916
918
|
async previewManagedResource(input) {
|
|
917
|
-
await this.#
|
|
919
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
918
920
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
919
921
|
this.#requireManagedResourceOperation(module, input.resourceType, "preview");
|
|
920
922
|
return resource.preview(await this.#managedResourceContext(input, module, "preview"), input.value);
|
|
921
923
|
}
|
|
922
924
|
async createManagedResource(input) {
|
|
923
|
-
await this.#
|
|
925
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
924
926
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
925
927
|
this.#requireManagedResourceOperation(module, input.resourceType, "create");
|
|
926
928
|
const context = await this.#managedResourceContext(input, module, "create");
|
|
@@ -930,7 +932,7 @@ export class BotControlService {
|
|
|
930
932
|
return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.create(context, input.value, input.intent)), managedResourceColumnKeys(module, input.resourceType));
|
|
931
933
|
}
|
|
932
934
|
async updateManagedResource(input) {
|
|
933
|
-
await this.#
|
|
935
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
934
936
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
935
937
|
this.#requireManagedResourceOperation(module, input.resourceType, "update");
|
|
936
938
|
return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.update(await this.#managedResourceContext(input, module, "update"), {
|
|
@@ -940,7 +942,7 @@ export class BotControlService {
|
|
|
940
942
|
})), managedResourceColumnKeys(module, input.resourceType));
|
|
941
943
|
}
|
|
942
944
|
async deleteManagedResource(input) {
|
|
943
|
-
await this.#
|
|
945
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
944
946
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
945
947
|
this.#requireManagedResourceOperation(module, input.resourceType, "delete");
|
|
946
948
|
return managedResourceDeleteResultSchema.parse(await resource.delete(await this.#managedResourceContext(input, module, "delete"), {
|
|
@@ -949,7 +951,7 @@ export class BotControlService {
|
|
|
949
951
|
}));
|
|
950
952
|
}
|
|
951
953
|
async executeManagedResourceAction(input) {
|
|
952
|
-
await this.#
|
|
954
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
953
955
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
954
956
|
const declared = module.manifest.dashboard?.resources
|
|
955
957
|
?.find(({ id }) => id === input.resourceType)
|
|
@@ -1053,6 +1055,12 @@ export class BotControlService {
|
|
|
1053
1055
|
if (availability && !(await availability.isAvailable(moduleIdValue)))
|
|
1054
1056
|
throw new BotControlValidationError("This module is temporarily unavailable for all hosted servers.");
|
|
1055
1057
|
}
|
|
1058
|
+
async #requireModuleAccessible(guildId, moduleIdValue) {
|
|
1059
|
+
await this.#requireModuleAvailable(moduleIdValue);
|
|
1060
|
+
const module = this.#requireModule(moduleIdValue);
|
|
1061
|
+
if (!(await canAccessModule(this.#services, guildId, module)))
|
|
1062
|
+
throw new BotControlValidationError(PREMIUM_MODULE_UNAVAILABLE_MESSAGE);
|
|
1063
|
+
}
|
|
1056
1064
|
#requireDashboardModule(moduleIdValue) {
|
|
1057
1065
|
const module = this.#requireModule(moduleIdValue);
|
|
1058
1066
|
if (!module.manifest.dashboard)
|
|
@@ -1703,10 +1711,7 @@ export async function createBotControlServer(input) {
|
|
|
1703
1711
|
return reply.code(mapped.status).send({ message: mapped.message });
|
|
1704
1712
|
if (error instanceof BotControlTransportError)
|
|
1705
1713
|
return reply.code(error.status).send({ message: error.message });
|
|
1706
|
-
|
|
1707
|
-
? { name: error.name, message: error.message }
|
|
1708
|
-
: { name: "Error", message: "Unknown control failure" };
|
|
1709
|
-
request.log.error({ error: safeError }, "Private bot control request failed");
|
|
1714
|
+
request.log.error({ error: safeError(error) }, "Private bot control request failed");
|
|
1710
1715
|
return reply.code(500).send({ message: "The control request failed" });
|
|
1711
1716
|
});
|
|
1712
1717
|
return server;
|
|
@@ -1885,27 +1890,4 @@ function validateManagedResourceActionValue(inputs, value) {
|
|
|
1885
1890
|
throw new DashboardActionValidationError(`${input.label} is not a valid action value.`);
|
|
1886
1891
|
}
|
|
1887
1892
|
}
|
|
1888
|
-
function mapDashboardModule(module) {
|
|
1889
|
-
const dashboard = module.manifest.dashboard;
|
|
1890
|
-
return {
|
|
1891
|
-
id: module.manifest.id,
|
|
1892
|
-
name: module.manifest.name,
|
|
1893
|
-
version: module.manifest.version,
|
|
1894
|
-
summary: module.manifest.description,
|
|
1895
|
-
category: dashboardCategoryLabel(dashboard.category),
|
|
1896
|
-
icon: dashboard.icon,
|
|
1897
|
-
availability: "available",
|
|
1898
|
-
releaseStatus: module.manifest.releaseStatus,
|
|
1899
|
-
requiredPermissions: [...module.manifest.permissions],
|
|
1900
|
-
settings: dashboard.settings,
|
|
1901
|
-
commands: dashboardCommands(module),
|
|
1902
|
-
logEvents: (module.logging?.events ?? []).map((event) => ({ ...event })),
|
|
1903
|
-
actions: (dashboard.actions ?? []).map((action) => ({ ...action })),
|
|
1904
|
-
achievementTriggers: (module.manifest.contributions?.achievementTriggers ?? []).map((trigger) => structuredClone(trigger)),
|
|
1905
|
-
resources: (dashboard.resources ?? []).map((resource) => structuredClone(resource)),
|
|
1906
|
-
workspace: dashboard.workspace
|
|
1907
|
-
? structuredClone(dashboard.workspace)
|
|
1908
|
-
: undefined,
|
|
1909
|
-
};
|
|
1910
|
-
}
|
|
1911
1893
|
//# sourceMappingURL=control-server.js.map
|
|
@@ -4,6 +4,7 @@ export interface DashboardCommandDefinition {
|
|
|
4
4
|
id: string;
|
|
5
5
|
name: string;
|
|
6
6
|
description: string;
|
|
7
|
+
kind: "command" | "operation";
|
|
7
8
|
defaultPreset: DashboardPermissionPolicy["preset"];
|
|
8
9
|
}
|
|
9
10
|
export declare function dashboardCommands(module: ModuleDefinition): DashboardCommandDefinition[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ModuleTier } from "@helyx/module-manifest";
|
|
2
|
+
import type { ModuleDefinition } from "@helyx/sdk";
|
|
3
|
+
import { type DashboardCommandDefinition } from "./dashboard-commands.js";
|
|
4
|
+
interface DashboardLogEventDefinition {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
defaultEnabled: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface DashboardActionDefinition {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
description: string;
|
|
14
|
+
buttonLabel: string;
|
|
15
|
+
style: "primary" | "secondary" | "danger";
|
|
16
|
+
}
|
|
17
|
+
type DashboardResourceDefinition = NonNullable<NonNullable<ModuleDefinition["manifest"]["dashboard"]>["resources"]>[number];
|
|
18
|
+
export interface DashboardModuleDefinition {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
summary: string;
|
|
23
|
+
category: string;
|
|
24
|
+
icon: string;
|
|
25
|
+
availability: "available";
|
|
26
|
+
releaseStatus: "planned" | "testing" | "beta" | "live";
|
|
27
|
+
tier: ModuleTier;
|
|
28
|
+
requiredPermissions: string[];
|
|
29
|
+
settings: readonly unknown[];
|
|
30
|
+
commands: DashboardCommandDefinition[];
|
|
31
|
+
logEvents: DashboardLogEventDefinition[];
|
|
32
|
+
actions: DashboardActionDefinition[];
|
|
33
|
+
achievementTriggers: NonNullable<ModuleDefinition["manifest"]["contributions"]["achievementTriggers"]>;
|
|
34
|
+
resources: readonly DashboardResourceDefinition[];
|
|
35
|
+
workspace?: NonNullable<ModuleDefinition["manifest"]["dashboard"]>["workspace"];
|
|
36
|
+
}
|
|
37
|
+
export declare function mapDashboardModule(module: ModuleDefinition): DashboardModuleDefinition;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=dashboard-module-projection.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getModuleTier } from "@helyx/module-manifest";
|
|
2
|
+
import { dashboardCategoryLabel } from "./dashboard-category.js";
|
|
3
|
+
import { dashboardCommands, } from "./dashboard-commands.js";
|
|
4
|
+
export function mapDashboardModule(module) {
|
|
5
|
+
const dashboard = module.manifest.dashboard;
|
|
6
|
+
return {
|
|
7
|
+
id: module.manifest.id,
|
|
8
|
+
name: module.manifest.name,
|
|
9
|
+
version: module.manifest.version,
|
|
10
|
+
summary: module.manifest.description,
|
|
11
|
+
category: dashboardCategoryLabel(dashboard.category),
|
|
12
|
+
icon: dashboard.icon,
|
|
13
|
+
availability: "available",
|
|
14
|
+
releaseStatus: module.manifest.releaseStatus,
|
|
15
|
+
tier: getModuleTier(module.manifest),
|
|
16
|
+
requiredPermissions: [...module.manifest.permissions],
|
|
17
|
+
settings: dashboard.settings,
|
|
18
|
+
commands: dashboardCommands(module),
|
|
19
|
+
logEvents: (module.logging?.events ?? []).map((event) => ({ ...event })),
|
|
20
|
+
actions: (dashboard.actions ?? []).map((action) => ({ ...action })),
|
|
21
|
+
achievementTriggers: (module.manifest.contributions?.achievementTriggers ?? []).map((trigger) => structuredClone(trigger)),
|
|
22
|
+
resources: (dashboard.resources ?? []).map((resource) => structuredClone(resource)),
|
|
23
|
+
workspace: dashboard.workspace
|
|
24
|
+
? structuredClone(dashboard.workspace)
|
|
25
|
+
: undefined,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=dashboard-module-projection.js.map
|
package/dist/events.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DiscordChannelAudienceInspection, DiscordChannelInspection, DiscordEmojiInspection, DiscordMessageInspection, DiscordGuildCapabilityInspection, DiscordGuildChannelAudienceInspection, DiscordResourceInspectionService, DiscordRoleInspection, DiscordMessageService, InstallationService, MemberEnforcementService, MemberRoleService, ModuleDefinition, ServiceAccess, VerificationMemberService } from "@helyx/sdk";
|
|
2
2
|
import { type Client, type MessageReaction, type PartialMessageReaction } from "discord.js";
|
|
3
3
|
import { type InteractionLogger } from "./interactions.js";
|
|
4
|
-
export declare function availabilityAwareInstallations(services: ServiceAccess): Pick<InstallationService, "isModuleEnabled">;
|
|
4
|
+
export declare function availabilityAwareInstallations(services: ServiceAccess, modulesById?: ReadonlyMap<string, ModuleDefinition>): Pick<InstallationService, "isModuleEnabled">;
|
|
5
5
|
export declare function installModuleEvents(client: Client, modules: readonly ModuleDefinition[], logger: InteractionLogger, services: ServiceAccess): ModuleEventHandle;
|
|
6
6
|
export interface ModuleEventHandle {
|
|
7
7
|
stop(): Promise<void>;
|
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,
|
|
@@ -149,7 +154,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
149
154
|
if (!message.guildId || !privilegedEligibilityQueue)
|
|
150
155
|
return;
|
|
151
156
|
privilegedEligibilityQueue.enqueue(message.guildId, async () => {
|
|
152
|
-
const installations = availabilityAwareInstallations(services);
|
|
157
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
153
158
|
const eligible = [];
|
|
154
159
|
for (const item of privilegedMessageContributions) {
|
|
155
160
|
try {
|
|
@@ -301,7 +306,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
301
306
|
});
|
|
302
307
|
}
|
|
303
308
|
async function handleResourceLifecycle(contributions, serverId, identifier, resourceId, eventName) {
|
|
304
|
-
const installations = availabilityAwareInstallations(services);
|
|
309
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
305
310
|
for (const { moduleId, contribution } of contributions) {
|
|
306
311
|
try {
|
|
307
312
|
if (!(await installations.isModuleEnabled(serverId, moduleId))) {
|
|
@@ -327,7 +332,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
327
332
|
async function handlePrivilegedMessageCreate(message, contributions) {
|
|
328
333
|
if (!message.guildId || !message.channelId)
|
|
329
334
|
return;
|
|
330
|
-
const installations = availabilityAwareInstallations(services);
|
|
335
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
331
336
|
for (const { moduleId, contribution } of contributions) {
|
|
332
337
|
try {
|
|
333
338
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId)))
|
|
@@ -360,7 +365,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
360
365
|
async function handleGuildMessageCreate(message) {
|
|
361
366
|
if (!message.guildId || !message.channelId)
|
|
362
367
|
return;
|
|
363
|
-
const installations = availabilityAwareInstallations(services);
|
|
368
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
364
369
|
const parentChannelId = message.channel.isThread()
|
|
365
370
|
? (message.channel.parentId ?? undefined)
|
|
366
371
|
: undefined;
|
|
@@ -456,7 +461,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
456
461
|
: reaction.message;
|
|
457
462
|
if (!message.guildId || !message.channelId)
|
|
458
463
|
return;
|
|
459
|
-
const installations = availabilityAwareInstallations(services);
|
|
464
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
460
465
|
const member = await message.guild?.members.fetch(user.id);
|
|
461
466
|
const userRoleIds = member ? [...member.roles.cache.keys()] : [];
|
|
462
467
|
const parentChannelId = messageParentChannelId(message);
|
|
@@ -472,6 +477,14 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
472
477
|
messageId: message.id,
|
|
473
478
|
userId: receivedUser.id,
|
|
474
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,
|
|
475
488
|
emojiKey: reactionEmojiKey(reaction),
|
|
476
489
|
emojiIdentifier: reactionEmojiIdentifier(reaction),
|
|
477
490
|
reactionCount: Math.max(0, reaction.count ?? 0),
|
|
@@ -513,7 +526,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
513
526
|
async function handleMemberAdd(member) {
|
|
514
527
|
if (!member.joinedAt)
|
|
515
528
|
throw new Error("Discord member join timestamp is unavailable");
|
|
516
|
-
const installations = availabilityAwareInstallations(services);
|
|
529
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
517
530
|
const continuityGeneration = inviteInspection
|
|
518
531
|
? inviteInspection.continuityGeneration({ guildId: member.guild.id })
|
|
519
532
|
: `runtime:${client.readyTimestamp ?? 0}`;
|
|
@@ -563,7 +576,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
563
576
|
}
|
|
564
577
|
}
|
|
565
578
|
async function handleMemberRemove(member) {
|
|
566
|
-
const installations = availabilityAwareInstallations(services);
|
|
579
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
567
580
|
for (const { moduleId, contribution } of memberRemoveContributions) {
|
|
568
581
|
try {
|
|
569
582
|
if (!(await installations.isModuleEnabled(member.guild.id, moduleId))) {
|
|
@@ -603,7 +616,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
603
616
|
: reaction.message;
|
|
604
617
|
if (!message.guildId || !message.channelId)
|
|
605
618
|
return;
|
|
606
|
-
const installations = availabilityAwareInstallations(services);
|
|
619
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
607
620
|
const parentChannelId = messageParentChannelId(message);
|
|
608
621
|
for (const { moduleId, contribution } of reactionRemoveContributions) {
|
|
609
622
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId))) {
|
|
@@ -647,7 +660,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
647
660
|
: receivedMessage;
|
|
648
661
|
if (!message.guildId || !message.channelId)
|
|
649
662
|
return;
|
|
650
|
-
const installations = availabilityAwareInstallations(services);
|
|
663
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
651
664
|
for (const { moduleId, contribution } of reactionRemoveAllContributions) {
|
|
652
665
|
try {
|
|
653
666
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId)))
|
|
@@ -677,7 +690,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
677
690
|
const message = reaction.message;
|
|
678
691
|
if (!message.guildId || !message.channelId)
|
|
679
692
|
return;
|
|
680
|
-
const installations = availabilityAwareInstallations(services);
|
|
693
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
681
694
|
for (const { moduleId, contribution } of reactionRemoveEmojiContributions) {
|
|
682
695
|
try {
|
|
683
696
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId)))
|
|
@@ -705,7 +718,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
705
718
|
async function handleMessageUpdate(receivedMessage) {
|
|
706
719
|
if (!receivedMessage.guildId || !receivedMessage.channelId)
|
|
707
720
|
return;
|
|
708
|
-
const installations = availabilityAwareInstallations(services);
|
|
721
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
709
722
|
for (const { moduleId, contribution } of messageUpdateContributions) {
|
|
710
723
|
try {
|
|
711
724
|
if (!(await installations.isModuleEnabled(receivedMessage.guildId, moduleId)))
|
|
@@ -739,7 +752,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
739
752
|
async function handleMessageDelete(message) {
|
|
740
753
|
if (!message.guildId || !message.channelId)
|
|
741
754
|
return;
|
|
742
|
-
const installations = availabilityAwareInstallations(services);
|
|
755
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
743
756
|
for (const { moduleId, contribution } of messageDeleteContributions) {
|
|
744
757
|
try {
|
|
745
758
|
if (!(await installations.isModuleEnabled(message.guildId, moduleId))) {
|
|
@@ -766,7 +779,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
766
779
|
async function handleMessageDeleteBulk(guildId, channelId, messageIds) {
|
|
767
780
|
if (messageIds.length === 0)
|
|
768
781
|
return;
|
|
769
|
-
const installations = availabilityAwareInstallations(services);
|
|
782
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
770
783
|
const boundedIds = messageIds.slice(0, 100);
|
|
771
784
|
for (const { moduleId, contribution } of messageDeleteBulkContributions) {
|
|
772
785
|
try {
|
|
@@ -800,7 +813,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
800
813
|
voiceSessions.delete(key);
|
|
801
814
|
const endedAt = new Date();
|
|
802
815
|
const durationSeconds = Math.max(1, Math.floor((endedAt.getTime() - previous.startedAt.getTime()) / 1_000));
|
|
803
|
-
const installations = availabilityAwareInstallations(services);
|
|
816
|
+
const installations = availabilityAwareInstallations(services, modulesById);
|
|
804
817
|
for (const { moduleId, contribution } of guildVoiceSessionContributions) {
|
|
805
818
|
try {
|
|
806
819
|
if (!(await installations.isModuleEnabled(previous.guildId, moduleId)))
|
|
@@ -2061,8 +2074,4 @@ function isSingleUnicodeEmoji(value) {
|
|
|
2061
2074
|
];
|
|
2062
2075
|
return graphemes.length === 1;
|
|
2063
2076
|
}
|
|
2064
|
-
function safeError(error) {
|
|
2065
|
-
const value = error instanceof Error ? error : new Error(String(error));
|
|
2066
|
-
return { name: value.name, message: value.message };
|
|
2067
|
-
}
|
|
2068
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
|