chore: 6.0.0 beta prerelease

This commit is contained in:
aclist 2025-07-26 00:37:59 +09:00
parent 6d7b2a1d19
commit bc712e926d
5 changed files with 3339 additions and 2019 deletions

View File

@ -1,5 +1,40 @@
# 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
## Added
- Filter servers by official/unofficial status

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -o pipefail
version=5.8.0-beta.2
version=6.0.0.beta-1
#CONSTANTS
aid=221100
@ -585,11 +585,11 @@ fetch_helpers_by_sum(){
[[ -f "$config_file" ]] && source "$config_file"
declare -A sums
sums=(
["ui.py"]="f128a97e744e9e11036d707198feb8a8"
["ui.py"]="a68319e007172b36f7b4e4eaf0b03aab"
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
["funcs"]="94287c75a5ae0a06a95dd439711e6fba"
["lan"]="c62e84ddd1457b71a85ad21da662b9af"
["funcs"]="5d244114c501754a630988354306e470"
["servers.py"]="e77531286b215c6e94cf1f7d28ee640c"
)
local author="aclist"
local repo="dztui"
@ -631,7 +631,6 @@ fetch_helpers_by_sum(){
logger INFO "Updated '$full_path' to sum '$sum'"
fi
[[ $file == "funcs" ]] && chmod +x "$full_path"
[[ $file == "lan" ]] && chmod +x "$full_path"
done
return 0
}
@ -926,7 +925,7 @@ create_config(){
}
varcheck(){
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
qdialog "$msg" "Yes" "Exit"
if [[ $? -eq 1 ]]; then

View File

@ -114,6 +114,7 @@ declare -A funcs=(
["get_dist"]="get_dist"
["test_cooldown"]="test_cooldown"
["query_config"]="query_config"
["query_favorites"]="query_favorites"
["start_cooldown"]="start_cooldown"
["List installed mods"]="list_mods"
["Delete selected mods"]="delete_local_mod"
@ -146,6 +147,12 @@ lan_scan(){
printf "%s\n" "$res"
fi
}
query_favorites(){
if [[ -z "${ip_list[@]}" ]]; then
return 1
fi
printf "%s\n" "${ip_list[@]}"
}
find_stale_mods(){
local res
local mods=()
@ -723,8 +730,8 @@ test_cooldown(){
local old_time=$(< $_cache_cooldown)
local cur_time=$(date +%s)
local delta=$(($cur_time - $old_time))
if [[ $delta -lt 60 ]]; then
local remains=$((60 - $delta))
if [[ $delta -lt 30 ]]; then
local remains=$((30 - $delta))
local suffix=$(pluralize "seconds" $remains)
printf "Global API cooldown in effect. Please wait %s %s." "$remains" "$suffix"
exit 1
@ -752,20 +759,20 @@ dump_servers(){
case "$subcontext" in
*Server[[:space:]]browser*)
local file="$_cache_servers"
if [[ ! $subcontext =~ Name ]]; then
if [[ ! $context =~ filter ]]; then
initialize_remote_servers
fi
;;
*My[[:space:]]saved[[:space:]]servers*)
local file="$_cache_my_servers"
if [[ ! $subcontext =~ Name ]]; then
if [[ ! $context =~ filter ]]; then
[[ -f $file ]] && rm $file
_iterate "$file" "${ip_list[@]}"
fi
;;
*Recent[[:space:]]servers*)
local file="$_cache_history"
if [[ ! $subcontext =~ Name ]]; then
if [[ ! $context =~ filter ]]; then
[[ -f $file ]] && rm $file
readarray -t iters < <(cat $history_file)
_iterate "$file" "${iters[@]}"
@ -774,7 +781,7 @@ dump_servers(){
*Scan[[:space:]]LAN[[:space:]]servers*)
local port=$(<<< "$subcontext" awk -F: '{print $2}')
local file="$_cache_lan"
if [[ ! $subcontext =~ Name ]]; then
if [[ ! $context =~ filter ]]; then
[[ -f $file ]] && rm $file
local lan=$(lan_scan $port)
readarray -t iters <<< "$lan"
@ -1095,10 +1102,10 @@ test_steam_api(){
}
test_bm_api(){
local key="$1"
if [[ ! $key =~ ^[0-9]+$ ]]; then
if [[ -z $key ]]; then
echo 1
return
fi
[[ -z $key ]] && return 1
local code=$(curl -ILs "$bm_api" \
-H "Authorization: Bearer "$key"" -G \
-d "filter[game]=$game" \
@ -1112,6 +1119,10 @@ update_config_val(){
case $1 in
"Change player name")
key="name"
if [[ -z "${value// }" ]]; then
printf "Invalid name"
return 2
fi
;;
"Change Steam API key")
key="steam_api"
@ -1348,6 +1359,9 @@ update_history(){
echo "$record" >> "$history_file"
}
update_symlinks(){
if [[ ! -d "$workshop_dir" ]]; then
return
fi
legacy_symlinks
symlinks
}

179
helpers/servers.py Normal file
View File

@ -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

File diff suppressed because it is too large Load Diff