@tencent-connect/qqbot-nodejs review
A QQ bot that streams answers still needs a working QQ connection before it needs a model. @tencent-connect/qqbot-nodejs supplies that connection: a Node.js client for QQ Open Platform HTTP APIs, incoming events, replies and media uploads. It supports WebSocket and webhook transports. Its streaming helper accepts only C2C targets and requires an incoming message ID. Use it as the messaging layer of your own application, with your own answer generation and business logic.
Choose @tencent-connect/qqbot-nodejs when your application needs a QQ messaging client and you can own platform setup and bot logic. Start with one plain-text reply, then add C2C streaming only if private replies meet your product needs.
Answers from our run
@tencent-connect/qqbot-nodejs or qq-guild-bot: which should you use?
qq-guild-bot: Evaluate it for a QQ guild/channel bot: its published README describes a Node.js SDK for that task. Check its own event and API documentation before choosing it over this private-chat and group workflow. Choose @tencent-connect/qqbot-nodejs when your application needs a QQ messaging client and you can own platform setup and bot logic.
When should you not use @tencent-connect/qqbot-nodejs?
Your product requires streaming replies in groups. The streaming helper rejects targets outside C2C.
Use it if
- You already have a QQ Open Platform bot and want to implement its private-chat or group replies in a Node.js service.
- Your service needs a choice between a long-lived WebSocket connection and an HTTP webhook callback.
- You want to send incremental answers in private C2C replies and can retain the incoming reply target.
- You need QQ media sending alongside text, and can handle local files and the platform configuration yourself.
- Your product requires streaming replies in groups. The streaming helper rejects targets outside C2C.
- Your runtime is older than Node.js 20 or your deployment requires synchronous CommonJS require(). This package declares Node.js >=20 and import-only exports.
- You expect installing a package to provide a complete AI assistant. You still need to build answer generation, application state and access rules.
- You need a platform-independent bot framework. This client is tied to QQ Open Platform.
- Your team needs a complete English reference. The English README points to a fuller usage guide that is currently in Chinese.
Setup reality
Start with a QQ Open Platform bot's AppID and AppSecret. Pass them into the constructor from your application's secret store. The SDK does not load environment variables itself. The examples below use environment variable names only to show the injection boundary. They are documentation-based illustrations for version 1.0.5, not executed QQ sessions. We did not install the package, authenticate a bot, measure delivery or test webhook deployment.
Use Node.js 20 or newer and ESM. A CommonJS service can load the package with dynamic import(), but it cannot treat the import-only package exports as an ordinary synchronous require(). Keep the package version pinned while you establish one working reply. The default WebSocket mode needs a process that can keep its connection alive. Webhook mode moves incoming events to an HTTP callback, so you must configure the platform callback and a reachable HTTPS endpoint for your deployment. Choosing webhook mode alone does not prove your hosting setup will accept events.
Treat start() as the bot's lifetime, not a readiness check: it resolves when stopped or aborted. Register listeners before starting it. Handle recoverable error events as well as a rejected start(). The 1.0.5 release notes say reconnection now continues until the failure is clearly unrecoverable, replacing the previous finite default. Set reconnect.maxAttempts deliberately when another supervisor owns restarts. A banned or sandbox-only bot needs operator attention rather than an endless restart cycle.
Reply using the incoming msg.replyTarget so its message ID stays attached. The streaming helper rejects group targets and C2C targets without msgId. Each update receives the full text accumulated so far, followed by complete() when finished. This is a private-reply facility, so do not design a group streaming interface around it. Media and voice introduce more setup: the README lists optional silk-wasm and mpg123-decoder packages for voice encoding and MP3 decoding. Keep markdownSupport false unless the bot has that permission.
Rating rubric: these are editorial judgments from the version-pinned documentation, package metadata, implementation and release notes. On the five-point scale, 1 means substantial documented friction, 3 means a usable documented surface with caveats, and 5 requires broad inspectable support for the dimension. Intermediate scores reflect the evidence between those anchors. No score measures delivery speed, adoption, security or production compatibility.
Patterns
Pin the documented release pin-install
npm install --save-exact @tencent-connect/qqbot-nodejs@1.0.5Unexecuted illustration for 1.0.5. Use Node.js 20 or newer. Set your application to ESM or use an .mjs file for the JavaScript examples.
Construct a client with explicit credentials inject-credentials
import { QQBot } from "@tencent-connect/qqbot-nodejs";
const appId = process.env.QQBOT_APP_ID;
const appSecret = process.env.QQBOT_APP_SECRET;
if (!appId || !appSecret) throw new Error("QQ credentials are required");
const bot = new QQBot({ appId, appSecret, logger: console });Unexecuted illustration for 1.0.5. Your application reads these variables; the SDK receives their values. Later fragments assume this bot unless they construct another one. Do not print the credentials.
Reply using the incoming target reply-to-message
bot.on("message", async (_ctx, msg) => {
await bot.sendText(msg.replyTarget, "Message received.");
});Unexecuted illustration for 1.0.5. Register before starting. Keep replyTarget intact so the reply retains the triggering message ID. This listener is an alternative to the other message listeners below.
Observe connection and error events observe-lifecycle
bot.on("ready", () => console.log("QQ ready"));
bot.on("resumed", () => console.log("QQ resumed"));
bot.on("disconnected", ({ code, willReconnect }) => {
console.warn("QQ disconnected", code, willReconnect);
});
bot.on("error", (err) => console.error(err));Unexecuted illustration for 1.0.5. A recoverable error event and a rejected start() have different lifecycle meanings. Event logging does not replace handling the start promise.
Keep the start promise until shutdown run-until-abort
const controller = new AbortController();
process.once("SIGTERM", () => controller.abort());
process.once("SIGINT", () => controller.abort());
try {
await bot.start(controller.signal);
} catch (err) {
console.error(err);
process.exitCode = 1;
}Unexecuted illustration for 1.0.5. Top-level await requires ESM. start() resolves on stop or abort, so code after it is shutdown code rather than a readiness callback.
Let a supervisor own the restart budget cap-reconnects
const supervisedBot = new QQBot({
appId, appSecret,
reconnect: { maxAttempts: 10 },
logger: console,
});Unexecuted illustration for 1.0.5. This fragment only constructs the client. Register listeners and run start() as above. Ten attempts is an illustrative policy, not a recommended universal limit; classify terminal failures before restarting.
Construct a webhook client receive-webhooks
const webhookBot = new QQBot({
appId, appSecret,
transport: "webhook",
webhook: { port: 8080, path: "/callback" },
});Unexecuted illustration for 1.0.5. This is an alternative client configuration. Register its listeners and start it. The local listener settings do not provision public HTTPS or configure the QQ callback.
Measure your handler boundary time-middleware
bot.use(async (ctx, next) => {
const started = Date.now();
try {
await next();
} finally {
ctx.log.debug?.(`Handler elapsed: ${Date.now() - started}ms`);
}
});Unexecuted illustration for 1.0.5. Register middleware before starting. This measures time across the downstream handler, not QQ delivery latency. The example supplies no benchmark result.
Send cumulative private-chat output stream-private-reply
bot.on("message", async (_ctx, msg) => {
if (msg.replyTarget.scope !== "c2c") {
await bot.sendText(msg.replyTarget, "Streaming is available in private chat.");
return;
}
const stream = bot.openStream({ target: msg.replyTarget });
await stream.update("Preparing");
await stream.update("Preparing your answer.");
await stream.complete();
});Unexecuted illustration for 1.0.5. Use as an alternative message handler. update() takes the full accumulated text, not just the newest fragment. The incoming message supplies msgId. This illustrates one reply and makes no concurrency or cancellation guarantee.
Send an image from your service send-local-image
bot.on("message", async (_ctx, msg) => {
if (msg.content.trim() === "/diagram") {
await bot.sendImage(msg.replyTarget, { localPath: "/srv/bot/diagram.png" });
}
});Unexecuted illustration for 1.0.5. Use as an alternative message handler. The file must exist on the bot host and be readable. This example does not establish media delivery or QQ permission approval.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| qq-guild-bot | npm | Evaluate it for a QQ guild/channel bot: its published README describes a Node.js SDK for that task. Check its own event and API documentation before choosing it over this private-chat and group workflow. |
| koishi | npm | Evaluate it when the main requirement is a cross-platform chatbot framework. QQ connectivity still needs a suitable adapter; this comparison does not establish adapter compatibility. |
More ai / ml guides
openai · mcp · huggingface-hub · tiktoken · scikit-learn · tokenizers · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.