Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

qqbot-api

一个极简 TypeScript QQ Bot 私聊工具类:基于 QQ Bot WebSocket Gateway 接收 C2C 私聊消息,并提供私聊文本、流式 Markdown、图片发送 API。

特点:

  • 只支持 WebSocket 模式,可接收 QQ 私聊消息,不需要公网 Webhook。
  • 只封装 C2C 私聊能力,避免群聊、频道、DM 等额外复杂度。
  • 不依赖 OpenClaw 仓库代码。
  • 核心文件不 import node:*,浏览器和 Node 都能使用。
  • 对外 API 简洁:startstoponMessagereplyreplyImagereplyStreamcreateReplyStreamsendPrivatesendPrivateImagesendPrivateStreamcreatePrivateStream

环境要求

  • Node.js 24+ 或现代浏览器
  • QQ Bot 的 appIdclientSecret,或后端提供的 accessToken / tokenProvider
  • QQ Bot 已开通 C2C 私聊消息事件权限

安装

npm install

本项目只把 tsxtypescript 和 Node 类型作为开发依赖,核心文件 qqbot-api-tool.ts 本身不依赖第三方运行时包。

配置

复制环境变量示例:

cp .env.example .env

填写:

QQBOT_APP_ID=你的 appId
QQBOT_CLIENT_SECRET=你的 appSecret
QQBOT_USER_OPENID=可选,默认私聊用户 openid

QQBOT_USER_OPENID 不是由 appId / clientSecret 计算出来的。它来自 QQ 私聊消息事件里的 author.user_openid。如果要主动私信,必须先知道目标用户 openid,或者在收到消息后自己保存。

最小接收和回复示例

Node 里可以直接用 appId / clientSecret

import { QQBotApiTool } from "./qqbot-api-tool.ts";

const bot = new QQBotApiTool({
  appId: process.env.QQBOT_APP_ID!,
  clientSecret: process.env.QQBOT_CLIENT_SECRET!,
});

bot.onMessage(async (event, api) => {
  console.log("收到私聊", event.openid, event.text);

  if (event.text.trim() === "/ping") {
    await api.reply(event, "pong");
  }
});

await bot.start();

浏览器里不要放 clientSecret,应该让你的后端返回 token:

const bot = new QQBotApiTool({
  tokenProvider: async () => {
    const response = await fetch("/api/qqbot-token");
    const data = await response.json();
    return data.accessToken;
  },
});

运行后,程序会主动连接 QQ WebSocket Gateway。QQ 平台会沿这条连接推送私聊消息,所以不需要把本机 localhost 暴露给公网。

回复图片

replyImage 支持公网图片 URL、BlobArrayBufferUint8Array 或 base64:

bot.onMessage(async (event, api) => {
  if (event.text.trim() === "/image") {
    await api.replyImage(event, "https://example.com/demo.png");
  }
});

浏览器文件输入示例:

const file = fileInput.files?.[0];
if (file) {
  await bot.sendPrivateImage(file);
}

base64 示例:

await bot.sendPrivateImage({
  base64: "iVBORw0KGgo...",
  filename: "demo.png",
});

主动发送私信

如果构造时传了默认用户 openid:

const bot = new QQBotApiTool({
  appId: process.env.QQBOT_APP_ID!,
  clientSecret: process.env.QQBOT_CLIENT_SECRET!,
  userOpenid: process.env.QQBOT_USER_OPENID,
});

await bot.sendPrivate("你好");

也可以临时指定 openid:

await bot.sendPrivate("你好", "USER_OPENID");

主动发送图片私信

发送公网图片 URL:

await bot.sendPrivateImage("https://example.com/demo.png");

发送浏览器 File / Blob

await bot.sendPrivateImage(file, "USER_OPENID");

发送 base64:

await bot.sendPrivateImage({ base64: "iVBORw0KGgo...", filename: "demo.png" });

流式回复和发送私信

参考 QQ Bot markdown stream 消息接口实现。

回复当前消息:

bot.onMessage(async (event, api) => {
  if (event.text.trim() === "/stream") {
    await api.replyStream(event, `# 流式回复

你好,这是一条流式 Markdown 回复。`);
  }
});

主动发送:

await bot.sendPrivateStream(`# 流式消息

你好,这是一条流式 Markdown 私信。`);

接 LLM 流式输出时,用 writer API:

bot.onMessage(async (event, api) => {
  const reply = api.createReplyStream(event);

  for await (const chunk of llmStream) {
    await reply.write(chunk);
  }

  await reply.close();
});

主动私信也可以用 writer API:

const stream = bot.createPrivateStream("USER_OPENID");
await stream.write("第一段");
await stream.write("第二段");
await stream.close();

自定义分片大小和间隔:

await bot.sendPrivateStream(markdownText, {
  chunkSize: 50,
  intervalMs: 100,
});

临时指定 openid:

await bot.sendPrivateStream(markdownText, {
  openid: "USER_OPENID",
});

流式发送流程:

  1. 按行切分文本。
  2. 多次发送 state: 1 的 markdown stream 分片。
  3. 最后发送 state: 10reset: true 的完整文本收尾。

消息事件结构

onMessage 只会收到 C2C 私聊事件:

interface QQBotMessageEvent {
  messageId: string;
  text: string;
  openid: string;
  timestamp?: string;
  attachments: QQBotAttachment[];
  raw: unknown;
}

常用字段:

  • event.text:消息文本。
  • event.openid:私聊用户 openid。
  • event.messageId:回复当前消息时使用。
  • event.attachments:QQ 事件里携带的附件信息。
  • event.raw:QQ 原始事件数据。

API 速览

const bot = new QQBotApiTool(options);

bot.onMessage(handler);
await bot.start();
bot.stop();

await bot.reply(event, "你好");
await bot.replyImage(event, "https://example.com/demo.png");
await bot.replyStream(event, markdownText);

const replyStream = bot.createReplyStream(event);
await replyStream.write("第一段");
await replyStream.close();

await bot.sendPrivate("你好");
await bot.sendPrivateImage(fileOrBlobOrBase64);
await bot.sendPrivateStream(markdownText);

const privateStream = bot.createPrivateStream("USER_OPENID");
await privateStream.write("第一段");
await privateStream.close();

类型检查

npm run typecheck

注意事项

  • openid 不能通过 appIdclientSecret 拼接或计算得到。
  • 主动私信需要目标用户 openid。
  • 浏览器里不要暴露 clientSecret,请用后端提供 accessTokentokenProvider
  • QQ 平台 API 是否允许浏览器直连取决于 CORS;如果被拦截,需要经由你自己的后端代理发送。
  • QQ 平台可能限制主动消息发送频率和场景。
  • 流式消息当前封装的是 C2C 私信 markdown stream。
  • replyStream 会在 stream 消息体里带上当前事件的 msg_id
  • 图片发送会先调用 /v2/users/{openid}/files 获取 file_info,再发送 msg_type: 7 消息。
  • 本工具类只保留 C2C 私聊能力;群聊、频道、频道私信不在当前封装范围内。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages