# 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?;
```
### 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