# Lichess puzzle reporting script

**URL:** <https://forum.beeminder.com/t/lichess-puzzle-reporting-script/10570>\
**Category:** Akrasia\
**Created:** [October 11, 2022, 5:14am UTC](https://forum.beeminder.com/t/lichess-puzzle-reporting-script/10570 "2022-10-11T05:14:53Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![rperce](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/rperce/32/4197_2.png) [@rperce](https://forum.beeminder.com/u/rperce)\
**Post date:** [October 11, 2022, 5:14am UTC](https://forum.beeminder.com/t/lichess-puzzle-reporting-script/10570/1 "2022-10-11T05:14:54Z")

</div>

As I mentioned in my [journal thread](https://forum.beeminder.com/t/calendial-concomitant-continuous-commenting/10227/28), I’ve whipped up a quick script to get lichess puzzle stats into beeminder:

```bash
#!/usr/bin/env bash
red() { printf "\e[1;31m%s\e[0m" "$*"; }
blue() { printf "\e[1;34m%s\e[0m" "$*"; }
log() { lvl="$1"; shift; printf "[%s] %s\n" "$lvl" "$*"; }
error() { >&2 log "$(red 'Error')" "$*"; }
info() { >&2 log "$(blue ' Info')" "$*"; }
beeapi() { curl -sSL "https://beeminder.com/api/v1/$1?auth_token=$BEEMINDER_TOKEN"; }

if [[-z "$LICHESS_FILTER"]]; then
  error "env var LICHESS_FILTER empty or unset; edit /etc/conf.d/lichess-beeminder-reporter.conf"
  exit 1
fi
filter="$LICHESS_FILTER"

if [[! "$filter" =~ (chess960|atomic|racingKings|ultraBullet|blitz|kingOfTheHill|bullet|correspondence|horde|puzzle|classical|rapid|storm).(games|rating)]]; then
  >&2 cat << EOL
[$(red "Error")] env var \$LICHESS_FILTER ($LICHESS_FILTER) is not in required format <type>.<stat>.
  <type> ∈ chess960 atomic racingKings ultraBullet blitz kingOfTheHill
               bullet correspondence horde puzzle classical rapid storm
  <stat> ∈ games rating

  Examples: "puzzle.games"; "ultraBullet.rating".
EOL
  exit 2
fi

if [[-z "$BEEMINDER_GOAL"]]; then
  error "env var BEEMINDER_GOAL empty or unset; edit /etc/conf.d/lichess-beeminder-reporter.conf"
  exit 3
fi
goalname="$BEEMINDER_GOAL"

if [[-z "$LICHESS_USER"]]; then
  error "env var LICHESS_USER empty or unset; edit /etc/conf.d/lichess-beeminder-reporter.conf"
  exit 4
fi

if [[-z "$BEEMINDER_TOKEN"]]; then
  error "env var BEEMINDER_TOKEN empty or unset; edit /etc/conf.d/lichess-beeminder-reporter.conf"
  exit 5
fi

bee_user="$(beeapi "/users/me.json" | jq -r .username || echo '[FAILURE]')"
if [["$bee_user" == "[FAILURE]" ]]; then
  error "could not authenticate to beeminder; check env var BEEMINDER_TOKEN in /etc/conf.d/lichess-beeminder-reporter.conf"
  exit 6
fi
info "Authenticated to beeminder as user $(blue "$bee_user")"

info "Getting stat $(blue "$filter") for lichess user $(blue "$LICHESS_USER")"
lichess_stat="$(curl -sSL "https://lichess.org/api/user/$LICHESS_USER" | jq -r ".perfs.$filter")"
if [[! "$lichess_stat" =~ ^[[:digit:]]+$ ]]; then
  error "lichess stat from API is not numeric:" "$(red "$lichess_stat")"
  exit 7
fi

last_datapoint="$(beeapi "/users/me/goals/${goalname}/datapoints.json")"
if [["$last_datapoint" =~ resource\ not\ found]]; then
  error "beeminder user $(blue "$bee_user") does not have a goal called $(red "$goalname")"
  exit 8
fi
last_datapoint="$(printf "%s" "$last_datapoint" | jq '.[0]')"
last_timestamp="$(printf "%s" "$last_datapoint" | jq -r '.timestamp')"
last_value="$(printf "%s" "$last_datapoint" | jq -r '.value')"
if [[! "$last_timestamp$last_value" =~ ^[[:digit:]]+$ ]]; then
  error "malformed beeminder api datapoint" "$last_datapoint"
  exit 9
fi

if [["$last_value" == "$lichess_stat"]]; then
  printf "Most recent datapoint from %s is the same as current lichess stat for %s" \
    "$(blue "$last_timestamp")" \
    "$(blue "$filter")"
  exit 0
fi

info "Submitting datapoint with value $(blue "$lichess_stat") to beeminder goal $(blue "$goalname")..."
curl -XPOST \
  "https://www.beeminder.com/api/v1/users/me/goals/${goalname}/datapoints.json" \
  -d "auth_token=${BEEMINDER_TOKEN}" \
  -d "timestamp=$(date +%s)" \
  -d "value=${lichess_stat}" \
  -d "comment=Entered by lichess-beeminder-reporter at $(date -Is)"

```

It needs env vars set for `LICHESS_FILTER`, `LICHESS_USER`, `BEEMINDER_TOKEN`, and `BEEMINDER_GOAL`. I do that with a systemctl service file:

```auto
[Unit]
Description=Hourly update of lichess statistics to beeminder
After=network.target

[Service]
Type=oneshot
EnvironmentFile=/etc/conf.d/lichess-beeminder-reporter.conf
ExecStart=bash lichess-beeminder-reporter.bash

[Install]
WantedBy=multi-user.target

```

That has a `EnvironmentFile` defined, so I set those env vars in that conf file, with appropriate root-only permissions. I kick that service off with a timer:

```auto
[Unit]
Description=Timer to start lichess-beeminder-reporter.service

[Timer]
Persistent=true
OnCalendar=*-*-* *:58:00
AccuracySec=30s

[Install]
WantedBy=timers.target

```

but of course you could use any method of task-scheduling and env-var-setting that’s convenient.

---

<div class="post-metadata">

**Author:** ![rperce](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/rperce/32/4197_2.png) [@rperce](https://forum.beeminder.com/u/rperce)\
**Post date:** [October 11, 2022, 5:15am UTC](https://forum.beeminder.com/t/lichess-puzzle-reporting-script/10570/2 "2022-10-11T05:15:15Z")

</div>

Any bash improvements, of course, welcome 🙂

---

<div class="post-metadata">

**Author:** ![dreev](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/dreev/32/4311_2.png) [@dreev](https://forum.beeminder.com/u/dreev)\
**Post date:** [October 11, 2022, 5:46am UTC](https://forum.beeminder.com/t/lichess-puzzle-reporting-script/10570/3 "2022-10-11T05:46:23Z")

</div>

Ah, wonderful! Thank you so much for making this and sharing it!

Let me also add that from our perspective this is a huge, huge vote in favor of Lichess being higher in [the list of candidate official autodata integrations](https://beeminder.consider.it#what-should-our-next-autodata-integration-be-92).
