#!/usr/bin/env python3 """ Скачивает портативный Firefox и geckodriver (Windows) в папку vendor/, чтобы build.py / build_wine.sh встроили их в .exe. Тогда конечному пользователю нужно установить только КриптоПро — Firefox и драйвер уже внутри. Запуск (на машине сборки, можно на Linux): python fetch_vendor.py # win64 (по умолчанию) python fetch_vendor.py --arch win32 python fetch_vendor.py --lang en-US Для распаковки Firefox нужен 7-Zip: Linux: sudo apt install p7zip-full Windows: установите 7-Zip и добавьте 7z в PATH """ from __future__ import annotations import argparse import io import json import shutil import subprocess import sys import urllib.request import zipfile from pathlib import Path ROOT = Path(__file__).parent VENDOR = ROOT / "vendor" GECKO_API = "https://api.github.com/repos/mozilla/geckodriver/releases/latest" def _download(url: str) -> bytes: print(f" ↓ {url}") req = urllib.request.Request(url, headers={"User-Agent": "fetch-vendor"}) with urllib.request.urlopen(req, timeout=120) as resp: return resp.read() def _which_7z() -> str | None: for name in ("7z", "7za", "7zr"): if shutil.which(name): return name return None def fetch_geckodriver(arch: str) -> None: print("geckodriver:") data = json.loads(_download(GECKO_API).decode("utf-8")) asset = next( (a for a in data["assets"] if f"{arch}.zip" in a["name"] and a["name"].endswith(".zip")), None, ) if not asset: names = [a["name"] for a in data["assets"]] sys.exit(f" Не найден ассет для {arch}. Доступно: {names}") blob = _download(asset["browser_download_url"]) with zipfile.ZipFile(io.BytesIO(blob)) as z: member = next(n for n in z.namelist() if n.endswith("geckodriver.exe")) VENDOR.mkdir(exist_ok=True) with z.open(member) as src, open(VENDOR / "geckodriver.exe", "wb") as dst: shutil.copyfileobj(src, dst) print(f" ✓ {VENDOR / 'geckodriver.exe'} ({data['tag_name']})") def fetch_firefox(arch: str, lang: str) -> None: print("Firefox (портативный):") sevenzip = _which_7z() if not sevenzip: print( " ✗ 7-Zip не найден — не могу распаковать установщик Firefox.\n" " Установите 7-Zip (Linux: sudo apt install p7zip-full), либо вручную\n" " распакуйте Firefox так, чтобы получилось vendor/firefox/firefox.exe" ) return os_param = "win64" if arch == "win64" else "win" url = f"https://download.mozilla.org/?product=firefox-latest&os={os_param}&lang={lang}" installer = VENDOR / "_firefox_setup.exe" VENDOR.mkdir(exist_ok=True) installer.write_bytes(_download(url)) extract_dir = VENDOR / "_ff_extract" if extract_dir.exists(): shutil.rmtree(extract_dir) # Установщик Firefox — это 7-Zip SFX; внутри папка core/ с firefox.exe. subprocess.run( [sevenzip, "x", "-y", f"-o{extract_dir}", str(installer)], check=True, stdout=subprocess.DEVNULL, ) core = extract_dir / "core" if not (core / "firefox.exe").exists(): sys.exit(f" В установщике не найден core/firefox.exe (распаковано в {extract_dir})") target = VENDOR / "firefox" if target.exists(): shutil.rmtree(target) shutil.move(str(core), str(target)) installer.unlink(missing_ok=True) shutil.rmtree(extract_dir, ignore_errors=True) print(f" ✓ {target / 'firefox.exe'}") def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--arch", choices=("win64", "win32"), default="win64") ap.add_argument("--lang", default="ru") args = ap.parse_args() print(f"Папка: {VENDOR} (arch={args.arch}, lang={args.lang})\n") fetch_geckodriver(args.arch) print() fetch_firefox(args.arch, args.lang) print("\nГотово. Теперь соберите: python build.py") if __name__ == "__main__": main()