Files
Melora/app/database.py
Claude aaacd0b9bf Implement Melora: *arr media arrival webhook announcer for Matrix
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
2026-02-28 07:38:07 +00:00

86 lines
2.2 KiB
Python

import aiosqlite
from app.config import config
_db: aiosqlite.Connection | None = None
async def get_db() -> aiosqlite.Connection:
global _db
if _db is None:
raise RuntimeError("Database not initialised — call init_db() first")
return _db
async def init_db() -> None:
global _db
_db = await aiosqlite.connect(config.DATABASE_PATH)
_db.row_factory = aiosqlite.Row
await _db.execute(
"""
CREATE TABLE IF NOT EXISTS thread_roots (
media_type TEXT PRIMARY KEY,
event_id TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
)
await _db.execute(
"""
CREATE TABLE IF NOT EXISTS posted_events (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
media_type TEXT NOT NULL,
is_upgrade BOOLEAN NOT NULL,
posted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
)
await _db.commit()
async def close_db() -> None:
global _db
if _db is not None:
await _db.close()
_db = None
async def get_thread_root(media_type: str) -> str | None:
db = await get_db()
cursor = await db.execute(
"SELECT event_id FROM thread_roots WHERE media_type = ?",
(media_type,),
)
row = await cursor.fetchone()
return row["event_id"] if row else None
async def set_thread_root(media_type: str, event_id: str) -> None:
db = await get_db()
await db.execute(
"INSERT OR REPLACE INTO thread_roots (media_type, event_id) VALUES (?, ?)",
(media_type, event_id),
)
await db.commit()
async def is_event_posted(event_key: str) -> bool:
db = await get_db()
cursor = await db.execute(
"SELECT 1 FROM posted_events WHERE id = ?",
(event_key,),
)
return await cursor.fetchone() is not None
async def record_posted_event(
event_key: str, title: str, media_type: str, is_upgrade: bool
) -> None:
db = await get_db()
await db.execute(
"INSERT OR IGNORE INTO posted_events (id, title, media_type, is_upgrade) VALUES (?, ?, ?, ?)",
(event_key, title, media_type, is_upgrade),
)
await db.commit()