Merge branch 'main' into feature/rpc

This commit is contained in:
Zamitto 2024-12-18 12:29:07 -03:00 committed by GitHub
commit ad59fcd3e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 717 additions and 552 deletions

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="./resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="../resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="./resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="../resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="../resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="./resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -1,6 +1,6 @@
<div align="center"> <div align="center">
[<img src="../resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="../resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="./resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="./resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="./resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="./resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="../resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="../resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -2,7 +2,7 @@
<div align="center"> <div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site) [<img src="./resources/icon.png" width="144"/>](https://help.hydralauncher.gg)
<h1 align="center">Hydra Launcher</h1> <h1 align="center">Hydra Launcher</h1>

View File

@ -34,24 +34,24 @@
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^3.0.0", "@electron-toolkit/preload": "^3.0.0",
"@electron-toolkit/utils": "^3.0.0", "@electron-toolkit/utils": "^3.0.0",
"@fontsource/noto-sans": "^5.0.22", "@fontsource/noto-sans": "^5.1.0",
"@hookform/resolvers": "^3.9.0", "@hookform/resolvers": "^3.9.1",
"@primer/octicons-react": "^19.9.0", "@primer/octicons-react": "^19.9.0",
"@reduxjs/toolkit": "^2.2.3", "@reduxjs/toolkit": "^2.2.3",
"@vanilla-extract/css": "^1.14.2", "@vanilla-extract/css": "^1.14.2",
"@vanilla-extract/dynamic": "^2.1.1", "@vanilla-extract/dynamic": "^2.1.2",
"@vanilla-extract/recipes": "^0.5.2", "@vanilla-extract/recipes": "^0.5.2",
"auto-launch": "^5.0.6", "auto-launch": "^5.0.6",
"axios": "^1.7.7", "axios": "^1.7.9",
"better-sqlite3": "^11.3.0", "better-sqlite3": "^11.7.0",
"check-disk-space": "^3.4.0", "check-disk-space": "^3.4.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"color": "^4.2.3", "color": "^4.2.3",
"color.js": "^1.2.0", "color.js": "^1.2.0",
"create-desktop-shortcuts": "^1.11.0", "create-desktop-shortcuts": "^1.11.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"dexie": "^4.0.9", "dexie": "^4.0.10",
"electron-log": "^5.2.0", "electron-log": "^5.2.4",
"electron-updater": "^6.3.9", "electron-updater": "^6.3.9",
"file-type": "^19.6.0", "file-type": "^19.6.0",
"flexsearch": "^0.7.43", "flexsearch": "^0.7.43",
@ -72,14 +72,14 @@
"sudo-prompt": "^9.2.1", "sudo-prompt": "^9.2.1",
"tar": "^7.4.3", "tar": "^7.4.3",
"typeorm": "^0.3.20", "typeorm": "^0.3.20",
"user-agents": "^1.1.193", "user-agents": "^1.1.387",
"yaml": "^2.4.1", "yaml": "^2.6.1",
"yup": "^1.4.0", "yup": "^1.5.0",
"zod": "^3.23.8" "zod": "^3.24.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.5.0", "@commitlint/cli": "^19.6.0",
"@commitlint/config-conventional": "^19.5.0", "@commitlint/config-conventional": "^19.6.0",
"@electron-toolkit/eslint-config-prettier": "^2.0.0", "@electron-toolkit/eslint-config-prettier": "^2.0.0",
"@electron-toolkit/eslint-config-ts": "^2.0.0", "@electron-toolkit/eslint-config-ts": "^2.0.0",
"@electron-toolkit/tsconfig": "^1.0.1", "@electron-toolkit/tsconfig": "^1.0.1",
@ -87,8 +87,8 @@
"@types/auto-launch": "^5.0.5", "@types/auto-launch": "^5.0.5",
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/folder-hash": "^4.0.4", "@types/folder-hash": "^4.0.4",
"@types/jsdom": "^21.1.6", "@types/jsdom": "^21.1.7",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.7",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.7", "@types/node": "^20.12.7",
"@types/parse-torrent": "^5.8.7", "@types/parse-torrent": "^5.8.7",
@ -98,15 +98,15 @@
"@types/user-agents": "^1.0.4", "@types/user-agents": "^1.0.4",
"@vanilla-extract/vite-plugin": "^4.0.7", "@vanilla-extract/vite-plugin": "^4.0.7",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"electron": "^30.3.0", "electron": "^31.7.6",
"electron-builder": "^25.1.8", "electron-builder": "^25.1.8",
"electron-vite": "^2.0.0", "electron-vite": "^2.0.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"husky": "^9.0.11", "husky": "^9.1.7",
"prettier": "^3.2.4", "prettier": "^3.4.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"sass-embedded": "^1.80.6", "sass-embedded": "^1.80.6",

View File

@ -258,7 +258,8 @@
"user_unblocked": "User has been unblocked", "user_unblocked": "User has been unblocked",
"enable_achievement_notifications": "When an achievement is unlocked", "enable_achievement_notifications": "When an achievement is unlocked",
"launch_minimized": "Launch Hydra minimized", "launch_minimized": "Launch Hydra minimized",
"disable_nsfw_alert": "Disable NSFW alert" "disable_nsfw_alert": "Disable NSFW alert",
"show_hidden_achievement_description": "Show hidden achievements description before unlocking them"
}, },
"notifications": { "notifications": {
"download_complete": "Download complete", "download_complete": "Download complete",

View File

@ -254,7 +254,8 @@
"user_unblocked": "Usuário desbloqueado", "user_unblocked": "Usuário desbloqueado",
"enable_achievement_notifications": "Quando uma conquista é desbloqueada", "enable_achievement_notifications": "Quando uma conquista é desbloqueada",
"launch_minimized": "Iniciar o Hydra minimizado", "launch_minimized": "Iniciar o Hydra minimizado",
"disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado" "disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado",
"show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las"
}, },
"notifications": { "notifications": {
"download_complete": "Download concluído", "download_complete": "Download concluído",

View File

@ -4,12 +4,13 @@
"successfully_signed_in": "Успешный вход" "successfully_signed_in": "Успешный вход"
}, },
"home": { "home": {
"featured": "Рекомендованное", "featured": "Рекомендации",
"surprise_me": "Удиви меня", "surprise_me": "Удиви меня",
"no_results": "Ничего не найдено", "no_results": "Ничего не найдено",
"hot": "Сейчас в топе", "hot": "Сейчас в топе",
"start_typing": "Начинаю вводить текст для поиска...", "start_typing": "Начинаю вводить текст для поиска...",
"weekly": "📅 Лучшие игры недели" "weekly": "📅 Лучшие игры недели",
"achievements": "🏆 Игры, в которых нужно победить"
}, },
"sidebar": { "sidebar": {
"catalogue": "Каталог", "catalogue": "Каталог",
@ -19,7 +20,7 @@
"downloading_metadata": "{{title}} (Загрузка метаданных…)", "downloading_metadata": "{{title}} (Загрузка метаданных…)",
"paused": "{{title}} (Приостановлено)", "paused": "{{title}} (Приостановлено)",
"downloading": "{{title}} ({{percentage}} - Загрузка…)", "downloading": "{{title}} ({{percentage}} - Загрузка…)",
"filter": "Фильтр библиотеки", "filter": "Поиск",
"home": "Главная", "home": "Главная",
"queued": "{{title}} (В очереди)", "queued": "{{title}} (В очереди)",
"game_has_no_executable": "Файл запуска игры не выбран", "game_has_no_executable": "Файл запуска игры не выбран",
@ -49,10 +50,10 @@
"previous_page": "Предыдущая страница" "previous_page": "Предыдущая страница"
}, },
"game_details": { "game_details": {
"open_download_options": "Открыть варианты загрузки", "open_download_options": "Открыть источники",
"download_options_zero": "Нет вариантов загрузки", "download_options_zero": "Нет источников",
"download_options_one": "{{count}} вариант загрузки", "download_options_one": "{{count}} источник",
"download_options_other": "{{count}} вариантов загрузки", "download_options_other": "{{count}} источников",
"updated_at": "Обновлено {{updated_at}}", "updated_at": "Обновлено {{updated_at}}",
"install": "Установить", "install": "Установить",
"resume": "Возобновить", "resume": "Возобновить",
@ -63,7 +64,7 @@
"eta": "Окончание {{eta}}", "eta": "Окончание {{eta}}",
"calculating_eta": "Подсчёт оставшегося времени…", "calculating_eta": "Подсчёт оставшегося времени…",
"downloading_metadata": "Загрузка метаданных…", "downloading_metadata": "Загрузка метаданных…",
"filter": "Фильтр репаков", "filter": "Поиск репаков",
"requirements": "Системные требования", "requirements": "Системные требования",
"minimum": "Минимальные", "minimum": "Минимальные",
"recommended": "Рекомендуемые", "recommended": "Рекомендуемые",
@ -77,7 +78,7 @@
"accuracy": "точность {{accuracy}}%", "accuracy": "точность {{accuracy}}%",
"add_to_library": "Добавить в библиотеку", "add_to_library": "Добавить в библиотеку",
"remove_from_library": "Удалить из библиотеки", "remove_from_library": "Удалить из библиотеки",
"no_downloads": "Нет доступных загрузок", "no_downloads": "Нет доступных источников",
"play_time": "Сыграно {{amount}}", "play_time": "Сыграно {{amount}}",
"last_time_played": "Последний запуск {{period}}", "last_time_played": "Последний запуск {{period}}",
"not_played_yet": "Вы ещё не играли в {{title}}", "not_played_yet": "Вы ещё не играли в {{title}}",
@ -91,7 +92,7 @@
"select_folder_hint": "Чтобы изменить папку загрузок по умолчанию, откройте <0>Настройки</0>", "select_folder_hint": "Чтобы изменить папку загрузок по умолчанию, откройте <0>Настройки</0>",
"download_now": "Загрузить сейчас", "download_now": "Загрузить сейчас",
"no_shop_details": "Не удалось получить описание", "no_shop_details": "Не удалось получить описание",
"download_options": "Вариантов загрузки", "download_options": "Источники",
"download_path": "Путь для загрузок", "download_path": "Путь для загрузок",
"previous_screenshot": "Предыдущий скриншот", "previous_screenshot": "Предыдущий скриншот",
"next_screenshot": "Следующий скриншот", "next_screenshot": "Следующий скриншот",
@ -119,16 +120,53 @@
"last_downloaded_option": "Последний вариант загрузки", "last_downloaded_option": "Последний вариант загрузки",
"create_shortcut_success": "Ярлык создан", "create_shortcut_success": "Ярлык создан",
"create_shortcut_error": "Не удалось создать ярлык", "create_shortcut_error": "Не удалось создать ярлык",
"allow_nsfw_content": "Продолжать", "allow_nsfw_content": "Продолжить",
"download": "Скачать", "download": "Скачать",
"download_count": "Загрузки", "download_count": "Загрузки",
"download_error": "Этот вариант загрузки недоступен", "download_error": "Этот вариант загрузки недоступен",
"executable_path_in_use": "Исполняемый файл уже используется \"{{game}}\"", "executable_path_in_use": "Исполняемый файл уже используется \"{{game}}\"",
"nsfw_content_description": "{{title}} содержит контент, который может не подходить для всех возрастов. \nВы уверены, что хотите продолжить?", "nsfw_content_description": "{{title}} содержит контент, который может не подходить для всех возрастов. \nВы уверены, что хотите продолжить?",
"nsfw_content_title": "Эта игра содержит неприемлемый контент", "nsfw_content_title": "Эта игра содержит неприемлемый контент",
"refuse_nsfw_content": "Назад",
"stats": "Статистика",
"player_count": "Активные игроки", "player_count": "Активные игроки",
"refuse_nsfw_content": "Возвращаться", "warning": "Внимание:",
"stats": "Статистика" "hydra_needs_to_remain_open": "Для этой загрузки Hydra должна оставаться открытой до завершения. Если Hydra закроется до завершения, вы потеряете прогресс.",
"achievements": "Достижения",
"achievements_count": "Достижения {{unlockedCount}}/{{achievementsCount}}",
"cloud_save": "Облачное сохранение",
"cloud_save_description": "Сохраняйте ваш прогресс в облаке и продолжайте играть на любом устройстве",
"backups": "Резервные копии",
"install_backup": "Установить",
"delete_backup": "Удалить",
"create_backup": "Создать новую резервную копию",
"last_backup_date": "Последняя резервная копия от {{date}}",
"no_backup_preview": "Сохранения для этого заголовка не найдены",
"restoring_backup": "Восстановление резервной копии ({{progress}} завершено)…",
"uploading_backup": "Загрузка резервной копии…",
"no_backups": "Вы еще не создали резервных копий для этой игры",
"backup_uploaded": "Резервная копия загружена",
"backup_deleted": "Резервная копия удалена",
"backup_restored": "Резервная копия восстановлена",
"see_all_achievements": "Просмотреть все достижения",
"sign_in_to_see_achievements": "Войдите, чтобы увидеть достижения",
"mapping_method_automatic": "Автоматическая",
"mapping_method_manual": "Ручная",
"mapping_method_label": "Метод сопоставления",
"files_automatically_mapped": "Файлы автоматически сопоставлены",
"no_backups_created": "Для этой игры не создано резервных копий",
"manage_files": "Управление файлами",
"loading_save_preview": "Поиск сохранений…",
"wine_prefix": "Префикс Wine",
"wine_prefix_description": "Префикс Wine, используемый для запуска этой игры",
"no_download_option_info": "Информация недоступна",
"backup_deletion_failed": "Не удалось удалить резервную копию",
"max_number_of_artifacts_reached": "Достигнуто максимальное количество резервных копий для этой игры",
"achievements_not_sync": "Ваши достижения не синхронизированы",
"manage_files_description": "Управляйте файлами, которые будут сохраняться и восстанавливаться",
"select_folder": "Выбрать папку",
"backup_from": "Резервная копия от {{date}}",
"custom_backup_location_set": "Установлено настраиваемое местоположение резервной копии"
}, },
"activation": { "activation": {
"title": "Активировать Hydra", "title": "Активировать Hydra",
@ -147,7 +185,7 @@
"completed": "Завершено", "completed": "Завершено",
"removed": "Не скачано", "removed": "Не скачано",
"cancel": "Отмена", "cancel": "Отмена",
"filter": "Фильтр загруженных игр", "filter": "Поиск загруженных игр",
"remove": "Удалить", "remove": "Удалить",
"downloading_metadata": "Загрузка метаданных…", "downloading_metadata": "Загрузка метаданных…",
"deleting": "Удаление установщика…", "deleting": "Удаление установщика…",
@ -168,10 +206,13 @@
"change": "Изменить", "change": "Изменить",
"notifications": "Уведомления", "notifications": "Уведомления",
"enable_download_notifications": "По завершении загрузки", "enable_download_notifications": "По завершении загрузки",
"enable_achievement_notifications": "Когда достижение разблокировано",
"enable_repack_list_notifications": "При добавлении нового репака", "enable_repack_list_notifications": "При добавлении нового репака",
"real_debrid_api_token_label": "Real-Debrid API-токен", "real_debrid_api_token_label": "Real-Debrid API-токен",
"quit_app_instead_hiding": "Закрывать приложение вместо сворачивания в трей", "quit_app_instead_hiding": "Закрывать приложение вместо сворачивания в трей",
"launch_with_system": "Запускать Hydra вместе с системой", "launch_with_system": "Запускать Hydra вместе с системой",
"launch_minimized": "Запустить Hydra в свернутом виде",
"disable_nsfw_alert": "Отключить предупреждение о непристойном контенте",
"general": "Основные", "general": "Основные",
"behavior": "Поведение", "behavior": "Поведение",
"download_sources": "Источники загрузки", "download_sources": "Источники загрузки",
@ -196,7 +237,7 @@
"add_download_source_description": "Вставьте ссылку на .json-файл", "add_download_source_description": "Вставьте ссылку на .json-файл",
"download_source_up_to_date": "Обновлён", "download_source_up_to_date": "Обновлён",
"download_source_errored": "Ошибка", "download_source_errored": "Ошибка",
"sync_download_sources": "Синхронизировать источники", "sync_download_sources": "Обновить источники",
"removed_download_source": "Источник загрузок удален", "removed_download_source": "Источник загрузок удален",
"added_download_source": "Источник загрузок добавлен", "added_download_source": "Источник загрузок добавлен",
"download_sources_synced": "Все источники загрузок синхронизированы", "download_sources_synced": "Все источники загрузок синхронизированы",
@ -206,13 +247,13 @@
"found_download_option_other": "Найдено {{countFormatted}} вариантов загрузки", "found_download_option_other": "Найдено {{countFormatted}} вариантов загрузки",
"import": "Импортировать", "import": "Импортировать",
"blocked_users": "Заблокированные пользователи", "blocked_users": "Заблокированные пользователи",
"friends_only": "Только друзья", "friends_only": "Только для друзей",
"must_be_valid_url": "Источник должен быть действительным URL-адресом.", "must_be_valid_url": "Источник должен быть действительным URL-адресом.",
"privacy": "Конфиденциальность", "privacy": "Конфиденциальность",
"private": "Частный", "private": "Частный",
"profile_visibility": "Видимость профиля", "profile_visibility": "Видимость профиля",
"profile_visibility_description": "Выберите, кто может видеть ваш профиль и библиотеку", "profile_visibility_description": "Выберите, кто может видеть ваш профиль и библиотеку",
"public": "Общественный", "public": "Публичный",
"required_field": "Это поле обязательно к заполнению", "required_field": "Это поле обязательно к заполнению",
"source_already_exists": "Этот источник уже добавлен", "source_already_exists": "Этот источник уже добавлен",
"user_unblocked": "Пользователь разблокирован" "user_unblocked": "Пользователь разблокирован"
@ -223,15 +264,17 @@
"repack_list_updated": "Список репаков обновлен", "repack_list_updated": "Список репаков обновлен",
"repack_count_one": "{{count}} репак добавлен", "repack_count_one": "{{count}} репак добавлен",
"repack_count_other": "{{count}} репаков добавлено", "repack_count_other": "{{count}} репаков добавлено",
"new_update_available": "Доступна версия {{version}}", "new_update_available": "Доступна новая версия {{version}}",
"restart_to_install_update": "Перезапустите Hydra для установки обновления" "restart_to_install_update": "Перезапустите Hydra для установки обновления",
"notification_achievement_unlocked_title": "Достижение разблокировано для {{game}}",
"notification_achievement_unlocked_body": "были разблокированы {{achievement}} и другие {{count}}"
}, },
"system_tray": { "system_tray": {
"open": "Открыть Hydra", "open": "Открыть Hydra",
"quit": "Выйти" "quit": "Выйти"
}, },
"game_card": { "game_card": {
"no_downloads": "Нет доступных загрузок" "no_downloads": "Нет доступных источников"
}, },
"binary_not_found_modal": { "binary_not_found_modal": {
"title": "Программы не установлены", "title": "Программы не установлены",
@ -310,5 +353,25 @@
"report_reason_violence": "Насилие", "report_reason_violence": "Насилие",
"required_field": "Это поле обязательно к заполнению", "required_field": "Это поле обязательно к заполнению",
"undo_friendship_modal_text": "Это отменит вашу дружбу с {{displayName}}." "undo_friendship_modal_text": "Это отменит вашу дружбу с {{displayName}}."
},
"achievement": {
"achievement_unlocked": "Достижение разблокировано",
"user_achievements": "Достижения {{displayName}}",
"your_achievements": "Ваши достижения",
"unlocked_at": "Разблокировано:",
"subscription_needed": "Для просмотра этого содержимого необходима подписка на Hydra Cloud",
"new_achievements_unlocked": "Разблокировано {{achievementCount}} новых достижений из {{gameCount}} игр",
"achievement_progress": "{{unlockedCount}}/{{totalCount}} достижений",
"achievements_unlocked_for_game": "Разблокировано {{achievementCount}} новых достижений для {{gameTitle}}"
},
"tour": {
"subscription_tour_title": "Подписка Hydra Cloud",
"subscribe_now": "Подпишитесь прямо сейчас",
"cloud_saving": "Сохранение в облаке",
"cloud_achievements": "Сохраняйте свои достижения в облаке",
"animated_profile_picture": "Анимированные фотографии профиля",
"premium_support": "Премиальная поддержка",
"show_and_compare_achievements": "Показывайте и сравнивайте свои достижения с достижениями других пользователей",
"animated_profile_banner": "Анимированный баннер профиля"
} }
} }

