mirror of
				https://github.com/hydralauncher/hydra.git
				synced 2025-03-09 15:40:26 +00:00 
			
		
		
		
	ci: testing pipeline
This commit is contained in:
		
							parent
							
								
									7241869ed0
								
							
						
					
					
						commit
						7bc14c7f5c
					
				
					 27 changed files with 1349 additions and 1399 deletions
				
			
		
							
								
								
									
										78
									
								
								.github/workflows/build.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										78
									
								
								.github/workflows/build.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -8,21 +8,9 @@ jobs:
 | 
			
		|||
  build:
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        os:
 | 
			
		||||
          [
 | 
			
		||||
            {
 | 
			
		||||
              name: windows-latest,
 | 
			
		||||
              build_path: out/Hydra-win32-x64,
 | 
			
		||||
              artifact: Hydra-win32-x64,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              name: ubuntu-latest,
 | 
			
		||||
              build_path: out/Hydra-linux-x64,
 | 
			
		||||
              artifact: Hydra-linux-x64,
 | 
			
		||||
            },
 | 
			
		||||
          ]
 | 
			
		||||
        os: [windows-latest, ubuntu-latest]
 | 
			
		||||
 | 
			
		||||
    runs-on: ${{ matrix.os.name }}
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out Git repository
 | 
			
		||||
| 
						 | 
				
			
			@ -47,10 +35,10 @@ jobs:
 | 
			
		|||
      - name: Build with cx_Freeze
 | 
			
		||||
        run: python torrent-client/setup.py build
 | 
			
		||||
 | 
			
		||||
      - name: Publish
 | 
			
		||||
        run: yarn run publish
 | 
			
		||||
      - name: Build Linux
 | 
			
		||||
        if: matrix.os == 'ubuntu-latest'
 | 
			
		||||
        run: yarn build:linux
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          MAIN_VITE_STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
 | 
			
		||||
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
 | 
			
		||||
          MAIN_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
 | 
			
		||||
