Files
classifieds/app/services/contact.py
T
2026-06-15 17:43:47 -04:00

49 lines
1.6 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 app.models.enums import TrustTier
# 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
return (user.email_verified and
user.trust_tier in (TrustTier.trusted, TrustTier.verified))
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)))