diff --git a/README.md b/README.md index 91869a2..61405d7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ RGSX is a Python application developed using Pygame for graphics, created for the community by RetroGameSets. It is completely free. -The application supports multiple sources like myrient and 1fichier. These sources can be updated frequently. +The application supports multiple sources like myrient and 1fichier (with optional AllDebrid unlocking fallback). These sources can be updated frequently. --- @@ -21,7 +21,7 @@ RGSX also offers a headless command-line interface to list platforms/games and d - **Game downloads** : Support for ZIP files and handling of unsupported extensions based on EmulationStation's `es_systems.cfg` (and custom `es_systems_*.cfg` on Batocera). RGSX reads allowed extensions per system from these configs and will automatically extract archives when a system doesn't support them. - Downloads require no authentication or account for most sources. - - Systems marked `(1fichier)` in the name will only be accessible if you provide your 1fichier API key (see below). + - Systems marked `(1fichier)` in the name will only be accessible if you provide either your 1Fichier API key or an AllDebrid API key (see below). - **Download history** : View and re-download previous files. - **Multi-select downloads** : Mark multiple games in the game list with the key mapped to Clear History (default X) to enqueue several downloads in one batch. Press Confirm to start batch. - **Control customization** : Remap keyboard or controller keys to your preference with automatic button name detection from EmulationStation (beta). @@ -80,10 +80,14 @@ You will find RGSX in the "PORTS" system or "Homebrew and ports" and in `/roms/p ## 🏁 First startup --- -> ## IMPORTANT -> If you have a 1Fichier API key, you must enter it in -> `/saves/ports/rgsx/1FichierAPI.txt` -> if you want to download from 1Fichier links. +> ## IMPORTANT (1Fichier / AllDebrid) +> To download from 1Fichier links, you can use either your 1Fichier API key or an AllDebrid API key (automatic fallback if 1Fichier is missing). +> +> Where to paste your API key (file should contain the key only): +> - `/saves/ports/rgsx/1FichierAPI.txt` (1Fichier API key) +> - `/saves/ports/rgsx/AllDebridAPI.txt` (AllDebrid API key) +> +> Do NOT create these files manually. Start a 1Fichier download once: RGSX will auto-create empty files if missing. Then open the appropriate file and paste your key. --- - Launch RGSX from ports on batocera, from Windows on Retrobat. @@ -185,7 +189,8 @@ RGSX/ ├── controls.json # Control mapping file (generated after first startup). ├── history.json # Download history database (generated after first download). ├── rom_extensions.json # Generated from es_systems.cfg: per-system allowed ROM extensions cache. -└── 1FichierAPI.txt # 1fichier API key (premium account and + only) (empty by default). +├── 1FichierAPI.txt # 1fichier API key (premium account and + only) (empty by default). +└── AllDebridAPI.txt # AllDebrid API key (optional, fallback for 1Fichier links) (empty by default). ``` diff --git a/README_FR.md b/README_FR.md index a651156..4d684e3 100644 --- a/README_FR.md +++ b/README_FR.md @@ -4,7 +4,7 @@ RGSX est une application dĂ©veloppĂ©e en Python basĂ©e sur Pygame pour la partie graphique pour la communautĂ© par RetroGameSets. Elle est entiĂšrement gratuite. -L'application prend en charge plusieurs sources comme myrient, 1fichier. Ces sources pourront ĂȘtre mises Ă  jour frĂ©quemment. +L'application prend en charge plusieurs sources comme myrient, 1fichier (avec support de dĂ©bridage via AllDebrid en option). Ces sources pourront ĂȘtre mises Ă  jour frĂ©quemment. --- @@ -20,7 +20,7 @@ RGSX propose aussi une interface en ligne de commande (sans interface graphique) - **TĂ©lĂ©chargement de jeux** : Prise en charge des fichiers ZIP et gestion des extensions non supportĂ©es Ă  partir du fichier `es_systems.cfg` d'EmulationStation (et des `es_systems_*.cfg` personnalisĂ©s sur Batocera). RGSX lit les extensions autorisĂ©es par systĂšme depuis ces configurations et extrait automatiquement les archives si le systĂšme ne les supporte pas. - Les tĂ©lĂ©chargements ne nĂ©cessitent aucune authentification ni compte pour la plupart. - - Les systĂšmes notĂ©s `(1fichier)` dans le nom ne seront accessibles que si vous renseignez votre clĂ© API 1fichier (voir plus bas). + - Les systĂšmes notĂ©s `(1fichier)` dans le nom ne seront accessibles que si vous renseignez votre clĂ© API 1Fichier ou une clĂ© API AllDebrid (voir plus bas). - **Historique des tĂ©lĂ©chargements** : Consultez et retĂ©lĂ©chargez les anciens fichiers. - **TĂ©lĂ©chargements multi-sĂ©lection** : Marquez plusieurs jeux dans la liste avec la touche associĂ©e Ă  Vider Historique (par dĂ©faut X) pour prĂ©parer un lot. Appuyez ensuite sur Confirmer pour lancer les tĂ©lĂ©chargements en sĂ©quence. - **Personnalisation des contrĂŽles** : Remappez les touches du clavier ou de la manette Ă  votre convenance avec dĂ©tection automatique des noms de boutons depuis EmulationStation(beta). @@ -78,10 +78,14 @@ Vous trouverez RGSX dans le systĂšme "PORTS" ou "Jeux Amateurs et portages" et d ## 🏁 Premier dĂ©marrage --- -> ## IMPORTANT -> Si vous avez une clĂ© API 1Fichier, vous devez la renseigner dans -> `/saves/ports/rgsx/1FichierAPI.txt` -> si vous souhaitez tĂ©lĂ©charger depuis des liens 1Fichier. +> ## IMPORTANT (1Fichier / AllDebrid) +> Pour tĂ©lĂ©charger depuis des liens 1Fichier, vous pouvez utiliser soit votre clĂ© API 1Fichier, soit votre clĂ© API AllDebrid (fallback automatique si 1Fichier est absent). +> +> OĂč coller votre clĂ© API (le fichier doit contenir uniquement la clĂ©) : +> - `/saves/ports/rgsx/1FichierAPI.txt` (clĂ© API 1Fichier) +> - `/saves/ports/rgsx/AllDebridAPI.txt` (clĂ© API AllDebrid) +> +> Ne crĂ©ez PAS ces fichiers manuellement. Lancez une premiĂšre fois un tĂ©lĂ©chargement 1Fichier: RGSX crĂ©era automatiquement les fichiers vides s’ils sont absents. Ensuite, ouvrez le fichier correspondant et collez votre clĂ©. --- - Lancez RGSX depuis ports sur batocera, depuis Windows sur Retrobat. @@ -209,7 +213,8 @@ RGSX/ ├── controls.json # Fichier de mappage des contrĂŽles (gĂ©nĂ©rĂ© aprĂšs le premier dĂ©marrage). ├── history.json # Base de donnĂ©es de l'historique de tĂ©lĂ©chargements (gĂ©nĂ©rĂ© aprĂšs le premier tĂ©lĂ©chargement). ├── rom_extensions.json # GĂ©nĂ©rĂ© depuis es_systems.cfg : cache des extensions autorisĂ©es par systĂšme. -└── 1FichierAPI.txt # ClĂ© API 1fichier (compte premium et + uniquement) (vide par dĂ©faut). +├── 1FichierAPI.txt # ClĂ© API 1fichier (compte premium et + uniquement) (vide par dĂ©faut). +└── AllDebridAPI.txt # ClĂ© API AllDebrid (optionnelle, fallback pour les liens 1Fichier) (vide par dĂ©faut). ``` --- diff --git a/ports/RGSX/__main__.py b/ports/RGSX/__main__.py index bb9a6a2..df6d4b9 100644 --- a/ports/RGSX/__main__.py +++ b/ports/RGSX/__main__.py @@ -570,18 +570,27 @@ async def main(): config.current_history_item = len(config.history) - 1 # SĂ©lectionner l'entrĂ©e en cours if is_1fichier_url(url): if not config.API_KEY_1FICHIER: + # Fallback AllDebrid + try: + from utils import load_api_key_alldebrid + config.API_KEY_ALLDEBRID = load_api_key_alldebrid() + except Exception: + config.API_KEY_ALLDEBRID = getattr(config, "API_KEY_ALLDEBRID", "") + if not config.API_KEY_1FICHIER and not getattr(config, "API_KEY_ALLDEBRID", ""): config.previous_menu_state = config.menu_state config.menu_state = "error" - config.error_message = ( - f"Attention il faut renseigner sa clĂ© API (premium only) dans le fichier {os.path.join(config.SAVE_FOLDER, '1fichierAPI.txt')}" - ) + try: + both_paths = f"{os.path.join(config.SAVE_FOLDER,'1FichierAPI.txt')} or {os.path.join(config.SAVE_FOLDER,'AllDebridAPI.txt')}" + config.error_message = _("error_api_key").format(both_paths) + except Exception: + config.error_message = "Please enter API key (1fichier or AllDebrid)" # Mettre Ă  jour l'entrĂ©e temporaire avec l'erreur config.history[-1]["status"] = "Erreur" config.history[-1]["progress"] = 0 - config.history[-1]["message"] = "Erreur API : ClĂ© API 1fichier absente" + config.history[-1]["message"] = "API NOT FOUND" save_history(config.history) config.needs_redraw = True - logger.error("ClĂ© API 1fichier absente") + logger.error("ClĂ© API 1fichier et AllDebrid absentes") config.pending_download = None continue pending = check_extension_before_download(url, platform_name, game_name) @@ -670,13 +679,22 @@ async def main(): logger.debug(f"VĂ©rification pour retĂ©lĂ©chargement de {game_name}, URL: {url}") if is_1fichier_url(url): if not config.API_KEY_1FICHIER: + # Fallback AllDebrid + try: + from utils import load_api_key_alldebrid + config.API_KEY_ALLDEBRID = load_api_key_alldebrid() + except Exception: + config.API_KEY_ALLDEBRID = getattr(config, "API_KEY_ALLDEBRID", "") + if not config.API_KEY_1FICHIER and not getattr(config, "API_KEY_ALLDEBRID", ""): config.previous_menu_state = config.menu_state config.menu_state = "error" - config.error_message = ( - f"Attention il faut renseigner sa clĂ© API (premium only) dans le fichier {os.path.join(config.SAVE_FOLDER, '1fichierAPI.txt')}" - ) + try: + both_paths = f"{os.path.join(config.SAVE_FOLDER,'1FichierAPI.txt')} or {os.path.join(config.SAVE_FOLDER,'AllDebridAPI.txt')}" + config.error_message = _("error_api_key").format(both_paths) + except Exception: + config.error_message = "Please enter API key (1fichier or AllDebrid)" config.needs_redraw = True - logger.error("ClĂ© API 1fichier absente") + logger.error("ClĂ© API 1fichier et AllDebrid absentes") config.pending_download = None continue pending = check_extension_before_download(url, platform_name, game_name) diff --git a/ports/RGSX/config.py b/ports/RGSX/config.py index 46b8edd..0214cfe 100644 --- a/ports/RGSX/config.py +++ b/ports/RGSX/config.py @@ -104,7 +104,7 @@ JSON_EXTENSIONS = os.path.join(SAVE_FOLDER, "rom_extensions.json") PRECONF_CONTROLS_PATH = os.path.join(APP_FOLDER, "assets", "controls") CONTROLS_CONFIG_PATH = os.path.join(SAVE_FOLDER, "controls.json") HISTORY_PATH = os.path.join(SAVE_FOLDER, "history.json") -API_KEY_1FICHIER = os.path.join(SAVE_FOLDER, "1fichierAPI.txt") +API_KEY_1FICHIER = os.path.join(SAVE_FOLDER, "1FichierAPI.txt") RGSX_SETTINGS_PATH = os.path.join(SAVE_FOLDER, "rgsx_settings.json") # URL diff --git a/ports/RGSX/controls.py b/ports/RGSX/controls.py index 13fc862..ed30377 100644 --- a/ports/RGSX/controls.py +++ b/ports/RGSX/controls.py @@ -14,7 +14,7 @@ from network import download_rom, download_from_1fichier, is_1fichier_url, reque from utils import ( load_games, check_extension_before_download, is_extension_supported, load_extensions_json, play_random_music, sanitize_filename, - load_api_key_1fichier, save_music_config + load_api_key_1fichier, load_api_key_alldebrid, save_music_config ) from history import load_history, clear_history, add_to_history, save_history import logging @@ -584,8 +584,14 @@ def handle_controls(event, sources, joystick, screen): if is_1fichier_url(url): config.API_KEY_1FICHIER = load_api_key_1fichier() if not config.API_KEY_1FICHIER: + # Fallback AllDebrid + try: + config.API_KEY_ALLDEBRID = load_api_key_alldebrid() + except Exception: + config.API_KEY_ALLDEBRID = getattr(config, "API_KEY_ALLDEBRID", "") + if not config.API_KEY_1FICHIER and not getattr(config, "API_KEY_ALLDEBRID", ""): config.history[-1]["status"] = "Erreur" - config.history[-1]["message"] = "Erreur API : ClĂ© API 1fichier absente" + config.history[-1]["message"] = "API NOT FOUND" save_history(config.history) continue task = asyncio.create_task(download_from_1fichier(url, platform, game_name, config.pending_download[3], task_id)) @@ -620,19 +626,26 @@ def handle_controls(event, sources, joystick, screen): if is_1fichier_url(url): config.API_KEY_1FICHIER = load_api_key_1fichier() if not config.API_KEY_1FICHIER: + # Fallback AllDebrid + try: + config.API_KEY_ALLDEBRID = load_api_key_alldebrid() + except Exception: + config.API_KEY_ALLDEBRID = getattr(config, "API_KEY_ALLDEBRID", "") + if not config.API_KEY_1FICHIER and not getattr(config, "API_KEY_ALLDEBRID", ""): config.previous_menu_state = config.menu_state config.menu_state = "error" try: - config.error_message = _("error_api_key_extended") + both_paths = f"{os.path.join(config.SAVE_FOLDER,'1FichierAPI.txt')} or {os.path.join(config.SAVE_FOLDER,'AllDebridAPI.txt')}" + config.error_message = _("error_api_key").format(both_paths) except Exception as e: - logger.error(f"Erreur lors de la traduction de error_api_key_extended: {str(e)}") - config.error_message = "Missing 1fichier API key" # Message de secours + logger.error(f"Erreur lors de la traduction de error_api_key: {str(e)}") + config.error_message = "Please enter API key (1fichier or AllDebrid)" config.history[-1]["status"] = "Erreur" config.history[-1]["progress"] = 0 - config.history[-1]["message"] = "Erreur API : ClĂ© API 1fichier absente" + config.history[-1]["message"] = "API NOT FOUND" save_history(config.history) config.needs_redraw = True - logger.error("ClĂ© API 1fichier absente, tĂ©lĂ©chargement impossible.") + logger.error("ClĂ© API 1fichier et AllDebrid absentes, tĂ©lĂ©chargement impossible.") config.pending_download = None return action config.pending_download = check_extension_before_download(url, platform, game_name) @@ -734,17 +747,25 @@ def handle_controls(event, sources, joystick, screen): config.current_history_item = len(config.history) - 1 if is_1fichier_url(url): if not config.API_KEY_1FICHIER: + # Fallback AllDebrid + try: + config.API_KEY_ALLDEBRID = load_api_key_alldebrid() + except Exception: + config.API_KEY_ALLDEBRID = getattr(config, "API_KEY_ALLDEBRID", "") + if not config.API_KEY_1FICHIER and not getattr(config, "API_KEY_ALLDEBRID", ""): config.previous_menu_state = config.menu_state config.menu_state = "error" - config.error_message = _( - "error_api_key" - ).format(os.path.join(config.SAVE_FOLDER,"1fichierAPI.txt")) + try: + both_paths = f"{os.path.join(config.SAVE_FOLDER,'1FichierAPI.txt')} or {os.path.join(config.SAVE_FOLDER,'AllDebridAPI.txt')}" + config.error_message = _("error_api_key").format(both_paths) + except Exception: + config.error_message = "Please enter API key (1fichier or AllDebrid)" config.history[-1]["status"] = "Erreur" config.history[-1]["progress"] = 0 - config.history[-1]["message"] = "Erreur API : ClĂ© API 1fichier absente" + config.history[-1]["message"] = "API NOT FOUND" save_history(config.history) config.needs_redraw = True - logger.error("ClĂ© API 1fichier absente, tĂ©lĂ©chargement impossible.") + logger.error("ClĂ© API 1fichier et AllDebrid absentes, tĂ©lĂ©chargement impossible.") config.pending_download = None return action task_id = str(pygame.time.get_ticks()) @@ -800,8 +821,14 @@ def handle_controls(event, sources, joystick, screen): if is_1fichier_url(url): config.API_KEY_1FICHIER = load_api_key_1fichier() if not config.API_KEY_1FICHIER: + # Fallback AllDebrid + try: + config.API_KEY_ALLDEBRID = load_api_key_alldebrid() + except Exception: + config.API_KEY_ALLDEBRID = getattr(config, "API_KEY_ALLDEBRID", "") + if not config.API_KEY_1FICHIER and not getattr(config, "API_KEY_ALLDEBRID", ""): config.history[-1]["status"] = "Erreur" - config.history[-1]["message"] = "Erreur API : ClĂ© API 1fichier absente" + config.history[-1]["message"] = "API NOT FOUND" save_history(config.history) continue task = asyncio.create_task(download_from_1fichier(url, platform, game_name, config.pending_download[3], task_id)) @@ -865,8 +892,14 @@ def handle_controls(event, sources, joystick, screen): if is_1fichier_url(url): config.API_KEY_1FICHIER = load_api_key_1fichier() if not config.API_KEY_1FICHIER: + # Fallback AllDebrid + try: + config.API_KEY_ALLDEBRID = load_api_key_alldebrid() + except Exception: + config.API_KEY_ALLDEBRID = getattr(config, "API_KEY_ALLDEBRID", "") + if not config.API_KEY_1FICHIER and not getattr(config, "API_KEY_ALLDEBRID", ""): config.history[-1]["status"] = "Erreur" - config.history[-1]["message"] = "Erreur API : ClĂ© API 1fichier absente" + config.history[-1]["message"] = "API NOT FOUND" save_history(config.history) continue task = asyncio.create_task(download_from_1fichier(url, platform, game_name, config.pending_download[3], task_id)) @@ -960,17 +993,27 @@ def handle_controls(event, sources, joystick, screen): task_id = str(pygame.time.get_ticks()) if is_1fichier_url(url): if not config.API_KEY_1FICHIER: + # Fallback AllDebrid + try: + config.API_KEY_ALLDEBRID = load_api_key_alldebrid() + except Exception: + config.API_KEY_ALLDEBRID = getattr(config, "API_KEY_ALLDEBRID", "") + if not config.API_KEY_1FICHIER and not getattr(config, "API_KEY_ALLDEBRID", ""): config.previous_menu_state = config.menu_state config.menu_state = "error" - logger.warning("clĂ© api absente dans os.path.join(config.SAVE_FOLDER, '1fichierAPI.txt')\n") - config.error_message = _("error_api_key").format(os.path.join(config.SAVE_FOLDER, "1fichierAPI.txt")) + logger.warning("clĂ© api absente pour 1fichier et AllDebrid") + try: + both_paths = f"{os.path.join(config.SAVE_FOLDER,'1FichierAPI.txt')} or {os.path.join(config.SAVE_FOLDER,'AllDebridAPI.txt')}" + config.error_message = _("error_api_key").format(both_paths) + except Exception: + config.error_message = "Please enter API key (1fichier or AllDebrid)" config.history[-1]["status"] = "Erreur" config.history[-1]["progress"] = 0 - config.history[-1]["message"] = "Erreur API : ClĂ© API 1fichier absente" + config.history[-1]["message"] = "API NOT FOUND" save_history(config.history) config.needs_redraw = True - logger.error("ClĂ© API 1fichier absente, retĂ©lĂ©chargement impossible.") + logger.error("ClĂ© API 1fichier et AllDebrid absentes, retĂ©lĂ©chargement impossible.") config.pending_download = None return action task = asyncio.create_task(download_from_1fichier(url, platform, game_name, is_zip_non_supported, task_id)) diff --git a/ports/RGSX/languages/de.json b/ports/RGSX/languages/de.json index 424802c..73e2a62 100644 --- a/ports/RGSX/languages/de.json +++ b/ports/RGSX/languages/de.json @@ -24,7 +24,7 @@ "error_no_internet": "Keine Internetverbindung. ÜberprĂŒfe dein Netzwerk.", "error_controls_mapping": "Fehler beim Zuordnen der Steuerung", "error_api_key": "Achtung, du musst deinen API-SchlĂŒssel (nur Premium) in der Datei {0} eingeben", - "error_api_key_extended": "Achtung, du musst deinen API-SchlĂŒssel (nur Premium) in der Datei /userdata/saves/ports/rgsx/1fichierAPI.txt einfĂŒgen. Öffne die Datei in einem Texteditor und fĂŒge den API-SchlĂŒssel ein", + "error_api_key_extended": "Achtung, du musst deinen API-SchlĂŒssel (nur Premium) in der Datei /userdata/saves/ports/rgsx/1FichierAPI.txt einfĂŒgen. Öffne die Datei in einem Texteditor und fĂŒge den API-SchlĂŒssel ein", "error_invalid_download_data": "UngĂŒltige Downloaddaten", "error_delete_sources": "Fehler beim Löschen der Datei systems_list.json oder Ordner", "error_extension": "Nicht unterstĂŒtzte Erweiterung oder Downloadfehler", diff --git a/ports/RGSX/languages/en.json b/ports/RGSX/languages/en.json index 4bcaa0b..1d263f0 100644 --- a/ports/RGSX/languages/en.json +++ b/ports/RGSX/languages/en.json @@ -24,7 +24,7 @@ "error_no_internet": "No Internet connection. Check your network.", "error_controls_mapping": "Failed to map controls", "error_api_key": "Please enter your API key (premium only) in the file {0}", - "error_api_key_extended": "Please enter your API key (premium only) in the file /userdata/saves/ports/rgsx/1fichierAPI.txt by opening it in a text editor and pasting your API key", + "error_api_key_extended": "Please enter your API key (premium only) in the file /userdata/saves/ports/rgsx/1FichierAPI.txt by opening it in a text editor and pasting your API key", "error_invalid_download_data": "Invalid download data", "error_delete_sources": "Error deleting systems_list.json file or folders", "error_extension": "Unsupported extension or download error", diff --git a/ports/RGSX/languages/es.json b/ports/RGSX/languages/es.json index 87dcd78..2100648 100644 --- a/ports/RGSX/languages/es.json +++ b/ports/RGSX/languages/es.json @@ -25,7 +25,7 @@ "error_no_internet": "Sin conexiĂłn a Internet. Verifica tu red.", "error_controls_mapping": "Error al mapear los controles", "error_api_key": "AtenciĂłn, debes ingresar tu clave API (solo premium) en el archivo {0}", - "error_api_key_extended": "AtenciĂłn, debes ingresar tu clave API (solo premium) en el archivo /userdata/saves/ports/rgsx/1fichierAPI.txt, abrirlo en un editor de texto y pegar la clave API", + "error_api_key_extended": "AtenciĂłn, debes ingresar tu clave API (solo premium) en el archivo /userdata/saves/ports/rgsx/1FichierAPI.txt, abrirlo en un editor de texto y pegar la clave API", "error_invalid_download_data": "Datos de descarga no vĂĄlidos", "error_delete_sources": "Error al eliminar el archivo systems_list.json o carpetas", "error_extension": "ExtensiĂłn no soportada o error de descarga", diff --git a/ports/RGSX/languages/fr.json b/ports/RGSX/languages/fr.json index 6cfa4e0..8d97c5a 100644 --- a/ports/RGSX/languages/fr.json +++ b/ports/RGSX/languages/fr.json @@ -21,7 +21,7 @@ "error_no_internet": "Pas de connexion Internet. VĂ©rifiez votre rĂ©seau.", "error_controls_mapping": "Échec du mappage des contrĂŽles", "error_api_key": "Attention il faut renseigner sa clĂ© API (premium only) dans le fichier {0}", - "error_api_key_extended": "Attention il faut renseigner sa clĂ© API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt Ă  ouvrir dans un Ă©diteur de texte et coller la clĂ© API", + "error_api_key_extended": "Attention il faut renseigner sa clĂ© API (premium only) dans le fichier /userdata/saves/ports/rgsx/1FichierAPI.txt Ă  ouvrir dans un Ă©diteur de texte et coller la clĂ© API", "error_invalid_download_data": "DonnĂ©es de tĂ©lĂ©chargement invalides", "error_delete_sources": "Erreur lors de la suppression du fichier systems_list.json ou dossiers", "error_extension": "Extension non supportĂ©e ou erreur de tĂ©lĂ©chargement", diff --git a/ports/RGSX/languages/it.json b/ports/RGSX/languages/it.json index 5c8996c..886e850 100644 --- a/ports/RGSX/languages/it.json +++ b/ports/RGSX/languages/it.json @@ -24,7 +24,7 @@ "error_no_internet": "Nessuna connessione Internet. Controlla la rete.", "error_controls_mapping": "Impossibile mappare i controlli", "error_api_key": "Inserisci la tua API key (solo premium) nel file {0}", - "error_api_key_extended": "Inserisci la tua API key (solo premium) nel file /userdata/saves/ports/rgsx/1fichierAPI.txt aprendolo in un editor e incollando la chiave", + "error_api_key_extended": "Inserisci la tua API key (solo premium) nel file /userdata/saves/ports/rgsx/1FichierAPI.txt aprendolo in un editor e incollando la chiave", "error_invalid_download_data": "Dati di download non validi", "error_delete_sources": "Errore nell'eliminazione del file systems_list.json o delle cartelle", "error_extension": "Estensione non supportata o errore di download", diff --git a/ports/RGSX/languages/pt.json b/ports/RGSX/languages/pt.json index 6a29eb8..e7ef277 100644 --- a/ports/RGSX/languages/pt.json +++ b/ports/RGSX/languages/pt.json @@ -24,7 +24,7 @@ "error_no_internet": "Sem conexĂŁo com a Internet. Verifique sua rede.", "error_controls_mapping": "Falha ao mapear controles", "error_api_key": "Insira sua chave API (somente premium) no arquivo {0}", - "error_api_key_extended": "Insira sua chave API (somente premium) no arquivo /userdata/saves/ports/rgsx/1fichierAPI.txt abrindo-o em um editor de texto e colando sua chave", + "error_api_key_extended": "Insira sua chave API (somente premium) no arquivo /userdata/saves/ports/rgsx/1FichierAPI.txt abrindo-o em um editor de texto e colando sua chave", "error_invalid_download_data": "Dados de download invĂĄlidos", "error_delete_sources": "Erro ao deletar arquivo sources.json ou pastas", "error_extension": "ExtensĂŁo nĂŁo suportada ou erro no download", diff --git a/ports/RGSX/network.py b/ports/RGSX/network.py index 6930c8e..0e0a8ae 100644 --- a/ports/RGSX/network.py +++ b/ports/RGSX/network.py @@ -15,7 +15,7 @@ try: except Exception: pygame = None # type: ignore from config import OTA_VERSION_ENDPOINT,APP_FOLDER, UPDATE_FOLDER, OTA_UPDATE_ZIP -from utils import sanitize_filename, extract_zip, extract_rar, load_api_key_1fichier, normalize_platform_name +from utils import sanitize_filename, extract_zip, extract_rar, load_api_key_1fichier, load_api_key_alldebrid, normalize_platform_name from history import save_history import logging import datetime @@ -635,6 +635,10 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False, task_id=None): config.API_KEY_1FICHIER = load_api_key_1fichier() + if not config.API_KEY_1FICHIER: + # Fallback: essayer AllDebrid + config.API_KEY_ALLDEBRID = load_api_key_alldebrid() + logger.debug(f"ClĂ© API 1fichier absente, fallback AllDebrid: {'prĂ©sente' if config.API_KEY_ALLDEBRID else 'absente'}") logger.debug(f"DĂ©but tĂ©lĂ©chargement 1fichier: {game_name} depuis {url}, is_zip_non_supported={is_zip_non_supported}, task_id={task_id}") logger.debug(f"ClĂ© API 1fichier: {'prĂ©sente' if config.API_KEY_1FICHIER else 'absente'}") result = [None, None] @@ -679,50 +683,83 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported= logger.error(f"Pas de permission d'Ă©criture dans {dest_dir}") raise PermissionError(f"Pas de permission d'Ă©criture dans {dest_dir}") - headers = { - "Authorization": f"Bearer {config.API_KEY_1FICHIER}", - "Content-Type": "application/json" - } - payload = { - "url": link, - "pretty": 1 - } - logger.debug(f"PrĂ©paration requĂȘte file/info pour {link}") - response = requests.post("https://api.1fichier.com/v1/file/info.cgi", headers=headers, json=payload, timeout=30) - logger.debug(f"RĂ©ponse file/info reçue, code: {response.status_code}") - response.raise_for_status() - file_info = response.json() - - if "error" in file_info and file_info["error"] == "Resource not found": - logger.error(f"Le fichier {game_name} n'existe pas sur 1fichier") - result[0] = False - result[1] = _("network_file_not_found").format(game_name) - return - - filename = file_info.get("filename", "").strip() - if not filename: - logger.error(f"Impossible de rĂ©cupĂ©rer le nom du fichier") - result[0] = False - result[1] = _("network_cannot_get_filename") - return - - sanitized_filename = sanitize_filename(filename) - dest_path = os.path.join(dest_dir, sanitized_filename) - logger.debug(f"Chemin destination: {dest_path}") - logger.debug(f"Envoi requĂȘte get_token pour {link}") - response = requests.post("https://api.1fichier.com/v1/download/get_token.cgi", headers=headers, json=payload, timeout=30) - logger.debug(f"RĂ©ponse get_token reçue, code: {response.status_code}") - response.raise_for_status() - download_info = response.json() - - final_url = download_info.get("url") - if not final_url: - logger.error(f"Impossible de rĂ©cupĂ©rer l'URL de tĂ©lĂ©chargement") - result[0] = False - result[1] = _("network_cannot_get_download_url") - return - - logger.debug(f"URL de tĂ©lĂ©chargement obtenue: {final_url}") + # Choisir la stratĂ©gie d'accĂšs: 1fichier direct via API, sinon AllDebrid pour dĂ©brider + if config.API_KEY_1FICHIER: + headers = { + "Authorization": f"Bearer {config.API_KEY_1FICHIER}", + "Content-Type": "application/json" + } + payload = { + "url": link, + "pretty": 1 + } + logger.debug(f"PrĂ©paration requĂȘte 1fichier file/info pour {link}") + response = requests.post("https://api.1fichier.com/v1/file/info.cgi", headers=headers, json=payload, timeout=30) + logger.debug(f"RĂ©ponse file/info reçue, code: {response.status_code}") + response.raise_for_status() + file_info = response.json() + if "error" in file_info and file_info["error"] == "Resource not found": + logger.error(f"Le fichier {game_name} n'existe pas sur 1fichier") + result[0] = False + result[1] = _("network_file_not_found").format(game_name) + return + filename = file_info.get("filename", "").strip() + if not filename: + logger.error("Impossible de rĂ©cupĂ©rer le nom du fichier") + result[0] = False + result[1] = _("network_cannot_get_filename") + return + sanitized_filename = sanitize_filename(filename) + dest_path = os.path.join(dest_dir, sanitized_filename) + logger.debug(f"Chemin destination: {dest_path}") + logger.debug(f"Envoi requĂȘte 1fichier get_token pour {link}") + response = requests.post("https://api.1fichier.com/v1/download/get_token.cgi", headers=headers, json=payload, timeout=30) + logger.debug(f"RĂ©ponse get_token reçue, code: {response.status_code}") + response.raise_for_status() + download_info = response.json() + final_url = download_info.get("url") + if not final_url: + logger.error("Impossible de rĂ©cupĂ©rer l'URL de tĂ©lĂ©chargement") + result[0] = False + result[1] = _("network_cannot_get_download_url") + return + logger.debug(f"URL de tĂ©lĂ©chargement obtenue via 1fichier: {final_url}") + else: + # AllDebrid: dĂ©brider l'URL 1fichier vers une URL directe + if not getattr(config, 'API_KEY_ALLDEBRID', ''): + logger.error("Aucune clĂ© API (1fichier/AllDebrid) disponible") + result[0] = False + result[1] = _("network_api_error").format("Missing API key") if _ else "API key missing" + return + ad_key = config.API_KEY_ALLDEBRID + # AllDebrid API v4 example: GET https://api.alldebrid.com/v4/link/unlock?agent=&apikey=&link= + params = { + 'agent': 'RGSX', + 'apikey': ad_key, + 'link': link + } + logger.debug("RequĂȘte AllDebrid link/unlock en cours") + response = requests.get("https://api.alldebrid.com/v4/link/unlock", params=params, timeout=30) + logger.debug(f"RĂ©ponse AllDebrid reçue, code: {response.status_code}") + response.raise_for_status() + ad_json = response.json() + if ad_json.get('status') != 'success': + err = ad_json.get('error', {}).get('code') or ad_json + logger.error(f"AllDebrid Ă©chec dĂ©bridage: {err}") + result[0] = False + result[1] = _("network_api_error").format(f"AllDebrid unlock failed: {err}") if _ else f"AllDebrid unlock failed: {err}" + return + data = ad_json.get('data', {}) + filename = data.get('filename') or game_name + final_url = data.get('link') or data.get('download') or data.get('streamingLink') + if not final_url: + logger.error("AllDebrid n'a pas renvoyĂ© de lien direct") + result[0] = False + result[1] = _("network_cannot_get_download_url") + return + sanitized_filename = sanitize_filename(filename) + dest_path = os.path.join(dest_dir, sanitized_filename) + logger.debug(f"URL directe obtenue via AllDebrid: {final_url}") lock = threading.Lock() retries = 10 retry_delay = 10 @@ -754,6 +791,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported= downloaded = 0 chunk_size = 8192 last_update_time = time.time() + last_downloaded = 0 update_interval = 0.1 # Mettre Ă  jour toutes les 0,1 secondes logger.debug(f"Ouverture fichier: {dest_path}") with open(dest_path, 'wb') as f: @@ -789,8 +827,12 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported= entry["total_size"] = total_size config.needs_redraw = True break - progress_queues[task_id].put((task_id, downloaded, total_size)) + # Calcul de la vitesse en Mo/s + delta = downloaded - last_downloaded + speed = (delta / (current_time - last_update_time) / (1024 * 1024)) if (current_time - last_update_time) > 0 else 0.0 + last_downloaded = downloaded last_update_time = current_time + progress_queues[task_id].put((task_id, downloaded, total_size, speed)) if is_zip_non_supported: with lock: @@ -893,7 +935,11 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported= logger.debug(f"Mise Ă  jour finale historique: status={entry['status']}, progress={entry['progress']}%, message={message}, task_id={task_id}") break else: - downloaded, total_size = data[1], data[2] + if len(data) >= 4: + downloaded, total_size, speed = data[1], data[2], data[3] + else: + downloaded, total_size = data[1], data[2] + speed = 0.0 progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0 progress_percent = max(0, min(100, progress_percent)) @@ -904,6 +950,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported= entry["status"] = "TĂ©lĂ©chargement" entry["downloaded_size"] = downloaded entry["total_size"] = total_size + entry["speed"] = speed # Ajout de la vitesse config.needs_redraw = True break await asyncio.sleep(0.1) diff --git a/ports/RGSX/utils.py b/ports/RGSX/utils.py index b8a3fdd..559aee2 100644 --- a/ports/RGSX/utils.py +++ b/ports/RGSX/utils.py @@ -1284,6 +1284,33 @@ def load_api_key_1fichier(): logger.error(f"Erreur lors de la lecture de la clĂ© API : {e}") return "" +def load_api_key_alldebrid(): + """Charge la clĂ© API AllDebrid depuis le dossier de sauvegarde, crĂ©e le fichier si absent.""" + try: + api_file = os.path.join(config.SAVE_FOLDER, "AllDebridAPI.txt") + logger.debug(f"Chemin du fichier de clĂ© API AllDebrid: {api_file}") + if not os.path.exists(api_file): + logger.info("Fichier de clĂ© API AllDebrid non trouvĂ©") + os.makedirs(config.SAVE_FOLDER, exist_ok=True) + with open(api_file, "w", encoding="utf-8") as f: + f.write("") + logger.info(f"Fichier de clĂ© API AllDebrid créé : {api_file}") + return "" + with open(api_file, "r", encoding="utf-8") as f: + api_key = f.read().strip() + logger.debug(f"ClĂ© API AllDebrid lue: '{api_key}' (longueur: {len(api_key)})") + if not api_key: + logger.warning("ClĂ© API AllDebrid vide, renseignez-la dans AllDebridAPI.txt pour activer le dĂ©bridage.") + # Stocke dans la config pour usage global + try: + config.API_KEY_ALLDEBRID = api_key + except Exception: + pass + return api_key + except Exception as e: + logger.error(f"Erreur lors du chargement de la clĂ© API AllDebrid: {e}") + return "" + def load_music_config(): """Charge la configuration musique depuis rgsx_settings.json.""" try: