From c2273dbf712842ef7f6241d8a68b154816bbb64a Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 18 Oct 2025 14:07:44 +0100 Subject: [PATCH 01/81] feat: moving sources to worker --- src/main/events/download-sources/helpers.ts | 85 ++-------- .../download-sources/sync-download-sources.ts | 20 +-- src/main/main.ts | 4 + .../services/game-matcher-worker-manager.ts | 145 ++++++++++++++++ src/main/services/index.ts | 1 + src/main/workers/game-matcher-worker.ts | 158 ++++++++++++++++++ 6 files changed, 329 insertions(+), 84 deletions(-) create mode 100644 src/main/services/game-matcher-worker-manager.ts create mode 100644 src/main/workers/game-matcher-worker.ts diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts index 2e7489fd..edd3878e 100644 --- a/src/main/events/download-sources/helpers.ts +++ b/src/main/events/download-sources/helpers.ts @@ -192,83 +192,30 @@ export const addNewDownloads = async ( const batch = repacksSublevel.batch(); + // Get title hash mapping and perform matching in worker thread const titleHashMapping = await getTitleHashMapping(); - let hashMatchCount = 0; - let fuzzyMatchCount = 0; - let noMatchCount = 0; - for (const download of downloads) { - let objectIds: string[] = []; - let usedHashMatch = false; + const { GameMatcherWorkerManager } = await import("@main/services"); + const matchResult = await GameMatcherWorkerManager.matchDownloads( + downloads, + steamGames, + titleHashMapping + ); - const titleHash = hashTitle(download.title); - const steamIdsFromHash = titleHashMapping[titleHash]; - - if (steamIdsFromHash && steamIdsFromHash.length > 0) { - hashMatchCount++; - usedHashMatch = true; - - objectIds = steamIdsFromHash.map(String); - } - - if (!usedHashMatch) { - let gamesInSteam: FormattedSteamGame[] = []; - const formattedTitle = formatRepackName(download.title); - - if (formattedTitle && formattedTitle.length > 0) { - const [firstLetter] = formattedTitle; - const games = steamGames[firstLetter] || []; - - gamesInSteam = games.filter((game) => - formattedTitle.startsWith(game.formattedName) - ); - - if (gamesInSteam.length === 0) { - gamesInSteam = games.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) - ); - } - - if (gamesInSteam.length === 0) { - for (const letter of Object.keys(steamGames)) { - const letterGames = steamGames[letter] || []; - const matches = letterGames.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) - ); - if (matches.length > 0) { - gamesInSteam = matches; - break; - } - } - } - - if (gamesInSteam.length > 0) { - fuzzyMatchCount++; - objectIds = gamesInSteam.map((game) => String(game.id)); - } else { - noMatchCount++; - } - } else { - noMatchCount++; - } - } - - for (const id of objectIds) { + // Process matched results and write to database + for (const matchedDownload of matchResult.matchedDownloads) { + for (const id of matchedDownload.objectIds) { objectIdsOnSource.add(id); } const repack = { id: nextRepackId++, - objectIds: objectIds, - title: download.title, - uris: download.uris, - fileSize: download.fileSize, + objectIds: matchedDownload.objectIds, + title: matchedDownload.title, + uris: matchedDownload.uris, + fileSize: matchedDownload.fileSize, repacker: downloadSource.name, - uploadDate: download.uploadDate, + uploadDate: matchedDownload.uploadDate, downloadSourceId: downloadSource.id, createdAt: now, updatedAt: now, @@ -280,7 +227,7 @@ export const addNewDownloads = async ( await batch.write(); logger.info( - `Matching stats for ${downloadSource.name}: Hash=${hashMatchCount}, Fuzzy=${fuzzyMatchCount}, None=${noMatchCount}` + `Matching stats for ${downloadSource.name}: Hash=${matchResult.stats.hashMatchCount}, Fuzzy=${matchResult.stats.fuzzyMatchCount}, None=${matchResult.stats.noMatchCount}` ); const existingSource = await downloadSourcesSublevel.get( diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts index 88861074..3bb78f22 100644 --- a/src/main/events/download-sources/sync-download-sources.ts +++ b/src/main/events/download-sources/sync-download-sources.ts @@ -31,20 +31,10 @@ const syncDownloadSources = async ( downloadSources.push(source); } - const existingRepacks: Array<{ - id: number; - title: string; - uris: string[]; - repacker: string; - fileSize: string | null; - objectIds: string[]; - uploadDate: Date | string | null; - downloadSourceId: number; - createdAt: Date; - updatedAt: Date; - }> = []; + // Use a Set for O(1) lookups instead of O(n) with array.some() + const existingRepackTitles = new Set(); for await (const [, repack] of repacksSublevel.iterator()) { - existingRepacks.push(repack); + existingRepackTitles.add(repack.title); } // Handle sources with missing fingerprints individually, don't delete all sources @@ -77,9 +67,9 @@ const syncDownloadSources = async ( const source = downloadSourceSchema.parse(response.data); const steamGames = await getSteamGames(); + // O(1) lookup instead of O(n) - massive performance improvement const repacks = source.downloads.filter( - (download) => - !existingRepacks.some((repack) => repack.title === download.title) + (download) => !existingRepackTitles.has(download.title) ); await downloadSourcesSublevel.put(`${downloadSource.id}`, { diff --git a/src/main/main.ts b/src/main/main.ts index 5eecb101..e9b6187c 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -17,6 +17,7 @@ import { Lock, DeckyPlugin, ResourceCache, + GameMatcherWorkerManager, } from "@main/services"; export const loadState = async () => { @@ -25,6 +26,9 @@ export const loadState = async () => { ResourceCache.initialize(); await ResourceCache.updateResourcesOnStartup(); + // Initialize game matcher worker thread + GameMatcherWorkerManager.initialize(); + const userPreferences = await db.get( levelKeys.userPreferences, { diff --git a/src/main/services/game-matcher-worker-manager.ts b/src/main/services/game-matcher-worker-manager.ts new file mode 100644 index 00000000..b5d306c7 --- /dev/null +++ b/src/main/services/game-matcher-worker-manager.ts @@ -0,0 +1,145 @@ +import { Worker } from "worker_threads"; +import workerPath from "../workers/game-matcher-worker?modulePath"; + +interface WorkerMessage { + id: string; + data: unknown; +} + +interface WorkerResponse { + id: string; + success: boolean; + result?: unknown; + error?: string; +} + +export type TitleHashMapping = Record; + +export type FormattedSteamGame = { + id: string; + name: string; + formattedName: string; +}; +export type FormattedSteamGamesByLetter = Record; + +interface DownloadToMatch { + title: string; + uris: string[]; + uploadDate: string; + fileSize: string; +} + +interface MatchedDownload { + title: string; + uris: string[]; + uploadDate: string; + fileSize: string; + objectIds: string[]; + usedHashMatch: boolean; +} + +interface MatchResponse { + matchedDownloads: MatchedDownload[]; + stats: { + hashMatchCount: number; + fuzzyMatchCount: number; + noMatchCount: number; + }; +} + +export class GameMatcherWorkerManager { + private static worker: Worker | null = null; + private static messageId = 0; + private static pendingMessages = new Map< + string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { resolve: (value: any) => void; reject: (error: Error) => void } + >(); + + public static initialize() { + if (this.worker) { + return; + } + + try { + console.log( + "[GameMatcherWorker] Initializing worker with path:", + workerPath + ); + + this.worker = new Worker(workerPath); + + this.worker.on("message", (response: WorkerResponse) => { + const pending = this.pendingMessages.get(response.id); + if (pending) { + if (response.success) { + pending.resolve(response.result); + } else { + pending.reject(new Error(response.error || "Unknown error")); + } + this.pendingMessages.delete(response.id); + } + }); + + this.worker.on("error", (error) => { + console.error("[GameMatcherWorker] Worker error:", error); + for (const [id, pending] of this.pendingMessages.entries()) { + pending.reject(error); + this.pendingMessages.delete(id); + } + }); + + this.worker.on("exit", (code) => { + if (code !== 0) { + console.error( + `[GameMatcherWorker] Worker stopped with exit code ${code}` + ); + } + this.worker = null; + for (const [id, pending] of this.pendingMessages.entries()) { + pending.reject(new Error("Worker exited unexpectedly")); + this.pendingMessages.delete(id); + } + }); + + console.log("[GameMatcherWorker] Worker initialized successfully"); + } catch (error) { + console.error("[GameMatcherWorker] Failed to initialize worker:", error); + throw error; + } + } + + private static sendMessage(data: unknown): Promise { + if (!this.worker) { + return Promise.reject(new Error("Worker not initialized")); + } + + const id = `msg_${++this.messageId}`; + const message: WorkerMessage = { id, data }; + + return new Promise((resolve, reject) => { + this.pendingMessages.set(id, { resolve, reject }); + this.worker!.postMessage(message); + }); + } + + public static async matchDownloads( + downloads: DownloadToMatch[], + steamGames: FormattedSteamGamesByLetter, + titleHashMapping: TitleHashMapping + ): Promise { + return this.sendMessage({ + downloads, + steamGames, + titleHashMapping, + }); + } + + public static terminate() { + if (this.worker) { + this.worker.terminate(); + this.worker = null; + this.pendingMessages.clear(); + } + } +} diff --git a/src/main/services/index.ts b/src/main/services/index.ts index c98f09e1..0853859f 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -19,3 +19,4 @@ export * from "./wine"; export * from "./lock"; export * from "./decky-plugin"; export * from "./resource-cache"; +export * from "./game-matcher-worker-manager"; diff --git a/src/main/workers/game-matcher-worker.ts b/src/main/workers/game-matcher-worker.ts new file mode 100644 index 00000000..4930ada0 --- /dev/null +++ b/src/main/workers/game-matcher-worker.ts @@ -0,0 +1,158 @@ +import { parentPort } from "worker_threads"; +import crypto from "node:crypto"; + +export type TitleHashMapping = Record; + +export type FormattedSteamGame = { + id: string; + name: string; + formattedName: string; +}; +export type FormattedSteamGamesByLetter = Record; + +interface DownloadToMatch { + title: string; + uris: string[]; + uploadDate: string; + fileSize: string; +} + +interface MatchedDownload { + title: string; + uris: string[]; + uploadDate: string; + fileSize: string; + objectIds: string[]; + usedHashMatch: boolean; +} + +interface MatchRequest { + downloads: DownloadToMatch[]; + steamGames: FormattedSteamGamesByLetter; + titleHashMapping: TitleHashMapping; +} + +interface MatchResponse { + matchedDownloads: MatchedDownload[]; + stats: { + hashMatchCount: number; + fuzzyMatchCount: number; + noMatchCount: number; + }; +} + +const hashTitle = (title: string): string => { + return crypto.createHash("sha256").update(title).digest("hex"); +}; + +const formatName = (name: string) => { + return name + .normalize("NFD") + .replaceAll(/[\u0300-\u036f]/g, "") + .toLowerCase() + .replaceAll(/[^a-z0-9]/g, ""); +}; + +const formatRepackName = (name: string) => { + return formatName(name.replace("[DL]", "")); +}; + +const matchDownloads = (request: MatchRequest): MatchResponse => { + const { downloads, steamGames, titleHashMapping } = request; + const matchedDownloads: MatchedDownload[] = []; + + let hashMatchCount = 0; + let fuzzyMatchCount = 0; + let noMatchCount = 0; + + for (const download of downloads) { + let objectIds: string[] = []; + let usedHashMatch = false; + + const titleHash = hashTitle(download.title); + const steamIdsFromHash = titleHashMapping[titleHash]; + + if (steamIdsFromHash && steamIdsFromHash.length > 0) { + hashMatchCount++; + usedHashMatch = true; + objectIds = steamIdsFromHash.map(String); + } + + if (!usedHashMatch) { + let gamesInSteam: FormattedSteamGame[] = []; + const formattedTitle = formatRepackName(download.title); + + if (formattedTitle && formattedTitle.length > 0) { + const [firstLetter] = formattedTitle; + const games = steamGames[firstLetter] || []; + + gamesInSteam = games.filter((game) => + formattedTitle.startsWith(game.formattedName) + ); + + if (gamesInSteam.length === 0) { + gamesInSteam = games.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + } + + if (gamesInSteam.length === 0) { + for (const letter of Object.keys(steamGames)) { + const letterGames = steamGames[letter] || []; + const matches = letterGames.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + if (matches.length > 0) { + gamesInSteam = matches; + break; + } + } + } + + if (gamesInSteam.length > 0) { + fuzzyMatchCount++; + objectIds = gamesInSteam.map((game) => String(game.id)); + } else { + noMatchCount++; + } + } else { + noMatchCount++; + } + } + + matchedDownloads.push({ + ...download, + objectIds, + usedHashMatch, + }); + } + + return { + matchedDownloads, + stats: { + hashMatchCount, + fuzzyMatchCount, + noMatchCount, + }, + }; +}; + +// Message handler +if (parentPort) { + parentPort.on("message", (message: { id: string; data: MatchRequest }) => { + try { + const result = matchDownloads(message.data); + parentPort!.postMessage({ id: message.id, success: true, result }); + } catch (error) { + parentPort!.postMessage({ + id: message.id, + success: false, + error: error instanceof Error ? error.message : String(error), + }); + } + }); +} From 48ce9a247640347f6667546ad8f60dcc75feaa08 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 21 Oct 2025 04:18:11 +0100 Subject: [PATCH 02/81] feat: using api download sources --- .../download-sources/add-download-source.ts | 99 ++---- .../check-download-source-exists.ts | 17 - .../delete-all-download-sources.ts | 13 - .../delete-download-source.ts | 28 -- .../get-download-sources-list.ts | 19 -- .../download-sources/get-download-sources.ts | 4 +- src/main/events/download-sources/helpers.ts | 314 ------------------ .../remove-download-source.ts | 17 +- .../sync-download-sources-from-api.ts | 19 -- .../download-sources/sync-download-sources.ts | 113 +------ .../update-missing-fingerprints.ts | 67 ---- .../validate-download-source.ts | 32 -- src/main/events/index.ts | 7 - src/main/events/repacks/get-all-repacks.ts | 16 - src/main/level/sublevels/download-sources.ts | 14 +- src/main/level/sublevels/index.ts | 1 - src/main/level/sublevels/keys.ts | 1 - src/main/level/sublevels/repacks.ts | 22 -- src/main/main.ts | 8 - .../services/game-matcher-worker-manager.ts | 145 -------- src/main/services/hydra-api.ts | 5 - src/main/services/index.ts | 2 - src/main/services/resource-cache.ts | 157 --------- src/main/workers/game-matcher-worker.ts | 158 --------- src/preload/index.ts | 12 - src/renderer/src/app.tsx | 34 -- .../src/components/game-card/game-card.tsx | 40 +-- .../game-details/game-details.context.tsx | 61 ++-- src/renderer/src/declaration.d.ts | 22 +- .../src/features/download-sources-slice.ts | 21 -- src/renderer/src/features/index.ts | 2 - src/renderer/src/hooks/index.ts | 1 - src/renderer/src/hooks/use-catalogue.ts | 12 +- src/renderer/src/hooks/use-repacks.ts | 26 -- .../src/pages/catalogue/catalogue.tsx | 73 ++-- .../src/pages/catalogue/game-item.tsx | 14 +- .../src/pages/game-details/game-reviews.tsx | 4 - .../game-details/modals/repacks-modal.tsx | 27 +- src/renderer/src/pages/home/home.tsx | 18 +- .../settings/add-download-source-modal.scss | 7 + .../settings/add-download-source-modal.tsx | 128 ++----- .../settings/settings-download-sources.tsx | 102 +++--- src/renderer/src/store.ts | 4 - src/shared/constants.ts | 6 +- src/types/index.ts | 28 +- 45 files changed, 295 insertions(+), 1625 deletions(-) delete mode 100644 src/main/events/download-sources/check-download-source-exists.ts delete mode 100644 src/main/events/download-sources/delete-all-download-sources.ts delete mode 100644 src/main/events/download-sources/delete-download-source.ts delete mode 100644 src/main/events/download-sources/get-download-sources-list.ts delete mode 100644 src/main/events/download-sources/helpers.ts delete mode 100644 src/main/events/download-sources/sync-download-sources-from-api.ts delete mode 100644 src/main/events/download-sources/update-missing-fingerprints.ts delete mode 100644 src/main/events/download-sources/validate-download-source.ts delete mode 100644 src/main/events/repacks/get-all-repacks.ts delete mode 100644 src/main/level/sublevels/repacks.ts delete mode 100644 src/main/services/game-matcher-worker-manager.ts delete mode 100644 src/main/services/resource-cache.ts delete mode 100644 src/main/workers/game-matcher-worker.ts delete mode 100644 src/renderer/src/features/download-sources-slice.ts delete mode 100644 src/renderer/src/hooks/use-repacks.ts diff --git a/src/main/events/download-sources/add-download-source.ts b/src/main/events/download-sources/add-download-source.ts index e51cae3e..45bcd27c 100644 --- a/src/main/events/download-sources/add-download-source.ts +++ b/src/main/events/download-sources/add-download-source.ts @@ -1,76 +1,45 @@ import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { HydraApi, logger } from "@main/services"; -import { importDownloadSourceToLocal } from "./helpers"; +import { HydraApi } from "@main/services/hydra-api"; +import { downloadSourcesSublevel } from "@main/level"; +import type { DownloadSource } from "@types"; const addDownloadSource = async ( _event: Electron.IpcMainInvokeEvent, url: string ) => { - const result = await importDownloadSourceToLocal(url, true); - if (!result) { - throw new Error("Failed to import download source"); - } - - // Verify that repacks were actually written to the database (read-after-write) - // This ensures all async operations are complete before proceeding - let repackCount = 0; - for await (const [, repack] of repacksSublevel.iterator()) { - if (repack.downloadSourceId === result.id) { - repackCount++; - } - } - - await HydraApi.post("/profile/download-sources", { - urls: [url], - }); - - const { fingerprint } = await HydraApi.put<{ fingerprint: string }>( - "/download-sources", - { - objectIds: result.objectIds, - }, - { needsAuth: false } - ); - - // Update the source with fingerprint - const updatedSource = await downloadSourcesSublevel.get(`${result.id}`); - if (updatedSource) { - await downloadSourcesSublevel.put(`${result.id}`, { - ...updatedSource, - fingerprint, - updatedAt: new Date(), - }); - } - - // Final verification: ensure the source with fingerprint is persisted - const finalSource = await downloadSourcesSublevel.get(`${result.id}`); - if (!finalSource || !finalSource.fingerprint) { - throw new Error("Failed to persist download source with fingerprint"); - } - - // Verify repacks still exist after fingerprint update - let finalRepackCount = 0; - for await (const [, repack] of repacksSublevel.iterator()) { - if (repack.downloadSourceId === result.id) { - finalRepackCount++; - } - } - - if (finalRepackCount !== repackCount) { - logger.warn( - `Repack count mismatch! Before: ${repackCount}, After: ${finalRepackCount}` + try { + const downloadSource = await HydraApi.post( + "/download-sources", + { + url, + }, + { needsAuth: false } ); - } else { - logger.info( - `Final verification passed: ${finalRepackCount} repacks confirmed` - ); - } - return { - ...result, - fingerprint, - }; + if (HydraApi.isLoggedIn()) { + try { + await HydraApi.post("/profile/download-sources", { + urls: [url], + }); + } catch (error) { + console.error("Failed to add download source to profile:", error); + } + } + + const downloadSourceForStorage = { + ...downloadSource, + fingerprint: downloadSource.fingerprint || "", + }; + await downloadSourcesSublevel.put( + downloadSource.id, + downloadSourceForStorage + ); + + return downloadSource; + } catch (error) { + console.error("Failed to add download source:", error); + throw error; + } }; registerEvent("addDownloadSource", addDownloadSource); diff --git a/src/main/events/download-sources/check-download-source-exists.ts b/src/main/events/download-sources/check-download-source-exists.ts deleted file mode 100644 index 36dd88ce..00000000 --- a/src/main/events/download-sources/check-download-source-exists.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel } from "@main/level"; - -const checkDownloadSourceExists = async ( - _event: Electron.IpcMainInvokeEvent, - url: string -): Promise => { - for await (const [, source] of downloadSourcesSublevel.iterator()) { - if (source.url === url) { - return true; - } - } - - return false; -}; - -registerEvent("checkDownloadSourceExists", checkDownloadSourceExists); diff --git a/src/main/events/download-sources/delete-all-download-sources.ts b/src/main/events/download-sources/delete-all-download-sources.ts deleted file mode 100644 index cbf3958f..00000000 --- a/src/main/events/download-sources/delete-all-download-sources.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { invalidateIdCaches } from "./helpers"; - -const deleteAllDownloadSources = async ( - _event: Electron.IpcMainInvokeEvent -) => { - await Promise.all([repacksSublevel.clear(), downloadSourcesSublevel.clear()]); - - invalidateIdCaches(); -}; - -registerEvent("deleteAllDownloadSources", deleteAllDownloadSources); diff --git a/src/main/events/download-sources/delete-download-source.ts b/src/main/events/download-sources/delete-download-source.ts deleted file mode 100644 index 5322b96c..00000000 --- a/src/main/events/download-sources/delete-download-source.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { invalidateIdCaches } from "./helpers"; - -const deleteDownloadSource = async ( - _event: Electron.IpcMainInvokeEvent, - id: number -) => { - const repacksToDelete: string[] = []; - - for await (const [key, repack] of repacksSublevel.iterator()) { - if (repack.downloadSourceId === id) { - repacksToDelete.push(key); - } - } - - const batch = repacksSublevel.batch(); - for (const key of repacksToDelete) { - batch.del(key); - } - await batch.write(); - - await downloadSourcesSublevel.del(`${id}`); - - invalidateIdCaches(); -}; - -registerEvent("deleteDownloadSource", deleteDownloadSource); diff --git a/src/main/events/download-sources/get-download-sources-list.ts b/src/main/events/download-sources/get-download-sources-list.ts deleted file mode 100644 index db26ad01..00000000 --- a/src/main/events/download-sources/get-download-sources-list.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel, DownloadSource } from "@main/level"; - -const getDownloadSourcesList = async (_event: Electron.IpcMainInvokeEvent) => { - const sources: DownloadSource[] = []; - - for await (const [, source] of downloadSourcesSublevel.iterator()) { - sources.push(source); - } - - // Sort by createdAt descending - sources.sort( - (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() - ); - - return sources; -}; - -registerEvent("getDownloadSourcesList", getDownloadSourcesList); diff --git a/src/main/events/download-sources/get-download-sources.ts b/src/main/events/download-sources/get-download-sources.ts index bbebd06c..cf7cd4d7 100644 --- a/src/main/events/download-sources/get-download-sources.ts +++ b/src/main/events/download-sources/get-download-sources.ts @@ -1,8 +1,8 @@ -import { HydraApi } from "@main/services"; +import { downloadSourcesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => { - return HydraApi.get("/profile/download-sources"); + return downloadSourcesSublevel.values().all(); }; registerEvent("getDownloadSources", getDownloadSources); diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts deleted file mode 100644 index edd3878e..00000000 --- a/src/main/events/download-sources/helpers.ts +++ /dev/null @@ -1,314 +0,0 @@ -import axios from "axios"; -import { z } from "zod"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { DownloadSourceStatus } from "@shared"; -import crypto from "node:crypto"; -import { logger, ResourceCache } from "@main/services"; - -export const downloadSourceSchema = z.object({ - name: z.string().max(255), - downloads: z.array( - z.object({ - title: z.string().max(255), - uris: z.array(z.string()), - uploadDate: z.string().max(255), - fileSize: z.string().max(255), - }) - ), -}); - -export type TitleHashMapping = Record; - -let titleHashMappingCache: TitleHashMapping | null = null; - -export const getTitleHashMapping = async (): Promise => { - if (titleHashMappingCache) { - return titleHashMappingCache; - } - - try { - const cached = - ResourceCache.getCachedData("sources-manifest"); - if (cached) { - titleHashMappingCache = cached; - return cached; - } - - const fetched = await ResourceCache.fetchAndCache( - "sources-manifest", - "https://cdn.losbroxas.org/sources-manifest.json", - 10000 - ); - titleHashMappingCache = fetched; - return fetched; - } catch (error) { - logger.error("Failed to fetch title hash mapping:", error); - return {} as TitleHashMapping; - } -}; - -export const hashTitle = (title: string): string => { - return crypto.createHash("sha256").update(title).digest("hex"); -}; - -export type SteamGamesByLetter = Record; -export type FormattedSteamGame = { - id: string; - name: string; - formattedName: string; -}; -export type FormattedSteamGamesByLetter = Record; - -export const formatName = (name: string) => { - return name - .normalize("NFD") - .replaceAll(/[\u0300-\u036f]/g, "") - .toLowerCase() - .replaceAll(/[^a-z0-9]/g, ""); -}; - -export const formatRepackName = (name: string) => { - return formatName(name.replace("[DL]", "")); -}; - -interface DownloadSource { - id: number; - url: string; - name: string; - etag: string | null; - status: number; - downloadCount: number; - objectIds: string[]; - fingerprint?: string; - createdAt: Date; - updatedAt: Date; -} - -const getDownloadSourcesMap = async (): Promise< - Map -> => { - const map = new Map(); - for await (const [key, source] of downloadSourcesSublevel.iterator()) { - map.set(key, source); - } - - return map; -}; - -export const checkUrlExists = async (url: string): Promise => { - const sources = await getDownloadSourcesMap(); - for (const source of sources.values()) { - if (source.url === url) { - return true; - } - } - return false; -}; - -let steamGamesFormattedCache: FormattedSteamGamesByLetter | null = null; - -export const getSteamGames = async (): Promise => { - if (steamGamesFormattedCache) { - return steamGamesFormattedCache; - } - - let steamGames: SteamGamesByLetter; - - const cached = ResourceCache.getCachedData( - "steam-games-by-letter" - ); - if (cached) { - steamGames = cached; - } else { - steamGames = await ResourceCache.fetchAndCache( - "steam-games-by-letter", - `${import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json` - ); - } - - const formattedData: FormattedSteamGamesByLetter = {}; - for (const [letter, games] of Object.entries(steamGames)) { - formattedData[letter] = games.map((game) => ({ - ...game, - formattedName: formatName(game.name), - })); - } - - steamGamesFormattedCache = formattedData; - return formattedData; -}; - -export type SublevelIterator = AsyncIterable<[string, { id: number }]>; - -export interface SublevelWithId { - iterator: () => SublevelIterator; -} - -let maxRepackId: number | null = null; -let maxDownloadSourceId: number | null = null; - -export const getNextId = async (sublevel: SublevelWithId): Promise => { - const isRepackSublevel = sublevel === repacksSublevel; - const isDownloadSourceSublevel = sublevel === downloadSourcesSublevel; - - if (isRepackSublevel && maxRepackId !== null) { - return ++maxRepackId; - } - - if (isDownloadSourceSublevel && maxDownloadSourceId !== null) { - return ++maxDownloadSourceId; - } - - let maxId = 0; - for await (const [, value] of sublevel.iterator()) { - if (value.id > maxId) { - maxId = value.id; - } - } - - if (isRepackSublevel) { - maxRepackId = maxId; - } else if (isDownloadSourceSublevel) { - maxDownloadSourceId = maxId; - } - - return maxId + 1; -}; - -export const invalidateIdCaches = () => { - maxRepackId = null; - maxDownloadSourceId = null; -}; - -export const addNewDownloads = async ( - downloadSource: { id: number; name: string }, - downloads: z.infer["downloads"], - steamGames: FormattedSteamGamesByLetter -) => { - const now = new Date(); - const objectIdsOnSource = new Set(); - - let nextRepackId = await getNextId(repacksSublevel); - - const batch = repacksSublevel.batch(); - - // Get title hash mapping and perform matching in worker thread - const titleHashMapping = await getTitleHashMapping(); - - const { GameMatcherWorkerManager } = await import("@main/services"); - const matchResult = await GameMatcherWorkerManager.matchDownloads( - downloads, - steamGames, - titleHashMapping - ); - - // Process matched results and write to database - for (const matchedDownload of matchResult.matchedDownloads) { - for (const id of matchedDownload.objectIds) { - objectIdsOnSource.add(id); - } - - const repack = { - id: nextRepackId++, - objectIds: matchedDownload.objectIds, - title: matchedDownload.title, - uris: matchedDownload.uris, - fileSize: matchedDownload.fileSize, - repacker: downloadSource.name, - uploadDate: matchedDownload.uploadDate, - downloadSourceId: downloadSource.id, - createdAt: now, - updatedAt: now, - }; - - batch.put(`${repack.id}`, repack); - } - - await batch.write(); - - logger.info( - `Matching stats for ${downloadSource.name}: Hash=${matchResult.stats.hashMatchCount}, Fuzzy=${matchResult.stats.fuzzyMatchCount}, None=${matchResult.stats.noMatchCount}` - ); - - const existingSource = await downloadSourcesSublevel.get( - `${downloadSource.id}` - ); - if (existingSource) { - await downloadSourcesSublevel.put(`${downloadSource.id}`, { - ...existingSource, - objectIds: Array.from(objectIdsOnSource), - }); - } - - return Array.from(objectIdsOnSource); -}; - -export const importDownloadSourceToLocal = async ( - url: string, - throwOnDuplicate = false -) => { - const urlExists = await checkUrlExists(url); - if (urlExists) { - if (throwOnDuplicate) { - throw new Error("Download source with this URL already exists"); - } - return null; - } - - const response = await axios.get>(url); - - const steamGames = await getSteamGames(); - - const now = new Date(); - - const nextId = await getNextId(downloadSourcesSublevel); - - const downloadSource = { - id: nextId, - url, - name: response.data.name, - etag: response.headers["etag"] || null, - status: DownloadSourceStatus.UpToDate, - downloadCount: response.data.downloads.length, - objectIds: [], - createdAt: now, - updatedAt: now, - }; - - await downloadSourcesSublevel.put(`${downloadSource.id}`, downloadSource); - - const objectIds = await addNewDownloads( - downloadSource, - response.data.downloads, - steamGames - ); - - // Invalidate ID caches after creating new repacks to prevent ID collisions - invalidateIdCaches(); - - return { - ...downloadSource, - objectIds, - }; -}; - -export const updateDownloadSourcePreservingTimestamp = async ( - existingSource: DownloadSource, - url: string -) => { - const response = await axios.get>(url); - - const updatedSource = { - ...existingSource, - name: response.data.name, - etag: response.headers["etag"] || null, - status: DownloadSourceStatus.UpToDate, - downloadCount: response.data.downloads.length, - updatedAt: new Date(), - // Preserve the original createdAt timestamp - }; - - await downloadSourcesSublevel.put(`${existingSource.id}`, updatedSource); - - return updatedSource; -}; diff --git a/src/main/events/download-sources/remove-download-source.ts b/src/main/events/download-sources/remove-download-source.ts index bcc66998..8efe0072 100644 --- a/src/main/events/download-sources/remove-download-source.ts +++ b/src/main/events/download-sources/remove-download-source.ts @@ -1,18 +1,27 @@ import { HydraApi } from "@main/services"; +import { downloadSourcesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; const removeDownloadSource = async ( _event: Electron.IpcMainInvokeEvent, - url?: string, - removeAll = false + removeAll = false, + downloadSourceId?: string ) => { const params = new URLSearchParams({ all: removeAll.toString(), }); - if (url) params.set("url", url); + if (downloadSourceId) params.set("downloadSourceId", downloadSourceId); - return HydraApi.delete(`/profile/download-sources?${params.toString()}`); + if (HydraApi.isLoggedIn()) { + void HydraApi.delete(`/profile/download-sources?${params.toString()}`); + } + + if (removeAll) { + await downloadSourcesSublevel.clear(); + } else if (downloadSourceId) { + await downloadSourcesSublevel.del(downloadSourceId); + } }; registerEvent("removeDownloadSource", removeDownloadSource); diff --git a/src/main/events/download-sources/sync-download-sources-from-api.ts b/src/main/events/download-sources/sync-download-sources-from-api.ts deleted file mode 100644 index 3cac8819..00000000 --- a/src/main/events/download-sources/sync-download-sources-from-api.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { HydraApi, logger } from "@main/services"; -import { importDownloadSourceToLocal, checkUrlExists } from "./helpers"; - -export const syncDownloadSourcesFromApi = async () => { - try { - const apiSources = await HydraApi.get< - { url: string; createdAt: string; updatedAt: string }[] - >("/profile/download-sources"); - - for (const apiSource of apiSources) { - const exists = await checkUrlExists(apiSource.url); - if (!exists) { - await importDownloadSourceToLocal(apiSource.url, false); - } - } - } catch (error) { - logger.error("Failed to sync download sources from API:", error); - } -}; diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts index 3bb78f22..987ad1c1 100644 --- a/src/main/events/download-sources/sync-download-sources.ts +++ b/src/main/events/download-sources/sync-download-sources.ts @@ -1,105 +1,24 @@ +import { HydraApi } from "@main/services"; import { registerEvent } from "../register-event"; -import axios, { AxiosError } from "axios"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { DownloadSourceStatus } from "@shared"; -import { - invalidateIdCaches, - downloadSourceSchema, - getSteamGames, - addNewDownloads, -} from "./helpers"; +import { downloadSourcesSublevel } from "@main/level"; +import type { DownloadSource } from "@types"; -const syncDownloadSources = async ( - _event: Electron.IpcMainInvokeEvent -): Promise => { - let newRepacksCount = 0; +const syncDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => { + const downloadSources = await downloadSourcesSublevel.values().all(); - try { - const downloadSources: Array<{ - id: number; - url: string; - name: string; - etag: string | null; - status: number; - downloadCount: number; - objectIds: string[]; - fingerprint?: string; - createdAt: Date; - updatedAt: Date; - }> = []; - for await (const [, source] of downloadSourcesSublevel.iterator()) { - downloadSources.push(source); - } + const response = await HydraApi.post( + "/download-sources/sync", + { + ids: downloadSources.map((downloadSource) => downloadSource.id), + }, + { needsAuth: false } + ); - // Use a Set for O(1) lookups instead of O(n) with array.some() - const existingRepackTitles = new Set(); - for await (const [, repack] of repacksSublevel.iterator()) { - existingRepackTitles.add(repack.title); - } - - // Handle sources with missing fingerprints individually, don't delete all sources - const sourcesWithFingerprints = downloadSources.filter( - (source) => source.fingerprint - ); - const sourcesWithoutFingerprints = downloadSources.filter( - (source) => !source.fingerprint - ); - - // For sources without fingerprints, just continue with normal sync - // They will get fingerprints updated later by updateMissingFingerprints - const allSourcesToSync = [ - ...sourcesWithFingerprints, - ...sourcesWithoutFingerprints, - ]; - - for (const downloadSource of allSourcesToSync) { - const headers: Record = {}; - - if (downloadSource.etag) { - headers["If-None-Match"] = downloadSource.etag; - } - - try { - const response = await axios.get(downloadSource.url, { - headers, - }); - - const source = downloadSourceSchema.parse(response.data); - const steamGames = await getSteamGames(); - - // O(1) lookup instead of O(n) - massive performance improvement - const repacks = source.downloads.filter( - (download) => !existingRepackTitles.has(download.title) - ); - - await downloadSourcesSublevel.put(`${downloadSource.id}`, { - ...downloadSource, - etag: response.headers["etag"] || null, - downloadCount: source.downloads.length, - status: DownloadSourceStatus.UpToDate, - }); - - await addNewDownloads(downloadSource, repacks, steamGames); - - newRepacksCount += repacks.length; - } catch (err: unknown) { - const isNotModified = (err as AxiosError).response?.status === 304; - - await downloadSourcesSublevel.put(`${downloadSource.id}`, { - ...downloadSource, - status: isNotModified - ? DownloadSourceStatus.UpToDate - : DownloadSourceStatus.Errored, - }); - } - } - - invalidateIdCaches(); - - return newRepacksCount; - } catch (err) { - return -1; + for (const downloadSource of response) { + await downloadSourcesSublevel.put(downloadSource.id, downloadSource); } + + return response; }; registerEvent("syncDownloadSources", syncDownloadSources); diff --git a/src/main/events/download-sources/update-missing-fingerprints.ts b/src/main/events/download-sources/update-missing-fingerprints.ts deleted file mode 100644 index 7fd43c63..00000000 --- a/src/main/events/download-sources/update-missing-fingerprints.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel } from "@main/level"; -import { HydraApi, logger } from "@main/services"; - -const updateMissingFingerprints = async ( - _event: Electron.IpcMainInvokeEvent -): Promise => { - const sourcesNeedingFingerprints: Array<{ - id: number; - objectIds: string[]; - }> = []; - - for await (const [, source] of downloadSourcesSublevel.iterator()) { - if ( - !source.fingerprint && - source.objectIds && - source.objectIds.length > 0 - ) { - sourcesNeedingFingerprints.push({ - id: source.id, - objectIds: source.objectIds, - }); - } - } - - if (sourcesNeedingFingerprints.length === 0) { - return 0; - } - - logger.info( - `Updating fingerprints for ${sourcesNeedingFingerprints.length} sources` - ); - - await Promise.all( - sourcesNeedingFingerprints.map(async (source) => { - try { - const { fingerprint } = await HydraApi.put<{ fingerprint: string }>( - "/download-sources", - { - objectIds: source.objectIds, - }, - { needsAuth: false } - ); - - const existingSource = await downloadSourcesSublevel.get( - `${source.id}` - ); - if (existingSource) { - await downloadSourcesSublevel.put(`${source.id}`, { - ...existingSource, - fingerprint, - updatedAt: new Date(), - }); - } - } catch (error) { - logger.error( - `Failed to update fingerprint for source ${source.id}:`, - error - ); - } - }) - ); - - return sourcesNeedingFingerprints.length; -}; - -registerEvent("updateMissingFingerprints", updateMissingFingerprints); diff --git a/src/main/events/download-sources/validate-download-source.ts b/src/main/events/download-sources/validate-download-source.ts deleted file mode 100644 index 2bc86df7..00000000 --- a/src/main/events/download-sources/validate-download-source.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { registerEvent } from "../register-event"; -import axios from "axios"; -import { z } from "zod"; - -const downloadSourceSchema = z.object({ - name: z.string().max(255), - downloads: z.array( - z.object({ - title: z.string().max(255), - uris: z.array(z.string()), - uploadDate: z.string().max(255), - fileSize: z.string().max(255), - }) - ), -}); - -const validateDownloadSource = async ( - _event: Electron.IpcMainInvokeEvent, - url: string -) => { - const response = await axios.get>(url); - - const { name } = downloadSourceSchema.parse(response.data); - - return { - name, - etag: response.headers["etag"] || null, - downloadCount: response.data.downloads.length, - }; -}; - -registerEvent("validateDownloadSource", validateDownloadSource); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 8d21aa11..0ab5499a 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -63,14 +63,7 @@ import "./autoupdater/restart-and-install-update"; import "./user-preferences/authenticate-real-debrid"; import "./user-preferences/authenticate-torbox"; import "./download-sources/add-download-source"; -import "./download-sources/update-missing-fingerprints"; -import "./download-sources/delete-download-source"; -import "./download-sources/delete-all-download-sources"; -import "./download-sources/validate-download-source"; import "./download-sources/sync-download-sources"; -import "./download-sources/get-download-sources-list"; -import "./download-sources/check-download-source-exists"; -import "./repacks/get-all-repacks"; import "./auth/sign-out"; import "./auth/open-auth-window"; import "./auth/get-session-hash"; diff --git a/src/main/events/repacks/get-all-repacks.ts b/src/main/events/repacks/get-all-repacks.ts deleted file mode 100644 index 6eb83a39..00000000 --- a/src/main/events/repacks/get-all-repacks.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { registerEvent } from "../register-event"; -import { repacksSublevel, GameRepack } from "@main/level"; - -const getAllRepacks = async (_event: Electron.IpcMainInvokeEvent) => { - const repacks: GameRepack[] = []; - - for await (const [, repack] of repacksSublevel.iterator()) { - if (Array.isArray(repack.objectIds)) { - repacks.push(repack); - } - } - - return repacks; -}; - -registerEvent("getAllRepacks", getAllRepacks); diff --git a/src/main/level/sublevels/download-sources.ts b/src/main/level/sublevels/download-sources.ts index 59104e3c..b6cdad0b 100644 --- a/src/main/level/sublevels/download-sources.ts +++ b/src/main/level/sublevels/download-sources.ts @@ -1,18 +1,6 @@ import { db } from "../level"; import { levelKeys } from "./keys"; - -export interface DownloadSource { - id: number; - name: string; - url: string; - status: number; - objectIds: string[]; - downloadCount: number; - fingerprint?: string; - etag: string | null; - createdAt: Date; - updatedAt: Date; -} +import type { DownloadSource } from "@types"; export const downloadSourcesSublevel = db.sublevel( levelKeys.downloadSources, diff --git a/src/main/level/sublevels/index.ts b/src/main/level/sublevels/index.ts index 7224fc64..3619ae26 100644 --- a/src/main/level/sublevels/index.ts +++ b/src/main/level/sublevels/index.ts @@ -7,4 +7,3 @@ export * from "./game-achievements"; export * from "./keys"; export * from "./themes"; export * from "./download-sources"; -export * from "./repacks"; diff --git a/src/main/level/sublevels/keys.ts b/src/main/level/sublevels/keys.ts index 6faacd52..a28690b2 100644 --- a/src/main/level/sublevels/keys.ts +++ b/src/main/level/sublevels/keys.ts @@ -18,5 +18,4 @@ export const levelKeys = { screenState: "screenState", rpcPassword: "rpcPassword", downloadSources: "downloadSources", - repacks: "repacks", }; diff --git a/src/main/level/sublevels/repacks.ts b/src/main/level/sublevels/repacks.ts deleted file mode 100644 index 6257665b..00000000 --- a/src/main/level/sublevels/repacks.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { db } from "../level"; -import { levelKeys } from "./keys"; - -export interface GameRepack { - id: number; - title: string; - uris: string[]; - repacker: string; - fileSize: string | null; - objectIds: string[]; - uploadDate: Date | string | null; - downloadSourceId: number; - createdAt: Date; - updatedAt: Date; -} - -export const repacksSublevel = db.sublevel( - levelKeys.repacks, - { - valueEncoding: "json", - } -); diff --git a/src/main/main.ts b/src/main/main.ts index e9b6187c..617dd135 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -16,19 +16,11 @@ import { Ludusavi, Lock, DeckyPlugin, - ResourceCache, - GameMatcherWorkerManager, } from "@main/services"; export const loadState = async () => { await Lock.acquireLock(); - ResourceCache.initialize(); - await ResourceCache.updateResourcesOnStartup(); - - // Initialize game matcher worker thread - GameMatcherWorkerManager.initialize(); - const userPreferences = await db.get( levelKeys.userPreferences, { diff --git a/src/main/services/game-matcher-worker-manager.ts b/src/main/services/game-matcher-worker-manager.ts deleted file mode 100644 index b5d306c7..00000000 --- a/src/main/services/game-matcher-worker-manager.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { Worker } from "worker_threads"; -import workerPath from "../workers/game-matcher-worker?modulePath"; - -interface WorkerMessage { - id: string; - data: unknown; -} - -interface WorkerResponse { - id: string; - success: boolean; - result?: unknown; - error?: string; -} - -export type TitleHashMapping = Record; - -export type FormattedSteamGame = { - id: string; - name: string; - formattedName: string; -}; -export type FormattedSteamGamesByLetter = Record; - -interface DownloadToMatch { - title: string; - uris: string[]; - uploadDate: string; - fileSize: string; -} - -interface MatchedDownload { - title: string; - uris: string[]; - uploadDate: string; - fileSize: string; - objectIds: string[]; - usedHashMatch: boolean; -} - -interface MatchResponse { - matchedDownloads: MatchedDownload[]; - stats: { - hashMatchCount: number; - fuzzyMatchCount: number; - noMatchCount: number; - }; -} - -export class GameMatcherWorkerManager { - private static worker: Worker | null = null; - private static messageId = 0; - private static pendingMessages = new Map< - string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { resolve: (value: any) => void; reject: (error: Error) => void } - >(); - - public static initialize() { - if (this.worker) { - return; - } - - try { - console.log( - "[GameMatcherWorker] Initializing worker with path:", - workerPath - ); - - this.worker = new Worker(workerPath); - - this.worker.on("message", (response: WorkerResponse) => { - const pending = this.pendingMessages.get(response.id); - if (pending) { - if (response.success) { - pending.resolve(response.result); - } else { - pending.reject(new Error(response.error || "Unknown error")); - } - this.pendingMessages.delete(response.id); - } - }); - - this.worker.on("error", (error) => { - console.error("[GameMatcherWorker] Worker error:", error); - for (const [id, pending] of this.pendingMessages.entries()) { - pending.reject(error); - this.pendingMessages.delete(id); - } - }); - - this.worker.on("exit", (code) => { - if (code !== 0) { - console.error( - `[GameMatcherWorker] Worker stopped with exit code ${code}` - ); - } - this.worker = null; - for (const [id, pending] of this.pendingMessages.entries()) { - pending.reject(new Error("Worker exited unexpectedly")); - this.pendingMessages.delete(id); - } - }); - - console.log("[GameMatcherWorker] Worker initialized successfully"); - } catch (error) { - console.error("[GameMatcherWorker] Failed to initialize worker:", error); - throw error; - } - } - - private static sendMessage(data: unknown): Promise { - if (!this.worker) { - return Promise.reject(new Error("Worker not initialized")); - } - - const id = `msg_${++this.messageId}`; - const message: WorkerMessage = { id, data }; - - return new Promise((resolve, reject) => { - this.pendingMessages.set(id, { resolve, reject }); - this.worker!.postMessage(message); - }); - } - - public static async matchDownloads( - downloads: DownloadToMatch[], - steamGames: FormattedSteamGamesByLetter, - titleHashMapping: TitleHashMapping - ): Promise { - return this.sendMessage({ - downloads, - steamGames, - titleHashMapping, - }); - } - - public static terminate() { - if (this.worker) { - this.worker.terminate(); - this.worker = null; - this.pendingMessages.clear(); - } - } -} diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index dd26e6f0..ffc5756c 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -105,11 +105,6 @@ export class HydraApi { // WSClient.close(); // WSClient.connect(); - - const { syncDownloadSourcesFromApi } = await import( - "../events/download-sources/sync-download-sources-from-api" - ); - syncDownloadSourcesFromApi(); } } diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 0853859f..88b39d1b 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -18,5 +18,3 @@ export * from "./library-sync"; export * from "./wine"; export * from "./lock"; export * from "./decky-plugin"; -export * from "./resource-cache"; -export * from "./game-matcher-worker-manager"; diff --git a/src/main/services/resource-cache.ts b/src/main/services/resource-cache.ts deleted file mode 100644 index c59f873d..00000000 --- a/src/main/services/resource-cache.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { app } from "electron"; -import axios from "axios"; -import fs from "node:fs"; -import path from "node:path"; -import { logger } from "./logger"; - -interface CachedResource { - data: T; - etag: string | null; -} - -export class ResourceCache { - private static cacheDir: string; - - static initialize() { - this.cacheDir = path.join(app.getPath("userData"), "resource-cache"); - - if (!fs.existsSync(this.cacheDir)) { - fs.mkdirSync(this.cacheDir, { recursive: true }); - } - } - - private static getCacheFilePath(resourceName: string): string { - return path.join(this.cacheDir, `${resourceName}.json`); - } - - private static getEtagFilePath(resourceName: string): string { - return path.join(this.cacheDir, `${resourceName}.etag`); - } - - private static readCachedResource( - resourceName: string - ): CachedResource | null { - const dataPath = this.getCacheFilePath(resourceName); - const etagPath = this.getEtagFilePath(resourceName); - - if (!fs.existsSync(dataPath)) { - return null; - } - - try { - const data = JSON.parse(fs.readFileSync(dataPath, "utf-8")) as T; - const etag = fs.existsSync(etagPath) - ? fs.readFileSync(etagPath, "utf-8") - : null; - - return { data, etag }; - } catch (error) { - logger.error(`Failed to read cached resource ${resourceName}:`, error); - return null; - } - } - - private static writeCachedResource( - resourceName: string, - data: T, - etag: string | null - ): void { - const dataPath = this.getCacheFilePath(resourceName); - const etagPath = this.getEtagFilePath(resourceName); - - try { - fs.writeFileSync(dataPath, JSON.stringify(data), "utf-8"); - - if (etag) { - fs.writeFileSync(etagPath, etag, "utf-8"); - } - - logger.info( - `Cached resource ${resourceName} with etag: ${etag || "none"}` - ); - } catch (error) { - logger.error(`Failed to write cached resource ${resourceName}:`, error); - } - } - - static async fetchAndCache( - resourceName: string, - url: string, - timeout: number = 10000 - ): Promise { - const cached = this.readCachedResource(resourceName); - const headers: Record = {}; - - if (cached?.etag) { - headers["If-None-Match"] = cached.etag; - } - - try { - const response = await axios.get(url, { - headers, - timeout, - }); - - const newEtag = response.headers["etag"] || null; - this.writeCachedResource(resourceName, response.data, newEtag); - - return response.data; - } catch (error: unknown) { - const axiosError = error as { - response?: { status?: number }; - message?: string; - }; - - if (axiosError.response?.status === 304 && cached) { - logger.info(`Resource ${resourceName} not modified, using cache`); - return cached.data; - } - - if (cached) { - logger.warn( - `Failed to fetch ${resourceName}, using cached version:`, - axiosError.message || "Unknown error" - ); - return cached.data; - } - - logger.error( - `Failed to fetch ${resourceName} and no cache available:`, - error - ); - throw error; - } - } - - static getCachedData(resourceName: string): T | null { - const cached = this.readCachedResource(resourceName); - return cached?.data || null; - } - - static async updateResourcesOnStartup(): Promise { - logger.info("Starting background resource cache update..."); - - const resources = [ - { - name: "steam-games-by-letter", - url: `${import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json`, - }, - { - name: "sources-manifest", - url: "https://cdn.losbroxas.org/sources-manifest.json", - }, - ]; - - await Promise.allSettled( - resources.map(async (resource) => { - try { - await this.fetchAndCache(resource.name, resource.url); - } catch (error) { - logger.error(`Failed to update ${resource.name} on startup:`, error); - } - }) - ); - - logger.info("Resource cache update complete"); - } -} diff --git a/src/main/workers/game-matcher-worker.ts b/src/main/workers/game-matcher-worker.ts deleted file mode 100644 index 4930ada0..00000000 --- a/src/main/workers/game-matcher-worker.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { parentPort } from "worker_threads"; -import crypto from "node:crypto"; - -export type TitleHashMapping = Record; - -export type FormattedSteamGame = { - id: string; - name: string; - formattedName: string; -}; -export type FormattedSteamGamesByLetter = Record; - -interface DownloadToMatch { - title: string; - uris: string[]; - uploadDate: string; - fileSize: string; -} - -interface MatchedDownload { - title: string; - uris: string[]; - uploadDate: string; - fileSize: string; - objectIds: string[]; - usedHashMatch: boolean; -} - -interface MatchRequest { - downloads: DownloadToMatch[]; - steamGames: FormattedSteamGamesByLetter; - titleHashMapping: TitleHashMapping; -} - -interface MatchResponse { - matchedDownloads: MatchedDownload[]; - stats: { - hashMatchCount: number; - fuzzyMatchCount: number; - noMatchCount: number; - }; -} - -const hashTitle = (title: string): string => { - return crypto.createHash("sha256").update(title).digest("hex"); -}; - -const formatName = (name: string) => { - return name - .normalize("NFD") - .replaceAll(/[\u0300-\u036f]/g, "") - .toLowerCase() - .replaceAll(/[^a-z0-9]/g, ""); -}; - -const formatRepackName = (name: string) => { - return formatName(name.replace("[DL]", "")); -}; - -const matchDownloads = (request: MatchRequest): MatchResponse => { - const { downloads, steamGames, titleHashMapping } = request; - const matchedDownloads: MatchedDownload[] = []; - - let hashMatchCount = 0; - let fuzzyMatchCount = 0; - let noMatchCount = 0; - - for (const download of downloads) { - let objectIds: string[] = []; - let usedHashMatch = false; - - const titleHash = hashTitle(download.title); - const steamIdsFromHash = titleHashMapping[titleHash]; - - if (steamIdsFromHash && steamIdsFromHash.length > 0) { - hashMatchCount++; - usedHashMatch = true; - objectIds = steamIdsFromHash.map(String); - } - - if (!usedHashMatch) { - let gamesInSteam: FormattedSteamGame[] = []; - const formattedTitle = formatRepackName(download.title); - - if (formattedTitle && formattedTitle.length > 0) { - const [firstLetter] = formattedTitle; - const games = steamGames[firstLetter] || []; - - gamesInSteam = games.filter((game) => - formattedTitle.startsWith(game.formattedName) - ); - - if (gamesInSteam.length === 0) { - gamesInSteam = games.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) - ); - } - - if (gamesInSteam.length === 0) { - for (const letter of Object.keys(steamGames)) { - const letterGames = steamGames[letter] || []; - const matches = letterGames.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) - ); - if (matches.length > 0) { - gamesInSteam = matches; - break; - } - } - } - - if (gamesInSteam.length > 0) { - fuzzyMatchCount++; - objectIds = gamesInSteam.map((game) => String(game.id)); - } else { - noMatchCount++; - } - } else { - noMatchCount++; - } - } - - matchedDownloads.push({ - ...download, - objectIds, - usedHashMatch, - }); - } - - return { - matchedDownloads, - stats: { - hashMatchCount, - fuzzyMatchCount, - noMatchCount, - }, - }; -}; - -// Message handler -if (parentPort) { - parentPort.on("message", (message: { id: string; data: MatchRequest }) => { - try { - const result = matchDownloads(message.data); - parentPort!.postMessage({ id: message.id, success: true, result }); - } catch (error) { - parentPort!.postMessage({ - id: message.id, - success: false, - error: error instanceof Error ? error.message : String(error), - }); - } - }); -} diff --git a/src/preload/index.ts b/src/preload/index.ts index da914b92..f89ec4db 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -99,22 +99,10 @@ contextBridge.exposeInMainWorld("electron", { /* Download sources */ addDownloadSource: (url: string) => ipcRenderer.invoke("addDownloadSource", url), - updateMissingFingerprints: () => - ipcRenderer.invoke("updateMissingFingerprints"), removeDownloadSource: (url: string, removeAll?: boolean) => ipcRenderer.invoke("removeDownloadSource", url, removeAll), getDownloadSources: () => ipcRenderer.invoke("getDownloadSources"), - deleteDownloadSource: (id: number) => - ipcRenderer.invoke("deleteDownloadSource", id), - deleteAllDownloadSources: () => - ipcRenderer.invoke("deleteAllDownloadSources"), - validateDownloadSource: (url: string) => - ipcRenderer.invoke("validateDownloadSource", url), syncDownloadSources: () => ipcRenderer.invoke("syncDownloadSources"), - getDownloadSourcesList: () => ipcRenderer.invoke("getDownloadSourcesList"), - checkDownloadSourceExists: (url: string) => - ipcRenderer.invoke("checkDownloadSourceExists", url), - getAllRepacks: () => ipcRenderer.invoke("getAllRepacks"), /* Library */ toggleAutomaticCloudSync: ( diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 74a2a97e..168a4435 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -7,7 +7,6 @@ import { useAppSelector, useDownload, useLibrary, - useRepacks, useToast, useUserDetails, } from "@renderer/hooks"; @@ -20,7 +19,6 @@ import { setUserDetails, setProfileBackground, setGameRunning, - setIsImportingSources, } from "@renderer/features"; import { useTranslation } from "react-i18next"; import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; @@ -40,8 +38,6 @@ export function App() { const { t } = useTranslation("app"); - const { updateRepacks } = useRepacks(); - const { clearDownload, setLastPacket } = useDownload(); const { @@ -199,36 +195,6 @@ export function App() { }); }, [dispatch, draggingDisabled]); - useEffect(() => { - (async () => { - dispatch(setIsImportingSources(true)); - - try { - // Initial repacks load - await updateRepacks(); - - // Sync all local sources (check for updates) - const newRepacksCount = await window.electron.syncDownloadSources(); - - if (newRepacksCount > 0) { - window.electron.publishNewRepacksNotification(newRepacksCount); - } - - // Update fingerprints for sources that don't have them - await window.electron.updateMissingFingerprints(); - - // Update repacks AFTER all syncing and fingerprint updates are complete - await updateRepacks(); - } catch (error) { - console.error("Error syncing download sources:", error); - // Still update repacks even if sync fails - await updateRepacks(); - } finally { - dispatch(setIsImportingSources(false)); - } - })(); - }, [updateRepacks, dispatch]); - const loadAndApplyTheme = useCallback(async () => { const activeTheme = await window.electron.getActiveCustomTheme(); if (activeTheme?.code) { diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 5752ba19..598874b5 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -1,5 +1,5 @@ import { DownloadIcon, PeopleIcon } from "@primer/octicons-react"; -import type { GameStats } from "@types"; +import type { GameStats, ShopAssets } from "@types"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; @@ -8,15 +8,15 @@ import "./game-card.scss"; import { useTranslation } from "react-i18next"; import { Badge } from "../badge/badge"; import { StarRating } from "../star-rating/star-rating"; -import { useCallback, useState, useMemo } from "react"; -import { useFormat, useRepacks } from "@renderer/hooks"; +import { useCallback, useState } from "react"; +import { useFormat } from "@renderer/hooks"; export interface GameCardProps extends React.DetailedHTMLProps< React.ButtonHTMLAttributes, HTMLButtonElement > { - game: any; + game: ShopAssets; } const shopIcon = { @@ -28,13 +28,6 @@ export function GameCard({ game, ...props }: GameCardProps) { const [stats, setStats] = useState(null); - const { getRepacksForObjectId } = useRepacks(); - const repacks = getRepacksForObjectId(game.objectId); - - const uniqueRepackers = Array.from( - new Set(repacks.map((repack) => repack.repacker)) - ); - const handleHover = useCallback(() => { if (!stats) { window.electron.getGameStats(game.objectId, game.shop).then((stats) => { @@ -45,14 +38,7 @@ export function GameCard({ game, ...props }: GameCardProps) { const { numberFormatter } = useFormat(); - const firstThreeRepackers = useMemo( - () => uniqueRepackers.slice(0, 3), - [uniqueRepackers] - ); - const remainingCount = useMemo( - () => uniqueRepackers.length - 3, - [uniqueRepackers] - ); + console.log("game", game); return ( - } - /> - - {validationResult && ( -
-
-

{validationResult?.name}

- - {t("found_download_option", { - count: validationResult?.downloadCount, - countFormatted: - validationResult?.downloadCount.toLocaleString(), - })} - -
- - + +
- )} + ); diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index f873b321..85c0569a 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -16,7 +16,7 @@ import { TrashIcon, } from "@primer/octicons-react"; import { AddDownloadSourceModal } from "./add-download-source-modal"; -import { useAppDispatch, useRepacks, useToast } from "@renderer/hooks"; +import { useAppDispatch, useToast } from "@renderer/hooks"; import { DownloadSourceStatus } from "@shared"; import { settingsContext } from "@renderer/context"; import { useNavigate } from "react-router-dom"; @@ -35,7 +35,6 @@ export function SettingsDownloadSources() { useState(false); const [isRemovingDownloadSource, setIsRemovingDownloadSource] = useState(false); - const [isFetchingSources, setIsFetchingSources] = useState(true); const { sourceUrl, clearSourceUrl } = useContext(settingsContext); @@ -46,37 +45,29 @@ export function SettingsDownloadSources() { const navigate = useNavigate(); - const { updateRepacks } = useRepacks(); - - const getDownloadSources = async () => { - await window.electron - .getDownloadSourcesList() - .then((sources) => { - setDownloadSources(sources); - }) - .finally(() => { - setIsFetchingSources(false); - }); - }; - - useEffect(() => { - getDownloadSources(); - }, []); - useEffect(() => { if (sourceUrl) setShowAddDownloadSourceModal(true); }, [sourceUrl]); + useEffect(() => { + const fetchDownloadSources = async () => { + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources); + }; + + fetchDownloadSources(); + }, []); + const handleRemoveSource = async (downloadSource: DownloadSource) => { setIsRemovingDownloadSource(true); try { - await window.electron.deleteDownloadSource(downloadSource.id); - await window.electron.removeDownloadSource(downloadSource.url); - + await window.electron.removeDownloadSource(false, downloadSource.id); + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources as DownloadSource[]); showSuccessToast(t("removed_download_source")); - await getDownloadSources(); - updateRepacks(); + } catch (error) { + console.error("Failed to remove download source:", error); } finally { setIsRemovingDownloadSource(false); } @@ -86,53 +77,44 @@ export function SettingsDownloadSources() { setIsRemovingDownloadSource(true); try { - await window.electron.deleteAllDownloadSources(); - await window.electron.removeDownloadSource("", true); - - showSuccessToast(t("removed_download_sources")); - await getDownloadSources(); - setShowConfirmationDeleteAllSourcesModal(false); - updateRepacks(); + await window.electron.removeDownloadSource(true); + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources as DownloadSource[]); + showSuccessToast(t("removed_all_download_sources")); + } catch (error) { + console.error("Failed to remove all download sources:", error); } finally { setIsRemovingDownloadSource(false); + setShowConfirmationDeleteAllSourcesModal(false); } }; const handleAddDownloadSource = async () => { - // Refresh sources list and repacks after import completes - await getDownloadSources(); - - // Force repacks update to ensure UI reflects new data - await updateRepacks(); - - showSuccessToast(t("added_download_source")); + try { + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources as DownloadSource[]); + } catch (error) { + console.error("Failed to refresh download sources:", error); + } }; const syncDownloadSources = async () => { setIsSyncingDownloadSources(true); - try { - // Sync local sources (check for updates) - await window.electron.syncDownloadSources(); - - // Refresh sources and repacks AFTER sync completes - await getDownloadSources(); - await updateRepacks(); - - showSuccessToast(t("download_sources_synced")); - } catch (error) { - console.error("Error syncing download sources:", error); - // Still refresh the UI even if sync fails - await getDownloadSources(); - await updateRepacks(); + const sources = await window.electron.syncDownloadSources(); + setDownloadSources(sources); } finally { setIsSyncingDownloadSources(false); } }; const statusTitle = { - [DownloadSourceStatus.UpToDate]: t("download_source_up_to_date"), - [DownloadSourceStatus.Errored]: t("download_source_errored"), + [DownloadSourceStatus.PendingMatching]: t( + "download_source_pending_matching" + ), + [DownloadSourceStatus.Matched]: t("download_source_matched"), + [DownloadSourceStatus.Matching]: t("download_source_matching"), + [DownloadSourceStatus.Failed]: t("download_source_failed"), }; const handleModalClose = () => { @@ -180,8 +162,7 @@ export function SettingsDownloadSources() { disabled={ !downloadSources.length || isSyncingDownloadSources || - isRemovingDownloadSource || - isFetchingSources + isRemovingDownloadSource } onClick={syncDownloadSources} > @@ -197,8 +178,7 @@ export function SettingsDownloadSources() { disabled={ isRemovingDownloadSource || isSyncingDownloadSources || - !downloadSources.length || - isFetchingSources + !downloadSources.length } > @@ -209,11 +189,7 @@ export function SettingsDownloadSources() { type="button" theme="outline" onClick={() => setShowAddDownloadSourceModal(true)} - disabled={ - isSyncingDownloadSources || - isFetchingSources || - isRemovingDownloadSource - } + disabled={isSyncingDownloadSources || isRemovingDownloadSource} > {t("add_download_source")} diff --git a/src/renderer/src/store.ts b/src/renderer/src/store.ts index 264b1296..9903271c 100644 --- a/src/renderer/src/store.ts +++ b/src/renderer/src/store.ts @@ -8,8 +8,6 @@ import { userDetailsSlice, gameRunningSlice, subscriptionSlice, - repacksSlice, - downloadSourcesSlice, catalogueSearchSlice, } from "@renderer/features"; @@ -23,8 +21,6 @@ export const store = configureStore({ userDetails: userDetailsSlice.reducer, gameRunning: gameRunningSlice.reducer, subscription: subscriptionSlice.reducer, - repacks: repacksSlice.reducer, - downloadSources: downloadSourcesSlice.reducer, catalogueSearch: catalogueSearchSlice.reducer, }, }); diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 851aec49..619dca65 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -11,8 +11,10 @@ export enum Downloader { } export enum DownloadSourceStatus { - UpToDate, - Errored, + PendingMatching = "PENDING_MATCHING", + Matched = "MATCHED", + Matching = "MATCHING", + Failed = "FAILED", } export enum CatalogueCategory { diff --git a/src/types/index.ts b/src/types/index.ts index 63b18645..092adaf8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -16,29 +16,22 @@ export interface DiskUsage { } export interface GameRepack { - id: number; + id: string; title: string; - uris: string[]; - repacker: string; fileSize: string | null; - objectIds: string[]; - uploadDate: Date | string | null; - createdAt: Date; - updatedAt: Date; + uris: string[]; + uploadDate: string | null; + downloadSourceId: string; + downloadSourceName: string; } export interface DownloadSource { - id: number; + id: string; name: string; url: string; - repackCount: number; status: DownloadSourceStatus; - objectIds: string[]; downloadCount: number; fingerprint?: string; - etag: string | null; - createdAt: Date; - updatedAt: Date; } export interface ShopAssets { @@ -51,6 +44,7 @@ export interface ShopAssets { logoImageUrl: string; logoPosition: string | null; coverImageUrl: string | null; + downloadSources: string[]; } export type ShopDetails = SteamAppDetails & { @@ -231,12 +225,6 @@ export interface DownloadSourceDownload { fileSize: string; } -export interface DownloadSourceValidationResult { - name: string; - etag: string; - downloadCount: number; -} - export interface GameStats { downloadCount: number; playerCount: number; @@ -366,7 +354,7 @@ export type CatalogueSearchResult = { title: string; shop: GameShop; genres: string[]; -} & Pick; +} & Pick; export type LibraryGame = Game & Partial & { From e1ce5bc6cb8e6d2cbc14a7e7615c0b95619d9cf2 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 21 Oct 2025 04:20:11 +0100 Subject: [PATCH 03/81] feat: using api download sources --- src/main/events/library/add-custom-game-to-library.ts | 1 + src/main/services/library-sync/merge-with-remote-games.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/events/library/add-custom-game-to-library.ts b/src/main/events/library/add-custom-game-to-library.ts index f2f2dd40..6a90087e 100644 --- a/src/main/events/library/add-custom-game-to-library.ts +++ b/src/main/events/library/add-custom-game-to-library.ts @@ -37,6 +37,7 @@ const addCustomGameToLibrary = async ( logoImageUrl: logoImageUrl || "", logoPosition: null, coverImageUrl: iconUrl || "", + downloadSources: [], }; await gamesShopAssetsSublevel.put(gameKey, assets); diff --git a/src/main/services/library-sync/merge-with-remote-games.ts b/src/main/services/library-sync/merge-with-remote-games.ts index f7ea2744..c00e4961 100644 --- a/src/main/services/library-sync/merge-with-remote-games.ts +++ b/src/main/services/library-sync/merge-with-remote-games.ts @@ -72,6 +72,7 @@ export const mergeWithRemoteGames = async () => { logoImageUrl: game.logoImageUrl, iconUrl: game.iconUrl, logoPosition: game.logoPosition, + downloadSources: game.downloadSources, }); } }) From 8a40c678f7c2db1d4558cf1cca018359a592e3d7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 21 Oct 2025 04:21:56 +0100 Subject: [PATCH 04/81] feat: using api download sources --- src/renderer/src/pages/game-details/game-details.tsx | 1 - src/types/index.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details.tsx b/src/renderer/src/pages/game-details/game-details.tsx index f0778494..04b78aa4 100644 --- a/src/renderer/src/pages/game-details/game-details.tsx +++ b/src/renderer/src/pages/game-details/game-details.tsx @@ -102,7 +102,6 @@ export default function GameDetails() { automaticallyExtract: boolean ) => { const response = await startDownload({ - repackId: repack.id, objectId: objectId!, title: gameTitle, downloader, diff --git a/src/types/index.ts b/src/types/index.ts index 092adaf8..7d11171b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -107,7 +107,6 @@ export type AppUpdaterEvent = /* Events */ export interface StartGameDownloadPayload { - repackId: number; objectId: string; title: string; shop: GameShop; From 945173f48e7f4017a409705325ca7fdffd3133c1 Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 16:32:39 -0300 Subject: [PATCH 05/81] Interface modification for the default game page --- .env.example | 5 --- .../game-details/game-details-content.tsx | 12 +++--- .../src/pages/game-details/game-details.scss | 34 ++++++++++++++--- .../pages/game-details/hero/hero-panel.scss | 8 +++- .../pages/game-details/hero/hero-panel.tsx | 38 ++++++++++--------- 5 files changed, 63 insertions(+), 34 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 3f914eb3..00000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -MAIN_VITE_API_URL= -MAIN_VITE_AUTH_URL= -MAIN_VITE_WS_URL= -RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index edf314c7..e019d984 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -153,11 +153,13 @@ export function GameDetailsContent() { >
- {game?.title} +
+ {game?.title} +
-
{getInfo()}
-
- -
+
+
+
{getInfo()}
+
+ +
- {showProgressBar && ( - - )} + {showProgressBar && ( + + )} +
); } From 864fd282f002e0fda04f5e995f24799730a7273f Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 16:42:21 -0300 Subject: [PATCH 06/81] Interface modification for the default game page --- .../src/pages/game-details/game-details.scss | 8 +- .../pages/game-details/hero/hero-panel.scss | 124 +++++++++--------- 2 files changed, 68 insertions(+), 64 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index 38319b0a..14b583ee 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -584,13 +584,17 @@ $hero-height: 300px; z-index: 0; &::after { - content: ''; + content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; - background: linear-gradient(0deg, rgba(0, 0, 0, 0.3) 60%, transparent 100%); + background: linear-gradient( + 0deg, + rgba(0, 0, 0, 0.3) 60%, + transparent 100% + ); z-index: 1; pointer-events: none; border-radius: inherit; diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.scss b/src/renderer/src/pages/game-details/hero/hero-panel.scss index 8a3611ec..9fbdf453 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel.scss @@ -4,75 +4,75 @@ padding: 0px 12px 12px; margin: 0; -.hero-panel { - width: 100%; - height: 72px; - min-height: 72px; - padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 3); - background: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: solid 1px rgba(255, 255, 255, 0.15); - display: flex; - align-items: center; - justify-content: space-between; - transition: all ease 0.2s; - border-bottom: solid 1px globals.$border-color; - overflow: hidden; - top: 0; - z-index: 2; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - border-radius: 8px; - - &--stuck { - background: rgba(0, 0, 0, 0.7); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.8); - } - - &__content { - display: flex; - flex-direction: column; - gap: globals.$spacing-unit; - } - - &__actions { - display: flex; - gap: globals.$spacing-unit; - } - - &__download-details { - gap: globals.$spacing-unit; - display: flex; - color: globals.$body-color; - align-items: center; - } - - &__downloads-link { - color: globals.$body-color; - text-decoration: underline; - } - - &__progress-bar { - position: absolute; - bottom: 0; - left: 0; + .hero-panel { width: 100%; - height: 3px; + height: 72px; + min-height: 72px; + padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 3); + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: solid 1px rgba(255, 255, 255, 0.15); + display: flex; + align-items: center; + justify-content: space-between; transition: all ease 0.2s; + border-bottom: solid 1px globals.$border-color; + overflow: hidden; + top: 0; + z-index: 2; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; - &::-webkit-progress-bar { - background-color: transparent; + &--stuck { + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.8); } - &::-webkit-progress-value { - background-color: globals.$muted-color; + &__content { + display: flex; + flex-direction: column; + gap: globals.$spacing-unit; } - &--disabled { - opacity: globals.$disabled-opacity; + &__actions { + display: flex; + gap: globals.$spacing-unit; + } + + &__download-details { + gap: globals.$spacing-unit; + display: flex; + color: globals.$body-color; + align-items: center; + } + + &__downloads-link { + color: globals.$body-color; + text-decoration: underline; + } + + &__progress-bar { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 3px; + transition: all ease 0.2s; + + &::-webkit-progress-bar { + background-color: transparent; + } + + &::-webkit-progress-value { + background-color: globals.$muted-color; + } + + &--disabled { + opacity: globals.$disabled-opacity; + } } } } -} \ No newline at end of file From 7435bff64f1da9f94430509bd6da736c3999b1ad Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 17:17:37 -0300 Subject: [PATCH 07/81] Interface modification for the default game page --- .env.example | 8 ++ .../pages/game-details/hero/hero-panel.scss | 124 +++++++++--------- .../pages/game-details/hero/hero-panel.tsx | 2 +- 3 files changed, 71 insertions(+), 63 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..c71b5e68 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +MAIN_VITE_API_URL=https://api-staging.hydralauncher.gg +MAIN_VITE_AUTH_URL=https://auth-staging.hydralauncher.gg +MAIN_VITE_WS_URL=wss://ws-staging.hydralauncher.gg +MAIN_VITE_EXTERNAL_RESOURCES_URL=https://assets.hydralauncher.gg +RENDERER_VITE_EXTERNAL_RESOURCES_URL=https://assets.hydralauncher.gg +MAIN_VITE_CHECKOUT_URL=https://checkout-staging.hydralauncher.gg +RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= +RENDERER_VITE_TORBOX_REFERRAL_CODE= diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.scss b/src/renderer/src/pages/game-details/hero/hero-panel.scss index 9fbdf453..fa797988 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel.scss @@ -1,78 +1,78 @@ @use "../../../scss/globals.scss"; -.hero-panel-wrapper { +.hero-panel { + width: 100%; + height: 72px; + min-height: 72px; + padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 3); + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: solid 1px rgba(255, 255, 255, 0.15); + display: flex; + align-items: center; + justify-content: space-between; + transition: all ease 0.2s; + border-bottom: solid 1px globals.$border-color; + overflow: hidden; + top: 0; + z-index: 2; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; + + &__container { padding: 0px 12px 12px; margin: 0; + } - .hero-panel { - width: 100%; - height: 72px; - min-height: 72px; - padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 3); - background: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: solid 1px rgba(255, 255, 255, 0.15); + &--stuck { + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.8); + } + + &__content { display: flex; + flex-direction: column; + gap: globals.$spacing-unit; + } + + &__actions { + display: flex; + gap: globals.$spacing-unit; + } + + &__download-details { + gap: globals.$spacing-unit; + display: flex; + color: globals.$body-color; align-items: center; - justify-content: space-between; + } + + &__downloads-link { + color: globals.$body-color; + text-decoration: underline; + } + + &__progress-bar { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 3px; transition: all ease 0.2s; - border-bottom: solid 1px globals.$border-color; - overflow: hidden; - top: 0; - z-index: 2; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - border-radius: 8px; - &--stuck { - background: rgba(0, 0, 0, 0.7); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.8); + &::-webkit-progress-bar { + background-color: transparent; } - &__content { - display: flex; - flex-direction: column; - gap: globals.$spacing-unit; + &::-webkit-progress-value { + background-color: globals.$muted-color; } - &__actions { - display: flex; - gap: globals.$spacing-unit; - } - - &__download-details { - gap: globals.$spacing-unit; - display: flex; - color: globals.$body-color; - align-items: center; - } - - &__downloads-link { - color: globals.$body-color; - text-decoration: underline; - } - - &__progress-bar { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 3px; - transition: all ease 0.2s; - - &::-webkit-progress-bar { - background-color: transparent; - } - - &::-webkit-progress-value { - background-color: globals.$muted-color; - } - - &--disabled { - opacity: globals.$disabled-opacity; - } + &--disabled { + opacity: globals.$disabled-opacity; } } } diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.tsx b/src/renderer/src/pages/game-details/hero/hero-panel.tsx index db0164de..a1e6fed5 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel.tsx @@ -50,7 +50,7 @@ export function HeroPanel() { game?.download?.status === "paused"; return ( -
+
{getInfo()}
From ca35da37ededae768923576e4b4fef78aadd818b Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 17:20:48 -0300 Subject: [PATCH 08/81] Interface modification for the default game page --- src/renderer/src/pages/game-details/hero/hero-panel.scss | 8 ++++---- src/renderer/src/pages/game-details/hero/hero-panel.tsx | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.scss b/src/renderer/src/pages/game-details/hero/hero-panel.scss index fa797988..a0d32e9e 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel.scss @@ -18,11 +18,11 @@ top: 0; z-index: 2; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - border-radius: 8px; - + border-radius: 8px; + &__container { - padding: 0px 12px 12px; - margin: 0; + padding: 0px 12px 12px; + margin: 0; } &--stuck { diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.tsx b/src/renderer/src/pages/game-details/hero/hero-panel.tsx index a1e6fed5..799f2c36 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel.tsx @@ -61,7 +61,9 @@ export function HeroPanel() { Date: Wed, 22 Oct 2025 17:51:31 -0300 Subject: [PATCH 09/81] Interface modification for the default game page --- .env.example | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.env.example b/.env.example index c71b5e68..e69de29b 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +0,0 @@ -MAIN_VITE_API_URL=https://api-staging.hydralauncher.gg -MAIN_VITE_AUTH_URL=https://auth-staging.hydralauncher.gg -MAIN_VITE_WS_URL=wss://ws-staging.hydralauncher.gg -MAIN_VITE_EXTERNAL_RESOURCES_URL=https://assets.hydralauncher.gg -RENDERER_VITE_EXTERNAL_RESOURCES_URL=https://assets.hydralauncher.gg -MAIN_VITE_CHECKOUT_URL=https://checkout-staging.hydralauncher.gg -RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= From 99e34ce0601c67aac63785e2cca7bc24caddc601 Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 17:56:20 -0300 Subject: [PATCH 10/81] Interface modification for the default game page --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.env.example b/.env.example index e69de29b..19c67fe5 100644 --- a/.env.example +++ b/.env.example @@ -0,0 +1,5 @@ +MAIN_VITE_API_URL= +MAIN_VITE_AUTH_URL= +MAIN_VITE_WS_URL= +RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= +RENDERER_VITE_TORBOX_REFERRAL_CODE= \ No newline at end of file From a7b5bdb3b47013d329d596970fc8fcb20dab3af1 Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 18:03:24 -0300 Subject: [PATCH 11/81] Interface modification for the default game page --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 19c67fe5..3f914eb3 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,4 @@ MAIN_VITE_API_URL= MAIN_VITE_AUTH_URL= MAIN_VITE_WS_URL= RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= \ No newline at end of file +RENDERER_VITE_TORBOX_REFERRAL_CODE= From 00e597c910db941cc862f3628749bc15eb5ebf26 Mon Sep 17 00:00:00 2001 From: GrimmDevel Date: Thu, 23 Oct 2025 00:51:00 +0300 Subject: [PATCH 12/81] Added Latvian translation --- .env.example | 5 - src/locales/index.ts | 2 + src/locales/lv/translation.json | 708 ++++++++++++++++++++++++++++++++ 3 files changed, 710 insertions(+), 5 deletions(-) delete mode 100644 .env.example create mode 100644 src/locales/lv/translation.json diff --git a/.env.example b/.env.example deleted file mode 100644 index 3f914eb3..00000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -MAIN_VITE_API_URL= -MAIN_VITE_AUTH_URL= -MAIN_VITE_WS_URL= -RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= diff --git a/src/locales/index.ts b/src/locales/index.ts index a44480e6..d58ee59a 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -28,6 +28,7 @@ import bg from "./bg/translation.json"; import uz from "./uz/translation.json"; import fi from "./fi/translation.json"; import sv from "./sv/translation.json"; +import lv from "./lv/translation.json"; export default { "pt-BR": ptBR, @@ -60,4 +61,5 @@ export default { et, uz, sv, + lv, }; diff --git a/src/locales/lv/translation.json b/src/locales/lv/translation.json new file mode 100644 index 00000000..a33a1986 --- /dev/null +++ b/src/locales/lv/translation.json @@ -0,0 +1,708 @@ +{ + "language_name": "Latviešu", + "app": { + "successfully_signed_in": "Veiksmīga pieteikšanās" + }, + "home": { + "surprise_me": "Pārsteidz mani", + "no_results": "Nekas nav atrasts", + "start_typing": "Sākt rakstīt...", + "hot": "Šobrīd populārs", + "weekly": "📅 Nedēļas labākās spēles", + "achievements": "🏆 Spēles ar sasniegumiem" + }, + "sidebar": { + "catalogue": "Katalogs", + "downloads": "Lejupielādes", + "settings": "Iestatījumi", + "my_library": "Bibliotēka", + "downloading_metadata": "{{title}} (Lejupielādē metadatus…)", + "paused": "{{title}} (Apturēts)", + "downloading": "{{title}} ({{percentage}} - Lejupielādē…)", + "filter": "Meklēt", + "home": "Sākums", + "queued": "{{title}} (Rindā)", + "game_has_no_executable": "Spēles palaišanas fails nav izvēlēts", + "sign_in": "Pieteikties", + "friends": "Draugi", + "need_help": "Nepieciešama palīdzība?", + "favorites": "Izlase", + "playable_button_title": "Rādīt tikai instalētās spēles.", + "add_custom_game_tooltip": "Pievienot pielāgotu spēli", + "show_playable_only_tooltip": "Rādīt tikai spēlēšanai pieejamās", + "custom_game_modal": "Pievienot pielāgotu spēli", + "custom_game_modal_description": "Pievienojiet pielāgotu spēli bibliotēkai, izvēloties izpildāmo failu", + "custom_game_modal_executable_path": "Ceļš uz izpildāmo failu", + "custom_game_modal_select_executable": "Izvēlieties izpildāmo failu", + "custom_game_modal_title": "Spēles nosaukums", + "custom_game_modal_enter_title": "Ievadiet spēles nosaukumu", + "custom_game_modal_browse": "Pārlūkot", + "custom_game_modal_cancel": "Atcelt", + "custom_game_modal_add": "Pievienot spēli", + "custom_game_modal_adding": "Pievieno spēli...", + "custom_game_modal_success": "Pielāgota spēle veiksmīgi pievienota", + "custom_game_modal_failed": "Neizdevās pievienot pielāgotu spēli", + "custom_game_modal_executable": "Izpildāmais fails", + "edit_game_modal": "Konfigurēt resursus", + "edit_game_modal_description": "Konfigurējiet spēles resursus un detaļas", + "edit_game_modal_title": "Nosaukums", + "edit_game_modal_enter_title": "Ievadiet nosaukumu", + "edit_game_modal_image": "Attēls", + "edit_game_modal_select_image": "Izvēlieties attēlu", + "edit_game_modal_browse": "Pārlūkot", + "edit_game_modal_image_preview": "Attēla priekšskatījums", + "edit_game_modal_icon": "Ikona", + "edit_game_modal_select_icon": "Izvēlieties ikonu", + "edit_game_modal_icon_preview": "Ikona priekšskatījums", + "edit_game_modal_logo": "Logotips", + "edit_game_modal_select_logo": "Izvēlieties logotipu", + "edit_game_modal_logo_preview": "Logotipa priekšskatījums", + "edit_game_modal_hero": "Vāka attēls", + "edit_game_modal_select_hero": "Izvēlieties spēles vāka attēlu", + "edit_game_modal_hero_preview": "Spēles vāka attēla priekšskatījums", + "edit_game_modal_cancel": "Atcelt", + "edit_game_modal_update": "Atjaunināt", + "edit_game_modal_updating": "Atjaunina...", + "edit_game_modal_fill_required": "Lūdzu, aizpildiet visus obligātos laukus", + "edit_game_modal_success": "Resursi veiksmīgi atjaunināti", + "edit_game_modal_failed": "Neizdevās atjaunināt resursus", + "edit_game_modal_image_filter": "Attēls", + "edit_game_modal_icon_resolution": "Ieteicamā izšķirtspēja: 256x256px", + "edit_game_modal_logo_resolution": "Ieteicamā izšķirtspēja: 640x360px", + "edit_game_modal_hero_resolution": "Ieteicamā izšķirtspēja: 1920x620px", + "edit_game_modal_assets": "Resursi", + "edit_game_modal_drop_icon_image_here": "Ievelciet ikonas attēlu šeit", + "edit_game_modal_drop_logo_image_here": "Ievelciet logotipa attēlu šeit", + "edit_game_modal_drop_hero_image_here": "Ievelciet vāka attēlu šeit", + "edit_game_modal_drop_to_replace_icon": "Ievelciet, lai aizstātu ikonu", + "edit_game_modal_drop_to_replace_logo": "Ievelciet, lai aizstātu logotipu", + "edit_game_modal_drop_to_replace_hero": "Ievelciet, lai aizstātu vāku", + "install_decky_plugin": "Instalēt Decky spraudni", + "update_decky_plugin": "Atjaunināt Decky spraudni", + "decky_plugin_installed_version": "Decky spraudnis (v{{version}})", + "install_decky_plugin_title": "Instalēt Hydra Decky spraudni", + "install_decky_plugin_message": "Tas lejupielādēs un instalēs Hydra spraudni Decky Loader. Var būt nepieciešamas paaugstinātas atļaujas. Turpināt?", + "update_decky_plugin_title": "Atjaunināt Hydra Decky spraudni", + "update_decky_plugin_message": "Ir pieejama jauna Hydra Decky spraudņa versija. Vai vēlaties to atjaunināt tagad?", + "decky_plugin_installed": "Decky spraudnis v{{version}} veiksmīgi instalēts", + "decky_plugin_installation_failed": "Neizdevās instalēt Decky spraudni: {{error}}", + "decky_plugin_installation_error": "Decky spraudņa instalēšanas kļūda: {{error}}", + "confirm": "Apstiprināt", + "cancel": "Atcelt" + }, + "header": { + "search": "Meklēt", + "home": "Sākums", + "catalogue": "Katalogs", + "downloads": "Lejupielādes", + "search_results": "Meklēšanas rezultāti", + "settings": "Iestatījumi", + "version_available_install": "Pieejama versija {{version}}. Noklikšķiniet šeit, lai instalētu.", + "version_available_download": "Pieejama versija {{version}}. Noklikšķiniet šeit, lai lejupielādētu." + }, + "bottom_panel": { + "no_downloads_in_progress": "Nav aktīvu lejupielāžu", + "downloading_metadata": "Lejupielādē metadatus {{title}}…", + "downloading": "Lejupielādē {{title}}… ({{percentage}} pabeigts) - Beigsies {{eta}} - {{speed}}", + "calculating_eta": "Lejupielādē {{title}}… ({{percentage}} pabeigts) - Aprēķina atlikušo laiku…", + "checking_files": "Pārbauda failus {{title}}… ({{percentage}} pabeigts)", + "installing_common_redist": "{{log}}…", + "installation_complete": "Instalēšana pabeigta", + "installation_complete_message": "Bibliotēkas veiksmīgi instalētas" + }, + "catalogue": { + "search": "Filtrs…", + "developers": "Izstrādātāji", + "genres": "Žanri", + "tags": "Atzīmes", + "publishers": "Izdevēji", + "download_sources": "Lejupielādes avoti", + "result_count": "{{resultCount}} rezultāti", + "filter_count": "{{filterCount}} pieejami", + "clear_filters": "Notīrīt {{filterCount}} atlasītos" + }, + "game_details": { + "open_download_options": "Atvērt avotus", + "download_options_zero": "Nav avotu", + "download_options_one": "{{count}} avots", + "download_options_other": "{{count}} avoti", + "updated_at": "Atjaunināts {{updated_at}}", + "install": "Instalēt", + "resume": "Atsākt", + "pause": "Apturēt", + "cancel": "Atcelt", + "remove": "Dzēst", + "space_left_on_disk": "{{space}} brīvs diskā", + "eta": "Beigsies {{eta}}", + "calculating_eta": "Aprēķina atlikušo laiku…", + "downloading_metadata": "Lejupielādē metadatus…", + "filter": "Meklēt repakus", + "requirements": "Sistēmas prasības", + "minimum": "Minimālās", + "recommended": "Ieteicamās", + "paused": "Apturēts", + "release_date": "Izdots {{date}}", + "publisher": "Izdevējs {{publisher}}", + "hours": "stundas", + "minutes": "minūtes", + "amount_hours": "{{amount}} stundas", + "amount_minutes": "{{amount}} minūtes", + "accuracy": "precizitāte {{accuracy}}%", + "add_to_library": "Pievienot bibliotēkai", + "already_in_library": "Jau bibliotēkā", + "remove_from_library": "Dzēst no bibliotēkas", + "no_downloads": "Nav pieejamu avotu", + "play_time": "Spēlēts {{amount}}", + "last_time_played": "Pēdējo reizi spēlēts {{period}}", + "not_played_yet": "Jūs vēl neesat spēlējis {{title}}", + "next_suggestion": "Nākamais ieteikums", + "play": "Spēlēt", + "deleting": "Dzēš instalētāju…", + "close": "Aizvērt", + "playing_now": "Palaists", + "change": "Mainīt", + "repacks_modal_description": "Izvēlieties repaku lejupielādei", + "select_folder_hint": "Lai mainītu noklusējuma lejupielāžu mapi, atveriet <0>Iestatījumus", + "download_now": "Lejupielādēt tagad", + "no_shop_details": "Neizdevās iegūt aprakstu", + "download_options": "Avoti", + "download_path": "Ceļš lejupielādēm", + "previous_screenshot": "Iepriekšējais ekrānuzņēmums", + "next_screenshot": "Nākamais ekrānuzņēmums", + "screenshot": "Ekrānuzņēmums {{number}}", + "open_screenshot": "Atvērt ekrānuzņēmumu {{number}}", + "download_settings": "Lejupielādes parametri", + "downloader": "Lejupielādētājs", + "select_executable": "Izvēlēties", + "no_executable_selected": "Fails nav izvēlēts", + "open_folder": "Atvērt mapi", + "open_download_location": "Pārlūkot lejupielādes mapi", + "create_shortcut": "Izveidot īsceļu uz darbvirsmas", + "create_shortcut_simple": "Izveidot īsceļu", + "clear": "Notīrīt", + "remove_files": "Dzēst failus", + "remove_from_library_title": "Vai esat pārliecināts?", + "remove_from_library_description": "{{game}} tiks dzēsta no jūsu bibliotēkas.", + "options": "Iestatījumi", + "properties": "Īpašības", + "executable_section_title": "Fails", + "executable_section_description": "Ceļš uz failu, kas tiks palaists, nospiežot \"Spēlēt\"", + "downloads_section_title": "Lejupielādes", + "downloads_section_description": "Pārbaudīt atjauninājumu vai citu spēles versiju pieejamību", + "danger_zone_section_title": "Bīstamā zona", + "danger_zone_section_description": "Jūs varat dzēst šo spēli no savas bibliotēkas vai failus, kas lejupielādēti no Hydra", + "download_in_progress": "Notiek lejupielāde", + "download_paused": "Lejupielāde apturēta", + "last_downloaded_option": "Pēdējais lejupielādes variants", + "create_steam_shortcut": "Izveidot Steam īsceļu", + "create_shortcut_success": "Īsceļš izveidots", + "you_might_need_to_restart_steam": "Iespējams, jums būs jāpārstartē Steam, lai redzētu izmaiņas", + "create_shortcut_error": "Neizdevās izveidot īsceļu", + "add_to_favorites": "Pievienot izlasei", + "remove_from_favorites": "Dzēst no izlases", + "failed_update_favorites": "Neizdevās atjaunināt izlasi", + "game_removed_from_library": "Spēle dzēsta no bibliotēkas", + "failed_remove_from_library": "Neizdevās dzēst no bibliotēkas", + "files_removed_success": "Faili veiksmīgi dzēsti", + "failed_remove_files": "Neizdevās dzēst failus", + "nsfw_content_title": "Šajā spēlē ir nepiemērots saturs", + "nsfw_content_description": "{{title}} satur saturu, kas var nebūt piemērots visiem vecumiem. \nVai esat pārliecināts, ka vēlaties turpināt?", + "allow_nsfw_content": "Turpināt", + "refuse_nsfw_content": "Atpakaļ", + "stats": "Statistika", + "download_count": "Lejupielādes", + "player_count": "Aktīvie spēlētāji", + "download_error": "Šis lejupielādes variants nav pieejams", + "download": "Lejupielādēt", + "executable_path_in_use": "Izpildāmais fails jau tiek izmantots \"{{game}}\"", + "warning": "Uzmanību:", + "hydra_needs_to_remain_open": "Lai veiktu šo lejupielādi, Hydra jāpaliek atvērtai līdz beigām. Ja Hydra aizvērsies pirms pabeigšanas, jūs zaudēsiet progresu.", + "achievements": "Sasniegumi", + "achievements_count": "Sasniegumi {{unlockedCount}}/{{achievementsCount}}", + "show_more": "Rādīt vairāk", + "show_less": "Rādīt mazāk", + "reviews": "Atsauksmes", + "leave_a_review": "Atstāt atsauksmi", + "write_review_placeholder": "Dalieties savās domās par šo spēli...", + "sort_newest": "Vispirms jaunākās", + "no_reviews_yet": "Pagaidām nav atsauksmju", + "be_first_to_review": "Esiet pirmais, kurš dalīsies savās domās par šo spēli!", + "sort_oldest": "Vispirms vecākās", + "sort_highest_score": "Augstākais vērtējums", + "sort_lowest_score": "Zemākais vērtējums", + "sort_most_voted": "Vispopulārākās", + "rating": "Vērtējums", + "rating_stats": "Vērtējums", + "rating_very_negative": "Ļoti negatīvs", + "rating_negative": "Negatīvs", + "rating_neutral": "Neitrāls", + "rating_positive": "Pozitīvs", + "rating_very_positive": "Ļoti pozitīvs", + "submit_review": "Iesniegt", + "submitting": "Iesniegšana...", + "review_submitted_successfully": "Atsauksme veiksmīgi iesniegta!", + "review_submission_failed": "Neizdevās iesniegt atsauksmi. Lūdzu, mēģiniet vēlreiz.", + "review_cannot_be_empty": "Atsauksmes teksta lauks nevar būt tukšs.", + "review_deleted_successfully": "Atsauksme veiksmīgi dzēsta.", + "review_deletion_failed": "Neizdevās dzēst atsauksmi. Lūdzu, mēģiniet vēlreiz.", + "loading_reviews": "Ielādē atsauksmes...", + "loading_more_reviews": "Ielādē papildu atsauksmes...", + "load_more_reviews": "Ielādēt vairāk atsauksmju", + "you_seemed_to_enjoy_this_game": "Šķiet, jums patika šī spēle", + "would_you_recommend_this_game": "Vai vēlaties atstāt atsauksmi par šo spēli?", + "yes": "Jā", + "maybe_later": "Varbūt vēlāk", + "rating_count": "Vērtējums", + "delete_review": "Dzēst atsauksmi", + "remove_review": "Dzēst atsauksmi", + "delete_review_modal_title": "Vai esat pārliecināts, ka vēlaties dzēst savu atsauksmi?", + "delete_review_modal_description": "Šo darbību nevar atsaukt.", + "delete_review_modal_delete_button": "Dzēst", + "delete_review_modal_cancel_button": "Atcelt", + "show_original": "Rādīt oriģinālu", + "show_translation": "Rādīt tulkojumu", + "show_original_translated_from": "Rādīt oriģinālu (tulkot no {{language}})", + "hide_original": "Slēpt oriģinālu", + "cloud_save": "Mākoņglabāšana", + "cloud_save_description": "Glabājiet savu progresu mākonī un turpiniet spēlēt jebkurā ierīcē", + "backups": "Rezerves kopijas", + "install_backup": "Instalēt", + "delete_backup": "Dzēst", + "create_backup": "Izveidot jaunu rezerves kopiju", + "last_backup_date": "Pēdējā rezerves kopija no {{date}}", + "no_backup_preview": "Šim nosaukumam saglabājumi nav atrasti", + "restoring_backup": "Atjauno rezerves kopiju ({{progress}} pabeigts)…", + "uploading_backup": "Augšupielādē rezerves kopiju…", + "no_backups": "Jūs vēl neesat izveidojis rezerves kopijas šai spēlei", + "backup_uploaded": "Rezerves kopija augšupielādēta", + "backup_failed": "Rezerves kopēšanas kļūda", + "backup_deleted": "Rezerves kopija dzēsta", + "backup_restored": "Rezerves kopija atjaunota", + "see_all_achievements": "Skatīt visus sasniegumus", + "sign_in_to_see_achievements": "Piesakieties, lai redzētu sasniegumus", + "mapping_method_automatic": "Automātiska", + "mapping_method_manual": "Manuāla", + "mapping_method_label": "Kartēšanas metode", + "files_automatically_mapped": "Faili automātiski kartēti", + "no_backups_created": "Šai spēlei nav izveidotas rezerves kopijas", + "manage_files": "Failu pārvaldība", + "loading_save_preview": "Meklē saglabājumus…", + "wine_prefix": "Wine prefikss", + "wine_prefix_description": "Wine prefikss, ko izmanto šīs spēles palaišanai", + "launch_options": "Palaišanas parametri", + "launch_options_description": "Pieredzējuši lietotāji var veikt izmaiņas palaišanas parametros", + "launch_options_placeholder": "Parametrs nav norādīts", + "no_download_option_info": "Informācija nav pieejama", + "backup_deletion_failed": "Neizdevās dzēst rezerves kopiju", + "max_number_of_artifacts_reached": "Sasniegts maksimālais rezerves kopiju skaits šai spēlei", + "achievements_not_sync": "Jūsu sasniegumi nav sinhronizēti", + "manage_files_description": "Pārvaldiet failus, kas tiks saglabāti un atjaunoti", + "select_folder": "Izvēlēties mapi", + "backup_from": "Rezerves kopija no {{date}}", + "automatic_backup_from": "Automātiska rezerves kopija no {{date}}", + "enable_automatic_cloud_sync": "Iespējot automātisku sinhronizāciju mākonī", + "custom_backup_location_set": "Iestatīta pielāgota rezerves kopēšanas vieta", + "no_directory_selected": "Nav izvēlēts katalogs", + "no_write_permission": "Nevar augšupielādēt šajā direktorijā. Noklikšķiniet šeit, lai uzzinātu vairāk.", + "reset_achievements": "Atiestatīt sasniegumus", + "reset_achievements_description": "Tas atiestatīs visus sasniegumus {{game}} spēlei", + "reset_achievements_title": "Vai esat pārliecināts?", + "reset_achievements_success": "Sasniegumi veiksmīgi atiestatīti", + "reset_achievements_error": "Neizdevās atiestatīt sasniegumus", + "download_error_gofile_quota_exceeded": "Jūs pārsniedzāt Gofile mēneša kvotu. Lūdzu, uzgaidiet, kamēr kvota tiks atjaunota.", + "download_error_real_debrid_account_not_authorized": "Jūsu Real-Debrid konts nav autorizēts jaunām lejupielādēm. Lūdzu, pārbaudiet konta iestatījumus un mēģiniet vēlreiz.", + "download_error_not_cached_on_real_debrid": "Šī lejupielāde nav pieejama Real-Debrid, un Real-Debrid lejupielādes statusu pagaidām nav iespējams iegūt.", + "update_playtime_title": "Atjaunināt spēles laiku", + "update_playtime_description": "Manuāli atjauniniet spēles laiku {{game}} spēlei", + "update_playtime": "Atjaunināt spēles laiku", + "update_playtime_success": "Spēles laiks veiksmīgi atjaunināts", + "update_playtime_error": "Neizdevās atjaunināt spēles laiku", + "update_game_playtime": "Atjaunināt spēles laiku", + "manual_playtime_warning": "Jūsu stundas tiks atzīmētas kā manuāli atjauninātas. Šo darbību nevar atcelt.", + "manual_playtime_tooltip": "Šis spēles laiks tika atjaunināts manuāli", + "download_error_not_cached_on_torbox": "Šī lejupielāde nav pieejama TorBox, un TorBox lejupielādes statusu pagaidām nav iespējams iegūt.", + "download_error_not_cached_on_hydra": "Šī lejupielāde nav pieejama Nimbus.", + "game_removed_from_favorites": "Spēle dzēsta no izlases", + "game_added_to_favorites": "Spēle pievienota izlasei", + "game_removed_from_pinned": "Spēle dzēsta no piespraustajiem", + "game_added_to_pinned": "Spēle pievienota piespraustajiem", + "automatically_extract_downloaded_files": "Automātiska lejupielādēto failu izpakošana", + "create_start_menu_shortcut": "Izveidot saīsni sākuma izvēlnē", + "invalid_wine_prefix_path": "Nederīgs Wine prefiksa ceļš", + "invalid_wine_prefix_path_description": "Wine prefiksa ceļš nav derīgs. Lūdzu, pārbaudiet ceļu un mēģiniet vēlreiz.", + "missing_wine_prefix": "Wine prefikss ir nepieciešams, lai izveidotu rezerves kopiju Linux vidē", + "artifact_renamed": "Rezerves kopija veiksmīgi pārsaukta", + "rename_artifact": "Pārsaukt rezerves kopiju", + "rename_artifact_description": "Pārsauciet rezerves kopiju, piešķirot tai aprakstošāku nosaukumu.", + "artifact_name_label": "Rezerves kopijas nosaukums", + "artifact_name_placeholder": "Ievadiet nosaukumu rezerves kopijai", + "save_changes": "Saglabāt izmaiņas", + "required_field": "Šis lauks ir obligāts", + "max_length_field": "Šim laukam jābūt mazāk par {{length}} simboliem", + "freeze_backup": "Piespraust, lai to nepārrakstītu automātiskās rezerves kopijas", + "unfreeze_backup": "Atspraust", + "backup_frozen": "Rezerves kopija piesprausta", + "backup_unfrozen": "Rezerves kopija atsprausta", + "backup_freeze_failed": "Neizdevās piespraust rezerves kopiju", + "backup_freeze_failed_description": "Jums jāatstāj vismaz viens brīvs slots automātiskajām rezerves kopijām", + "edit_game_modal_button": "Rediģēt spēles detaļas", + "game_details": "Spēles detaļas", + "currency_symbol": "₽", + "currency_country": "ru", + "prices": "Cenas", + "no_prices_found": "Cenas nav atrastas", + "view_all_prices": "Noklikšķiniet, lai skatītu visas cenas", + "retail_price": "Mazumtirdzniecības cena", + "keyshop_price": "Atslēgu veikala cena", + "historical_retail": "Vēsturiskās mazumtirdzniecības cenas", + "historical_keyshop": "Vēsturiskās atslēgu veikalu cenas", + "language": "Valoda", + "caption": "Subtitri", + "audio": "Audio", + "filter_by_source": "Filtrēt pēc avota", + "no_repacks_found": "Avoti šai spēlei nav atrasti" + }, + "activation": { + "title": "Aktivizēt Hydra", + "installation_id": "Instalācijas ID:", + "enter_activation_code": "Ievadiet savu aktivizācijas kodu", + "message": "Ja nezināt, kur to pieprasīt, jums to nevajadzētu būt.", + "activate": "Aktivizēt", + "loading": "Ielādēšana…" + }, + "downloads": { + "resume": "Atsākt", + "pause": "Apturēt", + "eta": "Beigsies {{eta}}", + "paused": "Apturēts", + "verifying": "Pārbauda…", + "completed": "Pabeigts", + "removed": "Nav lejupielādēts", + "cancel": "Atcelt", + "filter": "Meklēt lejupielādētās spēles", + "remove": "Dzēst", + "downloading_metadata": "Lejupielādē metadatus…", + "deleting": "Dzēš instalētāju…", + "delete": "Dzēst instalētāju", + "delete_modal_title": "Vai esat pārliecināts?", + "delete_modal_description": "Tas dzēsīs visus instalētājus no jūsu datora", + "install": "Instalēt", + "download_in_progress": "Procesā", + "queued_downloads": "Lejupielādes rindā", + "downloads_completed": "Pabeigts", + "queued": "Rindā", + "no_downloads_title": "Šeit ir tik tukšs...", + "no_downloads_description": "Jūs vēl neko neesat lejupielādējis, izmantojot Hydra, bet nekad nav par vēlu sākt.", + "checking_files": "Pārbauda failus…", + "seeding": "Sēdēšana", + "stop_seeding": "Apturēt sēdēšanu", + "resume_seeding": "Turpināt sēdēšanu", + "options": "Pārvaldīt", + "extract": "Izpakot failus", + "extracting": "Izpako failus…" + }, + "settings": { + "downloads_path": "Lejupielāžu ceļš", + "change": "Mainīt", + "notifications": "Paziņojumi", + "enable_download_notifications": "Pēc lejupielādes pabeigšanas", + "enable_repack_list_notifications": "Pievienojot jaunu repaku", + "real_debrid_api_token_label": "Real-Debrid API-atslēga", + "quit_app_instead_hiding": "Aizvērt lietotni, nevis minimizēt uz paplātes", + "launch_with_system": "Palaist Hydra kopā ar sistēmu", + "general": "Vispārīgi", + "behavior": "Uzvedība", + "download_sources": "Lejupielādes avoti", + "language": "Valoda", + "api_token": "API atslēga", + "enable_real_debrid": "Iespējot Real-Debrid", + "real_debrid_description": "Real-Debrid ir neierobežots lejupielādētājs, kas ļauj ātri lejupielādēt failus, kas izvietoti internetā, vai uzreiz pārsūtīt tos uz atskaņotāju, izmantojot privātu tīklu, kas ļauj apiet jebkādus bloķējumus.", + "debrid_invalid_token": "Nederīga API atslēga", + "debrid_api_token_hint": "API atslēgu var iegūt <0>šeit", + "real_debrid_free_account_error": "Kontam \"{{username}}\" nav abonementa. Lūdzu, iegādājieties Real-Debrid abonementu", + "debrid_linked_message": "Piesaistīts konts \"{{username}}\"", + "save_changes": "Saglabāt izmaiņas", + "changes_saved": "Izmaiņas veiksmīgi saglabātas", + "download_sources_description": "Hydra saņems lejupielādes saites no šiem avotiem. URL jāietver tieša saite uz .json failu ar lejupielādes saitēm.", + "validate_download_source": "Pārbaudīt", + "remove_download_source": "Dzēst", + "add_download_source": "Pievienot avotu", + "download_count_zero": "Sarakstā nav lejupielāžu", + "download_count_one": "{{countFormatted}} lejupielāde sarakstā", + "download_count_other": "{{countFormatted}} lejupielādes sarakstā", + "download_source_url": "Saite uz avotu", + "add_download_source_description": "Ievietojiet saiti uz .json failu", + "download_source_up_to_date": "Atjaunināts", + "download_source_errored": "Kļūda", + "sync_download_sources": "Atjaunināt avotus", + "removed_download_source": "Avots dzēsts", + "removed_download_sources": "Avoti dzēsti", + "cancel_button_confirmation_delete_all_sources": "Nē", + "confirm_button_confirmation_delete_all_sources": "Jā, dzēst visus", + "title_confirmation_delete_all_sources": "Dzēst visus avotus", + "description_confirmation_delete_all_sources": "Jūs dzēsīsiet visus avotus", + "button_delete_all_sources": "Dzēst visus avotus", + "added_download_source": "Avots pievienots", + "download_sources_synced": "Visi avoti atjaunināti", + "insert_valid_json_url": "Ievietojiet derīgu JSON faila URL", + "found_download_option_zero": "Nav atrasts lejupielādes variantu", + "found_download_option_one": "Atrasts {{countFormatted}} lejupielādes variants", + "found_download_option_other": "Atrasti {{countFormatted}} lejupielādes varianti", + "import": "Importēt", + "importing": "Importē...", + "public": "Publisks", + "private": "Privāts", + "friends_only": "Tikai draugiem", + "privacy": "Konfidencialitāte", + "profile_visibility": "Profila redzamība", + "profile_visibility_description": "Izvēlieties, kurš var redzēt jūsu profilu un bibliotēku", + "required_field": "Šis lauks ir obligāts", + "source_already_exists": "Šis avots jau ir pievienots", + "must_be_valid_url": "Avotam jābūt pareizam URL", + "blocked_users": "Bloķētie lietotāji", + "user_unblocked": "Lietotājs atbloķēts", + "enable_achievement_notifications": "Kad sasniegums ir atbloķēts", + "launch_minimized": "Palaist Hydra minimizētā veidā", + "disable_nsfw_alert": "Atspējot brīdinājumu par neķītru saturu", + "seed_after_download_complete": "Sēdēt pēc lejupielādes pabeigšanas", + "show_hidden_achievement_description": "Rādīt slēpto sasniegumu aprakstu pirms to iegūšanas", + "account": "Konts", + "no_users_blocked": "Jums nav bloķētu lietotāju", + "subscription_active_until": "Jūsu Hydra Cloud abonements ir aktīvs līdz {{date}}", + "manage_subscription": "Pārvaldīt abonementu", + "update_email": "Atjaunināt e-pastu", + "update_password": "Atjaunināt paroli", + "current_email": "Pašreizējais e-pasts:", + "no_email_account": "Jūs vēl neesat iestatījis e-pastu", + "account_data_updated_successfully": "Konta dati veiksmīgi atjaunināti", + "renew_subscription": "Atjaunot Hydra Cloud abonementu", + "subscription_expired_at": "Jūsu abonementa termiņš beidzās {{date}}", + "no_subscription": "Izbaudiet Hydra pilnībā", + "become_subscriber": "Kļūstiet par Hydra Cloud īpašnieku", + "subscription_renew_cancelled": "Automātiskā atjaunošana atspējota", + "subscription_renews_on": "Jūsu abonements tiek atjaunots {{date}}", + "bill_sent_until": "Jūsu nākamais rēķins tiks nosūtīts līdz šai dienai", + "no_themes": "Šķiet, ka jums vēl nav tēmu, bet neuztraucieties, noklikšķiniet šeit, lai izveidotu savu pirmo šedevru", + "editor_tab_code": "Kods", + "editor_tab_info": "Informācija", + "editor_tab_save": "Saglabāt", + "web_store": "Tīmekļa veikals", + "clear_themes": "Notīrīt", + "create_theme": "Izveidot", + "create_theme_modal_title": "Izveidot pielāgotu tēmu", + "create_theme_modal_description": "Izveidot jaunu tēmu, lai pielāgotu Hydra izskatu", + "theme_name": "Nosaukums", + "insert_theme_name": "Ievietot tēmas nosaukumu", + "set_theme": "Iestatīt tēmu", + "unset_theme": "Noņemt tēmu", + "delete_theme": "Dzēst tēmu", + "edit_theme": "Rediģēt tēmu", + "delete_all_themes": "Dzēst visas tēmas", + "delete_all_themes_description": "Tas dzēsīs visas jūsu pielāgotās tēmas", + "delete_theme_description": "Tas dzēsīs tēmu {{theme}}", + "cancel": "Atcelt", + "appearance": "Izskats", + "debrid": "Debrid", + "debrid_description": "Debrid servisi ir premium lejupielādētāji bez ierobežojumiem, kas ļauj ātri lejupielādēt failus no dažādiem failu apmaiņas servisiem, ierobežojoties tikai ar jūsu interneta ātrumu.", + "enable_torbox": "Iespējot TorBox", + "torbox_description": "TorBox ir jūsu premium serviss, kas konkurē pat ar labākajiem serveriem tirgū.", + "torbox_account_linked": "TorBox konts piesaistīts", + "create_real_debrid_account": "Noklikšķiniet šeit, ja jums vēl nav Real-Debrid konta", + "create_torbox_account": "Noklikšķiniet šeit, ja jums vēl nav TorBox konta", + "real_debrid_account_linked": "Real-Debrid konts piesaistīts", + "name_min_length": "Tēmas nosaukumam jābūt vismaz 3 simbolus garam", + "import_theme": "Importēt tēmu", + "import_theme_description": "Jūs importēsiet {{theme}} no tēmu veikala", + "error_importing_theme": "Kļūda importējot tēmu", + "theme_imported": "Tēma veiksmīgi importēta", + "enable_friend_request_notifications": "Saņemot draudzības pieprasījumu", + "enable_auto_install": "Automātiski lejupielādēt atjauninājumus", + "common_redist": "Bibliotēkas", + "common_redist_description": "Dažu spēļu palaišanai ir nepieciešamas bibliotēkas. Lai izvairītos no problēmām, ieteicams tās instalēt.", + "install_common_redist": "Instalēt", + "installing_common_redist": "Instalēšana…", + "show_download_speed_in_megabytes": "Rādīt lejupielādes ātrumu megabaitos sekundē", + "extract_files_by_default": "Izpakot failus pēc noklusējuma pēc lejupielādes", + "enable_steam_achievements": "Iespējot Steam sasniegumu meklēšanu", + "achievement_custom_notification_position": "Sasniegumu paziņojumu pozīcija", + "top-left": "Augšējais kreisais stūris", + "top-center": "Augšējais centrs", + "top-right": "Augšējais labais stūris", + "bottom-left": "Apakšējais kreisais stūris", + "bottom-center": "Apakšējais centrs", + "bottom-right": "Apakšējais labais stūris", + "enable_achievement_custom_notifications": "Iespējot sasniegumu paziņojumus", + "alignment": "Izlīdzināšana", + "variation": "Variācija", + "default": "Pēc noklusējuma", + "rare": "Retais", + "platinum": "Platīna", + "hidden": "Slēpts", + "test_notification": "Testa paziņojums", + "notification_preview": "Sasnieguma paziņojuma priekšskatījums", + "enable_friend_start_game_notifications": "Kad draugs sāk spēlēt spēli" + }, + "notifications": { + "download_complete": "Lejupielāde pabeigta", + "game_ready_to_install": "{{title}} ir gatava instalēšanai", + "repack_list_updated": "Repaku saraksts atjaunināts", + "repack_count_one": "{{count}} repaks pievienots", + "repack_count_other": "{{count}} repaki pievienoti", + "new_update_available": "Pieejama jauna versija {{version}}", + "restart_to_install_update": "Pārstartējiet Hydra, lai instalētu atjauninājumu", + "notification_achievement_unlocked_title": "Sasniegums atbloķēts spēlei {{game}}", + "notification_achievement_unlocked_body": "tika atbloķēti {{achievement}} un citi {{count}}", + "new_friend_request_description": "{{displayName}} nosūtīja jums draudzības pieprasījumu", + "new_friend_request_title": "Jauns draudzības pieprasījums", + "extraction_complete": "Izpakošana pabeigta", + "game_extracted": "{{title}} veiksmīgi izpakots", + "friend_started_playing_game": "{{displayName}} sāka spēlēt spēli", + "test_achievement_notification_title": "Šis ir testa paziņojums", + "test_achievement_notification_description": "Diezgan forši, vai ne?" + }, + "system_tray": { + "open": "Atvērt Hydra", + "quit": "Iziet" + }, + "game_card": { + "available_one": "Pieejams", + "available_other": "Pieejams", + "no_downloads": "Nav pieejamu avotu", + "calculating": "Aprēķina" + }, + "binary_not_found_modal": { + "title": "Programmas nav instalētas", + "description": "Wine vai Lutris nav atrasti", + "instructions": "Uzziniet pareizo veidu, kā instalēt kādu no tiem jūsu Linux distribūcijā, lai spēle varētu normāli darboties" + }, + "modal": { + "close": "Aizvērt" + }, + "forms": { + "toggle_password_visibility": "Rādīt paroli" + }, + "user_profile": { + "amount_hours": "{{amount}} stundas", + "amount_minutes": "{{amount}} minūtes", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "last_time_played": "Pēdējā spēle {{period}}", + "activity": "Nesenā aktivitāte", + "library": "Bibliotēka", + "pinned": "Piespraustās", + "achievements_earned": "Nopelnītie sasniegumi", + "played_recently": "Nesen spēlētās", + "playtime": "Spēles laiks", + "total_play_time": "Kopējais spēles laiks", + "manual_playtime_tooltip": "Spēles laiks tika atjaunināts manuāli", + "no_recent_activity_title": "Hmmmm... Šeit nav nekā", + "no_recent_activity_description": "Jūs sen neesat neko spēlējis. Ir laiks to mainīt!", + "display_name": "Parādāmais vārds", + "saving": "Saglabāšana", + "save": "Saglabāt", + "edit_profile": "Rediģēt profilu", + "saved_successfully": "Veiksmīgi saglabāts", + "try_again": "Lūdzu, mēģiniet vēlreiz", + "sign_out_modal_title": "Vai esat pārliecināts?", + "cancel": "Atcelt", + "successfully_signed_out": "Veiksmīga izrakstīšanās no konta", + "sign_out": "Iziet", + "playing_for": "Spēlēts {{amount}}", + "sign_out_modal_text": "Jūsu bibliotēka ir saistīta ar pašreizējo kontu. Izejot no sistēmas, jūsu bibliotēka kļūs nepieejama, un progress netiks saglabāts. Iziet?", + "add_friends": "Pievienot draugus", + "add": "Pievienot", + "friend_code": "Drauga kods", + "see_profile": "Skatīt profilu", + "sending": "Sūtīšana", + "friend_request_sent": "Draudzības pieprasījums nosūtīts", + "friends": "Draugi", + "friends_list": "Draugu saraksts", + "user_not_found": "Lietotājs nav atrasts", + "block_user": "Bloķēt lietotāju", + "add_friend": "Pievienot draugu", + "request_sent": "Pieprasījums nosūtīts", + "request_received": "Pieprasījums saņemts", + "accept_request": "Pieņemt pieprasījumu", + "ignore_request": "Ignorēt pieprasījumu", + "cancel_request": "Atcelt pieprasījumu", + "undo_friendship": "Dzēst draugu", + "request_accepted": "Pieprasījums pieņemts", + "user_blocked_successfully": "Lietotājs veiksmīgi bloķēts", + "user_block_modal_text": "{{displayName}} tiks bloķēts", + "blocked_users": "Bloķētie lietotāji", + "unblock": "Atbloķēt", + "no_friends_added": "Jūs vēl neesat pievienojis nevienu draugu", + "pending": "Gaida", + "no_pending_invites": "Jums nav pieprasījumu, kas gaida atbildi", + "no_blocked_users": "Jūs neesat bloķējis nevienu lietotāju", + "friend_code_copied": "Drauga kods kopēts", + "undo_friendship_modal_text": "Tas atcels jūsu draudzību ar {{displayName}}.", + "privacy_hint": "Lai norādītu, kurš to var redzēt, dodieties uz <0>Iestatījumiem.", + "locked_profile": "Šis profils ir privāts", + "image_process_failure": "Attēlu apstrādes kļūme", + "required_field": "Šis lauks ir obligāts", + "displayname_min_length": "Parādāmam vārdam jābūt vismaz 3 simbolus garam.", + "displayname_max_length": "Parādāmam vārdam jābūt ne vairāk kā 50 simboliem.", + "report_profile": "Ziņot par šo profilu", + "report_reason": "Kāpēc jūs ziņojat par šo profilu?", + "report_description": "Papildu informācija", + "report_description_placeholder": "Papildu informācija", + "report": "Ziņot", + "report_reason_hate": "Naida runa", + "report_reason_sexual_content": "Seksuāls saturs", + "report_reason_violence": "Vardarbība", + "report_reason_spam": "Surogātpasts", + "report_reason_other": "Cits", + "profile_reported": "Ziņojums par profilu nosūtīts", + "your_friend_code": "Jūsu drauga kods:", + "upload_banner": "Augšupielādēt reklāmkarogu", + "uploading_banner": "Augšupielādē reklāmkarogu...", + "background_image_updated": "Fona attēls atjaunināts", + "stats": "Statistika", + "achievements": "Sasniegumi", + "games": "Spēles", + "top_percentile": "Top {{percentile}}%", + "ranking_updated_weekly": "Reitings tiek atjaunināts katru nedēļu", + "playing": "Spēlē {{game}}", + "achievements_unlocked": "Sasniegumi atbloķēti", + "earned_points": "Nopelnītie punkti:", + "show_achievements_on_profile": "Rādīt savus sasniegumus profilā", + "show_points_on_profile": "Rādīt nopelnītos punktus savā profilā", + "error_adding_friend": "Neizdevās nosūtīt draudzības pieprasījumu. Lūdzu, pārbaudiet drauga kodu", + "friend_code_length_error": "Drauga kodam jāsatur 8 simboli", + "game_removed_from_pinned": "Spēle dzēsta no piespraustajiem", + "game_added_to_pinned": "Spēle pievienota piespraustajiem", + "karma": "Karma", + "karma_count": "karma", + "karma_description": "Nopelnīta ar pozitīviem atsauksmju vērtējumiem" + }, + "achievement": { + "achievement_unlocked": "Sasniegums atbloķēts", + "user_achievements": "{{displayName}} sasniegumi", + "your_achievements": "Jūsu sasniegumi", + "unlocked_at": "Atbloķēts: {{date}}", + "subscription_needed": "Šī satura apskatīšanai nepieciešams Hydra Cloud abonements", + "new_achievements_unlocked": "Atbloķēti {{achievementCount}} jauni sasniegumi no {{gameCount}} spēlēm", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} sasniegumi", + "achievements_unlocked_for_game": "Atbloķēti {{achievementCount}} jauni sasniegumi spēlei {{gameTitle}}", + "hidden_achievement_tooltip": "Šis ir slēpts sasniegums", + "achievement_earn_points": "Nopelniet {{points}} punktus ar šo sasniegumu", + "earned_points": "Nopelnītie punkti:", + "available_points": "Pieejamie punkti:", + "how_to_earn_achievements_points": "Kā nopelnīt sasniegumu punktus?" + }, + "hydra_cloud": { + "subscription_tour_title": "Hydra Cloud abonements", + "subscribe_now": "Abonējiet tūlīt", + "cloud_saving": "Saglabāšana mākonī", + "cloud_achievements": "Saglabājiet savus sasniegumus mākonī", + "animated_profile_picture": "Animētas profila bildes", + "premium_support": "Premium atbalsts", + "show_and_compare_achievements": "Rādiet un salīdziniet savus sasniegumus ar citu lietotāju sasniegumiem", + "animated_profile_banner": "Animēts profila reklāmkarogs", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Jūs tikko atklājāt Hydra Cloud funkciju!", + "learn_more": "Uzzināt vairāk", + "debrid_description": "Lejupielādējiet 4 reizes ātrāk ar Nimbus" + } +} \ No newline at end of file From 3df07fefe5f480fac232cc98a95c28cfc0fd1104 Mon Sep 17 00:00:00 2001 From: GrimmDevel Date: Thu, 23 Oct 2025 00:59:38 +0300 Subject: [PATCH 13/81] fixed lint error --- src/locales/index.ts | 2 +- src/locales/lv/translation.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/index.ts b/src/locales/index.ts index d58ee59a..ca9ec757 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -28,7 +28,7 @@ import bg from "./bg/translation.json"; import uz from "./uz/translation.json"; import fi from "./fi/translation.json"; import sv from "./sv/translation.json"; -import lv from "./lv/translation.json"; +import lv from "./lv/translation.json"; export default { "pt-BR": ptBR, diff --git a/src/locales/lv/translation.json b/src/locales/lv/translation.json index a33a1986..26aacb74 100644 --- a/src/locales/lv/translation.json +++ b/src/locales/lv/translation.json @@ -705,4 +705,4 @@ "learn_more": "Uzzināt vairāk", "debrid_description": "Lejupielādējiet 4 reizes ātrāk ar Nimbus" } -} \ No newline at end of file +} From 9a278dc6144b5c93bbd52b57529e99ee17961053 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:49:07 -0300 Subject: [PATCH 14/81] chore: action to update aur --- .github/workflows/update-aur.yml | 120 +++++++++++++++++++++++++++++++ scripts/update-pkgver.js | 32 +++++++++ 2 files changed, 152 insertions(+) create mode 100644 .github/workflows/update-aur.yml create mode 100755 scripts/update-pkgver.js diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml new file mode 100644 index 00000000..7ccfb746 --- /dev/null +++ b/.github/workflows/update-aur.yml @@ -0,0 +1,120 @@ +name: Update AUR Package + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + update-aur: + runs-on: ubuntu-latest + container: + image: archlinux:latest + + steps: + - name: Checkout main repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install dependencies + run: | + pacman -Syu --noconfirm + pacman -S --noconfirm nodejs npm git base-devel + + - name: Get version to update + id: get-version + run: | + if [ "${{ github.event_name }}" = "release" ]; then + # Remove 'v' prefix if present + VERSION="${{ github.event.release.tag_name }}" + VERSION="${VERSION#v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "source=release" >> $GITHUB_OUTPUT + else + # Get latest release version + VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "source=latest" >> $GITHUB_OUTPUT + fi + echo "Version to update: $VERSION" + + - name: Setup SSH for AUR + run: | + mkdir -p ~/.ssh + echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts + eval "$(ssh-agent -s)" + ssh-add ~/.ssh/id_rsa + + - name: Clone AUR repository + run: | + git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git + + - name: Check if update is needed + id: check-update + run: | + cd hydra-launcher-bin + CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) + NEW_VERSION="${{ steps.get-version.outputs.version }}" + + echo "Current AUR version: $CURRENT_VERSION" + echo "New version: $NEW_VERSION" + + if [ "$CURRENT_VERSION" = "$NEW_VERSION" ]; then + echo "update_needed=false" >> $GITHUB_OUTPUT + echo "No update needed - versions are the same" + else + echo "update_needed=true" >> $GITHUB_OUTPUT + echo "Update needed" + fi + + - name: Update PKGBUILD and .SRCINFO + if: steps.check-update.outputs.update_needed == 'true' + run: | + cd hydra-launcher-bin + node ../scripts/update-pkgver.js "${{ steps.get-version.outputs.version }}" ./PKGBUILD + updpkgsums + makepkg --printsrcinfo > .SRCINFO + + - name: Configure Git + if: steps.check-update.outputs.update_needed == 'true' + run: | + cd hydra-launcher-bin + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Commit and push changes + if: steps.check-update.outputs.update_needed == 'true' + run: | + cd hydra-launcher-bin + git add PKGBUILD .SRCINFO + + if git diff --staged --quiet; then + echo "No changes to commit" + else + COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }}" + if [ "${{ steps.get-version.outputs.source }}" = "release" ]; then + COMMIT_MSG="$COMMIT_MSG (automated release update)" + else + COMMIT_MSG="$COMMIT_MSG (latest release)" + fi + + git commit -m "$COMMIT_MSG" + git push origin master + echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}" + fi + + - name: Create summary + if: always() + run: | + echo "## AUR Update Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ steps.get-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Source**: ${{ steps.get-version.outputs.source }}" >> $GITHUB_STEP_SUMMARY + echo "- **Update needed**: ${{ steps.check-update.outputs.update_needed }}" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.check-update.outputs.update_needed }}" = "true" ]; then + echo "- **Status**: ✅ AUR package updated successfully" >> $GITHUB_STEP_SUMMARY + else + echo "- **Status**: ⏭️ No update needed" >> $GITHUB_STEP_SUMMARY + fi diff --git a/scripts/update-pkgver.js b/scripts/update-pkgver.js new file mode 100755 index 00000000..41d87f0b --- /dev/null +++ b/scripts/update-pkgver.js @@ -0,0 +1,32 @@ +const fs = require("node:fs"); + +function updatePkgver(newVersion, pkgbuildPath) { + try { + const content = fs.readFileSync(pkgbuildPath, "utf8"); + const lines = content.split("\n"); + + const updatedLines = lines.map((line) => { + if (line.trim().startsWith("pkgver=")) { + return `pkgver=${newVersion}`; + } + return line; + }); + + fs.writeFileSync(pkgbuildPath, updatedLines.join("\n"), "utf8"); + + console.log( + `✅ Successfully updated pkgver to ${newVersion} in ${pkgbuildPath}` + ); + } catch (error) { + console.error(`❌ Error updating pkgver: ${error.message}`); + process.exit(1); + } +} + +// Get version from command line arguments +const args = process.argv.slice(2); + +const newVersion = args[0]; +const pkgbuildPath = args[1] || "./PKGBUILD"; + +updatePkgver(newVersion, pkgbuildPath); From face25916725096a4eb6f9ca4bcdf724aba295d0 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:02:31 -0300 Subject: [PATCH 15/81] chore: dont apply aur changes on workflow_dispatch --- .github/workflows/update-aur.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 7ccfb746..fce745d9 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -85,8 +85,20 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Commit and push changes - if: steps.check-update.outputs.update_needed == 'true' + - name: Show changes (workflow_dispatch only) + if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'workflow_dispatch' + run: | + cd hydra-launcher-bin + echo "## Git Diff Preview" + echo "Changes that would be made:" + git diff PKGBUILD .SRCINFO || echo "No changes to show" + echo "" + echo "Staged changes:" + git add PKGBUILD .SRCINFO + git diff --staged || echo "No staged changes" + + - name: Commit and push changes (release only) + if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'release' run: | cd hydra-launcher-bin git add PKGBUILD .SRCINFO @@ -94,12 +106,7 @@ jobs: if git diff --staged --quiet; then echo "No changes to commit" else - COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }}" - if [ "${{ steps.get-version.outputs.source }}" = "release" ]; then - COMMIT_MSG="$COMMIT_MSG (automated release update)" - else - COMMIT_MSG="$COMMIT_MSG (latest release)" - fi + COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }} (automated release update)" git commit -m "$COMMIT_MSG" git push origin master From 1e8983d0c0595d5a0955676b2bb7f7cd3d774ca0 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:23:40 -0300 Subject: [PATCH 16/81] chore: add openssh in arch packages --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index fce745d9..f49cf821 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -20,7 +20,7 @@ jobs: - name: Install dependencies run: | pacman -Syu --noconfirm - pacman -S --noconfirm nodejs npm git base-devel + pacman -S --noconfirm nodejs npm git base-devel openssh - name: Get version to update id: get-version From 313f2cd5853be692f09d35d4885d1b82175f9b82 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:52:53 -0300 Subject: [PATCH 17/81] chore: try fixing action --- .github/workflows/update-aur.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index f49cf821..d7297859 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -26,20 +26,19 @@ jobs: id: get-version run: | if [ "${{ github.event_name }}" = "release" ]; then - # Remove 'v' prefix if present VERSION="${{ github.event.release.tag_name }}" - VERSION="${VERSION#v}" echo "version=$VERSION" >> $GITHUB_OUTPUT echo "source=release" >> $GITHUB_OUTPUT else - # Get latest release version - VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//') + echo "Getting latest release version" + VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name') echo "version=$VERSION" >> $GITHUB_OUTPUT echo "source=latest" >> $GITHUB_OUTPUT fi + echo "Version to update: $VERSION" - - name: Setup SSH for AUR + - name: Clone AUR repository run: | mkdir -p ~/.ssh echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa @@ -47,9 +46,6 @@ jobs: ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa - - - name: Clone AUR repository - run: | git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git - name: Check if update is needed From 805d67d2d176145713a5053c4d0e76c842760f4f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:56:39 -0300 Subject: [PATCH 18/81] fix: add missing package --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index d7297859..8a17f1bb 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -20,7 +20,7 @@ jobs: - name: Install dependencies run: | pacman -Syu --noconfirm - pacman -S --noconfirm nodejs npm git base-devel openssh + pacman -S --noconfirm nodejs npm git base-devel openssh jq - name: Get version to update id: get-version From 65ae5991e79f0e23722ef9f4a0fe6b1dd2d3ee3b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:14:41 -0300 Subject: [PATCH 19/81] chore: update-aur --- .github/workflows/update-aur.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 8a17f1bb..39883ed1 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -12,16 +12,27 @@ jobs: image: archlinux:latest steps: - - name: Checkout main repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install dependencies run: | pacman -Syu --noconfirm pacman -S --noconfirm nodejs npm git base-devel openssh jq + - name: Clone AUR repository + run: | + mkdir -p ~/.ssh + echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan aur.archlinux.org > ~/.ssh/known_hosts + cat ~/.ssh/known_hosts + eval "$(ssh-agent -s)" + ssh-add ~/.ssh/id_rsa + git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git + + - name: Checkout main repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Get version to update id: get-version run: | @@ -38,16 +49,6 @@ jobs: echo "Version to update: $VERSION" - - name: Clone AUR repository - run: | - mkdir -p ~/.ssh - echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts - eval "$(ssh-agent -s)" - ssh-add ~/.ssh/id_rsa - git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git - - name: Check if update is needed id: check-update run: | From a1552020c0595483ed2d0cc8666544afbe2e1944 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:22:54 -0300 Subject: [PATCH 20/81] chore: update aur --- .github/workflows/update-aur.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 39883ed1..7d24bb34 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -17,15 +17,34 @@ jobs: pacman -Syu --noconfirm pacman -S --noconfirm nodejs npm git base-devel openssh jq - - name: Clone AUR repository + - name: Setup SSH for AUR run: | mkdir -p ~/.ssh echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa - ssh-keyscan aur.archlinux.org > ~/.ssh/known_hosts - cat ~/.ssh/known_hosts + chmod 700 ~/.ssh + + # Add AUR host key to known_hosts + ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts + + # Configure SSH to use the key + cat > ~/.ssh/config << EOF + Host aur.archlinux.org + HostName aur.archlinux.org + User aur + IdentityFile ~/.ssh/id_rsa + StrictHostKeyChecking no + EOF + + # Start SSH agent and add key eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa + + # Test SSH connection + ssh aur@aur.archlinux.org "echo 'SSH connection successful'" + + - name: Clone AUR repository + run: | git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git - name: Checkout main repository From 4d3ba51b61d425582d8e85afaf382f9a937730a3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:47:38 -0300 Subject: [PATCH 21/81] update-aur.yml --- .github/workflows/update-aur.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 7d24bb34..0aa1153a 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -30,22 +30,17 @@ jobs: # Configure SSH to use the key cat > ~/.ssh/config << EOF Host aur.archlinux.org - HostName aur.archlinux.org - User aur IdentityFile ~/.ssh/id_rsa - StrictHostKeyChecking no + IdentitiesOnly yes + User aur + UserKnownHostsFile ~/.ssh/known_hosts EOF # Start SSH agent and add key eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa - # Test SSH connection - ssh aur@aur.archlinux.org "echo 'SSH connection successful'" - - - name: Clone AUR repository - run: | - git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git + ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' - name: Checkout main repository uses: actions/checkout@v4 From 95a5c3716c7bba133e5434ad24e4dc7e6788223a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:06:37 -0300 Subject: [PATCH 22/81] update-aur --- .github/workflows/update-aur.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 0aa1153a..6bfd7f2b 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -34,6 +34,7 @@ jobs: IdentitiesOnly yes User aur UserKnownHostsFile ~/.ssh/known_hosts + StrictHostKeyChecking no EOF # Start SSH agent and add key From 00e716375eabb71eb0a619fd8e8c99afe41066dd Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:13:13 -0300 Subject: [PATCH 23/81] clone with https for testing --- .github/workflows/update-aur.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 6bfd7f2b..dcb257a9 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -41,7 +41,8 @@ jobs: eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa - ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' + # ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' + git clone https://aur.archlinux.org/hydra-launcher-bin.git - name: Checkout main repository uses: actions/checkout@v4 From 19d8a09f9d07f3350d933cd743a5b201c3951a60 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:17:02 -0300 Subject: [PATCH 24/81] update aur --- .github/workflows/update-aur.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index dcb257a9..af5b6ba6 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -86,7 +86,7 @@ jobs: - name: Update PKGBUILD and .SRCINFO if: steps.check-update.outputs.update_needed == 'true' run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin node ../scripts/update-pkgver.js "${{ steps.get-version.outputs.version }}" ./PKGBUILD updpkgsums makepkg --printsrcinfo > .SRCINFO @@ -94,14 +94,14 @@ jobs: - name: Configure Git if: steps.check-update.outputs.update_needed == 'true' run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Show changes (workflow_dispatch only) if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'workflow_dispatch' run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin echo "## Git Diff Preview" echo "Changes that would be made:" git diff PKGBUILD .SRCINFO || echo "No changes to show" @@ -113,7 +113,7 @@ jobs: - name: Commit and push changes (release only) if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'release' run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin git add PKGBUILD .SRCINFO if git diff --staged --quiet; then From 7f28929c68e9c82d72bfd6c70f6f2ffbe654cdef Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:19:12 -0300 Subject: [PATCH 25/81] update yml --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index af5b6ba6..8aa9400f 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -68,7 +68,7 @@ jobs: - name: Check if update is needed id: check-update run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" From 6545c7d7cd7588343b3e8e7e3c23022d8af83d5a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:22:08 -0300 Subject: [PATCH 26/81] update aur --- .github/workflows/update-aur.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 8aa9400f..f1ef2543 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -43,6 +43,8 @@ jobs: # ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' git clone https://aur.archlinux.org/hydra-launcher-bin.git + pwd + ls - name: Checkout main repository uses: actions/checkout@v4 @@ -68,6 +70,8 @@ jobs: - name: Check if update is needed id: check-update run: | + pwd + ls cd ~/hydra-launcher-bin CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" From 2529bdf5ca7fb81ab0b99e7c3df4db3679cb53df Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:33:19 -0300 Subject: [PATCH 27/81] fix --- .github/workflows/update-aur.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index f1ef2543..34b114f8 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -43,14 +43,10 @@ jobs: # ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' git clone https://aur.archlinux.org/hydra-launcher-bin.git + cd hydra-launcher-bin pwd ls - - name: Checkout main repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Get version to update id: get-version run: | @@ -72,7 +68,6 @@ jobs: run: | pwd ls - cd ~/hydra-launcher-bin CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" @@ -90,7 +85,6 @@ jobs: - name: Update PKGBUILD and .SRCINFO if: steps.check-update.outputs.update_needed == 'true' run: | - cd ~/hydra-launcher-bin node ../scripts/update-pkgver.js "${{ steps.get-version.outputs.version }}" ./PKGBUILD updpkgsums makepkg --printsrcinfo > .SRCINFO @@ -98,14 +92,12 @@ jobs: - name: Configure Git if: steps.check-update.outputs.update_needed == 'true' run: | - cd ~/hydra-launcher-bin git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Show changes (workflow_dispatch only) if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'workflow_dispatch' run: | - cd ~/hydra-launcher-bin echo "## Git Diff Preview" echo "Changes that would be made:" git diff PKGBUILD .SRCINFO || echo "No changes to show" @@ -117,7 +109,6 @@ jobs: - name: Commit and push changes (release only) if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'release' run: | - cd ~/hydra-launcher-bin git add PKGBUILD .SRCINFO if git diff --staged --quiet; then From 52714e3323eb04bcd260b40afd9f37dad63479be Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:38:31 -0300 Subject: [PATCH 28/81] remove v from tag --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 34b114f8..575b6e13 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -56,7 +56,7 @@ jobs: echo "source=release" >> $GITHUB_OUTPUT else echo "Getting latest release version" - VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name') + VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//') echo "version=$VERSION" >> $GITHUB_OUTPUT echo "source=latest" >> $GITHUB_OUTPUT fi From e12fdf8f8f8a920aa24adb33f8753278f8292d90 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:45:09 -0300 Subject: [PATCH 29/81] remove js script --- .github/workflows/update-aur.yml | 15 ++++++++++++++- scripts/update-pkgver.js | 32 -------------------------------- 2 files changed, 14 insertions(+), 33 deletions(-) delete mode 100755 scripts/update-pkgver.js diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 575b6e13..4382d7da 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -85,7 +85,20 @@ jobs: - name: Update PKGBUILD and .SRCINFO if: steps.check-update.outputs.update_needed == 'true' run: | - node ../scripts/update-pkgver.js "${{ steps.get-version.outputs.version }}" ./PKGBUILD + # Update pkgver in PKGBUILD + NEW_VERSION="${{ steps.get-version.outputs.version }}" + + echo "Updating PKGBUILD pkgver to $NEW_VERSION" + + # Read PKGBUILD and update pkgver line + sed -i "s/^pkgver=.*/pkgver=$NEW_VERSION/" ./PKGBUILD + + # Reset pkgrel to 1 when version changes + sed -i "s/^pkgrel=.*/pkgrel=1/" "$PKGBUILD_PATH" + + echo "✅ Successfully updated pkgver to $NEW_VERSION in $PKGBUILD_PATH" + + # Update package checksums and generate .SRCINFO updpkgsums makepkg --printsrcinfo > .SRCINFO diff --git a/scripts/update-pkgver.js b/scripts/update-pkgver.js deleted file mode 100755 index 41d87f0b..00000000 --- a/scripts/update-pkgver.js +++ /dev/null @@ -1,32 +0,0 @@ -const fs = require("node:fs"); - -function updatePkgver(newVersion, pkgbuildPath) { - try { - const content = fs.readFileSync(pkgbuildPath, "utf8"); - const lines = content.split("\n"); - - const updatedLines = lines.map((line) => { - if (line.trim().startsWith("pkgver=")) { - return `pkgver=${newVersion}`; - } - return line; - }); - - fs.writeFileSync(pkgbuildPath, updatedLines.join("\n"), "utf8"); - - console.log( - `✅ Successfully updated pkgver to ${newVersion} in ${pkgbuildPath}` - ); - } catch (error) { - console.error(`❌ Error updating pkgver: ${error.message}`); - process.exit(1); - } -} - -// Get version from command line arguments -const args = process.argv.slice(2); - -const newVersion = args[0]; -const pkgbuildPath = args[1] || "./PKGBUILD"; - -updatePkgver(newVersion, pkgbuildPath); From b96e6095dca4b1e8bfc5363c4054afc0aa71324f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:03:51 -0300 Subject: [PATCH 30/81] update aur --- .github/workflows/update-aur.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 4382d7da..aa11aedb 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -41,8 +41,9 @@ jobs: eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa - # ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' - git clone https://aur.archlinux.org/hydra-launcher-bin.git + export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -F ~/.ssh/config -o UserKnownHostsFile=$SSH_PATH/known_hosts" + + git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git cd hydra-launcher-bin pwd ls From 321d1706348894f8207db142132612ce815bdde3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:13:24 -0300 Subject: [PATCH 31/81] force different version to test script --- .github/workflows/update-aur.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index aa11aedb..9ee9ef50 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -45,8 +45,6 @@ jobs: git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git cd hydra-launcher-bin - pwd - ls - name: Get version to update id: get-version @@ -67,10 +65,9 @@ jobs: - name: Check if update is needed id: check-update run: | - pwd - ls CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" + NEW_VERSION="3.7.0" echo "Current AUR version: $CURRENT_VERSION" echo "New version: $NEW_VERSION" @@ -88,6 +85,7 @@ jobs: run: | # Update pkgver in PKGBUILD NEW_VERSION="${{ steps.get-version.outputs.version }}" + NEW_VERSION=3.7.0 echo "Updating PKGBUILD pkgver to $NEW_VERSION" @@ -109,9 +107,11 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Show changes (workflow_dispatch only) - if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'workflow_dispatch' + - name: Commit and push changes + if: steps.check-update.outputs.update_needed == 'true' run: | + git add PKGBUILD .SRCINFO + echo "## Git Diff Preview" echo "Changes that would be made:" git diff PKGBUILD .SRCINFO || echo "No changes to show" @@ -120,18 +120,13 @@ jobs: git add PKGBUILD .SRCINFO git diff --staged || echo "No staged changes" - - name: Commit and push changes (release only) - if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'release' - run: | - git add PKGBUILD .SRCINFO - if git diff --staged --quiet; then echo "No changes to commit" else COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }} (automated release update)" git commit -m "$COMMIT_MSG" - git push origin master + # git push origin master echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}" fi From 9e84cd970e6541437915415c00e8801e381acd33 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:24:49 -0300 Subject: [PATCH 32/81] update aur --- .github/workflows/update-aur.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 9ee9ef50..9ec77b92 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -44,7 +44,6 @@ jobs: export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -F ~/.ssh/config -o UserKnownHostsFile=$SSH_PATH/known_hosts" git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git - cd hydra-launcher-bin - name: Get version to update id: get-version @@ -84,6 +83,7 @@ jobs: if: steps.check-update.outputs.update_needed == 'true' run: | # Update pkgver in PKGBUILD + cd hydra-launcher-bin NEW_VERSION="${{ steps.get-version.outputs.version }}" NEW_VERSION=3.7.0 @@ -104,12 +104,14 @@ jobs: - name: Configure Git if: steps.check-update.outputs.update_needed == 'true' run: | + cd hydra-launcher-bin git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Commit and push changes if: steps.check-update.outputs.update_needed == 'true' run: | + cd hydra-launcher-bin git add PKGBUILD .SRCINFO echo "## Git Diff Preview" From 0814c084594b1f8d9c0e442edef0ad79a42bd1d3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:30:01 -0300 Subject: [PATCH 33/81] update aur --- .github/workflows/update-aur.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 9ec77b92..d34b27ae 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -93,9 +93,9 @@ jobs: sed -i "s/^pkgver=.*/pkgver=$NEW_VERSION/" ./PKGBUILD # Reset pkgrel to 1 when version changes - sed -i "s/^pkgrel=.*/pkgrel=1/" "$PKGBUILD_PATH" + sed -i "s/^pkgrel=.*/pkgrel=1/" ./PKGBUILD - echo "✅ Successfully updated pkgver to $NEW_VERSION in $PKGBUILD_PATH" + echo "✅ Successfully updated pkgver to $NEW_VERSION in ./PKGBUILD" # Update package checksums and generate .SRCINFO updpkgsums From 2179086285ffcb7aa2ced4811fd64d5eff941be8 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:34:47 -0300 Subject: [PATCH 34/81] install missing arch package --- .github/workflows/build.yml | 3 --- .github/workflows/release.yml | 4 ---- .github/workflows/update-aur.yml | 2 +- electron-builder.yml | 1 - scripts/upload-build.cjs | 2 +- 5 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6bc508ec..5062c7ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,8 +41,6 @@ jobs: - name: Build Linux if: matrix.os == 'ubuntu-latest' run: | - sudo apt-get update - sudo apt-get install -y libarchive-tools yarn build:linux env: MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }} @@ -98,5 +96,4 @@ jobs: dist/*.tar.gz dist/*.yml dist/*.blockmap - dist/*.pacman dist/*.AppImage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 72e6e0f3..3ceb42c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,8 +42,6 @@ jobs: - name: Build Linux if: matrix.os == 'ubuntu-latest' run: | - sudo apt-get update - sudo apt-get install -y libarchive-tools yarn build:linux env: MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }} @@ -90,7 +88,6 @@ jobs: dist/*.tar.gz dist/*.yml dist/*.blockmap - dist/*.pacman - name: Upload build env: @@ -119,6 +116,5 @@ jobs: dist/*.tar.gz dist/*.yml dist/*.blockmap - dist/*.pacman env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index d34b27ae..97e84209 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -15,7 +15,7 @@ jobs: - name: Install dependencies run: | pacman -Syu --noconfirm - pacman -S --noconfirm nodejs npm git base-devel openssh jq + pacman -S --noconfirm nodejs npm git base-devel openssh jq pacman-contrib - name: Setup SSH for AUR run: | diff --git a/electron-builder.yml b/electron-builder.yml index 50fe8139..ec162530 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -56,7 +56,6 @@ linux: - AppImage - snap - deb - - pacman - rpm maintainer: electronjs.org category: Game diff --git a/scripts/upload-build.cjs b/scripts/upload-build.cjs index fe475163..15e3a5b4 100644 --- a/scripts/upload-build.cjs +++ b/scripts/upload-build.cjs @@ -20,7 +20,7 @@ const s3 = new S3Client({ const dist = path.resolve(__dirname, "..", "dist"); -const extensionsToUpload = [".deb", ".exe", ".pacman", ".AppImage"]; +const extensionsToUpload = [".deb", ".exe", ".AppImage"]; fs.readdir(dist, async (err, files) => { if (err) throw err; From 7fc9962e040c338f79409439b61e19754663367c Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:55:51 -0300 Subject: [PATCH 35/81] chore: create builder user to run makepkg --- .github/workflows/update-aur.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 97e84209..1e170ace 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -17,6 +17,17 @@ jobs: pacman -Syu --noconfirm pacman -S --noconfirm nodejs npm git base-devel openssh jq pacman-contrib + - name: Create builder user + run: | + # Create builder user with home directory + useradd -m -s /bin/bash builder + + # Add builder to wheel group for sudo access + usermod -aG wheel builder + + # Configure sudo for builder user (no password required) + echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + - name: Setup SSH for AUR run: | mkdir -p ~/.ssh @@ -45,6 +56,9 @@ jobs: git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git + # Give builder user ownership of the repository + chown -R builder:builder hydra-launcher-bin + - name: Get version to update id: get-version run: | @@ -97,9 +111,9 @@ jobs: echo "✅ Successfully updated pkgver to $NEW_VERSION in ./PKGBUILD" - # Update package checksums and generate .SRCINFO - updpkgsums - makepkg --printsrcinfo > .SRCINFO + # Update package checksums and generate .SRCINFO as builder user + sudo -u builder updpkgsums + sudo -u builder makepkg --printsrcinfo > .SRCINFO - name: Configure Git if: steps.check-update.outputs.update_needed == 'true' From 8a64b5e245a9f6ca93c8cffe495be3006e041e51 Mon Sep 17 00:00:00 2001 From: GrimmDevel Date: Thu, 23 Oct 2025 23:02:44 +0300 Subject: [PATCH 36/81] Fix --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..19c67fe5 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +MAIN_VITE_API_URL= +MAIN_VITE_AUTH_URL= +MAIN_VITE_WS_URL= +RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= +RENDERER_VITE_TORBOX_REFERRAL_CODE= \ No newline at end of file From 089d4179508fa05b0ec74e3a8508131851fe9eee Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:04:03 -0300 Subject: [PATCH 37/81] debug --- .github/workflows/update-aur.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 1e170ace..2fa762a9 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -4,6 +4,8 @@ on: workflow_dispatch: release: types: [published] + push: + branches: [main] jobs: update-aur: @@ -119,13 +121,17 @@ jobs: if: steps.check-update.outputs.update_needed == 'true' run: | cd hydra-launcher-bin - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + pwd + ls -la + git config --global user.name "github-actions[bot]" + git config --galobal user.email "github-actions[bot]@users.noreply.github.com" - name: Commit and push changes if: steps.check-update.outputs.update_needed == 'true' run: | cd hydra-launcher-bin + pwd + ls -la git add PKGBUILD .SRCINFO echo "## Git Diff Preview" From e23ee8940cc96d95dc552b56a5f443bce9e16e4a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:06:55 -0300 Subject: [PATCH 38/81] debug --- .github/workflows/update-aur.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 2fa762a9..9bd8d447 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -117,21 +117,16 @@ jobs: sudo -u builder updpkgsums sudo -u builder makepkg --printsrcinfo > .SRCINFO - - name: Configure Git - if: steps.check-update.outputs.update_needed == 'true' - run: | - cd hydra-launcher-bin - pwd - ls -la - git config --global user.name "github-actions[bot]" - git config --galobal user.email "github-actions[bot]@users.noreply.github.com" - - name: Commit and push changes if: steps.check-update.outputs.update_needed == 'true' run: | cd hydra-launcher-bin pwd ls -la + + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add PKGBUILD .SRCINFO echo "## Git Diff Preview" From 03770c03f1dcacaccce84bc34df687a88bd09d60 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:14:08 -0300 Subject: [PATCH 39/81] debug --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 9bd8d447..1941ac42 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -123,7 +123,7 @@ jobs: cd hydra-launcher-bin pwd ls -la - + git config --global --add safe.directory . git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" From ef8c6c90fbffd23b9234c9ed800bfa296dc67191 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:16:54 -0300 Subject: [PATCH 40/81] remove debug --- .github/workflows/update-aur.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 1941ac42..2a3583bc 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -4,8 +4,6 @@ on: workflow_dispatch: release: types: [published] - push: - branches: [main] jobs: update-aur: @@ -82,7 +80,6 @@ jobs: run: | CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" - NEW_VERSION="3.7.0" echo "Current AUR version: $CURRENT_VERSION" echo "New version: $NEW_VERSION" @@ -101,7 +98,6 @@ jobs: # Update pkgver in PKGBUILD cd hydra-launcher-bin NEW_VERSION="${{ steps.get-version.outputs.version }}" - NEW_VERSION=3.7.0 echo "Updating PKGBUILD pkgver to $NEW_VERSION" @@ -121,8 +117,6 @@ jobs: if: steps.check-update.outputs.update_needed == 'true' run: | cd hydra-launcher-bin - pwd - ls -la git config --global --add safe.directory . git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" @@ -140,10 +134,10 @@ jobs: if git diff --staged --quiet; then echo "No changes to commit" else - COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }} (automated release update)" + COMMIT_MSG="v${{ steps.get-version.outputs.version }}" git commit -m "$COMMIT_MSG" - # git push origin master + git push origin master echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}" fi From 0a8db2a9760c8dddeb73978d1ce733f61f86f3ab Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:18:46 -0300 Subject: [PATCH 41/81] Fix newline at end of .env.example file --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 19c67fe5..3f914eb3 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,4 @@ MAIN_VITE_API_URL= MAIN_VITE_AUTH_URL= MAIN_VITE_WS_URL= RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= \ No newline at end of file +RENDERER_VITE_TORBOX_REFERRAL_CODE= From 40f7e6e2ad210d56bddd15b2b59828358f3919fb Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:47:54 -0300 Subject: [PATCH 42/81] chore: bump electron version to 35 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 342b078a..59497aad 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@types/winreg": "^1.2.36", "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.2.1", - "electron": "^33.4.11", + "electron": "^35.7.5", "electron-builder": "^26.0.12", "electron-vite": "^3.0.0", "eslint": "^8.56.0", From a388acf9481ac1dcdff964b7c24749d084ba6247 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:51:15 -0300 Subject: [PATCH 43/81] chore: update node version on gh actions --- .github/workflows/build-renderer.yml | 4 ++-- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- yarn.lock | 19 +++++++++++++------ 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index 6aefac43..ed7a99ab 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -6,7 +6,7 @@ concurrency: on: push: - branches: main + branches: [main] jobs: build: @@ -19,7 +19,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 20.18.0 + node-version: 22.19.5 - name: Install dependencies run: yarn --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5062c7ad..32688379 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 20.18.3 + node-version: 22.19.5 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ac359364..6d08525c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 20.18.3 + node-version: 22.19.5 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ceb42c7..a06eeb21 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 20.18.3 + node-version: 22.19.5 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/yarn.lock b/yarn.lock index 0337a77b..5ffc3f03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3206,13 +3206,20 @@ dependencies: undici-types "~7.14.0" -"@types/node@^20.12.7", "@types/node@^20.9.0": +"@types/node@^20.12.7": version "20.19.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.21.tgz#6e5378e04993c40395473b13baf94a09875157b8" integrity sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA== dependencies: undici-types "~6.21.0" +"@types/node@^22.7.7": + version "22.18.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.18.12.tgz#e165d87bc25d7bf6d3657035c914db7485de84fb" + integrity sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog== + dependencies: + undici-types "~6.21.0" + "@types/parse-torrent-file@*": version "4.0.6" resolved "https://registry.yarnpkg.com/@types/parse-torrent-file/-/parse-torrent-file-4.0.6.tgz#11801dfd5b0a017302a164b72c8869f2bcba15b1" @@ -4651,13 +4658,13 @@ electron-vite@^3.0.0: magic-string "^0.30.17" picocolors "^1.1.1" -electron@^33.4.11: - version "33.4.11" - resolved "https://registry.yarnpkg.com/electron/-/electron-33.4.11.tgz#225d7f106ed3edf788ced318c63858d8b8a446dc" - integrity sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg== +electron@^35.7.5: + version "35.7.5" + resolved "https://registry.yarnpkg.com/electron/-/electron-35.7.5.tgz#294a4aebb2ad2a884de730c410f2358d061e8d53" + integrity sha512-dnL+JvLraKZl7iusXTVTGYs10TKfzUi30uEDTqsmTm0guN9V2tbOjTzyIZbh9n3ygUjgEYyo+igAwMRXIi3IPw== dependencies: "@electron/get" "^2.0.0" - "@types/node" "^20.9.0" + "@types/node" "^22.7.7" extract-zip "^2.0.1" embla-carousel-autoplay@^8.6.0: From 29e822f2f110a3ae83d0f18862a37d5614112fbe Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:56:45 -0300 Subject: [PATCH 44/81] fix: node version on gh actions files --- .github/workflows/build-renderer.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index ed7a99ab..f7361883 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -19,7 +19,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 22.19.5 + node-version: 22.21.0 - name: Install dependencies run: yarn --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 32688379..86fce350 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 22.19.5 + node-version: 22.21.0 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6d08525c..89e8b59f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 22.19.5 + node-version: 22.21.0 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a06eeb21..11df9b9f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 22.19.5 + node-version: 22.21.0 - name: Install dependencies run: yarn --frozen-lockfile From dec0af8a80dbc19cd5cd0eaf709e7da12347f55c Mon Sep 17 00:00:00 2001 From: Kyatto <140931995+Lianela@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:49:18 -0700 Subject: [PATCH 45/81] feat: translated new strings --- src/locales/es/translation.json | 92 +++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index de9ccfdb..c14a3df8 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -70,6 +70,24 @@ "edit_game_modal_icon_resolution": "Resolución recomendada: 256x256px", "edit_game_modal_logo_resolution": "Resolución recomendada: 640x360px", "edit_game_modal_hero_resolution": "Resolución recomendada: 1920x620px", + "cancel": "Cancelar", + "confirm": "Confirmar", + "decky_plugin_installation_error": "Error instalando plugin Decky: {{error}}", + "decky_plugin_installation_failed": "Falló instalar plugin Decky: {{error}}", + "decky_plugin_installed": "Plugin Decky v{{version}} instalanda exitosamente", + "decky_plugin_installed_version": "Plugin Decky (v{{version}})", + "edit_game_modal_drop_hero_image_here": "Soltá la imagen hero acá", + "edit_game_modal_drop_icon_image_here": "Soltá la imagen de ícono hero acá", + "edit_game_modal_drop_logo_image_here": "Soltá la imagen de logo hero acá", + "edit_game_modal_drop_to_replace_hero": "Soltá para reemplazar hero", + "edit_game_modal_drop_to_replace_icon": "Soltá para reemplazar el ícono", + "edit_game_modal_drop_to_replace_logo": "Soltá para reemplazar el logo", + "install_decky_plugin": "Instalar plugin Decky", + "install_decky_plugin_message": "Esto va a descargar e instalar el plugin de Decky Loader para Hydra. Esto quizás requierea permisos elevados, ¿querés continuar?", + "install_decky_plugin_title": "Instarlar el plugin Decky Hydra", + "update_decky_plugin": "Actualizar plugin Decky", + "update_decky_plugin_message": "Una nueva versión del plugin Decky para Hydra está disponible. ¿Querés actualizarlo ahora?", + "update_decky_plugin_title": "Actualizar plugin Decky para Hydra" "edit_game_modal_assets": "Recursos" }, "header": { @@ -285,6 +303,63 @@ "keyshop_price": "Precio de tiendas de terceros", "historical_retail": "Precio de tiendas", "historical_keyshop": "Precio de tiendas de terceros", + "add_to_favorites": "Añadir a favoritos", + "be_first_to_review": "¡Sé la primera persona en compartir lo que pensas de este juego!", + "create_shortcut_simple": "Crear atajo", + "delete_review": "Eliminar reseña", + "delete_review_modal_cancel_button": "Cancelar", + "delete_review_modal_delete_button": "Eliminar", + "delete_review_modal_description": "Esta acción no se puede deshacer.", + "delete_review_modal_title": "¿De verdad querés eliminar esta reseña?", + "failed_remove_files": "Error al eliminar los archivos", + "failed_remove_from_library": "Error al eliminar de la librería", + "failed_update_favorites": "Error al actualizar favoritos", + "files_removed_success": "Archivos eliminados correctamente", + "filter_by_source": "Filtrar por fuente", + "game_removed_from_library": "Juego eliminado de la librería", + "hide_original": "Ocultar original", + "leave_a_review": "Crear una reseña", + "load_more_reviews": "Cargar más reseñas", + "loading_more_reviews": "Cargando más reseñas...", + "loading_reviews": "Cargando reseñas...", + "maybe_later": "Tal vez después", + "no_repacks_found": "Sin fuentes encontradas para este juego", + "no_reviews_yet": "Sin reseñas aún", + "properties": "Propiedades", + "rating": "Calificación", + "rating_count": "Calificación", + "rating_negative": "Negativa", + "rating_neutral": "Neutral", + "rating_positive": "Positiva", + "rating_stats": "Calificación", + "rating_very_negative": "Muy Negativa", + "rating_very_positive": "Muy Positiva", + "remove_from_favorites": "Eliminar de favoritos", + "remove_review": "Eliminar reseña", + "review_cannot_be_empty": "El campo de la reseña no puede estar vacío.", + "review_deleted_successfully": "Reseña eliminada exitosamente.", + "review_deletion_failed": "Error al eliminar reseña. Por favor intentá de nuevo.", + "review_submission_failed": "Error al subir reseña. Por favor intentá de nuevo.", + "review_submitted_successfully": "¡Reseña eliminada exitosamente!", + "reviews": "Reseñas", + "show_less": "Ver menos", + "show_more": "Ver más", + "show_original": "Ver original", + "show_original_translated_from": "Ver original (traducido del {{language}})", + "show_translation": "Ver traducción", + "sort_highest_score": "Puntuación más alta", + "sort_lowest_score": "Puntuación más baja", + "sort_most_voted": "Más votads", + "sort_newest": "Más nuevos", + "sort_oldest": "Más viejos", + "submit_review": "Enviar", + "submitting": "Subiendo...", + "vote_failed": "Error al registrar tu voto. Por favor intentá de nuevo.", + "would_you_recommend_this_game": "¿Querés escribir una reseña para este juego?", + "write_review_placeholder": "Compartí tus pensamientos sobre este juego...", + "yes": "Si", + "you_seemed_to_enjoy_this_game": "Parece que has disfrutado de este juego", + "language": "Idioma", "caption": "Subtítulo", "audio": "Audio" @@ -345,7 +420,7 @@ "enable_real_debrid": "Habilitar Real-Debrid", "real_debrid_description": "Real-Debrid es un descargador que te permite descargar archivos más rápidos, solo límitado por la velocidad de tu internet.", "debrid_invalid_token": "Token API inválido", - "debrid_api_token_hint": "Podés obtener la el token de tu API <0>acá", + "debrid_api_token_hint": "Podés obtener el token de tu API <0>acá", "real_debrid_free_account_error": "La cuenta \"{{username}}\" es una cuenta gratis. Por favor suscribíte a Real-Debrid", "debrid_linked_message": "Cuenta \"{{username}}\" vinculada", "save_changes": "Guardar cambios", @@ -357,7 +432,7 @@ "download_count_zero": "Sin opciones de descarga", "download_count_one": "{{countFormatted}} opción de descarga", "download_count_other": "{{countFormatted}} opciones de descarga", - "download_source_url": "Descargar fuente URL", + "download_source_url": "Añadir URL de una fuente", "add_download_source_description": "Introducí la URL del archivo .json", "download_source_up_to_date": "Actualizado", "download_source_errored": "Error", @@ -409,7 +484,7 @@ "subscription_renew_cancelled": "Renovación automática desactivada", "subscription_renews_on": "Tu suscripción se renueva el {{date}}", "bill_sent_until": "Tu próxima factura se enviará este día", - "no_themes": "Parece que no tenés ningún tema aún, pero no te preocupés, presiona acá para hacer tu primera obra maestra.", + "no_themes": "Parece que no tenés ningún tema aún, pero no te preocupes, presiona acá para hacer tu primera obra maestra.", "editor_tab_code": "Código", "editor_tab_info": "Info", "editor_tab_save": "Guardar", @@ -443,7 +518,7 @@ "enable_friend_request_notifications": "Cuando recibís una solicitud de amistad", "enable_auto_install": "Descargar actualizaciones automáticamente", "common_redist": "Common redistributables", - "common_redist_description": "Common redistributables son requeridos para algunos juegos. Es recomendable instalarlos para evitar algunos problemas.", + "common_redist_description": "Los common redistributables son requeridos para algunos juegos. Es recomendable instalarlos para evitar algunos problemas.", "install_common_redist": "Instalar", "installing_common_redist": "Instalando…", "show_download_speed_in_megabytes": "Mostrar velocidad de descarga en megabytes por segundo", @@ -465,6 +540,8 @@ "hidden": "Oculto", "test_notification": "Probar notificación", "notification_preview": "Probar notificación de logro", + "debrid": "Debrid", + "debrid_description": "Los servicios Debrid son descargadores premium sin restricciones que te dejan descargar más rápido archivos alojados en servicios de alojamiento siendo que la única limitación es tu velocidad de internet.", "enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego" }, "notifications": { @@ -492,6 +569,7 @@ "game_card": { "available_one": "Disponible", "available_other": "Disponibles", + "calculating": "Calculando", "no_downloads": "Sin descargas disponibles" }, "binary_not_found_modal": { @@ -593,6 +671,12 @@ "error_adding_friend": "No se pudo enviar la solicitud de amistad. Por favor revisá el código", "friend_code_length_error": "El código de amistad debe tener mínimo 8 caracteres", "game_removed_from_pinned": "Juego removido de fijados", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "karma": "Karma", + "karma_count": "karma", + "karma_description": "Conseguido por me gustas positivos en reseñas", + "sort_by": "Filtrar por:", "game_added_to_pinned": "Juego añadido a fijados" }, "achievement": { From 362774a3ccb46772c900a454b56e67b2d4551865 Mon Sep 17 00:00:00 2001 From: Kyatto <140931995+Lianela@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:53:16 -0700 Subject: [PATCH 46/81] fix: comma --- src/locales/es/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index c14a3df8..a69ac127 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -87,7 +87,7 @@ "install_decky_plugin_title": "Instarlar el plugin Decky Hydra", "update_decky_plugin": "Actualizar plugin Decky", "update_decky_plugin_message": "Una nueva versión del plugin Decky para Hydra está disponible. ¿Querés actualizarlo ahora?", - "update_decky_plugin_title": "Actualizar plugin Decky para Hydra" + "update_decky_plugin_title": "Actualizar plugin Decky para Hydra", "edit_game_modal_assets": "Recursos" }, "header": { From 2c1a8bf639a36b74080f59c150f5fdb69bc46172 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 24 Oct 2025 06:42:39 -0300 Subject: [PATCH 47/81] Remove extra newline in Spanish translation file --- src/locales/es/translation.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index a69ac127..dfa7f7a1 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -359,7 +359,6 @@ "write_review_placeholder": "Compartí tus pensamientos sobre este juego...", "yes": "Si", "you_seemed_to_enjoy_this_game": "Parece que has disfrutado de este juego", - "language": "Idioma", "caption": "Subtítulo", "audio": "Audio" From 881564daa73b0f17b8b36a339e21ba5d859c47b8 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 24 Oct 2025 17:13:36 +0100 Subject: [PATCH 48/81] fix: fixing game hero --- src/main/services/hydra-api.ts | 2 +- src/main/services/python-rpc.ts | 2 +- .../description-header.scss | 1 - .../gallery-slider/gallery-slider.scss | 2 +- .../game-details/game-details-content.tsx | 16 +- .../src/pages/game-details/game-details.scss | 112 ++----------- .../src/pages/game-details/game-reviews.tsx | 150 +++++++++--------- .../pages/game-details/hero/hero-panel.scss | 1 + 8 files changed, 92 insertions(+), 194 deletions(-) diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index dd26e6f0..07f81d68 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -29,7 +29,7 @@ export class HydraApi { private static instance: AxiosInstance; private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes - private static readonly ADD_LOG_INTERCEPTOR = true; + private static readonly ADD_LOG_INTERCEPTOR = false; private static secondsToMilliseconds(seconds: number) { return seconds * 1000; diff --git a/src/main/services/python-rpc.ts b/src/main/services/python-rpc.ts index f3ce9f6c..2a1dce79 100644 --- a/src/main/services/python-rpc.ts +++ b/src/main/services/python-rpc.ts @@ -106,7 +106,7 @@ export class PythonRPC { "main.py" ); - const childProcess = cp.spawn("python3", [scriptPath, ...commonArgs], { + const childProcess = cp.spawn("python", [scriptPath, ...commonArgs], { stdio: ["inherit", "inherit"], }); diff --git a/src/renderer/src/pages/game-details/description-header/description-header.scss b/src/renderer/src/pages/game-details/description-header/description-header.scss index a29caa34..74126fd5 100644 --- a/src/renderer/src/pages/game-details/description-header/description-header.scss +++ b/src/renderer/src/pages/game-details/description-header/description-header.scss @@ -11,7 +11,6 @@ border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - margin-bottom: calc(globals.$spacing-unit * 1.5); &__info { display: flex; diff --git a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss index 6f9e753c..f9da431d 100644 --- a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss +++ b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss @@ -2,7 +2,7 @@ .gallery-slider { &__container { - padding: calc(globals.$spacing-unit * 1.5) calc(globals.$spacing-unit * 1); + padding: calc(globals.$spacing-unit * 1.5) 0; width: 100%; display: flex; flex-direction: column; diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index edf314c7..8f2d840c 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -53,8 +53,6 @@ const getImageWithCustomPriority = ( }; export function GameDetailsContent() { - const heroRef = useRef(null); - const { t } = useTranslation("game_details"); const { @@ -152,18 +150,12 @@ export function GameDetailsContent() { className={`game-details__wrapper ${hasNSFWContentBlocked ? "game-details__wrapper--blurred" : ""}`} >
-
+
{game?.title} -
+ +
+ +
- -
diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index 56022b07..313896fc 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -1,6 +1,6 @@ @use "../../scss/globals.scss"; -$hero-height: 300px; +$hero-height: 350px; @keyframes slide-in { 0% { @@ -27,6 +27,10 @@ $hero-height: 300px; } } + &__hero-panel { + padding: globals.$spacing-unit; + } + &__review-form { display: flex; flex-direction: column; @@ -34,19 +38,6 @@ $hero-height: 300px; margin-bottom: 24px; } - &__review-form-controls { - display: flex; - gap: calc(globals.$spacing-unit * 2); - align-items: flex-end; - flex-wrap: wrap; - - @media (max-width: 768px) { - flex-direction: column; - align-items: stretch; - gap: calc(globals.$spacing-unit * 1.5); - } - } - &__review-form-bottom { display: flex; justify-content: space-between; @@ -55,39 +46,12 @@ $hero-height: 300px; flex-wrap: wrap; } - &__review-message { - padding: calc(globals.$spacing-unit * 1); - border-radius: 4px; - font-size: globals.$small-font-size; - font-weight: 500; - margin-top: calc(globals.$spacing-unit * 1); - border: 1px solid; - - &--success { - background: rgba(34, 197, 94, 0.1); - color: #86efac; - border-color: rgba(34, 197, 94, 0.3); - } - - &--error { - background: rgba(239, 68, 68, 0.1); - color: #fca5a5; - border-color: rgba(239, 68, 68, 0.3); - } - } - &__review-score-container { display: flex; align-items: center; gap: 4px; } - &__review-score-label { - font-size: 14px; - color: #ffffff; - font-weight: 500; - } - &__review-score-select { background-color: #2a2a2a; border: 1px solid #3a3a3a; @@ -220,10 +184,6 @@ $hero-height: 300px; } } - &__reviews-list { - margin-top: calc(globals.$spacing-unit * 3); - } - &__reviews-container { display: flex; flex-direction: column; @@ -578,8 +538,8 @@ $hero-height: 300px; &__hero-image { width: 100%; - height: calc($hero-height + 72px); - min-height: calc($hero-height + 72px); + height: $hero-height; + min-height: $hero-height; object-fit: cover; object-position: top; transition: all ease 0.2s; @@ -588,8 +548,8 @@ $hero-height: 300px; @media (min-width: 1250px) { object-position: center; - height: calc(350px + 72px); - min-height: calc(350px + 72px); + height: $hero-height; + min-height: $hero-height; } } @@ -630,14 +590,6 @@ $hero-height: 300px; } } - &__hero-image-skeleton { - height: 300px; - - @media (min-width: 1250px) { - height: 350px; - } - } - &__container { width: 100%; height: 100%; @@ -782,34 +734,6 @@ $hero-height: 300px; } } - &__randomizer-button { - animation: slide-in 0.2s; - position: fixed; - bottom: calc(globals.$spacing-unit * 3); - right: calc(9px + globals.$spacing-unit * 2); - box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 10px 1px; - border: solid 2px globals.$border-color; - z-index: 1; - background-color: globals.$background-color; - - &:hover { - background-color: globals.$background-color; - box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 15px 5px; - opacity: 1; - } - - &:active { - transform: scale(0.98); - } - - &:disabled { - box-shadow: none; - transform: none; - opacity: 0.8; - background-color: globals.$background-color; - } - } - &__hero-panel-skeleton { width: 100%; padding: calc(globals.$spacing-unit * 2); @@ -852,19 +776,6 @@ $hero-height: 300px; } } - &__stars-icon-container { - width: 16px; - height: 16px; - position: relative; - } - - &__stars-icon { - width: 70px; - position: absolute; - top: -28px; - left: -27px; - } - &__cloud-icon-container { width: 20px; height: 16px; @@ -880,11 +791,6 @@ $hero-height: 300px; top: -3px; } - &__hero-backdrop { - flex: 1; - transition: opacity 0.2s ease; - } - &__reviews-section { margin-top: calc(globals.$spacing-unit * 3); padding-top: calc(globals.$spacing-unit * 3); diff --git a/src/renderer/src/pages/game-details/game-reviews.tsx b/src/renderer/src/pages/game-details/game-reviews.tsx index f8117f43..02901982 100644 --- a/src/renderer/src/pages/game-details/game-reviews.tsx +++ b/src/renderer/src/pages/game-details/game-reviews.tsx @@ -469,84 +469,82 @@ export function GameReviews({ )} -
-
-
-

{t("reviews")}

- - {totalReviewCount} - -
+
+
+

{t("reviews")}

+ + {totalReviewCount} +
- - - {reviewsLoading && reviews.length === 0 && ( -
- {t("loading_reviews")} -
- )} - - {!reviewsLoading && reviews.length === 0 && ( -
-
- -
-

- {t("no_reviews_yet")} -

-

- {t("be_first_to_review")} -

-
- )} - -
0 ? 0.5 : 1, - transition: "opacity 0.2s ease", - }} - > - {reviews.map((review) => ( - - ))} -
- - {hasMoreReviews && !reviewsLoading && ( - - )} - - {reviewsLoading && reviews.length > 0 && ( -
- {t("loading_more_reviews")} -
- )}
+ + + {reviewsLoading && reviews.length === 0 && ( +
+ {t("loading_reviews")} +
+ )} + + {!reviewsLoading && reviews.length === 0 && ( +
+
+ +
+

+ {t("no_reviews_yet")} +

+

+ {t("be_first_to_review")} +

+
+ )} + +
0 ? 0.5 : 1, + transition: "opacity 0.2s ease", + }} + > + {reviews.map((review) => ( + + ))} +
+ + {hasMoreReviews && !reviewsLoading && ( + + )} + + {reviewsLoading && reviews.length > 0 && ( +
+ {t("loading_more_reviews")} +
+ )}
); } diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.scss b/src/renderer/src/pages/game-details/hero/hero-panel.scss index 4dd1cc22..4ff74fbf 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel.scss @@ -18,6 +18,7 @@ top: 0; z-index: 2; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; &--stuck { background: rgba(0, 0, 0, 0.7); From 0c7767de362c5f55df139b08a9650e9d4984b565 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 24 Oct 2025 18:22:46 +0100 Subject: [PATCH 49/81] fix: remove unused useRef import --- src/renderer/src/pages/game-details/game-details-content.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 1ec17d8a..ab51a212 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import { useContext, useEffect, useMemo, useState } from "react"; import { PencilIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; From 11c19f5fe5bd49e7fb5560f65e383e98fb3000fa Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:20:51 -0300 Subject: [PATCH 50/81] chore: downgrade to latest of 34 --- package.json | 2 +- src/renderer/src/pages/game-details/game-reviews.tsx | 4 ---- yarn.lock | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 59497aad..f74825a1 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@types/winreg": "^1.2.36", "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.2.1", - "electron": "^35.7.5", + "electron": "^34.5.8", "electron-builder": "^26.0.12", "electron-vite": "^3.0.0", "eslint": "^8.56.0", diff --git a/src/renderer/src/pages/game-details/game-reviews.tsx b/src/renderer/src/pages/game-details/game-reviews.tsx index f8117f43..1ce44550 100644 --- a/src/renderer/src/pages/game-details/game-reviews.tsx +++ b/src/renderer/src/pages/game-details/game-reviews.tsx @@ -144,8 +144,6 @@ export function GameReviews({ } }, [objectId, userDetailsId, shop, game, onUserReviewedChange]); - console.log("reviews", reviews); - const loadReviews = useCallback( async (reset = false) => { if (!objectId) return; @@ -440,8 +438,6 @@ export function GameReviews({ }); }, [reviews]); - console.log("reviews", reviews); - return (
{showReviewPrompt && diff --git a/yarn.lock b/yarn.lock index 5ffc3f03..d936ff61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4658,7 +4658,7 @@ electron-vite@^3.0.0: magic-string "^0.30.17" picocolors "^1.1.1" -electron@^35.7.5: +electron@^35.2.1: version "35.7.5" resolved "https://registry.yarnpkg.com/electron/-/electron-35.7.5.tgz#294a4aebb2ad2a884de730c410f2358d061e8d53" integrity sha512-dnL+JvLraKZl7iusXTVTGYs10TKfzUi30uEDTqsmTm0guN9V2tbOjTzyIZbh9n3ygUjgEYyo+igAwMRXIi3IPw== From 4471bf0f8bc9ee7e27a48dbfc2c9a3be3299f6a7 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:05:40 -0300 Subject: [PATCH 51/81] chore: bump to electron 37 --- package.json | 4 ++-- yarn.lock | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f74825a1..b34425a0 100644 --- a/package.json +++ b/package.json @@ -116,9 +116,9 @@ "@types/winreg": "^1.2.36", "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.2.1", - "electron": "^34.5.8", + "electron": "^37.7.1", "electron-builder": "^26.0.12", - "electron-vite": "^3.0.0", + "electron-vite": "^3.1.0", "eslint": "^8.56.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.4", diff --git a/yarn.lock b/yarn.lock index d936ff61..c362ada8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4646,7 +4646,7 @@ electron-updater@^6.6.2: semver "^7.6.3" tiny-typed-emitter "^2.1.0" -electron-vite@^3.0.0: +electron-vite@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/electron-vite/-/electron-vite-3.1.0.tgz#1784907a83d23c6c8093ec68b8e414a74d814385" integrity sha512-M7aAzaRvSl5VO+6KN4neJCYLHLpF/iWo5ztchI/+wMxIieDZQqpbCYfaEHHHPH6eupEzfvZdLYdPdmvGqoVe0Q== @@ -4658,10 +4658,10 @@ electron-vite@^3.0.0: magic-string "^0.30.17" picocolors "^1.1.1" -electron@^35.2.1: - version "35.7.5" - resolved "https://registry.yarnpkg.com/electron/-/electron-35.7.5.tgz#294a4aebb2ad2a884de730c410f2358d061e8d53" - integrity sha512-dnL+JvLraKZl7iusXTVTGYs10TKfzUi30uEDTqsmTm0guN9V2tbOjTzyIZbh9n3ygUjgEYyo+igAwMRXIi3IPw== +electron@^37.7.1: + version "37.7.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-37.7.1.tgz#7d771b3d3365b5458f8bc758385defee14387034" + integrity sha512-2EmIqWv4T8BtgFQosB3/0Fezs09X3l0wXhIzes/cNt/GI+UDljbQr3NiF2J9WnqP0aFSbUEfztGUQMiX+qDvsw== dependencies: "@electron/get" "^2.0.0" "@types/node" "^22.7.7" From ee35bc24b27c05458f888cbf2e477bdb80f7c1df Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:11:56 -0300 Subject: [PATCH 52/81] chore: undo remove hydra api logs --- src/main/services/hydra-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 07f81d68..dd26e6f0 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -29,7 +29,7 @@ export class HydraApi { private static instance: AxiosInstance; private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes - private static readonly ADD_LOG_INTERCEPTOR = false; + private static readonly ADD_LOG_INTERCEPTOR = true; private static secondsToMilliseconds(seconds: number) { return seconds * 1000; From 7f2343413efc80cb9ba894b77162b96ee1aa34dc Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 26 Oct 2025 17:26:25 +0200 Subject: [PATCH 53/81] feat: added manual page selection and changed functionality of pagination --- .../src/pages/catalogue/pagination.scss | 29 +++++ .../src/pages/catalogue/pagination.tsx | 112 ++++++++++++++---- 2 files changed, 121 insertions(+), 20 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.scss b/src/renderer/src/pages/catalogue/pagination.scss index 141dfe54..cac10211 100644 --- a/src/renderer/src/pages/catalogue/pagination.scss +++ b/src/renderer/src/pages/catalogue/pagination.scss @@ -1,3 +1,5 @@ +@use "../../scss/globals.scss"; + .pagination { display: flex; gap: 4px; @@ -18,4 +20,31 @@ font-size: 16px; } } + + &__page-input { + box-sizing: border-box; + width: 40px; + min-width: 40px; + max-width: 40px; + min-height: 40px; + border-radius: 8px; + border: solid 1px globals.$border-color; + background-color: transparent; + color: globals.$muted-color; + text-align: center; + font-size: 12px; + padding: 0 6px; + outline: none; + } + + &__double-chevron { + display: flex; + align-items: center; + justify-content: center; + font-size: 0; // remove whitespace node width between SVGs + } + + &__double-chevron > svg + svg { + margin-left: -8px; // pull the second chevron closer + } } diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index dfae6164..4040c4b5 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -1,6 +1,7 @@ import { Button } from "@renderer/components/button/button"; import { ChevronLeftIcon, ChevronRightIcon } from "@primer/octicons-react"; import { useFormat } from "@renderer/hooks/use-format"; +import { useEffect, useRef, useState } from "react"; import "./pagination.scss"; interface PaginationProps { @@ -16,6 +17,17 @@ export function Pagination({ }: PaginationProps) { const { formatNumber } = useFormat(); + const [isJumpOpen, setIsJumpOpen] = useState(false); + const [jumpValue, setJumpValue] = useState(""); + const jumpInputRef = useRef(null); + + useEffect(() => { + if (isJumpOpen) { + setJumpValue(""); + setTimeout(() => jumpInputRef.current?.focus(), 0); + } + }, [isJumpOpen, page]); + if (totalPages <= 1) return null; const visiblePages = 3; @@ -30,6 +42,19 @@ export function Pagination({ return (
+ {startPage > 1 && ( + + )} + - {page > 2 && ( - <> - - -
- ... -
- - )} - {Array.from( { length: endPage - startPage + 1 }, (_, i) => startPage + i @@ -72,9 +80,60 @@ export function Pagination({ {page < totalPages - 1 && ( <> -
- ... -
+ {isJumpOpen ? ( + { + const val = e.target.value; + if (val === "") { + setJumpValue(""); + return; + } + const num = Number(val); + if (Number.isNaN(num)) { + return; + } + if (num < 1) { + setJumpValue("1"); + return; + } + if (num > totalPages) { + setJumpValue(String(totalPages)); + return; + } + setJumpValue(val); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + if (jumpValue.trim() === "") return; + const parsed = Number(jumpValue); + if (Number.isNaN(parsed)) return; + const target = Math.max(1, Math.min(totalPages, parsed)); + onPageChange(target); + setIsJumpOpen(false); + } else if (e.key === "Escape") { + setIsJumpOpen(false); + } + }} + onBlur={() => { + setIsJumpOpen(false); + }} + aria-label="Go to page" + /> + ) : ( + + )} + + {endPage < totalPages && ( + + )}
); } From cb3e52de34be44bf731aa2f5a04b477d7454d5c8 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 26 Oct 2025 19:37:57 +0200 Subject: [PATCH 54/81] fix: go to page button did not appear correctly for the last pages --- .../src/pages/catalogue/pagination.tsx | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index 4040c4b5..eaaa97a8 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -31,11 +31,15 @@ export function Pagination({ if (totalPages <= 1) return null; const visiblePages = 3; + const isLastThree = totalPages > 3 && page >= totalPages - 2; let startPage = Math.max(1, page - 1); let endPage = startPage + visiblePages - 1; - if (endPage > totalPages) { + if (isLastThree) { + startPage = Math.max(1, totalPages - 2); + endPage = totalPages; + } else if (endPage > totalPages) { endPage = totalPages; startPage = Math.max(1, endPage - visiblePages + 1); } @@ -64,6 +68,72 @@ export function Pagination({ + {isLastThree && startPage > 1 && ( + <> + + {isJumpOpen ? ( + { + const val = e.target.value; + if (val === "") { + setJumpValue(""); + return; + } + const num = Number(val); + if (Number.isNaN(num)) { + return; + } + if (num < 1) { + setJumpValue("1"); + return; + } + if (num > totalPages) { + setJumpValue(String(totalPages)); + return; + } + setJumpValue(val); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + if (jumpValue.trim() === "") return; + const parsed = Number(jumpValue); + if (Number.isNaN(parsed)) return; + const target = Math.max(1, Math.min(totalPages, parsed)); + onPageChange(target); + setIsJumpOpen(false); + } else if (e.key === "Escape") { + setIsJumpOpen(false); + } + }} + onBlur={() => { + setIsJumpOpen(false); + }} + aria-label="Go to page" + /> + ) : ( + + )} + + )} + {Array.from( { length: endPage - startPage + 1 }, (_, i) => startPage + i @@ -78,7 +148,7 @@ export function Pagination({ ))} - {page < totalPages - 1 && ( + {!isLastThree && page < totalPages - 1 && ( <> {isJumpOpen ? ( Date: Sun, 26 Oct 2025 19:49:15 +0200 Subject: [PATCH 55/81] fix: duplications --- .../src/pages/catalogue/pagination.tsx | 171 +++++++----------- 1 file changed, 63 insertions(+), 108 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index eaaa97a8..1ba02d06 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -2,6 +2,7 @@ import { Button } from "@renderer/components/button/button"; import { ChevronLeftIcon, ChevronRightIcon } from "@primer/octicons-react"; import { useFormat } from "@renderer/hooks/use-format"; import { useEffect, useRef, useState } from "react"; +import type { ChangeEvent, KeyboardEvent } from "react"; import "./pagination.scss"; interface PaginationProps { @@ -44,6 +45,66 @@ export function Pagination({ startPage = Math.max(1, endPage - visiblePages + 1); } + const onJumpChange = (e: ChangeEvent) => { + const val = e.target.value; + if (val === "") { + setJumpValue(""); + return; + } + const num = Number(val); + if (Number.isNaN(num)) { + return; + } + if (num < 1) { + setJumpValue("1"); + return; + } + if (num > totalPages) { + setJumpValue(String(totalPages)); + return; + } + setJumpValue(val); + }; + + const onJumpKeyDown = (e: KeyboardEvent) => { + if (e.key === "Enter") { + if (jumpValue.trim() === "") return; + const parsed = Number(jumpValue); + if (Number.isNaN(parsed)) return; + const target = Math.max(1, Math.min(totalPages, parsed)); + onPageChange(target); + setIsJumpOpen(false); + } else if (e.key === "Escape") { + setIsJumpOpen(false); + } + }; + + const JumpControl = () => + isJumpOpen ? ( + { + setIsJumpOpen(false); + }} + aria-label="Go to page" + /> + ) : ( + + ); + return (
{startPage > 1 && ( @@ -77,60 +138,7 @@ export function Pagination({ > {formatNumber(1)} - {isJumpOpen ? ( - { - const val = e.target.value; - if (val === "") { - setJumpValue(""); - return; - } - const num = Number(val); - if (Number.isNaN(num)) { - return; - } - if (num < 1) { - setJumpValue("1"); - return; - } - if (num > totalPages) { - setJumpValue(String(totalPages)); - return; - } - setJumpValue(val); - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - if (jumpValue.trim() === "") return; - const parsed = Number(jumpValue); - if (Number.isNaN(parsed)) return; - const target = Math.max(1, Math.min(totalPages, parsed)); - onPageChange(target); - setIsJumpOpen(false); - } else if (e.key === "Escape") { - setIsJumpOpen(false); - } - }} - onBlur={() => { - setIsJumpOpen(false); - }} - aria-label="Go to page" - /> - ) : ( - - )} + )} @@ -150,60 +158,7 @@ export function Pagination({ {!isLastThree && page < totalPages - 1 && ( <> - {isJumpOpen ? ( - { - const val = e.target.value; - if (val === "") { - setJumpValue(""); - return; - } - const num = Number(val); - if (Number.isNaN(num)) { - return; - } - if (num < 1) { - setJumpValue("1"); - return; - } - if (num > totalPages) { - setJumpValue(String(totalPages)); - return; - } - setJumpValue(val); - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - if (jumpValue.trim() === "") return; - const parsed = Number(jumpValue); - if (Number.isNaN(parsed)) return; - const target = Math.max(1, Math.min(totalPages, parsed)); - onPageChange(target); - setIsJumpOpen(false); - } else if (e.key === "Escape") { - setIsJumpOpen(false); - } - }} - onBlur={() => { - setIsJumpOpen(false); - }} - aria-label="Go to page" - /> - ) : ( - - )} + + ); +} + interface PaginationProps { page: number; totalPages: number; @@ -79,32 +120,6 @@ export function Pagination({ } }; - const JumpControl = () => - isJumpOpen ? ( - { - setIsJumpOpen(false); - }} - aria-label="Go to page" - /> - ) : ( - - ); - return (
{startPage > 1 && ( @@ -138,7 +153,16 @@ export function Pagination({ > {formatNumber(1)} - + setIsJumpOpen(true)} + onClose={() => setIsJumpOpen(false)} + onChange={onJumpChange} + onKeyDown={onJumpKeyDown} + /> )} @@ -158,7 +182,16 @@ export function Pagination({ {!isLastThree && page < totalPages - 1 && ( <> - + setIsJumpOpen(true)} + onClose={() => setIsJumpOpen(false)} + onChange={onJumpChange} + onKeyDown={onJumpKeyDown} + />
-

Hydra Cloud

+

{t("hydra_cloud")}

{getHydraCloudSectionContent().description}
diff --git a/src/renderer/src/pages/settings/settings-download-sources.scss b/src/renderer/src/pages/settings/settings-download-sources.scss index a12bdff3..df0f5c8b 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.scss +++ b/src/renderer/src/pages/settings/settings-download-sources.scss @@ -1,5 +1,14 @@ @use "../../scss/globals.scss"; +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + .settings-download-sources { &__list { padding: 0; @@ -22,6 +31,17 @@ &--syncing { opacity: globals.$disabled-opacity; } + + &--pending { + opacity: 0.6; + } + } + + &__spinner { + animation: spin 1s linear infinite; + margin-right: calc(globals.$spacing-unit / 2); + width: 12px; + height: 12px; } &__item-header { diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index 08670145..75f0cc73 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -22,6 +22,7 @@ import { settingsContext } from "@renderer/context"; import { useNavigate } from "react-router-dom"; import { setFilters, clearFilters } from "@renderer/features"; import "./settings-download-sources.scss"; +import { logger } from "@renderer/logger"; export function SettingsDownloadSources() { const [ @@ -58,6 +59,30 @@ export function SettingsDownloadSources() { fetchDownloadSources(); }, []); + useEffect(() => { + const hasPendingOrMatchingSource = downloadSources.some( + (source) => + source.status === DownloadSourceStatus.PendingMatching || + source.status === DownloadSourceStatus.Matching + ); + + if (!hasPendingOrMatchingSource || !downloadSources.length) { + return; + } + + const intervalId = setInterval(async () => { + try { + await window.electron.syncDownloadSources(); + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources); + } catch (error) { + logger.error("Failed to fetch download sources:", error); + } + }, 5000); + + return () => clearInterval(intervalId); + }, [downloadSources]); + const handleRemoveSource = async (downloadSource: DownloadSource) => { setIsRemovingDownloadSource(true); @@ -67,7 +92,7 @@ export function SettingsDownloadSources() { setDownloadSources(sources as DownloadSource[]); showSuccessToast(t("removed_download_source")); } catch (error) { - console.error("Failed to remove download source:", error); + logger.error("Failed to remove download source:", error); } finally { setIsRemovingDownloadSource(false); } @@ -82,7 +107,7 @@ export function SettingsDownloadSources() { setDownloadSources(sources as DownloadSource[]); showSuccessToast(t("removed_all_download_sources")); } catch (error) { - console.error("Failed to remove all download sources:", error); + logger.error("Failed to remove all download sources:", error); } finally { setIsRemovingDownloadSource(false); setShowConfirmationDeleteAllSourcesModal(false); @@ -94,7 +119,7 @@ export function SettingsDownloadSources() { const sources = await window.electron.getDownloadSources(); setDownloadSources(sources as DownloadSource[]); } catch (error) { - console.error("Failed to refresh download sources:", error); + logger.error("Failed to refresh download sources:", error); } }; @@ -127,7 +152,7 @@ export function SettingsDownloadSources() { const navigateToCatalogue = (fingerprint?: string) => { if (!fingerprint) { - console.error("Cannot navigate: fingerprint is undefined"); + logger.error("Cannot navigate: fingerprint is undefined"); return; } @@ -202,16 +227,25 @@ export function SettingsDownloadSources() {
    {downloadSources.map((downloadSource) => { + const isPendingOrMatching = + downloadSource.status === DownloadSourceStatus.PendingMatching || + downloadSource.status === DownloadSourceStatus.Matching; + return (
  • {downloadSource.name}

    - {statusTitle[downloadSource.status]} + + {isPendingOrMatching && ( + + )} + {statusTitle[downloadSource.status]} +
    diff --git a/src/renderer/src/pages/settings/settings-real-debrid.tsx b/src/renderer/src/pages/settings/settings-real-debrid.tsx index 42ba6ad9..db3a29a3 100644 --- a/src/renderer/src/pages/settings/settings-real-debrid.tsx +++ b/src/renderer/src/pages/settings/settings-real-debrid.tsx @@ -133,7 +133,7 @@ export function SettingsRealDebrid() { {t("save_changes")} } - placeholder="API Token" + placeholder={t("api_token")} hint={ diff --git a/src/renderer/src/pages/settings/settings-torbox.tsx b/src/renderer/src/pages/settings/settings-torbox.tsx index 610dc942..46c8e2f9 100644 --- a/src/renderer/src/pages/settings/settings-torbox.tsx +++ b/src/renderer/src/pages/settings/settings-torbox.tsx @@ -116,7 +116,7 @@ export function SettingsTorBox() { onChange={(event) => setForm({ ...form, torBoxApiToken: event.target.value }) } - placeholder="API Token" + placeholder={t("api_token")} rightContent={ )} - {game?.shop !== "custom" && shop && objectId && ( + {shop !== "custom" && shop && objectId && ( - {game?.shop !== "custom" && } + {shop !== "custom" && }
diff --git a/src/types/game.types.ts b/src/types/game.types.ts index ed8fb852..35d537a8 100644 --- a/src/types/game.types.ts +++ b/src/types/game.types.ts @@ -1,4 +1,4 @@ -export type GameShop = "steam" | "epic" | "custom"; +export type GameShop = "steam" | "custom"; export type ShortcutLocation = "desktop" | "start_menu"; From ad588b5600a1172c4f7e67d1c2a8f78f6b0404ce Mon Sep 17 00:00:00 2001 From: Moyasee Date: Wed, 29 Oct 2025 19:51:09 +0200 Subject: [PATCH 81/81] fix: images with big height breaking layout --- src/renderer/src/pages/game-details/hero.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/src/pages/game-details/hero.scss b/src/renderer/src/pages/game-details/hero.scss index 6bd63320..41264fe4 100644 --- a/src/renderer/src/pages/game-details/hero.scss +++ b/src/renderer/src/pages/game-details/hero.scss @@ -146,6 +146,8 @@ $hero-height: 350px; &__game-logo { width: 200px; align-self: flex-end; + object-fit: contain; + object-position: left bottom; @media (min-width: 768px) { width: 250px; @@ -153,6 +155,7 @@ $hero-height: 350px; @media (min-width: 1024px) { width: 300px; + max-height: 150px; } }