mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 13:34:54 +03:00
Merge branch 'main' into feature/seed-completed-downloads
This commit is contained in:
commit
1a286df3f7
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@ -2,9 +2,6 @@ name: Build
|
|||||||
|
|
||||||
on: pull_request
|
on: pull_request
|
||||||
|
|
||||||
env:
|
|
||||||
AWS_REGION: us-east-1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
@ -22,16 +19,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20.18.0
|
node-version: 20.18.0
|
||||||
|
|
||||||
- name: Configure AWS credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
|
||||||
with:
|
|
||||||
aws-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
||||||
aws-region: ${{ env.AWS_REGION }}
|
|
||||||
|
|
||||||
- name: Push build to R2
|
|
||||||
run: aws s3 sync ./docs s3://${{ vars.BUILDS_BUCKET_NAME }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
@ -58,7 +45,9 @@ jobs:
|
|||||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }}
|
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }}
|
||||||
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
||||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||||
|
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
|
|
||||||
- name: Build Windows
|
- name: Build Windows
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
@ -69,7 +58,21 @@ jobs:
|
|||||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }}
|
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }}
|
||||||
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
||||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||||
|
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
|
|
||||||
|
- name: Test Upload build
|
||||||
|
env:
|
||||||
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||||
|
S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||||
|
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||||
|
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||||
|
S3_BUILDS_BUCKET_NAME: ${{ secrets.S3_BUILDS_BUCKET_NAME }}
|
||||||
|
BUILDS_URL: ${{ secrets.BUILDS_URL }}
|
||||||
|
BUILD_WEBHOOK_URL: ${{ secrets.BUILD_WEBHOOK_URL }}
|
||||||
|
GITHUB_ACTOR: ${{ github.actor }}
|
||||||
|
run: node scripts/upload-build.cjs
|
||||||
|
|
||||||
- name: Create artifact
|
- name: Create artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -47,7 +47,9 @@ jobs:
|
|||||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }}
|
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }}
|
||||||
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
||||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||||
|
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
- name: Build Windows
|
- name: Build Windows
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
run: yarn build:win
|
run: yarn build:win
|
||||||
@ -57,7 +59,9 @@ jobs:
|
|||||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }}
|
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }}
|
||||||
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
||||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||||
|
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
|
||||||
- name: Create artifact
|
- name: Create artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.9.20
|
@ -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>
|
||||||
|
|
||||||
@ -125,6 +125,10 @@ cd hydra
|
|||||||
yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Install OpenSSL 1.1
|
||||||
|
|
||||||
|
[OpenSSL 1.1](https://slproweb.com/download/Win64OpenSSL-1_1_1w.exe) is required by libtorrent in Windows environments.
|
||||||
|
|
||||||
### Install Python 3.9
|
### Install Python 3.9
|
||||||
|
|
||||||
Ensure you have Python 3.9 installed on your machine. You can download and install it from [python.org](https://www.python.org/downloads/release/python-3913/).
|
Ensure you have Python 3.9 installed on your machine. You can download and install it from [python.org](https://www.python.org/downloads/release/python-3913/).
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
[![release](https://img.shields.io/github/package-json/v/hydralauncher/hydra)](https://github.com/hydralauncher/hydra/releases)
|
[![release](https://img.shields.io/github/package-json/v/hydralauncher/hydra)](https://github.com/hydralauncher/hydra/releases)
|
||||||
|
|
||||||
[![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md)
|
[![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md)
|
||||||
[![en](https://img.shields.io/badge/lang-en-red.svg)](README.md)
|
[![en](https://img.shields.io/badge/lang-en-red.svg)](../README.md)
|
||||||
[![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md)
|
[![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md)
|
||||||
[![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md)
|
[![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md)
|
||||||
[![be](https://img.shields.io/badge/lang-be-orange)](README.be.md)
|
[![be](https://img.shields.io/badge/lang-be-orange)](README.be.md)
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
46
package.json
46
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydralauncher",
|
"name": "hydralauncher",
|
||||||
"version": "3.0.5",
|
"version": "3.0.8",
|
||||||
"description": "Hydra",
|
"description": "Hydra",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "Los Broxas",
|
"author": "Los Broxas",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"start": "electron-vite preview",
|
"start": "electron-vite preview",
|
||||||
"dev": "electron-vite dev",
|
"dev": "electron-vite dev",
|
||||||
"build": "npm run typecheck && electron-vite build",
|
"build": "npm run typecheck && electron-vite build",
|
||||||
"postinstall": "electron-builder install-app-deps && node ./postinstall.cjs",
|
"postinstall": "electron-builder install-app-deps && node ./scripts/postinstall.cjs",
|
||||||
"build:unpack": "npm run build && electron-builder --dir",
|
"build:unpack": "npm run build && electron-builder --dir",
|
||||||
"build:win": "electron-vite build && electron-builder --win",
|
"build:win": "electron-vite build && electron-builder --win",
|
||||||
"build:mac": "electron-vite build && electron-builder --mac",
|
"build:mac": "electron-vite build && electron-builder --mac",
|
||||||
@ -34,26 +34,25 @@
|
|||||||
"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",
|
||||||
"@intercom/messenger-js-sdk": "^0.0.14",
|
|
||||||
"@primer/octicons-react": "^19.9.0",
|
"@primer/octicons-react": "^19.9.0",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||||
"@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",
|
||||||
@ -74,14 +73,15 @@
|
|||||||
"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",
|
"@aws-sdk/client-s3": "^3.705.0",
|
||||||
"@commitlint/config-conventional": "^19.5.0",
|
"@commitlint/cli": "^19.6.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",
|
||||||
@ -89,8 +89,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",
|
||||||
@ -100,15 +100,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",
|
||||||
|
64
scripts/upload-build.cjs
Normal file
64
scripts/upload-build.cjs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
const fs = require("node:fs");
|
||||||
|
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
|
||||||
|
const path = require("node:path");
|
||||||
|
const packageJson = require("../package.json");
|
||||||
|
|
||||||
|
if (!process.env.BUILD_WEBHOOK_URL) {
|
||||||
|
console.log("No BUILD_WEBHOOK_URL provided, skipping upload");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const s3 = new S3Client({
|
||||||
|
region: "auto",
|
||||||
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
|
forcePathStyle: true,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
||||||
|
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const dist = path.resolve(__dirname, "..", "dist");
|
||||||
|
|
||||||
|
const extensionsToUpload = [".deb", ".exe"];
|
||||||
|
|
||||||
|
fs.readdir(dist, async (err, files) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
const uploads = await Promise.all(
|
||||||
|
files
|
||||||
|
.filter((file) => extensionsToUpload.includes(path.extname(file)))
|
||||||
|
.map(async (file) => {
|
||||||
|
console.log(`⌛️ Uploading ${file}...`);
|
||||||
|
const fileName = `${new Date().getTime()}-${file}`;
|
||||||
|
|
||||||
|
const command = new PutObjectCommand({
|
||||||
|
Bucket: process.env.S3_BUILDS_BUCKET_NAME,
|
||||||
|
Key: fileName,
|
||||||
|
Body: fs.createReadStream(path.resolve(dist, file)),
|
||||||
|
});
|
||||||
|
|
||||||
|
await s3.send(command);
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: `${process.env.BUILDS_URL}/${fileName}`,
|
||||||
|
name: fileName,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (uploads.length > 0) {
|
||||||
|
await fetch(process.env.BUILD_WEBHOOK_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
uploads,
|
||||||
|
branchName: process.env.BRANCH_NAME,
|
||||||
|
version: packageJson.version,
|
||||||
|
githubActor: process.env.GITHUB_ACTOR,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -1,33 +1,49 @@
|
|||||||
{
|
{
|
||||||
"language_name": "اَلْعَرَبِيَّةُ",
|
"language_name": "اَلْعَرَبِيَّةُ",
|
||||||
|
"app": {
|
||||||
|
"successfully_signed_in": "تم تسجيل الدخول بنجاح"
|
||||||
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "مميّز",
|
"featured": "مميّز",
|
||||||
"surprise_me": "فاجئني",
|
"surprise_me": "فاجئني",
|
||||||
"no_results": "لم يتم العثور على نتائج"
|
"no_results": "لم يتم العثور على نتائج",
|
||||||
|
"start_typing": "بدء الكتابة للبحث...",
|
||||||
|
"hot": "الأكثر رواجا الآن",
|
||||||
|
"weekly": "📅 أفضل ألعاب الأسبوع",
|
||||||
|
"achievements": "🏆 ألعاب للتغلب عليها"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"catalogue": "قائمة الألعاب",
|
"catalogue": "قائمة الألعاب",
|
||||||
"downloads": "التحميلات",
|
"downloads": "التنزيلات",
|
||||||
"settings": "إعدادات",
|
"settings": "إعدادات",
|
||||||
"my_library": "مكتبتي",
|
"my_library": "مكتبتي",
|
||||||
"downloading_metadata": "{{title}} (جارٍ تنزيل البيانات الوصفية...)",
|
"downloading_metadata": "{{title}} (جارٍ تنزيل البيانات الوصفية...)",
|
||||||
"paused": "{{title}} (متوقف)",
|
"paused": "{{title}} (متوقف مؤقتًا)",
|
||||||
"downloading": "{{title}} ({{percentage}} - جارٍ التنزيل...)",
|
"downloading": "{{title}} ({{percentage}} - جاري التنزيل...)",
|
||||||
"filter": "بحث في المكتبة",
|
"filter": "بحث في المكتبة",
|
||||||
"home": "الرئيسية"
|
"home": "الرئيسية",
|
||||||
|
"queued": "{{title}} (في قائمة الانتظار)",
|
||||||
|
"game_has_no_executable": "لم يتم تحديد اللعبة القابلة للتنفيذ",
|
||||||
|
"sign_in": "تسجيل الدخول",
|
||||||
|
"friends": "أصدقاء",
|
||||||
|
"need_help": "بحاجة الى مساعدة؟"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "ابحث عن الألعاب",
|
"search": "ابحث عن الألعاب",
|
||||||
"home": "الرئيسية",
|
"home": "الرئيسية",
|
||||||
"catalogue": "قائمة الألعاب",
|
"catalogue": "قائمة الألعاب",
|
||||||
"downloads": "التحميلات",
|
"downloads": "التنزيلات",
|
||||||
"search_results": "نتائج البحث",
|
"search_results": "نتائج البحث",
|
||||||
"settings": "إعدادات"
|
"settings": "إعدادات",
|
||||||
|
"version_available_install": "إصدار {{version}} متاح. ",
|
||||||
|
"version_available_download": "إصدار {{version}} متاح. "
|
||||||
},
|
},
|
||||||
"bottom_panel": {
|
"bottom_panel": {
|
||||||
"no_downloads_in_progress": "لا يوجد تنزيلات جارية",
|
"no_downloads_in_progress": "لا توجد تنزيلات قيد التقدم",
|
||||||
"downloading_metadata": "جارٍ تنزيل بيانات وصف {{title}}",
|
"downloading_metadata": "جارٍ التنزيل {{title}} البيانات الوصفية...",
|
||||||
"downloading": "جارٍ تنزيل {{title}}… ({{percentage}} مكتملة) - الانتهاء {{eta}} - {{speed}}"
|
"downloading": "جارٍ التنزيل {{title}}… ({{percentage}} مكتملة) - الانتهاء {{eta}} - {{speed}}",
|
||||||
|
"calculating_eta": "جارٍ التنزيل {{title}}… ({{percentage}} مكتمل) - حساب الوقت المتبقي...",
|
||||||
|
"checking_files": "التحقق {{title}} ملفات…({{percentage}} مكتمل)"
|
||||||
},
|
},
|
||||||
"catalogue": {
|
"catalogue": {
|
||||||
"next_page": "الصفحة التالية",
|
"next_page": "الصفحة التالية",
|
||||||
@ -35,101 +51,242 @@
|
|||||||
},
|
},
|
||||||
"game_details": {
|
"game_details": {
|
||||||
"open_download_options": "افتح خيارات التنزيل",
|
"open_download_options": "افتح خيارات التنزيل",
|
||||||
"download_options_zero": "لا يوجد خيار تنزيل",
|
"download_options_zero": "{{count}} خيارات التنزيل",
|
||||||
"download_options_one": "{{count}} خيار تنزيل",
|
|
||||||
"download_options_other": "{{count}} خيار تنزيل",
|
|
||||||
"updated_at": "تم التحديث {{updated_at}}",
|
"updated_at": "تم التحديث {{updated_at}}",
|
||||||
"install": "تثبيت",
|
"install": "ثَبَّتَ",
|
||||||
"resume": "استئناف",
|
"resume": "استئناف",
|
||||||
"pause": "إيقاف",
|
"pause": "إيقاف",
|
||||||
"cancel": "إلغاء",
|
"cancel": "إلغاء",
|
||||||
"remove": "إزالة",
|
"remove": "إزالة",
|
||||||
"space_left_on_disk": "{{space}} متبقية على القرص",
|
"space_left_on_disk": "{{space}} متبقية على القرص",
|
||||||
"eta": "الوقت المتبقي {{eta}}",
|
"eta": "الوقت المتبقي {{eta}}",
|
||||||
"downloading_metadata": "جاري تنزيل البيانات الوصفية...",
|
"calculating_eta": "جارٍ حساب الوقت المتبقي…",
|
||||||
"filter": "تصفية حزم إعادة التجميع",
|
"downloading_metadata": "جارٍ تنزيل البيانات الوصفية…",
|
||||||
|
"filter": "إعادة حزم التصفية",
|
||||||
"requirements": "متطلبات النظام",
|
"requirements": "متطلبات النظام",
|
||||||
"minimum": "الحد الأدنى",
|
"minimum": "الحد الأدنى",
|
||||||
"recommended": "موصى به",
|
"recommended": "مُستَحسَن",
|
||||||
"release_date": "تم الإصدار في {{date}}",
|
"paused": "متوقف مؤقتًا",
|
||||||
"publisher": "نشر بواسطة {{publisher}}",
|
"release_date": "صدر بتاريخ {{date}}",
|
||||||
|
"publisher": "نشرت من قبل {{publisher}}",
|
||||||
"hours": "ساعات",
|
"hours": "ساعات",
|
||||||
"minutes": "دقائق",
|
"minutes": "دقائق",
|
||||||
"amount_hours": "{{amount}} ساعات",
|
"amount_hours": "{{amount}} ساعات",
|
||||||
"amount_minutes": "{{amount}} دقائق",
|
"amount_minutes": "{{amount}} دقائق",
|
||||||
"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}} حتى الآن",
|
||||||
"next_suggestion": "الاقتراح التالي",
|
"next_suggestion": "الاقتراح التالي",
|
||||||
"play": "لعب",
|
"play": "لعب",
|
||||||
"deleting": "جاري حذف المثبت...",
|
"deleting": "جارٍ حذف المثبت…",
|
||||||
"close": "إغلاق",
|
"close": "إغلاق",
|
||||||
"playing_now": "قيد التشغيل الآن",
|
"playing_now": "قيداللعب الآن",
|
||||||
"change": "تغيير",
|
"change": "تغيير",
|
||||||
"repacks_modal_description": "اختر الحزمة التي تريد تنزيلها",
|
"repacks_modal_description": "اختر الحزمة التي تريد تنزيلها",
|
||||||
"select_folder_hint": "لتغيير المجلد الافتراضي، انتقل إلى الإعدادات",
|
"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": "لقطة الشاشة التالية",
|
||||||
"screenshot": "لقطة شاشة {{number}}",
|
"screenshot": "لقطة الشاشة {{number}}",
|
||||||
"open_screenshot": "افتح لقطة الشاشة {{number}}"
|
"open_screenshot": "فتح لقطة الشاشة {{number}}",
|
||||||
|
"download_settings": "تحميل الإعدادات",
|
||||||
|
"downloader": "أداة التنزيل",
|
||||||
|
"select_executable": "يختار",
|
||||||
|
"no_executable_selected": "لم يتم تحديد أي ملف قابل للتنفيذ",
|
||||||
|
"open_folder": "افتح المجلد",
|
||||||
|
"open_download_location": "انظر الملفات التي تم تنزيلها",
|
||||||
|
"create_shortcut": "إنشاء اختصار سطح المكتب",
|
||||||
|
"clear": "واضح",
|
||||||
|
"remove_files": "إزالة الملفات",
|
||||||
|
"remove_from_library_title": "هل أنت متأكد؟",
|
||||||
|
"remove_from_library_description": "سيتم إزالة هذا {{game}} من مكتبتك",
|
||||||
|
"options": "خيارات",
|
||||||
|
"executable_section_title": "قابل للتنفيذ",
|
||||||
|
"executable_section_description": "مسار الملف الذي سيتم تنفيذه عند النقر فوق \"تشغيل\".",
|
||||||
|
"downloads_secion_title": "التنزيلات",
|
||||||
|
"downloads_section_description": "تحقق من التحديثات أو الإصدارات الأخرى من هذه اللعبة",
|
||||||
|
"danger_zone_section_title": "منطقة الخطر",
|
||||||
|
"danger_zone_section_description": "قم بإزالة هذه اللعبة من مكتبتك أو الملفات التي تم تنزيلها بواسطة Hydra",
|
||||||
|
"download_in_progress": "التنزيل قيد التقدم",
|
||||||
|
"download_paused": "تم إيقاف التنزيل مؤقتًا",
|
||||||
|
"last_downloaded_option": "آخر خيار تم تنزيله",
|
||||||
|
"create_shortcut_success": "تم إنشاء الاختصار بنجاح",
|
||||||
|
"create_shortcut_error": "حدث خطأ أثناء إنشاء الاختصار",
|
||||||
|
"nsfw_content_title": "تحتوي هذه اللعبة على محتوى غير مناسب",
|
||||||
|
"nsfw_content_description": "{{title}} يحتوي على محتوى قد لا يكون مناسبًا لجميع الأعمار. ",
|
||||||
|
"allow_nsfw_content": "اسمح",
|
||||||
|
"refuse_nsfw_content": "عُد",
|
||||||
|
"stats": "احصائيات",
|
||||||
|
"download_count": "التنزيلات",
|
||||||
|
"player_count": "اللاعبين النشطين",
|
||||||
|
"download_error": "خيار التنزيل هذا غير متوفر",
|
||||||
|
"download": "تحميل",
|
||||||
|
"executable_path_in_use": "قابل للتنفيذ قيد الاستخدام بالفعل بواسطة \"{{game}}\"",
|
||||||
|
"warning": "تحذير:",
|
||||||
|
"hydra_needs_to_remain_open": "لإجراء هذا التنزيل، يجب أن يظل 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_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": "تعيين موقع النسخ الاحتياطي المخصص",
|
||||||
|
"no_directory_selected": "لم يتم تحديد أي دليل",
|
||||||
|
"download_options_one": "{{count}} خيار التنزيل",
|
||||||
|
"download_options_two": "{{count}} خيارات التنزيل",
|
||||||
|
"download_options_few": "{{count}} خيارات التنزيل",
|
||||||
|
"download_options_many": "{{count}} خيارات التنزيل",
|
||||||
|
"download_options_other": "{{count}} خيارات التنزيل"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "تفعيل هايدرا",
|
"title": "تفعيل Hydra",
|
||||||
"installation_id": "معرف التثبيت:",
|
"installation_id": "معرف التثبيت:",
|
||||||
"enter_activation_code": "أدخل رمز التفعيل الخاص بك",
|
"enter_activation_code": "أدخل رمز التفعيل الخاص بك",
|
||||||
"message": "إذا كنت لا تعرف أين تسأل عن هذا ، فلا يجب أن يكون لديك هذا.",
|
"message": "إذا كنت لا تعرف أين تطلب هذا، فلا ينبغي أن يكون لديك هذا.",
|
||||||
"activate": "تفعيل",
|
"activate": "فعل",
|
||||||
"loading": "جار التحميل…"
|
"loading": "تحميل…"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"resume": "استئناف",
|
"resume": "استئناف",
|
||||||
"pause": "إيقاف مؤقت",
|
"pause": "إيقاف مؤقت",
|
||||||
"eta": "الوقت المتبقي {{eta}}",
|
"eta": "الوقت المتبقي {{eta}}",
|
||||||
"paused": "متوقفة مؤقتًا",
|
"paused": "متوقف مؤقتًا",
|
||||||
"verifying": "جار التحقق…",
|
"verifying": "جارٍ التحقق…",
|
||||||
"completed": "اكتمل",
|
"completed": "مكتمل",
|
||||||
|
"removed": "لم يتم تحميلها",
|
||||||
"cancel": "إلغاء",
|
"cancel": "إلغاء",
|
||||||
"filter": "تصفية الألعاب التي تم تنزيلها",
|
"filter": "تصفية الألعاب التي تم تنزيلها",
|
||||||
"remove": "إزالة",
|
"remove": "إزالة",
|
||||||
"downloading_metadata": "جار تنزيل البيانات الوصفية…",
|
"downloading_metadata": "جارٍ تنزيل البيانات الوصفية…",
|
||||||
"deleting": "جار حذف المثبت…",
|
"deleting": "جارٍ حذف المثبت…",
|
||||||
"delete": "إزالة المثبت",
|
"delete": "إزالة المثبت",
|
||||||
"delete_modal_title": "هل أنت متأكد؟",
|
"delete_modal_title": "هل أنت متأكد؟",
|
||||||
"delete_modal_description": "سيؤدي هذا إلى إزالة جميع ملفات التثبيت من جهاز الكمبيوتر الخاص بك",
|
"delete_modal_description": "سيؤدي هذا إلى إزالة كافة ملفات التثبيت من جهاز الكمبيوتر الخاص بك",
|
||||||
"install": "تثبيت"
|
"install": "ثَبَّتَ",
|
||||||
|
"download_in_progress": "في تَقَدم",
|
||||||
|
"queued_downloads": "التنزيلات في قائمة الانتظار",
|
||||||
|
"downloads_completed": "مكتمل",
|
||||||
|
"queued": "في قائمة الانتظار",
|
||||||
|
"no_downloads_title": "هذا فارغ",
|
||||||
|
"no_downloads_description": "لم تقم بتنزيل أي شيء باستخدام Hydra بعد، ولكن لم يفت الأوان بعد للبدء.",
|
||||||
|
"checking_files": "جارٍ فحص الملفات…"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"downloads_path": "مسار التنزيلات",
|
"downloads_path": "مسار التنزيلات",
|
||||||
"change": "تحديث",
|
"change": "تحديث",
|
||||||
"notifications": "الإشعارات",
|
"notifications": "إشعارات",
|
||||||
"enable_download_notifications": "عند اكتمال التنزيل",
|
"enable_download_notifications": "عند اكتمال التنزيل",
|
||||||
"enable_repack_list_notifications": "عند إضافة حزمة جديدة",
|
"enable_repack_list_notifications": "عند إضافة حزمة جديدة",
|
||||||
"real_debrid_api_token_label": "رمز واجهة برمجة التطبيقات (API) لـReal-Debrid ",
|
"real_debrid_api_token_label": "رمز Real-Debrid API",
|
||||||
"quit_app_instead_hiding": "إنهاء هايدرا بدلاً من التصغير الى شريط الحالة",
|
"quit_app_instead_hiding": "لا تخفي Hydra عند الإغلاق",
|
||||||
"launch_with_system": "تشغيل هايدرا عند بدء تشغيل النظام",
|
"launch_with_system": "قم بتشغيل Hydra عند بدء تشغيل النظام",
|
||||||
"general": "عام",
|
"general": "عام",
|
||||||
"behavior": "السلوك",
|
"behavior": "سلوك",
|
||||||
"enable_real_debrid": "تفعيل Real-Debrid ",
|
"download_sources": "تحميل المصادر",
|
||||||
"real_debrid_api_token_hint": "يمكنك الحصول على مفتاح API الخاص بك هنا",
|
"language": "لغة",
|
||||||
"save_changes": "حفظ التغييرات"
|
"real_debrid_api_token": "رمز API",
|
||||||
|
"enable_real_debrid": "تمكين ريال ديبريد",
|
||||||
|
"real_debrid_description": "Real-Debrid هو برنامج تنزيل غير مقيد يسمح لك بتنزيل الملفات بسرعة، ولا يقتصر ذلك إلا على سرعة الإنترنت لديك.",
|
||||||
|
"real_debrid_invalid_token": "رمز API غير صالح",
|
||||||
|
"real_debrid_api_token_hint": "يمكنك الحصول على رمز API الخاص بك <0>هنا</0>",
|
||||||
|
"real_debrid_free_account_error": "الحساب \"{{username}}\" هو حساب مجاني. يرجى الاشتراك في Real-Debrid",
|
||||||
|
"real_debrid_linked_message": "حساب \"{{username}}\"مرتبط",
|
||||||
|
"save_changes": "حفظ التغييرات",
|
||||||
|
"changes_saved": "تم حفظ التغييرات بنجاح",
|
||||||
|
"download_sources_description": "ستقوم Hydra بجلب روابط التنزيل من هذه المصادر. ",
|
||||||
|
"validate_download_source": "التحقق من صحة",
|
||||||
|
"remove_download_source": "إزالة",
|
||||||
|
"add_download_source": "أضف المصدر",
|
||||||
|
"download_count_zero": "{{countFormatted}} خيارات التنزيل",
|
||||||
|
"download_source_url": "تنزيل عنوان URL المصدر",
|
||||||
|
"add_download_source_description": "أدخل عنوان URL لملف .json",
|
||||||
|
"download_source_up_to_date": "محدث",
|
||||||
|
"download_source_errored": "خطأ",
|
||||||
|
"sync_download_sources": "مصادر المزامنة",
|
||||||
|
"removed_download_source": "تمت إزالة مصدر التنزيل",
|
||||||
|
"added_download_source": "تمت إضافة مصدر التنزيل",
|
||||||
|
"download_sources_synced": "تتم مزامنة جميع مصادر التنزيل",
|
||||||
|
"insert_valid_json_url": "أدخل عنوان URL صالحًا لـ JSON",
|
||||||
|
"found_download_option_zero": "وجد {{countFormatted}} خيارات التنزيل",
|
||||||
|
"import": "يستورد",
|
||||||
|
"public": "عام",
|
||||||
|
"private": "خاص",
|
||||||
|
"friends_only": "الأصدقاء فقط",
|
||||||
|
"privacy": "خصوصية",
|
||||||
|
"profile_visibility": "رؤية الملف الشخصي",
|
||||||
|
"profile_visibility_description": "اختر من يمكنه رؤية ملفك الشخصي ومكتبتك",
|
||||||
|
"required_field": "هذه الخانة مطلوبه",
|
||||||
|
"source_already_exists": "تمت إضافة هذا المصدر بالفعل",
|
||||||
|
"must_be_valid_url": "يجب أن يكون المصدر عنوان URL صالحًا",
|
||||||
|
"blocked_users": "المستخدمين المحظورين",
|
||||||
|
"user_unblocked": "تم إلغاء حظر المستخدم",
|
||||||
|
"enable_achievement_notifications": "عندما يتم فتح الإنجاز",
|
||||||
|
"launch_minimized": "تم تصغير إطلاق Hydra",
|
||||||
|
"disable_nsfw_alert": "تعطيل تنبيه NSFW",
|
||||||
|
"show_hidden_achievement_description": "إظهار وصف الإنجازات المخفية قبل فتحها",
|
||||||
|
"download_count_one": "{{countFormatted}} خيار التنزيل",
|
||||||
|
"download_count_two": "{{countFormatted}} خيارات التنزيل",
|
||||||
|
"download_count_few": "{{countFormatted}} خيارات التنزيل",
|
||||||
|
"download_count_many": "{{countFormatted}} خيارات التنزيل",
|
||||||
|
"download_count_other": "{{countFormatted}} خيارات التنزيل",
|
||||||
|
"found_download_option_one": "وجد {{countFormatted}} خيار التنزيل",
|
||||||
|
"found_download_option_two": "وجد {{countFormatted}} خيارات التنزيل",
|
||||||
|
"found_download_option_few": "وجد {{countFormatted}} خيارات التنزيل",
|
||||||
|
"found_download_option_many": "وجد {{countFormatted}} خيارات التنزيل",
|
||||||
|
"found_download_option_other": "وجد {{countFormatted}} خيارات التنزيل"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "تم التحميل",
|
"download_complete": "اكتمل التنزيل",
|
||||||
"game_ready_to_install": "{{title}} جاهزة للتثبيت",
|
"game_ready_to_install": "{{title}} جاهز للتثبيت",
|
||||||
"repack_list_updated": "قائمة التجميعات المحدثة",
|
"repack_list_updated": "تم تحديث قائمة إعادة التعبئة",
|
||||||
"repack_count_one": "{{count}} حزمة مضافة",
|
"new_update_available": "إصدار {{version}} متاح",
|
||||||
"repack_count_other": "{{count}} حزم مُضافة"
|
"restart_to_install_update": "أعد تشغيل Hydra لتثبيت التحديث",
|
||||||
|
"notification_achievement_unlocked_title": "تم فتح الإنجاز لـ {{game}}",
|
||||||
|
"notification_achievement_unlocked_body": "{{achievement}} وغيرها {{count}} تم فتحها",
|
||||||
|
"repack_count_zero": "{{count}} تمت إضافة العبوات",
|
||||||
|
"repack_count_one": "{{count}} تمت إضافة أعد حزم",
|
||||||
|
"repack_count_two": "{{count}} تمت إضافة العبوات",
|
||||||
|
"repack_count_few": "{{count}} تمت إضافة العبوات",
|
||||||
|
"repack_count_many": "{{count}} تمت إضافة العبوات",
|
||||||
|
"repack_count_other": "{{count}} تمت إضافة العبوات"
|
||||||
},
|
},
|
||||||
"system_tray": {
|
"system_tray": {
|
||||||
"open": "فتح هايدرا",
|
"open": "افتح Hydra",
|
||||||
"quit": "خروج"
|
"quit": "خروج"
|
||||||
},
|
},
|
||||||
"game_card": {
|
"game_card": {
|
||||||
@ -137,10 +294,109 @@
|
|||||||
},
|
},
|
||||||
"binary_not_found_modal": {
|
"binary_not_found_modal": {
|
||||||
"title": "البرامج غير مثبتة",
|
"title": "البرامج غير مثبتة",
|
||||||
"description": "لم يتم العثور على ملفات Wine أو Lutris التنفيذية على نظامك",
|
"description": "لم يتم العثور على الملفات التنفيذية الخاصة بـ Wine أو Lutris على نظامك",
|
||||||
"instructions": "تحقق من الطريقة الصحيحة لتثبيت أي منها على توزيعة Linux الخاصة بك حتى تعمل اللعبة بشكل طبيعي"
|
"instructions": "تحقق من الطريقة الصحيحة لتثبيت أي منها على توزيعة Linux لديك حتى تعمل اللعبة بشكل طبيعي"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"close": "زر إغلاق"
|
"close": "زر الإغلاق"
|
||||||
|
},
|
||||||
|
"forms": {
|
||||||
|
"toggle_password_visibility": "تبديل رؤية كلمة المرور"
|
||||||
|
},
|
||||||
|
"user_profile": {
|
||||||
|
"amount_hours": "{{amount}} ساعات",
|
||||||
|
"amount_minutes": "{{amount}} دقائق",
|
||||||
|
"last_time_played": "لعبت آخر مرة {{period}}",
|
||||||
|
"activity": "النشاط الأخير",
|
||||||
|
"library": "مكتبة",
|
||||||
|
"total_play_time": "إجمالي وقت اللعب: {{amount}}",
|
||||||
|
"no_recent_activity_title": "هممم... لا شيء هنا",
|
||||||
|
"no_recent_activity_description": "لم تلعب أي مباراة مؤخرًا. ",
|
||||||
|
"display_name": "اسم العرض",
|
||||||
|
"saving": "توفير",
|
||||||
|
"save": "يحفظ",
|
||||||
|
"edit_profile": "تحرير الملف الشخصي",
|
||||||
|
"saved_successfully": "تم الحفظ بنجاح",
|
||||||
|
"try_again": "من فضلك، حاول مرة أخرى",
|
||||||
|
"sign_out_modal_title": "هل أنت متأكد؟",
|
||||||
|
"cancel": "إلغاء",
|
||||||
|
"successfully_signed_out": "تم تسجيل الخروج بنجاح",
|
||||||
|
"sign_out": "تسجيل الخروج",
|
||||||
|
"playing_for": "اللعب من أجل {{amount}}",
|
||||||
|
"sign_out_modal_text": "مكتبتك مرتبطة بحسابك الحالي. ",
|
||||||
|
"add_friends": "أضف أصدقاء",
|
||||||
|
"add": "يضيف",
|
||||||
|
"friend_code": "رمز الصديق",
|
||||||
|
"see_profile": "انظر الملف الشخصي",
|
||||||
|
"sending": "إرسال",
|
||||||
|
"friend_request_sent": "تم إرسال طلب الصداقة",
|
||||||
|
"friends": "أصدقاء",
|
||||||
|
"friends_list": "قائمة الأصدقاء",
|
||||||
|
"user_not_found": "لم يتم العثور على المستخدم",
|
||||||
|
"block_user": "حظر المستخدم",
|
||||||
|
"add_friend": "إضافة صديق",
|
||||||
|
"request_sent": "تم إرسال الطلب",
|
||||||
|
"request_received": "تم استلام الطلب",
|
||||||
|
"accept_request": "قبول الطلب",
|
||||||
|
"ignore_request": "تجاهل الطلب",
|
||||||
|
"cancel_request": "إلغاء الطلب",
|
||||||
|
"undo_friendship": "التراجع عن الصداقة",
|
||||||
|
"request_accepted": "تم قبول الطلب",
|
||||||
|
"user_blocked_successfully": "تم حظر المستخدم بنجاح",
|
||||||
|
"user_block_modal_text": "هذا سوف يمنع {{displayName}}",
|
||||||
|
"blocked_users": "المستخدمين المحظورين",
|
||||||
|
"unblock": "إلغاء الحظر",
|
||||||
|
"no_friends_added": "ليس لديك أي أصدقاء مضافين",
|
||||||
|
"pending": "قيد الانتظار",
|
||||||
|
"no_pending_invites": "ليس لديك أي دعوات معلقة",
|
||||||
|
"no_blocked_users": "ليس لديك أي مستخدمين محظورين",
|
||||||
|
"friend_code_copied": "تم نسخ رمز الصديق",
|
||||||
|
"undo_friendship_modal_text": "سيؤدي هذا إلى التراجع عن صداقتك معه {{displayName}}",
|
||||||
|
"privacy_hint": "لضبط من يمكنه رؤية هذا، انتقل إلى <0>إعدادات</0>",
|
||||||
|
"locked_profile": "هذا الملف الشخصي خاص",
|
||||||
|
"image_process_failure": "فشل أثناء معالجة الصورة",
|
||||||
|
"required_field": "هذه الخانة مطلوبه",
|
||||||
|
"displayname_min_length": "يجب أن يتكون اسم العرض من 3 أحرف على الأقل",
|
||||||
|
"displayname_max_length": "يجب ألا يزيد طول اسم العرض عن 50 حرفًا",
|
||||||
|
"report_profile": "الإبلاغ عن هذا الملف الشخصي",
|
||||||
|
"report_reason": "لماذا تقوم بالإبلاغ عن هذا الملف الشخصي؟",
|
||||||
|
"report_description": "معلومات إضافية",
|
||||||
|
"report_description_placeholder": "معلومات إضافية",
|
||||||
|
"report": "تقرير",
|
||||||
|
"report_reason_hate": "خطاب الكراهية",
|
||||||
|
"report_reason_sexual_content": "المحتوى الجنسي",
|
||||||
|
"report_reason_violence": "عنف",
|
||||||
|
"report_reason_spam": "رسائل إلكترونية مزعجة",
|
||||||
|
"profile_reported": "تم الإبلاغ عن الملف الشخصي",
|
||||||
|
"your_friend_code": "رمز صديقك:",
|
||||||
|
"upload_banner": "تحميل لافتة",
|
||||||
|
"uploading_banner": "جارٍ تحميل البانر…",
|
||||||
|
"background_image_updated": "تم تحديث صورة الخلفية",
|
||||||
|
"report_reason_zero": "آخر",
|
||||||
|
"report_reason_one": "لماذا تقوم بالإبلاغ عن هذا الملف الشخصي؟",
|
||||||
|
"report_reason_two": "آخر",
|
||||||
|
"report_reason_few": "آخر",
|
||||||
|
"report_reason_many": "آخر",
|
||||||
|
"report_reason_other": "آخر"
|
||||||
|
},
|
||||||
|
"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 كلاود",
|
||||||
|
"subscribe_now": "اشترك الآن",
|
||||||
|
"cloud_saving": "الحفظ السحابي",
|
||||||
|
"cloud_achievements": "احفظ إنجازاتك على السحابة",
|
||||||
|
"animated_profile_picture": "صور شخصية متحركة",
|
||||||
|
"premium_support": "دعم متميز",
|
||||||
|
"show_and_compare_achievements": "عرض ومقارنة إنجازاتك مع المستخدمين الآخرين",
|
||||||
|
"animated_profile_banner": "لافتة الملف الشخصي المتحركة"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
"need_help": "Имате нужда от помощ??"
|
"need_help": "Имате нужда от помощ??"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Търси игри",
|
"search": "Търсене",
|
||||||
"home": "Начало",
|
"home": "Начало",
|
||||||
"catalogue": "Каталог",
|
"catalogue": "Каталог",
|
||||||
"downloads": "Изтегляния",
|
"downloads": "Изтегляния",
|
||||||
@ -65,7 +65,7 @@
|
|||||||
"calculating_eta": "Калкулиране на оставащо време…",
|
"calculating_eta": "Калкулиране на оставащо време…",
|
||||||
"downloading_metadata": "Изтегляне на метадата…",
|
"downloading_metadata": "Изтегляне на метадата…",
|
||||||
"filter": "Филтрирай repacks",
|
"filter": "Филтрирай repacks",
|
||||||
"requirements": "Състемни изисквания",
|
"requirements": "Системни изисквания",
|
||||||
"minimum": "Минимални",
|
"minimum": "Минимални",
|
||||||
"recommended": "Препоръчителни",
|
"recommended": "Препоръчителни",
|
||||||
"paused": "Паузирано",
|
"paused": "Паузирано",
|
||||||
@ -79,8 +79,8 @@
|
|||||||
"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}} все още",
|
||||||
"next_suggestion": "Следващо предложение",
|
"next_suggestion": "Следващо предложение",
|
||||||
"play": "Пускане",
|
"play": "Пускане",
|
||||||
@ -110,7 +110,7 @@
|
|||||||
"remove_from_library_description": "Това ще премахне {{game}} от Библиотеката",
|
"remove_from_library_description": "Това ще премахне {{game}} от Библиотеката",
|
||||||
"options": "Опции",
|
"options": "Опции",
|
||||||
"executable_section_title": "Стартиращ файл",
|
"executable_section_title": "Стартиращ файл",
|
||||||
"executable_section_description": "Пътят на файла, който ще се изпълни, когато се щракне върху \"Играй\"",
|
"executable_section_description": "Пътят на файла, който ще се изпълни, когато се щракне върху \"Пускане\"",
|
||||||
"downloads_secion_title": "Свалени",
|
"downloads_secion_title": "Свалени",
|
||||||
"downloads_section_description": "Вижте актуализации или други версии на тази игра",
|
"downloads_section_description": "Вижте актуализации или други версии на тази игра",
|
||||||
"danger_zone_section_title": "Опасна зона",
|
"danger_zone_section_title": "Опасна зона",
|
||||||
@ -162,7 +162,7 @@
|
|||||||
"no_download_option_info": "Няма налични данни",
|
"no_download_option_info": "Няма налични данни",
|
||||||
"backup_deletion_failed": "Неуспешно изтриване на резервно копие",
|
"backup_deletion_failed": "Неуспешно изтриване на резервно копие",
|
||||||
"max_number_of_artifacts_reached": "Достигнат максимален брой резервни копия за тази игра",
|
"max_number_of_artifacts_reached": "Достигнат максимален брой резервни копия за тази игра",
|
||||||
"achievements_not_sync": "Постиженията ви не са синхронизирани",
|
"achievements_not_sync": "Постиженията не са синхронизирани",
|
||||||
"manage_files_description": "Управлявайте кои файлове ще бъдат архивирани и възстановени",
|
"manage_files_description": "Управлявайте кои файлове ще бъдат архивирани и възстановени",
|
||||||
"select_folder": "Избери папка",
|
"select_folder": "Избери папка",
|
||||||
"backup_from": "Резервно копие от {{date}}",
|
"backup_from": "Резервно копие от {{date}}",
|
||||||
@ -198,7 +198,7 @@
|
|||||||
"downloads_completed": "Приключени",
|
"downloads_completed": "Приключени",
|
||||||
"queued": "В опашка",
|
"queued": "В опашка",
|
||||||
"no_downloads_title": "Толкова е празно",
|
"no_downloads_title": "Толкова е празно",
|
||||||
"no_downloads_description": "Все още не сте изтеглили нищо с Hydra, но никога не е късно да започнете..",
|
"no_downloads_description": "Все още не сте изтеглили нищо с Hydra, но никога не е късно да започнете...",
|
||||||
"checking_files": "Проверка на файлове…"
|
"checking_files": "Проверка на файлове…"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -331,7 +331,7 @@
|
|||||||
"blocked_users": "Блокирани потребители",
|
"blocked_users": "Блокирани потребители",
|
||||||
"unblock": "Отблокирай",
|
"unblock": "Отблокирай",
|
||||||
"no_friends_added": "Не сте добавили приятели",
|
"no_friends_added": "Не сте добавили приятели",
|
||||||
"pending": "Чакащо",
|
"pending": "Чакащи",
|
||||||
"no_pending_invites": "Нямате чакащи покани",
|
"no_pending_invites": "Нямате чакащи покани",
|
||||||
"no_blocked_users": "Нямате блокирани потребители",
|
"no_blocked_users": "Нямате блокирани потребители",
|
||||||
"friend_code_copied": "Приятелския код е копиран",
|
"friend_code_copied": "Приятелския код е копиран",
|
||||||
|
@ -6,7 +6,11 @@
|
|||||||
"home": {
|
"home": {
|
||||||
"featured": "Doporučené",
|
"featured": "Doporučené",
|
||||||
"surprise_me": "Překvap mě",
|
"surprise_me": "Překvap mě",
|
||||||
"no_results": "Výsledek nenalezen"
|
"no_results": "Výsledek nenalezen",
|
||||||
|
"start_typing": "Začni psát pro vyhledávání...",
|
||||||
|
"hot": "Teď populární",
|
||||||
|
"weekly": "📅 Nejlepší hry týdne",
|
||||||
|
"achievements": "🏆 Hry k překonání"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"catalogue": "Katalog",
|
"catalogue": "Katalog",
|
||||||
@ -20,7 +24,9 @@
|
|||||||
"home": "Domov",
|
"home": "Domov",
|
||||||
"queued": "{{title}} (V řadě)",
|
"queued": "{{title}} (V řadě)",
|
||||||
"game_has_no_executable": "Hra nemá zvolen žádný spustitelný soubor",
|
"game_has_no_executable": "Hra nemá zvolen žádný spustitelný soubor",
|
||||||
"sign_in": "Přihlásit se"
|
"sign_in": "Přihlásit se",
|
||||||
|
"friends": "Přátelé",
|
||||||
|
"need_help": "Potřebujete pomoc?"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Vyhledat hry",
|
"search": "Vyhledat hry",
|
||||||
@ -113,7 +119,54 @@
|
|||||||
"download_paused": "Stahování pozastaveno",
|
"download_paused": "Stahování pozastaveno",
|
||||||
"last_downloaded_option": "Poslední stažená možnost",
|
"last_downloaded_option": "Poslední stažená možnost",
|
||||||
"create_shortcut_success": "Zástupce vytvořen úspěšně",
|
"create_shortcut_success": "Zástupce vytvořen úspěšně",
|
||||||
"create_shortcut_error": "Chyba při pokusu vytvořit zástupce"
|
"create_shortcut_error": "Chyba při pokusu vytvořit zástupce",
|
||||||
|
"nsfw_content_title": "Tahle hra obsahuje nevhodný obsah",
|
||||||
|
"nsfw_content_description": "{{title}} obsahuje obsah, který by nemusel být vhodný pro všechny věkové skupiny. Jste si jisti, že chcete pokračovat?",
|
||||||
|
"allow_nsfw_content": "Pokračovat",
|
||||||
|
"refuse_nsfw_content": "Jít zpět",
|
||||||
|
"stats": "Statistiky",
|
||||||
|
"download_count": "Stažení",
|
||||||
|
"player_count": "Aktivní hráči",
|
||||||
|
"download_error": "Tahle možnost stažení není dostupná",
|
||||||
|
"download": "Stáhnout",
|
||||||
|
"executable_path_in_use": "Spustitelný soubor již používá \"{{game}}\"",
|
||||||
|
"warning": "Varování",
|
||||||
|
"hydra_needs_to_remain_open": "Pro tohle stažení, musí Hydra zůstat otevřená až do konce stahování. Pokud Hydru zavřete dříve, postup stahování bude ztracen.",
|
||||||
|
"achievements": "Achievementy",
|
||||||
|
"achievements_count": "Achievementy {{unlockedCount}}/{{achievementsCount}}",
|
||||||
|
"cloud_save": "Uložení v cloudu",
|
||||||
|
"cloud_save_description": "Uložte si svůj postup v cloud a pokračujte v hraní na jakémkoliv zářízení",
|
||||||
|
"backups": "Zálohy",
|
||||||
|
"install_backup": "Nainstalovat",
|
||||||
|
"delete_backup": "Smazat",
|
||||||
|
"create_backup": "Vytvořit zálohu",
|
||||||
|
"last_backup_date": "Poslední záloha vytvořena {{date}}",
|
||||||
|
"no_backup_preview": "Žádné zálohy nebyly nalezeny pro tuhle hru",
|
||||||
|
"restoring_backup": "Obnovuji zálohu ({{progress}} hotovo)...",
|
||||||
|
"uploading_backup": "Nahrávání zálohy...",
|
||||||
|
"no_backups": "Nemáte zatím vytvořeny žádné zálohy pro tuto hru",
|
||||||
|
"backup_uploaded": "Záloha nahrána",
|
||||||
|
"backup_deleted": "Záloha odstraněna",
|
||||||
|
"backup_restored": "Záloha obnovena",
|
||||||
|
"see_all_achievements": "Zobrazit všechny achievementy",
|
||||||
|
"sign_in_to_see_achievements": "Musíte se přihlásit pro zobrazení achievementů",
|
||||||
|
"mapping_method_automatic": "Automaticky",
|
||||||
|
"mapping_method_manual": "Manuálně",
|
||||||
|
"mapping_method_label": "Metoda mapování",
|
||||||
|
"files_automatically_mapped": "Soubory automaticky zmapovány",
|
||||||
|
"no_backups_created": "Žádné zálohy nebyly vytvořeny pro tuto hru",
|
||||||
|
"manage_files": "Spravovat soubory",
|
||||||
|
"loading_save_preview": "Hledání uložených her...",
|
||||||
|
"wine_prefix": "Wine Prefix",
|
||||||
|
"wine_prefix_description": "Wine Prefix použit pro spuštění této hry",
|
||||||
|
"no_download_option_info": "Žádné informace nejsou dostupny",
|
||||||
|
"backup_deletion_failed": "Nepovedlo se odstranit zálohu",
|
||||||
|
"max_number_of_artifacts_reached": "Dosáhli jste maximálního počtu záloh pro tuto hru",
|
||||||
|
"achievements_not_sync": "Vaše achievementy nejsou synchronizovány",
|
||||||
|
"manage_files_description": "Spravovat, které soubory budou zálohovány a obnoveny",
|
||||||
|
"select_folder": "Vybrat složku",
|
||||||
|
"backup_from": "Zálohy z {{date}}",
|
||||||
|
"custom_backup_location_set": "Vlastní umístění záloh nastaveno"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Aktivovat hydru",
|
"title": "Aktivovat hydru",
|
||||||
@ -189,7 +242,21 @@
|
|||||||
"found_download_option_zero": "Nenalezena žádná možnost stahování",
|
"found_download_option_zero": "Nenalezena žádná možnost stahování",
|
||||||
"found_download_option_one": "Nalezena {{countFormatted}} možnost stahování",
|
"found_download_option_one": "Nalezena {{countFormatted}} možnost stahování",
|
||||||
"found_download_option_other": "Nalezeny {{countFormatted}} možnosti stahování",
|
"found_download_option_other": "Nalezeny {{countFormatted}} možnosti stahování",
|
||||||
"import": "Importovat"
|
"import": "Importovat",
|
||||||
|
"public": "Veřejné",
|
||||||
|
"private": "Soukromé",
|
||||||
|
"friends_only": "Pouze přátelé",
|
||||||
|
"privacy": "Soukromí",
|
||||||
|
"profile_visibility": "Viditelnost profilu",
|
||||||
|
"profile_visibility_description": "Vyberte si, kdo může vidět váš profil a knihovnu",
|
||||||
|
"required_field": "Toto pole je povinné",
|
||||||
|
"source_already_exists": "Tento zdroj byl již přidán",
|
||||||
|
"must_be_valid_url": "Zdroj musí být platký odkaz URL",
|
||||||
|
"blocked_users": "Zablokovaní uživatelé",
|
||||||
|
"user_unblocked": "Uživatel byl odblokován",
|
||||||
|
"enable_achievement_notifications": "Když je odemknut achievement",
|
||||||
|
"launch_minimized": "Spustit v minimalizovaném režimu",
|
||||||
|
"disable_nsfw_alert": "Deaktivovat upozornění na nevhodný obsah"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Stahování dokončeno",
|
"download_complete": "Stahování dokončeno",
|
||||||
@ -198,7 +265,9 @@
|
|||||||
"repack_count_one": "{{count}} repack přidán",
|
"repack_count_one": "{{count}} repack přidán",
|
||||||
"repack_count_other": "{{count}} repacky přidány",
|
"repack_count_other": "{{count}} repacky přidány",
|
||||||
"new_update_available": "Version {{version}} je dostupná",
|
"new_update_available": "Version {{version}} je dostupná",
|
||||||
"restart_to_install_update": "Restartuj Hydru pro aktualizaci"
|
"restart_to_install_update": "Restartuj Hydru pro aktualizaci",
|
||||||
|
"notification_achievement_unlocked_title": "Achievement pro {{game}} byl odemknut",
|
||||||
|
"notification_achievement_unlocked_body": "{{achievement}} a dalších {{count}} byly odemknuty"
|
||||||
},
|
},
|
||||||
"system_tray": {
|
"system_tray": {
|
||||||
"open": "Otevřít Hydru",
|
"open": "Otevřít Hydru",
|
||||||
@ -266,6 +335,47 @@
|
|||||||
"no_pending_invites": "Nemáte žádné příchozí žádosti",
|
"no_pending_invites": "Nemáte žádné příchozí žádosti",
|
||||||
"no_blocked_users": "Nemáte nikoho zablokovaného",
|
"no_blocked_users": "Nemáte nikoho zablokovaného",
|
||||||
"friend_code_copied": "Kód přítele zkopírován",
|
"friend_code_copied": "Kód přítele zkopírován",
|
||||||
"undo_friendship_modal_text": "Tímto zrušíte své přátelství s {{displayName}}"
|
"undo_friendship_modal_text": "Tímto zrušíte své přátelství s {{displayName}}",
|
||||||
|
"privacy_hint": "Pro změnu toho, kdo tohle může vidět, jděte do <0>Nastavení</0>",
|
||||||
|
"locked_profile": "Tento profil je soukromý",
|
||||||
|
"image_process_failure": "Nastala chyba při zpracování obrázku",
|
||||||
|
"required_field": "Toto pole je povinné",
|
||||||
|
"displayname_min_length": "Uživatelské jméno musí být minimálně 3 znaky dlouhé",
|
||||||
|
"displayname_max_length": "Uživatelské jméno musí být maximálně 50 znaků dlouhé",
|
||||||
|
"report_profile": "Nahlásit profil",
|
||||||
|
"report_reason": "Proč nahlašujete tento profil?",
|
||||||
|
"report_description": "Přídavné informace",
|
||||||
|
"report_description_placeholder": "Přídavné informace",
|
||||||
|
"report": "Nahlásit",
|
||||||
|
"report_reason_hate": "Nenávistné projevy",
|
||||||
|
"report_reason_sexual_content": "Sexuální obsah",
|
||||||
|
"report_reason_violence": "Násilí",
|
||||||
|
"report_reason_spam": "Spam",
|
||||||
|
"report_reason_other": "Ostatní",
|
||||||
|
"profile_reported": "Profil nahlášen",
|
||||||
|
"your_friend_code": "Tvůj kód přítele:",
|
||||||
|
"upload_banner": "Nahrát banner profilu",
|
||||||
|
"uploading_banner": "Nahrávání banneru",
|
||||||
|
"background_image_updated": "Obrázek pozadí byl změněn"
|
||||||
|
},
|
||||||
|
"achievement": {
|
||||||
|
"achievement_unlocked": "Achievement odemčen",
|
||||||
|
"user_achievements": "Achievementy uživatele {{displayName}}",
|
||||||
|
"your_achievements": "Vaše achievementy",
|
||||||
|
"unlocked_at": "Odemčeno:",
|
||||||
|
"subscription_needed": "Je vyžadováno předplatné Hydra Cloud pro zobrazení tohoto obsahu",
|
||||||
|
"new_achievements_unlocked": "Odemčeno {{achievementCount}} nových achievementů z {{gameCount}} her",
|
||||||
|
"achievement_progress": "{{unlockedCount}}/{{totalCount}} achievementů",
|
||||||
|
"achievements_unlocked_for_game": "Odemčeno {{achievementCount}} nových achievementů pro {{gameTitle}}"
|
||||||
|
},
|
||||||
|
"tour": {
|
||||||
|
"subscription_tour_title": "Předplatné Hydra Cloud",
|
||||||
|
"subscribe_now": "Připojit se",
|
||||||
|
"cloud_saving": "Ukládání v cloudu",
|
||||||
|
"cloud_achievements": "Ukládejte vaše achievementy do cloudu",
|
||||||
|
"animated_profile_picture": "Animované profilové obrázky",
|
||||||
|
"premium_support": "Prémiová podpora",
|
||||||
|
"show_and_compare_achievements": "Zobraz a porovnej achievementy s ostatními uživateli",
|
||||||
|
"animated_profile_banner": "Animovaný banner na profilu"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,7 @@
|
|||||||
"open_folder": "Open folder",
|
"open_folder": "Open folder",
|
||||||
"open_download_location": "See downloaded files",
|
"open_download_location": "See downloaded files",
|
||||||
"create_shortcut": "Create desktop shortcut",
|
"create_shortcut": "Create desktop shortcut",
|
||||||
|
"clear": "Clear",
|
||||||
"remove_files": "Remove files",
|
"remove_files": "Remove files",
|
||||||
"remove_from_library_title": "Are you sure?",
|
"remove_from_library_title": "Are you sure?",
|
||||||
"remove_from_library_description": "This will remove {{game}} from your library",
|
"remove_from_library_description": "This will remove {{game}} from your library",
|
||||||
@ -166,7 +167,8 @@
|
|||||||
"manage_files_description": "Manage which files will be backed up and restored",
|
"manage_files_description": "Manage which files will be backed up and restored",
|
||||||
"select_folder": "Select folder",
|
"select_folder": "Select folder",
|
||||||
"backup_from": "Backup from {{date}}",
|
"backup_from": "Backup from {{date}}",
|
||||||
"custom_backup_location_set": "Custom backup location set"
|
"custom_backup_location_set": "Custom backup location set",
|
||||||
|
"no_directory_selected": "No directory selected"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Activate Hydra",
|
"title": "Activate Hydra",
|
||||||
@ -261,7 +263,8 @@
|
|||||||
"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",
|
||||||
"seed_after_download_complete": "Seed after download complete"
|
"seed_after_download_complete": "Seed after download complete",
|
||||||
|
"show_hidden_achievement_description": "Show hidden achievements description before unlocking them"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download complete",
|
"download_complete": "Download complete",
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
"add_to_library": "Agregar a la biblioteca",
|
"add_to_library": "Agregar a la biblioteca",
|
||||||
"remove_from_library": "Eliminar de la biblioteca",
|
"remove_from_library": "Eliminar de la biblioteca",
|
||||||
"no_downloads": "No hay descargas disponibles",
|
"no_downloads": "No hay descargas disponibles",
|
||||||
"play_time": "Jugado por {{amount}}",
|
"play_time": "Has jugado {{amount}}",
|
||||||
"last_time_played": "Jugado por última vez: {{period}}",
|
"last_time_played": "Jugado por última vez: {{period}}",
|
||||||
"not_played_yet": "Aún no has jugado a {{title}}",
|
"not_played_yet": "Aún no has jugado a {{title}}",
|
||||||
"next_suggestion": "Siguiente sugerencia",
|
"next_suggestion": "Siguiente sugerencia",
|
||||||
@ -100,7 +100,7 @@
|
|||||||
"open_screenshot": "Abrir captura {{number}}",
|
"open_screenshot": "Abrir captura {{number}}",
|
||||||
"download_settings": "Ajustes de descarga",
|
"download_settings": "Ajustes de descarga",
|
||||||
"downloader": "Método de descarga",
|
"downloader": "Método de descarga",
|
||||||
"select_executable": "Seleccionar ejecutable",
|
"select_executable": "Seleccionar",
|
||||||
"no_executable_selected": "No se seleccionó un ejecutable",
|
"no_executable_selected": "No se seleccionó un ejecutable",
|
||||||
"open_folder": "Abrir carpeta",
|
"open_folder": "Abrir carpeta",
|
||||||
"open_download_location": "Ver archivos descargados",
|
"open_download_location": "Ver archivos descargados",
|
||||||
@ -166,7 +166,9 @@
|
|||||||
"manage_files_description": "Gestiona los archivos que serán respaldados y restaurados",
|
"manage_files_description": "Gestiona los archivos que serán respaldados y restaurados",
|
||||||
"select_folder": "Seleccionar carpeta",
|
"select_folder": "Seleccionar carpeta",
|
||||||
"backup_from": "Copia de seguridad de {{date}}",
|
"backup_from": "Copia de seguridad de {{date}}",
|
||||||
"custom_backup_location_set": "Se configuró la carpeta de copia de seguridad"
|
"custom_backup_location_set": "Se configuró la carpeta de copia de seguridad",
|
||||||
|
"clear": "Limpiar",
|
||||||
|
"no_directory_selected": "No se seleccionó un directório"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Activar Hydra",
|
"title": "Activar Hydra",
|
||||||
@ -254,7 +256,9 @@
|
|||||||
"must_be_valid_url": "La fuente debe ser una URL válida.",
|
"must_be_valid_url": "La fuente debe ser una URL válida.",
|
||||||
"blocked_users": "Usuarios bloqueados",
|
"blocked_users": "Usuarios bloqueados",
|
||||||
"user_unblocked": "El usuario ha sido desbloqueado",
|
"user_unblocked": "El usuario ha sido desbloqueado",
|
||||||
"enable_achievement_notifications": "Cuando un logro se desbloquea"
|
"enable_achievement_notifications": "Cuando un logro se desbloquea",
|
||||||
|
"launch_minimized": "Iniciar Hydra minimizado",
|
||||||
|
"disable_nsfw_alert": "Desactivar alerta NSFW"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Descarga completada",
|
"download_complete": "Descarga completada",
|
||||||
@ -304,7 +308,7 @@
|
|||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"successfully_signed_out": "Sesión cerrada exitosamente",
|
"successfully_signed_out": "Sesión cerrada exitosamente",
|
||||||
"sign_out": "Cerrar sesión",
|
"sign_out": "Cerrar sesión",
|
||||||
"playing_for": "Jugando por {{amount}}",
|
"playing_for": "Llevas jugando {{amount}}",
|
||||||
"sign_out_modal_text": "Tu biblioteca se ha vinculado con tu cuenta. Cuando cierres sesión, tú biblioteca ya no será visible y cualquier progreso no se guardará. ¿Continuar con el cierre de sesión?",
|
"sign_out_modal_text": "Tu biblioteca se ha vinculado con tu cuenta. Cuando cierres sesión, tú biblioteca ya no será visible y cualquier progreso no se guardará. ¿Continuar con el cierre de sesión?",
|
||||||
"add_friends": "Añadir amigos",
|
"add_friends": "Añadir amigos",
|
||||||
"add": "Añadir",
|
"add": "Añadir",
|
||||||
@ -361,8 +365,10 @@
|
|||||||
"user_achievements": "Logros de {{displayName}}",
|
"user_achievements": "Logros de {{displayName}}",
|
||||||
"your_achievements": "Tus Logros",
|
"your_achievements": "Tus Logros",
|
||||||
"unlocked_at": "Desbloqueado el:",
|
"unlocked_at": "Desbloqueado el:",
|
||||||
"subscription_needed": "Se necesita una suscripción a Hydra Cloud se necesita para ver este contenido",
|
"subscription_needed": "Se necesita una suscripción a Hydra Cloud necesita para ver este contenido",
|
||||||
"new_achievements_unlocked": "Desbloqueados {{achievementCount}} nuevos logros de {{gameCount}} juegos"
|
"new_achievements_unlocked": "Desbloqueados {{achievementCount}} nuevos logros de {{gameCount}} juegos",
|
||||||
|
"achievement_progress": "{{unlockedCount}}/{{totalCount}} logros",
|
||||||
|
"achievements_unlocked_for_game": "Se han desbloqueado {{achievementCount}} nuevos logros de {{gameTitle}}"
|
||||||
},
|
},
|
||||||
"tour": {
|
"tour": {
|
||||||
"subscription_tour_title": "Suscripción Hydra Cloud",
|
"subscription_tour_title": "Suscripción Hydra Cloud",
|
||||||
|
@ -162,7 +162,9 @@
|
|||||||
"backup_from": "Backup de {{date}}",
|
"backup_from": "Backup de {{date}}",
|
||||||
"custom_backup_location_set": "Localização customizada selecionada",
|
"custom_backup_location_set": "Localização customizada selecionada",
|
||||||
"select_folder": "Selecione a pasta",
|
"select_folder": "Selecione a pasta",
|
||||||
"manage_files_description": "Gerencie quais arquivos serão feitos backup"
|
"manage_files_description": "Gerencie quais arquivos serão feitos backup",
|
||||||
|
"clear": "Limpar",
|
||||||
|
"no_directory_selected": "Nenhum diretório selecionado"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Ativação",
|
"title": "Ativação",
|
||||||
@ -257,7 +259,8 @@
|
|||||||
"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",
|
||||||
"seed_after_download_complete": "Semear após a conclusão do download"
|
"seed_after_download_complete": "Semear após a conclusão do download",
|
||||||
|
"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",
|
||||||
|
@ -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,16 +247,17 @@
|
|||||||
"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": "Пользователь разблокирован",
|
||||||
|
"show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Загрузка завершена",
|
"download_complete": "Загрузка завершена",
|
||||||
@ -223,15 +265,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 +354,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": "Анимированный баннер профиля"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,9 @@ export class UserPreferences {
|
|||||||
@Column("boolean", { default: true })
|
@Column("boolean", { default: true })
|
||||||
seedAfterDownloadComplete: boolean;
|
seedAfterDownloadComplete: boolean;
|
||||||
|
|
||||||
|
@Column("boolean", { default: false })
|
||||||
|
showHiddenAchievementsDescription: boolean;
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import type { GameShop, HowLongToBeatCategory } from "@types";
|
import type { GameShop, HowLongToBeatCategory } from "@types";
|
||||||
import { HydraApi } from "@main/services";
|
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
import { HydraApi } from "@main/services";
|
||||||
|
|
||||||
const getHowLongToBeat = async (
|
const getHowLongToBeat = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
shop: GameShop,
|
objectId: string,
|
||||||
objectId: string
|
shop: GameShop
|
||||||
): Promise<HowLongToBeatCategory[] | null> => {
|
): Promise<HowLongToBeatCategory[] | null> => {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
|
objectId,
|
||||||
shop,
|
shop,
|
||||||
objectId: objectId.toString(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return HydraApi.get(`/games/how-long-to-beat?${params.toString()}`, null, {
|
return HydraApi.get(`/games/how-long-to-beat?${params.toString()}`, null, {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { appVersion, defaultDownloadsPath } from "@main/constants";
|
import { appVersion, defaultDownloadsPath, isStaging } from "@main/constants";
|
||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
|
|
||||||
import "./catalogue/get-catalogue";
|
import "./catalogue/get-catalogue";
|
||||||
@ -74,5 +74,6 @@ import "./misc/show-item-in-folder";
|
|||||||
|
|
||||||
ipcMain.handle("ping", () => "pong");
|
ipcMain.handle("ping", () => "pong");
|
||||||
ipcMain.handle("getVersion", () => appVersion);
|
ipcMain.handle("getVersion", () => appVersion);
|
||||||
|
ipcMain.handle("isStaging", () => isStaging);
|
||||||
ipcMain.handle("isPortableVersion", () => isPortableVersion());
|
ipcMain.handle("isPortableVersion", () => isPortableVersion());
|
||||||
ipcMain.handle("getDefaultDownloadsPath", () => defaultDownloadsPath);
|
ipcMain.handle("getDefaultDownloadsPath", () => defaultDownloadsPath);
|
||||||
|
@ -25,7 +25,11 @@ const closeGame = async (
|
|||||||
if (!game) return;
|
if (!game) return;
|
||||||
|
|
||||||
const gameProcess = processes.find((runningProcess) => {
|
const gameProcess = processes.find((runningProcess) => {
|
||||||
return runningProcess.exe === game.executablePath;
|
if (process.platform === "linux") {
|
||||||
|
return runningProcess.name === game.executablePath?.split("/").at(-1);
|
||||||
|
} else {
|
||||||
|
return runningProcess.exe === game.executablePath;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (gameProcess) {
|
if (gameProcess) {
|
||||||
|
@ -5,9 +5,9 @@ import { registerEvent } from "../register-event";
|
|||||||
const selectGameWinePrefix = async (
|
const selectGameWinePrefix = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
id: number,
|
id: number,
|
||||||
winePrefixPath: string
|
winePrefixPath: string | null
|
||||||
) => {
|
) => {
|
||||||
return gameRepository.update({ id }, { winePrefixPath });
|
return gameRepository.update({ id }, { winePrefixPath: winePrefixPath });
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("selectGameWinePrefix", selectGameWinePrefix);
|
registerEvent("selectGameWinePrefix", selectGameWinePrefix);
|
||||||
|
@ -6,14 +6,18 @@ import { parseExecutablePath } from "../helpers/parse-executable-path";
|
|||||||
const updateExecutablePath = async (
|
const updateExecutablePath = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
id: number,
|
id: number,
|
||||||
executablePath: string
|
executablePath: string | null
|
||||||
) => {
|
) => {
|
||||||
|
const parsedPath = executablePath
|
||||||
|
? parseExecutablePath(executablePath)
|
||||||
|
: null;
|
||||||
|
|
||||||
return gameRepository.update(
|
return gameRepository.update(
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
executablePath: parseExecutablePath(executablePath),
|
executablePath: parsedPath,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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,24 +76,23 @@ const startGameDownload = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
createGame(updatedGame!).catch(() => {});
|
|
||||||
|
|
||||||
HydraApi.post(
|
|
||||||
"/games/download",
|
|
||||||
{
|
|
||||||
objectId: updatedGame!.objectID,
|
|
||||||
shop: updatedGame!.shop,
|
|
||||||
},
|
|
||||||
{ needsAuth: false }
|
|
||||||
).catch((err) => {
|
|
||||||
logger.error("Failed to create game download", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
await DownloadManager.cancelDownload(updatedGame!.id);
|
await DownloadManager.cancelDownload(updatedGame!.id);
|
||||||
await DownloadManager.startDownload(updatedGame!);
|
await DownloadManager.startDownload(updatedGame!);
|
||||||
|
|
||||||
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
||||||
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
createGame(updatedGame!).catch(() => {}),
|
||||||
|
HydraApi.post(
|
||||||
|
"/games/download",
|
||||||
|
{
|
||||||
|
objectId: updatedGame!.objectID,
|
||||||
|
shop: updatedGame!.shop,
|
||||||
|
},
|
||||||
|
{ needsAuth: false }
|
||||||
|
).catch(() => {}),
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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) => {
|
||||||
|
@ -15,6 +15,8 @@ import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_m
|
|||||||
import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_column";
|
import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_column";
|
||||||
import { AddShouldSeedColumn } from "./migrations/20241108200154_add_should_seed_colum";
|
import { AddShouldSeedColumn } from "./migrations/20241108200154_add_should_seed_colum";
|
||||||
import { AddSeedAfterDownloadColumn } from "./migrations/20241108201806_add_seed_after_download";
|
import { AddSeedAfterDownloadColumn } from "./migrations/20241108201806_add_seed_after_download";
|
||||||
|
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> {
|
||||||
@ -34,6 +36,7 @@ class MigrationSource implements Knex.MigrationSource<HydraMigration> {
|
|||||||
AddDisableNsfwAlertColumn,
|
AddDisableNsfwAlertColumn,
|
||||||
AddShouldSeedColumn,
|
AddShouldSeedColumn,
|
||||||
AddSeedAfterDownloadColumn,
|
AddSeedAfterDownloadColumn,
|
||||||
|
AddHiddenAchievementDescriptionColumn,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
getMigrationName(migration: HydraMigration): string {
|
getMigrationName(migration: HydraMigration): string {
|
||||||
|
@ -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");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
@ -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");
|
|
||||||
}
|
|
@ -236,24 +236,28 @@ export class AchievementWatcherManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public static preSearchAchievements = async () => {
|
public static preSearchAchievements = async () => {
|
||||||
const newAchievementsCount =
|
try {
|
||||||
process.platform === "win32"
|
const newAchievementsCount =
|
||||||
? await this.preSearchAchievementsWindows()
|
process.platform === "win32"
|
||||||
: await this.preSearchAchievementsWithWine();
|
? await this.preSearchAchievementsWindows()
|
||||||
|
: await this.preSearchAchievementsWithWine();
|
||||||
|
|
||||||
const totalNewGamesWithAchievements = newAchievementsCount.filter(
|
const totalNewGamesWithAchievements = newAchievementsCount.filter(
|
||||||
(achievements) => achievements
|
(achievements) => achievements
|
||||||
).length;
|
).length;
|
||||||
const totalNewAchievements = newAchievementsCount.reduce(
|
const totalNewAchievements = newAchievementsCount.reduce(
|
||||||
(acc, val) => acc + val,
|
(acc, val) => acc + val,
|
||||||
0
|
0
|
||||||
);
|
|
||||||
|
|
||||||
if (totalNewAchievements > 0) {
|
|
||||||
publishCombinedNewAchievementNotification(
|
|
||||||
totalNewAchievements,
|
|
||||||
totalNewGamesWithAchievements
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (totalNewAchievements > 0) {
|
||||||
|
publishCombinedNewAchievementNotification(
|
||||||
|
totalNewAchievements,
|
||||||
|
totalNewGamesWithAchievements
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
achievementsLogger.error("Error on preSearchAchievements", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasFinishedMergingWithRemote = true;
|
this.hasFinishedMergingWithRemote = true;
|
||||||
|
@ -6,20 +6,15 @@ 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) {
|
if (cachedAchievements && cachedAchievements.achievements) {
|
||||||
const cachedAchievements = await gameAchievementRepository.findOne({
|
return JSON.parse(cachedAchievements.achievements) as AchievementData[];
|
||||||
where: { objectId, shop },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cachedAchievements && cachedAchievements.achievements) {
|
|
||||||
return JSON.parse(cachedAchievements.achievements) as AchievementData[];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userPreferences = await userPreferencesRepository.findOne({
|
const userPreferences = await userPreferencesRepository.findOne({
|
||||||
|
@ -9,144 +9,134 @@ export const parseAchievementFile = (
|
|||||||
): UnlockedAchievement[] => {
|
): UnlockedAchievement[] => {
|
||||||
if (!existsSync(filePath)) return [];
|
if (!existsSync(filePath)) return [];
|
||||||
|
|
||||||
if (type == Cracker.codex) {
|
try {
|
||||||
const parsed = iniParse(filePath);
|
if (type == Cracker.codex) {
|
||||||
return processDefault(parsed);
|
const parsed = iniParse(filePath);
|
||||||
|
return processDefault(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == Cracker.rune) {
|
||||||
|
const parsed = iniParse(filePath);
|
||||||
|
return processDefault(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === Cracker.onlineFix) {
|
||||||
|
const parsed = iniParse(filePath);
|
||||||
|
return processOnlineFix(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === Cracker.goldberg) {
|
||||||
|
const parsed = jsonParse(filePath);
|
||||||
|
return processGoldberg(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == Cracker.userstats) {
|
||||||
|
const parsed = iniParse(filePath);
|
||||||
|
return processUserStats(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == Cracker.rld) {
|
||||||
|
const parsed = iniParse(filePath);
|
||||||
|
return processRld(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === Cracker.skidrow) {
|
||||||
|
const parsed = iniParse(filePath);
|
||||||
|
return processSkidrow(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === Cracker._3dm) {
|
||||||
|
const parsed = iniParse(filePath);
|
||||||
|
return process3DM(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === Cracker.flt) {
|
||||||
|
const achievements = readdirSync(filePath);
|
||||||
|
|
||||||
|
return achievements.map((achievement) => {
|
||||||
|
return {
|
||||||
|
name: achievement,
|
||||||
|
unlockTime: Date.now(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === Cracker.creamAPI) {
|
||||||
|
const parsed = iniParse(filePath);
|
||||||
|
return processCreamAPI(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === Cracker.empress) {
|
||||||
|
const parsed = jsonParse(filePath);
|
||||||
|
return processGoldberg(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === Cracker.razor1911) {
|
||||||
|
return processRazor1911(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
achievementsLogger.log(
|
||||||
|
`Unprocessed ${type} achievements found on ${filePath}`
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
} catch (err) {
|
||||||
|
achievementsLogger.error(`Error parsing ${type} - ${filePath}`, err);
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == Cracker.rune) {
|
|
||||||
const parsed = iniParse(filePath);
|
|
||||||
return processDefault(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === Cracker.onlineFix) {
|
|
||||||
const parsed = iniParse(filePath);
|
|
||||||
return processOnlineFix(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === Cracker.goldberg) {
|
|
||||||
const parsed = jsonParse(filePath);
|
|
||||||
return processGoldberg(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == Cracker.userstats) {
|
|
||||||
const parsed = iniParse(filePath);
|
|
||||||
return processUserStats(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == Cracker.rld) {
|
|
||||||
const parsed = iniParse(filePath);
|
|
||||||
return processRld(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === Cracker.skidrow) {
|
|
||||||
const parsed = iniParse(filePath);
|
|
||||||
return processSkidrow(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === Cracker._3dm) {
|
|
||||||
const parsed = iniParse(filePath);
|
|
||||||
return process3DM(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === Cracker.flt) {
|
|
||||||
const achievements = readdirSync(filePath);
|
|
||||||
|
|
||||||
return achievements.map((achievement) => {
|
|
||||||
return {
|
|
||||||
name: achievement,
|
|
||||||
unlockTime: Date.now(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === Cracker.creamAPI) {
|
|
||||||
const parsed = iniParse(filePath);
|
|
||||||
return processCreamAPI(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === Cracker.empress) {
|
|
||||||
const parsed = jsonParse(filePath);
|
|
||||||
return processGoldberg(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === Cracker.razor1911) {
|
|
||||||
return processRazor1911(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
achievementsLogger.log(
|
|
||||||
`Unprocessed ${type} achievements found on ${filePath}`
|
|
||||||
);
|
|
||||||
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 =
|
||||||
fileContent.charCodeAt(0) === 0xfeff
|
fileContent.charCodeAt(0) === 0xfeff
|
||||||
? fileContent.slice(1).split(/[\r\n]+/)
|
? fileContent.slice(1).split(/[\r\n]+/)
|
||||||
: fileContent.split(/[\r\n]+/);
|
: fileContent.split(/[\r\n]+/);
|
||||||
|
|
||||||
let objectName = "";
|
let objectName = "";
|
||||||
const object: Record<string, Record<string, string | number>> = {};
|
const object: Record<string, Record<string, string | number>> = {};
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.startsWith("###") || !line.length) continue;
|
if (line.startsWith("###") || !line.length) continue;
|
||||||
|
|
||||||
if (line.startsWith("[") && line.endsWith("]")) {
|
if (line.startsWith("[") && line.endsWith("]")) {
|
||||||
objectName = line.slice(1, -1);
|
objectName = line.slice(1, -1);
|
||||||
object[objectName] = {};
|
object[objectName] = {};
|
||||||
} else {
|
} else {
|
||||||
const [name, ...value] = line.split("=");
|
const [name, ...value] = line.split("=");
|
||||||
object[objectName][name.trim()] = value.join("=").trim();
|
object[objectName][name.trim()] = value.join("=").trim();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
|
||||||
} catch (err) {
|
|
||||||
achievementsLogger.error(`Error parsing ${filePath}`, err);
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
};
|
};
|
||||||
|
|
||||||
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 =
|
||||||
fileContent.charCodeAt(0) === 0xfeff
|
fileContent.charCodeAt(0) === 0xfeff
|
||||||
? fileContent.slice(1).split(/[\r\n]+/)
|
? fileContent.slice(1).split(/[\r\n]+/)
|
||||||
: fileContent.split(/[\r\n]+/);
|
: fileContent.split(/[\r\n]+/);
|
||||||
|
|
||||||
const achievements: UnlockedAchievement[] = [];
|
const achievements: UnlockedAchievement[] = [];
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (!line.length) continue;
|
if (!line.length) continue;
|
||||||
|
|
||||||
const [name, unlocked, unlockTime] = line.split(" ");
|
const [name, unlocked, unlockTime] = line.split(" ");
|
||||||
if (unlocked === "1") {
|
if (unlocked === "1") {
|
||||||
achievements.push({
|
achievements.push({
|
||||||
name,
|
name,
|
||||||
unlockTime: Number(unlockTime) * 1000,
|
unlockTime: Number(unlockTime) * 1000,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return achievements;
|
|
||||||
} catch (err) {
|
|
||||||
achievementsLogger.error(`Error processing ${filePath}`, err);
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return achievements;
|
||||||
};
|
};
|
||||||
|
|
||||||
const processOnlineFix = (unlockedAchievements: any): UnlockedAchievement[] => {
|
const processOnlineFix = (unlockedAchievements: any): UnlockedAchievement[] => {
|
||||||
|
@ -30,6 +30,7 @@ export interface LibtorrentPayload {
|
|||||||
export interface ProcessPayload {
|
export interface ProcessPayload {
|
||||||
exe: string;
|
exe: string;
|
||||||
pid: number;
|
pid: number;
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PauseSeedingPayload {
|
export interface PauseSeedingPayload {
|
||||||
|
@ -153,21 +153,26 @@ export class HydraApi {
|
|||||||
(error) => {
|
(error) => {
|
||||||
logger.error(" ---- RESPONSE ERROR -----");
|
logger.error(" ---- RESPONSE ERROR -----");
|
||||||
const { config } = error;
|
const { config } = error;
|
||||||
|
const data = JSON.parse(config.data);
|
||||||
|
|
||||||
logger.error(
|
logger.error(
|
||||||
config.method,
|
config.method,
|
||||||
config.baseURL,
|
config.baseURL,
|
||||||
config.url,
|
config.url,
|
||||||
config.headers,
|
omit(config.headers, ["accessToken", "refreshToken"]),
|
||||||
config.data
|
Array.isArray(data)
|
||||||
|
? data
|
||||||
|
: omit(data, ["accessToken", "refreshToken"])
|
||||||
);
|
);
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Response",
|
"Response error:",
|
||||||
error.response.status,
|
error.response.status,
|
||||||
error.response.data
|
error.response.data
|
||||||
);
|
);
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
logger.error("Request", error.request);
|
const errorData = error.toJSON();
|
||||||
|
logger.error("Request error:", errorData.message);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Error", error.message);
|
logger.error("Error", error.message);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,216 @@
|
|||||||
import { IsNull, Not } from "typeorm";
|
|
||||||
import { gameRepository } from "@main/repository";
|
import { gameRepository } from "@main/repository";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
import { createGame, updateGamePlaytime } from "./library-sync";
|
import { createGame, updateGamePlaytime } from "./library-sync";
|
||||||
import type { GameRunning } from "@types";
|
import type { GameRunning } from "@types";
|
||||||
// import { PythonInstance } from "./download";
|
// import { PythonInstance } from "./download";
|
||||||
import { Game } from "@main/entity";
|
import { Game } from "@main/entity";
|
||||||
|
import axios from "axios";
|
||||||
|
import { exec } from "child_process";
|
||||||
|
|
||||||
|
const commands = {
|
||||||
|
findWineDir: `lsof -c wine 2>/dev/null | grep '/drive_c/windows$' | head -n 1 | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
|
||||||
|
findWineExecutables: `lsof -c wine 2>/dev/null | grep '\\.exe$' | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
|
||||||
|
};
|
||||||
|
|
||||||
export const gamesPlaytime = new Map<
|
export const gamesPlaytime = new Map<
|
||||||
number,
|
number,
|
||||||
{ lastTick: number; firstTick: number; lastSyncTick: number }
|
{ lastTick: number; firstTick: number; lastSyncTick: number }
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
interface ExecutableInfo {
|
||||||
|
name: string;
|
||||||
|
os: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GameExecutables {
|
||||||
|
[key: string]: ExecutableInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
const TICKS_TO_UPDATE_API = 120;
|
const TICKS_TO_UPDATE_API = 120;
|
||||||
let currentTick = 1;
|
let currentTick = 1;
|
||||||
|
|
||||||
const onGameTick = (game: Game) => {
|
const gameExecutables = (
|
||||||
|
await axios
|
||||||
|
.get(
|
||||||
|
import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL +
|
||||||
|
"/game-executables.json"
|
||||||
|
)
|
||||||
|
.catch(() => {
|
||||||
|
return { data: {} };
|
||||||
|
})
|
||||||
|
).data as GameExecutables;
|
||||||
|
|
||||||
|
const findGamePathByProcess = (
|
||||||
|
processMap: Map<string, Set<string>>,
|
||||||
|
gameId: string
|
||||||
|
) => {
|
||||||
|
const executables = gameExecutables[gameId].filter((info) => {
|
||||||
|
if (process.platform === "linux" && info.os === "linux") return true;
|
||||||
|
return info.os === "win32";
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const executable of executables) {
|
||||||
|
const exe = executable.name.slice(executable.name.lastIndexOf("/") + 1);
|
||||||
|
|
||||||
|
if (!exe) continue;
|
||||||
|
|
||||||
|
const pathSet = processMap.get(exe);
|
||||||
|
|
||||||
|
if (pathSet) {
|
||||||
|
const executableName =
|
||||||
|
process.platform === "win32"
|
||||||
|
? executable.name.replace(/\//g, "\\")
|
||||||
|
: executable.name;
|
||||||
|
|
||||||
|
pathSet.forEach((path) => {
|
||||||
|
if (path.toLowerCase().endsWith(executableName)) {
|
||||||
|
gameRepository.update(
|
||||||
|
{ objectID: gameId, shop: "steam" },
|
||||||
|
{ executablePath: path }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (process.platform === "linux") {
|
||||||
|
exec(commands.findWineDir, (err, out) => {
|
||||||
|
if (err) return;
|
||||||
|
|
||||||
|
gameRepository.update(
|
||||||
|
{ objectID: gameId, shop: "steam" },
|
||||||
|
{
|
||||||
|
winePrefixPath: out.trim().replace("/drive_c/windows", ""),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSystemProcessMap = async () => {
|
||||||
|
const processes = await PythonInstance.getProcessList();
|
||||||
|
|
||||||
|
const map = new Map<string, Set<string>>();
|
||||||
|
|
||||||
|
processes.forEach((process) => {
|
||||||
|
const key = process.name.toLowerCase();
|
||||||
|
const value = process.exe;
|
||||||
|
|
||||||
|
if (!key || !value) return;
|
||||||
|
|
||||||
|
const currentSet = map.get(key) ?? new Set();
|
||||||
|
map.set(key, currentSet.add(value));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.platform === "linux") {
|
||||||
|
await new Promise((res) => {
|
||||||
|
exec(commands.findWineExecutables, (err, out) => {
|
||||||
|
if (err) {
|
||||||
|
res(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathSet = new Set(
|
||||||
|
out
|
||||||
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
.map((path) => path.trim())
|
||||||
|
);
|
||||||
|
|
||||||
|
pathSet.forEach((path) => {
|
||||||
|
if (path.startsWith("/usr")) return;
|
||||||
|
|
||||||
|
const key = path.slice(path.lastIndexOf("/") + 1).toLowerCase();
|
||||||
|
|
||||||
|
if (!key || !path) return;
|
||||||
|
|
||||||
|
const currentSet = map.get(key) ?? new Set();
|
||||||
|
map.set(key, currentSet.add(path));
|
||||||
|
});
|
||||||
|
|
||||||
|
res(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const watchProcesses = async () => {
|
||||||
|
const games = await gameRepository.find({
|
||||||
|
where: {
|
||||||
|
isDeleted: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!games.length) return;
|
||||||
|
|
||||||
|
const processMap = await getSystemProcessMap();
|
||||||
|
|
||||||
|
for (const game of games) {
|
||||||
|
const executablePath = game.executablePath;
|
||||||
|
|
||||||
|
if (!executablePath) {
|
||||||
|
if (gameExecutables[game.objectID]) {
|
||||||
|
findGamePathByProcess(processMap, game.objectID);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const executable = executablePath
|
||||||
|
.slice(
|
||||||
|
executablePath.lastIndexOf(process.platform === "win32" ? "\\" : "/") +
|
||||||
|
1
|
||||||
|
)
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
const hasProcess = processMap.get(executable)?.has(executablePath);
|
||||||
|
|
||||||
|
if (hasProcess) {
|
||||||
|
if (gamesPlaytime.has(game.id)) {
|
||||||
|
onTickGame(game);
|
||||||
|
} else {
|
||||||
|
onOpenGame(game);
|
||||||
|
}
|
||||||
|
} else if (gamesPlaytime.has(game.id)) {
|
||||||
|
onCloseGame(game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTick++;
|
||||||
|
|
||||||
|
if (WindowManager.mainWindow) {
|
||||||
|
const gamesRunning = Array.from(gamesPlaytime.entries()).map((entry) => {
|
||||||
|
return {
|
||||||
|
id: entry[0],
|
||||||
|
sessionDurationInMillis: performance.now() - entry[1].firstTick,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
WindowManager.mainWindow.webContents.send(
|
||||||
|
"on-games-running",
|
||||||
|
gamesRunning as Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function onOpenGame(game: Game) {
|
||||||
|
const now = performance.now();
|
||||||
|
|
||||||
|
gamesPlaytime.set(game.id, {
|
||||||
|
lastTick: now,
|
||||||
|
firstTick: now,
|
||||||
|
lastSyncTick: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (game.remoteId) {
|
||||||
|
updateGamePlaytime(game, 0, new Date()).catch(() => {});
|
||||||
|
} else {
|
||||||
|
createGame({ ...game, lastTimePlayed: new Date() }).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTickGame(game: Game) {
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
const gamePlaytime = gamesPlaytime.get(game.id)!;
|
const gamePlaytime = gamesPlaytime.get(game.id)!;
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ export const getUserData = () => {
|
|||||||
if (err instanceof UserNotLoggedInError) {
|
if (err instanceof UserNotLoggedInError) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
logger.error("Failed to get logged user", err);
|
logger.error("Failed to get logged user");
|
||||||
const loggedUser = await userAuthRepository.findOne({
|
const loggedUser = await userAuthRepository.findOne({
|
||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
relations: { subscription: true },
|
relations: { subscription: true },
|
||||||
|
@ -85,7 +85,11 @@ export class WindowManager {
|
|||||||
return callback(details);
|
return callback(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.url.includes("intercom.io")) {
|
if (details.url.includes("featurebase")) {
|
||||||
|
return callback(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.url.includes("chatwoot")) {
|
||||||
return callback(details);
|
return callback(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +195,7 @@ export class WindowManager {
|
|||||||
this.mainWindow?.focus();
|
this.mainWindow?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createSystemTray(language: string) {
|
public static async createSystemTray(language: string) {
|
||||||
let tray: Tray;
|
let tray: Tray;
|
||||||
|
|
||||||
if (process.platform === "darwin") {
|
if (process.platform === "darwin") {
|
||||||
@ -259,6 +263,7 @@ export class WindowManager {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
tray.setContextMenu(contextMenu);
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -270,6 +275,8 @@ export class WindowManager {
|
|||||||
tray.setToolTip("Hydra");
|
tray.setToolTip("Hydra");
|
||||||
|
|
||||||
if (process.platform !== "darwin") {
|
if (process.platform !== "darwin") {
|
||||||
|
await updateSystemTray();
|
||||||
|
|
||||||
tray.addListener("click", () => {
|
tray.addListener("click", () => {
|
||||||
if (this.mainWindow) {
|
if (this.mainWindow) {
|
||||||
if (
|
if (
|
||||||
|
1
src/main/vite-env.d.ts
vendored
1
src/main/vite-env.d.ts
vendored
@ -5,6 +5,7 @@ interface ImportMetaEnv {
|
|||||||
readonly MAIN_VITE_ANALYTICS_API_URL: string;
|
readonly MAIN_VITE_ANALYTICS_API_URL: string;
|
||||||
readonly MAIN_VITE_AUTH_URL: string;
|
readonly MAIN_VITE_AUTH_URL: string;
|
||||||
readonly MAIN_VITE_CHECKOUT_URL: string;
|
readonly MAIN_VITE_CHECKOUT_URL: string;
|
||||||
|
readonly MAIN_VITE_EXTERNAL_RESOURCES_URL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
@ -12,10 +12,10 @@ import type {
|
|||||||
FriendRequestAction,
|
FriendRequestAction,
|
||||||
UpdateProfileRequest,
|
UpdateProfileRequest,
|
||||||
SeedingStatus,
|
SeedingStatus,
|
||||||
|
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 */
|
||||||
@ -60,8 +60,8 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
getGameShopDetails: (objectId: string, shop: GameShop, language: string) =>
|
getGameShopDetails: (objectId: string, shop: GameShop, language: string) =>
|
||||||
ipcRenderer.invoke("getGameShopDetails", objectId, shop, language),
|
ipcRenderer.invoke("getGameShopDetails", objectId, shop, language),
|
||||||
getRandomGame: () => ipcRenderer.invoke("getRandomGame"),
|
getRandomGame: () => ipcRenderer.invoke("getRandomGame"),
|
||||||
getHowLongToBeat: (shop: GameShop, objectId: string) =>
|
getHowLongToBeat: (objectId: string, shop: GameShop) =>
|
||||||
ipcRenderer.invoke("getHowLongToBeat", shop, objectId),
|
ipcRenderer.invoke("getHowLongToBeat", objectId, shop),
|
||||||
getGames: (take?: number, skip?: number) =>
|
getGames: (take?: number, skip?: number) =>
|
||||||
ipcRenderer.invoke("getGames", take, skip),
|
ipcRenderer.invoke("getGames", take, skip),
|
||||||
searchGameRepacks: (query: string) =>
|
searchGameRepacks: (query: string) =>
|
||||||
@ -105,9 +105,9 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
ipcRenderer.invoke("addGameToLibrary", objectId, title, shop),
|
ipcRenderer.invoke("addGameToLibrary", objectId, title, shop),
|
||||||
createGameShortcut: (id: number) =>
|
createGameShortcut: (id: number) =>
|
||||||
ipcRenderer.invoke("createGameShortcut", id),
|
ipcRenderer.invoke("createGameShortcut", id),
|
||||||
updateExecutablePath: (id: number, executablePath: string) =>
|
updateExecutablePath: (id: number, executablePath: string | null) =>
|
||||||
ipcRenderer.invoke("updateExecutablePath", id, executablePath),
|
ipcRenderer.invoke("updateExecutablePath", id, executablePath),
|
||||||
selectGameWinePrefix: (id: number, winePrefixPath: string) =>
|
selectGameWinePrefix: (id: number, winePrefixPath: string | null) =>
|
||||||
ipcRenderer.invoke("selectGameWinePrefix", id, winePrefixPath),
|
ipcRenderer.invoke("selectGameWinePrefix", id, winePrefixPath),
|
||||||
verifyExecutablePathInUse: (executablePath: string) =>
|
verifyExecutablePathInUse: (executablePath: string) =>
|
||||||
ipcRenderer.invoke("verifyExecutablePathInUse", executablePath),
|
ipcRenderer.invoke("verifyExecutablePathInUse", executablePath),
|
||||||
@ -216,6 +216,7 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
ping: () => ipcRenderer.invoke("ping"),
|
ping: () => ipcRenderer.invoke("ping"),
|
||||||
getVersion: () => ipcRenderer.invoke("getVersion"),
|
getVersion: () => ipcRenderer.invoke("getVersion"),
|
||||||
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
|
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
|
||||||
|
isStaging: () => ipcRenderer.invoke("isStaging"),
|
||||||
isPortableVersion: () => ipcRenderer.invoke("isPortableVersion"),
|
isPortableVersion: () => ipcRenderer.invoke("isPortableVersion"),
|
||||||
openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
|
openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
|
||||||
openCheckout: () => ipcRenderer.invoke("openCheckout"),
|
openCheckout: () => ipcRenderer.invoke("openCheckout"),
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<title>Hydra</title>
|
<title>Hydra</title>
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src *; style-src 'self' 'unsafe-inline'; img-src 'self' data: local: *; media-src 'self' local: data: *; connect-src *; font-src *;"
|
content="default-src 'self' 'unsafe-inline' * data: local:;"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -2,8 +2,6 @@ import { useCallback, useContext, useEffect, useRef } from "react";
|
|||||||
|
|
||||||
import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components";
|
import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components";
|
||||||
|
|
||||||
import Intercom from "@intercom/messenger-js-sdk";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
@ -36,10 +34,6 @@ export interface AppProps {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intercom({
|
|
||||||
app_id: import.meta.env.RENDERER_VITE_INTERCOM_APP_ID,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
const { updateLibrary, library } = useLibrary();
|
const { updateLibrary, library } = useLibrary();
|
||||||
@ -128,12 +122,21 @@ export function App() {
|
|||||||
dispatch(setProfileBackground(profileBackground));
|
dispatch(setProfileBackground(profileBackground));
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchUserDetails().then((response) => {
|
fetchUserDetails()
|
||||||
if (response) {
|
.then((response) => {
|
||||||
updateUserDetails(response);
|
if (response) {
|
||||||
syncFriendRequests();
|
updateUserDetails(response);
|
||||||
}
|
syncFriendRequests();
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (document.getElementById("external-resources")) return;
|
||||||
|
|
||||||
|
const $script = document.createElement("script");
|
||||||
|
$script.id = "external-resources";
|
||||||
|
$script.src = `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}/bundle.js?t=${Date.now()}`;
|
||||||
|
document.head.appendChild($script);
|
||||||
|
});
|
||||||
}, [fetchUserDetails, syncFriendRequests, updateUserDetails, dispatch]);
|
}, [fetchUserDetails, syncFriendRequests, updateUserDetails, dispatch]);
|
||||||
|
|
||||||
const onSignIn = useCallback(() => {
|
const onSignIn = useCallback(() => {
|
||||||
@ -223,9 +226,7 @@ export function App() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
new MutationObserver(() => {
|
new MutationObserver(() => {
|
||||||
const modal = document.body.querySelector(
|
const modal = document.body.querySelector("[data-hydra-dialog]");
|
||||||
"[role=dialog]:not([data-intercom-frame='true'])"
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(toggleDraggingDisabled(Boolean(modal)));
|
dispatch(toggleDraggingDisabled(Boolean(modal)));
|
||||||
}).observe(document.body, {
|
}).observe(document.body, {
|
||||||
|
@ -107,6 +107,7 @@ export function Modal({
|
|||||||
aria-labelledby={title}
|
aria-labelledby={title}
|
||||||
aria-describedby={description}
|
aria-describedby={description}
|
||||||
ref={modalContentRef}
|
ref={modalContentRef}
|
||||||
|
data-hydra-dialog
|
||||||
>
|
>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<div style={{ display: "flex", gap: 4, flexDirection: "column" }}>
|
<div style={{ display: "flex", gap: 4, flexDirection: "column" }}>
|
||||||
|
@ -22,8 +22,6 @@ import { SidebarProfile } from "./sidebar-profile";
|
|||||||
import { sortBy } from "lodash-es";
|
import { sortBy } from "lodash-es";
|
||||||
import { CommentDiscussionIcon } from "@primer/octicons-react";
|
import { CommentDiscussionIcon } from "@primer/octicons-react";
|
||||||
|
|
||||||
import { show, update } from "@intercom/messenger-js-sdk";
|
|
||||||
|
|
||||||
const SIDEBAR_MIN_WIDTH = 200;
|
const SIDEBAR_MIN_WIDTH = 200;
|
||||||
const SIDEBAR_INITIAL_WIDTH = 250;
|
const SIDEBAR_INITIAL_WIDTH = 250;
|
||||||
const SIDEBAR_MAX_WIDTH = 450;
|
const SIDEBAR_MAX_WIDTH = 450;
|
||||||
@ -50,20 +48,7 @@ export function Sidebar() {
|
|||||||
return sortBy(library, (game) => game.title);
|
return sortBy(library, (game) => game.title);
|
||||||
}, [library]);
|
}, [library]);
|
||||||
|
|
||||||
const { userDetails, hasActiveSubscription } = useUserDetails();
|
const { hasActiveSubscription } = useUserDetails();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (userDetails) {
|
|
||||||
update({
|
|
||||||
name: userDetails.displayName,
|
|
||||||
Username: userDetails.username,
|
|
||||||
email: userDetails.email ?? undefined,
|
|
||||||
Email: userDetails.email,
|
|
||||||
"Subscription expiration date": userDetails?.subscription?.expiresAt,
|
|
||||||
"Payment status": userDetails?.subscription?.status,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [userDetails, hasActiveSubscription]);
|
|
||||||
|
|
||||||
const { lastPacket, progress } = useDownload();
|
const { lastPacket, progress } = useDownload();
|
||||||
|
|
||||||
@ -266,7 +251,11 @@ export function Sidebar() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasActiveSubscription && (
|
{hasActiveSubscription && (
|
||||||
<button type="button" className={styles.helpButton} onClick={show}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.helpButton}
|
||||||
|
data-open-support-chat
|
||||||
|
>
|
||||||
<div className={styles.helpButtonIcon}>
|
<div className={styles.helpButtonIcon}>
|
||||||
<CommentDiscussionIcon size={14} />
|
<CommentDiscussionIcon size={14} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -181,6 +181,7 @@ export function GameDetailsContextProvider({
|
|||||||
shop,
|
shop,
|
||||||
i18n.language,
|
i18n.language,
|
||||||
userDetails,
|
userDetails,
|
||||||
|
userPreferences,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
46
src/renderer/src/cookies.ts
Normal file
46
src/renderer/src/cookies.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
export function addCookieInterceptor(isStaging: boolean) {
|
||||||
|
const cookieKey = isStaging ? "cookies-staging" : "cookies";
|
||||||
|
|
||||||
|
Object.defineProperty(document, "cookie", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
return localStorage.getItem(cookieKey) || "";
|
||||||
|
},
|
||||||
|
set(cookieString) {
|
||||||
|
try {
|
||||||
|
const [cookieName, cookieValue] = cookieString.split(";")[0].split("=");
|
||||||
|
|
||||||
|
const currentCookies = localStorage.getItem(cookieKey) || "";
|
||||||
|
|
||||||
|
const cookiesObject = parseCookieStringsToObjects(currentCookies);
|
||||||
|
cookiesObject[cookieName] = cookieValue;
|
||||||
|
|
||||||
|
const newString = Object.entries(cookiesObject)
|
||||||
|
.map(([key, value]) => {
|
||||||
|
return key + "=" + value;
|
||||||
|
})
|
||||||
|
.join("; ");
|
||||||
|
|
||||||
|
localStorage.setItem(cookieKey, newString);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseCookieStringsToObjects = (
|
||||||
|
cookieStrings: string
|
||||||
|
): { [key: string]: string } => {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
if (cookieStrings === "") return result;
|
||||||
|
|
||||||
|
cookieStrings.split(";").forEach((cookieString) => {
|
||||||
|
const [name, value] = cookieString.split("=");
|
||||||
|
result[name.trim()] = value.trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
15
src/renderer/src/declaration.d.ts
vendored
15
src/renderer/src/declaration.d.ts
vendored
@ -67,8 +67,8 @@ declare global {
|
|||||||
) => Promise<ShopDetails | null>;
|
) => Promise<ShopDetails | null>;
|
||||||
getRandomGame: () => Promise<Steam250Game>;
|
getRandomGame: () => Promise<Steam250Game>;
|
||||||
getHowLongToBeat: (
|
getHowLongToBeat: (
|
||||||
shop: GameShop,
|
objectId: string,
|
||||||
objectId: string
|
shop: GameShop
|
||||||
) => Promise<HowLongToBeatCategory[] | null>;
|
) => Promise<HowLongToBeatCategory[] | null>;
|
||||||
getGames: (take?: number, skip?: number) => Promise<CatalogueEntry[]>;
|
getGames: (take?: number, skip?: number) => Promise<CatalogueEntry[]>;
|
||||||
searchGameRepacks: (query: string) => Promise<GameRepack[]>;
|
searchGameRepacks: (query: string) => Promise<GameRepack[]>;
|
||||||
@ -87,8 +87,14 @@ declare global {
|
|||||||
shop: GameShop
|
shop: GameShop
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
createGameShortcut: (id: number) => Promise<boolean>;
|
createGameShortcut: (id: number) => Promise<boolean>;
|
||||||
updateExecutablePath: (id: number, executablePath: string) => Promise<void>;
|
updateExecutablePath: (
|
||||||
selectGameWinePrefix: (id: number, winePrefixPath: string) => Promise<void>;
|
id: number,
|
||||||
|
executablePath: string | null
|
||||||
|
) => Promise<void>;
|
||||||
|
selectGameWinePrefix: (
|
||||||
|
id: number,
|
||||||
|
winePrefixPath: string | null
|
||||||
|
) => Promise<void>;
|
||||||
verifyExecutablePathInUse: (executablePath: string) => Promise<Game>;
|
verifyExecutablePathInUse: (executablePath: string) => Promise<Game>;
|
||||||
getLibrary: () => Promise<LibraryGame[]>;
|
getLibrary: () => Promise<LibraryGame[]>;
|
||||||
openGameInstaller: (gameId: number) => Promise<boolean>;
|
openGameInstaller: (gameId: number) => Promise<boolean>;
|
||||||
@ -170,6 +176,7 @@ declare global {
|
|||||||
openExternal: (src: string) => Promise<void>;
|
openExternal: (src: string) => Promise<void>;
|
||||||
openCheckout: () => Promise<void>;
|
openCheckout: () => Promise<void>;
|
||||||
getVersion: () => Promise<string>;
|
getVersion: () => Promise<string>;
|
||||||
|
isStaging: () => Promise<boolean>;
|
||||||
ping: () => string;
|
ping: () => string;
|
||||||
getDefaultDownloadsPath: () => Promise<string>;
|
getDefaultDownloadsPath: () => Promise<string>;
|
||||||
isPortableVersion: () => Promise<boolean>;
|
isPortableVersion: () => Promise<boolean>;
|
||||||
|
@ -56,6 +56,8 @@ export function useUserDetails() {
|
|||||||
clearUserDetails();
|
clearUserDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window["userDetails"] = userDetails;
|
||||||
|
|
||||||
return userDetails;
|
return userDetails;
|
||||||
});
|
});
|
||||||
}, [clearUserDetails]);
|
}, [clearUserDetails]);
|
||||||
|
@ -20,6 +20,8 @@ import resources from "@locales";
|
|||||||
|
|
||||||
import { RepacksContextProvider } from "./context";
|
import { RepacksContextProvider } from "./context";
|
||||||
import { SuspenseWrapper } from "./components";
|
import { SuspenseWrapper } from "./components";
|
||||||
|
import { logger } from "./logger";
|
||||||
|
import { addCookieInterceptor } from "./cookies";
|
||||||
|
|
||||||
const Home = React.lazy(() => import("./pages/home/home"));
|
const Home = React.lazy(() => import("./pages/home/home"));
|
||||||
const GameDetails = React.lazy(
|
const GameDetails = React.lazy(
|
||||||
@ -34,6 +36,11 @@ const Achievements = React.lazy(
|
|||||||
() => import("./pages/achievements/achievements")
|
() => import("./pages/achievements/achievements")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log = logger.log;
|
||||||
|
|
||||||
|
const isStaging = await window.electron.isStaging();
|
||||||
|
addCookieInterceptor(isStaging);
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
|
@ -95,6 +95,11 @@ export function GameOptionsModal({
|
|||||||
await window.electron.openGameExecutablePath(game.id);
|
await window.electron.openGameExecutablePath(game.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClearExecutablePath = async () => {
|
||||||
|
await window.electron.updateExecutablePath(game.id, null);
|
||||||
|
updateGame();
|
||||||
|
};
|
||||||
|
|
||||||
const handleChangeWinePrefixPath = async () => {
|
const handleChangeWinePrefixPath = async () => {
|
||||||
const { filePaths } = await window.electron.showOpenDialog({
|
const { filePaths } = await window.electron.showOpenDialog({
|
||||||
properties: ["openDirectory"],
|
properties: ["openDirectory"],
|
||||||
@ -106,6 +111,11 @@ export function GameOptionsModal({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClearWinePrefixPath = async () => {
|
||||||
|
await window.electron.selectGameWinePrefix(game.id, null);
|
||||||
|
updateGame();
|
||||||
|
};
|
||||||
|
|
||||||
const shouldShowWinePrefixConfiguration =
|
const shouldShowWinePrefixConfiguration =
|
||||||
window.electron.platform === "linux";
|
window.electron.platform === "linux";
|
||||||
|
|
||||||
@ -145,14 +155,21 @@ export function GameOptionsModal({
|
|||||||
disabled
|
disabled
|
||||||
placeholder={t("no_executable_selected")}
|
placeholder={t("no_executable_selected")}
|
||||||
rightContent={
|
rightContent={
|
||||||
<Button
|
<>
|
||||||
type="button"
|
<Button
|
||||||
theme="outline"
|
type="button"
|
||||||
onClick={handleChangeExecutableLocation}
|
theme="outline"
|
||||||
>
|
onClick={handleChangeExecutableLocation}
|
||||||
<FileIcon />
|
>
|
||||||
{t("select_executable")}
|
<FileIcon />
|
||||||
</Button>
|
{t("select_executable")}
|
||||||
|
</Button>
|
||||||
|
{game.executablePath && (
|
||||||
|
<Button onClick={handleClearExecutablePath} theme="outline">
|
||||||
|
{t("clear")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -186,14 +203,24 @@ export function GameOptionsModal({
|
|||||||
disabled
|
disabled
|
||||||
placeholder={t("no_directory_selected")}
|
placeholder={t("no_directory_selected")}
|
||||||
rightContent={
|
rightContent={
|
||||||
<Button
|
<>
|
||||||
type="button"
|
<Button
|
||||||
theme="outline"
|
type="button"
|
||||||
onClick={handleChangeWinePrefixPath}
|
theme="outline"
|
||||||
>
|
onClick={handleChangeWinePrefixPath}
|
||||||
<FileDirectoryIcon />
|
>
|
||||||
{t("select_executable")}
|
<FileDirectoryIcon />
|
||||||
</Button>
|
{t("select_executable")}
|
||||||
|
</Button>
|
||||||
|
{game.winePrefixPath && (
|
||||||
|
<Button
|
||||||
|
onClick={handleClearWinePrefixPath}
|
||||||
|
theme="outline"
|
||||||
|
>
|
||||||
|
{t("clear")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -98,8 +98,8 @@ export function Sidebar() {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const howLongToBeat = await window.electron.getHowLongToBeat(
|
const howLongToBeat = await window.electron.getHowLongToBeat(
|
||||||
shop,
|
objectId,
|
||||||
objectId
|
shop
|
||||||
);
|
);
|
||||||
|
|
||||||
if (howLongToBeat) {
|
if (howLongToBeat) {
|
||||||
|
@ -20,6 +20,7 @@ export function SettingsBehavior() {
|
|||||||
startMinimized: false,
|
startMinimized: false,
|
||||||
disableNsfwAlert: false,
|
disableNsfwAlert: false,
|
||||||
seedAfterDownloadComplete: false,
|
seedAfterDownloadComplete: false,
|
||||||
|
showHiddenAchievementsDescription: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
@ -32,6 +33,8 @@ export function SettingsBehavior() {
|
|||||||
startMinimized: userPreferences.startMinimized,
|
startMinimized: userPreferences.startMinimized,
|
||||||
disableNsfwAlert: userPreferences.disableNsfwAlert,
|
disableNsfwAlert: userPreferences.disableNsfwAlert,
|
||||||
seedAfterDownloadComplete: userPreferences.seedAfterDownloadComplete,
|
seedAfterDownloadComplete: userPreferences.seedAfterDownloadComplete,
|
||||||
|
showHiddenAchievementsDescription:
|
||||||
|
userPreferences.showHiddenAchievementsDescription,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [userPreferences]);
|
}, [userPreferences]);
|
||||||
@ -108,6 +111,17 @@ export function SettingsBehavior() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CheckboxField
|
||||||
|
label={t("show_hidden_achievement_description")}
|
||||||
|
checked={form.showHiddenAchievementsDescription}
|
||||||
|
onChange={() =>
|
||||||
|
handleChange({
|
||||||
|
showHiddenAchievementsDescription:
|
||||||
|
!form.showHiddenAchievementsDescription,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
2
src/renderer/src/vite-env.d.ts
vendored
2
src/renderer/src/vite-env.d.ts
vendored
@ -2,7 +2,7 @@
|
|||||||
/// <reference types="vite-plugin-svgr/client" />
|
/// <reference types="vite-plugin-svgr/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly RENDERER_VITE_INTERCOM_APP_ID: string;
|
readonly RENDERER_VITE_EXTERNAL_RESOURCES_URL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
@ -46,7 +46,7 @@ export const removeSymbolsFromName = (name: string) =>
|
|||||||
|
|
||||||
export const removeSpecialEditionFromName = (name: string) =>
|
export const removeSpecialEditionFromName = (name: string) =>
|
||||||
name.replace(
|
name.replace(
|
||||||
/(The |Digital )?(GOTY|Deluxe|Standard|Ultimate|Definitive|Enhanced|Collector's|Premium|Digital|Limited|Game of the Year|Reloaded|[0-9]{4}) Edition/g,
|
/(The |Digital )?(GOTY|Deluxe|Standard|Ultimate|Definitive|Enhanced|Collector's|Premium|Digital|Limited|Game of the Year|Reloaded|[0-9]{4}) Edition/gi,
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -73,7 +73,8 @@ export const formatName = pipe<string>(
|
|||||||
replaceUnderscoreWithSpace,
|
replaceUnderscoreWithSpace,
|
||||||
replaceDotsWithSpace,
|
replaceDotsWithSpace,
|
||||||
replaceNbspWithSpace,
|
replaceNbspWithSpace,
|
||||||
(str) => str.replace(/DIRECTOR'S CUT/g, ""),
|
(str) => str.replace(/DIRECTOR'S CUT/gi, ""),
|
||||||
|
(str) => str.replace(/Friend's Pass/gi, ""),
|
||||||
removeSymbolsFromName,
|
removeSymbolsFromName,
|
||||||
removeDuplicateSpaces,
|
removeDuplicateSpaces,
|
||||||
(str) => str.trim()
|
(str) => str.trim()
|
||||||
|
@ -171,6 +171,7 @@ export interface UserPreferences {
|
|||||||
startMinimized: boolean;
|
startMinimized: boolean;
|
||||||
disableNsfwAlert: boolean;
|
disableNsfwAlert: boolean;
|
||||||
seedAfterDownloadComplete: boolean;
|
seedAfterDownloadComplete: boolean;
|
||||||
|
showHiddenAchievementsDescription: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Steam250Game {
|
export interface Steam250Game {
|
||||||
@ -250,6 +251,7 @@ export interface Subscription {
|
|||||||
status: SubscriptionStatus;
|
status: SubscriptionStatus;
|
||||||
plan: { id: string; name: string };
|
plan: { id: string; name: string };
|
||||||
expiresAt: string | null;
|
expiresAt: string | null;
|
||||||
|
paymentMethod: "pix" | "paypal";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserDetails {
|
export interface UserDetails {
|
||||||
|
Loading…
Reference in New Issue
Block a user