mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-02 16:23:48 +03:00
feat: migrating achievements to level
This commit is contained in:
parent
2c881a6100
commit
a23106b0b1
@ -1,23 +1,11 @@
|
|||||||
import { DataSource } from "typeorm";
|
import { DataSource } from "typeorm";
|
||||||
import {
|
import { DownloadQueue, Game, UserPreferences } from "@main/entity";
|
||||||
DownloadQueue,
|
|
||||||
Game,
|
|
||||||
GameShopCache,
|
|
||||||
UserPreferences,
|
|
||||||
GameAchievement,
|
|
||||||
} from "@main/entity";
|
|
||||||
|
|
||||||
import { databasePath } from "./constants";
|
import { databasePath } from "./constants";
|
||||||
|
|
||||||
export const dataSource = new DataSource({
|
export const dataSource = new DataSource({
|
||||||
type: "better-sqlite3",
|
type: "better-sqlite3",
|
||||||
entities: [
|
entities: [Game, UserPreferences, DownloadQueue],
|
||||||
Game,
|
|
||||||
UserPreferences,
|
|
||||||
GameShopCache,
|
|
||||||
DownloadQueue,
|
|
||||||
GameAchievement,
|
|
||||||
],
|
|
||||||
synchronize: false,
|
synchronize: false,
|
||||||
database: databasePath,
|
database: databasePath,
|
||||||
});
|
});
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
|
||||||
|
|
||||||
@Entity("game_achievement")
|
|
||||||
export class GameAchievement {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column("text")
|
|
||||||
objectId: string;
|
|
||||||
|
|
||||||
@Column("text")
|
|
||||||
shop: string;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
unlockedAchievements: string | null;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
achievements: string | null;
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
PrimaryColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
} from "typeorm";
|
|
||||||
import type { GameShop } from "@types";
|
|
||||||
|
|
||||||
@Entity("game_shop_cache")
|
|
||||||
export class GameShopCache {
|
|
||||||
@PrimaryColumn("text", { unique: true })
|
|
||||||
objectID: string;
|
|
||||||
|
|
||||||
@Column("text")
|
|
||||||
shop: GameShop;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
serializedData: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use IndexedDB's `howLongToBeatEntries` instead
|
|
||||||
*/
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
howLongToBeatSerializedData: string;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
language: string;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
@ -1,5 +1,3 @@
|
|||||||
export * from "./game.entity";
|
export * from "./game.entity";
|
||||||
export * from "./user-preferences.entity";
|
export * from "./user-preferences.entity";
|
||||||
export * from "./game-shop-cache.entity";
|
|
||||||
export * from "./game-achievements.entity";
|
|
||||||
export * from "./download-queue.entity";
|
export * from "./download-queue.entity";
|
||||||
|
@ -3,7 +3,7 @@ import jwt from "jsonwebtoken";
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { db } from "@main/level";
|
import { db } from "@main/level";
|
||||||
import type { Auth } from "@types";
|
import type { Auth } from "@types";
|
||||||
import { levelKeys } from "@main/level/sublevels/keys";
|
import { levelKeys } from "@main/level";
|
||||||
import { Crypto } from "@main/services";
|
import { Crypto } from "@main/services";
|
||||||
|
|
||||||
const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
|
const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
@ -4,7 +4,7 @@ import { dataSource } from "@main/data-source";
|
|||||||
import { DownloadQueue, Game } from "@main/entity";
|
import { DownloadQueue, Game } from "@main/entity";
|
||||||
import { PythonRPC } from "@main/services/python-rpc";
|
import { PythonRPC } from "@main/services/python-rpc";
|
||||||
import { db } from "@main/level";
|
import { db } from "@main/level";
|
||||||
import { levelKeys } from "@main/level/sublevels/keys";
|
import { levelKeys } from "@main/level";
|
||||||
|
|
||||||
const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
|
const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
const databaseOperations = dataSource
|
const databaseOperations = dataSource
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { gameShopCacheRepository } from "@main/repository";
|
import { getSteamAppDetails, logger } from "@main/services";
|
||||||
import { getSteamAppDetails } from "@main/services";
|
|
||||||
|
|
||||||
import type { ShopDetails, GameShop, SteamAppDetails } from "@types";
|
import type { ShopDetails, GameShop } from "@types";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { steamGamesWorker } from "@main/workers";
|
import { steamGamesWorker } from "@main/workers";
|
||||||
|
import { gamesShopCacheSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
const getLocalizedSteamAppDetails = async (
|
const getLocalizedSteamAppDetails = async (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
@ -39,35 +39,27 @@ const getGameShopDetails = async (
|
|||||||
language: string
|
language: string
|
||||||
): Promise<ShopDetails | null> => {
|
): Promise<ShopDetails | null> => {
|
||||||
if (shop === "steam") {
|
if (shop === "steam") {
|
||||||
const cachedData = await gameShopCacheRepository.findOne({
|
const cachedData = await gamesShopCacheSublevel.get(
|
||||||
where: { objectID: objectId, language },
|
levelKeys.gameShopCacheItem(shop, objectId, language)
|
||||||
});
|
);
|
||||||
|
|
||||||
const appDetails = getLocalizedSteamAppDetails(objectId, language).then(
|
const appDetails = getLocalizedSteamAppDetails(objectId, language).then(
|
||||||
(result) => {
|
(result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
gameShopCacheRepository.upsert(
|
gamesShopCacheSublevel
|
||||||
{
|
.put(levelKeys.gameShopCacheItem(shop, objectId, language), result)
|
||||||
objectID: objectId,
|
.catch((err) => {
|
||||||
shop: "steam",
|
logger.error("Could not cache game details", err);
|
||||||
language,
|
});
|
||||||
serializedData: JSON.stringify(result),
|
|
||||||
},
|
|
||||||
["objectID"]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const cachedGame = cachedData?.serializedData
|
if (cachedData) {
|
||||||
? (JSON.parse(cachedData?.serializedData) as SteamAppDetails)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (cachedGame) {
|
|
||||||
return {
|
return {
|
||||||
...cachedGame,
|
...cachedData,
|
||||||
objectId,
|
objectId,
|
||||||
} as ShopDetails;
|
} as ShopDetails;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { gameAchievementRepository, gameRepository } from "@main/repository";
|
import { gameRepository } from "@main/repository";
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { findAchievementFiles } from "@main/services/achievements/find-achivement-files";
|
import { findAchievementFiles } from "@main/services/achievements/find-achivement-files";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { achievementsLogger, HydraApi, WindowManager } from "@main/services";
|
import { achievementsLogger, HydraApi, WindowManager } from "@main/services";
|
||||||
import { getUnlockedAchievements } from "../user/get-unlocked-achievements";
|
import { getUnlockedAchievements } from "../user/get-unlocked-achievements";
|
||||||
|
import { gameAchievementsSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
const resetGameAchievements = async (
|
const resetGameAchievements = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
@ -23,12 +24,21 @@ const resetGameAchievements = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await gameAchievementRepository.update(
|
const levelKey = levelKeys.game(game.shop, game.objectID);
|
||||||
{ objectId: game.objectID },
|
|
||||||
{
|
await gameAchievementsSublevel
|
||||||
unlockedAchievements: null,
|
.get(levelKey)
|
||||||
}
|
.then(async (gameAchievements) => {
|
||||||
);
|
if (gameAchievements) {
|
||||||
|
await gameAchievementsSublevel.put(
|
||||||
|
levelKeys.game(game.shop, game.objectID),
|
||||||
|
{
|
||||||
|
...gameAchievements,
|
||||||
|
unlockedAchievements: [],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then(
|
await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then(
|
||||||
() =>
|
() =>
|
||||||
|
@ -3,7 +3,7 @@ import { registerEvent } from "../register-event";
|
|||||||
import { Crypto, HydraApi } from "@main/services";
|
import { Crypto, HydraApi } from "@main/services";
|
||||||
import { db } from "@main/level";
|
import { db } from "@main/level";
|
||||||
import type { Auth } from "@types";
|
import type { Auth } from "@types";
|
||||||
import { levelKeys } from "@main/level/sublevels/keys";
|
import { levelKeys } from "@main/level";
|
||||||
|
|
||||||
const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => {
|
const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
const auth = await db.get<string, Auth>(levelKeys.auth, {
|
const auth = await db.get<string, Auth>(levelKeys.auth, {
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import type { GameShop, UnlockedAchievement, UserAchievement } from "@types";
|
import type { GameShop, UserAchievement } from "@types";
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import {
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
gameAchievementRepository,
|
|
||||||
userPreferencesRepository,
|
|
||||||
} from "@main/repository";
|
|
||||||
import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data";
|
import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data";
|
||||||
|
import { gameAchievementsSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
export const getUnlockedAchievements = async (
|
export const getUnlockedAchievements = async (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
shop: GameShop,
|
shop: GameShop,
|
||||||
useCachedData: boolean
|
useCachedData: boolean
|
||||||
): Promise<UserAchievement[]> => {
|
): Promise<UserAchievement[]> => {
|
||||||
const cachedAchievements = await gameAchievementRepository.findOne({
|
const cachedAchievements = await gameAchievementsSublevel.get(
|
||||||
where: { objectId, shop },
|
levelKeys.game(shop, objectId)
|
||||||
});
|
);
|
||||||
|
|
||||||
const userPreferences = await userPreferencesRepository.findOne({
|
const userPreferences = await userPreferencesRepository.findOne({
|
||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
@ -25,12 +23,10 @@ export const getUnlockedAchievements = async (
|
|||||||
const achievementsData = await getGameAchievementData(
|
const achievementsData = await getGameAchievementData(
|
||||||
objectId,
|
objectId,
|
||||||
shop,
|
shop,
|
||||||
useCachedData ? cachedAchievements : null
|
useCachedData
|
||||||
);
|
);
|
||||||
|
|
||||||
const unlockedAchievements = JSON.parse(
|
const unlockedAchievements = cachedAchievements?.unlockedAchievements ?? [];
|
||||||
cachedAchievements?.unlockedAchievements || "[]"
|
|
||||||
) as UnlockedAchievement[];
|
|
||||||
|
|
||||||
return achievementsData
|
return achievementsData
|
||||||
.map((achievementData) => {
|
.map((achievementData) => {
|
||||||
|
@ -2,7 +2,7 @@ import { db } from "@main/level";
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { HydraApi } from "@main/services";
|
import { HydraApi } from "@main/services";
|
||||||
import type { User, UserFriends } from "@types";
|
import type { User, UserFriends } from "@types";
|
||||||
import { levelKeys } from "@main/level/sublevels/keys";
|
import { levelKeys } from "@main/level/sublevels";
|
||||||
|
|
||||||
export const getUserFriends = async (
|
export const getUserFriends = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
|
11
src/main/level/sublevels/game-achievements.ts
Normal file
11
src/main/level/sublevels/game-achievements.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { GameAchievement } from "@types";
|
||||||
|
|
||||||
|
import { db } from "../level";
|
||||||
|
import { levelKeys } from "./keys";
|
||||||
|
|
||||||
|
export const gameAchievementsSublevel = db.sublevel<string, GameAchievement>(
|
||||||
|
levelKeys.gameAchievements,
|
||||||
|
{
|
||||||
|
valueEncoding: "json",
|
||||||
|
}
|
||||||
|
);
|
11
src/main/level/sublevels/game-shop-cache.ts
Normal file
11
src/main/level/sublevels/game-shop-cache.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { ShopDetails } from "@types";
|
||||||
|
|
||||||
|
import { db } from "../level";
|
||||||
|
import { levelKeys } from "./keys";
|
||||||
|
|
||||||
|
export const gamesShopCacheSublevel = db.sublevel<string, ShopDetails>(
|
||||||
|
levelKeys.gameShopCache,
|
||||||
|
{
|
||||||
|
valueEncoding: "json",
|
||||||
|
}
|
||||||
|
);
|
@ -1,4 +1,5 @@
|
|||||||
import { Game } from "@types";
|
import type { Game } from "@types";
|
||||||
|
|
||||||
import { db } from "../level";
|
import { db } from "../level";
|
||||||
import { levelKeys } from "./keys";
|
import { levelKeys } from "./keys";
|
||||||
|
|
||||||
|
@ -1 +1,5 @@
|
|||||||
export * from "./games";
|
export * from "./games";
|
||||||
|
export * from "./game-shop-cache";
|
||||||
|
export * from "./game-achievements";
|
||||||
|
|
||||||
|
export * from "./keys";
|
||||||
|
@ -5,4 +5,8 @@ export const levelKeys = {
|
|||||||
game: (shop: GameShop, objectId: string) => `${shop}:${objectId}`,
|
game: (shop: GameShop, objectId: string) => `${shop}:${objectId}`,
|
||||||
user: "user",
|
user: "user",
|
||||||
auth: "auth",
|
auth: "auth",
|
||||||
|
gameShopCache: "gameShopCache",
|
||||||
|
gameShopCacheItem: (shop: GameShop, objectId: string, language: string) =>
|
||||||
|
`${shop}:${objectId}:${language}`,
|
||||||
|
gameAchievements: "gameAchievements",
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,9 @@
|
|||||||
import { dataSource } from "./data-source";
|
import { dataSource } from "./data-source";
|
||||||
import {
|
import { DownloadQueue, Game, UserPreferences } from "@main/entity";
|
||||||
DownloadQueue,
|
|
||||||
Game,
|
|
||||||
GameShopCache,
|
|
||||||
UserPreferences,
|
|
||||||
GameAchievement,
|
|
||||||
} from "@main/entity";
|
|
||||||
|
|
||||||
export const gameRepository = dataSource.getRepository(Game);
|
export const gameRepository = dataSource.getRepository(Game);
|
||||||
|
|
||||||
export const userPreferencesRepository =
|
export const userPreferencesRepository =
|
||||||
dataSource.getRepository(UserPreferences);
|
dataSource.getRepository(UserPreferences);
|
||||||
|
|
||||||
export const gameShopCacheRepository = dataSource.getRepository(GameShopCache);
|
|
||||||
|
|
||||||
export const downloadQueueRepository = dataSource.getRepository(DownloadQueue);
|
export const downloadQueueRepository = dataSource.getRepository(DownloadQueue);
|
||||||
|
|
||||||
export const gameAchievementRepository =
|
|
||||||
dataSource.getRepository(GameAchievement);
|
|
||||||
|
@ -1,40 +1,36 @@
|
|||||||
import {
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
gameAchievementRepository,
|
|
||||||
userPreferencesRepository,
|
|
||||||
} from "@main/repository";
|
|
||||||
import { HydraApi } from "../hydra-api";
|
import { HydraApi } from "../hydra-api";
|
||||||
import type { AchievementData, GameShop } from "@types";
|
import type { GameShop, SteamAchievement } from "@types";
|
||||||
import { UserNotLoggedInError } from "@shared";
|
import { UserNotLoggedInError } from "@shared";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import { GameAchievement } from "@main/entity";
|
import { gameAchievementsSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
export const getGameAchievementData = async (
|
export const getGameAchievementData = async (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
shop: GameShop,
|
shop: GameShop,
|
||||||
cachedAchievements: GameAchievement | null
|
useCachedData: boolean
|
||||||
) => {
|
) => {
|
||||||
if (cachedAchievements && cachedAchievements.achievements) {
|
const cachedAchievements = await gameAchievementsSublevel.get(
|
||||||
return JSON.parse(cachedAchievements.achievements) as AchievementData[];
|
levelKeys.game(shop, objectId)
|
||||||
}
|
);
|
||||||
|
|
||||||
|
if (cachedAchievements && useCachedData)
|
||||||
|
return cachedAchievements.achievements;
|
||||||
|
|
||||||
const userPreferences = await userPreferencesRepository.findOne({
|
const userPreferences = await userPreferencesRepository.findOne({
|
||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
});
|
});
|
||||||
|
|
||||||
return HydraApi.get<AchievementData[]>("/games/achievements", {
|
return HydraApi.get<SteamAchievement[]>("/games/achievements", {
|
||||||
shop,
|
shop,
|
||||||
objectId,
|
objectId,
|
||||||
language: userPreferences?.language || "en",
|
language: userPreferences?.language || "en",
|
||||||
})
|
})
|
||||||
.then((achievements) => {
|
.then(async (achievements) => {
|
||||||
gameAchievementRepository.upsert(
|
await gameAchievementsSublevel.put(levelKeys.game(shop, objectId), {
|
||||||
{
|
unlockedAchievements: cachedAchievements?.unlockedAchievements ?? [],
|
||||||
objectId,
|
achievements,
|
||||||
shop,
|
});
|
||||||
achievements: JSON.stringify(achievements),
|
|
||||||
},
|
|
||||||
["objectId", "shop"]
|
|
||||||
);
|
|
||||||
|
|
||||||
return achievements;
|
return achievements;
|
||||||
})
|
})
|
||||||
@ -42,15 +38,9 @@ export const getGameAchievementData = async (
|
|||||||
if (err instanceof UserNotLoggedInError) {
|
if (err instanceof UserNotLoggedInError) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error("Failed to get game achievements", err);
|
logger.error("Failed to get game achievements", err);
|
||||||
return gameAchievementRepository
|
|
||||||
.findOne({
|
return [];
|
||||||
where: { objectId, shop },
|
|
||||||
})
|
|
||||||
.then((gameAchievements) => {
|
|
||||||
return JSON.parse(
|
|
||||||
gameAchievements?.achievements || "[]"
|
|
||||||
) as AchievementData[];
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import {
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
gameAchievementRepository,
|
import type { GameShop, UnlockedAchievement } from "@types";
|
||||||
userPreferencesRepository,
|
|
||||||
} from "@main/repository";
|
|
||||||
import type { AchievementData, GameShop, UnlockedAchievement } from "@types";
|
|
||||||
import { WindowManager } from "../window-manager";
|
import { WindowManager } from "../window-manager";
|
||||||
import { HydraApi } from "../hydra-api";
|
import { HydraApi } from "../hydra-api";
|
||||||
import { getUnlockedAchievements } from "@main/events/user/get-unlocked-achievements";
|
import { getUnlockedAchievements } from "@main/events/user/get-unlocked-achievements";
|
||||||
@ -10,33 +7,36 @@ import { Game } from "@main/entity";
|
|||||||
import { publishNewAchievementNotification } from "../notifications";
|
import { publishNewAchievementNotification } from "../notifications";
|
||||||
import { SubscriptionRequiredError } from "@shared";
|
import { SubscriptionRequiredError } from "@shared";
|
||||||
import { achievementsLogger } from "../logger";
|
import { achievementsLogger } from "../logger";
|
||||||
|
import { gameAchievementsSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
const saveAchievementsOnLocal = async (
|
const saveAchievementsOnLocal = async (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
shop: GameShop,
|
shop: GameShop,
|
||||||
achievements: UnlockedAchievement[],
|
unlockedAchievements: UnlockedAchievement[],
|
||||||
sendUpdateEvent: boolean
|
sendUpdateEvent: boolean
|
||||||
) => {
|
) => {
|
||||||
return gameAchievementRepository
|
const levelKey = levelKeys.game(shop, objectId);
|
||||||
.upsert(
|
|
||||||
{
|
|
||||||
objectId,
|
|
||||||
shop,
|
|
||||||
unlockedAchievements: JSON.stringify(achievements),
|
|
||||||
},
|
|
||||||
["objectId", "shop"]
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
if (!sendUpdateEvent) return;
|
|
||||||
|
|
||||||
return getUnlockedAchievements(objectId, shop, true)
|
return gameAchievementsSublevel
|
||||||
.then((achievements) => {
|
.get(levelKey)
|
||||||
WindowManager.mainWindow?.webContents.send(
|
.then(async (gameAchievement) => {
|
||||||
`on-update-achievements-${objectId}-${shop}`,
|
if (gameAchievement) {
|
||||||
achievements
|
await gameAchievementsSublevel.put(levelKey, {
|
||||||
);
|
...gameAchievement,
|
||||||
})
|
unlockedAchievements: unlockedAchievements,
|
||||||
.catch(() => {});
|
});
|
||||||
|
|
||||||
|
if (!sendUpdateEvent) return;
|
||||||
|
|
||||||
|
return getUnlockedAchievements(objectId, shop, true)
|
||||||
|
.then((achievements) => {
|
||||||
|
WindowManager.mainWindow?.webContents.send(
|
||||||
|
`on-update-achievements-${objectId}-${shop}`,
|
||||||
|
achievements
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,22 +46,12 @@ export const mergeAchievements = async (
|
|||||||
publishNotification: boolean
|
publishNotification: boolean
|
||||||
) => {
|
) => {
|
||||||
const [localGameAchievement, userPreferences] = await Promise.all([
|
const [localGameAchievement, userPreferences] = await Promise.all([
|
||||||
gameAchievementRepository.findOne({
|
gameAchievementsSublevel.get(levelKeys.game(game.shop, game.objectID)),
|
||||||
where: {
|
|
||||||
objectId: game.objectID,
|
|
||||||
shop: game.shop,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
userPreferencesRepository.findOne({ where: { id: 1 } }),
|
userPreferencesRepository.findOne({ where: { id: 1 } }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const achievementsData = JSON.parse(
|
const achievementsData = localGameAchievement?.achievements ?? [];
|
||||||
localGameAchievement?.achievements || "[]"
|
const unlockedAchievements = localGameAchievement?.unlockedAchievements ?? [];
|
||||||
) as AchievementData[];
|
|
||||||
|
|
||||||
const unlockedAchievements = JSON.parse(
|
|
||||||
localGameAchievement?.unlockedAchievements || "[]"
|
|
||||||
).filter((achievement) => achievement.name) as UnlockedAchievement[];
|
|
||||||
|
|
||||||
const newAchievementsMap = new Map(
|
const newAchievementsMap = new Map(
|
||||||
achievements.reverse().map((achievement) => {
|
achievements.reverse().map((achievement) => {
|
||||||
|
@ -10,7 +10,7 @@ import { appVersion } from "@main/constants";
|
|||||||
import { getUserData } from "./user/get-user-data";
|
import { getUserData } from "./user/get-user-data";
|
||||||
import { isFuture, isToday } from "date-fns";
|
import { isFuture, isToday } from "date-fns";
|
||||||
import { db } from "@main/level";
|
import { db } from "@main/level";
|
||||||
import { levelKeys } from "@main/level/sublevels/keys";
|
import { levelKeys } from "@main/level/sublevels";
|
||||||
import type { Auth, User } from "@types";
|
import type { Auth, User } from "@types";
|
||||||
import { Crypto } from "./crypto";
|
import { Crypto } from "./crypto";
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { HydraApi } from "../hydra-api";
|
|||||||
import { UserNotLoggedInError } from "@shared";
|
import { UserNotLoggedInError } from "@shared";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import { db } from "@main/level";
|
import { db } from "@main/level";
|
||||||
import { levelKeys } from "@main/level/sublevels/keys";
|
import { levelKeys } from "@main/level/sublevels";
|
||||||
|
|
||||||
export const getUserData = async () => {
|
export const getUserData = async () => {
|
||||||
return HydraApi.get<UserDetails>(`/profile/me`)
|
return HydraApi.get<UserDetails>(`/profile/me`)
|
||||||
|
@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import type { LibraryGame } from "@types";
|
import type { Game } from "@types";
|
||||||
|
|
||||||
import { TextField } from "@renderer/components";
|
import { TextField } from "@renderer/components";
|
||||||
import {
|
import {
|
||||||
@ -35,7 +35,7 @@ export function Sidebar() {
|
|||||||
const { library, updateLibrary } = useLibrary();
|
const { library, updateLibrary } = useLibrary();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [filteredLibrary, setFilteredLibrary] = useState<LibraryGame[]>([]);
|
const [filteredLibrary, setFilteredLibrary] = useState<Game[]>([]);
|
||||||
|
|
||||||
const [isResizing, setIsResizing] = useState(false);
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
const [sidebarWidth, setSidebarWidth] = useState(
|
const [sidebarWidth, setSidebarWidth] = useState(
|
||||||
@ -117,7 +117,7 @@ export function Sidebar() {
|
|||||||
};
|
};
|
||||||
}, [isResizing]);
|
}, [isResizing]);
|
||||||
|
|
||||||
const getGameTitle = (game: LibraryGame) => {
|
const getGameTitle = (game: Game) => {
|
||||||
if (lastPacket?.game.id === game.id) {
|
if (lastPacket?.game.id === game.id) {
|
||||||
return t("downloading", {
|
return t("downloading", {
|
||||||
title: game.title,
|
title: game.title,
|
||||||
@ -140,10 +140,7 @@ export function Sidebar() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSidebarGameClick = (
|
const handleSidebarGameClick = (event: React.MouseEvent, game: Game) => {
|
||||||
event: React.MouseEvent,
|
|
||||||
game: LibraryGame
|
|
||||||
) => {
|
|
||||||
const path = buildGameDetailsPath({
|
const path = buildGameDetailsPath({
|
||||||
...game,
|
...game,
|
||||||
objectId: game.objectID,
|
objectId: game.objectID,
|
||||||
|
6
src/renderer/src/declaration.d.ts
vendored
6
src/renderer/src/declaration.d.ts
vendored
@ -2,7 +2,6 @@ import type { CatalogueCategory } from "@shared";
|
|||||||
import type {
|
import type {
|
||||||
AppUpdaterEvent,
|
AppUpdaterEvent,
|
||||||
Game,
|
Game,
|
||||||
LibraryGame,
|
|
||||||
GameShop,
|
GameShop,
|
||||||
HowLongToBeatCategory,
|
HowLongToBeatCategory,
|
||||||
ShopDetails,
|
ShopDetails,
|
||||||
@ -23,7 +22,6 @@ import type {
|
|||||||
UserStats,
|
UserStats,
|
||||||
UserDetails,
|
UserDetails,
|
||||||
FriendRequestSync,
|
FriendRequestSync,
|
||||||
GameAchievement,
|
|
||||||
GameArtifact,
|
GameArtifact,
|
||||||
LudusaviBackup,
|
LudusaviBackup,
|
||||||
UserAchievement,
|
UserAchievement,
|
||||||
@ -77,7 +75,7 @@ declare global {
|
|||||||
onUpdateAchievements: (
|
onUpdateAchievements: (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
shop: GameShop,
|
shop: GameShop,
|
||||||
cb: (achievements: GameAchievement[]) => void
|
cb: (achievements: UserAchievement[]) => void
|
||||||
) => () => Electron.IpcRenderer;
|
) => () => Electron.IpcRenderer;
|
||||||
getPublishers: () => Promise<string[]>;
|
getPublishers: () => Promise<string[]>;
|
||||||
getDevelopers: () => Promise<string[]>;
|
getDevelopers: () => Promise<string[]>;
|
||||||
@ -102,7 +100,7 @@ declare global {
|
|||||||
winePrefixPath: string | null
|
winePrefixPath: string | null
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
verifyExecutablePathInUse: (executablePath: string) => Promise<Game>;
|
verifyExecutablePathInUse: (executablePath: string) => Promise<Game>;
|
||||||
getLibrary: () => Promise<LibraryGame[]>;
|
getLibrary: () => Promise<Game[]>;
|
||||||
openGameInstaller: (gameId: number) => Promise<boolean>;
|
openGameInstaller: (gameId: number) => Promise<boolean>;
|
||||||
openGameInstallerPath: (gameId: number) => Promise<boolean>;
|
openGameInstallerPath: (gameId: number) => Promise<boolean>;
|
||||||
openGameExecutablePath: (gameId: number) => Promise<void>;
|
openGameExecutablePath: (gameId: number) => Promise<void>;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import type { PayloadAction } from "@reduxjs/toolkit";
|
import type { PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
import type { LibraryGame } from "@types";
|
import type { Game } from "@types";
|
||||||
|
|
||||||
export interface LibraryState {
|
export interface LibraryState {
|
||||||
value: LibraryGame[];
|
value: Game[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: LibraryState = {
|
const initialState: LibraryState = {
|
||||||
|
@ -47,7 +47,14 @@ export function AchievementList({ achievements }: AchievementListProps) {
|
|||||||
</h4>
|
</h4>
|
||||||
<p>{achievement.description}</p>
|
<p>{achievement.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "8px",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{achievement.points != undefined ? (
|
{achievement.points != undefined ? (
|
||||||
<div
|
<div
|
||||||
style={{ display: "flex", alignItems: "center", gap: "4px" }}
|
style={{ display: "flex", alignItems: "center", gap: "4px" }}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import type { LibraryGame, SeedingStatus } from "@types";
|
import type { Game, SeedingStatus } from "@types";
|
||||||
|
|
||||||
import { Badge, Button } from "@renderer/components";
|
import { Badge, Button } from "@renderer/components";
|
||||||
import {
|
import {
|
||||||
@ -32,7 +32,7 @@ import {
|
|||||||
} from "@primer/octicons-react";
|
} from "@primer/octicons-react";
|
||||||
|
|
||||||
export interface DownloadGroupProps {
|
export interface DownloadGroupProps {
|
||||||
library: LibraryGame[];
|
library: Game[];
|
||||||
title: string;
|
title: string;
|
||||||
openDeleteGameModal: (gameId: number) => void;
|
openDeleteGameModal: (gameId: number) => void;
|
||||||
openGameInstaller: (gameId: number) => void;
|
openGameInstaller: (gameId: number) => void;
|
||||||
@ -65,7 +65,7 @@ export function DownloadGroup({
|
|||||||
resumeSeeding,
|
resumeSeeding,
|
||||||
} = useDownload();
|
} = useDownload();
|
||||||
|
|
||||||
const getFinalDownloadSize = (game: LibraryGame) => {
|
const getFinalDownloadSize = (game: Game) => {
|
||||||
const isGameDownloading = lastPacket?.game.id === game.id;
|
const isGameDownloading = lastPacket?.game.id === game.id;
|
||||||
|
|
||||||
if (game.fileSize) return formatBytes(game.fileSize);
|
if (game.fileSize) return formatBytes(game.fileSize);
|
||||||
@ -86,7 +86,7 @@ export function DownloadGroup({
|
|||||||
return map;
|
return map;
|
||||||
}, [seedingStatus]);
|
}, [seedingStatus]);
|
||||||
|
|
||||||
const getGameInfo = (game: LibraryGame) => {
|
const getGameInfo = (game: Game) => {
|
||||||
const isGameDownloading = lastPacket?.game.id === game.id;
|
const isGameDownloading = lastPacket?.game.id === game.id;
|
||||||
const finalDownloadSize = getFinalDownloadSize(game);
|
const finalDownloadSize = getFinalDownloadSize(game);
|
||||||
const seedingStatus = seedingMap.get(game.id);
|
const seedingStatus = seedingMap.get(game.id);
|
||||||
@ -165,7 +165,7 @@ export function DownloadGroup({
|
|||||||
return <p>{t(game.status as string)}</p>;
|
return <p>{t(game.status as string)}</p>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getGameActions = (game: LibraryGame): DropdownMenuItem[] => {
|
const getGameActions = (game: Game): DropdownMenuItem[] => {
|
||||||
const isGameDownloading = lastPacket?.game.id === game.id;
|
const isGameDownloading = lastPacket?.game.id === game.id;
|
||||||
|
|
||||||
const deleting = isGameDeleting(game.id);
|
const deleting = isGameDeleting(game.id);
|
||||||
|
@ -7,7 +7,7 @@ import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";
|
|||||||
import * as styles from "./downloads.css";
|
import * as styles from "./downloads.css";
|
||||||
import { DeleteGameModal } from "./delete-game-modal";
|
import { DeleteGameModal } from "./delete-game-modal";
|
||||||
import { DownloadGroup } from "./download-group";
|
import { DownloadGroup } from "./download-group";
|
||||||
import type { LibraryGame, SeedingStatus } from "@types";
|
import type { Game, SeedingStatus } from "@types";
|
||||||
import { orderBy } from "lodash-es";
|
import { orderBy } from "lodash-es";
|
||||||
import { ArrowDownIcon } from "@primer/octicons-react";
|
import { ArrowDownIcon } from "@primer/octicons-react";
|
||||||
|
|
||||||
@ -49,8 +49,8 @@ export default function Downloads() {
|
|||||||
setShowDeleteModal(true);
|
setShowDeleteModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const libraryGroup: Record<string, LibraryGame[]> = useMemo(() => {
|
const libraryGroup: Record<string, Game[]> = useMemo(() => {
|
||||||
const initialValue: Record<string, LibraryGame[]> = {
|
const initialValue: Record<string, Game[]> = {
|
||||||
downloading: [],
|
downloading: [],
|
||||||
queued: [],
|
queued: [],
|
||||||
complete: [],
|
complete: [],
|
||||||
|
@ -23,7 +23,7 @@ import { buildGameAchievementPath } from "@renderer/helpers";
|
|||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
import { useSubscription } from "@renderer/hooks/use-subscription";
|
import { useSubscription } from "@renderer/hooks/use-subscription";
|
||||||
|
|
||||||
const fakeAchievements: UserAchievement[] = [
|
const achievementsPlaceholder: UserAchievement[] = [
|
||||||
{
|
{
|
||||||
displayName: "Timber!!",
|
displayName: "Timber!!",
|
||||||
name: "",
|
name: "",
|
||||||
@ -140,7 +140,7 @@ export function Sidebar() {
|
|||||||
<h3>{t("sign_in_to_see_achievements")}</h3>
|
<h3>{t("sign_in_to_see_achievements")}</h3>
|
||||||
</div>
|
</div>
|
||||||
<ul className={styles.list} style={{ filter: "blur(4px)" }}>
|
<ul className={styles.list} style={{ filter: "blur(4px)" }}>
|
||||||
{fakeAchievements.map((achievement, index) => (
|
{achievementsPlaceholder.map((achievement, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<div className={styles.listItem}>
|
<div className={styles.listItem}>
|
||||||
<img
|
<img
|
||||||
|
167
src/types/download.types.ts
Normal file
167
src/types/download.types.ts
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import type { Game, GameStatus } from "./game.types";
|
||||||
|
|
||||||
|
export interface DownloadProgress {
|
||||||
|
downloadSpeed: number;
|
||||||
|
timeRemaining: number;
|
||||||
|
numPeers: number;
|
||||||
|
numSeeds: number;
|
||||||
|
isDownloadingMetadata: boolean;
|
||||||
|
isCheckingFiles: boolean;
|
||||||
|
progress: number;
|
||||||
|
gameId: number;
|
||||||
|
game: Game;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Torbox */
|
||||||
|
export interface TorBoxUser {
|
||||||
|
id: number;
|
||||||
|
email: string;
|
||||||
|
plan: string;
|
||||||
|
expiration: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TorBoxUserRequest {
|
||||||
|
success: boolean;
|
||||||
|
detail: string;
|
||||||
|
error: string;
|
||||||
|
data: TorBoxUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TorBoxFile {
|
||||||
|
id: number;
|
||||||
|
md5: string;
|
||||||
|
s3_path: string;
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
mimetype: string;
|
||||||
|
short_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TorBoxTorrentInfo {
|
||||||
|
id: number;
|
||||||
|
hash: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
magnet: string;
|
||||||
|
size: number;
|
||||||
|
active: boolean;
|
||||||
|
cached: boolean;
|
||||||
|
auth_id: string;
|
||||||
|
download_state:
|
||||||
|
| "downloading"
|
||||||
|
| "uploading"
|
||||||
|
| "stalled (no seeds)"
|
||||||
|
| "paused"
|
||||||
|
| "completed"
|
||||||
|
| "cached"
|
||||||
|
| "metaDL"
|
||||||
|
| "checkingResumeData";
|
||||||
|
seeds: number;
|
||||||
|
ratio: number;
|
||||||
|
progress: number;
|
||||||
|
download_speed: number;
|
||||||
|
upload_speed: number;
|
||||||
|
name: string;
|
||||||
|
eta: number;
|
||||||
|
files: TorBoxFile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TorBoxTorrentInfoRequest {
|
||||||
|
success: boolean;
|
||||||
|
detail: string;
|
||||||
|
error: string;
|
||||||
|
data: TorBoxTorrentInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TorBoxAddTorrentRequest {
|
||||||
|
success: boolean;
|
||||||
|
detail: string;
|
||||||
|
error: string;
|
||||||
|
data: {
|
||||||
|
torrent_id: number;
|
||||||
|
name: string;
|
||||||
|
hash: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TorBoxRequestLinkRequest {
|
||||||
|
success: boolean;
|
||||||
|
detail: string;
|
||||||
|
error: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Real-Debrid */
|
||||||
|
export interface RealDebridUnrestrictLink {
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
mimeType: string;
|
||||||
|
filesize: number;
|
||||||
|
link: string;
|
||||||
|
host: string;
|
||||||
|
host_icon: string;
|
||||||
|
chunks: number;
|
||||||
|
crc: number;
|
||||||
|
download: string;
|
||||||
|
streamable: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RealDebridAddMagnet {
|
||||||
|
id: string;
|
||||||
|
// URL of the created resource
|
||||||
|
uri: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RealDebridTorrentInfo {
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
original_filename: string;
|
||||||
|
hash: string;
|
||||||
|
bytes: number;
|
||||||
|
original_bytes: number;
|
||||||
|
host: string;
|
||||||
|
split: number;
|
||||||
|
progress: number;
|
||||||
|
status:
|
||||||
|
| "magnet_error"
|
||||||
|
| "magnet_conversion"
|
||||||
|
| "waiting_files_selection"
|
||||||
|
| "queued"
|
||||||
|
| "downloading"
|
||||||
|
| "downloaded"
|
||||||
|
| "error"
|
||||||
|
| "virus"
|
||||||
|
| "compressing"
|
||||||
|
| "uploading"
|
||||||
|
| "dead";
|
||||||
|
added: string;
|
||||||
|
files: {
|
||||||
|
id: number;
|
||||||
|
path: string;
|
||||||
|
bytes: number;
|
||||||
|
selected: number;
|
||||||
|
}[];
|
||||||
|
links: string[];
|
||||||
|
ended: string;
|
||||||
|
speed: number;
|
||||||
|
seeders: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RealDebridUser {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
points: number;
|
||||||
|
locale: string;
|
||||||
|
avatar: string;
|
||||||
|
type: string;
|
||||||
|
premium: number;
|
||||||
|
expiration: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Torrent */
|
||||||
|
export interface SeedingStatus {
|
||||||
|
gameId: number;
|
||||||
|
status: GameStatus;
|
||||||
|
uploadSpeed: number;
|
||||||
|
}
|
59
src/types/game.types.ts
Normal file
59
src/types/game.types.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import type { Downloader } from "@shared";
|
||||||
|
|
||||||
|
export type GameStatus =
|
||||||
|
| "active"
|
||||||
|
| "waiting"
|
||||||
|
| "paused"
|
||||||
|
| "error"
|
||||||
|
| "complete"
|
||||||
|
| "seeding"
|
||||||
|
| "removed";
|
||||||
|
|
||||||
|
export type GameShop = "steam" | "epic";
|
||||||
|
|
||||||
|
export interface Game {
|
||||||
|
// TODO: To be depreacted
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
iconUrl: string;
|
||||||
|
status: GameStatus | null;
|
||||||
|
folderName: string;
|
||||||
|
downloadPath: string | null;
|
||||||
|
progress: number;
|
||||||
|
bytesDownloaded: number;
|
||||||
|
playTimeInMilliseconds: number;
|
||||||
|
downloader: Downloader;
|
||||||
|
winePrefixPath: string | null;
|
||||||
|
executablePath: string | null;
|
||||||
|
launchOptions: string | null;
|
||||||
|
lastTimePlayed: Date | null;
|
||||||
|
uri: string | null;
|
||||||
|
fileSize: number;
|
||||||
|
objectID: string;
|
||||||
|
shop: GameShop;
|
||||||
|
// downloadQueue: DownloadQueue | null;
|
||||||
|
downloadQueue: any | null;
|
||||||
|
shouldSeed: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnlockedAchievement {
|
||||||
|
name: string;
|
||||||
|
unlockTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamAchievement {
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
description?: string;
|
||||||
|
icon: string;
|
||||||
|
icongray: string;
|
||||||
|
hidden: boolean;
|
||||||
|
points?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserAchievement extends SteamAchievement {
|
||||||
|
unlocked: boolean;
|
||||||
|
unlockTime: number | null;
|
||||||
|
}
|
@ -1,17 +1,7 @@
|
|||||||
import type { Cracker, DownloadSourceStatus, Downloader } from "@shared";
|
import type { Cracker, DownloadSourceStatus, Downloader } from "@shared";
|
||||||
import type { SteamAppDetails } from "./steam.types";
|
import type { SteamAppDetails } from "./steam.types";
|
||||||
import type { Subscription } from "./level.types";
|
import type { Subscription } from "./level.types";
|
||||||
|
import type { GameShop } from "./game.types";
|
||||||
export type GameStatus =
|
|
||||||
| "active"
|
|
||||||
| "waiting"
|
|
||||||
| "paused"
|
|
||||||
| "error"
|
|
||||||
| "complete"
|
|
||||||
| "seeding"
|
|
||||||
| "removed";
|
|
||||||
|
|
||||||
export type GameShop = "steam" | "epic";
|
|
||||||
|
|
||||||
export type FriendRequestAction = "ACCEPTED" | "REFUSED" | "CANCEL";
|
export type FriendRequestAction = "ACCEPTED" | "REFUSED" | "CANCEL";
|
||||||
|
|
||||||
@ -32,48 +22,6 @@ export interface GameRepack {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AchievementData {
|
|
||||||
name: string;
|
|
||||||
displayName: string;
|
|
||||||
description?: string;
|
|
||||||
icon: string;
|
|
||||||
icongray: string;
|
|
||||||
hidden: boolean;
|
|
||||||
points?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserAchievement {
|
|
||||||
name: string;
|
|
||||||
hidden: boolean;
|
|
||||||
displayName: string;
|
|
||||||
points?: number;
|
|
||||||
description?: string;
|
|
||||||
unlocked: boolean;
|
|
||||||
unlockTime: number | null;
|
|
||||||
icon: string;
|
|
||||||
icongray: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RemoteUnlockedAchievement {
|
|
||||||
name: string;
|
|
||||||
hidden: boolean;
|
|
||||||
icon: string;
|
|
||||||
displayName: string;
|
|
||||||
description?: string;
|
|
||||||
unlockTime: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GameAchievement {
|
|
||||||
name: string;
|
|
||||||
hidden: boolean;
|
|
||||||
displayName: string;
|
|
||||||
description?: string;
|
|
||||||
unlocked: boolean;
|
|
||||||
unlockTime: number | null;
|
|
||||||
icon: string;
|
|
||||||
icongray: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ShopDetails = SteamAppDetails & {
|
export type ShopDetails = SteamAppDetails & {
|
||||||
objectId: string;
|
objectId: string;
|
||||||
};
|
};
|
||||||
@ -96,40 +44,6 @@ export interface UserGame {
|
|||||||
achievementsPointsEarnedSum: number;
|
achievementsPointsEarnedSum: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloadQueue {
|
|
||||||
id: number;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Used by the library */
|
|
||||||
export interface Game {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
iconUrl: string;
|
|
||||||
status: GameStatus | null;
|
|
||||||
folderName: string;
|
|
||||||
downloadPath: string | null;
|
|
||||||
progress: number;
|
|
||||||
bytesDownloaded: number;
|
|
||||||
playTimeInMilliseconds: number;
|
|
||||||
downloader: Downloader;
|
|
||||||
winePrefixPath: string | null;
|
|
||||||
executablePath: string | null;
|
|
||||||
launchOptions: string | null;
|
|
||||||
lastTimePlayed: Date | null;
|
|
||||||
uri: string | null;
|
|
||||||
fileSize: number;
|
|
||||||
objectID: string;
|
|
||||||
shop: GameShop;
|
|
||||||
downloadQueue: DownloadQueue | null;
|
|
||||||
shouldSeed: boolean;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LibraryGame = Omit<Game, "repacks">;
|
|
||||||
|
|
||||||
export interface GameRunning {
|
export interface GameRunning {
|
||||||
id?: number;
|
id?: number;
|
||||||
title: string;
|
title: string;
|
||||||
@ -139,24 +53,6 @@ export interface GameRunning {
|
|||||||
sessionDurationInMillis: number;
|
sessionDurationInMillis: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloadProgress {
|
|
||||||
downloadSpeed: number;
|
|
||||||
timeRemaining: number;
|
|
||||||
numPeers: number;
|
|
||||||
numSeeds: number;
|
|
||||||
isDownloadingMetadata: boolean;
|
|
||||||
isCheckingFiles: boolean;
|
|
||||||
progress: number;
|
|
||||||
gameId: number;
|
|
||||||
game: LibraryGame;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SeedingStatus {
|
|
||||||
gameId: number;
|
|
||||||
status: GameStatus;
|
|
||||||
uploadSpeed: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserPreferences {
|
export interface UserPreferences {
|
||||||
downloadsPath: string | null;
|
downloadsPath: string | null;
|
||||||
language: string;
|
language: string;
|
||||||
@ -344,11 +240,6 @@ export interface UserStats {
|
|||||||
unlockedAchievementSum?: number;
|
unlockedAchievementSum?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnlockedAchievement {
|
|
||||||
name: string;
|
|
||||||
unlockTime: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AchievementFile {
|
export interface AchievementFile {
|
||||||
type: Cracker;
|
type: Cracker;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
@ -407,9 +298,9 @@ export interface CatalogueSearchPayload {
|
|||||||
developers: string[];
|
developers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export * from "./game.types";
|
||||||
export * from "./steam.types";
|
export * from "./steam.types";
|
||||||
export * from "./real-debrid.types";
|
export * from "./download.types";
|
||||||
export * from "./ludusavi.types";
|
export * from "./ludusavi.types";
|
||||||
export * from "./how-long-to-beat.types";
|
export * from "./how-long-to-beat.types";
|
||||||
export * from "./torbox.types";
|
|
||||||
export * from "./level.types";
|
export * from "./level.types";
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import type { SteamAchievement, UnlockedAchievement } from "./game.types";
|
||||||
|
|
||||||
export type SubscriptionStatus = "active" | "pending" | "cancelled";
|
export type SubscriptionStatus = "active" | "pending" | "cancelled";
|
||||||
|
|
||||||
export interface Subscription {
|
export interface Subscription {
|
||||||
@ -21,3 +23,8 @@ export interface User {
|
|||||||
backgroundImageUrl: string | null;
|
backgroundImageUrl: string | null;
|
||||||
subscription: Subscription | null;
|
subscription: Subscription | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GameAchievement {
|
||||||
|
achievements: SteamAchievement[];
|
||||||
|
unlockedAchievements: UnlockedAchievement[];
|
||||||
|
}
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
export interface RealDebridUnrestrictLink {
|
|
||||||
id: string;
|
|
||||||
filename: string;
|
|
||||||
mimeType: string;
|
|
||||||
filesize: number;
|
|
||||||
link: string;
|
|
||||||
host: string;
|
|
||||||
host_icon: string;
|
|
||||||
chunks: number;
|
|
||||||
crc: number;
|
|
||||||
download: string;
|
|
||||||
streamable: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RealDebridAddMagnet {
|
|
||||||
id: string;
|
|
||||||
// URL of the created resource
|
|
||||||
uri: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RealDebridTorrentInfo {
|
|
||||||
id: string;
|
|
||||||
filename: string;
|
|
||||||
original_filename: string;
|
|
||||||
hash: string;
|
|
||||||
bytes: number;
|
|
||||||
original_bytes: number;
|
|
||||||
host: string;
|
|
||||||
split: number;
|
|
||||||
progress: number;
|
|
||||||
status:
|
|
||||||
| "magnet_error"
|
|
||||||
| "magnet_conversion"
|
|
||||||
| "waiting_files_selection"
|
|
||||||
| "queued"
|
|
||||||
| "downloading"
|
|
||||||
| "downloaded"
|
|
||||||
| "error"
|
|
||||||
| "virus"
|
|
||||||
| "compressing"
|
|
||||||
| "uploading"
|
|
||||||
| "dead";
|
|
||||||
added: string;
|
|
||||||
files: {
|
|
||||||
id: number;
|
|
||||||
path: string;
|
|
||||||
bytes: number;
|
|
||||||
selected: number;
|
|
||||||
}[];
|
|
||||||
links: string[];
|
|
||||||
ended: string;
|
|
||||||
speed: number;
|
|
||||||
seeders: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RealDebridUser {
|
|
||||||
id: number;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
points: number;
|
|
||||||
locale: string;
|
|
||||||
avatar: string;
|
|
||||||
type: string;
|
|
||||||
premium: number;
|
|
||||||
expiration: string;
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
export interface TorBoxUser {
|
|
||||||
id: number;
|
|
||||||
email: string;
|
|
||||||
plan: string;
|
|
||||||
expiration: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TorBoxUserRequest {
|
|
||||||
success: boolean;
|
|
||||||
detail: string;
|
|
||||||
error: string;
|
|
||||||
data: TorBoxUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TorBoxFile {
|
|
||||||
id: number;
|
|
||||||
md5: string;
|
|
||||||
s3_path: string;
|
|
||||||
name: string;
|
|
||||||
size: number;
|
|
||||||
mimetype: string;
|
|
||||||
short_name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TorBoxTorrentInfo {
|
|
||||||
id: number;
|
|
||||||
hash: string;
|
|
||||||
created_at: string;
|
|
||||||
updated_at: string;
|
|
||||||
magnet: string;
|
|
||||||
size: number;
|
|
||||||
active: boolean;
|
|
||||||
cached: boolean;
|
|
||||||
auth_id: string;
|
|
||||||
download_state:
|
|
||||||
| "downloading"
|
|
||||||
| "uploading"
|
|
||||||
| "stalled (no seeds)"
|
|
||||||
| "paused"
|
|
||||||
| "completed"
|
|
||||||
| "cached"
|
|
||||||
| "metaDL"
|
|
||||||
| "checkingResumeData";
|
|
||||||
seeds: number;
|
|
||||||
ratio: number;
|
|
||||||
progress: number;
|
|
||||||
download_speed: number;
|
|
||||||
upload_speed: number;
|
|
||||||
name: string;
|
|
||||||
eta: number;
|
|
||||||
files: TorBoxFile[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TorBoxTorrentInfoRequest {
|
|
||||||
success: boolean;
|
|
||||||
detail: string;
|
|
||||||
error: string;
|
|
||||||
data: TorBoxTorrentInfo[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TorBoxAddTorrentRequest {
|
|
||||||
success: boolean;
|
|
||||||
detail: string;
|
|
||||||
error: string;
|
|
||||||
data: {
|
|
||||||
torrent_id: number;
|
|
||||||
name: string;
|
|
||||||
hash: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TorBoxRequestLinkRequest {
|
|
||||||
success: boolean;
|
|
||||||
detail: string;
|
|
||||||
error: string;
|
|
||||||
data: string;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user