View File

@ -41,6 +41,9 @@ export class UserPreferences {
@Column("boolean", { default: false }) @Column("boolean", { default: false })
disableNsfwAlert: boolean; disableNsfwAlert: boolean;
@Column("boolean", { default: false })
showHiddenAchievementsDescription: boolean;
@CreateDateColumn() @CreateDateColumn()
createdAt: Date; createdAt: Date;

View File

@ -1,6 +1,6 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import type { StartGameDownloadPayload } from "@types"; import type { StartGameDownloadPayload } from "@types";
import { DownloadManager, HydraApi, logger } from "@main/services"; import { DownloadManager, HydraApi } from "@main/services";
import { Not } from "typeorm"; import { Not } from "typeorm";
import { steamGamesWorker } from "@main/workers"; import { steamGamesWorker } from "@main/workers";
@ -76,8 +76,14 @@ const startGameDownload = async (
}, },
}); });
createGame(updatedGame!).catch(() => {}); await DownloadManager.cancelDownload(updatedGame!.id);
await DownloadManager.startDownload(updatedGame!);
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
await Promise.all([
createGame(updatedGame!).catch(() => {}),
HydraApi.post( HydraApi.post(
"/games/download", "/games/download",
{ {
@ -85,15 +91,8 @@ const startGameDownload = async (
shop: updatedGame!.shop, shop: updatedGame!.shop,
}, },
{ needsAuth: false } { needsAuth: false }
).catch((err) => { ).catch(() => {}),
logger.error("Failed to create game download", err); ]);
});
await DownloadManager.cancelDownload(updatedGame!.id);
await DownloadManager.startDownload(updatedGame!);
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
}); });
}; };

View File

@ -1,6 +1,9 @@
import type { GameShop, UnlockedAchievement, UserAchievement } from "@types"; import type { GameShop, UnlockedAchievement, UserAchievement } from "@types";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gameAchievementRepository } from "@main/repository"; import {
gameAchievementRepository,
userPreferencesRepository,
} from "@main/repository";
import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data"; import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data";
export const getUnlockedAchievements = async ( export const getUnlockedAchievements = async (
@ -12,10 +15,17 @@ export const getUnlockedAchievements = async (
where: { objectId, shop }, where: { objectId, shop },
}); });
const userPreferences = await userPreferencesRepository.findOne({
where: { id: 1 },
});
const showHiddenAchievementsDescription =
userPreferences?.showHiddenAchievementsDescription || false;
const achievementsData = await getGameAchievementData( const achievementsData = await getGameAchievementData(
objectId, objectId,
shop, shop,
useCachedData useCachedData ? cachedAchievements : null
); );
const unlockedAchievements = JSON.parse( const unlockedAchievements = JSON.parse(
@ -50,6 +60,10 @@ export const getUnlockedAchievements = async (
unlocked: false, unlocked: false,
unlockTime: null, unlockTime: null,
icongray: icongray, icongray: icongray,
description:
!achievementData.hidden || showHiddenAchievementsDescription
? achievementData.description
: undefined,
} as UserAchievement; } as UserAchievement;
}) })
.sort((a, b) => { .sort((a, b) => {

View File

@ -13,6 +13,8 @@ import { AddBackgroundImageUrl } from "./migrations/20241016100249_add_backgroun
import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game"; import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game";
import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column"; import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column";
import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_column"; import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_column";
import { AddHiddenAchievementDescriptionColumn } from "./migrations/20241216140633_add_hidden_achievement_description_column ";
export type HydraMigration = Knex.Migration & { name: string }; export type HydraMigration = Knex.Migration & { name: string };
class MigrationSource implements Knex.MigrationSource<HydraMigration> { class MigrationSource implements Knex.MigrationSource<HydraMigration> {
@ -30,6 +32,7 @@ class MigrationSource implements Knex.MigrationSource<HydraMigration> {
AddWinePrefixToGame, AddWinePrefixToGame,
AddStartMinimizedColumn, AddStartMinimizedColumn,
AddDisableNsfwAlertColumn, AddDisableNsfwAlertColumn,
AddHiddenAchievementDescriptionColumn,
]); ]);
} }
getMigrationName(migration: HydraMigration): string { getMigrationName(migration: HydraMigration): string {

View File

@ -0,0 +1,20 @@
import type { HydraMigration } from "@main/knex-client";
import type { Knex } from "knex";
export const AddHiddenAchievementDescriptionColumn: HydraMigration = {
name: "AddHiddenAchievementDescriptionColumn",
up: (knex: Knex) => {
return knex.schema.alterTable("user_preferences", (table) => {
return table
.boolean("showHiddenAchievementsDescription")
.notNullable()
.defaultTo(0);
});
},
down: async (knex: Knex) => {
return knex.schema.alterTable("user_preferences", (table) => {
return table.dropColumn("showHiddenAchievementsDescription");
});
},
};

View File

@ -1,40 +0,0 @@
import path from "node:path";
import fs from "node:fs";
import { getSteamGameClientIcon, logger } from "@main/services";
import { chunk } from "lodash-es";
import { seedsPath } from "@main/constants";
import type { SteamGame } from "@types";
const steamGamesPath = path.join(seedsPath, "steam-games.json");
const steamGames = JSON.parse(
fs.readFileSync(steamGamesPath, "utf-8")
) as SteamGame[];
const chunks = chunk(steamGames, 1500);
for (const chunk of chunks) {
await Promise.all(
chunk.map(async (steamGame) => {
if (steamGame.clientIcon) return;
const index = steamGames.findIndex((game) => game.id === steamGame.id);
try {
const clientIcon = await getSteamGameClientIcon(String(steamGame.id));
steamGames[index].clientIcon = clientIcon;
logger.log("info", `Set ${steamGame.name} client icon`);
} catch (err) {
steamGames[index].clientIcon = null;
logger.log("info", `Could not set icon for ${steamGame.name}`);
}
})
);
fs.writeFileSync(steamGamesPath, JSON.stringify(steamGames));
logger.log("info", "Updated steam games");
}

View File

@ -236,6 +236,7 @@ export class AchievementWatcherManager {
}; };
public static preSearchAchievements = async () => { public static preSearchAchievements = async () => {
try {
const newAchievementsCount = const newAchievementsCount =
process.platform === "win32" process.platform === "win32"
? await this.preSearchAchievementsWindows() ? await this.preSearchAchievementsWindows()
@ -255,6 +256,9 @@ export class AchievementWatcherManager {
totalNewGamesWithAchievements totalNewGamesWithAchievements
); );
} }
} catch (err) {
achievementsLogger.error("Error on preSearchAchievements", err);
}
this.hasFinishedMergingWithRemote = true; this.hasFinishedMergingWithRemote = true;
}; };

