@helyx/module-automod 1.0.1

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 (79) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +725 -0
  3. package/README.md +80 -0
  4. package/dist/activity-resource.d.ts +3 -0
  5. package/dist/activity-resource.js +114 -0
  6. package/dist/case-link-provider.d.ts +3 -0
  7. package/dist/case-link-provider.js +13 -0
  8. package/dist/configuration.d.ts +22 -0
  9. package/dist/configuration.js +85 -0
  10. package/dist/constants.d.ts +30 -0
  11. package/dist/constants.js +37 -0
  12. package/dist/contracts.d.ts +30 -0
  13. package/dist/contracts.js +51 -0
  14. package/dist/domain.d.ts +61 -0
  15. package/dist/domain.js +274 -0
  16. package/dist/engine/canonicalisation.d.ts +9 -0
  17. package/dist/engine/canonicalisation.js +150 -0
  18. package/dist/engine/compile.d.ts +4 -0
  19. package/dist/engine/compile.js +169 -0
  20. package/dist/engine/confidence.d.ts +13 -0
  21. package/dist/engine/confidence.js +57 -0
  22. package/dist/engine/configuration-cache.d.ts +29 -0
  23. package/dist/engine/configuration-cache.js +115 -0
  24. package/dist/engine/contracts.d.ts +82 -0
  25. package/dist/engine/contracts.js +25 -0
  26. package/dist/engine/index.d.ts +11 -0
  27. package/dist/engine/index.js +11 -0
  28. package/dist/engine/matcher.d.ts +3 -0
  29. package/dist/engine/matcher.js +206 -0
  30. package/dist/engine/observations.d.ts +45 -0
  31. package/dist/engine/observations.js +105 -0
  32. package/dist/engine/operation-id.d.ts +18 -0
  33. package/dist/engine/operation-id.js +24 -0
  34. package/dist/engine/similar-message-window.d.ts +68 -0
  35. package/dist/engine/similar-message-window.js +259 -0
  36. package/dist/engine/term-import.d.ts +15 -0
  37. package/dist/engine/term-import.js +43 -0
  38. package/dist/enhanced-configuration.d.ts +16 -0
  39. package/dist/enhanced-configuration.js +91 -0
  40. package/dist/events.d.ts +4 -0
  41. package/dist/events.js +371 -0
  42. package/dist/health.d.ts +9 -0
  43. package/dist/health.js +45 -0
  44. package/dist/index.d.ts +10 -0
  45. package/dist/index.js +141 -0
  46. package/dist/policy-provider.d.ts +3 -0
  47. package/dist/policy-provider.js +66 -0
  48. package/dist/publication-support.d.ts +14 -0
  49. package/dist/publication-support.js +69 -0
  50. package/dist/publication.d.ts +32 -0
  51. package/dist/publication.js +333 -0
  52. package/dist/receipt-statistics.d.ts +11 -0
  53. package/dist/receipt-statistics.js +58 -0
  54. package/dist/records.d.ts +299 -0
  55. package/dist/records.js +288 -0
  56. package/dist/repository-model.d.ts +54 -0
  57. package/dist/repository-model.js +132 -0
  58. package/dist/repository.d.ts +133 -0
  59. package/dist/repository.js +523 -0
  60. package/dist/resource-cursor.d.ts +5 -0
  61. package/dist/resource-cursor.js +45 -0
  62. package/dist/resource-presentation.d.ts +21 -0
  63. package/dist/resource-presentation.js +111 -0
  64. package/dist/resource-validation.d.ts +6 -0
  65. package/dist/resource-validation.js +80 -0
  66. package/dist/resources.d.ts +4 -0
  67. package/dist/resources.js +319 -0
  68. package/dist/scanner-configuration.d.ts +10 -0
  69. package/dist/scanner-configuration.js +146 -0
  70. package/dist/scanner-provider.d.ts +5 -0
  71. package/dist/scanner-provider.js +310 -0
  72. package/dist/scanner-result-validation.d.ts +3 -0
  73. package/dist/scanner-result-validation.js +61 -0
  74. package/dist/settings-preview.d.ts +6 -0
  75. package/dist/settings-preview.js +84 -0
  76. package/manifest.json +1601 -0
  77. package/migrations/0001_automod_standard.sql +114 -0
  78. package/migrations/0002_automod_enhanced_receipts.sql +40 -0
  79. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @helyx/module-automod
