From 354d24a7f6fae325b08e259d450b6bcbc6d73d49 Mon Sep 17 00:00:00 2001 From: Froidefond Julien Date: Sat, 28 Mar 2026 14:07:02 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20notification=20Telegram=20=C3=A0=20la?= =?UTF-8?q?=20fin=20d'un=20import=20torrent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajoute les événements TorrentImportCompleted et TorrentImportFailed au système de notifications. Une notif Telegram est envoyée après l'import des fichiers dans la bibliothèque (succès ou erreur), avec le nom de la série, la bibliothèque et le nombre de fichiers. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/api/src/torrent_import.rs | 23 +++++++++++++-- crates/notifications/src/lib.rs | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/apps/api/src/torrent_import.rs b/apps/api/src/torrent_import.rs index 1d478cb..e55b446 100644 --- a/apps/api/src/torrent_import.rs +++ b/apps/api/src/torrent_import.rs @@ -410,8 +410,8 @@ async fn is_torrent_import_enabled(pool: &PgPool) -> bool { async fn process_torrent_import(pool: PgPool, torrent_id: Uuid) -> anyhow::Result<()> { let row = sqlx::query( - "SELECT library_id, series_name, expected_volumes, content_path, qb_hash, replace_existing \ - FROM torrent_downloads WHERE id = $1", + "SELECT td.library_id, td.series_name, td.expected_volumes, td.content_path, td.qb_hash, td.replace_existing, l.name AS library_name \ + FROM torrent_downloads td LEFT JOIN libraries l ON l.id = td.library_id WHERE td.id = $1", ) .bind(torrent_id) .fetch_one(&pool) @@ -419,6 +419,7 @@ async fn process_torrent_import(pool: PgPool, torrent_id: Uuid) -> anyhow::Resul let library_id: Uuid = row.get("library_id"); let series_name: String = row.get("series_name"); + let library_name: Option = row.get("library_name"); let expected_volumes: Vec = row.get("expected_volumes"); let content_path: Option = row.get("content_path"); let qb_hash: Option = row.get("qb_hash"); @@ -569,6 +570,15 @@ async fn process_torrent_import(pool: PgPool, torrent_id: Uuid) -> anyhow::Resul } } + notifications::notify( + pool.clone(), + notifications::NotificationEvent::TorrentImportCompleted { + library_name: library_name.clone(), + series_name: series_name.clone(), + imported_count: imported.len(), + }, + ); + info!( "Torrent import {} done: {} files imported, scan job {} queued", torrent_id, @@ -586,6 +596,15 @@ async fn process_torrent_import(pool: PgPool, torrent_id: Uuid) -> anyhow::Resul .bind(torrent_id) .execute(&pool) .await?; + + notifications::notify( + pool.clone(), + notifications::NotificationEvent::TorrentImportFailed { + library_name: library_name.clone(), + series_name: series_name.clone(), + error: msg, + }, + ); } } diff --git a/crates/notifications/src/lib.rs b/crates/notifications/src/lib.rs index 72fcecf..47b70d8 100644 --- a/crates/notifications/src/lib.rs +++ b/crates/notifications/src/lib.rs @@ -55,6 +55,10 @@ pub struct EventToggles { pub download_detection_completed: bool, #[serde(default = "default_true")] pub download_detection_failed: bool, + #[serde(default = "default_true")] + pub torrent_import_completed: bool, + #[serde(default = "default_true")] + pub torrent_import_failed: bool, } fn default_true() -> bool { @@ -81,6 +85,8 @@ fn default_events() -> EventToggles { reading_status_push_failed: true, download_detection_completed: true, download_detection_failed: true, + torrent_import_completed: true, + torrent_import_failed: true, } } @@ -296,6 +302,17 @@ pub enum NotificationEvent { library_name: Option, error: String, }, + // Torrent import (qBittorrent download completed → files imported into library) + TorrentImportCompleted { + library_name: Option, + series_name: String, + imported_count: usize, + }, + TorrentImportFailed { + library_name: Option, + series_name: String, + error: String, + }, } /// Classify an indexer job_type string into the right event constructor category. @@ -604,6 +621,38 @@ fn format_event(event: &NotificationEvent) -> String { ] .join("\n") } + NotificationEvent::TorrentImportCompleted { + library_name, + series_name, + imported_count, + } => { + let lib = library_name.as_deref().unwrap_or("Unknown"); + [ + format!("✅ Torrent import completed"), + String::new(), + format!("📂 Library: {lib}"), + format!("📚 Series: {series_name}"), + format!("📥 Files imported: {imported_count}"), + ] + .join("\n") + } + NotificationEvent::TorrentImportFailed { + library_name, + series_name, + error, + } => { + let lib = library_name.as_deref().unwrap_or("Unknown"); + let err = truncate(error, 200); + [ + format!("🚨 Torrent import failed"), + String::new(), + format!("📂 Library: {lib}"), + format!("📚 Series: {series_name}"), + String::new(), + format!("💬 {err}"), + ] + .join("\n") + } } } @@ -650,6 +699,8 @@ fn is_event_enabled(config: &TelegramConfig, event: &NotificationEvent) -> bool NotificationEvent::ReadingStatusPushFailed { .. } => config.events.reading_status_push_failed, NotificationEvent::DownloadDetectionCompleted { .. } => config.events.download_detection_completed, NotificationEvent::DownloadDetectionFailed { .. } => config.events.download_detection_failed, + NotificationEvent::TorrentImportCompleted { .. } => config.events.torrent_import_completed, + NotificationEvent::TorrentImportFailed { .. } => config.events.torrent_import_failed, } }