wx-bot-sdk 0.1.3

Standalone Weixin Bot SDK in Rust
Documentation
use wx_bot_sdk::{
    BotAccountOptions, MultiStartOptions, MultiWeixinBot, MultiWeixinBotOptions,
    auth::accounts::list_weixin_account_ids, bot::handler, resolve_weixin_account,
};

#[tokio::main]
async fn main() -> wx_bot_sdk::Result<()> {
    let accounts = read_accounts();
    if accounts.is_empty() {
        eprintln!("Usage:");
        eprintln!("  WEIXIN_BOT_TOKENS=\"token1,token2\" cargo run --example multi_echo");
        eprintln!("  cargo run --example multi_echo -- token1 token2");
        eprintln!("\n如果已通过扫码登录保存账号,也可以直接运行:");
        eprintln!("  cargo run --example multi_echo");
        std::process::exit(1);
    }

    let multi = MultiWeixinBot::new(MultiWeixinBotOptions {
        accounts,
        state_dir: None,
    });

    println!("starting accounts: {:?}", multi.account_ids());
    multi
        .start(MultiStartOptions {
            long_poll_timeout_ms: None,
            on_message: handler(|ctx| async move {
                println!("[{}] 来自 {}: {}", ctx.account_id, ctx.from, ctx.body);
                Ok(Some(format!("你说了: {}", ctx.body)))
            }),
        })
        .await?;

    tokio::signal::ctrl_c().await?;
    println!("stopping all accounts...");
    multi.stop().await?;
    multi.join().await?;
    Ok(())
}

fn read_accounts() -> Vec<BotAccountOptions> {
    let tokens = read_tokens();
    if !tokens.is_empty() {
        return tokens
            .into_iter()
            .map(|token| BotAccountOptions {
                token,
                account_id: None,
                base_url: None,
                cdn_base_url: None,
            })
            .collect();
    }

    list_weixin_account_ids()
        .into_iter()
        .filter_map(|account_id| match resolve_weixin_account(&account_id) {
            Ok(account) => account.token.map(|token| BotAccountOptions {
                token,
                account_id: Some(account.account_id),
                base_url: Some(account.base_url),
                cdn_base_url: Some(account.cdn_base_url),
            }),
            Err(err) => {
                eprintln!("跳过账号 {account_id}: {err}");
                None
            }
        })
        .collect()
}

fn read_tokens() -> Vec<String> {
    let mut tokens = std::env::args()
        .skip(1)
        .flat_map(|arg| split_tokens(&arg).collect::<Vec<_>>())
        .collect::<Vec<_>>();

    if tokens.is_empty()
        && let Ok(raw) = std::env::var("WEIXIN_BOT_TOKENS")
    {
        tokens = split_tokens(&raw).collect();
    }

    tokens
}

fn split_tokens(raw: &str) -> impl Iterator<Item = String> + '_ {
    raw.split(',')
        .map(str::trim)
        .filter(|s| !s.is_empty())
        .map(ToOwned::to_owned)
}