fix: handle Element X MSC4143 v2 encryption key format (memberships array)

Element X embeds E2EE keys inside memberships[].encryption_keys,
not at the top level of the call.member state event content.
Bot was only checking content.encryption_keys, so it never found
the caller's key — causing 'Warten auf Medien' (waiting for media)
because encrypted audio couldn't be decrypted.

- Added _extract_enc_keys_from_content() helper handling both formats
- Updated on_unknown handler, VoiceSession creation, and key fetch
- Bot now publishes keys in both formats for compatibility
- Updated voice.py state fetch to check memberships[] fallback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-03-24 08:57:24 +02:00
parent 3363b4238f
commit c11dd73ce3
2 changed files with 81 additions and 36 deletions

View File

@@ -554,6 +554,7 @@ class VoiceSession:
user_id = self.nio_client.user_id
# MSC4143: check call.member state events first
# Handles both top-level encryption_keys and memberships[].encryption_keys (Element X)
try:
state_url = f"{homeserver}/_matrix/client/v3/rooms/{self.room_id}/state"
async with httpx.AsyncClient(timeout=10.0) as http:
@@ -566,7 +567,17 @@ class VoiceSession:
if sender == user_id:
continue
content = evt.get("content", {})
# Extract keys from both formats
enc_keys = content.get("encryption_keys", [])
# MSC4143 v2 / Element X: keys inside memberships array
if not enc_keys:
for m in content.get("memberships", []):
m_keys = m.get("encryption_keys", [])
if m_keys:
enc_keys = m_keys
# Use device_id from membership entry
content = {**content, "device_id": m.get("device_id", content.get("device_id", ""))}
break
if enc_keys:
device = content.get("device_id", "")
import base64 as b64
@@ -581,7 +592,8 @@ class VoiceSession:
self.on_encryption_key(sender, device, key_bytes, key_index)
max_idx = max(self._caller_all_keys.keys()) if self._caller_all_keys else key_index
latest = self._caller_all_keys.get(max_idx, key_bytes)
logger.info("Got key from call.member state (sender=%s, index=%d)", sender, key_index)
logger.info("Got key from call.member state (sender=%s, device=%s, index=%d)",
sender, device, key_index)
return latest
except Exception as e:
logger.debug("call.member state key fetch failed: %s", e)