use crate::util::requester; use dashmap::DashMap; use once_cell::sync::Lazy; use serde_json::json; use std::error::Error; use std::fmt::Write as _; use std::time::{SystemTime, UNIX_EPOCH}; // Global cache: Map static ERROR_CACHE: Lazy> = Lazy::new(DashMap::new); // const COOLDOWN_SECONDS: u64 = 3600; // 1 Hour cooldown pub fn format_error_chain(err: &dyn Error) -> String { let mut chain_str = String::new(); let mut current_err: Option<&dyn Error> = Some(err); let mut index = 1; while let Some(e) = current_err { let _ = writeln!(chain_str, "{}. {}", index, e); current_err = e.source(); index += 1; } chain_str } pub async fn send_discord_error_report( error_msg: String, error_chain: Option, context: Option<&str>, extra_info: Option<&str>, file: &str, line: u32, module: &str, ) { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); // --- Deduplication Logic --- // Create a unique key based on error content and location let error_signature = format!("{}-{}-{}", error_msg, file, line); if let Some(_) = ERROR_CACHE.get(&error_signature) { // if now - *last_sent < COOLDOWN_SECONDS { // Error is still in cooldown, skip sending return; // } } // Update the cache with the current timestamp ERROR_CACHE.insert(error_signature, now); // --------------------------- let webhook_url = match std::env::var("DISCORD_WEBHOOK") { Ok(url) => url, Err(_) => return, }; const MAX_FIELD: usize = 1024; let truncate = |s: &str| { if s.len() > MAX_FIELD { format!("{}…", &s[..MAX_FIELD - 1]) } else { s.to_string() } }; let payload = json!({ "embeds": [{ "title": "🚨 Rust Error Report", "color": 0xE74C3C, "fields": [ { "name": "Error", "value": format!("```{}```", truncate(&error_msg)), "inline": false }, { "name": "Error Chain", "value": truncate(&error_chain.unwrap_or_else(|| "No chain provided".to_string())), "inline": false }, { "name": "Location", "value": format!("`{}`:{}\n`{}`", file, line, module), "inline": true }, { "name": "Context", "value": truncate(context.unwrap_or("n/a")), "inline": true }, { "name": "Extra Info", "value": truncate(extra_info.unwrap_or("n/a")), "inline": false } ], "footer": { "text": format!("Unix time: {} | Cooldown active", now) } }] }); let mut requester = requester::Requester::new(); let _ = requester.post_json(&webhook_url, &payload, vec![]).await; }