forked from FOSS/Steam-Panel
v3.0.6
This commit is contained in:
+211
-42
@@ -1,5 +1,8 @@
|
||||
import asyncio
|
||||
import json
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -13,6 +16,93 @@ STEAM_DOMAINS = [
|
||||
".login.steampowered.com",
|
||||
]
|
||||
|
||||
# Common Chrome/Chromium install locations on Windows
|
||||
_WINDOWS_CHROME_PATHS = [
|
||||
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
|
||||
r"C:\Program Files\Google\Chrome Beta\Application\chrome.exe",
|
||||
r"C:\Program Files\Google\Chrome Dev\Application\chrome.exe",
|
||||
r"C:\Program Files\Chromium\Application\chrome.exe",
|
||||
r"C:\Program Files (x86)\Chromium\Application\chrome.exe",
|
||||
]
|
||||
|
||||
|
||||
def _find_chrome() -> str | None:
|
||||
"""Try to locate a Chrome/Chromium binary. Returns path or None."""
|
||||
# 1. Check PATH
|
||||
for name in ("chrome", "chromium", "chromium-browser", "google-chrome"):
|
||||
found = shutil.which(name)
|
||||
if found:
|
||||
return found
|
||||
|
||||
# 2. Windows-specific fixed paths + Registry
|
||||
if sys.platform == "win32":
|
||||
import os
|
||||
|
||||
local = os.environ.get("LOCALAPPDATA", "")
|
||||
user_path = Path(local) / "Google" / "Chrome" / "Application" / "chrome.exe"
|
||||
candidates = _WINDOWS_CHROME_PATHS + ([str(user_path)] if local else [])
|
||||
for p in candidates:
|
||||
if Path(p).exists():
|
||||
return p
|
||||
|
||||
# Registry lookup (handles non-standard install paths)
|
||||
try:
|
||||
import winreg
|
||||
|
||||
reg_keys = [
|
||||
(
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe",
|
||||
),
|
||||
(
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe",
|
||||
),
|
||||
]
|
||||
for hive, key_path in reg_keys:
|
||||
try:
|
||||
with winreg.OpenKey(hive, key_path) as key:
|
||||
path, _ = winreg.QueryValueEx(key, "")
|
||||
if path and Path(path).exists():
|
||||
return path
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
async def _wait_connection_closed(browser) -> None:
|
||||
"""Fallback: wait until CDP connection closes (with grace period)."""
|
||||
await asyncio.sleep(3)
|
||||
consecutive_closed = 0
|
||||
tick = 0
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
tick += 1
|
||||
try:
|
||||
closed = not browser or not browser.connection or browser.connection.closed
|
||||
except Exception as e:
|
||||
logger.debug(f"[browser] connection check error at tick={tick}: {e}")
|
||||
closed = True
|
||||
|
||||
if tick % 5 == 0:
|
||||
logger.debug(
|
||||
f"[browser] fallback tick={tick} closed={closed} consecutive={consecutive_closed}"
|
||||
)
|
||||
|
||||
if closed:
|
||||
consecutive_closed += 1
|
||||
logger.debug(
|
||||
f"[browser] connection closed tick={tick}, consecutive={consecutive_closed}"
|
||||
)
|
||||
if consecutive_closed >= 3:
|
||||
break
|
||||
else:
|
||||
consecutive_closed = 0
|
||||
|
||||
|
||||
async def open_browser_with_cookies(account: dict) -> None:
|
||||
"""Open a Chrome browser with Steam session cookies using nodriver."""
|
||||
@@ -24,55 +114,134 @@ async def open_browser_with_cookies(account: dict) -> None:
|
||||
|
||||
cookies = json.loads(cookies_json)
|
||||
|
||||
browser = await uc.start()
|
||||
# Try to find Chrome ourselves so we can give a better error
|
||||
chrome_path = _find_chrome()
|
||||
if chrome_path:
|
||||
logger.debug(f"[browser] Using Chrome at: {chrome_path}")
|
||||
else:
|
||||
logger.warning(
|
||||
"[browser] Chrome not found in standard locations — "
|
||||
"nodriver will attempt its own detection"
|
||||
)
|
||||
|
||||
try:
|
||||
if chrome_path:
|
||||
browser = await uc.start(browser_executable_path=chrome_path)
|
||||
else:
|
||||
browser = await uc.start()
|
||||
except Exception as exc:
|
||||
exc_str = str(exc).lower()
|
||||
if (
|
||||
"chrome" in exc_str
|
||||
or "binary" in exc_str
|
||||
or "executable" in exc_str
|
||||
or "not found" in exc_str
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Chrome не найден. Установите Google Chrome с https://www.google.com/chrome/ "
|
||||
f"(искали в: {chrome_path or 'стандартные пути'})"
|
||||
) from exc
|
||||
raise RuntimeError(f"Не удалось запустить браузер: {exc}") from exc
|
||||
|
||||
_active_browsers.append(browser)
|
||||
|
||||
tab = await browser.get("about:blank")
|
||||
|
||||
for c in cookies:
|
||||
domain = c.get("domain") or ".steamcommunity.com"
|
||||
domains_to_set = [domain]
|
||||
if "steam" in domain:
|
||||
domains_to_set = list(set([domain] + STEAM_DOMAINS))
|
||||
|
||||
for d in domains_to_set:
|
||||
try:
|
||||
await tab.send(uc.cdp.network.set_cookie(
|
||||
name=c["name"],
|
||||
value=c["value"],
|
||||
domain=d,
|
||||
path=c.get("path") or "/",
|
||||
secure=c.get("secure", True),
|
||||
http_only=c.get("httpOnly", False),
|
||||
))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
steam_id = account.get("steam_id") or ""
|
||||
if steam_id:
|
||||
start_url = f"https://steamcommunity.com/profiles/{steam_id}/"
|
||||
else:
|
||||
start_url = "https://steamcommunity.com/"
|
||||
await browser.get(start_url)
|
||||
logger.info(f"Browser opened for {account['login']}")
|
||||
|
||||
# Wait until browser is closed by user, suppress connection errors
|
||||
try:
|
||||
while browser:
|
||||
await asyncio.sleep(1)
|
||||
tab = await browser.get("about:blank")
|
||||
|
||||
for c in cookies:
|
||||
domain = c.get("domain") or ".steamcommunity.com"
|
||||
domains_to_set = [domain]
|
||||
if "steam" in domain:
|
||||
domains_to_set = list(set([domain] + STEAM_DOMAINS))
|
||||
|
||||
for d in domains_to_set:
|
||||
try:
|
||||
await tab.send(
|
||||
uc.cdp.network.set_cookie(
|
||||
name=c["name"],
|
||||
value=c["value"],
|
||||
domain=d,
|
||||
path=c.get("path") or "/",
|
||||
secure=c.get("secure", True),
|
||||
http_only=c.get("httpOnly", False),
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
steam_id = account.get("steam_id") or ""
|
||||
start_url = (
|
||||
f"https://steamcommunity.com/profiles/{steam_id}/"
|
||||
if steam_id
|
||||
else "https://steamcommunity.com/"
|
||||
)
|
||||
await browser.get(start_url)
|
||||
logger.info(f"[browser] Opened for {account.get('login', account.get('id'))}")
|
||||
|
||||
# Wait until the Chrome process exits.
|
||||
# We check the process PID directly — this is immune to CDP
|
||||
# connection state fluctuations during page navigation.
|
||||
await asyncio.sleep(2) # let Chrome fully start
|
||||
|
||||
pid = getattr(browser, "_process_pid", None) or getattr(
|
||||
getattr(browser, "_process", None), "pid", None
|
||||
)
|
||||
logger.debug(f"[browser] monitoring Chrome: pid={pid}")
|
||||
|
||||
if pid:
|
||||
try:
|
||||
if not browser.connection or browser.connection.closed:
|
||||
break
|
||||
except Exception:
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
import psutil # type: ignore
|
||||
|
||||
proc = psutil.Process(pid)
|
||||
logger.debug(
|
||||
f"[browser] Chrome pid={pid} status={proc.status()} — waiting for user to close"
|
||||
)
|
||||
tick = 0
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
tick += 1
|
||||
try:
|
||||
running = proc.is_running()
|
||||
status = proc.status()
|
||||
except psutil.NoSuchProcess:
|
||||
logger.debug(
|
||||
f"[browser] Chrome pid={pid} — process no longer exists (tick={tick})"
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"[browser] Chrome pid={pid} — psutil error (tick={tick}): {e}"
|
||||
)
|
||||
break
|
||||
if tick % 5 == 0:
|
||||
conn_closed = getattr(
|
||||
getattr(browser, "connection", None), "closed", "?"
|
||||
)
|
||||
logger.debug(
|
||||
f"[browser] Chrome pid={pid} alive tick={tick} status={status} conn.closed={conn_closed}"
|
||||
)
|
||||
if not running or status == psutil.STATUS_ZOMBIE:
|
||||
logger.debug(
|
||||
f"[browser] Chrome pid={pid} exited at tick={tick}, final status={status}"
|
||||
)
|
||||
break
|
||||
except ImportError:
|
||||
logger.debug(
|
||||
"[browser] psutil not installed — falling back to connection check"
|
||||
)
|
||||
await _wait_connection_closed(browser)
|
||||
except Exception as e:
|
||||
logger.debug(f"[browser] pid monitoring error: {e}")
|
||||
else:
|
||||
logger.debug(
|
||||
"[browser] could not get Chrome PID — falling back to connection check"
|
||||
)
|
||||
await _wait_connection_closed(browser)
|
||||
|
||||
finally:
|
||||
if browser in _active_browsers:
|
||||
_active_browsers.remove(browser)
|
||||
|
||||
# Temporarily suppress nodriver's internal task errors during cleanup
|
||||
# (Browser.update_targets() raises ConnectionRefusedError after close)
|
||||
loop = asyncio.get_running_loop()
|
||||
_original_handler = loop.get_exception_handler()
|
||||
|
||||
@@ -93,4 +262,4 @@ async def open_browser_with_cookies(account: dict) -> None:
|
||||
await asyncio.sleep(0.5)
|
||||
loop.set_exception_handler(_original_handler)
|
||||
|
||||
logger.debug(f"Browser closed for {account['login']}")
|
||||
logger.debug(f"[browser] Closed for {account.get('login', account.get('id'))}")
|
||||
|
||||
Reference in New Issue
Block a user