mirror of
https://github.com/RetroGameSets/RGSX.git
synced 2026-03-23 10:15:20 +01:00
v2.2.4.2
- add 1fichier free mode handling (test) with wait time, like you download on 1fichier website. Don't need an api key anymore if you don"t have a subscription. If you have any api key it will use in priority
This commit is contained in:
@@ -697,22 +697,30 @@ async def main():
|
||||
except Exception as e:
|
||||
logger.error(f"Impossible de charger les clés via helpers: {e}")
|
||||
keys_info = {'1fichier': getattr(config,'API_KEY_1FICHIER',''), 'alldebrid': getattr(config,'API_KEY_ALLDEBRID',''), 'realdebrid': getattr(config,'API_KEY_REALDEBRID','')}
|
||||
|
||||
# SUPPRIMÉ: Vérification clés API obligatoires
|
||||
# Maintenant on a le mode gratuit en fallback automatique
|
||||
# if missing_all_provider_keys():
|
||||
# config.previous_menu_state = config.menu_state
|
||||
# config.menu_state = "error"
|
||||
# try:
|
||||
# config.error_message = _("error_api_key").format(build_provider_paths_string())
|
||||
# except Exception:
|
||||
# config.error_message = "Please enter API key (1fichier or AllDebrid or RealDebrid)"
|
||||
# # Mise à jour historique
|
||||
# config.history[-1]["status"] = "Erreur"
|
||||
# config.history[-1]["progress"] = 0
|
||||
# config.history[-1]["message"] = "API NOT FOUND"
|
||||
# save_history(config.history)
|
||||
# config.needs_redraw = True
|
||||
# logger.error("Aucune clé fournisseur (1fichier/AllDebrid/RealDebrid) disponible")
|
||||
# config.pending_download = None
|
||||
# continue
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
if missing_all_provider_keys():
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "error"
|
||||
try:
|
||||
config.error_message = _("error_api_key").format(build_provider_paths_string())
|
||||
except Exception:
|
||||
config.error_message = "Please enter API key (1fichier or AllDebrid or RealDebrid)"
|
||||
# Mise à jour historique
|
||||
config.history[-1]["status"] = "Erreur"
|
||||
config.history[-1]["progress"] = 0
|
||||
config.history[-1]["message"] = "API NOT FOUND"
|
||||
save_history(config.history)
|
||||
config.needs_redraw = True
|
||||
logger.error("Aucune clé fournisseur (1fichier/AllDebrid/RealDebrid) disponible")
|
||||
config.pending_download = None
|
||||
continue
|
||||
logger.warning("Aucune clé API - Mode gratuit 1fichier sera utilisé (attente requise)")
|
||||
|
||||
pending = check_extension_before_download(url, platform_name, game_name)
|
||||
if not pending:
|
||||
config.menu_state = "error"
|
||||
@@ -957,6 +965,9 @@ async def main():
|
||||
draw_history_extract_archive(screen)
|
||||
elif config.menu_state == "confirm_clear_history":
|
||||
draw_clear_history_dialog(screen)
|
||||
elif config.menu_state == "support_dialog":
|
||||
from display import draw_support_dialog
|
||||
draw_support_dialog(screen)
|
||||
elif config.menu_state == "confirm_cancel_download":
|
||||
draw_cancel_download_dialog(screen)
|
||||
elif config.menu_state == "reload_games_data":
|
||||
|
||||
@@ -13,7 +13,7 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "2.2.4.1"
|
||||
app_version = "2.2.4.2"
|
||||
|
||||
|
||||
def get_application_root():
|
||||
|
||||
@@ -660,22 +660,30 @@ def handle_controls(event, sources, joystick, screen):
|
||||
if is_1fichier_url(url):
|
||||
from utils import ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
# SUPPRIMÉ: Vérification clés API obligatoires
|
||||
# Maintenant on a le mode gratuit en fallback automatique
|
||||
# if missing_all_provider_keys():
|
||||
# config.previous_menu_state = config.menu_state
|
||||
# config.menu_state = "error"
|
||||
# try:
|
||||
# config.error_message = _("error_api_key").format(build_provider_paths_string())
|
||||
# except Exception as e:
|
||||
# logger.error(f"Erreur lors de la traduction de error_api_key: {str(e)}")
|
||||
# config.error_message = "Please enter API key (1fichier or AllDebrid or RealDebrid)"
|
||||
# config.history[-1]["status"] = "Erreur"
|
||||
# config.history[-1]["progress"] = 0
|
||||
# config.history[-1]["message"] = "API NOT FOUND"
|
||||
# save_history(config.history)
|
||||
# config.needs_redraw = True
|
||||
# logger.error("Clés API manquantes pour tous les fournisseurs (1fichier/AllDebrid/RealDebrid).")
|
||||
# config.pending_download = None
|
||||
# return action
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
if missing_all_provider_keys():
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "error"
|
||||
try:
|
||||
config.error_message = _("error_api_key").format(build_provider_paths_string())
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la traduction de error_api_key: {str(e)}")
|
||||
config.error_message = "Please enter API key (1fichier or AllDebrid or RealDebrid)"
|
||||
config.history[-1]["status"] = "Erreur"
|
||||
config.history[-1]["progress"] = 0
|
||||
config.history[-1]["message"] = "API NOT FOUND"
|
||||
save_history(config.history)
|
||||
config.needs_redraw = True
|
||||
logger.error("Clés API manquantes pour tous les fournisseurs (1fichier/AllDebrid/RealDebrid).")
|
||||
config.pending_download = None
|
||||
return action
|
||||
logger.warning("Aucune clé API - Mode gratuit 1fichier sera utilisé (attente requise)")
|
||||
|
||||
config.pending_download = check_extension_before_download(url, platform, game_name)
|
||||
if config.pending_download:
|
||||
is_supported = is_extension_supported(
|
||||
@@ -778,21 +786,29 @@ def handle_controls(event, sources, joystick, screen):
|
||||
if is_1fichier_url(url):
|
||||
from utils import ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
# SUPPRIMÉ: Vérification clés API obligatoires
|
||||
# Maintenant on a le mode gratuit en fallback automatique
|
||||
# if missing_all_provider_keys():
|
||||
# config.previous_menu_state = config.menu_state
|
||||
# config.menu_state = "error"
|
||||
# try:
|
||||
# config.error_message = _("error_api_key").format(build_provider_paths_string())
|
||||
# except Exception:
|
||||
# config.error_message = "Please enter API key (1fichier or AllDebrid or RealDebrid)"
|
||||
# config.history[-1]["status"] = "Erreur"
|
||||
# config.history[-1]["progress"] = 0
|
||||
# config.history[-1]["message"] = "API NOT FOUND"
|
||||
# save_history(config.history)
|
||||
# config.needs_redraw = True
|
||||
# logger.error("Clés API manquantes pour tous les fournisseurs (1fichier/AllDebrid/RealDebrid).")
|
||||
# config.pending_download = None
|
||||
# return action
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
if missing_all_provider_keys():
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "error"
|
||||
try:
|
||||
config.error_message = _("error_api_key").format(build_provider_paths_string())
|
||||
except Exception:
|
||||
config.error_message = "Please enter API key (1fichier or AllDebrid or RealDebrid)"
|
||||
config.history[-1]["status"] = "Erreur"
|
||||
config.history[-1]["progress"] = 0
|
||||
config.history[-1]["message"] = "API NOT FOUND"
|
||||
save_history(config.history)
|
||||
config.needs_redraw = True
|
||||
logger.error("Clés API manquantes pour tous les fournisseurs (1fichier/AllDebrid/RealDebrid).")
|
||||
config.pending_download = None
|
||||
return action
|
||||
logger.warning("Aucune clé API - Mode gratuit 1fichier sera utilisé (attente requise)")
|
||||
|
||||
task_id = str(pygame.time.get_ticks())
|
||||
task = asyncio.create_task(download_from_1fichier(url, platform, game_name, is_zip_non_supported, task_id))
|
||||
else:
|
||||
@@ -1071,6 +1087,19 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
logger.debug("Annulation du vidage de l'historique, retour à history")
|
||||
|
||||
# Dialogue fichier de support
|
||||
elif config.menu_state == "support_dialog":
|
||||
if is_input_matched(event, "confirm") or is_input_matched(event, "cancel"):
|
||||
# Retour au menu pause
|
||||
config.menu_state = "pause_menu"
|
||||
config.needs_redraw = True
|
||||
# Nettoyage des variables temporaires
|
||||
if hasattr(config, 'support_zip_path'):
|
||||
delattr(config, 'support_zip_path')
|
||||
if hasattr(config, 'support_zip_error'):
|
||||
delattr(config, 'support_zip_error')
|
||||
logger.debug("Retour au menu pause depuis support_dialog")
|
||||
|
||||
# Menu options du jeu dans l'historique
|
||||
elif config.menu_state == "history_game_options":
|
||||
if not config.history or config.current_history_item >= len(config.history):
|
||||
@@ -1438,7 +1467,19 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif config.selected_option == 5: # Restart
|
||||
from utils import restart_application
|
||||
restart_application(2000)
|
||||
elif config.selected_option == 6: # Quit
|
||||
elif config.selected_option == 6: # Support
|
||||
from utils import generate_support_zip
|
||||
success, message, zip_path = generate_support_zip()
|
||||
if success:
|
||||
config.support_zip_path = zip_path
|
||||
config.support_zip_error = None
|
||||
else:
|
||||
config.support_zip_path = None
|
||||
config.support_zip_error = message
|
||||
config.menu_state = "support_dialog"
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 7: # Quit
|
||||
# Capturer l'origine pause_menu pour retour si annulation
|
||||
config.confirm_exit_origin = "pause_menu"
|
||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||
|
||||
@@ -1069,13 +1069,22 @@ def draw_history_list(screen):
|
||||
|
||||
# Precompute provider prefix once
|
||||
provider_prefix = entry.get("provider_prefix") or (entry.get("provider") + ":" if entry.get("provider") else "")
|
||||
|
||||
# Compute status text (optimized version without redundant prefix for errors)
|
||||
if status in ["Téléchargement", "downloading"]:
|
||||
status_text = _("history_status_downloading").format(progress)
|
||||
# Coerce to string and prefix provider when relevant
|
||||
status_text = str(status_text or "")
|
||||
if provider_prefix and not status_text.startswith(provider_prefix):
|
||||
status_text = f"{provider_prefix} {status_text}"
|
||||
# Vérifier si un message personnalisé existe (ex: mode gratuit avec attente)
|
||||
custom_message = entry.get('message', '')
|
||||
# Détecter les messages du mode gratuit (commencent par '[' dans toutes les langues)
|
||||
if custom_message and custom_message.strip().startswith('['):
|
||||
# Utiliser le message personnalisé pour le mode gratuit
|
||||
status_text = custom_message
|
||||
else:
|
||||
# Comportement normal: afficher le pourcentage
|
||||
status_text = _("history_status_downloading").format(progress)
|
||||
# Coerce to string and prefix provider when relevant
|
||||
status_text = str(status_text or "")
|
||||
if provider_prefix and not status_text.startswith(provider_prefix):
|
||||
status_text = f"{provider_prefix} {status_text}"
|
||||
elif status == "Extracting":
|
||||
status_text = _("history_status_extracting").format(progress)
|
||||
status_text = str(status_text or "")
|
||||
@@ -1653,7 +1662,7 @@ def draw_display_menu(screen):
|
||||
def draw_pause_menu(screen, selected_option):
|
||||
"""Dessine le menu pause racine (catégories)."""
|
||||
screen.blit(OVERLAY, (0, 0))
|
||||
# Nouvel ordre: Language / Controls / Display / Games / Settings / Restart / Quit
|
||||
# Nouvel ordre: Language / Controls / Display / Games / Settings / Restart / Support / Quit
|
||||
options = [
|
||||
_("menu_language") if _ else "Language", # 0 -> sélecteur de langue direct
|
||||
_("menu_controls"), # 1 -> sous-menu controls
|
||||
@@ -1661,7 +1670,8 @@ def draw_pause_menu(screen, selected_option):
|
||||
_("menu_games") if _ else "Games", # 3 -> sous-menu games (history + sources + update)
|
||||
_("menu_settings_category") if _ else "Settings", # 4 -> sous-menu settings
|
||||
_("menu_restart"), # 5 -> reboot
|
||||
_("menu_quit") # 6 -> quit
|
||||
_("menu_support"), # 6 -> support
|
||||
_("menu_quit") # 7 -> quit
|
||||
]
|
||||
menu_width = int(config.screen_width * 0.6)
|
||||
button_height = int(config.screen_height * 0.048)
|
||||
@@ -1692,6 +1702,7 @@ def draw_pause_menu(screen, selected_option):
|
||||
"instruction_pause_games",
|
||||
"instruction_pause_settings",
|
||||
"instruction_pause_restart",
|
||||
"instruction_pause_support",
|
||||
"instruction_pause_quit",
|
||||
]
|
||||
try:
|
||||
@@ -2415,6 +2426,75 @@ def draw_reload_games_data_dialog(screen):
|
||||
draw_stylized_button(screen, _("button_yes"), yes_x, buttons_y, button_width, button_height, selected=config.redownload_confirm_selection == 1)
|
||||
draw_stylized_button(screen, _("button_no"), no_x, buttons_y, button_width, button_height, selected=config.redownload_confirm_selection == 0)
|
||||
|
||||
|
||||
def draw_support_dialog(screen):
|
||||
"""Affiche la boîte de dialogue du fichier de support généré."""
|
||||
global OVERLAY
|
||||
if OVERLAY is None or OVERLAY.get_size() != (config.screen_width, config.screen_height):
|
||||
OVERLAY = pygame.Surface((config.screen_width, config.screen_height), pygame.SRCALPHA)
|
||||
OVERLAY.fill((0, 0, 0, 150))
|
||||
logger.debug("OVERLAY recréé dans draw_support_dialog")
|
||||
|
||||
screen.blit(OVERLAY, (0, 0))
|
||||
|
||||
# Récupérer le nom du bouton "cancel/back" depuis la configuration des contrôles
|
||||
cancel_key = "SELECT"
|
||||
try:
|
||||
from controls_mapper import get_mapped_button
|
||||
cancel_key = get_mapped_button("cancel") or "SELECT"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Déterminer le message à afficher (succès ou erreur)
|
||||
if hasattr(config, 'support_zip_error') and config.support_zip_error:
|
||||
title = _("support_dialog_title")
|
||||
message = _("support_dialog_error").format(config.support_zip_error, cancel_key)
|
||||
else:
|
||||
title = _("support_dialog_title")
|
||||
zip_path = getattr(config, 'support_zip_path', 'rgsx_support.zip')
|
||||
message = _("support_dialog_message").format(zip_path, cancel_key)
|
||||
|
||||
# Diviser le message par les retours à la ligne puis wrapper chaque segment
|
||||
raw_segments = message.split('\n') if message else []
|
||||
wrapped_message = []
|
||||
for seg in raw_segments:
|
||||
if seg.strip() == "":
|
||||
wrapped_message.append("") # Ligne vide pour espacement
|
||||
else:
|
||||
wrapped_message.extend(wrap_text(seg, config.small_font, config.screen_width - 100))
|
||||
|
||||
line_height = config.small_font.get_height() + 5
|
||||
text_height = len(wrapped_message) * line_height
|
||||
|
||||
# Calculer la hauteur du titre
|
||||
title_height = config.font.get_height() + 10
|
||||
|
||||
# Calculer les dimensions de la boîte
|
||||
margin_top_bottom = 20
|
||||
rect_height = title_height + text_height + 2 * margin_top_bottom
|
||||
max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message if line], default=300)
|
||||
title_width = config.font.size(title)[0]
|
||||
rect_width = max(max_text_width, title_width) + 100
|
||||
rect_x = (config.screen_width - rect_width) // 2
|
||||
rect_y = (config.screen_height - rect_height) // 2
|
||||
|
||||
# Dessiner la boîte
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||||
|
||||
# Afficher le titre
|
||||
title_surf = config.font.render(title, True, THEME_COLORS["text"])
|
||||
title_rect = title_surf.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + title_height // 2))
|
||||
screen.blit(title_surf, title_rect)
|
||||
|
||||
# Afficher le message
|
||||
for i, line in enumerate(wrapped_message):
|
||||
if line: # Ne pas rendre les lignes vides
|
||||
text = config.small_font.render(line, True, THEME_COLORS["text"])
|
||||
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + title_height + i * line_height + line_height // 2))
|
||||
screen.blit(text, text_rect)
|
||||
|
||||
|
||||
# Popup avec compte à rebours
|
||||
def draw_popup(screen):
|
||||
"""Dessine un popup avec un message (adapté en largeur) et un compte à rebours."""
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
"history_status_completed": "Abgeschlossen",
|
||||
"history_status_error": "Fehler: {0}",
|
||||
"history_status_canceled": "Abgebrochen",
|
||||
"free_mode_waiting": "[Kostenloser Modus] Warten: {0}/{1}s",
|
||||
"free_mode_download": "[Kostenloser Modus] Download: {0}",
|
||||
"free_mode_submitting": "[Kostenloser Modus] Formular wird gesendet...",
|
||||
"free_mode_link_found": "[Kostenloser Modus] Link gefunden: {0}...",
|
||||
"free_mode_completed": "[Kostenloser Modus] Abgeschlossen: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download vom Benutzer abgebrochen.",
|
||||
"extension_warning_zip": "Die Datei '{0}' ist ein Archiv und Batocera unterstützt keine Archive für dieses System. Die automatische Extraktion der Datei erfolgt nach dem Download, fortfahren?",
|
||||
@@ -63,6 +68,7 @@
|
||||
"menu_music_enabled": "Musik aktiviert: {0}",
|
||||
"menu_music_disabled": "Musik deaktiviert",
|
||||
"menu_restart": "Neustart",
|
||||
"menu_support": "Unterstützung",
|
||||
"menu_filter_platforms": "Systeme filtern",
|
||||
"filter_platforms_title": "Systemsichtbarkeit",
|
||||
"filter_platforms_info": "Sichtbar: {0} | Versteckt: {1} / Gesamt: {2}",
|
||||
@@ -74,6 +80,9 @@
|
||||
"menu_allow_unknown_ext_enabled": "Ausblenden der Warnung bei unbekannter Erweiterung aktiviert",
|
||||
"menu_allow_unknown_ext_disabled": "Ausblenden der Warnung bei unbekannter Erweiterung deaktiviert",
|
||||
"menu_quit": "Beenden",
|
||||
"support_dialog_title": "Support-Datei",
|
||||
"support_dialog_message": "Eine Support-Datei wurde mit allen Ihren Konfigurations- und Protokolldateien erstellt.\n\nDatei: {0}\n\nUm Hilfe zu erhalten:\n1. Treten Sie dem RGSX Discord-Server bei\n2. Beschreiben Sie Ihr Problem\n3. Teilen Sie diese ZIP-Datei\n\nDrücken Sie {1}, um zum Menü zurückzukehren.",
|
||||
"support_dialog_error": "Fehler beim Erstellen der Support-Datei:\n{0}\n\nDrücken Sie {1}, um zum Menü zurückzukehren.",
|
||||
"button_yes": "Ja",
|
||||
"button_no": "Nein",
|
||||
"button_OK": "OK",
|
||||
@@ -171,6 +180,7 @@
|
||||
,"instruction_pause_games": "Verlauf öffnen, Quelle wechseln oder Liste aktualisieren"
|
||||
,"instruction_pause_settings": "Musik, Symlink-Option & API-Schlüsselstatus"
|
||||
,"instruction_pause_restart": "RGSX neu starten um Konfiguration neu zu laden"
|
||||
,"instruction_pause_support": "Eine Diagnose-ZIP-Datei für den Support erstellen"
|
||||
,"instruction_pause_quit": "RGSX Anwendung beenden"
|
||||
,"instruction_controls_help": "Komplette Referenz für Controller & Tastatur anzeigen"
|
||||
,"instruction_controls_remap": "Tasten / Buttons neu zuordnen"
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
"history_status_completed": "Completed",
|
||||
"history_status_error": "Error: {0}",
|
||||
"history_status_canceled": "Canceled",
|
||||
"free_mode_waiting": "[Free mode] Waiting: {0}/{1}s",
|
||||
"free_mode_download": "[Free mode] Downloading: {0}",
|
||||
"free_mode_submitting": "[Free mode] Submitting form...",
|
||||
"free_mode_link_found": "[Free mode] Link found: {0}...",
|
||||
"free_mode_completed": "[Free mode] Completed: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download canceled by user.",
|
||||
"extension_warning_zip": "The file '{0}' is an archive and Batocera does not support archives for this system. Automatic extraction will occur after download, continue?",
|
||||
@@ -73,16 +78,20 @@
|
||||
"menu_allow_unknown_ext_off": "Hide unknown extension warning: No",
|
||||
"menu_allow_unknown_ext_enabled": "Hide unknown extension warning enabled",
|
||||
"menu_allow_unknown_ext_disabled": "Hide unknown extension warning disabled",
|
||||
"menu_support": "Support",
|
||||
"menu_quit": "Quit",
|
||||
"button_yes": "Yes",
|
||||
"button_no": "No",
|
||||
"button_OK": "OK",
|
||||
"popup_restarting": "Restarting...",
|
||||
"controls_action_clear_history": "Multi-select / Clear History",
|
||||
"controls_action_history": "History",
|
||||
"controls_action_delete": "Delete",
|
||||
"controls_action_space": "Space",
|
||||
"controls_action_start": "Help / Settings",
|
||||
"support_dialog_title": "Support File",
|
||||
"support_dialog_message": "A support file has been created with all your configuration and log files.\n\nFile: {0}\n\nTo get help:\n1. Join the RGSX Discord server\n2. Describe your issue\n3. Share this ZIP file\n\nPress {1} to return to the menu.",
|
||||
"support_dialog_error": "Error generating support file:\n{0}\n\nPress {1} to return to the menu.",
|
||||
"controls_action_history": "History",
|
||||
"network_checking_updates": "Checking for updates...",
|
||||
"network_update_available": "Update available: {0}",
|
||||
"network_extracting_update": "Extracting update...",
|
||||
@@ -171,6 +180,7 @@
|
||||
"instruction_pause_games": "Open history, switch source or refresh list",
|
||||
"instruction_pause_settings": "Music, symlink option & API keys status",
|
||||
"instruction_pause_restart": "Restart RGSX to reload configuration"
|
||||
,"instruction_pause_support": "Generate a diagnostic ZIP file for support"
|
||||
,"instruction_pause_quit": "Exit the RGSX application"
|
||||
,"instruction_controls_help": "Show full controller & keyboard reference"
|
||||
,"instruction_controls_remap": "Change button / key bindings"
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
"history_status_completed": "Completado",
|
||||
"history_status_error": "Error: {0}",
|
||||
"history_status_canceled": "Cancelado",
|
||||
"free_mode_waiting": "[Modo gratuito] Esperando: {0}/{1}s",
|
||||
"free_mode_download": "[Modo gratuito] Descargando: {0}",
|
||||
"free_mode_submitting": "[Modo gratuito] Enviando formulario...",
|
||||
"free_mode_link_found": "[Modo gratuito] Enlace encontrado: {0}...",
|
||||
"free_mode_completed": "[Modo gratuito] Completado: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Descarga cancelada por el usuario.",
|
||||
"extension_warning_zip": "El archivo '{0}' es un archivo comprimido y Batocera no soporta archivos comprimidos para este sistema. La extracción automática del archivo se realizará después de la descarga, ¿continuar?",
|
||||
@@ -63,6 +68,7 @@
|
||||
"menu_music_enabled": "Música activada: {0}",
|
||||
"menu_music_disabled": "Música desactivada",
|
||||
"menu_restart": "Reiniciar",
|
||||
"menu_support": "Soporte",
|
||||
"menu_filter_platforms": "Filtrar sistemas",
|
||||
"filter_platforms_title": "Visibilidad de sistemas",
|
||||
"filter_platforms_info": "Visibles: {0} | Ocultos: {1} / Total: {2}",
|
||||
@@ -74,6 +80,9 @@
|
||||
"menu_allow_unknown_ext_enabled": "Aviso de extensión desconocida oculto (activado)",
|
||||
"menu_allow_unknown_ext_disabled": "Aviso de extensión desconocida visible (desactivado)",
|
||||
"menu_quit": "Salir",
|
||||
"support_dialog_title": "Archivo de soporte",
|
||||
"support_dialog_message": "Se ha creado un archivo de soporte con todos sus archivos de configuración y registros.\n\nArchivo: {0}\n\nPara obtener ayuda:\n1. Únete al servidor Discord de RGSX\n2. Describe tu problema\n3. Comparte este archivo ZIP\n\nPresiona {1} para volver al menú.",
|
||||
"support_dialog_error": "Error al generar el archivo de soporte:\n{0}\n\nPresiona {1} para volver al menú.",
|
||||
"button_yes": "Sí",
|
||||
"button_no": "No",
|
||||
"button_OK": "OK",
|
||||
@@ -171,6 +180,7 @@
|
||||
,"instruction_pause_games": "Abrir historial, cambiar fuente o refrescar lista"
|
||||
,"instruction_pause_settings": "Música, opción symlink y estado de claves API"
|
||||
,"instruction_pause_restart": "Reiniciar RGSX para recargar configuración"
|
||||
,"instruction_pause_support": "Generar un archivo ZIP de diagnóstico para soporte"
|
||||
,"instruction_pause_quit": "Salir de la aplicación RGSX"
|
||||
,"instruction_controls_help": "Mostrar referencia completa de mando y teclado"
|
||||
,"instruction_controls_remap": "Cambiar asignación de botones / teclas"
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
"history_status_completed": "Terminé",
|
||||
"history_status_error": "Erreur : {0}",
|
||||
"history_status_canceled": "Annulé",
|
||||
"free_mode_waiting": "[Mode gratuit] Attente: {0}/{1}s",
|
||||
"free_mode_download": "[Mode gratuit] Téléchargement: {0}",
|
||||
"free_mode_submitting": "[Mode gratuit] Soumission formulaire...",
|
||||
"free_mode_link_found": "[Mode gratuit] Lien trouvé: {0}...",
|
||||
"free_mode_completed": "[Mode gratuit] Terminé: {0}",
|
||||
"download_status": "{0} : {1}",
|
||||
"download_canceled": "Téléchargement annulé par l'utilisateur.",
|
||||
"extension_warning_zip": "Le fichier '{0}' est une archive et Batocera ne prend pas en charge les archives pour ce système. L'extraction automatique du fichier aura lieu après le téléchargement, continuer ?",
|
||||
@@ -59,6 +64,7 @@
|
||||
"menu_display": "Affichage",
|
||||
"display_layout": "Disposition",
|
||||
"menu_redownload_cache": "Mettre à jour la liste des jeux",
|
||||
"menu_support": "Support",
|
||||
"menu_quit": "Quitter",
|
||||
"menu_music_enabled": "Musique activée : {0}",
|
||||
"menu_music_disabled": "Musique désactivée",
|
||||
@@ -68,6 +74,9 @@
|
||||
"filter_platforms_info": "Visibles: {0} | Masqués: {1} / Total: {2}",
|
||||
"filter_unsaved_warning": "Modifications non sauvegardées",
|
||||
"menu_show_unsupported_enabled": "Affichage systèmes non supportés activé",
|
||||
"support_dialog_title": "Fichier de support",
|
||||
"support_dialog_message": "Un fichier de support a été créé avec tous vos fichiers de configuration et logs.\n\nFichier: {0}\n\nPour obtenir de l'aide :\n1. Rejoignez le Discord RGSX\n2. Décrivez votre problème\n3. Partagez ce fichier ZIP\n\nAppuyez sur {1} pour revenir au menu.",
|
||||
"support_dialog_error": "Erreur lors de la génération du fichier de support :\n{0}\n\nAppuyez sur {1} pour revenir au menu.",
|
||||
"menu_show_unsupported_disabled": "Affichage systèmes non supportés désactivé",
|
||||
"menu_allow_unknown_ext_on": "Masquer avertissement extension inconnue : Oui",
|
||||
"menu_allow_unknown_ext_off": "Masquer avertissement extension inconnue : Non",
|
||||
@@ -170,6 +179,7 @@
|
||||
,"instruction_pause_games": "Historique, source de liste ou rafraîchissement"
|
||||
,"instruction_pause_settings": "Musique, option symlink & statut des clés API"
|
||||
,"instruction_pause_restart": "Redémarrer RGSX pour recharger la configuration"
|
||||
,"instruction_pause_support": "Générer un fichier ZIP de diagnostic pour l'assistance"
|
||||
,"instruction_pause_quit": "Quitter l'application RGSX"
|
||||
,"instruction_controls_help": "Afficher la référence complète manette & clavier"
|
||||
,"instruction_controls_remap": "Modifier l'association boutons / touches"
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
"history_status_completed": "Completato",
|
||||
"history_status_error": "Errore: {0}",
|
||||
"history_status_canceled": "Annullato",
|
||||
"free_mode_waiting": "[Modalità gratuita] Attesa: {0}/{1}s",
|
||||
"free_mode_download": "[Modalità gratuita] Download: {0}",
|
||||
"free_mode_submitting": "[Modalità gratuita] Invio modulo...",
|
||||
"free_mode_link_found": "[Modalità gratuita] Link trovato: {0}...",
|
||||
"free_mode_completed": "[Modalità gratuita] Completato: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download annullato dall'utente.",
|
||||
"extension_warning_zip": "Il file '{0}' è un archivio e Batocera non supporta archivi per questo sistema. L'estrazione automatica avverrà dopo il download, continuare?",
|
||||
@@ -63,6 +68,7 @@
|
||||
"menu_music_enabled": "Musica attivata: {0}",
|
||||
"menu_music_disabled": "Musica disattivata",
|
||||
"menu_restart": "Riavvia",
|
||||
"menu_support": "Supporto",
|
||||
"menu_filter_platforms": "Filtra sistemi",
|
||||
"filter_platforms_title": "Visibilità sistemi",
|
||||
"filter_platforms_info": "Visibili: {0} | Nascosti: {1} / Totale: {2}",
|
||||
@@ -74,6 +80,9 @@
|
||||
"menu_allow_unknown_ext_enabled": "Nascondi avviso estensione sconosciuta abilitato",
|
||||
"menu_allow_unknown_ext_disabled": "Nascondi avviso estensione sconosciuta disabilitato",
|
||||
"menu_quit": "Esci",
|
||||
"support_dialog_title": "File di supporto",
|
||||
"support_dialog_message": "È stato creato un file di supporto con tutti i file di configurazione e di registro.\n\nFile: {0}\n\nPer ottenere aiuto:\n1. Unisciti al server Discord RGSX\n2. Descrivi il tuo problema\n3. Condividi questo file ZIP\n\nPremi {1} per tornare al menu.",
|
||||
"support_dialog_error": "Errore durante la generazione del file di supporto:\n{0}\n\nPremi {1} per tornare al menu.",
|
||||
"button_yes": "Sì",
|
||||
"button_no": "No",
|
||||
"button_OK": "OK",
|
||||
@@ -171,6 +180,7 @@
|
||||
,"instruction_pause_games": "Aprire cronologia, cambiare sorgente o aggiornare elenco"
|
||||
,"instruction_pause_settings": "Musica, opzione symlink e stato chiavi API"
|
||||
,"instruction_pause_restart": "Riavvia RGSX per ricaricare la configurazione"
|
||||
,"instruction_pause_support": "Genera un file ZIP diagnostico per il supporto"
|
||||
,"instruction_pause_quit": "Uscire dall'applicazione RGSX"
|
||||
,"instruction_controls_help": "Mostrare riferimento completo controller & tastiera"
|
||||
,"instruction_controls_remap": "Modificare associazione pulsanti / tasti"
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
"history_status_completed": "Concluído",
|
||||
"history_status_error": "Erro: {0}",
|
||||
"history_status_canceled": "Cancelado",
|
||||
"free_mode_waiting": "[Modo gratuito] Aguardando: {0}/{1}s",
|
||||
"free_mode_download": "[Modo gratuito] Baixando: {0}",
|
||||
"free_mode_submitting": "[Modo gratuito] Enviando formulário...",
|
||||
"free_mode_link_found": "[Modo gratuito] Link encontrado: {0}...",
|
||||
"free_mode_completed": "[Modo gratuito] Concluído: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download cancelado pelo usuário.",
|
||||
"extension_warning_zip": "O arquivo '{0}' é um arquivo compactado e o Batocera não suporta arquivos compactados para este sistema. A extração automática ocorrerá após o download, continuar?",
|
||||
@@ -63,6 +68,7 @@
|
||||
"menu_music_enabled": "Música ativada: {0}",
|
||||
"menu_music_disabled": "Música desativada",
|
||||
"menu_restart": "Reiniciar",
|
||||
"menu_support": "Suporte",
|
||||
"menu_filter_platforms": "Filtrar sistemas",
|
||||
"filter_platforms_title": "Visibilidade dos sistemas",
|
||||
"filter_platforms_info": "Visíveis: {0} | Ocultos: {1} / Total: {2}",
|
||||
@@ -74,6 +80,9 @@
|
||||
"menu_allow_unknown_ext_enabled": "Aviso de extensão desconhecida oculto (ativado)",
|
||||
"menu_allow_unknown_ext_disabled": "Aviso de extensão desconhecida visível (desativado)",
|
||||
"menu_quit": "Sair",
|
||||
"support_dialog_title": "Arquivo de suporte",
|
||||
"support_dialog_message": "Foi criado um arquivo de suporte com todos os seus arquivos de configuração e logs.\n\nArquivo: {0}\n\nPara obter ajuda:\n1. Junte-se ao servidor Discord RGSX\n2. Descreva seu problema\n3. Compartilhe este arquivo ZIP\n\nPressione {1} para voltar ao menu.",
|
||||
"support_dialog_error": "Erro ao gerar o arquivo de suporte:\n{0}\n\nPressione {1} para voltar ao menu.",
|
||||
"button_yes": "Sim",
|
||||
"button_no": "Não",
|
||||
"button_OK": "OK",
|
||||
@@ -171,6 +180,7 @@
|
||||
,"instruction_pause_games": "Abrir histórico, mudar fonte ou atualizar lista"
|
||||
,"instruction_pause_settings": "Música, opção symlink e status das chaves API"
|
||||
,"instruction_pause_restart": "Reiniciar RGSX para recarregar configuração"
|
||||
,"instruction_pause_support": "Gerar um arquivo ZIP de diagnóstico para suporte"
|
||||
,"instruction_pause_quit": "Sair da aplicação RGSX"
|
||||
,"instruction_controls_help": "Mostrar referência completa de controle e teclado"
|
||||
,"instruction_controls_remap": "Modificar associação de botões / teclas"
|
||||
|
||||
@@ -28,10 +28,198 @@ import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from language import _ # Import de la fonction de traduction
|
||||
import re
|
||||
import html as html_module
|
||||
from urllib.parse import urljoin, unquote
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ================== TÉLÉCHARGEMENT 1FICHIER GRATUIT ==================
|
||||
# Fonction pour télécharger depuis 1fichier sans API key (mode gratuit)
|
||||
# Compatible RGSX - Sans BeautifulSoup ni httpx
|
||||
|
||||
# Regex pour détecter le compte à rebours
|
||||
WAIT_REGEXES_1F = [
|
||||
r'(?:veuillez\s+)?patiente[rz]\s*(\d+)\s*(?:sec|secondes?|s)\b',
|
||||
r'please\s+wait\s*(\d+)\s*(?:sec|seconds?)\b',
|
||||
r'var\s+ct\s*=\s*(\d+)\s*;',
|
||||
r'var\s+ct\s*=\s*(\d+)\s*\*\s*60\s*;',
|
||||
]
|
||||
|
||||
def extract_wait_seconds_1f(html_text):
|
||||
"""Extrait le temps d'attente depuis le HTML 1fichier"""
|
||||
for pattern in WAIT_REGEXES_1F:
|
||||
match = re.search(pattern, html_text, re.IGNORECASE)
|
||||
if match:
|
||||
seconds = int(match.group(1))
|
||||
# Si c'est en minutes (pattern avec *60)
|
||||
if '*60' in pattern or r'*\s*60' in pattern:
|
||||
seconds = seconds * 60
|
||||
return seconds
|
||||
return 0
|
||||
|
||||
def download_1fichier_free_mode(url, dest_dir, session, log_callback=None, progress_callback=None, wait_callback=None, cancel_event=None):
|
||||
"""
|
||||
Télécharge un fichier depuis 1fichier.com en mode gratuit (sans API key).
|
||||
Compatible RGSX - Sans BeautifulSoup ni httpx.
|
||||
|
||||
Args:
|
||||
url: URL 1fichier
|
||||
dest_dir: Dossier de destination
|
||||
session: Session requests
|
||||
log_callback: Fonction appelée avec les messages de log
|
||||
progress_callback: Fonction appelée avec (filename, downloaded, total, percent)
|
||||
wait_callback: Fonction appelée avec (remaining_seconds, total_seconds)
|
||||
cancel_event: threading.Event pour annuler le téléchargement
|
||||
|
||||
Returns:
|
||||
(success: bool, filepath: str|None, error_message: str|None)
|
||||
"""
|
||||
|
||||
def _log(msg):
|
||||
if log_callback:
|
||||
try:
|
||||
log_callback(msg)
|
||||
except Exception:
|
||||
pass
|
||||
logger.info(msg)
|
||||
|
||||
def _progress(filename, downloaded, total, pct):
|
||||
if progress_callback:
|
||||
try:
|
||||
progress_callback(filename, downloaded, total, pct)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _wait(remaining, total_wait):
|
||||
if wait_callback:
|
||||
try:
|
||||
wait_callback(remaining, total_wait)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
_log(_("free_mode_download").format(url))
|
||||
|
||||
# 1. GET page initiale
|
||||
if cancel_event and cancel_event.is_set():
|
||||
return (False, None, "Annulé")
|
||||
|
||||
r = session.get(url, allow_redirects=True, timeout=30)
|
||||
r.raise_for_status()
|
||||
html = r.text
|
||||
|
||||
# 2. Détection compte à rebours
|
||||
wait_s = extract_wait_seconds_1f(html)
|
||||
|
||||
if wait_s > 0:
|
||||
_log(f"{wait_s}s...")
|
||||
for remaining in range(wait_s, 0, -1):
|
||||
if cancel_event and cancel_event.is_set():
|
||||
return (False, None, "Annulé")
|
||||
_wait(remaining, wait_s)
|
||||
time.sleep(1)
|
||||
|
||||
# 3. Chercher formulaire et soumettre
|
||||
if cancel_event and cancel_event.is_set():
|
||||
return (False, None, "Annulé")
|
||||
|
||||
form_match = re.search(r'<form[^>]*id=[\"\']f1[\"\'][^>]*>(.*?)</form>', html, re.DOTALL | re.IGNORECASE)
|
||||
|
||||
if form_match:
|
||||
form_html = form_match.group(1)
|
||||
|
||||
# Extraire les champs
|
||||
data = {}
|
||||
for inp_match in re.finditer(r'<input[^>]+>', form_html, re.IGNORECASE):
|
||||
inp = inp_match.group(0)
|
||||
|
||||
name_m = re.search(r'name=[\"\']([^\"\']+)', inp)
|
||||
value_m = re.search(r'value=[\"\']([^\"\']*)', inp)
|
||||
|
||||
if name_m:
|
||||
name = name_m.group(1)
|
||||
value = value_m.group(1) if value_m else ''
|
||||
data[name] = html_module.unescape(value)
|
||||
|
||||
# POST formulaire
|
||||
_log(_("free_mode_submitting"))
|
||||
r2 = session.post(str(r.url), data=data, allow_redirects=True, timeout=30)
|
||||
r2.raise_for_status()
|
||||
html = r2.text
|
||||
|
||||
# 4. Chercher lien de téléchargement
|
||||
if cancel_event and cancel_event.is_set():
|
||||
return (False, None, "Annulé")
|
||||
|
||||
patterns = [
|
||||
r'href=[\"\']([^\"\']+)[\"\'][^>]*>(?:cliquer|click|télécharger|download)',
|
||||
r'href=[\"\']([^\"\']*/dl/[^\"\']+)',
|
||||
r'https?://[a-z0-9.-]*1fichier\.com/[A-Za-z0-9]{8,}'
|
||||
]
|
||||
|
||||
direct_link = None
|
||||
for pattern in patterns:
|
||||
match = re.search(pattern, html, re.IGNORECASE)
|
||||
if match:
|
||||
direct_link = match.group(1) if '//' in match.group(0) else urljoin(str(r.url), match.group(1))
|
||||
break
|
||||
|
||||
if not direct_link:
|
||||
return (False, None, "Lien de téléchargement introuvable")
|
||||
|
||||
_log(_("free_mode_link_found").format(direct_link[:60]))
|
||||
|
||||
# 5. HEAD pour infos fichier
|
||||
if cancel_event and cancel_event.is_set():
|
||||
return (False, None, "Annulé")
|
||||
|
||||
head = session.head(direct_link, allow_redirects=True, timeout=30)
|
||||
|
||||
# Nom fichier
|
||||
filename = 'downloaded_file'
|
||||
cd = head.headers.get('content-disposition', '')
|
||||
if cd:
|
||||
fn_match = re.search(r'filename\*?=[\"\']?([^\"\';]+)', cd, re.IGNORECASE)
|
||||
if fn_match:
|
||||
filename = unquote(fn_match.group(1))
|
||||
|
||||
filename = sanitize_filename(filename)
|
||||
filepath = os.path.join(dest_dir, filename)
|
||||
|
||||
# 6. Téléchargement
|
||||
_log(_("free_mode_download").format(filename))
|
||||
|
||||
with session.get(direct_link, stream=True, allow_redirects=True, timeout=30) as resp:
|
||||
resp.raise_for_status()
|
||||
total = int(resp.headers.get('content-length', 0))
|
||||
|
||||
with open(filepath, 'wb') as f:
|
||||
downloaded = 0
|
||||
for chunk in resp.iter_content(chunk_size=128*1024):
|
||||
if cancel_event and cancel_event.is_set():
|
||||
return (False, None, "Annulé")
|
||||
|
||||
f.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
|
||||
if total:
|
||||
pct = downloaded / total * 100
|
||||
_progress(filename, downloaded, total, pct)
|
||||
|
||||
_log(_("free_mode_completed").format(filepath))
|
||||
return (True, filepath, None)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Erreur mode gratuit: {str(e)}"
|
||||
_log(error_msg)
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return (False, None, error_msg)
|
||||
|
||||
# ==================== FIN TÉLÉCHARGEMENT GRATUIT ====================
|
||||
|
||||
# Plus besoin de web_progress.json - l'interface web lit directement history.json
|
||||
# Les fonctions update_web_progress() et remove_web_progress() sont supprimées
|
||||
|
||||
@@ -1303,7 +1491,110 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
except Exception as e:
|
||||
logger.error(f"Exception RealDebrid fallback: {e}")
|
||||
if not final_url:
|
||||
logger.error("Aucune URL directe obtenue (AllDebrid & RealDebrid échoués ou absents)")
|
||||
# NOUVEAU: Fallback mode gratuit 1fichier si aucune clé API disponible
|
||||
logger.warning("Aucune URL directe obtenue via API - Tentative mode gratuit 1fichier")
|
||||
|
||||
# Créer un lock pour ce téléchargement
|
||||
free_lock = threading.Lock()
|
||||
|
||||
try:
|
||||
# Créer une session requests pour le mode gratuit
|
||||
free_session = requests.Session()
|
||||
free_session.headers.update({'User-Agent': 'Mozilla/5.0'})
|
||||
|
||||
# Callbacks pour le mode gratuit
|
||||
def log_cb(msg):
|
||||
logger.info(msg)
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url:
|
||||
entry["message"] = msg
|
||||
config.needs_redraw = True
|
||||
break
|
||||
|
||||
def progress_cb(filename, downloaded, total, pct):
|
||||
with free_lock:
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
|
||||
entry["progress"] = int(pct) if pct else 0
|
||||
entry["downloaded_size"] = downloaded
|
||||
entry["total_size"] = total
|
||||
# Effacer le message personnalisé pour afficher le pourcentage
|
||||
entry["message"] = ""
|
||||
config.needs_redraw = True
|
||||
save_history(config.history)
|
||||
break
|
||||
progress_queues[task_id].put((task_id, downloaded, total))
|
||||
|
||||
def wait_cb(remaining, total_wait):
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url:
|
||||
entry["message"] = _("free_mode_waiting").format(remaining, total_wait)
|
||||
config.needs_redraw = True
|
||||
save_history(config.history)
|
||||
break
|
||||
|
||||
# Lancer le téléchargement gratuit
|
||||
success, filepath, error_msg = download_1fichier_free_mode(
|
||||
url=link,
|
||||
dest_dir=dest_dir,
|
||||
session=free_session,
|
||||
log_callback=log_cb,
|
||||
progress_callback=progress_cb,
|
||||
wait_callback=wait_cb,
|
||||
cancel_event=cancel_ev
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info(f"Téléchargement gratuit réussi: {filepath}")
|
||||
result[0] = True
|
||||
result[1] = _("network_download_ok").format(game_name) if _ else f"Download successful: {game_name}"
|
||||
provider_used = 'FREE'
|
||||
_set_provider_in_history(provider_used)
|
||||
|
||||
# Mettre à jour l'historique
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url:
|
||||
entry["status"] = "Completed"
|
||||
entry["progress"] = 100
|
||||
entry["message"] = result[1]
|
||||
entry["provider"] = "FREE"
|
||||
entry["provider_prefix"] = "FREE:"
|
||||
save_history(config.history)
|
||||
config.needs_redraw = True
|
||||
break
|
||||
|
||||
# Traiter le fichier (extraction si nécessaire)
|
||||
if not is_zip_non_supported:
|
||||
try:
|
||||
if filepath.lower().endswith('.zip'):
|
||||
logger.info(f"Extraction ZIP: {filepath}")
|
||||
extract_zip(filepath, dest_dir)
|
||||
os.remove(filepath)
|
||||
logger.info("ZIP extrait et supprimé")
|
||||
elif filepath.lower().endswith('.rar'):
|
||||
logger.info(f"Extraction RAR: {filepath}")
|
||||
extract_rar(filepath, dest_dir)
|
||||
os.remove(filepath)
|
||||
logger.info("RAR extrait et supprimé")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur extraction: {e}")
|
||||
|
||||
return
|
||||
else:
|
||||
logger.error(f"Échec téléchargement gratuit: {error_msg}")
|
||||
result[0] = False
|
||||
result[1] = f"Erreur mode gratuit: {error_msg}"
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Exception mode gratuit: {e}", exc_info=True)
|
||||
|
||||
# Si le mode gratuit a échoué aussi
|
||||
logger.error("Échec de tous les providers (API + mode gratuit)")
|
||||
result[0] = False
|
||||
if result[1] is None:
|
||||
result[1] = _("network_api_error").format("No provider available") if _ else "No provider available"
|
||||
|
||||
@@ -77,6 +77,91 @@ def restart_application(delay_ms: int = 2000):
|
||||
logger.exception(f"Failed to restart immediately: {e}")
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to schedule restart: {e}")
|
||||
|
||||
|
||||
def generate_support_zip():
|
||||
"""Génère un fichier ZIP contenant tous les fichiers de support pour le diagnostic.
|
||||
|
||||
Returns:
|
||||
tuple: (success: bool, message: str, zip_path: str ou None)
|
||||
"""
|
||||
import zipfile
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
# Créer un fichier ZIP temporaire
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
zip_filename = f"rgsx_support_{timestamp}.zip"
|
||||
zip_path = os.path.join(config.SAVE_FOLDER, zip_filename)
|
||||
|
||||
# Liste des fichiers à inclure
|
||||
files_to_include = []
|
||||
|
||||
# Ajouter les fichiers de configuration
|
||||
if hasattr(config, 'CONTROLS_CONFIG_PATH') and os.path.exists(config.CONTROLS_CONFIG_PATH):
|
||||
files_to_include.append(('controls.json', config.CONTROLS_CONFIG_PATH))
|
||||
|
||||
if hasattr(config, 'HISTORY_PATH') and os.path.exists(config.HISTORY_PATH):
|
||||
files_to_include.append(('history.json', config.HISTORY_PATH))
|
||||
|
||||
if hasattr(config, 'RGSX_SETTINGS_PATH') and os.path.exists(config.RGSX_SETTINGS_PATH):
|
||||
files_to_include.append(('rgsx_settings.json', config.RGSX_SETTINGS_PATH))
|
||||
|
||||
# Ajouter les fichiers de log
|
||||
if hasattr(config, 'log_file') and os.path.exists(config.log_file):
|
||||
files_to_include.append(('RGSX.log', config.log_file))
|
||||
|
||||
# Log du serveur web
|
||||
if hasattr(config, 'log_dir'):
|
||||
web_log = os.path.join(config.log_dir, 'rgsx_web.log')
|
||||
if os.path.exists(web_log):
|
||||
files_to_include.append(('rgsx_web.log', web_log))
|
||||
|
||||
web_startup_log = os.path.join(config.log_dir, 'rgsx_web_startup.log')
|
||||
if os.path.exists(web_startup_log):
|
||||
files_to_include.append(('rgsx_web_startup.log', web_startup_log))
|
||||
|
||||
# Créer le fichier ZIP
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
for archive_name, file_path in files_to_include:
|
||||
try:
|
||||
zipf.write(file_path, archive_name)
|
||||
logger.debug(f"Ajouté au ZIP: {archive_name}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Impossible d'ajouter {archive_name}: {e}")
|
||||
|
||||
# Ajouter un fichier README avec des informations système
|
||||
readme_content = f"""RGSX Support Package
|
||||
Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||
|
||||
System Information:
|
||||
- OS: {config.OPERATING_SYSTEM}
|
||||
- Python: {sys.version}
|
||||
- Platform: {sys.platform}
|
||||
|
||||
Included Files:
|
||||
"""
|
||||
for archive_name, _ in files_to_include:
|
||||
readme_content += f"- {archive_name}\n"
|
||||
|
||||
readme_content += """
|
||||
Instructions:
|
||||
1. Join RGSX Discord server
|
||||
2. Describe your issue in the support channel
|
||||
3. Upload this ZIP file to help the team diagnose your problem
|
||||
|
||||
DO NOT share this file publicly as it may contain sensitive information.
|
||||
"""
|
||||
zipf.writestr('README.txt', readme_content)
|
||||
|
||||
logger.info(f"Fichier de support généré: {zip_path}")
|
||||
return (True, f"Support file created: {zip_filename}", zip_path)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la génération du fichier de support: {e}")
|
||||
return (False, str(e), None)
|
||||
|
||||
_extensions_cache = None # type: ignore
|
||||
_extensions_json_regenerated = False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user