mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-26 20:31:03 +00:00
feat: separate game assets from game stats
This commit is contained in:
51
src/main/events/catalogue/get-game-assets.ts
Normal file
51
src/main/events/catalogue/get-game-assets.ts
Normal 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);
|
||||
@@ -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);
|
||||
@@ -27,6 +27,7 @@ const addCustomGameToLibrary = async (
|
||||
}
|
||||
|
||||
const assets = {
|
||||
updatedAt: Date.now(),
|
||||
objectId,
|
||||
shop,
|
||||
title,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user