// Shariah-compliant auto-moderation for Falah forum content // Uses keyword / simple pattern matching — not AI-based. export interface ModerationResult { status: "approved" | "flagged"; flags: string[]; flaggedWords: string[]; } // ─── Rule definitions ─────────────────────────────────────────────────────── interface Rule { name: string; label: string; /** Individual words/phrases to match with word-boundary regex */ words: string[]; } const RULES: Rule[] = [ // ── Explicit / profane language ────────────────────────────────────────── { name: "profanity", label: "Profanity / explicit language", words: [ // English "fuck", "shit", "asshole", "bitch", "bastard", "cunt", "dickhead", "motherfucker", "pissed off", "slut", "whore", "wanker", "douchebag", "cock sucker", // Common Malay profanity (written in Latin script) "babi", "pukimak", "bangsat", "celaka", "taik", "mampus", "haram jadah", "anak haram", "keparat", "paloi", "palia", // Common Arabic profanity (Latin script) "kalb", "himar", "qahba", "ahmaq", "yakhra", ], }, // ── Hate speech / racial slurs ─────────────────────────────────────────── { name: "hate_speech", label: "Hate speech / racial slur", words: [ "nigger", "kike", "spic", "chink", "gook", "wetback", "coon", "paki", "towelhead", "raghead", "camel jockey", ], }, // ── Riba / interest ────────────────────────────────────────────────────── { name: "riba", label: "Promotion of riba / interest-based transactions", words: [ "riba", "ribawi", "usury", "usurious", ], }, // ── Maysir / gambling ──────────────────────────────────────────────────── { name: "gambling", label: "Promotion of gambling / maysir", words: [ "maysir", "gambling", "gamble", "casino", "slot machine", "poker", "blackjack", "roulette", "lottery", "bookmaker", "betting", ], }, // ── Khamr / alcohol ────────────────────────────────────────────────────── { name: "alcohol", label: "Promotion of alcohol / khamr", words: [ "khamr", "alcohol", "beer", "wine", "vodka", "whiskey", "whisky", "liquor", "intoxicant", ], }, // ── Drugs ──────────────────────────────────────────────────────────────── { name: "drugs", label: "Promotion of drugs / intoxicants", words: [ "marijuana", "cocaine", "heroin", "methamphetamine", "shabu", "ecstasy", "opium", "cannabis", "hashish", "crack cocaine", "ketamine", "lsd", "meth", "amphetamine", ], }, // ── Zina / adultery ────────────────────────────────────────────────────── { name: "zina", label: "Promotion of zina / adultery / fornication", words: [ "zina", "adultery", "fornication", "prostitution", "prostitute", "escort service", ], }, // ── Pornography ────────────────────────────────────────────────────────── { name: "pornography", label: "Pornographic / obscene content", words: [ "porn", "pornography", "xxx", "onlyfans", "adult content", ], }, // ── Shirk / kufr statements ────────────────────────────────────────────── { name: "shirk_kufr", label: "Clear shirk / kufr statement", words: [ "allah is not real", "allah does not exist", "there is no god", "god is not allah", "muhammad is false", "islam is false", "quran is false", "koran is false", "worship shaitan", "worship satan", "allah is a lie", ], }, ]; // ── Helper: build a combined word-boundary regex for a rule ──────────────── function buildRuleRegex(words: string[]): RegExp { // Escape special regex chars, then join as alternation const escaped = words.map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") ); return new RegExp(`\\b(${escaped.join("|")})\\b`, "gi"); } // ── Spam detection helpers ───────────────────────────────────────────────── function hasExcessiveUrls(text: string): boolean { const urlMatches = text.match(/https?:\/\/[^\s]+/g); return urlMatches !== null && urlMatches.length >= 3; } function hasRepetitiveText(text: string): boolean { const words = text.toLowerCase().split(/\s+/); const freq: Record = {}; for (const word of words) { // Skip very short words to reduce false positives if (word.length < 3) continue; freq[word] = (freq[word] || 0) + 1; } // Flag if the same word appears more than 8 times return Object.values(freq).some((count) => count > 8); } function hasExcessiveCaps(text: string): boolean { // Ignore very short texts if (text.length < 30) return false; const letters = text.replace(/[^a-zA-Z]/g, ""); if (letters.length < 10) return false; const upper = letters.replace(/[a-z]/g, "").length; return upper / letters.length > 0.7; } // ── Harassment detection ─────────────────────────────────────────────────── function hasHarassment(text: string): boolean { const lower = text.toLowerCase(); const threatPatterns = [ /i will kill/i, /i will hurt/i, /i will destroy/i, /i will beat/i, /i (?:will|am going to) (?:murder|kill|harm)/i, /(?:shut up|stfu)\s+(?:bitch|bastard|fuck)/i, ]; return threatPatterns.some((p) => p.test(lower)); } // ── Main moderation function ─────────────────────────────────────────────── /** * Moderate forum content against Shariah compliance rules. * * @param content - The body text of the thread or post. * @param title - Optional title (for thread moderation). * @returns An object with the moderation verdict, matching rule labels, * and the specific words/phrases that triggered flags. */ export function moderateContent( content: string, title?: string, ): ModerationResult { const text = title ? `${title}\n${content}` : content; const flags: string[] = []; const flaggedWords: string[] = []; // ── Check keyword-based rules ────────────────────────────────────────── for (const rule of RULES) { const regex = buildRuleRegex(rule.words); const matches = [...text.matchAll(regex)]; if (matches.length > 0) { flags.push(rule.label); for (const m of matches) { if (!flaggedWords.includes(m[0].toLowerCase())) { flaggedWords.push(m[0].toLowerCase()); } } } } // ── Check harassment patterns ────────────────────────────────────────── if (hasHarassment(text)) { flags.push("Harassment / bullying"); if (!flaggedWords.includes("(harassment pattern)")) { flaggedWords.push("(harassment pattern)"); } } // ── Check spam patterns ──────────────────────────────────────────────── let spamFlag = false; if (hasExcessiveUrls(text)) { spamFlag = true; if (!flaggedWords.includes("(excessive URLs)")) { flaggedWords.push("(excessive URLs)"); } } if (hasRepetitiveText(text)) { spamFlag = true; if (!flaggedWords.includes("(repetitive text)")) { flaggedWords.push("(repetitive text)"); } } if (hasExcessiveCaps(text)) { spamFlag = true; if (!flaggedWords.includes("(excessive caps)")) { flaggedWords.push("(excessive caps)"); } } if (spamFlag) { flags.push("Spam patterns"); } // ── No flags → approved ──────────────────────────────────────────────── if (flags.length === 0) { return { status: "approved", flags: [], flaggedWords: [] }; } return { status: "flagged", flags, flaggedWords }; }