117 lines
4.1 KiB
Python
117 lines
4.1 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}]\n{notification.content}"
|
|
# HTML message
|
|
html_message = f"<b>[{notification.service_name}]</b><br>{notification.content}"
|
|
|
|
# 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
|
|
content = ""
|
|
if payload.item_type == "Movie":
|
|
content = f"New Movie: {payload.name}"
|
|
if payload.year:
|
|
content += f" ({payload.year})"
|
|
if payload.overview:
|
|
content += f"\n{payload.overview}"
|
|
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??"
|
|
content = f"New Episode: {show} - {s}{e} - {payload.name}"
|
|
else:
|
|
# Fallback for Series, Season, etc.
|
|
content = f"New {payload.item_type}: {payload.name}"
|
|
|
|
plain_message = f"[Jellyfin]\n{content}"
|
|
html_message = f"<b>[Jellyfin]</b><br>{content.replace(chr(10), '<br>')}"
|
|
|
|
logger.info(f"Queueing Jellyfin message for room_id: {payload.room_id or 'Default'}")
|
|
background_tasks.add_task(bot.send_message, plain_message, html_message, 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"}
|