View File

@ -6,21 +6,16 @@ import { HydraApi } from "../hydra-api";
import type { AchievementData, GameShop } from "@types"; import type { AchievementData, GameShop } from "@types";
import { UserNotLoggedInError } from "@shared"; import { UserNotLoggedInError } from "@shared";
import { logger } from "../logger"; import { logger } from "../logger";
import { GameAchievement } from "@main/entity";
export const getGameAchievementData = async ( export const getGameAchievementData = async (
objectId: string, objectId: string,
shop: GameShop, shop: GameShop,
useCachedData: boolean cachedAchievements: GameAchievement | null
) => { ) => {
if (useCachedData) {
const cachedAchievements = await gameAchievementRepository.findOne({
where: { objectId, shop },
});
if (cachedAchievements && cachedAchievements.achievements) { if (cachedAchievements && cachedAchievements.achievements) {
return JSON.parse(cachedAchievements.achievements) as AchievementData[]; return JSON.parse(cachedAchievements.achievements) as AchievementData[];
} }
}
const userPreferences = await userPreferencesRepository.findOne({ const userPreferences = await userPreferencesRepository.findOne({
where: { id: 1 }, where: { id: 1 },

View File

@ -9,6 +9,7 @@ export const parseAchievementFile = (
): UnlockedAchievement[] => { ): UnlockedAchievement[] => {
if (!existsSync(filePath)) return []; if (!existsSync(filePath)) return [];
try {
if (type == Cracker.codex) { if (type == Cracker.codex) {
const parsed = iniParse(filePath); const parsed = iniParse(filePath);
return processDefault(parsed); return processDefault(parsed);
@ -78,10 +79,13 @@ export const parseAchievementFile = (
`Unprocessed ${type} achievements found on ${filePath}` `Unprocessed ${type} achievements found on ${filePath}`
); );
return []; return [];
} catch (err) {
achievementsLogger.error(`Error parsing ${type} - ${filePath}`, err);
return [];
}
}; };
const iniParse = (filePath: string) => { const iniParse = (filePath: string) => {
try {
const fileContent = readFileSync(filePath, "utf-8"); const fileContent = readFileSync(filePath, "utf-8");
const lines = const lines =
@ -105,23 +109,13 @@ const iniParse = (filePath: string) => {
} }
return object; return object;
} catch (err) {
achievementsLogger.error(`Error parsing ${filePath}`, err);
return {};
}
}; };
const jsonParse = (filePath: string) => { const jsonParse = (filePath: string) => {
try {
return JSON.parse(readFileSync(filePath, "utf-8")); return JSON.parse(readFileSync(filePath, "utf-8"));
} catch (err) {
achievementsLogger.error(`Error parsing ${filePath}`, err);
return {};
}
}; };
const processRazor1911 = (filePath: string): UnlockedAchievement[] => { const processRazor1911 = (filePath: string): UnlockedAchievement[] => {
try {
const fileContent = readFileSync(filePath, "utf-8"); const fileContent = readFileSync(filePath, "utf-8");
const lines = const lines =
@ -143,10 +137,6 @@ const processRazor1911 = (filePath: string): UnlockedAchievement[] => {
} }
return achievements; return achievements;
} catch (err) {
achievementsLogger.error(`Error processing ${filePath}`, err);
return [];
}
}; };
const processOnlineFix = (unlockedAchievements: any): UnlockedAchievement[] => { const processOnlineFix = (unlockedAchievements: any): UnlockedAchievement[] => {

View File

@ -1,7 +1,6 @@
export * from "./logger"; export * from "./logger";
export * from "./steam"; export * from "./steam";
export * from "./steam-250"; export * from "./steam-250";
export * from "./steam-grid";
export * from "./window-manager"; export * from "./window-manager";
export * from "./download"; export * from "./download";
export * from "./process-watcher"; export * from "./process-watcher";

View File

@ -1,69 +0,0 @@
import type { GameShop } from "@types";
import axios from "axios";
export interface SteamGridResponse {
success: boolean;
data: {
id: number;
};
}
export interface SteamGridGameResponse {
data: {
platforms: {
steam: {
metadata: {
clienticon: string;
};
};
};
};
}
export const getSteamGridData = async (
objectId: string,
path: string,
shop: GameShop,
params: Record<string, string> = {}
): Promise<SteamGridResponse> => {
const searchParams = new URLSearchParams(params);
if (!import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY) {
throw new Error("MAIN_VITE_STEAMGRIDDB_API_KEY is not set");
}
const response = await axios.get(
`https://www.steamgriddb.com/api/v2/${path}/${shop}/${objectId}?${searchParams.toString()}`,
{
headers: {
Authorization: `Bearer ${import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY}`,
},
}
);
return response.data;
};
export const getSteamGridGameById = async (
id: number
): Promise<SteamGridGameResponse> => {
const response = await axios.get(
`https://www.steamgriddb.com/api/public/game/${id}`,
{
headers: {
Referer: "https://www.steamgriddb.com/",
},
}
);
return response.data;
};
export const getSteamGameClientIcon = async (objectId: string) => {
const {
data: { id: steamGridGameId },
} = await getSteamGridData(objectId, "games", "steam");
const steamGridGame = await getSteamGridGameById(steamGridGameId);
return steamGridGame.data.platforms.steam.metadata.clienticon;
};

View File

@ -11,10 +11,10 @@ import type {
GameRunning, GameRunning,
FriendRequestAction, FriendRequestAction,
UpdateProfileRequest, UpdateProfileRequest,
GameAchievement,
} from "@types"; } from "@types";
import type { CatalogueCategory } from "@shared"; import type { CatalogueCategory } from "@shared";
import type { AxiosProgressEvent } from "axios"; import type { AxiosProgressEvent } from "axios";
import { GameAchievement } from "@main/entity";
contextBridge.exposeInMainWorld("electron", { contextBridge.exposeInMainWorld("electron", {
/* Torrenting */ /* Torrenting */

View File

@ -19,6 +19,7 @@ export function SettingsBehavior() {
runAtStartup: false, runAtStartup: false,
startMinimized: false, startMinimized: false,
disableNsfwAlert: false, disableNsfwAlert: false,
showHiddenAchievementsDescription: false,
}); });
const { t } = useTranslation("settings"); const { t } = useTranslation("settings");
@ -30,6 +31,8 @@ export function SettingsBehavior() {
runAtStartup: userPreferences.runAtStartup, runAtStartup: userPreferences.runAtStartup,
startMinimized: userPreferences.startMinimized, startMinimized: userPreferences.startMinimized,
disableNsfwAlert: userPreferences.disableNsfwAlert, disableNsfwAlert: userPreferences.disableNsfwAlert,
showHiddenAchievementsDescription:
userPreferences.showHiddenAchievementsDescription,
}); });
} }
}, [userPreferences]); }, [userPreferences]);
@ -96,6 +99,17 @@ export function SettingsBehavior() {
handleChange({ disableNsfwAlert: !form.disableNsfwAlert }) handleChange({ disableNsfwAlert: !form.disableNsfwAlert })
} }
/> />
<CheckboxField
label={t("show_hidden_achievement_description")}
checked={form.showHiddenAchievementsDescription}
onChange={() =>
handleChange({
showHiddenAchievementsDescription:
!form.showHiddenAchievementsDescription,
})
}
/>
</> </>
); );
} }

View File

@ -162,6 +162,7 @@ export interface UserPreferences {
runAtStartup: boolean; runAtStartup: boolean;
startMinimized: boolean; startMinimized: boolean;
disableNsfwAlert: boolean; disableNsfwAlert: boolean;
showHiddenAchievementsDescription: boolean;
} }
export interface Steam250Game { export interface Steam250Game {

621
yarn.lock

File diff suppressed because it is too large Load Diff