@helyx/bot 1.1.0 → 1.2.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +16 -1
  3. package/dist/auto-moderation-scanner-registry.d.ts +27 -0
  4. package/dist/auto-moderation-scanner-registry.js +172 -0
  5. package/dist/cloud-connector.js +3 -0
  6. package/dist/cloud-control-dispatcher.js +32 -0
  7. package/dist/command-path-contract.js +1 -0
  8. package/dist/control-permissions.d.ts +2 -2
  9. package/dist/control-permissions.js +4 -2
  10. package/dist/control-server.d.ts +72 -16
  11. package/dist/control-server.js +150 -250
  12. package/dist/dashboard-action-control.d.ts +25 -0
  13. package/dist/dashboard-action-control.js +73 -0
  14. package/dist/dashboard-action-routes.d.ts +9 -0
  15. package/dist/dashboard-action-routes.js +36 -0
  16. package/dist/dashboard-feature-control.d.ts +37 -0
  17. package/dist/dashboard-feature-control.js +206 -0
  18. package/dist/dashboard-module-projection.d.ts +9 -0
  19. package/dist/dashboard-module-projection.js +15 -0
  20. package/dist/dashboard-preview-import-routes.d.ts +6 -0
  21. package/dist/dashboard-preview-import-routes.js +121 -0
  22. package/dist/discord-analytics.d.ts +3 -0
  23. package/dist/discord-analytics.js +49 -5
  24. package/dist/discord-auto-moderation-service.d.ts +21 -0
  25. package/dist/discord-auto-moderation-service.js +425 -0
  26. package/dist/discord-guild-health.d.ts +2 -2
  27. package/dist/discord-member-enforcement-service.d.ts +26 -0
  28. package/dist/discord-member-enforcement-service.js +590 -0
  29. package/dist/discord-message-enforcement-service.d.ts +9 -0
  30. package/dist/discord-message-enforcement-service.js +102 -0
  31. package/dist/discord-violation-thread-service.d.ts +33 -0
  32. package/dist/discord-violation-thread-service.js +789 -0
  33. package/dist/ecko-evidence-receiver.d.ts +32 -0
  34. package/dist/ecko-evidence-receiver.js +173 -0
  35. package/dist/ecko-transport-auth.d.ts +19 -0
  36. package/dist/ecko-transport-auth.js +80 -0
  37. package/dist/ecko-transport-routes.d.ts +19 -0
  38. package/dist/ecko-transport-routes.js +130 -0
  39. package/dist/embedded-auto-moderation-pool.d.ts +46 -0
  40. package/dist/embedded-auto-moderation-pool.js +324 -0
  41. package/dist/embedded-auto-moderation-scanner.d.ts +34 -0
  42. package/dist/embedded-auto-moderation-scanner.js +263 -0
  43. package/dist/embedded-auto-moderation-worker-runtime.d.ts +37 -0
  44. package/dist/embedded-auto-moderation-worker-runtime.js +37 -0
  45. package/dist/embedded-auto-moderation-worker.d.ts +2 -0
  46. package/dist/embedded-auto-moderation-worker.js +13 -0
  47. package/dist/events.d.ts +5 -1
  48. package/dist/events.js +243 -4
  49. package/dist/guilds.d.ts +2 -2
  50. package/dist/guilds.js +14 -8
  51. package/dist/hosted-server-moderation.d.ts +16 -5
  52. package/dist/hosted-server-moderation.js +32 -4
  53. package/dist/index.d.ts +1 -1
  54. package/dist/index.js +84 -14
  55. package/dist/moderation-action-registry.d.ts +11 -0
  56. package/dist/moderation-action-registry.js +375 -0
  57. package/dist/moderation-case-disassociation.d.ts +13 -0
  58. package/dist/moderation-case-disassociation.js +83 -0
  59. package/dist/moderation-platform-services.d.ts +27 -0
  60. package/dist/moderation-platform-services.js +47 -0
  61. package/dist/modules.d.ts +6 -0
  62. package/dist/modules.js +65 -3
  63. package/dist/privileged-message-metadata-cache.d.ts +35 -0
  64. package/dist/privileged-message-metadata-cache.js +70 -0
  65. package/dist/scheduled-task-runner.js +24 -1
  66. package/dist/shard-contracts.d.ts +151 -1
  67. package/dist/shard-contracts.js +142 -1
  68. package/dist/shard-coordinator.d.ts +1 -1
  69. package/dist/shard-ecko-scanner-registry.d.ts +38 -0
  70. package/dist/shard-ecko-scanner-registry.js +96 -0
  71. package/dist/shard-router.d.ts +23 -0
  72. package/dist/shard-router.js +69 -5
  73. package/dist/shard-supervisor.d.ts +11 -0
  74. package/dist/shard-supervisor.js +67 -4
  75. package/dist/shard-worker.d.ts +15 -1
  76. package/dist/shard-worker.js +95 -17
  77. package/dist/transient-violation-evidence.d.ts +39 -0
  78. package/dist/transient-violation-evidence.js +132 -0
  79. package/documentation/README.md +2 -0
  80. package/documentation/examples/.env.example +12 -0
  81. package/documentation/guides/self-hosting.md +17 -1
  82. package/documentation/modules/auto-moderation.md +51 -0
  83. package/documentation/modules/moderation.md +42 -0
  84. package/documentation/reference/environment.md +11 -0
  85. package/package.json +17 -5
