mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
Merge branch 'feature/game-achievements' into chore/test-preview
# Conflicts: # src/main/events/library/add-game-to-library.ts # src/main/services/achievements/parse-achievement-file.ts
This commit is contained in:
commit
45c3cb8ca9
@ -1,88 +1,45 @@
|
||||
import type { GameAchievement, GameShop } from "@types";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services";
|
||||
import {
|
||||
gameAchievementRepository,
|
||||
gameRepository,
|
||||
userPreferencesRepository,
|
||||
} from "@main/repository";
|
||||
import { UserNotLoggedInError } from "@shared";
|
||||
import { Game } from "@main/entity";
|
||||
|
||||
const getAchievementsDataFromApi = async (
|
||||
objectId: string,
|
||||
shop: string,
|
||||
game: Game | null
|
||||
) => {
|
||||
const userPreferences = await userPreferencesRepository.findOne({
|
||||
where: { id: 1 },
|
||||
});
|
||||
|
||||
return HydraApi.get("/games/achievements", {
|
||||
objectId,
|
||||
shop,
|
||||
language: userPreferences?.language || "en",
|
||||
})
|
||||
.then((achievements) => {
|
||||
if (game) {
|
||||
gameAchievementRepository.upsert(
|
||||
{
|
||||
objectId,
|
||||
shop,
|
||||
achievements: JSON.stringify(achievements),
|
||||
},
|
||||
["objectId", "shop"]
|
||||
);
|
||||
}
|
||||
|
||||
return achievements;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err instanceof UserNotLoggedInError) throw err;
|
||||
return [];
|
||||
});
|
||||
};
|
||||
import { gameAchievementRepository } from "@main/repository";
|
||||
import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data";
|
||||
|
||||
const getGameAchievements = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
objectId: string,
|
||||
shop: GameShop
|
||||
): Promise<GameAchievement[]> => {
|
||||
const [game, cachedAchievements] = await Promise.all([
|
||||
gameRepository.findOne({
|
||||
where: { objectID: objectId, shop },
|
||||
}),
|
||||
gameAchievementRepository.findOne({ where: { objectId, shop } }),
|
||||
]);
|
||||
const cachedAchievements = await gameAchievementRepository.findOne({
|
||||
where: { objectId, shop },
|
||||
});
|
||||
|
||||
const gameAchievements = cachedAchievements?.achievements
|
||||
const achievementsData = cachedAchievements?.achievements
|
||||
? JSON.parse(cachedAchievements.achievements)
|
||||
: await getAchievementsDataFromApi(objectId, shop, game);
|
||||
: await getGameAchievementData(objectId, shop);
|
||||
|
||||
const unlockedAchievements = JSON.parse(
|
||||
cachedAchievements?.unlockedAchievements || "[]"
|
||||
) as { name: string; unlockTime: number }[];
|
||||
|
||||
return gameAchievements
|
||||
.map((achievement) => {
|
||||
return achievementsData
|
||||
.map((achievementData) => {
|
||||
const unlockedAchiement = unlockedAchievements.find(
|
||||
(localAchievement) => {
|
||||
return (
|
||||
localAchievement.name.toUpperCase() ==
|
||||
achievement.name.toUpperCase()
|
||||
achievementData.name.toUpperCase()
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (unlockedAchiement) {
|
||||
return {
|
||||
...achievement,
|
||||
...achievementData,
|
||||
unlocked: true,
|
||||
unlockTime: unlockedAchiement.unlockTime,
|
||||
};
|
||||
}
|
||||
|
||||
return { ...achievement, unlocked: false, unlockTime: null };
|
||||
return { ...achievementData, unlocked: false, unlockTime: null };
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (a.unlocked && !b.unlocked) return -1;
|
||||
|
@ -44,12 +44,12 @@ const addGameToLibrary = async (
|
||||
});
|
||||
}
|
||||
|
||||
updateLocalUnlockedAchivements(objectId);
|
||||
|
||||
const game = await gameRepository.findOne({
|
||||
where: { objectID: objectId },
|
||||
});
|
||||
|
||||
updateLocalUnlockedAchivements(game!);
|
||||
|
||||
createGame(game!).catch(() => {});
|
||||
});
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import fs, { readdirSync } from "node:fs";
|
||||
import {
|
||||
findAchievementFileInExecutableDirectory,
|
||||
findAllAchievementFiles,
|
||||
getAlternativeObjectIds,
|
||||
} from "./find-achivement-files";
|
||||
import type { AchievementFile } from "@types";
|
||||
import { achievementsLogger, logger } from "../logger";
|
||||
@ -23,25 +24,27 @@ export const watchAchievements = async () => {
|
||||
|
||||
if (games.length === 0) return;
|
||||
|
||||
const achievementFiles = await findAllAchievementFiles();
|
||||
const achievementFiles = findAllAchievementFiles();
|
||||
|
||||
for (const game of games) {
|
||||
const gameAchievementFiles = achievementFiles.get(game.objectID) || [];
|
||||
const achievementFileInsideDirectory =
|
||||
findAchievementFileInExecutableDirectory(game);
|
||||
for (const objectId of getAlternativeObjectIds(game.objectID)) {
|
||||
const gameAchievementFiles = achievementFiles.get(objectId) || [];
|
||||
const achievementFileInsideDirectory =
|
||||
findAchievementFileInExecutableDirectory(game);
|
||||
|
||||
gameAchievementFiles.push(...achievementFileInsideDirectory);
|
||||
gameAchievementFiles.push(...achievementFileInsideDirectory);
|
||||
|
||||
if (!gameAchievementFiles.length) continue;
|
||||
if (!gameAchievementFiles.length) continue;
|
||||
|
||||
console.log(
|
||||
"Achievements files to observe for:",
|
||||
game.title,
|
||||
gameAchievementFiles
|
||||
);
|
||||
console.log(
|
||||
"Achievements files to observe for:",
|
||||
game.title,
|
||||
gameAchievementFiles
|
||||
);
|
||||
|
||||
for (const file of gameAchievementFiles) {
|
||||
compareFile(game, file);
|
||||
for (const file of gameAchievementFiles) {
|
||||
compareFile(game, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -50,10 +53,7 @@ const processAchievementFileDiff = async (
|
||||
game: Game,
|
||||
file: AchievementFile
|
||||
) => {
|
||||
const unlockedAchievements = await parseAchievementFile(
|
||||
file.filePath,
|
||||
file.type
|
||||
);
|
||||
const unlockedAchievements = parseAchievementFile(file.filePath, file.type);
|
||||
|
||||
logger.log("Achievements from file", file.filePath, unlockedAchievements);
|
||||
|
||||
|
@ -27,7 +27,7 @@ const crackers = [
|
||||
Cracker.flt,
|
||||
];
|
||||
|
||||
const getPathFromCracker = async (cracker: Cracker) => {
|
||||
const getPathFromCracker = (cracker: Cracker) => {
|
||||
if (cracker === Cracker.codex) {
|
||||
return [
|
||||
{
|
||||
@ -167,7 +167,8 @@ const getPathFromCracker = async (cracker: Cracker) => {
|
||||
throw new Error(`Cracker ${cracker} not implemented`);
|
||||
};
|
||||
|
||||
const getAlternativeObjectIds = (objectId: string) => {
|
||||
export const getAlternativeObjectIds = (objectId: string) => {
|
||||
// Dishonored
|
||||
if (objectId === "205100") {
|
||||
return ["205100", "217980", "31292"];
|
||||
}
|
||||
@ -175,13 +176,11 @@ const getAlternativeObjectIds = (objectId: string) => {
|
||||
return [objectId];
|
||||
};
|
||||
|
||||
export const findAchievementFiles = async (game: Game) => {
|
||||
export const findAchievementFiles = (game: Game) => {
|
||||
const achievementFiles: AchievementFile[] = [];
|
||||
|
||||
for (const cracker of crackers) {
|
||||
for (const { folderPath, fileLocation } of await getPathFromCracker(
|
||||
cracker
|
||||
)) {
|
||||
for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) {
|
||||
for (const objectId of getAlternativeObjectIds(game.objectID)) {
|
||||
const filePath = path.join(folderPath, objectId, ...fileLocation);
|
||||
|
||||
@ -229,13 +228,11 @@ export const findAchievementFileInExecutableDirectory = (
|
||||
];
|
||||
};
|
||||
|
||||
export const findAllAchievementFiles = async () => {
|
||||
export const findAllAchievementFiles = () => {
|
||||
const gameAchievementFiles = new Map<string, AchievementFile[]>();
|
||||
|
||||
for (const cracker of crackers) {
|
||||
for (const { folderPath, fileLocation } of await getPathFromCracker(
|
||||
cracker
|
||||
)) {
|
||||
for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) {
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { userPreferencesRepository } from "@main/repository";
|
||||
import {
|
||||
gameAchievementRepository,
|
||||
userPreferencesRepository,
|
||||
} from "@main/repository";
|
||||
import { HydraApi } from "../hydra-api";
|
||||
|
||||
export const getGameAchievementData = async (
|
||||
@ -13,5 +16,18 @@ export const getGameAchievementData = async (
|
||||
shop,
|
||||
objectId,
|
||||
language: userPreferences?.language || "en",
|
||||
});
|
||||
})
|
||||
.then(async (achievements) => {
|
||||
await gameAchievementRepository.upsert(
|
||||
{
|
||||
objectId,
|
||||
shop,
|
||||
achievements: JSON.stringify(achievements),
|
||||
},
|
||||
["objectId", "shop"]
|
||||
);
|
||||
|
||||
return achievements;
|
||||
})
|
||||
.catch(() => []);
|
||||
};
|
||||
|
@ -1,57 +1,51 @@
|
||||
import { Cracker } from "@shared";
|
||||
import { UnlockedAchievement } from "@types";
|
||||
import {
|
||||
existsSync,
|
||||
createReadStream,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
} from "node:fs";
|
||||
import readline from "node:readline";
|
||||
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
||||
import { achievementsLogger } from "../logger";
|
||||
|
||||
export const parseAchievementFile = async (
|
||||
export const parseAchievementFile = (
|
||||
filePath: string,
|
||||
type: Cracker
|
||||
): Promise<UnlockedAchievement[]> => {
|
||||
): UnlockedAchievement[] => {
|
||||
if (!existsSync(filePath)) return [];
|
||||
|
||||
if (type == Cracker.codex) {
|
||||
const parsed = await iniParse(filePath);
|
||||
const parsed = iniParse(filePath);
|
||||
return processDefault(parsed);
|
||||
}
|
||||
|
||||
if (type == Cracker.rune) {
|
||||
const parsed = await iniParse(filePath);
|
||||
const parsed = iniParse(filePath);
|
||||
return processDefault(parsed);
|
||||
}
|
||||
|
||||
if (type === Cracker.onlineFix) {
|
||||
const parsed = await iniParse(filePath);
|
||||
const parsed = iniParse(filePath);
|
||||
return processOnlineFix(parsed);
|
||||
}
|
||||
|
||||
if (type === Cracker.goldberg) {
|
||||
const parsed = await jsonParse(filePath);
|
||||
const parsed = jsonParse(filePath);
|
||||
return processGoldberg(parsed);
|
||||
}
|
||||
|
||||
if (type == Cracker.userstats) {
|
||||
const parsed = await iniParse(filePath);
|
||||
const parsed = iniParse(filePath);
|
||||
return processUserStats(parsed);
|
||||
}
|
||||
|
||||
if (type == Cracker.rld) {
|
||||
const parsed = await iniParse(filePath);
|
||||
const parsed = iniParse(filePath);
|
||||
return processRld(parsed);
|
||||
}
|
||||
|
||||
if (type === Cracker.skidrow) {
|
||||
const parsed = await iniParse(filePath);
|
||||
const parsed = iniParse(filePath);
|
||||
return processSkidrow(parsed);
|
||||
}
|
||||
|
||||
if (type === Cracker._3dm) {
|
||||
const parsed = await iniParse(filePath);
|
||||
const parsed = iniParse(filePath);
|
||||
return process3DM(parsed);
|
||||
}
|
||||
|
||||
@ -67,27 +61,24 @@ export const parseAchievementFile = async (
|
||||
}
|
||||
|
||||
if (type === Cracker.creamAPI) {
|
||||
const parsed = await iniParse(filePath);
|
||||
const parsed = iniParse(filePath);
|
||||
return processCreamAPI(parsed);
|
||||
}
|
||||
|
||||
achievementsLogger.log(`${type} achievements found on ${filePath}`);
|
||||
achievementsLogger.log(
|
||||
`Unprocessed ${type} achievements found on ${filePath}`
|
||||
);
|
||||
return [];
|
||||
};
|
||||
|
||||
const iniParse = async (filePath: string) => {
|
||||
const iniParse = (filePath: string) => {
|
||||
try {
|
||||
const file = createReadStream(filePath);
|
||||
|
||||
const lines = readline.createInterface({
|
||||
input: file,
|
||||
crlfDelay: Infinity,
|
||||
});
|
||||
const lines = readFileSync(filePath, "utf-8").split(/[\r\n]+/);
|
||||
|
||||
let objectName = "";
|
||||
const object: Record<string, Record<string, string | number>> = {};
|
||||
|
||||
for await (const line of lines) {
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("###") || !line.length) continue;
|
||||
|
||||
if (line.startsWith("[") && line.endsWith("]")) {
|
||||
@ -100,7 +91,8 @@ const iniParse = async (filePath: string) => {
|
||||
}
|
||||
|
||||
return object;
|
||||
} catch {
|
||||
} catch (err) {
|
||||
achievementsLogger.error(`Error parsing ${filePath}`, err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@ -108,7 +100,8 @@ const iniParse = async (filePath: string) => {
|
||||
const jsonParse = (filePath: string) => {
|
||||
try {
|
||||
return JSON.parse(readFileSync(filePath, "utf-8"));
|
||||
} catch {
|
||||
} catch (err) {
|
||||
achievementsLogger.error(`Error parsing ${filePath}`, err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
@ -3,15 +3,17 @@ import {
|
||||
findAllAchievementFiles,
|
||||
findAchievementFiles,
|
||||
findAchievementFileInExecutableDirectory,
|
||||
getAlternativeObjectIds,
|
||||
} from "./find-achivement-files";
|
||||
import { parseAchievementFile } from "./parse-achievement-file";
|
||||
import { mergeAchievements } from "./merge-achievements";
|
||||
import type { UnlockedAchievement } from "@types";
|
||||
import { getGameAchievementData } from "./get-game-achievement-data";
|
||||
import { achievementsLogger } from "../logger";
|
||||
import { Game } from "@main/entity";
|
||||
|
||||
export const updateAllLocalUnlockedAchievements = async () => {
|
||||
const gameAchievementFilesMap = await findAllAchievementFiles();
|
||||
const gameAchievementFilesMap = findAllAchievementFiles();
|
||||
|
||||
const games = await gameRepository.find({
|
||||
where: {
|
||||
@ -20,69 +22,50 @@ export const updateAllLocalUnlockedAchievements = async () => {
|
||||
});
|
||||
|
||||
for (const game of games) {
|
||||
const gameAchievementFiles =
|
||||
gameAchievementFilesMap.get(game.objectID) || [];
|
||||
const achievementFileInsideDirectory =
|
||||
findAchievementFileInExecutableDirectory(game);
|
||||
for (const objectId of getAlternativeObjectIds(game.objectID)) {
|
||||
const gameAchievementFiles = gameAchievementFilesMap.get(objectId) || [];
|
||||
const achievementFileInsideDirectory =
|
||||
findAchievementFileInExecutableDirectory(game);
|
||||
|
||||
gameAchievementFiles.push(...achievementFileInsideDirectory);
|
||||
gameAchievementFiles.push(...achievementFileInsideDirectory);
|
||||
|
||||
const localAchievements = await gameAchievementRepository.findOne({
|
||||
where: { objectId: game.objectID, shop: "steam" },
|
||||
});
|
||||
|
||||
if (!localAchievements || !localAchievements.achievements) {
|
||||
await getGameAchievementData(game.objectID, "steam")
|
||||
.then((achievements) => {
|
||||
return gameAchievementRepository.upsert(
|
||||
{
|
||||
objectId: game.objectID,
|
||||
shop: "steam",
|
||||
achievements: JSON.stringify(achievements),
|
||||
},
|
||||
["objectId", "shop"]
|
||||
);
|
||||
gameAchievementRepository
|
||||
.findOne({
|
||||
where: { objectId: game.objectID, shop: "steam" },
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
.then((localAchievements) => {
|
||||
if (!localAchievements || !localAchievements.achievements) {
|
||||
getGameAchievementData(game.objectID, "steam");
|
||||
}
|
||||
});
|
||||
|
||||
const unlockedAchievements: UnlockedAchievement[] = [];
|
||||
const unlockedAchievements: UnlockedAchievement[] = [];
|
||||
|
||||
for (const achievementFile of gameAchievementFiles) {
|
||||
const parsedAchievements = await parseAchievementFile(
|
||||
achievementFile.filePath,
|
||||
achievementFile.type
|
||||
);
|
||||
for (const achievementFile of gameAchievementFiles) {
|
||||
const parsedAchievements = parseAchievementFile(
|
||||
achievementFile.filePath,
|
||||
achievementFile.type
|
||||
);
|
||||
|
||||
if (parsedAchievements.length) {
|
||||
unlockedAchievements.push(...parsedAchievements);
|
||||
if (parsedAchievements.length) {
|
||||
unlockedAchievements.push(...parsedAchievements);
|
||||
}
|
||||
|
||||
achievementsLogger.log(
|
||||
"Achievement file for",
|
||||
game.title,
|
||||
achievementFile.filePath,
|
||||
parsedAchievements
|
||||
);
|
||||
}
|
||||
|
||||
achievementsLogger.log(
|
||||
"Achievement file for",
|
||||
game.title,
|
||||
achievementFile.filePath,
|
||||
parsedAchievements
|
||||
);
|
||||
mergeAchievements(game.objectID, "steam", unlockedAchievements, false);
|
||||
}
|
||||
|
||||
mergeAchievements(game.objectID, "steam", unlockedAchievements, false);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateLocalUnlockedAchivements = async (objectId: string) => {
|
||||
const [game, localAchievements] = await Promise.all([
|
||||
gameRepository.findOne({
|
||||
where: { objectID: objectId, shop: "steam", isDeleted: false },
|
||||
}),
|
||||
gameAchievementRepository.findOne({
|
||||
where: { objectId, shop: "steam" },
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!game) return;
|
||||
|
||||
const gameAchievementFiles = await findAchievementFiles(game);
|
||||
export const updateLocalUnlockedAchivements = async (game: Game) => {
|
||||
const gameAchievementFiles = findAchievementFiles(game);
|
||||
|
||||
const achievementFileInsideDirectory =
|
||||
findAchievementFileInExecutableDirectory(game);
|
||||
@ -91,25 +74,10 @@ export const updateLocalUnlockedAchivements = async (objectId: string) => {
|
||||
|
||||
console.log("Achievements files for", game.title, gameAchievementFiles);
|
||||
|
||||
if (!localAchievements || !localAchievements.achievements) {
|
||||
await getGameAchievementData(objectId, "steam")
|
||||
.then((achievements) => {
|
||||
return gameAchievementRepository.upsert(
|
||||
{
|
||||
objectId,
|
||||
shop: "steam",
|
||||
achievements: JSON.stringify(achievements),
|
||||
},
|
||||
["objectId", "shop"]
|
||||
);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
const unlockedAchievements: UnlockedAchievement[] = [];
|
||||
|
||||
for (const achievementFile of gameAchievementFiles) {
|
||||
const localAchievementFile = await parseAchievementFile(
|
||||
const localAchievementFile = parseAchievementFile(
|
||||
achievementFile.filePath,
|
||||
achievementFile.type
|
||||
);
|
||||
@ -119,5 +87,5 @@ export const updateLocalUnlockedAchivements = async (objectId: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
mergeAchievements(objectId, "steam", unlockedAchievements, false);
|
||||
mergeAchievements(game.objectID, "steam", unlockedAchievements, false);
|
||||
};
|
||||
|
@ -12,6 +12,6 @@ export const startMainLoop = async () => {
|
||||
watchAchievements(),
|
||||
]);
|
||||
|
||||
await sleep(1000);
|
||||
await sleep(1500);
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user