文档索引

签到机器人接入说明

面向站外开发者。读完本文即可实现一个每日签到机器人,无需阅读本站源码。

本文是签到接口的唯一对外口径。字段、错误码、语义均照实抄录, 若与源码不符以源码为准(但也请提 issue —— 那就是本文过期了)。

0. 一句话说清

签到是两步,不是一个请求:

  1. POST /api/checkin —— 签个到,服务端洗好一副牌落库,此时还不发鱼干;
  2. POST /api/checkin/claim —— 翻牌定命,从五张牌里点一张(chosenIndex 0–4), 这一步才发鱼干。

鱼干数 = 你翻到的那张牌的数字(1–5)。每人每天一次。

⚠️ 两步是刻意的,别想着合并。 五张牌(1,2,3,4,5 各一张)在签到那一刻洗好、 落到库里,翻牌时由你点的位置决定拿哪一张 —— 你的选择真的决定了结果, 而不是服务端早抽好了再由前端演一遍。所以缺了第二步,第一步等于没签。

📌 本文自包含。账号准备(注册 / 提权)与鉴权(cookie)是全部机器人共用的前置步骤, 三份完整口径见 docs/bot/chat-bot.md §2 与 §3 —— 本文 §2 只给最小可跑的部分。


1. 能力边界(先读这段)

范围 能读 能写 说明
签到 + 翻牌领鱼干 —— ✅ 需 core+,每人每天一次
查自己今天的签到状态 ✅ ❌ GET /api/checkin
查别人的签到 ❌ ❌ 没有这类接口(排行榜是另一回事,见 §7.2)
补签 / 撤销 / 重翻 ❌ ❌ 没有这类接口。翻牌只能一次

⚠️ 签到是 core+ 专属。 非核心账号(user)不但拿不到鱼干, 连签到接口都进不去(403 需要核心用户权限)—— 本站的鱼干是 core+ 体系的报酬, 全站没有「注册就有鱼干」的渠道。这是刻意的,别指望用脚本刷出来。

⚠️ 机器人享受不到任何豁免:它同样会被禁言、同样受两步流程与 每日一次的限制约束。签到接口本身不判禁言(它不是发言), 但被禁言的账号在别的鱼干路径上会被挡(见 docs/bot/fish-bot.md)。


2. 准备账号与鉴权

签到接口全部需要 core+ 且登录。注册出来是 user,要提权一次:

  • 注册时带有效邀请码 → 直接是 core(推荐,全自动);
  • 让站长在服务器上执行:npm run cli -- promote-core mybot

完整流程(注册字段、人机验证、错误码)见 docs/bot/chat-bot.md §2。登录换 cookie:

POST /api/auth/login
Content-Type: application/json

{ "username": "mybot", "password": "********" }

响应会 Set-Cookie: raricy_session=<JWT>(HttpOnly、SameSite=Lax、30 天)。 之后每个请求带上 Cookie: raricy_session=<JWT> 即可。

⚠️ 写请求会校验来源,跨站会被拦成 403 跨源请求被拒绝 (CSRF)。 非浏览器客户端本来就不发 Origin/Referer,天然通过 —— 不要自作主张伪造一个。

会话失效(改密、被强制下线、被禁言 / 重置密码)后一律 401 请先登录 —— 当成「重新登录」的信号,而不是重试。

⚠️ 被降权(core → user)不属于会话失效 —— 会话仍有效,只是签到接口改回 403 需要核心用户权限。重登解决不了这个(那是档位问题,不是登录问题)。


3. 「今天」是本站时区的今天(最容易踩)

签到按 UTC+8 的零点切分,不是 UTC 零点,也不是你机器的本地时区。 响应里的 today 就是服务端认的那个日期("2026-09-19")。

⚠️ 跨午夜的窗口是已知语义,不是 bug:23:59 签了到、00:00 之后才去翻牌 —— 翻牌按「今天」查记录,查不着,回 400 今天还没有签到,那张牌作废 (等于这一天白签了)。这是两步式设计的固有代价。

