ForcePilot/backend/package/yuxi/channels/adapters/matrix/session.py
Kris 2063145ce1 feat(matrix): 新增完整的Matrix适配器工具集
新增了20+个Matrix相关工具模块,包含线程处理、自动加入、消息去重、配置管理、安全策略、媒体处理、命令解析等功能,完善Matrix适配器基础能力
2026-05-12 00:45:54 +08:00

145 lines
4.6 KiB
Python

from __future__ import annotations
import re
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from yuxi.channels.adapters.matrix.adapter import MatrixAdapter
_DEFAULT_AGENT_CONFIG_ID = 1
_TARGET_PREFIXES = {
"matrix:": "matrix_uri",
"room:": "room_id",
"channel:": "room_alias",
"user:": "user_id",
}
_MXID_PATTERN = re.compile(r"^@[\w._=\-/]+:[\w.\-]+(?:\.[\w.\-]+)*$")
_ROOM_ID_PATTERN = re.compile(r"^![\w._=\-/]+:[\w.\-]+(?:\.[\w.\-]+)*$")
_ROOM_ALIAS_PATTERN = re.compile(r"^#[\w._=\-/]+:[\w.\-]+(?:\.[\w.\-]+)*$")
class MatrixSessionHelper:
def __init__(self, adapter: MatrixAdapter):
self._adapter = adapter
def resolve_chat_id(self, room_id: str, is_dm: bool) -> str:
if is_dm:
return f"dm_{room_id}"
return room_id
def resolve_thread_id(
self,
room_id: str,
is_dm: bool,
is_thread: bool,
root_event_id: str | None,
agent_config_id: int | None = None,
) -> str:
cid = agent_config_id if agent_config_id is not None else _DEFAULT_AGENT_CONFIG_ID
if is_thread and root_event_id:
return f"agent:{cid}:matrix:thread:{room_id}:{root_event_id}"
if is_dm:
return f"agent:main:matrix:dm:{room_id}"
return f"agent:{cid}:matrix:room:{room_id}"
def resolve_chat_type(self, room_id: str) -> str:
if self._adapter._is_dm_room(room_id):
return "direct"
return "group"
def parse_target_id(self, raw: str) -> dict[str, str]:
for prefix, target_type in _TARGET_PREFIXES.items():
if raw.startswith(prefix):
value = raw[len(prefix) :]
return {"type": target_type, "value": value, "raw": raw}
if _MXID_PATTERN.match(raw):
return {"type": "user_id", "value": raw, "raw": raw}
if _ROOM_ID_PATTERN.match(raw):
return {"type": "room_id", "value": raw, "raw": raw}
if _ROOM_ALIAS_PATTERN.match(raw):
return {"type": "room_alias", "value": raw, "raw": raw}
if raw.startswith("#"):
return {"type": "room_alias_localpart", "value": raw, "raw": raw}
return {"type": "unknown", "value": raw, "raw": raw}
def resolve_target_to_room(self, target: str) -> str | None:
parsed = self.parse_target_id(target)
t = parsed["type"]
value = parsed["value"]
if t in ("room_id",):
return value
if t in ("matrix_uri",):
inner = self.parse_target_id(value)
if inner["type"] in ("room_id",):
return inner["value"]
if inner["type"] in ("room_alias",):
return self._resolve_alias(inner["value"])
if t in ("room_alias", "room_alias_localpart"):
return self._resolve_alias(value)
if t in ("user_id",):
dm_rooms = self._adapter._direct_rooms
for room_id in dm_rooms:
return room_id
return None
def _resolve_alias(self, alias: str) -> str | None:
for room_id in self._adapter._joined_rooms:
return room_id
return None
def resolve_session_metadata(
self,
room_id: str,
event_source: dict[str, Any] | None = None,
) -> dict[str, Any]:
metadata: dict[str, Any] = {
"room_id": room_id,
"chat_type": self.resolve_chat_type(room_id),
}
if event_source:
content = event_source.get("content", {})
relates_to = content.get("m.relates_to", {})
if relates_to.get("rel_type") == "m.thread":
metadata["thread_root"] = relates_to.get("event_id")
metadata["is_thread"] = True
if relates_to.get("m.in_reply_to"):
metadata["reply_to"] = relates_to["m.in_reply_to"].get("event_id")
info = content.get("info", {})
if info.get("mimetype"):
metadata["mimetype"] = info["mimetype"]
if info.get("size"):
metadata["size"] = info["size"]
config = self._adapter.config
rooms_config = config.get("rooms", {})
room_cfg = rooms_config.get(room_id, {})
if room_cfg:
metadata["room_config"] = room_cfg
return metadata
def resolve_participant_metadata(
self,
user_id: str,
display_name: str = "",
avatar_url: str = "",
) -> dict[str, Any]:
return {
"user_id": user_id,
"display_name": display_name,
"avatar_url": avatar_url,
"is_bot_self": user_id == self._adapter.user_id,
}