From 8d50310c76e0d4cf9f49f7ddb66f060b3454d6fa Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Mon, 9 May 2022 20:42:24 +0900 Subject: [PATCH] initial GUI version --- dzgui.sh | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 dzgui.sh diff --git a/dzgui.sh b/dzgui.sh new file mode 100644 index 0000000..c57404b --- /dev/null +++ b/dzgui.sh @@ -0,0 +1,243 @@ +#!/bin/bash + +set -eo pipefail +version=0.1.0 +aid=221100 +game="dayz" +workshop="https://steamcommunity.com/sharedfiles/filedetails/?id=" +api="https://api.battlemetrics.com/servers" + +#BEGIN CONFIG================ +steam_path="" +workshop_dir="$steam_path/steamapps/workshop/content/$aid" +game_dir="$steam_path/steamapps/common/DayZ" +key="" +whitelist="" +fav="" +name="player" +separator="│" +#TODO: ping flag is unimplemented. Table creation requires rigid number of arguments. Add binary switch when constructing table +#ping=1 +debug=0 +#END CONFIG================== + +export dzpipe=/tmp/dzfifo +export tmp=tmp/dz.tmp +[[ -p $dzpipe ]] && rm $dzpipe +mkfifo $dzpipe +exec 3<> $dzpipe +trap cleanup EXIT INT + +declare -A deps +deps=([awk]="5.1.1" [curl]="7.80.0" [jq]="1.6" [tr]="9.0" [yad]="12.0") + +depcheck(){ + for dep in "${!deps[@]}"; do + command -v $dep 2>&1>/dev/null || (printf "[ERROR] Requires %s >= %s\n" $dep ${deps[$dep]} ; exit 1) + done +} + +logger(){ + printf "[DZGUI] %s\n" "$1" +} + +query_api(){ + logger "Querying BattleMetrics" + response=$(curl -s "$api" -H "Authorization: Bearer "$key"" -G -d "sort=-players" \ + -d "filter[game]=$game" -d "filter[ids][whitelist]=$whitelist") + if [[ "$(jq -r 'keys[]' <<< "$response")" == "errors" ]]; then + code=$(jq -r '.errors[] .status' <<< $response) + refresh_yad + #TODO: fix granular api codes + err "Error $code: malformed API key" + fi +# elif [[ -z "$(jq -r '.data[]' <<< "$response")" ]]; then +# refresh_yad +# echo "Malformed server IDs" > $dzpipe +# return 1 +# fi +} +concat_mods(){ + readarray -t serv <<< "$remote_mods" + for i in "${serv[@]}"; do + id=$(awk -F"= " '/publishedid/ {print $2}' "$workshop_dir"/$i/meta.cpp | awk -F\; '{print $1}') + mod=$(awk -F\" '/name/ {print $2}' "$workshop_dir"/$i/meta.cpp | sed -E 's/[^[:alpha:]0-9]+/_/g; s/^_|_$//g') + link="@$id-$mod;" + echo -e "$link" + done | tr -d '\n' | perl -ple 'chop' +} +launch(){ + mods=$(concat_mods) + if [[ $debug -eq 1 ]]; then + printf "[DZGUI] [DEBUG] steam -applaunch $aid -connect=$ip -nolauncher -nosplash -skipintro \"-mod=$mods\"\n" + else + steam -applaunch $aid -connect=$ip -nolauncher -nosplash -skipintro -name=$name \"-mod=$mods\" + exit + fi +} + +fetch_mods(){ + remote_mods=$(curl -s "$api" -H "Authorization: Bearer "$key"" -G -d filter[ids][whitelist]="$1" -d "sort=-players" \ + | jq -r '.data[] .attributes .details .modIds[]') +} +#TODO: unimplemented +#symlinks(){ +# for d in "$workshop_dir"/*; do +# id=$(awk -F"= " '/publishedid/ {print $2}' "$d"/meta.cpp | awk -F\; '{print $1}') +# mod=$(awk -F\" '/name/ {print $2}' "$d"/meta.cpp | sed -E 's/[^[:alpha:]0-9]+/_/g; s/^_|_$//g') +# link="@$id-$mod" +# [[ -h "$game_dir/$link" ]] && : || +# printf "[INFO] Creating symlink for $mod\n" +# ln -fs "$d" "$game_dir/$link" +# done +#} + +check_ping(){ + ping_ip=$(echo "$1" | awk -F'\t' '{print $2}' | awk -F: '{print $1}') + ms=$(ping -c 1 "$ping_ip" | awk -Ftime= '/time=/ {print $2}') + [[ -z $ms ]] && echo "Timeout" || echo "$ms" +} +create_array(){ + list=$(cat $tmp) + #TODO: improve error handling for null values + logger "Formatting output" + while read line; do + name=$(echo "$line" | awk -F'\t' '{print $1}') + if [[ $(echo "$name" | wc -m) -gt 30 ]]; then + name="$(echo $name | awk '{print substr($0,1,30) "..."}')" + else + : + fi + ip=$(echo "$line" | awk -F'\t' '{print $2}') + players=$(echo "$line" | awk -F'\t' '{print $3}') + stat=$(echo "$line" | awk -F'\t' '{print $4}') + [[ $stat == "online" ]] && stat="online" || : + #TODO: probe offline return codes + id=$(echo "$line" | awk -F'\t' '{print $5}') + ping=$(check_ping "$line") + declare -g -a rows=("${rows[@]}" "$name" "$ip" "$players" "$stat" "$id" "$ping") + done <<< "$list" +} + +parse_json(){ + logger "Checking ping" + list=$(jq -r '.data[] .attributes | "\(.name)\t\(.ip):\(.port)\t\(.players)/\(.maxPlayers)\t\(.status)\t\(.id)"') + echo -e "$list" > $tmp +} + +refresh(){ + refresh_yad + placeholder + + ## + query_api + parse_json <<< "$response" + create_array + populate + ## + +} +refresh_yad(){ + echo -e '\f' > $dzpipe +} +populate(){ + logger "Writing data to pipe" + refresh_yad + echo "${rows[@]}" > $k.tmp + for f in "${rows[@]}"; do echo -e "$f"; done > $dzpipe +} + +placeholder(){ + echo "Fetching data..." > $dzpipe +} +cleanup(){ + #while kill -0 $pid 2>/dev/null; do + # : + #done + rm -f $dzpipe + rm -f $tmp + unset -f check_ping + unset -f create_array + unset -f placeholder + unset -f populate + unset -f query_api + unset -f refresh + unset -f refresh_yad + #logger "Removing temp files" +} + +export -f check_ping +export -f create_array +export -f placeholder +export -f populate +export -f query_api +export -f refresh +export -f refresh_yad + + +printsel(){ + #TODO: abstract configs +yad --listen --cycle-read --align=center --separator="" --list --multiple --column="Server":TEXT \ + --column="IP":TEXT --column="Players":TEXT --column="Status":TEXT --column="ID":TEXT --column="Ping":TEXT \ + --buttons-layout=start --button=Cancel:1 --button=Connect:0 --buttons-layout=end --button="Refresh":"bash -c refresh" \ + --text="DZGUI\t$version" --width=1200 --height=500 --separator="$separator" --center --no-escape 2>/dev/null < $dzpipe +} +connect(){ + #TODO: validate inputs with standalone function + if [[ $? -eq 1 ]]; then + kill $spid + else + if [[ -z $1 ]]; then + logger "Exiting with no selection" + kill $spid + else + ip=$(echo "$1" | awk -F"$separator" '{print $2}') + bid=$(echo "$1" | awk -F"$separator" '{print $5}') + logger "Connecting to $bid @ $ip" + fetch_mods "$bid" + launch + + #TODO: symlink validation, mod validation + fi + fi +} +err(){ + printf "[ERROR] %s\n" "$1" + cleanup + exit +} +varcheck(){ + [[ -z $key ]] && (err "Missing API key") + [[ -z $whitelist ]] && (err "Missing server IDs") + [[ ! -d $workshop_dir ]] && (err "Malformed workshop path") + [[ ! -d $game_dir ]] && (err "Malformed game path") + [[ $whitelist =~ [[:space:]] ]] && (err "Separate whitelist values with commas") + IFS=, + [[ ! "${whitelist[*]}" =~ ${fav} ]] && (err "Fav key value not in whitelist") + unset IFS +} +checks() { + depcheck + varcheck +} +main(){ + spid=$$ + checks + refresh_yad + placeholder + connect "$(printsel)" & + pid=$! + + ###bundle these + query_api + parse_json <<< "$response" + create_array + populate + ###end bundle + wait $pid + +} + +main + +