Skip to content

HTTP API 文档 ​

项目:对面是AI吗
协议:HTTP/1.1
Content-Type:application/json(除标注外)
基础URL:https://game.xfcode.top
更新日期:2026-10-06


时间格式约定 ​

全站时间唯一格式为 Unix 秒级时间戳(整数):

  • 存储:MySQL / SQLite 时间列一律 BIGINT / INTEGER(Redis、消息 JSON 同理)。
  • API 返回:所有时间字段(created_at、updated_at、last_played_at、saved_at、favorited_at、generated_at、period_start、period_end 等)均为秒级整数,展示格式由消费端自行解析(前端统一用 shared.js 的全局 XQFTime)。
  • 例外:duration / timeout / seconds / expires_in / cooldown 等是相对时长(秒),不是时间戳;日历业务键(week、date)、HTTP Last-Modified / ETag 等按格式要求保留原样。

下文各示例中的时间字段均为整数;若为相对时长会在字段说明中标注。


认证机制 ​

大部分需要身份的 API 使用 Bearer Token 认证:

  • 请求头:Authorization: Bearer <token>
  • Token 类型:自签 JWT(HMAC-SHA256,密钥为玩家的 password_hash,有效期 365 天)
  • 获取方式:POST /api/email/auth、POST /api/oauth/confirm-create、GET /api/oauth/complete、/api/generate-player-id?action=recover 等身份入口下发
  • 特点:修改密码后旧 token 自动失效(签名密钥变化),此时会下发新 token

需要认证的 API(共 45 个):

/api/chat-history、/api/chat-history/detail、/api/player-messages、/api/player/tags、/api/sticker/list、 /api/soup/my-puzzles(GET 列表)、/api/account/overview、/api/account/bot-access、/api/bot/panel、/api/oauth/bindings、/api/community/my、 /api/player-message/hide、/api/player-message/settings、/api/player/worn-tags、/api/chat-history/collect、/api/collection/like、 /api/sticker/upload、/api/image/upload、/api/sticker/delete、/api/sticker/add-to-mine、 /api/macros(POST 保存)、/api/macros/delete、/api/soup/my-puzzles(POST 新建)、/api/soup/my-puzzles/{id}/share、/api/soup/public-puzzles/{id}/copy、 /api/temp/invite、/api/temp/invite/decline、/api/bot/apply、 /api/account/nickname、/api/account/avatar、/api/account/password、/api/account/email、 /api/bot/panel/nickname、/api/bot/panel/key、 /api/community/post/save、/api/community/post/delete、/api/community/like、/api/community/favorite、 /api/community/comment、/api/community/comment/delete、/api/community/report、 /api/oauth/unbind、/api/oauth/sync-avatar、 /api/soup/my-puzzles/{id}(PUT)、/api/soup/my-puzzles/{id}(DELETE)

条件认证:

  • /api/generate-player-id:仅 action=change_password(以及带 Authorization 头查询战绩时)需要 token
  • /api/email/send-code:scene=bind_email 时需要 token,scene=auth 不需要

可选认证(带 token 时行为不同,不带也能访问):

  • /api/macros:带 token 时返回的宏带 mine 标记
  • /api/temp/users:带 token 时搜索结果排除自己
  • 社区读接口 /api/community/posts、/api/community/search、/api/community/post、/api/community/comments:带 token 时返回 liked / favorited / is_own 等互动状态

BOT KEY 认证:

  • /api/bot/stickers:不走玩家 token,采用 player_id 查询参数 + Authorization: Bearer <bot_key>(BOT 的独立密钥,见 BOT 面板)

免认证:其余公开接口、页面路由与静态资源;部分接口通过昵称+密码手动校验(如 /api/player-stats)。


通用错误响应 ​

需要玩家 token 的接口(requirePlayerId)认证失败时返回:

json
{"error": "缺少 token"}
{"error": "token 无效或已过期"}

交流社区写接口未登录时返回 HTTP 401:

json
{"error": "请先登录后再操作"}

OAuth 相关接口(requirePlayerId)认证失败时返回:

json
{"ok": false, "error": "缺少 token"}
{"ok": false, "error": "token 无效或已过期"}

1. 身份与账号 ​

源码位置:GameController.php(generate-player-id / player-stats / player-profile) · 路由定义见 Router.php

1.1 获取/恢复玩家身份 ​

GET /api/generate-player-id

说明:通过 Token 查询战绩、通过 IP+指纹恢复、密码恢复身份、修改密码的复合接口。

「代号 + 密码」注册入口已下线:action=register 会直接返回引导文案,注册请改用邮箱验证码(POST /api/email/auth)。

请求参数(Query):

参数类型必填说明
actionstring否recover(恢复)、change_password(改密码)、register(已下线)、空(查询)
nicknamestring条件恢复时必填,最长 16 字符
passwordstring条件恢复时的原密码
old_passwordstring条件改密码时的旧密码
new_passwordstring条件改密码时的新密码(走 PasswordPolicy,见 7.4)
fpstring否设备指纹,用于 IP+指纹联合查找

请求头:改密码时(及通过 Token 查询时)必须带 Authorization: Bearer <token>


场景一:通过 Token 查询战绩

bash
curl "https://game.xfcode.top/api/generate-player-id" \
  -H "Authorization: Bearer <token>"

响应 200:

json
{
  "stats": {
    "total_games": 42,
    "wins": 18,
    "losses": 20,
    "draws": 4,
    "last_played_at": 1751000000
  }
}

场景二:通过 IP+指纹恢复战绩

bash
curl "https://game.xfcode.top/api/generate-player-id?fp=<设备指纹>"

响应 200:

json
{
  "stats": { "total_games": 42, "wins": 18, "losses": 20, "draws": 4 }
}

场景三:密码恢复身份

bash
curl "https://game.xfcode.top/api/generate-player-id?action=recover&nickname=小明&password=Abc12345"

成功 200:

json
{
  "token": "eyJ...",
  "nickname": "小明",
  "stats": { "total_games": 42, "wins": 18, "losses": 20, "draws": 4 }
}

昵称不存在或密码错误 200(不区分,防枚举昵称):

json
{"error": "用户名或密码错误"}

场景四:修改密码

bash
curl "https://game.xfcode.top/api/generate-player-id?action=change_password&old_password=Abc12345&new_password=Xyz98765" \
  -H "Authorization: Bearer <token>"

成功 200(密码已改,旧 token 失效,下发新 token):

json
{"ok": true, "token": "eyJ..."}

新密码强度不足 200(文案来自 PasswordPolicy,见 7.4):

json
{"error": "密码至少 8 位"}

旧密码错误 200:

json
{"error": "旧密码不正确"}

场景五:注册入口已下线 / 无匹配

json
{"error": "注册方式已升级,请使用邮箱验证码注册"}
json
{"error": "未找到玩家"}

1.2 查询玩家战绩 ​

GET /api/player-stats

说明:通过昵称+密码查询战绩(不依赖 Token,适用于换了设备/浏览器的情况)。

请求参数(Query):

参数类型必填说明
nicknamestring是昵称,最长 16 字符
passwordstring是密码

请求示例:

bash
curl "https://game.xfcode.top/api/player-stats?nickname=小明&password=Abc12345"

成功 200:

json
{
  "player_id": "abc123...",
  "stats": { "total_games": 42, "wins": 18, "losses": 20, "draws": 4 }
}

错误 200:

json
{"error": "昵称不能为空"}
{"error": "密码不能为空"}
{"error": "用户名或密码错误"}

1.3 玩家公开档案 ​

GET /api/player-profile

说明:公开接口,无需认证。返回玩家公开身份档案(风格画像 + 称号等)。

请求参数(Query):

参数类型必填说明
nicknamestring是昵称,最长 16 字符

请求示例:

bash
curl "https://game.xfcode.top/api/player-profile?nickname=小明"

成功 200:返回档案对象(字段由 PlayerStatsRepository::getPlayerProfileByNickname 组装)。

错误 200:

json
{"error": "昵称不能为空"}
{"error": "玩家不存在"}

2. 聊天记录 ​

2.1 聊天记录列表 ​

GET /api/chat-history

认证:需要 Authorization 请求头。

请求参数(Query):

参数类型必填说明
pageint否页码,默认 1

请求示例:

bash
curl "https://game.xfcode.top/api/chat-history?page=1" \
  -H "Authorization: Bearer <token>"

成功 200(created_at 为 Unix 秒级整数):

json
{
  "list": [
    {
      "id": 1,
      "opponent_name": "小红",
      "truth": "ai",
      "guess": "human",
      "result": "lose",
      "duration": 600,
      "created_at": 1751000000
    }
  ],
  "total": 42,
  "page": 1,
  "per_page": 10
}

