Compare commits
7 Commits
41f6943998
...
e37422fc95
Author | SHA1 | Date | |
---|---|---|---|
e37422fc95
|
|||
d7951e8587
|
|||
556533785a
|
|||
a13aca4d84
|
|||
35736e1723
|
|||
|
24a7c2e657
|
||
|
279f7ec36b
|
@@ -15,6 +15,7 @@
|
|||||||
- Анимации теперь можно настраивать через темы (за подробностями в документацию)
|
- Анимации теперь можно настраивать через темы (за подробностями в документацию)
|
||||||
- Общие json (steam_apps и anticheat_games) теперь перекачиваются если сломаны
|
- Общие json (steam_apps и anticheat_games) теперь перекачиваются если сломаны
|
||||||
- Временно удалена светлая тема
|
- Временно удалена светлая тема
|
||||||
|
- Добавление и удаление игр из Steam теперь не требует перезагрузки Steam
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- legendary list теперь не вызывается если вход в EGS не был произведён
|
- legendary list теперь не вызывается если вход в EGS не был произведён
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
|
|
||||||
|
|
||||||
### Contributors
|
### Contributors
|
||||||
|
- @Alex Smith
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
2
TODO.md
2
TODO.md
@@ -41,7 +41,7 @@
|
|||||||
- [X] Получать slug через GraphQL [запрос](https://launcher.store.epicgames.com/graphql)
|
- [X] Получать slug через GraphQL [запрос](https://launcher.store.epicgames.com/graphql)
|
||||||
- [X] Добавить на карточку бейдж, указывающий, что игра из Steam
|
- [X] Добавить на карточку бейдж, указывающий, что игра из Steam
|
||||||
- [X] Добавить поддержку версий Steam для Flatpak и Snap
|
- [X] Добавить поддержку версий Steam для Flatpak и Snap
|
||||||
- [ ] Реализовать добавление игры как сторонней в Steam без перезапуска
|
- [X] Реализовать добавление игры как сторонней в Steam без перезапуска
|
||||||
- [X] Отображать данные о самом последнем пользователе Steam, а не первом попавшемся
|
- [X] Отображать данные о самом последнем пользователе Steam, а не первом попавшемся
|
||||||
- [X] Исправить склонения в детальном выводе времени, например, не «3 часов назад», а «3 часа назад»
|
- [X] Исправить склонения в детальном выводе времени, например, не «3 часов назад», а «3 часа назад»
|
||||||
- [X] Добавить перевод через gettext [Документация](documentation/localization_guide)
|
- [X] Добавить перевод через gettext [Документация](documentation/localization_guide)
|
||||||
|
@@ -6,7 +6,7 @@ arch=('any')
|
|||||||
url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
|
url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
|
||||||
license=('GPL-3.0')
|
license=('GPL-3.0')
|
||||||
depends=('python-numpy' 'python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson'
|
depends=('python-numpy' 'python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson'
|
||||||
'python-psutil' 'python-tqdm' 'python-vdf' 'pyside6' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4')
|
'python-psutil' 'python-tqdm' 'python-vdf' 'pyside6' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client')
|
||||||
makedepends=('python-'{'build','installer','setuptools','wheel'})
|
makedepends=('python-'{'build','installer','setuptools','wheel'})
|
||||||
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt#tag=v$pkgver")
|
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt#tag=v$pkgver")
|
||||||
sha256sums=('SKIP')
|
sha256sums=('SKIP')
|
||||||
|
@@ -6,7 +6,7 @@ arch=('any')
|
|||||||
url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
|
url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
|
||||||
license=('GPL-3.0')
|
license=('GPL-3.0')
|
||||||
depends=('python-numpy' 'python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson'
|
depends=('python-numpy' 'python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson'
|
||||||
'python-psutil' 'python-tqdm' 'python-vdf' 'pyside6' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4')
|
'python-psutil' 'python-tqdm' 'python-vdf' 'pyside6' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client')
|
||||||
makedepends=('python-'{'build','installer','setuptools','wheel'})
|
makedepends=('python-'{'build','installer','setuptools','wheel'})
|
||||||
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git")
|
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git")
|
||||||
sha256sums=('SKIP')
|
sha256sums=('SKIP')
|
||||||
|
@@ -33,6 +33,7 @@ Requires: python3-babel
|
|||||||
Requires: python3-evdev
|
Requires: python3-evdev
|
||||||
Requires: python3-icoextract
|
Requires: python3-icoextract
|
||||||
Requires: python3-numpy
|
Requires: python3-numpy
|
||||||
|
Requires: python3-websocket-client
|
||||||
Requires: python3-orjson
|
Requires: python3-orjson
|
||||||
Requires: python3-psutil
|
Requires: python3-psutil
|
||||||
Requires: python3-pyside6
|
Requires: python3-pyside6
|
||||||
|
@@ -30,6 +30,7 @@ Requires: python3-babel
|
|||||||
Requires: python3-evdev
|
Requires: python3-evdev
|
||||||
Requires: python3-icoextract
|
Requires: python3-icoextract
|
||||||
Requires: python3-numpy
|
Requires: python3-numpy
|
||||||
|
Requires: python3-websocket-client
|
||||||
Requires: python3-orjson
|
Requires: python3-orjson
|
||||||
Requires: python3-psutil
|
Requires: python3-psutil
|
||||||
Requires: python3-pyside6
|
Requires: python3-pyside6
|
||||||
|
@@ -18,6 +18,10 @@ from collections.abc import Callable
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import zlib
|
import zlib
|
||||||
|
import websocket
|
||||||
|
import requests
|
||||||
|
import random
|
||||||
|
import base64
|
||||||
|
|
||||||
downloader = Downloader()
|
downloader = Downloader()
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -771,6 +775,126 @@ def get_steam_apps_and_index_async(callback: Callable[[tuple[list, dict]], None]
|
|||||||
|
|
||||||
load_steam_apps_async(on_steam_apps)
|
load_steam_apps_async(on_steam_apps)
|
||||||
|
|
||||||
|
def enable_steam_cef() -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Проверяет и при необходимости активирует режим удаленной отладки Steam CEF.
|
||||||
|
|
||||||
|
Создает файл .cef-enable-remote-debugging в директории Steam.
|
||||||
|
Steam необходимо перезапустить после первого создания этого файла.
|
||||||
|
|
||||||
|
Возвращает кортеж:
|
||||||
|
- (True, "already_enabled") если уже было активно.
|
||||||
|
- (True, "restart_needed") если было только что активировано и нужен перезапуск Steam.
|
||||||
|
- (False, "steam_not_found") если директория Steam не найдена.
|
||||||
|
"""
|
||||||
|
steam_home = get_steam_home()
|
||||||
|
if not steam_home:
|
||||||
|
return (False, "steam_not_found")
|
||||||
|
|
||||||
|
cef_flag_file = steam_home / ".cef-enable-remote-debugging"
|
||||||
|
logger.info(f"Проверка CEF флага: {cef_flag_file}")
|
||||||
|
|
||||||
|
if cef_flag_file.exists():
|
||||||
|
logger.info("CEF Remote Debugging уже активирован.")
|
||||||
|
return (True, "already_enabled")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.makedirs(cef_flag_file.parent, exist_ok=True)
|
||||||
|
cef_flag_file.touch()
|
||||||
|
logger.info("CEF Remote Debugging активирован. Steam необходимо перезапустить.")
|
||||||
|
return (True, "restart_needed")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Не удалось создать CEF флаг {cef_flag_file}: {e}")
|
||||||
|
return (False, str(e))
|
||||||
|
|
||||||
|
def call_steam_api(js_cmd: str, *args) -> dict | None:
|
||||||
|
"""
|
||||||
|
Выполняет JavaScript функцию в контексте Steam через CEF Remote Debugging.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
js_cmd: Имя JS функции для вызова (напр. 'createShortcut').
|
||||||
|
*args: Аргументы для передачи в JS функцию.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь с результатом выполнения или None в случае ошибки.
|
||||||
|
"""
|
||||||
|
status, message = enable_steam_cef()
|
||||||
|
if not (status is True and message == "already_enabled"):
|
||||||
|
if message == "restart_needed":
|
||||||
|
logger.warning("Steam CEF API доступен, но требует перезапуска Steam для полной активации.")
|
||||||
|
elif message == "steam_not_found":
|
||||||
|
logger.error("Не удалось найти директорию Steam для проверки CEF API.")
|
||||||
|
else:
|
||||||
|
logger.error(f"Steam CEF API недоступен или не готов: {message}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
steam_debug_url = "http://localhost:8080/json"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(steam_debug_url, timeout=2)
|
||||||
|
response.raise_for_status()
|
||||||
|
contexts = response.json()
|
||||||
|
ws_url = next((ctx['webSocketDebuggerUrl'] for ctx in contexts if ctx['title'] == 'SharedJSContext'), None)
|
||||||
|
if not ws_url:
|
||||||
|
logger.warning("Не удалось найти SharedJSContext. Steam запущен с флагом -cef-enable-remote-debugging?")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Не удалось подключиться к Steam CEF API по адресу {steam_debug_url}. Steam запущен? {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
js_code = """
|
||||||
|
async function createShortcut(name, exe, dir, icon, args) {
|
||||||
|
const id = await SteamClient.Apps.AddShortcut(name, exe, dir, args);
|
||||||
|
console.log("Shortcut created with ID:", id);
|
||||||
|
await SteamClient.Apps.SetShortcutName(id, name);
|
||||||
|
if (icon)
|
||||||
|
await SteamClient.Apps.SetShortcutIcon(id, icon);
|
||||||
|
if (args)
|
||||||
|
await SteamClient.Apps.SetAppLaunchOptions(id, args);
|
||||||
|
return { id };
|
||||||
|
};
|
||||||
|
|
||||||
|
async function setGrid(id, i, ext, image) {
|
||||||
|
await SteamClient.Apps.SetCustomArtworkForApp(id, image, ext, i);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function removeShortcut(id) {
|
||||||
|
await SteamClient.Apps.RemoveShortcut(+id);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ws = websocket.create_connection(ws_url, timeout=5)
|
||||||
|
js_args = ", ".join(orjson.dumps(arg).decode('utf-8') for arg in args)
|
||||||
|
expression = f"{js_code} {js_cmd}({js_args});"
|
||||||
|
payload = {
|
||||||
|
"id": random.randint(0, 32767),
|
||||||
|
"method": "Runtime.evaluate",
|
||||||
|
"params": {
|
||||||
|
"expression": expression,
|
||||||
|
"awaitPromise": True,
|
||||||
|
"returnByValue": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.send(orjson.dumps(payload))
|
||||||
|
response_str = ws.recv()
|
||||||
|
ws.close()
|
||||||
|
|
||||||
|
response_data = orjson.loads(response_str)
|
||||||
|
if "error" in response_data:
|
||||||
|
logger.error(f"Ошибка выполнения JS в Steam: {response_data['error']['message']}")
|
||||||
|
return None
|
||||||
|
result = response_data.get('result', {}).get('result', {})
|
||||||
|
if result.get('type') == 'object' and result.get('subtype') == 'error':
|
||||||
|
logger.error(f"Ошибка выполнения JS в Steam: {result.get('description')}")
|
||||||
|
return None
|
||||||
|
return result.get('value')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при взаимодействии с WebSocket Steam: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def add_to_steam(game_name: str, exec_line: str, cover_path: str) -> tuple[bool, str]:
|
def add_to_steam(game_name: str, exec_line: str, cover_path: str) -> tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
Add a non-Steam game to Steam via shortcuts.vdf with PortProton tag,
|
Add a non-Steam game to Steam via shortcuts.vdf with PortProton tag,
|
||||||
@@ -872,6 +996,25 @@ export START_FROM_STEAM=1
|
|||||||
grid_dir = user_dir / "config" / "grid"
|
grid_dir = user_dir / "config" / "grid"
|
||||||
os.makedirs(grid_dir, exist_ok=True)
|
os.makedirs(grid_dir, exist_ok=True)
|
||||||
|
|
||||||
|
appid = None
|
||||||
|
was_api_used = False
|
||||||
|
|
||||||
|
logger.info("Попытка добавления ярлыка через Steam CEF API...")
|
||||||
|
api_response = call_steam_api(
|
||||||
|
"createShortcut",
|
||||||
|
game_name,
|
||||||
|
script_path,
|
||||||
|
str(Path(script_path).parent),
|
||||||
|
icon_path,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
|
if api_response and isinstance(api_response, dict) and 'id' in api_response:
|
||||||
|
appid = api_response['id']
|
||||||
|
was_api_used = True
|
||||||
|
logger.info(f"Ярлык успешно добавлен через API. AppID: {appid}")
|
||||||
|
else:
|
||||||
|
logger.warning("Не удалось добавить ярлык через API. Используется запасной метод (запись в shortcuts.vdf).")
|
||||||
backup_path = f"{steam_shortcuts_path}.backup"
|
backup_path = f"{steam_shortcuts_path}.backup"
|
||||||
if os.path.exists(steam_shortcuts_path):
|
if os.path.exists(steam_shortcuts_path):
|
||||||
try:
|
try:
|
||||||
@@ -889,28 +1032,6 @@ export START_FROM_STEAM=1
|
|||||||
else:
|
else:
|
||||||
aidvdf = appid
|
aidvdf = appid
|
||||||
|
|
||||||
steam_appid = None
|
|
||||||
downloaded_count = 0
|
|
||||||
total_covers = 4 # количество обложек
|
|
||||||
|
|
||||||
download_lock = threading.Lock()
|
|
||||||
|
|
||||||
def on_cover_download(cover_file: str, cover_type: str):
|
|
||||||
nonlocal downloaded_count
|
|
||||||
try:
|
|
||||||
if cover_file and os.path.exists(cover_file):
|
|
||||||
logger.info(f"Downloaded cover {cover_type} to {cover_file}")
|
|
||||||
else:
|
|
||||||
logger.warning(f"Failed to download cover {cover_type} for appid {steam_appid}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing cover {cover_type} for appid {steam_appid}: {e}")
|
|
||||||
with download_lock:
|
|
||||||
downloaded_count += 1
|
|
||||||
if downloaded_count == total_covers:
|
|
||||||
finalize_shortcut()
|
|
||||||
|
|
||||||
def finalize_shortcut():
|
|
||||||
tags_dict = {'0': 'PortProton'}
|
|
||||||
shortcut = {
|
shortcut = {
|
||||||
"appid": aidvdf,
|
"appid": aidvdf,
|
||||||
"AppName": game_name,
|
"AppName": game_name,
|
||||||
@@ -925,7 +1046,7 @@ export START_FROM_STEAM=1
|
|||||||
"Devkit": 0,
|
"Devkit": 0,
|
||||||
"DevkitGameID": "",
|
"DevkitGameID": "",
|
||||||
"LastPlayTime": 0,
|
"LastPlayTime": 0,
|
||||||
"tags": tags_dict
|
"tags": {'0': 'PortProton'}
|
||||||
}
|
}
|
||||||
logger.info(f"Shortcut entry to be written: {shortcut}")
|
logger.info(f"Shortcut entry to be written: {shortcut}")
|
||||||
|
|
||||||
@@ -955,6 +1076,7 @@ export START_FROM_STEAM=1
|
|||||||
|
|
||||||
with open(steam_shortcuts_path, 'wb') as f:
|
with open(steam_shortcuts_path, 'wb') as f:
|
||||||
vdf.binary_dump({"shortcuts": shortcuts}, f)
|
vdf.binary_dump({"shortcuts": shortcuts}, f)
|
||||||
|
logger.info(f"Game '{game_name}' successfully added to Steam with covers")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to update shortcuts.vdf: {e}")
|
logger.error(f"Failed to update shortcuts.vdf: {e}")
|
||||||
if os.path.exists(backup_path):
|
if os.path.exists(backup_path):
|
||||||
@@ -963,34 +1085,54 @@ export START_FROM_STEAM=1
|
|||||||
logger.info("Restored shortcuts.vdf from backup due to update failure")
|
logger.info("Restored shortcuts.vdf from backup due to update failure")
|
||||||
except Exception as restore_err:
|
except Exception as restore_err:
|
||||||
logger.error(f"Failed to restore shortcuts.vdf from backup: {restore_err}")
|
logger.error(f"Failed to restore shortcuts.vdf from backup: {restore_err}")
|
||||||
return (False, f"Failed to update shortcuts.vdf: {e}")
|
appid = None
|
||||||
|
|
||||||
logger.info(f"Game '{game_name}' successfully added to Steam with covers")
|
if not appid:
|
||||||
return (True, f"Game '{game_name}' added to Steam with covers")
|
return (False, "Не удалось создать ярлык ни одним из способов.")
|
||||||
|
|
||||||
|
steam_appid = None
|
||||||
|
|
||||||
def on_game_info(game_info: dict):
|
def on_game_info(game_info: dict):
|
||||||
nonlocal steam_appid
|
nonlocal steam_appid
|
||||||
steam_appid = game_info.get("appid")
|
steam_appid = game_info.get("appid")
|
||||||
if not steam_appid or not isinstance(steam_appid, int):
|
if not steam_appid or not isinstance(steam_appid, int):
|
||||||
logger.info("No valid Steam appid found, skipping cover download")
|
logger.info("No valid Steam appid found, skipping cover download")
|
||||||
return finalize_shortcut()
|
return
|
||||||
|
logger.info(f"Найден Steam AppID {steam_appid} для загрузки обложек.")
|
||||||
|
|
||||||
# Обложки и имена, соответствующие bash-скрипту и твоим размерам
|
|
||||||
cover_types = [
|
cover_types = [
|
||||||
(".jpg", "header.jpg"), # базовый, сохранится как AppId.jpg
|
("p.jpg", "library_600x900_2x.jpg"),
|
||||||
("p.jpg", "library_600x900_2x.jpg"), # сохранится как AppIdp.jpg
|
("_hero.jpg", "library_hero.jpg"),
|
||||||
("_hero.jpg", "library_hero.jpg"), # AppId_hero.jpg
|
("_logo.png", "logo.png"),
|
||||||
("_logo.png", "logo.png") # AppId_logo.png
|
(".jpg", "header.jpg")
|
||||||
]
|
]
|
||||||
|
|
||||||
for suffix, cover_type in cover_types:
|
def on_cover_download(result_path: str | None, steam_name: str, index: int):
|
||||||
|
try:
|
||||||
|
if result_path and os.path.exists(result_path):
|
||||||
|
logger.info(f"Downloaded cover {steam_name} to {result_path}")
|
||||||
|
if was_api_used:
|
||||||
|
try:
|
||||||
|
with open(result_path, 'rb') as f:
|
||||||
|
img_b64 = base64.b64encode(f.read()).decode('utf-8')
|
||||||
|
logger.info(f"Применение обложки типа '{steam_name}' через API для AppID {appid}")
|
||||||
|
ext = Path(steam_name).suffix.lstrip('.')
|
||||||
|
call_steam_api("setGrid", appid, index, ext, img_b64)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при применении обложки '{steam_name}' через API: {e}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"Failed to download cover {steam_name} for appid {steam_appid}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing cover {steam_name} for appid {steam_appid}: {e}")
|
||||||
|
|
||||||
|
for i, (suffix, steam_name) in enumerate(cover_types):
|
||||||
cover_file = os.path.join(grid_dir, f"{appid}{suffix}")
|
cover_file = os.path.join(grid_dir, f"{appid}{suffix}")
|
||||||
cover_url = f"https://cdn.cloudflare.steamstatic.com/steam/apps/{steam_appid}/{cover_type}"
|
cover_url = f"https://cdn.cloudflare.steamstatic.com/steam/apps/{steam_appid}/{steam_name}"
|
||||||
downloader.download_async(
|
downloader.download_async(
|
||||||
cover_url,
|
cover_url,
|
||||||
cover_file,
|
cover_file,
|
||||||
timeout=5,
|
timeout=5,
|
||||||
callback=lambda result, cfile=cover_file, ctype=cover_type: on_cover_download(cfile, ctype)
|
callback=lambda result, index=i, name=steam_name: on_cover_download(result, name, index)
|
||||||
)
|
)
|
||||||
|
|
||||||
get_steam_game_info_async(game_name, exec_line, on_game_info)
|
get_steam_game_info_async(game_name, exec_line, on_game_info)
|
||||||
@@ -1043,19 +1185,7 @@ def remove_from_steam(game_name: str, exec_line: str) -> tuple[bool, str]:
|
|||||||
logger.info(f"shortcuts.vdf not found at {steam_shortcuts_path}")
|
logger.info(f"shortcuts.vdf not found at {steam_shortcuts_path}")
|
||||||
return (False, f"Game '{game_name}' not found in Steam")
|
return (False, f"Game '{game_name}' not found in Steam")
|
||||||
|
|
||||||
# Generate appid for identifying cover files
|
appid = None
|
||||||
unique_string = f"{script_path}{game_name}"
|
|
||||||
baseid = zlib.crc32(unique_string.encode('utf-8')) & 0xffffffff
|
|
||||||
appid = baseid | 0x80000000
|
|
||||||
|
|
||||||
# Create backup of shortcuts.vdf
|
|
||||||
backup_path = f"{steam_shortcuts_path}.backup"
|
|
||||||
try:
|
|
||||||
shutil.copy2(steam_shortcuts_path, backup_path)
|
|
||||||
logger.info(f"Created backup of shortcuts.vdf at {backup_path}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to create backup of shortcuts.vdf: {e}")
|
|
||||||
return (False, f"Failed to create backup of shortcuts.vdf: {e}")
|
|
||||||
|
|
||||||
# Load and modify shortcuts.vdf
|
# Load and modify shortcuts.vdf
|
||||||
try:
|
try:
|
||||||
@@ -1069,23 +1199,37 @@ def remove_from_steam(game_name: str, exec_line: str) -> tuple[bool, str]:
|
|||||||
return (False, f"Failed to load shortcuts.vdf: {load_err}")
|
return (False, f"Failed to load shortcuts.vdf: {load_err}")
|
||||||
|
|
||||||
shortcuts = shortcuts_data.get("shortcuts", {})
|
shortcuts = shortcuts_data.get("shortcuts", {})
|
||||||
found = False
|
|
||||||
new_shortcuts = {}
|
new_shortcuts = {}
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
# Filter out the matching shortcut
|
# Filter out the matching shortcut
|
||||||
for _key, entry in shortcuts.items():
|
for _key, entry in shortcuts.items():
|
||||||
if entry.get("AppName") == game_name and entry.get("Exe") == f'"{script_path}"':
|
if entry.get("AppName") == game_name and entry.get("Exe") == f'"{script_path}"':
|
||||||
found = True
|
appid = convert_steam_id(int(entry.get("appid")))
|
||||||
logger.info(f"Found matching shortcut for '{game_name}' to remove")
|
logger.info(f"Found matching shortcut for '{game_name}' to remove")
|
||||||
continue
|
continue
|
||||||
new_shortcuts[str(index)] = entry
|
new_shortcuts[str(index)] = entry
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
if not found:
|
if not appid:
|
||||||
logger.info(f"Game '{game_name}' not found in Steam shortcuts")
|
logger.info(f"Game '{game_name}' not found in Steam shortcuts")
|
||||||
return (False, f"Game '{game_name}' not found in Steam")
|
return (False, f"Game '{game_name}' not found in Steam")
|
||||||
|
|
||||||
|
api_response = call_steam_api("removeShortcut", appid)
|
||||||
|
if api_response is not None: # API ответил, даже если ответ пустой
|
||||||
|
logger.info(f"Ярлык для AppID {appid} успешно удален через API.")
|
||||||
|
else:
|
||||||
|
logger.warning("Не удалось удалить ярлык через API. Используется запасной метод (редактирование shortcuts.vdf).")
|
||||||
|
|
||||||
|
# Create backup of shortcuts.vdf
|
||||||
|
backup_path = f"{steam_shortcuts_path}.backup"
|
||||||
|
try:
|
||||||
|
shutil.copy2(steam_shortcuts_path, backup_path)
|
||||||
|
logger.info(f"Created backup of shortcuts.vdf at {backup_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to create backup of shortcuts.vdf: {e}")
|
||||||
|
return (False, f"Failed to create backup of shortcuts.vdf: {e}")
|
||||||
|
|
||||||
# Save updated shortcuts.vdf
|
# Save updated shortcuts.vdf
|
||||||
try:
|
try:
|
||||||
with open(steam_shortcuts_path, 'wb') as f:
|
with open(steam_shortcuts_path, 'wb') as f:
|
||||||
|
@@ -39,6 +39,7 @@ dependencies = [
|
|||||||
"requests>=2.32.3",
|
"requests>=2.32.3",
|
||||||
"tqdm>=4.67.1",
|
"tqdm>=4.67.1",
|
||||||
"vdf>=3.4",
|
"vdf>=3.4",
|
||||||
|
"websocket-client>=1.8.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
11
uv.lock
generated
11
uv.lock
generated
@@ -482,6 +482,7 @@ dependencies = [
|
|||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "tqdm" },
|
{ name = "tqdm" },
|
||||||
{ name = "vdf" },
|
{ name = "vdf" },
|
||||||
|
{ name = "websocket-client" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
@@ -506,6 +507,7 @@ requires-dist = [
|
|||||||
{ name = "requests", specifier = ">=2.32.3" },
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
{ name = "tqdm", specifier = ">=4.67.1" },
|
{ name = "tqdm", specifier = ">=4.67.1" },
|
||||||
{ name = "vdf", specifier = ">=3.4" },
|
{ name = "vdf", specifier = ">=3.4" },
|
||||||
|
{ name = "websocket-client", specifier = ">=1.8.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
@@ -760,3 +762,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c
|
|||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982 },
|
{ url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "websocket-client"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 },
|
||||||
|
]
|
||||||
|
Reference in New Issue
Block a user