diff --git a/config/development.yaml b/config/development.yaml index 43b7af3..ed10c36 100644 --- a/config/development.yaml +++ b/config/development.yaml @@ -38,3 +38,7 @@ tg_bot: ding_bot: access_token: {{ get_env(name="DING_ACCESS_TOKEN", default="") }} secret_token: {{ get_env(name="DING_SECRET_TOKEN", default="") }} + +lark_bot: + access_token: {{ get_env(name="LARK_ACCESS_TOKEN", default="") }} + secret_token: {{ get_env(name="LARK_SECRET_TOKEN", default="") }} diff --git a/src/config.rs b/src/config.rs index 0a49fb3..5c4c13b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,6 +22,7 @@ pub struct Config { pub logger: Logger, pub tg_bot: TgBot, pub ding_bot: DingBot, + pub lark_bot: LarkBot, } impl Config { @@ -108,3 +109,9 @@ pub struct DingBot { pub access_token: String, pub secret_token: String, } + +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +pub struct LarkBot { + pub access_token: String, + pub secret_token: String, +} diff --git a/src/error.rs b/src/error.rs index 6302925..52f29c9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use std::num::ParseIntError; +use std::{num::ParseIntError, time::SystemTimeError}; use hmac::digest::crypto_common; use migration::sea_orm; @@ -66,7 +66,10 @@ pub enum Error { IO(#[from] std::io::Error), #[error(transparent)] - DataParse(#[from] chrono::ParseError), + DateParse(#[from] chrono::ParseError), + + #[error(transparent)] + SystemTime(#[from] SystemTimeError), #[error(transparent)] Any(#[from] Box), diff --git a/src/push/dingding.rs b/src/push/dingding.rs index e9784a0..80472c0 100644 --- a/src/push/dingding.rs +++ b/src/push/dingding.rs @@ -1,5 +1,3 @@ -use std::time::SystemTime; - use crate::{ error::{Error, Result}, utils::{calc_hmac_sha256, http_client::Help}, @@ -74,42 +72,30 @@ impl DingDing { Help::new(headers) } pub fn generate_sign(&self) -> Result { - let sign = if !self.secret_token.is_empty() { - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis(); - let timestamp_and_secret = &format!("{}\n{}", timestamp, self.secret_token); - let hmac_sha256 = calc_hmac_sha256( - self.secret_token.as_bytes(), - timestamp_and_secret.as_bytes(), - )?; - let sign = BASE64_STANDARD.encode(hmac_sha256); - Sign { - access_token: self.access_token.clone(), - timestamp: Some(timestamp), - sign: Some(sign), - } - } else { - Sign { - access_token: self.access_token.clone(), - timestamp: None, - sign: None, - } - }; - Ok(sign) + let now = chrono::Local::now().timestamp(); + let timestamp_and_secret = &format!("{}\n{}", now, self.secret_token); + let hmac_sha256 = calc_hmac_sha256( + self.secret_token.as_bytes(), + timestamp_and_secret.as_bytes(), + )?; + let sign = BASE64_STANDARD.encode(hmac_sha256); + Ok(Sign { + access_token: self.access_token.clone(), + timestamp: now, + sign, + }) } } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Sign { - pub access_token: String, - pub timestamp: Option, - pub sign: Option, -} - #[derive(Debug, Serialize, Deserialize)] pub struct DingResponse { pub errmsg: String, pub errcode: i64, } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Sign { + pub access_token: String, + pub timestamp: i64, + pub sign: String, +} diff --git a/src/push/lark.rs b/src/push/lark.rs new file mode 100644 index 0000000..11245d6 --- /dev/null +++ b/src/push/lark.rs @@ -0,0 +1,108 @@ +use async_trait::async_trait; +use base64::prelude::*; +use hmac::{Hmac, Mac}; +use reqwest::header; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use sha2::Sha256; +use tracing::warn; + +use crate::error::{Error, Result}; +use crate::utils::http_client::Help; + +use super::MessageBot; + +const LARK_HOOK_URL: &str = "https://open.feishu.cn/open-apis/bot/v2/hook"; +const MSG_TYPE: &str = "interactive"; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Lark { + pub access_token: String, + pub secret_token: String, +} + +#[async_trait] +impl MessageBot for Lark { + async fn push_markdown(&self, title: String, msg: String) -> Result<()> { + let help = self.get_help(); + let message = self.generate_lark_card(title, msg)?; + let url = format!("{}/{}", LARK_HOOK_URL, self.access_token); + let res: LarkResponse = help + .http_client + .post(url) + .json(&message) + .send() + .await? + .json() + .await?; + if res.code != 0 { + warn!("lark push markdown message error, err msg is {}", res.msg); + return Err(Error::Message( + "lark push markdown message response errorcode not eq 0".to_owned(), + )); + } + Ok(()) + } +} + +impl Lark { + pub fn new(access_token: String, secret_token: String) -> Self { + Lark { + access_token, + secret_token, + } + } + + pub fn get_help(&self) -> Help { + let mut headers = header::HeaderMap::new(); + headers.insert("Accept-Charset", header::HeaderValue::from_static("utf8")); + Help::new(headers) + } + + pub fn generate_sign(&self, timestamp: i64) -> Result { + let timestamp_and_secret = format!("{}\n{}", timestamp, self.secret_token); + let hmac: Hmac = Hmac::new_from_slice(timestamp_and_secret.as_bytes())?; + let hmac_code = hmac.finalize().into_bytes(); + let sign = BASE64_STANDARD.encode(hmac_code); + Ok(sign) + } + + pub fn generate_lark_card(&self, title: String, message: String) -> Result { + println!("{}", title); + println!("{}", message); + let message = message.replace(" ", ""); + let now = chrono::Local::now().timestamp(); + let sign = self.generate_sign(now)?; + let card = json!({ + "msg_type": MSG_TYPE, + "card": { + "elements": [ + { + "tag": "div", + "text": { + "content": message, + "tag": "lark_md" + } + + }, + ], + "header": { + "title": { + "content": title, + "tag": "plain_text" + } + } + }, + "timestamp":now, + "sign": sign, + }); + Ok(card) + } +} + +// LarkResponse ignore data +#[derive(Debug, Serialize, Deserialize)] +pub struct LarkResponse { + pub msg: String, + pub code: i32, +} diff --git a/src/push/mod.rs b/src/push/mod.rs index 53147da..5cdf2cd 100644 --- a/src/push/mod.rs +++ b/src/push/mod.rs @@ -1,10 +1,12 @@ use crate::{config::Config, error::Result}; use async_trait::async_trait; use dingding::DingDing; +use lark::Lark; use std::sync::Arc; use telegram::Telegram; pub mod dingding; +pub mod lark; pub mod msg_template; pub mod telegram; @@ -34,9 +36,17 @@ pub fn init(config: Config) -> BotManager { let tg_bot = Telegram::new(config.tg_bot.token, config.tg_bot.chat_id); bots.add_bot(tg_bot) } - if !config.ding_bot.access_token.trim().is_empty() { + if !config.ding_bot.access_token.trim().is_empty() + && !config.ding_bot.secret_token.trim().is_empty() + { let ding_bot = DingDing::new(config.ding_bot.access_token, config.ding_bot.secret_token); bots.add_bot(ding_bot); } + if !config.lark_bot.access_token.trim().is_empty() + && !config.lark_bot.secret_token.trim().is_empty() + { + let lark_bot = Lark::new(config.lark_bot.access_token, config.lark_bot.secret_token); + bots.add_bot(lark_bot); + } bots }