@helyx/bot 0.4.2 → 0.4.4
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 +29 -0
- package/README.md +13 -0
- package/dist/cloud-connector.d.ts +7 -0
- package/dist/cloud-connector.js +118 -1
- package/dist/cloud-control-dispatcher.js +84 -0
- package/dist/control-server.d.ts +187 -5
- package/dist/control-server.js +673 -6
- package/dist/developer-operations-module.js +8 -0
- package/dist/discord-owned-message-service.d.ts +39 -0
- package/dist/discord-owned-message-service.js +138 -0
- package/dist/events.d.ts +70 -1
- package/dist/events.js +557 -5
- package/dist/index.js +61 -13
- package/dist/interactions.d.ts +3 -2
- package/dist/interactions.js +66 -31
- package/dist/module-logging.d.ts +1 -0
- package/dist/module-logging.js +19 -1
- package/dist/modules.d.ts +9 -0
- package/dist/modules.js +45 -0
- package/dist/resource-authorization.d.ts +7 -0
- package/dist/resource-authorization.js +12 -0
- package/dist/scheduled-task-runner.d.ts +130 -0
- package/dist/scheduled-task-runner.js +417 -0
- package/dist/shard-cloud-proxy.d.ts +4 -0
- package/dist/shard-cloud-proxy.js +34 -0
- package/dist/shard-contracts.d.ts +574 -23
- package/dist/shard-contracts.js +346 -0
- package/dist/shard-coordinator.d.ts +5 -3
- package/dist/shard-coordinator.js +85 -2
- package/dist/shard-router.d.ts +10 -0
- package/dist/shard-router.js +58 -0
- package/dist/shard-supervisor.d.ts +2 -1
- package/dist/shard-supervisor.js +187 -5
- package/dist/shard-worker.d.ts +9 -3
- package/dist/shard-worker.js +97 -16
- package/dist/support-module.d.ts +9 -0
- package/dist/support-module.js +172 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add the reusable managed-resource, durable scheduling, secure checkpointed
|
|
8
|
+
selection and hosted/self-hosted runtime support required by the Reaction
|
|
9
|
+
Roles, Polls and Giveaways launch stages.
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
- @helyx/control-protocol@0.1.4
|
|
12
|
+
- @helyx/module-manifest@0.4.1
|
|
13
|
+
- @helyx/database@0.4.2
|
|
14
|
+
- @helyx/sdk@0.3.3
|
|
15
|
+
- @helyx/platform@0.2.5
|
|
16
|
+
- @helyx/core@0.1.8
|
|
17
|
+
- @helyx/framework@0.1.8
|
|
18
|
+
|
|
19
|
+
## 0.4.3
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 18b228d: Add the unified Support transport, hosted configuration, shard-safe staff
|
|
24
|
+
notifications and paired self-host bug-report claim flow.
|
|
25
|
+
- Updated dependencies [18b228d]
|
|
26
|
+
- Updated dependencies [18b228d]
|
|
27
|
+
- @helyx/control-protocol@0.1.3
|
|
28
|
+
- @helyx/config@0.4.2
|
|
29
|
+
- @helyx/database@0.4.1
|
|
30
|
+
- @helyx/platform@0.2.4
|
|
31
|
+
|
|
3
32
|
## 0.4.2
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -18,6 +18,19 @@ When `HELYX_BOT_CONTROL_SECRET` is configured, the bot also starts a private aut
|
|
|
18
18
|
|
|
19
19
|
Optional Helyx Cloud Connect is a separate outbound management path for self-hosted deployments. It is disabled unless `HELYX_CLOUD_CONNECT_ENABLED=true` and requires a separate local `HELYX_DATA_ENCRYPTION_KEY`. Preserve that key across every restart and deployment. If it is lost or changed, revoke the old installation in the dashboard and use a new one-use code with `/helyx-cloud connect`; the connector stops retrying until that recovery is completed. The owner-only `/helyx-cloud` command supports connection-code enrolment, status, credential rotation, disconnection and bounded support consent. The bot never sends its Discord token, database URL or connector private key to Helyx Cloud, and local Discord operation continues when Cloud is unavailable.
|
|
20
20
|
|
|
21
|
+
Giveaways also requires the existing `HELYX_DATA_ENCRYPTION_KEY` so core can
|
|
22
|
+
resume secure weighted-selection checkpoints without exposing the selection
|
|
23
|
+
key to a module. Keep it stable across deployments; do not rotate it while a
|
|
24
|
+
giveaway is drawing.
|
|
25
|
+
|
|
26
|
+
The always-available guild-only `/bug` command can be used by any guild member.
|
|
27
|
+
Hosted deployments submit through the dedicated `HELYX_SUPPORT_SERVICE_SECRET`;
|
|
28
|
+
paired self-hosted deployments use their authenticated Cloud Connect session
|
|
29
|
+
and may receive a one-time link for claiming, tracking and replying to the
|
|
30
|
+
report. The hosted secret is never part of a self-host package. Configure
|
|
31
|
+
`HELYX_SUPPORT_STAFF_CHANNEL_ID` with `DISCORD_INTERNAL_GUILD_ID` to deliver
|
|
32
|
+
bounded new-case metadata to a private staff channel.
|
|
33
|
+
|
|
21
34
|
Privacy providers ship in the same hosted and self-hosted module packages.
|
|
22
35
|
`HELYX_PRIVACY_ENABLED` defaults to `false`; enabling it requires the private
|
|
23
36
|
bot-control endpoint and a separate least-privilege PostgreSQL executor
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BotEnvironment } from "@helyx/config";
|
|
2
|
+
import { type SupportBugCreatePayload, type SupportBugCreateResponsePayload } from "@helyx/control-protocol";
|
|
2
3
|
import { type CloudConnectorConsentRecord, type DatabasePool } from "@helyx/database";
|
|
3
4
|
import type { Client } from "discord.js";
|
|
4
5
|
import type { Logger } from "pino";
|
|
@@ -38,6 +39,9 @@ export interface CloudConnector {
|
|
|
38
39
|
enrol(rawToken: string, source: "startup" | "discord" | "file"): Promise<CloudConnectorStatus>;
|
|
39
40
|
disconnect(): Promise<void>;
|
|
40
41
|
rotate(): Promise<CloudConnectorStatus>;
|
|
42
|
+
submitBug(input: SupportBugCreatePayload): Promise<Extract<SupportBugCreateResponsePayload, {
|
|
43
|
+
ok: true;
|
|
44
|
+
}>["result"]>;
|
|
41
45
|
stop(): Promise<void>;
|
|
42
46
|
}
|
|
43
47
|
export declare class ClientCloudDiscordDirectory implements CloudDiscordDirectory {
|
|
@@ -68,6 +72,9 @@ export declare class CloudConnectorController implements CloudConnector {
|
|
|
68
72
|
enrol(rawToken: string, source: "startup" | "discord" | "file"): Promise<CloudConnectorStatus>;
|
|
69
73
|
disconnect(): Promise<void>;
|
|
70
74
|
rotate(): Promise<CloudConnectorStatus>;
|
|
75
|
+
submitBug(rawInput: SupportBugCreatePayload): Promise<Extract<SupportBugCreateResponsePayload, {
|
|
76
|
+
ok: true;
|
|
77
|
+
}>["result"]>;
|
|
71
78
|
stop(): Promise<void>;
|
|
72
79
|
}
|
|
73
80
|
export declare class CloudConnectorUserError extends Error {
|
package/dist/cloud-connector.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CLOUD_DEFAULT_REQUEST_TIMEOUT_MS, CLOUD_PROTOCOL_VERSION, ConnectionSequenceGuard, assertCloudHandshakeSize, assertCloudMessageSize, cloudControlResponsePayloadSchema, connectedCloudMessageSchema, connectorAcceptedSchema, connectorChallengeSchema, connectorCredentialActionSchema, connectorHelloSchema, connectorRejectedSchema, generateConnectorKeyPair, signConnectorCredentialAction, signConnectorChallenge, } from "@helyx/control-protocol";
|
|
1
|
+
import { CLOUD_DEFAULT_REQUEST_TIMEOUT_MS, CLOUD_PROTOCOL_VERSION, ConnectionSequenceGuard, assertCloudHandshakeSize, assertCloudMessageSize, cloudControlResponsePayloadSchema, connectedCloudMessageSchema, connectorAcceptedSchema, connectorChallengeSchema, connectorCredentialActionSchema, connectorHelloSchema, connectorRejectedSchema, generateConnectorKeyPair, signConnectorCredentialAction, signConnectorChallenge, supportBugCreatePayloadSchema, } from "@helyx/control-protocol";
|
|
2
2
|
import { CloudConnectorRepository, } from "@helyx/database";
|
|
3
3
|
import { createCipheriv, createDecipheriv, createHash, randomBytes, randomUUID, } from "node:crypto";
|
|
4
4
|
import { readFile, rm } from "node:fs/promises";
|
|
@@ -82,6 +82,8 @@ export class CloudConnectorController {
|
|
|
82
82
|
#stopping = false;
|
|
83
83
|
#connectionBlocked = false;
|
|
84
84
|
#loop;
|
|
85
|
+
#activeSupport;
|
|
86
|
+
#pendingSupport = new Map();
|
|
85
87
|
constructor(input) {
|
|
86
88
|
this.input = input;
|
|
87
89
|
this.#repository = new CloudConnectorRepository(input.database);
|
|
@@ -313,10 +315,41 @@ export class CloudConnectorController {
|
|
|
313
315
|
this.#state = "connecting";
|
|
314
316
|
return this.status();
|
|
315
317
|
}
|
|
318
|
+
async submitBug(rawInput) {
|
|
319
|
+
const input = supportBugCreatePayloadSchema.parse(rawInput);
|
|
320
|
+
if (this.input.environment.HELYX_SUPPORT_SERVICE_SECRET)
|
|
321
|
+
return this.#submitHostedBug(input);
|
|
322
|
+
const session = this.#activeSupport;
|
|
323
|
+
if (!this.input.environment.HELYX_CLOUD_CONNECT_ENABLED ||
|
|
324
|
+
this.#state !== "online" ||
|
|
325
|
+
!session?.capabilities.includes("support.bug.create.v1"))
|
|
326
|
+
throw new CloudConnectorUserError("This deployment is not connected to Helyx Support");
|
|
327
|
+
return new Promise((resolve, reject) => {
|
|
328
|
+
const timer = setTimeout(() => {
|
|
329
|
+
this.#pendingSupport.delete(input.correlationId);
|
|
330
|
+
reject(new Error("support_request_timed_out"));
|
|
331
|
+
}, CLOUD_DEFAULT_REQUEST_TIMEOUT_MS);
|
|
332
|
+
timer.unref();
|
|
333
|
+
this.#pendingSupport.set(input.correlationId, {
|
|
334
|
+
resolve,
|
|
335
|
+
reject,
|
|
336
|
+
timer,
|
|
337
|
+
});
|
|
338
|
+
try {
|
|
339
|
+
session.send(input.correlationId, input);
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
clearTimeout(timer);
|
|
343
|
+
this.#pendingSupport.delete(input.correlationId);
|
|
344
|
+
reject(error instanceof Error ? error : new Error("support_send_failed"));
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
316
348
|
async stop() {
|
|
317
349
|
this.#stopping = true;
|
|
318
350
|
this.#state = "stopped";
|
|
319
351
|
this.#socket?.close(1001, "Helyx bot shutting down");
|
|
352
|
+
this.#rejectPendingSupport("Cloud Connect stopped");
|
|
320
353
|
await this.#loop?.catch(() => undefined);
|
|
321
354
|
}
|
|
322
355
|
#ensureLoop() {
|
|
@@ -392,6 +425,8 @@ export class CloudConnectorController {
|
|
|
392
425
|
clearInterval(heartbeat);
|
|
393
426
|
clearTimeout(plannedReconnect);
|
|
394
427
|
this.#socket = undefined;
|
|
428
|
+
this.#activeSupport = undefined;
|
|
429
|
+
this.#rejectPendingSupport("Cloud Connect disconnected");
|
|
395
430
|
if (this.#stopping)
|
|
396
431
|
resolveFinished();
|
|
397
432
|
else
|
|
@@ -412,6 +447,19 @@ export class CloudConnectorController {
|
|
|
412
447
|
if (accepted.success) {
|
|
413
448
|
incomingGuard = new ConnectionSequenceGuard(identity.installationId, accepted.data.connectionEpoch);
|
|
414
449
|
this.#state = "online";
|
|
450
|
+
this.#activeSupport = {
|
|
451
|
+
capabilities: accepted.data.capabilities,
|
|
452
|
+
send: (correlationId, payload) => {
|
|
453
|
+
socket.send(JSON.stringify(connectedMessage({
|
|
454
|
+
type: "support.request",
|
|
455
|
+
installationId: identity.installationId,
|
|
456
|
+
connectionEpoch: accepted.data.connectionEpoch,
|
|
457
|
+
sequence: ++outgoingSequence,
|
|
458
|
+
correlationId,
|
|
459
|
+
payload,
|
|
460
|
+
})));
|
|
461
|
+
},
|
|
462
|
+
};
|
|
415
463
|
this.#lastErrorCode = undefined;
|
|
416
464
|
await this.#repository.markConnected();
|
|
417
465
|
this.#identity = {
|
|
@@ -455,6 +503,18 @@ export class CloudConnectorController {
|
|
|
455
503
|
}
|
|
456
504
|
const message = connectedCloudMessageSchema.parse(payload);
|
|
457
505
|
incomingGuard?.accept(message);
|
|
506
|
+
if (message.type === "support.response") {
|
|
507
|
+
const pending = this.#pendingSupport.get(message.correlationId);
|
|
508
|
+
if (!pending)
|
|
509
|
+
return;
|
|
510
|
+
clearTimeout(pending.timer);
|
|
511
|
+
this.#pendingSupport.delete(message.correlationId);
|
|
512
|
+
if (message.payload.ok)
|
|
513
|
+
pending.resolve(message.payload.result);
|
|
514
|
+
else
|
|
515
|
+
pending.reject(new Error(message.payload.error.code));
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
458
518
|
if (message.type !== "control.request")
|
|
459
519
|
return;
|
|
460
520
|
const response = await this.#dispatch(message.payload, message.correlationId);
|
|
@@ -535,6 +595,54 @@ export class CloudConnectorController {
|
|
|
535
595
|
});
|
|
536
596
|
}
|
|
537
597
|
}
|
|
598
|
+
async #submitHostedBug(input) {
|
|
599
|
+
let response;
|
|
600
|
+
try {
|
|
601
|
+
response = await fetch(`${this.input.environment.HELYX_CLOUD_API_URL.replace(/\/$/u, "")}/v1/internal/discord-bugs`, {
|
|
602
|
+
method: "POST",
|
|
603
|
+
headers: {
|
|
604
|
+
Accept: "application/json",
|
|
605
|
+
Authorization: `Bearer ${this.input.environment.HELYX_SUPPORT_SERVICE_SECRET}`,
|
|
606
|
+
"Content-Type": "application/json",
|
|
607
|
+
},
|
|
608
|
+
body: JSON.stringify({
|
|
609
|
+
source: "discord_command",
|
|
610
|
+
transport: "hosted",
|
|
611
|
+
reporterDiscordUserId: input.reporterDiscordUserId,
|
|
612
|
+
guildId: input.guildId,
|
|
613
|
+
moduleOrArea: input.moduleOrArea,
|
|
614
|
+
summary: input.summary,
|
|
615
|
+
whatHappened: input.whatHappened,
|
|
616
|
+
...(input.extraDetail ? { extraDetail: input.extraDetail } : {}),
|
|
617
|
+
interactionId: input.interactionId,
|
|
618
|
+
correlationId: input.correlationId,
|
|
619
|
+
}),
|
|
620
|
+
signal: AbortSignal.timeout(CLOUD_DEFAULT_REQUEST_TIMEOUT_MS),
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
throw new Error("support_api_unavailable");
|
|
625
|
+
}
|
|
626
|
+
const payload = z
|
|
627
|
+
.object({
|
|
628
|
+
reference: z.string().trim().min(1).max(40),
|
|
629
|
+
created: z.boolean(),
|
|
630
|
+
})
|
|
631
|
+
.strict()
|
|
632
|
+
.safeParse(await response.json().catch(() => null));
|
|
633
|
+
if (!response.ok || !payload.success)
|
|
634
|
+
throw new Error(response.status === 429
|
|
635
|
+
? "support_rate_limited"
|
|
636
|
+
: "support_api_rejected");
|
|
637
|
+
return payload.data;
|
|
638
|
+
}
|
|
639
|
+
#rejectPendingSupport(message) {
|
|
640
|
+
for (const pending of this.#pendingSupport.values()) {
|
|
641
|
+
clearTimeout(pending.timer);
|
|
642
|
+
pending.reject(new Error(message));
|
|
643
|
+
}
|
|
644
|
+
this.#pendingSupport.clear();
|
|
645
|
+
}
|
|
538
646
|
async #readBootstrap() {
|
|
539
647
|
const environmentToken = this.input.environment.HELYX_CLOUD_ENROLMENT_TOKEN;
|
|
540
648
|
const filePath = resolve(process.cwd(), this.input.environment.HELYX_CLOUD_ENROLMENT_FILE);
|
|
@@ -615,7 +723,16 @@ function connectorCapabilities() {
|
|
|
615
723
|
"guild.module.permissions.write.v1",
|
|
616
724
|
"guild.module.logging.write.v1",
|
|
617
725
|
"guild.module.action.execute.v1",
|
|
726
|
+
"guild.module.resource.list.v1",
|
|
727
|
+
"guild.module.resource.read.v1",
|
|
728
|
+
"guild.module.resource.relation.list.v1",
|
|
729
|
+
"guild.module.resource.preview.v1",
|
|
730
|
+
"guild.module.resource.create.v1",
|
|
731
|
+
"guild.module.resource.update.v1",
|
|
732
|
+
"guild.module.resource.delete.v1",
|
|
733
|
+
"guild.module.resource.action.execute.v1",
|
|
618
734
|
"support.guild.snapshot.read.v1",
|
|
735
|
+
"support.bug.create.v1",
|
|
619
736
|
];
|
|
620
737
|
}
|
|
621
738
|
function normalizeEnrolmentToken(value) {
|
|
@@ -70,6 +70,90 @@ export class CloudControlDispatcher {
|
|
|
70
70
|
});
|
|
71
71
|
break;
|
|
72
72
|
}
|
|
73
|
+
case "guild.module.resource.list.v1": {
|
|
74
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
75
|
+
result = await this.control.listManagedResources({
|
|
76
|
+
guildId: input.guildId,
|
|
77
|
+
moduleId: input.moduleId,
|
|
78
|
+
resourceType: input.resourceType,
|
|
79
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
80
|
+
...(input.search === undefined ? {} : { search: input.search }),
|
|
81
|
+
...(input.filters === undefined ? {} : { filters: input.filters }),
|
|
82
|
+
limit: input.limit,
|
|
83
|
+
actorUserId: requiredActor(actorUserId),
|
|
84
|
+
correlationId,
|
|
85
|
+
});
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case "guild.module.resource.read.v1": {
|
|
89
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
90
|
+
result = await this.control.readManagedResource({
|
|
91
|
+
...input,
|
|
92
|
+
actorUserId: requiredActor(actorUserId),
|
|
93
|
+
correlationId,
|
|
94
|
+
});
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "guild.module.resource.relation.list.v1": {
|
|
98
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
99
|
+
result = await this.control.listManagedResourceRelation({
|
|
100
|
+
guildId: input.guildId,
|
|
101
|
+
moduleId: input.moduleId,
|
|
102
|
+
resourceType: input.resourceType,
|
|
103
|
+
resourceId: input.resourceId,
|
|
104
|
+
relationId: input.relationId,
|
|
105
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
106
|
+
limit: input.limit,
|
|
107
|
+
actorUserId: requiredActor(actorUserId),
|
|
108
|
+
correlationId,
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case "guild.module.resource.preview.v1": {
|
|
113
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
114
|
+
result = await this.control.previewManagedResource({
|
|
115
|
+
...input,
|
|
116
|
+
actorUserId: requiredActor(actorUserId),
|
|
117
|
+
correlationId,
|
|
118
|
+
});
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case "guild.module.resource.create.v1": {
|
|
122
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
123
|
+
result = await this.control.createManagedResource({
|
|
124
|
+
...input,
|
|
125
|
+
actorUserId: requiredActor(actorUserId),
|
|
126
|
+
correlationId,
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "guild.module.resource.update.v1": {
|
|
131
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
132
|
+
result = await this.control.updateManagedResource({
|
|
133
|
+
...input,
|
|
134
|
+
actorUserId: requiredActor(actorUserId),
|
|
135
|
+
correlationId,
|
|
136
|
+
});
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case "guild.module.resource.delete.v1": {
|
|
140
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
141
|
+
result = await this.control.deleteManagedResource({
|
|
142
|
+
...input,
|
|
143
|
+
actorUserId: requiredActor(actorUserId),
|
|
144
|
+
correlationId,
|
|
145
|
+
});
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "guild.module.resource.action.execute.v1": {
|
|
149
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
150
|
+
result = await this.control.executeManagedResourceAction({
|
|
151
|
+
...input,
|
|
152
|
+
actorUserId: requiredActor(actorUserId),
|
|
153
|
+
correlationId,
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
73
157
|
case "support.guild.snapshot.read.v1": {
|
|
74
158
|
const input = parseOperationInput(request.operation, request.input);
|
|
75
159
|
if (request.actor.kind !== "helyx_staff")
|
package/dist/control-server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DatabasePool } from "@helyx/database";
|
|
2
|
-
import { type DashboardActionResult, type MessageResponse, type ModuleDefinition, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
import { type DashboardActionResult, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type ServiceAccess } from "@helyx/sdk";
|
|
3
3
|
import { type Client } from "discord.js";
|
|
4
4
|
import { type FastifyInstance } from "fastify";
|
|
5
5
|
import { type ShardHealthSnapshot } from "./shard-contracts.js";
|
|
@@ -27,6 +27,7 @@ interface DashboardActionDefinition {
|
|
|
27
27
|
buttonLabel: string;
|
|
28
28
|
style: "primary" | "secondary" | "danger";
|
|
29
29
|
}
|
|
30
|
+
type DashboardResourceDefinition = NonNullable<NonNullable<ModuleDefinition["manifest"]["dashboard"]>["resources"]>[number];
|
|
30
31
|
interface DashboardModuleDefinition {
|
|
31
32
|
id: string;
|
|
32
33
|
name: string;
|
|
@@ -41,6 +42,7 @@ interface DashboardModuleDefinition {
|
|
|
41
42
|
commands: DashboardCommandDefinition[];
|
|
42
43
|
logEvents: DashboardLogEventDefinition[];
|
|
43
44
|
actions: DashboardActionDefinition[];
|
|
45
|
+
resources: readonly DashboardResourceDefinition[];
|
|
44
46
|
}
|
|
45
47
|
interface DashboardLoggingPolicy {
|
|
46
48
|
eventId: string;
|
|
@@ -75,6 +77,10 @@ export interface BotControlGuildResourceDirectory {
|
|
|
75
77
|
messageId: string;
|
|
76
78
|
}>;
|
|
77
79
|
deleteChannelMessage(channelId: string, messageId: string): Promise<void>;
|
|
80
|
+
editChannelMessage(channelId: string, messageId: string, response: MessageResponse): Promise<void>;
|
|
81
|
+
addChannelMessageReaction(channelId: string, messageId: string, emojiIdentifier: string): Promise<void>;
|
|
82
|
+
resolveActor(guildId: string, actorUserId: string): Promise<PermissionActor>;
|
|
83
|
+
sendPrivateSupportNotification?(guildId: string, channelId: string, response: MessageResponse, notificationId: string): Promise<void>;
|
|
78
84
|
}
|
|
79
85
|
export declare class DiscordGuildResourceDirectory implements BotControlGuildResourceDirectory {
|
|
80
86
|
private readonly client;
|
|
@@ -84,6 +90,18 @@ export declare class DiscordGuildResourceDirectory implements BotControlGuildRes
|
|
|
84
90
|
messageId: string;
|
|
85
91
|
}>;
|
|
86
92
|
deleteChannelMessage(channelId: string, messageId: string): Promise<void>;
|
|
93
|
+
editChannelMessage(channelId: string, messageId: string, response: MessageResponse): Promise<void>;
|
|
94
|
+
addChannelMessageReaction(channelId: string, messageId: string, emojiIdentifier: string): Promise<void>;
|
|
95
|
+
resolveActor(guildId: string, actorUserId: string): Promise<PermissionActor>;
|
|
96
|
+
sendPrivateSupportNotification(guildId: string, channelId: string, response: MessageResponse, notificationId: string): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
export interface SupportNotificationInput {
|
|
99
|
+
notificationId: string;
|
|
100
|
+
reference: string;
|
|
101
|
+
caseType: "bug" | "general_support" | "security";
|
|
102
|
+
categoryOrModule: string;
|
|
103
|
+
reportedSeverity: "low" | "medium" | "high" | "critical" | null;
|
|
104
|
+
source: "dashboard_bug" | "dashboard_support" | "dashboard_security" | "discord_command";
|
|
87
105
|
}
|
|
88
106
|
export interface BotControlState {
|
|
89
107
|
channels: BotControlGuildResources["channels"];
|
|
@@ -98,6 +116,7 @@ export interface BotControlState {
|
|
|
98
116
|
logging: Record<string, DashboardLoggingPolicy[]>;
|
|
99
117
|
}
|
|
100
118
|
export interface BotControlHandler {
|
|
119
|
+
sendSupportNotification(input: SupportNotificationInput): Promise<void>;
|
|
101
120
|
getGuildState(guildId: string): Promise<BotControlState>;
|
|
102
121
|
setModuleStatus(input: {
|
|
103
122
|
guildId: string;
|
|
@@ -140,6 +159,85 @@ export interface BotControlHandler {
|
|
|
140
159
|
actorUserId: string;
|
|
141
160
|
correlationId: string;
|
|
142
161
|
}): Promise<DashboardActionResult>;
|
|
162
|
+
listManagedResources(input: {
|
|
163
|
+
guildId: string;
|
|
164
|
+
moduleId: string;
|
|
165
|
+
resourceType: string;
|
|
166
|
+
cursor?: string;
|
|
167
|
+
limit: number;
|
|
168
|
+
search?: string;
|
|
169
|
+
filters?: Readonly<Record<string, string>>;
|
|
170
|
+
actorUserId: string;
|
|
171
|
+
correlationId: string;
|
|
172
|
+
}): Promise<ManagedResourcePage>;
|
|
173
|
+
readManagedResource(input: {
|
|
174
|
+
guildId: string;
|
|
175
|
+
moduleId: string;
|
|
176
|
+
resourceType: string;
|
|
177
|
+
resourceId: string;
|
|
178
|
+
actorUserId: string;
|
|
179
|
+
correlationId: string;
|
|
180
|
+
}): Promise<ManagedResourceDetail>;
|
|
181
|
+
listManagedResourceRelation(input: {
|
|
182
|
+
guildId: string;
|
|
183
|
+
moduleId: string;
|
|
184
|
+
resourceType: string;
|
|
185
|
+
resourceId: string;
|
|
186
|
+
relationId: string;
|
|
187
|
+
cursor?: string;
|
|
188
|
+
limit: number;
|
|
189
|
+
actorUserId: string;
|
|
190
|
+
correlationId: string;
|
|
191
|
+
}): Promise<ManagedResourceRelationPage>;
|
|
192
|
+
previewManagedResource(input: {
|
|
193
|
+
guildId: string;
|
|
194
|
+
moduleId: string;
|
|
195
|
+
resourceType: string;
|
|
196
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
197
|
+
actorUserId: string;
|
|
198
|
+
correlationId: string;
|
|
199
|
+
}): Promise<MessageResponse>;
|
|
200
|
+
createManagedResource(input: {
|
|
201
|
+
guildId: string;
|
|
202
|
+
moduleId: string;
|
|
203
|
+
resourceType: string;
|
|
204
|
+
intent: ManagedResourceCreationIntent;
|
|
205
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
206
|
+
actorUserId: string;
|
|
207
|
+
correlationId: string;
|
|
208
|
+
}): Promise<ManagedResourceDetail>;
|
|
209
|
+
updateManagedResource(input: {
|
|
210
|
+
guildId: string;
|
|
211
|
+
moduleId: string;
|
|
212
|
+
resourceType: string;
|
|
213
|
+
resourceId: string;
|
|
214
|
+
expectedRevision: number;
|
|
215
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
216
|
+
actorUserId: string;
|
|
217
|
+
correlationId: string;
|
|
218
|
+
}): Promise<ManagedResourceDetail>;
|
|
219
|
+
deleteManagedResource(input: {
|
|
220
|
+
guildId: string;
|
|
221
|
+
moduleId: string;
|
|
222
|
+
resourceType: string;
|
|
223
|
+
resourceId: string;
|
|
224
|
+
expectedRevision: number;
|
|
225
|
+
actorUserId: string;
|
|
226
|
+
correlationId: string;
|
|
227
|
+
}): Promise<{
|
|
228
|
+
deleted: boolean;
|
|
229
|
+
}>;
|
|
230
|
+
executeManagedResourceAction(input: {
|
|
231
|
+
guildId: string;
|
|
232
|
+
moduleId: string;
|
|
233
|
+
resourceType: string;
|
|
234
|
+
resourceId: string;
|
|
235
|
+
actionId: string;
|
|
236
|
+
expectedRevision: number;
|
|
237
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
238
|
+
actorUserId: string;
|
|
239
|
+
correlationId: string;
|
|
240
|
+
}): Promise<ManagedResourceDetail | null>;
|
|
143
241
|
listPrivacyProviders(): Promise<readonly {
|
|
144
242
|
id: string;
|
|
145
243
|
version: number;
|
|
@@ -163,14 +261,19 @@ export interface BotControlHandler {
|
|
|
163
261
|
}): Promise<PrivacyErasePage>;
|
|
164
262
|
}
|
|
165
263
|
export declare class BotControlTransportError extends Error {
|
|
166
|
-
readonly status: 400 | 404 | 409 | 502 | 503 | 504;
|
|
167
|
-
constructor(status: 400 | 404 | 409 | 502 | 503 | 504, message: string);
|
|
264
|
+
readonly status: 400 | 403 | 404 | 409 | 502 | 503 | 504;
|
|
265
|
+
constructor(status: 400 | 403 | 404 | 409 | 502 | 503 | 504, message: string);
|
|
168
266
|
}
|
|
169
267
|
export declare class BotControlService implements BotControlHandler {
|
|
170
268
|
#private;
|
|
171
269
|
private readonly database;
|
|
172
270
|
private readonly resources;
|
|
173
|
-
|
|
271
|
+
private readonly supportNotifications?;
|
|
272
|
+
constructor(database: DatabasePool, modules: readonly ModuleDefinition[], resources: BotControlGuildResourceDirectory, services: ServiceAccess, privacyExecutor?: PrivacyProviderExecutor, supportNotifications?: {
|
|
273
|
+
guildId: string;
|
|
274
|
+
channelId: string;
|
|
275
|
+
} | undefined);
|
|
276
|
+
sendSupportNotification(input: SupportNotificationInput): Promise<void>;
|
|
174
277
|
listPrivacyProviders(): Promise<readonly {
|
|
175
278
|
id: string;
|
|
176
279
|
version: number;
|
|
@@ -234,6 +337,85 @@ export declare class BotControlService implements BotControlHandler {
|
|
|
234
337
|
actorUserId: string;
|
|
235
338
|
correlationId: string;
|
|
236
339
|
}): Promise<DashboardActionResult>;
|
|
340
|
+
listManagedResources(input: {
|
|
341
|
+
guildId: string;
|
|
342
|
+
moduleId: string;
|
|
343
|
+
resourceType: string;
|
|
344
|
+
cursor?: string;
|
|
345
|
+
limit: number;
|
|
346
|
+
search?: string;
|
|
347
|
+
filters?: Readonly<Record<string, string>>;
|
|
348
|
+
actorUserId: string;
|
|
349
|
+
correlationId: string;
|
|
350
|
+
}): Promise<ManagedResourcePage>;
|
|
351
|
+
readManagedResource(input: {
|
|
352
|
+
guildId: string;
|
|
353
|
+
moduleId: string;
|
|
354
|
+
resourceType: string;
|
|
355
|
+
resourceId: string;
|
|
356
|
+
actorUserId: string;
|
|
357
|
+
correlationId: string;
|
|
358
|
+
}): Promise<ManagedResourceDetail>;
|
|
359
|
+
listManagedResourceRelation(input: {
|
|
360
|
+
guildId: string;
|
|
361
|
+
moduleId: string;
|
|
362
|
+
resourceType: string;
|
|
363
|
+
resourceId: string;
|
|
364
|
+
relationId: string;
|
|
365
|
+
cursor?: string;
|
|
366
|
+
limit: number;
|
|
367
|
+
actorUserId: string;
|
|
368
|
+
correlationId: string;
|
|
369
|
+
}): Promise<ManagedResourceRelationPage>;
|
|
370
|
+
previewManagedResource(input: {
|
|
371
|
+
guildId: string;
|
|
372
|
+
moduleId: string;
|
|
373
|
+
resourceType: string;
|
|
374
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
375
|
+
actorUserId: string;
|
|
376
|
+
correlationId: string;
|
|
377
|
+
}): Promise<MessageResponse>;
|
|
378
|
+
createManagedResource(input: {
|
|
379
|
+
guildId: string;
|
|
380
|
+
moduleId: string;
|
|
381
|
+
resourceType: string;
|
|
382
|
+
intent: ManagedResourceCreationIntent;
|
|
383
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
384
|
+
actorUserId: string;
|
|
385
|
+
correlationId: string;
|
|
386
|
+
}): Promise<ManagedResourceDetail>;
|
|
387
|
+
updateManagedResource(input: {
|
|
388
|
+
guildId: string;
|
|
389
|
+
moduleId: string;
|
|
390
|
+
resourceType: string;
|
|
391
|
+
resourceId: string;
|
|
392
|
+
expectedRevision: number;
|
|
393
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
394
|
+
actorUserId: string;
|
|
395
|
+
correlationId: string;
|
|
396
|
+
}): Promise<ManagedResourceDetail>;
|
|
397
|
+
deleteManagedResource(input: {
|
|
398
|
+
guildId: string;
|
|
399
|
+
moduleId: string;
|
|
400
|
+
resourceType: string;
|
|
401
|
+
resourceId: string;
|
|
402
|
+
expectedRevision: number;
|
|
403
|
+
actorUserId: string;
|
|
404
|
+
correlationId: string;
|
|
405
|
+
}): Promise<{
|
|
406
|
+
deleted: boolean;
|
|
407
|
+
}>;
|
|
408
|
+
executeManagedResourceAction(input: {
|
|
409
|
+
guildId: string;
|
|
410
|
+
moduleId: string;
|
|
411
|
+
resourceType: string;
|
|
412
|
+
resourceId: string;
|
|
413
|
+
actionId: string;
|
|
414
|
+
expectedRevision: number;
|
|
415
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
416
|
+
actorUserId: string;
|
|
417
|
+
correlationId: string;
|
|
418
|
+
}): Promise<ManagedResourceDetail | null>;
|
|
237
419
|
}
|
|
238
420
|
export declare function createBotControlServer(input: {
|
|
239
421
|
service: BotControlHandler;
|
|
@@ -244,7 +426,7 @@ export declare function createBotControlServer(input: {
|
|
|
244
426
|
shardHealth?: () => Promise<ShardHealthSnapshot>;
|
|
245
427
|
}): Promise<FastifyInstance>;
|
|
246
428
|
export declare function mapBotControlError(error: unknown): {
|
|
247
|
-
status: 400 | 404 | 409;
|
|
429
|
+
status: 400 | 403 | 404 | 409;
|
|
248
430
|
message: string;
|
|
249
431
|
} | undefined;
|
|
250
432
|
export {};
|