feat: improve Telegram notification UI with better formatting
Add visual separators, contextual emojis, bold labels, structured result sections, and conditional error lines for cleaner messages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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).
|
/// Send a test message. Returns the result directly (not fire-and-forget).
|
||||||
pub async fn send_test_message(config: &TelegramConfig) -> Result<()> {
|
pub async fn send_test_message(config: &TelegramConfig) -> Result<()> {
|
||||||
send_telegram(config, "🔔 <b>Stripstream Librarian</b>\nTest notification — connection OK!").await
|
send_telegram(
|
||||||
|
config,
|
||||||
|
"🔔 <b>Stripstream Librarian</b>\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 lib = library_name.as_deref().unwrap_or("All libraries");
|
||||||
let duration = format_duration(*duration_seconds);
|
let duration = format_duration(*duration_seconds);
|
||||||
format!(
|
let mut lines = vec![
|
||||||
"📚 <b>Scan completed</b>\n\
|
format!("✅ <b>Scan completed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Type: {job_type}\n\
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
New books: {}\n\
|
format!("🏷 <b>Type:</b> {job_type}"),
|
||||||
New series: {}\n\
|
format!("⏱ <b>Duration:</b> {duration}"),
|
||||||
Files scanned: {}\n\
|
String::new(),
|
||||||
Removed: {}\n\
|
format!("📊 <b>Results</b>"),
|
||||||
Errors: {}\n\
|
format!(" 📗 New books: <b>{}</b>", stats.indexed_files),
|
||||||
Duration: {duration}",
|
format!(" 📚 New series: <b>{}</b>", stats.new_series),
|
||||||
stats.indexed_files,
|
format!(" 🔎 Files scanned: <b>{}</b>", stats.scanned_files),
|
||||||
stats.new_series,
|
format!(" 🗑 Removed: <b>{}</b>", stats.removed_files),
|
||||||
stats.scanned_files,
|
];
|
||||||
stats.removed_files,
|
if stats.errors > 0 {
|
||||||
stats.errors,
|
lines.push(format!(" ⚠️ Errors: <b>{}</b>", stats.errors));
|
||||||
)
|
}
|
||||||
|
lines.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::ScanFailed {
|
NotificationEvent::ScanFailed {
|
||||||
job_type,
|
job_type,
|
||||||
@@ -289,23 +296,28 @@ fn format_event(event: &NotificationEvent) -> String {
|
|||||||
} => {
|
} => {
|
||||||
let lib = library_name.as_deref().unwrap_or("All libraries");
|
let lib = library_name.as_deref().unwrap_or("All libraries");
|
||||||
let err = truncate(error, 200);
|
let err = truncate(error, 200);
|
||||||
format!(
|
[
|
||||||
"❌ <b>Scan failed</b>\n\
|
format!("🚨 <b>Scan failed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Type: {job_type}\n\
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
Error: {err}"
|
format!("🏷 <b>Type:</b> {job_type}"),
|
||||||
)
|
String::new(),
|
||||||
|
format!("💬 <code>{err}</code>"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::ScanCancelled {
|
NotificationEvent::ScanCancelled {
|
||||||
job_type,
|
job_type,
|
||||||
library_name,
|
library_name,
|
||||||
} => {
|
} => {
|
||||||
let lib = library_name.as_deref().unwrap_or("All libraries");
|
let lib = library_name.as_deref().unwrap_or("All libraries");
|
||||||
format!(
|
[
|
||||||
"⏹ <b>Scan cancelled</b>\n\
|
format!("⏹ <b>Scan cancelled</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Type: {job_type}"
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
)
|
format!("🏷 <b>Type:</b> {job_type}"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::ThumbnailCompleted {
|
NotificationEvent::ThumbnailCompleted {
|
||||||
job_type,
|
job_type,
|
||||||
@@ -314,12 +326,14 @@ fn format_event(event: &NotificationEvent) -> String {
|
|||||||
} => {
|
} => {
|
||||||
let lib = library_name.as_deref().unwrap_or("All libraries");
|
let lib = library_name.as_deref().unwrap_or("All libraries");
|
||||||
let duration = format_duration(*duration_seconds);
|
let duration = format_duration(*duration_seconds);
|
||||||
format!(
|
[
|
||||||
"🖼 <b>Thumbnails completed</b>\n\
|
format!("✅ <b>Thumbnails completed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Type: {job_type}\n\
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
Duration: {duration}"
|
format!("🏷 <b>Type:</b> {job_type}"),
|
||||||
)
|
format!("⏱ <b>Duration:</b> {duration}"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::ThumbnailFailed {
|
NotificationEvent::ThumbnailFailed {
|
||||||
job_type,
|
job_type,
|
||||||
@@ -328,12 +342,15 @@ fn format_event(event: &NotificationEvent) -> String {
|
|||||||
} => {
|
} => {
|
||||||
let lib = library_name.as_deref().unwrap_or("All libraries");
|
let lib = library_name.as_deref().unwrap_or("All libraries");
|
||||||
let err = truncate(error, 200);
|
let err = truncate(error, 200);
|
||||||
format!(
|
[
|
||||||
"❌ <b>Thumbnails failed</b>\n\
|
format!("🚨 <b>Thumbnails failed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Type: {job_type}\n\
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
Error: {err}"
|
format!("🏷 <b>Type:</b> {job_type}"),
|
||||||
)
|
String::new(),
|
||||||
|
format!("💬 <code>{err}</code>"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::ConversionCompleted {
|
NotificationEvent::ConversionCompleted {
|
||||||
library_name,
|
library_name,
|
||||||
@@ -342,11 +359,13 @@ fn format_event(event: &NotificationEvent) -> String {
|
|||||||
} => {
|
} => {
|
||||||
let lib = library_name.as_deref().unwrap_or("Unknown");
|
let lib = library_name.as_deref().unwrap_or("Unknown");
|
||||||
let title = book_title.as_deref().unwrap_or("Unknown");
|
let title = book_title.as_deref().unwrap_or("Unknown");
|
||||||
format!(
|
[
|
||||||
"🔄 <b>CBR→CBZ conversion completed</b>\n\
|
format!("✅ <b>CBR → CBZ conversion completed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Book: {title}"
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
)
|
format!("📖 <b>Book:</b> {title}"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::ConversionFailed {
|
NotificationEvent::ConversionFailed {
|
||||||
library_name,
|
library_name,
|
||||||
@@ -357,23 +376,28 @@ fn format_event(event: &NotificationEvent) -> String {
|
|||||||
let lib = library_name.as_deref().unwrap_or("Unknown");
|
let lib = library_name.as_deref().unwrap_or("Unknown");
|
||||||
let title = book_title.as_deref().unwrap_or("Unknown");
|
let title = book_title.as_deref().unwrap_or("Unknown");
|
||||||
let err = truncate(error, 200);
|
let err = truncate(error, 200);
|
||||||
format!(
|
[
|
||||||
"❌ <b>CBR→CBZ conversion failed</b>\n\
|
format!("🚨 <b>CBR → CBZ conversion failed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Book: {title}\n\
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
Error: {err}"
|
format!("📖 <b>Book:</b> {title}"),
|
||||||
)
|
String::new(),
|
||||||
|
format!("💬 <code>{err}</code>"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::MetadataApproved {
|
NotificationEvent::MetadataApproved {
|
||||||
series_name,
|
series_name,
|
||||||
provider,
|
provider,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
format!(
|
[
|
||||||
"🔗 <b>Metadata linked</b>\n\
|
format!("✅ <b>Metadata linked</b>"),
|
||||||
Series: {series_name}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Provider: {provider}"
|
format!("📚 <b>Series:</b> {series_name}"),
|
||||||
)
|
format!("🔗 <b>Provider:</b> {provider}"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::MetadataBatchCompleted {
|
NotificationEvent::MetadataBatchCompleted {
|
||||||
library_name,
|
library_name,
|
||||||
@@ -381,11 +405,13 @@ fn format_event(event: &NotificationEvent) -> String {
|
|||||||
processed,
|
processed,
|
||||||
} => {
|
} => {
|
||||||
let lib = library_name.as_deref().unwrap_or("All libraries");
|
let lib = library_name.as_deref().unwrap_or("All libraries");
|
||||||
format!(
|
[
|
||||||
"🔍 <b>Metadata batch completed</b>\n\
|
format!("✅ <b>Metadata batch completed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Series processed: {processed}/{total_series}"
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
)
|
format!("📊 <b>Processed:</b> {processed}/{total_series} series"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::MetadataBatchFailed {
|
NotificationEvent::MetadataBatchFailed {
|
||||||
library_name,
|
library_name,
|
||||||
@@ -393,11 +419,14 @@ fn format_event(event: &NotificationEvent) -> String {
|
|||||||
} => {
|
} => {
|
||||||
let lib = library_name.as_deref().unwrap_or("All libraries");
|
let lib = library_name.as_deref().unwrap_or("All libraries");
|
||||||
let err = truncate(error, 200);
|
let err = truncate(error, 200);
|
||||||
format!(
|
[
|
||||||
"❌ <b>Metadata batch failed</b>\n\
|
format!("🚨 <b>Metadata batch failed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Error: {err}"
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
)
|
String::new(),
|
||||||
|
format!("💬 <code>{err}</code>"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::MetadataRefreshCompleted {
|
NotificationEvent::MetadataRefreshCompleted {
|
||||||
library_name,
|
library_name,
|
||||||
@@ -406,13 +435,19 @@ fn format_event(event: &NotificationEvent) -> String {
|
|||||||
errors,
|
errors,
|
||||||
} => {
|
} => {
|
||||||
let lib = library_name.as_deref().unwrap_or("All libraries");
|
let lib = library_name.as_deref().unwrap_or("All libraries");
|
||||||
format!(
|
let mut lines = vec![
|
||||||
"🔄 <b>Metadata refresh completed</b>\n\
|
format!("✅ <b>Metadata refresh completed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Updated: {refreshed}\n\
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
Unchanged: {unchanged}\n\
|
String::new(),
|
||||||
Errors: {errors}"
|
format!("📊 <b>Results</b>"),
|
||||||
)
|
format!(" 🔄 Updated: <b>{refreshed}</b>"),
|
||||||
|
format!(" ▪️ Unchanged: <b>{unchanged}</b>"),
|
||||||
|
];
|
||||||
|
if *errors > 0 {
|
||||||
|
lines.push(format!(" ⚠️ Errors: <b>{errors}</b>"));
|
||||||
|
}
|
||||||
|
lines.join("\n")
|
||||||
}
|
}
|
||||||
NotificationEvent::MetadataRefreshFailed {
|
NotificationEvent::MetadataRefreshFailed {
|
||||||
library_name,
|
library_name,
|
||||||
@@ -420,11 +455,14 @@ fn format_event(event: &NotificationEvent) -> String {
|
|||||||
} => {
|
} => {
|
||||||
let lib = library_name.as_deref().unwrap_or("All libraries");
|
let lib = library_name.as_deref().unwrap_or("All libraries");
|
||||||
let err = truncate(error, 200);
|
let err = truncate(error, 200);
|
||||||
format!(
|
[
|
||||||
"❌ <b>Metadata refresh failed</b>\n\
|
format!("🚨 <b>Metadata refresh failed</b>"),
|
||||||
Library: {lib}\n\
|
format!("━━━━━━━━━━━━━━━━━━━━"),
|
||||||
Error: {err}"
|
format!("📂 <b>Library:</b> {lib}"),
|
||||||
)
|
String::new(),
|
||||||
|
format!("💬 <code>{err}</code>"),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user