mirror of
https://github.com/aclist/dztui.git
synced 2026-10-31 15:12:48 +01:00
chore: 6.0.0 beta prerelease
This commit is contained in:
1 parent
6d7b2a1d19
commit
bc712e926d
5 files changed
+3338
-2018
No files matched your search
@@ -1,5 +1,40 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [6.0.0-beta.1] 2025-07-25
|
||||||
|
## Added
|
||||||
|
- Additional validation on entry dialogs to prevent submitting empty text
|
||||||
|
- Added "Return to main menu" button to dialog windows when failing to load server table
|
||||||
|
- Speed up load time of server tables
|
||||||
|
- More robust threading and cache system when filtering servers
|
||||||
|
- Filter by modded servers
|
||||||
|
- Refresh server ping context menu and hotkey
|
||||||
|
- Disable "show mods" context menu if server has no mods
|
||||||
|
- Breadcrumbs showing current menu context to top of window
|
||||||
|
- 0, minus, and backslash keybindings for filter toggles
|
||||||
|
- Dedicated changelog page
|
||||||
|
- Dedicated keybindings page
|
||||||
|
- Vim-style navigation keybindings
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Entry dialog sensitivity when validating API keys
|
||||||
|
- Normalized buttons in dialogs and restored proper padding
|
||||||
|
- GTK errors being emitted to stdout when inserting debug table
|
||||||
|
- Suppress errors during pre-boot checks when mods are not installed
|
||||||
|
- Centered filter checkboxes within panel
|
||||||
|
- Do not pop unhighlight/select stale buttons if no stale mods exist
|
||||||
|
- Improved keybinding interaction with side panels
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Reduced global API cooldown from 60s to 30s
|
||||||
|
- Clarify dialog messages when DayZ path could not be found
|
||||||
|
- Auto-focus first item when opening context menus
|
||||||
|
- Removed low opacity setting on side buttons when in a different context
|
||||||
|
- Refactored BM API key validation to account for new key format
|
||||||
|
- Do not trigger global API cooldown if no LAN/favorite servers are found
|
||||||
|
- Suppress typeahead search in mod dialogs
|
||||||
|
|
||||||
|
## Dropped
|
||||||
|
|
||||||
## [5.8.0-beta.2] 2025-07-06
|
## [5.8.0-beta.2] 2025-07-06
|
||||||
## Added
|
## Added
|
||||||
- Filter servers by official/unofficial status
|
- Filter servers by official/unofficial status
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
version=5.8.0-beta.2
|
version=6.0.0.beta-1
|
||||||
|
|
||||||
#CONSTANTS
|
#CONSTANTS
|
||||||
aid=221100
|
aid=221100
|
||||||
@@ -585,11 +585,11 @@ fetch_helpers_by_sum(){
|
|||||||
[[ -f "$config_file" ]] && source "$config_file"
|
[[ -f "$config_file" ]] && source "$config_file"
|
||||||
declare -A sums
|
declare -A sums
|
||||||
sums=(
|
sums=(
|
||||||
["ui.py"]="f128a97e744e9e11036d707198feb8a8"
|
["ui.py"]="a68319e007172b36f7b4e4eaf0b03aab"
|
||||||
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
||||||
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
||||||
["funcs"]="94287c75a5ae0a06a95dd439711e6fba"
|
["funcs"]="5d244114c501754a630988354306e470"
|
||||||
["lan"]="c62e84ddd1457b71a85ad21da662b9af"
|
["servers.py"]="e77531286b215c6e94cf1f7d28ee640c"
|
||||||
)
|
)
|
||||||
local author="aclist"
|
local author="aclist"
|
||||||
local repo="dztui"
|
local repo="dztui"
|
||||||
@@ -631,7 +631,6 @@ fetch_helpers_by_sum(){
|
|||||||
logger INFO "Updated '$full_path' to sum '$sum'"
|
logger INFO "Updated '$full_path' to sum '$sum'"
|
||||||
fi
|
fi
|
||||||
[[ $file == "funcs" ]] && chmod +x "$full_path"
|
[[ $file == "funcs" ]] && chmod +x "$full_path"
|
||||||
[[ $file == "lan" ]] && chmod +x "$full_path"
|
|
||||||
done
|
done
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -926,7 +925,7 @@ create_config(){
|
|||||||
}
|
}
|
||||||
varcheck(){
|
varcheck(){
|
||||||
local msg="Config file '$config_file' missing. Start first-time setup now?"
|
local msg="Config file '$config_file' missing. Start first-time setup now?"
|
||||||
local msg2="The Steam paths set in your config file appear to be invalid. Restart first-time setup now?"
|
local msg2="The Steam paths set in your config file appear to be invalid (DayZ was moved or uninstalled). Restart first-time setup now?"
|
||||||
if [[ ! -f $config_file ]]; then
|
if [[ ! -f $config_file ]]; then
|
||||||
qdialog "$msg" "Yes" "Exit"
|
qdialog "$msg" "Yes" "Exit"
|
||||||
if [[ $? -eq 1 ]]; then
|
if [[ $? -eq 1 ]]; then
|
||||||
|
|||||||
+22
-8
@@ -114,6 +114,7 @@ declare -A funcs=(
|
|||||||
["get_dist"]="get_dist"
|
["get_dist"]="get_dist"
|
||||||
["test_cooldown"]="test_cooldown"
|
["test_cooldown"]="test_cooldown"
|
||||||
["query_config"]="query_config"
|
["query_config"]="query_config"
|
||||||
|
["query_favorites"]="query_favorites"
|
||||||
["start_cooldown"]="start_cooldown"
|
["start_cooldown"]="start_cooldown"
|
||||||
["List installed mods"]="list_mods"
|
["List installed mods"]="list_mods"
|
||||||
["Delete selected mods"]="delete_local_mod"
|
["Delete selected mods"]="delete_local_mod"
|
||||||
@@ -146,6 +147,12 @@ lan_scan(){
|
|||||||
printf "%s\n" "$res"
|
printf "%s\n" "$res"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
query_favorites(){
|
||||||
|
if [[ -z "${ip_list[@]}" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf "%s\n" "${ip_list[@]}"
|
||||||
|
}
|
||||||
find_stale_mods(){
|
find_stale_mods(){
|
||||||
local res
|
local res
|
||||||
local mods=()
|
local mods=()
|
||||||
@@ -723,8 +730,8 @@ test_cooldown(){
|
|||||||
local old_time=$(< $_cache_cooldown)
|
local old_time=$(< $_cache_cooldown)
|
||||||
local cur_time=$(date +%s)
|
local cur_time=$(date +%s)
|
||||||
local delta=$(($cur_time - $old_time))
|
local delta=$(($cur_time - $old_time))
|
||||||
if [[ $delta -lt 60 ]]; then
|
if [[ $delta -lt 30 ]]; then
|
||||||
local remains=$((60 - $delta))
|
local remains=$((30 - $delta))
|
||||||
local suffix=$(pluralize "seconds" $remains)
|
local suffix=$(pluralize "seconds" $remains)
|
||||||
printf "Global API cooldown in effect. Please wait %s %s." "$remains" "$suffix"
|
printf "Global API cooldown in effect. Please wait %s %s." "$remains" "$suffix"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -752,20 +759,20 @@ dump_servers(){
|
|||||||
case "$subcontext" in
|
case "$subcontext" in
|
||||||
*Server[[:space:]]browser*)
|
*Server[[:space:]]browser*)
|
||||||
local file="$_cache_servers"
|
local file="$_cache_servers"
|
||||||
if [[ ! $subcontext =~ Name ]]; then
|
if [[ ! $context =~ filter ]]; then
|
||||||
initialize_remote_servers
|
initialize_remote_servers
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*My[[:space:]]saved[[:space:]]servers*)
|
*My[[:space:]]saved[[:space:]]servers*)
|
||||||
local file="$_cache_my_servers"
|
local file="$_cache_my_servers"
|
||||||
if [[ ! $subcontext =~ Name ]]; then
|
if [[ ! $context =~ filter ]]; then
|
||||||
[[ -f $file ]] && rm $file
|
[[ -f $file ]] && rm $file
|
||||||
_iterate "$file" "${ip_list[@]}"
|
_iterate "$file" "${ip_list[@]}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*Recent[[:space:]]servers*)
|
*Recent[[:space:]]servers*)
|
||||||
local file="$_cache_history"
|
local file="$_cache_history"
|
||||||
if [[ ! $subcontext =~ Name ]]; then
|
if [[ ! $context =~ filter ]]; then
|
||||||
[[ -f $file ]] && rm $file
|
[[ -f $file ]] && rm $file
|
||||||
readarray -t iters < <(cat $history_file)
|
readarray -t iters < <(cat $history_file)
|
||||||
_iterate "$file" "${iters[@]}"
|
_iterate "$file" "${iters[@]}"
|
||||||
@@ -774,7 +781,7 @@ dump_servers(){
|
|||||||
*Scan[[:space:]]LAN[[:space:]]servers*)
|
*Scan[[:space:]]LAN[[:space:]]servers*)
|
||||||
local port=$(<<< "$subcontext" awk -F: '{print $2}')
|
local port=$(<<< "$subcontext" awk -F: '{print $2}')
|
||||||
local file="$_cache_lan"
|
local file="$_cache_lan"
|
||||||
if [[ ! $subcontext =~ Name ]]; then
|
if [[ ! $context =~ filter ]]; then
|
||||||
[[ -f $file ]] && rm $file
|
[[ -f $file ]] && rm $file
|
||||||
local lan=$(lan_scan $port)
|
local lan=$(lan_scan $port)
|
||||||
readarray -t iters <<< "$lan"
|
readarray -t iters <<< "$lan"
|
||||||
@@ -1095,10 +1102,10 @@ test_steam_api(){
|
|||||||
}
|
}
|
||||||
test_bm_api(){
|
test_bm_api(){
|
||||||
local key="$1"
|
local key="$1"
|
||||||
if [[ ! $key =~ ^[0-9]+$ ]]; then
|
if [[ -z $key ]]; then
|
||||||
|
echo 1
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
[[ -z $key ]] && return 1
|
|
||||||
local code=$(curl -ILs "$bm_api" \
|
local code=$(curl -ILs "$bm_api" \
|
||||||
-H "Authorization: Bearer "$key"" -G \
|
-H "Authorization: Bearer "$key"" -G \
|
||||||
-d "filter[game]=$game" \
|
-d "filter[game]=$game" \
|
||||||
@@ -1112,6 +1119,10 @@ update_config_val(){
|
|||||||
case $1 in
|
case $1 in
|
||||||
"Change player name")
|
"Change player name")
|
||||||
key="name"
|
key="name"
|
||||||
|
if [[ -z "${value// }" ]]; then
|
||||||
|
printf "Invalid name"
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
"Change Steam API key")
|
"Change Steam API key")
|
||||||
key="steam_api"
|
key="steam_api"
|
||||||
@@ -1348,6 +1359,9 @@ update_history(){
|
|||||||
echo "$record" >> "$history_file"
|
echo "$record" >> "$history_file"
|
||||||
}
|
}
|
||||||
update_symlinks(){
|
update_symlinks(){
|
||||||
|
if [[ ! -d "$workshop_dir" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
legacy_symlinks
|
legacy_symlinks
|
||||||
symlinks
|
symlinks
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import typing # noqa
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
sys.path.append("a2s")
|
||||||
|
import a2s # noqa
|
||||||
|
|
||||||
|
params = [
|
||||||
|
r"\nor\1\map\chernarusplus\nor\1\map\sakhal\nor\1\map\enoch\empty\1\nor\1\map\namalsk",
|
||||||
|
r"\map\namalsk\empty\1",
|
||||||
|
r"\map\namalsk\noplayers\1",
|
||||||
|
r"\map\chernarusplus\empty\1",
|
||||||
|
r"\map\chernarusplus\noplayers\1",
|
||||||
|
r"\map\sakhal\empty\1",
|
||||||
|
r"\map\sakhal\noplayers\1",
|
||||||
|
r"\map\enoch\empty\1",
|
||||||
|
r"\map\enoch\noplayers\1",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_netmask() -> str:
|
||||||
|
hostname = os.uname()[1]
|
||||||
|
i = socket.gethostbyname(hostname)
|
||||||
|
netmask = i.rsplit(".", 1)[0]
|
||||||
|
return netmask
|
||||||
|
|
||||||
|
|
||||||
|
def test_ip(suffix: int, port: int) -> dict | None:
|
||||||
|
netmask = get_netmask()
|
||||||
|
hostname = f"{netmask}.{str(suffix)}"
|
||||||
|
ping = ["ping", "-c1", "-i", "0.1", "-w", "1"]
|
||||||
|
output = subprocess.run(ping + [hostname], capture_output=True)
|
||||||
|
if output.returncode == 0:
|
||||||
|
return query_direct(hostname, port)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize(name: str) -> str:
|
||||||
|
name = re.sub(r"\r", r"", name)
|
||||||
|
name = re.sub(r"\n", r"", name)
|
||||||
|
name = re.sub(r"\x01", r"", name)
|
||||||
|
name = re.sub(r"\ufeff", r"", name)
|
||||||
|
name = re.sub(r"(^!\s*)", r"-", name)
|
||||||
|
name = re.sub(r"(^-\s*)", r"", name)
|
||||||
|
name = re.sub(r"(^-)", r"", name)
|
||||||
|
name = re.sub(r"(^\s*)", r"", name)
|
||||||
|
name = re.sub(r"\t", "", name)
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def parse(json: list) -> list:
|
||||||
|
"""
|
||||||
|
Server metadata is underspecified and server operators
|
||||||
|
tend to insert random garbage in the headers. In case
|
||||||
|
sanitization failed, discard malformed rows rather than
|
||||||
|
aborting outright.
|
||||||
|
"""
|
||||||
|
rows = []
|
||||||
|
for row in json:
|
||||||
|
try:
|
||||||
|
name = sanitize(row["name"])
|
||||||
|
if name == "":
|
||||||
|
continue
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for key in [
|
||||||
|
"map",
|
||||||
|
"gametype",
|
||||||
|
"players",
|
||||||
|
"max_players",
|
||||||
|
"addr",
|
||||||
|
"gameport",
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
row[key]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
r = row["gametype"].split(",")
|
||||||
|
if "no3rd" in r:
|
||||||
|
view = "1PP"
|
||||||
|
else:
|
||||||
|
view = "3PP"
|
||||||
|
|
||||||
|
if "external" in r:
|
||||||
|
provider = "Unoffic."
|
||||||
|
else:
|
||||||
|
provider = "Official"
|
||||||
|
|
||||||
|
if "mod" in r:
|
||||||
|
modded = True
|
||||||
|
else:
|
||||||
|
modded = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = row["gametype"].split("lqs")
|
||||||
|
queue = r[1].split(",")[0]
|
||||||
|
except IndexError:
|
||||||
|
queue = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
test_time = re.search(r"[0-9]{2}:[0-9]{2}", row["gametype"])
|
||||||
|
if test_time:
|
||||||
|
time = test_time.group(0)
|
||||||
|
else:
|
||||||
|
time = "Unknown"
|
||||||
|
except AttributeError:
|
||||||
|
time = "Unknown"
|
||||||
|
|
||||||
|
try:
|
||||||
|
ip = row["addr"].split(":")[0] + ":" + str(row["gameport"])
|
||||||
|
qport = row["addr"].split(":")[1]
|
||||||
|
except IndexError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
mapname = row["map"].lower()
|
||||||
|
players = row["players"]
|
||||||
|
max_players = row["max_players"]
|
||||||
|
raw = [
|
||||||
|
name,
|
||||||
|
mapname,
|
||||||
|
view,
|
||||||
|
time,
|
||||||
|
int(players),
|
||||||
|
int(max_players),
|
||||||
|
int(queue),
|
||||||
|
ip,
|
||||||
|
int(qport),
|
||||||
|
provider,
|
||||||
|
modded,
|
||||||
|
]
|
||||||
|
rows.append(raw)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def query_direct(ip: str, qport: int) -> dict | None:
|
||||||
|
try:
|
||||||
|
info = a2s.info((ip, qport))
|
||||||
|
|
||||||
|
name = info.server_name
|
||||||
|
mapname = info.map_name
|
||||||
|
address = ip + ":" + str(qport)
|
||||||
|
gameport = str(info.port)
|
||||||
|
players = info.player_count
|
||||||
|
max_players = info.max_players
|
||||||
|
keywords = info.keywords
|
||||||
|
|
||||||
|
res = {}
|
||||||
|
res["name"] = name
|
||||||
|
res["map"] = mapname
|
||||||
|
res["gametype"] = keywords
|
||||||
|
res["players"] = players
|
||||||
|
res["max_players"] = max_players
|
||||||
|
res["addr"] = address
|
||||||
|
res["gameport"] = gameport
|
||||||
|
return res
|
||||||
|
except TimeoutError:
|
||||||
|
return None
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def query_api(key: str, param: str) -> requests.models.Response:
|
||||||
|
LIMIT = 10000
|
||||||
|
url = "https://api.steampowered.com/IGameServersService/GetServerList/v1/?"
|
||||||
|
|
||||||
|
payload: dict[str, Union[int, str]] = {
|
||||||
|
"filter": r"\appid\221100" + param,
|
||||||
|
"limit": LIMIT,
|
||||||
|
"key": key,
|
||||||
|
}
|
||||||
|
r = requests.get(url, params=payload)
|
||||||
|
return r
|
||||||
+3097
-2004
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user