Compare commits

..

2 Commits

Author SHA1 Message Date
Chubby Granny Chaser
3928770ef8 Merge branch 'main' into fix/HYD-860 2025-06-03 13:35:10 +01:00
Chubby Granny Chaser
de1dfca57e fix: fixing playtime dir on linux 2025-06-03 13:33:48 +01:00
29 changed files with 208 additions and 258 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "hydralauncher",
"version": "3.6.1",
"version": "3.6.0",
"description": "Hydra",
"main": "./out/main/index.js",
"author": "Los Broxas",

View File

@@ -4,6 +4,7 @@ from torrent_downloader import TorrentDownloader
from http_downloader import HttpDownloader
from profile_image_processor import ProfileImageProcessor
import libtorrent as lt
import os
app = Flask(__name__)
@@ -102,8 +103,26 @@ def process_list():
if auth_error:
return auth_error
process_list = [proc.info for proc in psutil.process_iter(['exe', 'cwd', 'pid', 'name', 'environ'])]
return jsonify(process_list), 200
processes = []
for proc in psutil.process_iter(['exe', 'cwd', 'pid', 'cmdline']):
try:
info = proc.info
cmdline = info.get('cmdline') or []
wine_launched_exe = None
for arg in cmdline:
if isinstance(arg, str) and arg.lower().endswith(".exe"):
wine_launched_exe = os.path.basename(arg)
break
exe_path = info.get('exe') or ''
info['name'] = wine_launched_exe or os.path.basename(exe_path)
processes.append(info)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
continue
return jsonify(processes), 200
@app.route("/profile-image", methods=["POST"])
def profile_image():

View File