@@ -0,0 +1,425 @@
1
+ import { AutoModerationActionType, AutoModerationRuleEventType, AutoModerationRuleKeywordPresetType, AutoModerationRuleTriggerType, DiscordAPIError, PermissionFlagsBits, } from "discord.js";
2
+ const SNOWFLAKE_PATTERN = /^\d{17,20}$/u;
3
+ const OPERATION_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u;
4
+ const MODULE_ID_PATTERN = /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)+$/u;
5
+ const MAX_RULE_NAME_CHARACTERS = 100;
6
+ const MAX_KEYWORDS = 1_000;
7
+ const MAX_KEYWORD_CHARACTERS = 60;
8
+ const MAX_REGEX_PATTERNS = 10;
9
+ const MAX_REGEX_CHARACTERS = 260;
10
+ const MAX_KEYWORD_ALLOW_LIST = 100;
11
+ const MAX_PRESET_ALLOW_LIST = 1_000;
12
+ const MAX_EXEMPT_ROLES = 20;
13
+ const MAX_EXEMPT_CHANNELS = 50;
14
+ const MAX_ACTIONS = 3;
15
+ const MAX_CUSTOM_MESSAGE_CHARACTERS = 150;
16
+ const MAX_TIMEOUT_SECONDS = 2_419_200;
17
+ const MAX_RULES_BY_TRIGGER = Object.freeze({
18
+ keyword: 6,
19
+ spam: 1,
20
+ keyword_preset: 1,
21
+ mention_spam: 1,
22
+ });
23
+ /** Discord REST adapter scoped to guilds owned by this client/shard. */
24
+ export class DiscordAutoModerationAdapter {
25
+ client;
26
+ constructor(client) {
27
+ this.client = client;
28
+ }
29
+ async list(input) {
30
+ const guild = this.#ownedGuild(input.guildId);
31
+ if (!guild)
32
+ return [];
33
+ this.#requireManageGuild(guild);
34
+ const rules = await guild.autoModerationRules.fetch();
35
+ return [...rules.values()].map(toRuleSnapshot);
36
+ }
37
+ async read(input) {
38
+ validateSnowflake(input.ruleId, "ruleId");
39
+ const guild = this.#ownedGuild(input.guildId);
40
+ if (!guild)
41
+ return null;
42
+ this.#requireManageGuild(guild);
43
+ try {
44
+ return toRuleSnapshot(await guild.autoModerationRules.fetch(input.ruleId));
45
+ }
46
+ catch (error) {
47
+ if (isDiscordStatus(error, 404))
48
+ return null;
49
+ throw error;
50
+ }
51
+ }
52
+ async create(input) {
53
+ validateMutationMetadata(input);
54
+ validateRule(input.rule);
55
+ const guild = this.#ownedGuild(input.guildId);
56
+ if (!guild)
57
+ return failed("guild_not_owned");
58
+ const permissionFailure = this.#permissionFailure(guild, input.rule);
59
+ if (permissionFailure)
60
+ return permissionFailure;
61
+ const rules = await guild.autoModerationRules.fetch().catch(() => null);
62
+ if (!rules)
63
+ return failed("rule_inspection_failed");
64
+ const matching = [...rules.values()].find((rule) => rule.name === input.rule.name &&
65
+ sameRule(toRuleSnapshot(rule), input.rule));
66
+ if (matching)
67
+ return { outcome: "already_applied", rule: toRuleSnapshot(matching) };
68
+ const triggerCount = [...rules.values()].filter((rule) => triggerType(rule.triggerType) === input.rule.trigger.type).length;
69
+ if (triggerCount >= MAX_RULES_BY_TRIGGER[input.rule.trigger.type])
70
+ return { outcome: "capacity_exceeded", safeCode: "trigger_capacity" };
71
+ if (!(await safelyRevalidate(() => input.revalidate())))
72
+ return failed("revalidation_rejected");
73
+ try {
74
+ const created = await guild.autoModerationRules.create(toCreateOptions(input.rule, auditReason(input)));
75
+ return { outcome: "succeeded", rule: toRuleSnapshot(created) };
76
+ }
77
+ catch (error) {
78
+ return mutationFailure(error);
79
+ }
80
+ }
81
+ async update(input) {
82
+ validateMutationMetadata(input);
83
+ validateSnowflake(input.ruleId, "ruleId");
84
+ validateRule(input.rule);
85
+ const guild = this.#ownedGuild(input.guildId);
86
+ if (!guild)
87
+ return failed("guild_not_owned");
88
+ const permissionFailure = this.#permissionFailure(guild, input.rule);
89
+ if (permissionFailure)
90
+ return permissionFailure;
91
+ let existing;
92
+ try {
93
+ existing = await guild.autoModerationRules.fetch(input.ruleId);
94
+ }
95
+ catch (error) {
96
+ return isDiscordStatus(error, 404)
97
+ ? { outcome: "already_absent" }
98
+ : mutationFailure(error);
99
+ }
100
+ if (triggerType(existing.triggerType) !== input.rule.trigger.type)
101
+ return { outcome: "invalid_rule", safeCode: "trigger_type_immutable" };
102
+ if (sameRule(toRuleSnapshot(existing), input.rule))
103
+ return { outcome: "already_applied", rule: toRuleSnapshot(existing) };
104
+ if (!(await safelyRevalidate(() => input.revalidate())))
105
+ return failed("revalidation_rejected");
106
+ try {
107
+ const { triggerType: _triggerType, ...editOptions } = toCreateOptions(input.rule, auditReason(input));
108
+ void _triggerType;
109
+ const updated = await guild.autoModerationRules.edit(input.ruleId, editOptions);
110
+ return { outcome: "succeeded", rule: toRuleSnapshot(updated) };
111
+ }
112
+ catch (error) {
113
+ return mutationFailure(error);
114
+ }
115
+ }
116
+ async delete(input) {
117
+ validateMutationMetadata(input);
118
+ validateSnowflake(input.ruleId, "ruleId");
119
+ const guild = this.#ownedGuild(input.guildId);
120
+ if (!guild)
121
+ return failed("guild_not_owned");
122
+ if (!this.#hasPermission(guild, PermissionFlagsBits.ManageGuild))
123
+ return { outcome: "missing_permission", safeCode: "manage_guild" };
124
+ const existing = await this.read({
125
+ guildId: input.guildId,
126
+ ruleId: input.ruleId,
127
+ }).catch(() => null);
128
+ if (!existing)
129
+ return { outcome: "already_absent" };
130
+ if (!(await safelyRevalidate(() => input.revalidate())))
131
+ return failed("revalidation_rejected");
132
+ try {
133
+ await guild.autoModerationRules.delete(input.ruleId, auditReason(input));
134
+ return { outcome: "succeeded", rule: null };
135
+ }
136
+ catch (error) {
137
+ return isDiscordStatus(error, 404)
138
+ ? { outcome: "already_absent" }
139
+ : mutationFailure(error);
140
+ }
141
+ }
142
+ #ownedGuild(guildId) {
143
+ validateSnowflake(guildId, "guildId");
144
+ return this.client.guilds.cache.get(guildId) ?? null;
145
+ }
146
+ #hasPermission(guild, permission) {
147
+ return Boolean(guild.members.me?.permissions.has(permission));
148
+ }
149
+ #requireManageGuild(guild) {
150
+ if (!this.#hasPermission(guild, PermissionFlagsBits.ManageGuild))
151
+ throw new Error("Discord Auto Moderation inspection is unavailable");
152
+ }
153
+ #permissionFailure(guild, rule) {
154
+ if (!this.#hasPermission(guild, PermissionFlagsBits.ManageGuild))
155
+ return { outcome: "missing_permission", safeCode: "manage_guild" };
156
+ if (rule.actions.some(({ type }) => type === "timeout") &&
157
+ !this.#hasPermission(guild, PermissionFlagsBits.ModerateMembers))
158
+ return { outcome: "missing_permission", safeCode: "moderate_members" };
159
+ return null;
160
+ }
161
+ }
162
+ export function toRuleSnapshot(rule) {
163
+ return {
164
+ guildId: rule.guild.id,
165
+ ruleId: rule.id,
166
+ creatorUserId: rule.creatorId,
167
+ name: rule.name,
168
+ enabled: rule.enabled,
169
+ eventType: "message_send",
170
+ trigger: fromDiscordTrigger(rule),
171
+ actions: rule.actions.map(fromDiscordAction),
172
+ exemptRoleIds: [...rule.exemptRoles.keys()].sort(),
173
+ exemptChannelIds: [...rule.exemptChannels.keys()].sort(),
174
+ };
175
+ }
176
+ export function fromDiscordAction(action) {
177
+ if (action.type === AutoModerationActionType.BlockMessage)
178
+ return {
179
+ type: "block_message",
180
+ ...(action.metadata.customMessage
181
+ ? { customMessage: action.metadata.customMessage }
182
+ : {}),
183
+ };
184
+ if (action.type === AutoModerationActionType.SendAlertMessage &&
185
+ action.metadata.channelId)
186
+ return { type: "send_alert_message", channelId: action.metadata.channelId };
187
+ if (action.type === AutoModerationActionType.Timeout &&
188
+ action.metadata.durationSeconds)
189
+ return {
190
+ type: "timeout",
191
+ durationSeconds: action.metadata.durationSeconds,
192
+ };
193
+ throw new Error("Discord Auto Moderation action is unsupported");
194
+ }
195
+ function fromDiscordTrigger(rule) {
196
+ const metadata = rule.triggerMetadata;
197
+ if (rule.triggerType === AutoModerationRuleTriggerType.Keyword)
198
+ return {
199
+ type: "keyword",
200
+ keywordFilter: [...metadata.keywordFilter],
201
+ regexPatterns: [...metadata.regexPatterns],
202
+ allowList: [...metadata.allowList],
203
+ };
204
+ if (rule.triggerType === AutoModerationRuleTriggerType.Spam)
205
+ return { type: "spam" };
206
+ if (rule.triggerType === AutoModerationRuleTriggerType.KeywordPreset)
207
+ return {
208
+ type: "keyword_preset",
209
+ presets: metadata.presets.map(fromPreset),
210
+ allowList: [...metadata.allowList],
211
+ };
212
+ if (rule.triggerType === AutoModerationRuleTriggerType.MentionSpam)
213
+ return {
214
+ type: "mention_spam",
215
+ mentionTotalLimit: metadata.mentionTotalLimit ?? 0,
216
+ mentionRaidProtectionEnabled: metadata.mentionRaidProtectionEnabled,
217
+ };
218
+ throw new Error("Discord Auto Moderation trigger is unsupported");
219
+ }
220
+ function toCreateOptions(rule, reason) {
221
+ const triggerMetadata = toDiscordTriggerMetadata(rule.trigger);
222
+ return {
223
+ name: rule.name,
224
+ eventType: AutoModerationRuleEventType.MessageSend,
225
+ triggerType: toDiscordTriggerType(rule.trigger.type),
226
+ ...(triggerMetadata ? { triggerMetadata } : {}),
227
+ actions: rule.actions.map(toDiscordAction),
228
+ enabled: rule.enabled,
229
+ exemptRoles: rule.exemptRoleIds,
230
+ exemptChannels: rule.exemptChannelIds,
231
+ reason,
232
+ };
233
+ }
234
+ function toDiscordTriggerMetadata(trigger) {
235
+ if (trigger.type === "keyword")
236
+ return {
237
+ keywordFilter: trigger.keywordFilter,
238
+ regexPatterns: trigger.regexPatterns,
239
+ allowList: trigger.allowList,
240
+ };
241
+ if (trigger.type === "keyword_preset")
242
+ return {
243
+ presets: trigger.presets.map(toPreset),
244
+ allowList: trigger.allowList,
245
+ };
246
+ if (trigger.type === "mention_spam")
247
+ return {
248
+ mentionTotalLimit: trigger.mentionTotalLimit,
249
+ mentionRaidProtectionEnabled: trigger.mentionRaidProtectionEnabled,
250
+ };
251
+ return undefined;
252
+ }
253
+ function toDiscordAction(action) {
254
+ if (action.type === "block_message")
255
+ return {
256
+ type: AutoModerationActionType.BlockMessage,
257
+ ...(action.customMessage
258
+ ? { metadata: { customMessage: action.customMessage } }
259
+ : {}),
260
+ };
261
+ if (action.type === "send_alert_message")
262
+ return {
263
+ type: AutoModerationActionType.SendAlertMessage,
264
+ metadata: { channel: action.channelId },
265
+ };
266
+ return {
267
+ type: AutoModerationActionType.Timeout,
268
+ metadata: { durationSeconds: action.durationSeconds },
269
+ };
270
+ }
271
+ function toDiscordTriggerType(type) {
272
+ if (type === "keyword")
273
+ return AutoModerationRuleTriggerType.Keyword;
274
+ if (type === "spam")
275
+ return AutoModerationRuleTriggerType.Spam;
276
+ if (type === "keyword_preset")
277
+ return AutoModerationRuleTriggerType.KeywordPreset;
278
+ return AutoModerationRuleTriggerType.MentionSpam;
279
+ }
280
+ function triggerType(type) {
281
+ if (type === AutoModerationRuleTriggerType.Keyword)
282
+ return "keyword";
283
+ if (type === AutoModerationRuleTriggerType.Spam)
284
+ return "spam";
285
+ if (type === AutoModerationRuleTriggerType.KeywordPreset)
286
+ return "keyword_preset";
287
+ if (type === AutoModerationRuleTriggerType.MentionSpam)
288
+ return "mention_spam";
289
+ throw new Error("Discord Auto Moderation trigger is unsupported");
290
+ }
291
+ function toPreset(preset) {
292
+ if (preset === "profanity")
293
+ return AutoModerationRuleKeywordPresetType.Profanity;
294
+ if (preset === "sexual_content")
295
+ return AutoModerationRuleKeywordPresetType.SexualContent;
296
+ return AutoModerationRuleKeywordPresetType.Slurs;
297
+ }
298
+ function fromPreset(preset) {
299
+ if (preset === AutoModerationRuleKeywordPresetType.Profanity)
300
+ return "profanity";
301
+ if (preset === AutoModerationRuleKeywordPresetType.SexualContent)
302
+ return "sexual_content";
303
+ if (preset === AutoModerationRuleKeywordPresetType.Slurs)
304
+ return "slurs";
305
+ throw new Error("Discord Auto Moderation preset is unsupported");
306
+ }
307
+ function validateRule(rule) {
308
+ if (rule.eventType !== "message_send" ||
309
+ rule.name.trim().length < 1 ||
310
+ rule.name.length > MAX_RULE_NAME_CHARACTERS ||
311
+ rule.actions.length < 1 ||
312
+ rule.actions.length > MAX_ACTIONS ||
313
+ rule.exemptRoleIds.length > MAX_EXEMPT_ROLES ||
314
+ rule.exemptChannelIds.length > MAX_EXEMPT_CHANNELS ||
315
+ new Set(rule.exemptRoleIds).size !== rule.exemptRoleIds.length ||
316
+ new Set(rule.exemptChannelIds).size !== rule.exemptChannelIds.length)
317
+ throw new Error("Discord Auto Moderation rule is invalid");
318
+ rule.exemptRoleIds.forEach((id) => validateSnowflake(id, "exemptRoleId"));
319
+ rule.exemptChannelIds.forEach((id) => validateSnowflake(id, "exemptChannelId"));
320
+ validateTrigger(rule.trigger);
321
+ validateActions(rule.actions, rule.trigger.type);
322
+ }
323
+ function validateTrigger(trigger) {
324
+ if (trigger.type === "keyword") {
325
+ validateStrings(trigger.keywordFilter, MAX_KEYWORDS, MAX_KEYWORD_CHARACTERS);
326
+ validateStrings(trigger.regexPatterns, MAX_REGEX_PATTERNS, MAX_REGEX_CHARACTERS);
327
+ validateStrings(trigger.allowList, MAX_KEYWORD_ALLOW_LIST, MAX_KEYWORD_CHARACTERS);
328
+ return;
329
+ }
330
+ if (trigger.type === "keyword_preset") {
331
+ if (trigger.presets.length < 1 ||
332
+ trigger.presets.length > 3 ||
333
+ new Set(trigger.presets).size !== trigger.presets.length)
334
+ throw new Error("Discord Auto Moderation presets are invalid");
335
+ validateStrings(trigger.allowList, MAX_PRESET_ALLOW_LIST, MAX_KEYWORD_CHARACTERS);
336
+ return;
337
+ }
338
+ if (trigger.type === "mention_spam" &&
339
+ (!Number.isInteger(trigger.mentionTotalLimit) ||
340
+ trigger.mentionTotalLimit < 1 ||
341
+ trigger.mentionTotalLimit > 50))
342
+ throw new Error("Discord Auto Moderation mention limit is invalid");
343
+ }
344
+ function validateActions(actions, triggerType) {
345
+ if (new Set(actions.map(({ type }) => type)).size !== actions.length)
346
+ throw new Error("Discord Auto Moderation actions must be unique");
347
+ for (const action of actions) {
348
+ if (action.type === "block_message" &&
349
+ action.customMessage !== undefined &&
350
+ (action.customMessage.trim().length < 1 ||
351
+ action.customMessage.length > MAX_CUSTOM_MESSAGE_CHARACTERS))
352
+ throw new Error("Discord Auto Moderation block message is invalid");
353
+ if (action.type === "send_alert_message")
354
+ validateSnowflake(action.channelId, "alertChannelId");
355
+ if (action.type === "timeout" &&
356
+ (triggerType === "spam" ||
357
+ triggerType === "keyword_preset" ||
358
+ !Number.isInteger(action.durationSeconds) ||
359
+ action.durationSeconds < 1 ||
360
+ action.durationSeconds > MAX_TIMEOUT_SECONDS))
361
+ throw new Error("Discord Auto Moderation timeout is invalid");
362
+ }
363
+ }
364
+ function validateStrings(values, maximumItems, maximumCharacters) {
365
+ if (values.length > maximumItems ||
366
+ new Set(values).size !== values.length ||
367
+ values.some((value) => value.length < 1 || value.length > maximumCharacters))
368
+ throw new Error("Discord Auto Moderation trigger values are invalid");
369
+ }
370
+ function validateMutationMetadata(input) {
371
+ validateSnowflake(input.guildId, "guildId");
372
+ if (!MODULE_ID_PATTERN.test(input.callerModuleId) ||
373
+ !OPERATION_KEY_PATTERN.test(input.operationKey))
374
+ throw new Error("Discord Auto Moderation mutation metadata is invalid");
375
+ }
376
+ function validateSnowflake(value, name) {
377
+ if (!SNOWFLAKE_PATTERN.test(value))
378
+ throw new Error(`${name} is invalid`);
379
+ }
380
+ function sameRule(snapshot, definition) {
381
+ return (JSON.stringify({
382
+ name: snapshot.name,
383
+ enabled: snapshot.enabled,
384
+ eventType: snapshot.eventType,
385
+ trigger: snapshot.trigger,
386
+ actions: snapshot.actions,
387
+ exemptRoleIds: [...snapshot.exemptRoleIds].sort(),
388
+ exemptChannelIds: [...snapshot.exemptChannelIds].sort(),
389
+ }) ===
390
+ JSON.stringify({
391
+ ...definition,
392
+ exemptRoleIds: [...definition.exemptRoleIds].sort(),
393
+ exemptChannelIds: [...definition.exemptChannelIds].sort(),
394
+ }));
395
+ }
396
+ function auditReason(input) {
397
+ return `Helyx ${input.callerModuleId} ${input.operationKey}`.slice(0, 512);
398
+ }
399
+ async function safelyRevalidate(revalidate) {
400
+ try {
401
+ return await revalidate();
402
+ }
403
+ catch {
404
+ return false;
405
+ }
406
+ }
407
+ function mutationFailure(error) {
408
+ if (isDiscordStatus(error, 403))
409
+ return { outcome: "missing_permission", safeCode: "discord_forbidden" };
410
+ if (isDiscordStatus(error, 400))
411
+ return { outcome: "invalid_rule", safeCode: "discord_invalid_rule" };
412
+ if (error instanceof DiscordAPIError)
413
+ return {
414
+ outcome: "failed",
415
+ safeCode: `discord_${error.code}`.slice(0, 80),
416
+ };
417
+ return { outcome: "ambiguous", safeCode: "discord_transport_unknown" };
418
+ }
419
+ function failed(safeCode) {
420
+ return { outcome: "failed", safeCode };
421
+ }
422
+ function isDiscordStatus(error, status) {
423
+ return error instanceof DiscordAPIError && error.status === status;
424
+ }
425
+ //# sourceMappingURL=discord-auto-moderation-service.js.map
@@ -163,8 +163,8 @@ export declare class DiscordGuildHealthInspection implements DiscordGuildHealthI
163
163
  resourceValidationValid: boolean | null;
