曲目别名与投票
用别名搜索曲目、提交别名提案、投票、结算规则与状态机;覆盖 maimai、Arcaea、Phigros、Milthm。
玩家对曲目有大量俗称。RankHub 用「提案 + 投票」维护一份社区别名表,第三方应用可以:
- 按别名/标题/艺术家搜索曲目(好用的
song_id解析器); - 读已生效的别名;
- 以用户身份提别名、投票、撤回自己的提案。
支持的游戏:maimai、arcaea、phigros、milthm(路径里的 {game})。
scope 与身份
别名接口在 /v0/games/** 下:读需要 game:read,写需要 game:write(写覆盖读)。
提案与投票必须用用户身份(Bearer access token 或浏览器会话),不能用 client_credentials
或匿名。账号处于注销流程 / 非 active 时写操作返回 403。
这一族是过渡例外
/v0/games/** 现在只服务 RankHub 自有客户端,第三方令牌打档案 / 成绩接口会得到
403 game.firstPartyOnly。曲目别名与投票是两族例外之一:它的 v1 形状还没设计,
所以过渡期内第三方继续用 /v0,行为不变。过渡结束后会迁到 v1 并另行公告 ——
新代码不要围绕 /v0 的 snake_case 形状做长期设计。整体边界见
权限范围与可用 API。
用别名找曲目
curl -sS "https://api.rankhub.cc/v0/games/maimai/song-aliases/songs?q=蜘蛛&limit=10" \
-H "Authorization: Bearer $ACCESS_TOKEN"[
{
"game": "maimai",
"song_id": "834",
"title": "スパイダー",
"artist": "…",
"aliases": ["蜘蛛", "spider"],
"matched_by": "rankhub_alias"
}
]| 参数 | 说明 |
|---|---|
q | 关键词(≤100 字符);匹配 song_id、标题、艺术家、外部别名、RankHub 别名 |
limit | 1–100,默认 30 |
matched_by 告诉你命中来源(id / title / artist / upstream_alias / rankhub_alias),
做搜索排序时可以据此加权。
读别名表
GET /v0/games/{game}/song-aliases?q=&song_id=&page=1&limit=20{
"items": [
{
"id": "0f9f…",
"game": "maimai",
"song_id": "834",
"song": { "song_id": "834", "title": "スパイダー", "artist": "…", "aliases": ["蜘蛛"], "matched_by": "id" },
"alias": "蜘蛛",
"source": "proposal",
"proposal_id": "6a1c…",
"created_at": "2026-08-01T00:00:00.000Z",
"updated_at": "2026-08-01T00:00:00.000Z"
}
],
"total": 1,
"page": 1,
"page_size": 20,
"total_pages": 1
}source 是 proposal(社区投票通过)或 admin(运维直接加)。
规则快照
GET /v0/games/{game}/song-aliases/rules{
"voting_duration_seconds": 604800,
"minimum_total_votes": 10,
"minimum_approve_votes": 5,
"minimum_approval_ratio": 0.6666666666666666,
"max_active_proposals_per_user": 10
}默认值(可由平台配置覆盖):投票期 7 天(下限 24 小时)、总票数 ≥ 10、赞成 ≥ 5、 赞成率 ≥ 2/3、每人最多 10 个进行中的提案。
两个容易被忽略的规则
- 一致通过可提前结算:只要「反对票为 0 且赞成票 ≥
minimum_approve_votes」,提案立即通过, 不用等总票数到 10。 - 达到阈值后投票期会缩短:赞成票与总票数都超过各自阈值、且赞成率达标后,截止时间按比例 提前(下限 24 小时),不是固定等满 7 天。
提案
列表与详情
GET /v0/games/{game}/song-aliases/proposals?status=active&mine=true&page=1&limit=20
GET /v0/games/{game}/song-aliases/proposals/{proposalId}可用筛选:q、song_id、status(active / approved / rejected / expired / canceled)、
mine=true|false、page、limit(≤100)。
创建
curl -sS -X POST https://api.rankhub.cc/v0/games/maimai/song-aliases/proposals \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"song_id": "834", "alias": "蜘蛛"}'song_id≤255 字符,alias≤80 字符(提交前会做规范化/查重)。- 创建者自动投一张赞成票(
approve_votes从 1 开始)。 - 如果该曲目已有同名标题/别名,或已存在同名的进行中提案 ⇒ 409。
- 超过「每人 10 个进行中提案」⇒ 409
Active proposal limit reached。
投票
PUT /v0/games/{game}/song-aliases/proposals/{proposalId}/vote
Content-Type: application/json
{"choice": "approve"}choice只能是approve或reject;同一个人重复投票是改票(不是累加)。- 提案不再是
active(已结算/过期)⇒ 409。 - 投票时正好跨过截止时间:服务端先按当前票数结算,再返回结算后的对象。
- 每个用户的票唯一(
(proposal_id, user_id)唯一约束)。
撤回
POST /v0/games/{game}/song-aliases/proposals/{proposalId}/cancel只有自己的、仍在进行中的提案能撤回(can_cancel 字段就是给 UI 用的)。
撤回后状态变成 canceled,不再参与结算。
提案对象
{
"id": "6a1c…",
"game": "maimai",
"song_id": "834",
"song": { "song_id": "834", "title": "スパイダー", "artist": "…", "aliases": [], "matched_by": "id" },
"alias": "蜘蛛",
"status": "active",
"approve_votes": 7,
"reject_votes": 1,
"total_votes": 8,
"approval_ratio": 0.875,
"rules": {
"voting_duration_seconds": 604800,
"minimum_total_votes": 10,
"minimum_approve_votes": 5,
"minimum_approval_ratio": 0.6666666666666666
},
"current_user_vote": "approve",
"is_mine": true,
"can_cancel": true,
"expires_at": "2026-11-01T00:00:00.000Z",
"resolved_at": null,
"resolution_reason": null,
"created_at": "2026-10-25T00:00:00.000Z",
"updated_at": "2026-10-26T00:00:00.000Z"
}每个提案都带创建时的规则快照(rules),所以规则调整不会改变已有提案的结算条件 ——
展示进度时用它,不要用当前的 /rules。
| 状态 | 含义 |
|---|---|
active | 投票中 |
approved | 通过(别名已生效) |
rejected | 被管理员驳回 |
expired | 到期未达阈值 |
canceled | 提案人撤回 |
结算(通过/过期/驳回)会给提案人发一条通知;resolution_reason 是给用户看的原因。