Lichess puzzle reporting script

As I mentioned in my journal thread, I’ve whipped up a quick script to get lichess puzzle stats into beeminder:

#!/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:

[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:

[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.

3 Likes

Any bash improvements, of course, welcome :slight_smile:

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.

1 Like