from __future__ import annotations import logging import re from .constants import MXC_URI_REGEX, MXID_REGEX, ROOM_ALIAS_REGEX, ROOM_ID_REGEX logger = logging.getLogger(__name__) _NIO_IMPORT_ERROR = "matrix-nio is not installed. Install with: pip install matrix-nio python-olm peewee" def get_nio(): try: import nio return nio except ImportError: raise ImportError(_NIO_IMPORT_ERROR) def parse_mxc_uri(uri: str) -> tuple[str, str] | None: match = re.match(MXC_URI_REGEX, uri) if not match: return None return match.group(1), match.group(2) def resolve_mxc_download_url(homeserver: str, mxc_uri: str) -> str: parsed = parse_mxc_uri(mxc_uri) if not parsed: raise ValueError(f"Invalid MXC URI: {mxc_uri}") server, media_id = parsed return f"{homeserver.rstrip('/')}/_matrix/media/v3/download/{server}/{media_id}" def is_matrix_user_id(value: str) -> bool: return bool(re.match(MXID_REGEX, value)) def is_room_id(value: str) -> bool: return bool(re.match(ROOM_ID_REGEX, value)) def is_room_alias(value: str) -> bool: return bool(re.match(ROOM_ALIAS_REGEX, value)) def normalize_user_id(user_id: str) -> str: return user_id.strip().lower() def extract_mention_user_ids(body: str) -> list[str]: return re.findall(r"@[^\s,:!?]+:[\w.\-]+", body) def extract_room_aliases(text: str) -> list[str]: return re.findall(r"#[^\s:]+:[^\s]+", text) def build_dedupe_key(account_id: str, event_id: str) -> str: return f"matrix:{account_id}:{event_id}" def strip_html_tags(html: str) -> str: return re.sub(r"<[^>]+>", "", html) def sanitize_matrix_text(text: str, limit: int = 4000) -> str: if len(text) <= limit: return text return text[: limit - 3] + "..." def chunk_text(text: str, limit: int = 4000) -> list[str]: chunks = [] remaining = text while len(remaining) > limit: split_at = remaining.rfind("\n", 0, limit) if split_at == -1: split_at = remaining.rfind(" ", 0, limit) if split_at == -1: split_at = limit chunks.append(remaining[:split_at]) remaining = remaining[split_at:].lstrip() if remaining: chunks.append(remaining) return chunks async def download_media(homeserver: str, access_token: str, mxc_uri: str) -> bytes | None: import aiohttp url = resolve_mxc_download_url(homeserver, mxc_uri) headers = {"Authorization": f"Bearer {access_token}"} try: async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers) as resp: if resp.status == 200: return await resp.read() logger.warning("Media download failed: HTTP %s for %s", resp.status, mxc_uri) return None except Exception: logger.exception("Media download error for %s", mxc_uri) return None