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.