Skip to content

title: "Opis CLI" description: "Sterowanie Mercy SF z innego programu: każde polecenie, jego wyjście JSON, kody wyjścia i rozpisane przykłady."

Opis CLI, sterowanie Mercy SF z innego programu

mercy-cli ma tryb nieinteraktywny: argumenty do środka, jeden obiekt JSON na zewnątrz. Jest przeznaczony dla paneli, nadzorców i skryptów.

Ta strona jest obietnicą. Polecenia i nazwy pól poniżej nie zmienią się bez uprzedzenia. Cała reszta CLI, menu, jego sformułowania, układ wyjścia, może się zmienić w każdej chwili, więc proszę, nie buduj na tym.

Uruchomione bez argumentów, startuje interaktywne menu dokładnie jak wcześniej.

Każda odpowiedź niesie "api". Dziś to 1. Dodanie pola tego nie zmienia, wywołujący, który czyta tylko znane sobie pola, pozostaje nietknięty. Zmienia się, gdy pole zostaje usunięte albo zmienia się jego znaczenie, więc możesz odmówić działania wobec kształtu, pod który nie byłeś budowany.


Logowanie

Konta założone przez pojedyncze logowanie S&F nie potrzebują niczego więcej. Konto należące do jednego serwera, które nigdy nie było częścią pojedynczego logowania, z jego punktu widzenia nie istnieje i wymaga --server:

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

--server działa z każdym poleceniem poniżej.

Wyjście

Każde polecenie oprócz --start wypisuje JSON i nic więcej, więc --json i tak jest wartością domyślną, a podanie go nic nie zmienia. Istnieje dla wywołujących, którzy wolą to powiedzieć, niż polegać na domyślnym zachowaniu, oraz dla --start, które w przeciwnym razie pisze dziennik dla człowieka.


Odczyt

Lista postaci na koncie

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 }
  ]
}

Jedno logowanie obejmuje każdą postać na koncie, na wszystkich serwerach. level jest null, jeśli stanu tej postaci nie udało się pobrać; wpis i tak jest wypisany, bo świadomość, że postać istnieje, jest sensem tego wywołania.

Odczyt stanu jednej postaci

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

--character można pominąć, gdy konto ma dokładnie jedną.

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
}

Wszystko pochodzi z jednego pobrania stanu, więc nie ma sekcji do zamawiania ani niczego do zaoszczędzenia przez proszenie o mniej.

guild jest null, gdy postać do żadnej nie należy. scrapbook_items jest null, dopóki album nie zostanie odczytany choć raz. Puste miejsce na ekwipunek jest pomijane, a nie wypisywane jako null.

O nazwach przedmiotów: protokół niesie identyfikator modelu i slot, a nie nazwę wyświetlaną, więc to właśnie dostajesz. To także sposób, w jaki adresowana jest grafika, więc panel może narysować dokładnie ten obrazek, który rysuje aplikacja na pulpicie. Wymyślanie tu nazwy byłoby zgadywanką, której nie dałoby się sprawdzić.

Powtarzanie odczytu w odstępach

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

Jedno logowanie, a potem jeden obiekt co 60 sekund, dopóki proces nie zostanie zatrzymany. Używaj tego zamiast uruchamiać polecenie w pętli: każde uruchomienie to świeże logowanie, a to najdroższy sposób obserwowania liczby i najbardziej rzucający się w oczy od strony serwera. Dolna granica to 30 sekund. Nieudana runda jest wypisywana i pętla leci dalej; obserwator, który poddaje się przy pierwszej czkawce, wygląda dokładnie tak samo jak taki, u którego nic się nie dzieje.

Z kim będzie walczył następnie i dlaczego

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
    }
  ]
}

Ta sama decyzja, którą rysuje aplikacja: kandydaci oceniani po przedmiotach, dziennym PD i miejscu w rankingu, a potem mnożeni przez szanse. outcome to planned, waiting, blocked albo empty, a reason mówi jednym zdaniem który.

To nie wydaje żadnej walki ani grzyba. Przeczesuje jednak w poszukiwaniu kandydatów, więc nie jest darmowe w zapytaniach; nie wsadzaj tego w ciasną pętlę.

Próba walki

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

Arytmetyka. Jedno zapytanie idzie do serwera, żeby ustalić, kto to jest; sama walka nigdy nie jest wysyłana. win_chance to wygrane wobec walk rozstrzygniętych, więc symulacja, która nie potrafi rozstrzygnąć, raportuje z mniejszej liczby, niż przeprowadziła.

Co jest noszone

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

Jedna lista, bo gra trzyma jedną. Przedmioty mają ten sam kształt co w --status, plus slot_index.

Album

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

unlocked jest false poniżej poziomu, który daje album, a owned jest tam null. To nie jest błąd.

Zapisane walki

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

Najnowsze na początku. Zapisuje je ta instalacja, która prowadziła bota, nie są pobierane z serwera. Proces, który dopiero wystartował, nie ma nic do pokazania i nie jest to usterka. total to to, co istnieje, returned to to, co zostawił --limit.


Działanie

Uruchomienie bota

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

Chodzi, dopóki proces nie zostanie zatrzymany, zapisując przy tym dziennik. --character jest tu wymagane: wypisywanie listy i czytanie stanu są nieszkodliwe, ale uruchomienie bota gra w grę, a wybranie złej postaci nie jest czymś, co da się cofnąć.

--all uruchamia zamiast tego każdą postać konta w tym jednym procesie. To jedno logowanie na konto zamiast jednego na postać, co jest tańsze i powstrzymuje postaci przed wzajemnym unieważnianiem sesji.

