Files
chatrebot_aireply_plug/README.md
root 070e48790c docs: Step 1改为自动推导,仅向用户索要两个必填项
- 移除NAPCAT_HOST/QQREBOT_HOST/NEW_AGENT_ID/SHARED_DIR 的询问
- 全部使用默认值:qq-agent, 127.0.0.1, 172.17.0.1:25580, /opt/napcat_shared
- GATEWAY_TOKEN 自动从 ~/.openclaw/openclaw.json 读取
- 仅保留 ADMIN_QQ 和(自动读取失败时的)GATEWAY_TOKEN 两个询问项
- 跨机部署场景留注释说明
2026-05-04 19:14:26 +08:00

663 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# OpenClaw Bridge — QQ AI Reply Plugin
> 将 QQ 消息转发到 OpenClaw Gateway,由 AI agent 自动处理并回复。
> 本 README 面向 AI agent(OpenClaw 基础智能体),引导其完成全流程部署(NapCat → qqrebot → 插件 → agent)。
## 架构
```
QQ 用户 →→→ NapCat(Docker:6099)
│
│ HTTP POST(事件推送)
▼
qqrebot(HTTP Server :25580) ← 消息接收
│
├── 执行 openclaw_bridge 插件
│ │
│ │ HTTP POST /v1/chat/completions
│ ▼
│ OpenClaw Gateway(:18789)
│ │
│ ▼
│ qq-agent(AI 模型)
│ │
◄── 返回回复文本 ──┘
│
│ HTTP POST(send_private_msg / send_group_msg)
▼
NapCat(:6099) → 回复发送到 QQ
```
**关键通信链路(全是 HTTP,无 WebSocket):**
| 链路 | 方向 | 协议 | 端口 |
|---|---|---|---|
| NapCat → qqrebot | 事件推送 | HTTP POST | `qqrebot:25580` |
| 插件 → OpenClaw | AI 请求 | HTTP POST | `127.0.0.1:18789` |
| 插件 → NapCat | 发送回复 | HTTP POST | `napcat:6099` |
| 脚本 → NapCat | 发消息/管理 | HTTP POST | `napcat:6099` |
## 前提条件
| 组件 | 状态 | 由本仓库部署 |
|---|---|---|
| OpenClaw Gateway | 假设已运行 | ❌(由用户自行部署) |
| NapCat(Docker) | 需部署 | ✅ |
| qqrebot(消息接收器)| 需部署 | ✅ |
| openclaw_bridge 插件 | 需部署 | ✅ |
| qq-agent(AI agent) | 需创建 | ✅ |
| QQ 技能/脚本 | 需部署 | ✅ |
## 部署步骤(阅读此文档的 AI agent 请按以下顺序执行)
---
### 第1步:环境自动检测 + 询问关键信息
本步骤将自动推导绝大多数配置,**只需向用户确认两个不可推导的值**。
#### 1.1 设置默认值(agent 自动执行)
```bash
# ─── 可自动推导的变量,全部使用默认值 ───
# Agent 唯一标识(默认 qq-agent,用户可改但通常不需要)
NEW_AGENT_ID="${NEW_AGENT_ID:-qq-agent}"
AGENT_WORKSPACE="/home/program/${NEW_AGENT_ID}-workspace"
# 共享目录路径
SHARED_DIR="${SHARED_DIR:-/opt/napcat_shared}"
# NapCat HTTP API 地址(同机部署走 127.0.0.1)
NAP CAT_HOST="127.0.0.1"
# 从 NapCat Docker 容器角度看到的 qqrebot 地址
# 同机部署时 NapCat 通过 Docker 网关访问宿主机
QQREBOT_HOST="172.17.0.1:25580"
# Gateway Token:自动从本机 OpenClaw 配置文件读取
GATEWAY_TOKEN=$(python3 -c "
import json
with open('$HOME/.openclaw/openclaw.json') as f:
cfg = json.load(f)
print(cfg.get('gateway', {}).get('auth', {}).get('token', ''))
" 2>/dev/null)
```
> 注意:`NAP CAT_HOST` 是**不含端口**的纯 IP/域名,后面所有用到 `send_url` 的地方拼入端口 `:6099`。
> `QQREBOT_HOST` 带端口 `:25580`,用于 onebot11.json 的 `httpPost.url`。
#### 1.2 向用户确认
只有以下两项无法自动推导,agent 需向用户询问:
```bash
# 向用户提问示例:
# "主人,我需要以下信息来完成部署:
# 1. ADMIN_QQ — 你的管理员 QQ 号(用于白名单和安全上报)
# 2. GATEWAY_TOKEN — 如上面自动读取的为空,请提供"
```
| 变量 | 必须 | 说明 |
|---|---|---|
| `ADMIN_QQ` | ✅ | 管理员 QQ 号,用于白名单和安全告警上报 |
| `GATEWAY_TOKEN` | 看情况 | 自动读取失败时(空值)才需要用户提供 |
> 其余变量(`NEW_AGENT_ID`、`SHARED_DIR`、`NAP CAT_HOST`、`QQREBOT_HOST`)全部使用默认值即可,**无需用户确认**。仅在跨机部署(NapCat 和 OpenClaw 不在同一台机器)时需要用户指定 `NAP CAT_HOST`。
#### 1.3 应用变量
```bash
# 确认 ADMIN_QQ
if [ -z "$ADMIN_QQ" ]; then
echo "请提供 ADMIN_QQ"
exit 1
fi
# 确认 GATEWAY_TOKEN 非空
if [ -z "$GATEWAY_TOKEN" ]; then
echo "自动读取 GATEWAY_TOKEN 失败,请提供"
exit 1
fi
export ADMIN_QQ NEW_AGENT_ID AGENT_WORKSPACE SHARED_DIR NAP_CAT_HOST QQREBOT_HOST GATEWAY_TOKEN
echo "环境变量已就绪:ADMIN_QQ=$ADMIN_QQ, AGENT=$NEW_AGENT_ID, NAPCAT=$NAP CAT_HOST"
```
---
### 第2步:通过 Docker 部署 NapCat
NapCat 是 QQ 协议实现,接收 QQ 消息并通过 **HTTP POST** 推送到 qqrebot。
#### 2.1 创建目录结构
```bash
# 共享文件目录(NapCat Docker 内发文件时读取)
SHARED_DIR="${SHARED_DIR:-/opt/napcat_shared}"
mkdir -p "${SHARED_DIR}"
# NapCat 配置目录 + QQ 登录数据持久化
mkdir -p /opt/napcat/config
mkdir -p /opt/napcat/qq_data
```
#### 2.2 启动 NapCat Docker 容器
```bash
docker run -d \
--name napcat \
--restart unless-stopped \
-p 6099:6099 \
-v /opt/napcat/config:/app/napcat/config \
-v /opt/napcat/qq_data:/app/.config/QQ \
-v "${SHARED_DIR}:/app/napcat/share" \
--dns=114.114.114.114 \
mlikiowa/napcat-docker:latest
```
端口说明:
| 端口 | 是否必开 | 用途 |
|---|---|---|
| `6099` | ✅ | OneBot HTTP API(脚本发消息/群管理/取数据都用这个端口) |
| `3001` | ❌ | WebSocket Server(本教程用 HTTP POST 推送,不需要) |
| `9000` | 按需 | WebUI 管理面板(首次扫码登录可临时用) |
如果机器防扫描严格,**只开 6099** 即可;WebUI 按需用完即关。
#### 2.3 首次登录配置
> NapCat 首次启动需要扫码登录 QQ 账号。登录数据持久化在 `/opt/napcat/qq_data`。
```bash
# 查看登录二维码(日志中会打印链接)
docker logs napcat 2>&1 | grep -o 'http[s]*://[^ ]*\.png'
# 或用浏览器打开 http://<服务器IP>:9000/webui 扫码登录
# 等待登录成功
docker logs napcat -f | grep "登录成功"
```
#### 2.4 配置事件推送(HTTP POST → qqrebot)
NapCat 需要将所有消息事件通过 HTTP POST 推送到 qqrebot(端口 `25580`)。
编辑 `/opt/napcat/config/onebot11.json`:
```json
{
"http": {
"enable": true,
"host": "0.0.0.0",
"port": 6099,
"enableHttpPost": false,
"enableHttpHeartbeat": false
},
"httpPost": [
{
"url": "http://${QQREBOT_HOST}/",
"secret": ""
}
],
"ws": {
"enable": false
},
"heartbeat": {
"enable": false
},
"enableLocalFile2Url": true
}
```
> **关键说明:**
> - `httpPost` 数组中的 URL 是 NapCat 向 qqrebot 推送消息事件的目标地址
> - 如果 NapCat 和 qqrebot 在**同一台宿主机上**,`url` 通常设置为 `http://172.17.0.1:25580/`(Docker 默认网关地址)
> - 如果**不同机器**,填 qqrebot 机器可达的真实 IP
> - `ws.enable = false` — 本教程不用 WebSocket
> - `http.enableHttpPost = false` — httpPost 由专门的 `httpPost` 数组控制
应用配置:
```bash
docker restart napcat
```
#### 2.5 验证 NapCat
```bash
# 验证 HTTP API 可访问
curl -s http://localhost:6099/get_version_info | python3 -m json.tool
# 预期输出:
# {"status": "ok", "data": {"app_name": "NapCat", ...}, ...}
```
---
### 第3步:部署 qqrebot(消息接收器)
qqrebot 是 HTTP 服务器,NapCat 将 QQ 消息 POST 到 qqrebot,qqrebot 加载插件处理。
#### 3.1 安装 qqrebot
从 [chatrebot1.0 release](https://jianfgit.xyz/jianf/chat_rebot-connect-with-onebot-standard-/src/tag/chatrebot1.0/) 下载:
```bash
wget https://jianfgit.xyz/jianf/chat_rebot-connect-with-onebot-standard-/archive/chatrebot1.0.tar.gz
tar xzf chatrebot1.0.tar.gz
mv chat_rebot-connect-with-onebot-standard- /opt/qqrebot
cd /opt/qqrebot
```
#### 3.2 配置
编辑 `config/config.toml`:
```toml
[app]
list_port = 25580
send_url = "http://${NAP CAT_HOST}:6099" # → 插件回复时调 NapCat HTTP API 用
[rebot]
id = ""
[plugins]
dir = ["plugins"]
```
> **`send_url`**:qqrebot 框架用它作为 NapCat HTTP API 的基础地址。插件中的 `ctx.group.url` 来自此值,后续所有 `send_private_msg` / `send_group_msg` 调用都走这个地址。如果 NapCat 和 qqrebot 同机,填 `http://127.0.0.1:6099`。
#### 3.3 创建 systemd 服务
```ini
# /etc/systemd/system/qqrebot.service
[Unit]
Description=QQ Robot
After=network.target docker.service
Requires=docker.service
[Service]
Type=simple
WorkingDirectory=/opt/qqrebot
ExecStart=/opt/qqrebot/run.sh
Restart=always
RestartSec=3
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
```bash
systemctl daemon-reload
systemctl enable --now qqrebot
```
---
### 第4步:安装 openclaw_bridge 插件
本仓库即 openclaw_bridge 插件的源码,需打包后部署到 qqrebot。
**打包(必须在目标主机上执行,因含 C 扩展):**
```bash
cd /path/to/chatrebot_aireply_plug
pip install -r requirements.txt
python3 package.py
```
**部署:**
```bash
cp dist/openclaw_bridge.zip /opt/qqrebot/plugins/
```
**创建插件配置 `/opt/qqrebot/config/openclawbridge/config.toml`:**
```toml
[openclaw]
gateway_url = "http://127.0.0.1:18789"
gateway_token = "${GATEWAY_TOKEN}"
allowed_sender = "${ADMIN_QQ}"
model = "openclaw/${NEW_AGENT_ID}"
agent_id = "${NEW_AGENT_ID}"
```
**重启 qqrebot 加载插件:**
```bash
systemctl restart qqrebot
```
**验证插件加载:**
```bash
journalctl -u qqrebot -f | grep OpenClawBridge
```
预期输出:
```
Config loaded: url=http://127.0.0.1:18789, allowed=123456789
```
---
### 第5步:创建新的 OpenClaw agent
在 `~/.openclaw/openclaw.json` 的 `agents.list` 中添加新 agent:
```bash
AGENT_ID="${NEW_AGENT_ID}"
AGENT_WORKSPACE="/home/program/${AGENT_ID}-workspace"
mkdir -p "${AGENT_WORKSPACE}/scripts"
mkdir -p "${AGENT_WORKSPACE}/skills"
mkdir -p "${AGENT_WORKSPACE}/self-workplace"
mkdir -p "${AGENT_WORKSPACE}/memory"
mkdir -p "${AGENT_WORKSPACE}/files"
```
添加 agent 配置项到 `agents.list`:
```json
{
"id": "${NEW_AGENT_ID}",
"name": "${NEW_AGENT_ID}",
"model": "kimi/kimi-for-coding",
"workspace": "/home/program/${NEW_AGENT_ID}-workspace"
}
```
> 通过 `GET /api/config/all` 获取当前配置,找到 `agents.list` 数组执行 push,然后 `POST /api/config/set`。或直接编辑 `~/.openclaw/openclaw.json` 后重启 Gateway。
---
### 第6步:获取源码并部署到 agent 的 workspace
> agent 需要在目标服务器上获取 chatrebot_aireply_plug 的 `scripts/`(26 个脚本)和 `skills/`(8 个 skill)目录。
#### 6.1 在目标服务器上克隆仓库
```bash
# 方案 A:git clone
git clone https://jianfgit.xyz/jianf/chatrebot_aireply_plug.git /opt/chatrebot_aireply_plug
# 方案 B:本机已有 repo,直接复制
# cp -r /home/program/pm-workspace /opt/chatrebot_aireply_plug
```
后续步骤假设源码解压到 `/opt/chatrebot_aireply_plug`。
#### 6.2 复制脚本
```bash
cp -r /opt/chatrebot_aireply_plug/scripts/* "${AGENT_WORKSPACE}/scripts/"
```
脚本清单(26 个),按功能分组:
| 分组 | 脚本 | 用途 |
|---|---|---|
| **QQ 消息** | `qq_send_msg.py` | 发送文本消息 |
| | `qq_send_file.py` | 发送文件/图片 |
| | `qq_upload_group_file.py` | base64 直接上传群文件 |
| | `qq_send_like.py` | 点赞 |
| | `qq_ocr_image.py` | 图片 OCR 识别 |
| **QQ 查询** | `qq_get_groups.py` | 获取群列表 |
| | `qq_get_friends.py` | 获取好友列表 |
| | `qq_get_group_files.py` | 获取群文件列表 |
| | `qq_get_file.py` | 下载文件 |
| | `qq_get_history.py` | 拉取历史消息 |
| | `qq_resolve_name.py` | QQ号/群号 ↔ 可读名称 |
| **QQ 管理** | `qq_group_action.py` | 退群、接受/拒绝群邀请 |
| | `qq_group_manage.py` | 群成员管理(踢/禁言等) |
| | `qq_friend_action.py` | 删好友、同意/拒绝好友请求 |
| **MC 服务器** | `mc_query.py` | 服务器状态查询 |
| | `mc_status.py` | 状态(简化版) |
| | `mc_players.py` | 玩家信息 |
| | `mc_world.py` | 世界详情 |
| | `mc_rcon.py` | 远程执行 RCON 命令 |
| | `mc_clear_items.py` | 清理掉落物 |
| | `mc_config.py` | 服务器配置管理 |
| **工具** | `browse.py` | 网页浏览/截图 |
| | `file_store_api.py` | 文件存储 API |
| | `ask_nix.py` | 向 ops-manager 求助 |
| | `qq_video_download.py` | 视频下载 |
| | `__init__.py` | 包初始化 |
#### 6.3 复制 Skills
Skills 是 agent 的能力描述文件(`SKILL.md`),每个 skill 一个子目录,agent 启动时会加载:
```bash
for SKILL in qq-messenger qq-management qq-resolver qq-napcat-extras mc-query nix-helper browser file-process; do
cp -r "/opt/chatrebot_aireply_plug/skills/${SKILL}" "${AGENT_WORKSPACE}/skills/"
done
```
> Skills 中的 SKILL.md 通过 `YOUR_WORKSPACE_PATH/scripts/xxx.py` 引用脚本路径。
> agent 执行 `subprocess.run` 时,实际调用的是 `${AGENT_WORKSPACE}/scripts/` 下的脚本。
#### 6.4 替换占位符(关键!必须执行)
**替换脚本占位符:**
```bash
cd "${AGENT_WORKSPACE}/scripts"
# NapCat HTTP API 地址(端口 6099)
sed -i "s|YOUR_NAP CAT_HOST:25570|${NAP CAT_HOST}:6099|g" *.py
sed -i "s|YOUR_NAP CAT_HOST|${NAP CAT_HOST}:6099|g" *.py
sed -i "s|YOUR_NAPCAT_HOST:25570|${NAP CAT_HOST}:6099|g" *.py
sed -i "s|YOUR_NAPCAT_HOST|${NAP CAT_HOST}:6099|g" *.py
# 管理员 QQ
sed -i "s|YOUR_ADMIN_QQ|${ADMIN_QQ}|g" *.py
# Gateway Token
sed -i "s|YOUR_GATEWAY_TOKEN|${GATEWAY_TOKEN}|g" *.py
# 工作区路径
sed -i "s|YOUR_WORKSPACE_PATH|${AGENT_WORKSPACE}|g" *.py
# 共享目录
sed -i "s|YOUR_SHARED_DIR|${SHARED_DIR}|g" *.py
```
**替换 Skills 占位符:**
```bash
cd "${AGENT_WORKSPACE}/skills"
for f in */SKILL.md; do
sed -i "s|YOUR_WORKSPACE_PATH|${AGENT_WORKSPACE}|g" "$f"
sed -i "s|YOUR_ADMIN_QQ|${ADMIN_QQ}|g" "$f"
done
```
> ⚠️ Skills 的替换很关键:如果漏掉,agent 调脚本时会指向不存在的旧路径。
#### 6.5 验证完整性
```bash
# 脚本数量
echo "scripts: $(ls "${AGENT_WORKSPACE}/scripts/" | wc -l) 个"
# 确认无残留占位符
if grep -r "YOUR_ADMIN_QQ\|YOUR_WORKSPACE_PATH" "${AGENT_WORKSPACE}/"; then
echo "⚠️ 还有未替换的占位符!"
else
echo "✅ 占位符全部替换完毕"
fi
```
---
### 第7步:重启 OpenClaw Gateway
```bash
openclaw gateway restart
```
确认新 agent 已加载:
```bash
openclaw config get agents.list
# 或 journalctl -u openclaw -f | grep "agent.${NEW_AGENT_ID}"
```
---
### 第8步:端到端验证
1. 在 QQ 上给机器人发消息
2. 检查 qqrebot 日志:`journalctl -u qqrebot -f | grep OpenClawBridge`
3. 检查 OpenClaw 日志:`journalctl -u openclaw -f`
4. 验证 agent 已回复
---
## 脚本适配说明
### 端口对照
| 组件 | go-cqhttp | NapCat(Docker) |
|---|---|---|
| HTTP API 端口 | `25570` | **6099** |
| WebSocket | `25570`(兼用) | 不启用(本教程) |
> 脚本中 `CQHTTP_URL = "http://YOUR_NAP CAT_HOST:25570"` 替换后变为 `http://<host>:6099`。
### 文件发送与共享目录
NapCat 运行在 Docker 内部,宿主机文件路径不能直接访问。解决方式:
```
宿主机 ${SHARED_DIR}/ ⇔ Docker volume → 容器内 /app/napcat/share/
```
| 脚本 | 关联 | 说明 |
|---|---|---|
| `qq_send_file.py` | 使用 `TARGET_DIR` | 发文件前复制到共享目录,NapCat 从 `/app/napcat/share/` 读取 |
| `qq_ocr_image.py` | 提到 NapCat 回退 | 只在本地 Tesseract 不可用时走 NapCat,此时需文件在共享目录 |
| `qq_get_file.py` | 无依赖 | NapCat 返回 base64 或 URL,自动下载,不依赖共享目录 |
| `qq_upload_group_file.py` | 无依赖 | 使用 base64:// 编码直接上传,不依赖共享目录 |
### 变量速查
| 占位符 | 替换值 | 出现位置 |
|---|---|---|
| `YOUR_NAP CAT_HOST:25570` | `${NAP CAT_HOST}:6099` | 所有脚本 |
| `YOUR_NAP CAT_HOST` | `${NAP CAT_HOST}:6099` | 所有脚本 |
| `YOUR_NAPCAT_HOST` | `${NAP CAT_HOST}:6099` | 部分脚本 |
| `YOUR_ADMIN_QQ` | `${ADMIN_QQ}` | 脚本 + SKILL.md |
| `YOUR_GATEWAY_TOKEN` | `${GATEWAY_TOKEN}` | 脚本 |
| `YOUR_SHARED_DIR` | `${SHARED_DIR}` | `qq_send_file.py`、`qq_ocr_image.py` |
| `YOUR_WORKSPACE_PATH` | `${AGENT_WORKSPACE}` | SKILL.md + 脚本 |
---
## 安全模块说明
部署完成后,新 agent 将具备以下安全能力(全部在 `src/process.py` 中实现):
### 高危词检测
内置 `HIGH_RISK_WORDS` 词库,覆盖以下类别:
| 类别 | 示例关键词 |
|---|---|
| 越狱/提示词攻击 | `ignore all instructions`、`forget previous` |
| 记忆操控 | `delete memory`、`forget everything` |
| 敏感信息泄漏 | `sudo password`、`api key` |
| 系统命令 | `cat /etc/passwd`、`rm -rf` |
**触发时行为:**
- 非管理员 → 自动拦截 + 上报管理员 + 该用户加入黑名单
- 管理员 → 记录日志 + 发送告警到 admin 私聊
### 管理员白名单
| 配置项 | 说明 |
|---|---|
| `allowed_sender` | 唯一的管理员 QQ 号 |
| 非管理员操作 | MC 指令过滤(`/` 开头消息跳过 AI 处理) |
### 消息过滤
- 纯媒体消息(无文字:纯图片/文件/视频)自动跳过
- 系统通知(`[系统通知]`、`[文件回执]`)自动跳过
- 非管理员发送的操作指令(`/` 开头)跳过 AI 处理
### 安全上报
当非管理员触发高危词时,插件自动通过 NapCat HTTP API 给 `allowed_sender` 发送告警(调用 `send_private_msg`)。
### 错误脱敏
所有 HTTP 响应体、异常详情不暴露给 QQ 用户,仅记录日志。
## 配置文件参考
| 配置项 | 必填 | 说明 |
|---|---|---|
| `gateway_url` | ✅ | OpenClaw Gateway 地址 |
| `gateway_token` | ✅ | Gateway 认证 Token |
| `allowed_sender` | ✅ | 管理员 QQ 号 |
| `model` | ✅ | Agent 模型 ID(格式:`openclaw/${AGENT_ID}`) |
| `agent_id` | ✅ | Agent ID(与 `agents.list` 一致) |
## 目录结构
```
chatrebot_aireply_plug/
├── config/
│ └── openclawbridge/
│ └── config.toml # 插件配置模板
├── src/
│ ├── process.py # 插件核心逻辑(含全部安全模块)
│ ├── config.toml # 本地测试配置
│ └── modules/
│ ├── plugin_modules.py # BasePlugin + MessageContext
│ └── user_module.py # User/Group 封装
├── scripts/ # ✅ 部署时复制到 agent workspace
│ ├── qq_send_msg.py # 发送消息(调 NapCat :6099)
│ ├── qq_send_file.py # 发送文件/图片
│ ├── qq_get_groups.py # 获取群列表
│ ├── qq_get_friends.py # 获取好友列表
│ ├── qq_group_manage.py # 群管理(踢/禁言/设管理)
│ └── ...
├── skills/ # ✅ 部署时复制到 agent skills/
│ ├── qq-messenger/SKILL.md
│ ├── qq-management/SKILL.md
│ ├── qq-resolver/SKILL.md
│ ├── qq-napcat-extras/SKILL.md
│ ├── mc-query/SKILL.md
│ └── ...
├── dependence.py
├── package.py
├── packup.sh / packup.bat
├── test.py
├── requirements.txt
└── README.md
```
## 自定义安全词库
编辑 `src/process.py` 中的 `HIGH_RISK_WORDS` 列表后重新打包部署重启:
```python
HIGH_RISK_WORDS = {
"prompt_injection": ["ignore all instructions", ...],
"memory_manipulation": ["delete memory", ...],
"info_leak": ["sudo password", ...],
"system_commands": ["cat /etc/passwd", ...],
}
```
## 许可证
MIT