49 lines
1.6 KiB
Python
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)))
|