mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 16:53:47 +03:00
Merge branch 'main' into linux-install
This commit is contained in:
commit
ee17b5106c
@ -1,5 +1,4 @@
|
|||||||
MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY
|
MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME=YOUR_USERNAME
|
MAIN_VITE_ONLINEFIX_USERNAME=YOUR_USERNAME
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD=YOUR_PASSWORD
|
MAIN_VITE_ONLINEFIX_PASSWORD=YOUR_PASSWORD
|
||||||
MAIN_VITE_SENTRY_DSN=YOUR_SENTRY_DSN
|
|
||||||
RENDERER_VITE_SENTRY_DSN=YOUR_SENTRY_DSN
|
|
||||||
|
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve. Write in English, please
|
|
||||||
title: "[BUG]"
|
|
||||||
labels: bug
|
|
||||||
assignees: ""
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
|
|
||||||
- OS: [Windows 11/Linux Distro/Steam Deck]
|
|
||||||
- Hydra Version:
|
|
||||||
- Additional information and context of your problem:
|
|
58
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
58
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Create a report to help us improve. Write in English, please.
|
||||||
|
title: "[BUG] "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for creating a bug report to help us improve!
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: bug-reproduce
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce
|
||||||
|
description: Steps to reproduce the behavior. For example, "1. Go to '...', 2. Click on '...', 3. See error"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: A clear and concise description of what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: screenshots
|
||||||
|
attributes:
|
||||||
|
label: Screenshots
|
||||||
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: OS
|
||||||
|
attributes:
|
||||||
|
label: Operating System
|
||||||
|
description: Which operating system are you using (e.g., Windows 11/Linux Distro/Steam Deck)?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: hydra-version
|
||||||
|
attributes:
|
||||||
|
label: Hydra Version
|
||||||
|
description: Please provide the version of Hydra you are using.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: additional-info
|
||||||
|
attributes:
|
||||||
|
label: Additional Information
|
||||||
|
description: Please provide any additional information and context about your problem.
|
||||||
|
validations:
|
||||||
|
required: false
|
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for Hydra. Write in English, please
|
|
||||||
title: "[REQUEST]"
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ""
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is.
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Request a new feature.
|
||||||
|
title: "[REQUEST] "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for taking the time to suggest a new feature!
|
||||||
|
- type: textarea
|
||||||
|
id: problem-related
|
||||||
|
attributes:
|
||||||
|
label: Is your feature request related to a problem? Please describe.
|
||||||
|
description: A clear and concise description of what the problem is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Describe the solution you'd like
|
||||||
|
description: A clear and concise description of what you want to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Describe alternatives you've considered
|
||||||
|
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context or screenshots about the feature request here.
|
||||||
|
validations:
|
||||||
|
required: false
|
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@ -1,8 +1,6 @@
|
|||||||
name: Build
|
name: Build
|
||||||
|
|
||||||
on:
|
on: [pull_request]
|
||||||
push:
|
|
||||||
branches: main
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@ -39,10 +37,6 @@ jobs:
|
|||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: yarn build:linux
|
run: yarn build:linux
|
||||||
env:
|
env:
|
||||||
MAIN_VITE_STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
|
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
MAIN_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
|
||||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@ -51,10 +45,6 @@ jobs:
|
|||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
run: yarn build:win
|
run: yarn build:win
|
||||||
env:
|
env:
|
||||||
MAIN_VITE_STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
|
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
MAIN_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
|
||||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@ -64,7 +54,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: Build-${{ matrix.os }}
|
name: Build-${{ matrix.os }}
|
||||||
path: |
|
path: |
|
||||||
dist/*.exe
|
dist/win-unpacked/**
|
||||||
dist/*.zip
|
dist/*.zip
|
||||||
dist/*.dmg
|
dist/*.dmg
|
||||||
dist/*.deb
|
dist/*.deb
|
||||||
@ -72,11 +62,3 @@ jobs:
|
|||||||
dist/*.tar.gz
|
dist/*.tar.gz
|
||||||
dist/*.yml
|
dist/*.yml
|
||||||
dist/*.blockmap
|
dist/*.blockmap
|
||||||
|
|
||||||
- name: VirusTotal Scan
|
|
||||||
uses: crazy-max/ghaction-virustotal@v4
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
with:
|
|
||||||
vt_api_key: ${{ secrets.VT_API_KEY }}
|
|
||||||
files: |
|
|
||||||
./dist/*.exe
|
|
||||||
|
3
.github/workflows/lint.yml
vendored
3
.github/workflows/lint.yml
vendored
@ -26,3 +26,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: yarn lint
|
run: yarn lint
|
||||||
|
|
||||||
|
- name: Format check
|
||||||
|
run: yarn format-check
|
||||||
|
70
.github/workflows/release.yml
vendored
Normal file
70
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [windows-latest, ubuntu-latest]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20.11.1
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn
|
||||||
|
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Build with cx_Freeze
|
||||||
|
run: python torrent-client/setup.py build
|
||||||
|
|
||||||
|
- name: Build Linux
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
run: yarn build:linux
|
||||||
|
env:
|
||||||
|
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||||
|
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Windows
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: yarn build:win
|
||||||
|
env:
|
||||||
|
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||||
|
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
files: |
|
||||||
|
dist/*.exe
|
||||||
|
dist/*.zip
|
||||||
|
dist/*.dmg
|
||||||
|
dist/*.AppImage
|
||||||
|
dist/*.snap
|
||||||
|
dist/*.deb
|
||||||
|
dist/*.rpm
|
||||||
|
dist/*.tar.gz
|
||||||
|
dist/*.yml
|
||||||
|
dist/*.blockmap
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,5 +10,3 @@ out
|
|||||||
.env
|
.env
|
||||||
.vite
|
.vite
|
||||||
|
|
||||||
# Sentry Config File
|
|
||||||
.env.sentry-build-plugin
|
|
||||||
|
@ -1,2 +1 @@
|
|||||||
yarn lint
|
yarn format
|
||||||
yarn typecheck
|
|
||||||
|
@ -1 +1,2 @@
|
|||||||
yarn format
|
yarn lint
|
||||||
|
yarn typecheck
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
out
|
out
|
||||||
dist
|
dist
|
||||||
|
seeds
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
LICENSE.md
|
LICENSE.md
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
|
@ -119,7 +119,7 @@ yarn
|
|||||||
|
|
||||||
### Усталёўка Python 3.9
|
### Усталёўка Python 3.9
|
||||||
|
|
||||||
Упэўніцеся, што ў вас усталяваны Python 3.9 на вашым кампутары. Вы можаце загрузіць і ўсталяваць яго з [python.org](https://www.python.org/downloads/release/python-3919/).
|
Упэўніцеся, што ў вас усталяваны Python 3.9 на вашым кампутары. Вы можаце загрузіць і ўсталяваць яго з [python.org](https://www.python.org/downloads/release/python-3913/).
|
||||||
|
|
||||||
### Усталёўка залежнасцяў Python
|
### Усталёўка залежнасцяў Python
|
||||||
|
|
||||||
|
182
README.es.md
Normal file
182
README.es.md
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
<br>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site)
|
||||||
|
|
||||||
|
<h1 align="center">Hydra Launcher</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Hydra es un launcher de juegos con su propio cliente de bittorrent y gestor propio de repacks.</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
||||||
|
[![release](https://img.shields.io/github/package-json/v/hydralauncher/hydra)](https://github.com/hydralauncher/hydra/releases)
|
||||||
|
|
||||||
|
[![be](https://img.shields.io/badge/lang-be-orange)](README.be.md)
|
||||||
|
[![pl](https://img.shields.io/badge/lang-pl-white)](README.pl.md)
|
||||||
|
[![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.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)
|
||||||
|
[![es](https://img.shields.io/badge/lang-es-red)](README.es.md)
|
||||||
|
|
||||||
|
![Hydra Catalogue](./docs/screenshot.png)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Tabla de Contenidos
|
||||||
|
|
||||||
|
- [Acerca de](#acerca-de)
|
||||||
|
- [Características](#caracteristicas)
|
||||||
|
- [Instalación](#Instalacion)
|
||||||
|
- [Contribuir](#contribuir)
|
||||||
|
- [Únete a nuestro Telegram](#unete-a-nuestro-telegram)
|
||||||
|
- [Haz un fork y clona tu repositorio](#haz-un-fork-y-clona-tu-repositorio)
|
||||||
|
- [Maneras en las que puedes contribuir](#maneras-en-las-que-puedes-contribuir)
|
||||||
|
- [Estructura del proyecto](#estructura-del-proyecto)
|
||||||
|
- [Compilar desde el código fuente](#compilar-desde-el-código-fuente)
|
||||||
|
- [Instalar Node.js](#instalar-nodejs)
|
||||||
|
- [Instalar Yarn](#instalar-yarn)
|
||||||
|
- [Instalar Dependencias de Node](#instalar-dependencias-de-node)
|
||||||
|
- [Instalar Python 3.9](#instalar-python-39)
|
||||||
|
- [Instalar Dependencias de Python](#Instalar-dependencias-de-python)
|
||||||
|
- [Variables del Entorno](#variables-del-entorno)
|
||||||
|
- [Ejecución](#ejecucion)
|
||||||
|
- [Compilación](#compilacion)
|
||||||
|
- [Compilar el cliente de bittorrent](#compilar-el-cliente-de-bittorrent)
|
||||||
|
- [Compilar la aplicación Electron](#compilar-la-aplicacion-electron)
|
||||||
|
- [Colaboradores](#colaboradores)
|
||||||
|
|
||||||
|
## Acerca de
|
||||||
|
|
||||||
|
**Hydra** es un **Launcher de Juegos** con su propio **Cliente Bittorrent** y **autogestor de Repacks**.
|
||||||
|
<br>
|
||||||
|
El launcher está escrito en TypeScript (Electron) y Python, el cuál se encarga del sistema de torrent usando libtorrent.
|
||||||
|
|
||||||
|
## Caracteristicas
|
||||||
|
|
||||||
|
- Buscador e instalador autogestionado de repacks a través de las páginas más confiables en él [Megahilo](https://www.reddit.com/r/Piracy/wiki/megathread/)
|
||||||
|
- Cliente propio de bittorrent integrado
|
||||||
|
- Integración de How Long To Beat (HLTB) en la página del juego
|
||||||
|
- Customización de rutas de descargas
|
||||||
|
- Notificaciones en actualizaciones a listas de repacks
|
||||||
|
- Soporte a Windows y Linux
|
||||||
|
- En constante actualización
|
||||||
|
- Y mucho más ...
|
||||||
|
|
||||||
|
## Instalacion
|
||||||
|
|
||||||
|
Sigue los pasos de abajo para instalar:
|
||||||
|
|
||||||
|
1. Descarga la última versión de Hydra desde la página de [Releases](https://github.com/hydralauncher/hydra/releases/latest).
|
||||||
|
- Descarga solo el .exe si quieres instalar Hydra en Windows.
|
||||||
|
- Descarga el .deb o .rpm o .zip si quieres instalar Hydra en Linux. (Depende de tu distro de Linux)
|
||||||
|
2. Ejecuta el archivo descargado.
|
||||||
|
3. ¡Disfruta de Hydra!
|
||||||
|
|
||||||
|
## <a name="contribuir"> Contribuir
|
||||||
|
|
||||||
|
### <a name="unete-a-nuestro-telegram"></a> Unete a nuestro Telegram
|
||||||
|
|
||||||
|
Puedes unirte a nuestra conversación y discusiones en nuestro canal de [Telegram](https://t.me/hydralauncher).
|
||||||
|
|
||||||
|
### Haz un fork y clona tu repositorio
|
||||||
|
|
||||||
|
1. Rea;iza un fork del repositorio [(Haz click acá para hacer un fork ahora)](https://github.com/hydralauncher/hydra/fork)
|
||||||
|
2. Clona el código forkeado `git clone https://github.com/tu_nombredeusuario/hydra`
|
||||||
|
3. Crea una nueva rama
|
||||||
|
4. Sube tus commits
|
||||||
|
5. Envía nuevas solicitudes de pull
|
||||||
|
|
||||||
|
### Maneras en las que puedes contribuir
|
||||||
|
|
||||||
|
- Traducción: Queremos que Hydra esté disponible para todas las personas que sean posible. Siéntete libre de ayudarnos a traducirlo a nuevos lenguajes o actualizar y mejorar las ya disponibles en Hydra.
|
||||||
|
- Código: Hydra está hecho con Typescript, Electron y un poquito de Python. Si quieres contribuir, ¡únete a nuestro [Telegram](https://t.me/hydralauncher)!
|
||||||
|
|
||||||
|
### Estructura del proyecto
|
||||||
|
|
||||||
|
- torrent-client: Usamos libtorrent, una librería de Python que se encarga de manejar las descargas torrent
|
||||||
|
- src/renderer: El UI de la aplicación
|
||||||
|
- src/main: El resto de la lógica va acá.
|
||||||
|
|
||||||
|
## Compilar desde el código fuente
|
||||||
|
|
||||||
|
### Instalar Node.js
|
||||||
|
|
||||||
|
Asegúrate que tienes Node.js instalado en tú máquina. Si no es así, puedes descargarlo e instalarlo desde [nodejs.org](https://nodejs.org/).
|
||||||
|
|
||||||
|
### Instalar Yarn
|
||||||
|
|
||||||
|
Yarn es un gestor de paquetes para Node.js. Si no tienes aún instalado Yarn todavía, puedes hacerlo siguiendo las instrucciones en [yarnpkg.com](https://classic.yarnpkg.com/lang/en/docs/install/).
|
||||||
|
|
||||||
|
### Instalar Dependencias de Node
|
||||||
|
|
||||||
|
Dirígete hasta el directorio del proyecto e instala las dependencias de Node usando Yarn:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd hydra
|
||||||
|
yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
### Instalar Python 3.9
|
||||||
|
|
||||||
|
Asegúrate que tienes Python 3.9 instalado en tu máquina. Puedes descargarlo e instalarlo desde [python.org](https://www.python.org/downloads/release/python-3913/).
|
||||||
|
|
||||||
|
### Instalar Dependencias de Python
|
||||||
|
|
||||||
|
Instala las dependencias de Python requeridas usando pip:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variables del Entorno
|
||||||
|
|
||||||
|
Necesitas una llave API de SteamGridDB para así poder obtener los íconos de los juegos en la instalación.
|
||||||
|
Si quieres también tener los repacks de onlinefix, necesitarás añadir tus credenciales al .env
|
||||||
|
|
||||||
|
Una vez que los tengas, puedes copiar o renombrar el archivo `.env.example` cómo `.env` y colocarlo en `STEAMGRIDDB_API_KEY`, `ONLINEFIX_USERNAME`, `ONLINEFIX_PASSWORD`.
|
||||||
|
|
||||||
|
## Ejecucion
|
||||||
|
|
||||||
|
Una vez que tengas todas las cosas listas, puedes ejecutar el siguiente comando para así iniciar el proceso de Electron y el cliente de bittorrent:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compilacion
|
||||||
|
|
||||||
|
### Compilar el cliente de bittorrent
|
||||||
|
|
||||||
|
Crea el cliente bittorrent usando este comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python torrent-client/setup.py build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compilar la aplicacion Electron
|
||||||
|
|
||||||
|
Crea la aplicación de Electron usando este comando:
|
||||||
|
|
||||||
|
En Windows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build:win
|
||||||
|
```
|
||||||
|
|
||||||
|
En Linux:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build:linux
|
||||||
|
```
|
||||||
|
|
||||||
|
## Colaboradores
|
||||||
|
|
||||||
|
<a href="https://github.com/hydralauncher/hydra/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=hydralauncher/hydra" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## Licencia
|
||||||
|
|
||||||
|
Hydra está licenciado bajo la [MIT License](LICENSE).
|
@ -13,11 +13,11 @@
|
|||||||
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
||||||
[![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)
|
||||||
|
|
||||||
|
[![be](https://img.shields.io/badge/lang-be-orange)](README.be.md)
|
||||||
|
[![pl](https://img.shields.io/badge/lang-pl-white)](README.pl.md)
|
||||||
[![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)
|
|
||||||
[![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)
|
|
||||||
|
|
||||||
![Hydra Catalogue](./docs/screenshot.png)
|
![Hydra Catalogue](./docs/screenshot.png)
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ yarn
|
|||||||
|
|
||||||
### 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-3919/).
|
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/).
|
||||||
|
|
||||||
### Install Python Dependencies
|
### Install Python Dependencies
|
||||||
|
|
||||||
|
185
README.pl.md
Normal file
185
README.pl.md
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<br>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site)
|
||||||
|
|
||||||
|
<h1 align="center">Hydra Launcher</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Hydra - to program uruchamiający gry z własnym wbudowanym klientem bittorrent i samodzielnie zarządzanym repackagerem..</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
||||||
|
[![release](https://img.shields.io/github/package-json/v/hydralauncher/hydra)](https://github.com/hydralauncher/hydra/releases)
|
||||||
|
|
||||||
|
[![be](https://img.shields.io/badge/lang-be-orange)](README.be.md)
|
||||||
|
[![en](https://img.shields.io/badge/lang-en-red.svg)](README.md)
|
||||||
|
[![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.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)
|
||||||
|
|
||||||
|
![Hydra Catalogue](./docs/screenshot.png)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Zawartość.
|
||||||
|
|
||||||
|
- [O nas](#o-nas)
|
||||||
|
- [Cechy.](#cechy)
|
||||||
|
- [Instalacja](#instalacja)
|
||||||
|
- [Dokonaj wpłaty](#dokonaj-wpłaty)
|
||||||
|
- [Dołącz do naszego kanału Telegram](#dołącz-do-naszego-kanału-telegram)
|
||||||
|
- [Rozwidlenie i sklonowanie repozytorium](#rozwidlenie-i-sklonowanie-repozytorium)
|
||||||
|
- [Jak możesz wnieść swój wkład](#jak-możesz-pomóc)
|
||||||
|
- [Struktura projektu](#struktura-projektu)
|
||||||
|
- [Utwórz kompilację z kodu źródłowego](#utwórz-kompilację-z-kodu-źródłowego)
|
||||||
|
- [Instalacja Node.js](#zainstaluj-nodejs)
|
||||||
|
- [Instalacja Yarn](#zainstaluj-yarn)
|
||||||
|
- [Instalacja Node zależności](#zainstaluj-zależności-node)
|
||||||
|
- [Instalacja Python 3.9](#zainstaluj-python-39)
|
||||||
|
- [Instalacja Python zależności](#zainstaluj-zależności-pythona)
|
||||||
|
- [Zmienne środowiskowe](#zmienne-środowiskowe)
|
||||||
|
- [Uruchomienie](#utwórz-kompilację-z-kodu-źródłowego)
|
||||||
|
- [Tworzenie kompilacji](#tworzenie-kompilacji)
|
||||||
|
- [Tworzenie klienta bittorrent](#zbuduj-klienta-bittorrent)
|
||||||
|
- [Tworzenie kompilacji aplikacji Electron](#tworzenie-aplikacji-electron)
|
||||||
|
- [Współtwórcy](#współtwórcy)
|
||||||
|
|
||||||
|
## O nas
|
||||||
|
|
||||||
|
**Hydra** - jest **programem uruchamiającym gry** z wbudowanym **klientem BitTorrent** i **samozarządzającym się repackagerem**.
|
||||||
|
<br>
|
||||||
|
Ten launcher jest napisany w TypeScript (Electron) i Pythonie, który współpracuje z systemem torrent przy użyciu libtorrent.
|
||||||
|
|
||||||
|
## Cechy
|
||||||
|
|
||||||
|
- Samodzielnie zarządzany repackager wśród wszystkich najbardziej zaufanych stron na [Megathread]("https://www.reddit.com/r/Piracy/wiki/megathread/").
|
||||||
|
- Własny wbudowany klient bittorrent
|
||||||
|
- Integracja funkcji How Long To Beat (HLTB) na stronie gry
|
||||||
|
- Personalizacja folderu pobierania
|
||||||
|
- Powiadomienia o aktualizacjach listy repacków
|
||||||
|
- Wsparcie dla systemów Windows i Linux
|
||||||
|
- Stała aktualizacja
|
||||||
|
- I nie tylko ...
|
||||||
|
|
||||||
|
## Instalacja
|
||||||
|
|
||||||
|
Aby zainstalować, wykonaj poniższe czynności:
|
||||||
|
|
||||||
|
1. Pobierz najnowszą wersję programu Hydra ze strony [Wydania](https://github.com/hydralauncher/hydra/releases/latest).
|
||||||
|
- Pobierz .exe tylko, jeśli chcesz zainstalować Hydrę w systemie Windows.
|
||||||
|
- Pobierz .deb lub .rpm lub .zip, jeśli chcesz zainstalować Hydrę w systemie Linux (zależy od dystrybucji systemu Linux).
|
||||||
|
2. Uruchom pobrany plik.
|
||||||
|
3. Ciesz się Hydrą!
|
||||||
|
|
||||||
|
## <a name="contributing"> Dokonaj wpłaty
|
||||||
|
|
||||||
|
### <a name="join-our-telegram"></a> Dołącz do naszego kanału Telegram
|
||||||
|
|
||||||
|
Skupiamy nasze dyskusje na naszym kanale [Telegram](https://t.me/hydralauncher).
|
||||||
|
|
||||||
|
1. Dołącz do naszego kanału
|
||||||
|
2. Przejdź do kanału ról i wybierz rolę Pracownik.
|
||||||
|
3. Wejdź na kanał dev, komunikuj się z nami i dziel się swoimi pomysłami.
|
||||||
|
|
||||||
|
### Rozwidlenie i sklonowanie repozytorium
|
||||||
|
|
||||||
|
1. Rozwidlenie repozytorium [(kliknij tutaj, aby rozwidlić teraz)](https://github.com/hydralauncher/hydra/fork)
|
||||||
|
2. Sklonuj swój rozwidlony kod `git clone https://github.com/your_username/hydra`.
|
||||||
|
3. Utwórz nowy brunch
|
||||||
|
4. Wypchnij swoje zatwierdzenia
|
||||||
|
5. Wyślij nowy Pull Request
|
||||||
|
|
||||||
|
### Jak możesz pomóc
|
||||||
|
|
||||||
|
- Tłumaczenie: Chcemy, aby Hydra była dostępna dla jak największej liczby osób. Zachęcamy do pomocy w tłumaczeniu na nowe języki lub aktualizowaniu i ulepszaniu tych, które są już dostępne na Hydrze.
|
||||||
|
- Kod: Hydra jest zbudowana przy użyciu Typescript, Electron i odrobiny Pythona. Jeśli chcesz wnieść swój wkład, dołącz do naszego kanału Telegram!
|
||||||
|
|
||||||
|
### Struktura projektu
|
||||||
|
|
||||||
|
- Klient torrent: Używamy libtorrent, biblioteki Pythona, do zarządzania pobieraniem torrentów.
|
||||||
|
- src/renderer: interfejs aplikacji
|
||||||
|
- src/main: cała logika jest tutaj.
|
||||||
|
|
||||||
|
## Utwórz kompilację z kodu źródłowego
|
||||||
|
|
||||||
|
### Zainstaluj Node.js
|
||||||
|
|
||||||
|
Upewnij się, że masz zainstalowany Node.js na swoim komputerze. Jeśli nie, pobierz i zainstaluj go ze strony [nodejs.org](https://nodejs.org/).
|
||||||
|
|
||||||
|
### Zainstaluj Yarn
|
||||||
|
|
||||||
|
Yarn to menedżer pakietów dla Node.js. Jeśli jeszcze nie zainstalowałeś Yarn, możesz to zrobić, postępując zgodnie z instrukcjami na stronie [yarnpkg.com](https://classic.yarnpkg.com/lang/en/docs/install/).
|
||||||
|
|
||||||
|
### Zainstaluj zależności Node
|
||||||
|
|
||||||
|
Przejdź do katalogu projektu i zainstaluj zależności Node za pomocą Yarn:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd hydra
|
||||||
|
yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zainstaluj Python 3.9
|
||||||
|
|
||||||
|
Upewnij się, że masz zainstalowany Python 3.9 na swoim komputerze. Można go pobrać i zainstalować ze strony [python.org](https://www.python.org/downloads/release/python-3913/).
|
||||||
|
|
||||||
|
### Zainstaluj zależności Pythona
|
||||||
|
|
||||||
|
Zainstaluj niezbędne zależności Pythona za pomocą pip:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zmienne środowiskowe
|
||||||
|
|
||||||
|
Będziesz potrzebował klucza API SteamGridDB, aby uzyskać ikony gier podczas instalacji.
|
||||||
|
Jeśli chcesz użyć onlinefix jako repackagera, musisz dodać swoje dane uwierzytelniające do .env
|
||||||
|
|
||||||
|
Po jego uzyskaniu można skopiować plik lub zmienić jego nazwę `.env.example` na `.env` i umieść go na`STEAMGRIDDB_API_KEY`, `ONLINEFIX_USERNAME`, `ONLINEFIX_PASSWORD`.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
Po skonfigurowaniu wszystkiego można uruchomić następujące polecenie, aby uruchomić zarówno proces Electron, jak i klienta bittorrent:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tworzenie kompilacji
|
||||||
|
|
||||||
|
### Zbuduj klienta bittorrent
|
||||||
|
|
||||||
|
Zbuduj klienta bittorrent za pomocą tego poleceniaи:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python torrent-client/setup.py build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tworzenie aplikacji Electron
|
||||||
|
|
||||||
|
Zbuduj aplikację Electron za pomocą tego polecenia:
|
||||||
|
|
||||||
|
W systemie Windows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build:win
|
||||||
|
```
|
||||||
|
|
||||||
|
W systemie Linux:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build:linux
|
||||||
|
```
|
||||||
|
|
||||||
|
## Współtwórcy
|
||||||
|
|
||||||
|
<a href="https://github.com/hydralauncher/hydra/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=hydralauncher/hydra" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Hydra posiada licencję [MIT License](LICENSE).
|
@ -13,13 +13,11 @@
|
|||||||
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
||||||
[![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)
|
[![be](https://img.shields.io/badge/lang-be-orange)](README.be.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)
|
||||||
|
[![pl](https://img.shields.io/badge/lang-pl-white)](README.pl.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)
|
|
||||||
|
|
||||||
|
|
||||||
![Hydra Catalogue](./docs/screenshot.png)
|
![Hydra Catalogue](./docs/screenshot.png)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -120,7 +118,7 @@ yarn
|
|||||||
|
|
||||||
### <a name="install-python-39"></a> Instale Python 3.9
|
### <a name="install-python-39"></a> Instale Python 3.9
|
||||||
|
|
||||||
Certifique-se de ter o Python 3.9 instalado em sua máquina. Você pode baixá-lo e instalá-lo em [python.org](https://www.python.org/downloads/release/python-3919/).
|
Certifique-se de ter o Python 3.9 instalado em sua máquina. Você pode baixá-lo e instalá-lo em [python.org](https://www.python.org/downloads/release/python-3913/).
|
||||||
|
|
||||||
### <a name="install-python-dependencies"></a> Instale Python Dependencies
|
### <a name="install-python-dependencies"></a> Instale Python Dependencies
|
||||||
|
|
||||||
|
10
README.ru.md
10
README.ru.md
@ -13,11 +13,11 @@
|
|||||||
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
||||||
[![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)
|
|
||||||
[![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)
|
|
||||||
[![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)
|
||||||
|
[![en](https://img.shields.io/badge/lang-en-red.svg)](README.md)
|
||||||
|
[![pl](https://img.shields.io/badge/lang-pl-white)](README.pl.md)
|
||||||
|
[![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md)
|
||||||
|
[![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md)
|
||||||
|
|
||||||
![Hydra Catalogue](./docs/screenshot.png)
|
![Hydra Catalogue](./docs/screenshot.png)
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ yarn
|
|||||||
|
|
||||||
### Установка Python 3.9
|
### Установка Python 3.9
|
||||||
|
|
||||||
Убедитесь, что у вас установлен Python 3.9 на вашем компьютере. Вы можете загрузить и установить его с [python.org](https://www.python.org/downloads/release/python-3919/).
|
Убедитесь, что у вас установлен Python 3.9 на вашем компьютере. Вы можете загрузить и установить его с [python.org](https://www.python.org/downloads/release/python-3913/).
|
||||||
|
|
||||||
### Установка зависимостей Python
|
### Установка зависимостей Python
|
||||||
|
|
||||||
|
@ -13,11 +13,11 @@
|
|||||||
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
|
||||||
[![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)
|
|
||||||
[![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)
|
|
||||||
[![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)
|
||||||
|
[![en](https://img.shields.io/badge/lang-en-red.svg)](README.md)
|
||||||
|
[![pl](https://img.shields.io/badge/lang-pl-white)](README.pl.md)
|
||||||
|
[![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md)
|
||||||
|
[![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md)
|
||||||
|
|
||||||
![Hydra Catalogue](./docs/screenshot.png)
|
![Hydra Catalogue](./docs/screenshot.png)
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ yarn
|
|||||||
|
|
||||||
### Встановіть Python 3.9
|
### Встановіть Python 3.9
|
||||||
|
|
||||||
Переконайтеся, що на вашому комп'ютері встановлено Python 3.9. Ви можете завантажити та встановити його з [python.org](https://www.python.org/downloads/release/python-3919/).
|
Переконайтеся, що на вашому комп'ютері встановлено Python 3.9. Ви можете завантажити та встановити його з [python.org](https://www.python.org/downloads/release/python-3913/).
|
||||||
|
|
||||||
### Встановіть Python залежності
|
### Встановіть Python залежності
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ extraResources:
|
|||||||
- hydra-download-manager
|
- hydra-download-manager
|
||||||
- hydra.db
|
- hydra.db
|
||||||
- fastlist.exe
|
- fastlist.exe
|
||||||
|
- seeds
|
||||||
files:
|
files:
|
||||||
- "!**/.vscode/*"
|
- "!**/.vscode/*"
|
||||||
- "!src/*"
|
- "!src/*"
|
||||||
@ -46,5 +47,7 @@ appImage:
|
|||||||
npmRebuild: false
|
npmRebuild: false
|
||||||
publish:
|
publish:
|
||||||
provider: github
|
provider: github
|
||||||
|
owner: hydralauncher
|
||||||
|
repo: hydra
|
||||||
electronDownload:
|
electronDownload:
|
||||||
mirror: https://npmmirror.com/mirrors/electron/
|
mirror: https://npmmirror.com/mirrors/electron/
|
||||||
|
@ -7,17 +7,10 @@ import {
|
|||||||
} from "electron-vite";
|
} from "electron-vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
|
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
|
||||||
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
|
||||||
import svgr from "vite-plugin-svgr";
|
import svgr from "vite-plugin-svgr";
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
loadEnv(mode);
|
loadEnv(mode);
|
||||||
|
|
||||||
const sentryPlugin = sentryVitePlugin({
|
|
||||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
|
||||||
org: "hydra-launcher",
|
|
||||||
project: "hydra-launcher",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
main: {
|
main: {
|
||||||
build: {
|
build: {
|
||||||
@ -34,7 +27,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
"@shared": resolve("src/shared"),
|
"@shared": resolve("src/shared"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [externalizeDepsPlugin(), swcPlugin(), sentryPlugin],
|
plugins: [externalizeDepsPlugin(), swcPlugin()],
|
||||||
},
|
},
|
||||||
preload: {
|
preload: {
|
||||||
plugins: [externalizeDepsPlugin()],
|
plugins: [externalizeDepsPlugin()],
|
||||||
@ -50,7 +43,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
"@shared": resolve("src/shared"),
|
"@shared": resolve("src/shared"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [svgr(), react(), vanillaExtractPlugin(), sentryPlugin],
|
plugins: [svgr(), react(), vanillaExtractPlugin()],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
16
package.json
16
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydra",
|
"name": "hydralauncher",
|
||||||
"version": "1.1.1",
|
"version": "1.2.4",
|
||||||
"description": "Hydra",
|
"description": "Hydra",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "Los Broxas",
|
"author": "Los Broxas",
|
||||||
@ -10,8 +10,13 @@
|
|||||||
"url": "https://github.com/hydralauncher/hydra.git"
|
"url": "https://github.com/hydralauncher/hydra.git"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"npm": "please-use-yarn",
|
||||||
|
"yarn": ">= 1.19.1"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
|
"format-check": "prettier --check .",
|
||||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||||
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
||||||
@ -24,7 +29,8 @@
|
|||||||
"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",
|
||||||
"build:linux": "electron-vite build && electron-builder --linux",
|
"build:linux": "electron-vite build && electron-builder --linux",
|
||||||
"prepare": "husky"
|
"prepare": "husky",
|
||||||
|
"typeorm:migration-create": "yarn typeorm migration:create"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/preload": "^3.0.0",
|
"@electron-toolkit/preload": "^3.0.0",
|
||||||
@ -33,9 +39,6 @@
|
|||||||
"@fontsource/fira-sans": "^5.0.20",
|
"@fontsource/fira-sans": "^5.0.20",
|
||||||
"@primer/octicons-react": "^19.9.0",
|
"@primer/octicons-react": "^19.9.0",
|
||||||
"@reduxjs/toolkit": "^2.2.3",
|
"@reduxjs/toolkit": "^2.2.3",
|
||||||
"@sentry/electron": "^4.23.0",
|
|
||||||
"@sentry/react": "^7.111.0",
|
|
||||||
"@sentry/vite-plugin": "^2.16.1",
|
|
||||||
"@vanilla-extract/css": "^1.14.2",
|
"@vanilla-extract/css": "^1.14.2",
|
||||||
"@vanilla-extract/recipes": "^0.5.2",
|
"@vanilla-extract/recipes": "^0.5.2",
|
||||||
"auto-launch": "^5.0.6",
|
"auto-launch": "^5.0.6",
|
||||||
@ -47,6 +50,7 @@
|
|||||||
"color.js": "^1.2.0",
|
"color.js": "^1.2.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"easydl": "^1.1.1",
|
"easydl": "^1.1.1",
|
||||||
|
"electron-updater": "^6.1.8",
|
||||||
"fetch-cookie": "^3.0.1",
|
"fetch-cookie": "^3.0.1",
|
||||||
"flexsearch": "^0.7.43",
|
"flexsearch": "^0.7.43",
|
||||||
"i18next": "^23.11.2",
|
"i18next": "^23.11.2",
|
||||||
|
1
seeds/steam-games.json
Normal file
1
seeds/steam-games.json
Normal file
File diff suppressed because one or more lines are too long
@ -176,5 +176,11 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"close": "Close button"
|
"close": "Close button"
|
||||||
|
},
|
||||||
|
"splash": {
|
||||||
|
"downloading_version": "Downloading version {{version}}",
|
||||||
|
"searching_updates": "Searching for updates",
|
||||||
|
"update_found": "Update {{version}} found",
|
||||||
|
"restarting_and_applying": "Restarting and applying update"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,12 @@
|
|||||||
"github": "Contribuye en GitHub"
|
"github": "Contribuye en GitHub"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Buscar",
|
"search": "Buscar juegos",
|
||||||
|
"home": "Inicio",
|
||||||
"catalogue": "Catálogo",
|
"catalogue": "Catálogo",
|
||||||
"downloads": "Descargas",
|
"downloads": "Descargas",
|
||||||
"search_results": "Resultados de búsqueda",
|
"search_results": "Resultados de búsqueda",
|
||||||
"settings": "Ajustes",
|
"settings": "Ajustes"
|
||||||
"home": "Inicio"
|
|
||||||
},
|
},
|
||||||
"bottom_panel": {
|
"bottom_panel": {
|
||||||
"no_downloads_in_progress": "Sin descargas en progreso",
|
"no_downloads_in_progress": "Sin descargas en progreso",
|
||||||
@ -37,12 +37,17 @@
|
|||||||
"checking_files": "Analizando archivos de {{title}} - ({{percentage}} completado)",
|
"checking_files": "Analizando archivos de {{title}} - ({{percentage}} completado)",
|
||||||
"downloading": "Descargando {{title}}… ({{percentage}} completado) - Finalizando {{eta}} - {{speed}}"
|
"downloading": "Descargando {{title}}… ({{percentage}} completado) - Finalizando {{eta}} - {{speed}}"
|
||||||
},
|
},
|
||||||
|
"catalogue": {
|
||||||
|
"next_page": "Siguiente página",
|
||||||
|
"previous_page": "Pagina anterior"
|
||||||
|
},
|
||||||
"game_details": {
|
"game_details": {
|
||||||
"open_download_options": "Ver opciones de descargas",
|
"open_download_options": "Ver opciones de descargas",
|
||||||
"download_options_zero": "No hay opciones de descargas disponibles",
|
"download_options_zero": "No hay opciones de descargas disponibles",
|
||||||
"download_options_one": "{{count}} opción de descarga",
|
"download_options_one": "{{count}} opción de descarga",
|
||||||
"download_options_other": "{{count}} opciones de descargas",
|
"download_options_other": "{{count}} opciones de descargas",
|
||||||
"updated_at": "Actualizado el {{updated_at}}",
|
"updated_at": "Actualizado el {{updated_at}}",
|
||||||
|
"install": "Instalar",
|
||||||
"resume": "Continuar",
|
"resume": "Continuar",
|
||||||
"pause": "Pausa",
|
"pause": "Pausa",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
@ -52,7 +57,7 @@
|
|||||||
"eta": "Finalizando en {{eta}}",
|
"eta": "Finalizando en {{eta}}",
|
||||||
"downloading_metadata": "Descargando metadatos…",
|
"downloading_metadata": "Descargando metadatos…",
|
||||||
"checking_files": "Analizando archivos…",
|
"checking_files": "Analizando archivos…",
|
||||||
"filter": "Filtrar repacks",
|
"filter": "Buscar repacks",
|
||||||
"requirements": "Requisitos del Sistema",
|
"requirements": "Requisitos del Sistema",
|
||||||
"minimum": "Mínimos",
|
"minimum": "Mínimos",
|
||||||
"recommended": "Recomendados",
|
"recommended": "Recomendados",
|
||||||
@ -71,20 +76,17 @@
|
|||||||
"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",
|
||||||
"next_suggestion": "Siguiente sugerencia",
|
|
||||||
"play_time": "Jugado por {{amount}}",
|
"play_time": "Jugado por {{amount}}",
|
||||||
"install": "Instalar",
|
|
||||||
"play": "Jugar",
|
|
||||||
"not_played_yet": "Aún no has jugado a {{title}}",
|
|
||||||
"close": "Cerrar",
|
|
||||||
"deleting": "Eliminando instalador…",
|
|
||||||
"playing_now": "Jugando ahora",
|
|
||||||
"last_time_played": "Jugado por última vez {{period}}",
|
"last_time_played": "Jugado por última vez {{period}}",
|
||||||
"got_it": "Entendido",
|
"not_played_yet": "Aún no has jugado a {{title}}",
|
||||||
|
"next_suggestion": "Siguiente sugerencia",
|
||||||
|
"play": "Jugar",
|
||||||
|
"deleting": "Eliminando instalador…",
|
||||||
|
"close": "Cerrar",
|
||||||
|
"playing_now": "Jugando ahora",
|
||||||
"change": "Cambiar",
|
"change": "Cambiar",
|
||||||
"repacks_modal_description": "Selecciona el repack que quieres descargar",
|
"repacks_modal_description": "Selecciona el repack que quieres descargar",
|
||||||
"downloads_path": "Ruta de descarga",
|
"select_folder_hint": "Para cambiar la carpeta predeterminada, ve a <0>Ajustes</0>",
|
||||||
"select_folder_hint": "Para cambiar la carpeta predeterminada, accede a",
|
|
||||||
"download_now": "Descargar ahora",
|
"download_now": "Descargar ahora",
|
||||||
"installation_instructions": "Instrucciones de instalación",
|
"installation_instructions": "Instrucciones de instalación",
|
||||||
"installation_instructions_description": "Se requieren de pasos adicionales para instalar este juego",
|
"installation_instructions_description": "Se requieren de pasos adicionales para instalar este juego",
|
||||||
@ -92,7 +94,15 @@
|
|||||||
"dodi_installation_instruction": "Cuando abras el instalador de DODI, presiona la tecla hacia arriba del teclado <0 /> para iniciar el proceso de instalación:",
|
"dodi_installation_instruction": "Cuando abras el instalador de DODI, presiona la tecla hacia arriba del teclado <0 /> para iniciar el proceso de instalación:",
|
||||||
"dont_show_it_again": "No mostrar de nuevo",
|
"dont_show_it_again": "No mostrar de nuevo",
|
||||||
"copy_to_clipboard": "Copiar",
|
"copy_to_clipboard": "Copiar",
|
||||||
"copied_to_clipboard": "Copiado"
|
"copied_to_clipboard": "Copiado",
|
||||||
|
"got_it": "Entendido",
|
||||||
|
"no_shop_details": "No se pudieron obtener detalles de la tienda.",
|
||||||
|
"download_options": "Opciones de descarga",
|
||||||
|
"download_path": "Ruta de descarga",
|
||||||
|
"previous_screenshot": "Anterior captura",
|
||||||
|
"next_screenshot": "Siguiente captura",
|
||||||
|
"screenshot": "Captura {{number}}",
|
||||||
|
"open_screenshot": "Abrir captura {{number}}"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Activar Hydra",
|
"title": "Activar Hydra",
|
||||||
@ -118,12 +128,14 @@
|
|||||||
"downloading_metadata": "Descargando metadatos…",
|
"downloading_metadata": "Descargando metadatos…",
|
||||||
"checking_files": "Verificando archivos…",
|
"checking_files": "Verificando archivos…",
|
||||||
"starting_download": "Iniciando descarga…",
|
"starting_download": "Iniciando descarga…",
|
||||||
"remove_from_list": "Eliminar",
|
|
||||||
"delete": "Eliminar instalador",
|
|
||||||
"delete_modal_description": "Esto eliminará todos los archivos de instalación de tu computadora.",
|
|
||||||
"delete_modal_title": "¿Estás seguro?",
|
|
||||||
"deleting": "Eliminando instalador…",
|
"deleting": "Eliminando instalador…",
|
||||||
"install": "Instalar"
|
"delete": "Eliminar instalador",
|
||||||
|
"remove_from_list": "Eliminar",
|
||||||
|
"delete_modal_title": "¿Estás seguro?",
|
||||||
|
"delete_modal_description": "Esto eliminará todos los archivos de instalación de tu computadora.",
|
||||||
|
"install": "Instalar",
|
||||||
|
"real_debrid": "Real Debrid",
|
||||||
|
"torrent": "Torrent"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"downloads_path": "Ruta de descarga",
|
"downloads_path": "Ruta de descarga",
|
||||||
@ -132,7 +144,16 @@
|
|||||||
"enable_download_notifications": "Cuando se completa una descarga",
|
"enable_download_notifications": "Cuando se completa una descarga",
|
||||||
"enable_repack_list_notifications": "Cuando se añade un repack nuevo",
|
"enable_repack_list_notifications": "Cuando se añade un repack nuevo",
|
||||||
"telemetry": "Telemetría",
|
"telemetry": "Telemetría",
|
||||||
"telemetry_description": "Habilitar recopilación de datos de manera anónima"
|
"telemetry_description": "Habilitar recopilación de datos de manera anónima",
|
||||||
|
"real_debrid_api_token_label": "Token API de Real Debrid",
|
||||||
|
"quit_app_instead_hiding": "Salir de Hydra en vez de minimizar en la bandeja del sistema",
|
||||||
|
"launch_with_system": "Iniciar Hydra al inicio del sistema",
|
||||||
|
"general": "General",
|
||||||
|
"behavior": "Otros",
|
||||||
|
"enable_real_debrid": "Activar Real Debrid",
|
||||||
|
"real_debrid": "Real Debrid",
|
||||||
|
"real_debrid_api_token_hint": "Puedes obtener tu clave de API <0>aquí</0>.",
|
||||||
|
"save_changes": "Guardar cambios"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Descarga completada",
|
"download_complete": "Descarga completada",
|
||||||
@ -156,8 +177,10 @@
|
|||||||
"modal": {
|
"modal": {
|
||||||
"close": "Botón de cierre"
|
"close": "Botón de cierre"
|
||||||
},
|
},
|
||||||
"catalogue": {
|
"splash": {
|
||||||
"next_page": "Siguiente página",
|
"downloading_version": "Descargando versión {{version}}",
|
||||||
"previous_page": "Pagina anterior"
|
"searching_updates": "Buscando actualizaciones",
|
||||||
|
"update_found": "Actualización {{version}} encontrada",
|
||||||
|
"restarting_and_applying": "Reiniciando y aplicando actualización"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"checking_files": "{{title}} ({{percentage}} - 파일 검사 중…)",
|
"checking_files": "{{title}} ({{percentage}} - 파일 검사 중…)",
|
||||||
"paused": "{{title}} (일시 정지됨)",
|
"paused": "{{title}} (일시 정지됨)",
|
||||||
"downloading": "{{title}} ({{percentage}} - 다운로드 중…)",
|
"downloading": "{{title}} ({{percentage}} - 다운로드 중…)",
|
||||||
"filter": "필터 라이브러리",
|
"filter": "라이브러리 정렬",
|
||||||
"follow_us": "공식 SNS",
|
"follow_us": "공식 SNS",
|
||||||
"home": "홈",
|
"home": "홈",
|
||||||
"discord": "공식 디스코드",
|
"discord": "공식 디스코드",
|
||||||
@ -54,10 +54,10 @@
|
|||||||
"remove": "제거",
|
"remove": "제거",
|
||||||
"remove_from_list": "목록에서 제거",
|
"remove_from_list": "목록에서 제거",
|
||||||
"space_left_on_disk": "여유 저장 용량 {{space}} 남음",
|
"space_left_on_disk": "여유 저장 용량 {{space}} 남음",
|
||||||
"eta": "왼료까지 {{eta}}",
|
"eta": "완료까지 {{eta}}",
|
||||||
"downloading_metadata": "메타데이터 다운로드 중…",
|
"downloading_metadata": "메타데이터 다운로드 중…",
|
||||||
"checking_files": "파일 검사 중…",
|
"checking_files": "파일 검사 중…",
|
||||||
"filter": "리팩들을 다음과 같이 걸러내기",
|
"filter": "리팩들을 다음과 같이 정렬하기",
|
||||||
"requirements": "시스템 사양",
|
"requirements": "시스템 사양",
|
||||||
"minimum": "최저 사양",
|
"minimum": "최저 사양",
|
||||||
"recommended": "권장 사양",
|
"recommended": "권장 사양",
|
||||||
@ -91,8 +91,8 @@
|
|||||||
"download_now": "지금 다운로드",
|
"download_now": "지금 다운로드",
|
||||||
"installation_instructions": "설치 방법",
|
"installation_instructions": "설치 방법",
|
||||||
"installation_instructions_description": "이 게임을 설치하기 위해서는 추가적인 단계가 필요합니다",
|
"installation_instructions_description": "이 게임을 설치하기 위해서는 추가적인 단계가 필요합니다",
|
||||||
"online_fix_instruction": "OnlineFix 게임들은 추출 시 암호가 필요합니다. 비밀번호를 물을 때 다음을 암호로 사용하기:",
|
"online_fix_instruction": "OnlineFix 게임들은 압축 해제 시 암호가 필요합니다. 비밀번호를 물을 때 다음을 암호로 사용하기:",
|
||||||
"dodi_installation_instruction": "DODI 인스톨러를 열었다면 키보드의 위 방향키를 눌러 설치를 시작하세요:",
|
"dodi_installation_instruction": "DODI 인스톨러를 실행했다면 키보드의 위 방향키를 눌러 설치를 시작하세요:",
|
||||||
"dont_show_it_again": "다시 보지 않기",
|
"dont_show_it_again": "다시 보지 않기",
|
||||||
"copy_to_clipboard": "복사하기",
|
"copy_to_clipboard": "복사하기",
|
||||||
"copied_to_clipboard": "복사됨",
|
"copied_to_clipboard": "복사됨",
|
||||||
|
@ -17,7 +17,11 @@
|
|||||||
"downloading": "{{title}} ({{percentage}} - Pobieranie…)",
|
"downloading": "{{title}} ({{percentage}} - Pobieranie…)",
|
||||||
"filter": "Filtruj biblioteke",
|
"filter": "Filtruj biblioteke",
|
||||||
"follow_us": "Śledź nas",
|
"follow_us": "Śledź nas",
|
||||||
"home": "Główna"
|
"home": "Główna",
|
||||||
|
"discord": "Dołącz nasz Discord",
|
||||||
|
"telegram": "Dołącz nasz Telegram",
|
||||||
|
"x": "Śledź na X",
|
||||||
|
"github": "Przyczyń się na GitHub"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Szukaj",
|
"search": "Szukaj",
|
||||||
@ -66,6 +70,8 @@
|
|||||||
"copied_link_to_clipboard": "Skopiowano łącze",
|
"copied_link_to_clipboard": "Skopiowano łącze",
|
||||||
"hours": "godzin",
|
"hours": "godzin",
|
||||||
"minutes": "minut",
|
"minutes": "minut",
|
||||||
|
"amount_hours": "{{amount}} godzin",
|
||||||
|
"amount_minutes": "{{amount}} minut",
|
||||||
"accuracy": "{{accuracy}}% dokładność",
|
"accuracy": "{{accuracy}}% dokładność",
|
||||||
"add_to_library": "Dodaj do biblioteki",
|
"add_to_library": "Dodaj do biblioteki",
|
||||||
"remove_from_library": "Usuń z biblioteki",
|
"remove_from_library": "Usuń z biblioteki",
|
||||||
@ -80,9 +86,23 @@
|
|||||||
"playing_now": "Granie teraz",
|
"playing_now": "Granie teraz",
|
||||||
"change": "Zmień",
|
"change": "Zmień",
|
||||||
"repacks_modal_description": "Wybierz repack, który chcesz pobrać",
|
"repacks_modal_description": "Wybierz repack, który chcesz pobrać",
|
||||||
"downloads_path": "Ścieżka pobierania",
|
|
||||||
"select_folder_hint": "Aby zmienić domyślny folder, przejdź do",
|
"select_folder_hint": "Aby zmienić domyślny folder, przejdź do",
|
||||||
"download_now": "Pobierz teraz"
|
"download_now": "Pobierz teraz",
|
||||||
|
"installation_instructions": "Instrukcja instalacji",
|
||||||
|
"installation_instructions_description": "Do zainstalowania tej gry wymagane są dodatkowe kroki",
|
||||||
|
"online_fix_instruction": "Gry OnlineFix wymagają hasła do wyodrębnienia. W razie potrzeby użyj następującego hasła:",
|
||||||
|
"dodi_installation_instruction": "Po otwarciu instalatora DODI naciśnij klawisz <0 /> w górę, aby rozpocząć proces instalacji:",
|
||||||
|
"dont_show_it_again": "Nie pokazuj tego ponownie",
|
||||||
|
"copy_to_clipboard": "Skopiuj",
|
||||||
|
"copied_to_clipboard": "Skopiowano",
|
||||||
|
"got_it": "Rozumiem",
|
||||||
|
"no_shop_details": "Nie udało się pobrać danych sklepu.",
|
||||||
|
"download_options": "Opcje pobierania",
|
||||||
|
"download_path": "Ścieżka pobierania",
|
||||||
|
"previous_screenshot": "Poprzedni zrzut ekranu",
|
||||||
|
"next_screenshot": "Następny zrzut ekranu",
|
||||||
|
"screenshot": "Zrzut ekranu {{number}}",
|
||||||
|
"open_screenshot": "Otwórz zrzut ekranu {{number}}"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Aktywuj Hydra",
|
"title": "Aktywuj Hydra",
|
||||||
@ -113,7 +133,9 @@
|
|||||||
"remove_from_list": "Usuń",
|
"remove_from_list": "Usuń",
|
||||||
"delete_modal_title": "Czy na pewno?",
|
"delete_modal_title": "Czy na pewno?",
|
||||||
"delete_modal_description": "Spowoduje to usunięcie wszystkich plików instalacyjnych z komputera",
|
"delete_modal_description": "Spowoduje to usunięcie wszystkich plików instalacyjnych z komputera",
|
||||||
"install": "Instaluj"
|
"install": "Instaluj",
|
||||||
|
"real_debrid": "Real Debrid",
|
||||||
|
"torrent": "Torrent"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"downloads_path": "Ścieżka pobierania",
|
"downloads_path": "Ścieżka pobierania",
|
||||||
@ -122,7 +144,16 @@
|
|||||||
"enable_download_notifications": "Gdy pobieranie zostanie zakończone",
|
"enable_download_notifications": "Gdy pobieranie zostanie zakończone",
|
||||||
"enable_repack_list_notifications": "Gdy dodawany jest nowy repack",
|
"enable_repack_list_notifications": "Gdy dodawany jest nowy repack",
|
||||||
"telemetry": "Telemetria",
|
"telemetry": "Telemetria",
|
||||||
"telemetry_description": "Włącz anonimowe statystyki użycia"
|
"telemetry_description": "Włącz anonimowe statystyki użycia",
|
||||||
|
"real_debrid_api_token_label": "Real Debrid API token",
|
||||||
|
"quit_app_instead_hiding": "Zamknij Hydr zamiast minimalizować do zasobnika",
|
||||||
|
"launch_with_system": "Uruchom Hydra przy starcie systemu",
|
||||||
|
"general": "Ogólne",
|
||||||
|
"behavior": "Zachowania",
|
||||||
|
"enable_real_debrid": "Włącz Real Debrid",
|
||||||
|
"real_debrid": "Real Debrid",
|
||||||
|
"real_debrid_api_token_hint": "Możesz uzyskać swój klucz API <0>tutaj</0>.",
|
||||||
|
"save_changes": "Zapisz zmiany"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Pobieranie zakończone",
|
"download_complete": "Pobieranie zakończone",
|
||||||
@ -142,5 +173,8 @@
|
|||||||
"title": "Programy nie są zainstalowane",
|
"title": "Programy nie są zainstalowane",
|
||||||
"description": "Pliki wykonywalne Wine lub Lutris nie zostały znalezione na twoim systemie",
|
"description": "Pliki wykonywalne Wine lub Lutris nie zostały znalezione na twoim systemie",
|
||||||
"instructions": "Sprawdź prawidłowy sposób instalacji dowolnego z nich w swojej dystrybucji Linuksa, aby gra działała normalnie"
|
"instructions": "Sprawdź prawidłowy sposób instalacji dowolnego z nich w swojej dystrybucji Linuksa, aby gra działała normalnie"
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"close": "Zamknij"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,5 +176,11 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"close": "Botão de fechar"
|
"close": "Botão de fechar"
|
||||||
|
},
|
||||||
|
"splash": {
|
||||||
|
"downloading_version": "Baixando versão {{version}}",
|
||||||
|
"searching_updates": "Buscando atualizações",
|
||||||
|
"update_found": "Versão {{version}} encontrada",
|
||||||
|
"restarting_and_applying": "Reiniciando e aplicando atualização"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@
|
|||||||
"behavior": "Поведение",
|
"behavior": "Поведение",
|
||||||
"enable_real_debrid": "Включить Real Debrid",
|
"enable_real_debrid": "Включить Real Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real Debrid",
|
||||||
"real_debrid_api_token_hint": "API ключ можно получить <0>здесь/0>.",
|
"real_debrid_api_token_hint": "API ключ можно получить <0>здесь</0>.",
|
||||||
"save_changes": "Сохранить изменения"
|
"save_changes": "Сохранить изменения"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
|
@ -150,7 +150,7 @@
|
|||||||
"launch_with_system": "随系统启动时运行应用程序",
|
"launch_with_system": "随系统启动时运行应用程序",
|
||||||
"enable_real_debrid": "启用 Real Debrid",
|
"enable_real_debrid": "启用 Real Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real Debrid",
|
||||||
"real_debrid_api_token_hint": "您可以将API密钥填入<0>这里</0>.",
|
"real_debrid_api_token_hint": "您可以从<0>这里</0>获取API密钥.",
|
||||||
"save_changes": "保存更改"
|
"save_changes": "保存更改"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
|
@ -12,34 +12,20 @@ export const repackersOn1337x = [
|
|||||||
export const repackers = [
|
export const repackers = [
|
||||||
...repackersOn1337x,
|
...repackersOn1337x,
|
||||||
"Xatab",
|
"Xatab",
|
||||||
"CPG",
|
|
||||||
"TinyRepacks",
|
"TinyRepacks",
|
||||||
|
"CPG",
|
||||||
"GOG",
|
"GOG",
|
||||||
"onlinefix",
|
"onlinefix",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const months = [
|
|
||||||
"Jan",
|
|
||||||
"Feb",
|
|
||||||
"Mar",
|
|
||||||
"Apr",
|
|
||||||
"May",
|
|
||||||
"Jun",
|
|
||||||
"Jul",
|
|
||||||
"Aug",
|
|
||||||
"Sep",
|
|
||||||
"Oct",
|
|
||||||
"Nov",
|
|
||||||
"Dec",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const defaultDownloadsPath = app.getPath("downloads");
|
export const defaultDownloadsPath = app.getPath("downloads");
|
||||||
|
|
||||||
export const databasePath = path.join(
|
export const databasePath = path.join(
|
||||||
app.getPath("appData"),
|
app.getPath("appData"),
|
||||||
app.getName(),
|
"hydra",
|
||||||
"hydra.db"
|
"hydra.db"
|
||||||
);
|
);
|
||||||
|
|
||||||
export const INSTALLATION_ID_LENGTH = 6;
|
export const seedsPath = app.isPackaged
|
||||||
export const ACTIVATION_KEY_MULTIPLIER = 7;
|
? path.join(process.resourcesPath, "seeds")
|
||||||
|
: path.join(__dirname, "..", "..", "seeds");
|
||||||
|
@ -1,33 +1,21 @@
|
|||||||
import { DataSource } from "typeorm";
|
import { DataSource } from "typeorm";
|
||||||
import {
|
import { Game, GameShopCache, Repack, UserPreferences } from "@main/entity";
|
||||||
Game,
|
import type { BetterSqlite3ConnectionOptions } from "typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions";
|
||||||
GameShopCache,
|
|
||||||
Repack,
|
|
||||||
RepackerFriendlyName,
|
|
||||||
UserPreferences,
|
|
||||||
MigrationScript,
|
|
||||||
SteamGame,
|
|
||||||
} from "@main/entity";
|
|
||||||
import type { SqliteConnectionOptions } from "typeorm/driver/sqlite/SqliteConnectionOptions";
|
|
||||||
|
|
||||||
import { databasePath } from "./constants";
|
import { databasePath } from "./constants";
|
||||||
|
import migrations from "./migrations";
|
||||||
|
|
||||||
export const createDataSource = (options: Partial<SqliteConnectionOptions>) =>
|
export const createDataSource = (
|
||||||
|
options: Partial<BetterSqlite3ConnectionOptions>
|
||||||
|
) =>
|
||||||
new DataSource({
|
new DataSource({
|
||||||
type: "better-sqlite3",
|
type: "better-sqlite3",
|
||||||
|
entities: [Game, Repack, UserPreferences, GameShopCache],
|
||||||
|
synchronize: true,
|
||||||
database: databasePath,
|
database: databasePath,
|
||||||
entities: [
|
|
||||||
Game,
|
|
||||||
Repack,
|
|
||||||
RepackerFriendlyName,
|
|
||||||
UserPreferences,
|
|
||||||
GameShopCache,
|
|
||||||
MigrationScript,
|
|
||||||
SteamGame,
|
|
||||||
],
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const dataSource = createDataSource({
|
export const dataSource = createDataSource({
|
||||||
synchronize: true,
|
migrations,
|
||||||
});
|
});
|
||||||
|
@ -23,8 +23,8 @@ export class Game {
|
|||||||
@Column("text")
|
@Column("text")
|
||||||
title: string;
|
title: string;
|
||||||
|
|
||||||
@Column("text")
|
@Column("text", { nullable: true })
|
||||||
iconUrl: string;
|
iconUrl: string | null;
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
@Column("text", { nullable: true })
|
||||||
folderName: string | null;
|
folderName: string | null;
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
export * from "./game.entity";
|
export * from "./game.entity";
|
||||||
export * from "./repack.entity";
|
export * from "./repack.entity";
|
||||||
export * from "./repacker-friendly-name.entity";
|
|
||||||
export * from "./user-preferences.entity";
|
export * from "./user-preferences.entity";
|
||||||
export * from "./game-shop-cache.entity";
|
export * from "./game-shop-cache.entity";
|
||||||
export * from "./migration-script.entity";
|
|
||||||
export * from "./steam-game.entity";
|
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
} from "typeorm";
|
|
||||||
|
|
||||||
@Entity("migration_script")
|
|
||||||
export class MigrationScript {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column("text", { unique: true })
|
|
||||||
version: string;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
} from "typeorm";
|
|
||||||
|
|
||||||
@Entity("repacker_friendly_name")
|
|
||||||
export class RepackerFriendlyName {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column("text", { unique: true })
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@Column("text")
|
|
||||||
friendlyName: string;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
import { Column, Entity, PrimaryColumn } from "typeorm";
|
|
||||||
|
|
||||||
@Entity("steam_game")
|
|
||||||
export class SteamGame {
|
|
||||||
@PrimaryColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
name: string;
|
|
||||||
}
|
|
@ -26,9 +26,6 @@ export class UserPreferences {
|
|||||||
@Column("boolean", { default: false })
|
@Column("boolean", { default: false })
|
||||||
repackUpdatesNotificationsEnabled: boolean;
|
repackUpdatesNotificationsEnabled: boolean;
|
||||||
|
|
||||||
@Column("boolean", { default: true })
|
|
||||||
telemetryEnabled: boolean;
|
|
||||||
|
|
||||||
@Column("boolean", { default: false })
|
@Column("boolean", { default: false })
|
||||||
preferQuitInsteadOfHiding: boolean;
|
preferQuitInsteadOfHiding: boolean;
|
||||||
|
|
||||||
|
48
src/main/events/autoupdater/check-for-updates.ts
Normal file
48
src/main/events/autoupdater/check-for-updates.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { AppUpdaterEvents } from "@types";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import updater, { ProgressInfo, UpdateInfo } from "electron-updater";
|
||||||
|
import { WindowManager } from "@main/services";
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
|
const { autoUpdater } = updater;
|
||||||
|
|
||||||
|
const sendEvent = (event: AppUpdaterEvents) => {
|
||||||
|
WindowManager.splashWindow?.webContents.send("autoUpdaterEvent", event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockValuesForDebug = async () => {
|
||||||
|
sendEvent({ type: "update-downloaded" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkForUpdates = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
autoUpdater
|
||||||
|
.addListener("error", () => {
|
||||||
|
sendEvent({ type: "error" });
|
||||||
|
})
|
||||||
|
.addListener("checking-for-update", () => {
|
||||||
|
sendEvent({ type: "checking-for-updates" });
|
||||||
|
})
|
||||||
|
.addListener("update-not-available", () => {
|
||||||
|
sendEvent({ type: "update-not-available" });
|
||||||
|
})
|
||||||
|
.addListener("update-available", (info: UpdateInfo) => {
|
||||||
|
sendEvent({ type: "update-available", info });
|
||||||
|
})
|
||||||
|
.addListener("update-downloaded", () => {
|
||||||
|
sendEvent({ type: "update-downloaded" });
|
||||||
|
})
|
||||||
|
.addListener("download-progress", (info: ProgressInfo) => {
|
||||||
|
sendEvent({ type: "download-progress", info });
|
||||||
|
})
|
||||||
|
.addListener("update-cancelled", () => {
|
||||||
|
sendEvent({ type: "update-cancelled" });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (app.isPackaged) {
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
} else {
|
||||||
|
await mockValuesForDebug();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("checkForUpdates", checkForUpdates);
|
12
src/main/events/autoupdater/continue-to-main-window.ts
Normal file
12
src/main/events/autoupdater/continue-to-main-window.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { WindowManager } from "@main/services";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import updater from "electron-updater";
|
||||||
|
|
||||||
|
const { autoUpdater } = updater;
|
||||||
|
|
||||||
|
const continueToMainWindow = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
autoUpdater.removeAllListeners();
|
||||||
|
WindowManager.prepareMainWindowAndCloseSplash();
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("continueToMainWindow", continueToMainWindow);
|
17
src/main/events/autoupdater/restart-and-install-update.ts
Normal file
17
src/main/events/autoupdater/restart-and-install-update.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { app } from "electron";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import updater from "electron-updater";
|
||||||
|
import { WindowManager } from "@main/services";
|
||||||
|
|
||||||
|
const { autoUpdater } = updater;
|
||||||
|
|
||||||
|
const restartAndInstallUpdate = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
if (app.isPackaged) {
|
||||||
|
autoUpdater.quitAndInstall(true, true);
|
||||||
|
} else {
|
||||||
|
autoUpdater.removeAllListeners();
|
||||||
|
WindowManager.prepareMainWindowAndCloseSplash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("restartAndInstallUpdate", restartAndInstallUpdate);
|
@ -92,7 +92,4 @@ const getRecentlyAddedCatalogue = async (
|
|||||||
return results.slice(0, resultSize);
|
return results.slice(0, resultSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getCatalogue, {
|
registerEvent("getCatalogue", getCatalogue);
|
||||||
name: "getCatalogue",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { gameShopCacheRepository, steamGameRepository } from "@main/repository";
|
import { gameShopCacheRepository } from "@main/repository";
|
||||||
import { getSteamAppDetails } from "@main/services";
|
import { getSteamAppDetails } from "@main/services";
|
||||||
|
|
||||||
import type { ShopDetails, GameShop, SteamAppDetails } from "@types";
|
import type { ShopDetails, GameShop, SteamAppDetails } from "@types";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
import { stateManager } from "@main/state-manager";
|
||||||
|
|
||||||
const getLocalizedSteamAppDetails = (
|
const getLocalizedSteamAppDetails = (
|
||||||
objectID: string,
|
objectID: string,
|
||||||
@ -13,10 +14,11 @@ const getLocalizedSteamAppDetails = (
|
|||||||
return getSteamAppDetails(objectID, language);
|
return getSteamAppDetails(objectID, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all([
|
return getSteamAppDetails(objectID, language).then((localizedAppDetails) => {
|
||||||
steamGameRepository.findOne({ where: { id: Number(objectID) } }),
|
const steamGame = stateManager
|
||||||
getSteamAppDetails(objectID, language),
|
.getValue("steamGames")
|
||||||
]).then(([steamGame, localizedAppDetails]) => {
|
.find((game) => game.id === Number(objectID));
|
||||||
|
|
||||||
if (steamGame && localizedAppDetails) {
|
if (steamGame && localizedAppDetails) {
|
||||||
return {
|
return {
|
||||||
...localizedAppDetails,
|
...localizedAppDetails,
|
||||||
@ -72,7 +74,4 @@ const getGameShopDetails = async (
|
|||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getGameShopDetails, {
|
registerEvent("getGameShopDetails", getGameShopDetails);
|
||||||
name: "getGameShopDetails",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -36,7 +36,4 @@ const getGames = async (
|
|||||||
return { results, cursor: i };
|
return { results, cursor: i };
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getGames, {
|
registerEvent("getGames", getGames);
|
||||||
name: "getGames",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -42,7 +42,4 @@ const getHowLongToBeat = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getHowLongToBeat, {
|
registerEvent("getHowLongToBeat", getHowLongToBeat);
|
||||||
name: "getHowLongToBeat",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -36,6 +36,4 @@ const getRandomGame = async (_event: Electron.IpcMainInvokeEvent) => {
|
|||||||
return state.games[state.index];
|
return state.games[state.index];
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getRandomGame, {
|
registerEvent("getRandomGame", getRandomGame);
|
||||||
name: "getRandomGame",
|
|
||||||
});
|
|
||||||
|
@ -8,7 +8,4 @@ const searchGameRepacks = (
|
|||||||
return searchRepacks(query);
|
return searchRepacks(query);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(searchGameRepacks, {
|
registerEvent("searchGameRepacks", searchGameRepacks);
|
||||||
name: "searchGameRepacks",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -9,7 +9,4 @@ const searchGamesEvent = async (
|
|||||||
return searchGames({ query, take: 12 });
|
return searchGames({ query, take: 12 });
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(searchGamesEvent, {
|
registerEvent("searchGames", searchGamesEvent);
|
||||||
name: "searchGames",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
||||||
|
@ -7,6 +7,4 @@ const getDiskFreeSpace = async (
|
|||||||
path: string
|
path: string
|
||||||
) => checkDiskSpace(path);
|
) => checkDiskSpace(path);
|
||||||
|
|
||||||
registerEvent(getDiskFreeSpace, {
|
registerEvent("getDiskFreeSpace", getDiskFreeSpace);
|
||||||
name: "getDiskFreeSpace",
|
|
||||||
});
|
|
||||||
|
@ -14,7 +14,6 @@ import "./library/close-game";
|
|||||||
import "./library/delete-game-folder";
|
import "./library/delete-game-folder";
|
||||||
import "./library/get-game-by-object-id";
|
import "./library/get-game-by-object-id";
|
||||||
import "./library/get-library";
|
import "./library/get-library";
|
||||||
import "./library/get-repackers-friendly-names";
|
|
||||||
import "./library/open-game";
|
import "./library/open-game";
|
||||||
import "./library/open-game-installer";
|
import "./library/open-game-installer";
|
||||||
import "./library/remove-game";
|
import "./library/remove-game";
|
||||||
@ -28,6 +27,9 @@ import "./torrenting/start-game-download";
|
|||||||
import "./user-preferences/get-user-preferences";
|
import "./user-preferences/get-user-preferences";
|
||||||
import "./user-preferences/update-user-preferences";
|
import "./user-preferences/update-user-preferences";
|
||||||
import "./user-preferences/auto-launch";
|
import "./user-preferences/auto-launch";
|
||||||
|
import "./autoupdater/check-for-updates";
|
||||||
|
import "./autoupdater/restart-and-install-update";
|
||||||
|
import "./autoupdater/continue-to-main-window";
|
||||||
|
|
||||||
ipcMain.handle("ping", () => "pong");
|
ipcMain.handle("ping", () => "pong");
|
||||||
ipcMain.handle("getVersion", () => app.getVersion());
|
ipcMain.handle("getVersion", () => app.getVersion());
|
||||||
|
@ -3,8 +3,8 @@ import { gameRepository } from "@main/repository";
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
|
||||||
import type { GameShop } from "@types";
|
import type { GameShop } from "@types";
|
||||||
import { getFileBase64 } from "@main/helpers";
|
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
||||||
import { getSteamGameIconUrl } from "@main/services";
|
import { stateManager } from "@main/state-manager";
|
||||||
|
|
||||||
const addGameToLibrary = async (
|
const addGameToLibrary = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
@ -27,21 +27,31 @@ const addGameToLibrary = async (
|
|||||||
)
|
)
|
||||||
.then(async ({ affected }) => {
|
.then(async ({ affected }) => {
|
||||||
if (!affected) {
|
if (!affected) {
|
||||||
const iconUrl = await getFileBase64(
|
const steamGame = stateManager
|
||||||
await getSteamGameIconUrl(objectID)
|
.getValue("steamGames")
|
||||||
);
|
.find((game) => game.id === Number(objectID));
|
||||||
|
|
||||||
await gameRepository.insert({
|
const iconUrl = steamGame?.clientIcon
|
||||||
title,
|
? getSteamAppAsset("icon", objectID, steamGame.clientIcon)
|
||||||
iconUrl,
|
: null;
|
||||||
objectID,
|
|
||||||
shop: gameShop,
|
await gameRepository
|
||||||
executablePath,
|
.insert({
|
||||||
});
|
title,
|
||||||
|
iconUrl,
|
||||||
|
objectID,
|
||||||
|
shop: gameShop,
|
||||||
|
executablePath,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (iconUrl) {
|
||||||
|
getFileBase64(iconUrl).then((base64) =>
|
||||||
|
gameRepository.update({ objectID }, { iconUrl: base64 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(addGameToLibrary, {
|
registerEvent("addGameToLibrary", addGameToLibrary);
|
||||||
name: "addGameToLibrary",
|
|
||||||
});
|
|
||||||
|
@ -36,6 +36,4 @@ const closeGame = async (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(closeGame, {
|
registerEvent("closeGame", closeGame);
|
||||||
name: "closeGame",
|
|
||||||
});
|
|
||||||
|
@ -47,6 +47,4 @@ const deleteGameFolder = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(deleteGameFolder, {
|
registerEvent("deleteGameFolder", deleteGameFolder);
|
||||||
name: "deleteGameFolder",
|
|
||||||
});
|
|
||||||
|
@ -16,6 +16,4 @@ const getGameByObjectID = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
registerEvent(getGameByObjectID, {
|
registerEvent("getGameByObjectID", getGameByObjectID);
|
||||||
name: "getGameByObjectID",
|
|
||||||
});
|
|
||||||
|
@ -28,6 +28,4 @@ const getLibrary = async () =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
registerEvent(getLibrary, {
|
registerEvent("getLibrary", getLibrary);
|
||||||
name: "getLibrary",
|
|
||||||
});
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import { registerEvent } from "../register-event";
|
|
||||||
import { stateManager } from "@main/state-manager";
|
|
||||||
|
|
||||||
const getRepackersFriendlyNames = async () =>
|
|
||||||
stateManager.getValue("repackersFriendlyNames").reduce((prev, next) => {
|
|
||||||
return { ...prev, [next.name]: next.friendlyName };
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
registerEvent(getRepackersFriendlyNames, {
|
|
||||||
name: "getRepackersFriendlyNames",
|
|
||||||
memoize: true,
|
|
||||||
});
|
|
@ -54,6 +54,4 @@ const openGameInstaller = async (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(openGameInstaller, {
|
registerEvent("openGameInstaller", openGameInstaller);
|
||||||
name: "openGameInstaller",
|
|
||||||
});
|
|
||||||
|
@ -13,6 +13,4 @@ const openGame = async (
|
|||||||
shell.openPath(executablePath);
|
shell.openPath(executablePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(openGame, {
|
registerEvent("openGame", openGame);
|
||||||
name: "openGame",
|
|
||||||
});
|
|
||||||
|
@ -8,6 +8,4 @@ const removeGameFromLibrary = async (
|
|||||||
gameRepository.update({ id: gameId }, { isDeleted: true });
|
gameRepository.update({ id: gameId }, { isDeleted: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(removeGameFromLibrary, {
|
registerEvent("removeGameFromLibrary", removeGameFromLibrary);
|
||||||
name: "removeGameFromLibrary",
|
|
||||||
});
|
|
||||||
|
@ -20,6 +20,4 @@ const removeGame = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(removeGame, {
|
registerEvent("removeGame", removeGame);
|
||||||
name: "removeGame",
|
|
||||||
});
|
|
||||||
|
@ -4,6 +4,4 @@ import { registerEvent } from "../register-event";
|
|||||||
const openExternal = async (_event: Electron.IpcMainInvokeEvent, src: string) =>
|
const openExternal = async (_event: Electron.IpcMainInvokeEvent, src: string) =>
|
||||||
shell.openExternal(src);
|
shell.openExternal(src);
|
||||||
|
|
||||||
registerEvent(openExternal, {
|
registerEvent("openExternal", openExternal);
|
||||||
name: "openExternal",
|
|
||||||
});
|
|
||||||
|
@ -13,6 +13,4 @@ const showOpenDialog = async (
|
|||||||
throw new Error("Main window is not available");
|
throw new Error("Main window is not available");
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(showOpenDialog, {
|
registerEvent("showOpenDialog", showOpenDialog);
|
||||||
name: "showOpenDialog",
|
|
||||||
});
|
|
||||||
|
@ -1,37 +1,11 @@
|
|||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
|
|
||||||
import { stateManager } from "@main/state-manager";
|
|
||||||
|
|
||||||
interface EventArgs {
|
|
||||||
name: string;
|
|
||||||
memoize?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const registerEvent = (
|
export const registerEvent = (
|
||||||
listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any,
|
name: string,
|
||||||
{ name, memoize = false }: EventArgs
|
listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any
|
||||||
) => {
|
) => {
|
||||||
ipcMain.handle(name, (event: Electron.IpcMainInvokeEvent, ...args) => {
|
ipcMain.handle(name, async (event: Electron.IpcMainInvokeEvent, ...args) => {
|
||||||
const eventResults = stateManager.getValue("eventResults");
|
|
||||||
const keys = Array.from(eventResults.keys());
|
|
||||||
|
|
||||||
const key = [name, args] as [string, any[]];
|
|
||||||
|
|
||||||
const memoizationKey = keys.find(([memoizedEvent, memoizedArgs]) => {
|
|
||||||
const sameEvent = name === memoizedEvent;
|
|
||||||
const sameArgs = memoizedArgs.every((arg, index) => arg === args[index]);
|
|
||||||
|
|
||||||
return sameEvent && sameArgs;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (memoizationKey) return eventResults.get(memoizationKey);
|
|
||||||
|
|
||||||
return Promise.resolve(listener(event, ...args)).then((result) => {
|
return Promise.resolve(listener(event, ...args)).then((result) => {
|
||||||
if (memoize) {
|
|
||||||
eventResults.set(key, JSON.parse(JSON.stringify(result)));
|
|
||||||
stateManager.setValue("eventResults", eventResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result) return result;
|
if (!result) return result;
|
||||||
return JSON.parse(JSON.stringify(result));
|
return JSON.parse(JSON.stringify(result));
|
||||||
});
|
});
|
||||||
|
@ -50,6 +50,4 @@ const cancelGameDownload = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(cancelGameDownload, {
|
registerEvent("cancelGameDownload", cancelGameDownload);
|
||||||
name: "cancelGameDownload",
|
|
||||||
});
|
|
||||||
|
@ -27,6 +27,4 @@ const pauseGameDownload = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(pauseGameDownload, {
|
registerEvent("pauseGameDownload", pauseGameDownload);
|
||||||
name: "pauseGameDownload",
|
|
||||||
});
|
|
||||||
|
@ -46,6 +46,4 @@ const resumeGameDownload = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(resumeGameDownload, {
|
registerEvent("resumeGameDownload", resumeGameDownload);
|
||||||
name: "resumeGameDownload",
|
|
||||||
});
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { getSteamGameIconUrl } from "@main/services";
|
|
||||||
import {
|
import {
|
||||||
gameRepository,
|
gameRepository,
|
||||||
repackRepository,
|
repackRepository,
|
||||||
@ -8,10 +7,11 @@ import {
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
|
||||||
import type { GameShop } from "@types";
|
import type { GameShop } from "@types";
|
||||||
import { getFileBase64 } from "@main/helpers";
|
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
||||||
import { In } from "typeorm";
|
import { In } from "typeorm";
|
||||||
import { DownloadManager } from "@main/services";
|
import { DownloadManager } from "@main/services";
|
||||||
import { Downloader, GameStatus } from "@shared";
|
import { Downloader, GameStatus } from "@shared";
|
||||||
|
import { stateManager } from "@main/state-manager";
|
||||||
|
|
||||||
const startGameDownload = async (
|
const startGameDownload = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
@ -76,18 +76,34 @@ const startGameDownload = async (
|
|||||||
|
|
||||||
return game;
|
return game;
|
||||||
} else {
|
} else {
|
||||||
const iconUrl = await getFileBase64(await getSteamGameIconUrl(objectID));
|
const steamGame = stateManager
|
||||||
|
.getValue("steamGames")
|
||||||
|
.find((game) => game.id === Number(objectID));
|
||||||
|
|
||||||
const createdGame = await gameRepository.save({
|
const iconUrl = steamGame?.clientIcon
|
||||||
title,
|
? getSteamAppAsset("icon", objectID, steamGame.clientIcon)
|
||||||
iconUrl,
|
: null;
|
||||||
objectID,
|
|
||||||
downloader,
|
const createdGame = await gameRepository
|
||||||
shop: gameShop,
|
.save({
|
||||||
status: GameStatus.Downloading,
|
title,
|
||||||
downloadPath,
|
iconUrl,
|
||||||
repack: { id: repackId },
|
objectID,
|
||||||
});
|
downloader,
|
||||||
|
shop: gameShop,
|
||||||
|
status: GameStatus.Downloading,
|
||||||
|
downloadPath,
|
||||||
|
repack: { id: repackId },
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
if (iconUrl) {
|
||||||
|
getFileBase64(iconUrl).then((base64) =>
|
||||||
|
gameRepository.update({ objectID }, { iconUrl: base64 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
DownloadManager.downloadGame(createdGame.id);
|
DownloadManager.downloadGame(createdGame.id);
|
||||||
|
|
||||||
@ -97,6 +113,4 @@ const startGameDownload = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(startGameDownload, {
|
registerEvent("startGameDownload", startGameDownload);
|
||||||
name: "startGameDownload",
|
|
||||||
});
|
|
||||||
|
@ -16,6 +16,4 @@ const autoLaunch = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(autoLaunch, {
|
registerEvent("autoLaunch", autoLaunch);
|
||||||
name: "autoLaunch",
|
|
||||||
});
|
|
||||||
|
@ -6,6 +6,4 @@ const getUserPreferences = async () =>
|
|||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
});
|
});
|
||||||
|
|
||||||
registerEvent(getUserPreferences, {
|
registerEvent("getUserPreferences", getUserPreferences);
|
||||||
name: "getUserPreferences",
|
|
||||||
});
|
|
||||||
|
@ -21,6 +21,4 @@ const updateUserPreferences = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(updateUserPreferences, {
|
registerEvent("updateUserPreferences", updateUserPreferences);
|
||||||
name: "updateUserPreferences",
|
|
||||||
});
|
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
gogFormatter,
|
gogFormatter,
|
||||||
onlinefixFormatter,
|
onlinefixFormatter,
|
||||||
} from "./formatters";
|
} from "./formatters";
|
||||||
import { months, repackers } from "../constants";
|
import { repackers } from "../constants";
|
||||||
|
|
||||||
export const pipe =
|
export const pipe =
|
||||||
<T>(...fns: ((arg: T) => any)[]) =>
|
<T>(...fns: ((arg: T) => any)[]) =>
|
||||||
@ -44,19 +44,6 @@ export const repackerFormatter: Record<
|
|||||||
onlinefix: onlinefixFormatter,
|
onlinefix: onlinefixFormatter,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatUploadDate = (str: string) => {
|
|
||||||
const date = new Date();
|
|
||||||
|
|
||||||
const [month, day, year] = str.split(" ");
|
|
||||||
|
|
||||||
date.setMonth(months.indexOf(month.replace(".", "")));
|
|
||||||
date.setDate(Number(day.substring(0, 2)));
|
|
||||||
date.setFullYear(Number("20" + year.replace("'", "")));
|
|
||||||
date.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
return date;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSteamAppAsset = (
|
export const getSteamAppAsset = (
|
||||||
category: "library" | "hero" | "logo" | "icon",
|
category: "library" | "hero" | "logo" | "icon",
|
||||||
objectID: string,
|
objectID: string,
|
||||||
|
@ -1,29 +1,26 @@
|
|||||||
import { app, BrowserWindow, net, protocol } from "electron";
|
import { app, BrowserWindow, net, protocol } from "electron";
|
||||||
import { init } from "@sentry/electron/main";
|
import updater from "electron-updater";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
||||||
import { resolveDatabaseUpdates, WindowManager } from "@main/services";
|
import { logger, resolveDatabaseUpdates, WindowManager } from "@main/services";
|
||||||
import { dataSource } from "@main/data-source";
|
import { dataSource } from "@main/data-source";
|
||||||
import * as resources from "@locales";
|
import * as resources from "@locales";
|
||||||
import { userPreferencesRepository } from "@main/repository";
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
|
const { autoUpdater } = updater;
|
||||||
|
|
||||||
|
autoUpdater.setFeedURL({
|
||||||
|
provider: "github",
|
||||||
|
owner: "hydralauncher",
|
||||||
|
repo: "hydra",
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.logger = logger;
|
||||||
|
|
||||||
const gotTheLock = app.requestSingleInstanceLock();
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
if (!gotTheLock) app.quit();
|
if (!gotTheLock) app.quit();
|
||||||
|
|
||||||
if (import.meta.env.MAIN_VITE_SENTRY_DSN) {
|
app.commandLine.appendSwitch("--no-sandbox");
|
||||||
init({
|
|
||||||
dsn: import.meta.env.MAIN_VITE_SENTRY_DSN,
|
|
||||||
beforeSend: async (event) => {
|
|
||||||
const userPreferences = await userPreferencesRepository.findOne({
|
|
||||||
where: { id: 1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userPreferences?.telemetryEnabled) return event;
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
i18n.init({
|
i18n.init({
|
||||||
resources,
|
resources,
|
||||||
@ -57,6 +54,8 @@ app.whenReady().then(() => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
dataSource.initialize().then(async () => {
|
dataSource.initialize().then(async () => {
|
||||||
|
await dataSource.runMigrations();
|
||||||
|
|
||||||
await resolveDatabaseUpdates();
|
await resolveDatabaseUpdates();
|
||||||
|
|
||||||
await import("./main");
|
await import("./main");
|
||||||
@ -65,7 +64,7 @@ app.whenReady().then(() => {
|
|||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
});
|
});
|
||||||
|
|
||||||
WindowManager.createMainWindow();
|
WindowManager.createSplashScreen();
|
||||||
WindowManager.createSystemTray(userPreferences?.language || "en");
|
WindowManager.createSystemTray(userPreferences?.language || "en");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { stateManager } from "./state-manager";
|
import { stateManager } from "./state-manager";
|
||||||
import { repackers } from "./constants";
|
import { repackersOn1337x, seedsPath } from "./constants";
|
||||||
import {
|
import {
|
||||||
getNewGOGGames,
|
getNewGOGGames,
|
||||||
getNewRepacksFromCPG,
|
|
||||||
getNewRepacksFromUser,
|
getNewRepacksFromUser,
|
||||||
getNewRepacksFromXatab,
|
getNewRepacksFromXatab,
|
||||||
getNewRepacksFromOnlineFix,
|
getNewRepacksFromOnlineFix,
|
||||||
@ -12,8 +11,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
gameRepository,
|
gameRepository,
|
||||||
repackRepository,
|
repackRepository,
|
||||||
repackerFriendlyNameRepository,
|
|
||||||
steamGameRepository,
|
|
||||||
userPreferencesRepository,
|
userPreferencesRepository,
|
||||||
} from "./repository";
|
} from "./repository";
|
||||||
import { TorrentDownloader } from "./services";
|
import { TorrentDownloader } from "./services";
|
||||||
@ -22,12 +19,16 @@ import { Notification } from "electron";
|
|||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { GameStatus } from "@shared";
|
import { GameStatus } from "@shared";
|
||||||
import { In } from "typeorm";
|
import { In } from "typeorm";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
import { RealDebridClient } from "./services/real-debrid";
|
import { RealDebridClient } from "./services/real-debrid";
|
||||||
|
import { orderBy } from "lodash-es";
|
||||||
|
import { SteamGame } from "@types";
|
||||||
|
|
||||||
startProcessWatcher();
|
startProcessWatcher();
|
||||||
|
|
||||||
const track1337xUsers = async (existingRepacks: Repack[]) => {
|
const track1337xUsers = async (existingRepacks: Repack[]) => {
|
||||||
for (const repacker of repackers) {
|
for (const repacker of repackersOn1337x) {
|
||||||
await getNewRepacksFromUser(
|
await getNewRepacksFromUser(
|
||||||
repacker,
|
repacker,
|
||||||
existingRepacks.filter((repack) => repack.repacker === repacker)
|
existingRepacks.filter((repack) => repack.repacker === repacker)
|
||||||
@ -39,19 +40,16 @@ const checkForNewRepacks = async (userPreferences: UserPreferences | null) => {
|
|||||||
const existingRepacks = stateManager.getValue("repacks");
|
const existingRepacks = stateManager.getValue("repacks");
|
||||||
|
|
||||||
Promise.allSettled([
|
Promise.allSettled([
|
||||||
getNewGOGGames(
|
track1337xUsers(existingRepacks),
|
||||||
existingRepacks.filter((repack) => repack.repacker === "GOG")
|
|
||||||
),
|
|
||||||
getNewRepacksFromXatab(
|
getNewRepacksFromXatab(
|
||||||
existingRepacks.filter((repack) => repack.repacker === "Xatab")
|
existingRepacks.filter((repack) => repack.repacker === "Xatab")
|
||||||
),
|
),
|
||||||
getNewRepacksFromCPG(
|
getNewGOGGames(
|
||||||
existingRepacks.filter((repack) => repack.repacker === "CPG")
|
existingRepacks.filter((repack) => repack.repacker === "GOG")
|
||||||
),
|
),
|
||||||
getNewRepacksFromOnlineFix(
|
getNewRepacksFromOnlineFix(
|
||||||
existingRepacks.filter((repack) => repack.repacker === "onlinefix")
|
existingRepacks.filter((repack) => repack.repacker === "onlinefix")
|
||||||
),
|
),
|
||||||
track1337xUsers(existingRepacks),
|
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
repackRepository.count().then((count) => {
|
repackRepository.count().then((count) => {
|
||||||
const total = count - stateManager.getValue("repacks").length;
|
const total = count - stateManager.getValue("repacks").length;
|
||||||
@ -74,23 +72,18 @@ const checkForNewRepacks = async (userPreferences: UserPreferences | null) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadState = async (userPreferences: UserPreferences | null) => {
|
const loadState = async (userPreferences: UserPreferences | null) => {
|
||||||
const [friendlyNames, repacks, steamGames] = await Promise.all([
|
const repacks = await repackRepository.find({
|
||||||
repackerFriendlyNameRepository.find(),
|
order: {
|
||||||
repackRepository.find({
|
createdAt: "desc",
|
||||||
order: {
|
},
|
||||||
createdAt: "desc",
|
});
|
||||||
},
|
|
||||||
}),
|
const steamGames = JSON.parse(
|
||||||
steamGameRepository.find({
|
fs.readFileSync(path.join(seedsPath, "steam-games.json"), "utf-8")
|
||||||
order: {
|
) as SteamGame[];
|
||||||
name: "asc",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
stateManager.setValue("repackersFriendlyNames", friendlyNames);
|
|
||||||
stateManager.setValue("repacks", repacks);
|
stateManager.setValue("repacks", repacks);
|
||||||
stateManager.setValue("steamGames", steamGames);
|
stateManager.setValue("steamGames", orderBy(steamGames, ["name"], "asc"));
|
||||||
|
|
||||||
import("./events");
|
import("./events");
|
||||||
|
|
||||||
|
78
src/main/migrations/1715900413313-fix_repack_uploadDate.ts
Normal file
78
src/main/migrations/1715900413313-fix_repack_uploadDate.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { createDataSource } from "@main/data-source";
|
||||||
|
import { Repack } from "@main/entity";
|
||||||
|
import { app } from "electron";
|
||||||
|
import { chunk } from "lodash-es";
|
||||||
|
import path from "path";
|
||||||
|
import { In, MigrationInterface, QueryRunner, Table } from "typeorm";
|
||||||
|
|
||||||
|
export class FixRepackUploadDate1715900413313 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: "repack_temp",
|
||||||
|
columns: [
|
||||||
|
{ name: "title", type: "varchar" },
|
||||||
|
{ name: "old_id", type: "int" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.query(
|
||||||
|
`INSERT INTO repack_temp (title, old_id) SELECT title, id FROM repack WHERE repacker IN ('onlinefix', 'Xatab');`
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.query(
|
||||||
|
`DELETE FROM repack WHERE repacker IN ('onlinefix', 'Xatab');`
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateDataSource = createDataSource({
|
||||||
|
database: app.isPackaged
|
||||||
|
? path.join(process.resourcesPath, "hydra.db")
|
||||||
|
: path.join(__dirname, "..", "..", "hydra.db"),
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateDataSource.initialize();
|
||||||
|
|
||||||
|
const updateRepackRepository = updateDataSource.getRepository(Repack);
|
||||||
|
|
||||||
|
const updatedRepacks = await updateRepackRepository.find({
|
||||||
|
where: {
|
||||||
|
repacker: In(["onlinefix", "Xatab"]),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const chunks = chunk(
|
||||||
|
updatedRepacks.map((repack) => {
|
||||||
|
const { id: _, ...rest } = repack;
|
||||||
|
return rest;
|
||||||
|
}),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
await queryRunner.manager
|
||||||
|
.createQueryBuilder(Repack, "repack")
|
||||||
|
.insert()
|
||||||
|
.values(chunk)
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
await queryRunner.query(
|
||||||
|
`UPDATE game
|
||||||
|
SET repackId = (
|
||||||
|
SELECT id
|
||||||
|
from repack LEFT JOIN repack_temp ON repack_temp.title = repack.title
|
||||||
|
WHERE repack_temp.old_id = game.repackId
|
||||||
|
)
|
||||||
|
WHERE EXISTS (select old_id from repack_temp WHERE old_id = game.repackId)`
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.dropTable("repack_temp");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(_: QueryRunner): Promise<void> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
3
src/main/migrations/index.ts
Normal file
3
src/main/migrations/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { FixRepackUploadDate1715900413313 } from "./1715900413313-fix_repack_uploadDate";
|
||||||
|
|
||||||
|
export default [FixRepackUploadDate1715900413313];
|
@ -1,27 +1,11 @@
|
|||||||
import { dataSource } from "./data-source";
|
import { dataSource } from "./data-source";
|
||||||
import {
|
import { Game, GameShopCache, Repack, UserPreferences } from "@main/entity";
|
||||||
Game,
|
|
||||||
GameShopCache,
|
|
||||||
Repack,
|
|
||||||
RepackerFriendlyName,
|
|
||||||
UserPreferences,
|
|
||||||
MigrationScript,
|
|
||||||
SteamGame,
|
|
||||||
} from "@main/entity";
|
|
||||||
|
|
||||||
export const gameRepository = dataSource.getRepository(Game);
|
export const gameRepository = dataSource.getRepository(Game);
|
||||||
|
|
||||||
export const repackRepository = dataSource.getRepository(Repack);
|
export const repackRepository = dataSource.getRepository(Repack);
|
||||||
|
|
||||||
export const repackerFriendlyNameRepository =
|
|
||||||
dataSource.getRepository(RepackerFriendlyName);
|
|
||||||
|
|
||||||
export const userPreferencesRepository =
|
export const userPreferencesRepository =
|
||||||
dataSource.getRepository(UserPreferences);
|
dataSource.getRepository(UserPreferences);
|
||||||
|
|
||||||
export const gameShopCacheRepository = dataSource.getRepository(GameShopCache);
|
export const gameShopCacheRepository = dataSource.getRepository(GameShopCache);
|
||||||
|
|
||||||
export const migrationScriptRepository =
|
|
||||||
dataSource.getRepository(MigrationScript);
|
|
||||||
|
|
||||||
export const steamGameRepository = dataSource.getRepository(SteamGame);
|
|
||||||
|
40
src/main/scripts/get-games-icon-hash.ts
Normal file
40
src/main/scripts/get-games-icon-hash.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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");
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import cp from "node:child_process";
|
import cp from "node:child_process";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import * as Sentry from "@sentry/electron/main";
|
|
||||||
import { app, dialog } from "electron";
|
import { app, dialog } from "electron";
|
||||||
import type { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import type { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
|
|
||||||
@ -87,8 +86,6 @@ export class TorrentDownloader extends Downloader {
|
|||||||
downloadSpeed: payload.downloadSpeed,
|
downloadSpeed: payload.downloadSpeed,
|
||||||
timeRemaining: payload.timeRemaining,
|
timeRemaining: payload.timeRemaining,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.captureException(err);
|
|
||||||
} finally {
|
} finally {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,39 @@
|
|||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
|
|
||||||
import { formatUploadDate } from "@main/helpers";
|
|
||||||
|
|
||||||
import { Repack } from "@main/entity";
|
import { Repack } from "@main/entity";
|
||||||
import { requestWebPage, savePage } from "./helpers";
|
import { requestWebPage, savePage } from "./helpers";
|
||||||
|
|
||||||
|
const months = [
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mar",
|
||||||
|
"Apr",
|
||||||
|
"May",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Oct",
|
||||||
|
"Nov",
|
||||||
|
"Dec",
|
||||||
|
];
|
||||||
|
|
||||||
export const request1337x = async (path: string) =>
|
export const request1337x = async (path: string) =>
|
||||||
requestWebPage(`https://1337xx.to${path}`);
|
requestWebPage(`https://1337xx.to${path}`);
|
||||||
|
|
||||||
|
const formatUploadDate = (str: string) => {
|
||||||
|
const date = new Date();
|
||||||
|
|
||||||
|
const [month, day, year] = str.split(" ");
|
||||||
|
|
||||||
|
date.setMonth(months.indexOf(month.replace(".", "")));
|
||||||
|
date.setDate(Number(day.substring(0, 2)));
|
||||||
|
date.setFullYear(Number("20" + year.replace("'", "")));
|
||||||
|
date.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
return date;
|
||||||
|
};
|
||||||
|
|
||||||
/* TODO: $a will often be null */
|
/* TODO: $a will often be null */
|
||||||
const getTorrentDetails = async (path: string) => {
|
const getTorrentDetails = async (path: string) => {
|
||||||
const response = await request1337x(path);
|
const response = await request1337x(path);
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
import { JSDOM } from "jsdom";
|
|
||||||
|
|
||||||
import { Repack } from "@main/entity";
|
|
||||||
|
|
||||||
import { requestWebPage, savePage } from "./helpers";
|
|
||||||
import { logger } from "../logger";
|
|
||||||
import type { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
|
||||||
|
|
||||||
export const getNewRepacksFromCPG = async (
|
|
||||||
existingRepacks: Repack[] = [],
|
|
||||||
page = 1
|
|
||||||
): Promise<void> => {
|
|
||||||
const data = await requestWebPage(`https://cpgrepacks.site/page/${page}`);
|
|
||||||
|
|
||||||
const { window } = new JSDOM(data);
|
|
||||||
|
|
||||||
const repacks: QueryDeepPartialEntity<Repack>[] = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
Array.from(window.document.querySelectorAll(".post")).forEach(($post) => {
|
|
||||||
const $title = $post.querySelector(".entry-title")!;
|
|
||||||
const uploadDate = $post.querySelector("time")?.getAttribute("datetime");
|
|
||||||
|
|
||||||
const $downloadInfo = Array.from(
|
|
||||||
$post.querySelectorAll(".wp-block-heading")
|
|
||||||
).find(($heading) => $heading.textContent?.startsWith("Download"));
|
|
||||||
|
|
||||||
/* Side note: CPG often misspells "Magnet" as "Magent" */
|
|
||||||
const $magnet = Array.from($post.querySelectorAll("a")).find(
|
|
||||||
($a) =>
|
|
||||||
$a.textContent?.startsWith("Magnet") ||
|
|
||||||
$a.textContent?.startsWith("Magent")
|
|
||||||
);
|
|
||||||
|
|
||||||
const fileSize = ($downloadInfo?.textContent ?? "")
|
|
||||||
.split("Download link => ")
|
|
||||||
.at(1);
|
|
||||||
|
|
||||||
repacks.push({
|
|
||||||
title: $title.textContent!,
|
|
||||||
fileSize: fileSize ?? "N/A",
|
|
||||||
magnet: $magnet!.href,
|
|
||||||
repacker: "CPG",
|
|
||||||
page,
|
|
||||||
uploadDate: uploadDate ? new Date(uploadDate) : new Date(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err: unknown) {
|
|
||||||
logger.error((err as Error).message, { method: "getNewRepacksFromCPG" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const newRepacks = repacks.filter(
|
|
||||||
(repack) =>
|
|
||||||
!existingRepacks.some(
|
|
||||||
(existingRepack) => existingRepack.title === repack.title
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!newRepacks.length) return;
|
|
||||||
|
|
||||||
await savePage(newRepacks);
|
|
||||||
|
|
||||||
return getNewRepacksFromCPG(existingRepacks, page + 1);
|
|
||||||
};
|
|
@ -6,32 +6,57 @@ import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity
|
|||||||
|
|
||||||
const virtualConsole = new VirtualConsole();
|
const virtualConsole = new VirtualConsole();
|
||||||
|
|
||||||
|
const getUploadDate = (document: Document) => {
|
||||||
|
const $modifiedTime = document.querySelector(
|
||||||
|
'[property="article:modified_time"]'
|
||||||
|
) as HTMLMetaElement;
|
||||||
|
if ($modifiedTime) return $modifiedTime.content;
|
||||||
|
|
||||||
|
const $publishedTime = document.querySelector(
|
||||||
|
'[property="article:published_time"]'
|
||||||
|
) as HTMLMetaElement;
|
||||||
|
return $publishedTime.content;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDownloadLink = (document: Document) => {
|
||||||
|
const $latestDownloadButton = document.querySelector(
|
||||||
|
".download-btn:not(.lightweight-accordion *)"
|
||||||
|
) as HTMLAnchorElement;
|
||||||
|
if ($latestDownloadButton) return $latestDownloadButton.href;
|
||||||
|
|
||||||
|
const $downloadButton = document.querySelector(
|
||||||
|
".download-btn"
|
||||||
|
) as HTMLAnchorElement;
|
||||||
|
if (!$downloadButton) return null;
|
||||||
|
|
||||||
|
return $downloadButton.href;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMagnet = (downloadLink: string) => {
|
||||||
|
if (downloadLink.startsWith("http")) {
|
||||||
|
const { searchParams } = new URL(downloadLink);
|
||||||
|
return Buffer.from(searchParams.get("url")!, "base64").toString("utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
return downloadLink;
|
||||||
|
};
|
||||||
|
|
||||||
const getGOGGame = async (url: string) => {
|
const getGOGGame = async (url: string) => {
|
||||||
const data = await requestWebPage(url);
|
const data = await requestWebPage(url);
|
||||||
const { window } = new JSDOM(data, { virtualConsole });
|
const { window } = new JSDOM(data, { virtualConsole });
|
||||||
|
|
||||||
const $modifiedTime = window.document.querySelector(
|
const downloadLink = getDownloadLink(window.document);
|
||||||
'[property="article:modified_time"]'
|
if (!downloadLink) return null;
|
||||||
) as HTMLMetaElement;
|
|
||||||
|
|
||||||
const $em = window.document.querySelector(
|
const $em = window.document.querySelector("p em");
|
||||||
"p:not(.lightweight-accordion *) em"
|
if (!$em) return null;
|
||||||
)!;
|
|
||||||
const fileSize = $em.textContent!.split("Size: ").at(1);
|
const fileSize = $em.textContent!.split("Size: ").at(1);
|
||||||
const $downloadButton = window.document.querySelector(
|
|
||||||
".download-btn:not(.lightweight-accordion *)"
|
|
||||||
) as HTMLAnchorElement;
|
|
||||||
|
|
||||||
const { searchParams } = new URL($downloadButton.href);
|
|
||||||
const magnet = Buffer.from(searchParams.get("url")!, "base64").toString(
|
|
||||||
"utf-8"
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fileSize: fileSize ?? "N/A",
|
fileSize: fileSize ?? "N/A",
|
||||||
uploadDate: new Date($modifiedTime.content),
|
uploadDate: new Date(getUploadDate(window.document)),
|
||||||
repacker: "GOG",
|
repacker: "GOG",
|
||||||
magnet,
|
magnet: getMagnet(downloadLink),
|
||||||
page: 1,
|
page: 1,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -62,7 +87,7 @@ export const getNewGOGGames = async (existingRepacks: Repack[] = []) => {
|
|||||||
if (!gameExists) {
|
if (!gameExists) {
|
||||||
const game = await getGOGGame(href);
|
const game = await getGOGGame(href);
|
||||||
|
|
||||||
repacks.push({ ...game, title });
|
if (game) repacks.push({ ...game, title });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import axios from "axios";
|
||||||
import UserAgent from "user-agents";
|
import UserAgent from "user-agents";
|
||||||
|
|
||||||
import type { Repack } from "@main/entity";
|
import type { Repack } from "@main/entity";
|
||||||
@ -13,12 +14,13 @@ export const savePage = async (repacks: QueryDeepPartialEntity<Repack>[]) =>
|
|||||||
export const requestWebPage = async (url: string) => {
|
export const requestWebPage = async (url: string) => {
|
||||||
const userAgent = new UserAgent();
|
const userAgent = new UserAgent();
|
||||||
|
|
||||||
return fetch(url, {
|
return axios
|
||||||
method: "GET",
|
.get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": userAgent.toString(),
|
"User-Agent": userAgent.toString(),
|
||||||
},
|
},
|
||||||
}).then((response) => response.text());
|
})
|
||||||
|
.then((response) => response.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decodeNonUtf8Response = async (res: Response) => {
|
export const decodeNonUtf8Response = async (res: Response) => {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export * from "./1337x";
|
export * from "./1337x";
|
||||||
export * from "./xatab";
|
export * from "./xatab";
|
||||||
export * from "./cpg-repacks";
|
|
||||||
export * from "./gog";
|
export * from "./gog";
|
||||||
export * from "./online-fix";
|
export * from "./online-fix";
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
import { Repack } from "@main/entity";
|
import { Repack } from "@main/entity";
|
||||||
import { decodeNonUtf8Response, savePage } from "./helpers";
|
import { decodeNonUtf8Response, savePage } from "./helpers";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import parseTorrent, {
|
|
||||||
toMagnetURI,
|
|
||||||
Instance as TorrentInstance,
|
|
||||||
} from "parse-torrent";
|
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
|
|
||||||
import { format, parse, sub } from "date-fns";
|
import createWorker from "@main/workers/torrent-parser.worker?nodeWorker";
|
||||||
import { ru } from "date-fns/locale";
|
import { toMagnetURI } from "parse-torrent";
|
||||||
|
|
||||||
|
const worker = createWorker({});
|
||||||
|
|
||||||
import { onlinefixFormatter } from "@main/helpers";
|
|
||||||
import makeFetchCookie from "fetch-cookie";
|
import makeFetchCookie from "fetch-cookie";
|
||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
import { formatBytes } from "@shared";
|
import { formatBytes } from "@shared";
|
||||||
|
|
||||||
const ONLINE_FIX_URL = "https://online-fix.me/";
|
const ONLINE_FIX_URL = "https://online-fix.me/";
|
||||||
|
|
||||||
|
let totalPages = 1;
|
||||||
|
|
||||||
export const getNewRepacksFromOnlineFix = async (
|
export const getNewRepacksFromOnlineFix = async (
|
||||||
existingRepacks: Repack[] = [],
|
existingRepacks: Repack[] = [],
|
||||||
page = 1,
|
page = 1,
|
||||||
@ -73,18 +72,16 @@ export const getNewRepacksFromOnlineFix = async (
|
|||||||
|
|
||||||
const repacks: QueryDeepPartialEntity<Repack>[] = [];
|
const repacks: QueryDeepPartialEntity<Repack>[] = [];
|
||||||
const articles = Array.from(document.querySelectorAll(".news"));
|
const articles = Array.from(document.querySelectorAll(".news"));
|
||||||
const totalPages = Number(
|
|
||||||
document.querySelector("nav > a:nth-child(13)")?.textContent
|
if (page == 1) {
|
||||||
);
|
totalPages = Number(
|
||||||
|
document.querySelector("nav > a:nth-child(13)")?.textContent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
articles.map(async (article) => {
|
articles.map(async (article) => {
|
||||||
const gameText = article.querySelector("h2.title")?.textContent?.trim();
|
|
||||||
if (!gameText) return;
|
|
||||||
|
|
||||||
const gameName = onlinefixFormatter(gameText);
|
|
||||||
|
|
||||||
const gameLink = article.querySelector("a")?.getAttribute("href");
|
const gameLink = article.querySelector("a")?.getAttribute("href");
|
||||||
if (!gameLink) return;
|
if (!gameLink) return;
|
||||||
|
|
||||||
@ -93,32 +90,6 @@ export const getNewRepacksFromOnlineFix = async (
|
|||||||
);
|
);
|
||||||
const gameDocument = new JSDOM(gamePage).window.document;
|
const gameDocument = new JSDOM(gamePage).window.document;
|
||||||
|
|
||||||
const uploadDateText = gameDocument.querySelector("time")?.textContent;
|
|
||||||
if (!uploadDateText) return;
|
|
||||||
|
|
||||||
let decodedDateText = uploadDateText;
|
|
||||||
|
|
||||||
// "Вчера" means yesterday.
|
|
||||||
if (decodedDateText.includes("Вчера")) {
|
|
||||||
const yesterday = sub(new Date(), { days: 1 });
|
|
||||||
const formattedYesterday = format(yesterday, "d LLLL yyyy", {
|
|
||||||
locale: ru,
|
|
||||||
});
|
|
||||||
decodedDateText = decodedDateText.replace(
|
|
||||||
"Вчера", // "Change yesterday to the default expected date format"
|
|
||||||
formattedYesterday
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadDate = parse(
|
|
||||||
decodedDateText,
|
|
||||||
"d LLLL yyyy, HH:mm",
|
|
||||||
new Date(),
|
|
||||||
{
|
|
||||||
locale: ru,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const torrentButtons = Array.from(
|
const torrentButtons = Array.from(
|
||||||
gameDocument.querySelectorAll("a")
|
gameDocument.querySelectorAll("a")
|
||||||
).filter((a) => a.textContent?.includes("Torrent"));
|
).filter((a) => a.textContent?.includes("Torrent"));
|
||||||
@ -139,27 +110,27 @@ export const getNewRepacksFromOnlineFix = async (
|
|||||||
?.getAttribute("href");
|
?.getAttribute("href");
|
||||||
|
|
||||||
const torrentFile = Buffer.from(
|
const torrentFile = Buffer.from(
|
||||||
await http(`${torrentPrePage}/${torrentLink}`).then((res) =>
|
await http(`${torrentPrePage}${torrentLink}`).then((res) =>
|
||||||
res.arrayBuffer()
|
res.arrayBuffer()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const torrent = parseTorrent(torrentFile) as TorrentInstance;
|
worker.once("message", (torrent) => {
|
||||||
const magnetLink = toMagnetURI({
|
if (!torrent) return;
|
||||||
infoHash: torrent.infoHash,
|
|
||||||
|
const { name, created } = torrent;
|
||||||
|
|
||||||
|
repacks.push({
|
||||||
|
fileSize: formatBytes(torrent.length ?? 0),
|
||||||
|
magnet: toMagnetURI(torrent),
|
||||||
|
page: 1,
|
||||||
|
repacker: "onlinefix",
|
||||||
|
title: name,
|
||||||
|
uploadDate: created,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const torrentSizeInBytes = torrent.length;
|
worker.postMessage(torrentFile);
|
||||||
if (!torrentSizeInBytes) return;
|
|
||||||
|
|
||||||
repacks.push({
|
|
||||||
fileSize: formatBytes(torrentSizeInBytes),
|
|
||||||
magnet: magnetLink,
|
|
||||||
page: 1,
|
|
||||||
repacker: "onlinefix",
|
|
||||||
title: gameName,
|
|
||||||
uploadDate: uploadDate,
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@ -177,9 +148,10 @@ export const getNewRepacksFromOnlineFix = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!newRepacks.length) return;
|
if (!newRepacks.length) return;
|
||||||
if (page === totalPages) return;
|
|
||||||
|
|
||||||
await savePage(newRepacks);
|
await savePage(newRepacks);
|
||||||
|
|
||||||
|
if (page === totalPages) return;
|
||||||
|
|
||||||
return getNewRepacksFromOnlineFix(existingRepacks, page + 1, cookieJar);
|
return getNewRepacksFromOnlineFix(existingRepacks, page + 1, cookieJar);
|
||||||
};
|
};
|
||||||
|
@ -9,8 +9,12 @@ import { toMagnetURI } from "parse-torrent";
|
|||||||
import type { Instance } from "parse-torrent";
|
import type { Instance } from "parse-torrent";
|
||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
import { formatBytes } from "@shared";
|
import { formatBytes } from "@shared";
|
||||||
|
import { getFileBuffer } from "@main/helpers";
|
||||||
|
|
||||||
const worker = createWorker({});
|
const worker = createWorker({});
|
||||||
|
worker.setMaxListeners(11);
|
||||||
|
|
||||||
|
let totalPages = 1;
|
||||||
|
|
||||||
const formatXatabDate = (str: string) => {
|
const formatXatabDate = (str: string) => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
@ -27,7 +31,7 @@ const formatXatabDate = (str: string) => {
|
|||||||
|
|
||||||
const getXatabRepack = (
|
const getXatabRepack = (
|
||||||
url: string
|
url: string
|
||||||
): Promise<{ fileSize: string; magnet: string; uploadDate: Date }> => {
|
): Promise<{ fileSize: string; magnet: string; uploadDate: Date } | null> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const data = await requestWebPage(url);
|
const data = await requestWebPage(url);
|
||||||
@ -40,15 +44,20 @@ const getXatabRepack = (
|
|||||||
".download-torrent"
|
".download-torrent"
|
||||||
) as HTMLAnchorElement;
|
) as HTMLAnchorElement;
|
||||||
|
|
||||||
if (!$downloadButton) throw new Error("Download button not found");
|
if (!$downloadButton) return resolve(null);
|
||||||
|
|
||||||
|
worker.once("message", (torrent: Instance | null) => {
|
||||||
|
if (!torrent) return resolve(null);
|
||||||
|
|
||||||
worker.once("message", (torrent: Instance) => {
|
|
||||||
resolve({
|
resolve({
|
||||||
fileSize: formatBytes(torrent.length ?? 0),
|
fileSize: formatBytes(torrent.length ?? 0),
|
||||||
magnet: toMagnetURI(torrent),
|
magnet: toMagnetURI(torrent),
|
||||||
uploadDate: formatXatabDate($uploadDate!.textContent!),
|
uploadDate: formatXatabDate($uploadDate!.textContent!),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const buffer = await getFileBuffer($downloadButton.href);
|
||||||
|
worker.postMessage(buffer);
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -63,25 +72,37 @@ export const getNewRepacksFromXatab = async (
|
|||||||
|
|
||||||
const repacks: QueryDeepPartialEntity<Repack>[] = [];
|
const repacks: QueryDeepPartialEntity<Repack>[] = [];
|
||||||
|
|
||||||
for (const $a of Array.from(
|
if (page === 1) {
|
||||||
window.document.querySelectorAll(".entry__title a")
|
totalPages = Number(
|
||||||
)) {
|
window.document.querySelector(
|
||||||
try {
|
"#bottom-nav > div.pagination > a:nth-child(12)"
|
||||||
const repack = await getXatabRepack(($a as HTMLAnchorElement).href);
|
)?.textContent
|
||||||
|
);
|
||||||
repacks.push({
|
|
||||||
title: $a.textContent!,
|
|
||||||
repacker: "Xatab",
|
|
||||||
...repack,
|
|
||||||
page,
|
|
||||||
});
|
|
||||||
} catch (err: unknown) {
|
|
||||||
logger.error((err as Error).message, {
|
|
||||||
method: "getNewRepacksFromXatab",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const repacksFromPage = Array.from(
|
||||||
|
window.document.querySelectorAll(".entry__title a")
|
||||||
|
).map(($a) => {
|
||||||
|
return getXatabRepack(($a as HTMLAnchorElement).href)
|
||||||
|
.then((repack) => {
|
||||||
|
if (repack) {
|
||||||
|
repacks.push({
|
||||||
|
title: $a.textContent!,
|
||||||
|
repacker: "Xatab",
|
||||||
|
...repack,
|
||||||
|
page,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
logger.error((err as Error).message, {
|
||||||
|
method: "getNewRepacksFromXatab",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(repacksFromPage);
|
||||||
|
|
||||||
const newRepacks = repacks.filter(
|
const newRepacks = repacks.filter(
|
||||||
(repack) =>
|
(repack) =>
|
||||||
!existingRepacks.some(
|
!existingRepacks.some(
|
||||||
@ -93,5 +114,7 @@ export const getNewRepacksFromXatab = async (
|
|||||||
|
|
||||||
await savePage(newRepacks);
|
await savePage(newRepacks);
|
||||||
|
|
||||||
|
if (page === totalPages) return;
|
||||||
|
|
||||||
return getNewRepacksFromXatab(existingRepacks, page + 1);
|
return getNewRepacksFromXatab(existingRepacks, page + 1);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { getSteamAppAsset } from "@main/helpers";
|
|
||||||
|
|
||||||
export interface SteamGridResponse {
|
export interface SteamGridResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@ -59,16 +58,11 @@ export const getSteamGridGameById = async (
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSteamGameIconUrl = async (objectID: string) => {
|
export const getSteamGameClientIcon = async (objectID: string) => {
|
||||||
const {
|
const {
|
||||||
data: { id: steamGridGameId },
|
data: { id: steamGridGameId },
|
||||||
} = await getSteamGridData(objectID, "games", "steam");
|
} = await getSteamGridData(objectID, "games", "steam");
|
||||||
|
|
||||||
const steamGridGame = await getSteamGridGameById(steamGridGameId);
|
const steamGridGame = await getSteamGridGameById(steamGridGameId);
|
||||||
|
return steamGridGame.data.platforms.steam.metadata.clienticon;
|
||||||
return getSteamAppAsset(
|
|
||||||
"icon",
|
|
||||||
objectID,
|
|
||||||
steamGridGame.data.platforms.steam.metadata.clienticon
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -3,107 +3,9 @@ import { app } from "electron";
|
|||||||
|
|
||||||
import { chunk } from "lodash-es";
|
import { chunk } from "lodash-es";
|
||||||
|
|
||||||
import { createDataSource, dataSource } from "@main/data-source";
|
import { createDataSource } from "@main/data-source";
|
||||||
import { Repack, RepackerFriendlyName, SteamGame } from "@main/entity";
|
import { Repack } from "@main/entity";
|
||||||
import {
|
import { repackRepository } from "@main/repository";
|
||||||
migrationScriptRepository,
|
|
||||||
repackRepository,
|
|
||||||
repackerFriendlyNameRepository,
|
|
||||||
steamGameRepository,
|
|
||||||
} from "@main/repository";
|
|
||||||
import { MigrationScript } from "@main/entity/migration-script.entity";
|
|
||||||
import { Like } from "typeorm";
|
|
||||||
|
|
||||||
const migrationScripts = {
|
|
||||||
/*
|
|
||||||
0.0.6 -> 0.0.7
|
|
||||||
Xatab repacks were previously created with an incorrect upload date.
|
|
||||||
This migration script will update the upload date of all Xatab repacks.
|
|
||||||
*/
|
|
||||||
"0.0.7": async (updateRepacks: Repack[]) => {
|
|
||||||
const VERSION = "0.0.7";
|
|
||||||
|
|
||||||
const migrationScript = await migrationScriptRepository.findOne({
|
|
||||||
where: {
|
|
||||||
version: VERSION,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!migrationScript) {
|
|
||||||
const xatabRepacks = updateRepacks.filter(
|
|
||||||
(repack) => repack.repacker === "Xatab"
|
|
||||||
);
|
|
||||||
|
|
||||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
|
||||||
await Promise.all(
|
|
||||||
xatabRepacks.map((repack) =>
|
|
||||||
transactionalEntityManager.getRepository(Repack).update(
|
|
||||||
{
|
|
||||||
title: repack.title,
|
|
||||||
repacker: repack.repacker,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uploadDate: repack.uploadDate,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
await transactionalEntityManager.getRepository(MigrationScript).insert({
|
|
||||||
version: VERSION,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/*
|
|
||||||
1.0.1 -> 1.1.0
|
|
||||||
A few torrents scraped from 1337x were previously created with an incorrect upload date.
|
|
||||||
*/
|
|
||||||
"1.1.0": async () => {
|
|
||||||
const VERSION = "1.1.0";
|
|
||||||
|
|
||||||
const migrationScript = await migrationScriptRepository.findOne({
|
|
||||||
where: {
|
|
||||||
version: VERSION,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!migrationScript) {
|
|
||||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
|
||||||
const repacks = await transactionalEntityManager
|
|
||||||
.getRepository(Repack)
|
|
||||||
.find({
|
|
||||||
where: {
|
|
||||||
uploadDate: Like("1%"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
repacks.map(async (repack) => {
|
|
||||||
return transactionalEntityManager
|
|
||||||
.getRepository(Repack)
|
|
||||||
.update(
|
|
||||||
{ id: repack.id },
|
|
||||||
{ uploadDate: new Date(repack.uploadDate) }
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await transactionalEntityManager.getRepository(MigrationScript).insert({
|
|
||||||
version: VERSION,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const runMigrationScripts = async (updateRepacks: Repack[]) => {
|
|
||||||
return Promise.all(
|
|
||||||
Object.values(migrationScripts).map((migrationScript) => {
|
|
||||||
return migrationScript(updateRepacks);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resolveDatabaseUpdates = async () => {
|
export const resolveDatabaseUpdates = async () => {
|
||||||
const updateDataSource = createDataSource({
|
const updateDataSource = createDataSource({
|
||||||
@ -114,25 +16,8 @@ export const resolveDatabaseUpdates = async () => {
|
|||||||
|
|
||||||
return updateDataSource.initialize().then(async () => {
|
return updateDataSource.initialize().then(async () => {
|
||||||
const updateRepackRepository = updateDataSource.getRepository(Repack);
|
const updateRepackRepository = updateDataSource.getRepository(Repack);
|
||||||
const updateRepackerFriendlyNameRepository =
|
|
||||||
updateDataSource.getRepository(RepackerFriendlyName);
|
|
||||||
const updateSteamGameRepository = updateDataSource.getRepository(SteamGame);
|
|
||||||
|
|
||||||
const [updateRepacks, updateSteamGames, updateRepackerFriendlyNames] =
|
const updateRepacks = await updateRepackRepository.find();
|
||||||
await Promise.all([
|
|
||||||
updateRepackRepository.find(),
|
|
||||||
updateSteamGameRepository.find(),
|
|
||||||
updateRepackerFriendlyNameRepository.find(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await runMigrationScripts(updateRepacks);
|
|
||||||
|
|
||||||
await repackerFriendlyNameRepository
|
|
||||||
.createQueryBuilder()
|
|
||||||
.insert()
|
|
||||||
.values(updateRepackerFriendlyNames)
|
|
||||||
.orIgnore()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
const updateRepacksChunks = chunk(updateRepacks, 800);
|
const updateRepacksChunks = chunk(updateRepacks, 800);
|
||||||
|
|
||||||
@ -144,16 +29,5 @@ export const resolveDatabaseUpdates = async () => {
|
|||||||
.orIgnore()
|
.orIgnore()
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
const steamGamesChunks = chunk(updateSteamGames, 800);
|
|
||||||
|
|
||||||
for (const chunk of steamGamesChunks) {
|
|
||||||
await steamGameRepository
|
|
||||||
.createQueryBuilder()
|
|
||||||
.insert()
|
|
||||||
.values(chunk)
|
|
||||||
.orIgnore()
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
import { BrowserWindow, Menu, Tray, app } from "electron";
|
import {
|
||||||
|
BrowserWindow,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuItemConstructorOptions,
|
||||||
|
Tray,
|
||||||
|
app,
|
||||||
|
shell,
|
||||||
|
} from "electron";
|
||||||
import { is } from "@electron-toolkit/utils";
|
import { is } from "@electron-toolkit/utils";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import icon from "@resources/icon.png?asset";
|
import icon from "@resources/icon.png?asset";
|
||||||
import trayIcon from "@resources/tray-icon.png?asset";
|
import trayIcon from "@resources/tray-icon.png?asset";
|
||||||
import { userPreferencesRepository } from "@main/repository";
|
import { gameRepository, userPreferencesRepository } from "@main/repository";
|
||||||
|
import { IsNull, Not } from "typeorm";
|
||||||
|
|
||||||
export class WindowManager {
|
export class WindowManager {
|
||||||
public static mainWindow: Electron.BrowserWindow | null = null;
|
public static mainWindow: Electron.BrowserWindow | null = null;
|
||||||
|
public static splashWindow: Electron.BrowserWindow | null = null;
|
||||||
|
public static isReadyToShowMainWindow = false;
|
||||||
|
|
||||||
private static loadURL(hash = "") {
|
private static loadURL(hash = "") {
|
||||||
// HMR for renderer base on electron-vite cli.
|
// HMR for renderer base on electron-vite cli.
|
||||||
@ -26,13 +37,51 @@ export class WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static loadSplashURL() {
|
||||||
|
// HMR for renderer base on electron-vite cli.
|
||||||
|
// Load the remote URL for development or the local html file for production.
|
||||||
|
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||||
|
this.splashWindow?.loadURL(
|
||||||
|
`${process.env["ELECTRON_RENDERER_URL"]}#/splash`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.splashWindow?.loadFile(
|
||||||
|
path.join(__dirname, "../renderer/index.html"),
|
||||||
|
{
|
||||||
|
hash: "splash",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createSplashScreen() {
|
||||||
|
if (this.splashWindow) return;
|
||||||
|
|
||||||
|
this.splashWindow = new BrowserWindow({
|
||||||
|
width: 380,
|
||||||
|
height: 380,
|
||||||
|
frame: false,
|
||||||
|
resizable: false,
|
||||||
|
backgroundColor: "#1c1c1c",
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, "../preload/index.mjs"),
|
||||||
|
sandbox: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadSplashURL();
|
||||||
|
this.splashWindow.removeMenu();
|
||||||
|
}
|
||||||
|
|
||||||
public static createMainWindow() {
|
public static createMainWindow() {
|
||||||
// Create the browser window.
|
if (this.mainWindow || !this.isReadyToShowMainWindow) return;
|
||||||
|
|
||||||
this.mainWindow = new BrowserWindow({
|
this.mainWindow = new BrowserWindow({
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 720,
|
height: 720,
|
||||||
minWidth: 1024,
|
minWidth: 1024,
|
||||||
minHeight: 540,
|
minHeight: 540,
|
||||||
|
backgroundColor: "#1c1c1c",
|
||||||
titleBarStyle: "hidden",
|
titleBarStyle: "hidden",
|
||||||
...(process.platform === "linux" ? { icon } : {}),
|
...(process.platform === "linux" ? { icon } : {}),
|
||||||
trafficLightPosition: { x: 16, y: 16 },
|
trafficLightPosition: { x: 16, y: 16 },
|
||||||
@ -66,6 +115,12 @@ export class WindowManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static prepareMainWindowAndCloseSplash() {
|
||||||
|
this.isReadyToShowMainWindow = true;
|
||||||
|
this.splashWindow?.close();
|
||||||
|
this.createMainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
public static redirect(hash: string) {
|
public static redirect(hash: string) {
|
||||||
if (!this.mainWindow) this.createMainWindow();
|
if (!this.mainWindow) this.createMainWindow();
|
||||||
this.loadURL(hash);
|
this.loadURL(hash);
|
||||||
@ -77,33 +132,66 @@ export class WindowManager {
|
|||||||
public static createSystemTray(language: string) {
|
public static createSystemTray(language: string) {
|
||||||
const tray = new Tray(trayIcon);
|
const tray = new Tray(trayIcon);
|
||||||
|
|
||||||
const contextMenu = Menu.buildFromTemplate([
|
const updateSystemTray = async () => {
|
||||||
{
|
const games = await gameRepository.find({
|
||||||
label: t("open", {
|
where: {
|
||||||
ns: "system_tray",
|
isDeleted: false,
|
||||||
lng: language,
|
executablePath: Not(IsNull()),
|
||||||
}),
|
lastTimePlayed: Not(IsNull()),
|
||||||
type: "normal",
|
|
||||||
click: () => {
|
|
||||||
if (this.mainWindow) {
|
|
||||||
this.mainWindow.show();
|
|
||||||
} else {
|
|
||||||
this.createMainWindow();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
take: 5,
|
||||||
{
|
order: {
|
||||||
label: t("quit", {
|
updatedAt: "DESC",
|
||||||
ns: "system_tray",
|
},
|
||||||
lng: language,
|
});
|
||||||
}),
|
|
||||||
type: "normal",
|
const recentlyPlayedGames: Array<MenuItemConstructorOptions | MenuItem> =
|
||||||
click: () => app.quit(),
|
games.map(({ title, executablePath }) => ({
|
||||||
},
|
label: title,
|
||||||
]);
|
type: "normal",
|
||||||
|
click: async () => {
|
||||||
|
if (!executablePath) return;
|
||||||
|
|
||||||
|
shell.openPath(executablePath);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: t("open", {
|
||||||
|
ns: "system_tray",
|
||||||
|
lng: language,
|
||||||
|
}),
|
||||||
|
type: "normal",
|
||||||
|
click: () => {
|
||||||
|
if (this.mainWindow) {
|
||||||
|
this.mainWindow.show();
|
||||||
|
} else {
|
||||||
|
this.createMainWindow();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
...recentlyPlayedGames,
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("quit", {
|
||||||
|
ns: "system_tray",
|
||||||
|
lng: language,
|
||||||
|
}),
|
||||||
|
type: "normal",
|
||||||
|
click: () => app.quit(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return contextMenu;
|
||||||
|
};
|
||||||
|
|
||||||
tray.setToolTip("Hydra");
|
tray.setToolTip("Hydra");
|
||||||
tray.setContextMenu(contextMenu);
|
|
||||||
|
|
||||||
if (process.platform === "win32" || process.platform === "linux") {
|
if (process.platform === "win32" || process.platform === "linux") {
|
||||||
tray.addListener("click", () => {
|
tray.addListener("click", () => {
|
||||||
@ -117,6 +205,11 @@ export class WindowManager {
|
|||||||
|
|
||||||
this.createMainWindow();
|
this.createMainWindow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tray.addListener("right-click", async () => {
|
||||||
|
const contextMenu = await updateSystemTray();
|
||||||
|
tray.popUpContextMenu(contextMenu);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import type { Repack, RepackerFriendlyName, SteamGame } from "@main/entity";
|
import type { Repack } from "@main/entity";
|
||||||
|
import type { SteamGame } from "@types";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
repacks: Repack[];
|
repacks: Repack[];
|
||||||
repackersFriendlyNames: RepackerFriendlyName[];
|
|
||||||
steamGames: SteamGame[];
|
steamGames: SteamGame[];
|
||||||
eventResults: Map<[string, any[]], any>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
repacks: [],
|
repacks: [],
|
||||||
repackersFriendlyNames: [],
|
|
||||||
steamGames: [],
|
steamGames: [],
|
||||||
eventResults: new Map(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class StateManager {
|
export class StateManager {
|
||||||
|
1
src/main/vite-env.d.ts
vendored
1
src/main/vite-env.d.ts
vendored
@ -4,7 +4,6 @@ interface ImportMetaEnv {
|
|||||||
readonly MAIN_VITE_STEAMGRIDDB_API_KEY: string;
|
readonly MAIN_VITE_STEAMGRIDDB_API_KEY: string;
|
||||||
readonly MAIN_VITE_ONLINEFIX_USERNAME: string;
|
readonly MAIN_VITE_ONLINEFIX_USERNAME: string;
|
||||||
readonly MAIN_VITE_ONLINEFIX_PASSWORD: string;
|
readonly MAIN_VITE_ONLINEFIX_PASSWORD: string;
|
||||||
readonly MAIN_VITE_SENTRY_DSN: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
@ -4,13 +4,11 @@ import parseTorrent from "parse-torrent";
|
|||||||
const port = parentPort;
|
const port = parentPort;
|
||||||
if (!port) throw new Error("IllegalState");
|
if (!port) throw new Error("IllegalState");
|
||||||
|
|
||||||
export const getFileBuffer = async (url: string) =>
|
port.on("message", async (buffer: Buffer) => {
|
||||||
fetch(url, { method: "GET" }).then((response) =>
|
try {
|
||||||
response.arrayBuffer().then((buffer) => Buffer.from(buffer))
|
const torrent = await parseTorrent(buffer);
|
||||||
);
|
port.postMessage(torrent);
|
||||||
|
} catch (err) {
|
||||||
port.on("message", async (url: string) => {
|
port.postMessage(null);
|
||||||
const buffer = await getFileBuffer(url);
|
}
|
||||||
const torrent = await parseTorrent(buffer);
|
|
||||||
port.postMessage(torrent);
|
|
||||||
});
|
});
|
||||||
|
@ -7,6 +7,7 @@ import type {
|
|||||||
GameShop,
|
GameShop,
|
||||||
TorrentProgress,
|
TorrentProgress,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
|
AppUpdaterEvents,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("electron", {
|
contextBridge.exposeInMainWorld("electron", {
|
||||||
@ -76,8 +77,6 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
executablePath
|
executablePath
|
||||||
),
|
),
|
||||||
getLibrary: () => ipcRenderer.invoke("getLibrary"),
|
getLibrary: () => ipcRenderer.invoke("getLibrary"),
|
||||||
getRepackersFriendlyNames: () =>
|
|
||||||
ipcRenderer.invoke("getRepackersFriendlyNames"),
|
|
||||||
openGameInstaller: (gameId: number) =>
|
openGameInstaller: (gameId: number) =>
|
||||||
ipcRenderer.invoke("openGameInstaller", gameId),
|
ipcRenderer.invoke("openGameInstaller", gameId),
|
||||||
openGame: (gameId: number, executablePath: string) =>
|
openGame: (gameId: number, executablePath: string) =>
|
||||||
@ -114,4 +113,21 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
showOpenDialog: (options: Electron.OpenDialogOptions) =>
|
showOpenDialog: (options: Electron.OpenDialogOptions) =>
|
||||||
ipcRenderer.invoke("showOpenDialog", options),
|
ipcRenderer.invoke("showOpenDialog", options),
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
|
|
||||||
|
/* Splash */
|
||||||
|
onAutoUpdaterEvent: (cb: (value: AppUpdaterEvents) => void) => {
|
||||||
|
const listener = (
|
||||||
|
_event: Electron.IpcRendererEvent,
|
||||||
|
value: AppUpdaterEvents
|
||||||
|
) => cb(value);
|
||||||
|
|
||||||
|
ipcRenderer.on("autoUpdaterEvent", listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipcRenderer.removeListener("autoUpdaterEvent", listener);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
checkForUpdates: () => ipcRenderer.invoke("checkForUpdates"),
|
||||||
|
restartAndInstallUpdate: () => ipcRenderer.invoke("restartAndInstallUpdate"),
|
||||||
|
continueToMainWindow: () => ipcRenderer.invoke("continueToMainWindow"),
|
||||||
});
|
});
|
||||||
|
@ -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 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com; media-src 'self' data: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com;"
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://steamcdn-a.akamaihd.net https://shared.akamai.steamstatic.com https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com; media-src 'self' data: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com https://shared.akamai.steamstatic.com;"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color: #1c1c1c">
|
<body style="background-color: #1c1c1c">
|
||||||
|
@ -12,12 +12,11 @@ import {
|
|||||||
import * as styles from "./app.css";
|
import * as styles from "./app.css";
|
||||||
import { themeClass } from "./theme.css";
|
import { themeClass } from "./theme.css";
|
||||||
|
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
setSearch,
|
setSearch,
|
||||||
clearSearch,
|
clearSearch,
|
||||||
setUserPreferences,
|
setUserPreferences,
|
||||||
setRepackersFriendlyNames,
|
|
||||||
toggleDraggingDisabled,
|
toggleDraggingDisabled,
|
||||||
} from "@renderer/features";
|
} from "@renderer/features";
|
||||||
import { GameStatusHelper } from "@shared";
|
import { GameStatusHelper } from "@shared";
|
||||||
@ -28,7 +27,7 @@ export interface AppProps {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function App({ children }: AppProps) {
|
export function App() {
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
const { updateLibrary } = useLibrary();
|
const { updateLibrary } = useLibrary();
|
||||||
|
|
||||||
@ -45,14 +44,11 @@ export function App({ children }: AppProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
Promise.all([window.electron.getUserPreferences(), updateLibrary()]).then(
|
||||||
window.electron.getUserPreferences(),
|
([preferences]) => {
|
||||||
window.electron.getRepackersFriendlyNames(),
|
dispatch(setUserPreferences(preferences));
|
||||||
updateLibrary(),
|
}
|
||||||
]).then(([preferences, repackersFriendlyNames]) => {
|
);
|
||||||
dispatch(setUserPreferences(preferences));
|
|
||||||
dispatch(setRepackersFriendlyNames(repackersFriendlyNames));
|
|
||||||
});
|
|
||||||
}, [navigate, location.pathname, dispatch, updateLibrary]);
|
}, [navigate, location.pathname, dispatch, updateLibrary]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -132,7 +128,7 @@ export function App({ children }: AppProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<section ref={contentRef} className={styles.content}>
|
<section ref={contentRef} className={styles.content}>
|
||||||
{children}
|
<Outlet />
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
BIN
src/renderer/src/assets/icon.png
Normal file
BIN
src/renderer/src/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
@ -5,7 +5,6 @@ import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
|||||||
import EpicGamesLogo from "@renderer/assets/epic-games-logo.svg?react";
|
import EpicGamesLogo from "@renderer/assets/epic-games-logo.svg?react";
|
||||||
|
|
||||||
import * as styles from "./game-card.css";
|
import * as styles from "./game-card.css";
|
||||||
import { useAppSelector } from "@renderer/hooks";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export interface GameCardProps
|
export interface GameCardProps
|
||||||
@ -24,10 +23,6 @@ const shopIcon = {
|
|||||||
export function GameCard({ game, ...props }: GameCardProps) {
|
export function GameCard({ game, ...props }: GameCardProps) {
|
||||||
const { t } = useTranslation("game_card");
|
const { t } = useTranslation("game_card");
|
||||||
|
|
||||||
const repackersFriendlyNames = useAppSelector(
|
|
||||||
(state) => state.repackersFriendlyNames.value
|
|
||||||
);
|
|
||||||
|
|
||||||
const uniqueRepackers = Array.from(
|
const uniqueRepackers = Array.from(
|
||||||
new Set(game.repacks.map(({ repacker }) => repacker))
|
new Set(game.repacks.map(({ repacker }) => repacker))
|
||||||
);
|
);
|
||||||
@ -47,7 +42,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
|
|||||||
<ul className={styles.downloadOptions}>
|
<ul className={styles.downloadOptions}>
|
||||||
{uniqueRepackers.map((repacker) => (
|
{uniqueRepackers.map((repacker) => (
|
||||||
<li key={repacker} className={styles.downloadOption}>
|
<li key={repacker} className={styles.downloadOption}>
|
||||||
<span>{repackersFriendlyNames[repacker]}</span>
|
<span>{repacker}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -42,7 +42,7 @@ export function Modal({
|
|||||||
}, [onClose]);
|
}, [onClose]);
|
||||||
|
|
||||||
const isTopMostModal = () => {
|
const isTopMostModal = () => {
|
||||||
const openModals = document.querySelectorAll("[role=modal]");
|
const openModals = document.querySelectorAll("[role=dialog]");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
openModals.length &&
|
openModals.length &&
|
||||||
|
@ -106,6 +106,8 @@ export const menuItemButtonLabel = style({
|
|||||||
export const gameIcon = style({
|
export const gameIcon = style({
|
||||||
width: "20px",
|
width: "20px",
|
||||||
height: "20px",
|
height: "20px",
|
||||||
|
minWidth: "20px",
|
||||||
|
minHeight: "20px",
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
});
|
});
|
||||||
@ -122,36 +124,3 @@ export const section = style({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
paddingBottom: `${SPACING_UNIT}px`,
|
paddingBottom: `${SPACING_UNIT}px`,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sidebarFooter = style({
|
|
||||||
marginTop: "auto",
|
|
||||||
padding: `${SPACING_UNIT * 2}px`,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const footerSocialsContainer = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: `${SPACING_UNIT * 1.5}px`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const footerSocialsItem = style({
|
|
||||||
color: vars.color.bodyText,
|
|
||||||
backgroundColor: vars.color.darkBackground,
|
|
||||||
width: "16px",
|
|
||||||
height: "16px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
transition: "all ease 0.2s",
|
|
||||||
cursor: "pointer",
|
|
||||||
":hover": {
|
|
||||||
opacity: "0.75",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const footerText = style({
|
|
||||||
color: vars.color.bodyText,
|
|
||||||
fontSize: "12px",
|
|
||||||
});
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user