diff --git a/crates/notifications/src/lib.rs b/crates/notifications/src/lib.rs
index 9b3e1fe..e415e6c 100644
--- a/crates/notifications/src/lib.rs
+++ b/crates/notifications/src/lib.rs
@@ -161,7 +161,13 @@ async fn send_telegram_photo(config: &TelegramConfig, caption: &str, photo_path:
/// Send a test message. Returns the result directly (not fire-and-forget).
pub async fn send_test_message(config: &TelegramConfig) -> Result<()> {
- send_telegram(config, "🔔 Stripstream Librarian\nTest notification — connection OK!").await
+ send_telegram(
+ config,
+ "🔔 Stripstream Librarian\n\
+ ━━━━━━━━━━━━━━━━━━━━\n\
+ ✅ Test notification — connection OK!",
+ )
+ .await
}
// ---------------------------------------------------------------------------
@@ -265,22 +271,23 @@ fn format_event(event: &NotificationEvent) -> String {
} => {
let lib = library_name.as_deref().unwrap_or("All libraries");
let duration = format_duration(*duration_seconds);
- format!(
- "📚 Scan completed\n\
- Library: {lib}\n\
- Type: {job_type}\n\
- New books: {}\n\
- New series: {}\n\
- Files scanned: {}\n\
- Removed: {}\n\
- Errors: {}\n\
- Duration: {duration}",
- stats.indexed_files,
- stats.new_series,
- stats.scanned_files,
- stats.removed_files,
- stats.errors,
- )
+ let mut lines = vec![
+ format!("✅ Scan completed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ format!("🏷 Type: {job_type}"),
+ format!("⏱ Duration: {duration}"),
+ String::new(),
+ format!("📊 Results"),
+ format!(" 📗 New books: {}", stats.indexed_files),
+ format!(" 📚 New series: {}", stats.new_series),
+ format!(" 🔎 Files scanned: {}", stats.scanned_files),
+ format!(" 🗑 Removed: {}", stats.removed_files),
+ ];
+ if stats.errors > 0 {
+ lines.push(format!(" ⚠️ Errors: {}", stats.errors));
+ }
+ lines.join("\n")
}
NotificationEvent::ScanFailed {
job_type,
@@ -289,23 +296,28 @@ fn format_event(event: &NotificationEvent) -> String {
} => {
let lib = library_name.as_deref().unwrap_or("All libraries");
let err = truncate(error, 200);
- format!(
- "❌ Scan failed\n\
- Library: {lib}\n\
- Type: {job_type}\n\
- Error: {err}"
- )
+ [
+ format!("🚨 Scan failed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ format!("🏷 Type: {job_type}"),
+ String::new(),
+ format!("💬 {err}"),
+ ]
+ .join("\n")
}
NotificationEvent::ScanCancelled {
job_type,
library_name,
} => {
let lib = library_name.as_deref().unwrap_or("All libraries");
- format!(
- "⏹ Scan cancelled\n\
- Library: {lib}\n\
- Type: {job_type}"
- )
+ [
+ format!("⏹ Scan cancelled"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ format!("🏷 Type: {job_type}"),
+ ]
+ .join("\n")
}
NotificationEvent::ThumbnailCompleted {
job_type,
@@ -314,12 +326,14 @@ fn format_event(event: &NotificationEvent) -> String {
} => {
let lib = library_name.as_deref().unwrap_or("All libraries");
let duration = format_duration(*duration_seconds);
- format!(
- "🖼 Thumbnails completed\n\
- Library: {lib}\n\
- Type: {job_type}\n\
- Duration: {duration}"
- )
+ [
+ format!("✅ Thumbnails completed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ format!("🏷 Type: {job_type}"),
+ format!("⏱ Duration: {duration}"),
+ ]
+ .join("\n")
}
NotificationEvent::ThumbnailFailed {
job_type,
@@ -328,12 +342,15 @@ fn format_event(event: &NotificationEvent) -> String {
} => {
let lib = library_name.as_deref().unwrap_or("All libraries");
let err = truncate(error, 200);
- format!(
- "❌ Thumbnails failed\n\
- Library: {lib}\n\
- Type: {job_type}\n\
- Error: {err}"
- )
+ [
+ format!("🚨 Thumbnails failed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ format!("🏷 Type: {job_type}"),
+ String::new(),
+ format!("💬 {err}"),
+ ]
+ .join("\n")
}
NotificationEvent::ConversionCompleted {
library_name,
@@ -342,11 +359,13 @@ fn format_event(event: &NotificationEvent) -> String {
} => {
let lib = library_name.as_deref().unwrap_or("Unknown");
let title = book_title.as_deref().unwrap_or("Unknown");
- format!(
- "🔄 CBR→CBZ conversion completed\n\
- Library: {lib}\n\
- Book: {title}"
- )
+ [
+ format!("✅ CBR → CBZ conversion completed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ format!("📖 Book: {title}"),
+ ]
+ .join("\n")
}
NotificationEvent::ConversionFailed {
library_name,
@@ -357,23 +376,28 @@ fn format_event(event: &NotificationEvent) -> String {
let lib = library_name.as_deref().unwrap_or("Unknown");
let title = book_title.as_deref().unwrap_or("Unknown");
let err = truncate(error, 200);
- format!(
- "❌ CBR→CBZ conversion failed\n\
- Library: {lib}\n\
- Book: {title}\n\
- Error: {err}"
- )
+ [
+ format!("🚨 CBR → CBZ conversion failed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ format!("📖 Book: {title}"),
+ String::new(),
+ format!("💬 {err}"),
+ ]
+ .join("\n")
}
NotificationEvent::MetadataApproved {
series_name,
provider,
..
} => {
- format!(
- "🔗 Metadata linked\n\
- Series: {series_name}\n\
- Provider: {provider}"
- )
+ [
+ format!("✅ Metadata linked"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📚 Series: {series_name}"),
+ format!("🔗 Provider: {provider}"),
+ ]
+ .join("\n")
}
NotificationEvent::MetadataBatchCompleted {
library_name,
@@ -381,11 +405,13 @@ fn format_event(event: &NotificationEvent) -> String {
processed,
} => {
let lib = library_name.as_deref().unwrap_or("All libraries");
- format!(
- "🔍 Metadata batch completed\n\
- Library: {lib}\n\
- Series processed: {processed}/{total_series}"
- )
+ [
+ format!("✅ Metadata batch completed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ format!("📊 Processed: {processed}/{total_series} series"),
+ ]
+ .join("\n")
}
NotificationEvent::MetadataBatchFailed {
library_name,
@@ -393,11 +419,14 @@ fn format_event(event: &NotificationEvent) -> String {
} => {
let lib = library_name.as_deref().unwrap_or("All libraries");
let err = truncate(error, 200);
- format!(
- "❌ Metadata batch failed\n\
- Library: {lib}\n\
- Error: {err}"
- )
+ [
+ format!("🚨 Metadata batch failed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ String::new(),
+ format!("💬 {err}"),
+ ]
+ .join("\n")
}
NotificationEvent::MetadataRefreshCompleted {
library_name,
@@ -406,13 +435,19 @@ fn format_event(event: &NotificationEvent) -> String {
errors,
} => {
let lib = library_name.as_deref().unwrap_or("All libraries");
- format!(
- "🔄 Metadata refresh completed\n\
- Library: {lib}\n\
- Updated: {refreshed}\n\
- Unchanged: {unchanged}\n\
- Errors: {errors}"
- )
+ let mut lines = vec![
+ format!("✅ Metadata refresh completed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ String::new(),
+ format!("📊 Results"),
+ format!(" 🔄 Updated: {refreshed}"),
+ format!(" ▪️ Unchanged: {unchanged}"),
+ ];
+ if *errors > 0 {
+ lines.push(format!(" ⚠️ Errors: {errors}"));
+ }
+ lines.join("\n")
}
NotificationEvent::MetadataRefreshFailed {
library_name,
@@ -420,11 +455,14 @@ fn format_event(event: &NotificationEvent) -> String {
} => {
let lib = library_name.as_deref().unwrap_or("All libraries");
let err = truncate(error, 200);
- format!(
- "❌ Metadata refresh failed\n\
- Library: {lib}\n\
- Error: {err}"
- )
+ [
+ format!("🚨 Metadata refresh failed"),
+ format!("━━━━━━━━━━━━━━━━━━━━"),
+ format!("📂 Library: {lib}"),
+ String::new(),
+ format!("💬 {err}"),
+ ]
+ .join("\n")
}
}
}