From 5bb1f753a28c53a0d55427a82ac7a88a305d5c33 Mon Sep 17 00:00:00 2001 From: Hydra Date: Sat, 20 Apr 2024 17:11:35 +0100 Subject: [PATCH] feat: adding onlinefix credentials --- .github/workflows/build.yml | 2 + src/index.ts | 12 +- src/locales/en/translation.json | 4 +- src/locales/es/translation.json | 4 +- src/locales/fr/translation.json | 4 +- src/locales/pt/translation.json | 4 +- src/main/entity/user-preferences.entity.ts | 3 + src/main/events/catalogue/get-games.ts | 36 +-- src/main/events/helpers/search-games.ts | 51 ++--- .../events/library/add-game-to-library.ts | 2 + src/main/index.ts | 9 +- src/main/services/process-watcher.ts | 9 +- src/main/services/repack-tracker/xatab.ts | 2 + src/main/state-manager.ts | 4 +- src/preload.ts | 19 +- src/renderer/components/sidebar/sidebar.tsx | 2 +- src/renderer/declaration.d.ts | 5 +- src/renderer/main.tsx | 13 +- src/renderer/pages/catalogue/catalogue.tsx | 2 +- .../pages/game-details/game-details.tsx | 2 + .../pages/game-details/hero-panel-actions.tsx | 207 ++++++++++++++++++ .../pages/game-details/hero-panel.tsx | 192 ++-------------- src/renderer/pages/home/search-results.tsx | 1 - src/renderer/pages/settings/settings.tsx | 12 + src/types/index.ts | 1 + 25 files changed, 357 insertions(+), 245 deletions(-) create mode 100644 src/renderer/pages/game-details/hero-panel-actions.tsx diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0c92fa1..d26f3641 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,6 +57,8 @@ jobs: STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN: ${{ vars.SENTRY_DSN }} + ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }} + ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }} - name: Create artifact uses: actions/upload-artifact@v4 diff --git a/src/index.ts b/src/index.ts index 48f19a73..87e599b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,17 @@ if (process.platform !== "darwin") { } if (process.env.SENTRY_DSN) { - init({ dsn: process.env.SENTRY_DSN }); + init({ + dsn: process.env.SENTRY_DSN, + beforeSend: async (event) => { + const userPreferences = await userPreferencesRepository.findOne({ + where: { id: 1 }, + }); + + if (userPreferences?.telemetryEnabled) return event; + return null; + }, + }); } i18n.init({ diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index f59f1e87..2f967b76 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -115,7 +115,9 @@ "change": "Update", "notifications": "Notifications", "enable_download_notifications": "When a download is complete", - "enable_repack_list_notifications": "When a new repack is added" + "enable_repack_list_notifications": "When a new repack is added", + "telemetry": "Telemetry", + "telemetry_description": "Enable anonymous usage statistics" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 9ac401bf..9f6c9b25 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -111,7 +111,9 @@ "change": "Cambiar", "notifications": "Notificaciones", "enable_download_notifications": "Cuando se completa una descarga", - "enable_repack_list_notifications": "Cuando se añade un repack nuevo" + "enable_repack_list_notifications": "Cuando se añade un repack nuevo", + "telemetry": "Telemetria", + "telemetry_description": "Habilitar estadísticas de uso anónimas" }, "notifications": { "download_complete": "Descarga completada", diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index c83a825b..da0521d8 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -111,7 +111,9 @@ "change": "Mettre à jour", "notifications": "Notifications", "enable_download_notifications": "Quand un téléchargement est terminé", - "enable_repack_list_notifications": "Quand une nouvelle réduction est ajoutée" + "enable_repack_list_notifications": "Quand une nouvelle réduction est ajoutée", + "telemetry": "Télémétrie", + "telemetry_description": "Activer les statistiques d'utilisation anonymes" }, "notifications": { "download_complete": "Téléchargement terminé", diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 145163b4..3498ad83 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -111,7 +111,9 @@ "change": "Mudar", "notifications": "Notificações", "enable_download_notifications": "Quando um download for concluído", - "enable_repack_list_notifications": "Quando a lista de repacks for atualizada" + "enable_repack_list_notifications": "Quando a lista de repacks for atualizada", + "telemetry": "Telemetria", + "telemetry_description": "Habilitar estatísticas de uso anônimas" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/main/entity/user-preferences.entity.ts b/src/main/entity/user-preferences.entity.ts index 426306e8..ba9bcafd 100644 --- a/src/main/entity/user-preferences.entity.ts +++ b/src/main/entity/user-preferences.entity.ts @@ -23,6 +23,9 @@ export class UserPreferences { @Column("boolean", { default: false }) repackUpdatesNotificationsEnabled: boolean; + @Column("boolean", { default: false }) + telemetryEnabled: boolean; + @CreateDateColumn() createdAt: Date; diff --git a/src/main/events/catalogue/get-games.ts b/src/main/events/catalogue/get-games.ts index b6a4b5bf..64c491ae 100644 --- a/src/main/events/catalogue/get-games.ts +++ b/src/main/events/catalogue/get-games.ts @@ -1,29 +1,39 @@ -import type { CatalogueEntry } from "@types"; +import type { CatalogueEntry, GameShop } from "@types"; import { registerEvent } from "../register-event"; -import { searchGames } from "../helpers/search-games"; -import slice from "lodash/slice"; +import { searchRepacks } from "../helpers/search-games"; +import { stateManager } from "@main/state-manager"; +import { getSteamAppAsset } from "@main/helpers"; + +const steamGames = stateManager.getValue("steamGames"); const getGames = async ( _event: Electron.IpcMainInvokeEvent, take?: number, - prevCursor = 0 + cursor = 0 ): Promise<{ results: CatalogueEntry[]; cursor: number }> => { - let results: CatalogueEntry[] = []; - let i = 0; + const results: CatalogueEntry[] = []; - const batchSize = 100; + let i = 0 + cursor; while (results.length < take) { - const games = await searchGames({ - take: batchSize, - skip: (i + prevCursor) * batchSize, - }); - results = [...results, ...games.filter((game) => game.repacks.length)]; + const game = steamGames[i]; + const repacks = searchRepacks(game.name); + + if (repacks.length) { + results.push({ + objectID: String(game.id), + title: game.name, + shop: "steam" as GameShop, + cover: getSteamAppAsset("library", String(game.id)), + repacks, + }); + } + i++; } - return { results: slice(results, 0, take), cursor: prevCursor + i }; + return { results, cursor: i }; }; registerEvent(getGames, { diff --git a/src/main/events/helpers/search-games.ts b/src/main/events/helpers/search-games.ts index 9e454917..5fb3cea0 100644 --- a/src/main/events/helpers/search-games.ts +++ b/src/main/events/helpers/search-games.ts @@ -5,14 +5,13 @@ import type { GameRepack, GameShop, CatalogueEntry } from "@types"; import { formatName, getSteamAppAsset, repackerFormatter } from "@main/helpers"; import { stateManager } from "@main/state-manager"; -import { steamGameRepository } from "@main/repository"; -import { FindManyOptions, Like } from "typeorm"; -import { SteamGame } from "@main/entity"; const { Index } = flexSearch; const repacksIndex = new Index(); +const steamGamesIndex = new Index({ tokenize: "reverse" }); const repacks = stateManager.getValue("repacks"); +const steamGames = stateManager.getValue("steamGames"); for (let i = 0; i < repacks.length; i++) { const repack = repacks[i]; @@ -22,9 +21,12 @@ for (let i = 0; i < repacks.length; i++) { repacksIndex.add(i, formatName(formatter(repack.title))); } -export const searchRepacks = (title: string): GameRepack[] => { - const repacks = stateManager.getValue("repacks"); +for (let i = 0; i < steamGames.length; i++) { + const steamGame = steamGames[i]; + steamGamesIndex.add(i, formatName(steamGame.name)); +} +export const searchRepacks = (title: string): GameRepack[] => { return orderBy( repacksIndex .search(formatName(title)) @@ -45,34 +47,21 @@ export const searchGames = async ({ take, skip, }: SearchGamesArgs): Promise => { - const options: FindManyOptions = {}; + const results = steamGamesIndex + .search(formatName(query || ""), { limit: take, offset: skip }) + .map((index) => { + const result = steamGames.at(index as number)!; - if (query) { - options.where = { - name: query ? Like(`%${formatName(query)}%`) : undefined, - }; - } + return { + objectID: String(result.id), + title: result.name, + shop: "steam" as GameShop, + cover: getSteamAppAsset("library", String(result.id)), + repacks: searchRepacks(result.name), + }; + }); - const steamResults = await steamGameRepository.find({ - ...options, - take, - skip, - order: { name: "ASC" }, - }); - - const results = steamResults.map((result) => ({ - objectID: String(result.id), - title: result.name, - shop: "steam" as GameShop, - cover: getSteamAppAsset("library", String(result.id)), - })); - - return Promise.all( - results.map(async (result) => ({ - ...result, - repacks: searchRepacks(result.title), - })) - ).then((resultsWithRepacks) => + return Promise.all(results).then((resultsWithRepacks) => orderBy( resultsWithRepacks, [({ repacks }) => repacks.length, "repacks"], diff --git a/src/main/events/library/add-game-to-library.ts b/src/main/events/library/add-game-to-library.ts index 8680b29a..abbba592 100644 --- a/src/main/events/library/add-game-to-library.ts +++ b/src/main/events/library/add-game-to-library.ts @@ -11,6 +11,7 @@ const addGameToLibrary = async ( objectID: string, title: string, gameShop: GameShop, + executablePath: string ) => { const iconUrl = await getImageBase64(await getSteamGameIconUrl(objectID)); @@ -19,6 +20,7 @@ const addGameToLibrary = async ( iconUrl, objectID, shop: gameShop, + executablePath, }); }; diff --git a/src/main/index.ts b/src/main/index.ts index e1d20a99..58f490bf 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -14,6 +14,7 @@ import { gameRepository, repackRepository, repackerFriendlyNameRepository, + steamGameRepository, userPreferencesRepository, } from "./repository"; import { TorrentClient } from "./services/torrent-client"; @@ -104,17 +105,23 @@ const checkForNewRepacks = async () => { }; const loadState = async () => { - const [friendlyNames, repacks] = await Promise.all([ + const [friendlyNames, repacks, steamGames] = await Promise.all([ repackerFriendlyNameRepository.find(), repackRepository.find({ order: { createdAt: "desc", }, }), + steamGameRepository.find({ + order: { + name: "asc", + }, + }), ]); stateManager.setValue("repackersFriendlyNames", friendlyNames); stateManager.setValue("repacks", repacks); + stateManager.setValue("steamGames", steamGames); import("./events"); }; diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index b845ab25..96244a60 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -3,7 +3,6 @@ import path from "node:path"; import { IsNull, Not } from "typeorm"; import { gameRepository } from "@main/repository"; -import { GameStatus } from "@main/constants"; import { getProcesses } from "@main/helpers"; import { WindowManager } from "./window-manager"; @@ -18,7 +17,6 @@ export const startProcessWatcher = async () => { const games = await gameRepository.find({ where: { executablePath: Not(IsNull()), - status: GameStatus.Seeding, }, }); @@ -54,15 +52,16 @@ export const startProcessWatcher = async () => { playTimeInMilliseconds: game.playTimeInMilliseconds + delta, }); + gameRepository.update(game.id, { + lastTimePlayed: new Date().toUTCString(), + }); + gamesPlaytime.set(game.id, performance.now()); await sleep(sleepTime); continue; } gamesPlaytime.set(game.id, performance.now()); - gameRepository.update(game.id, { - lastTimePlayed: new Date().toUTCString(), - }); await sleep(sleepTime); continue; diff --git a/src/main/services/repack-tracker/xatab.ts b/src/main/services/repack-tracker/xatab.ts index 91a0a4c4..4ea968fb 100644 --- a/src/main/services/repack-tracker/xatab.ts +++ b/src/main/services/repack-tracker/xatab.ts @@ -42,6 +42,8 @@ const getXatabRepack = async (url: string) => { if (!$downloadButton) throw new Error("Download button not found"); const torrentBuffer = await getTorrentBuffer($downloadButton.href); + console.log(url); + console.log(torrentBuffer.byteLength); return { fileSize: formatXatabDownloadSize($size.textContent).toUpperCase(), diff --git a/src/main/state-manager.ts b/src/main/state-manager.ts index 68d61c33..6dec3985 100644 --- a/src/main/state-manager.ts +++ b/src/main/state-manager.ts @@ -1,14 +1,16 @@ -import type { Repack, RepackerFriendlyName } from "@main/entity"; +import type { Repack, RepackerFriendlyName, SteamGame } from "@main/entity"; interface State { repacks: Repack[]; repackersFriendlyNames: RepackerFriendlyName[]; + steamGames: SteamGame[]; eventResults: Map<[string, any[]], any>; } const initialState: State = { repacks: [], repackersFriendlyNames: [], + steamGames: [], eventResults: new Map(), }; diff --git a/src/preload.ts b/src/preload.ts index 93acde24..97dcca0b 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -50,15 +50,26 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("updateUserPreferences", preferences), /* Library */ - addGameToLibrary: (objectID: string, title: string, shop: GameShop) => - ipcRenderer.invoke("addGameToLibrary", objectID, title, shop), + 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, path: string) => - ipcRenderer.invoke("openGame", gameId, path), + openGame: (gameId: number, executablePath: string) => + ipcRenderer.invoke("openGame", gameId, executablePath), closeGame: (gameId: number) => ipcRenderer.invoke("closeGame", gameId), removeGame: (gameId: number) => ipcRenderer.invoke("removeGame", gameId), deleteGameFolder: (gameId: number) => diff --git a/src/renderer/components/sidebar/sidebar.tsx b/src/renderer/components/sidebar/sidebar.tsx index ad1bcdf3..2d853553 100644 --- a/src/renderer/components/sidebar/sidebar.tsx +++ b/src/renderer/components/sidebar/sidebar.tsx @@ -203,7 +203,7 @@ export function Sidebar() { className={styles.menuItem({ active: location.pathname === `/game/${game.shop}/${game.objectID}`, - muted: game.status === null || game.status === "cancelled", + muted: game.status === "cancelled", })} > + ); + + if (isGameDownloading) { + return ( + <> + + + + ); + } + + if (game?.status === "paused") { + return ( + <> + + + + ); + } + + if (game?.status === "seeding" || (game && !game.status)) { + return ( + <> + {game?.status === "seeding" ? ( + + ) : ( + toggleGameOnLibraryButton + )} + + {isGamePlaying ? ( + + ) : ( + + )} + + ); + } + + if (game?.status === "cancelled") { + return ( + <> + + + + ); + } + + if (gameDetails && gameDetails.repacks.length) { + return ( + <> + {toggleGameOnLibraryButton} + + + ); + } + + return toggleGameOnLibraryButton; +} diff --git a/src/renderer/pages/game-details/hero-panel.tsx b/src/renderer/pages/game-details/hero-panel.tsx index b6f8e5a3..be3c933b 100644 --- a/src/renderer/pages/game-details/hero-panel.tsx +++ b/src/renderer/pages/game-details/hero-panel.tsx @@ -2,16 +2,15 @@ import { format } from "date-fns"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Button } from "@renderer/components"; -import { useDownload, useLibrary } from "@renderer/hooks"; +import { useDownload } from "@renderer/hooks"; import type { Game, ShopDetails } from "@types"; import { formatDownloadProgress } from "@renderer/helpers"; -import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react"; import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal"; import * as styles from "./hero-panel.css"; import { useDate } from "@renderer/hooks/use-date"; import { formatBytes } from "@renderer/utils"; +import { HeroPanelActions } from "./hero-panel-actions"; export interface HeroPanelProps { game: Game | null; @@ -44,21 +43,8 @@ export function HeroPanel({ eta, numPeers, numSeeds, - resumeDownload, - pauseDownload, - cancelDownload, - removeGame, isGameDeleting, } = useDownload(); - const { updateLibrary, library } = useLibrary(); - - const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] = - useState(false); - - const gameOnLibrary = library.find( - ({ objectID }) => objectID === gameDetails?.objectID - ); - const isGameDownloading = isDownloading && gameDownloading?.id === game?.id; const updateLastTimePlayed = useCallback(() => { @@ -83,41 +69,6 @@ export function HeroPanel({ } }, [game?.lastTimePlayed, updateLastTimePlayed]); - const openGameInstaller = () => { - window.electron.openGameInstaller(game.id).then((isBinaryInPath) => { - if (!isBinaryInPath) setShowBinaryNotFoundModal(true); - updateLibrary(); - }); - }; - - const openGame = () => { - if (game.executablePath) { - window.electron.openGame(game.id, game.executablePath); - return; - } - - if (game?.executablePath) { - window.electron.openGame(game.id, game.executablePath); - return; - } - - window.electron - .showOpenDialog({ - properties: ["openFile"], - filters: [{ name: "Game executable (.exe)", extensions: ["exe"] }], - }) - .then(({ filePaths }) => { - if (filePaths && filePaths.length > 0) { - const path = filePaths[0]; - window.electron.openGame(game.id, path); - } - }); - }; - - const closeGame = () => { - window.electron.closeGame(game.id); - }; - const finalDownloadSize = useMemo(() => { if (!game) return "N/A"; if (game.fileSize) return formatBytes(game.fileSize); @@ -128,26 +79,6 @@ export function HeroPanel({ return game.repack?.fileSize ?? "N/A"; }, [game, isGameDownloading, gameDownloading]); - const toggleLibraryGame = async () => { - setToggleLibraryGameDisabled(true); - - try { - if (gameOnLibrary) { - await window.electron.removeGame(gameOnLibrary.id); - } else { - await window.electron.addGameToLibrary( - gameDetails.objectID, - gameDetails.name, - "steam" - ); - } - - await updateLibrary(); - } finally { - setToggleLibraryGameDisabled(false); - } - }; - const getInfo = () => { if (!gameDetails) return null; @@ -196,7 +127,7 @@ export function HeroPanel({ ); } - if (game?.status === "seeding") { + if (game?.status === "seeding" || (game && !game.status)) { if (!game.lastTimePlayed) { return

{t("not_played_yet", { title: game.title })}

; } @@ -239,112 +170,6 @@ export function HeroPanel({ return

{t("no_downloads")}

; }; - const getActions = () => { - const deleting = isGameDeleting(game?.id); - - const toggleGameOnLibraryButton = ( - - ); - - if (isGameDownloading) { - return ( - <> - - - - ); - } - - if (game?.status === "paused") { - return ( - <> - - - - ); - } - - if (game?.status === "seeding") { - return ( - <> - - - {isGamePlaying ? ( - - ) : ( - - )} - - ); - } - - if (game?.status === "cancelled") { - return ( - <> - - - - ); - } - - if (gameDetails && gameDetails.repacks.length) { - return ( - <> - {toggleGameOnLibraryButton} - - - ); - } - - return toggleGameOnLibraryButton; - }; - return ( <>
{getInfo()}
-
{getActions()}
+
+ +
); diff --git a/src/renderer/pages/home/search-results.tsx b/src/renderer/pages/home/search-results.tsx index 40e55a82..90119456 100644 --- a/src/renderer/pages/home/search-results.tsx +++ b/src/renderer/pages/home/search-results.tsx @@ -67,7 +67,6 @@ export function SearchResults() { key={game.objectID} game={game} onClick={() => handleGameClick(game)} - disabled={!game.repacks.length} /> ))} diff --git a/src/renderer/pages/settings/settings.tsx b/src/renderer/pages/settings/settings.tsx index c80c89e3..47bd604b 100644 --- a/src/renderer/pages/settings/settings.tsx +++ b/src/renderer/pages/settings/settings.tsx @@ -10,6 +10,7 @@ export function Settings() { downloadsPath: "", downloadNotificationsEnabled: false, repackUpdatesNotificationsEnabled: false, + telemetryEnabled: false, }); const { t } = useTranslation("settings"); @@ -25,6 +26,7 @@ export function Settings() { userPreferences?.downloadNotificationsEnabled, repackUpdatesNotificationsEnabled: userPreferences?.repackUpdatesNotificationsEnabled, + telemetryEnabled: userPreferences?.telemetryEnabled, }); }); }, []); @@ -95,6 +97,16 @@ export function Settings() { ) } /> + +

{t("telemetry")}

+ + + updateUserPreferences("telemetryEnabled", !form.telemetryEnabled) + } + /> ); diff --git a/src/types/index.ts b/src/types/index.ts index 02271d3a..768f110b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -104,6 +104,7 @@ export interface UserPreferences { language: string; downloadNotificationsEnabled: boolean; repackUpdatesNotificationsEnabled: boolean; + telemetryEnabled: boolean; } export interface HowLongToBeatCategory {