Last active
October 21, 2017 09:34
-
-
Save mickael9/b2188bb49efc9c878e556c395cedb4c5 to your computer and use it in GitHub Desktop.
A shell/awk wrapper around qstat for UrbanTerror, with filtering and sorting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
QSTAT_ARGS="-iourtm master.urbanterror.info:27900" | |
show_help () { | |
echo "Usage: $0 [[-]<keyword>...] [<var>=<val>...] [<var>=!<val>...]" | |
echo | |
echo "Examples:" | |
echo | |
echo " Show all public TS or CTF servers running either abbey or turnpike:" | |
echo " $0 -private ts ctf map=ut4_abbey map=turnpike" | |
echo | |
echo " Show all non-empty servers without bots excluding JUMP servers, lowest pings first:" | |
echo " $0 -empty -bots -jump sort=ping" | |
echo | |
echo "Keywords (negate by prefixing with -)" | |
echo " private private servers" | |
echo | |
echo " empty empty servers" | |
echo " full full servers" | |
echo " bots servers with bots" | |
echo | |
echo " ffa FFA servers" | |
echo " lms LMS servers" | |
echo " tdm TDM servers" | |
echo " ts TS servers" | |
echo " ftl FTL servers" | |
echo " cah CAH servers" | |
echo " ctf CTF servers" | |
echo " bomb BOMB servers" | |
echo " jump JUMP servers" | |
echo " freeze FREEZE servers" | |
echo " gungame GUNGAME servers" | |
echo | |
echo "Variable filters:" | |
echo " <var> can be one of the exported server cvars (g_gametype, sv_matchmode, etc.)" | |
echo " <var>=<val> includes servers that have the variable <var> equal to <val>" | |
echo " <var>=!<val> excludes servers in the same way" | |
echo " <var> can also be one of those special vars:" | |
echo " address server address and port (address:port)" | |
echo " name server name" | |
echo " map map name (eg ut4_turnpike)" | |
echo " players number of connected players" | |
echo " max_players max number of players" | |
echo " full 1 if the server is full, 0 otherwise" | |
echo " has_bots 1 if the server has bots, 0 otherwise" | |
echo " mode game mode name (TDM, TS, CTF, ...)" | |
echo | |
echo "Options:" | |
echo " sort=[-]<sort>: select the column number/name to use for sorting." | |
echo " Prefix with - to sort in descending order" | |
echo | |
echo " sort=1 sort by ascending address (using column number)" | |
echo " sort=-address sort by descending address" | |
echo " sort=address sort by ascending address" | |
echo " sort=name sort by ascending name" | |
echo " sort=map sort by ascending map name" | |
echo " sort=players sort by ascending player count" | |
echo " sort=ping sort by ascending ping" | |
echo " sort=mode sort by ascending game mode" | |
echo | |
echo " color=1 | 0: select wether to use colored output" | |
echo " color=1 convert game colors to terminal colors" | |
echo " color=0 don't convert colors" | |
echo | |
echo " The default is to use colors if the standard output is a terminal." | |
echo | |
echo " limit=<limit>: set the maximum number of results to return" | |
echo | |
exit | |
} | |
main() { | |
filters=() | |
sort_key=-4 | |
color=0 | |
[[ -t 1 ]] && color=1 | |
while [[ $# > 0 ]]; do | |
arg=${1#-} | |
not= | |
[[ ${1:0:1} == '-' ]] && not='!' | |
shift | |
case ${arg,,*} in | |
private) filters+=("password=${not}1") ;; | |
empty) filters+=("cur_players=${not}0") ;; | |
full) filters+=("full=${not}1") ;; | |
bots) filters+=("has_bots=${not}1") ;; | |
[a-z_]*=*) filters+=("$arg") ;; | |
ffa|lms|tdm|ts|ftl|cah|ctf|bomb|jump|freeze|gungame) | |
filters+=("mode=${not}${arg^^*}") | |
;; | |
h|-help|help) | |
show_help | |
;; | |
*) | |
echo "Try $0 -h for help" | |
exit | |
;; | |
esac | |
done | |
show_servers "color=$color" "${filters[@]}" | |
} | |
show_servers() { | |
qstat -progress -carets -raw $'\t' -R -u $QSTAT_ARGS | | |
gawk ' | |
BEGIN { | |
OFS = "\t" | |
RS = "\n\n" | |
FS = "[\t\n]" | |
limit = 0 | |
sort[1] = "-cur_players" | |
# g_gametype string values | |
split("FFA LMS ? TDM TS FTL CAH CTF BOMB JUMP FREEZE GUNGAME", modes, " ") | |
# Quake -> ANSI color codes | |
split("30 31 32 33 34 36 35 0 33 33", colors, " ") | |
split("Address Name Map Players Ping Mode", columns, " ") | |
for (i = 1; i < ARGC; i++) { | |
arg = ARGV[i] | |
if (!parse_assignement(arg)) | |
continue | |
name = tolower(name) | |
if (name == "sort") { | |
split_append(tolower(value), sort) | |
continue | |
} else if (name == "color") { | |
USE_COLORS = value | |
continue | |
} else if (name == "columns") { | |
split_append(value, columns) | |
continue | |
} else if (name == "limit") { | |
limit = strtonum(value) | |
continue | |
} | |
if (match(value, /^!(.*)$/, m)) { | |
# Hack to make sure arrays are correctly defined | |
exclude_data[name][0] = 0 | |
delete exclude_data[name][0] | |
push(exclude_data[name], m[1]) | |
} else { | |
include_data[name][0] = 0 | |
delete include_data[name][0] | |
push(include_data[name], value) | |
} | |
} | |
for (c in columns) { | |
output[1][column_name(c)] = columns[c] | |
} | |
ARGC = 1 | |
} | |
/^Q3S|IOURTS/ { | |
delete data | |
if (NF < 8) | |
next | |
data["raw_name"] = $3 | |
colored_name = ansi_color($3) | |
for (i = 2; i <= 8; i++) { | |
$i = strip_colors($i) | |
} | |
for (i = 9; i <= NF; i++) { | |
if (parse_assignement($i)) { | |
data[tolower(name)] = value | |
} | |
} | |
mode = modes[data["g_gametype"] + 1] | |
data["mode"] = mode | |
data["address"] = $2 | |
data["name"] = $3 | |
data["map"] = $4 | |
data["cur_players"] = $6 | |
data["max_players"] = $5 | |
data["players"] = $6 "/" $5 | |
data["ping"] = $7 | |
data["full"] = $6 > 0 && $6 == $5 | |
data["has_bots"] = data["bots"] > 0 | |
data["colored_name"] = colored_name | |
for (name in include_data) { | |
if (!(name in data)) | |
next | |
matches = 0 | |
for (i in include_data[name]) { | |
if (include_data[name][i] == data[name]) { | |
matches = 1 | |
break | |
} | |
} | |
if (!matches) { | |
next | |
} | |
} | |
for (name in exclude_data) { | |
if (!(name in data)) { | |
break | |
} | |
for (i in exclude_data[name]) { | |
if (exclude_data[name][i] == data[name]) { | |
next | |
} | |
} | |
} | |
idx = length(output) + 1 | |
for (i in data) { | |
output[idx][i] = data[i] | |
} | |
} | |
END { | |
asort(output, output, "compare") | |
for (line in output) { | |
line = strtonum(line) | |
if (limit > 0 && line > limit + 1) | |
break | |
for (col in columns) { | |
col_name = column_name(col) | |
col_len = length(output[line][col_name]) | |
if (col_len > column_sizes[col]) | |
column_sizes[col] = col_len | |
} | |
} | |
for (line in output) { | |
line = strtonum(line) | |
if (limit > 0 && line > limit + 1) | |
break | |
NF = 0 | |
for (col in columns) { | |
col_name = column_name(col) | |
col_text = output[line][col_name] | |
padding = length(col_text) - column_sizes[col] | |
if (col_name == "name" && line > 1) | |
col_text = output[line]["colored_name"] | |
$col = sprintf("%s%*s", col_text, padding, "") | |
} | |
} | |
} | |
function strip_colors(str) { | |
gsub(/\^[0-9]/, "", str) | |
return str | |
} | |
function ansi_color(str) { | |
if (!USE_COLORS) { | |
return strip_colors(str) | |
} | |
if (str ~! /\^[0-9]/) | |
return strip_colors(str) | |
str = str "^7" | |
for (i in colors) { | |
pattern = "\\^" (i - 1) | |
gsub(pattern, "\x1B[" colors[i] "m", str) | |
} | |
return strip_colors(str) | |
} | |
function parse_assignement(a, result) | |
{ | |
result = match(a, /^([^=+]+)(\+?)=(.*)$/, assign) | |
if (result) { | |
name = assign[1] | |
append = assign[2] == "+" | |
value = assign[3] | |
} | |
return result | |
} | |
function push(arr, elem) { | |
arr[length(arr) + 1] = elem | |
} | |
function split_append(text, arr, i) | |
{ | |
if (append) { | |
split(text, append_split, /,/) | |
for (i in append_split) { | |
push(arr, append_split[i]) | |
} | |
} else { | |
split(text, arr, /,/) | |
} | |
} | |
function column_name(col, name) | |
{ | |
name = tolower(columns[col]) | |
gsub(/ /, "_", name) | |
return name | |
} | |
function compare_strip(str) | |
{ | |
str = tolower(str) | |
unstripped = str | |
gsub(/^[^a-zA-Z]+/, "", str) | |
if (str == "") | |
return unstripped | |
else | |
return str | |
} | |
function compare(i1, v1, i2, v2) | |
{ | |
# Header line always stays at the top | |
if (i1 == 1) return -1 | |
if (i2 == 1) return 1 | |
for (sort_i in sort) { | |
sort_name = sort[sort_i] | |
if (substr(sort_name, 1, 1) == "-") { | |
sort_name = substr(sort_name, 2) | |
sort_direction = -1 | |
} else { | |
sort_direction = 1 | |
} | |
if (sort_name in columns) | |
sort_name = column_name(sort_name) | |
s1 = v1[sort_name] | |
s2 = v2[sort_name] | |
if (s1 ~ /^[0-9]/ && s2 ~ /^[0-9]/) { | |
s1 = strtonum(s1) | |
s2 = strtonum(s2) | |
} else { | |
s1 = compare_strip(s1) | |
s2 = compare_strip(s2) | |
} | |
if (s1 > s2) | |
return sort_direction | |
else if (s1 < s2) | |
return -sort_direction | |
} | |
return 0 | |
}' "$@" | |
} | |
main "$@" | |
# vim: ts=4 sw=4 et |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment