from __future__ import annotations import hashlib import re from typing import Any from urllib.parse import urlparse _MXC_URL_PATTERN = re.compile(r"^mxc://([^/]+)/(.+)$") _HTTP_URL_PATTERN = re.compile(r"^https?://") def resolve_avatar_url(raw_url: str, homeserver: str | None = None) -> str | None: if not raw_url: return None if _MXC_URL_PATTERN.match(raw_url): return raw_url if _HTTP_URL_PATTERN.match(raw_url): if homeserver: return _http_to_mxc(raw_url, homeserver) return raw_url return None def _http_to_mxc(http_url: str, homeserver: str) -> str: media_id = hashlib.sha256(http_url.encode()).hexdigest()[:24] if homeserver.startswith("http"): hs_domain = urlparse(homeserver).hostname or "matrix.org" else: hs_domain = homeserver return f"mxc://{hs_domain}/{media_id}" def resolve_display_name( profile_data: dict[str, Any], fallback_user_id: str = "", ) -> str: displayname = profile_data.get("displayname", "") if displayname: return displayname.strip() user_id = profile_data.get("user_id", fallback_user_id) if user_id and user_id.startswith("@"): localpart = user_id[1:].split(":")[0] return localpart return user_id or "unknown" def compare_profiles( current: dict[str, Any], incoming: dict[str, Any], ) -> dict[str, tuple[Any, Any]]: changes: dict[str, tuple[Any, Any]] = {} for key in ("displayname", "avatar_url"): old_val = current.get(key) new_val = incoming.get(key) if old_val != new_val: changes[key] = (old_val, new_val) return changes def build_profile_update( display_name: str = "", avatar_url: str = "", ) -> dict[str, Any]: result: dict[str, Any] = {} if display_name: result["displayname"] = display_name if avatar_url: result["avatar_url"] = avatar_url return result def mxc_to_http(mxc_url: str, homeserver: str) -> str: match = _MXC_URL_PATTERN.match(mxc_url) if not match: return mxc_url base = homeserver.rstrip("/") if not base.startswith("http"): base = f"https://{base}" server_name = match.group(1) media_id = match.group(2) return f"{base}/_matrix/media/r0/download/{server_name}/{media_id}"