| 
						 | 
				
			
			@ -58,15 +46,55 @@ jobs:
 | 
			
		|||
          MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
 | 
			
		||||
          MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
 | 
			
		||||
 | 
			
		||||
      - name: VirusTotal Scan
 | 
			
		||||
        uses: crazy-max/ghaction-virustotal@v4
 | 
			
		||||
        with:
 | 
			
		||||
          vt_api_key: ${{ secrets.VT_API_KEY }}
 | 
			
		||||
          files: |
 | 
			
		||||
            ./hydra-download-manager/hydra-download-manager.exe
 | 
			
		||||
      - name: Build Windows
 | 
			
		||||
        if: matrix.os == 'windows-latest'
 | 
			
		||||
        run: yarn build:win
 | 
			
		||||
        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_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
 | 
			
		||||
 | 
			
		||||
      - name: Create artifact
 | 
			
		||||
        uses: actions/upload-artifact@v4
 | 
			
		||||
        with:
 | 
			
		||||
          name: ${{ matrix.os.artifact }}
 | 
			
		||||
          path: ${{ matrix.os.build_path }}
 | 
			
		||||
          name: Build-${{ matrix.os }}
 | 
			
		||||
          path: |
 | 
			
		||||
            dist/*.exe
 | 
			
		||||
            dist/*.zip
 | 
			
		||||
            dist/*.dmg
 | 
			
		||||
            dist/*.AppImage
 | 
			
		||||
            dist/*.snap
 | 
			
		||||
            dist/*.deb
 | 
			
		||||
            dist/*.rpm
 | 
			
		||||
            dist/*.tar.gz
 | 
			
		||||
            dist/*.yml
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
      - name: Publish
 | 
			
		||||
        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 }}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,7 @@ export default defineConfig(({ mode }) => {
 | 
			
		|||
        alias: {
 | 
			
		||||
          "@main": resolve("src/main"),
 | 
			
		||||
          "@locales": resolve("src/locales"),
 | 
			
		||||
          "@resources": resolve("resources"),
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      plugins: [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,7 @@
 | 
			
		|||
    "build": "npm run typecheck && electron-vite build",
 | 
			
		||||
    "postinstall": "electron-builder install-app-deps && node ./postinstall.cjs",
 | 
			
		||||
    "build:unpack": "npm run build && electron-builder --dir",
 | 
			
		||||
    "build:win": "npm run build && electron-builder --win",
 | 
			
		||||
    "build:win": "electron-builder --win",
 | 
			
		||||
    "build:mac": "electron-vite build && electron-builder --mac",
 | 
			
		||||
    "build:linux": "electron-vite build && electron-builder --linux"
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +70,7 @@
 | 
			
		|||
    "@types/jsdom": "^21.1.6",
 | 
			
		||||
    "@types/lodash-es": "^4.17.12",
 | 
			
		||||
    "@types/node": "^20.12.7",
 | 
			
		||||
    "@types/parse-torrent": "^5.8.7",
 | 
			
		||||
    "@types/react": "^18.2.48",
 | 
			
		||||
    "@types/react-dom": "^18.2.18",
 | 
			
		||||
    "@vanilla-extract/vite-plugin": "^4.0.7",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,9 @@
 | 
			
		|||
import path from "node:path";
 | 
			
		||||
 | 
			
		||||
import { gameRepository } from "@main/repository";
 | 
			
		||||
import { getProcesses } from "@main/helpers";
 | 
			
		||||
 | 
			
		||||
import { registerEvent } from "../register-event";
 | 
			
		||||
import { getProcesses } from "@main/helpers";
 | 
			
		||||
 | 
			
		||||
const closeGame = async (
 | 
			
		||||
  _event: Electron.IpcMainInvokeEvent,
 | 
			
		||||
| 
						 | 
				
			
			@ -12,13 +12,17 @@ const closeGame = async (
 | 
			
		|||
  const processes = await getProcesses();
 | 
			
		||||
  const game = await gameRepository.findOne({ where: { id: gameId } });
 | 
			
		||||
 | 
			
		||||
  const gameProcess = processes.find((runningProcess) => {
 | 
			
		||||
    const basename = path.win32.basename(game.executablePath);
 | 
			
		||||
    const basenameWithoutExtension = path.win32.basename(
 | 
			
		||||
      game.executablePath,
 | 
			
		||||
      path.extname(game.executablePath)
 | 
			
		||||
    );
 | 
			
		||||
  if (!game) return false;
 | 
			
		||||
 | 
			
		||||
  const executablePath = game.executablePath!;
 | 
			
		||||
 | 
			
		||||
  const basename = path.win32.basename(executablePath);
 | 
			
		||||
  const basenameWithoutExtension = path.win32.basename(
 | 
			
		||||
    executablePath,
 | 
			
		||||
    path.extname(executablePath)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const gameProcess = processes.find((runningProcess) => {
 | 
			
		||||
    if (process.platform === "win32") {
 | 
			
		||||
      return runningProcess.name === basename;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import {
 | 
			
		|||
  getNewGOGGames,
 | 
			
		||||
  getNewRepacksFromCPG,
 | 
			
		||||
  getNewRepacksFromUser,
 | 
			
		||||
  // getNewRepacksFromXatab,
 | 
			
		||||
  getNewRepacksFromXatab,
 | 
			
		||||
  getNewRepacksFromOnlineFix,
 | 
			
		||||
  readPipe,
 | 
			
		||||
  startProcessWatcher,
 | 
			
		||||
| 
						 | 
				
			
			@ -22,13 +22,6 @@ import { Repack } from "./entity";
 | 
			
		|||
import { Notification } from "electron";
 | 
			
		||||
import { t } from "i18next";
 | 
			
		||||
import { In } from "typeorm";
 | 
			
		||||
import creatWorker from "./workers/test?nodeWorker";
 | 
			
		||||
 | 
			
		||||
creatWorker({ workerData: "worker" })
 | 
			
		||||
  .on("message", (message) => {
 | 
			
		||||
    console.log(`\nMessage from worker: ${message}`);
 | 
			
		||||
  })
 | 
			
		||||
  .postMessage("");
 | 
			
		||||
 | 
			
		||||
startProcessWatcher();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,9 +73,9 @@ const checkForNewRepacks = async () => {
 | 
			
		|||
    getNewGOGGames(
 | 
			
		||||
      existingRepacks.filter((repack) => repack.repacker === "GOG")
 | 
			
		||||
    ),
 | 
			
		||||
    // getNewRepacksFromXatab(
 | 
			
		||||
    //   existingRepacks.filter((repack) => repack.repacker === "Xatab")
 | 
			
		||||
    // ),
 | 
			
		||||
    getNewRepacksFromXatab(
 | 
			
		||||
      existingRepacks.filter((repack) => repack.repacker === "Xatab")
 | 
			
		||||
    ),
 | 
			
		||||
    getNewRepacksFromCPG(
 | 
			
		||||
      existingRepacks.filter((repack) => repack.repacker === "CPG")
 | 
			
		||||
    ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ import { formatUploadDate } from "@main/helpers";
 | 
			
		|||
 | 
			
		||||
import { Repack } from "@main/entity";
 | 
			
		||||
import { requestWebPage, savePage } from "./helpers";
 | 
			
		||||
import type { GameRepackInput } from "./helpers";
 | 
			
		||||
 | 
			
		||||
export const request1337x = async (path: string) =>
 | 
			
		||||
  requestWebPage(`https://1337xx.to${path}`);
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +67,7 @@ export const extractTorrentsFromDocument = async (
 | 
			
		|||
  user: string,
 | 
			
		||||
  document: Document,
 | 
			
		||||
  existingRepacks: Repack[] = []
 | 
			
		||||
): Promise<GameRepackInput[]> => {
 | 
			
		||||
) => {
 | 
			
		||||
  const $trs = Array.from(document.querySelectorAll("tbody tr"));
 | 
			
		||||
 | 
			
		||||
  return Promise.all(
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +107,7 @@ export const getNewRepacksFromUser = async (
 | 
			
		|||
  user: string,
 | 
			
		||||
  existingRepacks: Repack[],
 | 
			
		||||
  page = 1
 | 
			
		||||
): Promise<Repack[]> => {
 | 
			
		||||
) => {
 | 
			
		||||
  const response = await request1337x(`/user/${user}/${page}`);
 | 
			
		||||
  const { window } = new JSDOM(response);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@ import { JSDOM } from "jsdom";
 | 
			
		|||
import { Repack } from "@main/entity";
 | 
			
		||||
 | 
			
		||||
import { requestWebPage, savePage } from "./helpers";
 | 
			
		||||
import type { GameRepackInput } from "./helpers";
 | 
			
		||||
import { logger } from "../logger";
 | 
			
		||||
 | 
			
		||||
export const getNewRepacksFromCPG = async (
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +13,7 @@ export const getNewRepacksFromCPG = async (
 | 
			
		|||
 | 
			
		||||
  const { window } = new JSDOM(data);
 | 
			
		||||
 | 
			
		||||
  const repacks: GameRepackInput[] = [];
 | 
			
		||||
  const repacks = [];
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    Array.from(window.document.querySelectorAll(".post")).forEach(($post) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
import { JSDOM, VirtualConsole } from "jsdom";
 | 
			
		||||
import { GameRepackInput, requestWebPage, savePage } from "./helpers";
 | 
			
		||||
import { requestWebPage, savePage } from "./helpers";
 | 
			
		||||
import { Repack } from "@main/entity";
 | 
			
		||||
import { logger } from "../logger";
 | 
			
		||||
 | 
			
		||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
 | 
			
		||||
 | 
			
		||||
const virtualConsole = new VirtualConsole();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,43 +37,35 @@ const getGOGGame = async (url: string) => {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
export const getNewGOGGames = async (existingRepacks: Repack[] = []) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const data = await requestWebPage(
 | 
			
		||||
      "https://freegogpcgames.com/a-z-games-list/"
 | 
			
		||||
    );
 | 
			
		||||
  const data = await requestWebPage(
 | 
			
		||||
    "https://freegogpcgames.com/a-z-games-list/"
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    const { window } = new JSDOM(data, { virtualConsole });
 | 
			
		||||
  const { window } = new JSDOM(data, { virtualConsole });
 | 
			
		||||
 | 
			
		||||
    const $uls = Array.from(window.document.querySelectorAll(".az-columns"));
 | 
			
		||||
  const $uls = Array.from(window.document.querySelectorAll(".az-columns"));
 | 
			
		||||
 | 
			
		||||
    for (const $ul of $uls) {
 | 
			
		||||
      const repacks: GameRepackInput[] = [];
 | 
			
		||||
      const $lis = Array.from($ul.querySelectorAll("li"));
 | 
			
		||||
  for (const $ul of $uls) {
 | 
			
		||||
    const repacks: QueryDeepPartialEntity<Repack>[] = [];
 | 
			
		||||
    const $lis = Array.from($ul.querySelectorAll("li"));
 | 
			
		||||
 | 
			
		||||
      for (const $li of $lis) {
 | 
			
		||||
        const $a = $li.querySelector("a");
 | 
			
		||||
        const href = $a.href;
 | 
			
		||||
    for (const $li of $lis) {
 | 
			
		||||
      const $a = $li.querySelector("a");
 | 
			
		||||
      const href = $a.href;
 | 
			
		||||
 | 
			
		||||
        const title = $a.textContent.trim();
 | 
			
		||||
      const title = $a.textContent.trim();
 | 
			
		||||
 | 
			
		||||
        const gameExists = existingRepacks.some(
 | 
			
		||||
          (existingRepack) => existingRepack.title === title
 | 
			
		||||
        );
 | 
			
		||||
      const gameExists = existingRepacks.some(
 | 
			
		||||
        (existingRepack) => existingRepack.title === title
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
        if (!gameExists) {
 | 
			
		||||
          try {
 | 
			
		||||
            const game = await getGOGGame(href);
 | 
			
		||||
      if (!gameExists) {
 | 
			
		||||
        const game = await getGOGGame(href);
 | 
			
		||||
 | 
			
		||||
            repacks.push({ ...game, title });
 | 
			
		||||
          } catch (err) {
 | 
			
		||||
            logger.error(err.message, { method: "getGOGGame", url: href });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        repacks.push({ ...game, title });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (repacks.length) await savePage(repacks);
 | 
			
		||||
    }
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    logger.error(err.message, { method: "getNewGOGGames" });
 | 
			
		||||
 | 
			
		||||
    if (repacks.length) await savePage(repacks);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,5 @@
 | 
			
		|||
import { Repack } from "@main/entity";
 | 
			
		||||
import { savePage } from "./helpers";
 | 
			
		||||
import type { GameRepackInput } from "./helpers";
 | 
			
		||||
import { logger } from "../logger";
 | 
			
		||||
import parseTorrent, {
 | 
			
		||||
  toMagnetURI,
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +84,7 @@ export const getNewRepacksFromOnlineFix = async (
 | 
			
		|||
  });
 | 
			
		||||
  const document = new JSDOM(home.body).window.document;
 | 
			
		||||
 | 
			
		||||
  const repacks: GameRepackInput[] = [];
 | 
			
		||||
  const repacks = [];
 | 
			
		||||
  const articles = Array.from(document.querySelectorAll(".news"));
 | 
			
		||||
  const totalPages = Number(
 | 
			
		||||
    document.querySelector("nav > a:nth-child(13)")?.textContent
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +1,14 @@
 | 
			
		|||
import { JSDOM } from "jsdom";
 | 
			
		||||
 | 
			
		||||
import parseTorrent, { toMagnetURI } from "parse-torrent";
 | 
			
		||||
 | 
			
		||||
import { Repack } from "@main/entity";
 | 
			
		||||
import { logger } from "../logger";
 | 
			
		||||
import { requestWebPage, savePage } from "./helpers";
 | 
			
		||||
import type { GameRepackInput } from "./helpers";
 | 
			
		||||
 | 
			
		||||
const getTorrentBuffer = (url: string) =>
 | 
			
		||||
  fetch(url, { method: "GET" }).then((response) =>
 | 
			
		||||
    response.arrayBuffer().then((buffer) => Buffer.from(buffer))
 | 
			
		||||
  );
 | 
			
		||||
import createWorker from "@main/workers/torrent-parser.worker?nodeWorker";
 | 
			
		||||
import { toMagnetURI } from "parse-torrent";
 | 
			
		||||
import type { Instance } from "parse-torrent";
 | 
			
		||||
 | 
			
		||||
const worker = createWorker({});
 | 
			
		||||
 | 
			
		||||
const formatXatabDate = (str: string) => {
 | 
			
		||||
  const date = new Date();
 | 
			
		||||
| 
						 | 
				
			
			@ -28,29 +26,36 @@ const formatXatabDate = (str: string) => {
 | 
			
		|||
const formatXatabDownloadSize = (str: string) =>
 | 
			
		||||
  str.replace(",", ".").replace(/Гб/g, "GB").replace(/Мб/g, "MB");
 | 
			
		||||
 | 
			
		||||
const getXatabRepack = async (url: string) => {
 | 
			
		||||
  const data = await requestWebPage(url);
 | 
			
		||||
  const { window } = new JSDOM(data);
 | 
			
		||||
  const { document } = window;
 | 
			
		||||
const getXatabRepack = (url: string) => {
 | 
			
		||||
  return new Promise((resolve) => {
 | 
			
		||||
    (async () => {
 | 
			
		||||
      const data = await requestWebPage(url);
 | 
			
		||||
      const { window } = new JSDOM(data);
 | 
			
		||||
      const { document } = window;
 | 
			
		||||
 | 
			
		||||
  const $uploadDate = document.querySelector(".entry__date");
 | 
			
		||||
  const $size = document.querySelector(".entry__info-size");
 | 
			
		||||
      const $uploadDate = document.querySelector(".entry__date");
 | 
			
		||||
      const $size = document.querySelector(".entry__info-size");
 | 
			
		||||
 | 
			
		||||
  const $downloadButton = document.querySelector(
 | 
			
		||||
    ".download-torrent"
 | 
			
		||||
  ) as HTMLAnchorElement;
 | 
			
		||||
      const $downloadButton = document.querySelector(
 | 
			
		||||
        ".download-torrent"
 | 
			
		||||
      ) as HTMLAnchorElement;
 | 
			
		||||
 | 
			
		||||
  if (!$downloadButton) throw new Error("Download button not found");
 | 
			
		||||
      if (!$downloadButton) throw new Error("Download button not found");
 | 
			
		||||
 | 
			
		||||
  const torrentBuffer = await getTorrentBuffer($downloadButton.href);
 | 
			
		||||
      const onMessage = (torrent: Instance) => {
 | 
			
		||||
        resolve({
 | 
			
		||||
          fileSize: formatXatabDownloadSize($size.textContent).toUpperCase(),
 | 
			
		||||
          magnet: toMagnetURI(torrent),
 | 
			
		||||
          uploadDate: formatXatabDate($uploadDate.textContent),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    fileSize: formatXatabDownloadSize($size.textContent).toUpperCase(),
 | 
			
		||||
    magnet: toMagnetURI({
 | 
			
		||||
      infoHash: parseTorrent(torrentBuffer).infoHash,
 | 
			
		||||
    }),
 | 
			
		||||
    uploadDate: formatXatabDate($uploadDate.textContent),
 | 
			
		||||
  };
 | 
			
		||||
        worker.removeListener("message", onMessage);
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      worker.on("message", onMessage);
 | 
			
		||||
      worker.postMessage($downloadButton.href);
 | 
			
		||||
    })();
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getNewRepacksFromXatab = async (
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +66,7 @@ export const getNewRepacksFromXatab = async (
 | 
			
		|||
 | 
			
		||||
  const { window } = new JSDOM(data);
 | 
			
		||||
 | 
			
		||||
  const repacks: GameRepackInput[] = [];
 | 
			
		||||
  const repacks = [];
 | 
			
		||||
 | 
			
		||||
  for (const $a of Array.from(
 | 
			
		||||
    window.document.querySelectorAll(".entry__title a")
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +89,6 @@ export const getNewRepacksFromXatab = async (
 | 
			
		|||
 | 
			
		||||
  const newRepacks = repacks.filter(
 | 
			
		||||
    (repack) =>
 | 
			
		||||
      repack.uploadDate &&
 | 
			
		||||
      !existingRepacks.some(
 | 
			
		||||
        (existingRepack) => existingRepack.title === repack.title
 | 
			
		||||
      )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,8 @@ import { BrowserWindow, Menu, Tray, app } from "electron";
 | 
			
		|||
import { is } from "@electron-toolkit/utils";
 | 
			
		||||
import { t } from "i18next";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import icon from "../../../resources/icon.png?asset";
 | 
			
		||||
import trayIcon from "../../../resources/tray-icon.png?asset";
 | 
			
		||||
import icon from "@resources/icon.png?asset";
 | 
			
		||||
import trayIcon from "@resources/tray-icon.png?asset";
 | 
			
		||||
 | 
			
		||||
export class WindowManager {
 | 
			
		||||
  public static mainWindow: Electron.BrowserWindow | null = null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								src/main/workers/torrent-parser.worker.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/main/workers/torrent-parser.worker.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
import { parentPort } from "worker_threads";
 | 
			
		||||
import parseTorrent from "parse-torrent";
 | 
			
		||||
 | 
			
		||||
const port = parentPort;
 | 
			
		||||
if (!port) throw new Error("IllegalState");
 | 
			
		||||
 | 
			
		||||
const getTorrentBuffer = (url: string) =>
 | 
			
		||||
  fetch(url, { method: "GET" }).then((response) =>
 | 
			
		||||
    response.arrayBuffer().then((buffer) => Buffer.from(buffer))
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
port.on("message", async (url: string) => {
 | 
			
		||||
  const buffer = await getTorrentBuffer(url);
 | 
			
		||||
  const torrent = await parseTorrent(buffer);
 | 
			
		||||
 | 
			
		||||
  port.postMessage(torrent);
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										3
									
								
								src/preload/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/preload/index.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -71,7 +71,8 @@ contextBridge.exposeInMainWorld("electron", {
 | 
			
		|||
  openGame: (gameId: number, executablePath: string) =>
 | 
			
		||||
    ipcRenderer.invoke("openGame", gameId, executablePath),
 | 
			
		||||
  closeGame: (gameId: number) => ipcRenderer.invoke("closeGame", gameId),
 | 
			
		||||
  removeGame: (gameId: number) => ipcRenderer.invoke("removeGame", gameId),
 | 
			
		||||
  removeGameFromLibrary: (gameId: number) =>
 | 
			
		||||
    ipcRenderer.invoke("removeGameFromLibrary", gameId),
 | 
			
		||||
  deleteGameFolder: (gameId: number) =>
 | 
			
		||||
    ipcRenderer.invoke("deleteGameFolder", gameId),
 | 
			
		||||
  getGameByObjectID: (objectID: string) =>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,8 @@ contextBridge.exposeInMainWorld("electron", {
 | 
			
		|||
  openGame: (gameId: number, executablePath: string) =>
 | 
			
		||||
    ipcRenderer.invoke("openGame", gameId, executablePath),
 | 
			
		||||
  closeGame: (gameId: number) => ipcRenderer.invoke("closeGame", gameId),
 | 
			
		||||
  removeGame: (gameId: number) => ipcRenderer.invoke("removeGame", gameId),
 | 
			
		||||
  removeGameFromLibrary: (gameId: number) =>
 | 
			
		||||
    ipcRenderer.invoke("removeGameFromLibrary", gameId),
 | 
			
		||||
  deleteGameFolder: (gameId: number) =>
 | 
			
		||||
    ipcRenderer.invoke("deleteGameFolder", gameId),
 | 
			
		||||
  getGameByObjectID: (objectID: string) =>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,9 +9,8 @@
 | 
			
		|||
      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;"
 | 
			
		||||
    />
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
  <body style="background-color: #1c1c1">
 | 
			
		||||
    <div id="root"></div>
 | 
			
		||||
    <h1>hello</h1>
 | 
			
		||||
    <script type="module" src="/src/main.tsx"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ export function BottomPanel() {
 | 
			
		|||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const status = useMemo(() => {
 | 
			
		||||
    if (isDownloading) {
 | 
			
		||||
    if (isDownloading && game) {
 | 
			
		||||
      if (game.status === "downloading_metadata")
 | 
			
		||||
        return t("downloading_metadata", { title: game.title });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,12 +62,14 @@ export function Modal({
 | 
			
		|||
    const onMouseDown = (e: MouseEvent) => {
 | 
			
		||||
      if (!isTopMostModal()) return;
 | 
			
		||||
 | 
			
		||||
      const clickedOutsideContent = !modalContentRef.current.contains(
 | 
			
		||||
        e.target as Node
 | 
			
		||||
      );
 | 
			
		||||
      if (modalContentRef.current) {
 | 
			
		||||
        const clickedOutsideContent = modalContentRef.current.contains(
 | 
			
		||||
          e.target as Node
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
      if (clickedOutsideContent) {
 | 
			
		||||
        handleCloseClick();
 | 
			
		||||
        if (clickedOutsideContent) {
 | 
			
		||||
          handleCloseClick();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								src/renderer/src/declaration.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/renderer/src/declaration.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -62,7 +62,7 @@ declare global {
 | 
			
		|||
    openGameInstaller: (gameId: number) => Promise<boolean>;
 | 
			
		||||
    openGame: (gameId: number, executablePath: string) => Promise<void>;
 | 
			
		||||
    closeGame: (gameId: number) => Promise<boolean>;
 | 
			
		||||
    removeGame: (gameId: number) => Promise<void>;
 | 
			
		||||
    removeGameFromLibrary: (gameId: number) => Promise<void>;
 | 
			
		||||
    deleteGameFolder: (gameId: number) => Promise<unknown>;
 | 
			
		||||
    getGameByObjectID: (objectID: string) => Promise<Game | null>;
 | 
			
		||||
    onPlaytime: (cb: (gameId: number) => void) => () => Electron.IpcRenderer;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,10 @@ export const userPreferencesSlice = createSlice({
 | 
			
		|||
  name: "userPreferences",
 | 
			
		||||
  initialState,
 | 
			
		||||
  reducers: {
 | 
			
		||||
    setUserPreferences: (state, action: PayloadAction<UserPreferences>) => {
 | 
			
		||||
    setUserPreferences: (
 | 
			
		||||
      state,
 | 
			
		||||
      action: PayloadAction<UserPreferences | null>
 | 
			
		||||
    ) => {
 | 
			
		||||
      state.value = action.payload;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,17 +58,17 @@ export function useDownload() {
 | 
			
		|||
      deleteGame(gameId);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  const removeGame = (gameId: number) =>
 | 
			
		||||
    window.electron.removeGame(gameId).then(() => {
 | 
			
		||||
  const removeGameFromLibrary = (gameId: number) =>
 | 
			
		||||
    window.electron.removeGameFromLibrary(gameId).then(() => {
 | 
			
		||||
      updateLibrary();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  const isVerifying = ["downloading_metadata", "checking_files"].includes(
 | 
			
		||||
    lastPacket?.game.status
 | 
			
		||||
    lastPacket?.game.status ?? ""
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const getETA = () => {
 | 
			
		||||
    if (isVerifying || !isFinite(lastPacket?.timeRemaining)) {
 | 
			
		||||
    if (isVerifying || !isFinite(lastPacket?.timeRemaining ?? 0)) {
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +124,7 @@ export function useDownload() {
 | 
			
		|||
    pauseDownload,
 | 
			
		||||
    resumeDownload,
 | 
			
		||||
    cancelDownload,
 | 
			
		||||
    removeGame,
 | 
			
		||||
    removeGameFromLibrary,
 | 
			
		||||
    deleteGame,
 | 
			
		||||
    isGameDeleting,
 | 
			
		||||
    clearDownload: () => dispatch(clearDownload()),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,6 +33,7 @@ export function Downloads() {
 | 
			
		|||
    numSeeds,
 | 
			
		||||
    pauseDownload,
 | 
			
		||||
    resumeDownload,
 | 
			
		||||
    removeGameFromLibrary,
 | 
			
		||||
    cancelDownload,
 | 
			
		||||
    deleteGame,
 | 
			
		||||
    isGameDeleting,
 | 
			
		||||
| 
						 | 
				
			
			@ -52,11 +53,6 @@ export function Downloads() {
 | 
			
		|||
      updateLibrary();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  const removeGame = (gameId: number) =>
 | 
			
		||||
    window.electron.removeGame(gameId).then(() => {
 | 
			
		||||
      updateLibrary();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  const getFinalDownloadSize = (game: Game) => {
 | 
			
		||||
    const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -194,7 +190,7 @@ export function Downloads() {
 | 
			
		|||
        </Button>
 | 
			
		||||
 | 
			
		||||
        <Button
 | 
			
		||||
          onClick={() => removeGame(game.id)}
 | 
			
		||||
          onClick={() => removeGameFromLibrary(game.id)}
 | 
			
		||||
          theme="outline"
 | 
			
		||||
          disabled={deleting}
 | 
			
		||||
        >
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ export function HeroPanelActions({
 | 
			
		|||
    resumeDownload,
 | 
			
		||||
    pauseDownload,
 | 
			
		||||
    cancelDownload,
 | 
			
		||||
    removeGame,
 | 
			
		||||
    removeGameFromLibrary,
 | 
			
		||||
    isGameDeleting,
 | 
			
		||||
  } = useDownload();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +63,7 @@ export function HeroPanelActions({
 | 
			
		|||
 | 
			
		||||
    try {
 | 
			
		||||
      if (game) {
 | 
			
		||||
        await removeGame(game.id);
 | 
			
		||||
        await removeGameFromLibrary(game.id);
 | 
			
		||||
      } else {
 | 
			
		||||
        const gameExecutablePath = await selectGameExecutable();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -187,7 +187,7 @@ export function HeroPanelActions({
 | 
			
		|||
          {t("open_download_options")}
 | 
			
		||||
        </Button>
 | 
			
		||||
        <Button
 | 
			
		||||
          onClick={() => removeGame(game.id).then(getGame)}
 | 
			
		||||
          onClick={() => removeGameFromLibrary(game.id).then(getGame)}
 | 
			
		||||
          theme="outline"
 | 
			
		||||
          disabled={deleting}
 | 
			
		||||
        >
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ export function SelectFolderModal({
 | 
			
		|||
}: SelectFolderModalProps) {
 | 
			
		||||
  const { t } = useTranslation("game_details");
 | 
			
		||||
 | 
			
		||||
  const [diskFreeSpace, setDiskFreeSpace] = useState<DiskSpace>(null);
 | 
			
		||||
  const [diskFreeSpace, setDiskFreeSpace] = useState<DiskSpace | null>(null);
 | 
			
		||||
  const [selectedPath, setSelectedPath] = useState("");
 | 
			
		||||
  const [downloadStarting, setDownloadStarting] = useState(false);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,7 @@ export function SearchResults() {
 | 
			
		|||
  const [searchResults, setSearchResults] = useState<CatalogueEntry[]>([]);
 | 
			
		||||
  const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const debouncedFunc = useRef<DebouncedFunc<() => void | null>>(null);
 | 
			
		||||
  const debouncedFunc = useRef<DebouncedFunc<() => void> | null>(null);
 | 
			
		||||
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ export function SearchResults() {
 | 
			
		|||
 | 
			
		||||
    debouncedFunc.current = debounce(() => {
 | 
			
		||||
      window.electron
 | 
			
		||||
        .searchGames(searchParams.get("query"))
 | 
			
		||||
        .searchGames(searchParams.get("query") ?? "")
 | 
			
		||||
        .then((results) => {
 | 
			
		||||
          setSearchResults(results);
 | 
			
		||||
        })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,10 +23,10 @@ export function Settings() {
 | 
			
		|||
      setForm({
 | 
			
		||||
        downloadsPath: userPreferences?.downloadsPath || path,
 | 
			
		||||
        downloadNotificationsEnabled:
 | 
			
		||||
          userPreferences?.downloadNotificationsEnabled,
 | 
			
		||||
          userPreferences?.downloadNotificationsEnabled ?? false,
 | 
			
		||||
        repackUpdatesNotificationsEnabled:
 | 
			
		||||
          userPreferences?.repackUpdatesNotificationsEnabled,
 | 
			
		||||
        telemetryEnabled: userPreferences?.telemetryEnabled,
 | 
			
		||||
          userPreferences?.repackUpdatesNotificationsEnabled ?? false,
 | 
			
		||||
        telemetryEnabled: userPreferences?.telemetryEnabled ?? false,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }, []);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,8 @@
 | 
			
		|||
      "@main/*": ["src/main/*"],
 | 
			
		||||
      "@renderer/*": ["src/renderer/*"],
 | 
			
		||||
      "@types": ["src/types/index.ts"],
 | 
			
		||||
      "@locales": ["src/locales/index.ts"]
 | 
			
		||||
      "@locales": ["src/locales/index.ts"],
 | 
			
		||||
      "@resources": ["src/resources/index.ts"]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue