use crate::{session::SessionStore, client::APIClient, request::{Method, RequestType, LabraResponse, LabraRequest, RequestMethod}, util::current_timestamp, LabradorResult, SimpleStorage, WechatCrypto, WechatRequest, get_timestamp, get_nonce_str, WechatCommonResponse};
use serde::{Serialize, Deserialize};
use serde_json::{json, Value};
mod method;
mod api;
#[allow(unused)]
mod constants;
mod tp;
mod messages;
mod msg_parser;
mod events;
mod replies;
mod msg_router;
pub use api::*;
pub use tp::*;
pub use messages::*;
pub use replies::*;
pub use events::*;
pub use msg_parser::*;
pub use msg_router::*;
use crate::wechat::cp::constants::{ACCESS_TOKEN, CORPID, CORPSECRET};
use crate::wechat::cp::method::{WechatCpMethod};
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct WechatCpClient<T: SessionStore> {
corp_id: String,
corp_secret: String,
token: Option<String>,
aes_key: Option<String>,
oauth2_redirect_uri: Option<String>,
webhook_url: Option<String>,
agent_id: Option<i32>,
client: APIClient<T>,
}
#[allow(unused)]
#[derive(Serialize, Deserialize)]
pub struct AccessTokenResponse{
pub access_token: String,
pub expires_in: i64,
}
#[allow(unused)]
#[derive(Serialize, Deserialize)]
pub struct JsapiTicket {
pub ticket: String,
pub expires_in: i64,
}
#[allow(unused)]
#[derive(Serialize, Deserialize)]
pub struct JsapiSignature {
pub app_id: String,
#[serde(rename="nonceStr")]
pub nonce_str: String,
pub url: String,
pub signature: String,
pub timestamp: i64,
}
#[allow(unused)]
#[derive(Serialize, Deserialize)]
pub struct AgentJsapiSignature {
pub agentid: String,
pub corpid: String,
#[serde(rename="nonceStr")]
pub nonce_str: String,
pub url: String,
pub signature: String,
pub timestamp: i64,
}
#[allow(unused)]
#[derive(Serialize, Deserialize)]
pub struct WechatCpProviderToken {
pub provider_access_token: String,
pub expires_in: i64,
}
#[allow(unused)]
impl<T: SessionStore> WechatCpClient<T> {
fn from_client(client: APIClient<T>) -> WechatCpClient<T> {
WechatCpClient {
corp_id: client.app_key.to_owned(),
corp_secret: client.secret.to_owned(),
token: None,
aes_key: None,
oauth2_redirect_uri: None,
webhook_url: None,
agent_id: None,
client
}
}
pub fn aes_key(mut self, aes_key: &str) -> Self {
self.aes_key = aes_key.to_string().into();
self
}
pub fn token(mut self, token: &str) -> Self {
self.token = token.to_string().into();
self
}
pub fn oauth2_redirect_uri(mut self, oauth2_redirect_uri: &str) -> Self {
self.oauth2_redirect_uri = oauth2_redirect_uri.to_string().into();
self
}
pub fn webhook_url(mut self, webhook_url: &str) -> Self {
self.webhook_url = webhook_url.to_string().into();
self
}
pub fn new<S: Into<String>>(corp_id: S, corp_secret: S) -> WechatCpClient<SimpleStorage> {
let session = SimpleStorage::new();
let client = APIClient::from_session(corp_id.into(), corp_secret.into(), "https://qyapi.weixin.qq.com", session);
WechatCpClient::from_client(client)
}
pub fn from_session<S: Into<String>>(corp_id: S, corp_secret: S, session: T) -> WechatCpClient<T> {
let client = APIClient::from_session(corp_id.into(), corp_secret.into(), "https://qyapi.weixin.qq.com", session);
Self::from_client(client)
}
#[inline]
pub async fn access_token(&self, force_refresh: bool) -> LabradorResult<String> {
let mut session = self.client.session();
let token_key = format!("{}_access_token_cp", self.corp_id);
let expires_key = format!("{}_expires_at_cp", self.corp_id);
let token: String = session.get(&token_key, Some("".to_owned()))?.unwrap_or_default();
let timestamp = current_timestamp();
let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
if expires_at <= timestamp || force_refresh {
let mut req = LabraRequest::<String>::new().url(WechatCpMethod::AccessToken.get_method()).params(vec![
(CORPID.to_string(), self.corp_id.to_string()),
(CORPSECRET.to_string(), self.corp_secret.to_string()),
]).method(Method::Get).req_type(RequestType::Json);
let res = self.client.request(req).await?.json::<AccessTokenResponse>()?;
let token = res.access_token;
let expires_in = res.expires_in;
let expires_at = current_timestamp() + expires_in - 200;
session.set(&token_key, token.to_owned(), Some(expires_in as usize));
session.set(&expires_key, expires_at, Some(expires_in as usize));
Ok(token.to_string())
} else {
Ok(token)
}
}
#[inline]
pub async fn get_provider_token(&self, corp_id: &str, provider_secret: &str) -> LabradorResult<WechatCpProviderToken> {
let mut req = LabraRequest::new().url(WechatCpMethod::GetProviderToken.get_method()).json(json!({
"corpid": corp_id,
"provider_secret": provider_secret,
})).method(Method::Post).req_type(RequestType::Json);
let res = self.client.request(req).await?.json::<WechatCpProviderToken>()?;
Ok(res)
}
pub fn check_signature(&self, signature: &str, timestamp: i64, nonce: &str, data: &str) -> LabradorResult<bool> {
let crp = WechatCrypto::new(&self.aes_key.to_owned().unwrap_or_default()).token(&self.token.to_owned().unwrap_or_default());
let _ = crp.check_signature(signature, timestamp, nonce, data)?;
Ok(true)
}
pub async fn create_jsapi_signature(&self, url: &str) -> LabradorResult<JsapiSignature> {
let timestamp = get_timestamp() / 1000;
let noncestr = get_nonce_str();
let jsapi_ticket = self.get_jsapi_ticket(false).await?;
let signature = WechatCrypto::get_sha1_sign(&vec!["jsapi_ticket=".to_string() + &jsapi_ticket,
"noncestr=".to_string() + &noncestr,
"timestamp=".to_string() + ×tamp.to_string(),"url=".to_string() + &url].join("&"));
Ok(JsapiSignature{
app_id: self.corp_id.to_string(),
nonce_str: noncestr,
url: url.to_string(),
signature,
timestamp,
})
}
pub async fn create_agent_jsapi_signature(&self, url: &str) -> LabradorResult<AgentJsapiSignature> {
let timestamp = get_timestamp() / 1000;
let noncestr = get_nonce_str();
let jsapi_ticket = self.get_jsapi_ticket(false).await?;
let signature = WechatCrypto::get_sha1_sign(&vec!["jsapi_ticket=".to_string() + &jsapi_ticket,
"noncestr=".to_string() + &noncestr,
"timestamp=".to_string() + ×tamp.to_string(),"url=".to_string() + &url].join("&"));
Ok(AgentJsapiSignature{
agentid: self.agent_id.unwrap_or_default().to_string(),
corpid: self.corp_id.to_string(),
nonce_str: noncestr,
url: url.to_string(),
signature,
timestamp,
})
}
pub async fn get_jsapi_ticket(&self, force_refresh: bool) -> LabradorResult<String> {
let mut session = self.client.session();
let token_key = format!("{}_jsapi_ticket_cp", self.corp_id);
let expires_key = format!("{}_jsapi_ticket_expires_at_cp", self.corp_id);
let ticket: String = session.get(&token_key, Some("".to_owned()))?.unwrap_or_default();
let timestamp = current_timestamp();
let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
if expires_at <= timestamp || force_refresh {
let mut req = LabraRequest::<String>::new().url(WechatCpMethod::GetJsapiTicket.get_method()).params(vec![]).method(Method::Get).req_type(RequestType::Json);
let res = self.client.request(req).await?.json::<JsapiTicket>()?;
let ticket = res.ticket;
let expires_in = res.expires_in;
let expires_at = current_timestamp() + expires_in - 200;
let ticket_key = format!("{}_jsapi_ticket_cp", self.corp_id);
let expires_key = format!("{}_jsapi_ticket_expires_at_cp", self.corp_id);
session.set(&ticket_key, ticket.to_owned(), Some(expires_in as usize));
session.set(&expires_key, expires_at, Some(expires_in as usize));
Ok(ticket.to_string())
} else {
Ok(ticket)
}
}
pub async fn get_agent_jsapi_ticket(&self, force_refresh: bool) -> LabradorResult<String> {
let mut session = self.client.session();
let token_key = format!("{}_agent_jsapi_ticket_cp", self.corp_id);
let expires_key = format!("{}_agent_jsapi_ticket_expires_at_cp", self.corp_id);
let ticket: String = session.get(&token_key, Some("".to_owned()))?.unwrap_or_default();
let timestamp = current_timestamp();
let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
if expires_at <= timestamp || force_refresh {
let mut req = LabraRequest::<String>::new().url(WechatCpMethod::GetAgentConfigTicket.get_method()).params(vec![]).method(Method::Get).req_type(RequestType::Json);
let res = self.client.request(req).await?.json::<JsapiTicket>()?;
let ticket = res.ticket;
let expires_in = res.expires_in;
let expires_at = current_timestamp() + expires_in - 200;
let ticket_key = format!("{}_agent_jsapi_ticket_cp", self.corp_id);
let expires_key = format!("{}_agent_jsapi_ticket_expires_at_cp", self.corp_id);
session.set(&ticket_key, ticket.to_owned(), Some(expires_in as usize));
session.set(&expires_key, expires_at, Some(expires_in as usize));
Ok(ticket.to_string())
} else {
Ok(ticket)
}
}
pub async fn get_callback_ip(&self, force_refresh: bool) -> LabradorResult<Vec<String>> {
let v = self.get(WechatCpMethod::GetCallbackIp, vec![], RequestType::Json).await?.json::<Value>()?;
let v = WechatCommonResponse::parse::<Value>(v)?;
let ip_list = v["ip_list"].as_array().unwrap_or(&vec![]).iter().map(|v| v.as_str().unwrap_or_default().to_string()).collect::<Vec<String>>();
Ok(ip_list)
}
async fn execute<D: WechatRequest, B: Serialize>(&self, request: D) -> LabradorResult<LabraResponse> {
let mut querys = request.get_query_params();
if request.is_need_token() {
let access_token = self.access_token(false).await?;
if !access_token.is_empty() {
querys.insert(ACCESS_TOKEN.to_string(), access_token);
}
}
let params = querys.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect::<Vec<(String, String)>>();
let mut req = LabraRequest::<B>::new().url(request.get_api_method_name())
.params(params).method(request.get_request_method()).req_type(request.get_request_type()).body(request.get_request_body::<B>());
self.client.request(req).await
}
async fn post<D: Serialize>(&self, method: WechatCpMethod, mut querys: Vec<(String, String)>, data: D, request_type: RequestType) -> LabradorResult<LabraResponse> {
let access_token = self.access_token(false).await?;
if !access_token.is_empty() && method.need_token() {
querys.push((ACCESS_TOKEN.to_string(), access_token));
}
self.client.post(method, querys, data, request_type).await
}
async fn get(&self, method: WechatCpMethod, mut params: Vec<(String, String)>, request_type: RequestType) -> LabradorResult<LabraResponse> {
let access_token = self.access_token(false).await?;
if !access_token.is_empty() && method.need_token() {
params.push((ACCESS_TOKEN.to_string(), access_token));
}
self.client.get(method, params, request_type).await
}
pub fn code_session(&self) -> WechatCpCodeSession<T> {
WechatCpCodeSession::new(self)
}
pub fn media(&self) -> WechatCpMedia<T> {
WechatCpMedia::new(self)
}
pub fn agent(&self) -> WechatCpAgent<T> {
WechatCpAgent::new(self)
}
pub fn department(&self) -> WechatCpDepartment<T> {
WechatCpDepartment::new(self)
}
pub fn external_contact(&self) -> WechatCpExternalContact<T> {
WechatCpExternalContact::new(self)
}
pub fn group_robot(&self) -> WechatCpGroupRobot<T> {
WechatCpGroupRobot::new(self)
}
pub fn menu(&self) -> WechatCpMenu<T> {
WechatCpMenu::new(self)
}
pub fn message(&self) -> WechatCpMessage<T> {
WechatCpMessage::new(self)
}
pub fn oauth2(&self) -> WechatCpOauth2<T> {
WechatCpOauth2::new(self)
}
pub fn tag(&self) -> WechatCpTag<T> {
WechatCpTag::new(self)
}
pub fn user(&self) -> WechatCpUser<T> {
WechatCpUser::new(self)
}
}