use crate::core::{
axios::Axios,
base64::{Engine, BASE64},
json, log, ApiResp, RespData,
};
use tokio::sync::OnceCell;
static WEBHOOK_URL: OnceCell<String> = OnceCell::const_new();
pub async fn get_cached_webhook_url() -> &'static str {
WEBHOOK_URL
.get_or_init(|| async { WeComGroup::get_wx_webhook_url().await })
.await
}
pub async fn send_to_wecom(images: &[(String, Vec<u8>)]) -> Result<(), String> {
let group = WeComGroup::default();
group.send_image_to_wecom(images).await
}
#[derive(Default)]
pub struct WeComGroup {
pub webhook_url: String,
}
impl WeComGroup {
pub async fn get_wx_webhook_url() -> String {
let resp = Axios::new()
.post("https://www.kamft.cn/allow/getcfg")
.json(&json::json!("wxwork"))
.send()
.await
.expect("请求配置失败");
let data = resp
.json::<ApiResp<RespData>>()
.await
.expect("解析配置响应失败");
log::info!("get_wx_webhook_url解析配置响应: {:?}", data);
format!(
"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={}",
data.data.key
)
}
pub async fn send_msg_to_wecom(&self, content: &str) -> Result<(), String> {
let client = Axios::builder()
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(|e| format!("创建 HTTP 客户端失败: {}", e))?;
let body = json::json!({
"msgtype": "text",
"text": { "content": content }
});
let webhook_url = get_cached_webhook_url().await;
let response = client
.post(webhook_url)
.json(&body)
.send()
.await
.map_err(|e| format!("发送失败: {}", e))?;
let status = response.status();
let resp_text = response.text().await.unwrap_or_default();
if status.is_success() {
if let Ok(parsed) = json::from_str::<json::JsonValue>(&resp_text) {
let errcode = parsed["errcode"].as_i64().unwrap_or(-1);
if errcode == 0 {
log::info!("企业微信消息发送成功");
} else {
return Err(format!(
"企业微信返回错误: errcode={}, errmsg={}",
errcode,
parsed["errmsg"].as_str().unwrap_or("")
));
}
} else {
log::info!("企业微信消息发送成功 (HTTP {})", status);
}
} else {
return Err(format!("HTTP {} - {}", status, resp_text));
}
Ok(())
}
pub async fn send_image_to_wecom(&self, images: &[(String, Vec<u8>)]) -> Result<(), String> {
if images.is_empty() {
return Err("没有需要发送的截图".into());
}
let client = Axios::builder()
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(|e| format!("创建 HTTP 客户端失败: {}", e))?;
let webhook_url = get_cached_webhook_url().await;
for (label, bytes) in images {
let md5_hex = format!("{:x}", md5::compute(bytes));
let base64_str = BASE64.encode(bytes);
let body = json::json!({
"msgtype": "image",
"image": {
"base64": base64_str,
"md5": md5_hex,
}
});
let response = client
.post(webhook_url)
.json(&body)
.send()
.await
.map_err(|e| format!("发送失败 ({}): {}", label, e))?;
let status = response.status();
let resp_text = response.text().await.unwrap_or_default();
if status.is_success() {
if let Ok(parsed) = json::from_str::<json::JsonValue>(&resp_text) {
let errcode = parsed["errcode"].as_i64().unwrap_or(-1);
if errcode == 0 {
log::info!("发送成功: {}", label);
} else {
log::warn!(
"发送失败 ({}): errcode={}, errmsg={}",
label,
errcode,
parsed["errmsg"].as_str().unwrap_or("")
);
}
} else {
log::info!("发送成功: {} (HTTP {})", label, status);
}
} else {
log::warn!("发送失败 ({}): HTTP {} - {}", label, status, resp_text);
}
}
Ok(())
}
}