diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 49aa25e2..564daa84 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -4,6 +4,7 @@ module.exports = { "plugin:react/recommended", "plugin:react/jsx-runtime", "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", "@electron-toolkit/eslint-config-ts/recommended", "prettier", ], diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 293a898b..5d5fc277 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,6 +1,6 @@ name: Lint -on: [pull_request, push] +on: [pull_request] jobs: lint: diff --git a/package.json b/package.json index 5830a320..85043c80 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "electron-builder": "^24.9.1", "electron-vite": "^2.0.0", "eslint": "^8.56.0", + "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "husky": "^9.0.11", diff --git a/resources/hydra.db b/resources/hydra.db deleted file mode 100644 index e69de29b..00000000 diff --git a/src/locales/be/translation.json b/src/locales/be/translation.json index ccada6a7..c55ec394 100644 --- a/src/locales/be/translation.json +++ b/src/locales/be/translation.json @@ -88,7 +88,6 @@ "repacks_modal_description": "Абярыце рэпак, які хочаце сьцягнуць", "downloads_path": "Шлях сьцягваньня", "select_folder_hint": "Каб зьмяніць папку па змоўчаньні, адкрыйце", - "settings": "Налады Hydra", "download_now": "Сьцягнуць зараз", "installation_instructions": "Інструкцыя ўсталёўкі", "installation_instructions_description": "Усталёўка гэтай гульні патрабуе дадатковых крокаў", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 0674d1b5..7b54b889 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -86,7 +86,6 @@ "playing_now": "Playing now", "change": "Change", "repacks_modal_description": "Choose the repack you want to download", - "downloads_path": "Downloads path", "select_folder_hint": "To change the default folder, go to the <0>Settings", "download_now": "Download now", "installation_instructions": "Installation Instructions", @@ -96,7 +95,14 @@ "dont_show_it_again": "Don't show it again", "copy_to_clipboard": "Copy", "copied_to_clipboard": "Copied", - "got_it": "Got it" + "got_it": "Got it", + "no_shop_details": "Could not retrieve shop details.", + "download_options": "Download options", + "download_path": "Download path", + "previous_screenshot": "Previous screenshot", + "next_screenshot": "Next screenshot", + "screenshot": "Screenshot {{number}}", + "open_screenshot": "Open screenshot {{number}}" }, "activation": { "title": "Activate Hydra", @@ -139,7 +145,7 @@ "enable_repack_list_notifications": "When a new repack is added", "telemetry": "Telemetry", "telemetry_description": "Enable anonymous usage statistics", - "real_debrid_api_token_description": "Real Debrid API token", + "real_debrid_api_token_label": "Real Debrid API token", "quit_app_instead_hiding": "Quit Hydra instead of minimizing to tray", "launch_with_system": "Launch Hydra on system start-up", "general": "General", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index a692fd16..1c97adae 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -85,7 +85,6 @@ "repacks_modal_description": "Selecciona el repack que quieres descargar", "downloads_path": "Ruta de descarga", "select_folder_hint": "Para cambiar la carpeta predeterminada, accede a", - "settings": "Ajustes", "download_now": "Descargar ahora", "installation_instructions": "Instrucciones de instalación", "installation_instructions_description": "Se requieren de pasos adicionales para instalar este juego", diff --git a/src/locales/id/translation.json b/src/locales/id/translation.json index 49d3a991..60de327a 100644 --- a/src/locales/id/translation.json +++ b/src/locales/id/translation.json @@ -88,7 +88,6 @@ "repacks_modal_description": "Pilih repack yang kamu ingin unduh", "downloads_path": "Lokasi Unduhan", "select_folder_hint": "Untuk merubah folder bawaan, akses melalui", - "settings": "Pengaturan", "download_now": "Unduh sekarang", "installation_instructions": "Instruksi Instalasi", "installation_instructions_description": "Langkah tambahan dibutuhkan untuk meng-instal game ini", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index 9889dd0c..57706dc9 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -88,7 +88,6 @@ "repacks_modal_description": "Scegli il repack che vuoi scaricare", "downloads_path": "Percorso dei download", "select_folder_hint": "Per cambiare la cartella predefinita, accedi alle", - "settings": "Impostazioni", "download_now": "Scarica ora", "installation_instructions": "Istruzioni di installazione", "installation_instructions_description": "Sono necessari passaggi aggiuntivi per installare questo gioco", diff --git a/src/locales/nl/translation.json b/src/locales/nl/translation.json index 22cb1d90..4be69007 100644 --- a/src/locales/nl/translation.json +++ b/src/locales/nl/translation.json @@ -139,7 +139,7 @@ "enable_repack_list_notifications": "Wanneer een nieuwe herverpakking wordt toegevoegd", "telemetry": "Telemetrie", "telemetry_description": "Schakel anonieme gebruiksstatistieken in", - "real_debrid_api_token_description": "Real Debrid API token", + "real_debrid_api_token_label": "Real Debrid API token", "quit_app_instead_hiding": "Sluit Hydra af in plaats van te minimaliseren naar de lade", "launch_with_system": "Start Hydra bij het opstarten van het systeem", "general": "Algemeen", diff --git a/src/locales/pl/translation.json b/src/locales/pl/translation.json index 5623c74e..5214019e 100644 --- a/src/locales/pl/translation.json +++ b/src/locales/pl/translation.json @@ -82,7 +82,6 @@ "repacks_modal_description": "Wybierz repack, który chcesz pobrać", "downloads_path": "Ścieżka pobierania", "select_folder_hint": "Aby zmienić domyślny folder, przejdź do", - "settings": "Ustawienia Hydra", "download_now": "Pobierz teraz" }, "activation": { diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index dda53065..57ec0470 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -82,7 +82,6 @@ "playing_now": "Jogando agora", "change": "Mudar", "repacks_modal_description": "Escolha o repack do jogo que deseja baixar", - "downloads_path": "Diretório do download", "select_folder_hint": "Para trocar a pasta padrão, acesse a <0>Tela de Configurações", "download_now": "Baixe agora", "installation_instructions": "Instruções de Instalação", @@ -92,7 +91,14 @@ "dont_show_it_again": "Não mostrar novamente", "copy_to_clipboard": "Copiar", "copied_to_clipboard": "Copiado", - "got_it": "Entendi" + "got_it": "Entendi", + "no_shop_details": "Não foi possível obter os detalhes da loja.", + "download_options": "Opções de download", + "download_path": "Diretório de download", + "previous_screenshot": "Captura de tela anterior", + "next_screenshot": "Próxima captura de tela", + "screenshot": "Captura de tela {{number}}", + "open_screenshot": "Ver captura de tela {{number}}" }, "activation": { "title": "Ativação", @@ -123,7 +129,9 @@ "delete_modal_description": "Isso removerá todos os arquivos de instalação do seu computador", "delete_modal_title": "Tem certeza?", "deleting": "Excluindo instalador…", - "install": "Instalar" + "install": "Instalar", + "torrent": "Torrent", + "real_debrid": "Real Debrid" }, "settings": { "downloads_path": "Diretório dos downloads", @@ -133,6 +141,7 @@ "enable_repack_list_notifications": "Quando a lista de repacks for atualizada", "telemetry": "Telemetria", "telemetry_description": "Habilitar estatísticas de uso anônimas", + "real_debrid_api_token_label": "Token de API do Real Debrid", "quit_app_instead_hiding": "Fechar o aplicativo em vez de minimizá-lo", "launch_with_system": "Iniciar aplicativo na inicialização do sistema", "general": "Geral", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index efeaba37..c2c30cd5 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -88,7 +88,6 @@ "repacks_modal_description": "Выберите репак для загрузки", "downloads_path": "Путь загрузок", "select_folder_hint": "Изменить папку по умолчанию", - "settings": "Настройки Hydra", "download_now": "Загрузить сейчас", "installation_instructions": "Инструкция по установке", "installation_instructions_description": "Для установки этой игры требуются дополнительные шаги", diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index 6a7033b3..be40e013 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -88,7 +88,6 @@ "repacks_modal_description": "İndirmek istediğiiniz repacki seçin", "downloads_path": "İndirme yolu", "select_folder_hint": "Varsayılan klasörü değiştirmek için ulaşmanız gereken ayar", - "settings": "Ayarlar", "download_now": "Şimdi", "installation_instructions": "Kurulum", "installation_instructions_description": "Bu oyunu kurmak için ek adımlar gerekiyor", diff --git a/src/locales/uk/translation.json b/src/locales/uk/translation.json index e8d1c117..e0f6af7b 100644 --- a/src/locales/uk/translation.json +++ b/src/locales/uk/translation.json @@ -88,7 +88,6 @@ "repacks_modal_description": "Виберіть репак, який хочете завантажити", "downloads_path": "Шлях завантажень", "select_folder_hint": "Щоб змінити теку за замовчуванням, відкрийте", - "settings": "Налаштування Hydra", "download_now": "Завантажити зараз", "installation_instructions": "Інструкція зі встановлення", "installation_instructions_description": "Для встановлення цієї гри потрібні додаткові кроки", diff --git a/src/main/events/catalogue/get-game-shop-details.ts b/src/main/events/catalogue/get-game-shop-details.ts index 61629242..bbc3b08a 100644 --- a/src/main/events/catalogue/get-game-shop-details.ts +++ b/src/main/events/catalogue/get-game-shop-details.ts @@ -1,10 +1,32 @@ -import { gameShopCacheRepository } from "@main/repository"; +import { gameShopCacheRepository, steamGameRepository } from "@main/repository"; import { getSteamAppDetails } from "@main/services"; import type { ShopDetails, GameShop, SteamAppDetails } from "@types"; import { registerEvent } from "../register-event"; -import { searchRepacks } from "../helpers/search-games"; + +const getLocalizedSteamAppDetails = ( + objectID: string, + language: string +): Promise => { + if (language === "english") { + return getSteamAppDetails(objectID, language); + } + + return Promise.all([ + steamGameRepository.findOne({ where: { id: Number(objectID) } }), + getSteamAppDetails(objectID, language), + ]).then(([steamGame, localizedAppDetails]) => { + if (steamGame && localizedAppDetails) { + return { + ...localizedAppDetails, + name: steamGame.name, + }; + } + + return null; + }); +}; const getGameShopDetails = async ( _event: Electron.IpcMainInvokeEvent, @@ -17,27 +39,21 @@ const getGameShopDetails = async ( where: { objectID, language }, }); - const result = Promise.all([ - getSteamAppDetails(objectID, "english"), - getSteamAppDetails(objectID, language), - ]).then(([appDetails, localizedAppDetails]) => { - if (appDetails && localizedAppDetails) { + const appDetails = getLocalizedSteamAppDetails(objectID, language).then( + (result) => { gameShopCacheRepository.upsert( { objectID, shop: "steam", language, - serializedData: JSON.stringify({ - ...localizedAppDetails, - name: appDetails.name, - }), + serializedData: JSON.stringify(result), }, ["objectID"] ); - } - return [appDetails, localizedAppDetails]; - }); + return result; + } + ); const cachedGame = cachedData?.serializedData ? (JSON.parse(cachedData?.serializedData) as SteamAppDetails) @@ -46,21 +62,11 @@ const getGameShopDetails = async ( if (cachedGame) { return { ...cachedGame, - repacks: searchRepacks(cachedGame.name), objectID, } as ShopDetails; } - return result.then(([appDetails, localizedAppDetails]) => { - if (!appDetails || !localizedAppDetails) return null; - - return { - ...localizedAppDetails, - name: appDetails.name, - repacks: searchRepacks(appDetails.name), - objectID, - } as ShopDetails; - }); + return Promise.resolve(appDetails); } throw new Error("Not implemented"); diff --git a/src/main/events/catalogue/get-random-game.ts b/src/main/events/catalogue/get-random-game.ts index 72f9cd90..dd3741e3 100644 --- a/src/main/events/catalogue/get-random-game.ts +++ b/src/main/events/catalogue/get-random-game.ts @@ -1,9 +1,10 @@ import { shuffle } from "lodash-es"; -import { Steam250Game, getSteam250List } from "@main/services"; +import { getSteam250List } from "@main/services"; import { registerEvent } from "../register-event"; import { searchGames, searchRepacks } from "../helpers/search-games"; +import type { Steam250Game } from "@types"; const state = { games: Array(), index: 0 }; @@ -25,8 +26,6 @@ const getRandomGame = async (_event: Electron.IpcMainInvokeEvent) => { return ""; } - const resultObjectId = state.games[state.index].objectID; - state.index += 1; if (state.index == state.games.length) { @@ -34,7 +33,7 @@ const getRandomGame = async (_event: Electron.IpcMainInvokeEvent) => { state.games = shuffle(state.games); } - return resultObjectId; + return state.games[state.index]; }; registerEvent(getRandomGame, { diff --git a/src/main/events/catalogue/search-game-repacks.ts b/src/main/events/catalogue/search-game-repacks.ts new file mode 100644 index 00000000..448c6daf --- /dev/null +++ b/src/main/events/catalogue/search-game-repacks.ts @@ -0,0 +1,14 @@ +import { searchRepacks } from "../helpers/search-games"; +import { registerEvent } from "../register-event"; + +const searchGameRepacks = ( + _event: Electron.IpcMainInvokeEvent, + query: string +) => { + return searchRepacks(query); +}; + +registerEvent(searchGameRepacks, { + name: "searchGameRepacks", + memoize: true, +}); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 822cb9d5..ab35ff79 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -7,6 +7,7 @@ import "./catalogue/get-games"; import "./catalogue/get-how-long-to-beat"; import "./catalogue/get-random-game"; import "./catalogue/search-games"; +import "./catalogue/search-game-repacks"; import "./hardware/get-disk-free-space"; import "./library/add-game-to-library"; import "./library/close-game"; diff --git a/src/main/services/real-debrid.ts b/src/main/services/real-debrid.ts index 44798062..355a59b3 100644 --- a/src/main/services/real-debrid.ts +++ b/src/main/services/real-debrid.ts @@ -12,8 +12,7 @@ export class RealDebridClient { private static instance: AxiosInstance; static async addMagnet(magnet: string) { - const searchParams = new URLSearchParams(); - searchParams.append("magnet", magnet); + const searchParams = new URLSearchParams({ magnet }); const response = await this.instance.post( "/torrents/addMagnet", @@ -31,8 +30,7 @@ export class RealDebridClient { } static async selectAllFiles(id: string) { - const searchParams = new URLSearchParams(); - searchParams.append("files", "all"); + const searchParams = new URLSearchParams({ files: "all" }); await this.instance.post( `/torrents/selectFiles/${id}`, @@ -41,8 +39,7 @@ export class RealDebridClient { } static async unrestrictLink(link: string) { - const searchParams = new URLSearchParams(); - searchParams.append("link", link); + const searchParams = new URLSearchParams({ link }); const response = await this.instance.post( "/unrestrict/link", diff --git a/src/main/services/steam-250.ts b/src/main/services/steam-250.ts index db505b47..9833c278 100644 --- a/src/main/services/steam-250.ts +++ b/src/main/services/steam-250.ts @@ -1,10 +1,7 @@ import axios from "axios"; import { JSDOM } from "jsdom"; -export interface Steam250Game { - title: string; - objectID: string; -} +import type { Steam250Game } from "@types"; export const requestSteam250 = async (path: string) => { return axios diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts deleted file mode 100644 index 51163f1a..00000000 --- a/src/preload/index.d.ts +++ /dev/null @@ -1,105 +0,0 @@ -// See the Electron documentation for details on how to use preload scripts: -// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts -import { contextBridge, ipcRenderer } from "electron"; - -import type { - CatalogueCategory, - GameShop, - TorrentProgress, - UserPreferences, -} from "@types"; - -contextBridge.exposeInMainWorld("electron", { - /* Torrenting */ - startGameDownload: ( - repackId: number, - objectID: string, - title: string, - shop: GameShop - ) => ipcRenderer.invoke("startGameDownload", repackId, objectID, title, shop), - cancelGameDownload: (gameId: number) => - ipcRenderer.invoke("cancelGameDownload", gameId), - pauseGameDownload: (gameId: number) => - ipcRenderer.invoke("pauseGameDownload", gameId), - resumeGameDownload: (gameId: number) => - ipcRenderer.invoke("resumeGameDownload", gameId), - onDownloadProgress: (cb: (value: TorrentProgress) => void) => { - const listener = ( - _event: Electron.IpcRendererEvent, - value: TorrentProgress - ) => cb(value); - ipcRenderer.on("on-download-progress", listener); - return () => ipcRenderer.removeListener("on-download-progress", listener); - }, - - /* Catalogue */ - searchGames: (query: string) => ipcRenderer.invoke("searchGames", query), - getCatalogue: (category: CatalogueCategory) => - ipcRenderer.invoke("getCatalogue", category), - getGameShopDetails: (objectID: string, shop: GameShop, language: string) => - ipcRenderer.invoke("getGameShopDetails", objectID, shop, language), - getRandomGame: () => ipcRenderer.invoke("getRandomGame"), - getHowLongToBeat: (objectID: string, shop: GameShop, title: string) => - ipcRenderer.invoke("getHowLongToBeat", objectID, shop, title), - getGames: (take?: number, prevCursor?: number) => - ipcRenderer.invoke("getGames", take, prevCursor), - - /* User preferences */ - getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), - updateUserPreferences: (preferences: UserPreferences) => - ipcRenderer.invoke("updateUserPreferences", preferences), - autoLaunch: (enabled: boolean) => ipcRenderer.invoke("autoLaunch", enabled), - - /* Library */ - addGameToLibrary: ( - objectID: string, - title: string, - shop: GameShop, - executablePath: string - ) => - ipcRenderer.invoke( - "addGameToLibrary", - objectID, - title, - shop, - executablePath - ), - getLibrary: () => ipcRenderer.invoke("getLibrary"), - getRepackersFriendlyNames: () => - ipcRenderer.invoke("getRepackersFriendlyNames"), - openGameInstaller: (gameId: number) => - ipcRenderer.invoke("openGameInstaller", gameId), - openGame: (gameId: number, executablePath: string) => - ipcRenderer.invoke("openGame", gameId, executablePath), - closeGame: (gameId: number) => ipcRenderer.invoke("closeGame", gameId), - removeGameFromLibrary: (gameId: number) => - ipcRenderer.invoke("removeGameFromLibrary", gameId), - deleteGameFolder: (gameId: number) => - ipcRenderer.invoke("deleteGameFolder", gameId), - getGameByObjectID: (objectID: string) => - ipcRenderer.invoke("getGameByObjectID", objectID), - onPlaytime: (cb: (gameId: number) => void) => { - const listener = (_event: Electron.IpcRendererEvent, gameId: number) => - cb(gameId); - ipcRenderer.on("on-playtime", listener); - return () => ipcRenderer.removeListener("on-playtime", listener); - }, - onGameClose: (cb: (gameId: number) => void) => { - const listener = (_event: Electron.IpcRendererEvent, gameId: number) => - cb(gameId); - ipcRenderer.on("on-game-close", listener); - return () => ipcRenderer.removeListener("on-game-close", listener); - }, - - /* Hardware */ - getDiskFreeSpace: () => ipcRenderer.invoke("getDiskFreeSpace"), - - /* Misc */ - ping: () => ipcRenderer.invoke("ping"), - getVersion: () => ipcRenderer.invoke("getVersion"), - getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"), - openExternal: (src: string) => ipcRenderer.invoke("openExternal", src), - showOpenDialog: (options: Electron.OpenDialogOptions) => - ipcRenderer.invoke("showOpenDialog", options), - platform: process.platform, -}); diff --git a/src/preload/index.ts b/src/preload/index.ts index 7d5eb7fe..9151942f 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -52,6 +52,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("getHowLongToBeat", objectID, shop, title), getGames: (take?: number, prevCursor?: number) => ipcRenderer.invoke("getGames", take, prevCursor), + searchGameRepacks: (query: string) => + ipcRenderer.invoke("searchGameRepacks", query), /* User preferences */ getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.tsx b/src/renderer/src/components/bottom-panel/bottom-panel.tsx index 6cce070e..44d125cd 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.tsx +++ b/src/renderer/src/components/bottom-panel/bottom-panel.tsx @@ -64,7 +64,7 @@ export function BottomPanel() { {status} - + v{version} "{VERSION_CODENAME}" diff --git a/src/renderer/src/components/checkbox-field/checkbox-field.tsx b/src/renderer/src/components/checkbox-field/checkbox-field.tsx index 9a7e71d5..bb81a910 100644 --- a/src/renderer/src/components/checkbox-field/checkbox-field.tsx +++ b/src/renderer/src/components/checkbox-field/checkbox-field.tsx @@ -24,7 +24,7 @@ export function CheckboxField({ label, ...props }: CheckboxFieldProps) { /> {props.checked && } -