diff --git a/services/discord-gateway/src/modules/ai-moderation/fastClassifier.ts b/services/discord-gateway/src/modules/ai-moderation/fastClassifier.ts index ca3577a..c707387 100644 --- a/services/discord-gateway/src/modules/ai-moderation/fastClassifier.ts +++ b/services/discord-gateway/src/modules/ai-moderation/fastClassifier.ts @@ -51,13 +51,28 @@ const ZERO_WIDTH_RE = /[​-‍⁠­؜]/; const URL_RE = /https?:\/\/[^\s"]+/gi; const INVITE_RE = /(?:discord\.(?:gg|com\/invite)|dsc\.gg)\/[a-zA-Z0-9_-]+/gi; const INVITE_CODE_RE = /(?:^|\s)([a-zA-Z0-9_-]{6,12})(?:\s|$)/g; +// Word-boundary phone match. `(?, ), user/role/channel mentions and +// timestamps embed long numeric snowflakes. If left in the text, digit-based +// patterns (phone_number, personal_info, ip_address_sharing) false-positive +// on them — e.g. <:mambotongue:1463255254220148939> "matched" phone_number. +const DISCORD_TOKEN_RE = + /<(?:a?:[^>]{1,32}:\d{17,20}|@!?\d{17,20}|@&\d{17,20}|#\d{17,20}|t:\d{10,11}(?::[tTdDRfF])?)>/g; + +/** Replaces Discord markdown tokens with a space so they can't trip patterns. */ +export function sanitizeDiscordTokens(content: string): string { + return content.replace(DISCORD_TOKEN_RE, " "); +} + // ── Spam / low-quality patterns ───────────────────────────────────────── const REPEATED_CHAR_RE = /(.)\1{8,}/; // 9+ repeated chars @@ -305,6 +320,9 @@ export function classifyMessage(message: MessageRecord): Layer1Result { } const mentions = countMentions(content); + // Strip Discord markdown tokens (emoji/mention snowflakes) before pattern + // matching so digit-based patterns don't false-positive on them. + const sanitized = sanitizeDiscordTokens(content); const matchedFlags: string[] = []; const severityWeights: Record = { low: 1, @@ -320,7 +338,7 @@ export function classifyMessage(message: MessageRecord): Layer1Result { const matchedPatternDetails: string[] = []; for (const pattern of PATTERNS) { - if (pattern.test(content, mentions)) { + if (pattern.test(sanitized, mentions)) { matchedFlags.push(pattern.name); matchedPatternDetails.push(pattern.name);