2.2 聊天记录详情 ​

GET /api/chat-history/detail

认证:需要 Authorization 请求头。

请求参数(Query):

参数类型必填说明
idint是聊天记录 ID

请求示例:

bash
curl "https://game.xfcode.top/api/chat-history/detail?id=1" \
  -H "Authorization: Bearer <token>"

成功 200:

json
{
  "id": 1,
  "opponent_name": "小红",
  "messages": [...],
  "truth": "ai",
  "guess": "human"
}

参数错误 200:

json
{"error": "参数错误"}

未找到 200:

json
{"error": "未找到该记录"}

2.3 收藏聊天记录(设置标题 / 公开状态) ​

POST /api/chat-history/collect

认证:需要 Authorization 请求头。

请求体(JSON):

字段类型必填说明
idint是聊天记录 ID
titlestring否收藏标题,最多 100 字符
is_publicbool否是否公开分享

请求示例:

bash
curl -X POST "https://game.xfcode.top/api/chat-history/collect" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{"id":1,"title":"精彩对局","is_public":true}'

成功 200:

json
{"ok": true, "token": "public_token_xxx"}

参数错误 200:

json
{"error": "参数错误"}

3. 收藏 ​

3.1 通过 Token 获取收藏 ​

GET /api/collection/by-token

说明:无需登录,通过公开令牌查看收藏。

请求参数(Query):

参数类型必填说明
tokenstring是公开令牌

成功 200:

json
{ "id": 1, "title": "精彩对局", "messages": [...] }

错误 200:

json
{"error": "参数错误"}
{"error": "该链接已失效或不存在"}

3.2 玩家公开收藏列表 ​

GET /api/player-collections

说明:公开接口,无需认证。

请求参数(Query):

参数类型必填说明
nicknamestring是玩家昵称
pageint否页码,默认 1

错误 200:

json
{"error": "昵称不能为空"}
{"error": "玩家不存在"}

3.3 收藏详情 ​

GET /api/collection/detail

说明:公开接口,无需认证。仅返回公开的收藏。

请求参数(Query):

参数类型必填说明
idint是收藏 ID

错误 200:

json
{"error": "参数错误"}
{"error": "该收藏不存在或未公开"}

3.4 点赞收藏 ​

POST /api/collection/like

认证:需要 Authorization 请求头。

请求体(JSON):

字段类型必填说明
idint是收藏 ID

错误 200:

json
{"error": "参数错误"}

4. 留言 ​

4.1 我的留言列表 ​

GET /api/player-messages

认证:需要 Authorization 请求头。

说明:获取其他玩家给自己的留言列表(含隐藏状态)。

错误 200:

json
{"error": "玩家不存在"}

4.2 隐藏/显示留言 ​

POST /api/player-message/hide

认证:需要 Authorization 请求头。

请求体(JSON):

字段类型必填说明
message_idstring是留言 ID
hiddenbool是true=隐藏,false=显示

错误 200:

json
{"error": "参数不完整"}

4.3 留言设置 ​

POST /api/player-message/settings

认证:需要 Authorization 请求头。

请求体(JSON):

字段类型必填说明
allow_messagesbool否是否允许他人留言,缺省视为 true

成功 200:

json
{"success": true, "message": "设置已更新"}

5. 表情 ​

5.1 表情列表 ​

GET /api/sticker/list

认证:需要 Authorization 请求头。

说明:返回默认表情 + 当前用户自定义表情。

成功 200:

json
{
  "stickers": [
    { "id": "s1", "name": "大笑", "url": "/stickers/xxx.png" }
  ]
}

5.2 上传表情 ​

POST /api/sticker/upload

认证:需要 Authorization 请求头。

限制:

  • image_data 字符串长度不超过约 3MB(解码后 ≤ 约 2MB)
  • 每个用户最多 100 个自定义表情

请求体(JSON):

字段类型必填说明
image_datastring是Base64 图片数据(可带 data:image/xxx;base64, 前缀)
file_extstring否文件扩展名,默认 png

成功 200:

json
{"success": true, "sticker": {"id": "us_xxx", "name": "", "url": "https://..."}}

错误 200:

json
{"error": "图片数据不能为空"}
{"error": "图片大小不能超过 2MB"}
{"error": "自定义表情已达上限(100个),请先删除旧表情"}
{"error": "图床未配置"}
{"error": "图片数据 base64 解码失败"}
{"error": "图床上传失败,未获取到有效URL"}

5.3 删除表情 ​

POST /api/sticker/delete

认证:需要 Authorization 请求头。

请求体(JSON):

字段类型必填说明
sticker_idstring是表情 ID

成功 200:

json
{"success": true}

错误 200:

json
{"error": "参数不完整"}

5.4 添加到我的表情 ​

POST /api/sticker/add-to-mine

认证:需要 Authorization 请求头。

说明:把默认表情 / 其他用户已审核的表情添加到自己的列表,无需审核,直接置为 approved。

请求体(JSON):

字段类型必填说明
sticker_idstring是表情 ID

成功 200:

json
{"success": true}

错误 200:

json
{"error": "缺少表情ID"}
{"error": "表情不存在"}
{"error": "该表情已在你的列表中"}
{"error": "自定义表情已达上限(100个),请先删除旧表情"}

5.5 图片上传(聊天室图床代理) ​

POST /api/image/upload

认证:需要 Authorization 请求头。

Content-Type:multipart/form-data

说明:把图片代理上传到图床,图床 URL 不下发,仅返回一次性 key;由 lobby_image 消息内部消费 key 换取图片地址,客户端无法伪造图片地址。不入库。

请求表单字段:

字段类型必填说明
filefile是图片文件
file_extstring否扩展名,缺省从文件名后缀推断,再缺省 png
fpstring否设备指纹(用于封禁检查)

限制:

  • 同一玩家 60 秒内最多 10 次,超出返回 {"error": "上传太频繁,请稍后再试"}
  • 返回的 key 为一次性凭证,有效期 120 秒,且与提交玩家绑定

成功 200:

json
{"success": true, "key": "a1b2c3d4e5f6..."}

错误 200:

json
{"error": "您已被管理员封禁"}
{"error": "上传太频繁,请稍后再试"}
{"error": "图片上传失败,请重试"}
{"error": "图片读取失败"}
{"error": "图床未配置"}
{"error": "图床上传失败,未获取到有效URL"}

6. 头像 ​

6.1 获取玩家头像 ​

GET /api/avatar/{player_id}

说明:公开接口,无需认证。返回玩家头像图片(WebP)。

响应头:

  • Content-Type: image/webp
  • Cache-Control: public, no-cache
  • ETag / Last-Modified(支持 304 协商缓存)

响应:

  • 200:头像二进制内容
  • 304:If-None-Match / If-Modified-Since 命中时
  • 404:玩家无头像(返回纯文本 Not Found,前端降级为首字符渲染)

说明:上传入口见「7.3 上传头像」POST /api/account/avatar。


7. 账号中心 ​

源码位置:GameController.php(账号中心一区) · 密码策略见 PasswordPolicy.php

页面:GET /account

7.1 账号总览 ​

GET /api/account/overview

认证:需要 Authorization 请求头(player_token,只读)。

成功 200(时间字段为 Unix 秒级整数):

json
{
  "ok": true,
  "player_id": "abc123...",
  "nickname": "小明",
  "email": "a@b.com",
  "discriminator": 1234,
  "created_at": 1700000000,
  "last_played_at": 1751000000,
  "avatar": "/api/avatar/abc123...?v=1751000000",
  "worn_tags": ["理性"],
  "worn_special_tags": [],
  "can_rename": true,
  "rename_hint": "本月可修改一次",
  "password_set": true,
  "bindings": [
    { "provider": "github", "provider_id": "12345", "email": "a@b.com", "created_at": 1700000000 }
  ],
  "stats": {
    "total_games": 42,
    "win_rate": 60,
    "avg_msgs": 8
  }
}
字段类型说明
can_renamebool本月是否还可改名(每月限改一次)
password_setbool是否已自行设置密码;false 时前端只展示「新密码」输入
bindingsarrayOAuth 绑定列表(provider / provider_id / email / created_at)

错误 200:

json
{"error": "账号不存在"}

7.2 修改昵称 ​

POST /api/account/nickname

认证:需要 Authorization 请求头。

请求体(JSON):

字段类型必填说明
nicknamestring是新昵称,最长 16 字符
fpstring否设备指纹

成功 200:

json
{"ok": true, "nickname": "新名字", "player_id": "abc123..."}

错误 200:

json
{"error": "昵称不能为空"}
{"error": "该昵称不可使用"}
{"error": "昵称已被占用"}
{"error": "本月已修改过昵称,每月仅可修改一次"}

7.3 上传头像 ​

