diff --git a/src/main/events/catalogue/get-random-game.ts b/src/main/events/catalogue/get-random-game.ts index 07a827dd..d0d013e7 100644 --- a/src/main/events/catalogue/get-random-game.ts +++ b/src/main/events/catalogue/get-random-game.ts @@ -1,27 +1,40 @@ import shuffle from "lodash/shuffle"; -import { getRandomSteam250List } from "@main/services"; +import { Steam250Game, getSteam250List } from "@main/services"; import { registerEvent } from "../register-event"; import { searchGames, searchRepacks } from "../helpers/search-games"; -import { formatName } from "@main/helpers"; + +const state = { games: Array(), index: 0 }; const getRandomGame = async (_event: Electron.IpcMainInvokeEvent) => { - return getRandomSteam250List().then(async (games) => { - const shuffledList = shuffle(games); + if (state.games.length == 0) { + const steam250List = await getSteam250List(); - for (const game of shuffledList) { - const repacks = searchRepacks(formatName(game.title)); + const filteredSteam250List = steam250List.filter((game) => { + const repacks = searchRepacks(game.title); + const catalogue = searchGames({ query: game.title }); - if (repacks.length) { - const results = await searchGames({ query: game.title }); + return repacks.length && catalogue.length; + }); - if (results.length) { - return results[0].objectID; - } - } - } - }); + state.games = shuffle(filteredSteam250List); + } + + if (state.games.length == 0) { + return ""; + } + + const resultObjectId = state.games[state.index].objectID; + + state.index += 1; + + if (state.index == state.games.length) { + state.index = 0; + state.games = shuffle(state.games); + } + + return resultObjectId; }; registerEvent(getRandomGame, { diff --git a/src/main/events/catalogue/search-games.ts b/src/main/events/catalogue/search-games.ts index eb9c0640..f0539562 100644 --- a/src/main/events/catalogue/search-games.ts +++ b/src/main/events/catalogue/search-games.ts @@ -1,11 +1,15 @@ import { registerEvent } from "../register-event"; import { searchGames } from "../helpers/search-games"; +import { CatalogueEntry } from "@types"; -registerEvent( - (_event: Electron.IpcMainInvokeEvent, query: string) => - searchGames({ query, take: 12 }), - { - name: "searchGames", - memoize: true, - } -); +const searchGamesEvent = async ( + _event: Electron.IpcMainInvokeEvent, + query: string +): Promise => { + return searchGames({ query, take: 12 }); +}; + +registerEvent(searchGamesEvent, { + name: "searchGames", + memoize: true, +}); diff --git a/src/main/events/helpers/search-games.ts b/src/main/events/helpers/search-games.ts index 5fb3cea0..50777dd7 100644 --- a/src/main/events/helpers/search-games.ts +++ b/src/main/events/helpers/search-games.ts @@ -42,11 +42,11 @@ export interface SearchGamesArgs { skip?: number; } -export const searchGames = async ({ +export const searchGames = ({ query, take, skip, -}: SearchGamesArgs): Promise => { +}: SearchGamesArgs): CatalogueEntry[] => { const results = steamGamesIndex .search(formatName(query || ""), { limit: take, offset: skip }) .map((index) => { @@ -61,11 +61,9 @@ export const searchGames = async ({ }; }); - return Promise.all(results).then((resultsWithRepacks) => - orderBy( - resultsWithRepacks, - [({ repacks }) => repacks.length, "repacks"], - ["desc"] - ) + return orderBy( + results, + [({ repacks }) => repacks.length, "repacks"], + ["desc"] ); }; diff --git a/src/main/services/steam-250.ts b/src/main/services/steam-250.ts index 6447c226..89836d89 100644 --- a/src/main/services/steam-250.ts +++ b/src/main/services/steam-250.ts @@ -1,24 +1,31 @@ import axios from "axios"; import { JSDOM } from "jsdom"; -import shuffle from "lodash/shuffle"; + +export interface Steam250Game { + title: string; + objectID: string; +} export const requestSteam250 = async (path: string) => { - return axios.get(`https://steam250.com${path}`).then((response) => { - const { window } = new JSDOM(response.data); - const { document } = window; + return axios + .get(`https://steam250.com${path}`) + .then((response) => { + const { window } = new JSDOM(response.data); + const { document } = window; - return Array.from(document.querySelectorAll(".appline .title a")).map( - ($title: HTMLAnchorElement) => { - const steamGameUrl = $title.href; - if (!steamGameUrl) return null; + return Array.from(document.querySelectorAll(".appline .title a")) + .map(($title: HTMLAnchorElement) => { + const steamGameUrl = $title.href; + if (!steamGameUrl) return null; - return { - title: $title.textContent, - objectID: steamGameUrl.split("/").pop(), - }; - } - ); - }); + return { + title: $title.textContent, + objectID: steamGameUrl.split("/").pop(), + } as Steam250Game; + }) + .filter((game) => game != null); + }) + .catch((_) => [] as Steam250Game[]); }; const steam250Paths = [ @@ -28,7 +35,15 @@ const steam250Paths = [ "/most_played", ]; -export const getRandomSteam250List = async () => { - const [path] = shuffle(steam250Paths); - return requestSteam250(path); +export const getSteam250List = async () => { + const gamesList = ( + await Promise.all(steam250Paths.map((path) => requestSteam250(path))) + ).flat(); + + const gamesMap: Map = gamesList.reduce((map, item) => { + map.set(item.objectID, item); + return map; + }, new Map()); + + return [...gamesMap.values()]; }; diff --git a/src/renderer/pages/game-details/game-details.tsx b/src/renderer/pages/game-details/game-details.tsx index 7f744198..41ba215e 100644 --- a/src/renderer/pages/game-details/game-details.tsx +++ b/src/renderer/pages/game-details/game-details.tsx @@ -1,6 +1,6 @@ import Color from "color"; import { average } from "color.js"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import type { @@ -33,6 +33,7 @@ export function GameDetails() { const { objectID, shop } = useParams(); const [isLoading, setIsLoading] = useState(false); + const [isLoadingRandomGame, setIsLoadingRandomGame] = useState(false); const [color, setColor] = useState(""); const [gameDetails, setGameDetails] = useState(null); const [howLongToBeat, setHowLongToBeat] = useState<{ @@ -53,18 +54,10 @@ export function GameDetails() { const [showRepacksModal, setShowRepacksModal] = useState(false); const [showSelectFolderModal, setShowSelectFolderModal] = useState(false); - const randomGameObjectID = useRef(null); - const dispatch = useAppDispatch(); const { game: gameDownloading, startDownload, isDownloading } = useDownload(); - const getRandomGame = useCallback(() => { - window.electron.getRandomGame().then((objectID) => { - randomGameObjectID.current = objectID; - }); - }, []); - const handleImageSettled = useCallback((url: string) => { average(url, { amount: 1, format: "hex" }) .then((color) => { @@ -89,8 +82,6 @@ export function GameDetails() { setIsGamePlaying(false); dispatch(setHeaderTitle("")); - getRandomGame(); - window.electron .getGameShopDetails(objectID, "steam", getSteamLanguage(i18n.language)) .then((result) => { @@ -107,6 +98,7 @@ export function GameDetails() { setGameDetails(result); dispatch(setHeaderTitle(result.name)); + setIsLoadingRandomGame(false); }) .finally(() => { setIsLoading(false); @@ -114,7 +106,7 @@ export function GameDetails() { getGame(); setHowLongToBeat({ isLoading: true, data: null }); - }, [getGame, getRandomGame, dispatch, navigate, objectID, i18n.language]); + }, [getGame, dispatch, navigate, objectID, i18n.language]); const isGameDownloading = isDownloading && gameDownloading?.id === game?.id; @@ -158,16 +150,15 @@ export function GameDetails() { }); }; - const handleRandomizerClick = () => { - if (!randomGameObjectID.current) return; + const handleRandomizerClick = async () => { + setIsLoadingRandomGame(true); + const randomGameObjectID = await window.electron.getRandomGame(); const searchParams = new URLSearchParams({ fromRandomizer: "1", }); - navigate( - `/game/steam/${randomGameObjectID.current}?${searchParams.toString()}` - ); + navigate(`/game/steam/${randomGameObjectID}?${searchParams.toString()}`); }; const fromRandomizer = searchParams.get("fromRandomizer"); @@ -281,6 +272,7 @@ export function GameDetails() { className={styles.randomizerButton} onClick={handleRandomizerClick} theme="outline" + disabled={isLoadingRandomGame} >
{ setIsLoadingRandomGame(true); - window.electron - .getRandomGame() - .then((objectID) => { + window.electron.getRandomGame().then((objectID) => { + if (objectID) { randomGameObjectID.current = objectID; - }) - .finally(() => { setIsLoadingRandomGame(false); - }); + } + }); }, []); const handleRandomizerClick = () => {