151 lines
5.3 KiB
Python
151 lines
5.3 KiB
Python
from fastapi import FastAPI, HTTPException, BackgroundTasks, Request
|
||
from pydantic import BaseModel
|
||
from bot import MatrixBot
|
||
import asyncio
|
||
from contextlib import asynccontextmanager
|
||
import logging
|
||
import sys
|
||
|
||
# Configure logging to output to stdout
|
||
logging.basicConfig(
|
||
stream=sys.stdout,
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||
)
|
||
logger = logging.getLogger("matrix_manager")
|
||
|
||
bot = MatrixBot()
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
# Startup
|
||
logger.info("Starting up Matrix Manager Service...")
|
||
try:
|
||
await bot.login()
|
||
logger.info("Bot logged in successfully.")
|
||
except Exception as e:
|
||
logger.error(f"Failed to login bot: {e}")
|
||
yield
|
||
# Shutdown
|
||
logger.info("Shutting down Matrix Manager Service...")
|
||
await bot.close()
|
||
|
||
app = FastAPI(lifespan=lifespan)
|
||
|
||
class Notification(BaseModel):
|
||
service_name: str
|
||
content: str
|
||
room_id: str | None = None
|
||
level: str = "info"
|
||
|
||
class JellyfinPayload(BaseModel):
|
||
notification_type: str
|
||
item_type: str
|
||
name: str
|
||
series_name: str | None = None
|
||
season: int | None = None
|
||
episode: int | None = None
|
||
year: int | None = None
|
||
overview: str | None = None
|
||
room_id: str | None = None
|
||
|
||
@app.post("/notify")
|
||
async def send_notification(notification: Notification, background_tasks: BackgroundTasks):
|
||
"""
|
||
Send a notification to a Matrix room.
|
||
"""
|
||
logger.info(f"Received notification request from service: {notification.service_name}")
|
||
try:
|
||
# Format the message to include the service name
|
||
# Plain text fallback
|
||
plain_message = f"[{notification.service_name}] {notification.content}"
|
||
|
||
# HTML message with better formatting
|
||
# Map levels to emojis
|
||
level_emojis = {
|
||
"info": "ℹ️",
|
||
"warning": "⚠️",
|
||
"error": "🚨",
|
||
"success": "✅"
|
||
}
|
||
emoji = level_emojis.get(notification.level, "📢")
|
||
# Clean up content
|
||
cleaned_content = notification.content.replace("{album_explicit}", "")
|
||
|
||
# Format the message to include the service name
|
||
# Plain text fallback
|
||
plain_message = f"[{notification.service_name}] {cleaned_content}"
|
||
|
||
# HTML message with better formatting
|
||
|
||
html_message = (
|
||
f"<h4>{emoji} {notification.service_name}</h4>"
|
||
f"<blockquote>{cleaned_content.replace(chr(10), '<br>')}</blockquote>"
|
||
)
|
||
|
||
# We can send it in background to not block the API response
|
||
logger.info(f"Queueing message for room_id: {notification.room_id or 'Default'}")
|
||
background_tasks.add_task(bot.send_message, plain_message, html_message, notification.room_id)
|
||
return {"status": "queued"}
|
||
except Exception as e:
|
||
logger.error(f"Error in send_notification: {e}")
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
@app.post("/jellyfin")
|
||
async def receive_jellyfin_webhook(payload: JellyfinPayload, background_tasks: BackgroundTasks):
|
||
"""
|
||
Receive webhook from Jellyfin and forward to Matrix.
|
||
"""
|
||
logger.info(f"Received Jellyfin webhook. Type: {payload.notification_type}, Item: {payload.item_type}")
|
||
try:
|
||
if payload.notification_type != "ItemAdded":
|
||
logger.info("Ignored Jellyfin event (not ItemAdded)")
|
||
return {"status": "ignored", "reason": "Not an ItemAdded event"}
|
||
|
||
# content construction
|
||
plain_content = ""
|
||
html_content = ""
|
||
|
||
if payload.item_type == "Movie":
|
||
plain_content = f"New Movie: {payload.name}"
|
||
html_content = f"<h4>🎬 New Movie Added</h4><b>{payload.name}</b>"
|
||
|
||
if payload.year:
|
||
plain_content += f" ({payload.year})"
|
||
html_content += f" ({payload.year})"
|
||
if payload.overview:
|
||
plain_content += f"\n{payload.overview}"
|
||
html_content += f"<blockquote>{payload.overview}</blockquote>"
|
||
|
||
elif payload.item_type == "Episode":
|
||
show = payload.series_name or "Unknown Series"
|
||
s = f"S{payload.season:02d}" if payload.season is not None else "S??"
|
||
e = f"E{payload.episode:02d}" if payload.episode is not None else "E??"
|
||
|
||
plain_content = f"New Episode: {show} - {s}{e} - {payload.name}"
|
||
html_content = (
|
||
f"<h4>📺 New Episode Added</h4>"
|
||
f"<b>{show}</b><br>"
|
||
f"{s}{e} - {payload.name}"
|
||
)
|
||
else:
|
||
# Fallback for Series, Season, etc.
|
||
plain_content = f"New {payload.item_type}: {payload.name}"
|
||
html_content = f"<h4>✨ New {payload.item_type} Added</h4><b>{payload.name}</b>"
|
||
|
||
plain_message = f"[Jellyfin] {plain_content}"
|
||
|
||
logger.info(f"Queueing Jellyfin message for room_id: {payload.room_id or 'Default'}")
|
||
background_tasks.add_task(bot.send_message, plain_message, html_content, payload.room_id)
|
||
return {"status": "queued"}
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in receive_jellyfin_webhook: {e}")
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
|
||
@app.get("/health")
|
||
async def health_check():
|
||
logger.info("Health check requested")
|
||
return {"status": "ok"}
|