Skip to content

CLI 参考:从别的程序驱动 Mercy SF

mercy-cli 有一个非交互模式:参数进去,一个 JSON 对象出来。它是给面板、守护进程和 脚本用的。

这一页就是承诺。 下面这些命令和字段名不会在不打招呼的情况下变。CLI 的其他一切, 菜单、它的措辞、输出的排版,随时都可以变,所以请不要在那些东西上搭建。

不带参数运行,交互菜单会和以前完全一样地启动。

每个响应都带 "api"。今天它是 1。加字段不会改变它,只读自己认识的字段的调用方不 受影响。它会在字段被删除或含义改变时变化,所以你可以拒绝在一个自己没有为之构建过的 形状上运行。


登录

通过 S&F 单点登录创建的账号不需要额外的东西。一个属于某个服务器、从未纳入单点登录的 账号,在它看来并不存在,需要 --server

bash
mercy-cli --status --user <account> --server https://f1.sfgame.net/

--server 对下面每一条命令都适用。

输出

--start 之外,每条命令都只打印 JSON,所以 --json 本来就是默认,传不传都一 样。它存在是为了那些宁愿说明白也不愿依赖默认值的调用方,以及为了 --start,因为它 否则会写一份给人看的日志。


读取

列出一个账号上的角色

bash
mercy-cli --characters --user <account>
json
{
  "ok": true,
  "characters": [
    { "character": "Hero", "server": "https://s1.sfgame.net/", "level": 345 },
    { "character": "Alt",  "server": "https://s42.sfgame.net/", "level": 88 }
  ]
}

一次登录就覆盖这个账号上的每个角色,跨服务器也一样。如果某个角色的状态取不到, level 会是 null,但这一条依然列出来,因为知道它存在正是这次调用的意义。

读取一个角色的状态

bash
mercy-cli --status --user <account> --character <name>

当账号恰好只有一个角色时,--character 可以省略。

json
{
  "ok": true,
  "character": "Hero",
  "server": "https://s1.sfgame.net/",
  "level": 345,
  "class": "DemonHunter",
  "race": "DarkElf",
  "experience": { "current": 740300000, "next_level": 769800000 },
  "silver": 725000000,
  "mushrooms": 8,
  "honor": 12043,
  "rank": 1800,
  "action": "CityGuard",
  "attributes": {
    "strength":     { "base": 4077, "bonus": 1200 },
    "dexterity":    { "base": 1000, "bonus": 300 },
    "intelligence": { "base": 900,  "bonus": 250 },
    "constitution": { "base": 3000, "bonus": 800 },
    "luck":         { "base": 700,  "bonus": 150 }
  },
  "equipment": [
    {
      "slot": "Weapon",
      "model_id": 55,
      "color": 1,
      "class": "DemonHunter",
      "epic": true,
      "legendary": false,
      "armor": 0,
      "min_damage": 900,
      "max_damage": 1400,
      "upgrades": 3,
      "attributes": { "strength": 420, "constitution": 210 },
      "rune": { "type": "FireDamage", "value": 60 },
      "enchantment": "SwordOfVengeance",
      "gem": { "type": "Strength", "value": 300 }
    }
  ],
  "arena": {
    "fights_for_xp": 7,
    "next_free_fight": "2026-08-06T12:04:11+02:00",
    "opponents": [50099, 34316, 13668]
  },
  "tavern": {
    "thirst_for_adventure_sec": 3600,
    "beer_drunk": 2,
    "mushroom_skip_allowed": true,
    "quests": [
      { "length_sec": 600, "silver": 120000, "experience": 45000,
        "location": "Wolves", "item": true }
    ]
  },
  "guild": {
    "name": "Blvck Clover",
    "rank": 12,
    "honor": 90000,
    "members": [
      { "name": "Hero", "level": 345, "last_online": "2026-08-06T09:12:00+02:00" }
    ]
  },
  "scrapbook_items": 1102
}

所有内容都来自同一次状态拉取,所以没有分区可以点名索取,也没法靠少要一点来省下什 么。

角色不在任何公会时,guildnull。在剪贴簿被读过至少一次之前, scrapbook_itemsnull。空的装备槽会被省略,而不是列成 null。

关于物品名称: 协议里带的是模型 id 和槽位,而不是显示名称,所以你拿到的就是这 些。它同时也是美术资源的寻址方式,因此面板可以画出和桌面应用一模一样的图。在这里编 一个名字,等于给你一个无从核对的猜测。

按间隔重复读取

bash
mercy-cli --status --user <account> --character <name> --watch 60