所以机器人应当在同一次运行里把两步连着做完,中间别隔夜,也别拖到接近午夜。 稳妥做法:把签到任务放在你所在时区的上午,并且每次运行都 「先签到、紧接着翻牌」,两步间隔越短越好。

📌 另外,鱼干流水里的时间戳是「UTC+8 墙上时间贴 Z 标签」的假 Z, 口径与其他接口一致,见 docs/bot/comment-bot.md §5。


4. 第一步:签到

POST /api/checkin
Cookie: raricy_session=<JWT>

没有请求体。成功:

{
  "code": 200,
  "message": "签到成功!",
  "total_count": 37,
  "fortune_pending": true,
  "show_fortune": true
}
字段 说明
total_count 累计签到天数(含今天)
fortune_pending true = 已签到但还没翻牌。看见它就说明你欠一个 §5
show_fortune 给网页端决定要不要弹翻牌动画的提示位;机器人忽略即可

4.1 今天已经签过了

注意:已签到返回的是 400,不是 200。

{
  "code": 400,
  "message": "今天已签到",
  "already_checked": true,
  "fortune_pending": true,
  "total_count": 37,
  "fortune_value": 3,
  "total_fortune": 92,
  "dried_fish": 41.5
}

⚠️ 这是本站少见的「错误码里带数据」的接口 —— 除了 code/message, 它还给了 already_checked 与当前状态。看到 already_checked: true 不要重试: 它是终态。此时分两种情况:

  • fortune_pending: true → 今天签了但还没翻牌,继续走 §5;
  • fortune_pending: false → 今天已经完成(fortune_value 就是翻到的数),收工。

📌 靠「签到返回 400」来判断今天签过没有是可以的,但那会让每天的正常运行 都带一条错误日志。更干净的做法是先 GET /api/checkin(§6)看 checked_in。

4.2 会被拒绝的情形

情形 返回
未登录 / Cookie 失效 401 请先登录
账号不是 core+ 403 需要核心用户权限
今天已签到 400 今天已签到(带数据,见上)
服务端故障 500 服务器开小差了,请稍后再试 —— 事务整体回滚,重发一次即可;若它其实已经成功,重发会拿到 400 今天已签到(照 §4.1 处理,别当成失败)

5. 第二步:翻牌定命(这一步才发鱼干)

POST /api/checkin/claim
Cookie: raricy_session=<JWT>
Content-Type: application/json

{ "chosenIndex": 2 }
字段 类型 必填 说明
chosenIndex number | 整数字符串 是 你在牌面上的位置:0–4

⚠️ chosenIndex 是位置(0–4),不是牌面的数字。 牌面数字是 1–5 (五张各一张),想拿「5」不代表要传 5 —— 传 5 是越界,回 400 无效的选择。 五张牌的值在你签到时已经洗好,但位置与数字的对应关系你事先无从得知。

成功:

{
  "code": 200,
  "message": "ok",
  "fortune_value": 3,
  "pool": [1, 5, 3, 2, 4],
  "total_fortune": 92,
  "dried_fish": 41.5,
  "already_claimed": false
}
字段 说明
fortune_value 今天翻到的数字(1–5),也就是今天发的鱼干数
pool 今天那副牌(五张全给你看,含你没翻的那四张)
total_fortune 累计运势值(历次 fortune_value 之和)
dried_fish 当前鱼干余额
already_claimed true = 这一天之前已经翻过,本次是幂等返回现值

✅ 幂等:重复提交(网络重试、脚本重跑)不会重复发鱼干 —— 第二次起 already_claimed 为 true,fortune_value 还是原来那个。 超时之后原样重发这个请求是安全的。

5.1 会被拒绝的情形