POST /api/account/avatar

认证:需要 Authorization 请求头。

Content-Type:multipart/form-data

请求表单字段:

字段类型必填说明
filefile是头像图片,大小 ≤ 2MB

限制:同一玩家 60 秒内最多 5 次。落盘路径 Storage/Avatars/{player_id}.webp,直接覆盖。

成功 200:

json
{"ok": true, "avatar": "/api/avatar/abc123...?v=1751000000"}

错误 200:

json
{"error": "操作太频繁,请稍后再试"}
{"error": "头像上传失败,请重试"}
{"error": "图片大小不能超过 2MB"}
{"error": "图片读取失败"}
{"error": "仅支持常见图片格式(PNG/JPG/GIF/WebP)"}

7.4 修改 / 首次设置密码 ​

POST /api/account/password

认证:需要 Authorization 请求头。

说明:语义与 WS 通道 change_password / set_password 一致:

  • password_set=1(已设置过密码):必须提供正确的 old_password;
  • password_set=0(OAuth / 邮箱注册的随机密码):免旧密码直接设置。

密码变更后旧 token 失效,故返回新 token。

请求体(JSON):

字段类型必填说明
new_passwordstring是新密码,走 PasswordPolicy
old_passwordstring条件password_set=1 时必填

密码策略(App\Services\Auth\PasswordPolicy,仅约束新设置的密码):至少 8 位;拒绝纯数字、6 位以上连续字符(123456 / abcdef / qwerty)、单字符重复与常见弱口令;需包含字母、数字、符号中的两类(纯中文口令 ≥2 个汉字可豁免)。

成功 200:

json
{
  "ok": true,
  "token": "eyJ...",
  "player_id": "abc123...",
  "nickname": "小明",
  "password_set": true
}

错误 200:

json
{"error": "密码不能为空或只包含空白字符"}
{"error": "密码不能包含换行、制表符等控制字符"}
{"error": "密码至少 8 位"}
{"error": "密码过长,请控制在 72 字节以内"}
{"error": "密码不能是同一个字符的重复"}
{"error": "密码不能包含 6 位以上连续字符(如 123456、abcdef、qwerty)"}
{"error": "密码太常见了,容易被猜到,请换一个"}
{"error": "密码需至少包含字母、数字、符号中的两类"}
{"error": "请输入当前密码"}
{"error": "旧密码不正确"}
{"error": "你已设置过密码,请刷新后重试"}

7.5 绑定 / 更换邮箱 ​

POST /api/account/email

认证:需要 Authorization 请求头。

请求体(JSON):

字段类型必填说明
emailstring是新邮箱
codestring是该邮箱收到的验证码(scene=bind_email)

成功 200:

json
{"ok": true, "email": "a@b.com"}

错误 200:

json
{"error": "请先登录"}
{"error": "邮箱格式不正确"}
{"error": "验证码错误或已过期"}
{"error": "该邮箱已被其他账号使用"}
{"error": "操作过于频繁,请稍后再试"}

7.6 BOT 权限状态 ​

GET /api/account/bot-access

认证:需要 Authorization 请求头。

说明:供聊天室内 BOT 弹窗动态决定展示「面板」还是「申请表单」。

成功 200(state 四态之一):

json
{
  "state": "approved",
  "nickname": "我的机器人",
  "bot_key": "bot_key_xxx",
  "account_id": "abc123...",
  "status_text": "启用",
  "created_at": 1700000000
}
json
{"state": "applied", "message": "您的申请已提交,请等待管理员审核"}
json
{"state": "disabled", "message": "该账号的 BOT 已被禁用,请联系管理员"}
json
{"state": "none"}
字段类型说明
statestringapproved(已启用)/ applied(已申请待审)/ disabled(被禁用)/ none(无 BOT)

8. 邮箱验证码登录 / 注册 ​

源码位置:GameController.php(sendEmailCode / emailAuth) · 业务逻辑见 EmailAuthService.php

/account 未登录页支持「邮箱 + 验证码」一键登录 / 注册。

8.1 发送邮箱验证码 ​

POST /api/email/send-code

认证:scene=auth 无需认证;scene=bind_email 需要 Authorization 请求头。

说明:限流 + 签发验证码并入队邮件队列(验证码只存哈希,Redis 自动过期)。

请求体(JSON):

字段类型必填说明
emailstring是收件邮箱
scenestring否auth(登录/注册,默认)或 bind_email(绑定/更换邮箱)

成功 200(cooldown 为冷却秒数,默认 60):

json
{"ok": true, "cooldown": 60}

错误 200:

json
{"error": "邮箱格式不正确"}
{"error": "请先登录"}
{"error": "该邮箱已被其他账号使用"}
{"error": "该邮箱已绑定到当前账号"}
{"error": "不支持的验证码用途"}
{"error": "验证码发送失败"}

8.2 邮箱验证码登录 / 注册 ​

POST /api/email/auth

认证:无需认证。

说明:邮箱未注册 → 自动建号,昵称由邮箱前缀生成({前缀}_{4位随机串},前缀最长 11 位、总长 ≤ 16,例:q1432777209@126.com → q1432777209_a1b2),password_set=0,可后续免旧密码首次设置密码;邮箱已注册 → 直接登录。

请求体(JSON):

字段类型必填说明
emailstring是邮箱
codestring是邮箱验证码
fpstring否设备指纹(用于注册限制)

成功 200:

json
{
  "ok": true,
  "is_new": true,
  "token": "eyJ...",
  "nickname": "q1432777209_a1b2",
  "player_id": "abc123...",
  "email": "q1432777209@126.com"
}

错误 200:

json
{"error": "邮箱格式不正确"}
{"error": "请输入验证码"}
{"error": "验证码错误或已过期"}
{"error": "操作过于频繁,请稍后再试"}
{"error": "不允许创建多个账号"}
{"error": "账号创建过于频繁,请稍后再试"}
{"error": "账号不存在"}

9. BOT 面板 ​

BOT 系统允许玩家申请一个 AI 机器人,BOT 可以以独立身份加入聊天室并发言。每个 BOT 拥有独立的 bot_key 用于 WebSocket 鉴权。

源码位置:GameController.php(BOT 面板/申请/表情接口) · BotGatewayWebSocketHandler.php(BOT 网关)

入口变更:BOT 面板已不再有独立页面,改为在 /lobby 聊天室内的弹窗中展示(由 GET /api/account/bot-access 决定展示面板还是申请表单)。

9.1 获取 BOT 信息 ​

GET /api/bot/panel

认证:需要 Authorization 请求头(玩家 token)。

说明:获取当前玩家绑定的 BOT 信息(需 BOT 存在且 status=1)。

请求示例:

bash
curl "https://game.xfcode.top/api/bot/panel" \
  -H "Authorization: Bearer <token>"

成功 200:

json
{
  "success": true,
  "nickname": "我的机器人",
  "bot_key": "bot_key_xxx",
  "account_id": "abc123...",
  "status": 1,
  "status_text": "启用",
  "created_at": 1700000000
}

未绑定 / 被禁用 200:

json
{"error": "该账号未绑定 BOT 或 BOT 已被禁用"}

9.2 更新 BOT 昵称 ​

POST /api/bot/panel/nickname

认证:需要 Authorization 请求头。

请求体(JSON):

字段类型必填说明
nicknamestring是新昵称,1~12 个字符

请求示例:

bash
curl -X POST "https://game.xfcode.top/api/bot/panel/nickname" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{"nickname":"新机器人"}'

成功 200:

json
{"success": true, "nickname": "新机器人"}

错误 200:

json
{"error": "该账号未绑定 BOT 或 BOT 已被禁用"}
{"error": "昵称需 1~12 个字符"}

9.3 轮换 BOT KEY ​

POST /api/bot/panel/key

认证:需要 Authorization 请求头。

说明:重新生成 BOT 的 bot_key,旧 KEY 立即失效。

成功 200:

json
{"success": true, "bot_key": "新的_bot_key_xxx"}

错误 200:

json
{"error": "该账号未绑定 BOT 或 BOT 已被禁用"}

9.4 申请 BOT ​

POST /api/bot/apply

认证:需要 Authorization 请求头。

说明:玩家提交 BOT 申请(邮箱 + 理由;一人仅可申请一次)。

请求体(JSON):

字段类型必填说明
emailstring是申请邮箱
reasonstring是申请理由,至少 10 个字,最长 500 字

请求示例:

bash
curl -X POST "https://game.xfcode.top/api/bot/apply" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{"email":"me@example.com","reason":"我想做一个陪聊机器人,为大家提供陪伴"}'

成功 200:

json
{"success": true, "message": "申请已提交,请等待管理员审核", "id": 12}

错误 200:

