Replace Bellhop scaffolding with full Melora implementation: - FastAPI webhook endpoints for Radarr, Sonarr, and Lidarr - matrix-nio integration with threaded room posting - SQLite for thread root persistence and event deduplication - Message formatting with plain text and HTML for each media type - Shared secret authentication via X-Arr-Webhook-Secret header - Updated dependencies, configuration, and documentation https://claude.ai/code/session_01DuzWyMMXvLMB4VxEwJyV4X
81 lines
2.1 KiB
Python
81 lines
2.1 KiB
Python
import logging
|
|
|
|
from nio import AsyncClient, RoomSendResponse
|
|
|
|
from app.config import config
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
_client: AsyncClient | None = None
|
|
|
|
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
|
|
if _client is not None:
|
|
await _client.close()
|
|
_client = None
|
|
|
|
|
|
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
|