"""Construye un ZIP reproducible y limpio del Bot de Descarga.""" from __future__ import annotations import argparse import hashlib from pathlib import Path from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo ROOT = Path(__file__).resolve().parents[1] DEFAULT_OUTPUT = ROOT.parent / "bot_descarga.zip" TOP_LEVEL_FILES = ( ".env.example", ".gitignore", "README.md", "requirements.txt", "run_gui.py", "main.py", ) EXCLUDED_PARTS = {"__pycache__", ".pytest_cache", ".venv", "data", "logs", "out"} EXCLUDED_SUFFIXES = {".pyc", ".pyo", ".log"} def package_files(root: Path) -> list[Path]: files: list[Path] = [] for name in TOP_LEVEL_FILES: path = root / name if path.is_file(): files.append(path) package_root = root / "bot_descarga" for path in package_root.rglob("*"): relative = path.relative_to(root) if not path.is_file() or any(part in EXCLUDED_PARTS for part in relative.parts): continue if path.suffix.lower() in EXCLUDED_SUFFIXES: continue files.append(path) return sorted(files, key=lambda path: path.relative_to(root).as_posix()) def build_package(root: Path = ROOT, output: Path = DEFAULT_OUTPUT) -> Path: output.parent.mkdir(parents=True, exist_ok=True) with ZipFile(output, "w", compression=ZIP_DEFLATED, compresslevel=9) as archive: for source in package_files(root): relative = source.relative_to(root).as_posix() info = ZipInfo(f"bot_descarga/{relative}", date_time=(2020, 1, 1, 0, 0, 0)) info.compress_type = ZIP_DEFLATED info.external_attr = 0o644 << 16 archive.writestr(info, source.read_bytes()) return output def sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as source: for chunk in iter(lambda: source.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT) args = parser.parse_args() target = build_package(output=args.output.resolve()) print(f"Paquete: {target}") print(f"SHA256: {sha256(target)}") return 0 if __name__ == "__main__": raise SystemExit(main())