diff --git a/src/main/events/achievements/upload-achievement-image.ts b/src/main/events/achievements/upload-achievement-image.ts index 4f771aca..2bd3e4c2 100644 --- a/src/main/events/achievements/upload-achievement-image.ts +++ b/src/main/events/achievements/upload-achievement-image.ts @@ -8,15 +8,12 @@ import { gameAchievementsSublevel, levelKeys, db } from "@main/level"; import { logger } from "@main/services/logger"; import type { GameShop, User } from "@types"; -/** - * Uploads an achievement image to CDN using presigned URL - */ + const uploadImageToCDN = async (imagePath: string): Promise => { const stat = fs.statSync(imagePath); const fileBuffer = fs.readFileSync(imagePath); const fileSizeInBytes = stat.size; - // Get presigned URL for achievement image const response = await HydraApi.post<{ presignedUrl: string; achievementImageUrl: string; @@ -27,7 +24,6 @@ const uploadImageToCDN = async (imagePath: string): Promise => { const mimeType = await fileTypeFromFile(imagePath); - // Upload to CDN await axios.put(response.presignedUrl, fileBuffer, { headers: { "Content-Type": mimeType?.mime, @@ -37,21 +33,16 @@ const uploadImageToCDN = async (imagePath: string): Promise => { return response.achievementImageUrl; }; -/** - * Stores achievement image locally in the database - */ + const storeImageLocally = async (imagePath: string): Promise => { const fileBuffer = fs.readFileSync(imagePath); const base64Image = fileBuffer.toString("base64"); const mimeType = await fileTypeFromFile(imagePath); - // Create a data URL for local storage return `data:${mimeType?.mime || "image/jpeg"};base64,${base64Image}`; }; -/** - * Updates the achievement with the image URL via API - */ + const updateAchievementWithImageUrl = async ( shop: GameShop, gameId: string, @@ -64,9 +55,7 @@ const updateAchievementWithImageUrl = async ( ); }; -/** - * Main function for uploading achievement images (called from mergeAchievements) - */ + export const uploadAchievementImage = async ( gameId: string, achievementName: string, @@ -76,7 +65,6 @@ export const uploadAchievementImage = async ( try { let imageUrl: string; - // Check if user has active subscription const hasSubscription = await db .get(levelKeys.user, { valueEncoding: "json" }) .then((user) => { @@ -86,7 +74,6 @@ export const uploadAchievementImage = async ( .catch(() => false); if (hasSubscription) { - // Upload to CDN and update via API imageUrl = await uploadImageToCDN(imagePath); if (shop) { await updateAchievementWithImageUrl( @@ -100,7 +87,6 @@ export const uploadAchievementImage = async ( `Achievement image uploaded to CDN for ${gameId}:${achievementName}` ); } else { - // Store locally imageUrl = await storeImageLocally(imagePath); logger.log( `Achievement image stored locally for ${gameId}:${achievementName}` @@ -117,9 +103,7 @@ export const uploadAchievementImage = async ( } }; -/** - * IPC event handler for uploading achievement images - */ + const uploadAchievementImageEvent = async ( _event: Electron.IpcMainInvokeEvent, params: { @@ -139,7 +123,6 @@ const uploadAchievementImageEvent = async ( shop ); - // Update local database with image URL const achievementKey = levelKeys.game(shop, gameId); const existingData = await gameAchievementsSublevel .get(achievementKey) @@ -152,7 +135,6 @@ const uploadAchievementImageEvent = async ( }); } - // Clean up the temporary screenshot file try { fs.unlinkSync(imagePath); } catch (error) { @@ -161,7 +143,6 @@ const uploadAchievementImageEvent = async ( return result; } catch (error) { - // Clean up the temporary screenshot file even on error try { fs.unlinkSync(imagePath); } catch (cleanupError) { diff --git a/src/main/events/user/get-unlocked-achievements.ts b/src/main/events/user/get-unlocked-achievements.ts index 3569ac3a..581b75dd 100644 --- a/src/main/events/user/get-unlocked-achievements.ts +++ b/src/main/events/user/get-unlocked-achievements.ts @@ -33,7 +33,6 @@ export const getUnlockedAchievements = async ( const unlockedAchievements = cachedAchievements?.unlockedAchievements ?? []; - // Try to get user achievements with image URLs from remote API let remoteUserAchievements: UserAchievement[] = []; try { const userDetails = await db.get(levelKeys.user, { @@ -51,7 +50,6 @@ export const getUnlockedAchievements = async ( ); } } catch (error) { - // If API call fails, continue with local data only if (!(error instanceof UserNotLoggedInError)) { console.warn("Failed to fetch remote user achievements:", error); } diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 8036004e..b32b82cf 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -5,7 +5,6 @@ import { logger } from "../logger"; import { db, gameAchievementsSublevel, levelKeys } from "@main/level"; import { AxiosError } from "axios"; - const getModifiedSinceHeader = ( cachedAchievements: GameAchievement | undefined, userLanguage: string @@ -32,8 +31,7 @@ export const getGameAchievementData = async ( const cachedAchievements = await gameAchievementsSublevel.get(gameKey); - - if(cachedAchievements?.achievements && useCachedData) { + if (cachedAchievements?.achievements && useCachedData) { return cachedAchievements.achievements; } diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 8c8ba971..7eaf44dc 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -190,21 +190,17 @@ export const mergeAchievements = async ( ); } - // Capture and upload screenshot AFTER achievements are synced to server if ( newAchievements.length && userPreferences.enableAchievementScreenshots === true ) { try { - // Import and trigger the upload process const { uploadAchievementImage } = await import( "@main/events/achievements/upload-achievement-image" ); - // Upload the screenshot for each new achievement for (const achievement of newAchievements) { try { - // Find the achievement data to get the display name const achievementData = achievementsData.find( (steamAchievement) => { return ( @@ -217,7 +213,6 @@ export const mergeAchievements = async ( const achievementDisplayName = achievementData?.displayName || achievement.name; - // Capture screenshot with game title and achievement name const screenshotPath = await ScreenshotService.captureDesktopScreenshot( game.title, diff --git a/src/main/services/screenshot.ts b/src/main/services/screenshot.ts index 76912ecf..1f19e786 100644 --- a/src/main/services/screenshot.ts +++ b/src/main/services/screenshot.ts @@ -5,20 +5,17 @@ import { app } from "electron"; import { logger } from "./logger"; export class ScreenshotService { - private static readonly SCREENSHOT_QUALITY = 60; // Reduced for better compression + private static readonly SCREENSHOT_QUALITY = 80; private static readonly SCREENSHOT_FORMAT = "jpeg"; - private static readonly MAX_WIDTH = 1280; // Maximum width for compression - private static readonly MAX_HEIGHT = 720; // Maximum height for compression + private static readonly MAX_WIDTH = 1280; + private static readonly MAX_HEIGHT = 720; + - /** - * Compresses an image by resizing and adjusting quality - */ private static compressImage( image: Electron.NativeImage ): Electron.NativeImage { const size = image.getSize(); - // Calculate new dimensions while maintaining aspect ratio let newWidth = size.width; let newHeight = size.height; @@ -34,7 +31,6 @@ export class ScreenshotService { } } - // Resize the image if dimensions changed if (newWidth !== size.width || newHeight !== size.height) { return image.resize({ width: newWidth, height: newHeight }); } @@ -47,7 +43,6 @@ export class ScreenshotService { achievementName?: string ): Promise { try { - // Get all available desktop sources const sources = await desktopCapturer.getSources({ types: ["screen"], thumbnailSize: { width: 1920, height: 1080 }, @@ -57,18 +52,14 @@ export class ScreenshotService { throw new Error("No desktop sources available for screenshot"); } - // Use the primary screen (first source) const primaryScreen = sources[0]; - // Convert the thumbnail to a higher quality image const originalImage = nativeImage.createFromDataURL( primaryScreen.thumbnail.toDataURL() ); - // Compress the image to reduce file size const compressedImage = this.compressImage(originalImage); - // Create screenshots directory structure const userDataPath = app.getPath("userData"); const screenshotsDir = path.join(userDataPath, "screenshots"); @@ -76,31 +67,26 @@ export class ScreenshotService { let filename: string; if (gameTitle && achievementName) { - // Create game-specific directory const sanitizedGameTitle = gameTitle.replace(/[<>:"/\\|?*]/g, "_"); const gameDir = path.join(screenshotsDir, sanitizedGameTitle); finalDir = gameDir; - // Use achievement name as filename (sanitized) const sanitizedAchievementName = achievementName.replace( /[<>:"/\\|?*]/g, "_" ); filename = `${sanitizedAchievementName}.${this.SCREENSHOT_FORMAT}`; } else { - // Fallback to timestamp-based naming const timestamp = Date.now(); filename = `achievement_screenshot_${timestamp}.${this.SCREENSHOT_FORMAT}`; } - // Ensure directory exists if (!fs.existsSync(finalDir)) { fs.mkdirSync(finalDir, { recursive: true }); } const screenshotPath = path.join(finalDir, filename); - // Save the compressed screenshot as JPEG with specified quality const jpegBuffer = compressedImage.toJPEG(this.SCREENSHOT_QUALITY); fs.writeFileSync(screenshotPath, jpegBuffer); @@ -121,7 +107,6 @@ export class ScreenshotService { return; } - // Get all files recursively from screenshots directory and subdirectories const getAllFiles = ( dir: string ): Array<{ name: string; path: string; mtime: Date }> => { @@ -133,7 +118,6 @@ export class ScreenshotService { const stat = fs.statSync(itemPath); if (stat.isDirectory()) { - // Recursively get files from subdirectories files.push(...getAllFiles(itemPath)); } else if (item.endsWith(`.${this.SCREENSHOT_FORMAT}`)) { files.push({ @@ -151,7 +135,6 @@ export class ScreenshotService { (a, b) => b.mtime.getTime() - a.mtime.getTime() ); - // Keep only the 50 most recent screenshots (increased from 10 to accommodate multiple games) const filesToDelete = allFiles.slice(50); for (const file of filesToDelete) { @@ -163,9 +146,8 @@ export class ScreenshotService { } } - // Clean up empty directories const cleanupEmptyDirs = (dir: string) => { - if (dir === screenshotsDir) return; // Don't delete the main screenshots directory + if (dir === screenshotsDir) return; try { const items = fs.readdirSync(dir); @@ -174,11 +156,9 @@ export class ScreenshotService { logger.log(`Cleaned up empty directory: ${dir}`); } } catch (error) { - // Directory might not be empty or might not exist, ignore } }; - // Check for empty game directories and clean them up const gameDirectories = fs .readdirSync(screenshotsDir) .map((item) => path.join(screenshotsDir, item)) diff --git a/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.scss b/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.scss index 4be4eeb4..18373e98 100644 --- a/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.scss +++ b/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.scss @@ -100,7 +100,6 @@ } } -// Mobile responsiveness @media (max-width: 768px) { .fullscreen-image-modal { &__close-button { @@ -118,7 +117,6 @@ } } -/* Extra safeguard for very low viewport heights */ @media (max-height: 420px) { .fullscreen-image-modal { &__close-button { diff --git a/src/renderer/src/context/user-profile/user-profile.context.tsx b/src/renderer/src/context/user-profile/user-profile.context.tsx index eb8f5c70..a5f080cd 100644 --- a/src/renderer/src/context/user-profile/user-profile.context.tsx +++ b/src/renderer/src/context/user-profile/user-profile.context.tsx @@ -131,7 +131,6 @@ export function UserProfileContextProvider({ getUserStats(); getUserLibraryGames(); - // Get current language for API call const currentLanguage = i18n.language.split("-")[0]; const supportedLanguages = ["pt", "ru", "es"]; const language = supportedLanguages.includes(currentLanguage)