mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:43:06 +02:00
Merge branch 'main' of https://github.com/kuhyx/testsAndMisc
This commit is contained in:
commit
89a8d57971
@ -311,6 +311,11 @@ sudo sed -i 's/^0\.0\.0\.0 www\.4chan\.org/#0.0.0.0 www.4chan.org/' /etc/hosts
|
|||||||
sudo sed -i 's/^0\.0\.0\.0 www\.facebook\.com/#0.0.0.0 www.facebook.com/' /etc/hosts
|
sudo sed -i 's/^0\.0\.0\.0 www\.facebook\.com/#0.0.0.0 www.facebook.com/' /etc/hosts
|
||||||
sudo sed -i 's/^0\.0\.0\.0 messenger\.com/#0.0.0.0 messenger.com/' /etc/hosts
|
sudo sed -i 's/^0\.0\.0\.0 messenger\.com/#0.0.0.0 messenger.com/' /etc/hosts
|
||||||
|
|
||||||
|
# Allow LinkedIn and all subdomains (linkedin.com + licdn.com CDN)
|
||||||
|
echo "Allowing LinkedIn by commenting out any blocking entries..."
|
||||||
|
sudo sed -i -E 's/^(0\.0\.0\.0[[:space:]]+[a-zA-Z0-9._-]*\.?linkedin\.com)/#\1/' /etc/hosts
|
||||||
|
sudo sed -i -E 's/^(0\.0\.0\.0[[:space:]]+[a-zA-Z0-9._-]*\.?licdn\.com)/#\1/' /etc/hosts
|
||||||
|
|
||||||
# Add custom entries for YouTube and Discord
|
# Add custom entries for YouTube and Discord
|
||||||
echo "Adding custom entries for YouTube and Discord..."
|
echo "Adding custom entries for YouTube and Discord..."
|
||||||
tee -a /etc/hosts >/dev/null <<'EOF'
|
tee -a /etc/hosts >/dev/null <<'EOF'
|
||||||
|
|||||||
@ -262,7 +262,7 @@ _full_adapter_reset_and_connect() {
|
|||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
log_info "Reconnecting to $mac after adapter reset..."
|
log_info "Reconnecting to $mac after adapter reset..."
|
||||||
{ echo "power on"; sleep 1; echo "connect $mac"; sleep 20; } \
|
{ echo "agent on"; echo "default-agent"; sleep 1; echo "power on"; sleep 1; echo "connect $mac"; sleep 20; } \
|
||||||
| bluetoothctl 2>/dev/null || true
|
| bluetoothctl 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +280,7 @@ _verify_audio_sink() {
|
|||||||
# Give PipeWire time to create the audio card
|
# Give PipeWire time to create the audio card
|
||||||
local _attempt
|
local _attempt
|
||||||
for _attempt in 1 2 3 4 5; do
|
for _attempt in 1 2 3 4 5; do
|
||||||
if pactl list cards short 2>/dev/null | grep -q "$card_name"; then
|
if _run_as_user pactl list cards short 2>/dev/null | grep -q "$card_name"; then
|
||||||
log_ok "Bluetooth audio card detected in PipeWire."
|
log_ok "Bluetooth audio card detected in PipeWire."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@ -426,7 +426,7 @@ check_pipewire_health() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Test if PipeWire is responding within 3 seconds
|
# Test if PipeWire is responding within 3 seconds
|
||||||
if timeout 3 wpctl status &>/dev/null; then
|
if timeout 3 _run_as_user wpctl status &>/dev/null; then
|
||||||
log_ok "PipeWire is responsive."
|
log_ok "PipeWire is responsive."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@ -436,8 +436,11 @@ check_pipewire_health() {
|
|||||||
_restart_pipewire_stack
|
_restart_pipewire_stack
|
||||||
}
|
}
|
||||||
|
|
||||||
_restart_pipewire_stack() {
|
# ---------------------------------------------------------------------------
|
||||||
# Restart as the calling user (these are user services)
|
# Helper: run a command as the actual (non-root) user with PipeWire env.
|
||||||
|
# Needed because pactl/wpctl/systemctl --user talk to the user session.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
_run_as_user() {
|
||||||
local target_user
|
local target_user
|
||||||
target_user="${SUDO_USER:-$USER}"
|
target_user="${SUDO_USER:-$USER}"
|
||||||
local target_uid
|
local target_uid
|
||||||
@ -446,7 +449,11 @@ _restart_pipewire_stack() {
|
|||||||
sudo -u "$target_user" \
|
sudo -u "$target_user" \
|
||||||
XDG_RUNTIME_DIR="/run/user/$target_uid" \
|
XDG_RUNTIME_DIR="/run/user/$target_uid" \
|
||||||
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$target_uid/bus" \
|
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$target_uid/bus" \
|
||||||
systemctl --user restart pipewire pipewire-pulse wireplumber
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
_restart_pipewire_stack() {
|
||||||
|
_run_as_user systemctl --user restart pipewire pipewire-pulse wireplumber
|
||||||
|
|
||||||
sleep 3
|
sleep 3
|
||||||
log_info "Waiting for audio stack to initialize..."
|
log_info "Waiting for audio stack to initialize..."
|
||||||
@ -478,7 +485,7 @@ connect_device() {
|
|||||||
# ---- Attempt 1: direct connect (existing pairing) ----
|
# ---- Attempt 1: direct connect (existing pairing) ----
|
||||||
if echo "$info" | grep -q "Paired: yes"; then
|
if echo "$info" | grep -q "Paired: yes"; then
|
||||||
log_info "Device is already paired. Trying direct connect..."
|
log_info "Device is already paired. Trying direct connect..."
|
||||||
{ echo "power on"; sleep 1; echo "connect $TARGET_MAC"; sleep 15; } \
|
{ echo "agent on"; echo "default-agent"; sleep 1; echo "power on"; sleep 1; echo "connect $TARGET_MAC"; sleep 15; } \
|
||||||
| bluetoothctl 2>/dev/null || true
|
| bluetoothctl 2>/dev/null || true
|
||||||
|
|
||||||
if _check_connection_health "$TARGET_MAC"; then
|
if _check_connection_health "$TARGET_MAC"; then
|
||||||
@ -510,7 +517,7 @@ connect_device() {
|
|||||||
|
|
||||||
# Pair
|
# Pair
|
||||||
log_info "Pairing..."
|
log_info "Pairing..."
|
||||||
{ echo "power on"; sleep 1; echo "pair $TARGET_MAC"; sleep 5; } \
|
{ echo "agent on"; echo "default-agent"; sleep 1; echo "power on"; sleep 1; echo "pair $TARGET_MAC"; sleep 5; } \
|
||||||
| bluetoothctl 2>/dev/null || true
|
| bluetoothctl 2>/dev/null || true
|
||||||
|
|
||||||
# Trust (so it auto-reconnects in the future)
|
# Trust (so it auto-reconnects in the future)
|
||||||
@ -519,7 +526,7 @@ connect_device() {
|
|||||||
|
|
||||||
# Connect
|
# Connect
|
||||||
log_info "Connecting..."
|
log_info "Connecting..."
|
||||||
{ echo "power on"; sleep 1; echo "connect $TARGET_MAC"; sleep 15; } \
|
{ echo "agent on"; echo "default-agent"; sleep 1; echo "power on"; sleep 1; echo "connect $TARGET_MAC"; sleep 15; } \
|
||||||
| bluetoothctl 2>/dev/null || true
|
| bluetoothctl 2>/dev/null || true
|
||||||
|
|
||||||
# Verify connection + services + audio
|
# Verify connection + services + audio
|
||||||
@ -595,7 +602,7 @@ set_audio_profile() {
|
|||||||
|
|
||||||
local card_name="bluez_card.${TARGET_MAC//:/_}"
|
local card_name="bluez_card.${TARGET_MAC//:/_}"
|
||||||
local card_info
|
local card_info
|
||||||
card_info=$(pactl list cards 2>/dev/null || true)
|
card_info=$(_run_as_user pactl list cards 2>/dev/null || true)
|
||||||
|
|
||||||
if ! echo "$card_info" | grep -q "$card_name"; then
|
if ! echo "$card_info" | grep -q "$card_name"; then
|
||||||
log_info "No PipeWire audio card found for device (may not be an audio device)."
|
log_info "No PipeWire audio card found for device (may not be an audio device)."
|
||||||
@ -608,7 +615,7 @@ set_audio_profile() {
|
|||||||
if [[ $current_profile == *"sbc_xq"* ]]; then
|
if [[ $current_profile == *"sbc_xq"* ]]; then
|
||||||
log_warn "SBC-XQ codec active — may cause audio dropouts on older adapters."
|
log_warn "SBC-XQ codec active — may cause audio dropouts on older adapters."
|
||||||
apply_fix "Switching to standard SBC codec" \
|
apply_fix "Switching to standard SBC codec" \
|
||||||
pactl set-card-profile "$card_name" a2dp-sink
|
_run_as_user pactl set-card-profile "$card_name" a2dp-sink
|
||||||
elif [[ -n $current_profile ]]; then
|
elif [[ -n $current_profile ]]; then
|
||||||
log_ok "Audio profile: $current_profile"
|
log_ok "Audio profile: $current_profile"
|
||||||
fi
|
fi
|
||||||
@ -693,6 +700,7 @@ main() {
|
|||||||
check_packages
|
check_packages
|
||||||
check_firmware
|
check_firmware
|
||||||
check_adapter_stuck
|
check_adapter_stuck
|
||||||
|
remove_stale_pairing
|
||||||
fix_usb_autosuspend
|
fix_usb_autosuspend
|
||||||
check_pipewire_health
|
check_pipewire_health
|
||||||
restart_bluetooth
|
restart_bluetooth
|
||||||
|
|||||||
@ -79,6 +79,9 @@ com.microsoft.teams
|
|||||||
|
|
||||||
# --- Manga reader ---
|
# --- Manga reader ---
|
||||||
eu.kanade.tachiyomi.sy
|
eu.kanade.tachiyomi.sy
|
||||||
|
|
||||||
|
# --- Development ---
|
||||||
|
com.github.android
|
||||||
"
|
"
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@ -46,6 +46,28 @@ build_sysprotect_file() {
|
|||||||
| sed 's/^[[:space:]]*//;s/[[:space:]]*$//' > "$STATE_DIR/sysprotect.txt"
|
| sed 's/^[[:space:]]*//;s/[[:space:]]*$//' > "$STATE_DIR/sysprotect.txt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reconcile_disabled_apps() {
|
||||||
|
[ -f "$DISABLED_APPS_FILE" ] || return
|
||||||
|
|
||||||
|
local tmp_disabled="$STATE_DIR/disabled_by_focus.tmp"
|
||||||
|
: > "$tmp_disabled"
|
||||||
|
|
||||||
|
while IFS= read -r pkg; do
|
||||||
|
[ -z "$pkg" ] && continue
|
||||||
|
|
||||||
|
if is_allowed "$pkg"; then
|
||||||
|
pm install-existing --user 0 "$pkg" >/dev/null 2>&1 || true
|
||||||
|
pm enable "$pkg" >/dev/null 2>&1 || true
|
||||||
|
log "Re-enabled allowed app during state reconciliation: $pkg"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$pkg" >> "$tmp_disabled"
|
||||||
|
done < "$DISABLED_APPS_FILE"
|
||||||
|
|
||||||
|
mv "$tmp_disabled" "$DISABLED_APPS_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
# ---- Initialization ----
|
# ---- Initialization ----
|
||||||
init() {
|
init() {
|
||||||
mkdir -p "$STATE_DIR"
|
mkdir -p "$STATE_DIR"
|
||||||
@ -66,6 +88,10 @@ init() {
|
|||||||
CURRENT_MODE="normal"
|
CURRENT_MODE="normal"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$CURRENT_MODE" = "focus" ]; then
|
||||||
|
reconcile_disabled_apps
|
||||||
|
fi
|
||||||
|
|
||||||
log "Focus mode daemon started (PID=$$, mode=$CURRENT_MODE, home=$HOME_LAT,$HOME_LON, radius=${RADIUS}m)"
|
log "Focus mode daemon started (PID=$$, mode=$CURRENT_MODE, home=$HOME_LAT,$HOME_LON, radius=${RADIUS}m)"
|
||||||
log "Intervals: focus=${CHECK_INTERVAL_FOCUS}s normal=${CHECK_INTERVAL_NORMAL}s"
|
log "Intervals: focus=${CHECK_INTERVAL_FOCUS}s normal=${CHECK_INTERVAL_NORMAL}s"
|
||||||
}
|
}
|
||||||
@ -116,13 +142,15 @@ is_allowed() {
|
|||||||
# ---- Focus Mode Control ----
|
# ---- Focus Mode Control ----
|
||||||
|
|
||||||
enable_focus_mode() {
|
enable_focus_mode() {
|
||||||
[ "$CURRENT_MODE" = "focus" ] && return
|
if [ "$CURRENT_MODE" = "focus" ]; then
|
||||||
|
reconcile_disabled_apps
|
||||||
|
return
|
||||||
|
fi
|
||||||
log "ENABLING focus mode - restricting non-whitelisted apps"
|
log "ENABLING focus mode - restricting non-whitelisted apps"
|
||||||
|
|
||||||
: > "$DISABLED_APPS_FILE"
|
: > "$DISABLED_APPS_FILE"
|
||||||
local tmp_pkgs="$STATE_DIR/pkg_list.txt"
|
local tmp_pkgs="$STATE_DIR/pkg_list.txt"
|
||||||
pm list packages -3 2>/dev/null | sed 's/^package://' > "$tmp_pkgs"
|
pm list packages -3 2>/dev/null | sed 's/^package://' > "$tmp_pkgs"
|
||||||
|
|
||||||
while IFS= read -r pkg; do
|
while IFS= read -r pkg; do
|
||||||
[ -z "$pkg" ] && continue
|
[ -z "$pkg" ] && continue
|
||||||
is_allowed "$pkg" && continue
|
is_allowed "$pkg" && continue
|
||||||
@ -153,6 +181,8 @@ enable_focus_mode() {
|
|||||||
CURRENT_MODE="focus"
|
CURRENT_MODE="focus"
|
||||||
echo "focus" > "$MODE_FILE"
|
echo "focus" > "$MODE_FILE"
|
||||||
log "Focus mode enabled - disabled $count apps"
|
log "Focus mode enabled - disabled $count apps"
|
||||||
|
|
||||||
|
reconcile_disabled_apps
|
||||||
}
|
}
|
||||||
|
|
||||||
disable_focus_mode() {
|
disable_focus_mode() {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ from http import HTTPStatus
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from howlongtobeatpy.HTMLRequests import HTMLRequests
|
from howlongtobeatpy.HTMLRequests import HTMLRequests
|
||||||
@ -175,6 +176,31 @@ def _build_search_payload(game_name: str) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _pick_best_hltb_entry(
|
||||||
|
search_name: str,
|
||||||
|
candidates: list[tuple[dict[str, Any], float]],
|
||||||
|
) -> tuple[dict[str, Any], float] | None:
|
||||||
|
"""Pick the best HLTB entry, preferring full editions over demos/chapters.
|
||||||
|
|
||||||
|
When a short name like "FAITH" matches both "FAITH" (demo) and
|
||||||
|
"FAITH: The Unholy Trinity" (full game), prefer the full game
|
||||||
|
since Steam often lists the full game under the shorter name.
|
||||||
|
"""
|
||||||
|
if not candidates:
|
||||||
|
return None
|
||||||
|
if len(candidates) == 1:
|
||||||
|
return candidates[0]
|
||||||
|
|
||||||
|
lower = search_name.lower()
|
||||||
|
for entry, sim in candidates:
|
||||||
|
entry_name = (entry.get("game_name") or "").lower()
|
||||||
|
if entry_name.startswith((lower + ":", lower + " -")):
|
||||||
|
return entry, sim
|
||||||
|
|
||||||
|
# Fall back to highest similarity.
|
||||||
|
return max(candidates, key=lambda x: x[1])
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────
|
||||||
# Async fetching with shared session & progress
|
# Async fetching with shared session & progress
|
||||||
# ──────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────
|
||||||
@ -211,6 +237,8 @@ async def _search_one(
|
|||||||
) as resp:
|
) as resp:
|
||||||
if resp.status == HTTPStatus.OK:
|
if resp.status == HTTPStatus.OK:
|
||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
|
candidates: list[tuple[dict[str, Any], float]] = []
|
||||||
|
lower_name = name.lower()
|
||||||
for entry in data.get("data", []):
|
for entry in data.get("data", []):
|
||||||
entry_name = entry.get("game_name", "")
|
entry_name = entry.get("game_name", "")
|
||||||
entry_alias = entry.get("game_alias", "") or ""
|
entry_alias = entry.get("game_alias", "") or ""
|
||||||
@ -218,18 +246,24 @@ async def _search_one(
|
|||||||
_similarity(name, entry_name),
|
_similarity(name, entry_name),
|
||||||
_similarity(name, entry_alias),
|
_similarity(name, entry_alias),
|
||||||
)
|
)
|
||||||
if sim >= MIN_SIMILARITY:
|
is_full_edition = entry_name.lower().startswith(
|
||||||
|
lower_name + ":"
|
||||||
|
) or entry_name.lower().startswith(lower_name + " -")
|
||||||
|
if sim >= MIN_SIMILARITY or is_full_edition:
|
||||||
comp_100 = entry.get("comp_100", 0)
|
comp_100 = entry.get("comp_100", 0)
|
||||||
if comp_100 and comp_100 > 0:
|
if comp_100 and comp_100 > 0:
|
||||||
hours = round(comp_100 / 3600, 2)
|
candidates.append((entry, sim))
|
||||||
result = HLTBResult(
|
best = _pick_best_hltb_entry(name, candidates)
|
||||||
app_id=app_id,
|
if best is not None:
|
||||||
game_name=name,
|
entry, sim = best
|
||||||
completionist_hours=hours,
|
hours = round(entry["comp_100"] / 3600, 2)
|
||||||
similarity=sim,
|
result = HLTBResult(
|
||||||
hltb_game_id=entry.get("game_id", 0),
|
app_id=app_id,
|
||||||
)
|
game_name=name,
|
||||||
break
|
completionist_hours=hours,
|
||||||
|
similarity=sim,
|
||||||
|
hltb_game_id=entry.get("game_id", 0),
|
||||||
|
)
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError) as exc:
|
except (aiohttp.ClientError, asyncio.TimeoutError) as exc:
|
||||||
logger.debug("HLTB search failed for '%s': %s", name, exc)
|
logger.debug("HLTB search failed for '%s': %s", name, exc)
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ from python_pkg.steam_backlog_enforcer.enforcer import (
|
|||||||
)
|
)
|
||||||
from python_pkg.steam_backlog_enforcer.hltb import (
|
from python_pkg.steam_backlog_enforcer.hltb import (
|
||||||
fetch_hltb_times_cached,
|
fetch_hltb_times_cached,
|
||||||
|
load_hltb_cache,
|
||||||
)
|
)
|
||||||
from python_pkg.steam_backlog_enforcer.library_hider import (
|
from python_pkg.steam_backlog_enforcer.library_hider import (
|
||||||
hide_other_games,
|
hide_other_games,
|
||||||
@ -891,27 +892,6 @@ def cmd_list(_config: Config, state: State) -> None:
|
|||||||
_echo(f"\n COMPLETE: {len(complete)} games")
|
_echo(f"\n COMPLETE: {len(complete)} games")
|
||||||
|
|
||||||
|
|
||||||
def cmd_skip(config: Config, state: State) -> None:
|
|
||||||
"""Skip the currently assigned game."""
|
|
||||||
if state.current_app_id is None:
|
|
||||||
_echo("No game currently assigned.")
|
|
||||||
return
|
|
||||||
|
|
||||||
_echo(f"Skipping: {state.current_game_name}")
|
|
||||||
config.skip_app_ids.append(state.current_app_id)
|
|
||||||
config.save()
|
|
||||||
|
|
||||||
snapshot = load_snapshot()
|
|
||||||
if snapshot:
|
|
||||||
games = [GameInfo.from_snapshot(d) for d in snapshot]
|
|
||||||
pick_next_game(games, state, config)
|
|
||||||
else:
|
|
||||||
state.current_app_id = None
|
|
||||||
state.current_game_name = ""
|
|
||||||
state.save()
|
|
||||||
_echo("Run 'scan' to pick a new game.")
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_unblock(_config: Config, _state: State) -> None:
|
def cmd_unblock(_config: Config, _state: State) -> None:
|
||||||
"""Remove store blocking."""
|
"""Remove store blocking."""
|
||||||
if unblock_store():
|
if unblock_store():
|
||||||
@ -1083,6 +1063,79 @@ def cmd_unhide(config: Config, _state: State) -> None:
|
|||||||
_echo("Done!")
|
_echo("Done!")
|
||||||
|
|
||||||
|
|
||||||
|
def _try_reassign_shorter_game(
|
||||||
|
hltb_cache: dict[int, float],
|
||||||
|
app_id: int,
|
||||||
|
hours: float,
|
||||||
|
state: State,
|
||||||
|
config: Config,
|
||||||
|
) -> bool:
|
||||||
|
"""Check if a shorter game is available and reassign if so."""
|
||||||
|
snapshot_data = load_snapshot()
|
||||||
|
if not snapshot_data:
|
||||||
|
return False
|
||||||
|
all_games = [GameInfo.from_snapshot(d) for d in snapshot_data]
|
||||||
|
for g in all_games:
|
||||||
|
cached_hours = hltb_cache.get(g.app_id, -1.0)
|
||||||
|
if cached_hours > 0:
|
||||||
|
g.completionist_hours = cached_hours
|
||||||
|
skip = set(config.skip_app_ids) | set(state.finished_app_ids)
|
||||||
|
candidates = [
|
||||||
|
g
|
||||||
|
for g in all_games
|
||||||
|
if not g.is_complete and g.app_id not in skip and g.completionist_hours > 0
|
||||||
|
]
|
||||||
|
candidates.sort(key=lambda g: g.completionist_hours)
|
||||||
|
if not candidates or candidates[0].app_id == app_id:
|
||||||
|
return False
|
||||||
|
shortest = candidates[0]
|
||||||
|
_echo(
|
||||||
|
f"\n Reassigning: {shortest.name} is shorter"
|
||||||
|
f" (~{shortest.completionist_hours:.1f}h vs ~{hours:.1f}h)"
|
||||||
|
)
|
||||||
|
pick_next_game(all_games, state, config)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _finalize_completion(
|
||||||
|
config: Config,
|
||||||
|
state: State,
|
||||||
|
game_name: str,
|
||||||
|
app_id: int,
|
||||||
|
) -> None:
|
||||||
|
"""Mark game complete, pick next, hide non-assigned games, notify."""
|
||||||
|
_echo(f"\n COMPLETED: {game_name}!")
|
||||||
|
state.finished_app_ids.append(app_id)
|
||||||
|
|
||||||
|
snapshot_data = load_snapshot()
|
||||||
|
_echo("\nPicking next game...")
|
||||||
|
if not snapshot_data:
|
||||||
|
_echo(" No snapshot found. Run 'scan' first.")
|
||||||
|
state.current_app_id = None
|
||||||
|
state.current_game_name = ""
|
||||||
|
state.save()
|
||||||
|
return
|
||||||
|
|
||||||
|
games = [GameInfo.from_snapshot(d) for d in snapshot_data]
|
||||||
|
pick_next_game(games, state, config)
|
||||||
|
|
||||||
|
if state.current_app_id is None:
|
||||||
|
_echo(" No more games to assign!")
|
||||||
|
return
|
||||||
|
|
||||||
|
owned_ids = _get_all_owned_app_ids(config)
|
||||||
|
if owned_ids:
|
||||||
|
hidden = hide_other_games(owned_ids, state.current_app_id)
|
||||||
|
if hidden > 0:
|
||||||
|
_echo(f"\n Library: hid {hidden} games")
|
||||||
|
|
||||||
|
send_notification(
|
||||||
|
"Game Complete!",
|
||||||
|
f"Finished {game_name}! Now playing: {state.current_game_name}",
|
||||||
|
)
|
||||||
|
_echo(f"\nAll done! Go play {state.current_game_name}!")
|
||||||
|
|
||||||
|
|
||||||
def cmd_done(config: Config, state: State) -> None:
|
def cmd_done(config: Config, state: State) -> None:
|
||||||
"""Check completion, pick next game, uninstall & hide.
|
"""Check completion, pick next game, uninstall & hide.
|
||||||
|
|
||||||
@ -1112,44 +1165,23 @@ def cmd_done(config: Config, state: State) -> None:
|
|||||||
f" ({game.completion_pct:.1f}%)"
|
f" ({game.completion_pct:.1f}%)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hltb_cache = load_hltb_cache()
|
||||||
|
hours = hltb_cache.get(app_id, -1.0)
|
||||||
|
if hours < 0:
|
||||||
|
hltb_cache = fetch_hltb_times_cached([(app_id, game_name)])
|
||||||
|
hours = hltb_cache.get(app_id, -1.0)
|
||||||
|
if hours > 0:
|
||||||
|
_echo(f" HLTB 100% estimate: {hours:.1f} hours")
|
||||||
|
|
||||||
|
if _try_reassign_shorter_game(hltb_cache, app_id, hours, state, config):
|
||||||
|
return
|
||||||
|
|
||||||
if not game.is_complete:
|
if not game.is_complete:
|
||||||
remaining = game.total_achievements - game.unlocked_achievements
|
remaining = game.total_achievements - game.unlocked_achievements
|
||||||
_echo(f"\n NOT COMPLETE: {remaining} achievements remaining. Keep going!")
|
_echo(f"\n NOT COMPLETE: {remaining} achievements remaining. Keep going!")
|
||||||
return
|
return
|
||||||
|
|
||||||
# ── Step 1: Mark complete ──
|
_finalize_completion(config, state, game_name, app_id)
|
||||||
_echo(f"\n COMPLETED: {game_name}!")
|
|
||||||
state.finished_app_ids.append(app_id)
|
|
||||||
|
|
||||||
# ── Step 2: Pick next game ──
|
|
||||||
snapshot_data = load_snapshot()
|
|
||||||
_echo("\nPicking next game...")
|
|
||||||
if not snapshot_data:
|
|
||||||
_echo(" No snapshot found. Run 'scan' first.")
|
|
||||||
state.current_app_id = None
|
|
||||||
state.current_game_name = ""
|
|
||||||
state.save()
|
|
||||||
return
|
|
||||||
|
|
||||||
games = [GameInfo.from_snapshot(d) for d in snapshot_data]
|
|
||||||
pick_next_game(games, state, config)
|
|
||||||
|
|
||||||
if state.current_app_id is None:
|
|
||||||
_echo(" No more games to assign!")
|
|
||||||
return
|
|
||||||
|
|
||||||
# ── Step 3: Hide non-assigned games in library ──
|
|
||||||
owned_ids = _get_all_owned_app_ids(config)
|
|
||||||
if owned_ids:
|
|
||||||
hidden = hide_other_games(owned_ids, state.current_app_id)
|
|
||||||
if hidden > 0:
|
|
||||||
_echo(f"\n Library: hid {hidden} games")
|
|
||||||
|
|
||||||
send_notification(
|
|
||||||
"Game Complete!",
|
|
||||||
f"Finished {game_name}! Now playing: {state.current_game_name}",
|
|
||||||
)
|
|
||||||
_echo(f"\nAll done! Go play {state.current_game_name}!")
|
|
||||||
|
|
||||||
|
|
||||||
COMMANDS = {
|
COMMANDS = {
|
||||||
@ -1157,7 +1189,6 @@ COMMANDS = {
|
|||||||
"check": ("Check assigned game completion", do_check),
|
"check": ("Check assigned game completion", do_check),
|
||||||
"status": ("Show current status", cmd_status),
|
"status": ("Show current status", cmd_status),
|
||||||
"list": ("List games from snapshot", cmd_list),
|
"list": ("List games from snapshot", cmd_list),
|
||||||
"skip": ("Skip currently assigned game", cmd_skip),
|
|
||||||
"enforce": ("Run enforcer: block, uninstall, kill, hide", do_enforce),
|
"enforce": ("Run enforcer: block, uninstall, kill, hide", do_enforce),
|
||||||
"install": ("Install the assigned game", cmd_install),
|
"install": ("Install the assigned game", cmd_install),
|
||||||
"hide": ("Hide all non-assigned games in library", cmd_hide),
|
"hide": ("Hide all non-assigned games in library", cmd_hide),
|
||||||
|
|||||||
@ -117,6 +117,11 @@ class SteamAPIClient:
|
|||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.steam_id = steam_id
|
self.steam_id = steam_id
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
|
adapter = requests.adapters.HTTPAdapter(
|
||||||
|
pool_maxsize=MAX_WORKERS,
|
||||||
|
pool_connections=MAX_WORKERS,
|
||||||
|
)
|
||||||
|
self.session.mount("https://", adapter)
|
||||||
self.session.headers["Accept"] = "application/json"
|
self.session.headers["Accept"] = "application/json"
|
||||||
self._rate_lock = threading.Lock()
|
self._rate_lock = threading.Lock()
|
||||||
self._request_times: list[float] = []
|
self._request_times: list[float] = []
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user