wechat-cli 0.2.1

A CLI tool to interact with a Wechat iLink bot.
use anyhow::{Context, Result, anyhow, bail};
use log::{info, warn};

use crate::{
    storage,
    wechat::{
        api::WeixinApiClient,
        models::{FetchQrCodeResponse, QrCodeStatusResponse},
    },
};

pub async fn fetch_qrcode() -> Result<FetchQrCodeResponse> {
    let client = WeixinApiClient::new("", None);
    client.fetch_qr_code().await
}

pub async fn fetch_qrcode_status(qrcode_id: &str) -> Result<QrCodeStatusResponse> {
    let client = WeixinApiClient::new("", None);
    client.get_qr_code_status(qrcode_id).await
}

pub async fn login() -> Result<String> {
    let qr_resp = fetch_qrcode().await?;
    let qrcode_url = qr_resp
        .qrcode_url()
        .context("Login failed: no qrcode_url")?;
    let qrcode_id = qr_resp.qrcode_id().context("Login failed: no qrcode_id")?;

    let qr = qrcode::QrCode::new(qrcode_url.as_bytes())
        .map_err(|e| anyhow!("Login failed: QR generation failed: {e}"))?;
    let image = qr
        .render::<char>()
        .quiet_zone(true)
        .module_dimensions(2, 1)
        .build();
    println!("{image}");
    println!("Scan the QR code above with WeChat to login");

    loop {
        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
        let status_resp = fetch_qrcode_status(qrcode_id).await?;

        match status_resp.status() {
            "wait" => {}
            "scaned" => {
                info!("QR code scanned, waiting for confirmation...");
            }
            "expired" => {
                bail!("QR code expired");
            }
            "confirmed" => {
                let bot_token = status_resp
                    .bot_token()
                    .context("Login failed: no bot_token")?;
                let user_id = status_resp
                    .ilink_user_id()
                    .context("Login failed: no ilink_user_id")?;
                let account_id = user_id.to_string();

                let account_data = storage::AccountData {
                    bot_token: bot_token.to_string(),
                    saved_at: chrono::Utc::now().to_rfc3339(),
                    user_id: user_id.to_string(),
                    route_tag: None,
                };
                storage::save_account_data(&account_id, &account_data)?;

                info!("Login successful! User ID: {account_id}");
                return Ok(account_id);
            }
            other => {
                warn!("Unknown QR status: {other}");
            }
        }
    }
}