feat: separate game assets from game stats

This commit is contained in:
Zamitto
2025-10-11 11:26:05 -03:00
parent 6146a1fbf1
commit 9bada771df
14 changed files with 111 additions and 84 deletions

View File

@@ -0,0 +1,51 @@
import type { GameShop, ShopAssets } from "@types";
import { registerEvent } from "../register-event";
import { HydraApi } from "@main/services";
import { gamesShopAssetsSublevel, levelKeys } from "@main/level";
const LOCAL_CACHE_EXPIRATION = 1000 * 60 * 30; // 30 minutes
export const getGameAssets = async (objectId: string, shop: GameShop) => {
const cachedAssets = await gamesShopAssetsSublevel.get(
levelKeys.game(shop, objectId)
);
if (
cachedAssets &&
cachedAssets.updatedAt + LOCAL_CACHE_EXPIRATION > Date.now()
) {
return cachedAssets;
}
return HydraApi.get<ShopAssets | null>(
`/games/${shop}/${objectId}/assets`,
null,
{
needsAuth: false,
}
).then(async (assets) => {
if (!assets) return null;
// Preserve existing title if it differs from the incoming title (indicating it was customized)
const shouldPreserveTitle =
cachedAssets?.title && cachedAssets.title !== assets.title;
await gamesShopAssetsSublevel.put(levelKeys.game(shop, objectId), {
...assets,
title: shouldPreserveTitle ? cachedAssets.title : assets.title,
updatedAt: Date.now(),
});
return assets;
});
};
const getGameAssetsEvent = async (
_event: Electron.IpcMainInvokeEvent,
objectId: string,
shop: GameShop
) => {
return getGameAssets(objectId, shop);
};
registerEvent("getGameAssets", getGameAssetsEvent);

View File

@@ -1,25 +0,0 @@
import type { GameShop, ShopAssets } from "@types";
import { gamesShopAssetsSublevel, levelKeys } from "@main/level";
import { registerEvent } from "../register-event";
const saveGameShopAssets = async (
_event: Electron.IpcMainInvokeEvent,
objectId: string,
shop: GameShop,
assets: ShopAssets
): Promise<void> => {
const key = levelKeys.game(shop, objectId);
const existingAssets = await gamesShopAssetsSublevel.get(key);
// Preserve existing title if it differs from the incoming title (indicating it was customized)
const shouldPreserveTitle =
existingAssets?.title && existingAssets.title !== assets.title;
return gamesShopAssetsSublevel.put(key, {
...existingAssets,
...assets,
title: shouldPreserveTitle ? existingAssets.title : assets.title,
});
};
registerEvent("saveGameShopAssets", saveGameShopAssets);

View File

@@ -27,6 +27,7 @@ const addCustomGameToLibrary = async (
}
const assets = {
updatedAt: Date.now(),
objectId,
shop,
title,

View File

@@ -1,12 +1,11 @@
import { registerEvent } from "../register-event";
import type { GameShop, GameStats } from "@types";
import type { GameShop, ShopAssets } from "@types";
import { gamesSublevel, levelKeys } from "@main/level";
import {
composeSteamShortcut,
getSteamLocation,
getSteamShortcuts,
getSteamUsersIds,
HydraApi,
logger,
SystemPath,
writeSteamShortcuts,
@@ -15,6 +14,7 @@ import fs from "node:fs";
import axios from "axios";
import path from "node:path";
import { ASSETS_PATH } from "@main/constants";
import { getGameAssets } from "../catalogue/get-game-assets";
const downloadAsset = async (downloadPath: string, url?: string | null) => {
try {
@@ -41,7 +41,7 @@ const downloadAsset = async (downloadPath: string, url?: string | null) => {
const downloadAssetsFromSteam = async (
shop: GameShop,
objectId: string,
assets: GameStats["assets"]
assets: ShopAssets | null
) => {
const gameAssetsPath = path.join(ASSETS_PATH, `${shop}-${objectId}`);
@@ -86,9 +86,7 @@ const createSteamShortcut = async (
throw new Error("No executable path found for game");
}
const { assets } = await HydraApi.get<GameStats>(
`/games/${shop}/${objectId}/stats`
);
const assets = await getGameAssets(objectId, shop);
const steamUserIds = await getSteamUsersIds();

View File

@@ -3,9 +3,9 @@ import type { ShopAssets } from "@types";
import { db } from "../level";
import { levelKeys } from "./keys";
export const gamesShopAssetsSublevel = db.sublevel<string, ShopAssets>(
levelKeys.gameShopAssets,
{
valueEncoding: "json",
}
);
export const gamesShopAssetsSublevel = db.sublevel<
string,
ShopAssets & { updatedAt: number }
>(levelKeys.gameShopAssets, {
valueEncoding: "json",
});

View File

@@ -58,7 +58,11 @@ export const mergeWithRemoteGames = async () => {
});
}
const localGameShopAsset = await gamesShopAssetsSublevel.get(gameKey);
await gamesShopAssetsSublevel.put(gameKey, {
updatedAt: Date.now(),
...localGameShopAsset,
shop: game.shop,
objectId: game.objectId,
title: localGame?.title || game.title, // Preserve local title if it exists

View File

@@ -10,7 +10,7 @@ import icon from "@resources/icon.png?asset";
import { NotificationOptions, toXmlString } from "./xml";
import { logger } from "../logger";
import { WindowManager } from "../window-manager";
import type { Game, GameStats, UserPreferences, UserProfile } from "@types";
import type { Game, UserPreferences, UserProfile } from "@types";
import { db, levelKeys } from "@main/level";
import { restartAndInstallUpdate } from "@main/events/autoupdater/restart-and-install-update";
import { SystemPath } from "../system-path";
@@ -108,15 +108,14 @@ export const publishNewFriendRequestNotification = async (
};
export const publishFriendStartedPlayingGameNotification = async (
friend: UserProfile,
game: GameStats
friend: UserProfile
) => {
new Notification({
title: t("friend_started_playing_game", {
ns: "notifications",
displayName: friend.displayName,
}),
body: game.assets?.title,
body: friend?.currentGame?.title,
icon: friend?.profileImageUrl
? await downloadImage(friend.profileImageUrl)
: trayIcon,

View File

@@ -2,7 +2,7 @@ import type { FriendGameSession } from "@main/generated/envelope";
import { db, levelKeys } from "@main/level";
import { HydraApi } from "@main/services/hydra-api";
import { publishFriendStartedPlayingGameNotification } from "@main/services/notifications";
import type { GameStats, UserPreferences, UserProfile } from "@types";
import type { UserPreferences, UserProfile } from "@types";
export const friendGameSessionEvent = async (payload: FriendGameSession) => {
const userPreferences = await db.get<string, UserPreferences | null>(
@@ -14,12 +14,9 @@ export const friendGameSessionEvent = async (payload: FriendGameSession) => {
if (userPreferences?.friendStartGameNotificationsEnabled === false) return;
const [friend, gameStats] = await Promise.all([
HydraApi.get<UserProfile>(`/users/${payload.friendId}`),
HydraApi.get<GameStats>(`/games/steam/${payload.objectId}/stats`),
]).catch(() => [null, null]);
const friend = await HydraApi.get<UserProfile>(`/users/${payload.friendId}`);
if (friend && gameStats) {
publishFriendStartedPlayingGameNotification(friend, gameStats);
if (friend) {
publishFriendStartedPlayingGameNotification(friend);
}
};