Compare commits
7 Commits
claude/med
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8bef94e7f | ||
|
|
e4c696d142 | ||
|
|
7cc5fcb514 | ||
|
|
dbaa7b0d8b | ||
|
|
d22fb6ae10 | ||
|
|
8d7a66c431 | ||
|
|
6812758af0 |
14
README.md
14
README.md
@@ -30,22 +30,30 @@ cp .env.example .env
|
||||
|
||||
Edit `.env` with your actual values (see [Environment Variables](#environment-variables) below).
|
||||
|
||||
### 2. Verify your configuration
|
||||
### 2. Set up a virtual environment
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. Verify your configuration
|
||||
|
||||
```bash
|
||||
python -m app --check
|
||||
```
|
||||
|
||||
This validates that all required environment variables are set, the Matrix homeserver is reachable, the bot token is valid, the bot has joined the announcements room, and the database path is writable. Fix any failing checks before starting the server.
|
||||
|
||||
### 3. Run locally
|
||||
### 4. Run locally
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
### 4. Run with Docker
|
||||
### 5. Run with Docker
|
||||
|
||||
```bash
|
||||
docker build -t melora .
|
||||
|
||||
@@ -9,17 +9,27 @@ def _strip_markdown(text: str) -> str:
|
||||
return text.replace("**", "").replace("*", "").replace("> ", "")
|
||||
|
||||
|
||||
def _extract_quality(quality_obj) -> str:
|
||||
"""Extract quality name from *arr quality field, which may be a string or nested dict."""
|
||||
if isinstance(quality_obj, str):
|
||||
return quality_obj
|
||||
if isinstance(quality_obj, dict):
|
||||
inner = quality_obj.get("quality", quality_obj)
|
||||
if isinstance(inner, str):
|
||||
return inner
|
||||
if isinstance(inner, dict):
|
||||
return inner.get("name", "Unknown")
|
||||
return "Unknown"
|
||||
|
||||
|
||||
def format_radarr(payload: dict) -> tuple[str, str]:
|
||||
movie = payload.get("movie", {})
|
||||
title = movie.get("title", "Unknown")
|
||||
year = movie.get("year", "")
|
||||
is_upgrade = payload.get("isUpgrade", False)
|
||||
|
||||
quality = (
|
||||
payload.get("movieFile", {})
|
||||
.get("quality", {})
|
||||
.get("quality", {})
|
||||
.get("name", "Unknown")
|
||||
quality = _extract_quality(
|
||||
payload.get("movieFile", {}).get("quality", {})
|
||||
)
|
||||
|
||||
if is_upgrade:
|
||||
@@ -52,11 +62,8 @@ def format_sonarr(payload: dict) -> tuple[str, str]:
|
||||
episode = ep.get("episodeNumber", 0)
|
||||
ep_title = ep.get("title", "")
|
||||
|
||||
quality = (
|
||||
payload.get("episodeFile", {})
|
||||
.get("quality", {})
|
||||
.get("quality", {})
|
||||
.get("name", "Unknown")
|
||||
quality = _extract_quality(
|
||||
payload.get("episodeFile", {}).get("quality", {})
|
||||
)
|
||||
|
||||
header = f"\U0001f4fa **{series_title}** \u2014 S{season:02d}E{episode:02d}"
|
||||
@@ -91,11 +98,7 @@ def format_lidarr(payload: dict) -> tuple[str, str]:
|
||||
|
||||
track_files = payload.get("trackFiles", [{}])
|
||||
tf = track_files[0] if track_files else {}
|
||||
quality = (
|
||||
tf.get("quality", {})
|
||||
.get("quality", {})
|
||||
.get("name", "Unknown")
|
||||
)
|
||||
quality = _extract_quality(tf.get("quality", {}))
|
||||
|
||||
header = f"\U0001f3b5 **{artist_name}**"
|
||||
if album_title:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from nio import AsyncClient, RoomSendResponse
|
||||
from nio import AsyncClient, PresenceSetError, RoomSendResponse
|
||||
|
||||
from app.config import config
|
||||
|
||||
@@ -38,23 +38,28 @@ async def close_client() -> None:
|
||||
pass
|
||||
_presence_task = None
|
||||
if _client is not None:
|
||||
await _set_presence("offline")
|
||||
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()
|
||||
try:
|
||||
await client.set_presence(state)
|
||||
except Exception:
|
||||
log.debug("Presence update failed", exc_info=True)
|
||||
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 _set_presence("online")
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user