json
{"error": "邮箱格式不正确"}
{"error": "申请理由至少 10 个字"}
{"error": "您已提交过申请,不可重复申请"}
{"error": "申请失败"}

9.5 BOT 表情列表(BOT 网关) ​

GET /api/bot/stickers?player_id=xxx

认证:特殊 BOT KEY 认证 —— player_id 放在查询参数中,bot_key 放在 Authorization: Bearer <bot_key> 请求头中(非玩家 token)。

说明:返回全站表情列表(StickerService::list(),即默认表情表)。

请求示例:

bash
curl "https://game.xfcode.top/api/bot/stickers?player_id=abc123..." \
  -H "Authorization: Bearer <bot_key>"

成功 200:

json
{
  "stickers": [
    {"id": "st_xxx", "name": "大笑", "url": "https://..."}
  ]
}

鉴权失败 200:

json
{"error": "鉴权失败:玩家ID或KEY不正确"}

10. 临时聊天 TempChat ​

临时聊天(1v1 私密房)系统,支持搜索在线用户、邀请进行私密对话、断线重连等功能。

源码位置:GameController.php(在线/邀请 HTTP 接口) · TempChatWebSocketHandler.php(WS 处理器)

页面:GET /temp-chat

10.1 搜索在线用户 ​

GET /api/temp/users

认证:可选 Authorization 请求头(带 token 时结果排除自己)。

说明:仅搜索在线玩家(内存在线索引 OnlineRegistry,不查全量注册用户);昵称实时回源 player_data。

请求参数(Query):

参数类型必填说明
keywordstring否搜索关键词,最长 20 字符

请求示例:

bash
curl "https://game.xfcode.top/api/temp/users?keyword=小明" \
  -H "Authorization: Bearer <token>"

成功 200:

json
{
  "success": true,
  "users": [
    {"player_id": "abc...", "nickname": "小明", "status": "online"}
  ]
}
字段类型说明
player_idstring玩家 ID
nicknamestring玩家昵称
statusstringonline(在线)、busy(临时聊天中)、ingame(对局中)

10.2 发起邀请 ​

POST /api/temp/invite

认证:需要 Authorization 请求头。

说明:向在线用户发起临时聊天邀请。邀请方必须保持在线(有活跃的 WebSocket 连接)。

请求体(JSON):

字段类型必填说明
target_player_idstring是目标玩家 ID
from_namestring否邀请方昵称,缺省回源账号昵称

请求示例:

bash
curl -X POST "https://game.xfcode.top/api/temp/invite" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{"target_player_id":"abc123..."}'

成功 200:

json
{"success": true, "invite_id": "iv_xxx"}

错误 200:

json
{"error": "缺少目标用户"}
{"error": "不能邀请自己"}
{"error": "服务未就绪"}
{"error": "邀请失败"}

10.3 拒绝邀请 ​

POST /api/temp/invite/decline

认证:需要 Authorization 请求头。

说明:原地拒绝收到的临时聊天邀请(不跳转临时聊天页)。

请求体(JSON):

字段类型必填说明
invite_idstring是邀请 ID

成功 200:

json
{"success": true}

错误 200:

json
{"error": "缺少邀请ID"}
{"error": "服务未就绪"}
{"error": "拒绝失败"}

11. 宏命令 ​

宏命令是聊天室自定义 MD 组件模板。玩家可以创建、管理、删除自己的宏,宏在聊天室中通过 [!触发宏:名称] 语法调用。

源码位置:MacroRepository.php(存取层) · GameController.php(列表/保存/删除接口) · MarkdownMessageParser.php(宏解析/展开)

11.1 宏列表 ​

GET /api/macros

认证:可选 Authorization 请求头(带 token 时返回的宏带 mine 标记,区分「我的宏」)。

说明:返回房间全部宏。

成功 200:

json
{
  "success": true,
  "macros": [
    {
      "name": "hello",
      "nick": "",
      "params": "",
      "template": "大家好!",
      "creator_id": "abc...",
      "creator": "小明",
      "mine": false,
      "updated_at": 1751000000
    }
  ]
}
字段类型说明
minebool当前 token 对应的玩家是否为该宏创建者
updated_atintUnix 秒级时间戳

11.2 保存宏(新建/更新) ​

POST /api/macros

认证:需要 Authorization 请求头。

说明:创建新宏或更新已有宏(仅创建者可更新)。创建者昵称回源 DB,不信任请求体。

请求体(JSON):

字段类型必填说明
namestring是宏名称,最长 20 字符
nickstring否宏昵称(触发别名),最长 32 字符
paramsstring否参数名列表,最长 128 字符
templatestring否模板内容

成功 200:

json
{"success": true, "macro": { "name": "greet", "nick": "", "params": "name", "template": "你好,{name}!", "creator_id": "abc...", "creator": "小明" }}

错误 200:

json
{"error": "缺少宏名称"}
{"error": "保存失败"}

11.3 删除宏 ​

POST /api/macros/delete

认证:需要 Authorization 请求头。

说明:删除自己注册的宏(仅创建者可删除)。

请求体(JSON):

字段类型必填说明
namestring是要删除的宏名称

成功 200:

json
{"success": true}

错误 200:

json
{"error": "缺少宏名称"}
{"error": "删除失败"}

11.4 宏触发语法 ​

宏在聊天室消息中通过以下语法触发:

[!触发宏:名称]
[!触发宏:名称:参数值1|参数值2]
[!触发宏:昵称]
[!触发宏:昵称:参数值1|参数值2]
  • 名称优先匹配昵称(nick),再匹配名称(name)
  • 参数值用 | 分隔,按顺序对应宏定义的 params
  • 模板内 {参数名} 会被替换为对应参数值
  • 展开深度上限 3 层,防止递归死循环

12. 玩家标签 ​

源码位置:PlayerStatsRepository.php(标签存取) · GameController.php(getMyTags/setWornTags 接口)

12.1 获取我的标签 ​

GET /api/player/tags

认证:需要 Authorization 请求头。

说明:获取自己的标签库 + 当前佩戴(设置页展示用)。

成功 200:

json
{
  "tags": ["理性", "幽默", "老玩家"],
  "worn": ["理性", "幽默"],
  "special": [{"name": "新年快乐", "icon": "🎉"}],
  "worn_special": [],
  "max": 3
}
字段类型说明
tagsarray玩家拥有的所有普通标签
wornarray当前佩戴的普通标签
specialarray获得的特殊/节日标签
worn_specialarray当前佩戴的特殊标签
maxint最大可佩戴标签数(PlayerStatsRepository::MAX_WORN_TAGS)

12.2 设置佩戴标签 ​

POST /api/player/worn-tags

认证:需要 Authorization 请求头。

说明:服务端校验 ≤ 上限且必须存在于标签库。

请求体(JSON):

字段类型必填说明
tagsarray否普通标签名数组
special_tagsarray否特殊标签名数组

成功 200:返回 PlayerStatsRepository::setWornTags() 的结果对象(含 ok 等字段)。


13. 海龟汤汤面库 ​

源码位置:GameController.php(soup* 接口) · 业务逻辑 SoupPuzzleService.php · 存储 SoupPuzzleRepository.php

页面:GET /soup

汤面表字段:id / owner_id / source(official|member) / scope(private|public) / origin_author_id / title / surface(汤面) / truth(汤底) / key_points(数组) / hints(数组) / difficulty(1~3) / tags / status(active|banned) / used_count / created_at / updated_at。

13.1 我的汤面列表 ​

GET /api/soup/my-puzzles

认证:需要 Authorization 请求头。

说明:含汤底,仅本人可见。

成功 200:

json
{
  "success": true,
  "puzzles": [
    {
      "id": 7,
      "source": "member",
      "scope": "private",
      "origin_author_id": "",
      "title": "雨夜的车站",
      "surface": "...",
      "truth": "...",
      "key_points": ["..."],
      "hints": ["..."],
      "difficulty": 2,
      "tags": "推理,悬疑",
      "status": "active",
      "used_count": 3,
      "created_at": 1751000000,
      "updated_at": 1751000000
    }
  ]
}

13.2 新建汤面 ​

POST /api/soup/my-puzzles

认证:需要 Authorization 请求头。

说明:新建一律私有。数量上限由 Soup.PublicMaxMyPuzzles 控制(默认 100)。

请求体(JSON):

字段类型必填说明
surfacestring是汤面(谜面),最长 1000 字符
truthstring是汤底(真相),最长 2000 字符
titlestring否标题,最长 100 字符;缺省取汤面前 20 字
key_pointsarray否关键点,每项最长 100 字符
hintsarray否逐级提示,每项最长 200 字符
difficultyint否难度 1~3,默认 1
tagsstring否标签,逗号分隔,最长 255 字符

成功 200:

json
{"success": true, "id": 7}

错误 200:

json
{"error": "汤面和汤底均不能为空"}
{"error": "汤面数量已达上限(100)"}
{"error": "保存失败,请稍后再试"}

13.3 编辑汤面 ​

PUT /api/soup/my-puzzles/{id}

认证:需要 Authorization 请求头。

说明:仅本人、且仅私有汤面可编辑(已公开不可编辑)。请求体字段同 13.2。

成功 200:

json
{"success": true}

错误 200:

json
{"error": "参数不完整"}
{"error": "汤面不存在或无权编辑"}
{"error": "已公开的汤面不可编辑"}
{"error": "汤面和汤底均不能为空"}
{"error": "保存失败"}

13.4 删除汤面 ​

DELETE /api/soup/my-puzzles/{id}

认证:需要 Authorization 请求头。

说明:仅本人可删。公开汤面同样可删除(删除即终态,仅移除原汤,已复制出去的衍生汤不受影响)。

成功 200:

json
{"success": true}

错误 200:

json
{"error": "参数不完整"}
{"error": "汤面不存在或无权删除"}
{"error": "删除失败"}

13.5 公开汤面 ​

POST /api/soup/my-puzzles/{id}/share

认证:需要 Authorization 请求头。

说明:单向不可逆 —— 公开后汤底对所有用户可见、退出可游玩池、不可编辑、不可撤回;仍可删除。

成功 200:

json
{"success": true, "scope": "public"}

错误 200:

json
{"error": "参数不完整"}
{"error": "汤面不存在或无权操作"}
{"error": "该汤面已公开,且不可撤回"}
{"error": "操作失败,请稍后再试"}

13.6 公开汤池 ​

GET /api/soup/public-puzzles

说明:公开接口,无需认证。官方题 + 玩家公开题,公开即作品,汤底一并展示,附原作者昵称。

成功 200:

json
{
  "success": true,
  "puzzles": [
    {
      "id": 3,
      "source": "official",
      "scope": "public",
      "title": "雨夜的车站",
      "surface": "...",
      "truth": "...",
      "author": "官方",
      "is_derivative": false,
      "difficulty": 2,
      "tags": "推理",
      "used_count": 12
    }
  ]
}

13.7 复制公开汤面 ​

POST /api/soup/public-puzzles/{id}/copy

认证:需要 Authorization 请求头。

说明:复制公开汤池 / 官方题库中的汤面到「我的汤面」(含汤底,复制后转为私有)。不能复制自己的汤面。

成功 200:

json
{"success": true, "id": 8}

错误 200:

json
{"error": "参数不完整"}
{"error": "汤面不存在或已下架"}
{"error": "该汤面不在公开汤池中"}
{"error": "这是你自己的汤面,无需复制"}
{"error": "汤面数量已达上限(100)"}

14. 点歌历史 ​

源码位置:GameController.php(songHistory*) · SongService.php

页面:GET /song-history(展示每日 00:00 自动归档的历史歌单,支持选择日期、试听与查看票数)

14.1 歌单快照列表 ​

GET /api/song-history

说明:公开接口,无需认证。返回每日歌单备份列表(按日期倒序,仅摘要不含歌曲明细)。

成功 200:

json
{
  "count": 2,
  "history": [
    {
      "date": "2026-10-05",
      "saved_at": 1759593600,
      "pool_count": 5,
      "queue_count": 3,
      "has_playing": true,
      "total_songs": 9
    }
  ]
}
字段类型说明
datestring归档日期 YYYY-MM-DD
saved_atint保存时间(Unix 秒级时间戳;历史快照缺失时回退到该归属日 0 点)
pool_countint投票池歌曲数
queue_countint播放队列歌曲数
has_playingbool是否有当前播放
total_songsint合计歌曲数

14.2 指定日期歌单详情 ​

GET /api/song-history/detail?date=YYYY-MM-DD

说明:公开接口,无需认证。返回指定日期歌单快照的完整内容(投票池 + 播放队列 + 当前播放)。

请求参数(Query):

参数类型必填说明
datestring是日期,格式 YYYY-MM-DD

成功 200:返回快照对象。

参数格式错误 400:

json
{"error": "date 参数格式应为 YYYY-MM-DD"}

未找到 404:

json
{"error": "未找到 2026-10-05 的歌单备份"}

15. 周报 ​

源码位置:GameController.php(weeklyReport / weeklyReportIndex)

页面:GET /weekly-report

15.1 获取周报数据 ​

GET /api/weekly-report

说明:公开接口,无需认证。查询指定周的全部玩家榜单(SQLite Storage/weekly_reports.db)。

请求参数(Query):

参数类型必填说明
weekstring否周标识(如 2026-W33),缺省取最新周
sortstring否排序字段,默认 total_games;白名单:total_games、total_wins、win_rate、turing_games、turing_guess_accuracy、turing_best_streak、board_gomoku_games、board_go_games,非法值回退为 total_games
pageint否页码,默认 1
limitint否每页数量,1~100,默认 20
min_gamesint否最低总局数过滤,默认 0

成功 200(时间字段为 Unix 秒级整数):

