diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 57511227..e2726b79 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -174,12 +174,9 @@ "validate_download_source": "Validate", "remove_download_source": "Remove", "add_download_source": "Add source", - "download_count_zero": "No downloads in list", - "download_count_one": "{{countFormatted}} download in list", - "download_count_other": "{{countFormatted}} downloads in list", - "download_options_zero": "No download available", - "download_options_one": "{{countFormatted}} download available", - "download_options_other": "{{countFormatted}} downloads available", + "download_count_zero": "No download options", + "download_count_one": "{{countFormatted}} download option", + "download_count_other": "{{countFormatted}} download options", "download_source_url": "Download source URL", "add_download_source_description": "Insert the URL containing the .json file", "download_source_up_to_date": "Up-to-date", diff --git a/src/main/data-source.ts b/src/main/data-source.ts index b47ce2c0..a88a8883 100644 --- a/src/main/data-source.ts +++ b/src/main/data-source.ts @@ -6,12 +6,12 @@ import { GameShopCache, Repack, UserPreferences, + UserAuth, } from "@main/entity"; import type { BetterSqlite3ConnectionOptions } from "typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions"; import { databasePath } from "./constants"; import migrations from "./migrations"; -import { UserAuth } from "./entity/user-auth"; export const createDataSource = ( options: Partial diff --git a/src/main/entity/repack.entity.ts b/src/main/entity/repack.entity.ts index 1d5259fd..ff3f16cb 100644 --- a/src/main/entity/repack.entity.ts +++ b/src/main/entity/repack.entity.ts @@ -16,11 +16,14 @@ export class Repack { @Column("text", { unique: true }) title: string; + /** + * @deprecated Use uris instead + */ @Column("text", { unique: true }) magnet: string; /** - * @deprecated + * @deprecated Direct scraping capability has been removed */ @Column("int", { nullable: true }) page: number; @@ -37,6 +40,9 @@ export class Repack { @ManyToOne(() => DownloadSource, { nullable: true, onDelete: "CASCADE" }) downloadSource: DownloadSource; + @Column("text", { default: "[]" }) + uris: string; + @CreateDateColumn() createdAt: Date; diff --git a/src/main/events/download-sources/get-download-sources.ts b/src/main/events/download-sources/get-download-sources.ts index 8f24caad..b8565645 100644 --- a/src/main/events/download-sources/get-download-sources.ts +++ b/src/main/events/download-sources/get-download-sources.ts @@ -1,16 +1,11 @@ import { downloadSourceRepository } from "@main/repository"; import { registerEvent } from "../register-event"; -const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => { - return downloadSourceRepository - .createQueryBuilder("downloadSource") - .leftJoin("downloadSource.repacks", "repacks") - .orderBy("downloadSource.createdAt", "DESC") - .loadRelationCountAndMap( - "downloadSource.repackCount", - "downloadSource.repacks" - ) - .getMany(); -}; +const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => + downloadSourceRepository.find({ + order: { + createdAt: "DESC", + }, + }); registerEvent("getDownloadSources", getDownloadSources); diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index cea41596..f4db999f 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -18,7 +18,8 @@ const startGameDownload = async ( _event: Electron.IpcMainInvokeEvent, payload: StartGameDownloadPayload ) => { - const { repackId, objectID, title, shop, downloadPath, downloader } = payload; + const { repackId, objectID, title, shop, downloadPath, downloader, uri } = + payload; const [game, repack] = await Promise.all([ gameRepository.findOne({ @@ -54,7 +55,7 @@ const startGameDownload = async ( bytesDownloaded: 0, downloadPath, downloader, - uri: repack.magnet, + uri, isDeleted: false, } ); @@ -76,7 +77,7 @@ const startGameDownload = async ( shop, status: "active", downloadPath, - uri: repack.magnet, + uri, }) .then((result) => { if (iconUrl) { @@ -100,6 +101,7 @@ const startGameDownload = async ( await downloadQueueRepository.delete({ game: { id: updatedGame!.id } }); await downloadQueueRepository.insert({ game: { id: updatedGame!.id } }); + await DownloadManager.cancelDownload(updatedGame!.id); await DownloadManager.startDownload(updatedGame!); }; diff --git a/src/main/helpers/download-source.ts b/src/main/helpers/download-source.ts index 012a4d24..c216212a 100644 --- a/src/main/helpers/download-source.ts +++ b/src/main/helpers/download-source.ts @@ -17,7 +17,8 @@ export const insertDownloadsFromSource = async ( const repacks: QueryDeepPartialEntity[] = downloads.map( (download) => ({ title: download.title, - magnet: download.uris[0], + uris: JSON.stringify(download.uris), + magnet: download.uris[0]!, fileSize: download.fileSize, repacker: downloadSource.name, uploadDate: download.uploadDate, diff --git a/src/main/helpers/index.ts b/src/main/helpers/index.ts index f2b86e5a..b0ff391f 100644 --- a/src/main/helpers/index.ts +++ b/src/main/helpers/index.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import { JSDOM } from "jsdom"; import UserAgent from "user-agents"; export const getSteamAppAsset = ( @@ -48,13 +49,16 @@ export const sleep = (ms: number) => export const requestWebPage = async (url: string) => { const userAgent = new UserAgent(); - return axios + const data = await axios .get(url, { headers: { "User-Agent": userAgent.toString(), }, }) .then((response) => response.data); + + const { window } = new JSDOM(data); + return window.document; }; export const isPortableVersion = () => diff --git a/src/main/index.ts b/src/main/index.ts index 9ff74bf6..e288302b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -20,8 +20,6 @@ autoUpdater.setFeedURL({ autoUpdater.logger = logger; -logger.log("Init Hydra"); - const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) app.quit(); @@ -123,7 +121,6 @@ app.on("window-all-closed", () => { app.on("before-quit", () => { /* Disconnects libtorrent */ PythonInstance.kill(); - logger.log("Quit Hydra"); }); app.on("activate", () => { diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 52a66693..d4733a32 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -6,8 +6,8 @@ import { downloadQueueRepository, gameRepository } from "@main/repository"; import { publishDownloadCompleteNotification } from "../notifications"; import { RealDebridDownloader } from "./real-debrid-downloader"; import type { DownloadProgress } from "@types"; -import { GofileApi } from "../hosters"; -import { GenericHTTPDownloader } from "./generic-http-downloader"; +import { GofileApi, QiwiApi } from "../hosters"; +import { GenericHttpDownloader } from "./generic-http-downloader"; export class DownloadManager { private static currentDownloader: Downloader | null = null; @@ -20,7 +20,7 @@ export class DownloadManager { } else if (this.currentDownloader === Downloader.RealDebrid) { status = await RealDebridDownloader.getStatus(); } else { - status = await GenericHTTPDownloader.getStatus(); + status = await GenericHttpDownloader.getStatus(); } if (status) { @@ -71,7 +71,7 @@ export class DownloadManager { } else if (this.currentDownloader === Downloader.RealDebrid) { await RealDebridDownloader.pauseDownload(); } else { - await GenericHTTPDownloader.pauseDownload(); + await GenericHttpDownloader.pauseDownload(); } WindowManager.mainWindow?.setProgressBar(-1); @@ -88,7 +88,7 @@ export class DownloadManager { } else if (this.currentDownloader === Downloader.RealDebrid) { RealDebridDownloader.cancelDownload(gameId); } else { - GenericHTTPDownloader.cancelDownload(gameId); + GenericHttpDownloader.cancelDownload(gameId); } WindowManager.mainWindow?.setProgressBar(-1); @@ -96,26 +96,38 @@ export class DownloadManager { } static async startDownload(game: Game) { - if (game.downloader === Downloader.Gofile) { - const id = game!.uri!.split("/").pop(); + switch (game.downloader) { + case Downloader.Gofile: { + const id = game!.uri!.split("/").pop(); - const token = await GofileApi.authorize(); - const downloadLink = await GofileApi.getDownloadLink(id!); + const token = await GofileApi.authorize(); + const downloadLink = await GofileApi.getDownloadLink(id!); - GenericHTTPDownloader.startDownload(game, downloadLink, { - Cookie: `accountToken=${token}`, - }); - } else if (game.downloader === Downloader.PixelDrain) { - const id = game!.uri!.split("/").pop(); + GenericHttpDownloader.startDownload(game, downloadLink, { + Cookie: `accountToken=${token}`, + }); + break; + } + case Downloader.PixelDrain: { + const id = game!.uri!.split("/").pop(); - await GenericHTTPDownloader.startDownload( - game, - `https://pixeldrain.com/api/file/${id}?download` - ); - } else if (game.downloader === Downloader.Torrent) { - PythonInstance.startDownload(game); - } else if (game.downloader === Downloader.RealDebrid) { - RealDebridDownloader.startDownload(game); + await GenericHttpDownloader.startDownload( + game, + `https://pixeldrain.com/api/file/${id}?download` + ); + break; + } + case Downloader.Qiwi: { + const downloadUrl = await QiwiApi.getDownloadUrl(game.uri!); + + await GenericHttpDownloader.startDownload(game, downloadUrl); + break; + } + case Downloader.Torrent: + PythonInstance.startDownload(game); + break; + case Downloader.RealDebrid: + RealDebridDownloader.startDownload(game); } this.currentDownloader = game.downloader; diff --git a/src/main/services/download/generic-http-downloader.ts b/src/main/services/download/generic-http-downloader.ts index 8384a5fd..055c8561 100644 --- a/src/main/services/download/generic-http-downloader.ts +++ b/src/main/services/download/generic-http-downloader.ts @@ -4,14 +4,14 @@ import { calculateETA } from "./helpers"; import { DownloadProgress } from "@types"; import { HttpDownload } from "./http-download"; -export class GenericHTTPDownloader { - private static downloads = new Map(); - private static downloadingGame: Game | null = null; +export class GenericHttpDownloader { + public static downloads = new Map(); + public static downloadingGame: Game | null = null; public static async getStatus() { if (this.downloadingGame) { - const gid = this.downloads.get(this.downloadingGame.id)!; - const status = HttpDownload.getStatus(gid); + const download = this.downloads.get(this.downloadingGame.id)!; + const status = download.getStatus(); if (status) { const progress = @@ -57,10 +57,10 @@ export class GenericHTTPDownloader { static async pauseDownload() { if (this.downloadingGame) { - const gid = this.downloads.get(this.downloadingGame!.id!); + const httpDownload = this.downloads.get(this.downloadingGame!.id!); - if (gid) { - await HttpDownload.pauseDownload(gid); + if (httpDownload) { + await httpDownload.pauseDownload(); } this.downloadingGame = null; @@ -79,29 +79,31 @@ export class GenericHTTPDownloader { return; } - const gid = await HttpDownload.startDownload( + const httpDownload = new HttpDownload( game.downloadPath!, downloadUrl, headers ); - this.downloads.set(game.id!, gid); + httpDownload.startDownload(); + + this.downloads.set(game.id!, httpDownload); } static async cancelDownload(gameId: number) { - const gid = this.downloads.get(gameId); + const httpDownload = this.downloads.get(gameId); - if (gid) { - await HttpDownload.cancelDownload(gid); + if (httpDownload) { + await httpDownload.cancelDownload(); this.downloads.delete(gameId); } } static async resumeDownload(gameId: number) { - const gid = this.downloads.get(gameId); + const httpDownload = this.downloads.get(gameId); - if (gid) { - await HttpDownload.resumeDownload(gid); + if (httpDownload) { + await httpDownload.resumeDownload(); } } } diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index cd8cbee5..4f6c31a9 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -1,67 +1,52 @@ -import { DownloadItem } from "electron"; import { WindowManager } from "../window-manager"; import path from "node:path"; export class HttpDownload { - private static id = 0; + private downloadItem: Electron.DownloadItem; - private static downloads: Record = {}; + constructor( + private downloadPath: string, + private downloadUrl: string, + private headers?: Record + ) {} - public static getStatus(gid: string): { - completedLength: number; - totalLength: number; - downloadSpeed: number; - folderName: string; - } | null { - const downloadItem = this.downloads[gid]; - if (downloadItem) { - return { - completedLength: downloadItem.getReceivedBytes(), - totalLength: downloadItem.getTotalBytes(), - downloadSpeed: downloadItem.getCurrentBytesPerSecond(), - folderName: downloadItem.getFilename(), - }; - } - - return null; + public getStatus() { + return { + completedLength: this.downloadItem.getReceivedBytes(), + totalLength: this.downloadItem.getTotalBytes(), + downloadSpeed: this.downloadItem.getCurrentBytesPerSecond(), + folderName: this.downloadItem.getFilename(), + }; } - static async cancelDownload(gid: string) { - const downloadItem = this.downloads[gid]; - downloadItem?.cancel(); - delete this.downloads[gid]; + async cancelDownload() { + this.downloadItem.cancel(); } - static async pauseDownload(gid: string) { - const downloadItem = this.downloads[gid]; - downloadItem?.pause(); + async pauseDownload() { + this.downloadItem.pause(); } - static async resumeDownload(gid: string) { - const downloadItem = this.downloads[gid]; - downloadItem?.resume(); + async resumeDownload() { + this.downloadItem.resume(); } - static async startDownload( - downloadPath: string, - downloadUrl: string, - headers?: Record - ) { - return new Promise((resolve) => { - const options = headers ? { headers } : {}; - WindowManager.mainWindow?.webContents.downloadURL(downloadUrl, options); + async startDownload() { + return new Promise((resolve) => { + const options = this.headers ? { headers: this.headers } : {}; + WindowManager.mainWindow?.webContents.downloadURL( + this.downloadUrl, + options + ); - const gid = ++this.id; - - WindowManager.mainWindow?.webContents.session.on( + WindowManager.mainWindow?.webContents.session.once( "will-download", (_event, item, _webContents) => { - this.downloads[gid.toString()] = item; + this.downloadItem = item; - // Set the save path, making Electron not to prompt a save dialog. - item.setSavePath(path.join(downloadPath, item.getFilename())); + item.setSavePath(path.join(this.downloadPath, item.getFilename())); - resolve(gid.toString()); + resolve(null); } ); }); diff --git a/src/main/services/download/real-debrid-downloader.ts b/src/main/services/download/real-debrid-downloader.ts index c6925f57..2818644a 100644 --- a/src/main/services/download/real-debrid-downloader.ts +++ b/src/main/services/download/real-debrid-downloader.ts @@ -1,14 +1,9 @@ import { Game } from "@main/entity"; import { RealDebridClient } from "../real-debrid"; -import { gameRepository } from "@main/repository"; -import { calculateETA } from "./helpers"; -import { DownloadProgress } from "@types"; import { HttpDownload } from "./http-download"; +import { GenericHttpDownloader } from "./generic-http-downloader"; -export class RealDebridDownloader { - private static downloads = new Map(); - private static downloadingGame: Game | null = null; - +export class RealDebridDownloader extends GenericHttpDownloader { private static realDebridTorrentId: string | null = null; private static async getRealDebridDownloadUrl() { @@ -48,66 +43,6 @@ export class RealDebridDownloader { return null; } - public static async getStatus() { - if (this.downloadingGame) { - const gid = this.downloads.get(this.downloadingGame.id)!; - const status = HttpDownload.getStatus(gid); - - if (status) { - const progress = - Number(status.completedLength) / Number(status.totalLength); - - await gameRepository.update( - { id: this.downloadingGame!.id }, - { - bytesDownloaded: Number(status.completedLength), - fileSize: Number(status.totalLength), - progress, - status: "active", - folderName: status.folderName, - } - ); - - const result = { - numPeers: 0, - numSeeds: 0, - downloadSpeed: Number(status.downloadSpeed), - timeRemaining: calculateETA( - Number(status.totalLength), - Number(status.completedLength), - Number(status.downloadSpeed) - ), - isDownloadingMetadata: false, - isCheckingFiles: false, - progress, - gameId: this.downloadingGame!.id, - } as DownloadProgress; - - if (progress === 1) { - this.downloads.delete(this.downloadingGame.id); - this.realDebridTorrentId = null; - this.downloadingGame = null; - } - - return result; - } - } - - return null; - } - - static async pauseDownload() { - if (this.downloadingGame) { - const gid = this.downloads.get(this.downloadingGame.id); - if (gid) { - await HttpDownload.pauseDownload(gid); - } - } - - this.realDebridTorrentId = null; - this.downloadingGame = null; - } - static async startDownload(game: Game) { if (this.downloads.has(game.id)) { await this.resumeDownload(game.id!); @@ -128,32 +63,10 @@ export class RealDebridDownloader { if (downloadUrl) { this.realDebridTorrentId = null; - const gid = await HttpDownload.startDownload( - game.downloadPath!, - downloadUrl - ); + const httpDownload = new HttpDownload(game.downloadPath!, downloadUrl); + httpDownload.startDownload(); - this.downloads.set(game.id!, gid); - } - } - - static async cancelDownload(gameId: number) { - const gid = this.downloads.get(gameId); - - if (gid) { - await HttpDownload.cancelDownload(gid); - this.downloads.delete(gameId); - } - - this.realDebridTorrentId = null; - this.downloadingGame = null; - } - - static async resumeDownload(gameId: number) { - const gid = this.downloads.get(gameId); - - if (gid) { - await HttpDownload.resumeDownload(gid); + this.downloads.set(game.id!, httpDownload); } } } diff --git a/src/main/services/hosters/gofile.ts b/src/main/services/hosters/gofile.ts index 770bb15f..2c23556f 100644 --- a/src/main/services/hosters/gofile.ts +++ b/src/main/services/hosters/gofile.ts @@ -16,6 +16,8 @@ export interface GofileContentsResponse { children: Record; } +export const WT = "4fd6sg89d7s6"; + export class GofileApi { private static token: string; @@ -35,7 +37,7 @@ export class GofileApi { public static async getDownloadLink(id: string) { const searchParams = new URLSearchParams({ - wt: "4fd6sg89d7s6", + wt: WT, }); const response = await axios.get<{ diff --git a/src/main/services/hosters/index.ts b/src/main/services/hosters/index.ts index 921c45b1..4c5b1803 100644 --- a/src/main/services/hosters/index.ts +++ b/src/main/services/hosters/index.ts @@ -1 +1,2 @@ export * from "./gofile"; +export * from "./qiwi"; diff --git a/src/main/services/hosters/qiwi.ts b/src/main/services/hosters/qiwi.ts new file mode 100644 index 00000000..e18b011c --- /dev/null +++ b/src/main/services/hosters/qiwi.ts @@ -0,0 +1,15 @@ +import { requestWebPage } from "@main/helpers"; + +export class QiwiApi { + public static async getDownloadUrl(url: string) { + const document = await requestWebPage(url); + const fileName = document.querySelector("h1")?.textContent; + + const slug = url.split("/").pop(); + const extension = fileName?.split(".").pop(); + + const downloadUrl = `https://spyderrock.com/${slug}.${extension}`; + + return downloadUrl; + } +} diff --git a/src/main/services/how-long-to-beat.ts b/src/main/services/how-long-to-beat.ts index 39b938c5..67e96942 100644 --- a/src/main/services/how-long-to-beat.ts +++ b/src/main/services/how-long-to-beat.ts @@ -1,5 +1,4 @@ import axios from "axios"; -import { JSDOM } from "jsdom"; import { requestWebPage } from "@main/helpers"; import { HowLongToBeatCategory } from "@types"; import { formatName } from "@shared"; @@ -52,10 +51,7 @@ const parseListItems = ($lis: Element[]) => { export const getHowLongToBeatGame = async ( id: string ): Promise => { - const response = await requestWebPage(`https://howlongtobeat.com/game/${id}`); - - const { window } = new JSDOM(response); - const { document } = window; + const document = await requestWebPage(`https://howlongtobeat.com/game/${id}`); const $ul = document.querySelector(".shadow_shadow ul"); if (!$ul) return []; diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 120d27ac..6f0e1905 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -77,54 +77,54 @@ export class HydraApi { baseURL: import.meta.env.MAIN_VITE_API_URL, }); - this.instance.interceptors.request.use( - (request) => { - logger.log(" ---- REQUEST -----"); - logger.log(request.method, request.url, request.params, request.data); - return request; - }, - (error) => { - logger.error("request error", error); - return Promise.reject(error); - } - ); + // this.instance.interceptors.request.use( + // (request) => { + // logger.log(" ---- REQUEST -----"); + // logger.log(request.method, request.url, request.params, request.data); + // return request; + // }, + // (error) => { + // logger.error("request error", error); + // return Promise.reject(error); + // } + // ); - this.instance.interceptors.response.use( - (response) => { - logger.log(" ---- RESPONSE -----"); - logger.log( - response.status, - response.config.method, - response.config.url, - response.data - ); - return response; - }, - (error) => { - logger.error(" ---- RESPONSE ERROR -----"); + // this.instance.interceptors.response.use( + // (response) => { + // logger.log(" ---- RESPONSE -----"); + // logger.log( + // response.status, + // response.config.method, + // response.config.url, + // response.data + // ); + // return response; + // }, + // (error) => { + // logger.error(" ---- RESPONSE ERROR -----"); - const { config } = error; + // const { config } = error; - logger.error( - config.method, - config.baseURL, - config.url, - config.headers, - config.data - ); + // logger.error( + // config.method, + // config.baseURL, + // config.url, + // config.headers, + // config.data + // ); - if (error.response) { - logger.error("Response", error.response.status, error.response.data); - } else if (error.request) { - logger.error("Request", error.request); - } else { - logger.error("Error", error.message); - } + // if (error.response) { + // logger.error("Response", error.response.status, error.response.data); + // } else if (error.request) { + // logger.error("Request", error.request); + // } else { + // logger.error("Error", error.message); + // } - logger.error(" ----- END RESPONSE ERROR -------"); - return Promise.reject(error); - } - ); + // logger.error(" ----- END RESPONSE ERROR -------"); + // return Promise.reject(error); + // } + // ); const userAuth = await userAuthRepository.findOne({ where: { id: 1 }, diff --git a/src/main/services/repacks-manager.ts b/src/main/services/repacks-manager.ts index 02821127..93157d6c 100644 --- a/src/main/services/repacks-manager.ts +++ b/src/main/services/repacks-manager.ts @@ -8,11 +8,25 @@ export class RepacksManager { private static repacksIndex = new flexSearch.Index(); public static async updateRepacks() { - this.repacks = await repackRepository.find({ - order: { - createdAt: "DESC", - }, - }); + this.repacks = await repackRepository + .find({ + order: { + createdAt: "DESC", + }, + }) + .then((repacks) => + repacks.map((repack) => { + const uris: string[] = []; + const magnet = repack?.magnet; + + if (magnet) uris.push(magnet); + + return { + ...repack, + uris: [...uris, ...JSON.parse(repack.uris)], + }; + }) + ); for (let i = 0; i < this.repacks.length; i++) { this.repacksIndex.remove(i); diff --git a/src/renderer/src/constants.ts b/src/renderer/src/constants.ts index 7025df2a..63368c88 100644 --- a/src/renderer/src/constants.ts +++ b/src/renderer/src/constants.ts @@ -7,4 +7,5 @@ export const DOWNLOADER_NAME = { [Downloader.Torrent]: "Torrent", [Downloader.Gofile]: "Gofile", [Downloader.PixelDrain]: "PixelDrain", + [Downloader.Qiwi]: "Qiwi", }; diff --git a/src/renderer/src/hooks/use-download.ts b/src/renderer/src/hooks/use-download.ts index f58a8765..07c885cf 100644 --- a/src/renderer/src/hooks/use-download.ts +++ b/src/renderer/src/hooks/use-download.ts @@ -22,9 +22,10 @@ export function useDownload() { ); const dispatch = useAppDispatch(); - const startDownload = (payload: StartGameDownloadPayload) => { + const startDownload = async (payload: StartGameDownloadPayload) => { dispatch(clearDownload()); - window.electron.startGameDownload(payload).then((game) => { + + return window.electron.startGameDownload(payload).then((game) => { updateLibrary(); return game; diff --git a/src/renderer/src/pages/game-details/game-details.tsx b/src/renderer/src/pages/game-details/game-details.tsx index 5f32965a..5ac9673f 100644 --- a/src/renderer/src/pages/game-details/game-details.tsx +++ b/src/renderer/src/pages/game-details/game-details.tsx @@ -23,7 +23,7 @@ import { } from "@renderer/context"; import { useDownload } from "@renderer/hooks"; import { GameOptionsModal, RepacksModal } from "./modals"; -import { Downloader } from "@shared"; +import { Downloader, getDownloadersForUri } from "@shared"; export function GameDetails() { const [randomGame, setRandomGame] = useState(null); @@ -70,6 +70,9 @@ export function GameDetails() { } }; + const selectRepackUri = (repack: GameRepack, downloader: Downloader) => + repack.uris.find((uri) => getDownloadersForUri(uri).includes(downloader))!; + return ( @@ -96,6 +99,7 @@ export function GameDetails() { downloader, shop: shop as GameShop, downloadPath, + uri: selectRepackUri(repack, downloader), }); await updateGame(); diff --git a/src/renderer/src/pages/game-details/modals/download-settings-modal.css.ts b/src/renderer/src/pages/game-details/modals/download-settings-modal.css.ts index d5655d94..5450378c 100644 --- a/src/renderer/src/pages/game-details/modals/download-settings-modal.css.ts +++ b/src/renderer/src/pages/game-details/modals/download-settings-modal.css.ts @@ -20,13 +20,16 @@ export const hintText = style({ }); export const downloaders = style({ - display: "flex", + display: "grid", gap: `${SPACING_UNIT}px`, + gridTemplateColumns: "repeat(2, 1fr)", }); export const downloaderOption = style({ - flex: "1", position: "relative", + ":only-child": { + gridColumn: "1 / -1", + }, }); export const downloaderIcon = style({ diff --git a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx index dff73ea0..3450af24 100644 --- a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx @@ -5,7 +5,7 @@ import { DiskSpace } from "check-disk-space"; import * as styles from "./download-settings-modal.css"; import { Button, Link, Modal, TextField } from "@renderer/components"; import { CheckCircleFillIcon, DownloadIcon } from "@primer/octicons-react"; -import { Downloader, formatBytes, getDownloadersForUri } from "@shared"; +import { Downloader, formatBytes, getDownloadersForUris } from "@shared"; import type { GameRepack } from "@types"; import { SPACING_UNIT } from "@renderer/theme.css"; @@ -48,8 +48,8 @@ export function DownloadSettingsModal({ }, [visible, selectedPath]); const downloaders = useMemo(() => { - return getDownloadersForUri(repack?.magnet ?? ""); - }, [repack?.magnet]); + return getDownloadersForUris(repack?.uris ?? []); + }, [repack?.uris]); useEffect(() => { if (userPreferences?.downloadsPath) { diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index f9d351af..0d1b9c1d 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -76,6 +76,13 @@ export function RepacksModal({ ); }; + const checkIfLastDownloadedOption = (repack: GameRepack) => { + if (infoHash) return repack.uris.some((uri) => uri.includes(infoHash)); + if (!game?.uri) return false; + + return repack.uris.some((uri) => uri.includes(game?.uri ?? "")); + }; + return ( <> {filteredRepacks.map((repack) => { - const isLastDownloadedOption = - infoHash !== null && - repack.magnet.toLowerCase().includes(infoHash); + const isLastDownloadedOption = checkIfLastDownloadedOption(repack); return (