From 8203399edada6c26cc70db1514f198665ef9de8d Mon Sep 17 00:00:00 2001 From: "Kiwo.2" Date: Sun, 12 Oct 2025 23:27:20 +0200 Subject: [PATCH 001/126] Matching to Latest Update --- src/locales/hu/translation.json | 95 ++++++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index 551c1363..b72811d3 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -76,7 +76,19 @@ "edit_game_modal_drop_hero_image_here": "Húzd ide a borítókép képét", "edit_game_modal_drop_to_replace_icon": "Ikon kicserélése ráhúzással", "edit_game_modal_drop_to_replace_logo": "Logó kicserélése ráhúzással", - "edit_game_modal_drop_to_replace_hero": "Borítókép kicserélése ráhúzással" + "edit_game_modal_drop_to_replace_hero": "Borítókép kicserélése ráhúzással", + "install_decky_plugin": "Decky Plugin Telepítése", + "update_decky_plugin": "Decky Plugin Frissítése", + "decky_plugin_installed_version": "Decky Plugin (v{{version}})", + "install_decky_plugin_title": "Telepítsd a Hydra Decky Plugint", + "install_decky_plugin_message": "Ez letölti és telepíteni fogja a Hydra plugint a Decky Loaderhez. Előfordulhat, hogy rendszergazdai jogosultságra lesz szükség. Folytatod?", + "update_decky_plugin_title": "Hydra Decky Plugin Frissítése", + "update_decky_plugin_message": "Egy új verzió elérhető a Hydra Decky Pluginhoz. Szeretnéd frissíteni?", + "decky_plugin_installed": "Decky plugin v{{version}} sikeresen telepítve", + "decky_plugin_installation_failed": "Decky plugin telepítése sikertelen: {{error}}", + "decky_plugin_installation_error": "Decky plugin telepítése hibával járt el: {{error}}", + "confirm": "Megerősít", + "cancel": "Mégse" }, "header": { "search": "Keresés", @@ -126,7 +138,7 @@ "downloading_metadata": "Metaadat letöltése", "filter": "Repackek szűrése", "requirements": "Rendszerkövetelmények", - "minimum": "Minimum", + "minimum": "Minimális", "recommended": "Ajánlott", "paused": "Szüneteltetve", "release_date": "Megjelenés: {{date}}", @@ -150,7 +162,7 @@ "playing_now": "Játékban: ", "change": "Változtatás", "repacks_modal_description": "Válaszd ki a repacket amit leszeretnél tölteni", - "select_folder_hint": "Hogy megváltoztasd a letöltési mappát, menj a <0>Beállítások menüjébe", + "select_folder_hint": "A letöltési mappát a <0>Beállítások menüjében változtathatod meg", "download_now": "Letöltés", "no_shop_details": "A bolt adatai nem érhetőek el.", "download_options": "Letöltési opciók", @@ -193,20 +205,54 @@ "failed_remove_from_library": "Játék eltávolítása a könyvtárból sikertelen", "files_removed_success": "Fájlok eltávolítása sikeres", "failed_remove_files": "Fájlok eltávolítása sikertelen", - "nsfw_content_title": "Ez a játék nem megfelelő tartalmat tartalmaz", + "nsfw_content_title": "Ez a játék tartalmaz nem megfelelő tartalmat", "nsfw_content_description": "A(z) {{title}} tartalma lehetséges hogy nem megfelelő minden korosztály számára. Biztosan folytatni szeretnéd?", "allow_nsfw_content": "Folytatás", "refuse_nsfw_content": "Vissza", "stats": "Statisztikák", "download_count": "Letöltések", "player_count": "Aktív játékosok", - "download_error": "Ez a letöltési opció nem elérhető", + "rating_count": "Értékelés", + "download_error": "Ez a letöltési opció nem elérhető", "download": "Letöltés", "executable_path_in_use": "Ez a futtatható fájl már használatban van a(z) \"{{game}}\" által", "warning": "Figyelmeztetés:", "hydra_needs_to_remain_open": "ehhez a letöltéshez, a Hydrának muszáj nyitva maradnia hogy letöltődjön. Ha a Hydra bezáródik letöltés előtt, a letöltés elveszik.", "achievements": "Achievementek", "achievements_count": "Achievementek {{unlockedCount}}/{{achievementsCount}}", + "show_more": "Mutass többet", + "show_less": "Mutass kevesebbet", + "reviews": "Vélemények", + "leave_a_review": "Hagyd itt a véleményed", + "write_review_placeholder": "Oszd meg a gondolataid a játékról...", + "sort_newest": "Legújabb", + "no_reviews_yet": "Még nem lett vélemény megosztva", + "be_first_to_review": "Légy az első, aki megossza a véleményét a játékról!", + "sort_oldest": "Legrégibb", + "sort_highest_score": "Legmagasabb Pontszám", + "sort_lowest_score": "Legalacsonyabb Pontszám", + "sort_most_voted": "Legszavazottabb", + "rating": "Értékelés", + "rating_stats": "Értékelés", + "rating_very_negative": "Nagyon Negatív", + "rating_negative": "Negatív", + "rating_neutral": "Átlagos", + "rating_positive": "Pozitív", + "rating_very_positive": "Nagyon Pozitív", + "submit_review": "Küldés", + "submitting": "Küldés alatt...", + "review_submitted_successfully": "Vélemény beküldve sikeresen!", + "review_submission_failed": "Vélemény beküldése sikertelen. Kérlek próbáld újra.", + "review_cannot_be_empty": "A vélemény mező nem lehet üres.", + "review_deleted_successfully": "Vélemény sikeresen törölve.", + "review_deletion_failed": "Vélemény törlése sikertelen. Kérlek próbáld újra.", + "loading_reviews": "Vélemények betöltése...", + "loading_more_reviews": "Több vélemény betöltése...", + "load_more_reviews": "Több vélemény betöltése", + "you_seemed_to_enjoy_this_game": "Úgy látszik élvezted ezt a játékot", + "would_you_recommend_this_game": "Szeretnél véleményt írni erről a játékról?", + "yes": "Igen", + "maybe_later": "Talán Később", "cloud_save": "Mentés felhőben", "cloud_save_description": "Mentsd el az előrehaladásod a felhőben, majd folytasd egy másik eszközön", "backups": "Biztonsági másolatok", @@ -219,6 +265,7 @@ "uploading_backup": "Biztonsági mentés feltöltése…", "no_backups": "Még nem hoztál létre biztonsági másolatot ehhez a játékhoz", "backup_uploaded": "Biztonsági mentés feltöltve", + "backup_failed": "Biztonsági mentés sikertelen", "backup_deleted": "Biztonsági mentés törölve", "backup_restored": "Biztonsági mentés helyreállítva", "see_all_achievements": "Achievementlista megtekintése", @@ -256,7 +303,7 @@ "download_error_real_debrid_account_not_authorized": "A Real-Debrid fiókod nem jogosult új letöltésekre. Kérlek, ellenőrízd a fiókbeállításaidat, majd próbáld újra.", "download_error_not_cached_on_real_debrid": "Ez a letöltés nem elérhető a Real-Debriden, és lekérdezni letöltési állapotot még nem lehet.", "update_playtime_title": "Játékidő frissítése", - "update_playtime_description": "A(z) {{game}} játékidejének frissítése", + "update_playtime_description": "A(z) {{game}} játékidejének frissítése manuálisan", "update_playtime": "Játékidő frissítése", "update_playtime_success": "Játékidő sikeresen frissítve", "update_playtime_error": "A Játékidőnek nem sikerült frissülnie", @@ -303,11 +350,17 @@ "caption": "Felirat", "audio": "Hang", "filter_by_source": "Szűrés forrás szerint", - "no_repacks_found": "Nem található forrás ehhez a játékhoz" + "no_repacks_found": "Nem található forrás ehhez a játékhoz" , + "delete_review": "Vélemény törlése", + "remove_review": "Vélemény eltávolítása", + "delete_review_modal_title": "Biztos vagy abban hogy törölni szeretnéd a véleményed?", + "delete_review_modal_description": "Ez a lépés nem vonható vissza.", + "delete_review_modal_delete_button": "Törlés", + "delete_review_modal_cancel_button": "Mégse" }, "activation": { - "title": "Hydra aktiválása", - "installation_id": "Telepítési azonosító:", + "title": "Hydra Aktiválása", + "installation_id": "Telepítési Azonosító:", "enter_activation_code": "Írd be az aktiválási kódod", "message": "Ha nem tudod hol kérdezz efelől, akkor nem kéne ilyened legyen.", "activate": "Aktiválás", @@ -341,7 +394,7 @@ "stop_seeding": "Seedelés leállítása", "resume_seeding": "Seedelés folytatása", "options": "Kezelés", - "alldebrid_size_not_supported": "Letöltési információ az AllDebridhez még nem támogatott", + "alldebrid_size_not_supported": "Letöltési információ az AllDebrid-hez még nem támogatott", "extract": "Fájlok kibontása", "extracting": "Fájlok kibontása…" }, @@ -445,6 +498,8 @@ "delete_theme_description": "Ez törölni fogja a(z) {{theme}} témát", "cancel": "Mégsem", "appearance": "Megjelenés", + "debrid": "Debrid", + "debrid_description": "A Debrid szolgáltatások prémium szolgáltatások amelyek lehetővé teszik, hogy gyorsan letölts különböző fájltároló szolgáltatásokon tárolt fájlokat, csak az internet sebességed szab határt.", "enable_torbox": "TorBox bekapcsolása", "torbox_description": "A TorBox egy olyan premium seedbox szolgáltatás, amely még a piacon elérhető legjobb szerverekkel is felveszi a versenyt.", "torbox_account_linked": "TorBox fiók összekapcsolva", @@ -520,11 +575,12 @@ "available_one": "Elérhető", "available_other": "Elérhető", "no_downloads": "Nincs elérhető letöltés" + "calculating": "Feldolgozás" }, "binary_not_found_modal": { "title": "A programok nincsenek telepítve", "description": "Wine vagy Lutris futtatható fájlok nem találhatók a rendszereden", - "instructions": "Ellenőrízd, hogy melyiket kell helyesen telepíteni a Linux disztribúcióra, hogy a játék normálisan fusson" + "instructions": "Ellenőrízd hogy melyiket kell helyesen telepíteni a Linux disztribúciódra, hogy a játék megfelelően fusson" }, "modal": { "close": "Bezárás gomb" @@ -604,7 +660,7 @@ "report_reason_violence": "Fenyegető", "report_reason_spam": "Spam", "report_reason_other": "Egyéb", - "profile_reported": "Profil jelentve", + "profile_reported": "Profil bejelentve", "your_friend_code": "A barát kódod:", "upload_banner": "Borítókép feltöltés", "uploading_banner": "Borítókép feltöltése…", @@ -622,17 +678,20 @@ "error_adding_friend": "Hiba, barátfelkérés sikertelen. Kérlek ellenőrízd a barát kódot", "friend_code_length_error": "A barát kódnak 8 karakterből kell állnia", "game_removed_from_pinned": "Játék eltávolítva a kitűzöttek közül", - "game_added_to_pinned": "Játék hozzáadva a kitűzöttekhez" + "game_added_to_pinned": "Játék hozzáadva a kitűzöttekhez" + "karma": "Karma", + "karma_count": "karma", + "karma_description": "Pozitív értékelésekre kapott pontok alapján" }, "achievement": { "achievement_unlocked": "Achievement feloldva", - "user_achievements": "{{displayName}} Achievementjei", - "your_achievements": "A te Achievementjeid", - "unlocked_at": "Feloldva ekkor: {{date}}", + "user_achievements": "{{displayName}} achievementjei", + "your_achievements": "A te achievementjeid", + "unlocked_at": "Feloldva: {{date}}", "subscription_needed": "A tartalom megtekintéséhez Hydra Cloud előfizetés szükséges", - "new_achievements_unlocked": "{{achievementCount}} új achievementet oldottál fel {{gameCount}} játékban", + "new_achievements_unlocked": "{{achievementCount}} új achievement feloldva {{gameCount}} játékban", "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievementek", - "achievements_unlocked_for_game": "{{achievementCount}} új achievementet oldottál fel a(z) {{gameTitle}} játékban", + "achievements_unlocked_for_game": "{{achievementCount}} új achievement feloldva itt: {{gameTitle}}", "hidden_achievement_tooltip": "Ez egy rejtett achievement", "achievement_earn_points": "Szerezz be {{points}} pontot ezzel az achievement-el", "earned_points": "Megszerzett pontok:", From 3dc71a8d1fb203d1403c07abcd0fa4e1806f80bb Mon Sep 17 00:00:00 2001 From: whintersnow0 Date: Wed, 15 Oct 2025 19:19:08 +0200 Subject: [PATCH 002/126] refactor: remove unnecessary useMemo hooks --- .../src/components/text-field/text-field.tsx | 52 +++++-------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/src/renderer/src/components/text-field/text-field.tsx b/src/renderer/src/components/text-field/text-field.tsx index 1c4d54af..7c0cbb58 100644 --- a/src/renderer/src/components/text-field/text-field.tsx +++ b/src/renderer/src/components/text-field/text-field.tsx @@ -1,16 +1,13 @@ -import React, { useId, useMemo, useState } from "react"; +import React, { useId, useState } from "react"; import { EyeClosedIcon, EyeIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; - import cn from "classnames"; - import "./text-field.scss"; -export interface TextFieldProps - extends React.DetailedHTMLProps< - React.InputHTMLAttributes, - HTMLInputElement - > { +export interface TextFieldProps extends React.DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement +> { theme?: "primary" | "dark"; label?: string | React.ReactNode; hint?: string | React.ReactNode; @@ -42,44 +39,27 @@ export const TextField = React.forwardRef( ) => { const id = useId(); const [isFocused, setIsFocused] = useState(false); - const [isPasswordVisible, setIsPasswordVisible] = useState(false); - const { t } = useTranslation("forms"); - const showPasswordToggleButton = props.type === "password"; - - const inputType = useMemo(() => { - if (props.type === "password" && isPasswordVisible) return "text"; - return props.type ?? "text"; - }, [props.type, isPasswordVisible]); - - const hintContent = useMemo(() => { - if (error) - return ( - {error} - ); - - if (hint) return {hint}; - return null; - }, [hint, error]); - + const inputType = props.type === "password" && isPasswordVisible ? "text" : props.type ?? "text"; + const hintContent = error ? ( + {error} + ) : hint ? ( + {hint} + ) : null; const handleFocus: React.FocusEventHandler = (event) => { setIsFocused(true); - if (props.onFocus) props.onFocus(event); + props.onFocus?.(event); }; - const handleBlur: React.FocusEventHandler = (event) => { setIsFocused(false); - if (props.onBlur) props.onBlur(event); + props.onBlur?.(event); }; - const hasError = !!error; - return (
{label && } -
( onBlur={handleBlur} type={inputType} /> - {showPasswordToggleButton && ( )}
- {rightContent}
- {hintContent}
); } ); - -TextField.displayName = "TextField"; +TextField.displayName = "TextField"; \ No newline at end of file From c2273dbf712842ef7f6241d8a68b154816bbb64a Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 18 Oct 2025 14:07:44 +0100 Subject: [PATCH 003/126] 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 004/126] 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 005/126] 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 006/126] 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 007/126] 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 008/126] 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 009/126] 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 010/126] 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 011/126] 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 012/126] 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 013/126] 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 014/126] 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 015/126] 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 016/126] 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 017/126] 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 018/126] 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 019/126] 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 020/126] 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 021/126] 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 022/126] 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 023/126] 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 024/126] 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 025/126] 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 026/126] 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 027/126] 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 028/126] 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 029/126] 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 030/126] 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 031/126] 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 032/126] 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 033/126] 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 034/126] 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 035/126] 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 036/126] 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 037/126] 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 038/126] 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 039/126] 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 040/126] 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 041/126] 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 042/126] 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 043/126] 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 044/126] 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 045/126] 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 046/126] 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 047/126] 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 048/126] 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 049/126] 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 050/126] 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 051/126] 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 052/126] 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 053/126] 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 054/126] 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 055/126] 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 056/126] 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 057/126] 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={ -
    - - {formatDistance(new Date(review.createdAt), new Date(), { - addSuffix: true, - })} +
    +
    + + + {review.score}/5 + +
    + {review.playTimeInSeconds && review.playTimeInSeconds > 0 && ( +
    + + + {t("played_for")} {formatPlayTime(review.playTimeInSeconds)} + +
    + )}
-
- {[1, 2, 3, 4, 5].map((starValue) => ( - - ))} +
+
+ + {formatDistance(new Date(review.createdAt), new Date(), { + addSuffix: true, + })} +
diff --git a/src/types/index.ts b/src/types/index.ts index 63b18645..38abdbb8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -255,6 +255,7 @@ export interface GameReview { isBlocked: boolean; hasUpvoted: boolean; hasDownvoted: boolean; + playTimeInSeconds?: number; user: { id: string; displayName: string; From 9e09a5decb7ed2556b9b881fc4d8f6c9a72c1a92 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 27 Oct 2025 19:28:29 +0200 Subject: [PATCH 071/126] fix: translation key fix and formatting --- src/locales/en/translation.json | 2 +- .../src/pages/game-details/review-item.tsx | 27 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index e578b251..cef82a4b 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -223,7 +223,7 @@ "show_more": "Show more", "show_less": "Show less", "reviews": "Reviews", - "played_for": "Played for", + "review_played_for": "Played for", "leave_a_review": "Leave a Review", "write_review_placeholder": "Share your thoughts about this game...", "sort_newest": "Newest", diff --git a/src/renderer/src/pages/game-details/review-item.tsx b/src/renderer/src/pages/game-details/review-item.tsx index d6b9fd9c..8ffe42c0 100644 --- a/src/renderer/src/pages/game-details/review-item.tsx +++ b/src/renderer/src/pages/game-details/review-item.tsx @@ -30,8 +30,6 @@ interface ReviewItemProps { ) => void; } - - const getRatingText = (score: number, t: (key: string) => string): string => { switch (score) { case 1: @@ -157,22 +155,23 @@ export function ReviewItem({
- - - {review.score}/5 - -
+ className="game-details__review-score-stars" + title={getRatingText(review.score, t)} + > + + + {review.score}/5 + +
{review.playTimeInSeconds && review.playTimeInSeconds > 0 && (
- {t("played_for")} {formatPlayTime(review.playTimeInSeconds)} + {t("review_played_for")}{" "} + {formatPlayTime(review.playTimeInSeconds)}
)} From b431ed479c84aa9406ae2fb581fe0cbfe880733e Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 27 Oct 2025 20:07:08 +0200 Subject: [PATCH 072/126] fix: converted conditional to boolean --- src/renderer/src/pages/game-details/review-item.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/review-item.tsx b/src/renderer/src/pages/game-details/review-item.tsx index 8ffe42c0..15b12fbe 100644 --- a/src/renderer/src/pages/game-details/review-item.tsx +++ b/src/renderer/src/pages/game-details/review-item.tsx @@ -166,12 +166,14 @@ export function ReviewItem({ {review.score}/5
- {review.playTimeInSeconds && review.playTimeInSeconds > 0 && ( + {Boolean( + review.playTimeInSeconds && review.playTimeInSeconds > 0 + ) && (
{t("review_played_for")}{" "} - {formatPlayTime(review.playTimeInSeconds)} + {formatPlayTime(review.playTimeInSeconds!)}
)} From 5c770bc7e7eed580485b8e68d423f21e9df7d3bb Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 27 Oct 2025 20:12:24 +0200 Subject: [PATCH 073/126] fix: unnecessary assertion --- src/renderer/src/pages/game-details/review-item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/game-details/review-item.tsx b/src/renderer/src/pages/game-details/review-item.tsx index 15b12fbe..82d7128c 100644 --- a/src/renderer/src/pages/game-details/review-item.tsx +++ b/src/renderer/src/pages/game-details/review-item.tsx @@ -173,7 +173,7 @@ export function ReviewItem({ {t("review_played_for")}{" "} - {formatPlayTime(review.playTimeInSeconds!)} + {formatPlayTime(review.playTimeInSeconds || 0)}
)} From ddd6af0d4c945a3b0907ce94645616a324e2e6f8 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:22:47 -0300 Subject: [PATCH 074/126] fix: add theme editor dev tools back --- src/main/services/window-manager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 7055fc09..178eb8de 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -462,6 +462,7 @@ export class WindowManager { editorWindow.once("ready-to-show", () => { editorWindow.show(); + this.mainWindow?.webContents.openDevTools(); if (!app.isPackaged || isStaging) { editorWindow.webContents.openDevTools(); } @@ -474,6 +475,7 @@ export class WindowManager { }); editorWindow.on("close", () => { + this.mainWindow?.webContents.closeDevTools(); this.editorWindows.delete(themeId); }); } From 61072aa02a1a36702e03687a34e3864ae53cee72 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:25:18 -0300 Subject: [PATCH 075/126] fix: add theme editor dev tools back --- src/main/services/window-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 178eb8de..118ff98b 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -470,7 +470,7 @@ export class WindowManager { editorWindow.webContents.on("before-input-event", (_event, input) => { if (input.key === "F12") { - editorWindow.webContents.toggleDevTools(); + this.mainWindow?.webContents.toggleDevTools(); } }); From 1123aaa65ea49bea512a3eef23210ce8389eddb2 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 28 Oct 2025 06:48:42 -0300 Subject: [PATCH 076/126] chore: remove zod dep --- package.json | 3 +-- yarn.lock | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index 08c1d80e..9ed25fa9 100644 --- a/package.json +++ b/package.json @@ -90,8 +90,7 @@ "winreg": "^1.2.5", "ws": "^8.18.1", "yaml": "^2.6.1", - "yup": "^1.5.0", - "zod": "^3.24.1" + "yup": "^1.5.0" }, "devDependencies": { "@aws-sdk/client-s3": "^3.705.0", diff --git a/yarn.lock b/yarn.lock index 6340a43f..6fb80492 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9190,8 +9190,3 @@ yup@^1.5.0: tiny-case "^1.0.3" toposort "^2.0.2" type-fest "^2.19.0" - -zod@^3.24.1: - version "3.25.76" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" - integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== From 120aad6c1cacc2406aad9def4baa63b89d69eef8 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 28 Oct 2025 17:34:20 +0200 Subject: [PATCH 077/126] feat: Hide to tray on game startup and ability to disable trailers auto-play --- src/locales/en/translation.json | 4 +++- src/main/services/process-watcher.ts | 15 +++++++++++- .../gallery-slider/gallery-slider.tsx | 7 +++++- .../src/pages/settings/settings-behavior.tsx | 24 +++++++++++++++++++ src/types/level.types.ts | 2 ++ 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 46bdb28c..b17dda80 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -541,7 +541,9 @@ "hidden": "Hidden", "test_notification": "Test notification", "notification_preview": "Achievement Notification Preview", - "enable_friend_start_game_notifications": "When a friend starts playing a game" + "enable_friend_start_game_notifications": "When a friend starts playing a game", + "autoplay_trailers_on_game_page": "Automatically start playing trailers on game page", + "hide_to_tray_on_game_start": "Hide Hydra to tray on game startup" }, "notifications": { "download_complete": "Download complete", diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 06f5f7d8..9ca40b24 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -4,7 +4,8 @@ import type { Game, GameRunning } from "@types"; import { PythonRPC } from "./python-rpc"; import axios from "axios"; import { ProcessPayload } from "./download/types"; -import { gamesSublevel, levelKeys } from "@main/level"; +import { db, gamesSublevel, levelKeys } from "@main/level"; +import type { UserPreferences } from "@types"; import { CloudSync } from "./cloud-sync"; import { logger } from "./logger"; import path from "path"; @@ -209,6 +210,18 @@ function onOpenGame(game: Game) { lastSyncTick: now, }); + // Hide Hydra to tray on game startup if enabled + db + .get(levelKeys.userPreferences, { + valueEncoding: "json", + }) + .then((userPreferences) => { + if (userPreferences?.hideToTrayOnGameStart) { + WindowManager.mainWindow?.hide(); + } + }) + .catch(() => {}); + if (game.remoteId) { updateGamePlaytime( game, diff --git a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.tsx b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.tsx index 4bf8dc48..c9658636 100644 --- a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.tsx +++ b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.tsx @@ -7,11 +7,16 @@ import { } from "@primer/octicons-react"; import useEmblaCarousel from "embla-carousel-react"; import { gameDetailsContext } from "@renderer/context"; +import { useAppSelector } from "@renderer/hooks"; import "./gallery-slider.scss"; export function GallerySlider() { const { shopDetails } = useContext(gameDetailsContext); const { t } = useTranslation("game_details"); + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + const autoplayEnabled = userPreferences?.autoplayGameTrailers !== false; const hasScreenshots = shopDetails && shopDetails.screenshots?.length; @@ -164,7 +169,7 @@ export function GallerySlider() { poster={item.poster} loop muted - autoPlay + autoPlay={autoplayEnabled} tabIndex={-1} > diff --git a/src/renderer/src/pages/settings/settings-behavior.tsx b/src/renderer/src/pages/settings/settings-behavior.tsx index 64df52d7..bc91fc9d 100644 --- a/src/renderer/src/pages/settings/settings-behavior.tsx +++ b/src/renderer/src/pages/settings/settings-behavior.tsx @@ -27,6 +27,8 @@ export function SettingsBehavior() { showDownloadSpeedInMegabytes: false, extractFilesByDefault: true, enableSteamAchievements: false, + autoplayGameTrailers: true, + hideToTrayOnGameStart: false, }); const { t } = useTranslation("settings"); @@ -49,6 +51,10 @@ export function SettingsBehavior() { extractFilesByDefault: userPreferences.extractFilesByDefault ?? true, enableSteamAchievements: userPreferences.enableSteamAchievements ?? false, + autoplayGameTrailers: + userPreferences.autoplayGameTrailers ?? true, + hideToTrayOnGameStart: + userPreferences.hideToTrayOnGameStart ?? false, }); } }, [userPreferences]); @@ -76,6 +82,16 @@ export function SettingsBehavior() { } /> + + handleChange({ + hideToTrayOnGameStart: !form.hideToTrayOnGameStart, + }) + } + /> + {showRunAtStartup && ( )} + + handleChange({ autoplayGameTrailers: !form.autoplayGameTrailers }) + } + /> + Date: Tue, 28 Oct 2025 17:36:11 +0200 Subject: [PATCH 078/126] ci: formatting --- src/main/services/process-watcher.ts | 7 +++---- src/renderer/src/pages/settings/settings-behavior.tsx | 6 ++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 9ca40b24..4130daba 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -211,10 +211,9 @@ function onOpenGame(game: Game) { }); // Hide Hydra to tray on game startup if enabled - db - .get(levelKeys.userPreferences, { - valueEncoding: "json", - }) + db.get(levelKeys.userPreferences, { + valueEncoding: "json", + }) .then((userPreferences) => { if (userPreferences?.hideToTrayOnGameStart) { WindowManager.mainWindow?.hide(); diff --git a/src/renderer/src/pages/settings/settings-behavior.tsx b/src/renderer/src/pages/settings/settings-behavior.tsx index bc91fc9d..c5698ef7 100644 --- a/src/renderer/src/pages/settings/settings-behavior.tsx +++ b/src/renderer/src/pages/settings/settings-behavior.tsx @@ -51,10 +51,8 @@ export function SettingsBehavior() { extractFilesByDefault: userPreferences.extractFilesByDefault ?? true, enableSteamAchievements: userPreferences.enableSteamAchievements ?? false, - autoplayGameTrailers: - userPreferences.autoplayGameTrailers ?? true, - hideToTrayOnGameStart: - userPreferences.hideToTrayOnGameStart ?? false, + autoplayGameTrailers: userPreferences.autoplayGameTrailers ?? true, + hideToTrayOnGameStart: userPreferences.hideToTrayOnGameStart ?? false, }); } }, [userPreferences]); From dbf5d7afc76bdfb7620c2597c744453a89cf40ad Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 28 Oct 2025 17:43:19 +0200 Subject: [PATCH 079/126] fix: multiple imports --- src/main/services/process-watcher.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 4130daba..6408c30d 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -1,11 +1,10 @@ import { WindowManager } from "./window-manager"; import { createGame, updateGamePlaytime } from "./library-sync"; -import type { Game, GameRunning } from "@types"; +import type { Game, GameRunning, UserPreferences } from "@types"; import { PythonRPC } from "./python-rpc"; import axios from "axios"; import { ProcessPayload } from "./download/types"; import { db, gamesSublevel, levelKeys } from "@main/level"; -import type { UserPreferences } from "@types"; import { CloudSync } from "./cloud-sync"; import { logger } from "./logger"; import path from "path"; From 6b96c99bb177f18dc8a54834acea21cfc4c0c495 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 28 Oct 2025 21:37:28 +0000 Subject: [PATCH 080/126] ci: fixing release pipeline --- .github/workflows/release.yml | 6 +++ src/locales/en/translation.json | 1 + src/locales/pt-BR/translation.json | 3 ++ .../download-sources/add-download-source.ts | 9 +++- .../remove-download-source.ts | 2 +- src/main/main.ts | 7 +++- src/main/services/hydra-api.ts | 5 ++- src/main/services/index.ts | 1 + src/main/services/user/index.ts | 3 ++ .../services/user/sync-download-sources.ts | 42 +++++++++++++++++++ .../settings/add-download-source-modal.tsx | 6 ++- 11 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 src/main/services/user/index.ts create mode 100644 src/main/services/user/sync-download-sources.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ceb42c7..9524c4b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,12 @@ jobs: - name: Build with cx_Freeze run: python python_rpc/setup.py build + - name: Copy OpenSSL DLLs + if: matrix.os == 'windows-2022' + run: | + cp hydra-python-rpc/lib/libcrypto-1_1.dll hydra-python-rpc/lib/libcrypto-1_1-x64.dll + cp hydra-python-rpc/lib/libssl-1_1.dll hydra-python-rpc/lib/libssl-1_1-x64.dll + - name: Build Linux if: matrix.os == 'ubuntu-latest' run: | diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index bfc4a379..3977c27d 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -430,6 +430,7 @@ "add_download_source": "Add source", "adding": "Adding…", "failed_add_download_source": "Failed to add download source. Please try again.", + "download_source_already_exists": "This download source URL already exists.", "download_count_zero": "No download options", "download_count_one": "{{countFormatted}} download option", "download_count_other": "{{countFormatted}} download options", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 968483a6..c9e908ac 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -416,6 +416,9 @@ "validate_download_source": "Validar", "remove_download_source": "Remover", "add_download_source": "Adicionar fonte", + "adding": "Adicionando…", + "failed_add_download_source": "Falha ao adicionar fonte de download. Tente novamente.", + "download_source_already_exists": "Esta URL de fonte de download já existe.", "download_count_zero": "Sem downloads na lista", "download_count_one": "{{countFormatted}} download na lista", "download_count_other": "{{countFormatted}} downloads na lista", diff --git a/src/main/events/download-sources/add-download-source.ts b/src/main/events/download-sources/add-download-source.ts index d4e65ef3..ee426a82 100644 --- a/src/main/events/download-sources/add-download-source.ts +++ b/src/main/events/download-sources/add-download-source.ts @@ -9,6 +9,13 @@ const addDownloadSource = async ( url: string ) => { try { + const existingSources = await downloadSourcesSublevel.values().all(); + const urlExists = existingSources.some((source) => source.url === url); + + if (urlExists) { + throw new Error("Download source with this URL already exists"); + } + const downloadSource = await HydraApi.post( "/download-sources", { @@ -17,7 +24,7 @@ const addDownloadSource = async ( { needsAuth: false } ); - if (HydraApi.isLoggedIn()) { + if (HydraApi.isLoggedIn() && HydraApi.hasActiveSubscription()) { try { await HydraApi.post("/profile/download-sources", { urls: [url], diff --git a/src/main/events/download-sources/remove-download-source.ts b/src/main/events/download-sources/remove-download-source.ts index 8efe0072..9caeaba5 100644 --- a/src/main/events/download-sources/remove-download-source.ts +++ b/src/main/events/download-sources/remove-download-source.ts @@ -13,7 +13,7 @@ const removeDownloadSource = async ( if (downloadSourceId) params.set("downloadSourceId", downloadSourceId); - if (HydraApi.isLoggedIn()) { + if (HydraApi.isLoggedIn() && HydraApi.hasActiveSubscription()) { void HydraApi.delete(`/profile/download-sources?${params.toString()}`); } diff --git a/src/main/main.ts b/src/main/main.ts index 6e477a18..f2440b9f 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -50,9 +50,14 @@ export const loadState = async () => { DeckyPlugin.checkAndUpdateIfOutdated(); } - await HydraApi.setupApi().then(() => { + await HydraApi.setupApi().then(async () => { uploadGamesBatch(); void migrateDownloadSources(); + + const { syncDownloadSourcesFromApi } = await import( + "./services/user" + ); + void syncDownloadSourcesFromApi(); // WSClient.connect(); }); diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index ffc5756c..12090df3 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -46,7 +46,7 @@ export class HydraApi { return this.userAuth.authToken !== ""; } - private static hasActiveSubscription() { + public static hasActiveSubscription() { const expiresAt = new Date(this.userAuth.subscription?.expiresAt ?? 0); return expiresAt > new Date(); } @@ -105,6 +105,9 @@ export class HydraApi { // WSClient.close(); // WSClient.connect(); + + const { syncDownloadSourcesFromApi } = await import("./user"); + syncDownloadSourcesFromApi(); } } diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 88b39d1b..da4e6848 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -18,3 +18,4 @@ export * from "./library-sync"; export * from "./wine"; export * from "./lock"; export * from "./decky-plugin"; +export * from "./user"; diff --git a/src/main/services/user/index.ts b/src/main/services/user/index.ts new file mode 100644 index 00000000..b5001f7a --- /dev/null +++ b/src/main/services/user/index.ts @@ -0,0 +1,3 @@ +export * from "./get-user-data"; +export * from "./sync-download-sources"; + diff --git a/src/main/services/user/sync-download-sources.ts b/src/main/services/user/sync-download-sources.ts new file mode 100644 index 00000000..c5695d68 --- /dev/null +++ b/src/main/services/user/sync-download-sources.ts @@ -0,0 +1,42 @@ +import { HydraApi, logger } from "../"; +import { downloadSourcesSublevel } from "@main/level"; +import type { DownloadSource } from "@types"; + +export const syncDownloadSourcesFromApi = async () => { + if (!HydraApi.isLoggedIn() || !HydraApi.hasActiveSubscription()) { + return; + } + + try { + const profileSources = await HydraApi.get( + "/profile/download-sources" + ); + + const existingSources = await downloadSourcesSublevel.values().all(); + const existingUrls = new Set(existingSources.map((source) => source.url)); + + for (const downloadSource of profileSources) { + if (!existingUrls.has(downloadSource.url)) { + try { + await downloadSourcesSublevel.put(downloadSource.id, { + ...downloadSource, + isRemote: true, + createdAt: new Date().toISOString(), + }); + + logger.log( + `Synced download source from profile: ${downloadSource.url}` + ); + } catch (error) { + logger.error( + `Failed to sync download source ${downloadSource.url}:`, + error + ); + } + } + } + } catch (error) { + logger.error("Failed to sync download sources from API:", error); + } +}; + diff --git a/src/renderer/src/pages/settings/add-download-source-modal.tsx b/src/renderer/src/pages/settings/add-download-source-modal.tsx index d7071391..d96c67a5 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -57,9 +57,13 @@ export function AddDownloadSourceModal({ onAddDownloadSource(); } catch (error) { logger.error("Failed to add download source:", error); + const errorMessage = error instanceof Error && error.message.includes("already exists") + ? t("download_source_already_exists") + : t("failed_add_download_source"); + setError("url", { type: "server", - message: t("failed_add_download_source"), + message: errorMessage, }); } finally { setIsLoading(false); From a11b3e887796966b64e2ec73786f27d9c5a2e741 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 28 Oct 2025 21:38:07 +0000 Subject: [PATCH 081/126] ci: fixing release pipeline --- src/main/events/download-sources/add-download-source.ts | 2 +- src/main/main.ts | 6 ++---- src/main/services/user/index.ts | 1 - src/main/services/user/sync-download-sources.ts | 1 - .../src/pages/settings/add-download-source-modal.tsx | 9 +++++---- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/main/events/download-sources/add-download-source.ts b/src/main/events/download-sources/add-download-source.ts index ee426a82..bea009cb 100644 --- a/src/main/events/download-sources/add-download-source.ts +++ b/src/main/events/download-sources/add-download-source.ts @@ -11,7 +11,7 @@ const addDownloadSource = async ( try { const existingSources = await downloadSourcesSublevel.values().all(); const urlExists = existingSources.some((source) => source.url === url); - + if (urlExists) { throw new Error("Download source with this URL already exists"); } diff --git a/src/main/main.ts b/src/main/main.ts index f2440b9f..ffb8f8a9 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -53,10 +53,8 @@ export const loadState = async () => { await HydraApi.setupApi().then(async () => { uploadGamesBatch(); void migrateDownloadSources(); - - const { syncDownloadSourcesFromApi } = await import( - "./services/user" - ); + + const { syncDownloadSourcesFromApi } = await import("./services/user"); void syncDownloadSourcesFromApi(); // WSClient.connect(); }); diff --git a/src/main/services/user/index.ts b/src/main/services/user/index.ts index b5001f7a..b1d8c9b7 100644 --- a/src/main/services/user/index.ts +++ b/src/main/services/user/index.ts @@ -1,3 +1,2 @@ export * from "./get-user-data"; export * from "./sync-download-sources"; - diff --git a/src/main/services/user/sync-download-sources.ts b/src/main/services/user/sync-download-sources.ts index c5695d68..ff9819ce 100644 --- a/src/main/services/user/sync-download-sources.ts +++ b/src/main/services/user/sync-download-sources.ts @@ -39,4 +39,3 @@ export const syncDownloadSourcesFromApi = async () => { logger.error("Failed to sync download sources from API:", error); } }; - diff --git a/src/renderer/src/pages/settings/add-download-source-modal.tsx b/src/renderer/src/pages/settings/add-download-source-modal.tsx index d96c67a5..af6f8b4d 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -57,10 +57,11 @@ export function AddDownloadSourceModal({ onAddDownloadSource(); } catch (error) { logger.error("Failed to add download source:", error); - const errorMessage = error instanceof Error && error.message.includes("already exists") - ? t("download_source_already_exists") - : t("failed_add_download_source"); - + const errorMessage = + error instanceof Error && error.message.includes("already exists") + ? t("download_source_already_exists") + : t("failed_add_download_source"); + setError("url", { type: "server", message: errorMessage, From ce0619bbe398b6e03e61641d3ecfae6d41d5bbca Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 28 Oct 2025 22:40:06 +0000 Subject: [PATCH 082/126] ci: adding releases --- .github/workflows/build-renderer.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index f7361883..0d219c1e 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -6,23 +6,38 @@ concurrency: on: push: - branches: [main] + branches: + - main + - release/** jobs: build: runs-on: ubuntu-latest + permissions: + contents: read + + env: + NODE_OPTIONS: --max-old-space-size=4096 + BRANCH_NAME: ${{ github.ref_name }} + steps: - name: Check out Git repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Install Node.js + - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 22.21.0 + cache: 'yarn' + + - name: Enable Corepack (Yarn) + run: corepack enable - name: Install dependencies - run: yarn --frozen-lockfile --ignore-scripts + run: yarn install --frozen-lockfile --ignore-scripts - name: Build Renderer run: yarn build @@ -36,5 +51,5 @@ jobs: run: | npx --yes wrangler@3 pages deploy out/renderer \ --project-name="hydra" \ - --commit-dirty=true \ - --branch="main" + --branch "$BRANCH_NAME" \ + --commit-dirty \ No newline at end of file From dc8a19e8451e8e8466fe1453cb64d1fb9452f7a1 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 28 Oct 2025 23:02:40 +0000 Subject: [PATCH 083/126] ci: adding ci vars --- .env.example | 1 + .github/workflows/build-renderer.yml | 4 ++-- .github/workflows/release.yml | 8 +++++--- src/main/services/window-manager.ts | 13 +++++++++---- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 3f914eb3..051d8aa3 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,4 @@ MAIN_VITE_AUTH_URL= MAIN_VITE_WS_URL= RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= RENDERER_VITE_TORBOX_REFERRAL_CODE= +MAIN_VITE_LAUNCHER_SUBDOMAIN= diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index 0d219c1e..2904bcb8 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -31,7 +31,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 22.21.0 - cache: 'yarn' + cache: "yarn" - name: Enable Corepack (Yarn) run: corepack enable @@ -52,4 +52,4 @@ jobs: npx --yes wrangler@3 pages deploy out/renderer \ --project-name="hydra" \ --branch "$BRANCH_NAME" \ - --commit-dirty \ No newline at end of file + --commit-dirty diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75ff209a..7408665f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,9 @@ concurrency: on: push: - branches: [main] + branches: + - main + - release/** jobs: build: @@ -61,7 +63,7 @@ jobs: RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }} RENDERER_VITE_REAL_DEBRID_REFERRAL_ID: ${{ vars.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID }} RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }} - MAIN_VITE_RENDERER_URL: ${{ vars.MAIN_VITE_RENDERER_URL }} + MAIN_VITE_LAUNCHER_SUBDOMAIN: ${{ vars.MAIN_VITE_LAUNCHER_SUBDOMAIN }} - name: Build Windows if: matrix.os == 'windows-2022' @@ -78,7 +80,7 @@ jobs: RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }} RENDERER_VITE_REAL_DEBRID_REFERRAL_ID: ${{ vars.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID }} RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }} - MAIN_VITE_RENDERER_URL: ${{ vars.MAIN_VITE_RENDERER_URL }} + MAIN_VITE_LAUNCHER_SUBDOMAIN: ${{ vars.MAIN_VITE_LAUNCHER_SUBDOMAIN }} - name: Create artifact uses: actions/upload-artifact@v4 diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 7055fc09..4c52b581 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -24,7 +24,8 @@ import type { UserPreferences, } from "@types"; import { AuthPage, generateAchievementCustomNotificationTest } from "@shared"; -import { isStaging } from "@main/constants"; +import { appVersion, isStaging } from "@main/constants"; +import { logger } from "./logger"; export class WindowManager { public static mainWindow: Electron.BrowserWindow | null = null; @@ -54,6 +55,10 @@ export class WindowManager { show: false, }; + private static formatVersionNumber(version: string) { + return version.replaceAll(".", "-"); + } + private static async loadWindowURL(window: BrowserWindow, hash: string = "") { // HMR for renderer base on electron-vite cli. // Load the remote URL for development or the local html file for production. @@ -63,12 +68,12 @@ export class WindowManager { // Try to load from remote URL in production try { await window.loadURL( - `${import.meta.env.MAIN_VITE_RENDERER_URL}#/${hash}` + `https://release-${this.formatVersionNumber(appVersion)}.${import.meta.env.MAIN_VITE_LAUNCHER_SUBDOMAIN}#/${hash}` ); } catch (error) { // Fall back to local file if remote URL fails - console.error( - "Failed to load from MAIN_VITE_RENDERER_URL, falling back to local file:", + logger.error( + "Failed to load from MAIN_VITE_LAUNCHER_SUBDOMAIN, falling back to local file:", error ); window.loadFile(path.join(__dirname, "../renderer/index.html"), { From 8a12c6e088195a6fb22e30c9ab2af0e1f1a7c3b7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 28 Oct 2025 23:26:28 +0000 Subject: [PATCH 084/126] chore: sync with main --- src/main/services/window-manager.ts | 2 +- src/main/vite-env.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 33b6785c..c7335f8a 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -68,7 +68,7 @@ export class WindowManager { // Try to load from remote URL in production try { await window.loadURL( - `https://release-${this.formatVersionNumber(appVersion)}.${import.meta.env.MAIN_VITE_LAUNCHER_SUBDOMAIN}#/${hash}` + `https://release-v${this.formatVersionNumber(appVersion)}.${import.meta.env.MAIN_VITE_LAUNCHER_SUBDOMAIN}#/${hash}` ); } catch (error) { // Fall back to local file if remote URL fails diff --git a/src/main/vite-env.d.ts b/src/main/vite-env.d.ts index c9b006d5..7b0ed536 100644 --- a/src/main/vite-env.d.ts +++ b/src/main/vite-env.d.ts @@ -7,7 +7,7 @@ interface ImportMetaEnv { readonly MAIN_VITE_CHECKOUT_URL: string; readonly MAIN_VITE_EXTERNAL_RESOURCES_URL: string; readonly MAIN_VITE_WS_URL: string; - readonly MAIN_VITE_RENDERER_URL: string; + readonly MAIN_VITE_LAUNCHER_SUBDOMAIN: string; readonly ELECTRON_RENDERER_URL: string; } From dc6d578462a392ba359b61a034df7598d70f4a70 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 28 Oct 2025 23:49:09 +0000 Subject: [PATCH 085/126] chore: sync with main --- src/main/services/window-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index c7335f8a..aeff3808 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -64,7 +64,7 @@ export class WindowManager { // Load the remote URL for development or the local html file for production. if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { window.loadURL(`${process.env["ELECTRON_RENDERER_URL"]}#/${hash}`); - } else if (import.meta.env.MAIN_VITE_RENDERER_URL) { + } else if (import.meta.env.MAIN_VITE_LAUNCHER_SUBDOMAIN) { // Try to load from remote URL in production try { await window.loadURL( From b1069426e4b035703ba2f79aeb758030bc0344c0 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 29 Oct 2025 01:47:35 +0000 Subject: [PATCH 086/126] chore: sync with main --- package.json | 2 +- src/main/services/window-manager.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9ed25fa9..5d84e763 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.7.1", + "version": "3.7.2", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index aeff3808..673bf1a0 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -24,7 +24,7 @@ import type { UserPreferences, } from "@types"; import { AuthPage, generateAchievementCustomNotificationTest } from "@shared"; -import { appVersion, isStaging } from "@main/constants"; +import { isStaging } from "@main/constants"; import { logger } from "./logger"; export class WindowManager { @@ -68,7 +68,7 @@ export class WindowManager { // Try to load from remote URL in production try { await window.loadURL( - `https://release-v${this.formatVersionNumber(appVersion)}.${import.meta.env.MAIN_VITE_LAUNCHER_SUBDOMAIN}#/${hash}` + `https://release-v${this.formatVersionNumber(app.getVersion())}.${import.meta.env.MAIN_VITE_LAUNCHER_SUBDOMAIN}#/${hash}` ); } catch (error) { // Fall back to local file if remote URL fails From 274080069fd7997600c9ad296c52a148f821c934 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 29 Oct 2025 02:12:17 +0000 Subject: [PATCH 087/126] feat: forcing dev tools --- src/main/services/window-manager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 673bf1a0..3e241467 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -197,6 +197,8 @@ export class WindowManager { this.mainWindow.removeMenu(); this.mainWindow.on("ready-to-show", () => { + WindowManager.mainWindow?.webContents.openDevTools(); + if (!app.isPackaged || isStaging) WindowManager.mainWindow?.webContents.openDevTools(); WindowManager.mainWindow?.show(); From f99f8d95548638678b21610b8511e44207e00730 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 29 Oct 2025 02:32:45 +0000 Subject: [PATCH 088/126] feat: forcing dev tools --- src/main/services/window-manager.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 3e241467..a9b742ba 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -24,7 +24,7 @@ import type { UserPreferences, } from "@types"; import { AuthPage, generateAchievementCustomNotificationTest } from "@shared"; -import { isStaging } from "@main/constants"; +import { appVersion, isStaging } from "@main/constants"; import { logger } from "./logger"; export class WindowManager { @@ -197,8 +197,6 @@ export class WindowManager { this.mainWindow.removeMenu(); this.mainWindow.on("ready-to-show", () => { - WindowManager.mainWindow?.webContents.openDevTools(); - if (!app.isPackaged || isStaging) WindowManager.mainWindow?.webContents.openDevTools(); WindowManager.mainWindow?.show(); From e143fadf381a5992e26b699ca6c7b4a38e0bfc06 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 29 Oct 2025 02:55:37 +0000 Subject: [PATCH 089/126] fix: fixing import --- src/main/services/window-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index a9b742ba..673bf1a0 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -24,7 +24,7 @@ import type { UserPreferences, } from "@types"; import { AuthPage, generateAchievementCustomNotificationTest } from "@shared"; -import { appVersion, isStaging } from "@main/constants"; +import { isStaging } from "@main/constants"; import { logger } from "./logger"; export class WindowManager { From 58bdbdab71297a7cc559c0c5cfcd38b2131ceeb0 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Wed, 29 Oct 2025 16:16:11 +0200 Subject: [PATCH 090/126] fix: disabling unnecessary api calls if game is custom --- src/main/events/catalogue/get-game-stats.ts | 4 ++++ .../achievements/get-game-achievement-data.ts | 4 ++++ .../src/context/cloud-sync/cloud-sync.context.tsx | 6 ++++++ .../context/game-details/game-details.context.tsx | 12 +++++++----- src/renderer/src/pages/game-details/game-reviews.tsx | 4 ++-- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/events/catalogue/get-game-stats.ts b/src/main/events/catalogue/get-game-stats.ts index b836531d..b7b7125c 100644 --- a/src/main/events/catalogue/get-game-stats.ts +++ b/src/main/events/catalogue/get-game-stats.ts @@ -10,6 +10,10 @@ const getGameStats = async ( objectId: string, shop: GameShop ) => { + if (shop === "custom") { + return null; + } + const cachedStats = await gamesStatsCacheSublevel.get( levelKeys.game(shop, objectId) ); diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index ffbfac1a..69437801 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -27,6 +27,10 @@ export const getGameAchievementData = async ( shop: GameShop, useCachedData: boolean ) => { + if (shop === "custom") { + return []; + } + const gameKey = levelKeys.game(shop, objectId); const cachedAchievements = await gameAchievementsSublevel.get(gameKey); diff --git a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx index b94c94d7..ce5f0a6e 100644 --- a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx +++ b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx @@ -98,6 +98,12 @@ export function CloudSyncContextProvider({ ); const getGameArtifacts = useCallback(async () => { + // Don't make API requests for custom games + if (shop === "custom") { + setArtifacts([]); + return; + } + const params = new URLSearchParams({ objectId, shop, diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 14e5d587..3706b02e 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -142,10 +142,12 @@ export function GameDetailsContextProvider({ } }); - window.electron.getGameStats(objectId, shop).then((result) => { - if (abortController.signal.aborted) return; - setStats(result); - }); + if (shop !== "custom") { + window.electron.getGameStats(objectId, shop).then((result) => { + if (abortController.signal.aborted) return; + setStats(result); + }); + } const assetsPromise = window.electron.getGameAssets(objectId, shop); @@ -167,7 +169,7 @@ export function GameDetailsContextProvider({ setIsLoading(false); }); - if (userDetails) { + if (userDetails && shop !== "custom") { window.electron .getUnlockedAchievements(objectId, shop) .then((achievements) => { diff --git a/src/renderer/src/pages/game-details/game-reviews.tsx b/src/renderer/src/pages/game-details/game-reviews.tsx index f70c84b2..1a6fc675 100644 --- a/src/renderer/src/pages/game-details/game-reviews.tsx +++ b/src/renderer/src/pages/game-details/game-reviews.tsx @@ -117,7 +117,7 @@ export function GameReviews({ }); const checkUserReview = useCallback(async () => { - if (!objectId || !userDetailsId) return; + if (!objectId || !userDetailsId || shop === "custom") return; try { const response = await window.electron.hydraApi.get<{ @@ -147,7 +147,7 @@ export function GameReviews({ const loadReviews = useCallback( async (reset = false) => { - if (!objectId) return; + if (!objectId || shop === "custom") return; if (abortControllerRef.current) { abortControllerRef.current.abort(); From dff68a3e260e5105ca1b51e6d1c5200cc8b470bb Mon Sep 17 00:00:00 2001 From: Moyasee Date: Wed, 29 Oct 2025 16:22:12 +0200 Subject: [PATCH 091/126] fix: removed comments --- src/renderer/src/context/cloud-sync/cloud-sync.context.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx index ce5f0a6e..abc359e9 100644 --- a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx +++ b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx @@ -98,7 +98,6 @@ export function CloudSyncContextProvider({ ); const getGameArtifacts = useCallback(async () => { - // Don't make API requests for custom games if (shop === "custom") { setArtifacts([]); return; From 4b8d64c72b51ac7eb8f74fa73eb0e5c94d6d659f Mon Sep 17 00:00:00 2001 From: Moyasee Date: Wed, 29 Oct 2025 16:44:48 +0200 Subject: [PATCH 092/126] feat: disabled favorite/unfavorite get request for custom games --- src/main/events/library/add-game-to-favorites.ts | 4 +++- src/main/events/library/remove-game-from-favorites.ts | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/events/library/add-game-to-favorites.ts b/src/main/events/library/add-game-to-favorites.ts index 68c81abb..53985a09 100644 --- a/src/main/events/library/add-game-to-favorites.ts +++ b/src/main/events/library/add-game-to-favorites.ts @@ -13,7 +13,9 @@ const addGameToFavorites = async ( const game = await gamesSublevel.get(gameKey); if (!game) return; - HydraApi.put(`/profile/games/${shop}/${objectId}/favorite`).catch(() => {}); + if (shop !== "custom") { + HydraApi.put(`/profile/games/${shop}/${objectId}/favorite`).catch(() => {}); + } try { await gamesSublevel.put(gameKey, { diff --git a/src/main/events/library/remove-game-from-favorites.ts b/src/main/events/library/remove-game-from-favorites.ts index f06f55ce..7c79cbf4 100644 --- a/src/main/events/library/remove-game-from-favorites.ts +++ b/src/main/events/library/remove-game-from-favorites.ts @@ -13,7 +13,11 @@ const removeGameFromFavorites = async ( const game = await gamesSublevel.get(gameKey); if (!game) return; - HydraApi.put(`/profile/games/${shop}/${objectId}/unfavorite`).catch(() => {}); + if (shop !== "custom") { + HydraApi.put(`/profile/games/${shop}/${objectId}/unfavorite`).catch( + () => {} + ); + } try { await gamesSublevel.put(gameKey, { From feedcb1dc7511fc336a3e7f15ed3ef6085d9f32c Mon Sep 17 00:00:00 2001 From: Moyasee Date: Wed, 29 Oct 2025 16:49:51 +0200 Subject: [PATCH 093/126] feat: disabled assets request for custom games --- src/main/events/catalogue/get-game-assets.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/events/catalogue/get-game-assets.ts b/src/main/events/catalogue/get-game-assets.ts index de1d2b1f..0e45f886 100644 --- a/src/main/events/catalogue/get-game-assets.ts +++ b/src/main/events/catalogue/get-game-assets.ts @@ -6,6 +6,10 @@ import { gamesShopAssetsSublevel, levelKeys } from "@main/level"; const LOCAL_CACHE_EXPIRATION = 1000 * 60 * 60 * 8; // 8 hours export const getGameAssets = async (objectId: string, shop: GameShop) => { + if (shop === "custom") { + return null; + } + const cachedAssets = await gamesShopAssetsSublevel.get( levelKeys.game(shop, objectId) ); From c24ad34bc785acc9c0ccdb15be4268792807921c Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:42:39 -0300 Subject: [PATCH 094/126] fix: hltb and achievements being called for custom games --- src/main/events/catalogue/get-game-shop-details.ts | 2 ++ src/main/events/library/remove-game-from-library.ts | 2 +- src/renderer/src/pages/game-details/game-details-content.tsx | 4 ++-- src/types/game.types.ts | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/events/catalogue/get-game-shop-details.ts b/src/main/events/catalogue/get-game-shop-details.ts index d6d27b9c..1a7fc455 100644 --- a/src/main/events/catalogue/get-game-shop-details.ts +++ b/src/main/events/catalogue/get-game-shop-details.ts @@ -26,6 +26,8 @@ const getGameShopDetails = async ( shop: GameShop, language: string ): Promise => { + if (shop === "custom") return null; + if (shop === "steam") { const [cachedData, cachedAssets] = await Promise.all([ gamesShopCacheSublevel.get( diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index fbb60ab2..95133c70 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -84,7 +84,7 @@ const removeGameFromLibrary = async ( await resetShopAssets(gameKey); } - if (game?.remoteId) { + if (game.remoteId) { HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {}); } 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 ab51a212..63c4c974 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -228,7 +228,7 @@ export function GameDetailsContent() { )} - {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 095/126] 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; } } From 499a830e3ec1bd50124f97c515be01363ecf770e Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 29 Oct 2025 18:23:06 +0000 Subject: [PATCH 096/126] chore: sync with main --- .github/workflows/release.yml | 1 - .../src/components/text-field/text-field.tsx | 16 ++++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7408665f..df01b358 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,6 @@ concurrency: on: push: branches: - - main - release/** jobs: diff --git a/src/renderer/src/components/text-field/text-field.tsx b/src/renderer/src/components/text-field/text-field.tsx index 7c0cbb58..76759126 100644 --- a/src/renderer/src/components/text-field/text-field.tsx +++ b/src/renderer/src/components/text-field/text-field.tsx @@ -4,10 +4,11 @@ import { useTranslation } from "react-i18next"; import cn from "classnames"; import "./text-field.scss"; -export interface TextFieldProps extends React.DetailedHTMLProps< - React.InputHTMLAttributes, - HTMLInputElement -> { +export interface TextFieldProps + extends React.DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement + > { theme?: "primary" | "dark"; label?: string | React.ReactNode; hint?: string | React.ReactNode; @@ -42,7 +43,10 @@ export const TextField = React.forwardRef( const [isPasswordVisible, setIsPasswordVisible] = useState(false); const { t } = useTranslation("forms"); const showPasswordToggleButton = props.type === "password"; - const inputType = props.type === "password" && isPasswordVisible ? "text" : props.type ?? "text"; + const inputType = + props.type === "password" && isPasswordVisible + ? "text" + : (props.type ?? "text"); const hintContent = error ? ( {error} ) : hint ? ( @@ -106,4 +110,4 @@ export const TextField = React.forwardRef( ); } ); -TextField.displayName = "TextField"; \ No newline at end of file +TextField.displayName = "TextField"; From 49df40650c8e8779b7cfff917441ead54df971f2 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:27:36 -0300 Subject: [PATCH 097/126] chore: prettier --- .../src/components/text-field/text-field.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/components/text-field/text-field.tsx b/src/renderer/src/components/text-field/text-field.tsx index 7c0cbb58..76759126 100644 --- a/src/renderer/src/components/text-field/text-field.tsx +++ b/src/renderer/src/components/text-field/text-field.tsx @@ -4,10 +4,11 @@ import { useTranslation } from "react-i18next"; import cn from "classnames"; import "./text-field.scss"; -export interface TextFieldProps extends React.DetailedHTMLProps< - React.InputHTMLAttributes, - HTMLInputElement -> { +export interface TextFieldProps + extends React.DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement + > { theme?: "primary" | "dark"; label?: string | React.ReactNode; hint?: string | React.ReactNode; @@ -42,7 +43,10 @@ export const TextField = React.forwardRef( const [isPasswordVisible, setIsPasswordVisible] = useState(false); const { t } = useTranslation("forms"); const showPasswordToggleButton = props.type === "password"; - const inputType = props.type === "password" && isPasswordVisible ? "text" : props.type ?? "text"; + const inputType = + props.type === "password" && isPasswordVisible + ? "text" + : (props.type ?? "text"); const hintContent = error ? ( {error} ) : hint ? ( @@ -106,4 +110,4 @@ export const TextField = React.forwardRef( ); } ); -TextField.displayName = "TextField"; \ No newline at end of file +TextField.displayName = "TextField"; From 2fb44a6c0e4fd899f85500ccad1c114d5481e5f2 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:49:43 -0300 Subject: [PATCH 098/126] chore: remove build renderer trigger on main --- .github/workflows/build-renderer.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index 2904bcb8..34f7d303 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -7,7 +7,6 @@ concurrency: on: push: branches: - - main - release/** jobs: From 53c162f0e49f91250e61ef910fb76f9ae3f0524b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:55:55 -0300 Subject: [PATCH 099/126] feat: add i18n --- src/locales/es/translation.json | 4 +++- src/locales/pt-BR/translation.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index dfa7f7a1..863b8332 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -541,7 +541,9 @@ "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" + "enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego", + "autoplay_trailers_on_game_page": "Reproducir trailers automáticamente en la página del juego", + "hide_to_tray_on_game_start": "Ocultar Hydra en la bandeja al iniciar un juego" }, "notifications": { "download_complete": "Descarga completada", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index c9e908ac..5bfc2af3 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -538,7 +538,9 @@ "hidden": "Oculta", "test_notification": "Testar notificação", "notification_preview": "Prévia da Notificação de Conquistas", - "enable_friend_start_game_notifications": "Quando um amigo iniciar um jogo" + "enable_friend_start_game_notifications": "Quando um amigo iniciar um jogo", + "autoplay_trailers_on_game_page": "Reproduzir trailers automaticamente na página do jogo", + "hide_to_tray_on_game_start": "Ocultar o Hydra na bandeja ao iniciar um jogo" }, "notifications": { "download_complete": "Download concluído", From 0990951183325aac65245911898f750932ed1ca6 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:06:46 -0300 Subject: [PATCH 100/126] chore: fix aur package --- .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 2a3583bc..4ce84f3c 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -98,6 +98,7 @@ jobs: # Update pkgver in PKGBUILD cd hydra-launcher-bin NEW_VERSION="${{ steps.get-version.outputs.version }}" + NEW_VERSION="${NEW_VERSION#v}" echo "Updating PKGBUILD pkgver to $NEW_VERSION" From 65e49550ad8f0f85c32f70f8c4ac2af79d62f22f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:10:27 -0300 Subject: [PATCH 101/126] chore: fix aur package --- .github/workflows/update-aur.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 4ce84f3c..52fe907e 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -138,6 +138,9 @@ jobs: COMMIT_MSG="v${{ steps.get-version.outputs.version }}" git commit -m "$COMMIT_MSG" + + export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -F ~/.ssh/config -o UserKnownHostsFile=$SSH_PATH/known_hosts" + git push origin master echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}" fi From 0e7e53478a5e02ca9b09769e15b2a8f408d459d5 Mon Sep 17 00:00:00 2001 From: Wkeynhk <86107421+Wkeynhk@users.noreply.github.com> Date: Thu, 30 Oct 2025 00:47:30 +0300 Subject: [PATCH 102/126] Update translation.json --- src/locales/ru/translation.json | 41 ++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 886c7d07..15a9c9cb 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -212,6 +212,7 @@ "stats": "Статистика", "download_count": "Загрузки", "player_count": "Активные игроки", + "rating_count": "Оценка", "download_error": "Этот вариант загрузки недоступен", "download": "Скачать", "executable_path_in_use": "Исполняемый файл уже используется \"{{game}}\"", @@ -252,17 +253,6 @@ "would_you_recommend_this_game": "Хотите оставить отзыв об этой игре?", "yes": "Да", "maybe_later": "Возможно позже", - "rating_count": "Оценка", - "delete_review": "Удалить отзыв", - "remove_review": "Удалить отзыв", - "delete_review_modal_title": "Вы уверены, что хотите удалить свой отзыв?", - "delete_review_modal_description": "Это действие нельзя отменить.", - "delete_review_modal_delete_button": "Удалить", - "delete_review_modal_cancel_button": "Отмена", - "show_original": "Показать оригинал", - "show_translation": "Показать перевод", - "show_original_translated_from": "Показать оригинал (переведено с {{language}})", - "hide_original": "Скрыть оригинал", "cloud_save": "Облачное сохранение", "cloud_save_description": "Сохраняйте ваш прогресс в облаке и продолжайте играть на любом устройстве", "backups": "Резервные копии", @@ -360,7 +350,18 @@ "caption": "Субтитры", "audio": "Аудио", "filter_by_source": "Фильтр по источнику", - "no_repacks_found": "Источники для этой игры не найдены" + "no_repacks_found": "Источники для этой игры не найдены", + "delete_review": "Удалить отзыв", + "remove_review": "Удалить отзыв", + "delete_review_modal_title": "Вы уверены, что хотите удалить свой отзыв?", + "delete_review_modal_description": "Это действие нельзя отменить.", + "delete_review_modal_delete_button": "Удалить", + "delete_review_modal_cancel_button": "Отмена", + "vote_failed": "Не удалось зарегистрировать ваш голос. Пожалуйста, попробуйте снова.", + "show_original": "Показать оригинал", + "show_translation": "Показать перевод", + "show_original_translated_from": "Показать оригинал (переведено с {{language}})", + "hide_original": "Скрыть оригинал" }, "activation": { "title": "Активировать Hydra", @@ -427,6 +428,9 @@ "validate_download_source": "Проверить", "remove_download_source": "Удалить", "add_download_source": "Добавить источник", + "adding": "Добавление…", + "failed_add_download_source": "Не удалось добавить источник. Пожалуйста, попробуйте снова.", + "download_source_already_exists": "Этот URL источника уже существует.", "download_count_zero": "В списке нет загрузок", "download_count_one": "{{countFormatted}} загрузка в списке", "download_count_other": "{{countFormatted}} загрузок в списке", @@ -434,9 +438,16 @@ "add_download_source_description": "Вставьте ссылку на .json-файл", "download_source_up_to_date": "Обновлён", "download_source_errored": "Ошибка", + "download_source_pending_matching": "Скоро обновится", + "download_source_matched": "Обновлен", + "download_source_matching": "Обновление", + "download_source_failed": "Ошибка", + "download_source_no_information": "Информация отсутствует", "sync_download_sources": "Обновить источники", "removed_download_source": "Источник удален", "removed_download_sources": "Источники удалены", + "removed_all_download_sources": "Все источники удалены", + "download_sources_synced_successfully": "Все источники синхронизированы", "cancel_button_confirmation_delete_all_sources": "Нет", "confirm_button_confirmation_delete_all_sources": "Да, удалить все", "title_confirmation_delete_all_sources": "Удалить все источники", @@ -467,6 +478,7 @@ "seed_after_download_complete": "Раздавать после завершения загрузки", "show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением", "account": "Аккаунт", + "hydra_cloud": "Hydra Cloud", "no_users_blocked": "У вас нет заблокированных пользователей", "subscription_active_until": "Ваша подписка на Hydra Cloud активна до {{date}}", "manage_subscription": "Управлять подпиской", @@ -540,7 +552,9 @@ "hidden": "Скрытый", "test_notification": "Тестовое уведомление", "notification_preview": "Предварительный просмотр уведомления о достижении", - "enable_friend_start_game_notifications": "Когда друг начинает играть в игру" + "enable_friend_start_game_notifications": "Когда друг начинает играть в игру", + "autoplay_trailers_on_game_page": "Автоматически начинать воспроизведение трейлеров на странице игры", + "hide_to_tray_on_game_start": "Скрывать Hydra в трей при запуске игры" }, "notifications": { "download_complete": "Загрузка завершена", @@ -590,6 +604,7 @@ "activity": "Недавняя активность", "library": "Библиотека", "pinned": "Закрепленные", + "sort_by": "Сортировать по:", "achievements_earned": "Заработанные достижения", "played_recently": "Недавно сыгранные", "playtime": "Время игры", From 4dd28bbbf149f138d9de384dc9eaba220d306b7f Mon Sep 17 00:00:00 2001 From: "Kiwo.2" Date: Thu, 30 Oct 2025 01:12:29 +0100 Subject: [PATCH 103/126] Hungarian Translation 3.7.2 --- src/locales/hu/translation.json | 96 ++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index b72811d3..a91902dc 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -8,7 +8,7 @@ "no_results": "Nincs találat", "start_typing": "Kereséshez gépelj...", "hot": "Most felkapott", - "weekly": "📅 A hét felkapott játékai", + "weekly": "📅 A hét felkapottjai", "achievements": "🏆 Achievement támogatott" }, "sidebar": { @@ -26,7 +26,7 @@ "sign_in": "Bejelentkezés", "friends": "Barátok", "need_help": "Elakadtál?", - "favorites": "Kedvenc játékok", + "favorites": "Kedvenc Játékaim", "playable_button_title": "Csak az azonnal játszható játékokat mutasd", "add_custom_game_tooltip": "Saját játék hozzáadása", "show_playable_only_tooltip": "Csak játszható játék mutatása", @@ -76,8 +76,8 @@ "edit_game_modal_drop_hero_image_here": "Húzd ide a borítókép képét", "edit_game_modal_drop_to_replace_icon": "Ikon kicserélése ráhúzással", "edit_game_modal_drop_to_replace_logo": "Logó kicserélése ráhúzással", - "edit_game_modal_drop_to_replace_hero": "Borítókép kicserélése ráhúzással", - "install_decky_plugin": "Decky Plugin Telepítése", + "edit_game_modal_drop_to_replace_hero": "Borítókép kicserélése ráhúzással", + "install_decky_plugin": "Decky Plugin Telepítése", "update_decky_plugin": "Decky Plugin Frissítése", "decky_plugin_installed_version": "Decky Plugin (v{{version}})", "install_decky_plugin_title": "Telepítsd a Hydra Decky Plugint", @@ -88,7 +88,7 @@ "decky_plugin_installation_failed": "Decky plugin telepítése sikertelen: {{error}}", "decky_plugin_installation_error": "Decky plugin telepítése hibával járt el: {{error}}", "confirm": "Megerősít", - "cancel": "Mégse" + "cancel": "Mégse" }, "header": { "search": "Keresés", @@ -120,7 +120,7 @@ "result_count": "{{resultCount}} találatok", "filter_count": "{{filterCount}} elérhető", "clear_filters": "{{filterCount}} kiválaszott szűrő törlése" - }, + }, "game_details": { "open_download_options": "Letöltési opciók megnyitása", "download_options_zero": "Nincs letöltési opció", @@ -178,13 +178,13 @@ "open_folder": "Mappa megnyitása", "open_download_location": "Letöltött fájlok megtekintése", "create_shortcut": "Asztali parancsikon létrehozása", - "create_shortcut_simple": "Parancsikon létrehozása", + "create_shortcut_simple": "Parancsikon létrehozása", "clear": "Visszavon", "remove_files": "Fájlok eltávolítása", "remove_from_library_title": "Biztos vagy ebben?", "remove_from_library_description": "Ezzel eltávolítod a játékot {{game}} a könyvtáradból", - "options": "Beállítások", - "properties": "További beállítások", + "options": "Beállítások",ä + "properties": "További beállítások", "executable_section_title": "Futtatható fájl", "executable_section_description": "A fájl amely futtatásra fog kerülni amikor a \"Játék\" lenyomásra kerül", "downloads_section_title": "Letöltések", @@ -204,27 +204,27 @@ "game_removed_from_library": "Játék eltávolítva a könyvtárból", "failed_remove_from_library": "Játék eltávolítása a könyvtárból sikertelen", "files_removed_success": "Fájlok eltávolítása sikeres", - "failed_remove_files": "Fájlok eltávolítása sikertelen", + "failed_remove_files": "Fájlok eltávolítása sikertelen", "nsfw_content_title": "Ez a játék tartalmaz nem megfelelő tartalmat", - "nsfw_content_description": "A(z) {{title}} tartalma lehetséges hogy nem megfelelő minden korosztály számára. Biztosan folytatni szeretnéd?", + "nsfw_content_description": "A(z) {{title}} tartalma lehetséges hogy nem megfelelő minden korosztály számára. Biztosan folytatni szeretnéd?", "allow_nsfw_content": "Folytatás", "refuse_nsfw_content": "Vissza", "stats": "Statisztikák", "download_count": "Letöltések", "player_count": "Aktív játékosok", - "rating_count": "Értékelés", - "download_error": "Ez a letöltési opció nem elérhető", + "rating_count": "Értékelés", + "download_error": "Ez a letöltési opció nem elérhető", "download": "Letöltés", "executable_path_in_use": "Ez a futtatható fájl már használatban van a(z) \"{{game}}\" által", "warning": "Figyelmeztetés:", "hydra_needs_to_remain_open": "ehhez a letöltéshez, a Hydrának muszáj nyitva maradnia hogy letöltődjön. Ha a Hydra bezáródik letöltés előtt, a letöltés elveszik.", "achievements": "Achievementek", "achievements_count": "Achievementek {{unlockedCount}}/{{achievementsCount}}", - "show_more": "Mutass többet", + "show_more": "Mutass többet", "show_less": "Mutass kevesebbet", "reviews": "Vélemények", "leave_a_review": "Hagyd itt a véleményed", - "write_review_placeholder": "Oszd meg a gondolataid a játékról...", + "write_review_placeholder": "Oszd meg gondolatod a játékról...", "sort_newest": "Legújabb", "no_reviews_yet": "Még nem lett vélemény megosztva", "be_first_to_review": "Légy az első, aki megossza a véleményét a játékról!", @@ -252,7 +252,7 @@ "you_seemed_to_enjoy_this_game": "Úgy látszik élvezted ezt a játékot", "would_you_recommend_this_game": "Szeretnél véleményt írni erről a játékról?", "yes": "Igen", - "maybe_later": "Talán Később", + "maybe_later": "Talán később", "cloud_save": "Mentés felhőben", "cloud_save_description": "Mentsd el az előrehaladásod a felhőben, majd folytasd egy másik eszközön", "backups": "Biztonsági másolatok", @@ -350,19 +350,24 @@ "caption": "Felirat", "audio": "Hang", "filter_by_source": "Szűrés forrás szerint", - "no_repacks_found": "Nem található forrás ehhez a játékhoz" , - "delete_review": "Vélemény törlése", + "no_repacks_found": "Nem található forrás ehhez a játékhoz", + "delete_review": "Vélemény törlése", "remove_review": "Vélemény eltávolítása", "delete_review_modal_title": "Biztos vagy abban hogy törölni szeretnéd a véleményed?", "delete_review_modal_description": "Ez a lépés nem vonható vissza.", "delete_review_modal_delete_button": "Törlés", - "delete_review_modal_cancel_button": "Mégse" + "delete_review_modal_cancel_button": "Mégse", + "vote_failed": "A szavazatod nem regisztrálódott. Kérlek próbáld újra.", + "show_original": "Eredeti megjelenítése", + "show_translation": "Fordítás megjelenítése", + "show_original_translated_from": "Eredeti megjelenítése (fordítva: {{language}})", + "hide_original": "Eredeti elrejtése" }, "activation": { "title": "Hydra Aktiválása", "installation_id": "Telepítési Azonosító:", "enter_activation_code": "Írd be az aktiválási kódod", - "message": "Ha nem tudod hol kérdezz efelől, akkor nem kéne ilyened legyen.", + "message": "Ha nem tudod merre kérdezz efelől, akkor nem kéne ilyened legyen.", "activate": "Aktiválás", "loading": "Töltés…" }, @@ -394,7 +399,6 @@ "stop_seeding": "Seedelés leállítása", "resume_seeding": "Seedelés folytatása", "options": "Kezelés", - "alldebrid_size_not_supported": "Letöltési információ az AllDebrid-hez még nem támogatott", "extract": "Fájlok kibontása", "extracting": "Fájlok kibontása…" }, @@ -420,20 +424,30 @@ "debrid_linked_message": "Fiók összekapcsolva: \"{{username}}\" ", "save_changes": "Változtatások mentése", "changes_saved": "Változtatások sikeresen mentve", - "download_sources_description": "A Hydra lefogja tölteni a letöltési linkeket a forrásokból. Az URL forrásnak közvetlen linknek kell lennie egy .json fájlhoz, ami tartalmazza a linkeket.", + "download_sources_description": "A Hydra lefogja tölteni a letöltési linkeket a forrásokból. Az URL Forrásnak közvetlen linknek kell lennie egy .json fájlhoz, ami tartalmazza a linkeket.", "validate_download_source": "Érvényesítés", "remove_download_source": "Eltávolítás", "add_download_source": "Forrás hozáadása", + "adding": "Hozzáadás…", + "failed_add_download_source": "Letöltési forrás hozzáadása sikertelen. Kérlek próbáld újra.", + "download_source_already_exists": "Ez a letöltési forrás URL már létezik.", "download_count_zero": "Nincs letöltési opció", "download_count_one": "{{countFormatted}} letöltési opció", "download_count_other": "{{countFormatted}} letöltési opció", - "download_source_url": "URL forrás:", + "download_source_url": "URL Forrás:", "add_download_source_description": "Helyezd be a .json fájl URL-jét", "download_source_up_to_date": "Naprakész", "download_source_errored": "Hiba történt", + "download_source_pending_matching": "Frissítés hamarosan", + "download_source_matched": "Naprakész", + "download_source_matching": "Frissítés..", + "download_source_failed": "Hiba", + "download_source_no_information": "Nincs elérhető információ", "sync_download_sources": "Források szinkronizálása", "removed_download_source": "Letöltési forrás eltávolítva", "removed_download_sources": "Letöltési források eltávolítva", + "removed_all_download_sources": "Összes letöltési forrás eltávolítva", + "download_sources_synced_successfully": "Az összes letöltési forrás szinkronizálva", "cancel_button_confirmation_delete_all_sources": "Nem", "confirm_button_confirmation_delete_all_sources": "Igen, törölj mindent", "title_confirmation_delete_all_sources": "Az összes letöltési forrás törlése", @@ -446,6 +460,7 @@ "found_download_option_one": "{{countFormatted}} Letöltési opció találva", "found_download_option_other": "{{countFormatted}} Letöltési opciók találva", "import": "Importálás", + "importing": "Importálás...", "public": "Publikus", "private": "Privát", "friends_only": "Csak barátok", @@ -463,6 +478,7 @@ "seed_after_download_complete": "Letöltés utáni seedelés", "show_hidden_achievement_description": "Rejtett achievementek leírásának megjelenítése feloldás előtt", "account": "Fiók", + "hydra_cloud": "Hydra Cloud", "no_users_blocked": "Nincsenek letiltott felhasználóid", "subscription_active_until": "Hydra Cloud előfizetésed aktív, eddig: {{date}}", "manage_subscription": "Előfizetés kezelése", @@ -498,26 +514,15 @@ "delete_theme_description": "Ez törölni fogja a(z) {{theme}} témát", "cancel": "Mégsem", "appearance": "Megjelenés", - "debrid": "Debrid", - "debrid_description": "A Debrid szolgáltatások prémium szolgáltatások amelyek lehetővé teszik, hogy gyorsan letölts különböző fájltároló szolgáltatásokon tárolt fájlokat, csak az internet sebességed szab határt.", + "debrid": "Debrid", + "debrid_description": "A Debrid szolgáltatások prémium szolgáltatások amelyek lehetővé teszik, hogy gyorsan letölts különböző fájltároló szolgáltatásokon tárolt fájlokat, és csak az internet sebességed szab határt.", "enable_torbox": "TorBox bekapcsolása", "torbox_description": "A TorBox egy olyan premium seedbox szolgáltatás, amely még a piacon elérhető legjobb szerverekkel is felveszi a versenyt.", "torbox_account_linked": "TorBox fiók összekapcsolva", "create_real_debrid_account": "Kattints ide ha még nincs Real-Debrid fiókod", "create_torbox_account": "Kattints ide ha még nincs TorBox fiókod", "real_debrid_account_linked": "Real-Debrid fiók összekapcsolva", - "enable_all_debrid": "All-Debrid bekapcsolása", - "all_debrid_description": "Az All-Debrid egy korlátozásmentes letöltőprogram, ami lehetővé teszi a fájlok gyors letöltését különböző forrásokból.", - "all_debrid_free_account_error": "Ez a fiók: \"{{username}}\" egy ingyenes fiók. Kérlek iratkozz fel az All-Debridre", - "all_debrid_account_linked": "All-Debrid fiók összekapcsolva", - "alldebrid_missing_key": "Kérlek adj meg egy API key-t", - "alldebrid_invalid_key": "Érvénytelen API key", - "alldebrid_blocked": "Az API key-ed Földrajzilag vagy IP-alapján van blokkolva", - "alldebrid_banned": "Ez a fiók kitiltásra került", - "alldebrid_unknown_error": "Egy ismeretlen hiba történt", - "alldebrid_invalid_response": "Érvénytelen válasz az All-Debrid felől", - "alldebrid_network_error": "Hálózati hiba. Ellenőrízd az internetkapcsolatod", - "name_min_length": "A téma neve legalább 3 karakter hosszú legyen", + "name_min_length": "A téma neve legalább 3 karakter hosszú kell legyen", "import_theme": "Téma importálása", "import_theme_description": "Ezt a témát fogod importálni a Témaáruház-ból: {{theme}}", "error_importing_theme": "Hiba lépett fel a téma importálása közben", @@ -548,6 +553,8 @@ "test_notification": "Értesítés tesztelése", "notification_preview": "Achievement Értesítés Előnézete", "enable_friend_start_game_notifications": "Amikor egy barátod elkezd játszani egy játékot" + "autoplay_trailers_on_game_page": "Játékelőzetes automatikus lejátszása a játék oldalán", + "hide_to_tray_on_game_start": "Hydra elrejtése játék elindításakor a tálcára" }, "notifications": { "download_complete": "Letöltés befejezve", @@ -574,11 +581,11 @@ "game_card": { "available_one": "Elérhető", "available_other": "Elérhető", - "no_downloads": "Nincs elérhető letöltés" - "calculating": "Feldolgozás" + "no_downloads": "Nincs elérhető letöltés", + "calculating": "Számítás alatt.." }, "binary_not_found_modal": { - "title": "A programok nincsenek telepítve", + "title": "Hiányzó programok", "description": "Wine vagy Lutris futtatható fájlok nem találhatók a rendszereden", "instructions": "Ellenőrízd hogy melyiket kell helyesen telepíteni a Linux disztribúciódra, hogy a játék megfelelően fusson" }, @@ -597,6 +604,7 @@ "activity": "Legutóbbi tevékenység", "library": "Könyvtár", "pinned": "Kitűzve", + "sort_by": "Rendezés:", "achievements_earned": "Elért achievementek", "played_recently": "Nemrég játszva", "playtime": "Játszottidő", @@ -666,7 +674,7 @@ "uploading_banner": "Borítókép feltöltése…", "background_image_updated": "Borítókép frissítve", "stats": "Statisztikák", - "achievements": "achievementek", + "achievements": "achievement", "games": "Játékok", "top_percentile": "Top {{percentile}}%", "ranking_updated_weekly": "A rangsor hetente frissül.", @@ -678,10 +686,10 @@ "error_adding_friend": "Hiba, barátfelkérés sikertelen. Kérlek ellenőrízd a barát kódot", "friend_code_length_error": "A barát kódnak 8 karakterből kell állnia", "game_removed_from_pinned": "Játék eltávolítva a kitűzöttek közül", - "game_added_to_pinned": "Játék hozzáadva a kitűzöttekhez" + "game_added_to_pinned": "Játék hozzáadva a kitűzöttekhez", "karma": "Karma", "karma_count": "karma", - "karma_description": "Pozitív értékelésekre kapott pontok alapján" + "karma_description": "Pozitív értékelésekkel szerzett pontok" }, "achievement": { "achievement_unlocked": "Achievement feloldva", @@ -690,7 +698,7 @@ "unlocked_at": "Feloldva: {{date}}", "subscription_needed": "A tartalom megtekintéséhez Hydra Cloud előfizetés szükséges", "new_achievements_unlocked": "{{achievementCount}} új achievement feloldva {{gameCount}} játékban", - "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievementek", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievement", "achievements_unlocked_for_game": "{{achievementCount}} új achievement feloldva itt: {{gameTitle}}", "hidden_achievement_tooltip": "Ez egy rejtett achievement", "achievement_earn_points": "Szerezz be {{points}} pontot ezzel az achievement-el", From 41092c2dd442c4efbf2ea15604df1cfc34a0c8be Mon Sep 17 00:00:00 2001 From: "Kiwo.2" Date: Thu, 30 Oct 2025 01:35:54 +0100 Subject: [PATCH 104/126] Update to 3.7.2 --- src/locales/hu/translation.json | 36 --------------------------------- 1 file changed, 36 deletions(-) diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index 88039aee..a91902dc 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -183,11 +183,7 @@ "remove_files": "Fájlok eltávolítása", "remove_from_library_title": "Biztos vagy ebben?", "remove_from_library_description": "Ezzel eltávolítod a játékot {{game}} a könyvtáradból", -<<<<<<< HEAD "options": "Beállítások",ä -======= - "options": "Beállítások", ->>>>>>> 21074322fa5ef3a1d6168a2b841ec2505db8f0de "properties": "További beállítások", "executable_section_title": "Futtatható fájl", "executable_section_description": "A fájl amely futtatásra fog kerülni amikor a \"Játék\" lenyomásra kerül", @@ -228,11 +224,7 @@ "show_less": "Mutass kevesebbet", "reviews": "Vélemények", "leave_a_review": "Hagyd itt a véleményed", -<<<<<<< HEAD "write_review_placeholder": "Oszd meg gondolatod a játékról...", -======= - "write_review_placeholder": "Oszd meg a gondolataid a játékról...", ->>>>>>> 21074322fa5ef3a1d6168a2b841ec2505db8f0de "sort_newest": "Legújabb", "no_reviews_yet": "Még nem lett vélemény megosztva", "be_first_to_review": "Légy az első, aki megossza a véleményét a játékról!", @@ -260,11 +252,7 @@ "you_seemed_to_enjoy_this_game": "Úgy látszik élvezted ezt a játékot", "would_you_recommend_this_game": "Szeretnél véleményt írni erről a játékról?", "yes": "Igen", -<<<<<<< HEAD "maybe_later": "Talán később", -======= - "maybe_later": "Talán Később", ->>>>>>> 21074322fa5ef3a1d6168a2b841ec2505db8f0de "cloud_save": "Mentés felhőben", "cloud_save_description": "Mentsd el az előrehaladásod a felhőben, majd folytasd egy másik eszközön", "backups": "Biztonsági másolatok", @@ -368,16 +356,12 @@ "delete_review_modal_title": "Biztos vagy abban hogy törölni szeretnéd a véleményed?", "delete_review_modal_description": "Ez a lépés nem vonható vissza.", "delete_review_modal_delete_button": "Törlés", -<<<<<<< HEAD "delete_review_modal_cancel_button": "Mégse", "vote_failed": "A szavazatod nem regisztrálódott. Kérlek próbáld újra.", "show_original": "Eredeti megjelenítése", "show_translation": "Fordítás megjelenítése", "show_original_translated_from": "Eredeti megjelenítése (fordítva: {{language}})", "hide_original": "Eredeti elrejtése" -======= - "delete_review_modal_cancel_button": "Mégse" ->>>>>>> 21074322fa5ef3a1d6168a2b841ec2505db8f0de }, "activation": { "title": "Hydra Aktiválása", @@ -531,22 +515,14 @@ "cancel": "Mégsem", "appearance": "Megjelenés", "debrid": "Debrid", -<<<<<<< HEAD "debrid_description": "A Debrid szolgáltatások prémium szolgáltatások amelyek lehetővé teszik, hogy gyorsan letölts különböző fájltároló szolgáltatásokon tárolt fájlokat, és csak az internet sebességed szab határt.", -======= - "debrid_description": "A Debrid szolgáltatások prémium szolgáltatások amelyek lehetővé teszik, hogy gyorsan letölts különböző fájltároló szolgáltatásokon tárolt fájlokat, csak az internet sebességed szab határt.", ->>>>>>> 21074322fa5ef3a1d6168a2b841ec2505db8f0de "enable_torbox": "TorBox bekapcsolása", "torbox_description": "A TorBox egy olyan premium seedbox szolgáltatás, amely még a piacon elérhető legjobb szerverekkel is felveszi a versenyt.", "torbox_account_linked": "TorBox fiók összekapcsolva", "create_real_debrid_account": "Kattints ide ha még nincs Real-Debrid fiókod", "create_torbox_account": "Kattints ide ha még nincs TorBox fiókod", "real_debrid_account_linked": "Real-Debrid fiók összekapcsolva", -<<<<<<< HEAD "name_min_length": "A téma neve legalább 3 karakter hosszú kell legyen", -======= - "name_min_length": "A téma neve legalább 3 karakter hosszú legyen", ->>>>>>> 21074322fa5ef3a1d6168a2b841ec2505db8f0de "import_theme": "Téma importálása", "import_theme_description": "Ezt a témát fogod importálni a Témaáruház-ból: {{theme}}", "error_importing_theme": "Hiba lépett fel a téma importálása közben", @@ -606,11 +582,7 @@ "available_one": "Elérhető", "available_other": "Elérhető", "no_downloads": "Nincs elérhető letöltés", -<<<<<<< HEAD "calculating": "Számítás alatt.." -======= - "calculating": "Feldolgozás" ->>>>>>> 21074322fa5ef3a1d6168a2b841ec2505db8f0de }, "binary_not_found_modal": { "title": "Hiányzó programok", @@ -717,11 +689,7 @@ "game_added_to_pinned": "Játék hozzáadva a kitűzöttekhez", "karma": "Karma", "karma_count": "karma", -<<<<<<< HEAD "karma_description": "Pozitív értékelésekkel szerzett pontok" -======= - "karma_description": "Pozitív értékelésekre kapott pontok alapján" ->>>>>>> 21074322fa5ef3a1d6168a2b841ec2505db8f0de }, "achievement": { "achievement_unlocked": "Achievement feloldva", @@ -730,11 +698,7 @@ "unlocked_at": "Feloldva: {{date}}", "subscription_needed": "A tartalom megtekintéséhez Hydra Cloud előfizetés szükséges", "new_achievements_unlocked": "{{achievementCount}} új achievement feloldva {{gameCount}} játékban", -<<<<<<< HEAD "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievement", -======= - "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievementek", ->>>>>>> 21074322fa5ef3a1d6168a2b841ec2505db8f0de "achievements_unlocked_for_game": "{{achievementCount}} új achievement feloldva itt: {{gameTitle}}", "hidden_achievement_tooltip": "Ez egy rejtett achievement", "achievement_earn_points": "Szerezz be {{points}} pontot ezzel az achievement-el", From 90c5ccb7969e16554218007c35f1fccf606f9a5a Mon Sep 17 00:00:00 2001 From: "Kiwo.2" Date: Thu, 30 Oct 2025 01:40:36 +0100 Subject: [PATCH 105/126] Update to 3.7.2 --- src/locales/hu/translation.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index a91902dc..3e0c408a 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -183,7 +183,7 @@ "remove_files": "Fájlok eltávolítása", "remove_from_library_title": "Biztos vagy ebben?", "remove_from_library_description": "Ezzel eltávolítod a játékot {{game}} a könyvtáradból", - "options": "Beállítások",ä + "options": "Beállítások", "properties": "További beállítások", "executable_section_title": "Futtatható fájl", "executable_section_description": "A fájl amely futtatásra fog kerülni amikor a \"Játék\" lenyomásra kerül", @@ -428,7 +428,7 @@ "validate_download_source": "Érvényesítés", "remove_download_source": "Eltávolítás", "add_download_source": "Forrás hozáadása", - "adding": "Hozzáadás…", + "adding": "Hozzáadás…", "failed_add_download_source": "Letöltési forrás hozzáadása sikertelen. Kérlek próbáld újra.", "download_source_already_exists": "Ez a letöltési forrás URL már létezik.", "download_count_zero": "Nincs letöltési opció", @@ -438,7 +438,7 @@ "add_download_source_description": "Helyezd be a .json fájl URL-jét", "download_source_up_to_date": "Naprakész", "download_source_errored": "Hiba történt", - "download_source_pending_matching": "Frissítés hamarosan", + "download_source_pending_matching": "Hamarosan frissítve", "download_source_matched": "Naprakész", "download_source_matching": "Frissítés..", "download_source_failed": "Hiba", @@ -460,7 +460,7 @@ "found_download_option_one": "{{countFormatted}} Letöltési opció találva", "found_download_option_other": "{{countFormatted}} Letöltési opciók találva", "import": "Importálás", - "importing": "Importálás...", + "importing": "Importálás...", "public": "Publikus", "private": "Privát", "friends_only": "Csak barátok", @@ -478,7 +478,7 @@ "seed_after_download_complete": "Letöltés utáni seedelés", "show_hidden_achievement_description": "Rejtett achievementek leírásának megjelenítése feloldás előtt", "account": "Fiók", - "hydra_cloud": "Hydra Cloud", + "hydra_cloud": "Hydra Cloud", "no_users_blocked": "Nincsenek letiltott felhasználóid", "subscription_active_until": "Hydra Cloud előfizetésed aktív, eddig: {{date}}", "manage_subscription": "Előfizetés kezelése", @@ -553,7 +553,7 @@ "test_notification": "Értesítés tesztelése", "notification_preview": "Achievement Értesítés Előnézete", "enable_friend_start_game_notifications": "Amikor egy barátod elkezd játszani egy játékot" - "autoplay_trailers_on_game_page": "Játékelőzetes automatikus lejátszása a játék oldalán", + "autoplay_trailers_on_game_page": "Játékelőzetes automatikus lejátszása a játék oldalán", "hide_to_tray_on_game_start": "Hydra elrejtése játék elindításakor a tálcára" }, "notifications": { From 3fce26f1f7a0384d14cbef10781e5a1472ecda3b Mon Sep 17 00:00:00 2001 From: "Kiwo.2" Date: Thu, 30 Oct 2025 01:55:15 +0100 Subject: [PATCH 106/126] Update to 3.7.2 --- src/locales/hu/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index 3e0c408a..e4bc7eb0 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -8,7 +8,7 @@ "no_results": "Nincs találat", "start_typing": "Kereséshez gépelj...", "hot": "Most felkapott", - "weekly": "📅 A hét felkapottjai", + "weekly": "📅 Heti kiemeltek", "achievements": "🏆 Achievement támogatott" }, "sidebar": { From 6e76111e2346ca2c3b1c53f4d33b5152884ed072 Mon Sep 17 00:00:00 2001 From: "Kiwo.2" Date: Thu, 30 Oct 2025 02:10:02 +0100 Subject: [PATCH 107/126] Missing Comma Fix --- src/locales/hu/translation.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index e4bc7eb0..d771a8d4 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -8,7 +8,7 @@ "no_results": "Nincs találat", "start_typing": "Kereséshez gépelj...", "hot": "Most felkapott", - "weekly": "📅 Heti kiemeltek", + "weekly": "📅 A hét felkapottjai", "achievements": "🏆 Achievement támogatott" }, "sidebar": { @@ -183,7 +183,7 @@ "remove_files": "Fájlok eltávolítása", "remove_from_library_title": "Biztos vagy ebben?", "remove_from_library_description": "Ezzel eltávolítod a játékot {{game}} a könyvtáradból", - "options": "Beállítások", + "options": "Beállítások",ä "properties": "További beállítások", "executable_section_title": "Futtatható fájl", "executable_section_description": "A fájl amely futtatásra fog kerülni amikor a \"Játék\" lenyomásra kerül", @@ -391,7 +391,7 @@ "download_in_progress": "Folyamatban lévő", "queued_downloads": "Várakozósoron lévő letöltések", "downloads_completed": "Befejezett", - "queued": "Várakozási sorban", + "queued": "Várakozásban", "no_downloads_title": "Oly üres..", "no_downloads_description": "Még nem töltöttél le semmit a Hydra segítségével, de soha nem késő elkezdeni.", "checking_files": "Fájlok ellenőrzése…", @@ -428,7 +428,7 @@ "validate_download_source": "Érvényesítés", "remove_download_source": "Eltávolítás", "add_download_source": "Forrás hozáadása", - "adding": "Hozzáadás…", + "adding": "Hozzáadás…", "failed_add_download_source": "Letöltési forrás hozzáadása sikertelen. Kérlek próbáld újra.", "download_source_already_exists": "Ez a letöltési forrás URL már létezik.", "download_count_zero": "Nincs letöltési opció", @@ -438,7 +438,7 @@ "add_download_source_description": "Helyezd be a .json fájl URL-jét", "download_source_up_to_date": "Naprakész", "download_source_errored": "Hiba történt", - "download_source_pending_matching": "Hamarosan frissítve", + "download_source_pending_matching": "Frissítés hamarosan", "download_source_matched": "Naprakész", "download_source_matching": "Frissítés..", "download_source_failed": "Hiba", @@ -460,7 +460,7 @@ "found_download_option_one": "{{countFormatted}} Letöltési opció találva", "found_download_option_other": "{{countFormatted}} Letöltési opciók találva", "import": "Importálás", - "importing": "Importálás...", + "importing": "Importálás...", "public": "Publikus", "private": "Privát", "friends_only": "Csak barátok", @@ -478,7 +478,7 @@ "seed_after_download_complete": "Letöltés utáni seedelés", "show_hidden_achievement_description": "Rejtett achievementek leírásának megjelenítése feloldás előtt", "account": "Fiók", - "hydra_cloud": "Hydra Cloud", + "hydra_cloud": "Hydra Cloud", "no_users_blocked": "Nincsenek letiltott felhasználóid", "subscription_active_until": "Hydra Cloud előfizetésed aktív, eddig: {{date}}", "manage_subscription": "Előfizetés kezelése", @@ -552,8 +552,8 @@ "hidden": "Rejtett", "test_notification": "Értesítés tesztelése", "notification_preview": "Achievement Értesítés Előnézete", - "enable_friend_start_game_notifications": "Amikor egy barátod elkezd játszani egy játékot" - "autoplay_trailers_on_game_page": "Játékelőzetes automatikus lejátszása a játék oldalán", + "enable_friend_start_game_notifications": "Amikor egy barátod elkezd játszani egy játékot", + "autoplay_trailers_on_game_page": "Játékelőzetes automatikus lejátszása a játék oldalán", "hide_to_tray_on_game_start": "Hydra elrejtése játék elindításakor a tálcára" }, "notifications": { From dcc671f9993c57e8311aa7a0683613a7fb9caab3 Mon Sep 17 00:00:00 2001 From: "Kiwo.2" Date: Thu, 30 Oct 2025 02:15:35 +0100 Subject: [PATCH 108/126] Mistake Correction --- src/locales/hu/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index d771a8d4..909f242b 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -183,7 +183,7 @@ "remove_files": "Fájlok eltávolítása", "remove_from_library_title": "Biztos vagy ebben?", "remove_from_library_description": "Ezzel eltávolítod a játékot {{game}} a könyvtáradból", - "options": "Beállítások",ä + "options": "Beállítások", "properties": "További beállítások", "executable_section_title": "Futtatható fájl", "executable_section_description": "A fájl amely futtatásra fog kerülni amikor a \"Játék\" lenyomásra kerül", From 4ff8dc4fa7a563f282a63ba226cb1205da9df6c5 Mon Sep 17 00:00:00 2001 From: "Kiwo.2" Date: Thu, 30 Oct 2025 02:32:18 +0100 Subject: [PATCH 109/126] Fix with Prettier --- src/locales/hu/translation.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index 909f242b..8aea356b 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -357,7 +357,7 @@ "delete_review_modal_description": "Ez a lépés nem vonható vissza.", "delete_review_modal_delete_button": "Törlés", "delete_review_modal_cancel_button": "Mégse", - "vote_failed": "A szavazatod nem regisztrálódott. Kérlek próbáld újra.", + "vote_failed": "A szavazatod nem regisztrálódott. Kérlek próbáld újra.", "show_original": "Eredeti megjelenítése", "show_translation": "Fordítás megjelenítése", "show_original_translated_from": "Eredeti megjelenítése (fordítva: {{language}})", @@ -428,9 +428,9 @@ "validate_download_source": "Érvényesítés", "remove_download_source": "Eltávolítás", "add_download_source": "Forrás hozáadása", - "adding": "Hozzáadás…", + "adding": "Hozzáadás…", "failed_add_download_source": "Letöltési forrás hozzáadása sikertelen. Kérlek próbáld újra.", - "download_source_already_exists": "Ez a letöltési forrás URL már létezik.", + "download_source_already_exists": "Ez a letöltési forrás URL már létezik.", "download_count_zero": "Nincs letöltési opció", "download_count_one": "{{countFormatted}} letöltési opció", "download_count_other": "{{countFormatted}} letöltési opció", @@ -438,16 +438,16 @@ "add_download_source_description": "Helyezd be a .json fájl URL-jét", "download_source_up_to_date": "Naprakész", "download_source_errored": "Hiba történt", - "download_source_pending_matching": "Frissítés hamarosan", + "download_source_pending_matching": "Frissítés hamarosan", "download_source_matched": "Naprakész", "download_source_matching": "Frissítés..", "download_source_failed": "Hiba", - "download_source_no_information": "Nincs elérhető információ", + "download_source_no_information": "Nincs elérhető információ", "sync_download_sources": "Források szinkronizálása", "removed_download_source": "Letöltési forrás eltávolítva", "removed_download_sources": "Letöltési források eltávolítva", "removed_all_download_sources": "Összes letöltési forrás eltávolítva", - "download_sources_synced_successfully": "Az összes letöltési forrás szinkronizálva", + "download_sources_synced_successfully": "Az összes letöltési forrás szinkronizálva", "cancel_button_confirmation_delete_all_sources": "Nem", "confirm_button_confirmation_delete_all_sources": "Igen, törölj mindent", "title_confirmation_delete_all_sources": "Az összes letöltési forrás törlése", @@ -460,7 +460,7 @@ "found_download_option_one": "{{countFormatted}} Letöltési opció találva", "found_download_option_other": "{{countFormatted}} Letöltési opciók találva", "import": "Importálás", - "importing": "Importálás...", + "importing": "Importálás...", "public": "Publikus", "private": "Privát", "friends_only": "Csak barátok", @@ -478,7 +478,7 @@ "seed_after_download_complete": "Letöltés utáni seedelés", "show_hidden_achievement_description": "Rejtett achievementek leírásának megjelenítése feloldás előtt", "account": "Fiók", - "hydra_cloud": "Hydra Cloud", + "hydra_cloud": "Hydra Cloud", "no_users_blocked": "Nincsenek letiltott felhasználóid", "subscription_active_until": "Hydra Cloud előfizetésed aktív, eddig: {{date}}", "manage_subscription": "Előfizetés kezelése", @@ -553,7 +553,7 @@ "test_notification": "Értesítés tesztelése", "notification_preview": "Achievement Értesítés Előnézete", "enable_friend_start_game_notifications": "Amikor egy barátod elkezd játszani egy játékot", - "autoplay_trailers_on_game_page": "Játékelőzetes automatikus lejátszása a játék oldalán", + "autoplay_trailers_on_game_page": "Játékelőzetes automatikus lejátszása a játék oldalán", "hide_to_tray_on_game_start": "Hydra elrejtése játék elindításakor a tálcára" }, "notifications": { From a2ef0f304daa827d29aeaca4a13e92a4fe87b5fb Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 30 Oct 2025 07:35:49 -0300 Subject: [PATCH 110/126] fix: playtime count and custom games request on process watcher --- .../achievement-watcher-manager.ts | 2 + src/main/services/library-sync/create-game.ts | 4 ++ .../library-sync/update-game-playtime.ts | 4 ++ src/main/services/process-watcher.ts | 44 +++++++++++++------ 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index b862abbe..dd65165a 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -167,6 +167,8 @@ export class AchievementWatcherManager { shop: GameShop, objectId: string ) { + if (shop === "custom") return; + const gameKey = levelKeys.game(shop, objectId); if (this.alreadySyncedGames.get(gameKey)) return; diff --git a/src/main/services/library-sync/create-game.ts b/src/main/services/library-sync/create-game.ts index a346d3b4..e9ec9612 100644 --- a/src/main/services/library-sync/create-game.ts +++ b/src/main/services/library-sync/create-game.ts @@ -3,6 +3,10 @@ import { HydraApi } from "../hydra-api"; import { gamesSublevel, levelKeys } from "@main/level"; export const createGame = async (game: Game) => { + if (game.shop === "custom") { + return; + } + return HydraApi.post(`/profile/games`, { objectId: game.objectId, playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds ?? 0), diff --git a/src/main/services/library-sync/update-game-playtime.ts b/src/main/services/library-sync/update-game-playtime.ts index 3689b302..b53ebebc 100644 --- a/src/main/services/library-sync/update-game-playtime.ts +++ b/src/main/services/library-sync/update-game-playtime.ts @@ -6,6 +6,10 @@ export const updateGamePlaytime = async ( deltaInMillis: number, lastTimePlayed: Date ) => { + if (game.shop === "custom") { + return; + } + return HydraApi.put(`/profile/games/${game.remoteId}`, { playTimeDeltaInSeconds: Math.trunc(deltaInMillis / 1000), lastTimePlayed, diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 6408c30d..a1449255 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -198,11 +198,6 @@ export const watchProcesses = async () => { function onOpenGame(game: Game) { const now = performance.now(); - AchievementWatcherManager.firstSyncWithRemoteIfNeeded( - game.shop, - game.objectId - ); - gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), { lastTick: now, firstTick: now, @@ -220,6 +215,13 @@ function onOpenGame(game: Game) { }) .catch(() => {}); + if (game.shop === "custom") return; + + AchievementWatcherManager.firstSyncWithRemoteIfNeeded( + game.shop, + game.objectId + ); + if (game.remoteId) { updateGamePlaytime( game, @@ -255,18 +257,20 @@ function onTickGame(game: Game) { const delta = now - gamePlaytime.lastTick; - gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + const updatedGame: Game = { ...game, playTimeInMilliseconds: (game.playTimeInMilliseconds ?? 0) + delta, lastTimePlayed: new Date(), - }); + }; + + gamesSublevel.put(levelKeys.game(game.shop, game.objectId), updatedGame); gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), { ...gamePlaytime, lastTick: now, }); - if (currentTick % TICKS_TO_UPDATE_API === 0) { + if (currentTick % TICKS_TO_UPDATE_API === 0 && game.shop !== "custom") { const deltaToSync = now - gamePlaytime.lastSyncTick + @@ -279,19 +283,20 @@ function onTickGame(game: Game) { gamePromise .then(() => { gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { - ...game, + ...updatedGame, unsyncedDeltaPlayTimeInMilliseconds: 0, }); }) .catch(() => { gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { - ...game, + ...updatedGame, unsyncedDeltaPlayTimeInMilliseconds: deltaToSync, }); }) .finally(() => { gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), { ...gamePlaytime, + lastTick: now, lastSyncTick: now, }); }); @@ -299,11 +304,24 @@ function onTickGame(game: Game) { } const onCloseGame = (game: Game) => { + const now = performance.now(); const gamePlaytime = gamesPlaytime.get( levelKeys.game(game.shop, game.objectId) )!; gamesPlaytime.delete(levelKeys.game(game.shop, game.objectId)); + const delta = now - gamePlaytime.lastTick; + + const updatedGame: Game = { + ...game, + playTimeInMilliseconds: (game.playTimeInMilliseconds ?? 0) + delta, + lastTimePlayed: new Date(), + }; + + gamesSublevel.put(levelKeys.game(game.shop, game.objectId), updatedGame); + + if (game.shop === "custom") return; + if (game.remoteId) { if (game.automaticCloudSync) { CloudSync.uploadSaveGame( @@ -315,20 +333,20 @@ const onCloseGame = (game: Game) => { } const deltaToSync = - performance.now() - + now - gamePlaytime.lastSyncTick + (game.unsyncedDeltaPlayTimeInMilliseconds ?? 0); return updateGamePlaytime(game, deltaToSync, game.lastTimePlayed!) .then(() => { return gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { - ...game, + ...updatedGame, unsyncedDeltaPlayTimeInMilliseconds: 0, }); }) .catch(() => { return gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { - ...game, + ...updatedGame, unsyncedDeltaPlayTimeInMilliseconds: deltaToSync, }); }); From 459bf731217ace778a9ff9e7c6dc18288d2a17bd Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 30 Oct 2025 07:36:23 -0300 Subject: [PATCH 111/126] fix: request download-sources on custom game --- src/renderer/src/context/game-details/game-details.context.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index c5b88607..bc1a6351 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -293,6 +293,8 @@ export function GameDetailsContextProvider({ }, [objectId, shop, userDetails]); useEffect(() => { + if (shop === "custom") return; + const fetchDownloadSources = async () => { try { const sources = await window.electron.getDownloadSources(); From aadf648a2bfe7a52db7036d90ec7b418663f68c6 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 30 Oct 2025 07:58:43 -0300 Subject: [PATCH 112/126] chore: unnecessary casting --- .../src/pages/settings/settings-download-sources.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index 75f0cc73..675919e3 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -89,7 +89,7 @@ export function SettingsDownloadSources() { try { await window.electron.removeDownloadSource(false, downloadSource.id); const sources = await window.electron.getDownloadSources(); - setDownloadSources(sources as DownloadSource[]); + setDownloadSources(sources); showSuccessToast(t("removed_download_source")); } catch (error) { logger.error("Failed to remove download source:", error); @@ -104,7 +104,7 @@ export function SettingsDownloadSources() { try { await window.electron.removeDownloadSource(true); const sources = await window.electron.getDownloadSources(); - setDownloadSources(sources as DownloadSource[]); + setDownloadSources(sources); showSuccessToast(t("removed_all_download_sources")); } catch (error) { logger.error("Failed to remove all download sources:", error); @@ -117,7 +117,7 @@ export function SettingsDownloadSources() { const handleAddDownloadSource = async () => { try { const sources = await window.electron.getDownloadSources(); - setDownloadSources(sources as DownloadSource[]); + setDownloadSources(sources); } catch (error) { logger.error("Failed to refresh download sources:", error); } @@ -128,7 +128,7 @@ export function SettingsDownloadSources() { try { await window.electron.syncDownloadSources(); const sources = await window.electron.getDownloadSources(); - setDownloadSources(sources as DownloadSource[]); + setDownloadSources(sources); showSuccessToast(t("download_sources_synced_successfully")); } finally { From 4bfe6d7f86f864c8989339d7ee6a6bc6ea483f64 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:32:08 -0300 Subject: [PATCH 113/126] feat: limit game text search to 255 chars --- src/renderer/src/components/header/header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/components/header/header.tsx b/src/renderer/src/components/header/header.tsx index e61f3954..8166658d 100644 --- a/src/renderer/src/components/header/header.tsx +++ b/src/renderer/src/components/header/header.tsx @@ -60,7 +60,7 @@ export function Header() { }; const handleSearch = (value: string) => { - dispatch(setFilters({ title: value })); + dispatch(setFilters({ title: value.trim().slice(0, 255) })); if (!location.pathname.startsWith("/catalogue")) { navigate("/catalogue"); From 2aa31c0db0556b1a674d6cd49d08e2ce1fecbf67 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:34:49 -0300 Subject: [PATCH 114/126] feat: limit game text search to 255 chars --- src/renderer/src/pages/catalogue/catalogue.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index bbeda906..b9eb3c24 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -35,7 +35,7 @@ export default function Catalogue() { const { steamDevelopers, steamPublishers, downloadSources } = useCatalogue(); - const { steamGenres, steamUserTags } = useAppSelector( + const { steamGenres, steamUserTags, filters, page } = useAppSelector( (state) => state.catalogueSearch ); @@ -47,8 +47,6 @@ export default function Catalogue() { const { formatNumber } = useFormat(); - const { filters, page } = useAppSelector((state) => state.catalogueSearch); - const dispatch = useAppDispatch(); const { t, i18n } = useTranslation("catalogue"); From 80e0adcd4969ffda3eb25c8006009a22f1e879f7 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Thu, 30 Oct 2025 23:33:07 +0200 Subject: [PATCH 115/126] fix: removed ability to enter non-number symbols to pagination --- .../src/pages/catalogue/pagination.tsx | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index 9febc8f8..8dd85cab 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -29,9 +29,11 @@ function JumpControl({ return isOpen ? ( ) => { - const val = e.target.value; - if (val === "") { + const raw = e.target.value; + const digitsOnly = raw.replace(/\D+/g, ""); + if (digitsOnly === "") { setJumpValue(""); return; } - const num = Number(val); + const num = parseInt(digitsOnly, 10); if (Number.isNaN(num)) { + setJumpValue(""); return; } if (num < 1) { @@ -104,19 +108,38 @@ export function Pagination({ setJumpValue(String(totalPages)); return; } - setJumpValue(val); + setJumpValue(String(num)); }; const onJumpKeyDown = (e: KeyboardEvent) => { + // Allow common control keys + const controlKeys = [ + "Backspace", + "Delete", + "Tab", + "ArrowLeft", + "ArrowRight", + "Home", + "End", + ]; + + if (controlKeys.includes(e.key) || e.ctrlKey || e.metaKey) { + return; + } + if (e.key === "Enter") { - if (jumpValue.trim() === "") return; - const parsed = Number(jumpValue); + const sanitized = jumpValue.replace(/\D+/g, ""); + if (sanitized.trim() === "") return; + const parsed = parseInt(sanitized, 10); 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); + } else if (!/^\d$/.test(e.key)) { + // Block any non-digit input (e.g., '.', ',') + e.preventDefault(); } }; From bbbf861594575fa8bfa685fecc3dd6e3ca967134 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Thu, 30 Oct 2025 23:36:41 +0200 Subject: [PATCH 116/126] fix: deleted comments --- src/renderer/src/pages/catalogue/pagination.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index 8dd85cab..c41635d0 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -112,7 +112,6 @@ export function Pagination({ }; const onJumpKeyDown = (e: KeyboardEvent) => { - // Allow common control keys const controlKeys = [ "Backspace", "Delete", @@ -138,7 +137,6 @@ export function Pagination({ } else if (e.key === "Escape") { setIsJumpOpen(false); } else if (!/^\d$/.test(e.key)) { - // Block any non-digit input (e.g., '.', ',') e.preventDefault(); } }; From bd059cc7fa3800276f053f9650bdd355a85677a9 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Thu, 30 Oct 2025 23:45:29 +0200 Subject: [PATCH 117/126] feat: update cursorrules --- .cursorrules | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.cursorrules b/.cursorrules index 0b0c009c..5015ab7e 100644 --- a/.cursorrules +++ b/.cursorrules @@ -27,3 +27,11 @@ - Follow TypeScript strict mode conventions - Use async/await instead of promises when possible - Prefer named exports over default exports for utilities and services + +## Comments + +- Keep comments concise and purposeful; avoid verbose explanations. +- Focus on the "why" or non-obvious context, not restating the code. +- Prefer self-explanatory naming and structure over excessive comments. +- Do not comment every line or obvious behavior; remove stale comments. +- Use docblocks only where they add value (public APIs, complex logic). From aadbda770b5a6479688c40ae6c6f36cc904e06ac Mon Sep 17 00:00:00 2001 From: Moyasee Date: Fri, 31 Oct 2025 00:19:49 +0200 Subject: [PATCH 118/126] fix: linting issue, marked props as read-only --- src/renderer/src/pages/catalogue/pagination.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index c41635d0..ecc2afe3 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -58,7 +58,7 @@ export function Pagination({ page, totalPages, onPageChange, -}: PaginationProps) { +}: Readonly) { const { formatNumber } = useFormat(); const [isJumpOpen, setIsJumpOpen] = useState(false); @@ -90,12 +90,12 @@ export function Pagination({ const onJumpChange = (e: ChangeEvent) => { const raw = e.target.value; - const digitsOnly = raw.replace(/\D+/g, ""); + const digitsOnly = raw.replaceAll(/\D+/g, ""); if (digitsOnly === "") { setJumpValue(""); return; } - const num = parseInt(digitsOnly, 10); + const num = Number.parseInt(digitsOnly, 10); if (Number.isNaN(num)) { setJumpValue(""); return; @@ -127,9 +127,9 @@ export function Pagination({ } if (e.key === "Enter") { - const sanitized = jumpValue.replace(/\D+/g, ""); + const sanitized = jumpValue.replaceAll(/\D+/g, ""); if (sanitized.trim() === "") return; - const parsed = parseInt(sanitized, 10); + const parsed = Number.parseInt(sanitized, 10); if (Number.isNaN(parsed)) return; const target = Math.max(1, Math.min(totalPages, parsed)); onPageChange(target); From aa148c0b70234013c2137b64afc7530faf1ea48f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:01:47 -0300 Subject: [PATCH 119/126] fix: trim --- src/renderer/src/components/header/header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/components/header/header.tsx b/src/renderer/src/components/header/header.tsx index 8166658d..6f97729f 100644 --- a/src/renderer/src/components/header/header.tsx +++ b/src/renderer/src/components/header/header.tsx @@ -60,7 +60,7 @@ export function Header() { }; const handleSearch = (value: string) => { - dispatch(setFilters({ title: value.trim().slice(0, 255) })); + dispatch(setFilters({ title: value.slice(0, 255) })); if (!location.pathname.startsWith("/catalogue")) { navigate("/catalogue"); From b8af69b0fbc52533b9c144b23ad22c107de53cd7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 31 Oct 2025 12:01:42 +0000 Subject: [PATCH 120/126] fix: fixing review partial --- src/locales/en/translation.json | 4 +++- src/locales/es/translation.json | 4 +++- src/locales/pt-BR/translation.json | 4 +++- src/locales/pt-PT/translation.json | 4 +++- src/locales/ru/translation.json | 4 +++- .../src/pages/game-details/game-reviews.tsx | 1 - .../src/pages/game-details/review-item.tsx | 21 ++++++------------- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 668f1547..b48c6ece 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -361,7 +361,9 @@ "show_original": "Show original", "show_translation": "Show translation", "show_original_translated_from": "Show original (translated from {{language}})", - "hide_original": "Hide original" + "hide_original": "Hide original", + "show": "Show", + "hide": "Hide" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 863b8332..75a9adce 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -361,7 +361,9 @@ "you_seemed_to_enjoy_this_game": "Parece que has disfrutado de este juego", "language": "Idioma", "caption": "Subtítulo", - "audio": "Audio" + "audio": "Audio", + "show": "Mostrar", + "hide": "Ocultar" }, "activation": { "title": "Activar Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 5bfc2af3..98946d51 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -349,7 +349,9 @@ "show_translation": "Mostrar tradução", "show_original_translated_from": "Mostrar original (traduzido do {{language}})", "hide_original": "Ocultar original", - "rating_count": "Avaliação" + "rating_count": "Avaliação", + "show": "Mostrar", + "hide": "Ocultar" }, "activation": { "title": "Ativação", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 2894cf65..a99bb93d 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -180,7 +180,9 @@ "download_error_not_cached_on_torbox": "Este download não está disponível no TorBox e a verificação do status do download não está disponível.", "game_removed_from_favorites": "Jogo removido dos favoritos", "game_added_to_favorites": "Jogo adicionado aos favoritos", - "create_start_menu_shortcut": "Criar atalho no Menu Iniciar" + "create_start_menu_shortcut": "Criar atalho no Menu Iniciar", + "show": "Mostrar", + "hide": "Ocultar" }, "activation": { "title": "Ativação", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 886c7d07..388763bd 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -360,7 +360,9 @@ "caption": "Субтитры", "audio": "Аудио", "filter_by_source": "Фильтр по источнику", - "no_repacks_found": "Источники для этой игры не найдены" + "no_repacks_found": "Источники для этой игры не найдены", + "show": "Показать", + "hide": "Скрыть" }, "activation": { "title": "Активировать Hydra", diff --git a/src/renderer/src/pages/game-details/game-reviews.tsx b/src/renderer/src/pages/game-details/game-reviews.tsx index 1a6fc675..2dfd8864 100644 --- a/src/renderer/src/pages/game-details/game-reviews.tsx +++ b/src/renderer/src/pages/game-details/game-reviews.tsx @@ -163,7 +163,6 @@ export function GameReviews({ take: "20", skip: skip.toString(), sortBy: reviewsSortBy, - language: i18n.language, }); const response = await window.electron.hydraApi.get( diff --git a/src/renderer/src/pages/game-details/review-item.tsx b/src/renderer/src/pages/game-details/review-item.tsx index f5e3528a..7e407e20 100644 --- a/src/renderer/src/pages/game-details/review-item.tsx +++ b/src/renderer/src/pages/game-details/review-item.tsx @@ -71,24 +71,16 @@ export function ReviewItem({ const [showOriginal, setShowOriginal] = useState(false); - // Check if this is the user's own review const isOwnReview = userDetailsId === review.user.id; - // Helper to get base language code (e.g., "pt" from "pt-BR") - const getBaseLanguage = (lang: string) => lang.split("-")[0]; + const getBaseLanguage = (lang: string | null) => lang?.split("-")[0] || ""; - // Check if the review is in a different language (comparing base language codes) const isDifferentLanguage = getBaseLanguage(review.detectedLanguage) !== getBaseLanguage(i18n.language); - // Check if translation is available and needed (but not for own reviews) const needsTranslation = - !isOwnReview && - isDifferentLanguage && - review.translations && - review.translations[i18n.language]; + !isOwnReview && isDifferentLanguage && review.translations[i18n.language]; - // Get the full language name using Intl.DisplayNames const getLanguageName = (languageCode: string) => { try { const displayNames = new Intl.DisplayNames([i18n.language], { @@ -100,7 +92,6 @@ export function ReviewItem({ } }; - // Determine which content to show - always show original for own reviews const displayContent = needsTranslation ? review.translations[i18n.language] : review.reviewHtml; @@ -109,12 +100,12 @@ export function ReviewItem({ return (
- Review from blocked user —{" "} + {t("review_from_blocked_user")}
@@ -191,7 +182,7 @@ export function ReviewItem({ {showOriginal ? t("hide_original") : t("show_original_translated_from", { - language: getLanguageName(review.detectedLanguage), + language: getLanguageName(review.detectedLanguage!), })} {showOriginal && ( @@ -323,7 +314,7 @@ export function ReviewItem({ className="game-details__blocked-review-hide-link" onClick={() => onToggleVisibility(review.id)} > - Hide + {t("hide")} )} From ff8a61ff7a83394e08609a48770cfe1323c8d8ce Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 31 Oct 2025 12:05:24 +0000 Subject: [PATCH 121/126] fix: fixing review partial --- src/locales/en/translation.json | 1 + src/locales/es/translation.json | 1 + src/locales/pt-BR/translation.json | 1 + src/locales/pt-PT/translation.json | 1 + src/locales/ru/translation.json | 3 ++- 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index b48c6ece..8b9ff73e 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -362,6 +362,7 @@ "show_translation": "Show translation", "show_original_translated_from": "Show original (translated from {{language}})", "hide_original": "Hide original", + "review_from_blocked_user": "Review from blocked user", "show": "Show", "hide": "Hide" }, diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 75a9adce..adf25e33 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -362,6 +362,7 @@ "language": "Idioma", "caption": "Subtítulo", "audio": "Audio", + "review_from_blocked_user": "Reseña de usuario bloqueado", "show": "Mostrar", "hide": "Ocultar" }, diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 98946d51..42743a64 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -350,6 +350,7 @@ "show_original_translated_from": "Mostrar original (traduzido do {{language}})", "hide_original": "Ocultar original", "rating_count": "Avaliação", + "review_from_blocked_user": "Avaliação de usuário bloqueado", "show": "Mostrar", "hide": "Ocultar" }, diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index a99bb93d..6c1963cc 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -181,6 +181,7 @@ "game_removed_from_favorites": "Jogo removido dos favoritos", "game_added_to_favorites": "Jogo adicionado aos favoritos", "create_start_menu_shortcut": "Criar atalho no Menu Iniciar", + "review_from_blocked_user": "Avaliação de utilizador bloqueado", "show": "Mostrar", "hide": "Ocultar" }, diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 682b1322..6f4d4b92 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -363,7 +363,8 @@ "show_original": "Показать оригинал", "show_translation": "Показать перевод", "show_original_translated_from": "Показать оригинал (переведено с {{language}})", - "hide_original": "Скрыть оригинал" + "hide_original": "Скрыть оригинал", + "review_from_blocked_user": "Отзыв от заблокированного пользователя" }, "activation": { "title": "Активировать Hydra", From c71f5947ba8dfb7856625477289bb9b2f44346f2 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:20:11 -0300 Subject: [PATCH 122/126] feat: use new ep to track game playtime --- src/main/services/library-sync/update-game-playtime.ts | 4 ++-- src/main/services/process-watcher.ts | 8 ++++---- src/main/services/window-manager.ts | 8 +------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/services/library-sync/update-game-playtime.ts b/src/main/services/library-sync/update-game-playtime.ts index b53ebebc..a669a363 100644 --- a/src/main/services/library-sync/update-game-playtime.ts +++ b/src/main/services/library-sync/update-game-playtime.ts @@ -1,7 +1,7 @@ import type { Game } from "@types"; import { HydraApi } from "../hydra-api"; -export const updateGamePlaytime = async ( +export const trackGamePlaytime = async ( game: Game, deltaInMillis: number, lastTimePlayed: Date @@ -10,7 +10,7 @@ export const updateGamePlaytime = async ( return; } - return HydraApi.put(`/profile/games/${game.remoteId}`, { + return HydraApi.put(`/profile/games/${game.shop}/${game.objectId}`, { playTimeDeltaInSeconds: Math.trunc(deltaInMillis / 1000), lastTimePlayed, }); diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index a1449255..db5bbee1 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -1,5 +1,5 @@ import { WindowManager } from "./window-manager"; -import { createGame, updateGamePlaytime } from "./library-sync"; +import { createGame, trackGamePlaytime } from "./library-sync"; import type { Game, GameRunning, UserPreferences } from "@types"; import { PythonRPC } from "./python-rpc"; import axios from "axios"; @@ -223,7 +223,7 @@ function onOpenGame(game: Game) { ); if (game.remoteId) { - updateGamePlaytime( + trackGamePlaytime( game, game.unsyncedDeltaPlayTimeInMilliseconds ?? 0, new Date() @@ -277,7 +277,7 @@ function onTickGame(game: Game) { (game.unsyncedDeltaPlayTimeInMilliseconds ?? 0); const gamePromise = game.remoteId - ? updateGamePlaytime(game, deltaToSync, game.lastTimePlayed!) + ? trackGamePlaytime(game, deltaToSync, game.lastTimePlayed!) : createGame(game); gamePromise @@ -337,7 +337,7 @@ const onCloseGame = (game: Game) => { gamePlaytime.lastSyncTick + (game.unsyncedDeltaPlayTimeInMilliseconds ?? 0); - return updateGamePlaytime(game, deltaToSync, game.lastTimePlayed!) + return trackGamePlaytime(game, deltaToSync, game.lastTimePlayed!) .then(() => { return gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { ...updatedGame, diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 673bf1a0..2484e8e7 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -289,12 +289,6 @@ export class WindowManager { } } - private static loadNotificationWindowURL() { - if (this.notificationWindow) { - this.loadWindowURL(this.notificationWindow, "achievement-notification"); - } - } - private static readonly NOTIFICATION_WINDOW_WIDTH = 360; private static readonly NOTIFICATION_WINDOW_HEIGHT = 140; @@ -387,7 +381,7 @@ export class WindowManager { this.notificationWindow.setIgnoreMouseEvents(true); this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); - this.loadNotificationWindowURL(); + this.loadWindowURL(this.notificationWindow, "achievement-notification"); if (!app.isPackaged || isStaging) { this.notificationWindow.webContents.openDevTools(); From 138120460cf0957eaf584162172f8791c2c318e1 Mon Sep 17 00:00:00 2001 From: jarexe Date: Fri, 31 Oct 2025 10:57:44 -0300 Subject: [PATCH 123/126] fix: correct achievement notification positioning on multi-monitor setups --- src/main/services/window-manager.ts | 38 +++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 673bf1a0..834bf7ab 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -302,46 +302,58 @@ export class WindowManager { position: AchievementCustomNotificationPosition | undefined ) { const display = screen.getPrimaryDisplay(); - const { width, height } = display.workAreaSize; + const { + x: displayX, + y: displayY, + width: displayWidth, + height: displayHeight, + } = display.bounds; if (position === "bottom-left") { return { - x: 0, - y: height - this.NOTIFICATION_WINDOW_HEIGHT, + x: displayX, + y: displayY + displayHeight - this.NOTIFICATION_WINDOW_HEIGHT, }; } if (position === "bottom-center") { return { - x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, - y: height - this.NOTIFICATION_WINDOW_HEIGHT, + x: displayX + (displayWidth - this.NOTIFICATION_WINDOW_WIDTH) / 2, + y: displayY + displayHeight - this.NOTIFICATION_WINDOW_HEIGHT, }; } if (position === "bottom-right") { return { - x: width - this.NOTIFICATION_WINDOW_WIDTH, - y: height - this.NOTIFICATION_WINDOW_HEIGHT, + x: displayX + displayWidth - this.NOTIFICATION_WINDOW_WIDTH, + y: displayY + displayHeight - this.NOTIFICATION_WINDOW_HEIGHT, + }; + } + + if (position === "top-left") { + return { + x: displayX, + y: displayY, }; } if (position === "top-center") { return { - x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, - y: 0, + x: displayX + (displayWidth - this.NOTIFICATION_WINDOW_WIDTH) / 2, + y: displayY, }; } if (position === "top-right") { return { - x: width - this.NOTIFICATION_WINDOW_WIDTH, - y: 0, + x: displayX + displayWidth - this.NOTIFICATION_WINDOW_WIDTH, + y: displayY, }; } return { - x: 0, - y: 0, + x: displayX, + y: displayY, }; } From 51c4e4f5b38b411f0ac92d13a1b15d8b676ceecc Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:07:06 -0300 Subject: [PATCH 124/126] chore: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d84e763..ee039574 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.7.2", + "version": "3.7.3", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", From d167628ed44e22c6090f352ee57cdb5710b32811 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:57:15 -0300 Subject: [PATCH 125/126] fix: prevent crash when detectedLanguage is null --- src/renderer/src/pages/game-details/review-item.tsx | 5 +++-- src/types/index.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/pages/game-details/review-item.tsx b/src/renderer/src/pages/game-details/review-item.tsx index 7e407e20..c411e0cb 100644 --- a/src/renderer/src/pages/game-details/review-item.tsx +++ b/src/renderer/src/pages/game-details/review-item.tsx @@ -81,7 +81,8 @@ export function ReviewItem({ const needsTranslation = !isOwnReview && isDifferentLanguage && review.translations[i18n.language]; - const getLanguageName = (languageCode: string) => { + const getLanguageName = (languageCode: string | null) => { + if (!languageCode) return ""; try { const displayNames = new Intl.DisplayNames([i18n.language], { type: "language", @@ -182,7 +183,7 @@ export function ReviewItem({ {showOriginal ? t("hide_original") : t("show_original_translated_from", { - language: getLanguageName(review.detectedLanguage!), + language: getLanguageName(review.detectedLanguage), })} {showOriginal && ( diff --git a/src/types/index.ts b/src/types/index.ts index 4b13c496..c04b6232 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -252,7 +252,7 @@ export interface GameReview { translations: { [key: string]: string; }; - detectedLanguage: string; + detectedLanguage: string | null; } export interface TrendingGame extends ShopAssets { From 19bf99ff119f65394bcdaed84d0a1423472b1d88 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:16:03 -0300 Subject: [PATCH 126/126] chore: add sleep to aur script --- .github/workflows/update-aur.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 52fe907e..fa12b500 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -95,6 +95,8 @@ jobs: - name: Update PKGBUILD and .SRCINFO if: steps.check-update.outputs.update_needed == 'true' run: | + # sleeps for 1 minute to be sure GH updated the release info + sleep 60 # Update pkgver in PKGBUILD cd hydra-launcher-bin NEW_VERSION="${{ steps.get-version.outputs.version }}"