br-wechat 0.0.23

This is an wechat
Documentation
use std::collections::HashMap;
use std::sync::Mutex;
use chrono::{Local, Utc};
use json::{object, JsonValue};
use lazy_static::lazy_static;
use rand::distr::Alphanumeric;
use rand::Rng;
use crate::{WechatMethod};
lazy_static! {
    pub static ref TICKET: Mutex<HashMap<String,JsonValue>> =Mutex::new(HashMap::new());
}
#[derive(Clone)]
pub struct Subscribe {
    pub appid: String,
    pub secret: String,
    pub access_token: String,
    pub expires_in: i64,
    pub ticket_expires_in: i64,
    pub ticket: String,
}
impl Subscribe {
    fn access_token(&mut self) -> Result<String, String> {
        let dt = Local::now();
        let timestamp = dt.timestamp_millis();

        if self.expires_in > timestamp {
            return Ok(self.access_token.clone());
        }
        let url = "https://api.weixin.qq.com/cgi-bin/stable_token".to_string();
        let mut http = br_reqwest::Client::new();
        http.post(url.as_str());
        http.raw_json(object! {
            "grant_type" =>"client_credential",
            "appid" => self.appid.clone(),
            "secret" => self.secret.clone(),
        });
        let res = http.send()?.json()?;
        if res["errcode"].is_empty() {
            self.access_token = res["access_token"].to_string();
            self.expires_in = timestamp + res["expires_in"].as_i64().unwrap();
            Ok(self.access_token.clone())
        } else {
            Err(res["errmsg"].to_string())
        }
    }
}


impl WechatMethod for Subscribe {
    fn access_token(&mut self) -> Result<JsonValue, String> {
        let dt = Local::now();
        let timestamp = dt.timestamp_millis();

        if self.expires_in > timestamp {
            return Ok(object! {
            appid: self.appid.clone(),
            access_token: self.access_token.clone(),
            expires_in: self.expires_in,
        });
        }
        let url = "https://api.weixin.qq.com/cgi-bin/stable_token".to_string();
        let mut http = br_reqwest::Client::new();
        http.post(url.as_str());
        http.raw_json(object! {
            "grant_type" =>"client_credential",
            "appid" => self.appid.clone(),
            "secret" => self.secret.clone(),
        });
        let res = http.send()?.json()?;
        if res["errcode"].is_empty() {
            self.access_token = res["access_token"].to_string();
            self.expires_in = timestamp + res["expires_in"].as_i64().unwrap();
            Ok(object! {
            appid: self.appid.clone(),
            access_token: self.access_token.clone(),
            expires_in: self.expires_in,
        })
        } else {
            Err(res["errmsg"].to_string())
        }
    }

    fn check(&mut self) -> Result<bool, String> {
        match self.access_token() {
            Ok(_) => Ok(true),
            Err(e) => Err(e),
        }
    }

    fn login(&mut self, code: &str) -> Result<JsonValue, String> {
        let mut http = br_reqwest::Client::new();
        let get_url = format!("https://api.weixin.qq.com/sns/oauth2/access_token?appid={}&secret={}&code={code}&grant_type=authorization_code", self.appid, self.secret);
        let res = http.get(get_url.as_str()).send()?.json()?;
        let data = object! {
            appid:self.appid.clone(),
            openid:res["openid"].as_str().unwrap_or(""),
            unionid:res["unionid"].as_str().unwrap_or(""),
        };
        Ok(data)
    }
    fn jsapi_ticket(&mut self, url: &str) -> Result<JsonValue, String> {
        self.access_token()?;
        let dt = Local::now();
        let timestamp = dt.timestamp_millis();
        if self.ticket_expires_in <= timestamp {
            let mut http = br_reqwest::Client::new();
            let get_url = format!("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={}&type=jsapi", self.access_token);
            let res = http.get(get_url.as_str()).send()?.json()?;
            if res["errcode"].as_i32().unwrap() != 0 {
                return Err(res["errmsg"].to_string());
            }
            self.ticket = res["ticket"].to_string();
            self.ticket_expires_in = res["expires_in"].as_i64().unwrap();
            TICKET.lock().unwrap().insert(self.appid.clone(), object! {
                ticket: self.ticket.clone(),
                ticket_expires_in:self.ticket_expires_in
            });
        }

        let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(16).map(char::from).collect();
        let timestamp = format!("{}", Utc::now().timestamp());
        let raw_string = format!("jsapi_ticket={}&noncestr={nonce_str}&timestamp={timestamp}&url={url}", self.ticket);
        let signature = br_crypto::sha1::encrypt_hex(raw_string.as_bytes());
        let data = object! {
            appId:self.appid.clone(),
            timestamp:timestamp,
            nonceStr:nonce_str,
            signature:signature,
        };


        Ok(data)
    }

    fn getuserinfo(&mut self, code: &str) -> Result<JsonValue, String> {
        //获取网页授权access_token
        let mut http = br_reqwest::Client::new();
        let access_token_url = format!(
            "https://api.weixin.qq.com/sns/oauth2/access_token?appid={}&secret={}&code={}&grant_type=authorization_code",
            self.appid, self.secret, code
        );
        let res = http.get(access_token_url.as_str()).send()?.json()?;
        let (web_access_token, openid) = if res["errcode"].is_empty() {
            (res["access_token"].to_string(), res["openid"].to_string())
        } else {
            return Err(res["errmsg"].to_string());
        };

        //通过网页授权access_token和openid获取用户信息
        let info_url = format!(
            "https://api.weixin.qq.com/sns/userinfo?access_token={}&openid={}&lang=zh_CN",
            web_access_token, openid
        );

        let mut http = br_reqwest::Client::new();
        let res = http.get(info_url.as_str()).send()?;

        let res = res.json()?;
        if res["errcode"].is_empty() {
            Ok(res)
        } else {
            Err(res["errmsg"].to_string())
        }
    }
}