2
+
3
+ Auto Moderation lets authorised managers publish transparent Discord-native safety rules, review content-free activity receipts, and optionally connect confirmed offences to Moderation cases and repeat-offender actions.
4
+
5
+ ## Availability and deployment
6
+
7
+ Auto Moderation is a Live module distributed under HSAL-1.0. Standard mode supports Hosted and Self-Hosted Helyx installations, uses no third-party credentials, and does not require the Message Content privileged intent.
8
+
9
+ Package installation does not enable the module for a server. The deployment-neutral Enhanced matching engine is included through the deliberate `@helyx/module-automod/engine` export. Hosted Helyx connects it through Ecko for eligible Enhanced servers, while Self-Hosted Helyx runs the same scanner contract through the embedded adapter. Standard rules remain effective without either adapter.
10
+
11
+ ## What it does
12
+
13
+ - Keeps one logical Helyx keyword rule mapped to one Helyx-owned Discord Auto Moderation rule.
14
+ - Supports keyword/phrase/wildcard and Discord regex filters, one Discord preset rule, one generic-spam rule, and one mention-spam rule.
15
+ - Applies global and per-rule role/channel exclusions and detects external Discord edits or deletion as drift or missing state.
16
+ - Records bounded structural action receipts without matched words or message content.
17
+ - Supplies the current rule revision, exclusions, repeat window, and first/second/third action policy to the closed Moderation integration.
18
+ - Uses a durable enforcement claim before requesting an optional Moderation effect so native action events collapse into one offence.
19
+ - Compiles immutable, versioned Enhanced rule snapshots and deterministically evaluates exact, phrase, wildcard, reviewed normalisation and explicitly opted-in bounded fuzzy matching.
20
+ - Keeps custom regex on Discord's native Rust engine until an approved linear-time Node engine is available; it never falls back to JavaScript's backtracking regular-expression engine.
21
+ - Provides bounded process-memory configuration, edit-metadata and keyed similar-message caches that remain correct when restarted empty.
22
+
23
+ Auto Moderation does not provide staff commands, own moderation case numbers, or perform Discord member punishment directly. The optional Moderation module remains the only provider allowed to create a case and dispatch a Helyx warning, mute, kick, ban, demotion, or violation thread.
24
+
25
+ ## Commands and interactions
26
+
27
+ Auto Moderation has no slash commands or Discord components. Managers use the dashboard resources. Each operation is separately authorised through `automod.rules.read`, `automod.rules.manage`, `automod.rules.publish`, `automod.rules.reconcile`, `automod.activity.read`, or `automod.settings.manage`; all default to Administrators.
28
+
29
+ ## Dashboard configuration
30
+
31
+ The **Rules** resource uses the exact responsive columns **Rule name**, **Filter**, **Action**, **Moderation thread**, **Ignored roles**, and **Repeat offender**. One **Save** validates the complete native definition, fixed mute durations, high-impact repeat actions, combined exemptions, Discord capacity, and current publishing permission before creating or updating the active Discord rule. The module must be enabled to apply a rule. A 4,000-byte bounded import reviews comma/newline-separated terms without saving or applying them and without silent truncation.
32
+
33
+ Published mappings support Disable, Adopt current Discord values, Restore Helyx values, and confirmed cleanup of only the mapped Helyx-owned Discord rule. A missing rule is never recreated automatically. Active rules can be edited and saved directly; mapped filter types cannot be changed because Discord treats them as immutable. Drifted or missing mappings must be reconciled before ordinary Save can apply changes.
34
+
35
+ Settings contain Standard/Enhanced mode identity, Strict (90), Balanced (80), Sensitive (70) and Custom (60–95) confidence choices, optional per-category overrides, native rule-type configuration switches, a default Discord alert channel, global ignored roles/channels, and a 1–365 day repeat-offender window. These switches permit configuration of preset, generic spam and mention spam rules; they do not create a rule or import existing Discord rules. Unmanaged Discord rules remain untouched and still count towards Discord's capacity. Adopt current Discord values applies only to an existing Helyx-owned mapping. Exact, native-equivalent and reviewed high-confidence normalised matches do not depend on the slider. Fuzzy matching must also be enabled explicitly on the rule's bounded keyword group, and terms under four characters never enter fuzzy comparison.
36
+
37
+ The settings preview tests unsaved confidence values against fictional content and currently published rules in memory. It reports the selected rule, category, method, deterministic score, effective threshold and native coverage without returning the matched term or storing the fictional content. Rule imports accept CRLF/LF and comma-separated lists, normalise duplicates, and return bounded row-level errors for every rejected item instead of silently truncating input.
38
+
39
+ The **Activity** resource is read-only and lists bounded structural receipts with their rule/action/outcome and optional Moderation case ID. It never shows a matched term or message body.
40
+
41
+ ## Discord requirements
42
+
43
+ Helyx needs **Manage Server** to inspect, create, update, disable, or delete its owned Discord Auto Moderation rules. A Discord-native timeout additionally needs **Moderate Members**. Auto Moderation consumes Auto Moderation rule create/update/delete and action-execution Gateway events through the core adapter. Those events use the code-level `AUTO_MODERATION_CONFIGURATION` and `AUTO_MODERATION_EXECUTION` intents and require no separate Developer Portal switch.
44
+
45
+ A native rule must block a message or send an alert; timeout cannot be its only action. Discord alerts may include triggering content in the selected Discord-owned channel, so publication displays that disclosure. The Helyx adapter removes content and matched fields before module code, persistence, Logging, Audit, metrics, or traces.
46
+
47
+ **Block message** also removes posted Enhanced matches after detection, independently of the selected offence action. Native blocking happens before posting; Enhanced removal is asynchronous. Unconfirmed removal or thread delivery is surfaced through safe Activity codes while the primary action result remains authoritative.
48
+
49
+ ## Data, privacy and retention
50
+
51
+ | Record category | Retained data |
52
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
53
+ | Rules | Logical/server identity, category, canonical rule document, owned Discord mapping/fingerprint, configuration revision, nullable creator/updater IDs, revision, and timestamps. |
54
+ | Action receipts | Structural rule/action, nullable subject/channel/message/alert IDs, claim/case links, safe outcome, and timestamps. |
55
+ | Enforcement claims | Stable claim fingerprint, nullable subject/case identity, policy generation, repeat ordinal, selected action, safe outcome, and timestamps. |
56
+ | Daily counts | Anonymous server/day/source/action counts and last observation time. |
57
+
58
+ Matched content, matched keywords, message bodies, attachments, embeds, and transient evidence are never written by this module. Native action receipts expire after 90 days. Settled anonymous claim/count history is bounded to 365 days; unresolved claims linked to an active case follow the case safety lifecycle. The versioned privacy provider covers creator, updater, detected-member, and enforced-member associations even while the module is disabled. Erasure detaches nullable user identities while declared rule bodies and active safety records follow manual review.
59
+
60
+ ## Operations and failure behavior
61
+
62
+ Saving a rule writes Discord first and attaches the mapping only after success. If persistence fails or the local revision changed, Helyx compensates a new Discord rule by deleting it, or restores the previous owned definition after an update where safe; compensation never overwrites a newer mapping. An unsuccessful creation retains an internal unpublished rule for recovery: open its existing Rules row before retrying Save. Ambiguous Discord results remain reviewable rather than being retried blindly, and a differing unconfirmed Discord rule with the same ownership marker blocks another creation.
63
+
64
+ Deleting an unmapped unpublished rule also requires successful Discord inspection. A native rule with the same ownership marker blocks record deletion so an unconfirmed creation cannot be orphaned.
65
+
66
+ External edits mark a rule drifted; external deletion marks it missing. Module disablement disables mapped owned rules without deleting mappings or definitions. Re-enablement revalidates and synchronises only rules whose logical status is Published. Uninstall leaves owned Discord rules disabled unless an authorised manager separately confirms cleanup.
67
+
68
+ The module is unhealthy while stopped or when required records, configuration, installation, Discord Auto Moderation, or authorization services are unavailable. Moderation and Logging are optional: their absence does not stop native Discord enforcement or structural receipt storage. The pure Enhanced engine performs no Discord, database, network, environment, clock or logging access; adapters supply immutable snapshots, message observations and explicit time values.
69
+
70
+ Before release, run formatting, lint, type checks, focused tests, module contracts, migration integration, packed lifecycle checks, and the repository release dry run. Live Discord behaviour remains unclaimed until the credentialed smoke matrix passes.
71
+
72
+ ## Documentation and support
73
+
74
+ - [Auto Moderation overview](https://docs.helyx.gg/modules/auto-moderation/)
75
+ - [Auto Moderation permissions](https://docs.helyx.gg/modules/auto-moderation/permissions/)
76
+ - [Auto Moderation failure recovery](https://docs.helyx.gg/modules/auto-moderation/failure-modes/)
77
+ - [Auto Moderation FAQ](https://docs.helyx.gg/modules/auto-moderation/faq/)
78
+ - [Report an issue](https://github.com/ZyC0R3/Helyx/issues)
79
+
80
+ Publication, licensing, release-state promotion, push, deployment, and production changes require explicit owner approval.
@@ -0,0 +1,3 @@
1
+ import { type ModuleManagedResource } from "@helyx/sdk";
2
+ export declare function createAutoModerationActivityResource(): ModuleManagedResource;
3
+ //# sourceMappingURL=activity-resource.d.ts.map
@@ -0,0 +1,114 @@
1
+ import { DashboardActionValidationError, } from "@helyx/sdk";
2
+ import { AutoModerationRepository, } from "./repository.js";
3
+ export function createAutoModerationActivityResource() {
4
+ return {
5
+ id: "automod-activity",
6
+ async list(context, input) {
7
+ const binding = JSON.stringify({
8
+ version: 1,
9
+ guildId: context.guildId,
10
+ ruleId: input.filters?.ruleId ?? null,
11
+ outcome: input.filters?.outcome ?? null,
12
+ });
13
+ const cursor = decodeCursor(input.cursor, binding);
14
+ const page = await new AutoModerationRepository(context.services).listReceipts({
15
+ guildId: context.guildId,
16
+ limit: input.limit,
17
+ ...(cursor ? { cursor } : {}),
18
+ ...(input.filters?.ruleId ? { ruleId: input.filters.ruleId } : {}),
19
+ ...(input.filters?.outcome ? { outcome: input.filters.outcome } : {}),
20
+ });
21
+ return {
22
+ items: page.items.map(summary),
23
+ ...(page.nextCursor
24
+ ? { nextCursor: encodeCursor(page.nextCursor, binding) }
25
+ : {}),
26
+ };
27
+ },
28
+ async read(context, resourceId) {
29
+ const receipt = await new AutoModerationRepository(context.services).findReceipt(context.guildId, resourceId);
30
+ return receipt ? detail(receipt) : null;
31
+ },
32
+ preview: unavailable,
33
+ create: unavailable,
34
+ update: unavailable,
35
+ delete: unavailable,
36
+ executeAction: unavailable,
37
+ };
38
+ }
39
+ function summary(receipt) {
40
+ return {
41
+ id: receipt.receiptId,
42
+ revision: 1,
43
+ status: receipt.outcome,
44
+ title: receipt.source === "enhanced"
45
+ ? "Enhanced Auto Moderation trigger"
46
+ : "Native Auto Moderation trigger",
47
+ description: `${receipt.actionType.replaceAll("_", " ")} · ${receipt.occurredAt.toISOString()}`,
48
+ attributes: {
49
+ ruleId: receipt.ruleId,
50
+ source: receipt.source,
51
+ action: receipt.actionType,
52
+ outcome: receipt.outcome,
53
+ caseId: receipt.moderationCaseId,
54
+ },
55
+ updatedAt: receipt.createdAt.toISOString(),
56
+ };
57
+ }
58
+ function detail(receipt) {
59
+ return {
60
+ ...summary(receipt),
61
+ value: {
62
+ ruleId: receipt.ruleId,
63
+ ruleRevision: receipt.ruleRevision,
64
+ configurationRevision: receipt.configurationRevision,
65
+ source: receipt.source,
66
+ detectionMethod: receipt.detectionMethod,
67
+ confidenceClass: receipt.confidenceClass,
68
+ confidenceScore: receipt.confidenceScore,
69
+ effectiveThreshold: receipt.effectiveThreshold,
70
+ action: receipt.actionType,
71
+ outcome: receipt.outcome,
72
+ safeCode: receipt.safeCode,
73
+ moderationCaseId: receipt.moderationCaseId,
74
+ occurredAt: receipt.occurredAt.toISOString(),
75
+ },
76
+ };
77
+ }
78
+ function unavailable() {
79
+ return Promise.reject(new DashboardActionValidationError("Auto Moderation activity is read-only."));
80
+ }
81
+ function encodeCursor(values, binding) {
82
+ return Buffer.from(JSON.stringify({
83
+ version: 1,
84
+ binding,
85
+ values: values.map((value) => value instanceof Date ? { date: value.toISOString() } : value),
86
+ }), "utf8").toString("base64url");
87
+ }
88
+ function decodeCursor(cursor, binding) {
89
+ if (!cursor)
90
+ return undefined;
91
+ try {
92
+ const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
93
+ if (parsed.version !== 1 ||
94
+ parsed.binding !== binding ||
95
+ !Array.isArray(parsed.values))
96
+ throw new Error();
97
+ return parsed.values.map((value) => {
98
+ if (value && typeof value === "object" && !Array.isArray(value)) {
99
+ const candidate = value;
100
+ if (typeof candidate.date === "string")
101
+ return new Date(candidate.date);
102
+ }
103
+ if (typeof value === "string" ||
104
+ typeof value === "number" ||
105
+ typeof value === "boolean")
106
+ return value;
107
+ throw new Error();
108
+ });
109
+ }
110
+ catch {
111
+ throw new DashboardActionValidationError("The Auto Moderation activity cursor is invalid.");
112
+ }
113
+ }
114
+ //# sourceMappingURL=activity-resource.js.map
@@ -0,0 +1,3 @@
1
+ import { type AutoModerationCaseLinkProviderContributionV1 } from "@helyx/sdk";
2
+ export declare function createAutoModerationCaseLinkProvider(): AutoModerationCaseLinkProviderContributionV1;
3
+ //# sourceMappingURL=case-link-provider.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { AUTOMOD_CASE_LINK_CONTRACT_VERSION, AUTOMOD_CASE_LINK_PROVIDER_ID, } from "@helyx/sdk";
2
+ import { AutoModerationRepository } from "./repository.js";
3
+ export function createAutoModerationCaseLinkProvider() {
4
+ return {
5
+ providerId: AUTOMOD_CASE_LINK_PROVIDER_ID,
6
+ contractVersion: AUTOMOD_CASE_LINK_CONTRACT_VERSION,
7
+ disassociateCase: (context, input) => new AutoModerationRepository(context.services).disassociateCaseLinks({
8
+ guildId: input.guildId,
9
+ moderationCaseId: input.moderationCaseId,
10
+ }),
11
+ };
12
+ }
13
+ //# sourceMappingURL=case-link-provider.js.map
@@ -0,0 +1,22 @@
1
+ export type AutoModerationMode = "standard" | "enhanced";
2
+ export type AutoModerationSensitivity = "strict" | "balanced" | "sensitive" | "custom";
3
+ export interface AutoModerationConfiguration {
4
+ configuredMode: AutoModerationMode;
5
+ enhancedSensitivityProfile: AutoModerationSensitivity;
6
+ enhancedMinimumConfidence: number;
7
+ enhancedCategoryOverridesEnabled: boolean;
8
+ enhancedLowMinimumConfidence: number;
9
+ enhancedMediumMinimumConfidence: number;
10
+ enhancedHighMinimumConfidence: number;
11
+ enhancedCriticalMinimumConfidence: number;
12
+ presetRuleEnabled: boolean;
13
+ genericSpamRuleEnabled: boolean;
14
+ mentionSpamRuleEnabled: boolean;
15
+ defaultNativeAlertChannelId: string | null;
16
+ defaultExemptRoleIds: readonly string[];
17
+ defaultExemptChannelIds: readonly string[];
18
+ repeatOffenderWindowDays: number;
19
+ }
20
+ export declare const DEFAULT_AUTOMOD_CONFIGURATION: AutoModerationConfiguration;
21
+ export declare function parseAutoModerationConfiguration(input: unknown): AutoModerationConfiguration;
22
+ //# sourceMappingURL=configuration.d.ts.map
@@ -0,0 +1,85 @@
1
+ import { AUTOMOD_LIMITS } from "./constants.js";
2
+ export const DEFAULT_AUTOMOD_CONFIGURATION = Object.freeze({
3
+ configuredMode: "standard",
4
+ enhancedSensitivityProfile: "balanced",
5
+ enhancedMinimumConfidence: 80,
6
+ enhancedCategoryOverridesEnabled: false,
7
+ enhancedLowMinimumConfidence: 80,
8
+ enhancedMediumMinimumConfidence: 80,
9
+ enhancedHighMinimumConfidence: 80,
10
+ enhancedCriticalMinimumConfidence: 80,
11
+ presetRuleEnabled: false,
12
+ genericSpamRuleEnabled: false,
13
+ mentionSpamRuleEnabled: false,
14
+ defaultNativeAlertChannelId: null,
15
+ defaultExemptRoleIds: [],
16
+ defaultExemptChannelIds: [],
17
+ repeatOffenderWindowDays: 30,
18
+ });
19
+ export function parseAutoModerationConfiguration(input) {
20
+ if (!isObject(input))
21
+ return { ...DEFAULT_AUTOMOD_CONFIGURATION };
22
+ return {
23
+ configuredMode: enumValue(input.configuredMode, ["standard", "enhanced"], "configuredMode", "standard"),
24
+ enhancedSensitivityProfile: enumValue(input.enhancedSensitivityProfile, ["strict", "balanced", "sensitive", "custom"], "enhancedSensitivityProfile", "balanced"),
25
+ enhancedMinimumConfidence: confidence(input.enhancedMinimumConfidence, 80),
26
+ enhancedCategoryOverridesEnabled: booleanValue(input.enhancedCategoryOverridesEnabled, false),
27
+ enhancedLowMinimumConfidence: confidence(input.enhancedLowMinimumConfidence, 80),
28
+ enhancedMediumMinimumConfidence: confidence(input.enhancedMediumMinimumConfidence, 80),
29
+ enhancedHighMinimumConfidence: confidence(input.enhancedHighMinimumConfidence, 80),
30
+ enhancedCriticalMinimumConfidence: confidence(input.enhancedCriticalMinimumConfidence, 80),
31
+ presetRuleEnabled: booleanValue(input.presetRuleEnabled, false),
32
+ genericSpamRuleEnabled: booleanValue(input.genericSpamRuleEnabled, false),
33
+ mentionSpamRuleEnabled: booleanValue(input.mentionSpamRuleEnabled, false),
34
+ defaultNativeAlertChannelId: optionalDiscordId(input.defaultNativeAlertChannelId, "defaultNativeAlertChannelId"),
35
+ defaultExemptRoleIds: discordIds(input.defaultExemptRoleIds, AUTOMOD_LIMITS.ignoredRoles, "defaultExemptRoleIds"),
36
+ defaultExemptChannelIds: discordIds(input.defaultExemptChannelIds, AUTOMOD_LIMITS.ignoredChannels, "defaultExemptChannelIds"),
37
+ repeatOffenderWindowDays: integerValue(input.repeatOffenderWindowDays, AUTOMOD_LIMITS.repeatWindowMinimumDays, AUTOMOD_LIMITS.repeatWindowMaximumDays, 30, "repeatOffenderWindowDays"),
38
+ };
39
+ }
40
+ function isObject(value) {
41
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
42
+ }
43
+ function enumValue(value, values, name, fallback) {
44
+ if (value === undefined)
45
+ return fallback;
46
+ if (typeof value !== "string" || !values.includes(value))
47
+ throw new Error(`Auto Moderation configuration contains an invalid ${name}.`);
48
+ return value;
49
+ }
50
+ function booleanValue(value, fallback) {
51
+ if (value === undefined)
52
+ return fallback;
53
+ if (typeof value !== "boolean")
54
+ throw new Error("Auto Moderation configuration requires boolean values.");
55
+ return value;
56
+ }
57
+ function confidence(value, fallback) {
58
+ return integerValue(value, 60, 95, fallback, "confidence threshold");
59
+ }
60
+ function integerValue(value, minimum, maximum, fallback, name) {
61
+ if (value === undefined)
62
+ return fallback;
63
+ if (!Number.isInteger(value) ||
64
+ value < minimum ||
65
+ value > maximum)
66
+ throw new Error(`Auto Moderation configuration contains an invalid ${name}.`);
67
+ return value;
68
+ }
69
+ function optionalDiscordId(value, name) {
70
+ if (value === undefined || value === null || value === "")
71
+ return null;
72
+ if (typeof value !== "string" || !/^\d{17,20}$/u.test(value))
73
+ throw new Error(`Auto Moderation configuration contains an invalid ${name}.`);
74
+ return value;
75
+ }
76
+ function discordIds(value, maximum, name) {
77
+ if (value === undefined)
78
+ return [];
79
+ if (!Array.isArray(value) ||
80
+ value.length > maximum ||
81
+ value.some((item) => typeof item !== "string" || !/^\d{17,20}$/u.test(item)))
82
+ throw new Error(`Auto Moderation configuration contains an invalid ${name}.`);
83
+ return [...new Set(value)].sort();
84
+ }
85
+ //# sourceMappingURL=configuration.js.map
@@ -0,0 +1,30 @@
1
+ export declare const AUTOMOD_MODULE_ID = "helyx.automod";
2
+ export declare const AUTOMOD_PERMISSION_IDS: Readonly<{
3
+ readonly rulesRead: "automod.rules.read";
4
+ readonly rulesManage: "automod.rules.manage";
5
+ readonly rulesPublish: "automod.rules.publish";
6
+ readonly rulesReconcile: "automod.rules.reconcile";
7
+ readonly activityRead: "automod.activity.read";
8
+ readonly settingsManage: "automod.settings.manage";
9
+ }>;
10
+ export declare const AUTOMOD_LIMITS: Readonly<{
11
+ rulePageSize: 50;
12
+ activityPageSize: 100;
13
+ rulesPerGuild: 100;
14
+ ruleNameCharacters: 80;
15
+ termsPerRule: 1000;
16
+ termCharacters: 60;
17
+ regexPerRule: 10;
18
+ regexCharacters: 260;
19
+ allowListEntries: 100;
20
+ ignoredRoles: 20;
21
+ ignoredChannels: 50;
22
+ demotionRoles: 10;
23
+ importBytes: 4000;
24
+ repeatWindowMinimumDays: 1;
25
+ repeatWindowMaximumDays: 365;
26
+ actionReceiptRetentionDays: 90;
27
+ settledClaimRetentionDays: 365;
28
+ }>;
29
+ export declare const AUTOMOD_RULE_STATUSES: readonly ["draft", "published", "disabled", "drifted", "missing", "review"];
30
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1,37 @@
1
+ export const AUTOMOD_MODULE_ID = "helyx.automod";
2
+ export const AUTOMOD_PERMISSION_IDS = Object.freeze({
3
+ rulesRead: "automod.rules.read",
4
+ rulesManage: "automod.rules.manage",
5
+ rulesPublish: "automod.rules.publish",
6
+ rulesReconcile: "automod.rules.reconcile",
7
+ activityRead: "automod.activity.read",
8
+ settingsManage: "automod.settings.manage",
9
+ });
10
+ export const AUTOMOD_LIMITS = Object.freeze({
11
+ rulePageSize: 50,
12
+ activityPageSize: 100,
13
+ rulesPerGuild: 100,
14
+ ruleNameCharacters: 80,
15
+ termsPerRule: 1_000,
16
+ termCharacters: 60,
17
+ regexPerRule: 10,
18
+ regexCharacters: 260,
19
+ allowListEntries: 100,
20
+ ignoredRoles: 20,
21
+ ignoredChannels: 50,
22
+ demotionRoles: 10,
23
+ importBytes: 4_000,
24
+ repeatWindowMinimumDays: 1,
25
+ repeatWindowMaximumDays: 365,
26
+ actionReceiptRetentionDays: 90,
27
+ settledClaimRetentionDays: 365,
28
+ });
29
+ export const AUTOMOD_RULE_STATUSES = Object.freeze([
30
+ "draft",
31
+ "published",
32
+ "disabled",
33
+ "drifted",
34
+ "missing",
35
+ "review",
36
+ ]);
37
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,30 @@
1
+ export declare const automodDefaultPermissions: {
2
+ module: {
3
+ readonly everyone: true;
4
+ readonly requiredPermissions: readonly [];
5
+ readonly allowedRoleIds: readonly [];
6
+ readonly deniedRoleIds: readonly [];
7
+ readonly allowedUserIds: readonly [];
8
+ readonly deniedUserIds: readonly [];
9
+ };
10
+ commands: {
11
+ [k: string]: {
12
+ readonly everyone: false;
13
+ readonly accessLevel: "administrator";
14
+ readonly requiredPermissions: readonly [];
15
+ readonly allowedRoleIds: readonly [];
16
+ readonly deniedRoleIds: readonly [];
17
+ readonly allowedUserIds: readonly [];
18
+ readonly deniedUserIds: readonly [];
19
+ };
20
+ };
21
+ };
22
+ export declare const automodLogging: {
23
+ events: {
24
+ id: string;
25
+ name: string;
26
+ description: string;
27
+ defaultEnabled: false;
28
+ }[];
29
+ };
30
+ //# sourceMappingURL=contracts.d.ts.map
@@ -0,0 +1,51 @@
1
+ import { AUTOMOD_PERMISSION_IDS } from "./constants.js";
2
+ const administrators = {
3
+ everyone: false,
4
+ accessLevel: "administrator",
5
+ requiredPermissions: [],
6
+ allowedRoleIds: [],
7
+ deniedRoleIds: [],
8
+ allowedUserIds: [],
9
+ deniedUserIds: [],
10
+ };
11
+ const everyone = {
12
+ everyone: true,
13
+ requiredPermissions: [],
14
+ allowedRoleIds: [],
15
+ deniedRoleIds: [],
16
+ allowedUserIds: [],
17
+ deniedUserIds: [],
18
+ };
19
+ export const automodDefaultPermissions = {
20
+ module: everyone,
21
+ commands: Object.fromEntries(Object.values(AUTOMOD_PERMISSION_IDS).map((id) => [id, administrators])),
22
+ };
23
+ export const automodLogging = {
24
+ events: [
25
+ {
26
+ id: "automod-rule-triggered",
27
+ name: "Auto Moderation rule triggered",
28
+ description: "Records structural rule, action and outcome identifiers without message content.",
29
+ defaultEnabled: false,
30
+ },
31
+ {
32
+ id: "automod-rule-drifted",
33
+ name: "Auto Moderation rule drifted",
34
+ description: "Records that an owned Discord rule differs from its Helyx definition.",
35
+ defaultEnabled: false,
36
+ },
37
+ {
38
+ id: "automod-enhanced-detected",
39
+ name: "Enhanced match detected",
40
+ description: "Reserved for content-free Enhanced detection receipts.",
41
+ defaultEnabled: false,
42
+ },
43
+ {
44
+ id: "automod-enforcement-failed",
45
+ name: "Auto Moderation enforcement failed",
46
+ description: "Records a safe enforcement failure code without copied content.",
47
+ defaultEnabled: false,
48
+ },
49
+ ],
50
+ };
51
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1,61 @@
1
+ import { AUTOMOD_TIMEOUT_SECONDS, type DiscordAutoModerationRuleDefinition, type ModerationRepeatActionPolicy } from "@helyx/sdk";
2
+ export type AutoModerationCategory = "low" | "medium" | "high" | "critical";
3
+ export type AutoModerationRuleKind = "keyword" | "keyword_preset" | "spam" | "mention_spam";
4
+ export type AutoModerationRuleStatus = "draft" | "published" | "disabled" | "drifted" | "missing" | "review";
5
+ export interface AutoModerationRuleDraft {
6
+ name: string;
7
+ category: AutoModerationCategory;
8
+ kind: AutoModerationRuleKind;
9
+ keywordFilter: readonly string[];
10
+ regexPatterns: readonly string[];
11
+ allowList: readonly string[];
12
+ enhancedNormalisedMatching: boolean;
13
+ enhancedFuzzyMatching: boolean;
14
+ presets: readonly ("profanity" | "sexual_content" | "slurs")[];
15
+ mentionTotalLimit: number;
16
+ mentionRaidProtectionEnabled: boolean;
17
+ blockMessage: boolean;
18
+ blockExplanation: string | null;
19
+ alertChannelId: string | null;
20
+ nativeTimeoutSeconds: (typeof AUTOMOD_TIMEOUT_SECONDS)[number] | null;
21
+ ignoredRoleIds: readonly string[];
22
+ ignoredChannelIds: readonly string[];
23
+ openViolationThread: boolean;
24
+ actionPolicy: ModerationRepeatActionPolicy;
25
+ }
26
+ export declare function parseRuleDraft(value: Readonly<Record<string, unknown>>): AutoModerationRuleDraft;
27
+ export declare function toNativeDefinition(input: {
28
+ ruleId: string;
29
+ draft: AutoModerationRuleDraft;
30
+ enabled: boolean;
31
+ globalIgnoredRoleIds: readonly string[];
32
+ globalIgnoredChannelIds: readonly string[];
33
+ defaultAlertChannelId: string | null;
34
+ }): DiscordAutoModerationRuleDefinition;
35
+ export declare function canonicalRuleFingerprint(rule: DiscordAutoModerationRuleDefinition): string;
36
+ export declare function enforcementClaimKey(input: {
37
+ guildId: string;
38
+ configurationRevision: string;
39
+ subjectUserId: string;
40
+ messageId: string | null;
41
+ channelId: string | null;
42
+ sourceEventId: string;
43
+ }): string;
44
+ export declare function enforcementClaimFingerprint(input: {
45
+ claimKey: string;
46
+ logicalRuleId: string;
47
+ ruleRevision: number;
48
+ actionFamily: string;
49
+ }): string;
50
+ export declare function enforcementActionFamily(policy: ModerationRepeatActionPolicy): string;
51
+ export declare function policyFingerprint(input: {
52
+ ruleRevision: number;
53
+ configurationRevision: string;
54
+ actionPolicy: ModerationRepeatActionPolicy;
55
+ openViolationThread: boolean;
56
+ deleteSourceMessage?: boolean;
57
+ globalIgnoredRoleIds: readonly string[];
58
+ ruleIgnoredRoleIds: readonly string[];
59
+ }): string;
60
+ export declare function nativeRuleOwnershipMarker(ruleId: string): string;
61
+ //# sourceMappingURL=domain.d.ts.map