《明日方舟》抽卡记录查询工具,按卡池算出每个 6 星实际花了多少抽。 纯 Go,单文件可执行,Windows 与 Android 双端。
页面分成四栏横排(普通池 / 限定池 / 中坚池 / 全部抽卡记录),
每一栏内每期卡池各成一块、可折叠;每个 6 星是一行,点开能看到这一段里抽到的
5★ / 4★ / 3★。首次获得的干员标 new。支持按卡池、星级、名称筛选与排序。
前端内嵌进二进制,编译产物是单个可执行文件,无外部运行时依赖。
姊妹项目:endlog · 终末寻访录(《明日方舟:终末地》版)。 两者架构相同,但抽卡接口是完全不同的两套,数据模型也有实质差异,详见「设计要点」。
到 Releases 取:
| 文件 | 平台 |
|---|---|
arklog.exe |
Windows 64 位,双击即用 |
arklog.apk |
Android 7.0+,仅 arm64-v8a |
拿到 arklog.exe 后双击即可。首次启动会在 exe 同目录生成 config.yaml,
并自动打开浏览器到 http://127.0.0.1:8788 。
配置与抽卡记录都存在 exe 旁边(config.yaml 和 data/),
所以整个目录可以随便挪、也可以放 U 盘带走 —— 但要连 data/ 一起搬,
只搬 exe 的话历史存档不会跟过去。
命令行参数:
arklog.exe -listen 127.0.0.1:9000 # 端口被占用时换一个
arklog.exe -no-open # 不自动打开浏览器
arklog.exe -config D:\my.yaml # 指定配置文件
arklog.exe -version # 打印版本号安装 arklog.apk,打开即用。界面与功能和 Windows 版完全一致 ——
应用内部就是把同一个 Go 服务跑起来,再用 WebView 显示它的页面。
- 仅支持 arm64-v8a(目前绝大多数安卓手机都是),Android 7.0 以上
- 数据存在应用私有目录,卸载即清空;换手机不会自动带走存档
- 两端的存档目前无法互相同步(想要的话得加导入/导出功能)
安卓上有一个必须处理的坑:Android 没有 /etc/resolv.conf,
Go 的纯 Go DNS 解析器找不到 DNS 服务器就会退回本机 53 端口,
于是每个请求都失败在 lookup xxx on [::1]:53: connection refused。
官方解法是开 cgo 用 Bionic 的 getaddrinfo,但那需要 NDK;
这里改由 Java 外壳从 ConnectivityManager 查出系统 DNS,用 -dns 传给 Go,
Go 再据此替换 net.DefaultResolver(见 internal/netdns)。
查不到系统 DNS 时会退回内置公共 DNS,也可以在 config.yaml 的 dns_servers 手填。
go run ./cmd/arklog # 直接运行
bash scripts/build-windows.sh 1.0.0 # → dist/windows/arklog.exe
bash scripts/build-android.sh 1.0.0 # → dist/android/arklog.apk两个脚本都会先跑 gofmt / go vet / go test,任一不过就中止。
Android 构建不走 Gradle,直接调 aapt2 / javac / d8 / zipalign / apksigner。
原因与两个踩过的坑记在 scripts/build-android.sh 的注释里:
build-tools 29 的 d8.bat / apksigner.bat 用了 JDK 9 就移除的 -Djava.ext.dirs,
必须绕过 .bat 直接调 jar;而那一版 d8 处理非静态内部类会 NPE,
所以 MainActivity.java 里的嵌套类必须全部是 static。
首次构建会在 build/ 下生成签名密钥。这个 keystore 要自己备份 ——
换密钥签名的 APK 无法覆盖安装,只能卸载重装(那会丢掉手机上的存档)。
手机号 + 短信验证码,仅此一种。全程不接触也不保存你的密码。
登录要连续走六个接口才能换到抽卡凭据,因此会比一般应用慢几秒。
只支持国服。国际服用 Yostar 账号体系(邮箱 + 图形验证码),与鹰角通行证完全无关, 而且官方并没有提供国际服的抽卡记录接口 —— 现有的国际服分析工具都是 手工导入/粘贴式的,不存在本程序所依赖的那个端点。
关于 B服:抽卡接口本身没有渠道参数,渠道信息来自 binding_list 返回的
每个绑定角色。因此只要 B服 角色确实绑在同一个鹰角账号下,就能通过
arknights.binding_index 选到它。但 B服 支持未经验证 ——
B服 账号通常无法通过鹰角通行证短信登录。
以下端点经交叉核对五个开源实现确认(AceDroidX/arknights-gacha-export
的 Rust 实现与其 API.md、RoLingG/Arknights_Gacha_App
的 Go 实现、bhaoo/ArknightsGachaExport、
bricks0411/astrbot_plugin_for_Arknights、qihang518887/astrbot_plugin_sklandv2 的 Python 实现),
并已用真实国服账号跑通全链路(登录 → 逐分类翻页 → 落盘),实测拉到数百条记录。
ak.hypergryph.com/user/api/inquiry/gacha(那个一次返回整个十连、内含 chars 数组的接口)
已经不再是 API —— 它现在落到前端 SPA 的 catch-all 路由上,返回 HTTP 200 + <!doctype html>。
网上大量教程还在用它。程序识别到返回网页时会直接把这件事说出来,而不是只报「不是合法 JSON」。
现行接口是下面两个。
| 步骤 | 端点 | 请求 | 得到 |
|---|---|---|---|
| 1 | POST as.<host>/general/v1/send_phone_code |
{phone, type} |
下发短信 |
| 2 | POST as.<host>/user/auth/v2/token_by_phone_code |
{phone, code} |
账号 token |
| 3 | POST as.<host>/user/oauth2/v2/grant |
{token, appCode, type:1} |
OAuth token |
| 4 | GET binding-api-account-prod.<host>/account/binding/v1/binding_list |
?token=&appCode=arknights |
uid(含渠道) |
| 5 | POST binding-api-account-prod.<host>/account/binding/v1/u8_token_by_uid |
{token, uid} |
u8_token |
| 6 | POST ak.<host>/user/api/role/login |
{token:<u8_token>, …} |
ak-user-center cookie |
第 1~5 步与 endlog 几乎一致(只有 binding_list 的 appCode 换成 arknights)——
这是鹰角通用账号体系,与具体游戏无关。第 6 步是明日方舟多出来的一环:
终末地凭 u8_token 直接访问 ef-webview 即可,这里少了那个 cookie,抽卡接口会回 MissingCookie。
appCode 最容易搞错:明日方舟用户中心是 be36d44aa36bfb5b,配 type:1,返回 data.token。
森空岛的 4ca99fa6b56cc2ba 配 type:0 返回的是 data.code(一次性授权码,
只能换森空岛的 cred),拿不到抽卡记录。程序检测到返回 code 而非 token 时会直接点明这一点。
凭 x-role-token 头 + ak-user-center cookie 访问,不需要 cred/sign/salt 签名
(签名只有森空岛 zonai.skland.com 的接口才要):
GET ak.<host>/user/api/inquiry/gacha/cate ?uid → data 是数组
GET ak.<host>/user/api/inquiry/gacha/history ?uid&category&size[&pos&gachaTs] → data.list + data.hasMore
- 必须按
category逐个查询。/cate给出分类列表(实测有normal、classic、anniver_fest、summer_fest、MH_02等),没有「查全部卡池」的用法。 - 翻页是游标:首次不带
pos/gachaTs,随后取本页最后一条的pos与gachaTs, 循环直到hasMore为 false。 - 凭据走请求头,不走 query 参数,因此无需 URL 转义。
单条记录:
{"poolId":"…","poolName":"…","charId":"…","charName":"…",
"rarity":2,"isNew":false,"gachaTs":"1770697079082","pos":0}鉴权失败完全绕过 code 外壳,只有 {"message":"未登录","reason":"UN_LOGIN"}
(缺 cookie 时是 reason:"MissingCookie")。这两种响应既没有 status 也没有 code,
朴素反序列化会得到 code=0 从而被误判成成功 —— 程序单独检查 reason。
以下几条是这个项目真正需要动脑的地方,不是把 endlog 改个名就能得到的。
现行接口一条 list 项就是一抽(旧接口才是整个十连含 chars 数组)。
一次十连表现为 10 条 gachaTs 完全相同、pos 为 0..9 的兄弟记录。
接口不提供唯一记录 ID,去重键必须自己合成,而且 pos 不能省:
同一次十连里出现两个同名同星级的干员非常常见(3★ 尤其如此),
它们的时间戳、卡池、名称三者全部相同。若沿用 endlog 那种
「时间 + 卡池 + 名称」的键,这些记录会被判成同一条而丢掉 ——
悄悄少算抽数,并让保底统计算错。
键取 poolId:charId:gachaTs:pos(见 gacha.RecordID),配了专门的回归测试。
pos 还有第二个用途:给同一时刻的记录定出确定的先后顺序。
保底计数要数「上次出金之后又抽了几次」,批次内顺序若错了,结果最多偏差 9 抽。
这一点与 endlog 相同,而且原因也相同 —— 因为 history 要按 category
逐个查询,那个 bug 的结构条件在这里同样成立:
历史卡池早已结束,其记录整体都早于「全局最新时间」。若给所有卡池共用一个全局水位,
每个历史卡池都会在第一条记录上判定「已追上」而停止翻页,
结果是历史卡池的数据永远补不回来。水位键为 category,
由 store.Watermarks() 从已存记录算出。
停止翻页用严格「早于」(Record.IsOlderThan):一次十连的多条记录时间戳相同,
把相等也判成旧记录会整批漏掉,由 Merge 按键去重兜底。
接口的 rarity 是 0 基:5 表示 6★、2 表示 3★。
转换(gacha.StarsFromRarity)只在数据源的映射边界做一次,用命名常量而非裸 +1 ——
这是这类项目最经典的 off-by-one 来源。
gachaTs 形如 "1770697079082":字符串、毫秒。用字符串接收再解析,
但仍走 gacha.NormalizeUnixTime 按数值量级判断单位,这是刻意的双重防御 ——
写死任一单位一旦接口改动就会算出公元五万年,而 time.Time 只能序列化 [0,9999] 年,
一条越界记录就能让整份快照连带 HTTP 响应一起失败。入库前另有 PlausibleTime 校验。
官方接口能回溯的历史有限(各实现都只靠 hasMore 收敛,未发现明确的页数上限,
但是否静默截断未经验证)。因此:
data/目录是你的长期存档。scripts/build-windows.sh里那段 「只删自己产出的文件、绝不rm -rf掉data/」的处理就是为此。store.Merge只增不删:接口不再返回的旧记录会继续留在本地, 绝不会被「本次拉到的结果」覆盖掉(有专门的测试锁住这条)。- 「完整重新同步」只能补回接口还愿意给的部分,补不回超出窗口的数据。
一次完整同步要访问若干卡池分类。单个分类失败时,已拿到的记录照常入库,
失败信息作为警告显示,不会让整次同步白跑(gacha.PartialError)。
同步结果按卡池列出条数,据此可以确认哪些卡池实际拉到了。
「这个 6 星花了多少抽」不能只数当前卡池内的抽数 —— 抽数会跨期结转。 三类卡池的规则各不相同,这是出金分析里唯一需要小心的地方:
| 分类 | 判定(按接口的 category) |
结转 |
|---|---|---|
| 中坚池 | 含 classic |
各期之间结转,但只和中坚池互通 |
| 普通池 | 含 normal |
各期之间结转,共享一条链 |
| 限定池 | 其余一切(含认不出的分类) | 每期归零,不结转 |
所以三条链彼此独立:在限定池垫的抽数不会带到普通池,反之也不会。 在一期卡池的第一个 6 星上,若实际消耗包含上一期垫的抽数,界面会标出 「上期结转 N 抽」。
认不出的 category 归限定池,这是刻意选的保守方向。 归限定只是不给它算跨期
结转(各卡池仍照常统计、照常显示);而归普通池会让它和普通池共用一条结转链,
凭空给普通池的出金添上不属于它的垫抽数 —— 宁可少算结转,不可无中生有。
这条规则是被真实数据纠正过的:实测账号里出现了一个 MH_02(联动池),
早先的判定规则(只认 fest/limit 为限定)没认出它,把它当成普通池,
于是联动池垫的抽数被错误地结转进了普通池。
实测出现过的 category:normal、classic、anniver_fest、summer_fest、MH_02。
分类与结转都在前端(web/app.js 的 poolClass / chainKeyOf / analyzePools),
与 endlog 的做法一致。代价是这部分没有单元测试,靠样例数据与真实数据实跑验证;
样例数据刻意给每个分类都安排了多期卡池,否则结转这条路径根本走不到。
顶部统计条的数值(总抽数、6★/5★ 个数与出现率、平均/最惨/最快出金间隔)
由 Go 侧算好下发。这些的边界条件不少:批次内顺序要按 pos、
最后一个 6★ 之后的抽数还没出金、不能算成一个「已完成间隔」、
样本量不足时不能给出误导性的百分比(10 抽出 1 金算成 10%,而实际综合概率约 2%)。
放在 internal/gacha/stats.go(纯函数)才能用单元测试锁住。
上层只依赖 gacha.Provider 接口。官方接口若有变动,可切到 provider: remote,
用 config.yaml 里的声明式配置描述新接口,无需改代码重新编译。
但要注意 remote 是通用实现、不认识 pos,同一十连里的重复干员会被去重掉,
所以它只适合应急。
cmd/arklog/ 入口
internal/config/ 配置加载 + 区域定义(域名、appCode)
internal/gacha/ Record 类型、去重合并、Provider 接口
├── stats.go 抽数统计 / 保底计数(纯函数)
├── arknights/ 官方接口实现(按 category 逐个查,pos+gachaTs 游标翻页)
├── mockprov/ 样例数据源(确定性生成,可反复同步而不产生重复)
└── remote/ 配置驱动的应急数据源 + JSON 路径取值
internal/hypergryph/ 账号体系:短信登录与六步认证链
internal/store/ 内存记录集 + JSON 快照(原子写入)
internal/server/ HTTP 路由、会话、接口处理
internal/netdns/ 安卓上的 DNS 兜底
web/ 前端单页,go:embed 内嵌
config.yaml 每项都有注释。常改的几处:
provider: "arknights" # arknights 官方接口 / mock 样例数据 / remote 自定义
arknights:
binding_index: 0 # 一个账号绑了多个角色时选第几个(官服/B服)
page_size: 50 # history 接口的 size
delay_ms: 1000 # 翻页间隔,别调太小不想登录先看效果的话,把 provider 改成 mock,任意手机号与验证码都能进,
会载入 300 条样例记录(含真实干员名、按十连成批、含同批重复干员),
用于验证筛选、排序、统计、持久化等功能。
程序出错时会把接口的原始返回一起报出来。常见情况:
- 接口返回的是网页而不是 JSON —— 打到了废弃的旧端点。检查
arknights.history_url是否被改成了/user/api/inquiry/gacha。 - 访问凭据已失效(UN_LOGIN) —— 退出登录后重新用短信验证码登录。 凭据有有效期,长时间不用会失效。
- 缺少 ak-user-center cookie(MissingCookie) —— 登录链路第 6 步没走完,重新登录。
- OAuth 授权返回的是一次性 code 而不是 token ——
auth.app_code填成了森空岛的4ca99fa6b56cc2ba。留空即用正确的默认值。 - 收不到短信验证码 —— 核对
auth.send_code_type。这个值只有单一来源佐证、 含义未确认,抓「获取验证码」那条请求看 body 里type的数字,改到配置里。 - 该账号下没有查到明日方舟角色 —— 确认游戏里已创建干员,且登录的是同一个鹰角账号。 B服 账号通常无法通过鹰角通行证登录。
- 部分卡池未能拉取 —— 通常是限流。等一会儿再点同步,已同步的部分不会重复拉。
- 某些卡池一条记录都没有 —— 点「完整重新同步」。但超出官方保留窗口的数据补不回来。
- 只用短信验证码,程序不接触也不保存密码。
u8_token与ak-user-centercookie 只存在服务端进程内存里, 不写磁盘、不下发给浏览器(有测试专门断言响应里不含凭据)。 浏览器只持有一个本地随机 session cookie。进程退出即失效。- 磁盘上只存抽卡记录快照,文件名用
区域-uid,不含手机号。 - 服务默认只监听
127.0.0.1,不要改成0.0.0.0。 - 这个工具只用于查询你自己账号的记录。请保持
delay_ms不要调得太小。
go test ./...覆盖:
- 同一十连内重复干员不被去重(去重键含
pos,最关键的一条) - 星级 0 基归一(接口
rarity=5必须是 6★) - 字符串毫秒时间戳解析,以及结果必须能 JSON 序列化
- 按
category逐个查询、pos+gachaTs游标翻页、游标不推进时及早停止 - 历史卡池不被新卡池的水位挡住(按卡池水位的回归测试)
- 增量同步时时间戳相等的记录不被丢弃
- 部分失败降级为
PartialError,已拿到的记录照常入库 - 鉴权失败(
UN_LOGIN/MissingCookie)不被静默当成成功,且报错可指导操作 - 打到废弃端点返回 HTML 时给出明确提示
- 统计:保底计数(含十连内按
pos定序)、出金间隔、样本量不足不输出百分比 - 快照只增不删(接口不再返回的旧记录不丢)、重启恢复、重复同步 0 新增
- 基于样例数据源的完整 HTTP 流程(登录 → 同步 → 记录 → 退出 401)、凭据不外泄
官方接口相关的测试通过改写出站 host 指向本地测试服务器, 既验证真实的 URL 拼装,又不会真的访问官方接口。
本项目采用 GNU General Public License v3.0,全文见 LICENSE。
Copyright (C) 2026 Gordenghost
本程序是自由软件:你可以依据自由软件基金会发布的 GNU 通用公共许可证
(第 3 版,或你选择的任何更新版本)的条款,重新发布和/或修改它。
本程序的发布是希望它能有用,但不作任何担保;甚至不含对
适销性或特定用途适用性的默示担保。详见 GNU 通用公共许可证。
你应当已随本程序收到一份 GNU 通用公共许可证的副本。
若没有,请查阅 <https://www.gnu.org/licenses/>。
需要注意:仓库里的 pic.jpg(应用图标源图)不是本人作品,仅作个人使用,
不在上述 GPL 授权范围内。若你要二次分发,请自行替换成你有权使用的图片,
再跑一次 bash scripts/gen-icons.sh 重新生成图标。
本项目是第三方工具,与上海鹰角网络科技有限公司(Hypergryph)没有任何隶属、 合作或背书关系。《明日方舟》及相关名称、素材的著作权归鹰角所有。
本工具只做一件事:用你自己的账号,读取你自己的抽卡记录。 它不修改任何游戏数据,不提供任何游戏内优势,也不代替你操作游戏。
- 凭据只留在你本机的进程内存里,不上传到任何第三方服务器(本程序没有服务端)。
- 请不要把
delay_ms调得过小 —— 高频请求可能触发风控,后果自负。 - 请勿用于查询他人账号。
使用本工具的风险由你自行承担。