Expand Discord download and Jellyfin notifications
This commit is contained in:
+117
-6
@@ -20,7 +20,7 @@ from uuid import uuid4
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent
|
||||
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
|
||||
APP_NAME = "ani-cli-web"
|
||||
VERSION = "0.45.0"
|
||||
VERSION = "0.45.1"
|
||||
ALLANIME_BASE = "allanime.day"
|
||||
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
|
||||
ALLANIME_REFERER = "https://allmanga.to"
|
||||
@@ -55,6 +55,8 @@ WATCHLIST_REFRESH_DELAY_MIN_SECONDS = 0
|
||||
WATCHLIST_REFRESH_DELAY_MAX_SECONDS = 300
|
||||
DISCORD_WEBHOOK_EVENT_CHOICES = (
|
||||
"download_completed",
|
||||
"jellyfin_sync_success",
|
||||
"jellyfin_sync_failed",
|
||||
"watchlist_refresh_new_episodes",
|
||||
"watchlist_refresh_failed",
|
||||
"watchlist_refresh_interrupted",
|
||||
@@ -62,6 +64,8 @@ DISCORD_WEBHOOK_EVENT_CHOICES = (
|
||||
)
|
||||
DISCORD_WEBHOOK_EVENT_LABELS = {
|
||||
"download_completed": "Download completed",
|
||||
"jellyfin_sync_success": "Jellyfin move succeeded",
|
||||
"jellyfin_sync_failed": "Jellyfin move failed",
|
||||
"watchlist_refresh_new_episodes": "Refresh finished with new episodes found",
|
||||
"watchlist_refresh_failed": "Refresh failed or finished with refresh errors",
|
||||
"watchlist_refresh_interrupted": "Refresh interrupted by shutdown or restart",
|
||||
@@ -503,6 +507,61 @@ def _post_discord_webhook(url, payload, attachments=None, timeout=10):
|
||||
raise RuntimeError("Discord webhook request timed out") from exc
|
||||
|
||||
|
||||
def _chunk_discord_lines(lines, max_chars=3800):
|
||||
chunks = []
|
||||
current = []
|
||||
current_length = 0
|
||||
for raw_line in lines or []:
|
||||
line = str(raw_line or "").strip()
|
||||
if not line:
|
||||
continue
|
||||
added_length = len(line) + (1 if current else 0)
|
||||
if current and current_length + added_length > max_chars:
|
||||
chunks.append(current)
|
||||
current = [line]
|
||||
current_length = len(line)
|
||||
continue
|
||||
current.append(line)
|
||||
current_length += added_length
|
||||
if current:
|
||||
chunks.append(current)
|
||||
return chunks
|
||||
|
||||
|
||||
def _post_discord_embeds_with_file_list(webhook_url, content, summary_embed, file_lines, attachments=None):
|
||||
chunks = _chunk_discord_lines(file_lines)
|
||||
if not chunks:
|
||||
_post_discord_webhook(webhook_url, {"content": content, "embeds": [summary_embed]}, attachments=attachments)
|
||||
return
|
||||
total_chunks = len(chunks)
|
||||
first_batch = chunks[:9]
|
||||
request_embeds = [summary_embed]
|
||||
for index, chunk in enumerate(first_batch, start=1):
|
||||
request_embeds.append(
|
||||
{
|
||||
"title": f"Saved files ({index}/{total_chunks})",
|
||||
"description": "\n".join(chunk)[:4096],
|
||||
"color": summary_embed.get("color", 5763719),
|
||||
}
|
||||
)
|
||||
_post_discord_webhook(webhook_url, {"content": content, "embeds": request_embeds}, attachments=attachments)
|
||||
sent_chunks = len(first_batch)
|
||||
while sent_chunks < total_chunks:
|
||||
batch = chunks[sent_chunks : sent_chunks + 10]
|
||||
embeds = []
|
||||
for offset, chunk in enumerate(batch, start=1):
|
||||
chunk_index = sent_chunks + offset
|
||||
embeds.append(
|
||||
{
|
||||
"title": f"Saved files ({chunk_index}/{total_chunks})",
|
||||
"description": "\n".join(chunk)[:4096],
|
||||
"color": summary_embed.get("color", 5763719),
|
||||
}
|
||||
)
|
||||
_post_discord_webhook(webhook_url, {"content": content, "embeds": embeds})
|
||||
sent_chunks += len(batch)
|
||||
|
||||
|
||||
def send_discord_webhook_event(config, event_name, payload=None, force=False):
|
||||
normalized = normalize_config(config or {})
|
||||
event = str(event_name or "").strip()
|
||||
@@ -582,7 +641,6 @@ def send_discord_webhook_event(config, event_name, payload=None, force=False):
|
||||
description_lines.append(f"Watchlist category: {str(payload.get('category')).strip()}")
|
||||
if moved_files:
|
||||
description_lines.append(f"Saved files: {len(moved_files)}")
|
||||
description_lines.extend(str(path).strip() for path in moved_files[:5] if str(path).strip())
|
||||
embed = {
|
||||
"title": title[:256],
|
||||
"description": "\n".join(description_lines)[:4096] or "Download completed.",
|
||||
@@ -594,11 +652,64 @@ def send_discord_webhook_event(config, event_name, payload=None, force=False):
|
||||
attachment_name = f"download-thumb{thumb_path.suffix or '.jpg'}"
|
||||
attachments.append({"path": thumb_path, "filename": attachment_name})
|
||||
embed["thumbnail"] = {"url": f"attachment://{attachment_name}"}
|
||||
request_payload = {
|
||||
"content": "ani-cli-web finished a download.",
|
||||
"embeds": [embed],
|
||||
_post_discord_embeds_with_file_list(
|
||||
webhook_url,
|
||||
"ani-cli-web finished a download.",
|
||||
embed,
|
||||
moved_files,
|
||||
attachments=attachments,
|
||||
)
|
||||
return {"sent": True, "event": event}
|
||||
|
||||
if event == "jellyfin_sync_success":
|
||||
title = str(payload.get("title") or payload.get("show_id") or "Unknown anime").strip()
|
||||
moved_files = payload.get("moved_files") if isinstance(payload.get("moved_files"), list) else []
|
||||
description_lines = [
|
||||
f"Media type: {str(payload.get('media_type') or 'tv').strip()}",
|
||||
f"Source: {str(payload.get('source') or 'manual').strip()}",
|
||||
]
|
||||
if payload.get("target"):
|
||||
description_lines.append(f"Target: {str(payload.get('target')).strip()}")
|
||||
if moved_files:
|
||||
description_lines.append(f"Moved files: {len(moved_files)}")
|
||||
embed = {
|
||||
"title": f"Jellyfin handoff: {title[:236]}",
|
||||
"description": "\n".join(description_lines)[:4096],
|
||||
"color": 5763719,
|
||||
}
|
||||
_post_discord_webhook(webhook_url, request_payload, attachments=attachments)
|
||||
_post_discord_embeds_with_file_list(
|
||||
webhook_url,
|
||||
"ani-cli-web moved a finished library into Jellyfin.",
|
||||
embed,
|
||||
moved_files,
|
||||
)
|
||||
return {"sent": True, "event": event}
|
||||
|
||||
if event == "jellyfin_sync_failed":
|
||||
title = str(payload.get("title") or payload.get("show_id") or "Unknown anime").strip()
|
||||
reason = str(payload.get("reason") or "unknown").strip()
|
||||
description_lines = [
|
||||
f"Reason: {reason}",
|
||||
f"Media type: {str(payload.get('media_type') or 'tv').strip()}",
|
||||
f"Source: {str(payload.get('source') or 'manual').strip()}",
|
||||
]
|
||||
if payload.get("error"):
|
||||
description_lines.append(f"Error: {str(payload.get('error')).strip()}")
|
||||
if payload.get("source_path"):
|
||||
description_lines.append(f"Source path: {str(payload.get('source_path')).strip()}")
|
||||
if payload.get("target"):
|
||||
description_lines.append(f"Target path: {str(payload.get('target')).strip()}")
|
||||
request_payload = {
|
||||
"content": "ani-cli-web could not move a finished library into Jellyfin.",
|
||||
"embeds": [
|
||||
{
|
||||
"title": f"Jellyfin handoff failed: {title[:229]}",
|
||||
"description": "\n".join(description_lines)[:4096],
|
||||
"color": 15158332,
|
||||
}
|
||||
],
|
||||
}
|
||||
_post_discord_webhook(webhook_url, request_payload)
|
||||
return {"sent": True, "event": event}
|
||||
|
||||
if event == "watchlist_refresh_failed":
|
||||
|
||||
Reference in New Issue
Block a user