use async_trait::async_trait;
use pingap_core::{
Notification, NotificationData, NotificationLevel, get_hostname,
};
use serde_json::{Map, Value};
use std::time::Duration;
use tracing::{error, info};
pub static LOG_TARGET: &str = "pingap::webhook";
pub struct WebhookNotificationSender {
url: String,
category: String,
notifications: Vec<String>,
}
impl WebhookNotificationSender {
pub fn new(
url: String,
category: String,
notifications: Vec<String>,
) -> Self {
Self {
url,
category,
notifications,
}
}
pub async fn send_notification(&self, params: NotificationData) {
let title = ¶ms.title;
info!(
target: LOG_TARGET,
notification = params.category,
title,
message = params.message,
"webhook notification"
);
let webhook_type = &self.category;
let url = &self.url;
if url.is_empty() {
return;
}
let found = self.notifications.contains(¶ms.category.to_string());
if !found {
return;
}
let category = params.category.to_string();
let level = params.level;
let ip = local_ip_list().join(";");
let client = reqwest::Client::new();
let mut data = serde_json::Map::new();
let hostname = get_hostname();
let name = "pingap".to_string();
let color_type = match level {
NotificationLevel::Error => "warning",
NotificationLevel::Warn => "warning",
_ => "comment",
};
let content = format!(
r###" <font color="{color_type}">{name}({level})</font>
>title: {title}
>hostname: {hostname}
>ip: {ip}
>category: {category}
>message: {}"###,
params.message
);
match webhook_type.to_lowercase().as_str() {
"wecom" => {
let mut markdown_data = Map::new();
markdown_data
.insert("content".to_string(), Value::String(content));
data.insert(
"msgtype".to_string(),
Value::String("markdown".to_string()),
);
data.insert(
"markdown".to_string(),
Value::Object(markdown_data),
);
},
"dingtalk" => {
let mut markdown_data = serde_json::Map::new();
markdown_data.insert(
"title".to_string(),
Value::String(category.to_string()),
);
markdown_data
.insert("text".to_string(), Value::String(content));
data.insert(
"msgtype".to_string(),
Value::String("markdown".to_string()),
);
data.insert(
"markdown".to_string(),
Value::Object(markdown_data),
);
},
_ => {
data.insert("name".to_string(), Value::String(name));
data.insert(
"level".to_string(),
Value::String(level.to_string()),
);
data.insert(
"hostname".to_string(),
Value::String(hostname.to_string()),
);
data.insert("ip".to_string(), Value::String(ip));
data.insert("category".to_string(), Value::String(category));
data.insert(
"message".to_string(),
Value::String(params.message),
);
},
}
match client
.post(url)
.json(&data)
.timeout(Duration::from_secs(30))
.send()
.await
{
Ok(res) => {
if res.status().as_u16() < 400 {
info!(target: LOG_TARGET, "send webhook success");
} else {
error!(
target: LOG_TARGET,
status = res.status().to_string(),
"send webhook fail"
);
}
},
Err(e) => {
error!(
target: LOG_TARGET,
error = %e,
"send webhook fail"
);
},
};
}
}
#[async_trait]
impl Notification for WebhookNotificationSender {
async fn notify(&self, data: NotificationData) {
self.send_notification(data).await;
}
}
fn local_ip_list() -> Vec<String> {
let mut ip_list = vec![];
if let Ok(value) = local_ip_address::local_ip() {
ip_list.push(value);
}
if let Ok(value) = local_ip_address::local_ipv6() {
ip_list.push(value);
}
ip_list
.iter()
.filter(|item| !item.is_loopback())
.map(|item| item.to_string())
.collect()
}