English (this file) · 简体中文
Tencent QQ Open Platform Node.js SDK. Provides every protocol-layer capability
needed to integrate with the QQ Bot Open Platform: HTTP REST, dual WebSocket /
Webhook transport, a Koa-style middleware pipeline, messaging, media upload
(including chunked upload for large files), and C2C streaming messages
(stream_messages).
- Node.js >= 20 (uses global
fetch/AbortController) - Pure ESM (
"type": "module"); CJS consumers should use dynamicimport()
pnpm add @tencent-connect/qqbot-nodejs
# or
npm install @tencent-connect/qqbot-nodejsimport { QQBot } from "@tencent-connect/qqbot-nodejs";
const bot = new QQBot({
appId: process.env.QQBOT_APP_ID!,
appSecret: process.env.QQBOT_APP_SECRET!,
logger: console,
});
bot.on("message", async (ctx, msg) => {
// ctx — Koa-style MiddlewareContext carrying data injected by middleware
await bot.sendText(msg.replyTarget, `Echo: ${msg.content}`);
});
await bot.start();See the complete examples under examples/:
| Example | Description |
|---|---|
| playground | Core capabilities demo (text, streaming, media, commands) |
| middleware | Full 13-layer Koa-style middleware pipeline |
| webhook | Webhook (HTTP callback) transport |
| send-plain-100 | Plain-text send baseline |
| send-streaming-100 | Streaming-message send baseline |
Examples read
QQBOT_APP_ID/QQBOT_APP_SECRETfromprocess.envonly for demo convenience. The SDK itself never reads environment variables — credentials are always injected vianew QQBot({ appId, appSecret }).
// WebSocket (default) — long-lived connection + heartbeat + RESUME
const bot = new QQBot({ appId, appSecret });
// Webhook — HTTP callback, suited to Serverless / horizontal scaling
const bot = new QQBot({
appId, appSecret,
transport: "webhook",
webhook: { port: 8080, path: "/callback" },
});Middleware, event listeners and send APIs are identical in both modes.
The SDK uses an onion-model middleware chain; bot.on("message") is the
innermost downstream:
import { errorHandler, messageFilter, contentSanitizer, mentionGate } from "@tencent-connect/qqbot-nodejs";
bot.use(errorHandler());
bot.use(messageFilter({ skipSelfEcho: true, dedup: { windowMs: 5000 } }));
bot.use(contentSanitizer({ stripBotMention: true }));
bot.use(mentionGate({ requireMentionInGroup: true }));
// Custom middleware
bot.use(async (ctx, next) => {
const start = Date.now();
await next();
ctx.log.debug?.(`elapsed: ${Date.now() - start}ms`);
});Built-in middleware:
| Middleware | Purpose |
|---|---|
errorHandler |
Unified error capture + friendly reply |
messageFilter |
Bot echo filtering + message dedup |
rateLimiter |
Three-tier rate limiting (sender / group / global) |
concurrencyGuard |
Serial processing per user / group (avoids stream races) |
accessPolicy |
Allow / block lists |
contentSanitizer |
Strip @markers / face tags / whitespace |
mentionGate |
Group @bot gating |
quoteRef |
Message index recording + quoted-message resolution |
historyBuffer |
Group history buffer |
envelopeFormatter |
Assemble LLM prompt context (XML tags) |
typingIndicator |
Automatic C2C typing |
slashCommand |
/cmd command framework |
await bot.sendText(target, "hello");sendImage / sendVoice / sendVideo / sendFile are convenience wrappers
over sendMedia. Sources ≥ 5 MB switch to chunked upload automatically.
await bot.sendImage(target, { localPath: "/tmp/cat.jpg" });
await bot.sendFile(
target,
{ buffer: bigBuffer },
{
fileName: "report.pdf",
onProgress: (uploaded, total) => console.log(`${uploaded}/${total}`),
},
);const stream = bot.openStream({ target });
for (const partial of generator) {
await stream.update(partial);
}
await stream.complete();QQ Open Platform constraint: stream_messages is available for C2C (private chat) only.
bot.on("ready", () => console.log("connected"));
bot.on("resumed", () => console.log("reconnected"));
bot.on("disconnected", ({ code, willReconnect }) => console.log("closed", code, willReconnect));
bot.on("error", (err) => console.error(err)); // recoverable failures; never ends start()
bot.on("message", (ctx, msg) => { /* C2C / Group / Guild / DM */ });
bot.on("interaction", (ctx, event) => { /* button click etc. */ });WebSocket mode reconnects forever by default. start() resolves on stop() /
abort, and rejects — after releasing the connection and token refresher — only
when it cannot recover:
| Rejection | Cause | Handling |
|---|---|---|
GatewayError GATEWAY_FATAL_CLOSE |
4914 offline / sandbox-only, 4915 banned | Needs operator action; don't restart |
GatewayError GATEWAY_RETRY_EXHAUSTED |
Only with a finite reconnect.maxAttempts |
Restart with backoff |
Error |
Startup token fetch failed (default tokenPrefetch: "sync") |
Check credentials / network |
import { GatewayErrorCode, QQBot } from "@tencent-connect/qqbot-nodejs";
// Set a limit only when an outer supervisor owns process-level restarts
const bot = new QQBot({ appId, appSecret, reconnect: { maxAttempts: 10 } });
try {
await bot.start(signal);
} catch (err) {
if ((err as { code?: string }).code === GatewayErrorCode.FATAL_CLOSE) markBlocked(err);
else scheduleRestart(err);
}bot.stop(); bot.start(); restarts in place. See USAGE.md §7
for close-code handling.
import {
ApiClient,
TokenManager,
GatewayConnection,
withRetry,
} from "@tencent-connect/qqbot-nodejs/protocol";- USAGE.md — complete usage guide (currently in Chinese; contributions to translate are very welcome).
These peerDependencies are declared as optional. Install them only if you
need the feature:
| Package | Enables |
|---|---|
silk-wasm |
Sending voice messages (SILK encoding) |
mpg123-decoder |
Decoding MP3 audio to PCM before encoding |
Bug reports, feature proposals and pull requests are welcome. Please read:
- CONTRIBUTING.md — dev workflow & commit convention
- SECURITY.md — how to report vulnerabilities privately
src/
├── QQBot.ts ← High-level facade
├── streaming.ts ← C2C streaming-message controller
├── index.ts ← Public API
├── middleware/ ← Koa-style middleware
│ ├── types.ts Middleware types + onion runner
│ ├── error-handler.ts Unified error capture
│ ├── message-filter.ts Echo filtering + dedup
│ ├── rate-limiter.ts Three-tier rate limiting
│ ├── concurrency-guard.ts Serial processing per user / group
│ ├── access-policy.ts Allow / block lists
│ ├── content-sanitizer.ts Content sanitizing
│ ├── mention-gate.ts @bot gating
│ ├── quote-ref.ts Quote resolution + message index
│ ├── history-buffer.ts Group history buffer
│ ├── envelope-formatter.ts LLM prompt assembly
│ ├── typing-indicator.ts Typing state
│ └── slash-command.ts Command framework
└── protocol/ ← Protocol layer
├── api/
│ ├── api-client.ts HTTP client
│ ├── token.ts access_token management
│ ├── messages.ts Message sending
│ ├── media.ts Small-file upload
│ ├── media-chunked.ts Chunked large-file upload
│ ├── retry.ts Retry engine
│ └── routes.ts Route templates
├── gateway/
│ ├── constants.ts opcode / intent / close code
│ ├── codec.ts Message decoding
│ ├── reconnect.ts Reconnect state machine + reconnect policy
│ ├── errors.ts GatewayError (terminal start() errors)
│ ├── event-dispatcher.ts Event → InboundMessage
│ └── gateway-connection.ts WebSocket lifecycle
├── transport/
│ ├── types.ts EventTransport interface
│ ├── webhook.ts Webhook transport
│ ├── webhook-verify.ts Ed25519 signature verification
│ └── webhook-server-node.ts Built-in node:http adapter
├── utils/
│ ├── format.ts Formatting helpers
│ ├── file-utils.ts File helpers
│ └── upload-cache.ts file_info TTL cache
├── types.ts All public types + errors
└── index.ts protocol sub-entry
Thanks to everyone who has contributed to this project!
Contributor avatars are rendered by contrib.rocks.
MIT © Tencent