"""Matrix bot adapter for Sénia. Runs as a long-lived asyncio task alongside the API server. Watches one room, forwards messages to `handle_turn`, posts the reply back. """ import asyncio import logging import os from collections import deque from nio import AsyncClient, MatrixRoom, RoomMessageText from .loop import handle_turn from .models import Message log = logging.getLogger(__name__) HOMESERVER = os.environ.get("MATRIX_HOMESERVER", "https://beep.volfaria.xyz") USER_ID = os.environ.get("MATRIX_USER_ID", "@senia:beep.volfaria.xyz") TOKEN = os.environ["MATRIX_TOKEN"] ROOM = os.environ.get("MATRIX_ROOM", "#senia:beep.volfaria.xyz") SENIA_NAME = "Sénia" HISTORY_LEN = 30 class MatrixBot: def __init__(self): self.client = AsyncClient(HOMESERVER, USER_ID) self.history: deque[Message] = deque(maxlen=HISTORY_LEN) self._room_id: str | None = None async def start(self): whoami = await self.client.whoami() self.client.restore_login( user_id=USER_ID, device_id=whoami.device_id, access_token=TOKEN ) self._room_id = await self._resolve_room(ROOM) await self.client.join(self._room_id) self.client.add_event_callback(self._on_message, RoomMessageText) log.info("Sénia matrix bot connected, watching %s", self._room_id) await self.client.sync_forever(timeout=30000, full_state=True) async def _resolve_room(self, room: str) -> str: if room.startswith("!"): return room resp = await self.client.room_resolve_alias(room) return resp.room_id async def _on_message(self, room: MatrixRoom, event: RoomMessageText): if event.sender == self.client.user_id or room.room_id != self._room_id: return sender_name = room.user_name(event.sender) or event.sender self.history.append(Message(role="user", content=event.body, name=sender_name)) try: reply = await asyncio.to_thread(handle_turn, list(self.history), sender_name) except Exception: log.exception("handle_turn failed") return self.history.append(Message(role="assistant", content=reply, name=SENIA_NAME)) await self.client.room_send( room_id=self._room_id, message_type="m.room.message", content={"msgtype": "m.text", "body": reply}, ) async def run(): bot = MatrixBot() await bot.start()