Compare commits

...

11 Commits

Author SHA1 Message Date
ikun0014
2c88a769a4 1.4.8 2025-05-31 15:09:14 +08:00
ikun0014
5fb2ed26bd 详细一点的日志 2025-05-31 15:09:04 +08:00
ikun0014
1a36dc507c Update README.md 2025-05-04 13:45:09 +08:00
ikun0014
f207604b0e Update release.yml 2025-05-02 12:32:10 +08:00
ikun0014
74a74e5fa3 1.4.7 2025-05-02 12:16:30 +08:00
ikun0014
f7118f0224 日常维护 2025-05-02 12:16:18 +08:00
ikun0014
a475dcb6b8 1.4.6 2025-03-20 22:55:17 +08:00
ikun0014
2ea7c76004 1.4.6 2025-03-20 22:55:11 +08:00
ikun0014
14684cf1b7 fix: ManifestDownload 2025-03-20 22:54:43 +08:00
ikun0014
f560dab35f 1.4.5 2025-03-11 18:05:34 +08:00
ikun0014
8cdd9aa208 fix: SteamTools Import 2025-03-11 18:05:14 +08:00
7 changed files with 116 additions and 110 deletions

View File

@@ -93,6 +93,7 @@ jobs:
- name: Upload to Telegram Channel
run: |
& curl -F "chat_id=${{ secrets.TELEGRAM_TO }}" `
-F "thread_id=${{ secrets.TELEGRAM_THREAD }}" `
-F "document=@build/Onekey_v${{ env.PACKAGE_VERSION }}.exe" `
-F "caption=Onekey's New Update ${{ env.PACKAGE_VERSION }}" `
-F "parse_mode=Markdown" `

View File

@@ -13,6 +13,10 @@
## Onekey
Onekey Steam Depot Manifest Downloader
## 先让我挂些人
- 沧海颐粟,早期倒卖大手子,现在不知道跑哪了,通过一点手段查到在景德镇
- 玩家资源站这两个月出现的也是很嚣张了B站https://space.bilibili.com/242622952已被拉黑
## 使用方法
去Releases处下载最新的发布并且安装好SteamTools或者GreenLuma
然后打开Onekey输入App ID即可使用
@@ -62,9 +66,10 @@ pip install -r requirements.txt
</a>
## 常见问题解答FAQ
查看 [FAQ](https://ikunshare.com/d/49) 获取常见问题的解答。
查看 [FAQ](https://ikunshare.top/d/49) 获取常见问题的解答。
## 社区和支持
加入我们的社区,参与讨论和支持:
- [GitHub Discussions](https://github.com/ikunshare/Onekey/discussions)
- [Telegram](https://t.me/ikunshare_qun)
- [QQ](https://qm.qq.com/q/NPRVbglteK)

View File

@@ -1,9 +1,15 @@
import os
import colorama
import logging
import logzero
from logzero import setup_logger, LogFormatter
from .variable import LOG_FILE, DEBUG_MODE
try:
import os
import colorama
import logging
import logzero
from logzero import setup_logger, LogFormatter
from .variable import LOG_FILE, DEBUG_MODE
except ImportError as e:
print(e)
import os
os.system("pause")
if not os.path.exists(f"./logs"):
os.makedirs(f"./logs")
@@ -42,4 +48,4 @@ def log(name: str) -> logging.Logger:
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
return logger
return logger

View File

@@ -1,9 +1,15 @@
import os
import httpx
import sys
import winreg
import ujson as json
from pathlib import Path
try:
import time
import httpx
import sys
import winreg
import ujson as json
from pathlib import Path
except ImportError as e:
print(e)
import os
os.system("pause")
def get_steam_path(config: dict) -> Path:
@@ -41,8 +47,9 @@ def generate_config() -> None:
def load_config() -> dict:
if not Path("./config.json").exists():
generate_config()
print("请填写配置文件后重新运行程序")
os.system("pause")
print("请填写配置文件后重新运行程序5秒后退出")
time.sleep(5)
sys.exit(1)
try:
with open(Path("./config.json"), "r", encoding="utf-8") as f:
@@ -68,4 +75,5 @@ REPO_LIST = [
"SteamAutoCracks/ManifestHub",
"ikun0014/ManifestHub",
"Auiowu/ManifestAutoUpdate",
]
"tymolu233/ManifestAutoUpdate-fix",
]

169
main.py
View File

@@ -1,23 +1,28 @@
import os
import sys
import traceback
import asyncio
import aiofiles
import httpx
import vdf
import time
from typing import Any, Tuple, List, Dict
from pathlib import Path
from common import log, variable
from common.variable import (
CLIENT,
HEADER,
STEAM_PATH,
REPO_LIST,
)
try:
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
import vdf
import time
import httpx
import asyncio
import traceback
from typing import Any, Tuple, List, Dict
from pathlib import Path
from common import log, variable
from common.variable import (
CLIENT,
HEADER,
STEAM_PATH,
REPO_LIST,
)
except ImportError as e:
import os
print(e)
os.system("pause")
LOCK = asyncio.Lock()
@@ -36,14 +41,14 @@ def init() -> None:
\_____/ |_| \_| |_____| |_| \_\ |_____| /_/
"""
LOG.info(banner)
LOG.info("作者: ikun0014 | 版本: 1.4.4 | 官网: ikunshare.com")
LOG.info("作者: ikun0014 | 版本: 1.4.8 | 官网: ikunshare.top")
LOG.info("项目仓库: GitHub: https://github.com/ikunshare/Onekey")
LOG.warning("ikunshare.com | 严禁倒卖")
LOG.warning("ikunshare.top | 严禁倒卖")
LOG.warning("提示: 请确保已安装Windows 10/11并正确配置Steam;SteamTools/GreenLuma")
LOG.warning("开梯子必须配置Token, 你的IP我不相信能干净到哪")
async def checkcn() -> bool:
async def CheckCN() -> bool:
try:
req = await CLIENT.get("https://mips.kugou.com/check/iscn?&format=json")
body = req.json()
@@ -66,14 +71,14 @@ async def checkcn() -> bool:
return False
def stack_error(exception: Exception) -> str:
def StackError(exception: Exception) -> str:
stack_trace = traceback.format_exception(
type(exception), exception, exception.__traceback__
)
return "".join(stack_trace)
async def check_github_api_rate_limit(headers):
async def CheckLimit(headers):
url = "https://api.github.com/rate_limit"
try:
r = await CLIENT.get(url, headers=headers)
@@ -85,24 +90,24 @@ async def check_github_api_rate_limit(headers):
reset_time_formatted = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(reset_time)
)
LOG.info(f"剩余请求次数: {remaining_requests}")
LOG.info(f"剩余Github API请求次数: {remaining_requests}")
if remaining_requests == 0:
LOG.warning(
f"GitHub API 请求数已用尽, 将在 {reset_time_formatted} 重置,建议生成一个填在配置文件里"
f"GitHub API 请求数已用尽, 将在 {reset_time_formatted} 重置, 建议生成一个填在配置文件里"
)
else:
LOG.error("Github请求数检查失败, 网络错误")
except KeyboardInterrupt:
LOG.info("程序已退出")
except httpx.ConnectError as e:
LOG.error(f"检查Github API 请求数失败, {stack_error(e)}")
LOG.error(f"检查Github API 请求数失败, {StackError(e)}")
except httpx.ConnectTimeout as e:
LOG.error(f"检查Github API 请求数超时: {stack_error(e)}")
LOG.error(f"检查Github API 请求数超时: {StackError(e)}")
except Exception as e:
LOG.error(f"发生错误: {stack_error(e)}")
LOG.error(f"发生错误: {StackError(e)}")
async def get_latest_repo_info(repos: list, app_id: str, headers) -> Any | str | None:
async def GetLatestRepoInfo(repos: list, app_id: str, headers) -> Any | str | None:
latest_date = None
selected_repo = None
for repo in repos:
@@ -117,13 +122,13 @@ async def get_latest_repo_info(repos: list, app_id: str, headers) -> Any | str |
return selected_repo, latest_date
async def handle_depot_files(
async def HandleDepotFiles(
repos: List, app_id: str, steam_path: Path
) -> List[Tuple[str, str]]:
collected = []
depot_map = {}
try:
selected_repo, latest_date = await get_latest_repo_info(
selected_repo, latest_date = await GetLatestRepoInfo(
repos, app_id, headers=HEADER
) # type: ignore
@@ -138,7 +143,7 @@ async def handle_depot_files(
tree_res = await CLIENT.get(tree_url)
tree_res.raise_for_status()
depot_cache = steam_path / "depotcache"
depot_cache = Path(f"{steam_path}/depotcache")
depot_cache.mkdir(exist_ok=True)
LOG.info(f"当前选择清单仓库: https://github.com/{selected_repo}")
@@ -151,17 +156,17 @@ async def handle_depot_files(
if save_path.exists():
LOG.warning(f"已存在清单: {save_path}")
continue
content = await fetch_from_cdn(
content = await FetchFiles(
branch_res.json()["commit"]["sha"], file_path, selected_repo
)
LOG.info(f"清单下载成功: {file_path}")
async with aiofiles.open(save_path, "wb") as f:
await f.write(content)
with open(save_path, "wb") as f:
f.write(content)
elif "key.vdf" in file_path.lower():
key_content = await fetch_from_cdn(
key_content = await FetchFiles(
branch_res.json()["commit"]["sha"], file_path, selected_repo
)
collected.extend(parse_key_vdf(key_content))
collected.extend(ParseKey(key_content))
for item in tree_res.json()["tree"]:
if not item["path"].endswith(".manifest"):
@@ -187,10 +192,9 @@ async def handle_depot_files(
return collected, depot_map # type: ignore
async def fetch_from_cdn(sha: str, path: str, repo: str):
async def FetchFiles(sha: str, path: str, repo: str):
if variable.IS_CN:
url_list = [
f"https://jsdelivr.pai233.top/gh/{repo}@{sha}/{path}",
f"https://cdn.jsdmirror.com/gh/{repo}@{sha}/{path}",
f"https://raw.gitmirror.com/{repo}/{sha}/{path}",
f"https://raw.dgithub.xyz/{repo}/{sha}/{path}",
@@ -222,7 +226,7 @@ async def fetch_from_cdn(sha: str, path: str, repo: str):
raise Exception(f"无法下载: {path}")
def parse_key_vdf(content: bytes) -> List[Tuple[str, str]]:
def ParseKey(content: bytes) -> List[Tuple[str, str]]:
try:
depots = vdf.loads(content.decode("utf-8"))["depots"]
return [(d_id, d_info["DecryptionKey"]) for d_id, d_info in depots.items()]
@@ -231,34 +235,29 @@ def parse_key_vdf(content: bytes) -> List[Tuple[str, str]]:
return []
async def setup_unlock_tool(
def SetupUnlock(
depot_data: List[Tuple[str, str]],
app_id: str,
tool_choice: int,
depot_map: Dict,
) -> bool:
isGreenLuma = any(
(STEAM_PATH / dll).exists()
for dll in ["GreenLuma_2024_x86.dll", "GreenLuma_2024_x64.dll", "User32.dll"]
)
isSteamTools = (STEAM_PATH / "config" / "stUI").is_dir()
if (tool_choice == 1) and (isSteamTools):
return await setup_steamtools(depot_data, app_id, depot_map)
elif (tool_choice == 2) and (isGreenLuma):
return await setup_greenluma(depot_data)
if tool_choice == 1:
return SetupTools(depot_data, app_id, depot_map)
elif tool_choice == 2:
return SetupGreenLuma(depot_data)
else:
LOG.error("你选的啥?")
return False
async def setup_steamtools(
depot_data: List[Tuple[str, str]], app_id: str, depot_map: Dict
) -> bool:
st_path = STEAM_PATH / "config" / "stplug-in"
def SetupTools(depot_data: List[Tuple[str, str]], app_id: str, depot_map: Dict) -> bool:
st_path = Path(f"{STEAM_PATH}/config/stplug-in")
st_path.mkdir(exist_ok=True)
choice = input(f"是否锁定版本(推荐在选择仓库1时使用)?(y/n): ").lower()
choice = input(
f"是否锁定版本(推荐在选择仓库SteamAutoCracks/ManifestHub时使用)?(y/n): "
).lower()
if choice == "y":
versionlock = True
@@ -274,30 +273,14 @@ async def setup_steamtools(
lua_content += f'addappid({d_id}, 1, "{d_key}")\n'
lua_file = st_path / f"{app_id}.lua"
async with aiofiles.open(lua_file, "w") as f:
await f.write(lua_content)
proc = await asyncio.create_subprocess_exec(
str(st_path / "luapacka.exe"),
str(lua_file),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
await proc.wait()
if proc.returncode != 0:
LOG.error(f"Lua编译失败: {await proc.stderr.read()}") # type: ignore
return False
if lua_file.exists():
os.remove(lua_file)
LOG.info(f"删除临时文件: {lua_file}")
with open(lua_file, "w") as f:
f.write(lua_content)
return True
async def setup_greenluma(depot_data: List[Tuple[str, str]]) -> bool:
applist_dir = STEAM_PATH / "AppList"
def SetupGreenLuma(depot_data: List[Tuple[str, str]]) -> bool:
applist_dir = Path(f"{STEAM_PATH}/AppList")
applist_dir.mkdir(exist_ok=True)
for f in applist_dir.glob("*.txt"):
@@ -306,18 +289,18 @@ async def setup_greenluma(depot_data: List[Tuple[str, str]]) -> bool:
for idx, (d_id, _) in enumerate(depot_data, 1):
(applist_dir / f"{idx}.txt").write_text(str(d_id))
config_path = STEAM_PATH / "config" / "config.vdf"
async with aiofiles.open(config_path, "r+") as f:
content = vdf.loads(await f.read())
config_path = Path(f"{STEAM_PATH}/config/config.vdf")
with open(config_path, "r+") as f:
content = vdf.loads(f.read())
content.setdefault("depots", {}).update(
{d_id: {"DecryptionKey": d_key} for d_id, d_key in depot_data}
)
await f.seek(0)
await f.write(vdf.dumps(content))
f.seek(0)
f.write(vdf.dumps(content))
return True
async def main_flow(app_id: str):
async def Main(app_id: str):
app_id_list = list(filter(str.isdecimal, app_id.strip().split("-")))
if not app_id_list:
LOG.error(f"App ID无效")
@@ -327,13 +310,18 @@ async def main_flow(app_id: str):
app_id = app_id_list[0]
try:
await checkcn()
await check_github_api_rate_limit(HEADER)
await CheckCN()
await CheckLimit(HEADER)
depot_data, depot_map = await HandleDepotFiles(REPO_LIST, app_id, STEAM_PATH)
if (not depot_data) or (not depot_map):
LOG.error(f"未找到此游戏的清单")
return os.system("pause")
tool_choice = int(input("请选择解锁工具 (1.SteamTools 2.GreenLuma): "))
depot_data, depot_map = await handle_depot_files(REPO_LIST, app_id, STEAM_PATH)
if await setup_unlock_tool(depot_data, app_id, tool_choice, depot_map): # type: ignore
if SetupUnlock(depot_data, app_id, tool_choice, depot_map): # type: ignore
LOG.info("游戏解锁配置成功!")
LOG.info("重启Steam后生效")
else:
@@ -342,22 +330,21 @@ async def main_flow(app_id: str):
os.system("pause")
return True
except Exception as e:
LOG.error(f"运行错误: {stack_error(e)}")
return False
LOG.error(f"运行错误: {StackError(e)}")
return os.system("pause")
except KeyboardInterrupt:
return False
return os.system("pause")
finally:
await CLIENT.aclose()
return True
if __name__ == "__main__":
try:
init()
app_id = input(f"请输入游戏AppID: ").strip()
asyncio.run(main_flow(app_id))
asyncio.run(Main(app_id))
except (asyncio.CancelledError, KeyboardInterrupt):
os.system("pause")
except Exception as e:
LOG.error(f"错误:{stack_error(e)}")
LOG.error(f"错误:{StackError(e)}")
os.system("pause")

View File

@@ -1,6 +1,6 @@
{
"name": "onekey",
"version": "1.4.4",
"version": "1.4.8",
"description": "一个Steam仓库清单下载器",
"main": "index.js",
"scripts": {

View File

@@ -1,4 +1,3 @@
aiofiles
httpx
logzero
ujson