diff --git a/src/main/events/catalogue/get-how-long-to-beat.ts b/src/main/events/catalogue/get-how-long-to-beat.ts index 01966afc..08b8fbf2 100644 --- a/src/main/events/catalogue/get-how-long-to-beat.ts +++ b/src/main/events/catalogue/get-how-long-to-beat.ts @@ -1,23 +1,21 @@ -import type { HowLongToBeatCategory } from "@types"; -import { getHowLongToBeatGame, searchHowLongToBeat } from "@main/services"; +import type { GameShop, HowLongToBeatCategory } from "@types"; +import { HydraApi } from "@main/services"; import { registerEvent } from "../register-event"; -import { formatName } from "@shared"; const getHowLongToBeat = async ( _event: Electron.IpcMainInvokeEvent, - title: string + shop: GameShop, + objectId: string ): Promise => { - const response = await searchHowLongToBeat(title); - - const game = response.data.find((game) => { - return formatName(game.game_name) === formatName(title); + const params = new URLSearchParams({ + shop, + objectId: objectId.toString(), }); - if (!game) return null; - const howLongToBeat = await getHowLongToBeatGame(String(game.game_id)); - - return howLongToBeat; + return HydraApi.get(`/games/how-long-to-beat?${params.toString()}`, null, { + needsAuth: false, + }); }; registerEvent("getHowLongToBeat", getHowLongToBeat); diff --git a/src/main/services/how-long-to-beat.ts b/src/main/services/how-long-to-beat.ts deleted file mode 100644 index 1e5f3279..00000000 --- a/src/main/services/how-long-to-beat.ts +++ /dev/null @@ -1,108 +0,0 @@ -import axios from "axios"; -import { requestWebPage } from "@main/helpers"; -import type { - HowLongToBeatCategory, - HowLongToBeatSearchResponse, -} from "@types"; -import { formatName } from "@shared"; -import { logger } from "./logger"; -import UserAgent from "user-agents"; - -const state = { - apiKey: null as string | null, -}; - -const getHowLongToBeatSearchApiKey = async () => { - const userAgent = new UserAgent(); - - const document = await requestWebPage("https://howlongtobeat.com/"); - const scripts = Array.from(document.querySelectorAll("script")); - - const appScript = scripts.find((script) => - script.src.startsWith("/_next/static/chunks/pages/_app") - ); - - if (!appScript) return null; - - const response = await axios.get( - `https://howlongtobeat.com${appScript.src}`, - { - headers: { - "User-Agent": userAgent.toString(), - }, - } - ); - - const results = /fetch\("\/api\/search\/"\.concat\("(.*?)"\)/gm.exec( - response.data - ); - - if (!results) return null; - - return results[1]; -}; - -export const searchHowLongToBeat = async (gameName: string) => { - state.apiKey = state.apiKey ?? (await getHowLongToBeatSearchApiKey()); - if (!state.apiKey) return { data: [] }; - - const userAgent = new UserAgent(); - - const response = await axios - .post( - `https://howlongtobeat.com/api/search/${state.apiKey}`, - { - searchType: "games", - searchTerms: formatName(gameName).split(" "), - searchPage: 1, - size: 20, - }, - { - headers: { - "User-Agent": userAgent.toString(), - Referer: "https://howlongtobeat.com/", - }, - } - ) - .catch((error) => { - logger.error("Error searching HowLongToBeat:", error?.response?.status); - return { data: { data: [] } }; - }); - - return response.data as HowLongToBeatSearchResponse; -}; - -const parseListItems = ($lis: Element[]) => { - return $lis.map(($li) => { - const title = $li.querySelector("h4")?.textContent; - const [, accuracyClassName] = Array.from(($li as HTMLElement).classList); - - const accuracy = accuracyClassName.split("time_").at(1); - - return { - title: title ?? "", - duration: $li.querySelector("h5")?.textContent ?? "", - accuracy: accuracy ?? "", - }; - }); -}; - -export const getHowLongToBeatGame = async ( - id: string -): Promise => { - const document = await requestWebPage(`https://howlongtobeat.com/game/${id}`); - - const $ul = document.querySelector(".shadow_shadow ul"); - if (!$ul) return []; - - const $lis = Array.from($ul.children); - - const [$firstLi] = $lis; - - if ($firstLi.tagName === "DIV") { - const $pcData = $lis.find(($li) => $li.textContent?.includes("PC")); - return parseListItems(Array.from($pcData?.querySelectorAll("li") ?? [])); - } - - return parseListItems($lis); -}; diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 27abf579..498159c9 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -4,7 +4,6 @@ export * from "./steam-250"; export * from "./steam-grid"; export * from "./window-manager"; export * from "./download"; -export * from "./how-long-to-beat"; export * from "./process-watcher"; export * from "./main-loop"; export * from "./hydra-api"; diff --git a/src/preload/index.ts b/src/preload/index.ts index 12c5054a..6d8049da 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -55,8 +55,8 @@ contextBridge.exposeInMainWorld("electron", { getGameShopDetails: (objectId: string, shop: GameShop, language: string) => ipcRenderer.invoke("getGameShopDetails", objectId, shop, language), getRandomGame: () => ipcRenderer.invoke("getRandomGame"), - getHowLongToBeat: (title: string) => - ipcRenderer.invoke("getHowLongToBeat", title), + getHowLongToBeat: (shop: GameShop, objectId: string) => + ipcRenderer.invoke("getHowLongToBeat", shop, objectId), getGames: (take?: number, skip?: number) => ipcRenderer.invoke("getGames", take, skip), searchGameRepacks: (query: string) => diff --git a/src/renderer/index.html b/src/renderer/index.html index bfc3a206..ffc16195 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -6,8 +6,45 @@ Hydra + + +
diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.scss b/src/renderer/src/components/bottom-panel/bottom-panel.scss index 35a1a0ee..5103e916 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.scss +++ b/src/renderer/src/components/bottom-panel/bottom-panel.scss @@ -1,7 +1,7 @@ @use "../../scss/globals.scss"; .bottom-panel { - width: "100%"; + width: 100%; border-top: solid 1px globals.$border-color; background-color: globals.$background-color; padding: calc(globals.$spacing-unit / 2) calc(globals.$spacing-unit * 2); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 9e658463..71a76e1a 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -66,7 +66,8 @@ declare global { ) => Promise; getRandomGame: () => Promise; getHowLongToBeat: ( - title: string + shop: GameShop, + objectId: string ) => Promise; getGames: (take?: number, skip?: number) => Promise; searchGameRepacks: (query: string) => Promise; diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index c4ad640b..ea958c1a 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -97,8 +97,10 @@ export function Sidebar() { }); } else { try { - const howLongToBeat = - await window.electron.getHowLongToBeat(gameTitle); + const howLongToBeat = await window.electron.getHowLongToBeat( + shop, + objectId + ); if (howLongToBeat) { howLongToBeatEntriesTable.add({ diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index d6b55490..9ee4e509 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -45,22 +45,25 @@ export function ProfileContent() { return userProfile?.relation?.status === "ACCEPTED"; }, [userProfile]); - const buildUserGameDetailsPath = (game: UserGame) => { - if (!userProfile?.hasActiveSubscription || game.achievementCount === 0) { - return buildGameDetailsPath({ - ...game, - objectId: game.objectId, - }); - } + const buildUserGameDetailsPath = useCallback( + (game: UserGame) => { + if (!userProfile?.hasActiveSubscription || game.achievementCount === 0) { + return buildGameDetailsPath({ + ...game, + objectId: game.objectId, + }); + } - const userParams = userProfile - ? { - userId: userProfile.id, - } - : undefined; + const userParams = userProfile + ? { + userId: userProfile.id, + } + : undefined; - return buildGameAchievementPath({ ...game }, userParams); - }; + return buildGameAchievementPath({ ...game }, userParams); + }, + [userProfile] + ); const formatPlayTime = useCallback( (playTimeInSeconds = 0) => { @@ -176,7 +179,7 @@ export function ProfileContent() { game.achievementCount > 0 && (
@@ -259,6 +264,7 @@ export function ProfileContent() { userStats, numberFormatter, t, + buildUserGameDetailsPath, formatPlayTime, navigate, ]); diff --git a/src/types/how-long-to-beat.types.ts b/src/types/how-long-to-beat.types.ts index 1ab7ee34..e77113ce 100644 --- a/src/types/how-long-to-beat.types.ts +++ b/src/types/how-long-to-beat.types.ts @@ -3,12 +3,3 @@ export interface HowLongToBeatCategory { duration: string; accuracy: string; } - -export interface HowLongToBeatResult { - game_id: number; - game_name: string; -} - -export interface HowLongToBeatSearchResponse { - data: HowLongToBeatResult[]; -}