@helyx/bot 0.4.43 → 0.4.46
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 +21 -0
- package/dist/control-errors.d.ts +11 -0
- package/dist/control-errors.js +13 -0
- package/dist/control-server.d.ts +1 -1
- package/dist/control-server.js +7 -15
- package/dist/events.js +4 -0
- package/dist/index.js +1 -1
- package/dist/interactions.js +18 -8
- package/dist/module-availability-control.d.ts +5 -2
- package/dist/module-availability-control.js +20 -6
- package/dist/shard-supervisor.js +3 -2
- package/dist/shard-worker.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.46
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add explicit role-mention allowlists and member-leave identity metadata to the
|
|
8
|
+
module runtime contract. Upgrade Welcome with multiline user, role and channel
|
|
9
|
+
placeholders, an inline dashboard test send, and independent member-left
|
|
10
|
+
logging.
|
|
11
|
+
|
|
12
|
+
## 0.4.45
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Expose bounded structural user mentions to guild message event contributions without exposing message content, and include AFK in the reviewed self-host catalogue.
|
|
17
|
+
|
|
18
|
+
## 0.4.44
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- c369ae4: Publish the complete loaded feature catalogue to the Status synchroniser while keeping global availability controls limited to customer-selectable modules.
|
|
23
|
+
|
|
3
24
|
## 0.4.43
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type PermissionMutationDenialReason } from "@helyx/core";
|
|
2
|
+
export declare class BotControlForbiddenError extends Error {
|
|
3
|
+
constructor(message?: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function assertPermissionMutationAllowed(decision: {
|
|
6
|
+
allowed: true;
|
|
7
|
+
} | {
|
|
8
|
+
allowed: false;
|
|
9
|
+
reason: PermissionMutationDenialReason;
|
|
10
|
+
}): void;
|
|
11
|
+
//# sourceMappingURL=control-errors.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { permissionMutationDenialMessage, } from "@helyx/core";
|
|
2
|
+
export class BotControlForbiddenError extends Error {
|
|
3
|
+
constructor(message = "You no longer have permission to perform this action") {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "BotControlForbiddenError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function assertPermissionMutationAllowed(decision) {
|
|
9
|
+
if (decision.allowed)
|
|
10
|
+
return;
|
|
11
|
+
throw new BotControlForbiddenError(permissionMutationDenialMessage(decision.reason));
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=control-errors.js.map
|
package/dist/control-server.d.ts
CHANGED
|
@@ -350,7 +350,7 @@ export declare class BotControlService implements BotControlHandler {
|
|
|
350
350
|
constructor(database: DatabasePool, modules: readonly ModuleDefinition[], resources: BotControlGuildResourceDirectory, services: ServiceAccess, privacyExecutor?: PrivacyProviderExecutor, supportNotifications?: {
|
|
351
351
|
guildId: string;
|
|
352
352
|
channelId: string;
|
|
353
|
-
} | undefined, presenceOwnership?: GuildShardOwnership | undefined, presenceShardCount?: number | undefined);
|
|
353
|
+
} | undefined, presenceOwnership?: GuildShardOwnership | undefined, presenceShardCount?: number | undefined, statusModules?: readonly ModuleDefinition[]);
|
|
354
354
|
cleanupOAuthConsumer(input: {
|
|
355
355
|
consumerBindingId: string;
|
|
356
356
|
moduleId: string;
|
package/dist/control-server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AuditRepository, GuildAccessPolicyRepository, GuildCommandActiveLeaseError, GuildCommandRevisionConflictError, GuildCommandSetupConflictError, InstallationRepository, ModuleLoggingPolicyRepository, OptimisticConcurrencyError, PermissionPolicyRepository, withTransaction, } from "@helyx/database";
|
|
2
2
|
import { DashboardActionValidationError, DEFAULT_GUILD_ACCESS_POLICY, HELYX_SERVICE_NAMES, ModuleStateValidationError, } from "@helyx/sdk";
|
|
3
3
|
import { actionReceiptSchema, signedAssessmentEnvelopeSchema, } from "@helyx/verification-contracts";
|
|
4
|
-
import { canAccessGuildDashboard, canMutatePermissionPolicy, canUseCommand,
|
|
4
|
+
import { canAccessGuildDashboard, canMutatePermissionPolicy, canUseCommand, resolveHelyxAuthority, } from "@helyx/core";
|
|
5
5
|
import { ChannelType, PermissionFlagsBits } from "discord.js";
|
|
6
6
|
import Fastify, {} from "fastify";
|
|
7
7
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
@@ -12,8 +12,9 @@ import { completeFirstTimeCommandSetup, } from "./first-time-command-setup.js";
|
|
|
12
12
|
import { SHARD_HEALTH_STALE_MS, } from "./shard-contracts.js";
|
|
13
13
|
import { dashboardCategoryLabel } from "./dashboard-category.js";
|
|
14
14
|
import { assertVersion, dashboardActionPermissionId, declaredManagedResourcePermission, fromDashboardPolicy, managedResourcePermissionId, permissionPoliciesEqual, permissionPolicyKey, safeOptionalVersion, safeVersion, toDashboardPolicy, } from "./control-permissions.js";
|
|
15
|
+
import { assertPermissionMutationAllowed, BotControlForbiddenError, } from "./control-errors.js";
|
|
15
16
|
import { dashboardCommands, } from "./dashboard-commands.js";
|
|
16
|
-
import { availabilityCatalogueDefinitions, availabilityCatalogue, dashboardModuleStatus, disabledModuleSummary, moduleAvailabilityService, moduleAvailabilityStates, } from "./module-availability-control.js";
|
|
17
|
+
import { availabilityCatalogueDefinitions, availabilityCatalogue, dashboardModuleStatus, disabledModuleSummary, moduleAvailabilityService, moduleAvailabilityStates, statusCatalogueDefinitions, } from "./module-availability-control.js";
|
|
17
18
|
import { registerGuildCommandControlRoutes } from "./guild-command-control-routes.js";
|
|
18
19
|
import { createPrivacyProviderRegistry, describePrivacyProviders, } from "./privacy-provider-registry.js";
|
|
19
20
|
const snowflake = z.string().regex(/^\d{17,20}$/u);
|
|
@@ -265,12 +266,13 @@ export class BotControlService {
|
|
|
265
266
|
#modules;
|
|
266
267
|
#dashboardModules;
|
|
267
268
|
#availabilityModules;
|
|
269
|
+
#statusModules;
|
|
268
270
|
#configurations;
|
|
269
271
|
#installations;
|
|
270
272
|
#permissions;
|
|
271
273
|
#services;
|
|
272
274
|
#privacyProviders;
|
|
273
|
-
constructor(database, modules, resources, services, privacyExecutor, supportNotifications, presenceOwnership, presenceShardCount) {
|
|
275
|
+
constructor(database, modules, resources, services, privacyExecutor, supportNotifications, presenceOwnership, presenceShardCount, statusModules) {
|
|
274
276
|
this.database = database;
|
|
275
277
|
this.resources = resources;
|
|
276
278
|
this.supportNotifications = supportNotifications;
|
|
@@ -281,6 +283,7 @@ export class BotControlService {
|
|
|
281
283
|
.filter((module) => module.manifest.dashboard !== undefined)
|
|
282
284
|
.map(mapDashboardModule);
|
|
283
285
|
this.#availabilityModules = availabilityCatalogueDefinitions(modules);
|
|
286
|
+
this.#statusModules = statusCatalogueDefinitions(statusModules ?? modules);
|
|
284
287
|
this.#configurations = services.get(HELYX_SERVICE_NAMES.configuration);
|
|
285
288
|
this.#installations = services.get(HELYX_SERVICE_NAMES.installations);
|
|
286
289
|
this.#permissions = services.get(HELYX_SERVICE_NAMES.permissions);
|
|
@@ -1037,7 +1040,7 @@ export class BotControlService {
|
|
|
1037
1040
|
return module;
|
|
1038
1041
|
}
|
|
1039
1042
|
listAvailabilityModules() {
|
|
1040
|
-
return availabilityCatalogue(this.#availabilityModules);
|
|
1043
|
+
return availabilityCatalogue(this.#availabilityModules, this.#statusModules);
|
|
1041
1044
|
}
|
|
1042
1045
|
async #requireModuleAvailable(moduleIdValue) {
|
|
1043
1046
|
const availability = moduleAvailabilityService(this.#services);
|
|
@@ -1743,12 +1746,6 @@ class BotControlNotFoundError extends Error {
|
|
|
1743
1746
|
this.name = "BotControlNotFoundError";
|
|
1744
1747
|
}
|
|
1745
1748
|
}
|
|
1746
|
-
class BotControlForbiddenError extends Error {
|
|
1747
|
-
constructor(message = "You no longer have permission to perform this action") {
|
|
1748
|
-
super(message);
|
|
1749
|
-
this.name = "BotControlForbiddenError";
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
1749
|
function digest(value) {
|
|
1753
1750
|
return createHash("sha256").update(value).digest();
|
|
1754
1751
|
}
|
|
@@ -1905,9 +1902,4 @@ function mapDashboardModule(module) {
|
|
|
1905
1902
|
: undefined,
|
|
1906
1903
|
};
|
|
1907
1904
|
}
|
|
1908
|
-
function assertPermissionMutationAllowed(decision) {
|
|
1909
|
-
if (decision.allowed)
|
|
1910
|
-
return;
|
|
1911
|
-
throw new BotControlForbiddenError(permissionMutationDenialMessage(decision.reason));
|
|
1912
|
-
}
|
|
1913
1905
|
//# sourceMappingURL=control-server.js.map
|
package/dist/events.js
CHANGED
|
@@ -343,6 +343,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
343
343
|
const authorDisplayName = message.member?.displayName ??
|
|
344
344
|
message.author.globalName ??
|
|
345
345
|
message.author.username;
|
|
346
|
+
const mentionedUserIds = [...message.mentions.users.keys()].slice(0, 25);
|
|
346
347
|
let classifiedMessage;
|
|
347
348
|
for (const { moduleId, classificationRequested, contribution, } of guildMessageContributions) {
|
|
348
349
|
try {
|
|
@@ -362,6 +363,7 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
362
363
|
authorIsBot: false,
|
|
363
364
|
authorIsWebhook: false,
|
|
364
365
|
authorIsSystem: false,
|
|
366
|
+
mentionedUserIds,
|
|
365
367
|
get classification() {
|
|
366
368
|
if (!classificationRequested || !messageClassificationAvailable)
|
|
367
369
|
return unavailableGuildMessageClassification;
|
|
@@ -523,7 +525,9 @@ export function installModuleEvents(client, modules, logger, services) {
|
|
|
523
525
|
}
|
|
524
526
|
await contribution.execute({
|
|
525
527
|
serverId: member.guild.id,
|
|
528
|
+
serverName: member.guild.name,
|
|
526
529
|
memberId: member.id,
|
|
530
|
+
memberDisplayName: member.displayName,
|
|
527
531
|
memberIsBot: member.user.bot,
|
|
528
532
|
accountCreatedAt: member.user.createdAt,
|
|
529
533
|
joinedAt: member.joinedAt,
|
package/dist/index.js
CHANGED
|
@@ -403,7 +403,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
403
403
|
guildId: environment.DISCORD_INTERNAL_GUILD_ID,
|
|
404
404
|
channelId: environment.HELYX_SUPPORT_STAFF_CHANNEL_ID,
|
|
405
405
|
}
|
|
406
|
-
: undefined);
|
|
406
|
+
: undefined, undefined, undefined, featureModules);
|
|
407
407
|
cloudConnector.setControlHandler(controlService);
|
|
408
408
|
let controlServer;
|
|
409
409
|
let shuttingDown = false;
|
package/dist/interactions.js
CHANGED
|
@@ -1003,7 +1003,7 @@ export function normalizeDiscordPermission(permission) {
|
|
|
1003
1003
|
return permission.replaceAll(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
|
|
1004
1004
|
}
|
|
1005
1005
|
export function toReplyOptions(response) {
|
|
1006
|
-
const allowedMentions =
|
|
1006
|
+
const allowedMentions = toAllowedMentions(response.allowedUserMentionIds, response.allowedRoleMentionIds);
|
|
1007
1007
|
const selectRows = toSelectRows(response);
|
|
1008
1008
|
if (response.componentsV2) {
|
|
1009
1009
|
if (response.content !== undefined) {
|
|
@@ -1154,15 +1154,25 @@ export function toUpdateOptions(response) {
|
|
|
1154
1154
|
ephemeral: false,
|
|
1155
1155
|
});
|
|
1156
1156
|
}
|
|
1157
|
-
function
|
|
1158
|
-
if (userIds === undefined)
|
|
1157
|
+
function toAllowedMentions(userIds, roleIds) {
|
|
1158
|
+
if (userIds === undefined && roleIds === undefined)
|
|
1159
1159
|
return undefined;
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1160
|
+
validateAllowedMentionIds(userIds, "user");
|
|
1161
|
+
validateAllowedMentionIds(roleIds, "role");
|
|
1162
|
+
return {
|
|
1163
|
+
parse: [],
|
|
1164
|
+
...(userIds === undefined ? {} : { users: [...userIds] }),
|
|
1165
|
+
...(roleIds === undefined ? {} : { roles: [...roleIds] }),
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function validateAllowedMentionIds(ids, label) {
|
|
1169
|
+
if (ids === undefined)
|
|
1170
|
+
return;
|
|
1171
|
+
if (ids.length > 100 ||
|
|
1172
|
+
new Set(ids).size !== ids.length ||
|
|
1173
|
+
ids.some((id) => !/^\d{17,20}$/u.test(id))) {
|
|
1174
|
+
throw new Error(`Allowed ${label} mentions must be unique Discord snowflakes`);
|
|
1164
1175
|
}
|
|
1165
|
-
return { parse: [], users: [...userIds] };
|
|
1166
1176
|
}
|
|
1167
1177
|
function toButtonEmoji(emojiIdentifier) {
|
|
1168
1178
|
const custom = /^([A-Za-z0-9_]{2,32}):(\d{17,20})$/u.exec(emojiIdentifier);
|
|
@@ -8,10 +8,13 @@ export interface AvailabilityModuleDefinition {
|
|
|
8
8
|
export interface AvailabilityModuleCatalogue {
|
|
9
9
|
catalogueDigest: string;
|
|
10
10
|
modules: AvailabilityModuleDefinition[];
|
|
11
|
+
statusCatalogueDigest?: string;
|
|
12
|
+
statusModules?: AvailabilityModuleDefinition[];
|
|
11
13
|
}
|
|
12
14
|
export declare function availabilityCatalogueDefinitions(modules: readonly ModuleDefinition[]): AvailabilityModuleDefinition[];
|
|
13
|
-
export declare function
|
|
14
|
-
export declare function
|
|
15
|
+
export declare function statusCatalogueDefinitions(modules: readonly ModuleDefinition[]): AvailabilityModuleDefinition[];
|
|
16
|
+
export declare function availabilityCatalogueSnapshot(definitions: readonly AvailabilityModuleDefinition[], statusDefinitions?: readonly AvailabilityModuleDefinition[]): AvailabilityModuleCatalogue;
|
|
17
|
+
export declare function availabilityCatalogue(definitions: readonly AvailabilityModuleDefinition[], statusDefinitions?: readonly AvailabilityModuleDefinition[]): Promise<AvailabilityModuleCatalogue>;
|
|
15
18
|
export declare function moduleAvailabilityService(services: ServiceAccess): ModuleAvailabilityService | undefined;
|
|
16
19
|
export declare function moduleAvailabilityStates(services: ServiceAccess, moduleIds: readonly string[]): Promise<ModuleAvailabilityState[]>;
|
|
17
20
|
export declare function dashboardModuleStatus(globallyDisabled: ReadonlySet<string>, moduleId: string, installation: {
|
|
@@ -12,17 +12,31 @@ export function availabilityCatalogueDefinitions(modules) {
|
|
|
12
12
|
}))
|
|
13
13
|
.sort((left, right) => left.name.localeCompare(right.name, "en-GB"));
|
|
14
14
|
}
|
|
15
|
-
export function
|
|
15
|
+
export function statusCatalogueDefinitions(modules) {
|
|
16
|
+
return modules
|
|
17
|
+
.map(({ manifest }) => ({
|
|
18
|
+
id: manifest.id,
|
|
19
|
+
name: manifest.name,
|
|
20
|
+
version: manifest.version,
|
|
21
|
+
releaseStatus: manifest.releaseStatus,
|
|
22
|
+
}))
|
|
23
|
+
.sort((left, right) => left.name.localeCompare(right.name, "en-GB"));
|
|
24
|
+
}
|
|
25
|
+
function catalogueDigest(definitions) {
|
|
26
|
+
return createHash("sha256").update(JSON.stringify(definitions)).digest("hex");
|
|
27
|
+
}
|
|
28
|
+
export function availabilityCatalogueSnapshot(definitions, statusDefinitions = definitions) {
|
|
16
29
|
const modules = definitions.map((module) => ({ ...module }));
|
|
30
|
+
const statusModules = statusDefinitions.map((module) => ({ ...module }));
|
|
17
31
|
return {
|
|
18
|
-
catalogueDigest:
|
|
19
|
-
.update(JSON.stringify(modules))
|
|
20
|
-
.digest("hex"),
|
|
32
|
+
catalogueDigest: catalogueDigest(modules),
|
|
21
33
|
modules,
|
|
34
|
+
statusCatalogueDigest: catalogueDigest(statusModules),
|
|
35
|
+
statusModules,
|
|
22
36
|
};
|
|
23
37
|
}
|
|
24
|
-
export function availabilityCatalogue(definitions) {
|
|
25
|
-
return Promise.resolve(availabilityCatalogueSnapshot(definitions));
|
|
38
|
+
export function availabilityCatalogue(definitions, statusDefinitions = definitions) {
|
|
39
|
+
return Promise.resolve(availabilityCatalogueSnapshot(definitions, statusDefinitions));
|
|
26
40
|
}
|
|
27
41
|
export function moduleAvailabilityService(services) {
|
|
28
42
|
if (!services.has(HELYX_SERVICE_NAMES.moduleAvailability))
|
package/dist/shard-supervisor.js
CHANGED
|
@@ -25,7 +25,7 @@ import { createSupportModule } from "./support-module.js";
|
|
|
25
25
|
import { createOAuthModule } from "./oauth-module.js";
|
|
26
26
|
import { createPrivacyModule } from "./privacy-module.js";
|
|
27
27
|
import { discordRestOptions } from "./discord-rest.js";
|
|
28
|
-
import { availabilityCatalogue, availabilityCatalogueDefinitions, } from "./module-availability-control.js";
|
|
28
|
+
import { availabilityCatalogue, availabilityCatalogueDefinitions, statusCatalogueDefinitions, } from "./module-availability-control.js";
|
|
29
29
|
import { createScheduledTaskRunner, schedulerOperationalHealth, } from "./scheduled-task-runner.js";
|
|
30
30
|
import { numericWriteBehindRegistrations } from "./numeric-write-behind.js";
|
|
31
31
|
import { PostgresTriviaDataProvider } from "./trivia-data-provider.js";
|
|
@@ -257,8 +257,9 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
257
257
|
];
|
|
258
258
|
commandModules = modules;
|
|
259
259
|
const availabilityModules = availabilityCatalogueDefinitions(modules);
|
|
260
|
+
const statusModules = statusCatalogueDefinitions(featureModules);
|
|
260
261
|
const availabilityService = {
|
|
261
|
-
listAvailabilityModules: () => availabilityCatalogue(availabilityModules),
|
|
262
|
+
listAvailabilityModules: () => availabilityCatalogue(availabilityModules, statusModules),
|
|
262
263
|
};
|
|
263
264
|
const initialiseLeader = async () => {
|
|
264
265
|
await runLeaderStartup(database, environment, modulePackages, modules, logger);
|
package/dist/shard-worker.js
CHANGED
|
@@ -259,7 +259,7 @@ export async function startShardWorker(processEnvironment = process.env, identit
|
|
|
259
259
|
guildId: environment.DISCORD_INTERNAL_GUILD_ID,
|
|
260
260
|
channelId: environment.HELYX_SUPPORT_STAFF_CHANNEL_ID,
|
|
261
261
|
}
|
|
262
|
-
: undefined, shardOwnership, identity.shardCount);
|
|
262
|
+
: undefined, shardOwnership, identity.shardCount, featureModules);
|
|
263
263
|
process.on("message", (raw) => {
|
|
264
264
|
const parsed = supervisorMessageSchema.safeParse(raw);
|
|
265
265
|
if (!parsed.success)
|