情形 返回
未登录 / 非 core+ 401 / 403 需要核心用户权限
请求体不是 JSON / 不是对象 400 无效的请求
缺 chosenIndex 400 请选择一个卡牌
chosenIndex 不是整数、或不在 0–4 400 无效的选择
今天还没签到 400 今天还没有签到
服务端牌池数据异常 400 运势池数据异常
服务端故障 500 服务器开小差了,请稍后再试(事务整体回滚,你仍停在「已签到、待翻牌」—— 见 §7.3)

⚠️ 翻牌不是「开盲盒」:1.5、"abc"、true、越界值一律 400, 绝不静默取整或随机给一张 —— 翻牌只能一次,蒙对了也说不清。

⚠️ 校验分两段,别混: · 类型在路由里先判 —— chosenIndex 不是整数(1.5 / "abc" / true)→ 400 无效的选择;整个字段缺失 → 400 请选择一个卡牌; · 过了类型才进 service,那里的顺序是「有没有签到 → 是不是已经翻过(是则幂等成功) → 牌池 → 越界」。

所以:已经翻过牌的用户带一个越界整数(如 99)再来,拿到的是现值; 但带 1.5 / "abc" 这类类型非法的值,照样 400。机器人不必为前者写特例分支。

⚠️ 翻牌失败(500)之后原样重来即可:翻牌是一个本地事务,失败即整笔没有 发生 —— 你仍处于「已签到、待翻牌」的状态,重新翻一次就行(牌还是那副牌, 重选一张也不会重复发鱼干)。那笔没发生的鱼干不会凭空出现。 但别把它写成紧凑循环:500 说明本站真的出了故障,隔一会儿再试,别贴着打。


6. 查今天的状态

GET /api/checkin
Cookie: raricy_session=<JWT>

适合当每日任务的第一个请求 —— 用它决定今天要不要签、签完要不要翻。

{
  "code": 200,
  "message": "ok",
  "checked_in": true,
  "fortune_pending": false,
  "total_count": 37,
  "today": "2026-09-19",
  "fortune_value": 3,
  "fortune_label": "运势不错",
  "total_fortune": 92,
  "dried_fish": 41.5
}
字段 说明
checked_in 今天签了没有
fortune_pending 签了但没翻牌(checked_in && fortune_value === null)
today 服务端认的「今天」(UTC+8)
fortune_value 翻到的数字;没翻牌时为 null
fortune_label 运势文案(见下),没翻牌时是空串

运势文案(fortune_value → fortune_label):

值 文案
1 平平淡淡也是真
2 小有运气
3 运势不错
4 好运连连
5 运势爆棚

⚠️ checked_in: false 但 fortune_pending: true 不会同时出现 —— fortune_pending 蕴含 checked_in。别把两者当成独立的两个状态位。


7. 鱼干这件事

7.1 每天 1–5,无其他签到收益

签到给的就是 fortune_value(1–5 鱼干),没有「连续签到加成」「首签奖励」这类东西。 total_count(累计天数)与 total_fortune(累计运势值)只是统计,不参与结算。

⚠️ 签到是鱼干的来源之一,不是转账。 想给别人鱼干请走 docs/bot/fish-bot.md 的转账接口 —— 签到没有任何「送给别人」的参数。

7.2 想看排名

GET /api/fish/leaderboard —— 免登录,返回小鱼干余额榜:按当前余额倒序, 字段 rank / user_id / username / avatar_path / balance。只读,与签到是两回事。

⚠️ 没有「运势榜」:累计运势值(total_fortune)既不下发也不展示 —— 站内刻意不公开任何人的运势总和,这个接口里没有它,别的地方也没有。

7.3 失败只有两种,都不会「账记了一半」

翻牌是鱼干写路径:发鱼干与写流水在同一个事务里提交 —— 要么都生效、要么都不 存在。所以不存在「本地记了账、别处没记」这种没人知道的状态,也没有「稍后会自己 变好」的暂态。

📌 对机器人的含义,按状态码分两类:

  • 400 = 业务拒绝(今天没签到 / 位置不合法)—— 改输入才有意义,原样重试无用;
  • 500 = 本站故障,事务已整体回滚,你仍停在「已签到、待翻牌」—— 原样重发或换一张牌都行(牌还是那副牌,重选也不会重复发鱼干)。

