use bytes::Bytes;
use serde::{Serialize, Deserialize};
use serde_json::{json, Value};
use crate::{session::SessionStore, request::{RequestType}, WechatCommonResponse, LabradorResult, WechatCrypto, current_timestamp, LabraError, JsapiTicket, JsapiSignature, get_timestamp, get_nonce_str, APIClient, WechatRequest, LabraResponse, LabraRequest, SimpleStorage, WechatCpProviderToken};
use crate::wechat::cp::constants::{ACCESS_TOKEN, ACCESS_TOKEN_KEY, AGENT_CONFIG, AUTH_URL_INSTALL, PROVIDER_ACCESS_TOKEN, SUITE_ACCESS_TOKEN, TYPE};
use crate::wechat::cp::method::WechatCpMethod;
use crate::wechat::cp::AccessTokenResponse;
mod tag;
mod license;
mod media;
mod department;
mod user;
mod order;
mod agent;
mod auth;
pub use tag::*;
pub use license::*;
pub use media::*;
pub use department::*;
pub use user::*;
pub use order::*;
pub use agent::*;
use crate::wechat::cp::tp::auth::WechatCpTpAuth;
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct WechatCpTpClient<T: SessionStore> {
token: Option<String>,
corp_id: String,
aes_key: Option<String>,
provider_secret: Option<String>,
agent_id: Option<i32>,
suite_id: Option<String>,
suite_secret: Option<String>,
client: APIClient<T>,
}
#[allow(unused)]
impl<T: SessionStore> WechatCpTpClient<T> {
fn from_client(client: APIClient<T>) -> WechatCpTpClient<T> {
WechatCpTpClient {
corp_id: client.app_key.to_owned(),
token: None,
aes_key: None,
agent_id: None,
suite_id: None,
suite_secret: None,
client,
provider_secret: None,
}
}
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 agent_id(mut self, agent_id: i32) -> Self {
self.agent_id = agent_id.into();
self
}
pub fn provider_secret(mut self, provider_secret: &str) -> Self {
self.provider_secret = provider_secret.to_string().into();
self
}
pub fn suite_id(mut self, suite_id: &str) -> Self {
self.suite_id = suite_id.to_string().into();
self
}
pub fn suite_secret(mut self, suite_secret: &str) -> Self {
self.suite_secret = suite_secret.to_string().into();
self
}
pub fn get_corpid(&self) -> &str {
&self.corp_id
}
fn key_with_prefix(&self, key: &str) -> String {
format!("cp:{}:{}", self.suite_id.to_owned().unwrap_or_default(), key)
}
pub fn new<S: Into<String>>(crop_id: S) -> WechatCpTpClient<SimpleStorage> {
let client = APIClient::<SimpleStorage>::from_session(crop_id.into(), "", "https://qyapi.weixin.qq.com", SimpleStorage::new());
WechatCpTpClient::<SimpleStorage>::from_client(client)
}
pub fn from_session<S: Into<String>>(crop_id: S, session: T) -> WechatCpTpClient<T> {
let client = APIClient::from_session(crop_id.into(), "", "https://qyapi.weixin.qq.com", session);
Self::from_client(client)
}
fn get_access_token(&self, auth_corp_id: &str) -> String {
let session = self.client.session();
session.get::<_, String>(self.key_with_prefix(auth_corp_id) + ACCESS_TOKEN_KEY, None).unwrap_or(None).unwrap_or_default()
}
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 fn get_crypto(&self) -> WechatCrypto {
let crp = WechatCrypto::new(&self.aes_key.to_owned().unwrap_or_default()).token(&self.token.to_owned().unwrap_or_default());
crp
}
pub fn get_suite_ticket(&self) -> LabradorResult<String> {
let session = self.client.session();
let token_key = format!("{}_suite_ticket_key_cp", self.corp_id);
let expires_key = format!("{}_suite_ticket_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 {
return Err(LabraError::ApiError("invaild suite ticket".to_string()));
}
Ok(token)
}
pub fn set_suite_ticket_expire(&self, suite_ticket: &str, expire_second: i64) -> LabradorResult<()> {
let expires_at = current_timestamp() + expire_second;
let session = self.client.session();
let token_key = format!("{}_suite_ticket_key_cp", self.corp_id);
let expires_key = format!("{}_suite_ticket_expires_at_cp", self.corp_id);
session.set(token_key.to_string(), suite_ticket, Some(expire_second as usize))?;
session.set(expires_key, expires_at, Some(expire_second as usize))?;
Ok(())
}
pub fn set_suite_ticket(&self, suite_ticket: &str) -> LabradorResult<()> {
self.set_suite_ticket_expire(suite_ticket, 28 * 60)
}
pub async fn get_suite_access_token(&self) -> LabradorResult<String> {
self.get_suite_access_token_force(false).await
}
pub async fn get_suite_access_token_force(&self, force_refresh: bool) -> LabradorResult<String> {
let session = self.client.session();
let token_key = format!("{}_suite_access_token_cp", self.corp_id);
let expires_key = format!("{}_suite_access_token_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 suite_ticket = self.get_suite_ticket().unwrap_or_default();
let req = json!({
"suite_id": self.suite_id,
"suite_secret": self.suite_secret,
"suite_ticket": suite_ticket
});
let result = self.client.post(WechatCpMethod::GetSuiteToken, vec![], req, RequestType::Json).await?.json::<Value>()?;
let result = WechatCommonResponse::parse::<WechatCpSuiteAccessTokenResponse>(result)?;
let token = result.suite_access_token;
let expires_in = result.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)
}
}
pub async fn get_suite_jsapi_ticket(&self, auth_corp_id: &str) -> LabradorResult<String> {
self.get_suite_jsapi_ticket_force(auth_corp_id, false).await
}
pub async fn get_suite_jsapi_ticket_force(&self, auth_corp_id: &str, force_refresh: bool) -> LabradorResult<String> {
let mut session = self.client.session();
let ticket_key = format!("{}_suite_jsapi_ticket_cp", self.corp_id);
let expires_key = format!("{}_suite_jsapi_ticket_expires_at_cp", self.corp_id);
let ticket: String = session.get(&ticket_key, Some("".to_string()))?.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 v = self.client.get(WechatCpMethod::GetSuiteJsapiTicket, vec![(TYPE.to_string(), AGENT_CONFIG.to_string()), (ACCESS_TOKEN.to_string(), self.get_access_token(auth_corp_id))], RequestType::Json).await?.json::<Value>()?;
let res = WechatCommonResponse::parse::<JsapiTicket>(v)?;
let ticket = res.ticket;
let expires_in = res.expires_in;
let expires_at = current_timestamp() + expires_in - 200;
session.set(&ticket_key, ticket.to_string(), 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_auth_corp_jsapi_ticket(&self, auth_corp_id: &str) -> LabradorResult<String> {
self.get_auth_corp_jsapi_ticket_force(auth_corp_id, false).await
}
pub async fn get_auth_corp_jsapi_ticket_force(&self, auth_corp_id: &str, force_refresh: bool) -> LabradorResult<String> {
let mut session = self.client.session();
let ticket_key = format!("{}_auth_corp_jsapi_ticket_cp", self.corp_id);
let expires_key = format!("{}_auth_corp_jsapi_ticket_expires_at_cp", self.corp_id);
let ticket: String = session.get(&ticket_key, Some("".to_string()))?.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 res = self.client.get(WechatCpMethod::GetJsapiTicket, vec![(ACCESS_TOKEN.to_string(), self.get_access_token(auth_corp_id))], RequestType::Json).await?.json::<JsapiTicket>()?;
let ticket = res.ticket;
let expires_in = res.expires_in;
let expires_at = current_timestamp() + expires_in - 200;
session.set(&ticket_key, ticket.to_string(), 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_corp_token(&self, auth_corpid: &str, permanent_code: &str) -> LabradorResult<AccessTokenResponse> {
self.get_corp_token_force(auth_corpid, permanent_code, false).await
}
pub async fn get_corp_token_force(&self, auth_corpid: &str, permanent_code: &str, force_refresh: bool) -> LabradorResult<AccessTokenResponse> {
let session = self.client.session();
let token_key = format!("{}_corp_access_token_cp", auth_corpid);
let expires_key = format!("{}_corp_access_token_expires_at_cp", auth_corpid);
let token: String = session.get(&token_key, Some("".to_owned()))?.unwrap_or_default();
let timestamp = get_timestamp();
let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
if expires_at <= timestamp || force_refresh {
let suite_ticket = self.get_suite_ticket()?;
let req = json!({
"auth_corpid": auth_corpid,
"permanent_code": permanent_code,
});
let v = self.client.post(WechatCpMethod::GetCorpToken, vec![], req, RequestType::Json).await?.json::<Value>()?;
let result = WechatCommonResponse::parse::<AccessTokenResponse>(v)?;
let token = result.access_token.to_string();
let expires_in = result.expires_in;
let expires_at = get_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(result)
} else {
Ok(AccessTokenResponse { access_token: token.to_string(), expires_in: expires_at })
}
}
pub async fn get_wechat_provider_token(&self) -> LabradorResult<String> {
let session = self.client.session();
let token_key = format!("{}_provider_access_token_cp", self.corp_id);
let expires_key = format!("{}_provider_access_token_expires_at_cp", self.corp_id);
let token: String = session.get(&token_key, Some("".to_owned()))?.unwrap_or_default();
let timestamp = get_timestamp();
let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
if expires_at <= timestamp {
let req = json!({
"corpid": self.corp_id,
"provider_secret": self.provider_secret,
});
let v = self.client.post(WechatCpMethod::GetProviderToken, vec![], req, RequestType::Json).await?.json::<Value>()?;
let result = WechatCommonResponse::parse::<WechatCpProviderToken>(v)?;
let token = result.provider_access_token.to_string();
let expires_in = result.expires_in;
let expires_at = get_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)
} else {
Ok(token)
}
}
pub async fn get_permanent_code_info(&self, auth_code: &str) -> LabradorResult<WechatCpThirdPermanentCodeInfo> {
let req = json!({
"auth_code": auth_code,
});
let result = self.post(WechatCpMethod::GetPermanentCode, vec![], req, RequestType::Json).await?.json::<Value>()?;
WechatCommonResponse::parse::<WechatCpThirdPermanentCodeInfo>(result)
}
pub async fn get_pre_auth_url(&self, redirect_uri: &str, state: Option<&str>) -> LabradorResult<String> {
let result = self.get(WechatCpMethod::GetPreAuthCode, vec![], RequestType::Json).await?.json::<WechatCpThirdPreauthCode>()?;
let mut pre_auth_url = format!("{}?suite_id={}&pre_auth_code={}&redirect_uri={}", AUTH_URL_INSTALL, self.suite_id.to_owned().unwrap_or_default(), result.pre_auth_code, urlencoding::encode(redirect_uri));
if let Some(state) = state {
pre_auth_url.push_str(&format!("&state={}", state));
}
Ok(pre_auth_url)
}
pub async fn set_session_info(&self, pre_auth_code: &str, app_ids: Vec<&str>, auth_type: u8) -> LabradorResult<WechatCommonResponse> {
let req = json!({
"pre_auth_code": pre_auth_code,
"session_info":
{
"appid": app_ids,
"auth_type": auth_type
}
});
self.post(WechatCpMethod::SetSessionInfo, vec![], req, RequestType::Json).await?.json::<WechatCommonResponse>()
}
pub async fn get_auth_info(&self, auth_corp_id: &str, permanent_code: &str) -> LabradorResult<WechatCpThirdAuthInfo> {
let req = json!({
"auth_corpid": auth_corp_id,
"permanent_code": permanent_code
});
let result = self.client.post(WechatCpMethod::GetAuthInfo, vec![], req, RequestType::Json).await?.json::<Value>()?;
WechatCommonResponse::parse::<WechatCpThirdAuthInfo>(result)
}
pub async fn get_admin_info(&self, auth_corp_id: &str, agent_id: i32) -> LabradorResult<Vec<AdminUserInfo>> {
let req = json!({
"auth_corpid": auth_corp_id,
"agentid": agent_id
});
let result = self.client.post(WechatCpMethod::GetAdminInfo, vec![], req, RequestType::Json).await?.json::<Value>()?;
let v = WechatCommonResponse::parse::<Value>(result)?;
serde_json::from_value::<Vec<AdminUserInfo>>(v["admin"].to_owned()).map_err(LabraError::from)
}
pub async fn get_app_qrcode_buffer(&self, suite_id: &str, appid: Option<i32>, state: Option<&str>, style: Option<u8>) -> LabradorResult<Bytes> {
let req = json!({
"suite_id": suite_id,
"appid": appid,
"state": state,
"style": style,
"result_type": 1
});
self.client.post(WechatCpMethod::GetAppQrcode, vec![], req, RequestType::Json).await?.bytes()
}
pub async fn get_app_qrcode_url(&self, suite_id: &str, appid: Option<i32>, state: Option<&str>, style: Option<u8>, result_type: Option<u8>) -> LabradorResult<String> {
let req = json!({
"suite_id": suite_id,
"appid": appid,
"state": state,
"style": style,
"result_type": 2
});
let v = self.client.post(WechatCpMethod::GetAppQrcode, vec![], req, RequestType::Json).await?.json::<Value>()?;
let v = WechatCommonResponse::parse::<Value>(v)?;
let qrcode = v["qrcode"].as_str().unwrap_or_default();
Ok(qrcode.to_string())
}
pub async fn corpid_to_opencorpid(&self, corpid: &str) -> LabradorResult<String> {
let req = json!({
"corpid": corpid,
});
let access_token = self.get_wechat_provider_token().await?;
let query = vec![(PROVIDER_ACCESS_TOKEN.to_string(), access_token)];
let v = self.client.post(WechatCpMethod::CorpToOpenCorpid, query, req, RequestType::Json).await?.json::<Value>()?;
let v = WechatCommonResponse::parse::<Value>(v)?;
let qrcode = v["open_corpid"].as_str().unwrap_or_default();
Ok(qrcode.to_string())
}
pub async fn create_auth_corp_jsapi_signature(&self, url: &str, auth_corp_id: &str) -> LabradorResult<JsapiSignature> {
Ok(self.created_wechat_jsapi_signature(url, auth_corp_id, &self.get_auth_corp_jsapi_ticket(auth_corp_id).await?))
}
pub async fn create_suite_jsapi_signature(&self, url: &str, auth_corp_id: &str) -> LabradorResult<JsapiSignature> {
Ok(self.created_wechat_jsapi_signature(url, auth_corp_id, &self.get_suite_jsapi_ticket(auth_corp_id).await?))
}
fn created_wechat_jsapi_signature(&self, url: &str, auth_corp_id: &str, jsapi_ticket: &str) -> JsapiSignature {
let timestamp = get_timestamp() / 1000;
let noncestr = get_nonce_str();
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("&"));
JsapiSignature {
app_id: auth_corp_id.to_string(),
nonce_str: noncestr,
url: url.to_string(),
signature,
timestamp,
}
}
async fn execute<D: WechatRequest, B: Serialize>(&self, request: D, corp_id: Option<&str>) -> LabradorResult<LabraResponse> {
let mut querys = request.get_query_params();
if request.is_need_token() {
if let Some(corp_id) = corp_id {
let access_token = self.get_access_token(corp_id);
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> {
if method.need_token() {
let token = self.get_suite_access_token_force(false).await?;
querys.push((SUITE_ACCESS_TOKEN.to_string(), 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> {
if method.need_token() {
let token = self.get_suite_access_token_force(false).await?.to_string();
params.push((SUITE_ACCESS_TOKEN.to_string(), token));
}
self.client.get(method, params, request_type).await
}
pub fn department(&self) -> WechatCpTpDepartment<T> {
WechatCpTpDepartment::new(self)
}
pub fn license(&self) -> WechatCpTpLicense<T> {
WechatCpTpLicense::new(self)
}
pub fn media(&self) -> WechatCpTpMedia<T> {
WechatCpTpMedia::new(self)
}
pub fn order(&self) -> WechatCpTpOrder<T> {
WechatCpTpOrder::new(self)
}
pub fn tag(&self) -> WechatCpTpTag<T> {
WechatCpTpTag::new(self)
}
pub fn user(&self) -> WechatCpTpUser<T> {
WechatCpTpUser::new(self)
}
pub fn agent(&self) -> WechatCpTpAgent<T> {
WechatCpTpAgent::new(self)
}
pub fn auth(&self) -> WechatCpTpAuth<T> {
WechatCpTpAuth::new(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WechatCpSuiteAccessTokenResponse {
pub suite_access_token: String,
pub expires_in: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WechatCpThirdPermanentCodeInfo {
pub access_token: Option<String>,
pub permanent_code: String,
pub auth_corp_info: AuthCorpInfo,
pub auth_info: Option<AuthInfo>,
pub auth_user_info: Option<AuthUserInfo>,
pub edition_info: Option<EditionInfo>,
pub expires_in: Option<i64>,
pub state: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthCorpInfo {
pub corpid: String,
pub corp_name: String,
pub corp_type: Option<String>,
pub corp_square_logo_url: Option<String>,
pub corp_round_logo_url: Option<String>,
pub corp_user_max: Option<i32>,
pub corp_agent_max: Option<i32>,
pub corp_full_name: Option<String>,
pub corp_wxqrcode: Option<String>,
pub corp_scale: Option<String>,
pub corp_industry: Option<String>,
pub corp_sub_industry: Option<String>,
pub location: Option<String>,
pub verified_end_time: Option<i64>,
pub subject_type: Option<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EditionInfo {
pub agent: Option<Vec<Agent>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthUserInfo {
pub userid: Option<String>,
pub name: Option<String>,
pub avatar: Option<String>,
pub open_userid: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdminUserInfo {
pub userid: Option<String>,
pub open_userid: Option<String>,
pub auth_type: Option<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthInfo {
pub agent: Vec<Agent>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Agent {
pub agentid: i32,
pub name: String,
pub round_logo_url: Option<String>,
pub square_logo_url: Option<String>,
pub edition_id: Option<String>,
pub edition_name: Option<String>,
pub app_status: Option<u8>,
pub auth_mode: Option<u8>,
pub is_customized_app: Option<bool>,
pub is_virtual_version: Option<bool>,
pub is_shared_from_other_corp: Option<bool>,
pub user_limit: Option<i32>,
pub expired_time: Option<i64>,
pub privilege: Option<Privilege>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Privilege {
pub level: Option<u8>,
pub allow_party: Option<Vec<String>>,
pub allow_user: Option<Vec<i32>>,
pub extra_party: Option<Vec<i32>>,
pub extra_tag: Option<Vec<i32>>,
pub extra_user: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WechatCpThirdPreauthCode {
pub pre_auth_code: String,
pub expires_in: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WechatCpThirdAuthInfo {
pub dealer_corp_info: Option<DealerCorpInfo>,
pub auth_corp_info: Option<AuthCorpInfo>,
pub auth_info: Option<AuthInfo>,
pub edition_info: Option<EditionInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DealerCorpInfo {
pub corpid: Option<String>,
pub corp_name: Option<String>,
}