File size: 3,235 Bytes
d2ce5f7
954e0aa
d2ce5f7
 
 
00c3bab
 
 
 
 
d2ce5f7
 
 
 
00c3bab
 
 
 
 
 
 
 
 
 
 
 
 
d2ce5f7
 
 
 
 
00c3bab
d2ce5f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
00c3bab
d2ce5f7
00c3bab
 
f412b66
d2ce5f7
f412b66
d2ce5f7
00c3bab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d2ce5f7
f412b66
00c3bab
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import re
from loguru import logger
from src.rag.rag import get_llm
from src.config import settings

# Load English NSFW word list from config settings (kept out of committed code)
LOCAL_NSFW_WORDS = set(settings.local_nsfw_words)

# Load Arabic NSFW word list from config settings (kept out of committed code)
LOCAL_ARABIC_NSFW_WORDS = set(settings.local_arabic_nsfw_words)

def is_local_nsfw(text: str) -> bool:
    text_clean = text.strip().lower()
    
    # Check Arabic NSFW words
    words = text_clean.split()
    for w in words:
        if w in LOCAL_ARABIC_NSFW_WORDS:
            return True
            
    # Substring checks for high-signal Arabic NSFW roots
    arabic_substrings = {"سكس", "بورن", "شرموط", "منيوك", "قحبة", "قحبه", "متناك"}
    for root in arabic_substrings:
        if root in text_clean:
            return True
            
    # Substring checks for high-signal English NSFW roots
    high_signal_substrings = {"porn", "nude", "sex", "vagina", "penis", "clitoris"}
    for root in high_signal_substrings:
        if root in text_clean:
            return True
            
    # Check for direct matches or common word boundary matches for English
    for word in LOCAL_NSFW_WORDS:
        # If it was already checked as a high-signal substring, skip
        if word in high_signal_substrings:
            continue
        pattern = rf"\b{re.escape(word)}\b"
        if re.search(pattern, text_clean):
            return True
        # Also check obfuscated variations like b**tch, f**k
        obfuscated = word[0] + r"\*+" + word[-1] if len(word) > 2 else ""
        if obfuscated and re.search(rf"\b{obfuscated}\b", text_clean):
            return True
            
    # Check common obfuscated patterns (e.g., f*ck, b*tch, f**k)
    # Match any word that contains asterisks inside it
    if "*" in text_clean:
        # Additional safety check for common swear structures
        for word in ["fuck", "bitch", "shit", "cunt", "asshole", "bastard"]:
            parts = list(word)
            pattern_parts = [parts[0]]
            for char in parts[1:-1]:
                # Allow the character itself, or one or more asterisks
                pattern_parts.append(rf"({re.escape(char)}|\*+)")
            pattern_parts.append(parts[-1])
            pattern = rf"\b{''.join(pattern_parts)}\b"
            if re.search(pattern, text_clean):
                return True
                
    return False

def validate_topics_batch(texts: list[str]) -> list[str]:
    """
    Validates a batch of topics.
    For each topic text:
      Checks against the local English and Arabic NSFW lists.
    Returns:
      A list of results: "ALLOWED" or "not safe for work words".
    """
    logger.info(f"Validating batch of {len(texts)} topics...")
    results = ["ALLOWED"] * len(texts)
    
    for i, text in enumerate(texts):
        if is_local_nsfw(text):
            results[i] = "not safe for work words"
            
    return results


def validate_topic_input(topic: str) -> str:
    """
    Synchronous validation fallback (e.g. for non-async contexts).
    """
    if is_local_nsfw(topic):
        return "not safe for work words"
        
    return "ALLOWED"