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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
mod error;

use serde::{Deserialize, Serialize};
use std::error::Error as StdError;

// 获取Access Token时的返回结果
#[derive(Deserialize)]
struct AccessTokenResponse {
    errcode: i64,
    errmsg: String,
    access_token: String,
}

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

impl MsgSendResponse {
    pub fn is_error(&self) -> bool {
        self.errcode != 0
    }

    pub fn error_code(&self) -> i64 {
        self.errcode
    }

    pub fn error_msg(&self) -> &str {
        &self.errmsg
    }
}

// 文本消息结构体
#[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,
}

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

// 获取AccessToken的方法
async fn get_access_token(
    corpid: &str,
    secret: &str,
) -> Result<String, Box<dyn StdError + Send + Sync>> {
    let url = format!(
        "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpid}&corpsecret={secret}",
    );
    let response = reqwest::get(url)
        .await?
        .json::<AccessTokenResponse>()
        .await?;
    match response.errcode {
        0 => Ok(response.access_token),
        _ => Err(Box::<error::Error>::new(error::Error::new(
            response.errcode,
            response.errmsg,
        ))),
    }
}

// 承载相关方法的结构体
#[derive(Debug, Clone)]
pub struct WecomAgent {
    corpid: String,
    secret: String,
    access_token: String,
    client: reqwest::Client,
}

impl WecomAgent {
    /// 创建一个Agent。
    pub async fn new(corpid: &str, secret: &str) -> Result<Self, Box<dyn StdError>> {
        match get_access_token(corpid, secret).await {
            Ok(token) => Ok(Self {
                corpid: String::from(corpid),
                secret: String::from(secret),
                access_token: token,
                client: reqwest::Client::new(),
            }),
            Err(e) => Err(e),
        }
    }

    /// 更新access_token。
    pub async fn update_token(&mut self) -> Result<(), Box<dyn StdError + Send + Sync>> {
        self.access_token = get_access_token(&self.corpid, &self.secret).await?;
        Ok(())
    }

    /// 发送文本消息
    pub async fn send_text(
        &self,
        msg: &TextMsg,
    ) -> Result<MsgSendResponse, Box<dyn StdError + Send + Sync>> {
        let url = format!(
            "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={}",
            &self.access_token
        );
        let response = self
            .client
            .post(&url)
            .json(msg)
            .send()
            .await?
            .json::<MsgSendResponse>()
            .await?;
        Ok(response)
    }
}