Przy Ctrl-C (albo SIGTERM) bieżący ruch może się dokończyć, zanim proces wyjdzie, zamiast być ucięty w środku zapytania.

Zdarzenia zamiast prozy

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

Jeden obiekt JSON na linię, więc nadzorca czyta linia po linii:

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"}

event to jedno z started, stopping, stopped, arena_win, arena_loss, dungeon_win, dungeon_loss, quest_done, scrapbook_win, scrapbook_loss, scrapbook_items (które niesie count).

Ta lista będzie rosła. Traktuj nierozpoznane zdarzenie jako takie do zignorowania, a nie jako błąd; to cały powód, dla którego nazwy są wypisane, a nie są tym, co akurat wypluł Rust.

Używaj tego zamiast czytać dziennik dla ludzi. Tamten dziennik pisany jest dla człowieka i może zostać przeredagowany w każdej chwili; parsowanie go znaczy, że twoje narzędzie psuje się, gdy ktoś poprawi zdanie.

Odbiór tego, co czeka

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

Kalendarz, zadania dzienne, oczekujące odblokowania. --character jest wymagane, z tego samego powodu co przy --start.

Odczyt lub zmiana konfiguracji

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 } ] }

Odczyt zwraca całą konfigurację pod config. Nazwy pól wewnątrz tego bloku nie są częścią tej obietnicy, są nią wyłącznie klucze wypisane w settable. Konfiguracja ma znacznie ponad sto pól i rośnie z każdym wydaniem; obiecanie ich wszystkich znaczyłoby, że nigdy więcej nie da się przebudować ustawień.

--set przyjmuje true/false, on/off, yes/no, 1/0, zawsze wymaga --character (przełącznik należy do jednej postaci, a „ta jedyna” to zgadywanka, po której czytać można, a pisać nie), i można je powtarzać. Wartość jest potem odczytywana z dysku, a wywołanie kończy się niepowodzeniem, jeśli nie wylądowała, bo zgłoszenie zmiany, która się nie wydarzyła, to jedyny wynik, po którym nie da się pozbierać.


Eksploatacja

Co to za kompilacja

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

Jedyne polecenie, które nie potrzebuje ani hasła, ani sieci. Nadzorca musi móc zapytać, z czym rozmawia, zanim będzie miał dane do tej rozmowy.

Czy jest osiągalne

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

Zaloguj, pobierz, zgłoś, wyjdź. Celowo maleńkie: kontrola zdrowia zwracająca pełną kartę postaci zachęca do jej parsowania, a wtedy sama kontrola staje się kolejnym interfejsem.


Hasło

Nie ma flagi --password i jest to celowe. Argumenty wiersza poleceń są czytelne dla każdego innego procesu na maszynie, ps, /proc, Menedżer zadań, więc panel uruchamiający to dla kilku osób oddawałby wszystkie ich hasła do gry każdemu, kto ma powłokę na tej maszynie.

Zamiast tego dwie drogi:

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

--password-stdin czyta pierwszą linię standardowego wejścia, co trzyma sekret także poza środowiskiem.


Kody wyjścia

KodZnaczenie
0Sukces, a także dla --help
1Ruszyło i zawiodło, powód jest w error, a ok to false
2Wywołanie było błędne: nieznana opcja, brak wartości, brak akcji. Na stderr

Różnica między 1 a 2 ma znaczenie dla nadzorcy: 2 zawiedzie za każdym razem tak samo i potrzebuje człowieka, 1 może całkiem dobrze zadziałać przy kolejnej próbie.


Błędy

Niepowodzenia to też JSON, więc jeden parser obsługuje jedno i drugie:

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

ok obecne jest w każdej odpowiedzi. Sprawdź je przed wszystkim innym.

Częste przyczyny:

KomunikatCo zrobić
--user is requiredBrakuje --user (każda akcja poza --version)
--start needs --character (or --all)Nazwij cel; nie zostanie zgadnięty
this account has N characters — name one with --characterJest ich kilka, wskaż jedną
no character called '…' on this accountLiterówka; --characters wypisuje poprawne nazwy
no password: set MERCY_SF_PASSWORD or pass --password-stdinWybierz jedną z dwóch dróg podania hasła
login failed: …Sprawdź dane logowania albo serwer; konta spoza pojedynczego logowania wymagają --server
'…' is not settable from hereNie ma tego na liście dozwolonych; --config bez --set wypisuje settable
'…' did not save — it still reads back unchangedZmiana nie wylądowała; sprawdź uprawnienia do zapisu

Przepisy

Odbieranie na każdym koncie raz dziennie

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

Utrzymywanie kafelka panelu na bieżąco

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

Jedno logowanie, jeden obiekt na minutę. Nie uruchamiaj tego zamiast tego z crona co minutę; to za każdym razem świeże logowanie.

Zapis zdarzeń do pliku

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

Sprawdzenie, z czym rozmawiasz, przed zalogowaniem

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

Jako usługa systemd

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

SIGTERM pozwala dokończyć bieżący ruch. Proszę, nie rób mu SIGKILL.


Czego to jeszcze nie robi

Uruchamiania kilku postaci i zatrzymywania ich pojedynczo (--all uruchamia wszystkie i zatrzymuje wszystkie naraz), kanału sterującego do działającego procesu oraz zmieniania czegokolwiek poza przełącznikami modułów.

Jeśli potrzebujesz którejś z tych rzeczy, powiedz której; łatwiej zbudować właściwą rzecz niż zgadywać i patrzeć, jak leży nieużywana.

Mercy SF nie jest powiązany z Shakes & Fidget ani Playa Games. Korzystasz na własne ryzyko.