164
164
  activationValid: boolean | null;
165
165
  catalogueMatches: boolean;
166
- requiredCapabilities: readonly ("message-content" | "guild-members")[];
167
- missingCapabilities: ("message-content" | "guild-members")[];
166
+ requiredCapabilities: readonly ("guild-members" | "message-content")[];
167
+ missingCapabilities: ("guild-members" | "message-content")[];
168
168
  missingPermissions: string[];
169
169
  healthStatus: "healthy" | "degraded" | "unhealthy";
170
170
  healthMessage: string | null;
@@ -0,0 +1,26 @@
1
+ import type { BanStateInspection, MemberEnforcementInspection, MemberEnforcementService, ModerationMemberEnforcementService } from "@helyx/sdk";
2
+ import { type Client } from "discord.js";
3
+ /** Extended Moderation adapter, leaving the legacy enforcement class unchanged. */
4
+ export declare class DiscordModerationMemberEnforcementAdapter implements ModerationMemberEnforcementService {
5
+ #private;
6
+ private readonly client;
7
+ constructor(client: Client);
8
+ kick(input: Parameters<MemberEnforcementService["kick"]>[0]): ReturnType<MemberEnforcementService["kick"]>;
9
+ ban(input: Parameters<MemberEnforcementService["ban"]>[0]): ReturnType<MemberEnforcementService["ban"]>;
10
+ inspectMember(input: {
11
+ guildId: string;
12
+ memberUserId: string;
13
+ actorUserId?: string;
14
+ }): Promise<MemberEnforcementInspection>;
15
+ inspectBan(input: {
16
+ guildId: string;
17
+ userId: string;
18
+ }): Promise<BanStateInspection>;
19
+ kickMember(input: Parameters<ModerationMemberEnforcementService["kickMember"]>[0]): ReturnType<ModerationMemberEnforcementService["kickMember"]>;
20
+ banMember(input: Parameters<ModerationMemberEnforcementService["banMember"]>[0]): ReturnType<ModerationMemberEnforcementService["banMember"]>;
21
+ timeout(input: Parameters<ModerationMemberEnforcementService["timeout"]>[0]): ReturnType<ModerationMemberEnforcementService["timeout"]>;
22
+ removeTimeout(input: Parameters<ModerationMemberEnforcementService["removeTimeout"]>[0]): ReturnType<ModerationMemberEnforcementService["removeTimeout"]>;
23
+ unban(input: Parameters<ModerationMemberEnforcementService["unban"]>[0]): ReturnType<ModerationMemberEnforcementService["unban"]>;
24
+ removeConfiguredRoles(input: Parameters<ModerationMemberEnforcementService["removeConfiguredRoles"]>[0]): ReturnType<ModerationMemberEnforcementService["removeConfiguredRoles"]>;
25
+ }
26
+ //# sourceMappingURL=discord-member-enforcement-service.d.ts.map