Files
hottub/src/util/discord.rs
2026-03-05 18:18:48 +00:00

108 lines
3.2 KiB
Rust

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<ErrorSignature, LastSentTimestamp>
static ERROR_CACHE: Lazy<DashMap<String, u64>> = 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<String>,
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;
}