From 715ac4b50aad24278912974b002e555f3e5036e4 Mon Sep 17 00:00:00 2001 From: Clem <58120735+clemdotla@users.noreply.github.com> Date: Sun, 1 Mar 2026 23:02:04 +0100 Subject: [PATCH 1/3] Use DOM to detect DLC and skip Steam API call Checking the DOM for the purple DLC box then getting parentid/name using dom again. Skipping a full steam api call Starts line 5256 --- public/luatools.js | 55 ++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/public/luatools.js b/public/luatools.js index 1ab029d..68c7b1e 100644 --- a/public/luatools.js +++ b/public/luatools.js @@ -5252,42 +5252,35 @@ startPolling(appid); }; - // First check if it's a DLC - fetch('https://store.steampowered.com/api/appdetails?appids=' + appid + '&filters=basic') - .then(function (res) { - return res.json(); - }) - .then(function (data) { - if (data && data[appid] && data[appid].success && data[appid].data) { - const info = data[appid].data; - if (info.type === 'dlc' && info.fullgame && info.fullgame.appid) { - showDlcWarning(appid, info.fullgame.appid, info.fullgame.name); - return; - } - } - // Not a DLC (or failed to check), proceed with database check - return fetchGamesDatabase().then(function (db) { - try { - const key = String(appid); - const gameData = db && db[key] ? db[key] : null; - if (gameData && gameData.playable === 0) { - // warning modal - showLuaToolsPlayableWarning('This game may not work, support for it wont be given in our discord', function () { - continueWithAdd(); - }, function () { }); - } else { + // Check if this is a dlc + const isdlc = !!document.querySelector(".game_area_dlc_bubble"); + const parentdiv = document.querySelector('.glance_details a[href*="/app/"]') + + if (isdlc && parentdiv) { + const id = parseInt(parentdiv.href.match(/app\/(\d+)\//)?.[1] ?? "") + const name = parentdiv.innerText ?? "name not found"; + + showDlcWarning(appid, id, name); + } else { + // Not a dlc (or failed) ? Then continue normally + return fetchGamesDatabase().then(function (db) { + try { + const gameData = db?.[String(appid)] ?? null; + if (gameData?.playable === 0) { + // warning modal + showLuaToolsPlayableWarning('This game may not work, support for it wont be given in our discord', function () { continueWithAdd(); - } - } catch (_) { + }, function () { }); + } else { continueWithAdd(); } - }); - }) - .catch(function (err) { - backendLog('LuaTools: DLC check failed: ' + err); - continueWithAdd(); + } catch (_) { + continueWithAdd(); + } }); + } + } } catch (_) { } } From e10836ce34254b5116d52541f6985aaaeda95cd2 Mon Sep 17 00:00:00 2001 From: Clem <58120735+clemdotla@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:41:03 +0100 Subject: [PATCH 2/3] Checking .lua content Includes the workshop check. Dlc check (You can modify the ugly frontend) --- backend/locales/en.json | 7 + backend/locales/fr.json | 397 +++++++++-------- backend/main.py | 962 ++++++++++++++++++++-------------------- public/luatools.js | 108 +++-- 4 files changed, 765 insertions(+), 709 deletions(-) diff --git a/backend/locales/en.json b/backend/locales/en.json index 77b5d4f..45ca4bc 100644 --- a/backend/locales/en.json +++ b/backend/locales/en.json @@ -23,11 +23,14 @@ "Cancelling...": "Cancelling...", "Check for updates": "Check for updates", "Checking availability…": "Checking availability…", + "Checking content…": "Checking content…", "Checking generic fix...": "Checking generic fix...", "Checking online-fix...": "Checking online-fix...", "Checking…": "Checking…", "Close": "Close", "Confirm": "Confirm", + "Content details =>": "Content details =>", + "Dlc: ": "Dlc: ", "DLC Detected": "DLC Detected", "DLCs are added together with the base game. To add fixes for this DLC, please go to the base game page:

{gameName}": "DLCs are added together with the base game. To add this DLC, please go to the base game page:

{gameName}", "Discord": "Discord", @@ -62,6 +65,7 @@ "Generic fix found!": "Generic fix found!", "Go to Base Game": "Go to Base Game", "Hide": "Hide", + "Included": "Included 🎉", "Installing…": "Installing…", "Join the Discord!": "Join the Discord!", "Left click to install, Right click for SteamDB": "Left click to install, Right click for SteamDB", @@ -75,11 +79,13 @@ "LuaTools · Menu": "LuaTools · Menu", "LuaTools · {api}": "LuaTools · {api}", "Manage Game": "Manage Game", + "Missing": "Missing ❌", "No games found.": "No games found.", "No generic fix": "No generic fix", "No online-fix": "No online-fix", "No updates available.": "No updates available.", "Not found": "Not found", + "No workshop for the game": "No workshop for the game ✅", "Online Fix": "Online Fix", "Online Fix (Unsteam)": "Online Fix (Unsteam)", "Online-fix found!": "Online-fix found!", @@ -98,6 +104,7 @@ "Unknown error": "Unknown error", "Waiting…": "Waiting…", "Working…": "Working…", + "Workshop: ": "Workshop: ", "bigpicture.mouseTip": "To use mouse mode in Steam: Guide Button + Right Joystick, click with RB", "common.alert.ok": "OK", "common.appName": "LuaTools", diff --git a/backend/locales/fr.json b/backend/locales/fr.json index 49592b1..853fcc9 100644 --- a/backend/locales/fr.json +++ b/backend/locales/fr.json @@ -1,195 +1,202 @@ -{ - "_meta": { - "code": "fr", - "name": "French", - "nativeName": "Français", - "credits": "Odjavel" - }, - "strings": { - "Add via LuaTools": "Ajouter via LuaTools", - "Advanced": "Avancé", - "All-In-One Fixes": "Correctifs tout-en-un", - "Apply": "Appliquer", - "Applying {fix}": "Application de {fix}", - "Are you sure you want to un-fix? This will remove fix files and verify game files.": "Êtes-vous sûr de vouloir supprimer le correctif ? Cela supprimera les fichiers de correctif et vérifiera les fichiers du jeu.", - "Are you sure?": "Êtes-vous sûr ?", - "Back": "Retour", - "Base Game": "Jeu de base", - "Cancel": "Annuler", - "Cancellation failed": "Échec de l'annulation", - "Cancelled": "Annulé", - "Cancelled by user": "Annulé par l'utilisateur", - "Cancelled: {reason}": "Annulé : {reason}", - "Cancelling...": "Annulation...", - "Check for updates": "Vérifier les mises à jour.", - "Checking availability…": "Vérification de la disponibilité…", - "Checking generic fix...": "Vérification du correctif générique...", - "Checking online-fix...": "Vérification du correctif Online-Fix...", - "Checking…": "Vérification…", - "Close": "Fermer", - "Confirm": "Confirmer", - "DLC Detected": "DLC Détecté", - "DLCs are added together with the base game. To add fixes for this DLC, please go to the base game page:

{gameName}": "Les DLC sont ajoutés avec le jeu de base. Pour ajouter des correctifs pour ce DLC, rendez-vous sur la page du jeu de base :

