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
🌐 前端技术