新增 Matrix 渠道扩展,支持在 Yuxi 平台中集成 Matrix 去中心化通讯协议。 包含以下功能模块: - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - crypto: 端到端加密 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - session: 会话管理 - room_resolver: 房间解析 - dm_tracker: 私聊追踪 - rate_limiter: 速率限制 - actions: 动作处理 - constants: 常量定义 - utils: 工具函数 - types: 类型定义
106 lines
2.9 KiB
Python
106 lines
2.9 KiB
Python
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
|