@wechatbot/wechatbot

March 23, 2026 · View on GitHub

WeChat iLink Bot SDK for Node.js — modular, extensible, production-grade.

Install

npm install @wechatbot/wechatbot

Requires Node.js >= 22 (for native fetch). Zero runtime dependencies.

Quick Start

import { WeChatBot } from '@wechatbot/wechatbot'

const bot = new WeChatBot()
await bot.login()

bot.onMessage(async (msg) => {
  await bot.sendTyping(msg.userId)
  await bot.reply(msg, `Echo: ${msg.text}`)
})

await bot.start()

Architecture

src/
├── core/           ← WeChatBot client, typed events, error hierarchy
├── transport/      ← HTTP client with retry & timeout
├── protocol/       ← Raw iLink API calls + wire types
├── auth/           ← QR login + credential persistence
├── messaging/      ← Poller, Sender, Typing, ContextStore
├── media/          ← AES-128-ECB crypto, CDN upload/download
├── middleware/      ← Express-style middleware engine + builtins
├── message/        ← Parser, Builder, friendly types
├── storage/        ← Pluggable storage (file, memory, custom)
├── logger/         ← Structured leveled logging
└── index.ts        ← Public exports

Configuration

const bot = new WeChatBot({
  storage: 'file',            // 'file' | 'memory' | custom Storage
  storageDir: '~/.wechatbot',
  logLevel: 'info',           // 'debug' | 'info' | 'warn' | 'error' | 'silent'
  loginCallbacks: {
    onQrUrl: (url) => renderQrCode(url),
    onScanned: () => console.log('Scanned!'),
  },
})

API Reference

Lifecycle

MethodDescription
new WeChatBot(options?)Create a bot instance
bot.login(options?)QR login (auto-skips if credentials exist)
bot.start()Start long-poll loop
bot.run(options?)login() + start() in one call
bot.stop()Stop gracefully
bot.isRunningWhether the poll loop is active

Receiving

MethodDescription
bot.onMessage(handler)Register message handler
bot.download(msg)Download any media from a message (image/file/video/voice)

Sending — reply(msg, content) / send(userId, content)

Both methods accept the same content types:

// Text (string shorthand)
await bot.reply(msg, 'Hello!')

// Text (object)
await bot.reply(msg, { text: 'Hello!' })

// Image with optional caption
await bot.reply(msg, { image: pngBuffer, caption: 'Screenshot' })

// Video with optional caption
await bot.reply(msg, { video: mp4Buffer, caption: 'Check this out' })

// File — auto-routes by extension (.png → image, .mp4 → video, else → file)
await bot.reply(msg, { file: data, fileName: 'report.pdf' })
await bot.reply(msg, { file: data, fileName: 'photo.png' })   // → sent as image

// From URL — auto-download + auto-detect type
await bot.reply(msg, { url: 'https://example.com/photo.jpg' })

// Send to a user by ID (same content options)
await bot.send(userId, { image: buffer, caption: 'Hi!' })

Typing

MethodDescription
bot.sendTyping(userId)Show "typing..." indicator
bot.stopTyping(userId)Cancel typing indicator

Advanced

MethodDescription
bot.sendRaw(payload)Send pre-built MessageBuilder payload
bot.upload(options)Upload to CDN without sending
bot.downloadRaw(media, aeskey?)Download from raw CDN reference
bot.createMessage(userId)Fluent MessageBuilder

Middleware

bot.use(loggingMiddleware(bot.logger))
bot.use(rateLimitMiddleware({ maxMessages: 10, windowMs: 60_000 }))
bot.use(typeFilterMiddleware('text', 'image'))
bot.use(filterMiddleware(/^\/\w+/))

// Custom middleware
bot.use(async (ctx, next) => {
  console.log(`From: ${ctx.message.userId}`)
  await next()
})

Events

bot.on('login', (creds) => { })
bot.on('message', (msg) => { })
bot.on('session:expired', () => { })
bot.on('session:restored', (creds) => { })
bot.on('error', (err) => { })
bot.on('poll:start', () => { })
bot.on('poll:stop', () => { })
bot.on('close', () => { })

Message Types

interface IncomingMessage {
  userId: string
  text: string
  type: 'text' | 'image' | 'voice' | 'file' | 'video'
  timestamp: Date
  images: ImageContent[]
  voices: VoiceContent[]
  files: FileContent[]
  videos: VideoContent[]
  quotedMessage?: QuotedMessage
  raw: WireMessage
}

Storage Interface

interface Storage {
  get<T>(key: string): Promise<T | undefined>
  set<T>(key: string, value: T): Promise<void>
  delete(key: string): Promise<void>
  has(key: string): Promise<boolean>
  clear(): Promise<void>
}

Development

npm install
npm run build    # TypeScript → dist/
npm test         # 69 unit tests
npm run lint     # Type check

License

MIT