mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-17 13:57:44 +02:00
Sync from development - prepare for v0.3.0
This commit is contained in:
144
jackify/backend/handlers/modlist_install_cli_nexus.py
Normal file
144
jackify/backend/handlers/modlist_install_cli_nexus.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""Nexus and engine methods for ModlistInstallCLI (Mixin)."""
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from .ui_colors import COLOR_ERROR, COLOR_INFO, COLOR_RESET
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ModlistInstallCLINexusMixin:
|
||||
"""Mixin providing Nexus API and engine methods."""
|
||||
|
||||
def _get_nexus_api_key(self) -> Optional[str]:
|
||||
return self.context.get('nexus_api_key')
|
||||
|
||||
def get_all_modlists_from_engine(self, game_type=None):
|
||||
"""
|
||||
Call the Jackify engine with 'list-modlists' and return a list of modlist dicts.
|
||||
Each dict should have at least 'id', 'game', 'download_size', 'install_size', 'total_size', and status flags.
|
||||
|
||||
Args:
|
||||
game_type (str, optional): Filter by game type (e.g., "Skyrim", "Fallout New Vegas")
|
||||
"""
|
||||
from .modlist_install_cli import get_jackify_engine_path
|
||||
|
||||
engine_executable = get_jackify_engine_path()
|
||||
engine_dir = os.path.dirname(engine_executable)
|
||||
if not os.path.exists(engine_executable):
|
||||
print(f"{COLOR_ERROR}Error: jackify-install-engine not found at expected location.{COLOR_RESET}")
|
||||
print(f"{COLOR_INFO}Expected: {engine_executable}{COLOR_RESET}")
|
||||
return []
|
||||
env = os.environ.copy()
|
||||
env["DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"] = "1"
|
||||
command = [engine_executable, 'list-modlists', '--show-all-sizes', '--show-machine-url']
|
||||
|
||||
# Add game filter if specified
|
||||
if game_type:
|
||||
command.extend(['--game', game_type])
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True, text=True, check=True,
|
||||
env=env, cwd=engine_dir
|
||||
)
|
||||
lines = result.stdout.splitlines()
|
||||
modlists = []
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('Loading') or line.startswith('Loaded'):
|
||||
continue
|
||||
|
||||
# Parse the new format: [STATUS] Modlist Name - Game - Download|Install|Total - MachineURL
|
||||
# STATUS indicators: [DOWN], [NSFW], or both [DOWN] [NSFW]
|
||||
|
||||
# Extract status indicators
|
||||
status_down = '[DOWN]' in line
|
||||
status_nsfw = '[NSFW]' in line
|
||||
|
||||
# Remove status indicators to get clean line
|
||||
clean_line = line.replace('[DOWN]', '').replace('[NSFW]', '').strip()
|
||||
|
||||
# Split from right to handle modlist names with dashes
|
||||
# Format: "NAME - GAME - SIZES - MACHINE_URL"
|
||||
parts = clean_line.rsplit(' - ', 3) # Split from right, max 3 splits = 4 parts
|
||||
if len(parts) != 4:
|
||||
continue # Skip malformed lines
|
||||
|
||||
modlist_name = parts[0].strip()
|
||||
game_name = parts[1].strip()
|
||||
sizes_str = parts[2].strip()
|
||||
machine_url = parts[3].strip()
|
||||
|
||||
# Parse sizes: "Download|Install|Total" (e.g., "203GB|130GB|333GB")
|
||||
size_parts = sizes_str.split('|')
|
||||
if len(size_parts) != 3:
|
||||
continue # Skip if sizes don't match expected format
|
||||
|
||||
download_size = size_parts[0].strip()
|
||||
install_size = size_parts[1].strip()
|
||||
total_size = size_parts[2].strip()
|
||||
|
||||
# Skip if any required data is missing
|
||||
if not modlist_name or not game_name or not machine_url:
|
||||
continue
|
||||
|
||||
modlists.append({
|
||||
'id': modlist_name, # Use modlist name as ID for compatibility
|
||||
'name': modlist_name,
|
||||
'game': game_name,
|
||||
'download_size': download_size,
|
||||
'install_size': install_size,
|
||||
'total_size': total_size,
|
||||
'machine_url': machine_url, # Store machine URL for installation
|
||||
'status_down': status_down,
|
||||
'status_nsfw': status_nsfw
|
||||
})
|
||||
return modlists
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.logger.error(f"list-modlists failed. Code: {e.returncode}")
|
||||
if e.stdout: self.logger.error(f"Engine stdout:\n{e.stdout}")
|
||||
if e.stderr: self.logger.error(f"Engine stderr:\n{e.stderr}")
|
||||
print(f"{COLOR_ERROR}Failed to fetch modlist list. Engine error (Code: {e.returncode}).{COLOR_ERROR}")
|
||||
return []
|
||||
except Exception as e:
|
||||
self.logger.error(f"Unexpected error fetching modlists: {e}", exc_info=True)
|
||||
print(f"{COLOR_ERROR}Unexpected error fetching modlists: {e}{COLOR_ERROR}")
|
||||
return []
|
||||
|
||||
def _enhance_nexus_error(self, line: str) -> str:
|
||||
"""
|
||||
Enhance Nexus download error messages by adding the mod URL for easier troubleshooting.
|
||||
"""
|
||||
import re
|
||||
|
||||
# Pattern to match Nexus download errors with ModID and FileID
|
||||
nexus_error_pattern = r"Failed to download '[^']+' from Nexus \(Game: ([^,]+), ModID: (\d+), FileID: \d+\):"
|
||||
|
||||
match = re.search(nexus_error_pattern, line)
|
||||
if match:
|
||||
game_name = match.group(1)
|
||||
mod_id = match.group(2)
|
||||
|
||||
# Map game names to Nexus URL segments
|
||||
game_url_map = {
|
||||
'SkyrimSpecialEdition': 'skyrimspecialedition',
|
||||
'Skyrim': 'skyrim',
|
||||
'Fallout4': 'fallout4',
|
||||
'FalloutNewVegas': 'newvegas',
|
||||
'Oblivion': 'oblivion',
|
||||
'Starfield': 'starfield'
|
||||
}
|
||||
|
||||
game_url = game_url_map.get(game_name, game_name.lower())
|
||||
mod_url = f"https://www.nexusmods.com/{game_url}/mods/{mod_id}"
|
||||
|
||||
# Add URL on next line for easier debugging
|
||||
return f"{line}\n Nexus URL: {mod_url}"
|
||||
|
||||
return line
|
||||
|
||||
Reference in New Issue
Block a user