{gameName}", - "Discord": "Discord", - "Dismiss": "Fermer", - "Downloading...": "Téléchargement...", - "Downloading: {percent}%": "Téléchargement : {percent}%", - "Downloading…": "Téléchargement…", - "Error applying fix": "Erreur lors de l'application du correctif.", - "Error checking for fixes": "Erreur lors de la vérification des correctifs.", - "Error starting Online Fix": "Erreur lors du démarrage des correctifs Online-Fix.", - "Error starting un-fix": "Erreur lors du démarrage de la suppression du correctif.", - "Error! Code: {code}": "Erreur ! Code : {code}", - "Error, Code: {code}": "Erreur, Code : {code}", - "Error, Timed Out": "Erreur, Délai d'attente dépassé", - "Extracting to game folder...": "Extraction vers le dossier du jeu...", - "Failed": "Échec", - "Failed to cancel fix download": "Échec de l'annulation du téléchargement du correctif.", - "Failed to check for fixes.": "Échec de la vérification des correctifs.", - "Failed to load free APIs.": "Échec du chargement des API gratuites.", - "Failed to start fix download": "Échec du démarrage du téléchargement du correctif.", - "Failed to start un-fix": "Échec du démarrage de la suppression du correctif.", - "Failed: {error}": "Échec : {error}", - "Fetch Free API's": "Récupérer les API Gratuites.", - "Fetching game name...": "Récupération du nom du jeu...", - "Finishing…": "Finalisation…", - "Fixes Menu": "Menu des correctifs", - "Found": "Trouvé", - "Game added!": "Jeu ajouté !", - "Game folder": "Dossier du jeu", - "Game install path not found": "Chemin d'installation du jeu introuvable.", - "Generic Fix": "Correctif Générique", - "Generic fix found!": "Correctif générique trouvé !", - "Go to Base Game": "Aller au jeu de base", - "Hide": "Masquer", - "Installing…": "Installation…", - "Join the Discord!": "Rejoignez le Discord !", - "Left click to install, Right click for SteamDB": "Clic gauche pour installer. Clic droit pour SteamDB.", - "Loaded free APIs: {count}": "API gratuites chargées : {count}", - "Loading fixes...": "Chargement des correctifs...", - "Look for Fixes": "Rechercher des correctifs", - "LuaTools backend unavailable": "Backend LuaTools indisponible.", - "LuaTools · AIO Fixes Menu": "LuaTools · Menu des correctifs tout-en-un", - "LuaTools · Added Games": "LuaTools · Jeux ajoutés", - "LuaTools · Fixes Menu": "LuaTools · Menu des correctifs", - "LuaTools · Menu": "LuaTools · Menu", - "LuaTools · {api}": "LuaTools · {api}", - "Manage Game": "Gérer le jeu", - "No games found.": "Aucun jeux trouvé.", - "No generic fix": "Aucun correctif générique", - "No online-fix": "Aucun correctif Online-Fix", - "No updates available.": "Aucune mise à jour disponible.", - "Not found": "Introuvable", - "Online Fix": "Correctif en ligne (Online-Fix)", - "Online Fix (Unsteam)": "Correctif en ligne (Unsteam)", - "Online-fix found!": "Online-Fix trouvé !", - "Only possible thanks to {name} 💜": "Possible uniquement grâce à {name} 💜", - "Processing package…": "Traitement du paquet…", - "Remove via LuaTools": "Retirer via LuaTools", - "Removed {count} files. Running Steam verification...": "{count} fichiers supprimés. Exécution de la vérification Steam...", - "Removing fix files...": "Suppression des fichiers de correctif...", - "Restart Steam": "Redémarrer Steam", - "Restart Steam now?": "Redémarrer Steam maintenant ?", - "Settings": "Paramètres", - "Skipped": "Ignoré", - "Un-Fix (verify game)": "Supprimer le correctif (vérifier le jeu)", - "Un-Fixing game": "Suppression du correctif du jeu.", - "Unknown Game": "Jeu Inconnu", - "Unknown error": "Erreur inconnue", - "Waiting…": "En attente…", - "Working…": "Travail en cours…", - "bigpicture.mouseTip": "Pour utiliser le mode souris dans Steam : Bouton Guide + Joystick droit, clic avec RB", - "common.alert.ok": "OK", - "common.appName": "LuaTools", - "common.error.unsupportedOption": "Type d'option non pris en charge : {type}", - "common.status.error": "Erreur", - "common.status.loading": "Chargement...", - "common.status.success": "Succès", - "common.translationMissing": "Traduction manquante", - "common.warning": "Avertissement", - "disclaimer.inputLabel": "tapez \"Je Comprends\" dans la case ci-dessous pour continuer", - "disclaimer.inputPlaceholder": "Je Comprends", - "disclaimer.line1": "LuaTools n'est affilié d'aucune façon à Millennium", - "disclaimer.line2": "Millennium ne vous offrira PAS de support pour ce plugin sur leur serveur discord", - "disclaimer.line3": "Vous serez BANNI des serveurs LuaTools et Millennium si vous allez sur leur discord demander de l'aide", - "disclaimer.title": "Avis Important", - "gameStatus.denuvo": "Denuvo", - "gameStatus.needsFixes": "Correctif disponible", - "gameStatus.playable": "Jouable", - "gameStatus.unplayable": "Injouable", - "menu.advancedLabel": "Avancé", - "menu.checkForUpdates": "Vérifier les mises à jour", - "menu.discord": "Discord", - "menu.error.getPath": "Erreur lors de la récupération du chemin du jeu.", - "menu.error.noAppId": "Impossible de déterminer l'AppID du jeu.", - "menu.error.noInstall": "Impossible de trouver l'installation du jeu.", - "menu.error.notInstalled": "Jeu non installé ! Ajoutez-le et installez-le d'abord :D", - "menu.fetchFreeApis": "Récupérer les API Gratuites", - "menu.fixesMenu": "Menu des correctifs", - "menu.joinDiscordLabel": "Rejoignez le Discord !", - "menu.manageGameLabel": "Gérer le Jeu", - "menu.remove.confirm": "Retirer LuaTools pour ce jeu ?", - "menu.remove.failure": "Échec du retrait de LuaTools.", - "menu.remove.success": "LuaTools retiré pour cette application.", - "menu.removeLuaTools": "Retirer via LuaTools", - "menu.settings": "Paramètres", - "menu.title": "LuaTools · Menu", - "settings.close": "Fermer", - "settings.donateKeys.description": "Faire don des clés de décryptage pour les jeux, aide tout le monde !", - "settings.donateKeys.label": "Faire don de clés", - "settings.donateKeys.no": "Non", - "settings.donateKeys.yes": "Oui", - "settings.empty": "Aucun paramètre disponible pour le moment.", - "settings.error": "Échec du chargement des paramètres.", - "settings.general": "Général", - "settings.generalDescription": "Préférences globales de LuaTools.", - "settings.installedFixes.date": "Installé :", - "settings.installedFixes.delete": "Supprimer", - "settings.installedFixes.deleteConfirm": "Êtes-vous sûr de vouloir supprimer ce correctif ? Cela supprimera les fichiers du correctif et exécutera la vérification Steam.", - "settings.installedFixes.deleteError": "Échec de la suppression du correctif.", - "settings.installedFixes.deleteSuccess": "Correctif supprimé avec succès !", - "settings.installedFixes.deleting": "Suppression du correctif...", - "settings.installedFixes.empty": "Aucun correctif installé pour le moment.", - "settings.installedFixes.error": "Échec du chargement des correctifs installés.", - "settings.installedFixes.files": "{count} fichiers", - "settings.installedFixes.loading": "Recherche de correctifs installés...", - "settings.installedFixes.title": "Correctifs Installés", - "settings.installedFixes.type": "Type :", - "settings.installedLua.delete": "Supprimer", - "settings.installedLua.deleteConfirm": "Supprimer via LuaTools pour ce jeu ?", - "settings.installedLua.deleteError": "Échec de la suppression via LuaTools.", - "settings.installedLua.deleteSuccess": "Supprimé via LuaTools avec succès !", - "settings.installedLua.deleting": "Suppression via LuaTools...", - "settings.installedLua.disabled": "Désactivé", - "settings.installedLua.empty": "Aucun script Lua installé pour le moment.", - "settings.installedLua.error": "Échec du chargement des scripts Lua installés.", - "settings.installedLua.loading": "Recherche de scripts Lua installés...", - "settings.installedLua.modified": "Modifié :", - "settings.installedLua.title": "Jeux via LuaTools", - "settings.installedLua.unknownInfo": "Les jeux affichant 'Jeu inconnu' ont été installés depuis des sources externes (pas via LuaTools).", - "settings.language.description": "Choisissez la langue utilisée par LuaTools.", - "settings.language.label": "Langue", - "settings.language.option.en": "Anglais", - "settings.language.option.pt-BR": "Portugais Brésilien", - "settings.loading": "Chargement des paramètres...", - "settings.noChanges": "Aucune modification à enregistrer.", - "settings.refresh": "Actualiser", - "settings.refreshing": "Actualisation...", - "settings.save": "Enregistrer les paramètres", - "settings.saveError": "Échec de l'enregistrement des paramètres.", - "settings.saveSuccess": "Paramètres enregistrés avec succès.", - "settings.saving": "Enregistrement...", - "settings.search.clear": "Effacer la recherche", - "settings.search.noResults": "Aucun résultat trouvé", - "settings.search.placeholder": "Rechercher paramètres, jeux, correctifs...", - "settings.theme.description": "Choisissez le thème de couleur pour l'interface LuaTools.", - "settings.theme.label": "Thème", - "settings.title": "LuaTools · Paramètres", - "settings.unsaved": "Modifications non enregistrées", - "{fix} applied successfully!": "{fix} appliqué avec succès !", - "settings.useSteamLanguage.label": "Utiliser la langue de Steam", - "settings.useSteamLanguage.description": "Utiliser la langue du client Steam au lieu de celle de LuaTools.", - "settings.useSteamLanguage.yes": "Oui", - "settings.useSteamLanguage.no": "Non" - } -} +{ + "_meta": { + "code": "fr", + "name": "French", + "nativeName": "Français", + "credits": "Odjavel" + }, + "strings": { + "Add via LuaTools": "Ajouter via LuaTools", + "Advanced": "Avancé", + "All-In-One Fixes": "Correctifs tout-en-un", + "Apply": "Appliquer", + "Applying {fix}": "Application de {fix}", + "Are you sure you want to un-fix? This will remove fix files and verify game files.": "Êtes-vous sûr de vouloir supprimer le correctif ? Cela supprimera les fichiers de correctif et vérifiera les fichiers du jeu.", + "Are you sure?": "Êtes-vous sûr ?", + "Back": "Retour", + "Base Game": "Jeu de base", + "Cancel": "Annuler", + "Cancellation failed": "Échec de l'annulation", + "Cancelled": "Annulé", + "Cancelled by user": "Annulé par l'utilisateur", + "Cancelled: {reason}": "Annulé : {reason}", + "Cancelling...": "Annulation...", + "Check for updates": "Vérifier les mises à jour.", + "Checking availability…": "Vérification de la disponibilité…", + "Checking content…": "Vérification du contenu…", + "Checking generic fix...": "Vérification du correctif générique...", + "Checking online-fix...": "Vérification du correctif Online-Fix...", + "Checking…": "Vérification…", + "Close": "Fermer", + "Confirm": "Confirmer", + "Content details =>": "Détails du contenu =>", + "DLC Detected": "DLC Détecté", + "DLCs are added together with the base game. To add fixes for this DLC, please go to the base game page:

{gameName}": "Les DLC sont ajoutés avec le jeu de base. Pour ajouter des correctifs pour ce DLC, rendez-vous sur la page du jeu de base :

{gameName}", + "Discord": "Discord", + "Dismiss": "Fermer", + "Dlc: ": "Dlc", + "Downloading...": "Téléchargement...", + "Downloading: {percent}%": "Téléchargement : {percent}%", + "Downloading…": "Téléchargement…", + "Error applying fix": "Erreur lors de l'application du correctif.", + "Error checking for fixes": "Erreur lors de la vérification des correctifs.", + "Error starting Online Fix": "Erreur lors du démarrage des correctifs Online-Fix.", + "Error starting un-fix": "Erreur lors du démarrage de la suppression du correctif.", + "Error! Code: {code}": "Erreur ! Code : {code}", + "Error, Code: {code}": "Erreur, Code : {code}", + "Error, Timed Out": "Erreur, Délai d'attente dépassé", + "Extracting to game folder...": "Extraction vers le dossier du jeu...", + "Failed": "Échec", + "Failed to cancel fix download": "Échec de l'annulation du téléchargement du correctif.", + "Failed to check for fixes.": "Échec de la vérification des correctifs.", + "Failed to load free APIs.": "Échec du chargement des API gratuites.", + "Failed to start fix download": "Échec du démarrage du téléchargement du correctif.", + "Failed to start un-fix": "Échec du démarrage de la suppression du correctif.", + "Failed: {error}": "Échec : {error}", + "Fetch Free API's": "Récupérer les API Gratuites.", + "Fetching game name...": "Récupération du nom du jeu...", + "Finishing…": "Finalisation…", + "Fixes Menu": "Menu des correctifs", + "Found": "Trouvé", + "Game added!": "Jeu ajouté !", + "Game folder": "Dossier du jeu", + "Game install path not found": "Chemin d'installation du jeu introuvable.", + "Generic Fix": "Correctif Générique", + "Generic fix found!": "Correctif générique trouvé !", + "Go to Base Game": "Aller au jeu de base", + "Hide": "Masquer", + "Included": "Inclus 🎉", + "Installing…": "Installation…", + "Join the Discord!": "Rejoignez le Discord !", + "Left click to install, Right click for SteamDB": "Clic gauche pour installer. Clic droit pour SteamDB.", + "Loaded free APIs: {count}": "API gratuites chargées : {count}", + "Loading fixes...": "Chargement des correctifs...", + "Look for Fixes": "Rechercher des correctifs", + "LuaTools backend unavailable": "Backend LuaTools indisponible.", + "LuaTools · AIO Fixes Menu": "LuaTools · Menu des correctifs tout-en-un", + "LuaTools · Added Games": "LuaTools · Jeux ajoutés", + "LuaTools · Fixes Menu": "LuaTools · Menu des correctifs", + "LuaTools · Menu": "LuaTools · Menu", + "LuaTools · {api}": "LuaTools · {api}", + "Manage Game": "Gérer le jeu", + "Missing": "Manquant ❌", + "No games found.": "Aucun jeux trouvé.", + "No generic fix": "Aucun correctif générique", + "No online-fix": "Aucun correctif Online-Fix", + "No updates available.": "Aucune mise à jour disponible.", + "Not found": "Introuvable", + "No workshop for the game": "Pas de workshop pour le jeu ✅", + "Online Fix": "Correctif en ligne (Online-Fix)", + "Online Fix (Unsteam)": "Correctif en ligne (Unsteam)", + "Online-fix found!": "Online-Fix trouvé !", + "Only possible thanks to {name} 💜": "Possible uniquement grâce à {name} 💜", + "Processing package…": "Traitement du paquet…", + "Remove via LuaTools": "Retirer via LuaTools", + "Removed {count} files. Running Steam verification...": "{count} fichiers supprimés. Exécution de la vérification Steam...", + "Removing fix files...": "Suppression des fichiers de correctif...", + "Restart Steam": "Redémarrer Steam", + "Restart Steam now?": "Redémarrer Steam maintenant ?", + "Settings": "Paramètres", + "Skipped": "Ignoré", + "Un-Fix (verify game)": "Supprimer le correctif (vérifier le jeu)", + "Un-Fixing game": "Suppression du correctif du jeu.", + "Unknown Game": "Jeu Inconnu", + "Unknown error": "Erreur inconnue", + "Waiting…": "En attente…", + "Working…": "Travail en cours…", + "Workshop: ": "Workshop:", + "bigpicture.mouseTip": "Pour utiliser le mode souris dans Steam : Bouton Guide + Joystick droit, clic avec RB", + "common.alert.ok": "OK", + "common.appName": "LuaTools", + "common.error.unsupportedOption": "Type d'option non pris en charge : {type}", + "common.status.error": "Erreur", + "common.status.loading": "Chargement...", + "common.status.success": "Succès", + "common.translationMissing": "Traduction manquante", + "common.warning": "Avertissement", + "disclaimer.inputLabel": "tapez \"Je Comprends\" dans la case ci-dessous pour continuer", + "disclaimer.inputPlaceholder": "Je Comprends", + "disclaimer.line1": "LuaTools n'est affilié d'aucune façon à Millennium", + "disclaimer.line2": "Millennium ne vous offrira PAS de support pour ce plugin sur leur serveur discord", + "disclaimer.line3": "Vous serez BANNI des serveurs LuaTools et Millennium si vous allez sur leur discord demander de l'aide", + "disclaimer.title": "Avis Important", + "gameStatus.denuvo": "Denuvo", + "gameStatus.needsFixes": "Correctif disponible", + "gameStatus.playable": "Jouable", + "gameStatus.unplayable": "Injouable", + "menu.advancedLabel": "Avancé", + "menu.checkForUpdates": "Vérifier les mises à jour", + "menu.discord": "Discord", + "menu.error.getPath": "Erreur lors de la récupération du chemin du jeu.", + "menu.error.noAppId": "Impossible de déterminer l'AppID du jeu.", + "menu.error.noInstall": "Impossible de trouver l'installation du jeu.", + "menu.error.notInstalled": "Jeu non installé ! Ajoutez-le et installez-le d'abord :D", + "menu.fetchFreeApis": "Récupérer les API Gratuites", + "menu.fixesMenu": "Menu des correctifs", + "menu.joinDiscordLabel": "Rejoignez le Discord !", + "menu.manageGameLabel": "Gérer le Jeu", + "menu.remove.confirm": "Retirer LuaTools pour ce jeu ?", + "menu.remove.failure": "Échec du retrait de LuaTools.", + "menu.remove.success": "LuaTools retiré pour cette application.", + "menu.removeLuaTools": "Retirer via LuaTools", + "menu.settings": "Paramètres", + "menu.title": "LuaTools · Menu", + "settings.close": "Fermer", + "settings.donateKeys.description": "Faire don des clés de décryptage pour les jeux, aide tout le monde !", + "settings.donateKeys.label": "Faire don de clés", + "settings.donateKeys.no": "Non", + "settings.donateKeys.yes": "Oui", + "settings.empty": "Aucun paramètre disponible pour le moment.", + "settings.error": "Échec du chargement des paramètres.", + "settings.general": "Général", + "settings.generalDescription": "Préférences globales de LuaTools.", + "settings.installedFixes.date": "Installé :", + "settings.installedFixes.delete": "Supprimer", + "settings.installedFixes.deleteConfirm": "Êtes-vous sûr de vouloir supprimer ce correctif ? Cela supprimera les fichiers du correctif et exécutera la vérification Steam.", + "settings.installedFixes.deleteError": "Échec de la suppression du correctif.", + "settings.installedFixes.deleteSuccess": "Correctif supprimé avec succès !", + "settings.installedFixes.deleting": "Suppression du correctif...", + "settings.installedFixes.empty": "Aucun correctif installé pour le moment.", + "settings.installedFixes.error": "Échec du chargement des correctifs installés.", + "settings.installedFixes.files": "{count} fichiers", + "settings.installedFixes.loading": "Recherche de correctifs installés...", + "settings.installedFixes.title": "Correctifs Installés", + "settings.installedFixes.type": "Type :", + "settings.installedLua.delete": "Supprimer", + "settings.installedLua.deleteConfirm": "Supprimer via LuaTools pour ce jeu ?", + "settings.installedLua.deleteError": "Échec de la suppression via LuaTools.", + "settings.installedLua.deleteSuccess": "Supprimé via LuaTools avec succès !", + "settings.installedLua.deleting": "Suppression via LuaTools...", + "settings.installedLua.disabled": "Désactivé", + "settings.installedLua.empty": "Aucun script Lua installé pour le moment.", + "settings.installedLua.error": "Échec du chargement des scripts Lua installés.", + "settings.installedLua.loading": "Recherche de scripts Lua installés...", + "settings.installedLua.modified": "Modifié :", + "settings.installedLua.title": "Jeux via LuaTools", + "settings.installedLua.unknownInfo": "Les jeux affichant 'Jeu inconnu' ont été installés depuis des sources externes (pas via LuaTools).", + "settings.language.description": "Choisissez la langue utilisée par LuaTools.", + "settings.language.label": "Langue", + "settings.language.option.en": "Anglais", + "settings.language.option.pt-BR": "Portugais Brésilien", + "settings.loading": "Chargement des paramètres...", + "settings.noChanges": "Aucune modification à enregistrer.", + "settings.refresh": "Actualiser", + "settings.refreshing": "Actualisation...", + "settings.save": "Enregistrer les paramètres", + "settings.saveError": "Échec de l'enregistrement des paramètres.", + "settings.saveSuccess": "Paramètres enregistrés avec succès.", + "settings.saving": "Enregistrement...", + "settings.search.clear": "Effacer la recherche", + "settings.search.noResults": "Aucun résultat trouvé", + "settings.search.placeholder": "Rechercher paramètres, jeux, correctifs...", + "settings.theme.description": "Choisissez le thème de couleur pour l'interface LuaTools.", + "settings.theme.label": "Thème", + "settings.title": "LuaTools · Paramètres", + "settings.unsaved": "Modifications non enregistrées", + "settings.useSteamLanguage.description": "Utiliser la langue du client Steam au lieu de celle de LuaTools.", + "settings.useSteamLanguage.label": "Utiliser la langue de Steam", + "settings.useSteamLanguage.no": "Non", + "settings.useSteamLanguage.yes": "Oui", + "{fix} applied successfully!": "{fix} appliqué avec succès !" + } +} \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 0ec693f..2460d82 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,481 +1,481 @@ -import json -import os -import shutil -import sys -import webbrowser - -from typing import Any - -import Millennium # type: ignore -import PluginUtils # type: ignore - -from api_manifest import ( - fetch_free_apis_now as api_fetch_free_apis_now, - get_api_list as api_get_api_list, - get_init_apis_message as api_get_init_message, - init_apis as api_init_apis, - store_last_message, -) -from auto_update import ( - apply_pending_update_if_any, - check_for_updates_now as auto_check_for_updates_now, - restart_steam as auto_restart_steam, - start_auto_update_background_check, -) -from config import WEBKIT_DIR_NAME, WEB_UI_ICON_FILE, WEB_UI_JS_FILE -from downloads import ( - cancel_add_via_luatools, - delete_luatools_for_app, - dismiss_loaded_apps, - get_add_status, - get_icon_data_url, - get_installed_lua_scripts, - has_luatools_for_app, - get_games_database, - init_applist, - read_loaded_apps, - start_add_via_luatools, -) -from fixes import ( - apply_game_fix, - cancel_apply_fix, - check_for_fixes, - get_apply_fix_status, - get_installed_fixes, - get_unfix_status, - unfix_game, -) -from utils import ensure_temp_download_dir -from http_client import close_http_client, ensure_http_client -from logger import logger as shared_logger -from paths import get_plugin_dir, public_path -from settings.manager import ( - apply_settings_changes, - get_available_locales, - get_settings_payload, - get_translation_map, - init_settings, -) -from steam_utils import detect_steam_install_path, get_game_install_path_response, open_game_folder - -logger = shared_logger - - -def GetPluginDir() -> str: # Legacy API used by the frontend - return get_plugin_dir() - - -class Logger: - @staticmethod - def log(message: str) -> str: - shared_logger.log(f"[Frontend] {message}") - return json.dumps({"success": True}) - - @staticmethod - def warn(message: str) -> str: - shared_logger.warn(f"[Frontend] {message}") - return json.dumps({"success": True}) - - @staticmethod - def error(message: str) -> str: - shared_logger.error(f"[Frontend] {message}") - return json.dumps({"success": True}) - - -def _steam_ui_path() -> str: - return os.path.join(Millennium.steam_path(), "steamui", WEBKIT_DIR_NAME) - - -def _copy_webkit_files() -> None: - plugin_dir = get_plugin_dir() - steam_ui_path = _steam_ui_path() - os.makedirs(steam_ui_path, exist_ok=True) - - js_src = public_path(WEB_UI_JS_FILE) - js_dst = os.path.join(steam_ui_path, WEB_UI_JS_FILE) - logger.log(f"Copying LuaTools web UI from {js_src} to {js_dst}") - try: - shutil.copy(js_src, js_dst) - except Exception as exc: - logger.error(f"Failed to copy LuaTools web UI: {exc}") - - icon_src = public_path(WEB_UI_ICON_FILE) - icon_dst = os.path.join(steam_ui_path, WEB_UI_ICON_FILE) - if os.path.exists(icon_src): - try: - shutil.copy(icon_src, icon_dst) - logger.log(f"Copied LuaTools icon to {icon_dst}") - except Exception as exc: - logger.error(f"Failed to copy LuaTools icon: {exc}") - else: - logger.warn(f"LuaTools icon not found at {icon_src}") - - # Copy theme CSS files - themes_src = os.path.join(plugin_dir, "public", "themes") - themes_dst = os.path.join(steam_ui_path, "themes") - if os.path.exists(themes_src): - try: - os.makedirs(themes_dst, exist_ok=True) - for filename in os.listdir(themes_src): - if filename.endswith(".css"): - theme_src = os.path.join(themes_src, filename) - theme_dst = os.path.join(themes_dst, filename) - shutil.copy(theme_src, theme_dst) - logger.log(f"Copied theme file {filename} to {theme_dst}") - except Exception as exc: - logger.warn(f"Failed to copy theme files: {exc}") - - -def _inject_webkit_files() -> None: - js_path = os.path.join(WEBKIT_DIR_NAME, WEB_UI_JS_FILE) - Millennium.add_browser_js(js_path) - logger.log(f"LuaTools injected web UI: {js_path}") - - -def InitApis(contentScriptQuery: str = "") -> str: - return api_init_apis(contentScriptQuery) - - -def GetInitApisMessage(contentScriptQuery: str = "") -> str: - return api_get_init_message(contentScriptQuery) - - -def FetchFreeApisNow(contentScriptQuery: str = "") -> str: - return api_fetch_free_apis_now(contentScriptQuery) - - -def CheckForUpdatesNow(contentScriptQuery: str = "") -> str: - result = auto_check_for_updates_now() - return json.dumps(result) - - -def RestartSteam(contentScriptQuery: str = "") -> str: - success = auto_restart_steam() - if success: - return json.dumps({"success": True}) - return json.dumps({"success": False, "error": "Failed to restart Steam"}) - - -def HasLuaToolsForApp(appid: int, contentScriptQuery: str = "") -> str: - return has_luatools_for_app(appid) - - -def StartAddViaLuaTools(appid: int, contentScriptQuery: str = "") -> str: - return start_add_via_luatools(appid) - - -def GetAddViaLuaToolsStatus(appid: int, contentScriptQuery: str = "") -> str: - return get_add_status(appid) - - -def GetApiList(contentScriptQuery: str = "") -> str: - return api_get_api_list(contentScriptQuery) - - -def CancelAddViaLuaTools(appid: int, contentScriptQuery: str = "") -> str: - return cancel_add_via_luatools(appid) - - -def GetIconDataUrl(contentScriptQuery: str = "") -> str: - return get_icon_data_url() - - -def GetGamesDatabase(contentScriptQuery: str = "") -> str: - return get_games_database() - - -def ReadLoadedApps(contentScriptQuery: str = "") -> str: - return read_loaded_apps() - - -def DismissLoadedApps(contentScriptQuery: str = "") -> str: - return dismiss_loaded_apps() - - -def DeleteLuaToolsForApp(appid: int, contentScriptQuery: str = "") -> str: - return delete_luatools_for_app(appid) - - -def CheckForFixes(appid: int, contentScriptQuery: str = "") -> str: - return check_for_fixes(appid) - - -def ApplyGameFix(appid: int, downloadUrl: str, installPath: str, fixType: str = "", gameName: str = "", contentScriptQuery: str = "") -> str: - return apply_game_fix(appid, downloadUrl, installPath, fixType, gameName) - - -def GetApplyFixStatus(appid: int, contentScriptQuery: str = "") -> str: - return get_apply_fix_status(appid) - - -def CancelApplyFix(appid: int, contentScriptQuery: str = "") -> str: - return cancel_apply_fix(appid) - - -def UnFixGame(appid: int, installPath: str = "", fixDate: str = "", contentScriptQuery: str = "") -> str: - return unfix_game(appid, installPath, fixDate) - - -def GetUnfixStatus(appid: int, contentScriptQuery: str = "") -> str: - return get_unfix_status(appid) - - -def GetInstalledFixes(contentScriptQuery: str = "") -> str: - return get_installed_fixes() - - -def GetInstalledLuaScripts(contentScriptQuery: str = "") -> str: - return get_installed_lua_scripts() - - -def GetGameInstallPath(appid: int, contentScriptQuery: str = "") -> str: - result = get_game_install_path_response(appid) - return json.dumps(result) - - -def OpenGameFolder(path: str, contentScriptQuery: str = "") -> str: - success = open_game_folder(path) - if success: - return json.dumps({"success": True}) - return json.dumps({"success": False, "error": "Failed to open path"}) - - -def OpenExternalUrl(url: str, contentScriptQuery: str = "") -> str: - try: - value = str(url or "").strip() - if not (value.startswith("http://") or value.startswith("https://")): - return json.dumps({"success": False, "error": "Invalid URL"}) - if sys.platform.startswith("win"): - try: - os.startfile(value) # type: ignore[attr-defined] - except Exception: - webbrowser.open(value) - else: - webbrowser.open(value) - return json.dumps({"success": True}) - except Exception as exc: - logger.warn(f"LuaTools: OpenExternalUrl failed: {exc}") - return json.dumps({"success": False, "error": str(exc)}) - - -def GetSettingsConfig(contentScriptQuery: str = "") -> str: - try: - payload = get_settings_payload() - response = { - "success": True, - "schemaVersion": payload.get("version"), - "schema": payload.get("schema", []), - "values": payload.get("values", {}), - "language": payload.get("language"), - "locales": payload.get("locales", []), - "translations": payload.get("translations", {}), - } - return json.dumps(response) - except Exception as exc: - logger.warn(f"LuaTools: GetSettingsConfig failed: {exc}") - return json.dumps({"success": False, "error": str(exc)}) - - -def GetThemes(contentScriptQuery: str = "") -> str: - """Return the full themes palette list for the frontend.""" - try: - themes_path = os.path.join(get_plugin_dir(), 'public', 'themes', 'themes.json') - if os.path.exists(themes_path): - try: - with open(themes_path, 'r', encoding='utf-8') as fh: - data = json.load(fh) - return json.dumps({"success": True, "themes": data}) - except Exception as exc: - logger.warn(f"LuaTools: Failed to read themes.json: {exc}") - return json.dumps({"success": False, "error": "Failed to read themes.json"}) - else: - return json.dumps({"success": True, "themes": []}) - except Exception as exc: - logger.warn(f"LuaTools: GetThemes failed: {exc}") - return json.dumps({"success": False, "error": str(exc)}) - - -def ApplySettingsChanges( - _contentScriptQuery: str = "", changes: Any = None, **kwargs: Any -) -> str: - try: - if "changes" in kwargs and changes is None: - changes = kwargs["changes"] - if changes is None: - changes = kwargs - - try: - logger.log( - "LuaTools: ApplySettingsChanges raw argument " - f"type={type(changes)} value={changes!r}" - ) - logger.log(f"LuaTools: ApplySettingsChanges kwargs: {kwargs}") - except Exception: - pass - - payload: Any = None - - if isinstance(changes, str) and changes: - try: - payload = json.loads(changes) - except Exception: - logger.warn("LuaTools: Failed to parse changes string payload") - return json.dumps({"success": False, "error": "Invalid JSON payload"}) - else: - # When a full payload dict was sent as JSON, unwrap keys we expect. - if isinstance(payload, dict) and "changes" in payload: - payload = payload.get("changes") - elif isinstance(payload, dict) and "changesJson" in payload and isinstance(payload["changesJson"], str): - try: - payload = json.loads(payload["changesJson"]) - except Exception: - logger.warn("LuaTools: Failed to parse changesJson string inside payload") - return json.dumps({"success": False, "error": "Invalid JSON payload"}) - elif isinstance(changes, dict) and changes: - # When the bridge passes a dict argument directly. - if "changesJson" in changes and isinstance(changes["changesJson"], str): - try: - payload = json.loads(changes["changesJson"]) - except Exception: - logger.warn("LuaTools: Failed to parse changesJson payload from dict") - return json.dumps({"success": False, "error": "Invalid JSON payload"}) - elif "changes" in changes: - payload = changes.get("changes") - else: - payload = changes - else: - # Look for JSON payload inside kwargs. - changes_json = kwargs.get("changesJson") - if isinstance(changes_json, dict): - payload = changes_json - elif isinstance(changes_json, str) and changes_json: - try: - payload = json.loads(changes_json) - except Exception: - logger.warn("LuaTools: Failed to parse changesJson payload") - return json.dumps({"success": False, "error": "Invalid JSON payload"}) - else: - payload = changes - - if payload is None: - payload = {} - elif not isinstance(payload, dict): - logger.warn(f"LuaTools: Parsed payload is not a dict: {payload!r}") - return json.dumps({"success": False, "error": "Invalid payload format"}) - - try: - logger.log(f"LuaTools: ApplySettingsChanges received payload: {payload}") - except Exception: - pass - - result = apply_settings_changes(payload) - try: - logger.log(f"LuaTools: ApplySettingsChanges result: {result}") - except Exception: - pass - response = json.dumps(result) - try: - logger.log(f"LuaTools: ApplySettingsChanges response json: {response}") - except Exception: - pass - return response - except Exception as exc: - logger.warn(f"LuaTools: ApplySettingsChanges failed: {exc}") - return json.dumps({"success": False, "error": str(exc)}) - - -def GetAvailableLocales(contentScriptQuery: str = "") -> str: - try: - locales = get_available_locales() - return json.dumps({"success": True, "locales": locales}) - except Exception as exc: - logger.warn(f"LuaTools: GetAvailableLocales failed: {exc}") - return json.dumps({"success": False, "error": str(exc)}) - - -def GetTranslations(contentScriptQuery: str = "", language: str = "", **kwargs: Any) -> str: - try: - if not language and "language" in kwargs: - language = kwargs["language"] - bundle = get_translation_map(language) - bundle["success"] = True - return json.dumps(bundle) - except Exception as exc: - logger.warn(f"LuaTools: GetTranslations failed: {exc}") - return json.dumps({"success": False, "error": str(exc)}) - - -def GetAvailableThemes(contentScriptQuery: str = "") -> str: - """Return list of available theme CSS files.""" - try: - themes_dir = os.path.join(get_plugin_dir(), "public", "themes") - themes = [] - if os.path.exists(themes_dir): - for filename in os.listdir(themes_dir): - if filename.endswith(".css"): - theme_name = filename[:-4] # Remove .css extension - # Capitalize first letter for display - display_name = theme_name.capitalize() - themes.append({"value": theme_name, "label": display_name}) - # Sort themes, but put 'original' first - themes.sort(key=lambda x: (x["value"] != "original", x["label"])) - return json.dumps({"success": True, "themes": themes}) - except Exception as exc: - logger.warn(f"LuaTools: GetAvailableThemes failed: {exc}") - return json.dumps({"success": False, "error": str(exc), "themes": []}) - - -class Plugin: - def _front_end_loaded(self): - _copy_webkit_files() - - def _load(self): - logger.log(f"bootstrapping LuaTools plugin, millennium {Millennium.version()}") - - try: - detect_steam_install_path() - except Exception as exc: - logger.warn(f"LuaTools: steam path detection failed: {exc}") - - ensure_http_client("InitApis") - ensure_temp_download_dir() - - try: - init_settings() - except Exception as exc: - logger.warn(f"LuaTools: settings initialization failed: {exc}") - - try: - message = apply_pending_update_if_any() - if message: - store_last_message(message) - except Exception as exc: - logger.warn(f"AutoUpdate: apply pending failed: {exc}") - - try: - init_applist() - except Exception as exc: - logger.warn(f"LuaTools: Applist initialization failed: {exc}") - - _copy_webkit_files() - _inject_webkit_files() - - try: - result = InitApis("boot") - logger.log(f"InitApis (boot) return: {result}") - except Exception as exc: - logger.error(f"InitApis (boot) failed: {exc}") - - try: - start_auto_update_background_check() - except Exception as exc: - logger.warn(f"AutoUpdate: start background check failed: {exc}") - - Millennium.ready() - - def _unload(self): - logger.log("unloading") - close_http_client("InitApis") - - -plugin = Plugin() +import json +import os +import shutil +import sys +import webbrowser + +from typing import Any + +import Millennium # type: ignore +import PluginUtils # type: ignore + +from api_manifest import ( + fetch_free_apis_now as api_fetch_free_apis_now, + get_api_list as api_get_api_list, + get_init_apis_message as api_get_init_message, + init_apis as api_init_apis, + store_last_message, +) +from auto_update import ( + apply_pending_update_if_any, + check_for_updates_now as auto_check_for_updates_now, + restart_steam as auto_restart_steam, + start_auto_update_background_check, +) +from config import WEBKIT_DIR_NAME, WEB_UI_ICON_FILE, WEB_UI_JS_FILE +from downloads import ( + cancel_add_via_luatools, + delete_luatools_for_app, + dismiss_loaded_apps, + get_add_status, + get_icon_data_url, + get_installed_lua_scripts, + has_luatools_for_app, + get_games_database, + init_applist, + read_loaded_apps, + start_add_via_luatools, +) +from fixes import ( + apply_game_fix, + cancel_apply_fix, + check_for_fixes, + get_apply_fix_status, + get_installed_fixes, + get_unfix_status, + unfix_game, +) +from utils import ensure_temp_download_dir +from http_client import close_http_client, ensure_http_client +from logger import logger as shared_logger +from paths import get_plugin_dir, public_path +from settings.manager import ( + apply_settings_changes, + get_available_locales, + get_settings_payload, + get_translation_map, + init_settings, +) +from steam_utils import detect_steam_install_path, get_game_install_path_response, open_game_folder + +logger = shared_logger + + +def GetPluginDir() -> str: # Legacy API used by the frontend + return get_plugin_dir() + + +class Logger: + @staticmethod + def log(message: str) -> str: + shared_logger.log(f"[Frontend] {message}") + return json.dumps({"success": True}) + + @staticmethod + def warn(message: str) -> str: + shared_logger.warn(f"[Frontend] {message}") + return json.dumps({"success": True}) + + @staticmethod + def error(message: str) -> str: + shared_logger.error(f"[Frontend] {message}") + return json.dumps({"success": True}) + + +def _steam_ui_path() -> str: + return os.path.join(Millennium.steam_path(), "steamui", WEBKIT_DIR_NAME) + + +def _copy_webkit_files() -> None: + plugin_dir = get_plugin_dir() + steam_ui_path = _steam_ui_path() + os.makedirs(steam_ui_path, exist_ok=True) + + js_src = public_path(WEB_UI_JS_FILE) + js_dst = os.path.join(steam_ui_path, WEB_UI_JS_FILE) + logger.log(f"Copying LuaTools web UI from {js_src} to {js_dst}") + try: + shutil.copy(js_src, js_dst) + except Exception as exc: + logger.error(f"Failed to copy LuaTools web UI: {exc}") + + icon_src = public_path(WEB_UI_ICON_FILE) + icon_dst = os.path.join(steam_ui_path, WEB_UI_ICON_FILE) + if os.path.exists(icon_src): + try: + shutil.copy(icon_src, icon_dst) + logger.log(f"Copied LuaTools icon to {icon_dst}") + except Exception as exc: + logger.error(f"Failed to copy LuaTools icon: {exc}") + else: + logger.warn(f"LuaTools icon not found at {icon_src}") + + # Copy theme CSS files + themes_src = os.path.join(plugin_dir, "public", "themes") + themes_dst = os.path.join(steam_ui_path, "themes") + if os.path.exists(themes_src): + try: + os.makedirs(themes_dst, exist_ok=True) + for filename in os.listdir(themes_src): + if filename.endswith(".css"): + theme_src = os.path.join(themes_src, filename) + theme_dst = os.path.join(themes_dst, filename) + shutil.copy(theme_src, theme_dst) + logger.log(f"Copied theme file {filename} to {theme_dst}") + except Exception as exc: + logger.warn(f"Failed to copy theme files: {exc}") + + +def _inject_webkit_files() -> None: + js_path = os.path.join(WEBKIT_DIR_NAME, WEB_UI_JS_FILE) + Millennium.add_browser_js(js_path) + logger.log(f"LuaTools injected web UI: {js_path}") + + +def InitApis(contentScriptQuery: str = "") -> str: + return api_init_apis(contentScriptQuery) + + +def GetInitApisMessage(contentScriptQuery: str = "") -> str: + return api_get_init_message(contentScriptQuery) + + +def FetchFreeApisNow(contentScriptQuery: str = "") -> str: + return api_fetch_free_apis_now(contentScriptQuery) + + +def CheckForUpdatesNow(contentScriptQuery: str = "") -> str: + result = auto_check_for_updates_now() + return json.dumps(result) + + +def RestartSteam(contentScriptQuery: str = "") -> str: + success = auto_restart_steam() + if success: + return json.dumps({"success": True}) + return json.dumps({"success": False, "error": "Failed to restart Steam"}) + + +def HasLuaToolsForApp(appid: int, contentScriptQuery: str = "") -> str: + return has_luatools_for_app(appid) + + +def StartAddViaLuaTools(appid: int, contentScriptQuery: str = "") -> str: + return start_add_via_luatools(appid) + + +def GetAddViaLuaToolsStatus(appid: int, contentScriptQuery: str = "") -> str: + return get_add_status(appid) + + +def GetApiList(contentScriptQuery: str = "") -> str: + return api_get_api_list(contentScriptQuery) + + +def CancelAddViaLuaTools(appid: int, contentScriptQuery: str = "") -> str: + return cancel_add_via_luatools(appid) + + +def GetIconDataUrl(contentScriptQuery: str = "") -> str: + return get_icon_data_url() + + +def GetGamesDatabase(contentScriptQuery: str = "") -> str: + return get_games_database() + + +def ReadLoadedApps(contentScriptQuery: str = "") -> str: + return read_loaded_apps() + + +def DismissLoadedApps(contentScriptQuery: str = "") -> str: + return dismiss_loaded_apps() + + +def DeleteLuaToolsForApp(appid: int, contentScriptQuery: str = "") -> str: + return delete_luatools_for_app(appid) + + +def CheckForFixes(appid: int, contentScriptQuery: str = "") -> str: + return check_for_fixes(appid) + + +def ApplyGameFix(appid: int, downloadUrl: str, installPath: str, fixType: str = "", gameName: str = "", contentScriptQuery: str = "") -> str: + return apply_game_fix(appid, downloadUrl, installPath, fixType, gameName) + + +def GetApplyFixStatus(appid: int, contentScriptQuery: str = "") -> str: + return get_apply_fix_status(appid) + + +def CancelApplyFix(appid: int, contentScriptQuery: str = "") -> str: + return cancel_apply_fix(appid) + + +def UnFixGame(appid: int, installPath: str = "", fixDate: str = "", contentScriptQuery: str = "") -> str: + return unfix_game(appid, installPath, fixDate) + + +def GetUnfixStatus(appid: int, contentScriptQuery: str = "") -> str: + return get_unfix_status(appid) + + +def GetInstalledFixes(contentScriptQuery: str = "") -> str: + return get_installed_fixes() + + +def GetInstalledLuaScripts(contentScriptQuery: str = "") -> str: + return get_installed_lua_scripts() + + +def GetGameInstallPath(appid: int, contentScriptQuery: str = "") -> str: + result = get_game_install_path_response(appid) + return json.dumps(result) + + +def OpenGameFolder(path: str, contentScriptQuery: str = "") -> str: + success = open_game_folder(path) + if success: + return json.dumps({"success": True}) + return json.dumps({"success": False, "error": "Failed to open path"}) + + +def OpenExternalUrl(url: str, contentScriptQuery: str = "") -> str: + try: + value = str(url or "").strip() + if not (value.startswith("http://") or value.startswith("https://")): + return json.dumps({"success": False, "error": "Invalid URL"}) + if sys.platform.startswith("win"): + try: + os.startfile(value) # type: ignore[attr-defined] + except Exception: + webbrowser.open(value) + else: + webbrowser.open(value) + return json.dumps({"success": True}) + except Exception as exc: + logger.warn(f"LuaTools: OpenExternalUrl failed: {exc}") + return json.dumps({"success": False, "error": str(exc)}) + + +def GetSettingsConfig(contentScriptQuery: str = "") -> str: + try: + payload = get_settings_payload() + response = { + "success": True, + "schemaVersion": payload.get("version"), + "schema": payload.get("schema", []), + "values": payload.get("values", {}), + "language": payload.get("language"), + "locales": payload.get("locales", []), + "translations": payload.get("translations", {}), + } + return json.dumps(response) + except Exception as exc: + logger.warn(f"LuaTools: GetSettingsConfig failed: {exc}") + return json.dumps({"success": False, "error": str(exc)}) + + +def GetThemes(contentScriptQuery: str = "") -> str: + """Return the full themes palette list for the frontend.""" + try: + themes_path = os.path.join(get_plugin_dir(), 'public', 'themes', 'themes.json') + if os.path.exists(themes_path): + try: + with open(themes_path, 'r', encoding='utf-8') as fh: + data = json.load(fh) + return json.dumps({"success": True, "themes": data}) + except Exception as exc: + logger.warn(f"LuaTools: Failed to read themes.json: {exc}") + return json.dumps({"success": False, "error": "Failed to read themes.json"}) + else: + return json.dumps({"success": True, "themes": []}) + except Exception as exc: + logger.warn(f"LuaTools: GetThemes failed: {exc}") + return json.dumps({"success": False, "error": str(exc)}) + + +def ApplySettingsChanges( + _contentScriptQuery: str = "", changes: Any = None, **kwargs: Any +) -> str: + try: + if "changes" in kwargs and changes is None: + changes = kwargs["changes"] + if changes is None: + changes = kwargs + + try: + logger.log( + "LuaTools: ApplySettingsChanges raw argument " + f"type={type(changes)} value={changes!r}" + ) + logger.log(f"LuaTools: ApplySettingsChanges kwargs: {kwargs}") + except Exception: + pass + + payload: Any = None + + if isinstance(changes, str) and changes: + try: + payload = json.loads(changes) + except Exception: + logger.warn("LuaTools: Failed to parse changes string payload") + return json.dumps({"success": False, "error": "Invalid JSON payload"}) + else: + # When a full payload dict was sent as JSON, unwrap keys we expect. + if isinstance(payload, dict) and "changes" in payload: + payload = payload.get("changes") + elif isinstance(payload, dict) and "changesJson" in payload and isinstance(payload["changesJson"], str): + try: + payload = json.loads(payload["changesJson"]) + except Exception: + logger.warn("LuaTools: Failed to parse changesJson string inside payload") + return json.dumps({"success": False, "error": "Invalid JSON payload"}) + elif isinstance(changes, dict) and changes: + # When the bridge passes a dict argument directly. + if "changesJson" in changes and isinstance(changes["changesJson"], str): + try: + payload = json.loads(changes["changesJson"]) + except Exception: + logger.warn("LuaTools: Failed to parse changesJson payload from dict") + return json.dumps({"success": False, "error": "Invalid JSON payload"}) + elif "changes" in changes: + payload = changes.get("changes") + else: + payload = changes + else: + # Look for JSON payload inside kwargs. + changes_json = kwargs.get("changesJson") + if isinstance(changes_json, dict): + payload = changes_json + elif isinstance(changes_json, str) and changes_json: + try: + payload = json.loads(changes_json) + except Exception: + logger.warn("LuaTools: Failed to parse changesJson payload") + return json.dumps({"success": False, "error": "Invalid JSON payload"}) + else: + payload = changes + + if payload is None: + payload = {} + elif not isinstance(payload, dict): + logger.warn(f"LuaTools: Parsed payload is not a dict: {payload!r}") + return json.dumps({"success": False, "error": "Invalid payload format"}) + + try: + logger.log(f"LuaTools: ApplySettingsChanges received payload: {payload}") + except Exception: + pass + + result = apply_settings_changes(payload) + try: + logger.log(f"LuaTools: ApplySettingsChanges result: {result}") + except Exception: + pass + response = json.dumps(result) + try: + logger.log(f"LuaTools: ApplySettingsChanges response json: {response}") + except Exception: + pass + return response + except Exception as exc: + logger.warn(f"LuaTools: ApplySettingsChanges failed: {exc}") + return json.dumps({"success": False, "error": str(exc)}) + + +def GetAvailableLocales(contentScriptQuery: str = "") -> str: + try: + locales = get_available_locales() + return json.dumps({"success": True, "locales": locales}) + except Exception as exc: + logger.warn(f"LuaTools: GetAvailableLocales failed: {exc}") + return json.dumps({"success": False, "error": str(exc)}) + + +def GetTranslations(contentScriptQuery: str = "", language: str = "", **kwargs: Any) -> str: + try: + if not language and "language" in kwargs: + language = kwargs["language"] + bundle = get_translation_map(language) + bundle["success"] = True + return json.dumps(bundle) + except Exception as exc: + logger.warn(f"LuaTools: GetTranslations failed: {exc}") + return json.dumps({"success": False, "error": str(exc)}) + + +def GetAvailableThemes(contentScriptQuery: str = "") -> str: + """Return list of available theme CSS files.""" + try: + themes_dir = os.path.join(get_plugin_dir(), "public", "themes") + themes = [] + if os.path.exists(themes_dir): + for filename in os.listdir(themes_dir): + if filename.endswith(".css"): + theme_name = filename[:-4] # Remove .css extension + # Capitalize first letter for display + display_name = theme_name.capitalize() + themes.append({"value": theme_name, "label": display_name}) + # Sort themes, but put 'original' first + themes.sort(key=lambda x: (x["value"] != "original", x["label"])) + return json.dumps({"success": True, "themes": themes}) + except Exception as exc: + logger.warn(f"LuaTools: GetAvailableThemes failed: {exc}") + return json.dumps({"success": False, "error": str(exc), "themes": []}) + + +class Plugin: + def _front_end_loaded(self): + _copy_webkit_files() + + def _load(self): + logger.log(f"bootstrapping LuaTools plugin, millennium {Millennium.version()}") + + try: + detect_steam_install_path() + except Exception as exc: + logger.warn(f"LuaTools: steam path detection failed: {exc}") + + ensure_http_client("InitApis") + ensure_temp_download_dir() + + try: + init_settings() + except Exception as exc: + logger.warn(f"LuaTools: settings initialization failed: {exc}") + + try: + message = apply_pending_update_if_any() + if message: + store_last_message(message) + except Exception as exc: + logger.warn(f"AutoUpdate: apply pending failed: {exc}") + + try: + init_applist() + except Exception as exc: + logger.warn(f"LuaTools: Applist initialization failed: {exc}") + + _copy_webkit_files() + _inject_webkit_files() + + try: + result = InitApis("boot") + logger.log(f"InitApis (boot) return: {result}") + except Exception as exc: + logger.error(f"InitApis (boot) failed: {exc}") + + try: + start_auto_update_background_check() + except Exception as exc: + logger.warn(f"AutoUpdate: start background check failed: {exc}") + + Millennium.ready() + + def _unload(self): + logger.log("unloading") + close_http_client("InitApis") + + +plugin = Plugin() diff --git a/public/luatools.js b/public/luatools.js index 68c7b1e..baae4f8 100644 --- a/public/luatools.js +++ b/public/luatools.js @@ -5252,35 +5252,42 @@ startPolling(appid); }; + // First check if it's a DLC + fetch('https://store.steampowered.com/api/appdetails?appids=' + appid + '&filters=basic') + .then(function (res) { + return res.json(); + }) + .then(function (data) { + if (data && data[appid] && data[appid].success && data[appid].data) { + const info = data[appid].data; + if (info.type === 'dlc' && info.fullgame && info.fullgame.appid) { + showDlcWarning(appid, info.fullgame.appid, info.fullgame.name); + return; + } + } - // Check if this is a dlc - const isdlc = !!document.querySelector(".game_area_dlc_bubble"); - const parentdiv = document.querySelector('.glance_details a[href*="/app/"]') - - if (isdlc && parentdiv) { - const id = parseInt(parentdiv.href.match(/app\/(\d+)\//)?.[1] ?? "") - const name = parentdiv.innerText ?? "name not found"; - - showDlcWarning(appid, id, name); - } else { - // Not a dlc (or failed) ? Then continue normally - return fetchGamesDatabase().then(function (db) { - try { - const gameData = db?.[String(appid)] ?? null; - if (gameData?.playable === 0) { - // warning modal - showLuaToolsPlayableWarning('This game may not work, support for it wont be given in our discord', function () { + // Not a DLC (or failed to check), proceed with database check + return fetchGamesDatabase().then(function (db) { + try { + const key = String(appid); + const gameData = db && db[key] ? db[key] : null; + if (gameData && gameData.playable === 0) { + // warning modal + showLuaToolsPlayableWarning('This game may not work, support for it wont be given in our discord', function () { + continueWithAdd(); + }, function () { }); + } else { continueWithAdd(); - }, function () { }); - } else { + } + } catch (_) { continueWithAdd(); } - } catch (_) { - continueWithAdd(); - } + }); + }) + .catch(function (err) { + backendLog('LuaTools: DLC check failed: ' + err); + continueWithAdd(); }); - } - } } catch (_) { } } @@ -5437,10 +5444,11 @@ if (st.status === 'downloading') status.textContent = lt('Downloading…'); if (st.status === 'processing') status.textContent = lt('Processing package…'); if (st.status === 'installing') status.textContent = lt('Installing…'); - if (st.status === 'done') status.textContent = lt('Finishing…'); + if (st.status === 'checking content') status.textContent = lt('Checking content…'); + // if (st.status === 'done') status.textContent = lt('Finishing…'); if (st.status === 'failed') status.textContent = lt('Failed'); } - if (st.status === 'downloading' || st.status === 'processing' || st.status === 'installing') { + if (["downloading", "processing", "installing"].includes(st.status)) { // reveal progress UI (if overlay visible) if (wrap && wrap.style.display === 'none') wrap.style.display = 'block'; if (progressInfo && progressInfo.style.display === 'none') { @@ -5477,17 +5485,13 @@ const cancelBtn = overlay ? overlay.querySelector('.luatools-cancel-btn') : null; if (cancelBtn && st.status === 'downloading') cancelBtn.style.display = ''; } - if (st.status === 'done') { + + if (["checking content", "done"].includes(st.status)) { // Update popup if visible if (title) title.textContent = t('common.appName', 'LuaTools'); if (bar) bar.style.width = '100%'; if (percent) percent.textContent = '100%'; - if (status) status.textContent = lt('Game added!'); - // Hide Cancel button and update Hide to Close - const cancelBtn = overlay ? overlay.querySelector('.luatools-cancel-btn') : null; - if (cancelBtn) cancelBtn.style.display = 'none'; - const hideBtn = overlay ? overlay.querySelector('.luatools-hide-btn') : null; - if (hideBtn) hideBtn.innerHTML = '' + lt('Close') + ''; + // hide progress visuals after a short beat if (wrap || progressInfo) { setTimeout(function () { @@ -5495,6 +5499,44 @@ if (progressInfo) progressInfo.style.display = 'none'; }, 300); } + + // Hide Cancel button + const cancelBtn = overlay ? overlay.querySelector('.luatools-cancel-btn') : null; + if (cancelBtn) cancelBtn.style.display = 'none'; + } + + if (st.status === 'done') { + // Update popup if visible + if (status) { + const result = st.contentCheckResult; + + if (!result) return status.innerText = lt('Game added!'); + + // \u00A0 is a white space (unless it's automatically trimmed) + const status_content = [ + lt("Game added!"), + lt("Content details =>"), + `\u00A0\u00A0• ${lt("Workshop: ")}${lt(result.workshop)}`, + ] + + if (result.dlc.missing.length || result.dlc.included.length) { + status_content.push(`\u00A0\u00A0• ${lt("Dlc: ")}`) + + if (result.dlc.included.length > 0) { + status_content.push(`\u00A0\u00A0\u00A0\u00A0◦ ${lt("Included")}: ${result.dlc.included.length}`) + } + if (result.dlc.missing.length > 0) { + status_content.push(`\u00A0\u00A0\u00A0\u00A0◦ ${lt("Missing")}: ${result.dlc.missing.length} (${result.dlc.missing.join(', ')})`) + } + } + + status.style.whiteSpace = "pre-line"; + status.innerText = status_content.join('\n'); + } + + // Update Hide button to Close + const hideBtn = overlay ? overlay.querySelector('.luatools-hide-btn') : null; + if (hideBtn) hideBtn.innerHTML = '' + lt('Close') + ''; done = true; clearInterval(timer); runState.inProgress = false; From 909d2b71e6b8465978f5a6cd8b7432d239efa511 Mon Sep 17 00:00:00 2001 From: Clem <58120735+clemdotla@users.noreply.github.com> Date: Sun, 8 Mar 2026 15:28:18 +0100 Subject: [PATCH 3/3] Added DOM dlc check --- public/luatools.js | 55 +++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/public/luatools.js b/public/luatools.js index baae4f8..c00d791 100644 --- a/public/luatools.js +++ b/public/luatools.js @@ -5252,42 +5252,33 @@ startPolling(appid); }; - // First check if it's a DLC - fetch('https://store.steampowered.com/api/appdetails?appids=' + appid + '&filters=basic') - .then(function (res) { - return res.json(); - }) - .then(function (data) { - if (data && data[appid] && data[appid].success && data[appid].data) { - const info = data[appid].data; - if (info.type === 'dlc' && info.fullgame && info.fullgame.appid) { - showDlcWarning(appid, info.fullgame.appid, info.fullgame.name); - return; - } - } + // Check if this is a dlc + const isdlc = !!document.querySelector(".game_area_dlc_bubble"); + const parentdiv = document.querySelector('.glance_details a[href*="/app/"]') - // Not a DLC (or failed to check), proceed with database check - return fetchGamesDatabase().then(function (db) { - try { - const key = String(appid); - const gameData = db && db[key] ? db[key] : null; - if (gameData && gameData.playable === 0) { - // warning modal - showLuaToolsPlayableWarning('This game may not work, support for it wont be given in our discord', function () { - continueWithAdd(); - }, function () { }); - } else { + if (isdlc && parentdiv) { + const id = parseInt(parentdiv.href.match(/app\/(\d+)\//)?.[1] ?? "") + const name = parentdiv.innerText ?? "name not found"; + + showDlcWarning(appid, id, name); + } else { + // Not a dlc (or failed) ? Then continue normally + return fetchGamesDatabase().then(function (db) { + try { + const gameData = db?.[String(appid)] ?? null; + if (gameData?.playable === 0) { + // warning modal + showLuaToolsPlayableWarning('This game may not work, support for it wont be given in our discord', function () { continueWithAdd(); - } - } catch (_) { + }, function () { }); + } else { continueWithAdd(); } - }); - }) - .catch(function (err) { - backendLog('LuaTools: DLC check failed: ' + err); - continueWithAdd(); + } catch (_) { + continueWithAdd(); + } }); + } } } catch (_) { } } @@ -5817,4 +5808,4 @@ // Note: The gamepad back handler is configured in the gamepad system at the top of this file // It already handles all overlay types automatically using OVERLAY_SELECTOR_STRING -})(); \ No newline at end of file +})();