RhythMC-Reborn HTTP API 规范文档
目录
API 概述
基础信息
| 项目 | 值 |
|---|---|
| 基础URL | {game-server} (配置文件指定) |
| 协议 | HTTPS (TLSv1.3) |
| 认证方式 | /server/login 使用 Authorization: Bearer <serverBootstrapToken> 建立服务器身份;其余受保护后端接口和 WebSocket 都使用短期 Bearer access token |
| 数据格式 | JSON |
| 字符编码 | UTF-8 |
请求架构
通用规范
请求头
所有请求自动添加以下请求头:
User-Agent: RhythMC/{version} Java/{java-version}
Content-Type: application/json
服务器登录成功后,受保护的后端请求额外附加:
Authorization: Bearer <serverAccessToken>
serverAccessToken 是服务器登录响应中返回的短期 opaque Bearer 令牌,由 ServerSession 持有。客户端仅对受保护的后端业务接口自动注入 Authorization: Bearer <serverAccessToken>,不会对 Mojang API、CDN 下载地址或资源直链附带该头。WebSocket 握手也使用同一令牌作为 Bearer 头。
/server/login 现在优先使用请求头中的服务器 bootstrap Bearer 令牌建立服务器身份;登录成功后的 /server/*、/player/* 等受保护后端接口使用短期 Bearer access token 继续鉴权。历史请求体字段 serverAuthToken 仅保留兼容读取。
serverAccessToken 当前有效期默认 10 分钟。若 WebSocket 意外断开,客户端会先调用 /server/refresh 刷新令牌;刷新失败则使本地令牌失效,并重新执行完整的 /server/login 登录流程。
响应格式
成功响应:
{
"field1": "value1",
"field2": "value2"
}
错误响应:
{
"error": "ERROR_CODE",
"message": "Error description"
}
HTTP 状态码
| 状态码 | 含义 | 处理方式 |
|---|---|---|
| 200 | 成功 | 正常处理 |
| 400 | 请求格式错误 | 记录日志,提示用户 |
| 401 | 认证失败 | 重新登录 |
| 403 | 权限不足 | 检查服务器登录 token 或后端授权配置 |
| 429 | 请求过于频繁 | 等待后重试 |
| 430 | 账号已封禁 | 踢出玩家,显示封禁原因和解封时间 |
| 500 | 服务器错误 | 记录日志,注册资源包 fallback 后进入离线模式 |
服务器 API
POST /server/login
描述: 服务器登录,获取服务器信息和资源服务器地址
请求类: LoginRequest
请求体: 请求头:
Authorization: Bearer <serverBootstrapToken>
请求体:
{
"pluginVersion": 1,
"pluginSHA": "4a8c1e..."
}
请求字段:
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
| pluginVersion | int | ✓ | 插件版本号/版本编码 |
| pluginSHA | String | ✓ | 当前插件构建 SHA,用于服务端校验版本来源 |
| Authorization Bearer | String | ✓ | 长期 bootstrap 凭证,仅允许用于 /server/login |
成功响应 (200):
{
"serverID": 12345,
"serverName": "CN-East-01",
"assetServer": "https://assets.rhythmc.example.com",
"serverIP": "192.168.1.100",
"country": "CN",
"wsServer": "wss://ws.rhythmc.example.com",
"accessToken": "opaque-server-access-token",
"expiresIn": 600
}
响应字段:
| 字段 | 类型 | 描述 |
|---|---|---|
| serverID | int | 服务器在远程系统的唯一ID |
| serverName | String | 服务器名称 |
| assetServer | String | 资源服务器基础URL |
| serverIP | String | 服务器公网IP |
| country | String | 服务器所在国家/地区 |
| wsServer | String | WebSocket服务器地址 |
| accessToken | String | 短期 opaque Bearer 令牌,后续所有 HTTP/WebSocket 请求携带此令牌 |
| expiresIn | int | accessToken 的 TTL 秒数 |
失败响应:
| 状态码 | 含义 | 说明 |
|---|---|---|
| 401 | 登录失败 | 典型场景为后端维护模式;响应 message 会被插件打印到控制台 |
| 426 | 插件异常 | 版本或 SHA 校验失败,响应会带 toVersion 与 downUrl |
| 429 | 速率限制 | 后端拒绝当前请求频率 |
会话覆盖规则:
- 若同一
serverID在旧会话尚未主动/server/logout时再次调用/server/login,后端会先强制下线旧服务器会话,再为新请求签发新的accessToken。 - 旧服务器会话绑定的玩家会话也会一并失效,避免同一物理服残留多个活动
serverSession。
流程图:
GET /server/refresh
描述: 刷新服务器当前使用的 Bearer Auth Token。
请求类: RefreshAuthTokenRequest
认证: 需要 Authorization: Bearer <serverAccessToken>
说明: 当前客户端在 WebSocket 非预期断开后优先调用此接口。若刷新成功,则更新内存中的 serverAccessToken 并继续使用新令牌进行重连;若刷新失败,则本地令牌失效并触发完整的 /server/login 重登录。
成功响应 (200):
{
"accessToken": "opaque-server-access-token",
"expiresIn": 600
}
响应字段:
| 字段 | 类型 | 描述 |
|---|---|---|
| accessToken | String | 新的短期 Bearer 令牌 |
| expiresIn | int | accessToken 的 TTL 秒数 |
GET /server/collections
描述: 获取服务器定义的收藏品列表,登录后与资源同步、资源包列表加载并行执行
请求类: GetCollectionsRequest
URL: {game-server}/server/collections
认证: 需要 Authorization: Bearer <serverAccessToken>。请求体中的 player.accessToken 会优先按玩家会话令牌校验;如果该令牌已经过期,但该玩家在同一个 serverSession 下仍存在有效在线会话,后端会回退到该会话继续接收成绩上传,避免长局结算时因玩家令牌过期丢成绩。
响应 (200):
[
{
"key": "rhythmc:title.beta",
"type": "title",
"unlockMethod": {
"type": "ACHIEVEMENT",
"name": "封测成就",
"mode": "ALL",
"events": [
"player.login"
],
"player": {
"betaTester": {
"operator": "EQUALS",
"value": true
}
}
},
"properties": {
"title": "Beta测试员",
"position": true
}
},
{
"key": "rhythmc:title.dev",
"type": "title",
"unlockMethod": {
"type": "MANUAL"
},
"properties": {
"title": "开发者",
"position": false
}
}
]
响应字段:
| 字段 | 类型 | 描述 |
|---|---|---|
| key | String | 收藏品唯一键,格式 namespace:category.id |
| type | String | 收藏品类型,当前客户端使用 title / tag |
| unlockMethod | Object | 解锁方式对象,至少包含 type,并可选携带完整规则字段:name、description、mode、events、info、values、player、conditions、methods、grants、defaultUnlocked、hideCondition |
| properties | Object | 类型相关属性;对 title / tag 至少包含 title、position |
处理逻辑: 响应数据转换为客户端收藏品对象后批量存入 CollectionsManager,与资源完整性校验、ResourcePackManager.init() 并行执行。客户端会直接使用 unlockMethod 的完整规则对象参与 UnlockService 判定。任一失败将导致注册本地资源包 fallback 并进入离线模式。
GET /server/baltop
描述: 获取当前 KWh 财富排行榜,供插件侧 /baltop 命令展示。
请求类: GetBaltopRequest
URL: {game-server}/server/baltop?limit=10
认证: 需要 Authorization: Bearer <serverAccessToken>
查询参数:
| 参数 | 类型 | 必需 | 描述 |
|---|---|---|---|
| limit | int | 否 | 返回条数,默认 10,范围 1-100 |
响应 (200):
[
{
"rank": 1,
"uid": 10001,
"username": "Player_1234",
"displayName": "Frkovo",
"wattHour": 952700
}
]
响应字段:
| 字段 | 类型 | 描述 |
|---|---|---|
| rank | int | 排行名次,当前按 watt_hour DESC, id ASC 计算 |
| uid | long | 平台玩家 UID,对应 players.id |
| username | String | 后端保存的账号名 |
| displayName | String | 展示名;为空时后端会回退为 username |
| wattHour | long | 当前 KWh 余额 |
错误响应:
| 状态码 | 含义 | 说明 |
|---|---|---|
| 400 | 参数错误 | limit 不是整数,或不在 1-100 范围内 |
| 401 | 认证失败 | 缺少或携带了无效的 serverAccessToken |
| 500 | 服务器错误 | 后端读取排行榜失败 |
说明:
- 当前排行榜仍以数据库
players.watt_hour为唯一来源。 - 当前经济模式改为插件本地计算
wattHour,每次本地余额变化后通过POST /player/economy上传最新余额到后端。
GET /server/ping
描述: 测试服务器连通性
返回: CompletableFuture<Boolean>
使用场景:
- 插件启动时检测网络
- 管理员手动测试连接
- 定期健康检查
玩家 API
POST /player/login
描述: 玩家登录,支持三种登录模式
离线例外: 当服务器自身尚未完成 /server/login 或已进入离线模式时,客户端不会调用本接口,而是直接在本地创建游客档案供玩家进入游戏。
请求类: PlayerLoginRequest
登录模式:
模式1: ONLINE (正版自动登录)
请求体:
{
"mode": "ONLINE",
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"authToken": "optional-token"
}
验证流程:
- 通过 Mojang API 验证 UUID
- 检查玩家是否为正版用户
- 返回玩家档案
模式2: TOKEN (Net Token手动登录)
请求体:
{
"mode": "TOKEN",
"version": 1,
"authToken": "user-provided-token"
}
使用场景: 玩家手动输入令牌进行登录
模式3: OFFLINE_REFRESH (盗版自动续登)
请求体:
{
"mode": "OFFLINE_REFRESH",
"version": 1,
"resumeToken": "locally-cached-token"
}
使用场景: 使用插件本地缓存的 token 自动续期。插件重载后,只要 token 未过期,玩家仍可自动续登;整服/JVM 关闭后本地缓存会被清空。
前置条件: 后端只会在玩家选项 options.continueEnabled = true 时下发 resumeToken。开发阶段默认值为 false,因此未显式开启续关的玩家不会收到该 token,也不会触发 OFFLINE_REFRESH。
成功响应 (200) 附加字段:
| 字段 | 类型 | 描述 |
|---|---|---|
| version | int | 客户端当前玩家数据/插件版本,会在服务端保存为 players.profile_version |
| banstate | int? | 可选。> 0 时表示账号存在警告,登录后显示红色 Title 提示 |
| accessToken | string | 短期玩家 access token;用于 WebSocket resume、选项同步、登出、上传归属校验 |
| resumeToken | string? | 仅当玩家已开启 options.continueEnabled 时返回;用于后续 OFFLINE_REFRESH |
| expiresIn | int | accessToken 的 TTL 秒数 |
| resumeExpiresIn | int? | resumeToken 的 TTL 秒数 |
| options.continueEnabled | boolean | 玩家是否允许服务端签发 resumeToken;默认 false |
| data.collections | string[] | 玩家当前已持久化解锁的收藏品 Key 列表;包含称号/标签以及 rhythmc:assets.* 资产类解锁 |
POST /player/options
描述: 更新玩家选项,并返回刷新后的玩家档案快照。
请求体: uid、accessToken、options,可选 playerPrefix、playerSuffix
options 当前还包含 songSelectionState 对象,用于持久化选歌界面状态:
sortMode: 选歌排序模式difficulty: 选歌难度collection: 当前精选集 KeysongId: 当前高亮歌曲 IDautoPlay: 当前自动播放开关
成功响应 (200): 返回最新 PlayerProfileResponse
- 当
options.continueEnabled从false切换为true时,响应会立即包含新的resumeToken - 当该字段切回
false时,响应中的resumeToken为空,服务端同时撤销该玩家已有的 resume token - 响应里的
data.collections会随最新玩家档案一起返回,便于客户端在设置同步后继续保留本地解锁快照 - 后端会将
options.sound与options.songSelectionState持久化到 JSON 列,其余核心选项继续使用结构化列。
POST /player/collections
描述: 同步玩家当前已解锁的收藏品 Key 集合,用于持久化本地触发的解锁逻辑。
认证: 需要 Authorization: Bearer <serverAccessToken>,且请求体中的玩家 accessToken 必须有效。
请求体:
{
"uid": 12345,
"accessToken": "player-access-token",
"collections": [
"rhythmc:title.beta",
"rhythmc:assets.song.100",
"rhythmc:assets.chart.100.wd"
]
}
字段说明:
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
| uid | long | 是 | 玩家 UID |
| accessToken | string | 是 | 玩家 access token |
| collections | string[] | 否 | 需要确保后端已持久化的收藏品 Key 列表;后端按增量 grant 处理,不会删除缺失项 |
成功响应:
| 状态码 | 描述 |
|---|---|
| 204 | 同步成功,无响应体 |
错误响应:
| 状态码 | 含义 | 说明 |
|---|---|---|
| 400 | 请求错误 | 缺少 uid 或 accessToken |
| 401 | 认证失败 | 玩家 access token 无效、过期,或与当前 server session 不匹配 |
| 500 | 服务器错误 | 后端持久化收藏品失败 |
说明:
- 插件侧
UnlockService在获得新的持久化解锁后,会异步调用本接口。 - 若后端尚不存在某个资产类 Key,对应记录会自动创建为
asset类型收藏品,再写入player_collection_unlocks。 GET /server/collections默认不会返回这些自动创建的asset记录;它们只用于玩家持久化解锁状态。
version 兼容性说明:
- 请求中的
version表示当前运行插件声明的玩家数据版本。 - 成功响应中的
version仍表示该玩家登录前已保存的档案版本,客户端据此决定是否提示升级或拒绝旧插件继续加载。 - 若响应
version高于本地plugin-version-code,客户端会直接踢出玩家并提示需要先升级服务器插件。 - 若响应
version低于本地plugin-version-code,客户端允许登录,但会提示当前档案较旧且可能需要数据升级。
错误响应 (430) 字段:
| 字段 | 类型 | 描述 |
|---|---|---|
| reason | String | 封禁原因 |
| until | String | 解封时间(ISO 格式或可读字符串) |
响应状态码:
| 状态码 | 含义 | 处理方式 |
|---|---|---|
| 200 | 登录成功 | 保存令牌,更新状态;若有 banstate > 0 则显示账号警告 |
| 401 | 认证失败 | 提示用户重新输入 Token(TOKEN 模式连续失败 login.max-failures 次则踢出) |
| 409 | 账号已在线 | 保持在手动 Token 登录流程,提示“当前帐号已登录,如已登出请等一分钟后再试。” |
| 429 | 请求频繁 | 等待后重试 |
| 430 | 账号已封禁 | 踢出玩家,在踢出消息中显示 reason 和 until |
客户端补充说明:
- 在线模式下,玩家加入后按
ONLINE -> OFFLINE_REFRESH -> TOKEN的顺序尝试认证。 - 离线模式下,玩家登录流程短路为本地游客模式,不创建远端会话,也不要求输入
Net Token。
POST /player/economy
描述: 上传插件本地计算后的最新 wattHour 余额,用于持久化与排行榜刷新。
请求类: SyncEconomyRequest
认证: 需要 Authorization: Bearer <serverAccessToken>,且请求体中的玩家 accessToken 必须有效。
请求体:
{
"uid": 12345,
"accessToken": "player-access-token",
"wattHour": 5600
}
响应 (200):
{
"uid": 12345,
"wattHour": 5600
}
错误响应:
| 状态码 | 含义 | 说明 |
|---|---|---|
| 400 | 请求错误 | 缺少 uid / accessToken / wattHour,或 wattHour < 0 |
| 401 | 认证失败 | 玩家会话无效或过期 |
| 404 | 玩家不存在 | uid 无法匹配数据库玩家 |
| 500 | 服务器错误 | 后端保存余额失败 |
说明:
- 插件侧经济变更当前以本地内存档案
PlayerData.wattHour为准。 - 每次调用本地
Economy服务修改余额后,都会异步调用本接口上传完整最新余额,而不是上传增量。 - 后端不会在本接口中自行做加减计算,只负责认证、覆盖保存和排行榜持久化。
POST /player/logout
描述: 玩家登出。
请求类: PlayerLogoutRequest
认证: 需要 Authorization: Bearer <serverAccessToken>
请求体:
{
"accessToken": "player-access-token",
"uid": 12345
}
说明: 当前客户端在 PlayerQuitEvent 中做 best-effort 调用此接口。请求使用 POST /player/logout 并携带 accessToken 与 uid;若调用失败,仅记录日志,不阻塞玩家退出流程。
成功响应:
| 状态码 | 描述 |
|---|---|
| 204 | 登出成功,无响应体 |
常见错误响应:
| 状态码 | 含义 |
|---|---|
| 401 | Bearer 令牌无效或已过期 |
| 403 | 服务器登录 token 无权限或已被拒绝 |
| 429 | 速率限制 |
资源 API
GET /resources/manifest
描述: 获取资源清单,包含所有资源文件的哈希值。客户端优先使用 sha256 校验,缺失时回退到 sha1。
请求类: GetManifestRequest
认证: 需要 Authorization: Bearer <serverAccessToken>
URL: {assetServer}/resources/manifest?lang={checkLang}
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
| lang | boolean | 是否包含语言资源 |
响应:
{
"Data": [
{
"path": "songs/song1.rsm",
"sha256": "9f86d081884c7d659a2feaa0c55ad015..."
},
{
"path": "levels/level1.json",
"sha256": "b7d00764f43b6358669a3d0107692faf..."
}
],
"Lang": [
{
"path": "lang/zh_cn.json",
"sha256": "5df6e0e2761358c2c4cb7bb135b2f3af..."
}
]
}
字段说明:
| 字段 | 类型 | 描述 |
|---|---|---|
| path | String | 资源相对路径 |
| sha256 | String | 必填。资源文件 SHA256,用于本地完整性校验 |
POST /resources/down
描述: 获取资源下载URL列表
请求类: GetDownloadUrlsRequest
认证: 需要 Authorization: Bearer <serverAccessToken>
URL: {assetServer}/resources/down
请求体:
[
"songs/song1.rsm",
"levels/level1.json"
]
响应:
[
{
"path": "songs/song1.rsm",
"url": "https://cdn.example.com/songs/song1.rsm",
"bytes": 1048576,
"sha256": "9f86d081884c7d659a2feaa0c55ad015..."
},
{
"path": "levels/level1.json",
"url": "https://cdn.example.com/levels/level1.json",
"bytes": 2048,
"sha256": "b7d00764f43b6358669a3d0107692faf..."
}
]
下载流程:
GET /resources/packmanifest
描述: 获取资源包清单。插件账户设置中的资源包菜单会使用这里返回的 size 字段,将大小格式化显示为 10.29MiB 这类人类可读文本。
请求类: GetRspListRequest
认证: 需要 Authorization: Bearer <serverAccessToken>
URL: {assetServer}/resources/packmanifest
返回字段补充:
| 字段 | 类型 | 描述 |
|---|---|---|
| name | String | 资源包名称 |
| displayName | String | 显示名称 |
| type | String | FULL / CHAPTER / SONG |
| availableSongs | int[] | 资源包覆盖的歌曲 ID 列表 |
| size | long | 资源包字节大小,客户端可格式化为 KiB / MiB |
| sha1 | String | 资源包 SHA1 |
| url | String | 资源包直链下载地址 |
数据上传 API
POST /player/upload
描述: 上传单条游戏记录数据
请求类: UploadRecordsRequest
认证: 需要 Authorization: Bearer <serverAccessToken>。请求体中的 player.accessToken 会优先按玩家会话令牌校验;如果该令牌已经过期,但该玩家在同一个 serverSession 下仍存在有效在线会话,后端会回退到该会话继续接收成绩上传,避免长局结算时因玩家令牌过期丢成绩。
请求体: 单个记录对象(非数组)
{
"player": {
"playerUID": 12345,
"accessToken": "player-access-token",
"timestamp": 1700000000000
},
"server": {
"serverID": 100,
"serverName": "CN-East-01",
"country": "CN"
},
"payload": {
"type": "gameplay",
"levelID": 1,
"acc": 98,
"isRival": false,
"isWin": true,
"rivalUID": 0,
"rivalAcc": 0,
"tapStats": {"perfect": 100, "great": 10, "miss": 0},
"holdStats": {"perfect": 50, "great": 5, "miss": 0},
"dodgeStats": {"perfect": 30, "great": 2, "miss": 0},
"lookStats": {"perfect": 20, "great": 1, "miss": 0},
"maxCombo": 150,
"pass": true,
"fullCombo": false,
"allPerfect": false,
"isNewRecord": true,
"player_arena": "arena_1",
"time": 1700000000000
}
}
响应说明:
200: 上传成功,返回新的 ATP、全站排名和曲目排名。400: 请求体缺失必要字段或记录内容非法。401: 服务器令牌无效,或玩家既没有可用的player.accessToken,也没有与当前服务器会话绑定的有效在线会话。500: 服务端处理上传失败。
数据结构图:
请求类详解
ApiRequest<T> (抽象基类)
public abstract class ApiRequest<T> {
public abstract CompletableFuture<T> execute(HttpClient httpClient);
}
设计模式: 命令模式 + 异步回调
优点:
- 统一的请求接口
- 支持异步执行
- 类型安全的响应
请求类汇总表
| 类名 | 端点 | 方法 | 返回类型 | 用途 |
|---|---|---|---|---|
| LoginRequest | /server/login | POST | Void | 服务器登录 |
| RefreshAuthTokenRequest | /server/refresh | GET | String | 刷新服务器 Bearer 令牌 |
| GetCollectionsRequest | /server/collections | GET | List<CollectionsInfo> | 获取收藏品列表 |
| GetBaltopRequest | /server/baltop | GET | List<BaltopEntry> | 获取 KWh 财富排行榜 |
| PlayerLoginRequest | /player/login | POST | HttpResult | 玩家登录 |
| PlayerLogoutRequest | /player/logout | POST | String | 玩家登出 |
| UploadRecordsRequest | /player/upload | POST | String | 上传单条记录 |
| GetManifestRequest | /resources/manifest | GET | ManifestResult | 获取清单(sha256/sha1) |
| GetDownloadUrlsRequest | /resources/down | POST | List<DownloadInfo> | 获取下载URL |
| GetRspListRequest | /resources/packmanifest | GET | String | 获取资源包清单 |
2026-03-16 Update: Charts Endpoint
GET /server/charts
描述: 获取当前启用的谱面目录。客户端在服务器登录成功后将此请求加入初始化链,用于决定本地 charts 的反序列化范围。
请求类: GetChartsRequest
URL: {game-server}/server/charts
认证: 需要 Authorization: Bearer <serverAccessToken>
响应 (200):
[
{
"levelId": 100091,
"name": "Song Title",
"difficultyConstant": 10.5,
"version": "1.0",
"createdAt": "2026-03-16T20:30:00"
}
]
说明:
- 仅返回
is_active = 1的标准谱面槽位 difficultySlot当前约定为WORLD/NETHER/END/VOID- 客户端收到后会构建
ActiveChartCatalog,交给RawSongDeserializer判断是否加载本地 chart
请求类补充:
GetChartsRequest | /server/charts | GET | List<ChartInfo> | 获取当前启用的谱面目录
2026-03-18 Update: Ban Enforcement And Immediate Kick
/player/login
- Active bans are now enforced during login.
- If the player has an active record in
player_bans, the backend returns HTTP430instead of allowing login to continue. - Response payload:
{
"error": "BANNED",
"message": "Player is banned",
"reason": "abusive chat",
"until": "2026-03-20T22:30:00"
}
Field notes:
reason: active ban reason, empty string when absent.until: active ban expiry time as string, empty string for permanent bans.
Online ban push
- Admin ban now immediately pushes a WebSocket
kick_playerpacket to the owning game server. - Backend also revokes player session token(s) and marks runtime player sessions as logged out.
- Client receives the packet and kicks the target Bukkit player with the provided
message.
Session binding note
- The client now sends
resume_sessionimmediately after successful player state sync. - This makes backend-side online actions reliable even if the player has not entered matchmaking yet.
2026-03-18 Update: Collection Metadata For Cross-Server Chat Hover
The cross-server chat feature does not add a new HTTP endpoint, but it relies on richer collection metadata that is already returned by:
GET /server/collections
properties additions
For title / tag style collections, properties may now include:
| Field | Type | Description |
|---|---|---|
title | String | rendered title/tag text |
description | String or String[] | hover description used by the client chat UI |
rarity | String | hover rarity label |
position | boolean | true prefix, false suffix |
Example:
[
{
"key": "rhythmc:title.beta",
"type": "title",
"unlockMethod": {
"type": "ACHIEVEMENT",
"mode": "ALL",
"events": [
"player.login"
],
"player": {
"betaTester": {
"operator": "EQUALS",
"value": true
}
}
},
"properties": {
"title": "&6[Beta]",
"description": [
"参与封测的玩家",
"感谢你的陪伴"
],
"rarity": "Legendary",
"position": true
}
}
]
Notes:
-
descriptionmay be a single string or a string array. -
The client stores
descriptionin its local collection cache. -
unlockMethodnow accepts generic rule payloads so the client can cache and evaluate event/info/value/player based unlock definitions without hard-coding specific business classes. -
When a local unlock rule matches, the plugin now adds that collection key into
player.data.collectionsin the in-memory profile snapshot. -
CommonUnlockMethodis target-agnostic: the same rule payload can be reused by arenas, collections, titles, songs, charts, or future unlockable entities. -
The backend also reuses the same metadata when building
ChatDecorationInfofor WebSocket chat delivery. 上传鉴权严格以 token 反查 session 为准: -
服务器身份来自
Authorization: Bearer <serverAccessToken> -
玩家身份来自
player.accessToken