use serde::{Deserialize, Serialize};
use serde_json::json;
const EXPO_PUSH_URL: &str = "https://exp.host/--/api/v2/push/send";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum NotifyTarget {
Webhook { url: String },
Telegram { bot_token: String, chat_id: String },
ExpoPush { token: String },
Email { to: String },
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AlertDeliveryTargets {
#[serde(default)]
pub targets: Vec<NotifyTarget>,
#[serde(default)]
pub emails: Vec<String>,
}
pub async fn send_webhook_text(
http: &reqwest::Client,
url: &str,
text: &str,
) -> Result<(), String> {
let body = json!({ "text": text, "content": text });
let resp = http
.post(url)
.json(&body)
.timeout(std::time::Duration::from_secs(15))
.send()
.await
.map_err(|e| format!("webhook send failed: {e}"))?;
let status = resp.status();
if status.is_success() {
Ok(())
} else {
Err(format!("webhook returned HTTP {status}"))
}
}
pub async fn send_telegram_text(
http: &reqwest::Client,
bot_token: &str,
chat_id: &str,
text: &str,
) -> Result<(), String> {
let api = format!("https://api.telegram.org/bot{bot_token}/sendMessage");
let resp = http
.post(&api)
.json(&json!({ "chat_id": chat_id, "text": text }))
.timeout(std::time::Duration::from_secs(15))
.send()
.await
.map_err(|e| format!("telegram send failed: {e}"))?;
let status = resp.status();
if status.is_success() {
Ok(())
} else {
Err(format!("telegram returned HTTP {status}"))
}
}
pub async fn send_webhook_alert(
http: &reqwest::Client,
url: &str,
title: &str,
message: &str,
alert: &serde_json::Value,
) {
let body = json!({
"text": format!("{title}\n{message}"),
"content": format!("{title}\n{message}"),
"alert": alert,
});
let result = http
.post(url)
.json(&body)
.timeout(std::time::Duration::from_secs(15))
.send()
.await;
if let Err(e) = result {
tracing::warn!("notify: webhook to {url} failed: {e}");
}
}
pub async fn send_telegram_alert(
http: &reqwest::Client,
bot_token: &str,
chat_id: &str,
title: &str,
message: &str,
) {
let api = format!("https://api.telegram.org/bot{bot_token}/sendMessage");
let text = format!("\u{1f514} {title}\n{message}");
let result = http
.post(&api)
.json(&json!({ "chat_id": chat_id, "text": text }))
.timeout(std::time::Duration::from_secs(15))
.send()
.await;
if let Err(e) = result {
tracing::warn!("notify: telegram alert failed: {e}");
}
}
pub async fn push_expo_message(
http: &reqwest::Client,
tokens: &[String],
title: &str,
body: &str,
data: serde_json::Value,
) {
if tokens.is_empty() {
return;
}
let messages: Vec<_> = tokens
.iter()
.map(|t| {
json!({
"to": t,
"title": title,
"body": body,
"sound": "default",
"data": data,
})
})
.collect();
let result = http
.post(EXPO_PUSH_URL)
.json(&messages)
.timeout(std::time::Duration::from_secs(15))
.send()
.await;
if let Err(e) = result {
tracing::warn!("notify: expo push message failed: {e}");
}
}