1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use reqwest;
use serde::{Deserialize, Serialize};
use std::error::Error;

#[derive(Debug, Clone)]
pub struct WecomAgent {
    corpid: String,
    secret: String,
    access_token: Option<String>,
    client: reqwest::Client,
}

#[derive(Deserialize)]
struct AccessTokenResponse {
    errcode: i64,
    errmsg: String,
    access_token: String,
    expires_in: usize,
}

// 应用消息发送结果
#[derive(Deserialize)]
pub struct MsgSendResponse {
    errcode: i64,
    errmsg: String,
    invaliduser: Option<String>,
    invalidparty: String,
    invalidtag: String,
    unlicenseduser: String,
    msgid: String,
    response_code: String,
}

// 文本消息
#[derive(Debug, Serialize, PartialEq)]
pub struct TextMsgContent {
    pub content: String,
}

// 文本消息结构体
#[derive(Debug, Serialize, PartialEq)]
pub struct TextMsg {
    pub touser: String,
    pub toparty: String,
    pub totag: String,
    pub msgtype: String,
    pub agentid: usize,
    pub text: TextMsgContent,
    pub safe: i64,
    pub enable_id_trans: i64,
    pub enable_duplicate_check: i64,
    pub duplicate_check_interval: usize,
}

impl WecomAgent {
    /// 创建一个Agent。注意默认access_token为空,需要更新后使用。
    pub fn new(corpid: &str, secret: &str) -> Self {
        Self {
            corpid: String::from(corpid),
            secret: String::from(secret),
            access_token: None,
            client: reqwest::Client::new(),
        }
    }

    /// 检查access_token是否初始化。
    pub fn token_is_some(&self) -> bool {
        self.access_token.is_some()
    }

    /// 更新access_token。
    pub async fn update_token(&mut self) -> Result<(), Box<dyn Error>> {
        let url = format!(
            "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={}&corpsecret={}",
            &self.corpid, &self.secret
        );
        let response = self
            .client
            .get(url)
            .send()
            .await?
            .json::<AccessTokenResponse>()
            .await?;
        if response.errcode != 0 {
            return Err(format!(
                "Failed to fetch access token. Error code: {}, {}",
                response.errcode, response.errmsg,
            )
            .into());
        }
        self.access_token = Some(response.access_token);
        Ok(())
    }

    /// 发送文本消息
    pub async fn send_text(&self, msg: &TextMsg) -> Result<MsgSendResponse, Box<dyn Error>> {
        if self.access_token.is_none() {
            return Err("Can not send message. Access token is None.".into());
        }
        let url = format!(
            "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={}",
            self.access_token.as_ref().unwrap()
        );
        let response = self
            .client
            .post(&url)
            .json(msg)
            .send()
            .await?
            .json::<MsgSendResponse>()
            .await?;
        Ok(response)
    }
}