@helyx/bot 0.4.36 → 0.4.38
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 +20 -0
- package/README.md +17 -2
- package/dist/cloud-management-module.js +275 -206
- package/dist/command-registration-policy.d.ts +8 -0
- package/dist/command-registration-policy.js +39 -0
- package/dist/command-sync.d.ts +5 -1
- package/dist/command-sync.js +67 -3
- package/dist/control-server.d.ts +15 -3
- package/dist/control-server.js +34 -48
- package/dist/dashboard-commands.js +5 -1
- package/dist/developer-operations-module.d.ts +2 -0
- package/dist/developer-operations-module.js +65 -4
- package/dist/discord-guild-health.d.ts +2 -2
- package/dist/discord-rest.d.ts +5 -0
- package/dist/discord-rest.js +9 -0
- package/dist/events.js +4 -2
- package/dist/first-time-command-setup.d.ts +25 -0
- package/dist/first-time-command-setup.js +22 -0
- package/dist/guild-command-control-routes.d.ts +5 -0
- package/dist/guild-command-control-routes.js +191 -0
- package/dist/guild-command-discord-client.d.ts +21 -0
- package/dist/guild-command-discord-client.js +50 -0
- package/dist/guild-command-projection.d.ts +46 -0
- package/dist/guild-command-projection.js +254 -0
- package/dist/guild-command-read-service.d.ts +201 -0
- package/dist/guild-command-read-service.js +272 -0
- package/dist/guild-command-release.d.ts +19 -0
- package/dist/guild-command-release.js +47 -0
- package/dist/guild-command-worker.d.ts +49 -0
- package/dist/guild-command-worker.js +443 -0
- package/dist/guilds.d.ts +1 -1
- package/dist/guilds.js +41 -26
- package/dist/index.js +75 -4
- package/dist/interactions.d.ts +15 -4
- package/dist/interactions.js +285 -70
- package/dist/management-module.js +117 -41
- package/dist/module-logging.d.ts +1 -0
- package/dist/module-logging.js +5 -5
- package/dist/module-status-control.d.ts +19 -0
- package/dist/module-status-control.js +69 -0
- package/dist/modules.d.ts +2 -0
- package/dist/modules.js +21 -2
- package/dist/nexus-commands.d.ts +1 -12
- package/dist/nexus-commands.js +1 -99
- package/dist/oauth-module.js +134 -88
- package/dist/permissions-module.js +112 -39
- package/dist/privacy-module.d.ts +2 -0
- package/dist/privacy-module.js +60 -0
- package/dist/scheduled-task-runner.js +36 -1
- package/dist/shard-contracts.d.ts +24 -0
- package/dist/shard-contracts.js +20 -0
- package/dist/shard-router.d.ts +1 -0
- package/dist/shard-router.js +7 -0
- package/dist/shard-supervisor.d.ts +2 -1
- package/dist/shard-supervisor.js +94 -16
- package/dist/shard-worker.d.ts +6 -1
- package/dist/shard-worker.js +9 -1
- package/dist/shared-commands.d.ts +13 -0
- package/dist/shared-commands.js +163 -0
- package/dist/support-module.js +51 -51
- package/dist/trivia-data-provider.d.ts +26 -0
- package/dist/trivia-data-provider.js +51 -0
- package/package.json +5 -4
- package/dist/oauth-module.test.d.ts +0 -2
- package/dist/oauth-module.test.js +0 -172
|
@@ -17,6 +17,7 @@ const MODULE_NEXT_ID = "helyx.management.module.next";
|
|
|
17
17
|
const SETTING_PREVIOUS_ID = "helyx.management.setting.previous";
|
|
18
18
|
const SETTING_NEXT_ID = "helyx.management.setting.next";
|
|
19
19
|
const VALUE_MODAL_ID = "helyx.management.value.modal";
|
|
20
|
+
const OPEN_VALUE_MODAL_ID = "helyx.management.value.open";
|
|
20
21
|
const VALUE_FIELD_ID = "value";
|
|
21
22
|
const SESSION_PURPOSE = "modules.manage";
|
|
22
23
|
const PAGE_SIZE = 25;
|
|
@@ -30,6 +31,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
30
31
|
.filter((module) => isCustomerSelectableModule(module.manifest))
|
|
31
32
|
.sort((left, right) => left.manifest.name.localeCompare(right.manifest.name, "en-GB"));
|
|
32
33
|
const byId = new Map(modules.map((module) => [module.manifest.id, module]));
|
|
34
|
+
const preparedValueModals = new Map();
|
|
33
35
|
return defineModule({
|
|
34
36
|
manifest: {
|
|
35
37
|
...INTERNAL_MODULE_MANIFEST_FIELDS,
|
|
@@ -42,56 +44,58 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
42
44
|
framework: "^0.1.0",
|
|
43
45
|
dependencies: [],
|
|
44
46
|
permissions: [],
|
|
45
|
-
contributions: { commands: ["helyx"], events: [] },
|
|
47
|
+
contributions: { commands: ["helyx.modules"], events: [] },
|
|
46
48
|
},
|
|
47
49
|
defaultPermissions: {
|
|
48
50
|
module: restrictedPolicy(),
|
|
49
51
|
commands: { helyx: restrictedPolicy() },
|
|
50
52
|
},
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
await context.services
|
|
79
|
-
.get(HELYX_SERVICE_NAMES.sessions)
|
|
80
|
-
.create({
|
|
81
|
-
guildId: context.serverId,
|
|
82
|
-
userId: context.userId,
|
|
83
|
-
purpose: SESSION_PURPOSE,
|
|
84
|
-
state: { modulePage: 0 },
|
|
85
|
-
expiresAt: sessionExpiry(),
|
|
86
|
-
replace: true,
|
|
53
|
+
sharedCommands: [
|
|
54
|
+
{
|
|
55
|
+
family: "helyx",
|
|
56
|
+
path: "modules",
|
|
57
|
+
permissionId: "helyx",
|
|
58
|
+
description: "Manage Helyx modules and settings",
|
|
59
|
+
acknowledgement: "deferred-ephemeral",
|
|
60
|
+
rateLimit: { scope: "user", limit: 30, windowSeconds: 60 },
|
|
61
|
+
async execute(context) {
|
|
62
|
+
if (!context.serverId) {
|
|
63
|
+
await context.reply(serverOnlyResponse("manage modules"));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (modules.length === 0) {
|
|
67
|
+
await context.reply({
|
|
68
|
+
ephemeral: true,
|
|
69
|
+
componentsV2: {
|
|
70
|
+
accentColor: DANGER,
|
|
71
|
+
text: [
|
|
72
|
+
{
|
|
73
|
+
content: "## No feature modules installed\nInstall at least one official Helyx module before configuring this server.",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
87
77
|
});
|
|
88
|
-
|
|
89
|
-
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
await context.services
|
|
81
|
+
.get(HELYX_SERVICE_NAMES.sessions)
|
|
82
|
+
.create({
|
|
83
|
+
guildId: context.serverId,
|
|
84
|
+
userId: context.userId,
|
|
85
|
+
purpose: SESSION_PURPOSE,
|
|
86
|
+
state: { modulePage: 0 },
|
|
87
|
+
expiresAt: sessionExpiry(),
|
|
88
|
+
replace: true,
|
|
89
|
+
});
|
|
90
|
+
await context.reply(await modulePickerResponse(context, modules, 0));
|
|
90
91
|
},
|
|
91
|
-
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
interactions: {
|
|
92
95
|
selects: [
|
|
93
96
|
{
|
|
94
97
|
customId: MODULE_SELECT_ID,
|
|
98
|
+
acknowledgement: "deferred-update",
|
|
95
99
|
async execute(context) {
|
|
96
100
|
const session = await getSession(context);
|
|
97
101
|
if (!session || !context.serverId)
|
|
@@ -116,6 +120,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
116
120
|
},
|
|
117
121
|
{
|
|
118
122
|
customId: SETTING_SELECT_ID,
|
|
123
|
+
acknowledgement: "deferred-update",
|
|
119
124
|
async execute(context) {
|
|
120
125
|
const session = await getSession(context);
|
|
121
126
|
if (!session)
|
|
@@ -125,7 +130,16 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
125
130
|
const state = { ...session.state, selectedSettingKey: setting.key };
|
|
126
131
|
await updateSession(context, session, state);
|
|
127
132
|
if (setting.type === "string" || setting.type === "integer") {
|
|
128
|
-
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
for (const [key, prepared] of preparedValueModals) {
|
|
135
|
+
if (prepared.expiresAt <= now)
|
|
136
|
+
preparedValueModals.delete(key);
|
|
137
|
+
}
|
|
138
|
+
preparedValueModals.set(preparedModalKey(context.serverId, context.userId), {
|
|
139
|
+
definition: settingModal(setting, draft(state)),
|
|
140
|
+
expiresAt: now + 2 * 60 * 1_000,
|
|
141
|
+
});
|
|
142
|
+
await respond(context, textSettingResponse(setting));
|
|
129
143
|
return;
|
|
130
144
|
}
|
|
131
145
|
await respond(context, settingValueResponse(setting));
|
|
@@ -133,6 +147,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
133
147
|
},
|
|
134
148
|
{
|
|
135
149
|
customId: VALUE_SELECT_ID,
|
|
150
|
+
acknowledgement: "deferred-update",
|
|
136
151
|
async execute(context) {
|
|
137
152
|
const session = await getSession(context);
|
|
138
153
|
if (!session)
|
|
@@ -150,8 +165,31 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
150
165
|
buttons: [
|
|
151
166
|
statusButton(ENABLE_ID, true),
|
|
152
167
|
statusButton(DISABLE_ID, false),
|
|
168
|
+
{
|
|
169
|
+
customId: OPEN_VALUE_MODAL_ID,
|
|
170
|
+
async execute(context) {
|
|
171
|
+
const key = preparedModalKey(context.serverId, context.userId);
|
|
172
|
+
const prepared = preparedValueModals.get(key);
|
|
173
|
+
preparedValueModals.delete(key);
|
|
174
|
+
if (!prepared || prepared.expiresAt <= Date.now()) {
|
|
175
|
+
await context.reply({
|
|
176
|
+
ephemeral: true,
|
|
177
|
+
componentsV2: {
|
|
178
|
+
text: [
|
|
179
|
+
{
|
|
180
|
+
content: "This setting form expired. Choose the setting again.",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
await context.showModal(prepared.definition);
|
|
188
|
+
},
|
|
189
|
+
},
|
|
153
190
|
{
|
|
154
191
|
customId: CONFIGURE_ID,
|
|
192
|
+
acknowledgement: "deferred-update",
|
|
155
193
|
async execute(context) {
|
|
156
194
|
const session = await getSession(context);
|
|
157
195
|
if (!session)
|
|
@@ -166,6 +204,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
166
204
|
},
|
|
167
205
|
{
|
|
168
206
|
customId: SAVE_ID,
|
|
207
|
+
acknowledgement: "deferred-update",
|
|
169
208
|
async execute(context) {
|
|
170
209
|
const session = await getSession(context);
|
|
171
210
|
if (!session || !context.serverId)
|
|
@@ -235,6 +274,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
235
274
|
},
|
|
236
275
|
{
|
|
237
276
|
customId: CLEAR_ID,
|
|
277
|
+
acknowledgement: "deferred-update",
|
|
238
278
|
async execute(context) {
|
|
239
279
|
const session = await getSession(context);
|
|
240
280
|
if (!session)
|
|
@@ -256,6 +296,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
256
296
|
},
|
|
257
297
|
{
|
|
258
298
|
customId: BACK_TO_MODULES_ID,
|
|
299
|
+
acknowledgement: "deferred-update",
|
|
259
300
|
async execute(context) {
|
|
260
301
|
const session = await getSession(context);
|
|
261
302
|
if (!session)
|
|
@@ -266,6 +307,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
266
307
|
},
|
|
267
308
|
{
|
|
268
309
|
customId: BACK_TO_MODULE_ID,
|
|
310
|
+
acknowledgement: "deferred-update",
|
|
269
311
|
async execute(context) {
|
|
270
312
|
const session = await getSession(context);
|
|
271
313
|
if (!session)
|
|
@@ -275,6 +317,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
275
317
|
},
|
|
276
318
|
{
|
|
277
319
|
customId: CANCEL_ID,
|
|
320
|
+
acknowledgement: "deferred-update",
|
|
278
321
|
async execute(context) {
|
|
279
322
|
const session = await getSession(context);
|
|
280
323
|
if (!session)
|
|
@@ -303,6 +346,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
303
346
|
modals: [
|
|
304
347
|
{
|
|
305
348
|
customId: VALUE_MODAL_ID,
|
|
349
|
+
acknowledgement: "deferred-ephemeral",
|
|
306
350
|
async execute(context) {
|
|
307
351
|
const session = await getSession(context);
|
|
308
352
|
if (!session)
|
|
@@ -332,6 +376,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
332
376
|
function statusButton(customId, enabled) {
|
|
333
377
|
return {
|
|
334
378
|
customId,
|
|
379
|
+
acknowledgement: "deferred-update",
|
|
335
380
|
async execute(context) {
|
|
336
381
|
const session = await getSession(context);
|
|
337
382
|
if (!session || !context.serverId)
|
|
@@ -379,6 +424,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
379
424
|
function modulePageButton(customId, change) {
|
|
380
425
|
return {
|
|
381
426
|
customId,
|
|
427
|
+
acknowledgement: "deferred-update",
|
|
382
428
|
async execute(context) {
|
|
383
429
|
const session = await getSession(context);
|
|
384
430
|
if (!session)
|
|
@@ -396,6 +442,7 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
396
442
|
function settingPageButton(customId, change) {
|
|
397
443
|
return {
|
|
398
444
|
customId,
|
|
445
|
+
acknowledgement: "deferred-update",
|
|
399
446
|
async execute(context) {
|
|
400
447
|
const session = await getSession(context);
|
|
401
448
|
if (!session)
|
|
@@ -412,6 +459,18 @@ export function createManagementModule(configurableModules, resources) {
|
|
|
412
459
|
};
|
|
413
460
|
}
|
|
414
461
|
}
|
|
462
|
+
function serverOnlyResponse(action) {
|
|
463
|
+
return {
|
|
464
|
+
ephemeral: true,
|
|
465
|
+
componentsV2: {
|
|
466
|
+
text: [
|
|
467
|
+
{
|
|
468
|
+
content: `This command needs a server. Open a server where Helyx is installed, then use \`/helyx modules\` there to ${action}.`,
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
}
|
|
415
474
|
function restrictedPolicy() {
|
|
416
475
|
return {
|
|
417
476
|
everyone: false,
|
|
@@ -526,6 +585,20 @@ function configurationResponse(module, state) {
|
|
|
526
585
|
],
|
|
527
586
|
};
|
|
528
587
|
}
|
|
588
|
+
function textSettingResponse(setting) {
|
|
589
|
+
return {
|
|
590
|
+
ephemeral: true,
|
|
591
|
+
componentsV2: {
|
|
592
|
+
accentColor: VIOLET,
|
|
593
|
+
text: [{ content: `## ${setting.label}\n${setting.description}` }],
|
|
594
|
+
},
|
|
595
|
+
buttons: [
|
|
596
|
+
button(OPEN_VALUE_MODAL_ID, "Enter value", "primary"),
|
|
597
|
+
button(CONFIGURE_ID, "Back to settings", "secondary"),
|
|
598
|
+
button(CANCEL_ID, "Cancel", "secondary"),
|
|
599
|
+
],
|
|
600
|
+
};
|
|
601
|
+
}
|
|
529
602
|
function settingValueResponse(setting) {
|
|
530
603
|
const clear = setting.required
|
|
531
604
|
? []
|
|
@@ -602,6 +675,9 @@ function settingValueResponse(setting) {
|
|
|
602
675
|
}
|
|
603
676
|
throw new Error("Selected setting requires a modal");
|
|
604
677
|
}
|
|
678
|
+
function preparedModalKey(serverId, userId) {
|
|
679
|
+
return `${serverId ?? "direct"}:${userId}`;
|
|
680
|
+
}
|
|
605
681
|
function settingModal(setting, values) {
|
|
606
682
|
const current = values[setting.key];
|
|
607
683
|
return {
|
package/dist/module-logging.d.ts
CHANGED
package/dist/module-logging.js
CHANGED
|
@@ -19,7 +19,7 @@ export class DiscordModuleLoggingService {
|
|
|
19
19
|
async emit(input) {
|
|
20
20
|
try {
|
|
21
21
|
if (!this.#loggingInstalled)
|
|
22
|
-
return { delivered: false };
|
|
22
|
+
return { delivered: false, retryable: false };
|
|
23
23
|
const source = this.#modules.get(input.moduleId);
|
|
24
24
|
const event = source?.logging?.events.find(({ id }) => id === input.eventId);
|
|
25
25
|
if (!source || !event) {
|
|
@@ -27,18 +27,18 @@ export class DiscordModuleLoggingService {
|
|
|
27
27
|
}
|
|
28
28
|
const installations = new InstallationRepository(this.database);
|
|
29
29
|
if (!(await installations.isModuleEnabled(input.guildId, LOGGING_MODULE_ID))) {
|
|
30
|
-
return { delivered: false };
|
|
30
|
+
return { delivered: false, retryable: false };
|
|
31
31
|
}
|
|
32
32
|
const [configuration, policy] = await Promise.all([
|
|
33
33
|
new ModuleConfigurationRepository(this.database).get(input.guildId, LOGGING_MODULE_ID),
|
|
34
34
|
new ModuleLoggingPolicyRepository(this.database).get(input.guildId, input.moduleId, input.eventId),
|
|
35
35
|
]);
|
|
36
36
|
if (!(policy?.enabled ?? event.defaultEnabled)) {
|
|
37
|
-
return { delivered: false };
|
|
37
|
+
return { delivered: false, retryable: false };
|
|
38
38
|
}
|
|
39
39
|
const channelId = configuration?.configuration.channelId;
|
|
40
40
|
if (typeof channelId !== "string" || !/^\d{17,20}$/u.test(channelId)) {
|
|
41
|
-
return { delivered: false };
|
|
41
|
+
return { delivered: false, retryable: false };
|
|
42
42
|
}
|
|
43
43
|
const summary = normalizeText(input.summary, 500, "summary");
|
|
44
44
|
const details = (input.details ?? []).map(({ label, value }) => ({
|
|
@@ -86,7 +86,7 @@ export class DiscordModuleLoggingService {
|
|
|
86
86
|
guildId: input.guildId,
|
|
87
87
|
error: safeError(error),
|
|
88
88
|
}, "Module activity log delivery failed");
|
|
89
|
-
return { delivered: false };
|
|
89
|
+
return { delivered: false, retryable: true };
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type InstallationService, type ModuleDefinition, type ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
export interface BotModuleStatusMutationResult {
|
|
3
|
+
moduleStatuses: Record<string, "enabled" | "disabled">;
|
|
4
|
+
}
|
|
5
|
+
export declare function mutateDashboardModuleStatus(input: {
|
|
6
|
+
change: {
|
|
7
|
+
guildId: string;
|
|
8
|
+
moduleId: string;
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
actorUserId: string;
|
|
11
|
+
correlationId: string;
|
|
12
|
+
};
|
|
13
|
+
module: ModuleDefinition;
|
|
14
|
+
dashboardModuleIds: readonly string[];
|
|
15
|
+
installations: InstallationService;
|
|
16
|
+
services: ServiceAccess;
|
|
17
|
+
requireModuleAvailable(): Promise<void>;
|
|
18
|
+
}): Promise<BotModuleStatusMutationResult>;
|
|
19
|
+
//# sourceMappingURL=module-status-control.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
2
|
+
import { runNestedGuildBarriers } from "./numeric-barriers.js";
|
|
3
|
+
export async function mutateDashboardModuleStatus(input) {
|
|
4
|
+
const { change, module } = input;
|
|
5
|
+
const scopes = Object.entries(module.records ?? {})
|
|
6
|
+
.filter(([, collection]) => (collection.incrementableFields?.length ?? 0) > 0)
|
|
7
|
+
.map(([collection]) => ({
|
|
8
|
+
moduleId: change.moduleId,
|
|
9
|
+
collection,
|
|
10
|
+
guildId: change.guildId,
|
|
11
|
+
}));
|
|
12
|
+
const writeBehind = scopes.length > 0 &&
|
|
13
|
+
input.services.has(HELYX_SERVICE_NAMES.numericWriteBehind)
|
|
14
|
+
? input.services.get(HELYX_SERVICE_NAMES.numericWriteBehind)
|
|
15
|
+
: null;
|
|
16
|
+
if (writeBehind)
|
|
17
|
+
await input.requireModuleAvailable();
|
|
18
|
+
let mutationResult;
|
|
19
|
+
const setEnabled = async () => {
|
|
20
|
+
mutationResult = await input.installations.setModuleEnabled({
|
|
21
|
+
guildId: change.guildId,
|
|
22
|
+
moduleId: change.moduleId,
|
|
23
|
+
enabled: change.enabled,
|
|
24
|
+
context: {
|
|
25
|
+
actorUserId: change.actorUserId,
|
|
26
|
+
correlationId: change.correlationId,
|
|
27
|
+
source: "dashboard",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
return mutationResult;
|
|
31
|
+
};
|
|
32
|
+
if (!writeBehind) {
|
|
33
|
+
await setEnabled();
|
|
34
|
+
}
|
|
35
|
+
else if (change.enabled) {
|
|
36
|
+
for (const scope of scopes) {
|
|
37
|
+
const suppression = await writeBehind.suppressGuild(scope);
|
|
38
|
+
await writeBehind.releaseGuild({
|
|
39
|
+
...scope,
|
|
40
|
+
suppressionId: suppression.suppressionId,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
await setEnabled();
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
await runNestedGuildBarriers({
|
|
47
|
+
scopes,
|
|
48
|
+
writeBehind,
|
|
49
|
+
operation: setEnabled,
|
|
50
|
+
});
|
|
51
|
+
for (const scope of scopes)
|
|
52
|
+
await writeBehind.flushGuild(scope);
|
|
53
|
+
for (const scope of scopes)
|
|
54
|
+
await writeBehind.suppressGuild(scope);
|
|
55
|
+
}
|
|
56
|
+
if (!mutationResult)
|
|
57
|
+
throw new Error("Module status mutation completed without a result");
|
|
58
|
+
const dashboardModuleIds = new Set(input.dashboardModuleIds);
|
|
59
|
+
const transitionedIds = new Set([
|
|
60
|
+
change.moduleId,
|
|
61
|
+
...mutationResult.transitionedModuleIds,
|
|
62
|
+
]);
|
|
63
|
+
return {
|
|
64
|
+
moduleStatuses: Object.fromEntries([...transitionedIds]
|
|
65
|
+
.filter((moduleId) => dashboardModuleIds.has(moduleId))
|
|
66
|
+
.map((moduleId) => [moduleId, change.enabled ? "enabled" : "disabled"])),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=module-status-control.js.map
|
package/dist/modules.d.ts
CHANGED
|
@@ -12,11 +12,13 @@ export interface ConfiguredModulePackage extends LoadedDefinition {
|
|
|
12
12
|
}
|
|
13
13
|
export declare const secureRandomSelectionWorkerKeyName = "HELYX_INTERNAL_SECURE_RANDOM_SELECTION_KEY";
|
|
14
14
|
interface PlatformServiceEnvironment extends Pick<BotEnvironment, "HELYX_DATA_ENCRYPTION_KEY"> {
|
|
15
|
+
DISCORD_CLIENT_ID?: string;
|
|
15
16
|
HELYX_DASHBOARD_PUBLIC_URL?: string;
|
|
16
17
|
HELYX_INTERNAL_SECURE_RANDOM_SELECTION_KEY?: string;
|
|
17
18
|
}
|
|
18
19
|
export declare function deriveSecureRandomSelectionKey(encodedDataEncryptionKey: string): Uint8Array;
|
|
19
20
|
export declare function platformServiceOptions(environment: PlatformServiceEnvironment, modules: readonly ModuleDefinition[]): {
|
|
21
|
+
applicationId?: string;
|
|
20
22
|
secureRandomSelectionKey?: Uint8Array;
|
|
21
23
|
dashboardBaseUrl?: string;
|
|
22
24
|
};
|
package/dist/modules.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { orderModuleManifests, parseModuleManifest, } from "@helyx/module-manifest";
|
|
2
|
-
import { NEXUS_COMMAND_SPECS, NEXUS_ROOT_MODULE_ID } from "@helyx/sdk";
|
|
2
|
+
import { NEXUS_COMMAND_SPECS, NEXUS_ROOT_MODULE_ID, sharedCommandFamilySpec, sharedCommandPathSpec, } from "@helyx/sdk";
|
|
3
3
|
import { createHmac } from "node:crypto";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
@@ -22,12 +22,16 @@ export function deriveSecureRandomSelectionKey(encodedDataEncryptionKey) {
|
|
|
22
22
|
.digest();
|
|
23
23
|
}
|
|
24
24
|
export function platformServiceOptions(environment, modules) {
|
|
25
|
+
const application = environment.DISCORD_CLIENT_ID
|
|
26
|
+
? { applicationId: environment.DISCORD_CLIENT_ID }
|
|
27
|
+
: {};
|
|
25
28
|
const urls = environment.HELYX_DASHBOARD_PUBLIC_URL
|
|
26
29
|
? { dashboardBaseUrl: environment.HELYX_DASHBOARD_PUBLIC_URL }
|
|
27
30
|
: {};
|
|
28
31
|
const internalSelectionKey = environment.HELYX_INTERNAL_SECURE_RANDOM_SELECTION_KEY;
|
|
29
32
|
if (internalSelectionKey) {
|
|
30
33
|
return {
|
|
34
|
+
...application,
|
|
31
35
|
...urls,
|
|
32
36
|
secureRandomSelectionKey: decodeSecureRandomSelectionKey(internalSelectionKey, secureRandomSelectionWorkerKeyName),
|
|
33
37
|
};
|
|
@@ -37,9 +41,10 @@ export function platformServiceOptions(environment, modules) {
|
|
|
37
41
|
if (modules.some(({ manifest }) => manifest.id === giveawaysModuleId)) {
|
|
38
42
|
throw new Error("HELYX_DATA_ENCRYPTION_KEY is required when Giveaways is loaded");
|
|
39
43
|
}
|
|
40
|
-
return urls;
|
|
44
|
+
return { ...application, ...urls };
|
|
41
45
|
}
|
|
42
46
|
return {
|
|
47
|
+
...application,
|
|
43
48
|
...urls,
|
|
44
49
|
secureRandomSelectionKey: deriveSecureRandomSelectionKey(encodedKey),
|
|
45
50
|
};
|
|
@@ -103,6 +108,20 @@ export function validateContributionParity(definition) {
|
|
|
103
108
|
throw new Error("Module requests an unsupported or duplicate Discord capability");
|
|
104
109
|
}
|
|
105
110
|
const executableCommands = (definition.interactions?.commands ?? []).map(({ name }) => name);
|
|
111
|
+
const sharedCommandPaths = new Set();
|
|
112
|
+
for (const command of definition.sharedCommands ?? []) {
|
|
113
|
+
const family = sharedCommandFamilySpec(command.family);
|
|
114
|
+
const spec = sharedCommandPathSpec(command.family, command.path);
|
|
115
|
+
if (!family || !spec || spec.ownerModuleId !== definition.manifest.id) {
|
|
116
|
+
throw new Error(`Module ${definition.manifest.id} cannot contribute shared command ${command.family}.${command.path}`);
|
|
117
|
+
}
|
|
118
|
+
const contributionId = `${command.family}.${command.path}`;
|
|
119
|
+
if (sharedCommandPaths.has(contributionId)) {
|
|
120
|
+
throw new Error(`Duplicate shared command path for ${definition.manifest.id}: ${contributionId}`);
|
|
121
|
+
}
|
|
122
|
+
sharedCommandPaths.add(contributionId);
|
|
123
|
+
}
|
|
124
|
+
executableCommands.push(...sharedCommandPaths);
|
|
106
125
|
const nexusCommandPaths = new Set();
|
|
107
126
|
for (const command of definition.nexusCommands ?? []) {
|
|
108
127
|
const spec = NEXUS_COMMAND_SPECS.find(({ path }) => path === command.path);
|
package/dist/nexus-commands.d.ts
CHANGED
|
@@ -1,13 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export interface CommandRouteOwner {
|
|
3
|
-
moduleId: string;
|
|
4
|
-
moduleName: string;
|
|
5
|
-
}
|
|
6
|
-
export interface RegisteredCommand {
|
|
7
|
-
moduleId: string;
|
|
8
|
-
moduleName: string;
|
|
9
|
-
contribution: CommandContribution;
|
|
10
|
-
routeOwners?: ReadonlyMap<string, CommandRouteOwner>;
|
|
11
|
-
}
|
|
12
|
-
export declare function collectCommandRegistrations(modules: readonly ModuleDefinition[]): Map<string, RegisteredCommand>;
|
|
1
|
+
export * from "./shared-commands.js";
|
|
13
2
|
//# sourceMappingURL=nexus-commands.d.ts.map
|
package/dist/nexus-commands.js
CHANGED
|
@@ -1,100 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export function collectCommandRegistrations(modules) {
|
|
3
|
-
const registrations = modules.flatMap((module) => (module.interactions?.commands ?? []).map((contribution) => ({
|
|
4
|
-
moduleId: module.manifest.id,
|
|
5
|
-
moduleName: module.manifest.name,
|
|
6
|
-
contribution,
|
|
7
|
-
})));
|
|
8
|
-
const nexusContributions = modules.flatMap((module) => (module.nexusCommands ?? []).map((contribution) => ({
|
|
9
|
-
moduleId: module.manifest.id,
|
|
10
|
-
moduleName: module.manifest.name,
|
|
11
|
-
contribution,
|
|
12
|
-
})));
|
|
13
|
-
if (nexusContributions.length > 0) {
|
|
14
|
-
const roots = registrations.filter(({ contribution }) => contribution.name === "nexus");
|
|
15
|
-
if (roots.length !== 1 || roots[0].moduleId !== NEXUS_ROOT_MODULE_ID) {
|
|
16
|
-
throw new Error("Nexus Core must be the sole owner of /nexus");
|
|
17
|
-
}
|
|
18
|
-
const root = roots[0];
|
|
19
|
-
if (root.contribution.execute ||
|
|
20
|
-
root.contribution.options?.length ||
|
|
21
|
-
root.contribution.subcommands?.length ||
|
|
22
|
-
root.contribution.subcommandGroups?.length) {
|
|
23
|
-
throw new Error("Nexus Core /nexus must be composed from Nexus commands");
|
|
24
|
-
}
|
|
25
|
-
const composed = composeNexusCommand(root, nexusContributions);
|
|
26
|
-
registrations.splice(registrations.indexOf(root), 1, composed);
|
|
27
|
-
}
|
|
28
|
-
return uniqueRegistrations(registrations);
|
|
29
|
-
}
|
|
30
|
-
function composeNexusCommand(root, contributions) {
|
|
31
|
-
const byPath = new Map(contributions.map((item) => {
|
|
32
|
-
const spec = NEXUS_COMMAND_SPECS.find(({ path }) => path === item.contribution.path);
|
|
33
|
-
if (!spec || spec.ownerModuleId !== item.moduleId) {
|
|
34
|
-
throw new Error(`Module ${item.moduleId} cannot contribute Nexus command ${item.contribution.path}`);
|
|
35
|
-
}
|
|
36
|
-
return [item.contribution.path, item];
|
|
37
|
-
}));
|
|
38
|
-
if (byPath.size !== contributions.length) {
|
|
39
|
-
throw new Error("Duplicate Nexus command path");
|
|
40
|
-
}
|
|
41
|
-
const subcommands = [];
|
|
42
|
-
const groups = new Map();
|
|
43
|
-
const routeOwners = new Map();
|
|
44
|
-
for (const spec of NEXUS_COMMAND_SPECS) {
|
|
45
|
-
const item = byPath.get(spec.path);
|
|
46
|
-
if (!item)
|
|
47
|
-
continue;
|
|
48
|
-
routeOwners.set(spec.path, {
|
|
49
|
-
moduleId: item.moduleId,
|
|
50
|
-
moduleName: item.moduleName,
|
|
51
|
-
});
|
|
52
|
-
const [first, second] = spec.path.split(".");
|
|
53
|
-
const command = toSubcommand(item.contribution, second ?? first);
|
|
54
|
-
if (!second) {
|
|
55
|
-
subcommands.push(command);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
const current = groups.get(first);
|
|
59
|
-
const group = current ?? {
|
|
60
|
-
name: first,
|
|
61
|
-
description: "groupDescription" in spec
|
|
62
|
-
? spec.groupDescription
|
|
63
|
-
: `Manage Nexus ${first}`,
|
|
64
|
-
subcommands: [],
|
|
65
|
-
};
|
|
66
|
-
groups.set(first, {
|
|
67
|
-
...group,
|
|
68
|
-
subcommands: [...group.subcommands, command],
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
...root,
|
|
73
|
-
contribution: {
|
|
74
|
-
...root.contribution,
|
|
75
|
-
subcommands,
|
|
76
|
-
subcommandGroups: [...groups.values()],
|
|
77
|
-
},
|
|
78
|
-
routeOwners,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function toSubcommand(contribution, name) {
|
|
82
|
-
const subcommand = {
|
|
83
|
-
...contribution,
|
|
84
|
-
name,
|
|
85
|
-
permissionId: `nexus.${contribution.path}`,
|
|
86
|
-
};
|
|
87
|
-
delete subcommand.path;
|
|
88
|
-
return subcommand;
|
|
89
|
-
}
|
|
90
|
-
function uniqueRegistrations(items) {
|
|
91
|
-
const result = new Map();
|
|
92
|
-
for (const item of items) {
|
|
93
|
-
const key = item.contribution.name;
|
|
94
|
-
if (result.has(key))
|
|
95
|
-
throw new Error(`Duplicate command name: ${key}`);
|
|
96
|
-
result.set(key, item);
|
|
97
|
-
}
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
1
|
+
export * from "./shared-commands.js";
|
|
100
2
|
//# sourceMappingURL=nexus-commands.js.map
|