forked from FOSS/Steam-Panel
v 3.0.3
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
from loguru import logger
|
||||
|
||||
from app.core.exceptions import SteamAuthError
|
||||
from app.core.proxy_manager import proxy_manager
|
||||
from pysteamauth.base.request import BaseRequestStrategy, DEFAULT_REQUEST_TIMEOUT
|
||||
|
||||
|
||||
class ProxyRequestStrategy(BaseRequestStrategy):
|
||||
"""Request strategy that routes traffic through a proxy connector."""
|
||||
|
||||
def __init__(self, connector: aiohttp.BaseConnector):
|
||||
super().__init__()
|
||||
self._proxy_connector = connector
|
||||
|
||||
def _create_session(self) -> ClientSession:
|
||||
return ClientSession(
|
||||
connector=self._proxy_connector,
|
||||
timeout=DEFAULT_REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
|
||||
def read_mafile_data(account: dict) -> dict | None:
|
||||
"""Read and parse the account's mafile JSON from disk."""
|
||||
mafile_path = account.get("mafile_path")
|
||||
if not mafile_path:
|
||||
return None
|
||||
try:
|
||||
return json.loads(Path(mafile_path).read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
async def _resolve_proxy(account: dict) -> dict | None:
|
||||
"""Determine proxy dict {address, protocol} for the account."""
|
||||
proxy_address = account.get("proxy")
|
||||
if proxy_address:
|
||||
from app.database import get_db
|
||||
db = await get_db()
|
||||
cursor = await db.execute(
|
||||
"SELECT protocol FROM proxies WHERE address = ?", (proxy_address,)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
protocol = row["protocol"] if row else "http"
|
||||
return {"address": proxy_address, "protocol": protocol}
|
||||
if proxy_manager.count > 0:
|
||||
return proxy_manager.get_next()
|
||||
return None
|
||||
|
||||
|
||||
async def create_steam_session(account: dict):
|
||||
"""Create an authenticated Steam session for an account."""
|
||||
try:
|
||||
from pysteamauth.auth import Steam
|
||||
except ImportError:
|
||||
raise SteamAuthError("pysteamauth not installed: pip install pysteamauth")
|
||||
|
||||
proxy = await _resolve_proxy(account)
|
||||
request_strategy = None
|
||||
if proxy:
|
||||
connector = proxy_manager.get_connector(proxy)
|
||||
request_strategy = ProxyRequestStrategy(connector)
|
||||
logger.debug(f"Using proxy {proxy['protocol']}://{proxy['address']} for {account['login']}")
|
||||
|
||||
mafile = read_mafile_data(account)
|
||||
|
||||
shared_secret = account.get("shared_secret", "")
|
||||
identity_secret = account.get("identity_secret", "")
|
||||
device_id = None
|
||||
steamid = None
|
||||
|
||||
if mafile:
|
||||
shared_secret = mafile.get("shared_secret", shared_secret)
|
||||
identity_secret = mafile.get("identity_secret", identity_secret)
|
||||
device_id = mafile.get("device_id")
|
||||
steamid = int(mafile.get("Session", {}).get("SteamID", 0)) or None
|
||||
|
||||
steam = Steam(
|
||||
login=account["login"],
|
||||
password=account["password"],
|
||||
shared_secret=shared_secret,
|
||||
identity_secret=identity_secret,
|
||||
device_id=device_id,
|
||||
steamid=steamid,
|
||||
request_strategy=request_strategy,
|
||||
)
|
||||
|
||||
try:
|
||||
await steam.login_to_steam()
|
||||
except Exception as exc:
|
||||
await close_steam(steam)
|
||||
exc_str = str(exc)
|
||||
# Pydantic ValidationError from pysteamauth = Steam rejected login
|
||||
if "validation error" in exc_str.lower() and "FinalizeLoginStatus" in exc_str:
|
||||
# Extract the raw response dict from the error
|
||||
m = __import__("re").search(r"input_value=(\{[^}]+\})", exc_str)
|
||||
raw = m.group(1) if m else ""
|
||||
logger.error(f"Auth failed for {account['login']}: Steam rejected login: {raw}")
|
||||
raise SteamAuthError(f"Login failed: Steam rejected credentials ({raw})")
|
||||
logger.error(f"Auth failed for {account['login']}: {exc}")
|
||||
raise SteamAuthError(f"Login failed: {exc}")
|
||||
|
||||
return steam
|
||||
|
||||
|
||||
async def close_steam(steam) -> None:
|
||||
"""Close the underlying aiohttp session and proxy connector of a pysteamauth Steam object."""
|
||||
requests = getattr(steam, "_requests", None)
|
||||
if requests is None:
|
||||
return
|
||||
session = getattr(requests, "_session", None)
|
||||
if session is not None and not getattr(session, "closed", True):
|
||||
try:
|
||||
await session.close()
|
||||
except Exception:
|
||||
pass
|
||||
requests._session = None
|
||||
|
||||
connector = getattr(requests, "_proxy_connector", None)
|
||||
if connector is not None and not getattr(connector, "closed", True):
|
||||
try:
|
||||
await connector.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def extract_session_cookies(steam) -> str | None:
|
||||
"""Extract session cookies from a pysteamauth Steam object as JSON string.
|
||||
|
||||
Returns JSON string of cookies list, or None if extraction fails.
|
||||
"""
|
||||
try:
|
||||
_req = getattr(steam, "_requests", None)
|
||||
_sess = getattr(_req, "_session", None) if _req else None
|
||||
if not _sess or not hasattr(_sess, "cookie_jar"):
|
||||
return None
|
||||
all_cookies = []
|
||||
for c in _sess.cookie_jar:
|
||||
all_cookies.append({
|
||||
"name": c.key,
|
||||
"value": c.value,
|
||||
"domain": c.get("domain", ""),
|
||||
"path": c.get("path", "/"),
|
||||
"secure": str(c.get("secure", "")).lower() == "true" or c.get("secure") is True,
|
||||
"httpOnly": str(c.get("httponly", "")).lower() == "true" or c.get("httponly") is True,
|
||||
})
|
||||
return json.dumps(all_cookies)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def check_cookies_alive(cookies_json: str, proxy: dict | None = None) -> bool:
|
||||
"""Check if Steam session cookies are still valid.
|
||||
|
||||
Makes a lightweight request to Steam's clientjstoken endpoint.
|
||||
Returns True if logged in, False otherwise.
|
||||
"""
|
||||
try:
|
||||
cookies = json.loads(cookies_json)
|
||||
jar = aiohttp.CookieJar(unsafe=True)
|
||||
for c in cookies:
|
||||
jar.update_cookies(
|
||||
{c["name"]: c["value"]},
|
||||
response_url=__import__("yarl").URL(f"https://{c.get('domain', '.steamcommunity.com').lstrip('.')}"),
|
||||
)
|
||||
connector = proxy_manager.get_connector(proxy) if proxy else None
|
||||
async with aiohttp.ClientSession(cookie_jar=jar, connector=connector) as session:
|
||||
async with session.get(
|
||||
"https://steamcommunity.com/chat/clientjstoken",
|
||||
timeout=aiohttp.ClientTimeout(total=10),
|
||||
) as resp:
|
||||
if resp.status != 200:
|
||||
return False
|
||||
data = await resp.json(content_type=None)
|
||||
return bool(data.get("logged_in"))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
async def raw_request(steam, url: str, method: str = "GET", **kwargs) -> aiohttp.ClientResponse:
|
||||
"""Low-level request returning raw aiohttp.ClientResponse (for redirects, etc.)."""
|
||||
from yarl import URL
|
||||
|
||||
host = URL(url).host or ""
|
||||
cookies = await steam.cookies(host)
|
||||
return await steam._requests.request(url=url, method=method, cookies=cookies, **kwargs)
|
||||
Reference in New Issue
Block a user