mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
Sync from development - prepare for v0.2.0
This commit is contained in:
@@ -68,7 +68,9 @@ class SystemInfo:
|
||||
steam_root: Optional[Path] = None
|
||||
steam_user_id: Optional[str] = None
|
||||
proton_version: Optional[str] = None
|
||||
|
||||
is_flatpak_steam: bool = False
|
||||
is_native_steam: bool = False
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
@@ -76,4 +78,6 @@ class SystemInfo:
|
||||
'steam_root': str(self.steam_root) if self.steam_root else None,
|
||||
'steam_user_id': self.steam_user_id,
|
||||
'proton_version': self.proton_version,
|
||||
'is_flatpak_steam': self.is_flatpak_steam,
|
||||
'is_native_steam': self.is_native_steam,
|
||||
}
|
||||
216
jackify/backend/models/modlist_metadata.py
Normal file
216
jackify/backend/models/modlist_metadata.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
Data models for modlist metadata from jackify-engine JSON output.
|
||||
|
||||
These models match the JSON schema documented in MODLIST_METADATA_IMPLEMENTATION.md
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModlistImages:
|
||||
"""Image URLs for modlist (small thumbnail and large banner)"""
|
||||
small: str
|
||||
large: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModlistLinks:
|
||||
"""External links associated with the modlist"""
|
||||
image: Optional[str] = None
|
||||
readme: Optional[str] = None
|
||||
download: Optional[str] = None
|
||||
discordURL: Optional[str] = None
|
||||
websiteURL: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModlistSizes:
|
||||
"""Size information for modlist downloads and installation"""
|
||||
downloadSize: int
|
||||
downloadSizeFormatted: str
|
||||
installSize: int
|
||||
installSizeFormatted: str
|
||||
totalSize: int
|
||||
totalSizeFormatted: str
|
||||
numberOfArchives: int
|
||||
numberOfInstalledFiles: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModlistValidation:
|
||||
"""Validation status from Wabbajack build server (optional)"""
|
||||
failed: int = 0
|
||||
passed: int = 0
|
||||
updating: int = 0
|
||||
mirrored: int = 0
|
||||
modListIsMissing: bool = False
|
||||
hasFailures: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModlistMetadata:
|
||||
"""Complete modlist metadata from jackify-engine"""
|
||||
# Basic information
|
||||
title: str
|
||||
description: str
|
||||
author: str
|
||||
maintainers: List[str]
|
||||
namespacedName: str
|
||||
repositoryName: str
|
||||
machineURL: str
|
||||
|
||||
# Game information
|
||||
game: str
|
||||
gameHumanFriendly: str
|
||||
|
||||
# Status flags
|
||||
official: bool
|
||||
nsfw: bool
|
||||
utilityList: bool
|
||||
forceDown: bool
|
||||
imageContainsTitle: bool
|
||||
|
||||
# Version information
|
||||
version: Optional[str] = None
|
||||
displayVersionOnlyInInstallerView: bool = False
|
||||
|
||||
# Dates
|
||||
dateCreated: Optional[str] = None # ISO8601 format
|
||||
dateUpdated: Optional[str] = None # ISO8601 format
|
||||
|
||||
# Categorization
|
||||
tags: List[str] = field(default_factory=list)
|
||||
|
||||
# Nested objects
|
||||
links: Optional[ModlistLinks] = None
|
||||
sizes: Optional[ModlistSizes] = None
|
||||
images: Optional[ModlistImages] = None
|
||||
|
||||
# Optional data (only if flags specified)
|
||||
validation: Optional[ModlistValidation] = None
|
||||
mods: List[str] = field(default_factory=list)
|
||||
|
||||
def is_available(self) -> bool:
|
||||
"""Check if modlist is available for installation"""
|
||||
if self.forceDown:
|
||||
return False
|
||||
if self.validation and self.validation.hasFailures:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_broken(self) -> bool:
|
||||
"""Check if modlist has validation failures"""
|
||||
return self.validation.hasFailures if self.validation else False
|
||||
|
||||
def get_date_updated_datetime(self) -> Optional[datetime]:
|
||||
"""Parse dateUpdated string to datetime object"""
|
||||
if not self.dateUpdated:
|
||||
return None
|
||||
try:
|
||||
return datetime.fromisoformat(self.dateUpdated.replace('Z', '+00:00'))
|
||||
except (ValueError, AttributeError):
|
||||
return None
|
||||
|
||||
def get_date_created_datetime(self) -> Optional[datetime]:
|
||||
"""Parse dateCreated string to datetime object"""
|
||||
if not self.dateCreated:
|
||||
return None
|
||||
try:
|
||||
return datetime.fromisoformat(self.dateCreated.replace('Z', '+00:00'))
|
||||
except (ValueError, AttributeError):
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModlistMetadataResponse:
|
||||
"""Root response object from jackify-engine list-modlists --json"""
|
||||
metadataVersion: str
|
||||
timestamp: str # ISO8601 format
|
||||
count: int
|
||||
modlists: List[ModlistMetadata]
|
||||
|
||||
def get_timestamp_datetime(self) -> Optional[datetime]:
|
||||
"""Parse timestamp string to datetime object"""
|
||||
try:
|
||||
return datetime.fromisoformat(self.timestamp.replace('Z', '+00:00'))
|
||||
except (ValueError, AttributeError):
|
||||
return None
|
||||
|
||||
def filter_by_game(self, game: str) -> List[ModlistMetadata]:
|
||||
"""Filter modlists by game name"""
|
||||
return [m for m in self.modlists if m.game.lower() == game.lower()]
|
||||
|
||||
def filter_available_only(self) -> List[ModlistMetadata]:
|
||||
"""Filter to only available (non-broken, non-forced-down) modlists"""
|
||||
return [m for m in self.modlists if m.is_available()]
|
||||
|
||||
def filter_by_tag(self, tag: str) -> List[ModlistMetadata]:
|
||||
"""Filter modlists by tag"""
|
||||
return [m for m in self.modlists if tag.lower() in [t.lower() for t in m.tags]]
|
||||
|
||||
def filter_official_only(self) -> List[ModlistMetadata]:
|
||||
"""Filter to only official modlists"""
|
||||
return [m for m in self.modlists if m.official]
|
||||
|
||||
def search(self, query: str) -> List[ModlistMetadata]:
|
||||
"""Search modlists by title, description, or author"""
|
||||
query_lower = query.lower()
|
||||
return [
|
||||
m for m in self.modlists
|
||||
if query_lower in m.title.lower()
|
||||
or query_lower in m.description.lower()
|
||||
or query_lower in m.author.lower()
|
||||
]
|
||||
|
||||
|
||||
def parse_modlist_metadata_from_dict(data: dict) -> ModlistMetadata:
|
||||
"""Parse a modlist metadata dictionary into ModlistMetadata object"""
|
||||
# Parse nested objects
|
||||
images = ModlistImages(**data['images']) if 'images' in data and data['images'] else None
|
||||
links = ModlistLinks(**data['links']) if 'links' in data and data['links'] else None
|
||||
sizes = ModlistSizes(**data['sizes']) if 'sizes' in data and data['sizes'] else None
|
||||
validation = ModlistValidation(**data['validation']) if 'validation' in data and data['validation'] else None
|
||||
|
||||
# Create ModlistMetadata with nested objects
|
||||
metadata = ModlistMetadata(
|
||||
title=data['title'],
|
||||
description=data['description'],
|
||||
author=data['author'],
|
||||
maintainers=data.get('maintainers', []),
|
||||
namespacedName=data['namespacedName'],
|
||||
repositoryName=data['repositoryName'],
|
||||
machineURL=data['machineURL'],
|
||||
game=data['game'],
|
||||
gameHumanFriendly=data['gameHumanFriendly'],
|
||||
official=data['official'],
|
||||
nsfw=data['nsfw'],
|
||||
utilityList=data['utilityList'],
|
||||
forceDown=data['forceDown'],
|
||||
imageContainsTitle=data['imageContainsTitle'],
|
||||
version=data.get('version'),
|
||||
displayVersionOnlyInInstallerView=data.get('displayVersionOnlyInInstallerView', False),
|
||||
dateCreated=data.get('dateCreated'),
|
||||
dateUpdated=data.get('dateUpdated'),
|
||||
tags=data.get('tags', []),
|
||||
links=links,
|
||||
sizes=sizes,
|
||||
images=images,
|
||||
validation=validation,
|
||||
mods=data.get('mods', [])
|
||||
)
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
def parse_modlist_metadata_response(data: dict) -> ModlistMetadataResponse:
|
||||
"""Parse the full JSON response from jackify-engine into ModlistMetadataResponse"""
|
||||
modlists = [parse_modlist_metadata_from_dict(m) for m in data.get('modlists', [])]
|
||||
|
||||
return ModlistMetadataResponse(
|
||||
metadataVersion=data['metadataVersion'],
|
||||
timestamp=data['timestamp'],
|
||||
count=data['count'],
|
||||
modlists=modlists
|
||||
)
|
||||
Reference in New Issue
Block a user