The Node.js SDK is the most feature-rich implementation. 69 tests, zero runtime dependencies. Requires Node.js β₯ 22.
Install
npm install @wechatbot/wechatbot
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()
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!'), onExpired: () => console.log('Expired...'), }, })
Sending β reply() and send()
Two methods handle all outgoing messages. reply() replies to an incoming message (auto context_token, auto cancel typing). send() sends to a user by ID.
Both accept the same SendContent type:
// 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/.jpg/.gif/.webp β sent as image // .mp4/.mov/.webm β sent as video // everything else β sent as file attachment await bot.reply(msg, { file: data, fileName: 'report.pdf' }) await bot.reply(msg, { file: data, fileName: 'photo.png' }) // β image! // From URL β auto-download + auto-detect type from Content-Type / extension await bot.reply(msg, { url: 'https://example.com/photo.jpg' }) await bot.reply(msg, { url: 'https://picsum.photos/400/300', caption: 'Random!' }) // send() works the same way, just takes userId instead of message await bot.send(userId, 'Hello!') await bot.send(userId, { image: buffer, caption: 'Hi!' })
Downloading media
One method downloads any media type from an incoming message:
bot.onMessage(async (msg) => { const media = await bot.download(msg) if (!media) return // no media in this message console.log(media.type) // 'image' | 'file' | 'video' | 'voice' console.log(media.data) // Buffer console.log(media.fileName) // 'report.pdf' (for files) console.log(media.format) // 'wav' | 'silk' (for voice) // Save to disk await writeFile(`/tmp/${media.fileName ?? 'download'}`, media.data) // Echo back if (media.type === 'image') { await bot.reply(msg, { image: media.data, caption: 'Got your image!' }) } })
Priority: image > file > video > voice. Voice is auto-transcoded from SILK to WAV (if silk-wasm is installed).
Middleware
Express/Koa-style composable middleware pipeline. Runs before message handlers.
import { WeChatBot, loggingMiddleware, rateLimitMiddleware, typeFilterMiddleware, filterMiddleware, } from '@wechatbot/wechatbot' const bot = new WeChatBot() 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) => { const start = Date.now() await next() console.log(`Processed in ${Date.now() - start}ms`) })
Middleware that doesn't call next() stops the chain β perfect for auth, filtering, routing.
Pluggable storage
// File storage (default) β survives restarts const bot1 = new WeChatBot({ storage: 'file' }) // Memory storage β fast, ephemeral const bot2 = new WeChatBot({ storage: 'memory' }) // Custom β implement 5 methods class RedisStorage { async get(key) { } async set(key, value) { } async delete(key) { } async has(key) { } async clear() { } } const bot3 = new WeChatBot({ storage: new RedisStorage() })
Events
bot.on('login', (creds) => console.log(`Logged in: ${creds.accountId}`)) bot.on('session:expired', () => console.log('Session expired')) bot.on('session:restored', (creds) => console.log('Restored')) bot.on('error', (err) => console.error(err)) bot.on('poll:start', () => { }) bot.on('poll:stop', () => { }) bot.on('close', () => { })
Advanced: MessageBuilder
For complex multi-item messages, use the builder directly:
const payload = bot.createMessage(userId) .text("Here's your report:") .file({ media: cdnRef, fileName: 'report.pdf', size: 542188 }) .build() await bot.sendRaw(payload)
Other advanced methods: bot.upload(opts) for CDN upload without sending, bot.downloadRaw(media, aeskey?) for raw CDN references.
API reference
| Method | Description | |
|---|---|---|
| new WeChatBot(opts?) | Create instance | |
| bot.login(opts?) | QR login (skips if credentials exist) | |
| bot.start() | Start long-poll loop | |
| bot.run(opts?) | login() + start() in one call | |
| bot.stop() | Stop gracefully | |
| bot.onMessage(handler) | Register message handler | |
| bot.reply(msg, content) | Reply β text, image, video, file, or URL | |
| bot.send(userId, content) | Send to user β same content types | |
| bot.download(msg) | Download any media from message | |
| bot.sendTyping(userId) | Show "typing..." indicator | |
| bot.stopTyping(userId) | Cancel typing indicator | |
| bot.use(middleware) | Add middleware to pipeline | |
| bot.sendRaw(payload) | Send pre-built MessageBuilder payload | |
| bot.upload(opts) | Upload to CDN without sending | |
| bot.downloadRaw(media) | Download from raw CDN reference | |
| bot.createMessage(userId) | Create MessageBuilder for user |