refactor(matrix): media http fallback derived once, dm-cache comprehension, encrypted-attachment metadata pairs, setup info loops (3008->2992)

This commit is contained in:
Teknium
2026-09-02 23:56:34 -07:00
parent ebec8fa1bb
commit e657c73f32
+27 -43
View File
@@ -152,8 +152,7 @@ def _resolve_matrix_bang_command(name: str) -> str | None:
logger.debug("Matrix: is_gateway_known_command failed for %r", name, exc_info=True)
try:
from agent.skill_commands import get_skill_commands
skill_commands = get_skill_commands() or {}
# Skill command keys are slash-prefixed ("/arxiv").
skill_commands = get_skill_commands() or {} # keys are slash-prefixed ("/arxiv")
for candidate in candidates:
if f"/{candidate}" in skill_commands:
return candidate
@@ -682,20 +681,16 @@ class _CryptoStateStore:
return (await self.get_encryption_info(room_id)) is not None
async def get_encryption_info(self, room_id: str):
info = None
if hasattr(self._ss, "get_encryption_info"):
info = await self._ss.get_encryption_info(room_id)
info = await self._ss.get_encryption_info(room_id) if hasattr(self._ss, "get_encryption_info") else None
if info is not None:
return info
if room_id in self._enc_info_cache:
return self._enc_info_cache[room_id]
client = self._client
if client is None:
if self._client is None:
return None
try:
from mautrix.types import (
EventType as _ET, RoomEncryptionStateEventContent as _Enc, RoomID as _RID)
raw = await client.get_state_event(_RID(room_id), _ET.ROOM_ENCRYPTION)
from mautrix.types import EventType as _ET, RoomEncryptionStateEventContent as _Enc, RoomID as _RID
raw = await self._client.get_state_event(_RID(room_id), _ET.ROOM_ENCRYPTION)
except Exception as exc:
logger.debug("Matrix: homeserver encryption-info query failed for %s: %s", room_id, exc)
return None
@@ -1745,8 +1740,7 @@ class MatrixAdapter(BasePlatformAdapter):
except Exception as exc:
if self._closing:
return
err_str = str(exc).lower()
if any(k in err_str for k in ("401", "403", "unauthorized", "forbidden")):
if any(k in str(exc).lower() for k in ("401", "403", "unauthorized", "forbidden")):
logger.error("Matrix: permanent auth error: %s — stopping sync", exc)
return
logger.warning("Matrix: sync error: %s — retrying in 5s", exc)
@@ -2011,7 +2005,6 @@ class MatrixAdapter(BasePlatformAdapter):
if url and not str(url).startswith("mxc://"):
logger.warning("[Matrix] Rejecting inbound media %s with non-MXC URL", event_id)
return
http_url = self._mxc_to_http(url) if url else ""
content_info = source_content.get("info", {})
if not isinstance(content_info, dict):
content_info = {}
@@ -2031,7 +2024,6 @@ class MatrixAdapter(BasePlatformAdapter):
if url and not str(url).startswith("mxc://"):
logger.warning("[Matrix] Rejecting inbound encrypted media %s with non-MXC URL", event_id)
return
http_url = self._mxc_to_http(url) if url else ""
is_encrypted_media = bool(file_content and isinstance(file_content, dict) and file_content.get("url"))
msg_type, media_type, is_voice_message = self._classify_inbound_media(msgtype, event_mimetype, source_content)
# Cache locally so downstream tools get a real file path.
@@ -2043,8 +2035,9 @@ class MatrixAdapter(BasePlatformAdapter):
is_voice_message, body)
except Exception as e:
logger.warning("[Matrix] Failed to cache media: %s", e)
allow_http_fallback = bool(http_url) and not is_encrypted_media
media_urls = [cached_path] if cached_path else ([http_url] if allow_http_fallback else None)
# Unencrypted media may fall back to the HTTP download URL when caching failed.
http_url = self._mxc_to_http(url) if url and not is_encrypted_media else ""
media_urls = [cached_path] if cached_path else ([http_url] if http_url else None)
msg_event = await self._build_inbound_event(
room_id, sender, event_id, body, source_content, relates_to, message_type=msg_type,
media_urls=media_urls, media_types=[media_type] if media_urls else None, media_msgtype=msgtype)
@@ -2073,11 +2066,9 @@ class MatrixAdapter(BasePlatformAdapter):
return None
if encrypted_file is not None:
from mautrix.crypto.attachments import decrypt_attachment
hashes_value = encrypted_file.get("hashes")
hashes_value, key_value = encrypted_file.get("hashes"), encrypted_file.get("key")
hash_value = hashes_value.get("sha256") if isinstance(hashes_value, dict) else None
key_value = encrypted_file.get("key")
if isinstance(key_value, dict):
key_value = key_value.get("k")
key_value = key_value.get("k") if isinstance(key_value, dict) else key_value
iv_value = encrypted_file.get("iv")
if not (key_value and hash_value and iv_value):
logger.warning("[Matrix] Encrypted media event missing decryption metadata for %s", event_id)
@@ -2110,10 +2101,8 @@ class MatrixAdapter(BasePlatformAdapter):
async def _join_room_by_id(self, room_id: str) -> bool:
"""Join a room by ID and refresh local caches on success."""
if not room_id:
return False
if room_id in self._joined_rooms:
return True
if not room_id or room_id in self._joined_rooms:
return bool(room_id)
try:
await self._client.join_room(RoomID(room_id))
self._joined_rooms.add(room_id)
@@ -2372,7 +2361,7 @@ class MatrixAdapter(BasePlatformAdapter):
room_id, target_event_id, "This model picker has expired. Run `/model` again to choose a model.")
async def _redact_bot_approval_reactions(self, room_id: str, prompt: Any) -> None:
"""Redact the bot's seeded approval reactions, leaving only the user's reaction."""
"""Redact the bot's seeded approval reactions (delayed), leaving only the user's reaction."""
for emoji, evt_id in prompt.bot_reaction_events.items():
self._schedule_reaction_redaction(room_id, evt_id, "approval resolved")
logger.debug("Matrix: scheduled bot reaction redaction %s (%s)", emoji, evt_id)
@@ -2418,8 +2407,7 @@ class MatrixAdapter(BasePlatformAdapter):
if not self._client:
return False
try:
room = RoomID(room_id)
event = EventID(event_id)
room, event = RoomID(room_id), EventID(event_id)
if hasattr(self._client, "set_fully_read_marker"):
await self._client.set_fully_read_marker(room, event, event)
elif hasattr(self._client, "send_receipt"):
@@ -2590,9 +2578,7 @@ class MatrixAdapter(BasePlatformAdapter):
return None
if hasattr(resp, "content") and (not require_dict or isinstance(resp.content, dict)):
return resp.content
if isinstance(resp, dict):
return resp
return None
return resp if isinstance(resp, dict) else None
async def _refresh_dm_cache(self) -> None:
"""Refresh the DM room cache from m.direct account data."""
@@ -2601,10 +2587,7 @@ class MatrixAdapter(BasePlatformAdapter):
dm_data = await self._fetch_m_direct(log_failure=True)
if dm_data is None:
return
dm_room_ids: Set[str] = set()
for rooms in dm_data.values():
if isinstance(rooms, list):
dm_room_ids.update(str(r) for r in rooms if isinstance(r, str))
dm_room_ids = {str(r) for rooms in dm_data.values() if isinstance(rooms, list) for r in rooms if isinstance(r, str)}
self._dm_rooms = {rid: (rid in dm_room_ids) for rid in self._joined_rooms}
self._invalidate_room_identities()
@@ -2616,8 +2599,7 @@ class MatrixAdapter(BasePlatformAdapter):
return
dm_data: Dict[str, list] = await self._fetch_m_direct(require_dict=True) or {}
rooms_for_user = dm_data.get(inviter, [])
if not isinstance(rooms_for_user, list):
rooms_for_user = []
rooms_for_user = rooms_for_user if isinstance(rooms_for_user, list) else []
if room_id not in rooms_for_user:
rooms_for_user.append(room_id)
dm_data[inviter] = rooms_for_user
@@ -2896,9 +2878,10 @@ def interactive_setup() -> None:
print_info("Matrix: already configured")
if not prompt_yes_no("Reconfigure Matrix?", False):
return
print_info("Works with any Matrix homeserver (Synapse, Conduit, Dendrite, or matrix.org).")
print_info(" 1. Create a bot user on your homeserver, or use your own account")
print_info(" 2. Get an access token from Element, or provide user ID + password")
for line in ("Works with any Matrix homeserver (Synapse, Conduit, Dendrite, or matrix.org).",
" 1. Create a bot user on your homeserver, or use your own account",
" 2. Get an access token from Element, or provide user ID + password"):
print_info(line)
def _ask(key: str, question: str, **kw) -> str:
value = prompt(question, **kw)
if value:
@@ -2940,10 +2923,11 @@ def interactive_setup() -> None:
print_success("Matrix allowlist configured")
else:
print_info("⚠️ No allowlist set - anyone who can message the bot can use it!")
print_info("📬 Home Room: where Hermes delivers cron job results and notifications.")
print_info(" Room IDs look like !abc123:server (shown in Element room settings)")
print_info(" You can also set this later by typing /set-home in a Matrix room.")
print_info("Leave blank to clear a previously saved home room (cron / notifications).")
for line in ("📬 Home Room: where Hermes delivers cron job results and notifications.",
" Room IDs look like !abc123:server (shown in Element room settings)",
" You can also set this later by typing /set-home in a Matrix room.",
"Leave blank to clear a previously saved home room (cron / notifications)."):
print_info(line)
home_room = prompt("Home room ID (leave empty to set later with /set-home)").strip()
if home_room:
save_env_value("MATRIX_HOME_ROOM", home_room)