matrix-nio's set_presence() doesn't raise on failure — it returns a PresenceSetError object. The code was ignoring the return value, so the bot would silently fail to go online even with valid credentials. https://claude.ai/code/session_019ANRdyL2jfi7ysWhqN4PfD
119 lines
3.3 KiB
Python
119 lines
3.3 KiB
Python
import asyncio
|
|
import logging
|
|
|
|
from nio import AsyncClient, PresenceSetError, RoomSendResponse
|
|
|
|
from app.config import config
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
_client: AsyncClient | None = None
|
|
_presence_task: asyncio.Task | None = None
|
|
|
|
PRESENCE_INTERVAL_SECONDS = 60
|
|
|
|
THREAD_ROOT_MESSAGES: dict[str, str] = {
|
|
"movies": "\U0001f3ac Movies",
|
|
"shows": "\U0001f4fa Shows",
|
|
"music": "\U0001f3b5 Music",
|
|
}
|
|
|
|
|
|
async def get_client() -> AsyncClient:
|
|
global _client
|
|
if _client is None:
|
|
_client = AsyncClient(config.MATRIX_HOMESERVER_URL)
|
|
_client.user_id = config.MATRIX_BOT_USER_ID
|
|
_client.access_token = config.MATRIX_BOT_ACCESS_TOKEN
|
|
return _client
|
|
|
|
|
|
async def close_client() -> None:
|
|
global _client, _presence_task
|
|
if _presence_task is not None:
|
|
_presence_task.cancel()
|
|
try:
|
|
await _presence_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
_presence_task = None
|
|
if _client is not None:
|
|
try:
|
|
await _set_presence("offline")
|
|
except Exception:
|
|
log.warning("Failed to set offline presence during shutdown", exc_info=True)
|
|
await _client.close()
|
|
_client = None
|
|
|
|
|
|
async def _set_presence(state: str) -> None:
|
|
client = await get_client()
|
|
resp = await client.set_presence(state)
|
|
if isinstance(resp, PresenceSetError):
|
|
raise RuntimeError(f"Presence update to '{state}' failed: {resp.message}")
|
|
|
|
|
|
async def _presence_loop() -> None:
|
|
while True:
|
|
await asyncio.sleep(PRESENCE_INTERVAL_SECONDS)
|
|
try:
|
|
await _set_presence("online")
|
|
except Exception:
|
|
log.warning("Presence heartbeat failed", exc_info=True)
|
|
|
|
|
|
async def start_presence() -> None:
|
|
global _presence_task
|
|
await _set_presence("online")
|
|
_presence_task = asyncio.create_task(_presence_loop())
|
|
log.info("Presence heartbeat started (every %ds)", PRESENCE_INTERVAL_SECONDS)
|
|
|
|
|
|
async def send_thread_root(media_type: str) -> str:
|
|
client = await get_client()
|
|
label = THREAD_ROOT_MESSAGES[media_type]
|
|
content = {
|
|
"msgtype": "m.text",
|
|
"body": label,
|
|
}
|
|
resp = await client.room_send(
|
|
config.MATRIX_ARRIVALS_ROOM_ID,
|
|
"m.room.message",
|
|
content,
|
|
)
|
|
if isinstance(resp, RoomSendResponse):
|
|
log.info("Created thread root for %s: %s", media_type, resp.event_id)
|
|
return resp.event_id
|
|
raise RuntimeError(f"Failed to create thread root for {media_type}: {resp}")
|
|
|
|
|
|
async def post_to_thread(
|
|
thread_root_event_id: str,
|
|
plain_body: str,
|
|
html_body: str,
|
|
) -> str | None:
|
|
client = await get_client()
|
|
content = {
|
|
"msgtype": "m.text",
|
|
"format": "org.matrix.custom.html",
|
|
"body": plain_body,
|
|
"formatted_body": html_body,
|
|
"m.relates_to": {
|
|
"rel_type": "m.thread",
|
|
"event_id": thread_root_event_id,
|
|
},
|
|
}
|
|
try:
|
|
resp = await client.room_send(
|
|
config.MATRIX_ARRIVALS_ROOM_ID,
|
|
"m.room.message",
|
|
content,
|
|
)
|
|
if isinstance(resp, RoomSendResponse):
|
|
return resp.event_id
|
|
log.error("Matrix send failed: %s", resp)
|
|
return None
|
|
except Exception:
|
|
log.exception("Matrix posting error")
|
|
return None
|