# wechatbot — Rust SDK WeChat iLink Bot SDK for Rust — async, type-safe, zero-copy where possible. ## Install ```toml [dependencies] wechatbot = "0.1" tokio = { version = "1", features = ["full"] } ``` Requires Rust 2021 edition. Built on `tokio` + `reqwest`. ## Quick Start ```rust use wechatbot::{WeChatBot, BotOptions}; #[tokio::main] async fn main() { let bot = WeChatBot::new(BotOptions::default()); let creds = bot.login(false).await.unwrap(); println!("Logged in: {}", creds.account_id); bot.on_message(Box::new(|msg| { println!("{}: {}", msg.user_id, msg.text); })).await; bot.run().await.unwrap(); } ``` ## Architecture ``` src/ ├── lib.rs ← Public re-exports ├── types.rs ← All protocol & public types (serde) ├── error.rs ← Error hierarchy (thiserror) ├── protocol.rs ← Raw iLink API calls (reqwest) ├── crypto.rs ← AES-128-ECB encrypt/decrypt + key encoding └── bot.rs ← WeChatBot client (login, run, reply, send) ``` ## API Reference ### Creating a Bot ```rust use wechatbot::{WeChatBot, BotOptions}; let bot = WeChatBot::new(BotOptions { base_url: None, // default: ilinkai.weixin.qq.com cred_path: None, // default: ~/.wechatbot/credentials.json on_qr_url: Some(Box::new(|url| { println!("Scan: {}", url); })), on_error: Some(Box::new(|err| { eprintln!("Error: {}", err); })), }); ``` ### Authentication ```rust // Login (skips QR if credentials exist) let creds = bot.login(false).await?; // Force re-login let creds = bot.login(true).await?; // Credentials struct println!("Token: {}", creds.token); println!("Base URL: {}", creds.base_url); println!("Account: {}", creds.account_id); println!("User: {}", creds.user_id); ``` ### Message Handling ```rust bot.on_message(Box::new(|msg| { match msg.content_type { ContentType::Text => println!("Text: {}", msg.text), ContentType::Image => { for img in &msg.images { println!("Image URL: {:?}", img.url); } } ContentType::Voice => { for voice in &msg.voices { println!("Voice: {:?} ({}ms)", voice.text, voice.duration_ms.unwrap_or(0)); } } ContentType::File => { for file in &msg.files { println!("File: {:?}", file.file_name); } } ContentType::Video => println!("Video received"), } if let Some(ref quoted) = msg.quoted { println!("Quoted: {:?}", quoted.title); } })).await; ``` ### Sending Messages ```rust // Reply to incoming message bot.reply(&msg, "Echo: hello").await?; // Send to user (needs prior context_token) bot.send(user_id, "Hello").await?; // Typing indicator bot.send_typing(user_id).await?; ``` ### Media Operations ```rust // Reply with media content bot.reply_media(&msg, SendContent::Image(png_bytes)).await?; bot.reply_media(&msg, SendContent::File { data, file_name: "report.pdf".into() }).await?; bot.reply_media(&msg, SendContent::Video(mp4_bytes)).await?; ``` ```rust // Download media from incoming message (priority: image > file > video > voice) if let Some(media) = bot.download(&msg).await? { println!("Type: {}, Size: {} bytes", media.media_type, media.data.len()); if let Some(name) = &media.file_name { println!("Filename: {}", name); } } // Download a raw CDN reference directly let raw = bot.download_raw(&msg.images[0].media.as_ref().unwrap(), None).await?; ``` ```rust // Upload to CDN without sending a message let result = bot.upload(&file_bytes, user_id, 3).await?; ``` ### Lifecycle ```rust // Start polling (blocks) bot.run().await?; // Stop bot.stop().await; ``` ## Error Handling ```rust use wechatbot::WeChatBotError; match result { Err(WeChatBotError::Api { message, errcode, .. }) => { if errcode == -14 { // session expired — handled automatically } } Err(WeChatBotError::NoContext(user_id)) => { // no context_token for this user yet } Err(WeChatBotError::Transport(e)) => { // network error } _ => {} } ``` ## AES-128-ECB Crypto ```rust use wechatbot::{generate_aes_key, encrypt_aes_ecb, decrypt_aes_ecb, decode_aes_key}; // Generate key let key = generate_aes_key(); // Encrypt/decrypt let ciphertext = encrypt_aes_ecb(b"Hello", &key); let plaintext = decrypt_aes_ecb(&ciphertext, &key)?; // Decode protocol key (handles all 3 formats) let key = decode_aes_key("ABEiM0RVZneImaq7zN3u/w==")?; let key = decode_aes_key("00112233445566778899aabbccddeeff")?; ``` ## Types All protocol types derive `Serialize` + `Deserialize` + `Clone` + `Debug`: ```rust // Wire-level (protocol) WireMessage, WireMessageItem, CDNMedia, TextItem, ImageItem, ... // Parsed (user-friendly) IncomingMessage, ImageContent, VoiceContent, FileContent, VideoContent // Auth Credentials // Enums MessageType, MessageState, MessageItemType, ContentType, MediaType ``` ## Testing ```bash cd rust cargo test ``` ## License MIT