use std::time::Duration;
use crate::api::client::HttpApiClient;
use crate::error::Result;
use crate::types::{
DEFAULT_ILINK_BOT_TYPE, DEFAULT_QR_GET_TIMEOUT_MS, DEFAULT_QR_POLL_TIMEOUT_MS, QrCodeResponse,
QrStatusResponse,
};
#[derive(Debug, Clone)]
pub struct QrLoginSession {
pub qrcode: String,
pub qrcode_img_content: String,
}
#[derive(Debug, Clone)]
pub enum LoginStatus {
Wait,
Scanned,
ScannedButRedirect {
redirect_host: String,
},
Confirmed {
bot_token: String,
ilink_bot_id: String,
base_url: String,
ilink_user_id: String,
},
Expired,
}
pub struct QrLoginApi<'a> {
api: &'a HttpApiClient,
}
impl<'a> QrLoginApi<'a> {
pub(crate) fn new(api: &'a HttpApiClient) -> Self {
Self { api }
}
pub async fn start(&self, bot_type: Option<&str>) -> Result<QrLoginSession> {
let bt = bot_type.unwrap_or(DEFAULT_ILINK_BOT_TYPE);
let endpoint = format!(
"ilink/bot/get_bot_qrcode?bot_type={}",
urlencoding::encode(bt)
);
let raw = self
.api
.api_get(&endpoint, Duration::from_millis(DEFAULT_QR_GET_TIMEOUT_MS))
.await?;
let resp: QrCodeResponse = serde_json::from_str(&raw)?;
Ok(QrLoginSession {
qrcode: resp.qrcode,
qrcode_img_content: resp.qrcode_img_content,
})
}
pub async fn poll_status(&self, session: &QrLoginSession) -> Result<LoginStatus> {
let endpoint = format!(
"ilink/bot/get_qrcode_status?qrcode={}",
urlencoding::encode(&session.qrcode)
);
let raw = match self
.api
.api_get(&endpoint, Duration::from_millis(DEFAULT_QR_POLL_TIMEOUT_MS))
.await
{
Ok(r) => r,
Err(crate::error::Error::Http(e)) if e.is_timeout() => {
return Ok(LoginStatus::Wait);
}
Err(e) => return Err(e),
};
let resp: QrStatusResponse = serde_json::from_str(&raw)?;
Ok(match resp.status.as_str() {
"scaned" => LoginStatus::Scanned,
"scaned_but_redirect" => LoginStatus::ScannedButRedirect {
redirect_host: resp.redirect_host.unwrap_or_default(),
},
"confirmed" => LoginStatus::Confirmed {
bot_token: resp.bot_token.unwrap_or_default(),
ilink_bot_id: resp.ilink_bot_id.unwrap_or_default(),
base_url: resp.baseurl.unwrap_or_default(),
ilink_user_id: resp.ilink_user_id.unwrap_or_default(),
},
"expired" => LoginStatus::Expired,
_ => LoginStatus::Wait,
})
}
}
pub struct StandaloneQrLogin {
api: HttpApiClient,
}
impl StandaloneQrLogin {
pub fn new(config: &crate::config::WeixinConfig) -> Self {
Self {
api: HttpApiClient::new(config),
}
}
pub async fn start(&self, bot_type: Option<&str>) -> Result<QrLoginSession> {
QrLoginApi::new(&self.api).start(bot_type).await
}
pub async fn poll_status(&self, session: &QrLoginSession) -> Result<LoginStatus> {
QrLoginApi::new(&self.api).poll_status(session).await
}
}