diff --git a/main.py b/main.py index 89a2dac..307dde9 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,7 @@ import os import sys -import time import threading -import webbrowser +import webview from pathlib import Path from PIL import Image @@ -13,6 +12,12 @@ from src.utils.i18n import t project_root = Path(__file__) config_manager = ConfigManager() sys.path.insert(0, str(project_root)) +window = webview.create_window( + title="Onekey", + url=f"http://localhost:{config_manager.app_config.port}", + width=1600, + height=900, +) def hide_console() -> None: @@ -33,7 +38,7 @@ def hide_console() -> None: def create_icon() -> Image.Image: """创建托盘图标""" try: - return Image.open("./icon.jpg") + return Image.open(project_root.parent / "icon.jpg") except Exception as e: if config_manager.app_config.show_console: print(t("error.load_icon", error=str(e))) @@ -50,11 +55,8 @@ def create_system_tray() -> bool: icon.stop() os._exit(0) - def on_open_browser(icon, item): - try: - webbrowser.open(f"http://localhost:{config_manager.app_config.port}") - except Exception: - pass + def on_show_window(icon, item): + window.show() def on_show_console(icon, item): try: @@ -70,7 +72,7 @@ def create_system_tray() -> bool: # 创建托盘菜单 menu = pystray.Menu( - pystray.MenuItem(t("tray.open_browser"), on_open_browser), + pystray.MenuItem(t("tray.show_window"), on_show_window), pystray.MenuItem(t("tray.show_console"), on_show_console), pystray.MenuItem(t("tray.exit"), on_quit), ) @@ -91,18 +93,6 @@ def create_system_tray() -> bool: return False -def open_browser_delayed(port: int) -> None: - """延迟打开浏览器""" - time.sleep(2) - try: - webbrowser.open(f"http://localhost:{port}") - if config_manager.app_config.show_console: - print(t("main.browser_opened", port=port)) - except Exception: - if config_manager.app_config.show_console: - print(t("main.browser_open_failed", port=port)) - - def start_web_server() -> None: """启动Web服务器""" from web.app import app @@ -136,16 +126,15 @@ def main() -> None: if tray_created: print(t("main.tray_created")) + def on_closing(): + if window.create_confirmation_dialog("Onekey", "是否关闭Onekey"): + os._exit(0) + return False + + window.events.closing += on_closing + # 启动浏览器 - browser_thread = threading.Thread( - target=open_browser_delayed, args=(config.port,) - ) - browser_thread.daemon = True - browser_thread.start() - - # 启动Web应用 - start_web_server() - + webview.start(func=start_web_server) except KeyboardInterrupt: if config_manager.app_config.show_console: print(f"\n{t('main.exit')}") diff --git a/requirements.txt b/requirements.txt index 7816f3b..8183881 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,15 @@ -vdf -httpx -Pillow -pystray +# Server +loguru +fastapi uvicorn -logzero -colorama -fastapi[all] -steam[client] \ No newline at end of file +# Encode +vdf +ujson +# Web +jinja2 +aiohttp +pywebview +websockets +# Any things +Pillow +pystray \ No newline at end of file diff --git a/src/config.py b/src/config.py index 24e89ac..50e26b2 100644 --- a/src/config.py +++ b/src/config.py @@ -1,6 +1,6 @@ import os import sys -import json +import ujson import winreg from pathlib import Path from typing import Dict, Optional @@ -34,7 +34,7 @@ class ConfigManager: """生成默认配置文件""" try: with open(self.config_path, "w", encoding="utf-8") as f: - json.dump(DEFAULT_CONFIG, f, indent=2, ensure_ascii=False) + ujson.dump(DEFAULT_CONFIG, f, indent=2, ensure_ascii=False) print(t("config.generated")) os.system("pause") sys.exit(1) @@ -50,7 +50,7 @@ class ConfigManager: try: with open(self.config_path, "r", encoding="utf-8") as f: - self._config_data = json.load(f) + self._config_data = ujson.load(f) self.app_config = AppConfig( key=self._config_data.get("KEY", ""), @@ -63,7 +63,7 @@ class ConfigManager: ) self.steam_path = self._get_steam_path() - except json.JSONDecodeError: + except ujson.JSONDecodeError: print(t("config.corrupted")) self._generate_config() print(t("config.regenerated")) diff --git a/src/constants.py b/src/constants.py index 29d4ab8..5b4b182 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,50 +1,24 @@ """常量定义""" from pathlib import Path -from httpx import Client LOG_DIR: Path = Path("logs") CONFIG_FILE: Path = Path("config.json") - - -def check_ip(): - try: - with Client(timeout=5.0) as client: - req = client.get( - "https://mips.kugou.com/check/iscn", - ) - req.raise_for_status() - body = req.json() - print("已获取IP属地") - return bool(body["flag"]) - except BaseException: - print("获取IP属地失败, 默认您位于中国大陆境内") - return True - - -IS_CN: bool = check_ip() - - STEAM_API_BASE: str = "https://steam.ikunshare.com/api" -STEAM_CACHE_CDN_LIST: list = ( - [ - "http://alibaba.cdn.steampipe.steamcontent.com", - "http://steampipe.steamcontent.tnkjmec.com", - ] - if IS_CN - else [ - "http://fastly.cdn.steampipe.steamcontent.com", - "http://akamai.cdn.steampipe.steamcontent.com", - "http://telus.cdn.steampipe.steamcontent.com", - "https://cache1-hkg1.steamcontent.com", - "https://cache2-hkg1.steamcontent.com", - "https://cache3-hkg1.steamcontent.com", - "https://cache4-hkg1.steamcontent.com", - "https://cache5-hkg1.steamcontent.com", - "https://cache6-hkg1.steamcontent.com", - "https://cache7-hkg1.steamcontent.com", - "https://cache8-hkg1.steamcontent.com", - "https://cache9-hkg1.steamcontent.com", - "https://cache10-hkg1.steamcontent.com", - ] -) +STEAM_CACHE_CDN_LIST: list = [ + "http://alibaba.cdn.steampipe.steamcontent.com", + "http://steampipe.steamcontent.tnkjmec.com", + "http://fastly.cdn.steampipe.steamcontent.com", + "http://akamai.cdn.steampipe.steamcontent.com", + "http://telus.cdn.steampipe.steamcontent.com", + "https://cache1-hkg1.steamcontent.com", + "https://cache2-hkg1.steamcontent.com", + "https://cache3-hkg1.steamcontent.com", + "https://cache4-hkg1.steamcontent.com", + "https://cache5-hkg1.steamcontent.com", + "https://cache6-hkg1.steamcontent.com", + "https://cache7-hkg1.steamcontent.com", + "https://cache8-hkg1.steamcontent.com", + "https://cache9-hkg1.steamcontent.com", + "https://cache10-hkg1.steamcontent.com", +] diff --git a/src/logger.py b/src/logger.py index 0fd7e21..9323960 100644 --- a/src/logger.py +++ b/src/logger.py @@ -1,9 +1,7 @@ """日志模块""" -import logging -import colorama -import logzero -from logzero import setup_logger, LogFormatter +import sys +from loguru import logger from .constants import LOG_DIR @@ -15,53 +13,51 @@ class Logger: self.name = name self.debug_mode = debug_mode self.log_file = log_file - self._logger = self._setup_logger() + self._setup_logger() + self._logger = logger.bind(name=name) - def _setup_logger(self) -> logging.Logger: + def _setup_logger(self): """设置日志器""" - level = logzero.DEBUG if self.debug_mode else logzero.INFO + # 移除默认的 handler + logger.remove() - colors = { - logzero.DEBUG: colorama.Fore.CYAN, - logzero.INFO: colorama.Fore.GREEN, - logzero.WARNING: colorama.Fore.YELLOW, - logzero.ERROR: colorama.Fore.RED, - logzero.CRITICAL: colorama.Fore.MAGENTA, - } + level = "DEBUG" if self.debug_mode else "INFO" - terminal_formatter = LogFormatter( - color=True, - fmt="%(color)s%(message)s%(end_color)s", - datefmt="%Y-%m-%d %H:%M:%S", - colors=colors, + # 控制台输出 + logger.add( + sys.stderr, format="{message}", level=level, colorize=True ) - logger = setup_logger(self.name, level=level, formatter=terminal_formatter) - if self.log_file: LOG_DIR.mkdir(exist_ok=True) logfile = LOG_DIR / f"{self.name}.log" - file_handler = logging.FileHandler(logfile, encoding="utf-8") - file_formatter = logging.Formatter( - "[%(asctime)s] | [%(name)s:%(levelname)s] | [%(module)s.%(funcName)s:%(lineno)d] - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - ) - file_handler.setFormatter(file_formatter) - logger.addHandler(file_handler) - return logger + # 文件输出 + file_format = ( + "[{time:YYYY-MM-DD HH:mm:ss}] | " + "[{extra[name]}:{level}] | " + "[{module}.{function}:{line}] - {message}" + ) + + logger.add( + logfile, + format=file_format, + level=level, + encoding="utf-8", + filter=lambda record: record["extra"].get("name") == self.name, + ) def debug(self, msg: str): - self._logger.debug(msg) + self._logger.opt(depth=1).debug(msg) def info(self, msg: str): - self._logger.info(msg) + self._logger.opt(depth=1).info(msg) def warning(self, msg: str): - self._logger.warning(msg) + self._logger.opt(depth=1).warning(msg) def error(self, msg: str): - self._logger.error(msg) + self._logger.opt(depth=1).error(msg) def critical(self, msg: str): - self._logger.critical(msg) + self._logger.opt(depth=1).critical(msg) diff --git a/src/main.py b/src/main.py index 32b02ce..5f2ef37 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,6 @@ from typing import List, Dict, Tuple + +import ujson from .constants import STEAM_API_BASE from .config import ConfigManager from .logger import Logger @@ -27,7 +29,7 @@ class OnekeyApp: f"{STEAM_API_BASE}/getKeyInfo", json={"key": self.config.app_config.key}, ) - body = response.json() + body = ujson.loads(await response.content.read()) if not body["info"]: self.logger.error(t("api.key_not_exist")) @@ -61,15 +63,15 @@ class OnekeyApp: headers={"X-Api-Key": self.config.app_config.key}, ) - if response.status_code == 401: + if response.status == 401: self.logger.error(t("api.invalid_key")) return SteamAppInfo(), SteamAppManifestInfo(mainapp=[], dlcs=[]) - if response.status_code != 200: - self.logger.error(t("api.request_failed", code=response.status_code)) + if response.status != 200: + self.logger.error(t("api.request_failed", code=response.status)) return SteamAppInfo(), SteamAppManifestInfo(mainapp=[], dlcs=[]) - data = response.json() + data = ujson.loads(await response.content.read()) if not data: self.logger.error(t("api.no_manifest")) diff --git a/src/manifest_handler.py b/src/manifest_handler.py index e74a6d2..1c438ce 100644 --- a/src/manifest_handler.py +++ b/src/manifest_handler.py @@ -1,7 +1,8 @@ -import vdf +import io +import struct +import zipfile from pathlib import Path from typing import List, Optional -from steam.client.cdn import DepotManifest from .constants import STEAM_CACHE_CDN_LIST from .models import ManifestInfo, SteamAppManifestInfo from .logger import Logger @@ -26,33 +27,40 @@ class ManifestHandler: url = cdn + manifest_info.url try: r = await self.client.get(url) - if r.status_code == 200: - return r.content + if r.status == 200: + return await r.content.read() except Exception as e: self.logger.debug(t("manifest.download.failed", url=url, error=e)) + @staticmethod + def _serialize_manifest_data(content: bytes) -> bytes: + magic_signature = struct.pack("= 4 and content[:4] == magic_signature: + payload = content[8:] + else: + try: + with zipfile.ZipFile(io.BytesIO(content)) as zf: + payload = zf.read("z") + except (zipfile.BadZipFile, KeyError): + pass + + return magic_signature + struct.pack(" bool: - """处理清单文件""" try: depot_id = manifest_info.depot_id manifest_id = manifest_info.manifest_id - depot_key = bytes.fromhex(manifest_info.depot_key) - manifest = DepotManifest(manifest_data) + _ = bytes.fromhex(manifest_info.depot_key) + + serialized_data = self._serialize_manifest_data(manifest_data) + manifest_path = self.depot_cache / f"{depot_id}_{manifest_id}.manifest" - config_path = self.depot_cache / "config.vdf" - if config_path.exists(): - with open(config_path, "r", encoding="utf-8") as f: - d = vdf.load(f) - else: - d = {"depots": {}} - - d["depots"][depot_id] = {"DecryptionKey": depot_key.hex()} - d = {"depots": dict(sorted(d["depots"].items()))} - if remove_old: for file in self.depot_cache.iterdir(): if file.suffix == ".manifest": @@ -66,10 +74,7 @@ class ManifestHandler: self.logger.info(t("manifest.delete_old", name=file.name)) with open(manifest_path, "wb") as f: - f.write(manifest.serialize(compress=False)) - - with open(config_path, "w", encoding="utf-8") as f: - vdf.dump(d, f, pretty=True) + f.write(serialized_data) self.logger.info( t( @@ -89,11 +94,9 @@ class ManifestHandler: ) -> List[ManifestInfo]: """批量处理清单""" processed = [] + all_manifests = manifests.mainapp + manifests.dlcs - app_manifest = manifests.mainapp - dlc_manifest = manifests.dlcs - - for manifest_info in app_manifest + dlc_manifest: + for manifest_info in all_manifests: manifest_path = ( self.depot_cache / f"{manifest_info.depot_id}_{manifest_info.manifest_id}.manifest" diff --git a/src/network/client.py b/src/network/client.py index 51c84b1..98d776c 100644 --- a/src/network/client.py +++ b/src/network/client.py @@ -1,6 +1,6 @@ """HTTP客户端模块""" -import httpx +import aiohttp from typing import Optional, Dict @@ -8,15 +8,15 @@ class HttpClient: """HTTP客户端封装""" def __init__(self): - self._client = httpx.AsyncClient(timeout=60.0) + self._client = aiohttp.ClientSession(conn_timeout=60.0) - async def get(self, url: str, headers: Optional[Dict] = None) -> httpx.Response: + async def get(self, url: str, headers: Optional[Dict] = None) -> aiohttp.ClientResponse: """GET请求""" return await self._client.get(url, headers=headers) async def close(self): """关闭客户端""" - await self._client.aclose() + await self._client.close() async def __aenter__(self): return self diff --git a/src/tools/greenluma.py b/src/tools/greenluma.py index 839abaa..20a6a62 100644 --- a/src/tools/greenluma.py +++ b/src/tools/greenluma.py @@ -4,23 +4,27 @@ from typing import List from .base import UnlockTool from ..models import DepotInfo + class GreenLuma(UnlockTool): """GreenLuma解锁工具实现""" + async def setup(self, depot_data: List[DepotInfo], app_id: str, **kwargs) -> bool: applist_dir = self.steam_path / "AppList" - + if applist_dir.is_file(): applist_dir.unlink(missing_ok=True) if not applist_dir.is_dir(): applist_dir.mkdir(parents=True, exist_ok=True) - + depot_dict = {} for i in applist_dir.iterdir(): - if i.stem.isdecimal() and i.suffix == '.txt': - with i.open('r', encoding='utf-8') as f: + if i.stem.isdecimal() and i.suffix == ".txt": + with i.open("r", encoding="utf-8") as f: app_id_ = f.read().strip() - depot_dict[int(i.stem)] = int(app_id_) if app_id_.isdecimal() else None - + depot_dict[int(i.stem)] = ( + int(app_id_) if app_id_.isdecimal() else None + ) + def find_next_index(): if not depot_dict: return 0 @@ -28,23 +32,23 @@ class GreenLuma(UnlockTool): if i not in depot_dict: return i return max(depot_dict.keys()) + 1 - + if app_id and app_id.isdecimal(): app_id_int = int(app_id) if app_id_int not in depot_dict.values(): index = find_next_index() - with (applist_dir / f'{index}.txt').open('w', encoding='utf-8') as f: + with (applist_dir / f"{index}.txt").open("w", encoding="utf-8") as f: f.write(str(app_id)) depot_dict[index] = app_id_int - + for depot in depot_data: depot_id = int(depot.depot_id) if depot_id not in depot_dict.values(): index = find_next_index() - with (applist_dir / f'{index}.txt').open('w', encoding='utf-8') as f: + with (applist_dir / f"{index}.txt").open("w", encoding="utf-8") as f: f.write(str(depot_id)) depot_dict[index] = depot_id - + config_path = self.steam_path / "config" / "config.vdf" try: if config_path.is_file(): @@ -53,16 +57,18 @@ class GreenLuma(UnlockTool): else: content = {} config_path.parent.mkdir(parents=True, exist_ok=True) - + if "depots" not in content: content["depots"] = {} - + for depot in depot_data: - content["depots"][depot.depot_id] = {"DecryptionKey": depot.decryption_key} - + content["depots"][depot.depot_id] = { + "DecryptionKey": depot.decryption_key + } + with open(config_path, "w", encoding="utf-8") as f: f.write(vdf.dumps(content)) return True except Exception as e: print(f"GreenLuma配置失败: {e}") - return False \ No newline at end of file + return False diff --git a/src/utils/i18n.py b/src/utils/i18n.py index f3bb6ef..1ba055d 100644 --- a/src/utils/i18n.py +++ b/src/utils/i18n.py @@ -14,14 +14,12 @@ class I18n: # 中文翻译 self.translations["zh"] = { # 系统托盘 - "tray.open_browser": "打开浏览器", + "tray.show_window": "显示程序", "tray.show_console": "显示控制台", "tray.exit": "退出程序", # 主程序 "main.starting": "正在启动Onekey...", "main.tray_created": "系统托盘已创建", - "main.browser_opened": "浏览器已自动打开", - "main.browser_open_failed": "无法自动打开浏览器,请手动访问: http://localhost:{port}", "main.exit": "程序已退出", "main.start_error": "启动错误: {error}", "main.press_enter": "按回车键退出...", @@ -72,7 +70,6 @@ class I18n: "error.ensure_root": "请确保在项目根目录中运行此程序", # Web相关 "web.starting": "启动Onekey Web GUI...", - "web.visit": "请在浏览器中访问: http://localhost:{port}", "web.task_running": "已有任务正在运行", "web.invalid_appid": "请输入有效的App ID", "web.invalid_format": "App ID格式无效", @@ -104,14 +101,12 @@ class I18n: # 英文翻译 self.translations["en"] = { # System tray - "tray.open_browser": "Open Browser", + "tray.show_window": "Show Window", "tray.show_console": "Show Console", "tray.exit": "Exit", # Main program "main.starting": "Starting Onekey...", "main.tray_created": "System tray created", - "main.browser_opened": "Browser opened automatically", - "main.browser_open_failed": "Failed to open browser automatically, please visit: http://localhost:{port}", "main.exit": "Program exited", "main.start_error": "Startup error: {error}", "main.press_enter": "Press Enter to exit...", @@ -162,7 +157,6 @@ class I18n: "error.ensure_root": "Please ensure running this program from project root", # Web related "web.starting": "Starting Onekey Web GUI...", - "web.visit": "Please visit: http://localhost:{port}", "web.task_running": "A task is already running", "web.invalid_appid": "Please enter a valid App ID", "web.invalid_format": "Invalid App ID format", diff --git a/web/app.py b/web/app.py index b2c63ee..9ca2132 100644 --- a/web/app.py +++ b/web/app.py @@ -1,8 +1,8 @@ import os import sys import time -import json -import httpx +import ujson +import aiohttp import asyncio from pathlib import Path @@ -16,14 +16,11 @@ from fastapi.templating import Jinja2Templates from src.constants import STEAM_API_BASE from src.utils.i18n import t - -# 添加项目根目录到Python路径 project_root = Path(__file__) sys.path.insert(0, str(project_root)) def get_base_path(): - """获取程序基础路径""" if hasattr(sys, "_MEIPASS"): return Path(sys._MEIPASS) elif getattr(sys, "frozen", False): @@ -44,7 +41,6 @@ except ImportError as e: class ConnectionManager: - """WebSocket 连接管理器""" def __init__(self): self.active_connections: List[WebSocket] = [] @@ -64,23 +60,19 @@ class ConnectionManager: try: await connection.send_text(message) except BaseException: - # 连接可能已关闭 pass class WebOnekeyApp: - """Web版本的Onekey应用""" - def __init__(self, manager: ConnectionManager): self.onekey_app = None self.current_task = None - self.task_status = "idle" # idle, running, completed, error + self.task_status = "idle" self.task_progress = [] self.task_result = None self.manager = manager def init_app(self): - """初始化Onekey应用""" try: self.onekey_app = OnekeyApp() return True @@ -88,18 +80,14 @@ class WebOnekeyApp: return False, str(e) async def run_unlock_task(self, app_id: str, tool_type: str, dlc: bool): - """运行解锁任务""" try: self.task_status = "running" self.task_progress = [] - # 重新初始化应用以确保新的任务状态 self.onekey_app = OnekeyApp() - # 添加自定义日志处理器来捕获进度 self._add_progress_handler() - # 执行解锁任务 result = await self.onekey_app.run(app_id, tool_type, dlc) if result: @@ -116,7 +104,6 @@ class WebOnekeyApp: self.task_status = "error" self.task_result = {"success": False, "message": f"配置失败: {str(e)}"} finally: - # 确保应用资源被清理 if hasattr(self, "onekey_app") and self.onekey_app: try: if hasattr(self.onekey_app, "client"): @@ -126,7 +113,6 @@ class WebOnekeyApp: self.onekey_app = None def _add_progress_handler(self): - """添加进度处理器""" if self.onekey_app and self.onekey_app.logger: original_info = self.onekey_app.logger.info original_warning = self.onekey_app.logger.warning @@ -136,10 +122,9 @@ class WebOnekeyApp: self.task_progress.append( {"type": "info", "message": str(msg), "timestamp": time.time()} ) - # 广播进度消息 asyncio.create_task( self.manager.broadcast( - json.dumps( + ujson.dumps( { "type": "task_progress", "data": {"type": "info", "message": str(msg)}, @@ -155,7 +140,7 @@ class WebOnekeyApp: ) asyncio.create_task( self.manager.broadcast( - json.dumps( + ujson.dumps( { "type": "task_progress", "data": {"type": "warning", "message": str(msg)}, @@ -171,7 +156,7 @@ class WebOnekeyApp: ) asyncio.create_task( self.manager.broadcast( - json.dumps( + ujson.dumps( { "type": "task_progress", "data": {"type": "error", "message": str(msg)}, @@ -186,7 +171,6 @@ class WebOnekeyApp: self.onekey_app.logger.error = error_with_progress -# 创建FastAPI应用 app = FastAPI(title="Onekey") app.add_middleware( CORSMiddleware, @@ -198,7 +182,6 @@ app.add_middleware( manager = ConnectionManager() -# 修复:为静态文件路由添加name参数 config = ConfigManager() app.mount( "/static", @@ -209,7 +192,6 @@ templates = Jinja2Templates( directory=f"{base_path}/{config.app_config.language}/templates" ) -# 创建Web应用实例 web_app = WebOnekeyApp(manager) @@ -267,7 +249,6 @@ async def start_unlock(request: Request): if not app_id: return JSONResponse({"success": False, "message": "请输入有效的App ID"}) - # 验证App ID格式 app_id_list = [id for id in app_id.split("-") if id.isdigit()] if not app_id_list: return JSONResponse({"success": False, "message": "App ID格式无效"}) @@ -293,9 +274,7 @@ async def get_task_status(): return JSONResponse( { "status": web_app.task_status, - "progress": ( - web_app.task_progress[-10:] if web_app.task_progress else [] - ), # 只返回最近10条 + "progress": (web_app.task_progress[-10:] if web_app.task_progress else []), "result": web_app.task_result, } ) @@ -319,14 +298,11 @@ async def update_config(request: Request): try: data = await request.json() - # 验证必需的字段 if not isinstance(data, dict): return {"success": False, "message": "无效的配置数据"} - # 加载当前配置 config_manager = ConfigManager() - # 准备新的配置数据 new_config = { "KEY": data.get("key", ""), "Port": config_manager.app_config.port, @@ -337,12 +313,11 @@ async def update_config(request: Request): "Language": data.get("language", "zh"), } - # 保存配置 - import json + import ujson config_path = config_manager.config_path with open(config_path, "w", encoding="utf-8") as f: - json.dump(new_config, f, indent=2, ensure_ascii=False) + ujson.dump(new_config, f, indent=2, ensure_ascii=False) return {"success": True, "message": "配置已保存"} @@ -355,13 +330,13 @@ async def reset_config(): """重置配置为默认值""" try: from src.config import DEFAULT_CONFIG - import json + import ujson config_manager = ConfigManager() config_path = config_manager.config_path with open(config_path, "w", encoding="utf-8") as f: - json.dump(DEFAULT_CONFIG, f, indent=2, ensure_ascii=False) + ujson.dump(DEFAULT_CONFIG, f, indent=2, ensure_ascii=False) return {"success": True, "message": "配置已重置为默认值"} @@ -402,19 +377,19 @@ async def get_key_info(request: Request): if not key: return JSONResponse({"success": False, "message": "卡密不能为空"}) - async with httpx.AsyncClient(timeout=10.0) as client: + async with aiohttp.ClientSession(conn_timeout=10.0) as client: response = await client.post( - f"{STEAM_API_BASE}/getKeyInfo", + url=f"{STEAM_API_BASE}/getKeyInfo", json={"key": key}, headers={"Content-Type": "application/json"}, ) - if response.status_code == 200: - result = response.json() + if response.status == 200: + result = ujson.loads(await response.content.read()) return JSONResponse(result) else: return JSONResponse({"success": False, "message": "卡密验证服务不可用"}) - except httpx.TimeoutException: + except aiohttp.ConnectionTimeoutError: return JSONResponse({"success": False, "message": "验证超时,请检查网络连接"}) except Exception as e: return JSONResponse({"success": False, "message": f"验证失败: {str(e)}"}) @@ -426,15 +401,15 @@ async def websocket_endpoint(websocket: WebSocket): await manager.connect(websocket) try: await websocket.send_text( - json.dumps({"type": "connected", "data": {"message": "已连接到服务器"}}) + ujson.dumps({"type": "connected", "data": {"message": "已连接到服务器"}}) ) while True: data = await websocket.receive_text() - message = json.loads(data) + message = ujson.loads(data) if message.get("type") == "ping": await websocket.send_text( - json.dumps({"type": "pong", "data": {"timestamp": time.time()}}) + ujson.dumps({"type": "pong", "data": {"timestamp": time.time()}}) ) except WebSocketDisconnect: manager.disconnect(websocket) @@ -445,4 +420,3 @@ async def websocket_endpoint(websocket: WebSocket): print(t("web.starting")) -print(t("web.visit", port=config.app_config.port)) diff --git a/web/en/templates/about.html b/web/en/templates/about.html index 5ba997b..5fa6837 100644 --- a/web/en/templates/about.html +++ b/web/en/templates/about.html @@ -33,7 +33,7 @@

- v2.1.1 + v2.1.2 Web UI
@@ -117,7 +117,7 @@
🐍 Backend Technology - Python 3.8+ • FastAPI • AsyncIO • HTTPX + Python 3.8+ • FastAPI • AsyncIO • AIOHTTP
🌐 Frontend Technology diff --git a/web/zh/templates/about.html b/web/zh/templates/about.html index 8cd5bcd..bf36302 100644 --- a/web/zh/templates/about.html +++ b/web/zh/templates/about.html @@ -31,7 +31,7 @@

直观,优雅的游戏解锁解决方案

- v2.1.1 + v2.1.2 Web UI
@@ -115,7 +115,7 @@
🐍 后端技术 - Python 3.8+ • FastAPI • AsyncIO • HTTPX + Python 3.8+ • FastAPI • AsyncIO • AIOHTTP
🌐 前端技术