"""Contact masking. Phone numbers and email addresses in listing bodies / messages are masked for low-trust senders to push communication through the in-site channel. Unmasked only once sender reaches TrustTier.trusted or verified_email=True. Masking is a display-layer concern: raw body stored as-is; masked on render. For the *sending* side we heuristic-flag high-contact-density messages so moderators can spot scraping attempts. """ import re from datetime import datetime from app.models.enums import TrustTier from app.services.settings import get_setting # patterns _PHONE_RE = re.compile( r"(\+?1[\s\-.]?)?" r"(\(?\d{3}\)?[\s\-.])" r"\d{3}[\s\-.]\d{4}" ) _EMAIL_RE = re.compile(r"[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}") _URL_RE = re.compile(r"https?://\S+|www\.\S+", re.I) def contact_revealed(user) -> bool: """True when this user's contact info may be shown unmasked.""" if user is None: return False if not (user.email_verified and user.trust_tier in (TrustTier.trusted, TrustTier.verified)): return False gate_days = get_setting("new_user_trust_gate_days", 0) if gate_days and user.created_at: if (datetime.utcnow() - user.created_at).days < gate_days: return False return True def mask_body(text: str, reveal: bool = False) -> str: """Optionally mask phones, emails, and URLs in a block of text.""" if reveal or not text: return text text = _PHONE_RE.sub("[phone hidden]", text) text = _EMAIL_RE.sub("[email hidden]", text) text = _URL_RE.sub("[link hidden]", text) return text def contact_density(text: str) -> int: """Count how many contact signals appear in text (for heuristic flagging).""" if not text: return 0 return (len(_PHONE_RE.findall(text)) + len(_EMAIL_RE.findall(text)) + len(_URL_RE.findall(text)))