两类都别写成循环:失败不会「多试几次就成」。


8. 限频

签到域没有任何 RULES 配额 —— 它是「每天一次」的自然节流:一天能成功的只有一次, 重复请求只会拿到 already_checked / already_claimed 的幂等响应。

⚠️ 但别拿它当心跳刷。它每天每账号最多产生 1 行记录 + 1 次发鱼, 刷它除了浪费你自己的时间没有任何收益。

📌 别的域(点赞、投喂、转账…)各有各的配额,用哪个域就去读那份接入文档的 「限频」一节 —— 这里没有配额不代表那里也没有。


9. 礼仪与约定

  • 签到是给自己签的,不产生任何通知、不影响别人。这大概是全站最不需要 「礼仪」的一段 —— 但同样别用多账号刷(那是在伪造 core+ 体系的报酬)。
  • 机器人也不该替人签到。 本站没有「代签」接口,一个账号一份凭据; 拿别人的密码去签等于替他做了决定,且凭据泄露的后果由他承担。
  • 两步之间别插入别的事情。 签完就翻,翻完就走 —— 跨午夜会让那一天的牌作废(§3)。

10. 排错速查

现象 多半是
403 需要核心用户权限 账号还是 user,需要站长提权一次(§2)
400 今天已签到 终态,别重试。看 fortune_pending 决定要不要继续翻牌(§4.1)
400 今天还没有签到 你跳过了第一步;或跨了 UTC+8 午夜,那张牌已作废(§3)
400 无效的选择 chosenIndex 传了牌面数字(1–5)而不是位置(0–4),或传了小数 / 字符串
400 请选择一个卡牌 请求体里根本没有 chosenIndex
翻了两次,鱼干只加了一次 正常的幂等(already_claimed: true),不是故障
500 本站故障(不是你的输入问题):事务已整体回滚,原样重发即可 —— 翻牌那一步你还停在「已签到、待翻牌」(§5.1 / §7.3),签到那一步重发会告诉你今天签过没有(§4.1)
余额涨了但流水里没有 不可能:发鱼一定伴随流水。对账请走 docs/bot/fish-bot.md §3.3 的游标模式
时间差 8 小时 假的 Z,见 §3

11. 最小可用流程

const BASE = 'https://raricy.com';
const cookie = 'raricy_session=<JWT>';   // 登录见 chat-bot.md §3

// 1. 先看今天的状态(省掉一次必然失败的 POST)
const st = await (await fetch(`${BASE}/api/checkin`, { headers: { cookie } })).json();
if (!st.checked_in) {
  const r = await fetch(`${BASE}/api/checkin`, { method: 'POST', headers: { cookie } });
  console.log('签到', r.status);           // 200 = 成功;400 = 今天已签过
}

// 2. 紧接着翻牌 —— 两步之间的间隔越短越好(跨午夜会作废,见 §3)
const st2 = await (await fetch(`${BASE}/api/checkin`, { headers: { cookie } })).json();
if (st2.fortune_pending) {
  const res = await fetch(`${BASE}/api/checkin/claim`, {
    method: 'POST',
    headers: { cookie, 'content-type': 'application/json' },
    body: JSON.stringify({ chosenIndex: Math.floor(Math.random() * 5) }),  // 0–4
  });
  const out = await res.json();
  if (res.ok) {
    console.log(`翻到 ${out.fortune_value}(${out.pool.join(',')} 里的一张),余额 ${out.dried_fish}`);
  } else if (res.status === 500) {
    // 本站故障,事务已整体回滚 —— 牌还在,原样重发(或换一张牌)即可
  } else {
    console.error('翻牌失败', out.code, out.message);
  }
}

断线 / 401 → 重新登录。429 → 签到域不会出现,但转账会遇到,见 docs/bot/fish-bot.md §4。

本页内容来自仓库 docs/bot/checkin-bot.md·在 GitHub 上查看