diff --git a/README.md b/README.md index feb8e0e..6799dde 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ ## API 文档 -完整接口文档见 **[docs/API.md](docs/API.md)**(认证/工单/封禁/用户/设置/通知/投票/更新/导出/外部插件 API)。 +- **完整接口文档**:[docs/API.md](docs/API.md)(认证/工单/封禁/用户/设置/通知/投票/更新/导出/外部插件 API) +- **第三方外部 API(机器人/插件)**:[docs/EXTERNAL-API.md](docs/EXTERNAL-API.md)(ID+Secret 鉴权、提交工单、进度追踪、封禁拉取、按子服拉数据) ## 快速开始 diff --git a/docs/API.md b/docs/API.md index 69b45cf..cc4884d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,5 +1,7 @@ # MC Report System API 文档 +> 面向**机器人 / 第三方系统**的免登录 API(鉴权方式、请求示例、子服定位语法)见独立文档 **[EXTERNAL-API.md](EXTERNAL-API.md)**。 + Base URL: `http://:3100/api` ## 通用约定 diff --git a/docs/EXTERNAL-API.md b/docs/EXTERNAL-API.md new file mode 100644 index 0000000..45015ff --- /dev/null +++ b/docs/EXTERNAL-API.md @@ -0,0 +1,349 @@ +# 外部 API 文档(机器人 / 第三方系统) + +适用对象:QQ 官方机器人、游戏服务器插件、统计面板等**无法做网页登录**的外部系统。 + +- Base URL:`https://<你的域名>/api/external` +- 数据格式:JSON(`Content-Type: application/json`) +- 本文档所有接口**不需要用户登录**,只需要客户端凭据(见下) + +--- + +## 一、鉴权(三种方式,任选其一) + +### 方式 A:ID + Secret(推荐,多客户端) + +在站点后台 →「外部API」页面创建客户端,获得一对凭据: + +```text +Client ID: c_1a2b3c4d5e6f7a8b9c0d1e2f +Secret: s_9f8e7d6c5b4a39281726354a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e +``` + +> ⚠️ Secret 只在创建时显示一次,请立即保存。支持创建多个客户端、单独停用/删除,互不影响。 + +调用时在请求头携带: + +| 请求头 | 值 | +|--------|-----| +| `x-api-client-id` | 你的 Client ID | +| `x-api-secret` | 你的 Secret | + +### 方式 B:旧版单 Key(兼容) + +| 请求头 | 值 | +|--------|-----| +| `x-external-key` | 部署时配置的 external_api_key | + +### 方式 C:用户 JWT(仅限插件) + +| 请求头 | 值 | +|--------|-----| +| `Authorization` | `Bearer `(来自 `POST /api/external/auth/login`) | + +--- + +## 二、快速开始(Python / Node 示例) + +### Python + +```python +import requests + +BASE = "https://你的域名/api/external" +HEADERS = { + "x-api-client-id": "c_1a2b3c4d5e6f7a8b9c0d1e2f", + "x-api-secret": "s_9f8e7d6c...", + "Content-Type": "application/json", +} + +# 提交举报工单 +r = requests.post(f"{BASE}/tickets", headers=HEADERS, json={ + "type": "report", + "title": "恶意破坏", + "reporter_game_name": "Steve", + "reporter_game_uid": "df273bda10b94aa18db345574d5a1e1d", + "target_game_name": "Alex", + "target_game_uid": "SKIN_UUID_002", + "reason": "刷屏+破坏他人建筑", + "description": "多次警告无效", + "server": "survival", # 可选: 子服别名 +}) +print(r.json()) # {"id": 123, "tracking_token": "..."} +``` + +### Node.js + +```javascript +const BASE = 'https://你的域名/api/external'; +const H = { + 'x-api-client-id': 'c_1a2b3c...', + 'x-api-secret': 's_9f8e7d6c...', + 'Content-Type': 'application/json', +}; + +// 查询工单进度(从提交到结束全程可查) +const res = await fetch(`${BASE}/tickets/track?token=你的tracking_token`, { headers: H }); +console.log(await res.json()); +``` + +--- + +## 三、子服务器定位(`server` 参数) + +子服可在后台「服务器管理」配置**外部别名(alias)**。所有带 `server` 的接口支持三种写法: + +| 写法 | 示例 | 说明 | +|------|------|------| +| 别名(推荐) | `survival` | 服务器改名不影响 | +| 分组/子服 | `网易服务器/生存服` | URL 编码或 JSON 直接传 | +| 子服名 | `生存服` | 同名时取第一个 | + +查询服务器列表(含别名):`GET /api/external/servers` + +--- + +## 四、接口清单 + +### 1. 提交工单 + +``` +POST /api/external/tickets +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| type | string | ✅ | `report` / `suggestion` / `appeal` | +| title | string | ✅ | 标题 | +| reporter_game_name | string | ✅ | 提交人游戏名 | +| reporter_game_uid | string | ✅ | 提交人 UID / UUID | +| target_game_name | string | 条件 | 被举报人游戏名(report 建议填) | +| target_game_uid | string | 条件 | 被举报人 UID | +| reason | string | 条件 | 举报原因(report/appeal 必填) | +| description | string | 条件 | 详情(suggestion/appeal 必填) | +| server | string | 否 | 子服(别名/分组/子服名) | + +限制:同一提交人**待处理工单最多 5 个**。 + +**成功响应 201:** + +```json +{ + "id": 123, + "tracking_token": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "server": "生存服" +} +``` + +> `tracking_token` 是查询进度的唯一凭证,请妥善保存。 + +### 2. 查询工单进度(提交 → 处理 → 结束) + +``` +GET /api/external/tickets/track?token= +``` + +**成功响应:** + +```json +{ + "id": 123, + "type": "report", + "title": "恶意破坏", + "status": "processing", + "priority": "medium", + "reporter_game_name": "Steve", + "target_game_name": "Alex", + "reason": "刷屏+破坏他人建筑", + "assigned_to": "管理员", + "claim_note": "已受理", + "server_name": "生存服", + "created_at": "2026-08-19T10:00:00.000Z", + "updated_at": "2026-08-19T11:30:00.000Z", + "responses": [ + { "content": "游戏内提交...", "is_staff": 0, "created_at": "..." }, + { "content": "正在核实,请耐心等待", "is_staff": 1, "created_at": "..." } + ] +} +``` + +**工单状态流转:** + +``` +pending(待处理) → processing(处理中) → resolved(已解决) + ↘ awaiting_info(待补充信息) → processing / rejected(驳回) + ↘ closed(关闭) appealed(申诉中) → resolved / rejected +``` + +### 3. 工单列表 + +``` +GET /api/external/all-tickets?type=&status=&server=&page=&limit= +``` + +| 参数 | 说明 | +|------|------| +| type | `report` / `suggestion` / `appeal` | +| status | `pending` / `processing` / `awaiting_info` / `appealing` / `resolved` / `rejected` / `closed` | +| server | 子服(别名/分组/子服名) | +| page / limit | 分页,limit ≤ 200,默认 page=1 limit=50 | + +### 4. 工单详情(含回复与附件) + +``` +GET /api/external/all-tickets/:id +``` + +### 5. 拉取封禁列表 + +``` +GET /api/external/bans?server=&status=&player=&page=&limit= +``` + +| 参数 | 说明 | +|------|------| +| server | 子服过滤(可选) | +| status | `active` / `expired` / `appealed` / `lifted`(默认不滤) | +| player | 按玩家名或 UID 精确匹配 | +| page / limit | 分页,limit ≤ 500 | + +**成功响应:** + +```json +[ + { + "id": 7, + "player_name": "Alex", + "player_uid": "SKIN_UUID_002", + "source": "netease", + "reason": "使用外挂", + "type": "ban", + "duration": "7d", + "server_name": "生存服", + "status": "active", + "created_at": "2026-08-19T10:00:00.000Z", + "expires_at": "2026-08-26T10:00:00.000Z" + } +] +``` + +### 6. 新增封禁 + +``` +POST /api/external/bans +``` + +```json +{ + "player_name": "Alex", + "player_uid": "SKIN_UUID_002", + "source": "netease", + "reason": "使用外挂", + "type": "ban", + "duration": "7d", + "expires_at": "2026-08-26T10:00:00.000Z", + "server": "survival", + "status": "active" +} +``` + +| 字段 | 必填 | 说明 | +|------|------|------| +| player_name | ✅ | 玩家名 | +| type | 否 | `ban` / `mute` / `warn` / `other`,默认 `ban` | +| status | 否 | `active` / `expired` / `appealed` / `lifted`,默认 `active` | +| expires_at | 否 | ISO 时间或 `null`(永久) | + +**响应 201:** `{ "id": 7 }` + +### 7. 被举报人统计 + +``` +GET /api/external/reported-players?server= +``` + +**响应:** + +```json +[ + { + "target_game_name": "Alex", + "target_game_uid": "SKIN_UUID_002", + "total": 5, + "active": 2, + "resolved": 3, + "last_report": "2026-08-19T10:00:00.000Z" + } +] +``` + +### 8. 服务器列表(含别名) + +``` +GET /api/external/servers +``` + +**响应:** + +```json +[ + { "group_name": "网易服务器", "server_name": "生存服", "alias": "survival" }, + { "group_name": "网易服务器", "server_name": "空岛服", "alias": "skyblock" } +] +``` + +### 9. 工单统计 + +``` +GET /api/external/stats?server= +``` + +**响应:** + +```json +{ + "total": 128, + "byType": [ { "type": "report", "c": 95 }, { "type": "suggestion", "c": 20 } ], + "byStatus": [ { "status": "pending", "c": 12 }, { "status": "resolved", "c": 100 } ] +} +``` + +--- + +## 五、错误码 + +| HTTP | 说明 | +|------|------| +| 400 | 参数错误(缺少必填字段 / 类型不正确 / 待处理工单超上限) | +| 401 | 鉴权失败(凭据错误或客户端已停用) | +| 404 | 工单 / 封禁不存在 | +| 500 | 服务器内部错误 | + +错误响应统一为 `{ "error": "描述信息" }`。 + +--- + +## 六、鉴权失败排查 + +1. **401 未授权**:检查 `x-api-client-id` / `x-api-secret` 是否与创建时一致;客户端是否被停用;secret 是否完整无换行空格。 +2. **客户端被停用**:后台「外部API」→ 启用。 +3. **需要多个客户端**:后台可创建多个,分别用于机器人 / 插件 / 统计面板,互不影响;删除即立即失效。 + +--- + +## 七、典型场景 + +### QQ 机器人:玩家提交举报 + +1. 玩家在 QQ 群发送举报信息 → 机器人调 `POST /tickets` 提交(reporter 为玩家 QQ 绑定的游戏名/UID) +2. 机器人把返回的 `tracking_token` 发给玩家 → 玩家随时发「查进度」→ 机器人调 `GET /tickets/track?token=...` +3. 处理结果(状态变化)可通过工单详情轮询,或后台「通知配置」设置 Webhook 推送 + +### 服务器插件:同步封禁 + +1. 插件启动时调 `GET /bans?status=active&server=生存服` 拉取本服封禁 +2. 服内封禁新玩家时调 `POST /bans` 同步到系统 +3. 定期轮询 `GET /bans?status=active` 保持同步 + +--- + +*协议版本 v1 · 服务端接口以部署版本为准*