登录一次,然后每 60 秒吐出一个对象,直到进程被停掉。请用这个,而不是在循环里反复跑 命令:每跑一次就是一次全新登录,而那是盯一个数字最贵的做法,也是在服务器那一侧最扎 眼的做法。下限是 30 秒。失败的一轮会被打印出来,循环继续;一个碰到第一次打嗝就放弃 的观察者,看起来和一个「什么事都没有」的观察者一模一样。

它接下来会打谁,以及为什么

bash
mercy-cli --plan --user <account> --character <name>
json
{
  "ok": true,
  "outcome": "planned",
  "reason": "Next: Someone — 3 scrapbook items, 72% odds",
  "candidates": [
    {
      "name": "Someone",
      "level": 340,
      "source": "pool",
      "missing_items": 3,
      "rank_gain": 12,
      "favourite": false,
      "win_chance": 0.72,
      "item_score": 300.0,
      "xp_score": 60.0,
      "rank_score": 25.0,
      "base": 385.0,
      "score": 277.2
    }
  ]
}

和应用画出来的是同一个判断:候选人按物品、每日经验和名次打分,再乘以胜率。outcomeplannedwaitingblockedempty 之一,而 reason 用一句话说明是哪 个。

这不会花掉任何一场战斗,也不会花蘑菇。但它确实会为了找候选人而爬取,所以在请求上并 不免费,别把它放进一个紧凑的循环里。

预演一场战斗

bash
mercy-cli --simulate --user <account> --character <name> --against <player>
json
{ "ok": true, "against": "Someone", "against_level": 340, "win_chance": 0.63, "iterations": 1000 }

纯算术。会有一次查询发到服务器去确认对方是谁;战斗本身从不发送。win_chance 是胜场 除以分出胜负的场数,所以一次无法收敛的模拟,报告的基数会少于它实际跑的次数。

身上带着什么

bash
mercy-cli --inventory --user <account> --character <name>
json
{ "ok": true, "carried": 12, "backpack": [ { "slot_index": 0, "model_id": 55 } ] }

只有一份列表,因为游戏也只保留一份。物品的结构和 --status 里相同,另加 slot_index

剪贴簿

bash
mercy-cli --scrapbook --user <account> --character <name>
json
{ "ok": true, "unlocked": true, "owned": 1102, "monsters": 214 }

在赋予剪贴簿的那个等级以下,unlockedfalse,而 owned 在那时是 null。这 不是错误。

记录下来的战斗

bash
mercy-cli --history --user <account> --character <name> --limit 50
json
{ "ok": true, "total": 4210, "returned": 50, "battles": [] }

最新的排在前面。这些是由跑过机器人的那台安装记录下来的,不是从服务器取回的,所 以一个刚启动的进程没有东西可展示,那并不是故障。total 是存在的总数,returned--limit 之后剩下的数量。


执行

运行机器人

bash
mercy-cli --start --user <account> --character <name>

一直跑到进程被停掉,其间持续记录日志。这里 --character必需的:列清单和读状 态都无害,但启动机器人就是在玩这个游戏,而选错角色不是一件可以撤销的事。

--all 则会在这一个进程里跑这个账号的每一个角色。那是每账号一次登录,而不是每角色 一次,既更省,也能避免角色之间互相把会话作废。

按下 Ctrl-C(或 SIGTERM)时,当前这一步会被允许走完,然后进程才退出,而不是在请求中 途被切断。

用事件代替文字

bash
mercy-cli --start --user <account> --character <name> --events

每行一个 JSON 对象,守护进程可以一行一行地读:

json
{"at":"2026-08-06T17:22:04+02:00","api":1,"event":"arena_win","character":"Hero"}
{"at":"2026-08-06T17:22:31+02:00","api":1,"event":"quest_done","character":"Hero"}

eventstartedstoppingstoppedarena_winarena_lossdungeon_windungeon_lossquest_donescrapbook_winscrapbook_lossscrapbook_items(它会带上 count)之一。

这份清单会变长。 请把不认识的事件当成可以忽略的一个,而不是当成错误;这些名字之 所以被明确写出来、而不是随 Rust 打印什么算什么,理由全在这里。

请用这个,而不是去读那份给人看的日志。那份日志是写给人的,随时可以被改写措辞;解析 它意味着别人把一句话写得更好时,你的工具就坏了。

把等着的东西收掉

bash
mercy-cli --claim --user <account> --character <name>

日历、每日任务、待领的解锁。--character 是必需的,理由和 --start 一样。

读取或修改配置

bash
mercy-cli --config --user <account> --character <name>
mercy-cli --config --user <account> --character <name> --set auto_arena=false
json
{ "ok": true, "settable": ["auto_arena"], "changed": [ { "key": "auto_arena", "from": true, "to": false } ] }

