---
slug: "qq-x-4"
source_type: "clawhub"
source_url: "https://clawhub.ai/skills/qq"
repo: ""
source_file: "description"
---
---
name: "QQ"
version: "1.1.0"
description: "QQ 机器人与开放平台开发指南。Use for: (1) 选择 QQ 机器人技术路线——官方 Bot API vs 社区 OneBot 生态, (2) 官方机器人从注册到上线全流程（鉴权/WebSocket网关/Intents/消息类型/Ark卡片）, (3) QQ 小程序与 OAuth2.0 接入。QQ bot development guide: official Bot API (auth, WebSocket gateway, message types) and the community OneBot ecosystem, plus Mini-Program and OAuth."
tags: ["qq", "bot", "onebot", "channel", "miniprogram", "oauth"]
author: "ClawSkills Team"
category: "social"
---

# QQ 开放平台开发 AI 助手

覆盖 QQ 机器人（官方与社区两条路线）、QQ 频道、QQ 小程序、
开放平台 OAuth2.0 的开发指导。

## 先选路线：官方 Bot API vs 社区 OneBot 生态

做 QQ 机器人第一个决策是走哪条路线，选错返工代价很大：

| 维度 | 官方 Bot API（本文主体） | 社区 OneBot 生态 |
|------|--------------------------|------------------|
| 合规性 | ✅ 官方支持，不封号 | ⚠️ 协议侧实现，有风控/封号风险 |
| 能力范围 | 频道全功能；群/C2C 需审核开通且受限 | 群/私聊全功能（模拟客户端） |
| 消息限制 | 被动回复为主，主动消息有条数限制 | 无官方限制 |
| 生态现状 | q.qq.com 注册，官方 SDK | OneBot 标准；go-cqhttp 已停止维护，现役实现为 NapCat、Lagrange 等，框架层 NoneBot2/Koishi 仍活跃 |
| 适合 | 对外服务、商业机器人 | 个人自用、内网工具 |

**建议**：对外产品一律走官方 Bot API；社区路线仅限个人自用且
自担风控风险，本 skill 不提供协议逆向细节。

## 身份与能力

- 精通 QQ 官方 Bot API，能指导机器人从注册到上线全流程
- 熟练掌握 QQ 频道开发（消息、富文本、Markdown、Embed、Ark 模板）
- 了解 QQ 小程序开发基础与发布流程
- 深入理解 QQ 开放平台 OAuth2.0 授权体系
- 熟悉 WebSocket 事件网关与事件分发机制

## QQ 机器人开发

### 注册与配置

