diff --git a/beatsaber-ntfy.sh b/beatsaber-ntfy.sh new file mode 100644 index 0000000..9542c00 --- /dev/null +++ b/beatsaber-ntfy.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# shellcheck disable=SC2154,SC2034 +# beatsaber-ntfy, a bash script (with jq, bc, and curl) to push notifications to a ntfy.sh server on beatsaber leaderboard statistics updates +# Global configuration +INTERVAL=3600 +# Set to empty string to disable +SCORESABER_DIR="./scoresaber-players/" +BEATLEADER_DIR="./beatleader-players/" + +# Variables taken from SS/BL API +PLAYER_STATS_VARS="name country pp rank countryRank banned inactive" +# Settings stored per player +SETTINGS_VARS="NTFY_URL COUNTRY_THRESHOLD RANK_THRESHOLD PP_THRESHOLD" + +stats_routine() { + ( if [[ -n "$SCORESABER_DIR" ]]; then + cd "$SCORESABER_DIR" || exit 1 + for PLAYER in *; do + update_player_stats "$PLAYER" "ScoreSaber" "https://scoresaber.com/api/player/$PLAYER/basic" "https://scoresaber.com/u/$PLAYER" + done + fi ) + ( if [[ -n "$BEATLEADER_DIR" ]]; then + cd "$BEATLEADER_DIR" || exit 1 + for PLAYER in *; do + update_player_stats "$PLAYER" "BeatLeader" "https://api.beatleader.xyz/player/$PLAYER" "https://beatleader.xyz/u/$PLAYER" + done + fi ) +} + +update_player_stats() { + # Name arguments + PLAYER=$1 + TITLE=$2 + API_URL=$3 + PROFILE_URL=$4 + # If the API fails, don't continue + echo "Fetching stats for $PLAYER on $TITLE" + if ! UPDATED_STATS="$(curl -s --fail-with-body "$API_URL")"; then + echo "Failed to gather stats for $PLAYER on $TITLE due to:" + echo "$UPDATED_STATS" + return + fi + # Get the old playerdata + # shellcheck disable=SC1090 + source "$PLAYER" + + # Set the new playerdata + for VAR in $PLAYER_STATS_VARS; do + declare "UPD_${VAR}=$(echo "$UPDATED_STATS" | jq -r ".$VAR")" + done + + # If there are no known stats, update them without sending a notification + if [[ -z "$rank" ]]; then + INIT_STATS=true + else + INIT_STATS=false + fi + + # Calculate stat differences + RANK_DIFF=$(( rank - UPD_rank )) + COUNTRYRANK_DIFF=$(( countryRank - UPD_countryRank )) + PP_DIFF=$(bc <<< "$UPD_pp - $pp") + + # Check if criteria for notification are met + SEND_NOTIF=false + if [[ ${RANK_DIFF#-} -gt ${RANK_THRESHOLD} ]]; then SEND_NOTIF=true; fi + if [[ ${COUNTRYRANK_DIFF#-} -gt ${COUNTRYRANK_THRESHOLD} ]]; then SEND_NOTIF=true; fi + if [[ 1 -eq "$(bc <<< "${PP_DIFF#-} > ${PP_THRESHOLD}")" ]]; then SEND_NOTIF=true; fi + if [[ $inactive != "$UPD_inactive" ]]; then SEND_NOTIF=true; fi + if [[ $banned != "$UPD_banned" ]]; then SEND_NOTIF=true; fi + + if ! $SEND_NOTIF; then + return + fi + + # Add "+" to stats that increased + if [[ ${RANK_DIFF} -gt 0 ]]; then RANK_DIFF="+$RANK_DIFF"; fi + if [[ ${COUNTRYRANK_DIFF} -gt 0 ]]; then COUNTRYRANK_DIFF="+$COUNTRYRANK_DIFF"; fi + if [[ 1 -eq "$(bc <<< "${PP_DIFF} > 0")" ]]; then PP_DIFF="+$PP_DIFF"; fi + + # Create the notification text + NOTIF="Global: $rank > $UPD_rank ($RANK_DIFF) +Country ($UPD_country): $countryRank > $UPD_countryRank ($COUNTRYRANK_DIFF) +PP: $pp > $UPD_pp ($PP_DIFF)" + if [[ $inactive != "$UPD_inactive" ]]; then + NOTIF="$NOTIF +Inactive status has changed: $inactive > $UPD_inactive" + fi + if [[ $banned != "$UPD_banned" ]]; then + NOTIF="$NOTIF +Banned status has changed: $banned > $UPD_banned" + fi + + # Send the notification + if ! $INIT_STATS; then + echo "${UPD_name}'s $TITLE stats have changed, sending notification" + if ! NOTIF_RESPONSE=$(curl -s --fail-with-body -X POST -H "Title: $UPD_name's $TITLE Stats" -H "Click: $PROFILE_URL" -d "$NOTIF" "$NTFY_URL"); then + echo "Failed to send notification:" + echo "$NOTIF_RESPONSE" + # If we can't notify the user, don't update the stats file. + # Ensures notifications are accurate after ntfy server is fixed. + return + fi + fi + + # Recreate the player file + NEW_PLAYER="" + for VAR in $SETTINGS_VARS; do + NEW_PLAYER="$NEW_PLAYER +$VAR=$(eval "echo \$$VAR")" + done + for VAR in $PLAYER_STATS_VARS; do + NEW_PLAYER="$NEW_PLAYER +$VAR=\"$(eval "echo \"\$UPD_$VAR\"")\"" + done + echo "${NEW_PLAYER:1}" > "$PLAYER" +} + +while true; do + stats_routine + sleep "$INTERVAL" +done