@helyx/bot 0.1.0
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/LICENSE +725 -0
- package/README.md +19 -0
- package/dist/control-server.d.ts +198 -0
- package/dist/control-server.js +663 -0
- package/dist/events.d.ts +7 -0
- package/dist/events.js +142 -0
- package/dist/guilds.d.ts +6 -0
- package/dist/guilds.js +53 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +185 -0
- package/dist/interactions.d.ts +26 -0
- package/dist/interactions.js +666 -0
- package/dist/management-module.d.ts +15 -0
- package/dist/management-module.js +865 -0
- package/dist/module-configuration.d.ts +10 -0
- package/dist/module-configuration.js +58 -0
- package/dist/module-logging.d.ts +21 -0
- package/dist/module-logging.js +97 -0
- package/dist/modules.d.ts +16 -0
- package/dist/modules.js +81 -0
- package/dist/permissions-module.d.ts +3 -0
- package/dist/permissions-module.js +497 -0
- package/package.json +62 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { defineModule, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
2
|
+
import { PermissionFlagsBits } from "discord.js";
|
|
3
|
+
const MODULE_SELECT_ID = "helyx.permissions.module";
|
|
4
|
+
const TARGET_SELECT_ID = "helyx.permissions.target";
|
|
5
|
+
const MODULE_PREVIOUS_ID = "helyx.permissions.module.previous";
|
|
6
|
+
const MODULE_NEXT_ID = "helyx.permissions.module.next";
|
|
7
|
+
const TARGET_PREVIOUS_ID = "helyx.permissions.target.previous";
|
|
8
|
+
const TARGET_NEXT_ID = "helyx.permissions.target.next";
|
|
9
|
+
const POLICY_MODAL_ID = "helyx.permissions.policy";
|
|
10
|
+
const EVERYONE_FIELD_ID = "helyx.permissions.policy.everyone";
|
|
11
|
+
const REQUIRED_PERMISSIONS_FIELD_ID = "helyx.permissions.policy.required";
|
|
12
|
+
const ALLOWED_MENTIONABLES_FIELD_ID = "helyx.permissions.policy.allowed";
|
|
13
|
+
const DENIED_MENTIONABLES_FIELD_ID = "helyx.permissions.policy.denied";
|
|
14
|
+
const SESSION_PURPOSE = "permissions.manage";
|
|
15
|
+
const MODULE_PAGE_SIZE = 25;
|
|
16
|
+
const TARGET_PAGE_SIZE = 24;
|
|
17
|
+
const VIOLET = 0x7c3aed;
|
|
18
|
+
export function createPermissionsModule(configurableModules) {
|
|
19
|
+
const modules = new Map(configurableModules.map((module) => [module.manifest.id, module]));
|
|
20
|
+
const commandModules = [...modules.values()]
|
|
21
|
+
.filter((module) => commandTargets(module).length > 0)
|
|
22
|
+
.sort((left, right) => left.manifest.name.localeCompare(right.manifest.name, "en-GB"));
|
|
23
|
+
return defineModule({
|
|
24
|
+
manifest: {
|
|
25
|
+
manifestVersion: "0.1",
|
|
26
|
+
id: "helyx.permissions",
|
|
27
|
+
name: "Permissions",
|
|
28
|
+
version: "0.1.0",
|
|
29
|
+
description: "Configures Helyx module and command access policies.",
|
|
30
|
+
entry: "./dist/permissions-module.js",
|
|
31
|
+
framework: "^0.1.0",
|
|
32
|
+
dependencies: [],
|
|
33
|
+
permissions: [],
|
|
34
|
+
contributions: { commands: ["permissions"], events: [] },
|
|
35
|
+
},
|
|
36
|
+
defaultPermissions: {
|
|
37
|
+
module: restrictedPolicy(),
|
|
38
|
+
commands: {
|
|
39
|
+
permissions: restrictedPolicy(),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
interactions: {
|
|
43
|
+
commands: [
|
|
44
|
+
{
|
|
45
|
+
name: "permissions",
|
|
46
|
+
description: "Configure access to Helyx modules and commands",
|
|
47
|
+
registration: "global",
|
|
48
|
+
contexts: ["guild"],
|
|
49
|
+
integrationTypes: ["guild_install"],
|
|
50
|
+
rateLimit: { scope: "user", limit: 20, windowSeconds: 60 },
|
|
51
|
+
async execute(context) {
|
|
52
|
+
if (!context.serverId)
|
|
53
|
+
return;
|
|
54
|
+
if (commandModules.length === 0) {
|
|
55
|
+
await context.reply({
|
|
56
|
+
ephemeral: true,
|
|
57
|
+
embed: {
|
|
58
|
+
title: "No configurable modules",
|
|
59
|
+
description: "Install a feature module that contributes Discord commands first.",
|
|
60
|
+
color: 0xdc2626,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await context.services
|
|
66
|
+
.get(HELYX_SERVICE_NAMES.sessions)
|
|
67
|
+
.create({
|
|
68
|
+
guildId: context.serverId,
|
|
69
|
+
userId: context.userId,
|
|
70
|
+
purpose: SESSION_PURPOSE,
|
|
71
|
+
state: { modulePage: 0 },
|
|
72
|
+
expiresAt: new Date(Date.now() + 10 * 60 * 1_000),
|
|
73
|
+
replace: true,
|
|
74
|
+
});
|
|
75
|
+
await context.reply(modulePickerResponse(commandModules, 0));
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
selects: [
|
|
80
|
+
{
|
|
81
|
+
customId: MODULE_SELECT_ID,
|
|
82
|
+
async execute(context) {
|
|
83
|
+
const session = await getSession(context);
|
|
84
|
+
if (!session || !context.serverId)
|
|
85
|
+
return;
|
|
86
|
+
const moduleId = context.selectedValues[0];
|
|
87
|
+
const module = moduleId
|
|
88
|
+
? commandModules.find((candidate) => candidate.manifest.id === moduleId)
|
|
89
|
+
: undefined;
|
|
90
|
+
if (!module)
|
|
91
|
+
throw new Error("Selected module is unavailable");
|
|
92
|
+
const targets = commandTargets(module);
|
|
93
|
+
await context.services
|
|
94
|
+
.get(HELYX_SERVICE_NAMES.sessions)
|
|
95
|
+
.update({
|
|
96
|
+
sessionId: session.sessionId,
|
|
97
|
+
state: {
|
|
98
|
+
...session.state,
|
|
99
|
+
moduleId,
|
|
100
|
+
targetPage: 0,
|
|
101
|
+
},
|
|
102
|
+
expectedVersion: session.version,
|
|
103
|
+
});
|
|
104
|
+
await context.reply(targetPickerResponse(module, targets, 0));
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
customId: TARGET_SELECT_ID,
|
|
109
|
+
async execute(context) {
|
|
110
|
+
const session = await getSession(context);
|
|
111
|
+
if (!session || !context.serverId)
|
|
112
|
+
return;
|
|
113
|
+
const moduleId = stringState(session.state, "moduleId");
|
|
114
|
+
const target = context.selectedValues[0];
|
|
115
|
+
const module = modules.get(moduleId);
|
|
116
|
+
if (!module || !target)
|
|
117
|
+
throw new Error("Permission target expired");
|
|
118
|
+
const commandId = target === "__module__" ? undefined : target;
|
|
119
|
+
if (commandId && !commandTargets(module).includes(commandId)) {
|
|
120
|
+
throw new Error("Selected command is unavailable");
|
|
121
|
+
}
|
|
122
|
+
const current = await context.services
|
|
123
|
+
.get(HELYX_SERVICE_NAMES.permissions)
|
|
124
|
+
.getPolicy(context.serverId, moduleId, commandId);
|
|
125
|
+
const allowedDefaults = mentionableDefaults(current.policy.allowedUserIds, current.policy.allowedRoleIds);
|
|
126
|
+
const deniedDefaults = mentionableDefaults(current.policy.deniedUserIds, current.policy.deniedRoleIds);
|
|
127
|
+
if (allowedDefaults.length > 25 || deniedDefaults.length > 25) {
|
|
128
|
+
await context.reply({
|
|
129
|
+
ephemeral: true,
|
|
130
|
+
componentsV2: {
|
|
131
|
+
accentColor: 0xdc2626,
|
|
132
|
+
text: [
|
|
133
|
+
{
|
|
134
|
+
content: "## Policy is too large for Discord\nA Discord modal can show at most 25 allowed and 25 denied users or roles. Use the Helyx dashboard to edit this policy without losing existing entries.",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
await context.services
|
|
142
|
+
.get(HELYX_SERVICE_NAMES.sessions)
|
|
143
|
+
.update({
|
|
144
|
+
sessionId: session.sessionId,
|
|
145
|
+
state: {
|
|
146
|
+
moduleId,
|
|
147
|
+
...(commandId ? { commandId } : {}),
|
|
148
|
+
expectedPolicyVersion: current.version?.toString() ?? null,
|
|
149
|
+
},
|
|
150
|
+
expectedVersion: session.version,
|
|
151
|
+
});
|
|
152
|
+
await context.showModal({
|
|
153
|
+
customId: POLICY_MODAL_ID,
|
|
154
|
+
title: "Edit permission policy",
|
|
155
|
+
fields: [
|
|
156
|
+
{
|
|
157
|
+
type: "string-select",
|
|
158
|
+
customId: EVERYONE_FIELD_ID,
|
|
159
|
+
label: "Allow everyone to use the command?",
|
|
160
|
+
description: "Overrides below still take priority over this setting.",
|
|
161
|
+
required: true,
|
|
162
|
+
minValues: 1,
|
|
163
|
+
maxValues: 1,
|
|
164
|
+
options: [
|
|
165
|
+
{
|
|
166
|
+
label: "Yes",
|
|
167
|
+
value: "yes",
|
|
168
|
+
default: current.policy.everyone,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
label: "No",
|
|
172
|
+
value: "no",
|
|
173
|
+
default: !current.policy.everyone,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
customId: REQUIRED_PERMISSIONS_FIELD_ID,
|
|
179
|
+
label: "Required Discord permissions",
|
|
180
|
+
description: "Optional permission names; separate multiple values with commas.",
|
|
181
|
+
style: "paragraph",
|
|
182
|
+
value: current.policy.requiredPermissions.join(", "),
|
|
183
|
+
placeholder: "MANAGE_GUILD, MODERATE_MEMBERS",
|
|
184
|
+
maxLength: 1000,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: "discord-mentionable",
|
|
188
|
+
customId: ALLOWED_MENTIONABLES_FIELD_ID,
|
|
189
|
+
label: "Allowed users and roles",
|
|
190
|
+
description: "Users override role denials; allowed roles bypass general rules.",
|
|
191
|
+
placeholder: "Choose allowed users and roles",
|
|
192
|
+
required: false,
|
|
193
|
+
minValues: 0,
|
|
194
|
+
maxValues: 25,
|
|
195
|
+
defaultValues: allowedDefaults,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: "discord-mentionable",
|
|
199
|
+
customId: DENIED_MENTIONABLES_FIELD_ID,
|
|
200
|
+
label: "Denied users and roles",
|
|
201
|
+
description: "User denials take priority; role denials apply next.",
|
|
202
|
+
placeholder: "Choose denied users and roles",
|
|
203
|
+
required: false,
|
|
204
|
+
minValues: 0,
|
|
205
|
+
maxValues: 25,
|
|
206
|
+
defaultValues: deniedDefaults,
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
buttons: [
|
|
214
|
+
modulePageButton(MODULE_PREVIOUS_ID, -1),
|
|
215
|
+
modulePageButton(MODULE_NEXT_ID, 1),
|
|
216
|
+
targetPageButton(TARGET_PREVIOUS_ID, -1),
|
|
217
|
+
targetPageButton(TARGET_NEXT_ID, 1),
|
|
218
|
+
],
|
|
219
|
+
modals: [
|
|
220
|
+
{
|
|
221
|
+
customId: POLICY_MODAL_ID,
|
|
222
|
+
async execute(context) {
|
|
223
|
+
const session = await getSession(context);
|
|
224
|
+
if (!session || !context.serverId)
|
|
225
|
+
return;
|
|
226
|
+
const moduleId = stringState(session.state, "moduleId");
|
|
227
|
+
const commandId = optionalStringState(session.state, "commandId");
|
|
228
|
+
const expected = session.state.expectedPolicyVersion;
|
|
229
|
+
const everyoneValues = context.getStringSelectValues(EVERYONE_FIELD_ID);
|
|
230
|
+
if (everyoneValues.length !== 1 ||
|
|
231
|
+
!["yes", "no"].includes(everyoneValues[0])) {
|
|
232
|
+
throw new Error("Allow everyone selection is invalid");
|
|
233
|
+
}
|
|
234
|
+
const allowed = context.getSelectedMentionables(ALLOWED_MENTIONABLES_FIELD_ID);
|
|
235
|
+
const denied = context.getSelectedMentionables(DENIED_MENTIONABLES_FIELD_ID);
|
|
236
|
+
assertNoMentionableConflicts(allowed, denied);
|
|
237
|
+
const policy = {
|
|
238
|
+
everyone: everyoneValues[0] === "yes",
|
|
239
|
+
requiredPermissions: parsePermissions(context.getField(REQUIRED_PERMISSIONS_FIELD_ID)),
|
|
240
|
+
allowedRoleIds: [...allowed.roleIds],
|
|
241
|
+
deniedRoleIds: [...denied.roleIds],
|
|
242
|
+
allowedUserIds: [...allowed.userIds],
|
|
243
|
+
deniedUserIds: [...denied.userIds],
|
|
244
|
+
};
|
|
245
|
+
await context.services
|
|
246
|
+
.get(HELYX_SERVICE_NAMES.permissions)
|
|
247
|
+
.setPolicy({
|
|
248
|
+
guildId: context.serverId,
|
|
249
|
+
moduleId,
|
|
250
|
+
...(commandId ? { commandId } : {}),
|
|
251
|
+
policy,
|
|
252
|
+
expectedVersion: typeof expected === "string" ? BigInt(expected) : null,
|
|
253
|
+
context: {
|
|
254
|
+
actorUserId: context.userId,
|
|
255
|
+
correlationId: context.interactionId,
|
|
256
|
+
source: "discord",
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
await context.services
|
|
260
|
+
.get(HELYX_SERVICE_NAMES.sessions)
|
|
261
|
+
.delete(session.sessionId);
|
|
262
|
+
await context.reply({
|
|
263
|
+
ephemeral: true,
|
|
264
|
+
embed: {
|
|
265
|
+
title: "Permissions updated",
|
|
266
|
+
description: commandId
|
|
267
|
+
? "The override for `" + commandId + "` was updated."
|
|
268
|
+
: "The module policy for `" + moduleId + "` was updated.",
|
|
269
|
+
color: 0x16a34a,
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
start: () => Promise.resolve(),
|
|
277
|
+
stop: () => Promise.resolve(),
|
|
278
|
+
});
|
|
279
|
+
function modulePageButton(customId, change) {
|
|
280
|
+
return {
|
|
281
|
+
customId,
|
|
282
|
+
async execute(context) {
|
|
283
|
+
const session = await getSession(context);
|
|
284
|
+
if (!session)
|
|
285
|
+
return;
|
|
286
|
+
const requested = numberState(session.state, "modulePage") + change;
|
|
287
|
+
const page = clampPage(requested, pageCount(commandModules.length, MODULE_PAGE_SIZE));
|
|
288
|
+
await context.services
|
|
289
|
+
.get(HELYX_SERVICE_NAMES.sessions)
|
|
290
|
+
.update({
|
|
291
|
+
sessionId: session.sessionId,
|
|
292
|
+
state: { ...session.state, modulePage: page },
|
|
293
|
+
expectedVersion: session.version,
|
|
294
|
+
});
|
|
295
|
+
await context.reply(modulePickerResponse(commandModules, page));
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function targetPageButton(customId, change) {
|
|
300
|
+
return {
|
|
301
|
+
customId,
|
|
302
|
+
async execute(context) {
|
|
303
|
+
const session = await getSession(context);
|
|
304
|
+
if (!session)
|
|
305
|
+
return;
|
|
306
|
+
const moduleId = stringState(session.state, "moduleId");
|
|
307
|
+
const module = commandModules.find((candidate) => candidate.manifest.id === moduleId);
|
|
308
|
+
if (!module)
|
|
309
|
+
throw new Error("Selected module is unavailable");
|
|
310
|
+
const targets = commandTargets(module);
|
|
311
|
+
const requested = numberState(session.state, "targetPage") + change;
|
|
312
|
+
const page = clampPage(requested, pageCount(targets.length, TARGET_PAGE_SIZE));
|
|
313
|
+
await context.services
|
|
314
|
+
.get(HELYX_SERVICE_NAMES.sessions)
|
|
315
|
+
.update({
|
|
316
|
+
sessionId: session.sessionId,
|
|
317
|
+
state: { ...session.state, targetPage: page },
|
|
318
|
+
expectedVersion: session.version,
|
|
319
|
+
});
|
|
320
|
+
await context.reply(targetPickerResponse(module, targets, page));
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function restrictedPolicy() {
|
|
326
|
+
return {
|
|
327
|
+
everyone: false,
|
|
328
|
+
requiredPermissions: ["MANAGE_GUILD"],
|
|
329
|
+
allowedRoleIds: [],
|
|
330
|
+
deniedRoleIds: [],
|
|
331
|
+
allowedUserIds: [],
|
|
332
|
+
deniedUserIds: [],
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function commandTargets(module) {
|
|
336
|
+
return (module.interactions?.commands ?? []).flatMap((command) => command.subcommands?.length
|
|
337
|
+
? command.subcommands.map((subcommand) => `${command.name}.${subcommand.name}`)
|
|
338
|
+
: [command.name]);
|
|
339
|
+
}
|
|
340
|
+
function modulePickerResponse(modules, requestedPage) {
|
|
341
|
+
const pages = pageCount(modules.length, MODULE_PAGE_SIZE);
|
|
342
|
+
const page = clampPage(requestedPage, pages);
|
|
343
|
+
const visible = pageItems(modules, page, MODULE_PAGE_SIZE);
|
|
344
|
+
return {
|
|
345
|
+
ephemeral: true,
|
|
346
|
+
embed: {
|
|
347
|
+
title: "Module permissions",
|
|
348
|
+
description: "Choose an installed module. Disabled modules can be configured before they are enabled. Module-level policy is inherited unless a command override exists." +
|
|
349
|
+
(pages > 1 ? ` Page ${String(page + 1)} of ${String(pages)}.` : ""),
|
|
350
|
+
color: VIOLET,
|
|
351
|
+
},
|
|
352
|
+
selects: [
|
|
353
|
+
{
|
|
354
|
+
customId: MODULE_SELECT_ID,
|
|
355
|
+
placeholder: "Choose a module",
|
|
356
|
+
options: visible.map((module) => ({
|
|
357
|
+
label: module.manifest.name,
|
|
358
|
+
value: module.manifest.id,
|
|
359
|
+
description: module.manifest.description.slice(0, 100),
|
|
360
|
+
})),
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
buttons: paginationButtons(page, pages, MODULE_PREVIOUS_ID, MODULE_NEXT_ID),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function targetPickerResponse(module, targets, requestedPage) {
|
|
367
|
+
const pages = pageCount(targets.length, TARGET_PAGE_SIZE);
|
|
368
|
+
const page = clampPage(requestedPage, pages);
|
|
369
|
+
const visible = pageItems(targets, page, TARGET_PAGE_SIZE);
|
|
370
|
+
return {
|
|
371
|
+
ephemeral: true,
|
|
372
|
+
embed: {
|
|
373
|
+
title: `${module.manifest.name} permissions`,
|
|
374
|
+
description: "Choose the whole module or one command. Command policies override the module policy." +
|
|
375
|
+
(pages > 1 ? ` Page ${String(page + 1)} of ${String(pages)}.` : ""),
|
|
376
|
+
color: VIOLET,
|
|
377
|
+
},
|
|
378
|
+
selects: [
|
|
379
|
+
{
|
|
380
|
+
customId: TARGET_SELECT_ID,
|
|
381
|
+
placeholder: "Choose a permission target",
|
|
382
|
+
options: [
|
|
383
|
+
{
|
|
384
|
+
label: "Entire module",
|
|
385
|
+
value: "__module__",
|
|
386
|
+
description: "Default policy for every command",
|
|
387
|
+
},
|
|
388
|
+
...visible.map((target) => ({
|
|
389
|
+
label: `/${target.replaceAll(".", " ")}`,
|
|
390
|
+
value: target,
|
|
391
|
+
})),
|
|
392
|
+
],
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
buttons: paginationButtons(page, pages, TARGET_PREVIOUS_ID, TARGET_NEXT_ID),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function paginationButtons(page, pages, previousId, nextId) {
|
|
399
|
+
return [
|
|
400
|
+
...(page > 0
|
|
401
|
+
? [
|
|
402
|
+
{
|
|
403
|
+
type: "button",
|
|
404
|
+
customId: previousId,
|
|
405
|
+
label: "Previous page",
|
|
406
|
+
style: "secondary",
|
|
407
|
+
},
|
|
408
|
+
]
|
|
409
|
+
: []),
|
|
410
|
+
...(page + 1 < pages
|
|
411
|
+
? [
|
|
412
|
+
{
|
|
413
|
+
type: "button",
|
|
414
|
+
customId: nextId,
|
|
415
|
+
label: "Next page",
|
|
416
|
+
style: "secondary",
|
|
417
|
+
},
|
|
418
|
+
]
|
|
419
|
+
: []),
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
function pageItems(items, page, size) {
|
|
423
|
+
return items.slice(page * size, (page + 1) * size);
|
|
424
|
+
}
|
|
425
|
+
function pageCount(itemCount, size) {
|
|
426
|
+
return Math.max(1, Math.ceil(itemCount / size));
|
|
427
|
+
}
|
|
428
|
+
function clampPage(page, pages) {
|
|
429
|
+
return Math.max(0, Math.min(page, pages - 1));
|
|
430
|
+
}
|
|
431
|
+
async function getSession(context) {
|
|
432
|
+
if (!context.serverId)
|
|
433
|
+
return null;
|
|
434
|
+
const session = await context.services
|
|
435
|
+
.get(HELYX_SERVICE_NAMES.sessions)
|
|
436
|
+
.getFor(context.serverId, context.userId, SESSION_PURPOSE);
|
|
437
|
+
if (!session)
|
|
438
|
+
throw new Error("Permission session expired; run /permissions again");
|
|
439
|
+
return session;
|
|
440
|
+
}
|
|
441
|
+
function stringState(state, field) {
|
|
442
|
+
const value = state[field];
|
|
443
|
+
if (typeof value !== "string")
|
|
444
|
+
throw new Error("Permission session is invalid");
|
|
445
|
+
return value;
|
|
446
|
+
}
|
|
447
|
+
function numberState(state, field) {
|
|
448
|
+
const value = state[field];
|
|
449
|
+
if (value === undefined)
|
|
450
|
+
return 0;
|
|
451
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
452
|
+
throw new Error("Permission session is invalid");
|
|
453
|
+
}
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
function optionalStringState(state, field) {
|
|
457
|
+
const value = state[field];
|
|
458
|
+
return typeof value === "string" ? value : undefined;
|
|
459
|
+
}
|
|
460
|
+
function mentionableDefaults(userIds, roleIds) {
|
|
461
|
+
return [
|
|
462
|
+
...userIds.map((id) => ({ type: "user", id })),
|
|
463
|
+
...roleIds.map((id) => ({ type: "role", id })),
|
|
464
|
+
];
|
|
465
|
+
}
|
|
466
|
+
function assertNoMentionableConflicts(allowed, denied) {
|
|
467
|
+
const deniedUsers = new Set(denied.userIds);
|
|
468
|
+
const deniedRoles = new Set(denied.roleIds);
|
|
469
|
+
if (allowed.userIds.some((id) => deniedUsers.has(id)) ||
|
|
470
|
+
allowed.roleIds.some((id) => deniedRoles.has(id))) {
|
|
471
|
+
throw new Error("A user or role cannot be both allowed and denied");
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function parsePermissions(value) {
|
|
475
|
+
const permissions = splitValues(value).map((item) => item.toUpperCase());
|
|
476
|
+
for (const permission of permissions) {
|
|
477
|
+
if (!/^[A-Z][A-Z0-9_]*$/u.test(permission)) {
|
|
478
|
+
throw new Error(`Invalid Discord permission: ${permission}`);
|
|
479
|
+
}
|
|
480
|
+
const discordName = permission
|
|
481
|
+
.toLowerCase()
|
|
482
|
+
.split("_")
|
|
483
|
+
.map((part) => part[0]?.toUpperCase() + part.slice(1))
|
|
484
|
+
.join("");
|
|
485
|
+
if (!(discordName in PermissionFlagsBits)) {
|
|
486
|
+
throw new Error(`Unknown Discord permission: ${permission}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return [...new Set(permissions)];
|
|
490
|
+
}
|
|
491
|
+
function splitValues(value) {
|
|
492
|
+
return value
|
|
493
|
+
.split(",")
|
|
494
|
+
.map((item) => item.trim())
|
|
495
|
+
.filter(Boolean);
|
|
496
|
+
}
|
|
497
|
+
//# sourceMappingURL=permissions-module.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@helyx/bot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Production Discord runtime for the modular Helyx platform.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"helyx",
|
|
7
|
+
"discord",
|
|
8
|
+
"bot",
|
|
9
|
+
"modular"
|
|
10
|
+
],
|
|
11
|
+
"private": false,
|
|
12
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
13
|
+
"homepage": "https://helyx.gg/",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/ZyC0R3/Helyx/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/ZyC0R3/Helyx.git",
|
|
20
|
+
"directory": "apps/bot"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=24 <27",
|
|
24
|
+
"npm": ">=11"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"provenance": true
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"!dist/*.tsbuildinfo",
|
|
34
|
+
"!dist/**/*.map",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"default": "./dist/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -b",
|
|
46
|
+
"dev": "node --env-file-if-exists=../../.env --import tsx src/index.ts",
|
|
47
|
+
"start": "node --env-file-if-exists=../../.env dist/index.js"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@helyx/config": "0.1.0",
|
|
51
|
+
"@helyx/core": "0.1.0",
|
|
52
|
+
"@helyx/database": "0.1.0",
|
|
53
|
+
"@helyx/framework": "0.1.0",
|
|
54
|
+
"@helyx/module-manifest": "0.1.0",
|
|
55
|
+
"@helyx/platform": "0.1.0",
|
|
56
|
+
"@helyx/sdk": "0.2.0",
|
|
57
|
+
"discord.js": "14.27.0",
|
|
58
|
+
"fastify": "5.10.0",
|
|
59
|
+
"pino": "10.3.1",
|
|
60
|
+
"zod": "4.4.3"
|
|
61
|
+
}
|
|
62
|
+
}
|