Files
classifieds/app/services/contact.py
T
2026-07-13 16:52:00 -04:00

60 lines
2.1 KiB
Python

"""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.utils.time import utcnow
from app.models.enums import TrustTier
from app.services.settings import get_setting
# patterns
_PHONE_RE = re.compile(
r"(?<!\d)" # not mid-way through a longer digit run
r"(?:\+?1[\s\-.]?)?" # optional country code
r"\(?\d{3}\)?[\s\-.]?" # area code — separators now optional
r"\d{3}[\s\-.]?\d{4}" # so 5551234567 is caught, not just 555-123-4567
r"(?!\d)"
)
_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 (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)))