From 6c6fff71fe145cf68e0817d7c2b8ec2364a611e4 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 9 Jul 2024 19:24:02 +0100 Subject: [PATCH 01/33] feat: adding generic http downloads --- .../events/torrenting/start-game-download.ts | 4 + src/main/main.ts | 3 +- .../services/download/download-manager.ts | 56 ++++++--- .../download/generic-http-downloader.ts | 109 ++++++++++++++++++ src/main/services/download/http-download.ts | 10 +- .../download/real-debrid-downloader.ts | 30 +++-- src/main/services/hosters/gofile.ts | 61 ++++++++++ src/main/services/hosters/index.ts | 1 + src/renderer/src/constants.ts | 2 + .../modals/download-settings-modal.tsx | 36 ++++-- .../game-details/modals/repacks-modal.tsx | 6 +- src/shared/index.ts | 17 +++ 12 files changed, 294 insertions(+), 41 deletions(-) create mode 100644 src/main/services/download/generic-http-downloader.ts create mode 100644 src/main/services/hosters/gofile.ts create mode 100644 src/main/services/hosters/index.ts diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index cea41596..aa33c99a 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -44,6 +44,8 @@ const startGameDownload = async ( ); if (game) { + console.log("game", game); + await gameRepository.update( { id: game.id, @@ -95,6 +97,8 @@ const startGameDownload = async ( }, }); + console.log(updatedGame); + createGame(updatedGame!); await downloadQueueRepository.delete({ game: { id: updatedGame!.id } }); diff --git a/src/main/main.ts b/src/main/main.ts index fbabc56c..af594e20 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -22,8 +22,9 @@ const loadState = async (userPreferences: UserPreferences | null) => { import("./events"); - if (userPreferences?.realDebridApiToken) + if (userPreferences?.realDebridApiToken) { RealDebridClient.authorize(userPreferences?.realDebridApiToken); + } HydraApi.setupApi().then(() => { uploadGamesBatch(); diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 31f28992..d6542396 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -6,6 +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"; export class DownloadManager { private static currentDownloader: Downloader | null = null; @@ -13,10 +15,12 @@ export class DownloadManager { public static async watchDownloads() { let status: DownloadProgress | null = null; - if (this.currentDownloader === Downloader.RealDebrid) { + if (this.currentDownloader === Downloader.Torrent) { + status = await PythonInstance.getStatus(); + } else if (this.currentDownloader === Downloader.RealDebrid) { status = await RealDebridDownloader.getStatus(); } else { - status = await PythonInstance.getStatus(); + status = await GenericHTTPDownloader.getStatus(); } if (status) { @@ -62,10 +66,12 @@ export class DownloadManager { } static async pauseDownload() { - if (this.currentDownloader === Downloader.RealDebrid) { + if (this.currentDownloader === Downloader.Torrent) { + await PythonInstance.pauseDownload(); + } else if (this.currentDownloader === Downloader.RealDebrid) { await RealDebridDownloader.pauseDownload(); } else { - await PythonInstance.pauseDownload(); + await GenericHTTPDownloader.pauseDownload(); } WindowManager.mainWindow?.setProgressBar(-1); @@ -73,20 +79,16 @@ export class DownloadManager { } static async resumeDownload(game: Game) { - if (game.downloader === Downloader.RealDebrid) { - RealDebridDownloader.startDownload(game); - this.currentDownloader = Downloader.RealDebrid; - } else { - PythonInstance.startDownload(game); - this.currentDownloader = Downloader.Torrent; - } + return this.startDownload(game); } static async cancelDownload(gameId: number) { - if (this.currentDownloader === Downloader.RealDebrid) { + if (this.currentDownloader === Downloader.Torrent) { + PythonInstance.cancelDownload(gameId); + } else if (this.currentDownloader === Downloader.RealDebrid) { RealDebridDownloader.cancelDownload(gameId); } else { - PythonInstance.cancelDownload(gameId); + GenericHTTPDownloader.cancelDownload(gameId); } WindowManager.mainWindow?.setProgressBar(-1); @@ -94,12 +96,30 @@ export class DownloadManager { } static async startDownload(game: Game) { - if (game.downloader === Downloader.RealDebrid) { - RealDebridDownloader.startDownload(game); - this.currentDownloader = Downloader.RealDebrid; - } else { + if (game.downloader === Downloader.Gofile) { + const id = game!.uri!.split("/").pop(); + + const token = await GofileApi.authorize(); + const downloadLink = await GofileApi.getDownloadLink(id!); + + console.log(downloadLink, token, "<<<"); + + GenericHTTPDownloader.startDownload(game, downloadLink, [ + `Cookie: accountToken=${token}`, + ]); + } else if (game.downloader === 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); - this.currentDownloader = Downloader.Torrent; + } else if (game.downloader === 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 new file mode 100644 index 00000000..688769a4 --- /dev/null +++ b/src/main/services/download/generic-http-downloader.ts @@ -0,0 +1,109 @@ +import { Game } from "@main/entity"; +import { gameRepository } from "@main/repository"; +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; + + public static async getStatus() { + if (this.downloadingGame) { + const gid = this.downloads.get(this.downloadingGame.id)!; + const status = await 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", + } + ); + + 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.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.downloadingGame = null; + } + } + + static async startDownload( + game: Game, + downloadUrl: string, + headers: string[] = [] + ) { + this.downloadingGame = game; + + if (this.downloads.has(game.id)) { + await this.resumeDownload(game.id!); + + return; + } + + if (downloadUrl) { + const gid = await HTTPDownload.startDownload( + game.downloadPath!, + downloadUrl, + headers + ); + + 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); + } + } + + static async resumeDownload(gameId: number) { + const gid = this.downloads.get(gameId); + + if (gid) { + await HTTPDownload.resumeDownload(gid); + } + } +} diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index 4553a6cb..d147e208 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -4,7 +4,7 @@ import { sleep } from "@main/helpers"; import { startAria2 } from "../aria2c"; import Aria2 from "aria2"; -export class HttpDownload { +export class HTTPDownload { private static connected = false; private static aria2c: ChildProcess | null = null; @@ -56,11 +56,17 @@ export class HttpDownload { await this.aria2.call("unpause", gid); } - static async startDownload(downloadPath: string, downloadUrl: string) { + static async startDownload( + downloadPath: string, + downloadUrl: string, + header: string[] = [] + ) { + console.log(header); if (!this.connected) await this.connect(); const options = { dir: downloadPath, + header, }; return this.aria2.call("addUri", [downloadUrl], options); diff --git a/src/main/services/download/real-debrid-downloader.ts b/src/main/services/download/real-debrid-downloader.ts index 8ead0067..034ffc49 100644 --- a/src/main/services/download/real-debrid-downloader.ts +++ b/src/main/services/download/real-debrid-downloader.ts @@ -3,7 +3,7 @@ import { RealDebridClient } from "../real-debrid"; import { gameRepository } from "@main/repository"; import { calculateETA } from "./helpers"; import { DownloadProgress } from "@types"; -import { HttpDownload } from "./http-download"; +import { HTTPDownload } from "./http-download"; export class RealDebridDownloader { private static downloads = new Map(); @@ -29,6 +29,18 @@ export class RealDebridDownloader { const { download } = await RealDebridClient.unrestrictLink(link); return decodeURIComponent(download); } + + return null; + } + + if (this.downloadingGame?.uri) { + const { download } = await RealDebridClient.unrestrictLink( + this.downloadingGame?.uri + ); + + console.log("download>>", download); + + return decodeURIComponent(download); } return null; @@ -37,7 +49,7 @@ export class RealDebridDownloader { public static async getStatus() { if (this.downloadingGame) { const gid = this.downloads.get(this.downloadingGame.id)!; - const status = await HttpDownload.getStatus(gid); + const status = await HTTPDownload.getStatus(gid); if (status) { const progress = @@ -111,7 +123,7 @@ export class RealDebridDownloader { static async pauseDownload() { const gid = this.downloads.get(this.downloadingGame!.id!); if (gid) { - await HttpDownload.pauseDownload(gid); + await HTTPDownload.pauseDownload(gid); } this.realDebridTorrentId = null; @@ -127,14 +139,18 @@ export class RealDebridDownloader { return; } - this.realDebridTorrentId = await RealDebridClient.getTorrentId(game!.uri!); + if (game.uri?.startsWith("magnet:")) { + this.realDebridTorrentId = await RealDebridClient.getTorrentId( + game!.uri! + ); + } const downloadUrl = await this.getRealDebridDownloadUrl(); if (downloadUrl) { this.realDebridTorrentId = null; - const gid = await HttpDownload.startDownload( + const gid = await HTTPDownload.startDownload( game.downloadPath!, downloadUrl ); @@ -147,7 +163,7 @@ export class RealDebridDownloader { const gid = this.downloads.get(gameId); if (gid) { - await HttpDownload.cancelDownload(gid); + await HTTPDownload.cancelDownload(gid); this.downloads.delete(gameId); } } @@ -156,7 +172,7 @@ export class RealDebridDownloader { const gid = this.downloads.get(gameId); if (gid) { - await HttpDownload.resumeDownload(gid); + await HTTPDownload.resumeDownload(gid); } } } diff --git a/src/main/services/hosters/gofile.ts b/src/main/services/hosters/gofile.ts new file mode 100644 index 00000000..770bb15f --- /dev/null +++ b/src/main/services/hosters/gofile.ts @@ -0,0 +1,61 @@ +import axios from "axios"; + +export interface GofileAccountsReponse { + id: string; + token: string; +} + +export interface GofileContentChild { + id: string; + link: string; +} + +export interface GofileContentsResponse { + id: string; + type: string; + children: Record; +} + +export class GofileApi { + private static token: string; + + public static async authorize() { + const response = await axios.post<{ + status: string; + data: GofileAccountsReponse; + }>("https://api.gofile.io/accounts"); + + if (response.data.status === "ok") { + this.token = response.data.data.token; + return this.token; + } + + throw new Error("Failed to authorize"); + } + + public static async getDownloadLink(id: string) { + const searchParams = new URLSearchParams({ + wt: "4fd6sg89d7s6", + }); + + const response = await axios.get<{ + status: string; + data: GofileContentsResponse; + }>(`https://api.gofile.io/contents/${id}?${searchParams.toString()}`, { + headers: { + Authorization: `Bearer ${this.token}`, + }, + }); + + if (response.data.status === "ok") { + if (response.data.data.type !== "folder") { + throw new Error("Only folders are supported"); + } + + const [firstChild] = Object.values(response.data.data.children); + return firstChild.link; + } + + throw new Error("Failed to get download link"); + } +} diff --git a/src/main/services/hosters/index.ts b/src/main/services/hosters/index.ts new file mode 100644 index 00000000..921c45b1 --- /dev/null +++ b/src/main/services/hosters/index.ts @@ -0,0 +1 @@ +export * from "./gofile"; diff --git a/src/renderer/src/constants.ts b/src/renderer/src/constants.ts index 6186bb85..7025df2a 100644 --- a/src/renderer/src/constants.ts +++ b/src/renderer/src/constants.ts @@ -5,4 +5,6 @@ export const VERSION_CODENAME = "Leviticus"; export const DOWNLOADER_NAME = { [Downloader.RealDebrid]: "Real-Debrid", [Downloader.Torrent]: "Torrent", + [Downloader.Gofile]: "Gofile", + [Downloader.PixelDrain]: "PixelDrain", }; 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 ef4ba040..d102d2b2 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 @@ -1,11 +1,11 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; 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 } from "@shared"; +import { Downloader, formatBytes, getDownloadersForUri } from "@shared"; import type { GameRepack } from "@types"; import { SPACING_UNIT } from "@renderer/theme.css"; @@ -23,8 +23,6 @@ export interface DownloadSettingsModalProps { repack: GameRepack | null; } -const downloaders = [Downloader.Torrent, Downloader.RealDebrid]; - export function DownloadSettingsModal({ visible, onClose, @@ -36,9 +34,8 @@ export function DownloadSettingsModal({ const [diskFreeSpace, setDiskFreeSpace] = useState(null); const [selectedPath, setSelectedPath] = useState(""); const [downloadStarting, setDownloadStarting] = useState(false); - const [selectedDownloader, setSelectedDownloader] = useState( - Downloader.Torrent - ); + const [selectedDownloader, setSelectedDownloader] = + useState(null); const userPreferences = useAppSelector( (state) => state.userPreferences.value @@ -50,6 +47,10 @@ export function DownloadSettingsModal({ } }, [visible, selectedPath]); + const downloaders = useMemo(() => { + return getDownloadersForUri(repack?.magnet ?? ""); + }, [repack?.magnet]); + useEffect(() => { if (userPreferences?.downloadsPath) { setSelectedPath(userPreferences.downloadsPath); @@ -59,9 +60,19 @@ export function DownloadSettingsModal({ .then((defaultDownloadsPath) => setSelectedPath(defaultDownloadsPath)); } - if (userPreferences?.realDebridApiToken) + if ( + userPreferences?.realDebridApiToken && + downloaders.includes(Downloader.RealDebrid) + ) { setSelectedDownloader(Downloader.RealDebrid); - }, [userPreferences?.downloadsPath, userPreferences?.realDebridApiToken]); + } else { + setSelectedDownloader(downloaders[0]); + } + }, [ + userPreferences?.downloadsPath, + downloaders, + userPreferences?.realDebridApiToken, + ]); const getDiskFreeSpace = (path: string) => { window.electron.getDiskFreeSpace(path).then((result) => { @@ -85,7 +96,7 @@ export function DownloadSettingsModal({ if (repack) { setDownloadStarting(true); - startDownload(repack, selectedDownloader, selectedPath).finally(() => { + startDownload(repack, selectedDownloader!, selectedPath).finally(() => { setDownloadStarting(false); onClose(); }); @@ -167,7 +178,10 @@ export function DownloadSettingsModal({

- 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 d2d8f5d9..f9d351af 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -44,8 +44,10 @@ export function RepacksModal({ }, [repacks]); const getInfoHash = useCallback(async () => { - const torrent = await parseTorrent(game?.uri ?? ""); - if (torrent.infoHash) setInfoHash(torrent.infoHash); + if (game?.uri?.startsWith("magnet:")) { + const torrent = await parseTorrent(game?.uri ?? ""); + if (torrent.infoHash) setInfoHash(torrent.infoHash); + } }, [game]); useEffect(() => { diff --git a/src/shared/index.ts b/src/shared/index.ts index 409bdbc9..ba68876f 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -1,6 +1,8 @@ export enum Downloader { RealDebrid, Torrent, + Gofile, + PixelDrain, } export enum DownloadSourceStatus { @@ -63,3 +65,18 @@ export const formatName = pipe( removeDuplicateSpaces, (str) => str.trim() ); + +const realDebridHosts = ["https://1fichier.com", "https://mediafire.com"]; + +export const getDownloadersForUri = (uri: string) => { + if (uri.startsWith("https://gofile.io")) return [Downloader.Gofile]; + if (uri.startsWith("https://pixeldrain.com")) return [Downloader.PixelDrain]; + + if (realDebridHosts.some((host) => uri.startsWith(host))) + return [Downloader.RealDebrid]; + + if (uri.startsWith("magnet:")) + return [Downloader.Torrent, Downloader.RealDebrid]; + + return []; +}; From b5b7fe31ae64a28e483ccbe7b1f0cc3470711b50 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:48:10 -0300 Subject: [PATCH 02/33] use webContents.downloadURL to download http --- src/main/services/download/http-download.ts | 107 +++++++++++--------- 1 file changed, 60 insertions(+), 47 deletions(-) diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index d147e208..ec16f5cd 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -1,59 +1,42 @@ -import type { ChildProcess } from "node:child_process"; -import { logger } from "../logger"; -import { sleep } from "@main/helpers"; -import { startAria2 } from "../aria2c"; -import Aria2 from "aria2"; +import { DownloadItem } from "electron"; +import { WindowManager } from "../window-manager"; export class HTTPDownload { - private static connected = false; - private static aria2c: ChildProcess | null = null; + private static id = 0; - private static aria2 = new Aria2({}); + private static downloads: Record = {}; - private static async connect() { - this.aria2c = startAria2(); - - let retries = 0; - - while (retries < 4 && !this.connected) { - try { - await this.aria2.open(); - logger.log("Connected to aria2"); - - this.connected = true; - } catch (err) { - await sleep(100); - logger.log("Failed to connect to aria2, retrying..."); - retries++; - } - } - } - - public static getStatus(gid: string) { - if (this.connected) { - return this.aria2.call("tellStatus", gid); + public static getStatus(gid: string): { + completedLength: number; + totalLength: number; + downloadSpeed: number; + } | null { + const downloadItem = this.downloads[gid]; + if (downloadItem) { + return { + completedLength: downloadItem.getReceivedBytes(), + totalLength: downloadItem.getTotalBytes(), + downloadSpeed: 0, + }; } return null; } - public static disconnect() { - if (this.aria2c) { - this.aria2c.kill(); - this.connected = false; - } - } - static async cancelDownload(gid: string) { - await this.aria2.call("forceRemove", gid); + const downloadItem: DownloadItem = this.downloads[gid]; + downloadItem?.cancel(); + this.downloads; } static async pauseDownload(gid: string) { - await this.aria2.call("forcePause", gid); + const downloadItem = this.downloads[gid]; + downloadItem?.pause(); } static async resumeDownload(gid: string) { - await this.aria2.call("unpause", gid); + const downloadItem = this.downloads[gid]; + downloadItem?.resume(); } static async startDownload( @@ -61,14 +44,44 @@ export class HTTPDownload { downloadUrl: string, header: string[] = [] ) { - console.log(header); - if (!this.connected) await this.connect(); + return new Promise((resolve) => { + WindowManager.mainWindow?.webContents.downloadURL(downloadUrl, { + headers: { Cookie: header[0].split(": ")[1] }, + }); - const options = { - dir: downloadPath, - header, - }; + WindowManager.mainWindow?.webContents.session.on( + "will-download", + (_event, item, _webContents) => { + const gid = ++this.id; - return this.aria2.call("addUri", [downloadUrl], options); + this.downloads[gid.toString()] = item; + + // Set the save path, making Electron not to prompt a save dialog. + item.setSavePath(downloadPath); + + item.on("updated", (_event, state) => { + if (state === "interrupted") { + console.log("Download is interrupted but can be resumed"); + } else if (state === "progressing") { + if (item.isPaused()) { + console.log("Download is paused"); + } else { + console.log(`Received bytes: ${item.getReceivedBytes()}`); + } + } + }); + + item.once("done", (_event, state) => { + if (state === "completed") { + console.log("Download successfully"); + } else { + console.log(`Download failed: ${state}`); + } + }); + + resolve(gid.toString()); + } + ); + }); } } From 781e0f4102a26e04a3fcbadbd1fe3f1058f9c849 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 15 Jul 2024 22:29:34 -0300 Subject: [PATCH 03/33] feat: update headers --- .../services/download/download-manager.ts | 6 ++--- .../download/generic-http-downloader.ts | 2 +- src/main/services/download/http-download.ts | 26 +++---------------- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index d6542396..b9c9d5a6 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -104,9 +104,9 @@ export class DownloadManager { console.log(downloadLink, token, "<<<"); - GenericHTTPDownloader.startDownload(game, downloadLink, [ - `Cookie: accountToken=${token}`, - ]); + GenericHTTPDownloader.startDownload(game, downloadLink, { + Cookie: `accountToken=${token}`, + }); } else if (game.downloader === Downloader.PixelDrain) { const id = game!.uri!.split("/").pop(); diff --git a/src/main/services/download/generic-http-downloader.ts b/src/main/services/download/generic-http-downloader.ts index 688769a4..a482c075 100644 --- a/src/main/services/download/generic-http-downloader.ts +++ b/src/main/services/download/generic-http-downloader.ts @@ -69,7 +69,7 @@ export class GenericHTTPDownloader { static async startDownload( game: Game, downloadUrl: string, - headers: string[] = [] + headers?: Record ) { this.downloadingGame = game; diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index ec16f5cd..ec9ba635 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -26,7 +26,7 @@ export class HTTPDownload { static async cancelDownload(gid: string) { const downloadItem: DownloadItem = this.downloads[gid]; downloadItem?.cancel(); - this.downloads; + delete this.downloads[gid]; } static async pauseDownload(gid: string) { @@ -42,11 +42,11 @@ export class HTTPDownload { static async startDownload( downloadPath: string, downloadUrl: string, - header: string[] = [] + headers?: Record ) { return new Promise((resolve) => { WindowManager.mainWindow?.webContents.downloadURL(downloadUrl, { - headers: { Cookie: header[0].split(": ")[1] }, + headers, }); WindowManager.mainWindow?.webContents.session.on( @@ -59,26 +59,6 @@ export class HTTPDownload { // Set the save path, making Electron not to prompt a save dialog. item.setSavePath(downloadPath); - item.on("updated", (_event, state) => { - if (state === "interrupted") { - console.log("Download is interrupted but can be resumed"); - } else if (state === "progressing") { - if (item.isPaused()) { - console.log("Download is paused"); - } else { - console.log(`Received bytes: ${item.getReceivedBytes()}`); - } - } - }); - - item.once("done", (_event, state) => { - if (state === "completed") { - console.log("Download successfully"); - } else { - console.log(`Download failed: ${state}`); - } - }); - resolve(gid.toString()); } ); From 8a1931f75ce068ff1bec78b968b27699d6149bad Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 20 Jul 2024 16:52:27 -0300 Subject: [PATCH 04/33] feat: use new electron version to get download speed --- package.json | 2 +- src/main/services/download/generic-http-downloader.ts | 8 ++++---- src/main/services/download/http-download.ts | 2 +- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index c99a9bac..d7996e6a 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@types/user-agents": "^1.0.4", "@vanilla-extract/vite-plugin": "^4.0.7", "@vitejs/plugin-react": "^4.2.1", - "electron": "^30.0.9", + "electron": "^30.3.0", "electron-builder": "^24.9.1", "electron-vite": "^2.0.0", "eslint": "^8.56.0", diff --git a/src/main/services/download/generic-http-downloader.ts b/src/main/services/download/generic-http-downloader.ts index a482c075..c078be80 100644 --- a/src/main/services/download/generic-http-downloader.ts +++ b/src/main/services/download/generic-http-downloader.ts @@ -30,11 +30,11 @@ export class GenericHTTPDownloader { const result = { numPeers: 0, numSeeds: 0, - downloadSpeed: Number(status.downloadSpeed), + downloadSpeed: status.downloadSpeed, timeRemaining: calculateETA( - Number(status.totalLength), - Number(status.completedLength), - Number(status.downloadSpeed) + status.totalLength, + status.completedLength, + status.downloadSpeed ), isDownloadingMetadata: false, isCheckingFiles: false, diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index ec9ba635..d9c36916 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -16,7 +16,7 @@ export class HTTPDownload { return { completedLength: downloadItem.getReceivedBytes(), totalLength: downloadItem.getTotalBytes(), - downloadSpeed: 0, + downloadSpeed: downloadItem.getCurrentBytesPerSecond(), }; } diff --git a/yarn.lock b/yarn.lock index 00172038..9c829f62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3739,10 +3739,10 @@ electron-vite@^2.0.0: magic-string "^0.30.5" picocolors "^1.0.0" -electron@^30.0.9: - version "30.0.9" - resolved "https://registry.npmjs.org/electron/-/electron-30.0.9.tgz" - integrity sha512-ArxgdGHVu3o5uaP+Tqj8cJDvU03R6vrGrOqiMs7JXLnvQHMqXJIIxmFKQAIdJW8VoT3ac3hD21tA7cPO10RLow== +electron@^30.1.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-30.3.0.tgz#9d7af06c11242d5c6ca5b9920c9dc49feab90299" + integrity sha512-/rWPcpCL4sYCUm1bY8if1dO8nyFTwXlPUP0dpL3ir5iLK/9NshN6lIJ8xceEY8CEYVLMIYRkxXb44Q9cdrjtOQ== dependencies: "@electron/get" "^2.0.0" "@types/node" "^20.9.0" From e1ef8a91934df6a0e0e2908740fc7595ab25baa9 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:21:05 -0300 Subject: [PATCH 05/33] feat: pass headers correctly to downloadURL --- src/main/events/auth/sign-out.ts | 2 ++ src/main/services/download/http-download.ts | 9 ++++----- src/main/services/hydra-api.ts | 8 ++++++++ yarn.lock | 8 ++++---- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/events/auth/sign-out.ts b/src/main/events/auth/sign-out.ts index fe640b9d..9998c733 100644 --- a/src/main/events/auth/sign-out.ts +++ b/src/main/events/auth/sign-out.ts @@ -26,6 +26,8 @@ const signOut = async (_event: Electron.IpcMainInvokeEvent) => { /* Disconnects libtorrent */ PythonInstance.killTorrent(); + HydraApi.handleSignOut(); + await Promise.all([ databaseOperations, HydraApi.post("/auth/logout").catch(() => {}), diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index d9c36916..d74e862e 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -45,15 +45,14 @@ export class HTTPDownload { headers?: Record ) { return new Promise((resolve) => { - WindowManager.mainWindow?.webContents.downloadURL(downloadUrl, { - headers, - }); + const options = headers ? { headers } : {}; + WindowManager.mainWindow?.webContents.downloadURL(downloadUrl, options); + + const gid = ++this.id; WindowManager.mainWindow?.webContents.session.on( "will-download", (_event, item, _webContents) => { - const gid = ++this.id; - this.downloads[gid.toString()] = item; // Set the save path, making Electron not to prompt a save dialog. diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 5365bd9e..120d27ac 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -64,6 +64,14 @@ export class HydraApi { } } + static handleSignOut() { + this.userAuth = { + authToken: "", + refreshToken: "", + expirationTimestamp: 0, + }; + } + static async setupApi() { this.instance = axios.create({ baseURL: import.meta.env.MAIN_VITE_API_URL, diff --git a/yarn.lock b/yarn.lock index c2d49574..2dd42cb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3751,10 +3751,10 @@ electron-vite@^2.0.0: magic-string "^0.30.5" picocolors "^1.0.0" -electron@^30.1.0: - version "30.3.0" - resolved "https://registry.yarnpkg.com/electron/-/electron-30.3.0.tgz#9d7af06c11242d5c6ca5b9920c9dc49feab90299" - integrity sha512-/rWPcpCL4sYCUm1bY8if1dO8nyFTwXlPUP0dpL3ir5iLK/9NshN6lIJ8xceEY8CEYVLMIYRkxXb44Q9cdrjtOQ== +electron@^30.3.0: + version "30.3.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-30.3.1.tgz#fe27ca2a4739bec832b2edd6f46140ab46bf53a0" + integrity sha512-Ai/OZ7VlbFAVYMn9J5lyvtr+ZWyEbXDVd5wBLb5EVrp4352SRmMAmN5chcIe3n9mjzcgehV9n4Hwy15CJW+YbA== dependencies: "@electron/get" "^2.0.0" "@types/node" "^20.9.0" From fab248de99c593d2ffbf85c2a7c567061ef6cb5b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:55:35 -0300 Subject: [PATCH 06/33] fix: duplicate download with real debrid --- src/main/services/download/real-debrid-downloader.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/services/download/real-debrid-downloader.ts b/src/main/services/download/real-debrid-downloader.ts index 034ffc49..4877e6e2 100644 --- a/src/main/services/download/real-debrid-downloader.ts +++ b/src/main/services/download/real-debrid-downloader.ts @@ -131,11 +131,9 @@ export class RealDebridDownloader { } static async startDownload(game: Game) { - this.downloadingGame = game; - if (this.downloads.has(game.id)) { await this.resumeDownload(game.id!); - + this.downloadingGame = game; return; } @@ -156,6 +154,7 @@ export class RealDebridDownloader { ); this.downloads.set(game.id!, gid); + this.downloadingGame = game; } } From 19b1d29713e57ec3d2229b4f7f11da24c5a44d69 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:15:36 -0300 Subject: [PATCH 07/33] fix: reset downloadingGame and torrentId on cancelDownload --- src/main/services/download/http-download.ts | 2 +- src/main/services/download/real-debrid-downloader.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index d74e862e..df1151d5 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -24,7 +24,7 @@ export class HTTPDownload { } static async cancelDownload(gid: string) { - const downloadItem: DownloadItem = this.downloads[gid]; + const downloadItem = this.downloads[gid]; downloadItem?.cancel(); delete this.downloads[gid]; } diff --git a/src/main/services/download/real-debrid-downloader.ts b/src/main/services/download/real-debrid-downloader.ts index 4877e6e2..ee5ee881 100644 --- a/src/main/services/download/real-debrid-downloader.ts +++ b/src/main/services/download/real-debrid-downloader.ts @@ -165,6 +165,9 @@ export class RealDebridDownloader { await HTTPDownload.cancelDownload(gid); this.downloads.delete(gameId); } + + this.realDebridTorrentId = null; + this.downloadingGame = null; } static async resumeDownload(gameId: number) { From 4d60317475285a49dea1435761eacc017601f456 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:39:55 -0300 Subject: [PATCH 08/33] fix: passing complete download path to setSavePath --- src/main/services/download/http-download.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index df1151d5..81f6b5fd 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -1,5 +1,6 @@ import { DownloadItem } from "electron"; import { WindowManager } from "../window-manager"; +import path from "node:path"; export class HTTPDownload { private static id = 0; @@ -56,7 +57,7 @@ export class HTTPDownload { this.downloads[gid.toString()] = item; // Set the save path, making Electron not to prompt a save dialog. - item.setSavePath(downloadPath); + item.setSavePath(path.join(downloadPath, item.getFilename())); resolve(gid.toString()); } From 4b7a0ff4023cc53dc7e80e6cc70d3cd3b6a3308f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:48:43 -0300 Subject: [PATCH 09/33] feat: setting folder name --- src/main/services/download/generic-http-downloader.ts | 1 + src/main/services/download/http-download.ts | 2 ++ src/main/services/download/real-debrid-downloader.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/src/main/services/download/generic-http-downloader.ts b/src/main/services/download/generic-http-downloader.ts index c078be80..4977d503 100644 --- a/src/main/services/download/generic-http-downloader.ts +++ b/src/main/services/download/generic-http-downloader.ts @@ -24,6 +24,7 @@ export class GenericHTTPDownloader { fileSize: Number(status.totalLength), progress, status: "active", + folderName: status.folderName, } ); diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index 81f6b5fd..fb573ff4 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -11,6 +11,7 @@ export class HTTPDownload { completedLength: number; totalLength: number; downloadSpeed: number; + folderName: string; } | null { const downloadItem = this.downloads[gid]; if (downloadItem) { @@ -18,6 +19,7 @@ export class HTTPDownload { completedLength: downloadItem.getReceivedBytes(), totalLength: downloadItem.getTotalBytes(), downloadSpeed: downloadItem.getCurrentBytesPerSecond(), + folderName: downloadItem.getFilename(), }; } diff --git a/src/main/services/download/real-debrid-downloader.ts b/src/main/services/download/real-debrid-downloader.ts index ee5ee881..fc32263c 100644 --- a/src/main/services/download/real-debrid-downloader.ts +++ b/src/main/services/download/real-debrid-downloader.ts @@ -62,6 +62,7 @@ export class RealDebridDownloader { fileSize: Number(status.totalLength), progress, status: "active", + folderName: status.folderName, } ); From f3f78248efc3a67e19b3dcd098fa94b1246fde00 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:49:10 -0300 Subject: [PATCH 10/33] fix: start another download after finishing one --- src/main/services/download/real-debrid-downloader.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/services/download/real-debrid-downloader.ts b/src/main/services/download/real-debrid-downloader.ts index fc32263c..f61cb1ff 100644 --- a/src/main/services/download/real-debrid-downloader.ts +++ b/src/main/services/download/real-debrid-downloader.ts @@ -122,9 +122,11 @@ export class RealDebridDownloader { } static async pauseDownload() { - const gid = this.downloads.get(this.downloadingGame!.id!); - if (gid) { - await HTTPDownload.pauseDownload(gid); + if (this.downloadingGame) { + const gid = this.downloads.get(this.downloadingGame.id); + if (gid) { + await HTTPDownload.pauseDownload(gid); + } } this.realDebridTorrentId = null; From 4a149aa62d733f85f11343c2cf4da175c38e332a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 5 Aug 2024 20:47:27 -0300 Subject: [PATCH 11/33] feat: remove aria2 (again) --- .gitignore | 1 - electron-builder.yml | 1 - package.json | 3 +- postinstall.cjs | 50 ----------------------- src/main/declaration.d.ts | 80 ------------------------------------- src/main/services/aria2c.ts | 20 ---------- yarn.lock | 15 +------ 7 files changed, 2 insertions(+), 168 deletions(-) delete mode 100644 postinstall.cjs delete mode 100644 src/main/declaration.d.ts delete mode 100644 src/main/services/aria2c.ts diff --git a/.gitignore b/.gitignore index 7a6496a5..fb4badd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ .vscode node_modules hydra-download-manager/ -aria2/ fastlist.exe __pycache__ dist diff --git a/electron-builder.yml b/electron-builder.yml index be300d36..cfdafe7d 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -3,7 +3,6 @@ productName: Hydra directories: buildResources: build extraResources: - - aria2 - hydra-download-manager - seeds - from: node_modules/create-desktop-shortcuts/src/windows.vbs diff --git a/package.json b/package.json index 4693f65b..54d689b2 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "electron-vite preview", "dev": "electron-vite dev", "build": "npm run typecheck && electron-vite build", - "postinstall": "electron-builder install-app-deps && node ./postinstall.cjs", + "postinstall": "electron-builder install-app-deps", "build:unpack": "npm run build && electron-builder --dir", "build:win": "electron-vite build && electron-builder --win", "build:mac": "electron-vite build && electron-builder --mac", @@ -42,7 +42,6 @@ "@vanilla-extract/css": "^1.14.2", "@vanilla-extract/dynamic": "^2.1.1", "@vanilla-extract/recipes": "^0.5.2", - "aria2": "^4.1.2", "auto-launch": "^5.0.6", "axios": "^1.6.8", "better-sqlite3": "^9.5.0", diff --git a/postinstall.cjs b/postinstall.cjs deleted file mode 100644 index 547af988..00000000 --- a/postinstall.cjs +++ /dev/null @@ -1,50 +0,0 @@ -const { default: axios } = require("axios"); -const util = require("node:util"); -const fs = require("node:fs"); - -const exec = util.promisify(require("node:child_process").exec); - -const downloadAria2 = async () => { - if (fs.existsSync("aria2")) { - console.log("Aria2 already exists, skipping download..."); - return; - } - - const file = - process.platform === "win32" - ? "aria2-1.37.0-win-64bit-build1.zip" - : "aria2-1.37.0-1-x86_64.pkg.tar.zst"; - - const downloadUrl = - process.platform === "win32" - ? `https://github.com/aria2/aria2/releases/download/release-1.37.0/${file}` - : "https://archlinux.org/packages/extra/x86_64/aria2/download/"; - - console.log(`Downloading ${file}...`); - - const response = await axios.get(downloadUrl, { responseType: "stream" }); - - const stream = response.data.pipe(fs.createWriteStream(file)); - - stream.on("finish", async () => { - console.log(`Downloaded ${file}, extracting...`); - - if (process.platform === "win32") { - await exec(`npx extract-zip ${file}`); - console.log("Extracted. Renaming folder..."); - - fs.renameSync(file.replace(".zip", ""), "aria2"); - } else { - await exec(`tar --zstd -xvf ${file} usr/bin/aria2c`); - console.log("Extracted. Copying binary file..."); - fs.mkdirSync("aria2"); - fs.copyFileSync("usr/bin/aria2c", "aria2/aria2c"); - fs.rmSync("usr", { recursive: true }); - } - - console.log(`Extracted ${file}, removing compressed downloaded file...`); - fs.rmSync(file); - }); -}; - -downloadAria2(); diff --git a/src/main/declaration.d.ts b/src/main/declaration.d.ts deleted file mode 100644 index ac2675a3..00000000 --- a/src/main/declaration.d.ts +++ /dev/null @@ -1,80 +0,0 @@ -declare module "aria2" { - export type Aria2Status = - | "active" - | "waiting" - | "paused" - | "error" - | "complete" - | "removed"; - - export interface StatusResponse { - gid: string; - status: Aria2Status; - totalLength: string; - completedLength: string; - uploadLength: string; - bitfield: string; - downloadSpeed: string; - uploadSpeed: string; - infoHash?: string; - numSeeders?: string; - seeder?: boolean; - pieceLength: string; - numPieces: string; - connections: string; - errorCode?: string; - errorMessage?: string; - followedBy?: string[]; - following: string; - belongsTo: string; - dir: string; - files: { - path: string; - length: string; - completedLength: string; - selected: string; - }[]; - bittorrent?: { - announceList: string[][]; - comment: string; - creationDate: string; - mode: "single" | "multi"; - info: { - name: string; - verifiedLength: string; - verifyIntegrityPending: string; - }; - }; - } - - export default class Aria2 { - constructor(options: any); - open: () => Promise; - call( - method: "addUri", - uris: string[], - options: { dir: string } - ): Promise; - call( - method: "tellStatus", - gid: string, - keys?: string[] - ): Promise; - call(method: "pause", gid: string): Promise; - call(method: "forcePause", gid: string): Promise; - call(method: "unpause", gid: string): Promise; - call(method: "remove", gid: string): Promise; - call(method: "forceRemove", gid: string): Promise; - call(method: "pauseAll"): Promise; - call(method: "forcePauseAll"): Promise; - listNotifications: () => [ - "onDownloadStart", - "onDownloadPause", - "onDownloadStop", - "onDownloadComplete", - "onDownloadError", - "onBtDownloadComplete", - ]; - on: (event: string, callback: (params: any) => void) => void; - } -} diff --git a/src/main/services/aria2c.ts b/src/main/services/aria2c.ts deleted file mode 100644 index b1b1da76..00000000 --- a/src/main/services/aria2c.ts +++ /dev/null @@ -1,20 +0,0 @@ -import path from "node:path"; -import { spawn } from "node:child_process"; -import { app } from "electron"; - -export const startAria2 = () => { - const binaryPath = app.isPackaged - ? path.join(process.resourcesPath, "aria2", "aria2c") - : path.join(__dirname, "..", "..", "aria2", "aria2c"); - - return spawn( - binaryPath, - [ - "--enable-rpc", - "--rpc-listen-all", - "--file-allocation=none", - "--allow-overwrite=true", - ], - { stdio: "inherit", windowsHide: true } - ); -}; diff --git a/yarn.lock b/yarn.lock index 2dd42cb8..cf749216 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2677,14 +2677,6 @@ aria-query@^5.3.0: dependencies: dequal "^2.0.3" -aria2@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/aria2/-/aria2-4.1.2.tgz#0ecbc50beea82856c88b4de71dac336154f67362" - integrity sha512-qTBr2RY8RZQmiUmbj2KXFvkErNxU4aTHZszszzwhE8svy2PEVX+IYR/c4Rp9Tuw4QkeU8cylGy6McV6Yl8i7Qw== - dependencies: - node-fetch "^2.6.1" - ws "^7.4.0" - array-buffer-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz" @@ -5833,7 +5825,7 @@ node-domexception@^1.0.0: resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== -node-fetch@^2.6.1, node-fetch@^2.6.7: +node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -7629,11 +7621,6 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^7.4.0: - version "7.5.10" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" - integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== - ws@^8.16.0: version "8.17.0" resolved "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz" From 6806787ca0e2cb4d4eeec2a68930f29ccd06732b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 6 Aug 2024 23:17:12 -0300 Subject: [PATCH 12/33] feat: set profile visibility --- src/locales/en/translation.json | 7 +- src/locales/pt/translation.json | 7 +- src/main/events/index.ts | 6 +- src/main/events/profile/update-profile.ts | 29 ++-- src/main/helpers/index.ts | 3 + src/preload/index.ts | 5 +- src/renderer/src/declaration.d.ts | 5 +- src/renderer/src/hooks/use-user-details.ts | 16 +- src/renderer/src/pages/user/user-content.tsx | 15 +- .../src/pages/user/user-edit-modal.tsx | 147 ----------------- .../user-profile-settings-modal/index.tsx | 1 + .../user-block-list.tsx | 0 .../user-edit-profile.tsx | 150 ++++++++++++++++++ .../user-profile-settings-modal.tsx | 72 +++++++++ src/types/index.ts | 7 + 15 files changed, 276 insertions(+), 194 deletions(-) delete mode 100644 src/renderer/src/pages/user/user-edit-modal.tsx create mode 100644 src/renderer/src/pages/user/user-profile-settings-modal/index.tsx create mode 100644 src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx create mode 100644 src/renderer/src/pages/user/user-profile-settings-modal/user-edit-profile.tsx create mode 100644 src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index b24509d3..631e50c1 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -261,6 +261,11 @@ "undo_friendship": "Undo friendship", "request_accepted": "Request accepted", "user_blocked_successfully": "User blocked successfully", - "user_block_modal_text": "This will block {{displayName}}" + "user_block_modal_text": "This will block {{displayName}}", + "settings": "Settings", + "public": "Public", + "private": "Private", + "friends_only": "Friends only", + "privacy": "Privacy" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index ef94c31f..5b24b574 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -261,6 +261,11 @@ "undo_friendship": "Desfazer amizade", "request_accepted": "Pedido de amizade aceito", "user_blocked_successfully": "Usuário bloqueado com sucesso", - "user_block_modal_text": "Bloquear {{displayName}}" + "user_block_modal_text": "Bloquear {{displayName}}", + "settings": "Configurações", + "privacy": "Privacidade", + "private": "Privado", + "friends_only": "Apenas amigos", + "public": "Público" } } diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 57daf51c..9cdc58b5 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -52,11 +52,9 @@ import "./profile/undo-friendship"; import "./profile/update-friend-request"; import "./profile/update-profile"; import "./profile/send-friend-request"; +import { isPortableVersion } from "@main/helpers"; ipcMain.handle("ping", () => "pong"); ipcMain.handle("getVersion", () => app.getVersion()); -ipcMain.handle( - "isPortableVersion", - () => process.env.PORTABLE_EXECUTABLE_FILE != null -); +ipcMain.handle("isPortableVersion", () => isPortableVersion()); ipcMain.handle("getDefaultDownloadsPath", () => defaultDownloadsPath); diff --git a/src/main/events/profile/update-profile.ts b/src/main/events/profile/update-profile.ts index 8620eaa1..50d2ab66 100644 --- a/src/main/events/profile/update-profile.ts +++ b/src/main/events/profile/update-profile.ts @@ -4,33 +4,22 @@ import axios from "axios"; import fs from "node:fs"; import path from "node:path"; import { fileTypeFromFile } from "file-type"; -import { UserProfile } from "@types"; +import { UpdateProfileProps, UserProfile } from "@types"; -const patchUserProfile = async ( - displayName: string, - profileImageUrl?: string -) => { - if (profileImageUrl) { - return HydraApi.patch("/profile", { - displayName, - profileImageUrl, - }); - } else { - return HydraApi.patch("/profile", { - displayName, - }); - } +const patchUserProfile = async (updateProfile: UpdateProfileProps) => { + return HydraApi.patch("/profile", updateProfile); }; const updateProfile = async ( _event: Electron.IpcMainInvokeEvent, - displayName: string, - newProfileImagePath: string | null + updateProfile: UpdateProfileProps ): Promise => { - if (!newProfileImagePath) { - return patchUserProfile(displayName); + if (!updateProfile.profileImageUrl) { + return patchUserProfile(updateProfile); } + const newProfileImagePath = updateProfile.profileImageUrl; + const stats = fs.statSync(newProfileImagePath); const fileBuffer = fs.readFileSync(newProfileImagePath); const fileSizeInBytes = stats.size; @@ -53,7 +42,7 @@ const updateProfile = async ( }) .catch(() => undefined); - return patchUserProfile(displayName, profileImageUrl); + return patchUserProfile({ ...updateProfile, profileImageUrl }); }; registerEvent("updateProfile", updateProfile); diff --git a/src/main/helpers/index.ts b/src/main/helpers/index.ts index 902b927d..f2b86e5a 100644 --- a/src/main/helpers/index.ts +++ b/src/main/helpers/index.ts @@ -57,4 +57,7 @@ export const requestWebPage = async (url: string) => { .then((response) => response.data); }; +export const isPortableVersion = () => + process.env.PORTABLE_EXECUTABLE_FILE != null; + export * from "./download-source"; diff --git a/src/preload/index.ts b/src/preload/index.ts index 3350a340..84100cab 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -10,6 +10,7 @@ import type { StartGameDownloadPayload, GameRunning, FriendRequestAction, + UpdateProfileProps, } from "@types"; contextBridge.exposeInMainWorld("electron", { @@ -137,8 +138,8 @@ contextBridge.exposeInMainWorld("electron", { getMe: () => ipcRenderer.invoke("getMe"), undoFriendship: (userId: string) => ipcRenderer.invoke("undoFriendship", userId), - updateProfile: (displayName: string, newProfileImagePath: string | null) => - ipcRenderer.invoke("updateProfile", displayName, newProfileImagePath), + updateProfile: (updateProfile: UpdateProfileProps) => + ipcRenderer.invoke("updateProfile", updateProfile), getFriendRequests: () => ipcRenderer.invoke("getFriendRequests"), updateFriendRequest: (userId: string, action: FriendRequestAction) => ipcRenderer.invoke("updateFriendRequest", userId, action), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index e022cffe..3e954d40 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -139,10 +139,7 @@ declare global { /* Profile */ getMe: () => Promise; undoFriendship: (userId: string) => Promise; - updateProfile: ( - displayName: string, - newProfileImagePath: string | null - ) => Promise; + updateProfile: (updateProfile: UpdateProfileProps) => Promise; getFriendRequests: () => Promise; updateFriendRequest: ( userId: string, diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 21690e7e..e33de978 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -8,8 +8,9 @@ import { setFriendsModalHidden, } from "@renderer/features"; import { profileBackgroundFromProfileImage } from "@renderer/helpers"; -import { FriendRequestAction, UserDetails } from "@types"; +import { FriendRequestAction, UpdateProfileProps, UserDetails } from "@types"; import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal"; +import { logger } from "@renderer/logger"; export function useUserDetails() { const dispatch = useAppDispatch(); @@ -43,7 +44,10 @@ export function useUserDetails() { if (userDetails.profileImageUrl) { const profileBackground = await profileBackgroundFromProfileImage( userDetails.profileImageUrl - ); + ).catch((err) => { + logger.error("profileBackgroundFromProfileImage", err); + return `#151515B3`; + }); dispatch(setProfileBackground(profileBackground)); window.localStorage.setItem( @@ -74,12 +78,8 @@ export function useUserDetails() { }, [clearUserDetails]); const patchUser = useCallback( - async (displayName: string, imageProfileUrl: string | null) => { - const response = await window.electron.updateProfile( - displayName, - imageProfileUrl - ); - + async (props: UpdateProfileProps) => { + const response = await window.electron.updateProfile(props); return updateUserDetails(response); }, [updateUserDetails] diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 81db119d..7367ed5f 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -25,7 +25,7 @@ import { XCircleIcon, } from "@primer/octicons-react"; import { Button, Link } from "@renderer/components"; -import { UserEditProfileModal } from "./user-edit-modal"; +import { UserProfileSettingsModal } from "./user-profile-settings-modal"; import { UserSignOutModal } from "./user-sign-out-modal"; import { UserFriendModalTab } from "../shared-modals/user-friend-modal"; import { UserBlockModal } from "./user-block-modal"; @@ -60,7 +60,8 @@ export function UserContent({ const [profileContentBoxBackground, setProfileContentBoxBackground] = useState(); - const [showEditProfileModal, setShowEditProfileModal] = useState(false); + const [showProfileSettingsModal, setShowProfileSettingsModal] = + useState(false); const [showSignOutModal, setShowSignOutModal] = useState(false); const [showUserBlockModal, setShowUserBlockModal] = useState(false); @@ -95,7 +96,7 @@ export function UserContent({ }; const handleEditProfile = () => { - setShowEditProfileModal(true); + setShowProfileSettingsModal(true); }; const handleOnClickFriend = (userId: string) => { @@ -165,7 +166,7 @@ export function UserContent({ return ( <> - - setDisplayName(e.target.value)} - /> - - - - - ); -}; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/index.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/index.tsx new file mode 100644 index 00000000..896d3684 --- /dev/null +++ b/src/renderer/src/pages/user/user-profile-settings-modal/index.tsx @@ -0,0 +1 @@ +export * from "./user-profile-settings-modal"; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-edit-profile.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-edit-profile.tsx new file mode 100644 index 00000000..aa78c1ea --- /dev/null +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-edit-profile.tsx @@ -0,0 +1,150 @@ +import { DeviceCameraIcon, PersonIcon } from "@primer/octicons-react"; +import { Button, SelectField, TextField } from "@renderer/components"; +import { useToast, useUserDetails } from "@renderer/hooks"; +import { UserProfile } from "@types"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import * as styles from "../user.css"; +import { SPACING_UNIT } from "@renderer/theme.css"; + +export interface UserEditProfileProps { + userProfile: UserProfile; + updateUserProfile: () => Promise; +} + +export const UserEditProfile = ({ + userProfile, + updateUserProfile, +}: UserEditProfileProps) => { + const { t } = useTranslation("user_profile"); + + const [form, setForm] = useState({ + displayName: userProfile.displayName, + profileVisibility: userProfile.profileVisibility, + imageProfileUrl: null as string | null, + }); + const [isSaving, setIsSaving] = useState(false); + + const { patchUser } = useUserDetails(); + + const { showSuccessToast, showErrorToast } = useToast(); + + const [profileVisibilityOptions, setProfileVisibilityOptions] = useState< + { value: string; label: string }[] + >([]); + + useEffect(() => { + setProfileVisibilityOptions([ + { value: "PUBLIC", label: t("public") }, + { value: "FRIENDS", label: t("friends_only") }, + { value: "PRIVATE", label: t("private") }, + ]); + }, [t]); + + const handleChangeProfileAvatar = async () => { + const { filePaths } = await window.electron.showOpenDialog({ + properties: ["openFile"], + filters: [ + { + name: "Image", + extensions: ["jpg", "jpeg", "png", "webp"], + }, + ], + }); + + if (filePaths && filePaths.length > 0) { + const path = filePaths[0]; + + setForm({ ...form, imageProfileUrl: path }); + } + }; + + const handleProfileVisibilityChange = (event) => { + setForm({ + ...form, + profileVisibility: event.target.value, + }); + }; + + const handleSaveProfile: React.FormEventHandler = async ( + event + ) => { + event.preventDefault(); + setIsSaving(true); + + patchUser(form) + .then(async () => { + await updateUserProfile(); + showSuccessToast(t("saved_successfully")); + }) + .catch(() => { + showErrorToast(t("try_again")); + }) + .finally(() => { + setIsSaving(false); + }); + }; + + const avatarUrl = useMemo(() => { + if (form.imageProfileUrl) return `local:${form.imageProfileUrl}`; + if (userProfile.profileImageUrl) return userProfile.profileImageUrl; + return null; + }, [form, userProfile]); + + return ( +
+ + + setForm({ ...form, displayName: e.target.value })} + /> + + ({ + key: visiblity.value, + value: visiblity.value, + label: visiblity.label, + }))} + /> + + + + ); +}; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx new file mode 100644 index 00000000..5615cf12 --- /dev/null +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx @@ -0,0 +1,72 @@ +import { Button, Modal } from "@renderer/components"; +import { UserProfile } from "@types"; +import { SPACING_UNIT } from "@renderer/theme.css"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UserEditProfile } from "./user-edit-profile"; + +export interface UserEditProfileModalProps { + userProfile: UserProfile; + visible: boolean; + onClose: () => void; + updateUserProfile: () => Promise; +} + +export const UserProfileSettingsModal = ({ + userProfile, + visible, + onClose, + updateUserProfile, +}: UserEditProfileModalProps) => { + const { t } = useTranslation("user_profile"); + + const tabs = [t("edit_profile"), "Ban list"]; + + const [currentTabIndex, setCurrentTabIndex] = useState(0); + + const renderTab = () => { + if (currentTabIndex == 0) { + return ( + + ); + } + + if (currentTabIndex == 1) { + return <>; + } + + return <>; + }; + + return ( + <> + +
+
+ {tabs.map((tab, index) => { + return ( + + ); + })} +
+ {renderTab()} +
+
+ + ); +}; diff --git a/src/types/index.ts b/src/types/index.ts index ac352a91..cb2f8c27 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -310,6 +310,13 @@ export interface UserProfile { relation: UserRelation | null; } +export interface UpdateProfileProps { + displayName?: string; + profileVisibility?: "PUBLIC" | "PRIVATE" | "FRIENDS"; + profileImageUrl?: string | null; + bio?: string; +} + export interface DownloadSource { id: number; name: string; From 51e86819c15df17095fe99a59360d3585b17a3d2 Mon Sep 17 00:00:00 2001 From: Lianela <140931995+Lianela@users.noreply.github.com> Date: Sun, 11 Aug 2024 15:29:05 -0600 Subject: [PATCH 13/33] Update ES translation.json --- src/locales/es/translation.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index fcb2b099..0800b0c9 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -250,6 +250,17 @@ "friend_request_sent": "Solicitud de amistad enviada", "friends": "Amigos", "friends_list": "Lista de amigos", - "user_not_found": "Usuario no encontrado" + "user_not_found": "Usuario no encontrado", + "block_user": "Bloquear usuario", + "add_friend": "Añadir amigo", + "request_sent": "Solicitud enviada", + "request_received": "Solicitud recibida", + "accept_request": "Aceptar solicitud", + "ignore_request": "Ignorar solicitud", + "cancel_request": "Cancelar solicitud", + "undo_friendship": "Eliminar amistad", + "request_accepted": "Solicitud aceptada", + "user_blocked_successfully": "Usuario bloqueado exitosamente", + "user_block_modal_text": "Esto va a bloquear a {{displayName}}" } } From 7e6b9ca825684fec61aa56b17fcf183ea85104d4 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:12:26 -0300 Subject: [PATCH 14/33] feat: conditional to show user content section based on visibility --- src/locales/en/translation.json | 3 +- src/locales/pt/translation.json | 3 +- src/renderer/src/pages/user/user-content.tsx | 324 +++++++++--------- .../user-profile-settings-modal.tsx | 2 +- 4 files changed, 173 insertions(+), 159 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 631e50c1..9e190778 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -266,6 +266,7 @@ "public": "Public", "private": "Private", "friends_only": "Friends only", - "privacy": "Privacy" + "privacy": "Privacy", + "blocked_users": "Blocked users" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 5b24b574..036e2ff0 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -266,6 +266,7 @@ "privacy": "Privacidade", "private": "Privado", "friends_only": "Apenas amigos", - "public": "Público" + "public": "Público", + "blocked_users": "Usuários bloqueados" } } diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 7367ed5f..b9502231 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -160,6 +160,11 @@ export function UserContent({ }; const showFriends = isMe || userProfile.totalFriends > 0; + const showProfileContent = + isMe || + userProfile.profileVisibility === "PUBLIC" || + (userProfile.relation?.status === "ACCEPTED" && + userProfile.profileVisibility === "FRIENDS"); const getProfileActions = () => { if (isMe) { @@ -362,125 +367,73 @@ export function UserContent({ -
-
-

{t("activity")}

- - {!userProfile.recentGames.length ? ( -
-
- -
-

{t("no_recent_activity_title")}

- {isMe && ( -

- {t("no_recent_activity_description")} -

- )} -
- ) : ( -
- {userProfile.recentGames.map((game) => ( - - ))} -
- )} -
- -
+ {showProfileContent && ( +
-
-

{t("library")}

+

{t("activity")}

+ {!userProfile.recentGames.length ? ( +
+
+ +
+

{t("no_recent_activity_title")}

+ {isMe && ( +

+ {t("no_recent_activity_description")} +

+ )} +
+ ) : (
-

- {userProfile.libraryGames.length} -

-
- {t("total_play_time", { amount: formatPlayTime() })} -
- {userProfile.libraryGames.map((game) => ( - - ))} -
+
+

{game.title}

+ + {t("last_time_played", { + period: formatDistance( + game.lastTimePlayed!, + new Date(), + { + addSuffix: true, + } + ), + })} + +
+ + ))} +
+ )}
- {showFriends && ( -
- - +
+ + {t("total_play_time", { amount: formatPlayTime() })} +
- {userProfile.friends.map((friend) => { - return ( - - ); - })} - - {isMe && ( - - )} + {game.iconUrl ? ( + {game.title} + ) : ( + + )} + + ))}
- )} + + {showFriends && ( +
+ + +
+ {userProfile.friends.map((friend) => { + return ( + + ); + })} + + {isMe && ( + + )} +
+
+ )} +
- + )} ); } diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx index 5615cf12..7818b4f8 100644 --- a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx @@ -20,7 +20,7 @@ export const UserProfileSettingsModal = ({ }: UserEditProfileModalProps) => { const { t } = useTranslation("user_profile"); - const tabs = [t("edit_profile"), "Ban list"]; + const tabs = [t("edit_profile"), t("blocked_users")]; const [currentTabIndex, setCurrentTabIndex] = useState(0); From fbe3c1973a7d9a2b6995d1d1b3f49d5b4a0ad85b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:45:48 -0300 Subject: [PATCH 15/33] feat: list blocked users --- src/main/events/index.ts | 1 + src/main/events/user/get-user-blocks.ts | 13 +++ src/preload/index.ts | 2 + src/renderer/src/declaration.d.ts | 2 + .../user-block-list.tsx | 92 +++++++++++++++++++ .../user-profile-settings-modal.tsx | 7 +- src/types/index.ts | 5 + 7 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 src/main/events/user/get-user-blocks.ts diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 9cdc58b5..3963e4b0 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -43,6 +43,7 @@ import "./auth/sign-out"; import "./auth/open-auth-window"; import "./auth/get-session-hash"; import "./user/get-user"; +import "./user/get-user-blocks"; import "./user/block-user"; import "./user/unblock-user"; import "./user/get-user-friends"; diff --git a/src/main/events/user/get-user-blocks.ts b/src/main/events/user/get-user-blocks.ts new file mode 100644 index 00000000..65bb3eb4 --- /dev/null +++ b/src/main/events/user/get-user-blocks.ts @@ -0,0 +1,13 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import { UserBlocks } from "@types"; + +export const getUserBlocks = async ( + _event: Electron.IpcMainInvokeEvent, + take: number, + skip: number +): Promise => { + return HydraApi.get(`/profile/blocks`, { take, skip }); +}; + +registerEvent("getUserBlocks", getUserBlocks); diff --git a/src/preload/index.ts b/src/preload/index.ts index 84100cab..087d573a 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -152,6 +152,8 @@ contextBridge.exposeInMainWorld("electron", { unblockUser: (userId: string) => ipcRenderer.invoke("unblockUser", userId), getUserFriends: (userId: string, take: number, skip: number) => ipcRenderer.invoke("getUserFriends", userId, take, skip), + getUserBlocks: (take: number, skip: number) => + ipcRenderer.invoke("getUserBlocks", take, skip), /* Auth */ signOut: () => ipcRenderer.invoke("signOut"), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 3e954d40..29e4dcbb 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -17,6 +17,7 @@ import type { FriendRequest, FriendRequestAction, UserFriends, + UserBlocks, } from "@types"; import type { DiskSpace } from "check-disk-space"; @@ -135,6 +136,7 @@ declare global { take: number, skip: number ) => Promise; + getUserBlocks: (take: number, skip: number) => Promise; /* Profile */ getMe: () => Promise; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx index e69de29b..5b5d0e74 100644 --- a/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx @@ -0,0 +1,92 @@ +import { SPACING_UNIT } from "@renderer/theme.css"; +import { UserFriend } from "@types"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useToast, useUserDetails } from "@renderer/hooks"; +import { useTranslation } from "react-i18next"; +import { UserFriendItem } from "@renderer/pages/shared-modals/user-friend-modal/user-friend-item"; + +export interface UserEditProfileBlockListProps { + closeModal: () => void; +} + +const pageSize = 12; + +export const UserEditProfileBlockList = ({ + closeModal, +}: UserEditProfileBlockListProps) => { + const { t } = useTranslation("user_profile"); + const { showErrorToast } = useToast(); + const navigate = useNavigate(); + + const [page, setPage] = useState(0); + const [maxPage, setMaxPage] = useState(0); + const [blocks, setBlocks] = useState([]); + + const { unblockUser } = useUserDetails(); + + const loadNextPage = () => { + if (page > maxPage) return; + window.electron + .getUserBlocks(pageSize, page * pageSize) + .then((newPage) => { + if (page === 0) { + setMaxPage(newPage.totalBlocks / pageSize); + } + + setBlocks([...blocks, ...newPage.blocks]); + setPage(page + 1); + }) + .catch(() => {}); + }; + + const reloadList = () => { + setPage(0); + setMaxPage(0); + setBlocks([]); + loadNextPage(); + }; + + useEffect(() => { + reloadList(); + }, []); + + const handleClickBlocked = (userId: string) => { + closeModal(); + navigate(`/user/${userId}`); + }; + + const handleUnblock = (userId: string) => { + unblockUser(userId) + .then(() => { + reloadList(); + }) + .catch(() => { + showErrorToast(t("try_again")); + }); + }; + + return ( +
+ {blocks.map((friend) => { + return ( + + ); + })} +
+ ); +}; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx index 7818b4f8..a2fcb372 100644 --- a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx @@ -4,8 +4,9 @@ import { SPACING_UNIT } from "@renderer/theme.css"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { UserEditProfile } from "./user-edit-profile"; +import { UserEditProfileBlockList } from "./user-block-list"; -export interface UserEditProfileModalProps { +export interface UserProfileSettingsModalProps { userProfile: UserProfile; visible: boolean; onClose: () => void; @@ -17,7 +18,7 @@ export const UserProfileSettingsModal = ({ visible, onClose, updateUserProfile, -}: UserEditProfileModalProps) => { +}: UserProfileSettingsModalProps) => { const { t } = useTranslation("user_profile"); const tabs = [t("edit_profile"), t("blocked_users")]; @@ -35,7 +36,7 @@ export const UserProfileSettingsModal = ({ } if (currentTabIndex == 1) { - return <>; + return ; } return <>; diff --git a/src/types/index.ts b/src/types/index.ts index cb2f8c27..6200d825 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -282,6 +282,11 @@ export interface UserFriends { friends: UserFriend[]; } +export interface UserBlocks { + totalBlocks: number; + blocks: UserFriend[]; +} + export interface FriendRequest { id: string; displayName: string; From 68b361e60512d5cd2acb30e300a532414986aeb6 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Thu, 15 Aug 2024 20:46:21 +0100 Subject: [PATCH 16/33] feat: fixing real debrid download --- package.json | 3 +- .../events/torrenting/start-game-download.ts | 4 - .../services/download/download-manager.ts | 2 - .../download/generic-http-downloader.ts | 25 ++- src/main/services/download/http-download.ts | 2 +- .../download/real-debrid-downloader.ts | 58 ++----- src/main/services/real-debrid.ts | 2 +- src/renderer/src/app.css.ts | 2 +- src/renderer/src/components/hero/hero.css.ts | 1 - .../src/components/modal/modal.css.ts | 1 + src/renderer/src/main.tsx | 10 +- .../src/pages/downloads/downloads.tsx | 4 +- .../description-header/description-header.tsx | 5 +- .../pages/game-details/game-details.css.ts | 1 - .../pages/game-details/hero/hero-panel.css.ts | 1 + .../modals/download-settings-modal.tsx | 24 ++- .../modals/game-options-modal.css.ts | 1 - .../pages/game-details/sidebar/sidebar.css.ts | 1 - .../settings/settings-download-sources.tsx | 4 +- .../settings/settings-real-debrid.css.ts | 1 - .../src/pages/user/user-block-modal.tsx | 4 +- src/renderer/src/pages/user/user-content.tsx | 6 +- .../src/pages/user/user-sign-out-modal.tsx | 2 +- src/shared/index.ts | 3 + torrent-client/downloader.py | 62 ------- torrent-client/main.py | 26 +-- torrent-client/torrent_downloader.py | 158 ++++++++++++++++++ yarn.lock | 13 +- 28 files changed, 241 insertions(+), 185 deletions(-) delete mode 100644 torrent-client/downloader.py create mode 100644 torrent-client/torrent_downloader.py diff --git a/package.json b/package.json index 54d689b2..aa77084e 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,7 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.0", "@electron-toolkit/utils": "^3.0.0", - "@fontsource/fira-mono": "^5.0.13", - "@fontsource/fira-sans": "^5.0.20", + "@fontsource/noto-sans": "^5.0.22", "@primer/octicons-react": "^19.9.0", "@reduxjs/toolkit": "^2.2.3", "@sentry/electron": "^5.1.0", diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index aa33c99a..cea41596 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -44,8 +44,6 @@ const startGameDownload = async ( ); if (game) { - console.log("game", game); - await gameRepository.update( { id: game.id, @@ -97,8 +95,6 @@ const startGameDownload = async ( }, }); - console.log(updatedGame); - createGame(updatedGame!); await downloadQueueRepository.delete({ game: { id: updatedGame!.id } }); diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index b9c9d5a6..52a66693 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -102,8 +102,6 @@ export class DownloadManager { const token = await GofileApi.authorize(); const downloadLink = await GofileApi.getDownloadLink(id!); - console.log(downloadLink, token, "<<<"); - GenericHTTPDownloader.startDownload(game, downloadLink, { Cookie: `accountToken=${token}`, }); diff --git a/src/main/services/download/generic-http-downloader.ts b/src/main/services/download/generic-http-downloader.ts index 4977d503..8384a5fd 100644 --- a/src/main/services/download/generic-http-downloader.ts +++ b/src/main/services/download/generic-http-downloader.ts @@ -2,7 +2,7 @@ import { Game } from "@main/entity"; import { gameRepository } from "@main/repository"; import { calculateETA } from "./helpers"; import { DownloadProgress } from "@types"; -import { HTTPDownload } from "./http-download"; +import { HttpDownload } from "./http-download"; export class GenericHTTPDownloader { private static downloads = new Map(); @@ -11,7 +11,7 @@ export class GenericHTTPDownloader { public static async getStatus() { if (this.downloadingGame) { const gid = this.downloads.get(this.downloadingGame.id)!; - const status = await HTTPDownload.getStatus(gid); + const status = HttpDownload.getStatus(gid); if (status) { const progress = @@ -60,7 +60,7 @@ export class GenericHTTPDownloader { const gid = this.downloads.get(this.downloadingGame!.id!); if (gid) { - await HTTPDownload.pauseDownload(gid); + await HttpDownload.pauseDownload(gid); } this.downloadingGame = null; @@ -76,26 +76,23 @@ export class GenericHTTPDownloader { if (this.downloads.has(game.id)) { await this.resumeDownload(game.id!); - return; } - if (downloadUrl) { - const gid = await HTTPDownload.startDownload( - game.downloadPath!, - downloadUrl, - headers - ); + const gid = await HttpDownload.startDownload( + game.downloadPath!, + downloadUrl, + headers + ); - this.downloads.set(game.id!, gid); - } + this.downloads.set(game.id!, gid); } static async cancelDownload(gameId: number) { const gid = this.downloads.get(gameId); if (gid) { - await HTTPDownload.cancelDownload(gid); + await HttpDownload.cancelDownload(gid); this.downloads.delete(gameId); } } @@ -104,7 +101,7 @@ export class GenericHTTPDownloader { const gid = this.downloads.get(gameId); if (gid) { - await HTTPDownload.resumeDownload(gid); + await HttpDownload.resumeDownload(gid); } } } diff --git a/src/main/services/download/http-download.ts b/src/main/services/download/http-download.ts index fb573ff4..cd8cbee5 100644 --- a/src/main/services/download/http-download.ts +++ b/src/main/services/download/http-download.ts @@ -2,7 +2,7 @@ import { DownloadItem } from "electron"; import { WindowManager } from "../window-manager"; import path from "node:path"; -export class HTTPDownload { +export class HttpDownload { private static id = 0; private static downloads: Record = {}; diff --git a/src/main/services/download/real-debrid-downloader.ts b/src/main/services/download/real-debrid-downloader.ts index f61cb1ff..c6925f57 100644 --- a/src/main/services/download/real-debrid-downloader.ts +++ b/src/main/services/download/real-debrid-downloader.ts @@ -3,7 +3,7 @@ import { RealDebridClient } from "../real-debrid"; import { gameRepository } from "@main/repository"; import { calculateETA } from "./helpers"; import { DownloadProgress } from "@types"; -import { HTTPDownload } from "./http-download"; +import { HttpDownload } from "./http-download"; export class RealDebridDownloader { private static downloads = new Map(); @@ -13,19 +13,23 @@ export class RealDebridDownloader { private static async getRealDebridDownloadUrl() { if (this.realDebridTorrentId) { - const torrentInfo = await RealDebridClient.getTorrentInfo( + let torrentInfo = await RealDebridClient.getTorrentInfo( this.realDebridTorrentId ); - const { status, links } = torrentInfo; - - if (status === "waiting_files_selection") { + if (torrentInfo.status === "waiting_files_selection") { await RealDebridClient.selectAllFiles(this.realDebridTorrentId); - return null; + + torrentInfo = await RealDebridClient.getTorrentInfo( + this.realDebridTorrentId + ); } + const { links, status } = torrentInfo; + if (status === "downloaded") { const [link] = links; + const { download } = await RealDebridClient.unrestrictLink(link); return decodeURIComponent(download); } @@ -38,8 +42,6 @@ export class RealDebridDownloader { this.downloadingGame?.uri ); - console.log("download>>", download); - return decodeURIComponent(download); } @@ -49,7 +51,7 @@ export class RealDebridDownloader { public static async getStatus() { if (this.downloadingGame) { const gid = this.downloads.get(this.downloadingGame.id)!; - const status = await HTTPDownload.getStatus(gid); + const status = HttpDownload.getStatus(gid); if (status) { const progress = @@ -91,33 +93,6 @@ export class RealDebridDownloader { } } - if (this.realDebridTorrentId && this.downloadingGame) { - const torrentInfo = await RealDebridClient.getTorrentInfo( - this.realDebridTorrentId - ); - - const { status } = torrentInfo; - - if (status === "downloaded") { - this.startDownload(this.downloadingGame); - } - - const progress = torrentInfo.progress / 100; - const totalDownloaded = progress * torrentInfo.bytes; - - return { - numPeers: 0, - numSeeds: torrentInfo.seeders, - downloadSpeed: torrentInfo.speed, - timeRemaining: calculateETA( - torrentInfo.bytes, - totalDownloaded, - torrentInfo.speed - ), - isDownloadingMetadata: status === "magnet_conversion", - } as DownloadProgress; - } - return null; } @@ -125,7 +100,7 @@ export class RealDebridDownloader { if (this.downloadingGame) { const gid = this.downloads.get(this.downloadingGame.id); if (gid) { - await HTTPDownload.pauseDownload(gid); + await HttpDownload.pauseDownload(gid); } } @@ -146,18 +121,19 @@ export class RealDebridDownloader { ); } + this.downloadingGame = game; + const downloadUrl = await this.getRealDebridDownloadUrl(); if (downloadUrl) { this.realDebridTorrentId = null; - const gid = await HTTPDownload.startDownload( + const gid = await HttpDownload.startDownload( game.downloadPath!, downloadUrl ); this.downloads.set(game.id!, gid); - this.downloadingGame = game; } } @@ -165,7 +141,7 @@ export class RealDebridDownloader { const gid = this.downloads.get(gameId); if (gid) { - await HTTPDownload.cancelDownload(gid); + await HttpDownload.cancelDownload(gid); this.downloads.delete(gameId); } @@ -177,7 +153,7 @@ export class RealDebridDownloader { const gid = this.downloads.get(gameId); if (gid) { - await HTTPDownload.resumeDownload(gid); + await HttpDownload.resumeDownload(gid); } } } diff --git a/src/main/services/real-debrid.ts b/src/main/services/real-debrid.ts index 2e0debe6..26ba4c79 100644 --- a/src/main/services/real-debrid.ts +++ b/src/main/services/real-debrid.ts @@ -46,7 +46,7 @@ export class RealDebridClient { static async selectAllFiles(id: string) { const searchParams = new URLSearchParams({ files: "all" }); - await this.instance.post( + return this.instance.post( `/torrents/selectFiles/${id}`, searchParams.toString() ); diff --git a/src/renderer/src/app.css.ts b/src/renderer/src/app.css.ts index a5f9394b..c829021a 100644 --- a/src/renderer/src/app.css.ts +++ b/src/renderer/src/app.css.ts @@ -26,7 +26,7 @@ globalStyle("html, body, #root, main", { globalStyle("body", { overflow: "hidden", userSelect: "none", - fontFamily: "'Fira Mono', monospace", + fontFamily: "Noto Sans, sans-serif", fontSize: vars.size.body, background: vars.color.background, color: vars.color.body, diff --git a/src/renderer/src/components/hero/hero.css.ts b/src/renderer/src/components/hero/hero.css.ts index cdb36ee2..eaf0a101 100644 --- a/src/renderer/src/components/hero/hero.css.ts +++ b/src/renderer/src/components/hero/hero.css.ts @@ -45,7 +45,6 @@ export const description = style({ maxWidth: "700px", color: vars.color.muted, textAlign: "left", - fontFamily: "'Fira Sans', sans-serif", lineHeight: "20px", marginTop: `${SPACING_UNIT * 2}px`, }); diff --git a/src/renderer/src/components/modal/modal.css.ts b/src/renderer/src/components/modal/modal.css.ts index 45154015..d9d14fda 100644 --- a/src/renderer/src/components/modal/modal.css.ts +++ b/src/renderer/src/components/modal/modal.css.ts @@ -24,6 +24,7 @@ export const modal = recipe({ animation: `${scaleFadeIn} 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running`, backgroundColor: vars.color.background, borderRadius: "4px", + minWidth: "400px", maxWidth: "600px", color: vars.color.body, maxHeight: "100%", diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index f87d66bf..b88348f0 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -8,12 +8,10 @@ import { HashRouter, Route, Routes } from "react-router-dom"; import * as Sentry from "@sentry/electron/renderer"; -import "@fontsource/fira-mono/400.css"; -import "@fontsource/fira-mono/500.css"; -import "@fontsource/fira-mono/700.css"; -import "@fontsource/fira-sans/400.css"; -import "@fontsource/fira-sans/500.css"; -import "@fontsource/fira-sans/700.css"; +import "@fontsource/noto-sans/400.css"; +import "@fontsource/noto-sans/500.css"; +import "@fontsource/noto-sans/700.css"; + import "react-loading-skeleton/dist/skeleton.css"; import { App } from "./app"; diff --git a/src/renderer/src/pages/downloads/downloads.tsx b/src/renderer/src/pages/downloads/downloads.tsx index 531bc526..5a9c228c 100644 --- a/src/renderer/src/pages/downloads/downloads.tsx +++ b/src/renderer/src/pages/downloads/downloads.tsx @@ -132,9 +132,7 @@ export function Downloads() {

{t("no_downloads_title")}

-

- {t("no_downloads_description")} -

+

{t("no_downloads_description")}

)} diff --git a/src/renderer/src/pages/game-details/description-header/description-header.tsx b/src/renderer/src/pages/game-details/description-header/description-header.tsx index e4272534..cd73c52a 100644 --- a/src/renderer/src/pages/game-details/description-header/description-header.tsx +++ b/src/renderer/src/pages/game-details/description-header/description-header.tsx @@ -19,7 +19,10 @@ export function DescriptionHeader() { date: shopDetails?.release_date.date, })}

-

{t("publisher", { publisher: shopDetails.publishers[0] })}

+ + {Array.isArray(shopDetails.publishers) && ( +

{t("publisher", { publisher: shopDetails.publishers[0] })}

+ )} ); diff --git a/src/renderer/src/pages/game-details/game-details.css.ts b/src/renderer/src/pages/game-details/game-details.css.ts index f0bbfd2e..3dc0ec94 100644 --- a/src/renderer/src/pages/game-details/game-details.css.ts +++ b/src/renderer/src/pages/game-details/game-details.css.ts @@ -101,7 +101,6 @@ export const descriptionContent = style({ export const description = style({ userSelect: "text", lineHeight: "22px", - fontFamily: "'Fira Sans', sans-serif", fontSize: "16px", padding: `${SPACING_UNIT * 3}px ${SPACING_UNIT * 2}px`, "@media": { diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.css.ts b/src/renderer/src/pages/game-details/hero/hero-panel.css.ts index e10d55a5..c379c1c3 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.css.ts +++ b/src/renderer/src/pages/game-details/hero/hero-panel.css.ts @@ -9,6 +9,7 @@ export const panel = recipe({ height: "72px", minHeight: "72px", padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 3}px`, + backgroundColor: vars.color.darkBackground, display: "flex", alignItems: "center", justifyContent: "space-between", 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 d102d2b2..dff73ea0 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 @@ -60,14 +60,22 @@ export function DownloadSettingsModal({ .then((defaultDownloadsPath) => setSelectedPath(defaultDownloadsPath)); } - if ( - userPreferences?.realDebridApiToken && - downloaders.includes(Downloader.RealDebrid) - ) { - setSelectedDownloader(Downloader.RealDebrid); - } else { - setSelectedDownloader(downloaders[0]); - } + const filteredDownloaders = downloaders.filter((downloader) => { + if (downloader === Downloader.RealDebrid) + return userPreferences?.realDebridApiToken; + return true; + }); + + /* Gives preference to Real Debrid */ + const selectedDownloader = filteredDownloaders.includes( + Downloader.RealDebrid + ) + ? Downloader.RealDebrid + : filteredDownloaders[0]; + + setSelectedDownloader( + selectedDownloader === undefined ? null : selectedDownloader + ); }, [ userPreferences?.downloadsPath, downloaders, diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.css.ts b/src/renderer/src/pages/game-details/modals/game-options-modal.css.ts index 8bf0ae7f..f844a686 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.css.ts +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.css.ts @@ -15,7 +15,6 @@ export const gameOptionHeader = style({ }); export const gameOptionHeaderDescription = style({ - fontFamily: "'Fira Sans', sans-serif", fontWeight: "400", }); diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts b/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts index e6d8b60a..734e6eb3 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts @@ -46,7 +46,6 @@ export const requirementButton = style({ export const requirementsDetails = style({ padding: `${SPACING_UNIT * 2}px`, lineHeight: "22px", - fontFamily: "'Fira Sans', sans-serif", fontSize: "16px", }); diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index 8214117a..64d653b0 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -82,9 +82,7 @@ export function SettingsDownloadSources() { onAddDownloadSource={handleAddDownloadSource} /> -

- {t("download_sources_description")} -

+

{t("download_sources_description")}

{t("no_recent_activity_title")}

- {isMe && ( -

- {t("no_recent_activity_description")} -

- )} + {isMe &&

{t("no_recent_activity_description")}

} ) : (
-

{t("sign_out_modal_text")}

+

{t("sign_out_modal_text")}

+ ); + } + return null; }; + if (type === "BLOCKED") { + return ( +
+
+
+ {profileImageUrl ? ( + {displayName} + ) : ( + + )} +
+
+

{displayName}

+
+
+ +
+ {getRequestActions()} +
+
+ ); + } + return (
- ); - })} - + <> +
+

Meu código de amigo:

+ +
+
+ {tabs.map((tab, index) => { + return ( + + ); + })} +
+ )} {renderTab()}
From f3276dd8fe6637bb3f8ccc1a2fd35a1f91a792c1 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:21:12 -0300 Subject: [PATCH 20/33] feat: add texts for no invites, no friends and no blocks --- src/locales/en/translation.json | 6 +++++- src/locales/pt/translation.json | 6 +++++- .../user-friend-modal-add-friend.tsx | 17 +++++++---------- .../user-friend-modal-list.tsx | 1 + .../user-friend-modal/user-friend-modal.css.ts | 12 ++++++++++++ .../user-friend-modal/user-friend-modal.tsx | 13 ++++--------- .../user-block-list.tsx | 1 + 7 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 5dce5893..db64ed3c 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -268,6 +268,10 @@ "friends_only": "Friends only", "privacy": "Privacy", "blocked_users": "Blocked users", - "unblock": "Unblock" + "unblock": "Unblock", + "no_friends_added": "You still don't have added friends", + "pending": "Pending", + "no_pending_invites": "You have no pending invites", + "no_blocked_users": "You have no blocked users" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 97c59b47..5cb6fd2f 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -268,6 +268,10 @@ "friends_only": "Apenas amigos", "public": "Público", "blocked_users": "Usuários bloqueados", - "unblock": "Desbloquear" + "unblock": "Desbloquear", + "no_friends_added": "Você ainda não possui amigos adicionados", + "pending": "Pendentes", + "no_pending_invites": "Você não possui convites de amizade pendentes", + "no_blocked_users": "Você não tem nenhum usuário bloqueado" } } diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx index 0725674e..b6e6aaea 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx @@ -40,20 +40,16 @@ export const UserFriendModalAddFriend = ({ }); }; - const resetAndClose = () => { - setFriendCode(""); - closeModal(); - }; - const handleClickRequest = (userId: string) => { - resetAndClose(); + closeModal(); navigate(`/user/${userId}`); }; const handleClickSeeProfile = () => { - resetAndClose(); - // TODO: add validation for this input? - navigate(`/user/${friendCode}`); + closeModal(); + if (friendCode.length === 8) { + navigate(`/user/${friendCode}`); + } }; const handleCancelFriendRequest = (userId: string) => { @@ -122,7 +118,8 @@ export const UserFriendModalAddFriend = ({ gap: `${SPACING_UNIT * 2}px`, }} > -

Pendentes

+

{t("pending")}

+ {friendRequests.length === 0 &&

{t("no_pending_invites")}

} {friendRequests.map((request) => { return ( + {friends.length === 0 &&

{t("no_friends_added")}

} {friends.map((friend) => { return ( -

Meu código de amigo:

+

Seu código de amigo:

diff --git a/src/shared/index.ts b/src/shared/index.ts index af4ac17d..3ae476ee 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -73,13 +73,26 @@ const realDebridHosts = ["https://1fichier.com", "https://mediafire.com"]; export const getDownloadersForUri = (uri: string) => { if (uri.startsWith("https://gofile.io")) return [Downloader.Gofile]; + if (uri.startsWith("https://pixeldrain.com")) return [Downloader.PixelDrain]; if (realDebridHosts.some((host) => uri.startsWith(host))) return [Downloader.RealDebrid]; - if (uri.startsWith("magnet:")) + if (uri.startsWith("magnet:")) { return [Downloader.Torrent, Downloader.RealDebrid]; + } return []; }; + +export const getDownloadersForUris = (uris: string[]) => { + const downloadersSet = uris.reduce>((prev, next) => { + const downloaders = getDownloadersForUri(next); + downloaders.forEach((downloader) => prev.add(downloader)); + + return prev; + }, new Set()); + + return Array.from(downloadersSet); +}; diff --git a/src/types/index.ts b/src/types/index.ts index 6200d825..46ab5421 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -67,7 +67,11 @@ export interface SteamAppDetails { export interface GameRepack { id: number; title: string; + /** + * @deprecated Use uris instead + */ magnet: string; + uris: string[]; repacker: string; fileSize: string | null; uploadDate: Date | string | null; @@ -194,6 +198,7 @@ export interface StartGameDownloadPayload { objectID: string; title: string; shop: GameShop; + uri: string; downloadPath: string; downloader: Downloader; } From 7a9247278db6d766030cd2e150cbd38c0be1013e Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 18 Aug 2024 02:31:20 +0100 Subject: [PATCH 28/33] fix: removing menu --- src/main/entity/repack.entity.ts | 2 +- src/main/services/window-manager.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/entity/repack.entity.ts b/src/main/entity/repack.entity.ts index 380d7b8c..3b546692 100644 --- a/src/main/entity/repack.entity.ts +++ b/src/main/entity/repack.entity.ts @@ -19,7 +19,7 @@ export class Repack { /** * @deprecated Use uris instead */ - @Column("text", { unique: true, nullable: true }) + @Column("text", { unique: true }) magnet: string; /** diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index fcef12d6..201b13ad 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -64,8 +64,6 @@ export class WindowManager { this.loadURL(); this.mainWindow.removeMenu(); - WindowManager.mainWindow?.webContents.openDevTools(); - this.mainWindow.on("ready-to-show", () => { if (!app.isPackaged) WindowManager.mainWindow?.webContents.openDevTools(); WindowManager.mainWindow?.show(); From c76ef630e1db69c80359712f01277ad5bdc01cdf Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 18 Aug 2024 02:45:05 +0100 Subject: [PATCH 29/33] fix: removing menu --- src/main/helpers/download-source.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/helpers/download-source.ts b/src/main/helpers/download-source.ts index 0b996fcc..c216212a 100644 --- a/src/main/helpers/download-source.ts +++ b/src/main/helpers/download-source.ts @@ -18,6 +18,7 @@ export const insertDownloadsFromSource = async ( (download) => ({ title: download.title, uris: JSON.stringify(download.uris), + magnet: download.uris[0]!, fileSize: download.fileSize, repacker: downloadSource.name, uploadDate: download.uploadDate, From 73a12488cdccbdcf757997c6079654b056aee0dd Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 18 Aug 2024 03:06:35 +0100 Subject: [PATCH 30/33] fix: adding default to uris --- src/main/entity/repack.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/entity/repack.entity.ts b/src/main/entity/repack.entity.ts index 3b546692..ff3f16cb 100644 --- a/src/main/entity/repack.entity.ts +++ b/src/main/entity/repack.entity.ts @@ -40,7 +40,7 @@ export class Repack { @ManyToOne(() => DownloadSource, { nullable: true, onDelete: "CASCADE" }) downloadSource: DownloadSource; - @Column("text") + @Column("text", { default: "[]" }) uris: string; @CreateDateColumn() From 76d3fead66df16144c0737c5fae6512220c44d0b Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 18 Aug 2024 03:38:12 +0100 Subject: [PATCH 31/33] fix: adding default to uris --- src/main/services/repacks-manager.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/services/repacks-manager.ts b/src/main/services/repacks-manager.ts index bfe4bc8a..93157d6c 100644 --- a/src/main/services/repacks-manager.ts +++ b/src/main/services/repacks-manager.ts @@ -15,10 +15,17 @@ export class RepacksManager { }, }) .then((repacks) => - repacks.map((repack) => ({ - ...repack, - uris: JSON.parse(repack.uris), - })) + 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++) { From 6c24a523b77fe40704803343a9adfa02ae44704a Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 18 Aug 2024 16:19:06 +0100 Subject: [PATCH 32/33] feat: adding support to qiwi --- .../events/torrenting/start-game-download.ts | 1 + src/main/helpers/index.ts | 6 ++- .../services/download/download-manager.ts | 48 ++++++++++++------- src/main/services/hosters/index.ts | 1 + src/main/services/how-long-to-beat.ts | 6 +-- src/renderer/src/constants.ts | 1 + .../modals/download-settings-modal.css.ts | 7 ++- src/shared/index.ts | 2 + 8 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 00978abc..f4db999f 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -101,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/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/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index f97af659..d4733a32 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -6,7 +6,7 @@ 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 { GofileApi, QiwiApi } from "../hosters"; import { GenericHttpDownloader } from "./generic-http-downloader"; export class DownloadManager { @@ -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/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/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/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/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/shared/index.ts b/src/shared/index.ts index 3ae476ee..28e7315b 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -3,6 +3,7 @@ export enum Downloader { Torrent, Gofile, PixelDrain, + Qiwi, } export enum DownloadSourceStatus { @@ -75,6 +76,7 @@ export const getDownloadersForUri = (uri: string) => { if (uri.startsWith("https://gofile.io")) return [Downloader.Gofile]; if (uri.startsWith("https://pixeldrain.com")) return [Downloader.PixelDrain]; + if (uri.startsWith("https://qiwi.gg")) return [Downloader.Qiwi]; if (realDebridHosts.some((host) => uri.startsWith(host))) return [Downloader.RealDebrid]; From c1bd1d30d7a702587a54a803224cb5173c731106 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 18 Aug 2024 16:21:05 +0100 Subject: [PATCH 33/33] feat: adding support to qiwi --- src/main/services/hosters/qiwi.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/services/hosters/qiwi.ts 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; + } +}