@@ -515,8 +515,7 @@
"earned_points": "Earned points",
"show_achievements_on_profile": "Show your achievements on your profile",
"show_points_on_profile": "Show your earned points on your profile",
"error_adding_friend": "Could not send friend request. Please check friend code",
"friend_code_length_error": "Friend code must have 8 characters"
"error_adding_friend": "Could not send friend request. Please check friend code"
},
"achievement": {
"achievement_unlocked": "Achievement unlocked",

View File

@@ -508,8 +508,7 @@
"earned_points": "Pontos ganhos",
"show_achievements_on_profile": "Exiba suas conquistas no perfil",
"show_points_on_profile": "Exiba seus pontos ganhos no perfil",
"error_adding_friend": "Não foi possível enviar o pedido de amizade. Verifique o código de amizade inserido",
"friend_code_length_error": "Código de amigo deve ter 8 caracteres"
"error_adding_friend": "Não foi possível enviar o pedido de amizade. Verifique o código de amizade inserido"
},
"achievement": {
"achievement_unlocked": "Conquista desbloqueada",

View File

@@ -365,14 +365,14 @@
"installing_common_redist": "Установка…",
"show_download_speed_in_megabytes": "Показать скорость загрузки в мегабайтах в секунду",
"extract_files_by_default": "Извлекать файлы по умолчанию после загрузки",
"achievement_custom_notification_position": "Позиция уведомлений достижений",
"achievement_custom_notification_position": "Позиция настраиваемых уведомлений о достижениях",
"top-left": "Верхний левый угол",
"top-center": "Верхний центр",
"top-right": "Верхний правый угол",
"bottom-left": "Нижний левый угол",
"bottom-center": "Нижний центр",
"bottom-right": "Нижний правый угол",
"enable_achievement_custom_notifications": "Включить уведомления о достижениях",
"enable_achievement_custom_notifications": "Включить настраиваемые уведомления о достижениях",
"alignment": "Выравнивание",
"variation": "Вариация",
"default": "По умолчанию",

View File

@@ -8,9 +8,7 @@ const saveGameShopAssets = async (
shop: GameShop,
assets: ShopAssets
): Promise<void> => {
const key = levelKeys.game(shop, objectId);
const existingAssets = await gamesShopAssetsSublevel.get(key);
return gamesShopAssetsSublevel.put(key, { ...existingAssets, ...assets });
return gamesShopAssetsSublevel.put(levelKeys.game(shop, objectId), assets);
};
registerEvent("saveGameShopAssets", saveGameShopAssets);

View File

@@ -1,13 +1,13 @@
import { registerEvent } from "../register-event";
import type { GameShop } from "@types";
import { createGame } from "@main/services/library-sync";
import { updateLocalUnlockedAchievements } from "@main/services/achievements/update-local-unlocked-achivements";
import {
downloadsSublevel,
gamesShopAssetsSublevel,
gamesSublevel,
levelKeys,
} from "@main/level";
import { AchievementWatcherManager } from "@main/services/achievements/achievement-watcher-manager";
const addGameToLibrary = async (
_event: Electron.IpcMainInvokeEvent,
@@ -43,10 +43,7 @@ const addGameToLibrary = async (
await createGame(game).catch(() => {});
AchievementWatcherManager.firstSyncWithRemoteIfNeeded(
game.shop,
game.objectId
);
updateLocalUnlockedAchievements(game);
};
registerEvent("addGameToLibrary", addGameToLibrary);

View File

@@ -16,8 +16,7 @@ const resetGameAchievements = async (
objectId: string
) => {
try {
const levelKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(levelKey);
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
if (!game) return;
@@ -30,6 +29,8 @@ const resetGameAchievements = async (
}
}
const levelKey = levelKeys.game(game.shop, game.objectId);
await gameAchievementsSublevel
.get(levelKey)
.then(async (gameAchievements) => {

View File

@@ -3,7 +3,6 @@ import { registerEvent } from "../register-event";
import { HydraApi } from "@main/services";
import { db, levelKeys } from "@main/level";
import { AchievementWatcherManager } from "@main/services/achievements/achievement-watcher-manager";
const getComparedUnlockedAchievements = async (
_event: Electron.IpcMainInvokeEvent,
@@ -11,8 +10,6 @@ const getComparedUnlockedAchievements = async (
shop: GameShop,
userId: string
) => {
await AchievementWatcherManager.firstSyncWithRemoteIfNeeded(shop, objectId);
const userPreferences = await db.get<string, UserPreferences | null>(
levelKeys.userPreferences,
{

View File

@@ -2,7 +2,6 @@ import type { GameShop, UserAchievement, UserPreferences } from "@types";
import { registerEvent } from "../register-event";
import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data";
import { db, gameAchievementsSublevel, levelKeys } from "@main/level";
import { AchievementWatcherManager } from "@main/services/achievements/achievement-watcher-manager";
export const getUnlockedAchievements = async (
objectId: string,
@@ -63,7 +62,7 @@ export const getUnlockedAchievements = async (
!achievementData.hidden || showHiddenAchievementsDescription
? achievementData.description
: undefined,
};
} as UserAchievement;
})
.sort((a, b) => {
if (a.unlocked && !b.unlocked) return -1;
@@ -80,7 +79,6 @@ const getUnlockedAchievementsEvent = async (
objectId: string,
shop: GameShop
): Promise<UserAchievement[]> => {
AchievementWatcherManager.firstSyncWithRemoteIfNeeded(shop, objectId);
return getUnlockedAchievements(objectId, shop, false);
};

View File

@@ -10,7 +10,6 @@ import {
import type {
AchievementFile,
Game,
GameShop,
UnlockedAchievement,
UserPreferences,
} from "@types";
@@ -19,7 +18,7 @@ import { Cracker } from "@shared";
import { publishCombinedNewAchievementNotification } from "../notifications";
import { db, gamesSublevel, levelKeys } from "@main/level";
import { WindowManager } from "../window-manager";
import { setTimeout } from "node:timers/promises";
import { sleep } from "@main/helpers";
const fileStats: Map<string, number> = new Map();
const fltFiles: Map<string, Set<string>> = new Map();
@@ -38,7 +37,7 @@ const watchAchievementsWindows = async () => {
const gameAchievementFiles: AchievementFile[] = [];
for (const objectId of getAlternativeObjectIds(game.objectId)) {
gameAchievementFiles.push(...(achievementFiles.get(objectId) ?? []));
gameAchievementFiles.push(...(achievementFiles.get(objectId) || []));
gameAchievementFiles.push(
...findAchievementFileInExecutableDirectory(game)
@@ -128,11 +127,6 @@ const compareFile = (game: Game, file: AchievementFile) => {
);
return processAchievementFileDiff(game, file);
} catch (err) {
achievementsLogger.error(
"Error reading file",
file.filePath,
err instanceof Error ? err.message : err
);
fileStats.set(file.filePath, -1);
return;
}
@@ -142,69 +136,20 @@ const processAchievementFileDiff = async (
game: Game,
file: AchievementFile
) => {
const parsedAchievements = parseAchievementFile(file.filePath, file.type);
const unlockedAchievements = parseAchievementFile(file.filePath, file.type);
if (parsedAchievements.length) {
return mergeAchievements(game, parsedAchievements, true);
if (unlockedAchievements.length) {
return mergeAchievements(game, unlockedAchievements, true);
}
return 0;
};
export class AchievementWatcherManager {
private static _hasFinishedPreSearch = false;
public static get hasFinishedPreSearch() {
return this._hasFinishedPreSearch;
}
public static readonly alreadySyncedGames: Map<string, boolean> = new Map();
public static async firstSyncWithRemoteIfNeeded(
shop: GameShop,
objectId: string
) {
const gameKey = levelKeys.game(shop, objectId);
if (this.alreadySyncedGames.get(gameKey)) return;
const game = await gamesSublevel.get(gameKey).catch(() => null);
if (!game || game.isDeleted) return;
const gameAchievementFiles = findAchievementFiles(game);
const achievementFileInsideDirectory =
findAchievementFileInExecutableDirectory(game);
gameAchievementFiles.push(...achievementFileInsideDirectory);
const unlockedAchievements: UnlockedAchievement[] = [];
for (const achievementFile of gameAchievementFiles) {
const localAchievementFile = parseAchievementFile(
achievementFile.filePath,
achievementFile.type
);
if (localAchievementFile.length) {
unlockedAchievements.push(...localAchievementFile);
}
}
this.alreadySyncedGames.set(gameKey, true);
const newAchievements = await mergeAchievements(
game,
unlockedAchievements,
false
);
if (newAchievements > 0) {
this.notifyCombinedAchievementsUnlocked(1, newAchievements);
}
}
private static hasFinishedMergingWithRemote = false;
public static watchAchievements() {
if (!this.hasFinishedPreSearch) return;
if (!this.hasFinishedMergingWithRemote) return;
if (process.platform === "win32") {
return watchAchievementsWindows();
@@ -243,11 +188,7 @@ export class AchievementWatcherManager {
}
}
if (unlockedAchievements.length) {
return mergeAchievements(game, unlockedAchievements, false);
}
return 0;
return mergeAchievements(game, unlockedAchievements, false);
}
private static async getGameAchievementFilesWindows() {
@@ -296,44 +237,25 @@ export class AchievementWatcherManager {
);
}
private static async notifyCombinedAchievementsUnlocked(
totalNewGamesWithAchievements: number,
totalNewAchievements: number
) {
const userPreferences = await db.get<string, UserPreferences>(
levelKeys.userPreferences,
{
valueEncoding: "json",
}
);
if (userPreferences.achievementCustomNotificationsEnabled !== false) {
WindowManager.notificationWindow?.webContents.send(
"on-combined-achievements-unlocked",
totalNewGamesWithAchievements,
totalNewAchievements,
userPreferences.achievementCustomNotificationPosition ?? "top-left"
);
} else {
publishCombinedNewAchievementNotification(
totalNewAchievements,
totalNewGamesWithAchievements
);
}
}
public static async preSearchAchievements() {
await sleep(2000);
try {
const gameAchievementFiles =
process.platform === "win32"
? await this.getGameAchievementFilesWindows()
: await this.getGameAchievementFilesLinux();
const newAchievementsCount = await Promise.all(
gameAchievementFiles.map(({ game, achievementFiles }) => {
return this.preProcessGameAchievementFiles(game, achievementFiles);
})
);
const newAchievementsCount: number[] = [];
for (const { game, achievementFiles } of gameAchievementFiles) {
const result = await this.preProcessGameAchievementFiles(
game,
achievementFiles
);
newAchievementsCount.push(result);
}
const totalNewGamesWithAchievements = newAchievementsCount.filter(
(achievements) => achievements
@@ -345,16 +267,34 @@ export class AchievementWatcherManager {
);
if (totalNewAchievements > 0) {
await setTimeout(4000);
this.notifyCombinedAchievementsUnlocked(
totalNewGamesWithAchievements,
totalNewAchievements
const userPreferences = await db.get<string, UserPreferences>(
levelKeys.userPreferences,
{
valueEncoding: "json",
}
);
if (userPreferences.achievementNotificationsEnabled !== false) {
if (userPreferences.achievementCustomNotificationsEnabled !== false) {
WindowManager.notificationWindow?.webContents.send(
"on-combined-achievements-unlocked",
totalNewGamesWithAchievements,
totalNewAchievements,
userPreferences.achievementCustomNotificationPosition ??
"top-left"
);
} else {
publishCombinedNewAchievementNotification(
totalNewAchievements,
totalNewGamesWithAchievements
);
}
}
}
} catch (err) {
achievementsLogger.error("Error on preSearchAchievements", err);
}
this._hasFinishedPreSearch = true;
this.hasFinishedMergingWithRemote = true;
}
}

View File

@@ -303,7 +303,7 @@ export const findAchievementFileInExecutableDirectory = (
"achievements.ini"
),
},
].filter((file) => fs.existsSync(file.filePath)) as AchievementFile[];
];
};
const mapFileLocationWithObjectId = (

View File

@@ -1,39 +1,24 @@
import { HydraApi } from "../hydra-api";
import type { GameAchievement, GameShop, SteamAchievement } from "@types";
import type { GameShop, SteamAchievement } from "@types";
import { UserNotLoggedInError } from "@shared";
import { logger } from "../logger";
import { db, gameAchievementsSublevel, levelKeys } from "@main/level";
import { AxiosError } from "axios";
const LOCAL_CACHE_EXPIRATION = 1000 * 60 * 30; // 30 minutes
const getModifiedSinceHeader = (
cachedAchievements: GameAchievement | undefined
): Date | undefined => {
if (!cachedAchievements) {
return undefined;
}
return cachedAchievements.updatedAt
? new Date(cachedAchievements.updatedAt)
: undefined;
};
export const getGameAchievementData = async (
objectId: string,
shop: GameShop,
useCachedData: boolean
) => {
const gameKey = levelKeys.game(shop, objectId);
const cachedAchievements = await gameAchievementsSublevel.get(
levelKeys.game(shop, objectId)
);
const cachedAchievements = await gameAchievementsSublevel.get(gameKey);
if (cachedAchievements?.achievements && useCachedData)
if (cachedAchievements && useCachedData)
return cachedAchievements.achievements;
if (
cachedAchievements?.achievements &&
Date.now() < (cachedAchievements.updatedAt ?? 0) + LOCAL_CACHE_EXPIRATION
cachedAchievements &&
Date.now() < (cachedAchievements.cacheExpiresTimestamp ?? 0)
) {
return cachedAchievements.achievements;
}
@@ -44,22 +29,18 @@ export const getGameAchievementData = async (
})
.then((language) => language || "en");
return HydraApi.get<SteamAchievement[]>(
"/games/achievements",
{
shop,
objectId,
language,
},
{
ifModifiedSince: getModifiedSinceHeader(cachedAchievements),
}
)
return HydraApi.get<SteamAchievement[]>("/games/achievements", {
shop,
objectId,
language,
})
.then(async (achievements) => {
await gameAchievementsSublevel.put(gameKey, {
await gameAchievementsSublevel.put(levelKeys.game(shop, objectId), {
unlockedAchievements: cachedAchievements?.unlockedAchievements ?? [],
achievements,
updatedAt: Date.now() + LOCAL_CACHE_EXPIRATION,
cacheExpiresTimestamp: achievements.length
? Date.now() + 1000 * 60 * 30 // 30 minutes
: undefined,
});
return achievements;
@@ -69,14 +50,8 @@ export const getGameAchievementData = async (
throw err;
}
const isNotModified = (err as AxiosError)?.response?.status === 304;
if (isNotModified) {
return cachedAchievements?.achievements ?? [];
}
logger.error("Failed to get game achievements for", objectId, err);
return cachedAchievements?.achievements ?? [];
return [];
});
};

View File

@@ -14,7 +14,6 @@ import { SubscriptionRequiredError } from "@shared";
import { achievementsLogger } from "../logger";
import { db, gameAchievementsSublevel, levelKeys } from "@main/level";
import { getGameAchievementData } from "./get-game-achievement-data";
import { AchievementWatcherManager } from "./achievement-watcher-manager";
const isRareAchievement = (points: number) => {
const rawPercentage = (50 - Math.sqrt(points)) * 2;
@@ -36,7 +35,7 @@ const saveAchievementsOnLocal = async (
await gameAchievementsSublevel.put(levelKey, {
achievements: gameAchievement?.achievements ?? [],
unlockedAchievements: unlockedAchievements,
updatedAt: gameAchievement?.updatedAt,
cacheExpiresTimestamp: gameAchievement?.cacheExpiresTimestamp,
});
if (!sendUpdateEvent) return;
@@ -57,9 +56,9 @@ export const mergeAchievements = async (
achievements: UnlockedAchievement[],
publishNotification: boolean
) => {
const gameKey = levelKeys.game(game.shop, game.objectId);
let localGameAchievement = await gameAchievementsSublevel.get(gameKey);
let localGameAchievement = await gameAchievementsSublevel.get(
levelKeys.game(game.shop, game.objectId)
);
const userPreferences = await db.get<string, UserPreferences>(
levelKeys.userPreferences,
{
@@ -68,8 +67,10 @@ export const mergeAchievements = async (
);
if (!localGameAchievement) {
await getGameAchievementData(game.objectId, game.shop, false);
localGameAchievement = await gameAchievementsSublevel.get(gameKey);
await getGameAchievementData(game.objectId, game.shop, true);
localGameAchievement = await gameAchievementsSublevel.get(
levelKeys.game(game.shop, game.objectId)
);
}
const achievementsData = localGameAchievement?.achievements ?? [];
@@ -135,12 +136,6 @@ export const mergeAchievements = async (
};
});
achievementsLogger.log(
"Publishing achievement notification",
game.objectId,
game.title
);
if (userPreferences.achievementCustomNotificationsEnabled !== false) {
WindowManager.notificationWindow?.webContents.send(
"on-achievement-unlocked",
@@ -158,11 +153,7 @@ export const mergeAchievements = async (
}
}
const shouldSyncWithRemote =
game.remoteId &&
(newAchievements.length || AchievementWatcherManager.hasFinishedPreSearch);
if (shouldSyncWithRemote) {
if (game.remoteId) {
await HydraApi.put<UpdatedUnlockedAchievements | undefined>(
"/profile/games/achievements",
{
@@ -203,11 +194,8 @@ export const mergeAchievements = async (
mergedLocalAchievements,
publishNotification
);
})
.finally(() => {
AchievementWatcherManager.alreadySyncedGames.set(gameKey, true);
});
} else if (newAchievements.length) {
} else {
await saveAchievementsOnLocal(
game.objectId,
game.shop,

View File

@@ -0,0 +1,31 @@
import {
findAchievementFiles,
findAchievementFileInExecutableDirectory,
} from "./find-achivement-files";
import { parseAchievementFile } from "./parse-achievement-file";
import { mergeAchievements } from "./merge-achievements";
import type { Game, UnlockedAchievement } from "@types";
export const updateLocalUnlockedAchievements = async (game: Game) => {
const gameAchievementFiles = findAchievementFiles(game);
const achievementFileInsideDirectory =
findAchievementFileInExecutableDirectory(game);
gameAchievementFiles.push(...achievementFileInsideDirectory);
const unlockedAchievements: UnlockedAchievement[] = [];
for (const achievementFile of gameAchievementFiles) {
const localAchievementFile = parseAchievementFile(
achievementFile.filePath,
achievementFile.type
);
if (localAchievementFile.length) {
unlockedAchievements.push(...localAchievementFile);
}
}
mergeAchievements(game, unlockedAchievements, false);
};

View File

@@ -16,7 +16,6 @@ import { WSClient } from "./ws/ws-client";
interface HydraApiOptions {
needsAuth?: boolean;
needsSubscription?: boolean;
ifModifiedSince?: Date;
}
interface HydraApiUserAuth {
@@ -338,13 +337,8 @@ export class HydraApi {
) {
await this.validateOptions(options);
const headers = {
...this.getAxiosConfig().headers,
"Hydra-If-Modified-Since": options?.ifModifiedSince?.toUTCString(),
};
return this.instance
.get<T>(url, { params, ...this.getAxiosConfig(), headers })
.get<T>(url, { params, ...this.getAxiosConfig() })
.then((response) => response.data)
.catch(this.handleUnauthorizedError);
}

View File

@@ -13,8 +13,9 @@ export const mergeWithRemoteGames = async () => {
return HydraApi.get<ProfileGame[]>("/profile/games")
.then(async (response) => {
for (const game of response) {
const gameKey = levelKeys.game(game.shop, game.objectId);
const localGame = await gamesSublevel.get(gameKey);
const localGame = await gamesSublevel.get(
levelKeys.game(game.shop, game.objectId)
);
if (localGame) {
const updatedLastTimePlayed =
@@ -29,7 +30,7 @@ export const mergeWithRemoteGames = async () => {
? game.playTimeInMilliseconds
: localGame.playTimeInMilliseconds;
await gamesSublevel.put(gameKey, {
await gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
...localGame,
remoteId: game.id,
lastTimePlayed: updatedLastTimePlayed,
@@ -37,7 +38,7 @@ export const mergeWithRemoteGames = async () => {
favorite: game.isFavorite ?? localGame.favorite,
});
} else {
await gamesSublevel.put(gameKey, {
await gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
objectId: game.objectId,
title: game.title,
remoteId: game.id,
@@ -50,17 +51,20 @@ export const mergeWithRemoteGames = async () => {
});
}
await gamesShopAssetsSublevel.put(gameKey, {
shop: game.shop,
objectId: game.objectId,
title: game.title,
coverImageUrl: game.coverImageUrl,
libraryHeroImageUrl: game.libraryHeroImageUrl,
libraryImageUrl: game.libraryImageUrl,
logoImageUrl: game.logoImageUrl,
iconUrl: game.iconUrl,
logoPosition: game.logoPosition,
});
await gamesShopAssetsSublevel.put(
levelKeys.game(game.shop, game.objectId),
{
shop: game.shop,
objectId: game.objectId,
title: game.title,
coverImageUrl: game.coverImageUrl,
libraryHeroImageUrl: game.libraryHeroImageUrl,
libraryImageUrl: game.libraryImageUrl,
logoImageUrl: game.logoImageUrl,
iconUrl: game.iconUrl,
logoPosition: game.logoPosition,
}
);
}
})
.catch(() => {});

View File

@@ -34,7 +34,9 @@ export const uploadGamesBatch = async () => {
await mergeWithRemoteGames();
AchievementWatcherManager.preSearchAchievements();
if (HydraApi.isLoggedIn()) {
AchievementWatcherManager.preSearchAchievements();
}
if (WindowManager.mainWindow)
WindowManager.mainWindow.webContents.send("on-library-batch-complete");

View File

@@ -8,7 +8,6 @@ import { gamesSublevel, levelKeys } from "@main/level";
import { CloudSync } from "./cloud-sync";
import { logger } from "./logger";
import path from "path";
import { AchievementWatcherManager } from "./achievements/achievement-watcher-manager";
export const gamesPlaytime = new Map<
string,
@@ -191,11 +190,6 @@ export const watchProcesses = async () => {
function onOpenGame(game: Game) {
const now = performance.now();
AchievementWatcherManager.firstSyncWithRemoteIfNeeded(
game.shop,
game.objectId
);
gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), {
lastTick: now,
firstTick: now,

View File

@@ -17,18 +17,30 @@ export interface SteamAppDetailsResponse {
};
}
export const getSteamLocation = async () => {
export const getSteamLocation = async (): Promise<string> => {
const home = SystemPath.getPath("home");
if (process.platform === "linux") {
return path.join(SystemPath.getPath("home"), ".local", "share", "Steam");
const candidates = [
path.join(home, ".local", "share", "Steam"),
path.join(home, ".steam", "steam"),
path.join(home, ".steam", "root"),
];
for (const candidate of candidates) {
try {
fs.accessSync(candidate, fs.constants.F_OK);
return candidate;
} catch {
continue;
}
}
throw new Error("Steam installation not found on Linux");
}
if (process.platform === "darwin") {
return path.join(
SystemPath.getPath("home"),
"Library",
"Application Support",
"Steam"
);
return path.join(home, "Library", "Application Support", "Steam");
}
const regKey = new WinReg({
@@ -39,7 +51,7 @@ export const getSteamLocation = async () => {
return new Promise<string>((resolve, reject) => {
regKey.get("SteamPath", (err, value) => {
if (err) {
reject(err);
return reject(err);
}
resolve(value.value);

View File

@@ -370,11 +370,14 @@ export class WindowManager {
},
});
this.notificationWindow.setIgnoreMouseEvents(true);
// this.notificationWindow.setVisibleOnAllWorkspaces(true, {
// visibleOnFullScreen: true,
// });
this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1);
this.loadNotificationWindowURL();
if (!app.isPackaged || isStaging) {
if (isStaging) {
this.notificationWindow.webContents.openDevTools();
}
}
@@ -461,7 +464,7 @@ export class WindowManager {
editorWindow.once("ready-to-show", () => {
editorWindow.show();
this.mainWindow?.webContents.openDevTools();
if (!app.isPackaged || isStaging) {
if (isStaging) {
editorWindow.webContents.openDevTools();
}
});

View File

@@ -74,6 +74,6 @@
}
&__error-label {
color: globals.$error-color;
color: globals.$danger-color;
}
}

View File

@@ -183,13 +183,10 @@ export function GameDetailsContextProvider({
.catch(() => {});
}
window.electron
.syncGameByObjectId(shop, objectId)
.then(() => {
if (abortController.signal.aborted) return;
updateGame();
})
.catch(() => {});
window.electron.syncGameByObjectId(shop, objectId).then(() => {
if (abortController.signal.aborted) return;
updateGame();
});
}, [
updateGame,
dispatch,

View File

@@ -158,8 +158,8 @@ $logo-max-width: 200px;
&-points {
display: flex;
align-items: center;
justify-content: end;
gap: 4px;
margin-right: 4px;
font-weight: 600;
&--locked {

View File

@@ -55,6 +55,7 @@ export function AchievementNotification() {
isHidden: false,
isRare: false,
isPlatinum: false,
points: 0,
iconUrl: "https://cdn.losbroxas.org/favicon.svg",
},
]);

View File

@@ -51,14 +51,6 @@ export const UserFriendModalAddFriend = ({
}
};
const validateFriendCode = (callback: () => void) => {
if (friendCode.length === 8) {
return callback();
}
showErrorToast(t("friend_code_length_error"));
};
const handleCancelFriendRequest = (userId: string) => {
updateFriendRequestState(userId, "CANCEL").catch(() => {
showErrorToast(t("try_again"));
@@ -99,13 +91,13 @@ export const UserFriendModalAddFriend = ({
disabled={isAddingFriend}
className="user-friend-modal-add-friend__button"
type="button"
onClick={() => validateFriendCode(handleClickAddFriend)}
onClick={handleClickAddFriend}
>
{isAddingFriend ? t("sending") : t("add")}
</Button>
<Button
onClick={() => validateFriendCode(handleClickSeeProfile)}
onClick={handleClickSeeProfile}
disabled={isAddingFriend}
className="user-friend-modal-add-friend__button"
type="button"

View File

@@ -7,7 +7,6 @@ $body-color: #8e919b;
$border-color: rgba(255, 255, 255, 0.15);
$success-color: #1c9749;
$danger-color: #801d1e;
$error-color: #e11d48;
$warning-color: #ffc107;
$brand-teal: #16b195;

View File

@@ -112,6 +112,8 @@ export interface UserFriend {
id: string;
displayName: string;
profileImageUrl: string | null;
createdAt: string;
updatedAt: string;
currentGame:
| (ShopAssets & {
sessionDurationInSeconds: number;
@@ -144,6 +146,8 @@ export interface UserRelation {
AId: string;
BId: string;
status: "ACCEPTED" | "PENDING";
createdAt: string;
updatedAt: string;
}
export type UserProfileCurrentGame = GameRunning &
@@ -322,11 +326,17 @@ export interface CatalogueSearchPayload {
export type CatalogueSearchResult = {
id: string;
objectId: string;
title: string;
shop: GameShop;
tags: string[];
genres: string[];
} & Pick<ShopAssets, "libraryImageUrl">;
objectId: string;
shop: GameShop;
createdAt: Date;
updatedAt: Date;
title: string;
installCount: number;
achievementCount: number;
shopData: string;
} & ShopAssets;
export type LibraryGame = Game &
Partial<ShopAssets> & {

View File

@@ -68,7 +68,7 @@ export interface Download {
export interface GameAchievement {
achievements: SteamAchievement[];
unlockedAchievements: UnlockedAchievement[];
updatedAt: number | undefined;
cacheExpiresTimestamp: number | undefined;
}
export type AchievementCustomNotificationPosition =