读取会把整份配置放在 config 下返回。那一块里面的字段名不属于这份承诺,只有 settable 中列出的键属于。配置有远超一百个字段,而且每个版本都在长;把它们全部承诺 下来,就等于以后再也不能重新整理设置了。

--set 接受 true/falseon/offyes/no1/0,始终要求 --character(一个 开关属于某一个角色,而「反正只有这一个」是一种读的时候没问题、写的时候就是错的猜 测),并且可以重复使用。值随后会从磁盘上回读一次,如果没有落盘,这次调用就会失败, 因为报告一个没有发生的改动,是唯一一种你无法补救的结果。


运维

这是哪个构建

bash
mercy-cli --version
json
{ "ok": true, "version": "2.12.0", "api": 1 }

唯一一条既不需要密码也不需要网络的命令。守护进程必须能在拿到用于对话的凭据之前,先 问清楚自己在跟什么对话。

它通不通

bash
mercy-cli --health --user <account> --character <name>
json
{ "ok": true, "character": "Hero", "level": 345, "round_trip_ms": 412 }

登录、拉取、报告、退出。刻意做得很小:一个返回完整角色面板的健康检查,会诱使别人去 解析它,而那之后,这个检查本身就变成了又一个接口。


密码

没有 --password 这个参数,是有意为之。命令行参数在这台机器上被其他每一个进程都读 得到,ps/proc、任务管理器都行,所以一个替好几个人跑这条命令的面板,等于把他们 全部的游戏密码交给任何一个在这台机器上有 shell 的人。

请改用这两条路:

bash
MERCY_SF_PASSWORD='…' mercy-cli --status --user someone
bash
printf '%s' "$password" | mercy-cli --status --user someone --password-stdin

--password-stdin 读标准输入的第一行,这样秘密连环境变量里也不会留下。


退出码

含义
0成功,--help 也是这个
1跑了,但失败了,原因在 error 里,okfalse
2调用本身写错了:未知选项、缺少取值、没给动作。输出在 stderr

1 和 2 的区别对守护进程有意义:2 每次都会以同样的方式失败,需要人来处理;1 则很可能 下一次就成了。


错误

失败同样是 JSON,所以一个解析器就能同时处理两种情况:

json
{ "ok": false, "error": "no character called 'Typo' on this account" }

每个响应里都有 ok。在做别的事之前先检查它。

常见原因:

消息该怎么办
--user is required少了 --user(除 --version 外的每个动作)
--start needs --character (or --all)点名目标;它不会去猜
this account has N characters — name one with --character有好几个,挑一个
no character called '…' on this account拼错了;--characters 会列出有效名字
no password: set MERCY_SF_PASSWORD or pass --password-stdin从两条密码路径里选一条
login failed: …检查凭据或服务器;非单点登录的账号需要 --server
'…' is not settable from here不在允许清单上;不带 --set--config 会打印 settable
'…' did not save — it still reads back unchanged改动没落盘;检查写入权限

常用配方

每天在每个账号上收一次

bash
for account in alpha beta gamma; do
  MERCY_SF_PASSWORD="$(pass mercy/$account)" \
    mercy-cli --claim --user "$account" --character Hero || echo "failed: $account"
done

让面板上的一块保持最新

bash
MERCY_SF_PASSWORD='…' mercy-cli --status --user someone --character Hero --watch 60

一次登录,一分钟一个对象。不要改成从 cron 每分钟重跑一次,那是每次都重新登录一遍。

把事件记进文件

bash
MERCY_SF_PASSWORD='…' mercy-cli --start --user someone --character Hero --events \
  >> events.ndjson

登录之前先确认你在跟什么说话

bash
api=$(mercy-cli --version | jq -r .api)
[ "$api" = "1" ] || { echo "unknown contract version: $api"; exit 1; }

作为 systemd 服务

ini
[Service]
Environment=MERCY_SF_PASSWORD=…
ExecStart=/usr/local/bin/mercy-cli --start --user someone --all --events
Restart=on-failure
KillSignal=SIGTERM

SIGTERM 会让当前这一步走完。请不要对它用 SIGKILL


目前还做不到的事

启动多个角色并逐个停止(--all 是全部启动、全部一起停止)、往运行中的进程里开一条 控制通道,以及修改模块开关之外的任何东西。

如果你需要其中某一项,说一声是哪一项;把对的东西造出来,比猜着造完又没人用要容易。

Mercy SF 与《Shakes & Fidget》和 Playa Games 没有任何关联。使用风险自负。