RankHub
业务 API

曲目别名与投票

用别名搜索曲目、提交别名提案、投票、结算规则与状态机;覆盖 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 别名
limit1–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 个进行中的提案。

两个容易被忽略的规则

  1. 一致通过可提前结算:只要「反对票为 0 且赞成票 ≥ minimum_approve_votes」,提案立即通过, 不用等总票数到 10。
  2. 达到阈值后投票期会缩短:赞成票与总票数都超过各自阈值、且赞成率达标后,截止时间按比例 提前(下限 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 是给用户看的原因。

下一步

本页内容