1. 访问 [QQ 开放平台](https://q.qq.com) 注册开发者账号
2. 创建机器人应用，获取 AppID 和 AppSecret
3. 配置沙箱频道用于测试
4. 机器人分为「频道机器人」和「群聊/私聊机器人」两种场景

### 鉴权方式

QQ 机器人 API 使用 Bearer Token 鉴权，需先获取 access_token：

```python
import requests

def get_qq_bot_token(app_id: str, app_secret: str) -> str:
    """获取 QQ 机器人 access_token"""
    url = "https://bots.qq.com/app/getAppAccessToken"
    payload = {
        "appId": app_id,
        "clientSecret": app_secret
    }
    resp = requests.post(url, json=payload)
    data = resp.json()
    # access_token 有效期通常为 7200 秒
    return data["access_token"]
```

### WebSocket 事件网关

机器人通过 WebSocket 接收实时事件，流程如下：

```
1. GET /gateway → 获取 WebSocket 连接地址
2. 建立 WebSocket 连接
3. 收到 Hello 事件（opcode=10），获取 heartbeat_interval
4. 发送 Identify 鉴权（opcode=2）
5. 收到 Ready 事件，开始接收消息
6. 定时发送心跳（opcode=1）
```

```python
import asyncio
import json
import aiohttp

class QQBotWebSocket:
    def __init__(self, token: str):
        self.token = token
        self.api_base = "https://api.sgroup.qq.com"
        self.session_id = None
        self.seq = None

    async def get_gateway(self) -> str:
        async with aiohttp.ClientSession() as session:
            headers = {"Authorization": f"QQBot {self.token}"}
            async with session.get(f"{self.api_base}/gateway", headers=headers) as resp:
                data = await resp.json()
                return data["url"]

    async def connect(self):
        gateway_url = await self.get_gateway()
        async with aiohttp.ClientSession() as session:
            async with session.ws_connect(gateway_url) as ws:
                async for msg in ws:
                    if msg.type == aiohttp.WSMsgType.TEXT:
                        payload = json.loads(msg.data)
                        await self.handle_event(ws, payload)

    async def handle_event(self, ws, payload: dict):
        op = payload.get("op")
        if op == 10:  # Hello
            interval = payload["d"]["heartbeat_interval"]
            # 发送 Identify
            await ws.send_json({
                "op": 2,
                "d": {
                    "token": f"QQBot {self.token}",
                    "intents": 0 | (1 << 30) | (1 << 1) | (1 << 0),
                    "shard": [0, 1]
                }
            })
            # 启动心跳
            asyncio.create_task(self.heartbeat(ws, interval))
        elif op == 0:  # Dispatch 事件
            self.seq = payload.get("s")
            event_type = payload.get("t")
            await self.dispatch(event_type, payload.get("d", {}))

    async def heartbeat(self, ws, interval_ms: int):
        while True:
            await asyncio.sleep(interval_ms / 1000)
            await ws.send_json({"op": 1, "d": self.seq})

    async def dispatch(self, event_type: str, data: dict):
        print(f"收到事件: {event_type}, 数据: {data}")
```

### Intents（事件订阅）

Intents 是位掩码，用于声明机器人需要接收的事件类型：

| Intent 值 | 事件类型 | 说明 |
|-----------|---------|------|
| `1 << 0` | GUILDS | 频道变更（创建/更新/删除） |
| `1 << 1` | GUILD_MEMBERS | 成员变更（加入/退出） |
| `1 << 9` | GUILD_MESSAGE_REACTIONS | 表情表态事件 |
| `1 << 12` | DIRECT_MESSAGE | 频道私信消息 |
| `1 << 25` | GROUP_AND_C2C_EVENT | 群聊和 C2C 消息事件 |
| `1 << 30` | AT_MESSAGES | @机器人 消息（公域） |

```python
# 常用 intents 组合
INTENTS_PUBLIC = (1 << 0) | (1 << 1) | (1 << 30)  # 频道 + 成员 + @消息
INTENTS_PRIVATE = (1 << 0) | (1 << 1) | (1 << 12)  # 含私信
INTENTS_GROUP = (1 << 25)  # 群聊和 C2C
```

### 消息发送

```python
async def send_channel_message(channel_id: str, content: str, token: str,
                                msg_id: str = None, image: str = None):
    """向频道子频道发送消息"""
    url = f"https://api.sgroup.qq.com/channels/{channel_id}/messages"
    headers = {
        "Authorization": f"QQBot {token}",
        "Content-Type": "application/json"
    }
    payload = {"content": content}
    if msg_id:
        payload["msg_id"] = msg_id  # 被动回复，引用原消息
    if image:
        payload["image"] = image  # 图片 URL
    async with aiohttp.ClientSession() as session:
        async with session.post(url, headers=headers, json=payload) as resp:
            return await resp.json()

async def send_group_message(group_openid: str, content: str, token: str, msg_id: str):
    """向 QQ 群发送消息（需要 msg_id 被动回复）"""
    url = f"https://api.sgroup.qq.com/v2/groups/{group_openid}/messages"
    headers = {
        "Authorization": f"QQBot {token}",
        "Content-Type": "application/json"
    }
    payload = {
        "content": content,
        "msg_type": 0,  # 0=文本, 2=Markdown, 3=Ark, 4=Embed, 7=富媒体
        "msg_id": msg_id
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, headers=headers, json=payload) as resp:
            return await resp.json()
```

### 消息类型

| msg_type | 类型 | 说明 |
|----------|------|------|
| 0 | 文本 | 纯文本消息 |
| 2 | Markdown | Markdown 格式消息 |
| 3 | Ark | Ark 模板消息（卡片） |
| 4 | Embed | 嵌入式消息 |
| 7 | 富媒体 | 图片/视频/语音/文件 |

## QQ 频道开发

### 频道管理 API

```python
async def get_guild_info(guild_id: str, token: str) -> dict:
    """获取频道详情"""
    url = f"https://api.sgroup.qq.com/guilds/{guild_id}"
    headers = {"Authorization": f"QQBot {token}"}
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as resp:
            return await resp.json()
            # 返回: {"id", "name", "icon", "owner_id", "member_count", ...}

async def get_channels(guild_id: str, token: str) -> list:
    """获取频道下的子频道列表"""
    url = f"https://api.sgroup.qq.com/guilds/{guild_id}/channels"
    headers = {"Authorization": f"QQBot {token}"}
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as resp:
            return await resp.json()
```

### 子频道类型

| type 值 | 类型 | 说明 |
|---------|------|------|
| 0 | 文字子频道 | 发送文本/图片/Ark 消息 |
| 1 | 保留 | - |
| 2 | 语音子频道 | 语音通话 |
| 3 | 保留 | - |
| 4 | 子频道分组 | 分类目录 |
| 10005 | 直播子频道 | 直播推流 |
| 10006 | 应用子频道 | 小程序 |
| 10007 | 论坛子频道 | 帖子讨论 |

### Markdown 消息

```python
async def send_markdown_message(channel_id: str, token: str, msg_id: str):
    """发送 Markdown 消息"""
    url = f"https://api.sgroup.qq.com/channels/{channel_id}/messages"
    headers = {
        "Authorization": f"QQBot {token}",
        "Content-Type": "application/json"
    }
    payload = {
        "msg_id": msg_id,
        "markdown": {
            "content": "# 标题\n**加粗文本**\n- 列表项1\n- 列表项2\n[链接](https://example.com)"
        }
    }
    # 也可使用模板 Markdown
    # payload = {
    #     "markdown": {
    #         "custom_template_id": "模板ID",
    #         "params": [
    #             {"key": "title", "values": ["标题内容"]},
    #             {"key": "content", "values": ["正文内容"]}
    #         ]
    #     }
    # }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, headers=headers, json=payload) as resp:
            return await resp.json()
```

### Ark 模板消息（卡片消息）

```python
# Ark 23：链接+文本列表
ark_23 = {
    "ark": {
        "template_id": 23,
        "kv": [
            {"key": "#DESC#", "value": "描述信息"},
            {"key": "#PROMPT#", "value": "通知提醒"},
            {"key": "#LIST#", "obj": [
                {"obj_kv": [
                    {"key": "desc", "value": "项目1: 详情内容"}
                ]},
                {"obj_kv": [
                    {"key": "desc", "value": "项目2: 详情内容"}
                ]}
            ]}
        ]
    }
}

# Ark 24：文本+缩略图
ark_24 = {
    "ark": {
        "template_id": 24,
        "kv": [
            {"key": "#DESC#", "value": "描述"},
            {"key": "#PROMPT#", "value": "提示"},
            {"key": "#TITLE#", "value": "标题"},
            {"key": "#METADESC#", "value": "详细描述"},
            {"key": "#IMG#", "value": "https://example.com/image.png"},
            {"key": "#LINK#", "value": "https://example.com"}
        ]
    }
}
```

## QQ 小程序

### 基础结构

QQ 小程序与微信小程序框架类似，使用 QXML + QSS + JS 开发：

```
├── app.js          # 应用入口
├── app.json        # 全局配置
├── app.qss         # 全局样式
└── pages/
    └── index/
        ├── index.js
        ├── index.json
        ├── index.qxml
        └── index.qss
```

核心差异点：
- 模板文件后缀为 `.qxml`（对应微信 `.wxml`）
- 样式文件后缀为 `.qss`（对应微信 `.wxss`）
- API 前缀为 `qq.xxx`（对应微信 `wx.xxx`）
- 支持 QQ 特有能力：QQ 钱包支付、QQ 运动、厘米秀等

### 登录流程

```javascript
// QQ 小程序登录
qq.login({
  success(res) {
    qq.request({
      url: 'https://your-server.com/api/qq-login',
      method: 'POST',
      data: { code: res.code },
      success(resp) {
        qq.setStorageSync('token', resp.data.token);
      }
    });
  }
});
```

```python
# 后端：code 换取 session
def qq_miniapp_login(code):
    url = (
        'https://api.q.qq.com/sns/jscode2session'
        f'?appid={QQ_APPID}&secret={QQ_SECRET}&js_code={code}'
        '&grant_type=authorization_code'
    )
    resp = requests.get(url).json()
    return resp  # {"openid": "...", "session_key": "..."}
```

## 开放平台 OAuth2.0

### 授权流程

```
用户 → 授权页面 → 同意授权 → 回调获取 code → 换取 access_token → 获取用户信息
```

```python
# 第一步：构造授权 URL
auth_url = (
    "https://graph.qq.com/oauth2.0/authorize"
    f"?response_type=code&client_id={APP_ID}"
    f"&redirect_uri={REDIRECT_URI}&state={STATE}&scope=get_user_info"
)

# 第二步：code 换取 access_token
def get_qq_access_token(code: str) -> dict:
    url = (
        "https://graph.qq.com/oauth2.0/token"
        f"?grant_type=authorization_code&client_id={APP_ID}"
        f"&client_secret={APP_SECRET}&code={code}&redirect_uri={REDIRECT_URI}"
        "&fmt=json"
    )
    return requests.get(url).json()
    # {"access_token": "...", "expires_in": 7776000, "refresh_token": "..."}

# 第三步：获取 openid
def get_openid(access_token: str) -> str:
    url = f"https://graph.qq.com/oauth2.0/me?access_token={access_token}&fmt=json"
    data = requests.get(url).json()
    return data["openid"]

# 第四步：获取用户信息
def get_user_info(access_token: str, openid: str) -> dict:
    url = (
        "https://graph.qq.com/user/get_user_info"
        f"?access_token={access_token}&oauth_consumer_key={APP_ID}&openid={openid}"
    )
    return requests.get(url).json()
    # {"nickname", "figureurl_qq", "gender", "province", "city", ...}
```

## 使用场景

1. QQ 群机器人：关键词自动回复、群管理（禁言/踢人）、定时推送
2. 频道内容管理：自动发帖、消息卡片推送、成员管理
3. 客服机器人：C2C 私聊自动应答、FAQ 问答、工单转接
4. 频道互动：表情表态统计、投票、签到打卡
5. QQ 小程序：轻量级应用、游戏中心、社交分享

## 最佳实践

### 安全规范

- AppSecret 仅在服务端使用，禁止出现在前端代码
- access_token 需中心化缓存，避免频繁请求
- WebSocket 连接需处理断线重连（使用 session_id + seq 恢复）
- 群聊/C2C 消息必须被动回复（需携带 msg_id），不支持主动推送

### 频率限制

| API 类型 | 限制 |
|----------|------|
| 全局 | 单机器人 800 次/分钟 |
| 频道消息 | 单子频道 5 条/秒 |
| 群消息 | 被动回复，无主动推送 |
| C2C 消息 | 被动回复，无主动推送 |

### 常用 API 端点

| 环境 | Base URL |
|------|----------|
| 正式环境 | `https://api.sgroup.qq.com` |
| 沙箱环境 | `https://sandbox.api.sgroup.qq.com` |

| API | 方法 | 路径 |
|-----|------|------|
| 获取网关 | GET | `/gateway` |
| 频道详情 | GET | `/guilds/{guild_id}` |
| 子频道列表 | GET | `/guilds/{guild_id}/channels` |
| 发送频道消息 | POST | `/channels/{channel_id}/messages` |
| 发送私信 | POST | `/dms/{guild_id}/messages` |
| 发送群消息 | POST | `/v2/groups/{group_openid}/messages` |
| 发送 C2C 消息 | POST | `/v2/users/{openid}/messages` |
| 频道成员列表 | GET | `/guilds/{guild_id}/members` |
| 禁言成员 | PATCH | `/guilds/{guild_id}/members/{user_id}/mute` |

### 断线重连

```python
async def reconnect(self, ws_url: str):
    """使用 session_id 和 seq 恢复会话"""
    async with aiohttp.ClientSession() as session:
        async with session.ws_connect(ws_url) as ws:
            # 发送 Resume（opcode=6）
            await ws.send_json({
                "op": 6,
                "d": {
                    "token": f"QQBot {self.token}",
                    "session_id": self.session_id,
                    "seq": self.seq
                }
            })
            # 恢复成功后继续接收事件
```

## 本 skill 不做什么

- 不提供 QQ 协议逆向、模拟客户端的实现细节（风控/封号高危，
  社区路线仅指路 OneBot 标准与现役实现名字，风险自担）
- 官方群/C2C 机器人能力需在 q.qq.com 提审开通，未过审前接口会报
  权限错误，属正常现象
- 接口参数以官方文档 bot.q.qq.com/wiki 为准，本文示例为骨架

---

**最后更新**: 2026-07-11
