open_lark/
custom_bot.rs

1#[cfg(feature = "im")]
2use base64::{prelude::BASE64_STANDARD, Engine};
3#[cfg(feature = "im")]
4use hmac::{Hmac, Mac};
5#[cfg(feature = "im")]
6use serde_json::{json, Value};
7#[cfg(feature = "im")]
8use sha2::Sha256;
9
10#[cfg(feature = "im")]
11use crate::core::{
12    api_resp::{BaseResponse, RawResponse},
13    http::Transport,
14    SDKResult,
15};
16
17#[cfg(feature = "im")]
18use crate::service::im::v1::message::{MessageCardTemplate, SendMessageTrait};
19
20/// 自定义机器人
21///
22/// [使用指南](https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot)
23#[allow(dead_code)]
24pub struct CustomBot<'a> {
25    /// webhook 地址
26    webhook_url: &'a str,
27    /// 密钥
28    secret: Option<&'a str>,
29    client: reqwest::Client,
30}
31
32impl<'a> CustomBot<'a> {
33    pub fn new(webhook_url: &'a str, secret: Option<&'a str>) -> Self {
34        CustomBot {
35            webhook_url,
36            secret,
37            client: reqwest::Client::new(),
38        }
39    }
40}
41
42impl CustomBot<'_> {
43    #[cfg(feature = "im")]
44    pub async fn send_message<T>(&self, message: T) -> SDKResult<BaseResponse<RawResponse>>
45    where
46        T: SendMessageTrait,
47    {
48        let mut json = json!({
49            "msg_type": message.msg_type(),
50            "content": message.content()
51        });
52        self.check_sign(&mut json);
53        Transport::do_send(
54            self.client.post(self.webhook_url),
55            json.to_string().into(),
56            false,
57        )
58        .await
59    }
60
61    /// 发送飞书卡片消息, 因为自定义机器人发送飞书卡片消息的格式比较特殊,所以单独提供一个方法
62    #[cfg(feature = "im")]
63    pub async fn send_card(
64        &self,
65        message: MessageCardTemplate,
66    ) -> SDKResult<BaseResponse<RawResponse>> {
67        let mut json = json!({
68            "msg_type": message.msg_type(),
69            "card": message.content()
70        });
71
72        self.check_sign(&mut json);
73
74        Transport::do_send(
75            self.client.post(self.webhook_url),
76            json.to_string().into_bytes(),
77            false,
78        )
79        .await
80    }
81
82    /// 如果设置了密钥,就计算签名
83    #[cfg(feature = "im")]
84    fn check_sign(&self, json: &mut Value) {
85        if let Some(secret) = self.secret.as_ref() {
86            let now = chrono::Local::now().timestamp();
87            json["timestamp"] = serde_json::to_value(now).unwrap();
88            let sign = CustomBot::sign(now, secret);
89            json["sign"] = serde_json::to_value(sign).unwrap();
90        }
91    }
92
93    /// 计算签名
94    #[cfg(feature = "im")]
95    fn sign(timestamp: i64, secret: &str) -> String {
96        let string_to_sign = format!("{timestamp}\n{secret}");
97        let hmac: Hmac<Sha256> = Hmac::new_from_slice(string_to_sign.as_bytes()).unwrap();
98        let hmac_code = hmac.finalize().into_bytes();
99        BASE64_STANDARD.encode(hmac_code)
100    }
101}