@helyx/bot 0.4.37 → 0.4.39
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 +22 -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-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 +5 -0
- 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/documentation/README.md +32 -0
- package/documentation/examples/.env.example +117 -0
- package/documentation/guides/getting-started.md +51 -0
- package/documentation/guides/self-hosting.md +40 -0
- package/documentation/modules/building-a-module.md +37 -0
- package/documentation/modules/publishing-a-module.md +27 -0
- package/documentation/modules/testing-a-module.md +46 -0
- package/documentation/reference/environment.md +42 -0
- package/documentation/runbooks/backup-and-recovery.md +41 -0
- package/documentation/runbooks/installation-and-upgrades.md +48 -0
- package/package.json +7 -4
- package/dist/oauth-module.test.d.ts +0 -2
- package/dist/oauth-module.test.js +0 -172
package/dist/oauth-module.js
CHANGED
|
@@ -2,6 +2,7 @@ import { defineModule, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
|
2
2
|
import { INTERNAL_MODULE_MANIFEST_FIELDS } from "./internal-module-manifest.js";
|
|
3
3
|
const MODULE_ID = "helyx.oauth";
|
|
4
4
|
const MODAL_ID = "helyx.oauth.revoke";
|
|
5
|
+
const OPEN_MODAL_ID = "helyx.oauth.revoke.open";
|
|
5
6
|
const SERVICE_FIELD_ID = "helyx.oauth.revoke.service";
|
|
6
7
|
const CONFIRM_FIELD_ID = "helyx.oauth.revoke.confirm";
|
|
7
8
|
const COMMENT_FIELD_ID = "helyx.oauth.revoke.comment";
|
|
@@ -14,6 +15,7 @@ const everyonePolicy = {
|
|
|
14
15
|
deniedUserIds: [],
|
|
15
16
|
};
|
|
16
17
|
export function createOAuthModule() {
|
|
18
|
+
const preparedModals = new Map();
|
|
17
19
|
return defineModule({
|
|
18
20
|
manifest: {
|
|
19
21
|
...INTERNAL_MODULE_MANIFEST_FIELDS,
|
|
@@ -26,108 +28,153 @@ export function createOAuthModule() {
|
|
|
26
28
|
framework: "^0.1.0",
|
|
27
29
|
dependencies: [],
|
|
28
30
|
permissions: [],
|
|
29
|
-
contributions: { commands: ["
|
|
31
|
+
contributions: { commands: ["helyx.connections"], events: [] },
|
|
30
32
|
},
|
|
31
33
|
defaultPermissions: {
|
|
32
34
|
module: everyonePolicy,
|
|
33
35
|
commands: { "oauth.revoke": everyonePolicy },
|
|
34
36
|
},
|
|
37
|
+
sharedCommands: [
|
|
38
|
+
{
|
|
39
|
+
family: "helyx",
|
|
40
|
+
path: "connections",
|
|
41
|
+
permissionId: "oauth.revoke",
|
|
42
|
+
description: "Review or revoke OAuth access granted to Helyx.",
|
|
43
|
+
acknowledgement: "deferred-ephemeral",
|
|
44
|
+
authorization: "public",
|
|
45
|
+
rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
|
|
46
|
+
execute: async (context) => {
|
|
47
|
+
const oauth = context.services.get(HELYX_SERVICE_NAMES.oauth);
|
|
48
|
+
let connections;
|
|
49
|
+
try {
|
|
50
|
+
connections = await oauth.listConnections({
|
|
51
|
+
discordUserId: context.userId,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
await context.reply(unavailable());
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (connections.length === 0) {
|
|
59
|
+
await context.reply({
|
|
60
|
+
ephemeral: true,
|
|
61
|
+
componentsV2: {
|
|
62
|
+
text: [
|
|
63
|
+
{
|
|
64
|
+
content: "You do not have any OAuth services connected to Helyx.",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const definition = {
|
|
72
|
+
customId: MODAL_ID,
|
|
73
|
+
title: "Revoke OAuth access",
|
|
74
|
+
items: [
|
|
75
|
+
{
|
|
76
|
+
type: "text-display",
|
|
77
|
+
content: "Choose one connection. This disconnects the service from Helyx everywhere. Any Helyx module, server or panel using it will lose access.",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: "string-select",
|
|
81
|
+
customId: SERVICE_FIELD_ID,
|
|
82
|
+
label: "Connected service",
|
|
83
|
+
placeholder: "Choose a connection",
|
|
84
|
+
required: true,
|
|
85
|
+
minValues: 1,
|
|
86
|
+
maxValues: 1,
|
|
87
|
+
options: connections.slice(0, 25).map((connection) => ({
|
|
88
|
+
label: `${providerName(connection.provider)} — ${connection.displayName}`.slice(0, 100),
|
|
89
|
+
value: connection.selectionRef,
|
|
90
|
+
description: "Remove this connection",
|
|
91
|
+
})),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: "string-select",
|
|
95
|
+
customId: CONFIRM_FIELD_ID,
|
|
96
|
+
label: "Confirm removal",
|
|
97
|
+
placeholder: "Select Remove access",
|
|
98
|
+
required: true,
|
|
99
|
+
minValues: 1,
|
|
100
|
+
maxValues: 1,
|
|
101
|
+
options: [
|
|
102
|
+
{
|
|
103
|
+
label: "Remove access",
|
|
104
|
+
value: "remove_access",
|
|
105
|
+
description: "Revoke access and delete my stored details",
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
customId: COMMENT_FIELD_ID,
|
|
111
|
+
label: "Reason or comment (optional)",
|
|
112
|
+
description: "Stored only in Helyx's restricted security audit.",
|
|
113
|
+
style: "paragraph",
|
|
114
|
+
required: false,
|
|
115
|
+
maxLength: 250,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
fields: [],
|
|
119
|
+
};
|
|
120
|
+
for (const [userId, prepared] of preparedModals) {
|
|
121
|
+
if (prepared.expiresAt <= Date.now())
|
|
122
|
+
preparedModals.delete(userId);
|
|
123
|
+
}
|
|
124
|
+
preparedModals.set(context.userId, {
|
|
125
|
+
definition,
|
|
126
|
+
expiresAt: Date.now() + 2 * 60 * 1_000,
|
|
127
|
+
});
|
|
128
|
+
await context.reply({
|
|
129
|
+
ephemeral: true,
|
|
130
|
+
componentsV2: {
|
|
131
|
+
text: [
|
|
132
|
+
{
|
|
133
|
+
content: `Found ${connections.length} connected service${connections.length === 1 ? "" : "s"}. Open the secure form to choose which connection to remove.`,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
buttons: [
|
|
138
|
+
{
|
|
139
|
+
type: "button",
|
|
140
|
+
customId: OPEN_MODAL_ID,
|
|
141
|
+
label: "Open connection form",
|
|
142
|
+
style: "primary",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
],
|
|
35
149
|
interactions: {
|
|
36
|
-
|
|
150
|
+
buttons: [
|
|
37
151
|
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
|
|
49
|
-
execute: async (context) => {
|
|
50
|
-
const oauth = context.services.get(HELYX_SERVICE_NAMES.oauth);
|
|
51
|
-
let connections;
|
|
52
|
-
try {
|
|
53
|
-
connections = await oauth.listConnections({
|
|
54
|
-
discordUserId: context.userId,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
await context.reply(unavailable());
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (connections.length === 0) {
|
|
62
|
-
await context.reply({
|
|
63
|
-
ephemeral: true,
|
|
64
|
-
componentsV2: {
|
|
65
|
-
text: [
|
|
66
|
-
{
|
|
67
|
-
content: "You do not have any OAuth services connected to Helyx.",
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
await context.showModal({
|
|
75
|
-
customId: MODAL_ID,
|
|
76
|
-
title: "Revoke OAuth access",
|
|
77
|
-
items: [
|
|
78
|
-
{
|
|
79
|
-
type: "text-display",
|
|
80
|
-
content: "Choose one connection. This disconnects the service from Helyx everywhere. Any Helyx module, server or panel using it will lose access.",
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
type: "string-select",
|
|
84
|
-
customId: SERVICE_FIELD_ID,
|
|
85
|
-
label: "Connected service",
|
|
86
|
-
placeholder: "Choose a connection",
|
|
87
|
-
required: true,
|
|
88
|
-
minValues: 1,
|
|
89
|
-
maxValues: 1,
|
|
90
|
-
options: connections.slice(0, 25).map((connection) => ({
|
|
91
|
-
label: `${providerName(connection.provider)} — ${connection.displayName}`.slice(0, 100),
|
|
92
|
-
value: connection.selectionRef,
|
|
93
|
-
description: "Remove this connection",
|
|
94
|
-
})),
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
type: "string-select",
|
|
98
|
-
customId: CONFIRM_FIELD_ID,
|
|
99
|
-
label: "Confirm removal",
|
|
100
|
-
placeholder: "Select Remove access",
|
|
101
|
-
required: true,
|
|
102
|
-
minValues: 1,
|
|
103
|
-
maxValues: 1,
|
|
104
|
-
options: [
|
|
105
|
-
{
|
|
106
|
-
label: "Remove access",
|
|
107
|
-
value: "remove_access",
|
|
108
|
-
description: "Revoke access and delete my stored details",
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
},
|
|
152
|
+
customId: OPEN_MODAL_ID,
|
|
153
|
+
authorization: "public",
|
|
154
|
+
execute: async (context) => {
|
|
155
|
+
const prepared = preparedModals.get(context.userId);
|
|
156
|
+
preparedModals.delete(context.userId);
|
|
157
|
+
if (!prepared || prepared.expiresAt <= Date.now()) {
|
|
158
|
+
await context.reply({
|
|
159
|
+
ephemeral: true,
|
|
160
|
+
componentsV2: {
|
|
161
|
+
text: [
|
|
112
162
|
{
|
|
113
|
-
|
|
114
|
-
label: "Reason or comment (optional)",
|
|
115
|
-
description: "Stored only in Helyx's restricted security audit.",
|
|
116
|
-
style: "paragraph",
|
|
117
|
-
required: false,
|
|
118
|
-
maxLength: 250,
|
|
163
|
+
content: "This connection form expired. Run `/helyx connections` again.",
|
|
119
164
|
},
|
|
120
165
|
],
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
await context.showModal(prepared.definition);
|
|
171
|
+
},
|
|
126
172
|
},
|
|
127
173
|
],
|
|
128
174
|
modals: [
|
|
129
175
|
{
|
|
130
176
|
customId: MODAL_ID,
|
|
177
|
+
acknowledgement: "deferred-ephemeral",
|
|
131
178
|
execute: async (context) => {
|
|
132
179
|
const selectionRef = context.getStringSelectValues(SERVICE_FIELD_ID)[0];
|
|
133
180
|
const confirmation = context.getStringSelectValues(CONFIRM_FIELD_ID)[0];
|
|
@@ -141,7 +188,6 @@ export function createOAuthModule() {
|
|
|
141
188
|
return;
|
|
142
189
|
}
|
|
143
190
|
const comment = context.getField(COMMENT_FIELD_ID).trim();
|
|
144
|
-
await context.deferReply?.(true);
|
|
145
191
|
try {
|
|
146
192
|
const result = await context.services
|
|
147
193
|
.get(HELYX_SERVICE_NAMES.oauth)
|
|
@@ -10,6 +10,7 @@ const MODULE_NEXT_ID = "helyx.permissions.module.next";
|
|
|
10
10
|
const TARGET_PREVIOUS_ID = "helyx.permissions.target.previous";
|
|
11
11
|
const TARGET_NEXT_ID = "helyx.permissions.target.next";
|
|
12
12
|
const POLICY_MODAL_ID = "helyx.permissions.policy";
|
|
13
|
+
const OPEN_POLICY_MODAL_ID = "helyx.permissions.policy.open";
|
|
13
14
|
const EVERYONE_FIELD_ID = "helyx.permissions.policy.everyone";
|
|
14
15
|
const REQUIRED_PERMISSIONS_FIELD_ID = "helyx.permissions.policy.required";
|
|
15
16
|
const ALLOWED_MENTIONABLES_FIELD_ID = "helyx.permissions.policy.allowed";
|
|
@@ -25,6 +26,8 @@ export function createPermissionsModule(configurableModules) {
|
|
|
25
26
|
const commandModules = [...modules.values()]
|
|
26
27
|
.filter((module) => commandTargets(module).length > 0)
|
|
27
28
|
.sort((left, right) => left.manifest.name.localeCompare(right.manifest.name, "en-GB"));
|
|
29
|
+
const preparedPolicies = new Map();
|
|
30
|
+
const preparedPolicyKey = (serverId, userId) => `${serverId}:${userId}`;
|
|
28
31
|
return defineModule({
|
|
29
32
|
manifest: {
|
|
30
33
|
...INTERNAL_MODULE_MANIFEST_FIELDS,
|
|
@@ -37,7 +40,7 @@ export function createPermissionsModule(configurableModules) {
|
|
|
37
40
|
framework: "^0.1.0",
|
|
38
41
|
dependencies: [],
|
|
39
42
|
permissions: [],
|
|
40
|
-
contributions: { commands: ["permissions"], events: [] },
|
|
43
|
+
contributions: { commands: ["helyx.permissions"], events: [] },
|
|
41
44
|
},
|
|
42
45
|
defaultPermissions: {
|
|
43
46
|
module: restrictedPolicy(),
|
|
@@ -45,47 +48,49 @@ export function createPermissionsModule(configurableModules) {
|
|
|
45
48
|
permissions: restrictedPolicy(),
|
|
46
49
|
},
|
|
47
50
|
},
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
await context.services
|
|
73
|
-
.get(HELYX_SERVICE_NAMES.sessions)
|
|
74
|
-
.create({
|
|
75
|
-
guildId: context.serverId,
|
|
76
|
-
userId: context.userId,
|
|
77
|
-
purpose: SESSION_PURPOSE,
|
|
78
|
-
state: { modulePage: 0 },
|
|
79
|
-
expiresAt: new Date(Date.now() + 10 * 60 * 1_000),
|
|
80
|
-
replace: true,
|
|
51
|
+
sharedCommands: [
|
|
52
|
+
{
|
|
53
|
+
family: "helyx",
|
|
54
|
+
path: "permissions",
|
|
55
|
+
permissionId: "permissions",
|
|
56
|
+
description: "Configure access to Helyx modules and commands",
|
|
57
|
+
acknowledgement: "deferred-ephemeral",
|
|
58
|
+
rateLimit: { scope: "user", limit: 20, windowSeconds: 60 },
|
|
59
|
+
async execute(context) {
|
|
60
|
+
if (!context.serverId) {
|
|
61
|
+
await context.reply(serverOnlyResponse());
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (commandModules.length === 0) {
|
|
65
|
+
await context.reply({
|
|
66
|
+
ephemeral: true,
|
|
67
|
+
embed: {
|
|
68
|
+
title: "No configurable modules",
|
|
69
|
+
description: "Install a feature module that contributes Discord commands first.",
|
|
70
|
+
color: 0xdc2626,
|
|
71
|
+
},
|
|
81
72
|
});
|
|
82
|
-
|
|
83
|
-
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await context.services
|
|
76
|
+
.get(HELYX_SERVICE_NAMES.sessions)
|
|
77
|
+
.create({
|
|
78
|
+
guildId: context.serverId,
|
|
79
|
+
userId: context.userId,
|
|
80
|
+
purpose: SESSION_PURPOSE,
|
|
81
|
+
state: { modulePage: 0 },
|
|
82
|
+
expiresAt: new Date(Date.now() + 10 * 60 * 1_000),
|
|
83
|
+
replace: true,
|
|
84
|
+
});
|
|
85
|
+
await context.reply(modulePickerResponse(commandModules, 0));
|
|
84
86
|
},
|
|
85
|
-
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
interactions: {
|
|
86
90
|
selects: [
|
|
87
91
|
{
|
|
88
92
|
customId: MODULE_SELECT_ID,
|
|
93
|
+
acknowledgement: "deferred-update",
|
|
89
94
|
async execute(context) {
|
|
90
95
|
const session = await getSession(context);
|
|
91
96
|
if (!session || !context.serverId)
|
|
@@ -113,6 +118,7 @@ export function createPermissionsModule(configurableModules) {
|
|
|
113
118
|
},
|
|
114
119
|
{
|
|
115
120
|
customId: TARGET_SELECT_ID,
|
|
121
|
+
acknowledgement: "deferred-update",
|
|
116
122
|
async execute(context) {
|
|
117
123
|
const session = await getSession(context);
|
|
118
124
|
if (!session || !context.serverId)
|
|
@@ -156,7 +162,7 @@ export function createPermissionsModule(configurableModules) {
|
|
|
156
162
|
},
|
|
157
163
|
expectedVersion: session.version,
|
|
158
164
|
});
|
|
159
|
-
|
|
165
|
+
const definition = {
|
|
160
166
|
customId: POLICY_MODAL_ID,
|
|
161
167
|
title: "Edit permission policy",
|
|
162
168
|
fields: [
|
|
@@ -213,11 +219,63 @@ export function createPermissionsModule(configurableModules) {
|
|
|
213
219
|
defaultValues: deniedDefaults,
|
|
214
220
|
},
|
|
215
221
|
],
|
|
222
|
+
};
|
|
223
|
+
for (const [key, prepared] of preparedPolicies) {
|
|
224
|
+
if (prepared.expiresAt <= Date.now())
|
|
225
|
+
preparedPolicies.delete(key);
|
|
226
|
+
}
|
|
227
|
+
preparedPolicies.set(preparedPolicyKey(context.serverId, context.userId), {
|
|
228
|
+
definition,
|
|
229
|
+
expiresAt: Date.now() + 2 * 60 * 1_000,
|
|
230
|
+
});
|
|
231
|
+
await context.reply({
|
|
232
|
+
ephemeral: true,
|
|
233
|
+
componentsV2: {
|
|
234
|
+
text: [
|
|
235
|
+
{
|
|
236
|
+
content: "The current permission policy is ready. Open the editor to make changes.",
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
buttons: [
|
|
241
|
+
{
|
|
242
|
+
type: "button",
|
|
243
|
+
customId: OPEN_POLICY_MODAL_ID,
|
|
244
|
+
label: "Open policy editor",
|
|
245
|
+
style: "primary",
|
|
246
|
+
},
|
|
247
|
+
],
|
|
216
248
|
});
|
|
217
249
|
},
|
|
218
250
|
},
|
|
219
251
|
],
|
|
220
252
|
buttons: [
|
|
253
|
+
{
|
|
254
|
+
customId: OPEN_POLICY_MODAL_ID,
|
|
255
|
+
authorization: "public",
|
|
256
|
+
async execute(context) {
|
|
257
|
+
const key = context.serverId
|
|
258
|
+
? preparedPolicyKey(context.serverId, context.userId)
|
|
259
|
+
: null;
|
|
260
|
+
const prepared = key ? preparedPolicies.get(key) : undefined;
|
|
261
|
+
if (key)
|
|
262
|
+
preparedPolicies.delete(key);
|
|
263
|
+
if (!prepared || prepared.expiresAt <= Date.now()) {
|
|
264
|
+
await context.reply({
|
|
265
|
+
ephemeral: true,
|
|
266
|
+
componentsV2: {
|
|
267
|
+
text: [
|
|
268
|
+
{
|
|
269
|
+
content: "This policy editor expired. Run `/helyx permissions` again.",
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
await context.showModal(prepared.definition);
|
|
277
|
+
},
|
|
278
|
+
},
|
|
221
279
|
modulePageButton(MODULE_PREVIOUS_ID, -1),
|
|
222
280
|
modulePageButton(MODULE_NEXT_ID, 1),
|
|
223
281
|
targetPageButton(TARGET_PREVIOUS_ID, -1),
|
|
@@ -226,6 +284,7 @@ export function createPermissionsModule(configurableModules) {
|
|
|
226
284
|
modals: [
|
|
227
285
|
{
|
|
228
286
|
customId: POLICY_MODAL_ID,
|
|
287
|
+
acknowledgement: "deferred-ephemeral",
|
|
229
288
|
async execute(context) {
|
|
230
289
|
const session = await getSession(context);
|
|
231
290
|
if (!session || !context.serverId)
|
|
@@ -309,6 +368,7 @@ export function createPermissionsModule(configurableModules) {
|
|
|
309
368
|
function modulePageButton(customId, change) {
|
|
310
369
|
return {
|
|
311
370
|
customId,
|
|
371
|
+
acknowledgement: "deferred-update",
|
|
312
372
|
async execute(context) {
|
|
313
373
|
const session = await getSession(context);
|
|
314
374
|
if (!session)
|
|
@@ -329,6 +389,7 @@ export function createPermissionsModule(configurableModules) {
|
|
|
329
389
|
function targetPageButton(customId, change) {
|
|
330
390
|
return {
|
|
331
391
|
customId,
|
|
392
|
+
acknowledgement: "deferred-update",
|
|
332
393
|
async execute(context) {
|
|
333
394
|
const session = await getSession(context);
|
|
334
395
|
if (!session)
|
|
@@ -352,6 +413,18 @@ export function createPermissionsModule(configurableModules) {
|
|
|
352
413
|
};
|
|
353
414
|
}
|
|
354
415
|
}
|
|
416
|
+
function serverOnlyResponse() {
|
|
417
|
+
return {
|
|
418
|
+
ephemeral: true,
|
|
419
|
+
componentsV2: {
|
|
420
|
+
text: [
|
|
421
|
+
{
|
|
422
|
+
content: "This command needs a server. Open a server where Helyx is installed, then use `/helyx permissions` there.",
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
355
428
|
function restrictedPolicy() {
|
|
356
429
|
return {
|
|
357
430
|
everyone: false,
|
|
@@ -373,7 +446,7 @@ function commandTargets(module) {
|
|
|
373
446
|
return nested;
|
|
374
447
|
return [command.name];
|
|
375
448
|
});
|
|
376
|
-
targets.push(...(module.nexusCommands ?? []).map((command) => `nexus.${command.path}`));
|
|
449
|
+
targets.push(...(module.sharedCommands ?? []).map((command) => command.permissionId ?? `${command.family}.${command.path}`), ...(module.nexusCommands ?? []).map((command) => `nexus.${command.path}`));
|
|
377
450
|
return [...new Set(targets)];
|
|
378
451
|
}
|
|
379
452
|
function modulePickerResponse(modules, requestedPage) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineModule } from "@helyx/sdk";
|
|
2
|
+
import { INTERNAL_MODULE_MANIFEST_FIELDS } from "./internal-module-manifest.js";
|
|
3
|
+
const everyonePolicy = {
|
|
4
|
+
everyone: true,
|
|
5
|
+
requiredPermissions: [],
|
|
6
|
+
allowedRoleIds: [],
|
|
7
|
+
deniedRoleIds: [],
|
|
8
|
+
allowedUserIds: [],
|
|
9
|
+
deniedUserIds: [],
|
|
10
|
+
};
|
|
11
|
+
export function createPrivacyModule() {
|
|
12
|
+
return defineModule({
|
|
13
|
+
manifest: {
|
|
14
|
+
...INTERNAL_MODULE_MANIFEST_FIELDS,
|
|
15
|
+
releaseStatus: "live",
|
|
16
|
+
id: "helyx.privacy",
|
|
17
|
+
name: "Helyx Privacy",
|
|
18
|
+
version: "0.1.0",
|
|
19
|
+
description: "Provides an always-available route to Helyx data controls.",
|
|
20
|
+
entry: "./dist/privacy-module.js",
|
|
21
|
+
framework: "^0.1.0",
|
|
22
|
+
dependencies: [],
|
|
23
|
+
permissions: [],
|
|
24
|
+
contributions: { commands: ["helyx.privacy"], events: [] },
|
|
25
|
+
},
|
|
26
|
+
defaultPermissions: {
|
|
27
|
+
module: everyonePolicy,
|
|
28
|
+
commands: { "helyx.privacy": everyonePolicy },
|
|
29
|
+
},
|
|
30
|
+
sharedCommands: [
|
|
31
|
+
{
|
|
32
|
+
family: "helyx",
|
|
33
|
+
path: "privacy",
|
|
34
|
+
permissionId: "helyx.privacy",
|
|
35
|
+
description: "Open the Helyx Privacy Centre and data controls",
|
|
36
|
+
acknowledgement: "immediate-response",
|
|
37
|
+
authorization: "public",
|
|
38
|
+
async execute(context) {
|
|
39
|
+
await context.reply({
|
|
40
|
+
ephemeral: true,
|
|
41
|
+
componentsV2: {
|
|
42
|
+
text: [
|
|
43
|
+
{
|
|
44
|
+
content: "## Helyx Privacy Centre\nView your data and request changes or deletion at [app.helyx.gg/privacy](https://app.helyx.gg/privacy).",
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
start() {
|
|
53
|
+
return Promise.resolve();
|
|
54
|
+
},
|
|
55
|
+
stop() {
|
|
56
|
+
return Promise.resolve();
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=privacy-module.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CORE_RECORD_RETENTION_TASK, ScheduledTaskRepository, } from "@helyx/database";
|
|
1
|
+
import { CORE_RECORD_RETENTION_TASK, MODULE_ACTIVATION_RECOVERY_TASK, ScheduledTaskRepository, } from "@helyx/database";
|
|
2
2
|
import { getModuleScheduledTaskContribution, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
const DEFAULT_POLL_INTERVAL_MS = 1_000;
|
|
@@ -279,6 +279,16 @@ export function createScheduledTaskRunner(input) {
|
|
|
279
279
|
delivery: "one-shot",
|
|
280
280
|
runWhenDisabled: true,
|
|
281
281
|
},
|
|
282
|
+
...input.modules
|
|
283
|
+
.filter((module) => typeof module.onActivated === "function" ||
|
|
284
|
+
typeof module.onDeactivated === "function")
|
|
285
|
+
.map((module) => ({
|
|
286
|
+
moduleId: module.manifest.id,
|
|
287
|
+
taskKind: MODULE_ACTIVATION_RECOVERY_TASK.taskKind,
|
|
288
|
+
taskVersion: MODULE_ACTIVATION_RECOVERY_TASK.taskVersion,
|
|
289
|
+
delivery: "one-shot",
|
|
290
|
+
runWhenDisabled: true,
|
|
291
|
+
})),
|
|
282
292
|
...input.modules.flatMap((module) => (module.scheduledTasks ?? []).map((contribution) => ({
|
|
283
293
|
moduleId: module.manifest.id,
|
|
284
294
|
taskKind: contribution.kind,
|
|
@@ -328,6 +338,24 @@ export function createLocalScheduledTaskRoute(input) {
|
|
|
328
338
|
const module = modules.get(task.moduleId);
|
|
329
339
|
if (!module)
|
|
330
340
|
return deferred("module_unavailable", DEFAULT_POLL_INTERVAL_MS * 30);
|
|
341
|
+
if (task.taskKind === MODULE_ACTIVATION_RECOVERY_TASK.taskKind &&
|
|
342
|
+
task.taskVersion === MODULE_ACTIVATION_RECOVERY_TASK.taskVersion &&
|
|
343
|
+
task.delivery === "one-shot") {
|
|
344
|
+
const state = parseActivationRecoveryState(task.payload);
|
|
345
|
+
if (!state || !task.guildId)
|
|
346
|
+
return { outcome: "requires_review", safeCode: "payload_invalid" };
|
|
347
|
+
const enabled = await input.services
|
|
348
|
+
.get(HELYX_SERVICE_NAMES.installations)
|
|
349
|
+
.isModuleEnabled(task.guildId, task.moduleId);
|
|
350
|
+
if (enabled !== (state === "enabled"))
|
|
351
|
+
return { outcome: "completed" };
|
|
352
|
+
const context = { guildId: task.guildId, services: input.services };
|
|
353
|
+
if (state === "enabled")
|
|
354
|
+
await module.onActivated?.(context);
|
|
355
|
+
else
|
|
356
|
+
await module.onDeactivated?.(context);
|
|
357
|
+
return { outcome: "completed" };
|
|
358
|
+
}
|
|
331
359
|
if (typeof input.services.has === "function" &&
|
|
332
360
|
input.services.has(HELYX_SERVICE_NAMES.moduleAvailability)) {
|
|
333
361
|
const availability = input.services.get(HELYX_SERVICE_NAMES.moduleAvailability);
|
|
@@ -368,6 +396,13 @@ export function createLocalScheduledTaskRoute(input) {
|
|
|
368
396
|
}, task);
|
|
369
397
|
};
|
|
370
398
|
}
|
|
399
|
+
function parseActivationRecoveryState(payload) {
|
|
400
|
+
if (Object.keys(payload).length !== 1)
|
|
401
|
+
return null;
|
|
402
|
+
return payload.stateId === "enabled" || payload.stateId === "disabled"
|
|
403
|
+
? payload.stateId
|
|
404
|
+
: null;
|
|
405
|
+
}
|
|
371
406
|
function toExecution(task) {
|
|
372
407
|
const common = {
|
|
373
408
|
taskId: task.record.id,
|