diff --git a/bot.py b/bot.py index d1a0d57..bc43562 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,5 @@ import os +import json import asyncio import logging @@ -27,6 +28,7 @@ LK_KEY = os.environ["LIVEKIT_API_KEY"] LK_SECRET = os.environ["LIVEKIT_API_SECRET"] AGENT_NAME = os.environ.get("AGENT_NAME", "matrix-ai") STORE_PATH = os.environ.get("CRYPTO_STORE_PATH", "/data/crypto_store") +CREDS_FILE = os.path.join(STORE_PATH, "credentials.json") class Bot: @@ -47,13 +49,31 @@ class Bot: self.dispatched_rooms = set() async def start(self): - resp = await self.client.login(BOT_PASS, device_name="ai-voice-bot") - if not isinstance(resp, LoginResponse): - logger.error("Login failed: %s", resp) - return - logger.info("Logged in as %s (device %s)", resp.user_id, resp.device_id) + # Restore existing session or create new one + if os.path.exists(CREDS_FILE): + with open(CREDS_FILE) as f: + creds = json.load(f) + self.client.restore_login( + user_id=creds["user_id"], + device_id=creds["device_id"], + access_token=creds["access_token"], + ) + self.client.load_store() + logger.info("Restored session as %s (device %s)", creds["user_id"], creds["device_id"]) + else: + resp = await self.client.login(BOT_PASS, device_name="ai-voice-bot") + if not isinstance(resp, LoginResponse): + logger.error("Login failed: %s", resp) + return + # Persist credentials for next restart + with open(CREDS_FILE, "w") as f: + json.dump({ + "user_id": resp.user_id, + "device_id": resp.device_id, + "access_token": resp.access_token, + }, f) + logger.info("Logged in as %s (device %s) — credentials saved", resp.user_id, resp.device_id) - # Trust our own device keys if self.client.should_upload_keys: await self.client.keys_upload() @@ -74,7 +94,6 @@ class Bot: logger.info("Invited to %s", room.room_id) await self.client.join(room.room_id) - # LiveKit room name = Matrix room ID (Element Call convention) lk_room_name = room.room_id try: await self.lkapi.agent_dispatch.create_dispatch( @@ -90,17 +109,17 @@ class Bot: async def on_sync(self, response: SyncResponse): """After each sync, trust all devices in our rooms.""" - for user_id in self.client.device_store.users: + for user_id in list(self.client.device_store.users): for device in self.client.device_store.active_user_devices(user_id): if not device.verified: self.client.verify_device(device) logger.info("Auto-trusted device %s of %s", device.device_id, user_id) async def on_megolm(self, room, event: MegolmEvent): - """Handle undecryptable messages by requesting keys.""" + """Log undecryptable messages.""" logger.warning( - "Can't decrypt event %s in %s from %s (session %s)", - event.event_id, room.room_id, event.sender, event.session_id, + "Undecryptable event %s in %s from %s", + event.event_id, room.room_id, event.sender, ) async def on_key_verification(self, event):