Both | БЛЯЯЯ ЗАГРУЗКА ПАКОВ С СЕРВЕРА СЮДААА

This commit is contained in:
Sashegdev
2026-04-06 00:32:36 +00:00
parent 4edbe7e910
commit 0b4af1353d
13 changed files with 1482 additions and 373 deletions
+46 -83
View File
@@ -3,25 +3,17 @@ import os
from datetime import datetime
from pathlib import Path
import json
import aiofiles
from typing import Optional, Dict
import structlog
from models import PackMeta, FileEntry
import aiohttp
from typing import List, Optional
logger = structlog.get_logger(__name__)
PACKS_DIR = Path("packs")
DATA_DIR = Path("data")
DATA_DIR.mkdir(exist_ok=True)
MINECRAFT_VERSION_MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json"
FABRIC_META_URL = "https://meta.fabricmc.net/v2/versions"
FORGE_META_URL = "https://files.minecraftforge.net/net/minecraftforge/forge/"
# Cache for loaded manifests
_manifest_cache: Dict[str, PackMeta] = {}
@@ -32,102 +24,63 @@ def get_cached_manifest(pack_name: str) -> Optional[PackMeta]:
def get_meta_path(pack_name: str) -> Path:
return DATA_DIR / f"{pack_name}.meta"
async def calculate_sha256(file_path: Path) -> str:
def calculate_sha256_sync(file_path: Path) -> str:
"""Calculate SHA256 hash of a file (synchronous version)"""
hash_sha = hashlib.sha256()
async with aiofiles.open(file_path, 'rb') as f:
while chunk := await f.read(8192):
with open(file_path, 'rb') as f:
while chunk := f.read(8192):
hash_sha.update(chunk)
return hash_sha.hexdigest()
async def get_minecraft_versions():
"""Fetch available Minecraft versions from Mojang"""
from models import MinecraftVersion
async with aiohttp.ClientSession() as session:
async with session.get(MINECRAFT_VERSION_MANIFEST_URL) as response:
data = await response.json()
versions = []
for v in data.get("versions", []):
versions.append(MinecraftVersion(
version=v["id"],
type=v["type"],
release_time=datetime.fromisoformat(v["releaseTime"].replace('Z', '+00:00')),
url=v["url"]
))
return versions
async def get_fabric_versions(minecraft_version: str) -> List[str]:
"""Get available Fabric versions for specific Minecraft version"""
async with aiohttp.ClientSession() as session:
async with session.get(f"{FABRIC_META_URL}/loader") as response:
data = await response.json()
fabric_versions = []
for loader in data:
if loader.get("stable", True):
fabric_versions.append(loader["version"])
return fabric_versions
async def get_forge_versions(minecraft_version: str) -> List[str]:
"""Get available Forge versions for specific Minecraft version"""
async with aiohttp.ClientSession() as session:
async with session.get(FORGE_META_URL) as response:
# Forge API is more complex, simplified for now
return []
async def download_minecraft_version(version: str, target_path: Path) -> bool:
"""Download Minecraft version JSON and client jar"""
async with aiohttp.ClientSession() as session:
async with session.get(f"https://piston-meta.mojang.com/mc/game/{version}/{version}.json") as response:
if response.status == 200:
version_data = await response.json()
async with aiofiles.open(target_path / f"{version}.json", 'w') as f:
await f.write(json.dumps(version_data, indent=2))
downloads = version_data.get("downloads", {})
client_info = downloads.get("client", {})
if client_info:
client_url = client_info.get("url")
async with session.get(client_url) as client_response:
if client_response.status == 200:
async with aiofiles.open(target_path / f"{version}.jar", 'wb') as f:
async for chunk in client_response.content.iter_chunked(8192):
await f.write(chunk)
return True
return False
async def calculate_sha256(file_path: Path) -> str:
"""Calculate SHA256 hash of a file (async wrapper)"""
# Используем синхронную версию для простоты
return calculate_sha256_sync(file_path)
async def scan_pack(pack_name: str, force_rescan: bool = False) -> PackMeta:
"""Scan pack directory and update manifest if needed"""
pack_path = PACKS_DIR / pack_name
if not pack_path.exists() or not pack_path.is_dir():
raise FileNotFoundError(f"Pack {pack_name} not found")
meta_path = get_meta_path(pack_name)
current_meta: Optional[PackMeta] = None
# Check cache first
if not force_rescan and pack_name in _manifest_cache:
return _manifest_cache[pack_name]
# Load existing meta if available (синхронно)
if meta_path.exists():
async with aiofiles.open(meta_path, 'r', encoding='utf-8') as f:
data = json.loads(await f.read())
try:
try:
with open(meta_path, 'r', encoding='utf-8') as f:
data = json.load(f)
current_meta = PackMeta.model_validate(data)
except Exception as e:
logger.warning(f"Failed to validate existing meta for pack {pack_name}", error=str(e))
current_meta = None
except Exception as e:
logger.warning(f"Failed to load existing meta for pack {pack_name}: {e}")
current_meta = None
new_files: Dict[str, FileEntry] = {}
changed = False
# Get ignored directories
ignored_dirs = current_meta.ignored_dirs if current_meta else [
"resourcepacks", "shaderpacks", "saves", "logs",
"crash-reports", "screenshots", "journeymap", "config"
]
# Walk through pack directory
for root, dirs, files in os.walk(pack_path):
ignored = current_meta.ignored_dirs if current_meta else []
dirs[:] = [d for d in dirs if d not in ignored]
# Filter ignored directories
dirs[:] = [d for d in dirs if d not in ignored_dirs]
for file in files:
file_path = Path(root) / file
rel_path = file_path.relative_to(pack_path).as_posix()
if any(ignored_dir in rel_path.split('/') for ignored_dir in ignored):
# Skip if in ignored directory
if any(ignored_dir in rel_path.split('/') for ignored_dir in ignored_dirs):
continue
stat = file_path.stat()
@@ -143,48 +96,58 @@ async def scan_pack(pack_name: str, force_rescan: bool = False) -> PackMeta:
new_files[rel_path] = entry
# Check if file changed
if current_meta and (rel_path not in current_meta.files or
current_meta.files[rel_path].hash != file_hash):
changed = True
# Check if we need to update
if not current_meta or changed or len(new_files) != len(current_meta.files if current_meta else 0):
version = (current_meta.version + 1) if current_meta else 1
pack_config_path = pack_path / "instance.json"
# Load instance.json for pack metadata
minecraft_version = "1.20.4"
loader_type = "vanilla"
loader_version = None
pack_config_path = pack_path / "instance.json"
if pack_config_path.exists():
try:
async with aiofiles.open(pack_config_path, 'r', encoding='utf-8') as f:
config = json.loads(await f.read())
# Синхронное чтение конфига
with open(pack_config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
minecraft_version = config.get("minecraftVersion", minecraft_version)
loader_type = config.get("loaderType", loader_type)
loader_version = config.get("loaderVersion")
except Exception as e:
logger.warning(f"Failed to load instance.json for {pack_name}", error=str(e))
logger.warning(f"Failed to load instance.json for {pack_name}: {e}")
# Create new manifest
new_meta = PackMeta(
pack_name=pack_name,
version=version,
files=new_files,
updated_at=datetime.utcnow(),
ignored_dirs=current_meta.ignored_dirs if current_meta else [],
ignored_dirs=ignored_dirs,
minecraft_version=minecraft_version,
loader_type=loader_type,
loader_version=loader_version
)
async with aiofiles.open(meta_path, 'w', encoding='utf-8') as f:
await f.write(new_meta.model_dump_json(indent=2))
# Save to disk (синхронно)
with open(meta_path, 'w', encoding='utf-8') as f:
f.write(new_meta.model_dump_json(indent=2))
# Update cache
_manifest_cache[pack_name] = new_meta
logger.info(f"Pack updated: {pack_name} v{version}, {len(new_files)} files")
return new_meta
# No changes, use existing
if current_meta:
_manifest_cache[pack_name] = current_meta
return current_meta
return current_meta
# Should not happen
raise Exception(f"Failed to scan pack {pack_name}")