Compare commits

..

12 Commits
1.0.5 ... 1.1.3

Author SHA1 Message Date
ikun
73c92e550e feat: 加入搜索游戏名入库功能
Co-Authored-By: Tibbar <49330075+tibbar213@users.noreply.github.com>
2024-08-26 19:06:05 +08:00
ikun
a2fa038324 feat&&fix: bug!bug!bug! 2024-08-22 22:40:50 +08:00
ikun
ea3aedbab3 fix: 史 2024-08-22 21:48:04 +08:00
ikun
51ccc579f4 feat:配置文件修改 2024-08-22 14:10:30 +08:00
ikun
17e654a68e feat:没什么用懒得发Release 2024-08-21 17:27:50 +08:00
ikun
c4f4fb9e92 feat: 增加一个清单库 2024-08-16 15:11:31 +08:00
ikun
e38af1675c fix: 修改QQ群链接 2024-08-15 19:53:41 +08:00
ikun
50194cf7de 更新 main.py 2024-08-14 19:00:27 +08:00
ikun
7063f2f5dc rm: 移除jsdelivr的CDN服务 2024-08-13 10:25:28 +08:00
ikun
2c6b3bebe1 fix: Github API请求数量为0时不返回错误
史中史代码,看不懂与我无关
2024-08-12 19:18:56 +08:00
ikun
5c74940702 feat: 加了几个加速镜像 2024-08-12 15:08:54 +08:00
ikun
dc64d2a9be feat:死妈倒狗给我坐下 2024-08-11 21:49:37 +08:00
3 changed files with 169 additions and 106 deletions

1
.gitignore vendored
View File

@@ -168,3 +168,4 @@ cython_debug/
*.bat
*.xml
*.exe
*.dll

View File

@@ -25,7 +25,7 @@
使用本项目的过程中可能会产生版权数据对于这些版权数据本项目不拥有它们的所有权为了避免造成侵权使用者务必在24 小时内清除使用本项目的过程中所产生的版权数据。
由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本项目不对项目内的技术可能存在违反当地法律法规的行为作保证,禁止在违反当地法律法规的情况下使用本项目,对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
而且,本项目已禁止使用于商业用途。
而且,本项目已禁止使用于商业用途,以及不得进行未经允许的二次修改,否则必须同时发布源代码
若你使用了本项目,将代表你接受以上协议。
Steam正版平台不易请尊重版权支持正版。

272
main.py
View File

