签到机器人接入说明
面向站外开发者。读完本文即可实现一个每日签到机器人,无需阅读本站源码。
本文是签到接口的唯一对外口径。字段、错误码、语义均照实抄录, 若与源码不符以源码为准(但也请提 issue —— 那就是本文过期了)。
0. 一句话说清
签到是两步,不是一个请求:
POST /api/checkin—— 签个到,服务端洗好一副牌落库,此时还不发鱼干;POST /api/checkin/claim—— 翻牌定命,从五张牌里点一张(chosenIndex0–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。