mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 05:24:55 +03:00
feat: adding steam client icon cache
This commit is contained in:
parent
e5fec91062
commit
19f022e0f6
@ -6,6 +6,7 @@ extraResources:
|
||||
- hydra-download-manager
|
||||
- hydra.db
|
||||
- fastlist.exe
|
||||
- seeds
|
||||
files:
|
||||
- "!**/.vscode/*"
|
||||
- "!src/*"
|
||||
|
1
seeds/steam-games.json
Normal file
1
seeds/steam-games.json
Normal file
File diff suppressed because one or more lines are too long
@ -18,21 +18,6 @@ export const repackers = [
|
||||
"onlinefix",
|
||||
] as const;
|
||||
|
||||
export const months = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
];
|
||||
|
||||
export const defaultDownloadsPath = app.getPath("downloads");
|
||||
|
||||
export const databasePath = path.join(
|
||||
@ -41,5 +26,6 @@ export const databasePath = path.join(
|
||||
"hydra.db"
|
||||
);
|
||||
|
||||
export const INSTALLATION_ID_LENGTH = 6;
|
||||
export const ACTIVATION_KEY_MULTIPLIER = 7;
|
||||
export const seedsPath = app.isPackaged
|
||||
? path.join(process.resourcesPath, "seeds")
|
||||
: path.join(__dirname, "..", "..", "seeds");
|
||||
|
@ -1,25 +1,21 @@
|
||||
import { DataSource } from "typeorm";
|
||||
import {
|
||||
Game,
|
||||
GameShopCache,
|
||||
Repack,
|
||||
UserPreferences,
|
||||
SteamGame,
|
||||
} from "@main/entity";
|
||||
import type { SqliteConnectionOptions } from "typeorm/driver/sqlite/SqliteConnectionOptions";
|
||||
import { Game, GameShopCache, Repack, UserPreferences } from "@main/entity";
|
||||
import type { BetterSqlite3ConnectionOptions } from "typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions";
|
||||
|
||||
import { databasePath } from "./constants";
|
||||
import migrations from "./migrations";
|
||||
|
||||
export const createDataSource = (options: Partial<SqliteConnectionOptions>) =>
|
||||
export const createDataSource = (
|
||||
options: Partial<BetterSqlite3ConnectionOptions>
|
||||
) =>
|
||||
new DataSource({
|
||||
type: "better-sqlite3",
|
||||
database: databasePath,
|
||||
entities: [Game, Repack, UserPreferences, GameShopCache, SteamGame],
|
||||
entities: [Game, Repack, UserPreferences, GameShopCache],
|
||||
synchronize: true,
|
||||
database: databasePath,
|
||||
...options,
|
||||
});
|
||||
|
||||
export const dataSource = createDataSource({
|
||||
migrations: migrations,
|
||||
migrations,
|
||||
});
|
||||
|
@ -23,8 +23,8 @@ export class Game {
|
||||
@Column("text")
|
||||
title: string;
|
||||
|
||||
@Column("text")
|
||||
iconUrl: string;
|
||||
@Column("text", { nullable: true })
|
||||
iconUrl: string | null;
|
||||
|
||||
@Column("text", { nullable: true })
|
||||
folderName: string | null;
|
||||
|
@ -2,4 +2,3 @@ export * from "./game.entity";
|
||||
export * from "./repack.entity";
|
||||
export * from "./user-preferences.entity";
|
||||
export * from "./game-shop-cache.entity";
|
||||
export * from "./steam-game.entity";
|
||||
|
@ -1,10 +0,0 @@
|
||||
import { Column, Entity, PrimaryColumn } from "typeorm";
|
||||
|
||||
@Entity("steam_game")
|
||||
export class SteamGame {
|
||||
@PrimaryColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import { gameShopCacheRepository, steamGameRepository } from "@main/repository";
|
||||
import { gameShopCacheRepository } from "@main/repository";
|
||||
import { getSteamAppDetails } from "@main/services";
|
||||
|
||||
import type { ShopDetails, GameShop, SteamAppDetails } from "@types";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
|
||||
const getLocalizedSteamAppDetails = (
|
||||
objectID: string,
|
||||
@ -13,10 +14,11 @@ const getLocalizedSteamAppDetails = (
|
||||
return getSteamAppDetails(objectID, language);
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
steamGameRepository.findOne({ where: { id: Number(objectID) } }),
|
||||
getSteamAppDetails(objectID, language),
|
||||
]).then(([steamGame, localizedAppDetails]) => {
|
||||
return getSteamAppDetails(objectID, language).then((localizedAppDetails) => {
|
||||
const steamGame = stateManager
|
||||
.getValue("steamGames")
|
||||
.find((game) => game.id === Number(objectID));
|
||||
|
||||
if (steamGame && localizedAppDetails) {
|
||||
return {
|
||||
...localizedAppDetails,
|
||||
|
@ -3,8 +3,8 @@ import { gameRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import type { GameShop } from "@types";
|
||||
import { getFileBase64 } from "@main/helpers";
|
||||
import { getSteamGameIconUrl } from "@main/services";
|
||||
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
|
||||
const addGameToLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@ -27,17 +27,29 @@ const addGameToLibrary = async (
|
||||
)
|
||||
.then(async ({ affected }) => {
|
||||
if (!affected) {
|
||||
const iconUrl = await getFileBase64(
|
||||
await getSteamGameIconUrl(objectID)
|
||||
);
|
||||
const steamGame = stateManager
|
||||
.getValue("steamGames")
|
||||
.find((game) => game.id === Number(objectID));
|
||||
|
||||
await gameRepository.insert({
|
||||
title,
|
||||
iconUrl,
|
||||
objectID,
|
||||
shop: gameShop,
|
||||
executablePath,
|
||||
});
|
||||
const iconUrl = steamGame?.clientIcon
|
||||
? getSteamAppAsset("icon", objectID, steamGame.clientIcon)
|
||||
: null;
|
||||
|
||||
await gameRepository
|
||||
.insert({
|
||||
title,
|
||||
iconUrl,
|
||||
objectID,
|
||||
shop: gameShop,
|
||||
executablePath,
|
||||
})
|
||||
.then(() => {
|
||||
if (iconUrl) {
|
||||
getFileBase64(iconUrl).then((base64) =>
|
||||
gameRepository.update({ objectID }, { iconUrl: base64 })
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { getSteamGameIconUrl } from "@main/services";
|
||||
import {
|
||||
gameRepository,
|
||||
repackRepository,
|
||||
@ -8,10 +7,11 @@ import {
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import type { GameShop } from "@types";
|
||||
import { getFileBase64 } from "@main/helpers";
|
||||
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
||||
import { In } from "typeorm";
|
||||
import { DownloadManager } from "@main/services";
|
||||
import { Downloader, GameStatus } from "@shared";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
|
||||
const startGameDownload = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@ -76,18 +76,34 @@ const startGameDownload = async (
|
||||
|
||||
return game;
|
||||
} else {
|
||||
const iconUrl = await getFileBase64(await getSteamGameIconUrl(objectID));
|
||||
const steamGame = stateManager
|
||||
.getValue("steamGames")
|
||||
.find((game) => game.id === Number(objectID));
|
||||
|
||||
const createdGame = await gameRepository.save({
|
||||
title,
|
||||
iconUrl,
|
||||
objectID,
|
||||
downloader,
|
||||
shop: gameShop,
|
||||
status: GameStatus.Downloading,
|
||||
downloadPath,
|
||||
repack: { id: repackId },
|
||||
});
|
||||
const iconUrl = steamGame?.clientIcon
|
||||
? getSteamAppAsset("icon", objectID, steamGame.clientIcon)
|
||||
: null;
|
||||
|
||||
const createdGame = await gameRepository
|
||||
.save({
|
||||
title,
|
||||
iconUrl,
|
||||
objectID,
|
||||
downloader,
|
||||
shop: gameShop,
|
||||
status: GameStatus.Downloading,
|
||||
downloadPath,
|
||||
repack: { id: repackId },
|
||||
})
|
||||
.then((result) => {
|
||||
if (iconUrl) {
|
||||
getFileBase64(iconUrl).then((base64) =>
|
||||
gameRepository.update({ objectID }, { iconUrl: base64 })
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
DownloadManager.downloadGame(createdGame.id);
|
||||
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
gogFormatter,
|
||||
onlinefixFormatter,
|
||||
} from "./formatters";
|
||||
import { months, repackers } from "../constants";
|
||||
import { repackers } from "../constants";
|
||||
|
||||
export const pipe =
|
||||
<T>(...fns: ((arg: T) => any)[]) =>
|
||||
@ -44,19 +44,6 @@ export const repackerFormatter: Record<
|
||||
onlinefix: onlinefixFormatter,
|
||||
};
|
||||
|
||||
export const formatUploadDate = (str: string) => {
|
||||
const date = new Date();
|
||||
|
||||
const [month, day, year] = str.split(" ");
|
||||
|
||||
date.setMonth(months.indexOf(month.replace(".", "")));
|
||||
date.setDate(Number(day.substring(0, 2)));
|
||||
date.setFullYear(Number("20" + year.replace("'", "")));
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
return date;
|
||||
};
|
||||
|
||||
export const getSteamAppAsset = (
|
||||
category: "library" | "hero" | "logo" | "icon",
|
||||
objectID: string,
|
||||
|
@ -19,8 +19,6 @@ autoUpdater.setFeedURL({
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) app.quit();
|
||||
|
||||
app.disableHardwareAcceleration();
|
||||
|
||||
i18n.init({
|
||||
resources,
|
||||
lng: "en",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { stateManager } from "./state-manager";
|
||||
import { repackersOn1337x } from "./constants";
|
||||
import { repackersOn1337x, seedsPath } from "./constants";
|
||||
import {
|
||||
getNewGOGGames,
|
||||
getNewRepacksFromUser,
|
||||
@ -11,7 +11,6 @@ import {
|
||||
import {
|
||||
gameRepository,
|
||||
repackRepository,
|
||||
steamGameRepository,
|
||||
userPreferencesRepository,
|
||||
} from "./repository";
|
||||
import { TorrentDownloader } from "./services";
|
||||
@ -20,6 +19,8 @@ import { Notification } from "electron";
|
||||
import { t } from "i18next";
|
||||
import { GameStatus } from "@shared";
|
||||
import { In } from "typeorm";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { RealDebridClient } from "./services/real-debrid";
|
||||
|
||||
startProcessWatcher();
|
||||
@ -69,18 +70,15 @@ const checkForNewRepacks = async (userPreferences: UserPreferences | null) => {
|
||||
};
|
||||
|
||||
const loadState = async (userPreferences: UserPreferences | null) => {
|
||||
const [repacks, steamGames] = await Promise.all([
|
||||
repackRepository.find({
|
||||
order: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
}),
|
||||
steamGameRepository.find({
|
||||
order: {
|
||||
name: "asc",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const repacks = await repackRepository.find({
|
||||
order: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
const steamGames = JSON.parse(
|
||||
fs.readFileSync(path.join(seedsPath, "steam-games.json"), "utf-8")
|
||||
);
|
||||
|
||||
stateManager.setValue("repacks", repacks);
|
||||
stateManager.setValue("steamGames", steamGames);
|
||||
|
@ -1,11 +1,5 @@
|
||||
import { dataSource } from "./data-source";
|
||||
import {
|
||||
Game,
|
||||
GameShopCache,
|
||||
Repack,
|
||||
UserPreferences,
|
||||
SteamGame,
|
||||
} from "@main/entity";
|
||||
import { Game, GameShopCache, Repack, UserPreferences } from "@main/entity";
|
||||
|
||||
export const gameRepository = dataSource.getRepository(Game);
|
||||
|
||||
@ -15,5 +9,3 @@ export const userPreferencesRepository =
|
||||
dataSource.getRepository(UserPreferences);
|
||||
|
||||
export const gameShopCacheRepository = dataSource.getRepository(GameShopCache);
|
||||
|
||||
export const steamGameRepository = dataSource.getRepository(SteamGame);
|
||||
|
40
src/main/scripts/get-games-icon-hash.ts
Normal file
40
src/main/scripts/get-games-icon-hash.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
import { getSteamGameClientIcon, logger } from "@main/services";
|
||||
import { chunk } from "lodash-es";
|
||||
import { seedsPath } from "@main/constants";
|
||||
|
||||
import type { SteamGame } from "@types";
|
||||
|
||||
const steamGamesPath = path.join(seedsPath, "steam-games.json");
|
||||
|
||||
const steamGames = JSON.parse(
|
||||
fs.readFileSync(steamGamesPath, "utf-8")
|
||||
) as SteamGame[];
|
||||
|
||||
const chunks = chunk(steamGames, 1500);
|
||||
|
||||
for (const chunk of chunks) {
|
||||
await Promise.all(
|
||||
chunk.map(async (steamGame) => {
|
||||
if (steamGame.clientIcon) return;
|
||||
|
||||
const index = steamGames.findIndex((game) => game.id === steamGame.id);
|
||||
|
||||
try {
|
||||
const clientIcon = await getSteamGameClientIcon(String(steamGame.id));
|
||||
|
||||
steamGames[index].clientIcon = clientIcon;
|
||||
|
||||
logger.log("info", `Set ${steamGame.name} client icon`);
|
||||
} catch (err) {
|
||||
steamGames[index].clientIcon = null;
|
||||
logger.log("info", `Could not set icon for ${steamGame.name}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
fs.writeFileSync(steamGamesPath, JSON.stringify(steamGames));
|
||||
logger.log("info", "Updated steam games");
|
||||
}
|
@ -1,13 +1,39 @@
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
import { formatUploadDate } from "@main/helpers";
|
||||
|
||||
import { Repack } from "@main/entity";
|
||||
import { requestWebPage, savePage } from "./helpers";
|
||||
|
||||
const months = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
];
|
||||
|
||||
export const request1337x = async (path: string) =>
|
||||
requestWebPage(`https://1337xx.to${path}`);
|
||||
|
||||
const formatUploadDate = (str: string) => {
|
||||
const date = new Date();
|
||||
|
||||
const [month, day, year] = str.split(" ");
|
||||
|
||||
date.setMonth(months.indexOf(month.replace(".", "")));
|
||||
date.setDate(Number(day.substring(0, 2)));
|
||||
date.setFullYear(Number("20" + year.replace("'", "")));
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
return date;
|
||||
};
|
||||
|
||||
/* TODO: $a will often be null */
|
||||
const getTorrentDetails = async (path: string) => {
|
||||
const response = await request1337x(path);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import axios from "axios";
|
||||
import { getSteamAppAsset } from "@main/helpers";
|
||||
|
||||
export interface SteamGridResponse {
|
||||
success: boolean;
|
||||
@ -59,16 +58,11 @@ export const getSteamGridGameById = async (
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getSteamGameIconUrl = async (objectID: string) => {
|
||||
export const getSteamGameClientIcon = async (objectID: string) => {
|
||||
const {
|
||||
data: { id: steamGridGameId },
|
||||
} = await getSteamGridData(objectID, "games", "steam");
|
||||
|
||||
const steamGridGame = await getSteamGridGameById(steamGridGameId);
|
||||
|
||||
return getSteamAppAsset(
|
||||
"icon",
|
||||
objectID,
|
||||
steamGridGame.data.platforms.steam.metadata.clienticon
|
||||
);
|
||||
return steamGridGame.data.platforms.steam.metadata.clienticon;
|
||||
};
|
||||
|
@ -4,8 +4,8 @@ import { app } from "electron";
|
||||
import { chunk } from "lodash-es";
|
||||
|
||||
import { createDataSource } from "@main/data-source";
|
||||
import { Repack, SteamGame } from "@main/entity";
|
||||
import { repackRepository, steamGameRepository } from "@main/repository";
|
||||
import { Repack } from "@main/entity";
|
||||
import { repackRepository } from "@main/repository";
|
||||
|
||||
export const resolveDatabaseUpdates = async () => {
|
||||
const updateDataSource = createDataSource({
|
||||
@ -16,12 +16,8 @@ export const resolveDatabaseUpdates = async () => {
|
||||
|
||||
return updateDataSource.initialize().then(async () => {
|
||||
const updateRepackRepository = updateDataSource.getRepository(Repack);
|
||||
const updateSteamGameRepository = updateDataSource.getRepository(SteamGame);
|
||||
|
||||
const [updateRepacks, updateSteamGames] = await Promise.all([
|
||||
updateRepackRepository.find(),
|
||||
updateSteamGameRepository.find(),
|
||||
]);
|
||||
const updateRepacks = await updateRepackRepository.find();
|
||||
|
||||
const updateRepacksChunks = chunk(updateRepacks, 800);
|
||||
|
||||
@ -33,16 +29,5 @@ export const resolveDatabaseUpdates = async () => {
|
||||
.orIgnore()
|
||||
.execute();
|
||||
}
|
||||
|
||||
const steamGamesChunks = chunk(updateSteamGames, 800);
|
||||
|
||||
for (const chunk of steamGamesChunks) {
|
||||
await steamGameRepository
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.values(chunk)
|
||||
.orIgnore()
|
||||
.execute();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { Repack, SteamGame } from "@main/entity";
|
||||
import type { Repack } from "@main/entity";
|
||||
import type { SteamGame } from "@types";
|
||||
|
||||
interface State {
|
||||
repacks: Repack[];
|
||||
|
@ -106,6 +106,8 @@ export const menuItemButtonLabel = style({
|
||||
export const gameIcon = style({
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
minWidth: "20px",
|
||||
minHeight: "20px",
|
||||
borderRadius: "4px",
|
||||
backgroundSize: "cover",
|
||||
});
|
||||
|
@ -13,6 +13,8 @@ import * as styles from "./sidebar.css";
|
||||
import { GameStatus, GameStatusHelper } from "@shared";
|
||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||
|
||||
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
||||
|
||||
const SIDEBAR_MIN_WIDTH = 200;
|
||||
const SIDEBAR_INITIAL_WIDTH = 250;
|
||||
const SIDEBAR_MAX_WIDTH = 450;
|
||||
@ -191,11 +193,16 @@ export function Sidebar() {
|
||||
handleSidebarItemClick(buildGameDetailsPath(game))
|
||||
}
|
||||
>
|
||||
<img
|
||||
className={styles.gameIcon}
|
||||
src={game.iconUrl}
|
||||
alt={game.title}
|
||||
/>
|
||||
{game.iconUrl ? (
|
||||
<img
|
||||
className={styles.gameIcon}
|
||||
src={game.iconUrl}
|
||||
alt={game.title}
|
||||
/>
|
||||
) : (
|
||||
<SteamLogo className={styles.gameIcon} />
|
||||
)}
|
||||
|
||||
<span className={styles.menuItemButtonLabel}>
|
||||
{getGameTitle(game)}
|
||||
</span>
|
||||
|
@ -137,3 +137,9 @@ export interface Steam250Game {
|
||||
title: string;
|
||||
objectID: string;
|
||||
}
|
||||
|
||||
export interface SteamGame {
|
||||
id: number;
|
||||
name: string;
|
||||
clientIcon: string | null;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user