fix(voice): add MSC4143 call.member encryption key support
Element Call v0.17+ embeds encryption_keys in call.member state events instead of separate timeline events. In E2EE rooms, timeline events are encrypted and the bot HTTP fetch cannot decrypt them, causing DEC_FAILED. - Extract caller keys from call.member state event on join - Embed bot key in call.member state event - Check call.member state in key fetch (before timeline fallback) - Handle key updates in call.member during active calls - Update voice.py key poller to check call.member state first - Add debug logging for UnknownEvent types in call rooms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
voice.py
41
voice.py
@@ -543,13 +543,50 @@ class VoiceSession:
|
||||
break
|
||||
|
||||
async def _fetch_encryption_key_http(self) -> bytes | None:
|
||||
"""Fetch encryption key from room timeline (NOT state) via Matrix HTTP API.
|
||||
"""Fetch encryption key from call.member state (MSC4143) or timeline (legacy).
|
||||
|
||||
Element Call distributes encryption keys as timeline events, not state.
|
||||
MSC4143: encryption_keys in org.matrix.msc3401.call.member state
|
||||
Legacy: io.element.call.encryption_keys timeline events
|
||||
"""
|
||||
import httpx
|
||||
homeserver = str(self.nio_client.homeserver)
|
||||
token = self.nio_client.access_token
|
||||
user_id = self.nio_client.user_id
|
||||
|
||||
# MSC4143: check call.member state events first
|
||||
try:
|
||||
state_url = f"{homeserver}/_matrix/client/v3/rooms/{self.room_id}/state"
|
||||
async with httpx.AsyncClient(timeout=10.0) as http:
|
||||
resp = await http.get(state_url, headers={"Authorization": f"Bearer {token}"})
|
||||
if resp.status_code == 200:
|
||||
for evt in resp.json():
|
||||
if evt.get("type") != "org.matrix.msc3401.call.member":
|
||||
continue
|
||||
sender = evt.get("sender", "")
|
||||
if sender == user_id:
|
||||
continue
|
||||
content = evt.get("content", {})
|
||||
enc_keys = content.get("encryption_keys", [])
|
||||
if enc_keys:
|
||||
device = content.get("device_id", "")
|
||||
import base64 as b64
|
||||
for k in enc_keys:
|
||||
key_b64 = k.get("key", "")
|
||||
key_index = k.get("index", 0)
|
||||
if key_b64:
|
||||
key_b64 += "=" * (-len(key_b64) % 4)
|
||||
key_bytes = b64.urlsafe_b64decode(key_b64)
|
||||
if device:
|
||||
self._caller_identity = f"{sender}:{device}"
|
||||
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)
|
||||
return latest
|
||||
except Exception as e:
|
||||
logger.debug("call.member state key fetch failed: %s", e)
|
||||
|
||||
# Legacy: timeline scan
|
||||
url = f"{homeserver}/_matrix/client/v3/rooms/{self.room_id}/messages"
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as http:
|
||||
|
||||
Reference in New Issue
Block a user