@@ -1,18 +1,17 @@
import os
import vdf
import winreg
import argparse
import aiohttp
import aiofiles
import traceback
import subprocess
import colorlog
import logging
import json
import ujson as json
import time
import sys
import psutil
import asyncio
from aiohttp import ClientSession, ClientError
from pathlib import Path
# 初始化日志记录器
@@ -37,9 +36,16 @@ def init_log():
# 生成配置文件
def gen_config_file():
default_config = {"Github_Persoal_Token": "", "Custom_Steam_Path": ""}
with open('./config.json', 'w', encoding='utf-8') as f:
json.dump(default_config, f)
default_config ={
"Github_Personal_Token": "",
"Custom_Steam_Path": "",
"QA1": "温馨提示Github_Personal_Token可在Github设置的最底下开发者选项找到详情看教程",
"教程": "https://lyvx-my.sharepoint.com/:w:/g/personal/ikun_ikunshare_com/EWqIqyCElLNLo_CKfLbqix0BWU_O03HLzEHQKHdJYrUz-Q?e=79MZjw"
}
with open("./config.json", "w", encoding="utf-8") as f:
f.write(json.dumps(default_config, indent=2, ensure_ascii=False,
escape_forward_slashes=False))
f.close()
log.info(' 🖱️ 程序可能为第一次启动,请填写配置文件后重新启动程序')
@@ -50,8 +56,8 @@ def load_config():
os.system('pause')
sys.exit()
else:
with open('./config.json', 'r', encoding='utf-8') as f:
config = json.load(f)
with open("./config.json", "r", encoding="utf-8") as f:
config = json.loads(f.read())
return config
@@ -68,11 +74,10 @@ print('\033[1;32;40m | |_| | | | \\ | | |___ | | \\ \\ | |___ / /' + '\033
print('\033[1;32;40m \\_____/ |_| \\_| |_____| |_| \\_\\ |_____| /_/' + '\033[0m')
log.info('作者ikun0014')
log.info('本项目基于wxy1343/ManifestAutoUpdate进行修改采用GPL V3许可证')
log.info('版本1.0.5')
log.info('版本1.1.3')
log.info('项目仓库https://github.com/ikunshare/Onekey')
log.debug('官网ikunshare.com')
log.warning('注意据传Steam新版本对部分解锁工具进行了检测但目前未发现问题如果你被封号可以issue反馈')
log.warning('本项目完全免费如果你在淘宝QQ群内通过购买方式获得赶紧回去骂商家死全家\n交流群组:\n点击链接加入群聊【ikun分享】https://qm.qq.com/q/D9Uiva3RVS\nhttps://t.me/ikunshare_group')
log.warning('本项目完全开源免费如果你在淘宝QQ群内通过购买方式获得赶紧回去骂商家死全家\n交流群组:\n点击链接加入群聊【𝗶𝗸𝘂𝗻分享】https://qm.qq.com/q/d7sWovfAGI\nhttps://t.me/ikunshare_group')
# 通过注册表获取Steam安装路径
@@ -97,61 +102,91 @@ def stack_error(exception):
return ''.join(stack_trace)
# 从Steam API直接搜索游戏信息
async def search_game_info(search_term):
async with ClientSession() as session:
url = f'https://steamui.com/loadGames.php?search={search_term}'
async with session.get(url) as r:
if r.status == 200:
data = await r.json()
games = data.get('games', [])
return games
else:
log.error("⚠ 获取游戏信息失败")
return []
# 通过游戏名查找appid
async def find_appid_by_name(game_name):
games = await search_game_info(game_name)
if games:
log.info("🔍 找到以下匹配的游戏:")
for idx, game in enumerate(games, 1):
if game['schinese_name'] == '':
gamename = game['name']
else:
gamename = game['schinese_name']
log.info(f"{idx}. {gamename} (AppID: {game['appid']})")
choice = input("请选择游戏编号:")
if choice.isdigit() and 1 <= int(choice) <= len(games):
selected_game = games[int(choice) - 1]
log.info(f"✅ 选择的游戏: {selected_game['schinese_name']} (AppID: {selected_game['appid']})")
return selected_game['appid'], selected_game['schinese_name']
log.error("⚠ 未找到匹配的游戏")
return None, None
# 下载清单
async def get(sha, path):
async def get(sha, path, repo, session):
url_list = [
f'https://gcore.jsdelivr.net/gh/{repo}@{sha}/{path}',
f'https://fastly.jsdelivr.net/gh/{repo}@{sha}/{path}',
f'https://cdn.jsdelivr.net/gh/{repo}@{sha}/{path}',
f'https://ghproxy.org/https://raw.githubusercontent.com/{repo}/{sha}/{path}',
f'https://raw.dgithub.xyz/{repo}/{sha}/{path}'
# f'https://gh.api.99988866.xyz/https://raw.githubusercontent.com/{repo}/{sha}/{path}',
f'https://cdn.jsdmirror.com/gh/{repo}@{sha}/{path}',
f'https://jsd.onmicrosoft.cn/gh/{repo}@{sha}/{path}',
f'https://mirror.ghproxy.com/https://raw.githubusercontent.com/{repo}/{sha}/{path}',
f'https://raw.githubusercontent.com/{repo}/{sha}/{path}',
f'https://gh.jiasu.in/https://raw.githubusercontent.com/{repo}/{sha}/{path}'
]
retry = 3
async with aiohttp.ClientSession() as session:
while retry:
for url in url_list:
try:
async with session.get(url, ssl=False) as r:
if r.status == 200:
return await r.read()
else:
log.error(f' 🔄 获取失败: {path} - 状态码: {r.status}')
except aiohttp.ClientError:
log.error(f' 🔄 获取失败: {path} - 连接错误')
retry -= 1
log.warning(f' 🔄 重试剩余次数: {retry} - {path}')
log.error(f' 🔄 超过最大重试次数: {path}')
raise Exception(f' 🔄 无法下载: {path}')
while retry:
for url in url_list:
try:
async with session.get(url, ssl=False) as r:
if r.status == 200:
return await r.read()
else:
log.error(f' 🔄 获取失败: {path} - 状态码: {r.status}')
except ClientError:
log.error(f' 🔄 获取失败: {path} - 连接错误')
retry -= 1
log.warning(f' 🔄 重试剩余次数: {retry} - {path}')
log.error(f' 🔄 超过最大重试次数: {path}')
raise Exception(f' 🔄 无法下载: {path}')
# 获取清单信息
async def get_manifest(sha, path, steam_path: Path):
async def get_manifest(sha, path, steam_path: Path, repo, session):
collected_depots = []
try:
if path.endswith('.manifest'):
depot_cache_path = steam_path / 'depotcache'
async with lock:
if not depot_cache_path.exists():
depot_cache_path.mkdir(exist_ok=True)
if not depot_cache_path.exists():
depot_cache_path.mkdir(exist_ok=True)
save_path = depot_cache_path / path
if save_path.exists():
async with lock:
log.warning(f'👋已存在清单: {path}')
log.warning(f'👋已存在清单: {path}')
return collected_depots
content = await get(sha, path)
async with lock:
log.info(f' 🔄 清单下载成功: {path}')
content = await get(sha, path, repo, session)
log.info(f' 🔄 清单下载成功: {path}')
async with aiofiles.open(save_path, 'wb') as f:
await f.write(content)
elif path == 'Key.vdf':
content = await get(sha, path)
async with lock:
log.info(f' 🔄 密钥下载成功: {path}')
content = await get(sha, path, repo, session)
log.info(f' 🔄 密钥下载成功: {path}')
depots_config = vdf.loads(content.decode(encoding='utf-8'))
for depot_id, depot_info in depots_config['depots'].items():
collected_depots.append((depot_id, depot_info['DecryptionKey']))
except KeyboardInterrupt:
raise
except Exception as e:
log.error(f'处理失败: {path} - {stack_error(e)}')
traceback.print_exc()
@@ -198,7 +233,7 @@ async def stool_add(depot_data, app_id):
# 增加GreenLuma解锁相关文件
async def greenluma_add(depot_id_list):
app_list_path = steam_path / 'appcache' / 'appinfo.vdf'
app_list_path = steam_path / 'AppList'
if app_list_path.exists() and app_list_path.is_file():
app_list_path.unlink(missing_ok=True)
if not app_list_path.is_dir():
@@ -225,24 +260,6 @@ async def greenluma_add(depot_id_list):
return True
# 检测Github Api请求数量
async def check_github_api_limit(headers):
url = 'https://api.github.com/rate_limit'
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, ssl=False) as r:
r_json = await r.json()
remain_limit = r_json['rate']['remaining']
use_limit = r_json['rate']['used']
reset_time = r_json['rate']['reset']
f_reset_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(reset_time))
log.info(f' 🔄 已用Github请求数{use_limit}')
log.info(f' 🔄 剩余Github请求数{remain_limit}')
if r.status == 429:
log.info(f' 🔄 你的Github Api请求数已超限请尝试增加Persoal Token')
log.info(f' 🔄 请求数重置时间:{f_reset_time}')
return True
# 检查进程是否运行
def check_process_running(process_name):
for process in psutil.process_iter(['name']):
@@ -251,59 +268,104 @@ def check_process_running(process_name):
return False
async def check_github_api_rate_limit(headers, session):
url = 'https://api.github.com/rate_limit'
async with session.get(url, headers=headers, ssl=False) as r:
if not r == None:
r_json = await r.json()
else:
log.error('孩子,你怎么做到的?')
os.system('pause')
if r.status == 200:
rate_limit = r_json['rate']
remaining_requests = rate_limit['remaining']
reset_time = rate_limit['reset']
reset_time_formatted = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(reset_time))
log.info(f' 🔄 剩余请求次数: {remaining_requests}')
if remaining_requests == 0:
log.warning(f' ⚠ GitHub API 请求数已用尽,将在 {reset_time_formatted} 重置, 不想等生成一个填配置文件里')
# 主函数
async def main(app_id):
async def main(app_id, game_name):
app_id_list = list(filter(str.isdecimal, app_id.strip().split('-')))
app_id = app_id_list[0]
github_token = config.get("Github_Persoal_Token", "")
headers = {'Authorization': f'Bearer {github_token}'} if github_token else None
async with ClientSession() as session:
github_token = config.get("Github_Personal_Token", "")
headers = {'Authorization': f'Bearer {github_token}'} if github_token else None
latest_date = None
selected_repo = None
await check_github_api_limit(headers)
# 检查Github API限额
await check_github_api_rate_limit(headers, session)
url = f'https://api.github.com/repos/{repo}/branches/{app_id}'
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, ssl=False) as r:
r_json = await r.json()
if 'commit' in r_json:
sha = r_json['commit']['sha']
url = r_json['commit']['commit']['tree']['url']
date = r_json['commit']['commit']['author']['date']
async with session.get(url, headers=headers, ssl=False) as r2:
r2_json = await r2.json()
if 'tree' in r2_json:
collected_depots = []
for i in r2_json['tree']:
result = await get_manifest(sha, i['path'], steam_path)
collected_depots.extend(result)
if collected_depots:
if isSteamTools:
await stool_add(collected_depots, app_id)
log.info(' ✅ 找到SteamTools已添加解锁文件')
if isGreenLuma:
await greenluma_add([app_id])
depot_config = {'depots': {depot_id: {'DecryptionKey': depot_key} for depot_id, depot_key in collected_depots}}
depotkey_merge(steam_path / 'config' / 'config.vdf', depot_config)
if await greenluma_add([int(i) for i in depot_config['depots'] if i.isdecimal()]):
log.info(' ✅ 找到GreenLuma已添加解锁文件')
log.info(f' ✅ 清单最后更新时间:{date}')
log.info(f' ✅ 入库成功: {app_id}')
return True
log.error(f' ⚠ 清单下载或生成.st失败: {app_id}')
return False
for repo in repos:
url = f'https://api.github.com/repos/{repo}/branches/{app_id}'
try:
async with session.get(url, headers=headers, ssl=False) as r:
r_json = await r.json()
if 'commit' in r_json:
date = r_json['commit']['commit']['author']['date']
if latest_date is None or date > latest_date:
latest_date = date
selected_repo = repo
except Exception as e:
log.error(f' ⚠ 获取分支信息失败: {stack_error(e)}')
traceback.print_exc()
if selected_repo:
log.info(f' 🔄 选择清单仓库:{selected_repo}')
url = f'https://api.github.com/repos/{selected_repo}/branches/{app_id}'
async with session.get(url, headers=headers, ssl=False) as r:
r_json = await r.json()
if 'commit' in r_json:
sha = r_json['commit']['sha']
url = r_json['commit']['commit']['tree']['url']
async with session.get(url, headers=headers, ssl=False) as r2:
r2_json = await r2.json()
if 'tree' in r2_json:
collected_depots = []
for i in r2_json['tree']:
result = await get_manifest(sha, i['path'], steam_path, selected_repo, session)
collected_depots.extend(result)
if collected_depots:
if isSteamTools:
await stool_add(collected_depots, app_id)
log.info(' ✅ 找到SteamTools已添加解锁文件')
if isGreenLuma:
await greenluma_add([app_id])
depot_config = {'depots': {depot_id: {'DecryptionKey': depot_key} for depot_id, depot_key in collected_depots}}
await depotkey_merge(steam_path / 'config' / 'config.vdf', depot_config)
if await greenluma_add([int(i) for i in depot_config['depots'] if i.isdecimal()]):
log.info(' ✅ 找到GreenLuma已添加解锁文件')
log.info(f' ✅ 清单最后更新时间:{date}')
log.info(f' ✅ 入库成功: {app_id}{game_name}')
return True
log.error(f' ⚠ 清单下载或生成失败: {app_id}{game_name}')
return False
parser = argparse.ArgumentParser()
parser.add_argument('-a', '--app-id')
args = parser.parse_args()
repo = 'ManifestHub/ManifestHub'
repos = [
'ManifestHub/ManifestHub',
'ikun0014/ManifestHub',
'Auiowu/ManifestAutoUpdate',
'tymolu233/ManifestAutoUpdate'
]
if __name__ == '__main__':
try:
log.debug('App ID可以在SteamDB或Steam商店链接页面查看')
asyncio.run(main(args.app_id or input('需要入库的App ID: ')))
user_input = input("请输入游戏AppID或名称:").strip()
appid, game_name = asyncio.run(find_appid_by_name(user_input))
if not appid:
log.error(' ⚠ 未找到匹配的游戏,请尝试其他名称。')
asyncio.run(main(appid, game_name))
except KeyboardInterrupt:
exit()
except Exception as e:
log.error(f' ⚠ 发生错误: {stack_error(e)}')
traceback.print_exc()
if not args.app_id:
if not user_input:
os.system('pause')