mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 13:34:54 +03:00
Merge pull request #1302 from hydralauncher/feat/hidden-achievement-description
feat: add description option for hidden achievements
This commit is contained in:
commit
ce2cf89fd6
@ -258,7 +258,8 @@
|
|||||||
"user_unblocked": "User has been unblocked",
|
"user_unblocked": "User has been unblocked",
|
||||||
"enable_achievement_notifications": "When an achievement is unlocked",
|
"enable_achievement_notifications": "When an achievement is unlocked",
|
||||||
"launch_minimized": "Launch Hydra minimized",
|
"launch_minimized": "Launch Hydra minimized",
|
||||||
"disable_nsfw_alert": "Disable NSFW alert"
|
"disable_nsfw_alert": "Disable NSFW alert",
|
||||||
|
"show_hidden_achievement_description": "Show hidden achievements description before unlocking them"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download complete",
|
"download_complete": "Download complete",
|
||||||
|
@ -254,7 +254,8 @@
|
|||||||
"user_unblocked": "Usuário desbloqueado",
|
"user_unblocked": "Usuário desbloqueado",
|
||||||
"enable_achievement_notifications": "Quando uma conquista é desbloqueada",
|
"enable_achievement_notifications": "Quando uma conquista é desbloqueada",
|
||||||
"launch_minimized": "Iniciar o Hydra minimizado",
|
"launch_minimized": "Iniciar o Hydra minimizado",
|
||||||
"disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado"
|
"disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado",
|
||||||
|
"show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download concluído",
|
"download_complete": "Download concluído",
|
||||||
|
@ -41,6 +41,9 @@ export class UserPreferences {
|
|||||||
@Column("boolean", { default: false })
|
@Column("boolean", { default: false })
|
||||||
disableNsfwAlert: boolean;
|
disableNsfwAlert: boolean;
|
||||||
|
|
||||||
|
@Column("boolean", { default: false })
|
||||||
|
showHiddenAchievementsDescription: boolean;
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import type { StartGameDownloadPayload } from "@types";
|
import type { StartGameDownloadPayload } from "@types";
|
||||||
import { DownloadManager, HydraApi, logger } from "@main/services";
|
import { DownloadManager, HydraApi } from "@main/services";
|
||||||
|
|
||||||
import { Not } from "typeorm";
|
import { Not } from "typeorm";
|
||||||
import { steamGamesWorker } from "@main/workers";
|
import { steamGamesWorker } from "@main/workers";
|
||||||
@ -76,24 +76,23 @@ const startGameDownload = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
createGame(updatedGame!).catch(() => {});
|
|
||||||
|
|
||||||
HydraApi.post(
|
|
||||||
"/games/download",
|
|
||||||
{
|
|
||||||
objectId: updatedGame!.objectID,
|
|
||||||
shop: updatedGame!.shop,
|
|
||||||
},
|
|
||||||
{ needsAuth: false }
|
|
||||||
).catch((err) => {
|
|
||||||
logger.error("Failed to create game download", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
await DownloadManager.cancelDownload(updatedGame!.id);
|
await DownloadManager.cancelDownload(updatedGame!.id);
|
||||||
await DownloadManager.startDownload(updatedGame!);
|
await DownloadManager.startDownload(updatedGame!);
|
||||||
|
|
||||||
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
||||||
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
createGame(updatedGame!).catch(() => {}),
|
||||||
|
HydraApi.post(
|
||||||
|
"/games/download",
|
||||||
|
{
|
||||||
|
objectId: updatedGame!.objectID,
|
||||||
|
shop: updatedGame!.shop,
|
||||||
|
},
|
||||||
|
{ needsAuth: false }
|
||||||
|
).catch(() => {}),
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import type { GameShop, UnlockedAchievement, UserAchievement } from "@types";
|
import type { GameShop, UnlockedAchievement, UserAchievement } from "@types";
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { gameAchievementRepository } from "@main/repository";
|
import {
|
||||||
|
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";
|
||||||
|
|
||||||
export const getUnlockedAchievements = async (
|
export const getUnlockedAchievements = async (
|
||||||
@ -12,10 +15,17 @@ export const getUnlockedAchievements = async (
|
|||||||
where: { objectId, shop },
|
where: { objectId, shop },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const userPreferences = await userPreferencesRepository.findOne({
|
||||||
|
where: { id: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const showHiddenAchievementsDescription =
|
||||||
|
userPreferences?.showHiddenAchievementsDescription || false;
|
||||||
|
|
||||||
const achievementsData = await getGameAchievementData(
|
const achievementsData = await getGameAchievementData(
|
||||||
objectId,
|
objectId,
|
||||||
shop,
|
shop,
|
||||||
useCachedData
|
useCachedData ? cachedAchievements : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const unlockedAchievements = JSON.parse(
|
const unlockedAchievements = JSON.parse(
|
||||||
@ -50,6 +60,10 @@ export const getUnlockedAchievements = async (
|
|||||||
unlocked: false,
|
unlocked: false,
|
||||||
unlockTime: null,
|
unlockTime: null,
|
||||||
icongray: icongray,
|
icongray: icongray,
|
||||||
|
description:
|
||||||
|
!achievementData.hidden || showHiddenAchievementsDescription
|
||||||
|
? achievementData.description
|
||||||
|
: undefined,
|
||||||
} as UserAchievement;
|
} as UserAchievement;
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
|
@ -13,6 +13,8 @@ import { AddBackgroundImageUrl } from "./migrations/20241016100249_add_backgroun
|
|||||||
import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game";
|
import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game";
|
||||||
import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column";
|
import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column";
|
||||||
import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_column";
|
import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_column";
|
||||||
|
import { AddHiddenAchievementDescriptionColumn } from "./migrations/20241216140633_add_hidden_achievement_description_column ";
|
||||||
|
|
||||||
export type HydraMigration = Knex.Migration & { name: string };
|
export type HydraMigration = Knex.Migration & { name: string };
|
||||||
|
|
||||||
class MigrationSource implements Knex.MigrationSource<HydraMigration> {
|
class MigrationSource implements Knex.MigrationSource<HydraMigration> {
|
||||||
@ -30,6 +32,7 @@ class MigrationSource implements Knex.MigrationSource<HydraMigration> {
|
|||||||
AddWinePrefixToGame,
|
AddWinePrefixToGame,
|
||||||
AddStartMinimizedColumn,
|
AddStartMinimizedColumn,
|
||||||
AddDisableNsfwAlertColumn,
|
AddDisableNsfwAlertColumn,
|
||||||
|
AddHiddenAchievementDescriptionColumn,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
getMigrationName(migration: HydraMigration): string {
|
getMigrationName(migration: HydraMigration): string {
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import type { HydraMigration } from "@main/knex-client";
|
||||||
|
import type { Knex } from "knex";
|
||||||
|
|
||||||
|
export const AddHiddenAchievementDescriptionColumn: HydraMigration = {
|
||||||
|
name: "AddHiddenAchievementDescriptionColumn",
|
||||||
|
up: (knex: Knex) => {
|
||||||
|
return knex.schema.alterTable("user_preferences", (table) => {
|
||||||
|
return table
|
||||||
|
.boolean("showHiddenAchievementsDescription")
|
||||||
|
.notNullable()
|
||||||
|
.defaultTo(0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: async (knex: Knex) => {
|
||||||
|
return knex.schema.alterTable("user_preferences", (table) => {
|
||||||
|
return table.dropColumn("showHiddenAchievementsDescription");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
@ -1,40 +0,0 @@
|
|||||||
import path from "node:path";
|
|
||||||
import fs from "node:fs";
|
|
||||||
|
|
||||||
import { getSteamGameClientIcon, logger } from "@main/services";
|
|
||||||
import { chunk } from "lodash-es";
|
|
||||||
import { seedsPath } from "@main/constants";
|
|
||||||
|
|
||||||
import type { SteamGame } from "@types";
|
|
||||||
|
|
||||||
const steamGamesPath = path.join(seedsPath, "steam-games.json");
|
|
||||||
|
|
||||||
const steamGames = JSON.parse(
|
|
||||||
fs.readFileSync(steamGamesPath, "utf-8")
|
|
||||||
) as SteamGame[];
|
|
||||||
|
|
||||||
const chunks = chunk(steamGames, 1500);
|
|
||||||
|
|
||||||
for (const chunk of chunks) {
|
|
||||||
await Promise.all(
|
|
||||||
chunk.map(async (steamGame) => {
|
|
||||||
if (steamGame.clientIcon) return;
|
|
||||||
|
|
||||||
const index = steamGames.findIndex((game) => game.id === steamGame.id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const clientIcon = await getSteamGameClientIcon(String(steamGame.id));
|
|
||||||
|
|
||||||
steamGames[index].clientIcon = clientIcon;
|
|
||||||
|
|
||||||
logger.log("info", `Set ${steamGame.name} client icon`);
|
|
||||||
} catch (err) {
|
|
||||||
steamGames[index].clientIcon = null;
|
|
||||||
logger.log("info", `Could not set icon for ${steamGame.name}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.writeFileSync(steamGamesPath, JSON.stringify(steamGames));
|
|
||||||
logger.log("info", "Updated steam games");
|
|
||||||
}
|
|
@ -6,20 +6,15 @@ import { HydraApi } from "../hydra-api";
|
|||||||
import type { AchievementData, GameShop } from "@types";
|
import type { AchievementData, GameShop } from "@types";
|
||||||
import { UserNotLoggedInError } from "@shared";
|
import { UserNotLoggedInError } from "@shared";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
|
import { GameAchievement } from "@main/entity";
|
||||||
|
|
||||||
export const getGameAchievementData = async (
|
export const getGameAchievementData = async (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
shop: GameShop,
|
shop: GameShop,
|
||||||
useCachedData: boolean
|
cachedAchievements: GameAchievement | null
|
||||||
) => {
|
) => {
|
||||||
if (useCachedData) {
|
if (cachedAchievements && cachedAchievements.achievements) {
|
||||||
const cachedAchievements = await gameAchievementRepository.findOne({
|
return JSON.parse(cachedAchievements.achievements) as AchievementData[];
|
||||||
where: { objectId, shop },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cachedAchievements && cachedAchievements.achievements) {
|
|
||||||
return JSON.parse(cachedAchievements.achievements) as AchievementData[];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userPreferences = await userPreferencesRepository.findOne({
|
const userPreferences = await userPreferencesRepository.findOne({
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
export * from "./logger";
|
export * from "./logger";
|
||||||
export * from "./steam";
|
export * from "./steam";
|
||||||
export * from "./steam-250";
|
export * from "./steam-250";
|
||||||
export * from "./steam-grid";
|
|
||||||
export * from "./window-manager";
|
export * from "./window-manager";
|
||||||
export * from "./download";
|
export * from "./download";
|
||||||
export * from "./process-watcher";
|
export * from "./process-watcher";
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
import type { GameShop } from "@types";
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
export interface SteamGridResponse {
|
|
||||||
success: boolean;
|
|
||||||
data: {
|
|
||||||
id: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SteamGridGameResponse {
|
|
||||||
data: {
|
|
||||||
platforms: {
|
|
||||||
steam: {
|
|
||||||
metadata: {
|
|
||||||
clienticon: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getSteamGridData = async (
|
|
||||||
objectId: string,
|
|
||||||
path: string,
|
|
||||||
shop: GameShop,
|
|
||||||
params: Record<string, string> = {}
|
|
||||||
): Promise<SteamGridResponse> => {
|
|
||||||
const searchParams = new URLSearchParams(params);
|
|
||||||
|
|
||||||
if (!import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY) {
|
|
||||||
throw new Error("MAIN_VITE_STEAMGRIDDB_API_KEY is not set");
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.get(
|
|
||||||
`https://www.steamgriddb.com/api/v2/${path}/${shop}/${objectId}?${searchParams.toString()}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSteamGridGameById = async (
|
|
||||||
id: number
|
|
||||||
): Promise<SteamGridGameResponse> => {
|
|
||||||
const response = await axios.get(
|
|
||||||
`https://www.steamgriddb.com/api/public/game/${id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Referer: "https://www.steamgriddb.com/",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSteamGameClientIcon = async (objectId: string) => {
|
|
||||||
const {
|
|
||||||
data: { id: steamGridGameId },
|
|
||||||
} = await getSteamGridData(objectId, "games", "steam");
|
|
||||||
|
|
||||||
const steamGridGame = await getSteamGridGameById(steamGridGameId);
|
|
||||||
return steamGridGame.data.platforms.steam.metadata.clienticon;
|
|
||||||
};
|
|
@ -11,10 +11,10 @@ import type {
|
|||||||
GameRunning,
|
GameRunning,
|
||||||
FriendRequestAction,
|
FriendRequestAction,
|
||||||
UpdateProfileRequest,
|
UpdateProfileRequest,
|
||||||
|
GameAchievement,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
import type { CatalogueCategory } from "@shared";
|
import type { CatalogueCategory } from "@shared";
|
||||||
import type { AxiosProgressEvent } from "axios";
|
import type { AxiosProgressEvent } from "axios";
|
||||||
import { GameAchievement } from "@main/entity";
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("electron", {
|
contextBridge.exposeInMainWorld("electron", {
|
||||||
/* Torrenting */
|
/* Torrenting */
|
||||||
|
@ -19,6 +19,7 @@ export function SettingsBehavior() {
|
|||||||
runAtStartup: false,
|
runAtStartup: false,
|
||||||
startMinimized: false,
|
startMinimized: false,
|
||||||
disableNsfwAlert: false,
|
disableNsfwAlert: false,
|
||||||
|
showHiddenAchievementsDescription: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
@ -30,6 +31,8 @@ export function SettingsBehavior() {
|
|||||||
runAtStartup: userPreferences.runAtStartup,
|
runAtStartup: userPreferences.runAtStartup,
|
||||||
startMinimized: userPreferences.startMinimized,
|
startMinimized: userPreferences.startMinimized,
|
||||||
disableNsfwAlert: userPreferences.disableNsfwAlert,
|
disableNsfwAlert: userPreferences.disableNsfwAlert,
|
||||||
|
showHiddenAchievementsDescription:
|
||||||
|
userPreferences.showHiddenAchievementsDescription,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [userPreferences]);
|
}, [userPreferences]);
|
||||||
@ -96,6 +99,17 @@ export function SettingsBehavior() {
|
|||||||
handleChange({ disableNsfwAlert: !form.disableNsfwAlert })
|
handleChange({ disableNsfwAlert: !form.disableNsfwAlert })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CheckboxField
|
||||||
|
label={t("show_hidden_achievement_description")}
|
||||||
|
checked={form.showHiddenAchievementsDescription}
|
||||||
|
onChange={() =>
|
||||||
|
handleChange({
|
||||||
|
showHiddenAchievementsDescription:
|
||||||
|
!form.showHiddenAchievementsDescription,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -162,6 +162,7 @@ export interface UserPreferences {
|
|||||||
runAtStartup: boolean;
|
runAtStartup: boolean;
|
||||||
startMinimized: boolean;
|
startMinimized: boolean;
|
||||||
disableNsfwAlert: boolean;
|
disableNsfwAlert: boolean;
|
||||||
|
showHiddenAchievementsDescription: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Steam250Game {
|
export interface Steam250Game {
|
||||||
|
Loading…
Reference in New Issue
Block a user