json
{
  "week": "2026-W33",
  "overview": {
    "week": "2026-W33",
    "generated_at": 1751000000,
    "period_start": 1750000000,
    "period_end": 1751000000,
    "total_players": 120,
    "active_players": 80
  },
  "players": [
    {
      "player_id": "abc...",
      "discriminator": 1234,
      "total_games": 20,
      "total_wins": 12,
      "win_rate": 60,
      "turing_games": 10,
      "turing_guess_accuracy": 70,
      "turing_best_streak": 5,
      "board_gomoku_games": 6,
      "board_go_games": 4,
      "peak_hours": [20, 21],
      "created_at": 1700000000,
      "last_played_at": 1751000000
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 80,
    "total_pages": 4
  },
  "available_weeks": [
    {"week": "2026-W33", "generated_at": 1751000000, "total_players": 120, "active_players": 80}
  ]
}

错误 200:

json
{"error": "暂无周榜数据"}
{"error": "无法读取周榜数据"}
{"error": "周榜 2026-W33 不存在"}

16. 交流社区 ​

交流社区(帖子 / 评论 / 点赞 / 收藏 / 举报)。公开读接口无需登录;写接口需 Authorization: Bearer <player_token>,未登录返回 HTTP 401 {"error": "请先登录后再操作"}。

源码位置:CommunityController.php · CommunityService.php · CommunityRepository.php

页面:GET /community(列表)、GET /community/post/{id}(详情)

帖子状态(status):pending(待审)/ approved(已通过)/ rejected(被拒)/ deleted(软删除)。列表仅返回 approved,作者本人额外可见自己的 pending/rejected。 帖子类型(type):common(纯文本)/ markdown(Markdown 原文)。审核模式由 Community.ReviewMode 决定。

统一分页结构(success/data):

字段类型说明
listarray数据列表
totalint总数
pageint当前页
page_sizeint每页数量(默认 Community.PageSize,20;上限 100)
has_morebool是否还有下一页

分页帖子对象字段(list[]):

字段类型说明
idint帖子 ID
titlestring标题
author_idstring作者 player_id
author_namestring作者昵称(实时回源)
typestringcommon / markdown
descriptionstring摘要
created_atint发布时间(Unix 秒级时间戳)
like_count / favorite_count / comment_countint计数
statusstring帖子状态
is_pinnedint是否置顶(1/0)
liked / favoritedbool当前浏览者是否已赞 / 已收藏(未登录为 false)
is_ownbool是否本人帖子
reject_reasonstring退回理由,仅作者本人可见(他人不返回该字段)
favorited_atint收藏时间(仅「我的收藏」列表返回)

author_ip / author_fp 属隐私字段,前台接口一律不返回。

16.1 帖子列表 ​

GET /api/community/posts

认证:可选 Authorization 请求头(带 token 时返回互动状态与自己的待审帖)。

请求参数(Query):

参数类型必填说明
pageint否页码,默认 1
page_sizeint否每页数量,默认 Community.PageSize(20),上限 100
typestring否帖子类型 common / markdown
sortstring否排序:new(默认,置顶优先+时间倒序)/ hot(置顶优先+热度)/ like(置顶优先+点赞数)
authorstring否作者 player_id,最长 64 字符

成功 200:

json
{
  "success": true,
  "data": {
    "list": [
      {
        "id": 12,
        "title": "大家好",
        "author_id": "abc...",
        "author_name": "小明",
        "type": "markdown",
        "description": "第一次发帖",
        "created_at": 1751000000,
        "like_count": 3,
        "favorite_count": 1,
        "comment_count": 2,
        "status": "approved",
        "is_pinned": 0,
        "liked": false,
        "favorited": false,
        "is_own": false
      }
    ],
    "total": 42,
    "page": 1,
    "page_size": 20,
    "has_more": true
  }
}

16.2 搜索帖子 ​

GET /api/community/search

认证:可选 Authorization 请求头。

说明:全文检索(ngram 短语搜索,索引不可用时自动降级 LIKE)。关键词只保留中英文与数字,长度须在 Community.SearchMinLen(默认 2)~ Community.SearchMaxLen(默认 50)之间。

请求参数(Query):

参数类型必填说明
qstring是搜索关键词
pageint否页码,默认 1
typestring否帖子类型过滤

成功 200(data 额外含 keyword、driver):

json
{
  "success": true,
  "data": {
    "list": [],
    "total": 0,
    "page": 1,
    "page_size": 20,
    "has_more": false,
    "driver": "ngram",
    "keyword": "数据库"
  }
}

错误 400:

json
{"error": "请输入搜索关键词"}
{"error": "搜索关键词至少 2 个字"}
{"error": "搜索功能未开放"}

16.3 帖子详情 ​

GET /api/community/post?id={id}

认证:可选 Authorization 请求头。

说明:返回帖子详情 + 评论首页(未通过审核的帖子不展示评论)。

请求参数(Query):

参数类型必填说明
idint是帖子 ID
comment_pageint否评论页码,默认 1

成功 200:

json
{
  "success": true,
  "post": { "id": 12, "title": "大家好", "status": "approved" },
  "comments": { "list": [], "total": 0, "page": 1, "page_size": 20, "has_more": false }
}

错误:

json
{"error": "参数不完整"}       // 400
{"error": "帖子不存在或不可见"}  // 404

16.4 评论列表 ​

GET /api/community/comments

认证:可选 Authorization 请求头。

说明:分页只对顶级评论(parent_id=0)进行,每条顶级评论附带其全部已通过回复 replies(楼中楼,仅一层)。

请求参数(Query):

参数类型必填说明
post_idint是帖子 ID
pageint否页码,默认 1

成功 200:

json
{
  "success": true,
  "data": {
    "list": [
      {
        "id": 5,
        "post_id": 12,
        "parent_id": 0,
        "author_id": "abc...",
        "author_name": "小红",
        "content": "写得好",
        "created_at": 1751000000,
        "is_own": false,
        "replies": []
      }
    ],
    "total": 1,
    "page": 1,
    "page_size": 20,
    "has_more": false
  }
}

错误:

json
{"error": "参数不完整"}       // 400
{"error": "帖子不存在或不可见"}  // 404

16.5 评论定位 ​

GET /api/community/comment-locate

说明:公开接口,无需认证。定位评论所在页码(支持 /community/post/{id}#comment-{cid} 跨页直达)。

请求参数(Query):

参数类型必填说明
comment_idint是评论 ID

成功 200:

json
{"success": true, "post_id": 12, "comment_id": 5, "page": 1}

错误:

json
{"error": "参数不完整"}   // 400
{"error": "评论不存在"}   // 404

16.6 我的帖子 / 我的收藏 ​

GET /api/community/my

认证:需要 Authorization 请求头。

请求参数(Query):

参数类型必填说明
tabstring否posts(我的帖子,默认)或 favorites(我的收藏)
pageint否页码,默认 1

成功 200(我的帖子,status='all' 会过滤掉已删除):

json
{"success": true, "tab": "posts", "data": {"list": [], "total": 0, "page": 1, "page_size": 20, "has_more": false}}
json
{"success": true, "tab": "favorites", "data": {"list": [], "total": 0, "page": 1, "page_size": 20, "has_more": false}}

16.7 发布 / 编辑帖子 ​

POST /api/community/post/save

认证:需要 Authorization 请求头。

说明:id 为空即新建,>0 为编辑(仅作者本人)。发帖限流:每分钟 1 条、每天 20 条。编辑会重置审核状态。

请求体(JSON):

字段类型必填说明
idint否帖子 ID(编辑时传)
titlestring是标题,最长 Community.MaxTitleLen(120)
contentstring是正文,最长 Community.MaxContentLen(40000)
descriptionstring否摘要,最长 Community.MaxDescLen(500)
typestring否common / markdown,默认 markdown,非法值回退 markdown
fpstring否设备指纹(也可放在表单字段)

成功 200:

json
{
  "success": true,
  "id": 12,
  "status": "pending",
  "message": "发布成功,已提交审核,通过后即可公开展示"
}

错误 400:

json
{"error": "标题不能为空"}
{"error": "正文不能为空"}
{"error": "您已被管理员封禁,无法发帖"}
{"error": "无效的玩家身份"}
{"error": "帖子不存在"}
{"error": "只能编辑自己的帖子"}
{"error": "帖子已删除,无法编辑"}
{"error": "编辑失败,请重试"}
{"error": "发布失败,请重试"}
{"error": "发帖太频繁,请稍后再试"}
{"error": "今日发帖太频繁,请稍后再试"}

16.8 删除帖子 ​

POST /api/community/post/delete

认证:需要 Authorization 请求头。

请求体(JSON):

字段类型必填说明
idint是帖子 ID

成功 200:

json
{"success": true, "message": "已删除"}

错误:

json
{"error": "参数不完整"}        // 400
{"error": "帖子不存在"}        // 404
{"error": "只能删除自己的帖子"}  // 403
{"error": "删除失败,请重试"}   // 400

16.9 点赞帖子 ​

POST /api/community/like

认证:需要 Authorization 请求头。

说明:幂等切换(赞 / 取消赞)。每秒频次受限(每分钟 30 次)。

请求体(JSON):

字段类型必填说明
idint是帖子 ID

成功 200:

json
{"success": true, "active": true, "count": 4, "message": "已点赞"}

错误 400:

json
{"error": "参数不完整"}
{"error": "帖子不存在或不可见"}
{"error": "您已被管理员封禁"}
{"error": "操作太频繁,请稍后再试"}
{"error": "点赞失败,请重试"}

16.10 收藏帖子 ​

POST /api/community/favorite

认证:需要 Authorization 请求头。

说明:幂等切换(收藏 / 取消收藏)。

请求体(JSON):

字段类型必填说明
idint是帖子 ID

成功 200:

json
{"success": true, "active": true, "count": 2, "message": "已收藏"}

错误 400:

json
{"error": "参数不完整"}
{"error": "帖子不存在或不可见"}
{"error": "您已被管理员封禁"}
{"error": "操作太频繁,请稍后再试"}
{"error": "收藏失败,请重试"}

16.11 发表评论 ​

POST /api/community/comment

认证:需要 Authorization 请求头。

说明:评论免审(始终即时 approved)。支持楼中楼:parent_id > 0 时为该顶级评论的回复(仅允许一层)。限流:每分钟 5 条。

请求体(JSON):

字段类型必填说明
post_idint是帖子 ID
contentstring是评论内容,最长 Community.MaxCommentLen(1000)
parent_idint否回复的顶级评论 ID,0=顶级评论
fpstring否设备指纹(也可放在表单字段)

成功 200:

json
{"success": true, "id": 9, "status": "approved", "message": "评论成功"}

错误 400:

json
{"error": "参数不完整"}
{"error": "评论内容不能为空"}
{"error": "您已被管理员封禁,无法评论"}
{"error": "无效的玩家身份"}
{"error": "帖子不存在或不可见"}
{"error": "回复的评论不存在"}
{"error": "评论太频繁,请稍后再试"}
{"error": "评论失败,请重试"}

16.12 删除评论 ​

POST /api/community/comment/delete

认证:需要 Authorization 请求头。

说明:仅作者本人。删除顶级评论时级联软删其全部回复并扣除计数。

请求体(JSON):

字段类型必填说明
idint是评论 ID

成功 200:

json
{"success": true, "message": "已删除"}

错误:

json
{"error": "参数不完整"}        // 400
{"error": "评论不存在"}        // 404
{"error": "只能删除自己的评论"}  // 403
{"error": "删除失败,请重试"}   // 400

16.13 举报帖子 ​

POST /api/community/report

认证:需要 Authorization 请求头。

说明:复用既有举报体系(source=community,与对局/聊天室举报同一张 reports 表)。仅允许举报「已通过」且非本人的帖子;同一用户对同一帖子只能举报一次。

请求体(JSON):

字段类型必填说明
idint是帖子 ID
reasonstring是举报原因,非空且不超过 100 字
fpstring否设备指纹(也可放在表单字段)

成功 200:

json
{"success": true, "message": "举报已提交,管理员将尽快处理"}

错误 400:

json
{"error": "参数不完整"}
{"error": "举报原因不能为空且不超过100字"}
{"error": "您已被管理员封禁,无法举报"}
{"error": "无效的玩家身份"}
{"error": "帖子不存在"}
{"error": "该帖子不可举报"}
{"error": "不能举报自己的帖子"}
{"error": "举报失败,请稍后再试"}

17. OAuth 快捷登录 ​

OAuth 2.0 快捷登录系统支持通过第三方平台(如 GitHub)快速登录/注册,使用 PKCE 授权码流程。登录结果产出现有的 player token(localStorage 存储),不引入 session/cookie。

源码位置:OAuthController.php(PKCE 授权码流程) · GenericOAuthProvider.php(提供商抽象) · OAuthBindingRepository.php(绑定存取)

17.1 支持的提供商 ​

GET /api/oauth/providers

说明:公开接口,无需认证。未配置 ClientId 或端点不完整的 provider 不会返回。

请求示例:

bash
curl "https://game.xfcode.top/api/oauth/providers"

成功 200(直接返回数组):

json
[
  {"key": "github", "name": "GitHub"}
]

17.2 发起 OAuth 登录 ​

GET /oauth/login/{provider}
POST /oauth/login/{provider}

说明:跳转到第三方 OAuth 授权页面。{provider} 为提供商 ID(如 github)。未知 / 不可用的 provider 返回 404 Unknown OAuth provider。

服务端处理:

  1. 生成 PKCE code_verifier 和 code_challenge
  2. 生成随机 state 参数防 CSRF
  3. 302 跳转到第三方 OAuth 授权 URL
  4. 临时数据(state、code_verifier、redirect、可选 bind_player_id)存入 Redis,600 秒过期

请求参数(Query 或 form body,POST 场景用 form):

参数类型必填说明
redirectstring否登录成功后的站内跳转路径(仅允许以 / 开头的同站路径)
bindint否1=绑定模式(需已登录),缺省为登录模式
tokenstring条件绑定模式的玩家 Token(form POST 时用,无法带 Authorization 头)

绑定模式:已登录玩家通过 bind=1 将第三方账号绑定到现有账号,须经 Authorization: Bearer 或 form token 解析出玩家;失效时返回 {"ok": false, "error": "登录状态已失效,请重新登录后再绑定"}。

登录模式:未登录玩家通过 OAuth 创建新账号或登录已有绑定账号。


17.3 OAuth 回调 ​

GET /oauth/callback/{provider}

说明:第三方 OAuth 授权后的回调地址。{provider} 为提供商 ID。

请求参数(Query):

参数类型必填说明
codestring是授权码(provider 回调携带)
statestring是状态参数(校验 CSRF)

处理流程:

  1. 校验 state(一次性,取后即删)与 Redis 中的一致、provider 匹配
  2. 用 code + code_verifier 向 provider 换取 access_token
  3. 用 access_token 获取第三方用户信息
  4. 分支处理:
    • 已绑定 → 生成一次性 exchange code,302 回到 redirect(携带 oauth_code)
    • 绑定模式 → 关联到已登录玩家,bind 失败则 302 携带 oauth_error=该 OAuth 账号已绑定其他玩家
    • 按邮箱匹配到已有玩家 → 自动绑定并登录
    • 邮箱未匹配 → 302 回 redirect 携带 pending_code,引导确认建号

错误响应:400 Missing code or state / 400 Invalid or expired state / 400 Provider mismatch;业务失败时 302 携带 oauth_error=<消息>。


17.4 完成登录(一次性换取 token) ​

GET /oauth/complete

说明:公开接口,无需认证。用回调下发的一次性 exchange code 换取 player token(前端存 localStorage)。exchange code 有效期 60 秒,且一次性。

请求参数(Query):

参数类型必填说明
codestring是一次性 exchange code

成功 200:

json
{"ok": true, "token": "eyJ...", "nickname": "小明", "player_id": "abc123..."}

错误 200:

json
{"ok": false, "error": "缺少凭证"}
{"ok": false, "error": "登录凭证无效或已过期"}
{"ok": false, "error": "登录凭证数据异常"}

17.5 获取待绑定信息 ​

GET /api/oauth/pending-info

说明:公开接口,无需认证。读取 pending 建号确认信息(供前端弹窗展示邮箱 + 预填昵称),只读不消费,不返回任何 token 等敏感数据。

请求参数(Query):

参数类型必填说明
codestring是pending 建号凭证

成功 200:

json
{"ok": true, "provider": "github", "email": "a@b.com", "nickname": "octocat"}

错误 200:

json
{"ok": false, "error": "缺少凭证"}
{"ok": false, "error": "确认凭证无效或已过期,请重新登录"}

17.6 确认创建账号 ​

POST /api/oauth/confirm-create

说明:公开接口,无需认证。参数从 Query 读取。与普通注册同一套防线:昵称唯一、IP/FP 3 账号上限、Redis 频率限制。

请求参数(Query):

参数类型必填说明
codestring是pending 建号凭证
nicknamestring是昵称,最长 12 个字符
fpstring否设备指纹

成功 200:

json
{"ok": true, "token": "eyJ...", "nickname": "小明", "player_id": "abc123..."}

错误 200:

json
{"ok": false, "error": "缺少确认凭证"}
{"ok": false, "error": "昵称不能为空"}
{"ok": false, "error": "昵称最长 12 个字符"}
{"ok": false, "error": "确认凭证无效或已过期,请重新登录"}
{"ok": false, "error": "该昵称已被占用,请换一个"}
{"ok": false, "error": "确认凭证已使用,请重新登录"}
{"ok": false, "error": "不允许创建多个账号!"}
{"ok": false, "error": "账号创建过于频繁,请稍后再试"}
{"ok": false, "error": "该 OAuth 账号已被其他玩家绑定"}

17.7 取消建号 ​

POST /api/oauth/cancel

说明:公开接口,无需认证。用户选择不创建账户时清理 pending(不创建任何数据)。code 从 Query 读取(可选)。

请求参数(Query):

参数类型必填说明
codestring否pending 建号凭证

成功 200:

json
{"ok": true}

17.8 获取绑定列表 ​

GET /api/oauth/bindings

认证:需要 Authorization 请求头。

成功 200(时间字段为 Unix 秒级整数):

json
{
  "ok": true,
  "bindings": [
    {"provider": "github", "provider_id": "12345", "email": "a@b.com", "created_at": 1700000000}
  ],
  "avatar": "/api/avatar/abc123...?v=1751000000"
}

17.9 解绑 OAuth ​

POST /api/oauth/unbind

认证:需要 Authorization 请求头。

说明:provider 从 form body 读取(Content-Type: application/x-www-form-urlencoded 或 multipart/form-data)。

请求表单字段:

字段类型必填说明
providerstring是提供商 ID

成功 200:

json
{"ok": true}

错误 200:

json
{"ok": false, "error": "缺少 provider"}

17.10 手动同步头像 ​

POST /api/oauth/sync-avatar

认证:需要 Authorization 请求头。

说明:用指定平台存储的 access_token 重新拉取最新头像并下载覆盖本地文件。provider 从 form body 读取。

请求表单字段:

字段类型必填说明
providerstring是提供商 ID

成功 200:

json
{"ok": true, "player_id": "abc123..."}

错误 200:

json
{"ok": false, "error": "缺少 provider"}
{"ok": false, "error": "未找到可同步的 OAuth 绑定"}
{"ok": false, "error": "该平台授权已过期,请重新登录后同步"}
{"ok": false, "error": "该平台未返回头像"}
{"ok": false, "error": "头像同步失败,请稍后再试"}

18. 页面路由 ​

以下为服务端渲染的 HTML 页面(GET,均免认证):

路径说明
/游戏中心首页(XQFGameHub:玩法入口、在线人数、账号便签)
/turing图灵测试 1v1 玩法页(同时承载玩家档案 / 公开收藏视图)
/lobby公共聊天室大厅
/temp-chat临时聊天私密房间
/board/gomoku五子棋对局页
/board/go围棋对局页
/soup海龟汤页
/blockworld方块世界(BlockWorld.Enabled=false 时返回 503 维护页)
/agreement用户协议页
/privacy隐私政策页
/weekly-report全服周报页
/account完整账号中心页
/community交流社区帖子列表页
/community/post/{id}交流社区帖子详情页
/song-history点歌回顾页(历史歌单)
/player/{nickname}玩家档案视图(由 1v1 SPA 渲染)
/collection/{token}公开收藏视图(由 1v1 SPA 渲染)
/{adminPath}管理后台页面(默认 /admin,由 Admin.Path 配置)

棋类页面已统一到 /board/... 命名空间,早期的旧版短路径已下线(不做 302 兼容)。

静态资源路由 ​

由 GameController::STATIC_RESOURCES 批量注册(GET,免认证,带强缓存 Cache-Control: public, max-age=31536000, immutable 与 ETag / Last-Modified 协商):

路径文件
/script.jsPublic/script.js
/style.cssPublic/style.css
/favicon.svgPublic/favicon.svg
/shared.jsPublic/shared.js
/shared/md-renderer.jsPublic/shared/md-renderer.js
/shared/md-renderer.cssPublic/shared/md-renderer.css
/hub.cssPublic/hub.css
/hub.jsPublic/hub.js
/hub-nav.jsPublic/hub-nav.js
/comment-widget.jsPublic/comment-widget.js
/temp-invite.jsPublic/temp-invite.js
/turing/index.htmlPublic/turing/index.html
/soup/index.htmlPublic/soup/index.html
/soup/soup.cssPublic/soup/soup.css
/soup/soup.jsPublic/soup/soup.js
/agreement/index.htmlPublic/agreement/index.html
/privacy/index.htmlPublic/privacy/index.html
/lobby/lobby.cssPublic/lobby/lobby.css
/lobby/lobby.jsPublic/lobby/lobby.js
/lobby/lobby-bot.jsPublic/lobby/lobby-bot.js
/board/board-game.cssPublic/board/board-game.css
/board/gomoku/gomoku.cssPublic/board/gomoku/gomoku.css
/board/gomoku/gomoku.jsPublic/board/gomoku/gomoku.js
/board/go/go.cssPublic/board/go/go.css
/board/go/go.jsPublic/board/go/go.js
/board/go/go-chat.jsPublic/board/go/go-chat.js
/weekly-report/weekly-report.cssPublic/weekly-report/weekly-report.css
/weekly-report/weekly-report.jsPublic/weekly-report/weekly-report.js
/temp-chat/temp-chat.cssPublic/temp-chat/temp-chat.css
/temp-chat/temp-chat.jsPublic/temp-chat/temp-chat.js
/account/index.htmlPublic/account/index.html
/account/account.cssPublic/account/account.css
/account/account.jsPublic/account/account.js
/account/sticker-manager.jsPublic/account/sticker-manager.js
/blockworld/index.htmlPublic/blockworld/index.html
/blockworld/blockworld.cssPublic/blockworld/blockworld.css
/blockworld/blockworld.jsPublic/blockworld/blockworld.js
/community/index.htmlPublic/community/index.html
/community/post.htmlPublic/community/post.html
/community/community.cssPublic/community/community.css
/community/community.jsPublic/community/community.js
/community/post.jsPublic/community/post.js
/song-history/song-history.cssPublic/song-history/song-history.css
/song-history/song-history.jsPublic/song-history/song-history.js
/admin/admin.jsPublic/admin/admin.js
/admin/admin_mdv3.jsPublic/admin/admin_mdv3.js
/admin/admin.cssPublic/admin/admin.css
/admin/favicon.svgPublic/admin/favicon.svg

19. 管理员 ​

19.1 管理员登录 ​

POST /{adminPath}/api/login

说明:{adminPath} 默认值为 /admin,可在配置中修改。注意此接口不走 Bearer Token,使用用户名+密码。

请求体(JSON):

字段类型必填说明
usernamestring是管理员用户名
passwordstring是管理员密码

请求示例:

bash
curl -X POST "https://你的域名/admin/api/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"xxx"}'

成功 200:

json
{
  "ok": true,
  "token": "eyJ...",
  "username": "admin",
  "role": "super_admin"
}

失败 200:

json
{"ok": false, "error": "用户名和密码不能为空"}
{"ok": false, "error": "用户名或密码错误"}
{"ok": false, "error": "该账号已被禁用"}

Token 说明:

  • 管理员 Token 有效期 24 小时
  • 签名密钥为 Admin.Password 配置项
  • Payload 包含 admin_id、username、role

附录:完整 API 索引 ​

API 路由 ​

方法路径认证说明
GET/api/generate-player-id条件获取/恢复身份、改密码、查战绩
GET/api/player-stats密码通过昵称+密码查战绩
GET/api/player-profile无玩家公开档案
GET/api/chat-historyBearer聊天记录列表
GET/api/chat-history/detailBearer聊天记录详情
POST/api/chat-history/collectBearer设置收藏标题/公开状态
GET/api/collection/by-token无通过 Token 获取收藏
GET/api/player-collections无玩家公开收藏列表
GET/api/collection/detail无收藏详情
POST/api/collection/likeBearer点赞收藏
GET/api/player-messagesBearer我的留言列表
POST/api/player-message/hideBearer隐藏/显示留言
POST/api/player-message/settingsBearer留言设置
GET/api/sticker/listBearer表情列表
POST/api/sticker/uploadBearer上传表情
POST/api/sticker/deleteBearer删除表情
POST/api/sticker/add-to-mineBearer添加到我的表情
POST/api/image/uploadBearer图片上传(返回一次性 key)
GET/api/avatar/{player_id}无玩家头像图片
GET/api/account/overviewBearer账号总览
POST/api/account/nicknameBearer修改昵称
POST/api/account/avatarBearer上传头像
POST/api/account/passwordBearer修改/首次设置密码
POST/api/account/emailBearer绑定/更换邮箱
GET/api/account/bot-accessBearerBOT 权限状态
POST/api/email/send-code条件发送邮箱验证码
POST/api/email/auth无邮箱验证码登录/注册
GET/api/bot/panelBearer获取 BOT 信息
POST/api/bot/panel/nicknameBearer更新 BOT 昵称
POST/api/bot/panel/keyBearer轮换 BOT KEY
POST/api/bot/applyBearer申请 BOT
GET/api/bot/stickersBOT KEYBOT 表情列表(player_id + bot_key)
GET/api/temp/users可选搜索在线用户
POST/api/temp/inviteBearer发起临时聊天邀请
POST/api/temp/invite/declineBearer拒绝邀请
GET/api/macros可选宏列表
POST/api/macrosBearer保存宏(新建/更新)
POST/api/macros/deleteBearer删除宏
GET/api/player/tagsBearer获取我的标签
POST/api/player/worn-tagsBearer设置佩戴标签
GET/api/soup/my-puzzlesBearer我的汤面列表
POST/api/soup/my-puzzlesBearer新建汤面
PUT/api/soup/my-puzzles/{id}Bearer编辑汤面
DELETE/api/soup/my-puzzles/{id}Bearer删除汤面
POST/api/soup/my-puzzles/{id}/shareBearer公开汤面
GET/api/soup/public-puzzles无公开汤池
POST/api/soup/public-puzzles/{id}/copyBearer复制公开汤面
GET/api/song-history无歌单快照列表
GET/api/song-history/detail无指定日期歌单详情
GET/api/weekly-report无周榜数据
GET/api/community/posts可选社区帖子列表
GET/api/community/search可选社区搜索
GET/api/community/post可选社区帖子详情
GET/api/community/comments可选社区评论列表
GET/api/community/comment-locate无定位评论所在页码
GET/api/community/myBearer我的帖子/我的收藏
POST/api/community/post/saveBearer发布/编辑帖子
POST/api/community/post/deleteBearer删除帖子
POST/api/community/likeBearer点赞帖子
POST/api/community/favoriteBearer收藏帖子
POST/api/community/commentBearer发表评论
POST/api/community/comment/deleteBearer删除评论
POST/api/community/reportBearer举报帖子
GET/api/oauth/providers无OAuth 提供商列表
GET/api/oauth/bindingsBearerOAuth 绑定列表
GET/api/oauth/pending-info无获取待建号信息
POST/api/oauth/confirm-create无确认创建 OAuth 账号(参数在 Query)
POST/api/oauth/cancel无取消 OAuth 建号
POST/api/oauth/unbindBearer解绑 OAuth(provider 为 form 字段)
POST/api/oauth/sync-avatarBearer手动同步头像(provider 为 form 字段)
GET/POST/oauth/login/{provider}条件发起 OAuth 授权(bind=1 时需登录)
GET/oauth/callback/{provider}无OAuth 回调
GET/oauth/complete无一次性 exchange code 换 token
POST/{adminPath}/api/login密码管理员登录

页面路由 ​

方法路径认证说明
GET/无游戏中心首页
GET/turing无图灵测试 1v1
GET/lobby无公共聊天室
GET/temp-chat无临时聊天
GET/board/gomoku无五子棋
GET/board/go无围棋
GET/soup无海龟汤
GET/blockworld无方块世界(可 503 维护)
GET/agreement无用户协议
GET/privacy无隐私政策
GET/weekly-report无全服周报
GET/account无账号中心
GET/community无交流社区列表
GET/community/post/{id}无交流社区详情
GET/song-history无点歌回顾
GET/player/{nickname}无玩家档案
GET/collection/{token}无公开收藏
GET/{adminPath}无管理后台页面
GET静态资源路径(见 18 节)无静态资源
最近更新