Architecture

March 23, 2026 Β· View on GitHub

All four SDKs (Node.js, Python, Go, Rust) follow the same layered architecture and expose a consistent API surface.

Layers

graph TD
    A["πŸ€– Application β€” Your Bot Code"] --> B["Middleware (Node.js only)"]
    B --> C["Bot Client β€” Orchestrator: login, run, reply"]
    C --> D["Poller"]
    C --> E["Sender"]
    C --> F["Typing"]
    C --> G["Media"]
    D --> H["Context Store β€” context_token lifecycle"]
    E --> H
    F --> H
    G --> H
    H --> I["Protocol / API β€” Raw HTTP calls to iLink"]
    I --> J["Transport / HTTP β€” HTTP client with retry"]
    J --> K["Storage β€” Credentials + state persistence"]

SDK Comparison

FeatureNode.jsPythonGoRust
Package@wechatbot/wechatbotwechatbot-sdk (PyPI)github.com/corespeed-io/wechatbot/golangwechatbot (crates.io)
Async modelasync/await (Promises)async/await (asyncio)goroutines + context.Contextasync/await (tokio)
Middlewareβœ“ Express-style pipelineβ€” (use decorator composition)β€” (use handler composition)β€” (use closures)
StoragePluggable (file/memory/custom)File-basedFile-basedFile-based
Media cryptoβœ“ AES-128-ECBβœ“ AES-128-ECBβœ“ AES-128-ECBβœ“ AES-128-ECB
EventsTyped EventEmitterCallbacks (on_qr_url, on_error…)Callbacks (OnError, OnQRURL)Callbacks
Error types6 typed error classesError hierarchyAPIError with methodsthiserror enum
Dependencies0 runtimeaiohttp, cryptographystdlib onlyreqwest, serde, aes, tokio

Shared Concepts

context_token

Every reply must include the context_token from the incoming message. All SDKs:

  1. Cache tokens in memory per (userId)
  2. Auto-extract from incoming messages
  3. Auto-inject into outgoing messages via reply()
  4. (Node.js) Persist to storage for restart survival

QR Login Flow

All SDKs implement the same flow:

  1. GET /get_bot_qrcode β†’ get QR URL
  2. Display QR to user
  3. GET /get_qrcode_status poll loop (2s interval)
  4. On confirmed β†’ extract credentials, persist to ~/.wechatbot/
  5. On expired β†’ request new QR

Long-Poll Loop

  1. POST /getupdates with cursor (35s server hold)
  2. Parse messages, cache context_tokens
  3. Dispatch to handlers
  4. On -14 error β†’ clear state, re-login
  5. On network error β†’ exponential backoff (1s β†’ 10s max)

Media Pipeline

All SDKs support encrypted media upload and download via the WeChat CDN:

  1. Upload: generate AES key β†’ encrypt (AES-128-ECB) β†’ getuploadurl β†’ POST to CDN β†’ get download param
  2. Download: GET from CDN β†’ decrypt (AES-128-ECB) with key from message

The Node.js SDK additionally provides:

  • Unified reply(msg, content) / send(userId, content) β€” one method handles text, image, video, file, and URL
  • Auto-routing by MIME β€” { file: data, fileName: 'photo.png' } routes as image; .mp4 as video; others as file attachment
  • Remote URL support β€” { url: 'https://...' } auto-downloads and sends
  • Voice transcode β€” SILK β†’ WAV via optional silk-wasm dependency
  • Markdown stripping β€” stripMarkdown() for cleaning AI model output before sending to WeChat

Text Chunking

All SDKs split text at 2000 characters:

  • Priority: paragraph break (\n\n) β†’ line break (\n) β†’ space β†’ hard cut
  • Each chunk gets a unique client_id
  • All chunks share the same context_token

File Structure

Node.js

nodejs/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ core/               # Client (unified reply/send/download), events, errors
β”‚   β”œβ”€β”€ transport/          # HTTP with retry
β”‚   β”œβ”€β”€ protocol/           # Wire types + API calls
β”‚   β”œβ”€β”€ auth/               # QR login
β”‚   β”œβ”€β”€ messaging/          # Poller, sender, typing, context
β”‚   β”œβ”€β”€ media/              # AES crypto, CDN up/down, MIME, voice transcode,
β”‚   β”‚                       #   remote URL download, markdown stripping
β”‚   β”œβ”€β”€ middleware/          # Engine + 4 builtins
β”‚   β”œβ”€β”€ message/            # Parser, builder, types
β”‚   β”œβ”€β”€ storage/            # File, memory, interface
β”‚   └── logger/             # Structured logging
β”œβ”€β”€ tests/                  # 69 unit tests
└── examples/               # 3 example bots

Python

python/
β”œβ”€β”€ wechatbot/
β”‚   β”œβ”€β”€ __init__.py         # Public exports
β”‚   β”œβ”€β”€ client.py           # WeChatBot (login, start, reply, send)
β”‚   β”œβ”€β”€ protocol.py         # Raw iLink API calls
β”‚   β”œβ”€β”€ auth.py             # QR login + credential persistence
β”‚   β”œβ”€β”€ types.py            # All types (dataclasses)
β”‚   β”œβ”€β”€ errors.py           # Error hierarchy
β”‚   └── crypto.py           # AES-128-ECB encrypt/decrypt
β”œβ”€β”€ examples/
β”‚   └── echo_bot.py
└── tests/
    β”œβ”€β”€ test_crypto.py      # 10 tests
    └── test_client.py      # 8 tests

Go

golang/
β”œβ”€β”€ types.go                # All public types
β”œβ”€β”€ bot.go                  # Bot client
β”œβ”€β”€ internal/
β”‚   β”œβ”€β”€ protocol/api.go     # iLink HTTP calls
β”‚   β”œβ”€β”€ auth/login.go       # QR login + credentials
β”‚   └── crypto/aes.go       # AES-128-ECB
└── examples/
    └── echo-bot/main.go

Rust

rust/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ lib.rs              # Re-exports
β”‚   β”œβ”€β”€ types.rs            # All types (serde)
β”‚   β”œβ”€β”€ error.rs            # Error hierarchy
β”‚   β”œβ”€β”€ protocol.rs         # iLink API calls
β”‚   β”œβ”€β”€ crypto.rs           # AES-128-ECB + tests
β”‚   └── bot.rs              # Bot client
└── examples/
    └── echo_bot.rs