#!/usr/bin/env python3
"""
═══════════════════════════════════════════════════════════════════════════════
    VERKLI LETÖLTŐ — helyi kiszolgáló a Verkli Letöltés füléhez

    Verzió: 1.0.0
    TZlab · Tóth Zoltán

    A böngésző önmagában nem tud videót letölteni. Ez a kis kiszolgáló a
    saját gépen figyel, és a Verkli Letöltés füle beszélget vele. Amit
    lehoz, azt a Verkli átveszi és beteszi a tárba.

    Az AvideoZ (Universal Video Downloader) tapasztalataira épül:
    Facebook/Dropbox URL-javítás, felirat-kezelés, emberi szünetek.

    MIÉRT TÖLT LE A COLAB MEGBÍZHATÓBBAN?
    Mert ott minden induláskor friss yt-dlp települ. A YouTube pár hetente
    változtat, és egy pár hónapos yt-dlp elkezd hibázni. Ezért ez a
    kiszolgáló induláskor kiírja a verziót, és van „frissítés" gombja.

    Függőség: yt-dlp (kötelező), ffmpeg (a jó minőséghez és az MP3-hoz).
        pip install -U yt-dlp

    Indítás:  python verkli_letolto.py
    Leállítás: Ctrl+C
═══════════════════════════════════════════════════════════════════════════════
"""

import json
import mimetypes
import os
import re
import secrets
import shutil
import subprocess
import sys
import threading
import time
import uuid
from datetime import datetime
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import unquote, urlparse

VERZIO = "1.7.0"
HOST = "127.0.0.1"          # csak a saját gép — kívülről nem érhető el
PORT = 8765
SAJAT_MAPPA = Path(__file__).resolve().parent


def verkli_html():
    """A legfrissebb Verkli HTML a szkript mappájából — verziószámtól
    függetlenül a legutóbb módosított fájl nyer, így nem kell a .bat-ot
    minden buildnél átírni."""
    jeloltek = sorted(SAJAT_MAPPA.glob("verkli*.html"),
                      key=lambda p: p.stat().st_mtime, reverse=True)
    return jeloltek[0] if jeloltek else None


def _alap_mappa():
    # Documents / Dokumentumok — ami van; különben a saját mappa
    for nev in ("Documents", "Dokumentumok"):
        p = Path.home() / nev
        if p.is_dir():
            return p / "Verkli" / "letoltesek"
    return Path.home() / "Verkli" / "letoltesek"


LETOLTES_DIR = _alap_mappa()

# ── VÉDELEM ────────────────────────────────────────────────────────────────
# A kiszolgáló csak a saját gépen figyel, DE a böngészőből bármelyik
# megnyitott weboldal is elérné. Ezért minden érdemi művelet
# munkamenet-tokenhez kötött: a token indításkor születik, és csak abba a
# felületbe kerül bele, amit ez a kiszolgáló ad ki. Idegen lap nem
# ismerheti, tehát nem indíthat letöltést és nem futtathat frissítést.
#
# Egyetlen kivétel a /api/pong: annyit árul el, hogy a letöltő fut. Erre
# a webről nyitott Verklinek van szüksége, hogy felajánlhassa az átlépést
# a helyi példányra. Semmilyen adatot nem ad ki és semmit nem indít el.
TOKEN = secrets.token_urlsafe(24)

# ── ÖNFRISSÍTÉS ────────────────────────────────────────────────────────────
# Induláskor a letöltő megnézi, van-e frissebb Verkli felület a szerveren,
# és ha igen, lehozza maga mellé. Így a felhasználónak soha nem kell
# fájlt cserélnie. Üres címmel az egész kimarad.
#
# A cím MUTATHAT KÖZVETLENÜL A FELÜLETRE (ajánlott):
#   FRISSITES_URL = "https://verkli.pelda.hu/index.html"
# Ilyenkor a szerveren elég az index.html — a build számát a fájlból
# olvassuk ki (KV_BUILD), és abból lesz a helyi fájlnév.
#
# Vagy mutathat egy manifestre, ha úgy kényelmesebb:
#   FRISSITES_URL = "https://verkli.pelda.hu/verzio.json"
#   {"build": "Verkli b47", "fajl": "verkli_b47.html"}
FRISSITES_URL = "https://verkli.tzlab.hu/index.html"    # ide a saját címed
FRISSITES = True

# ═══════════════════════════════════════════════════════════════════════════
# KONZOL — log-biztos mód: fix szélesség, ASCII-visszaesés
# ═══════════════════════════════════════════════════════════════════════════

UTF = True
try:
    if os.name == "nt":
        os.system("chcp 65001 >nul 2>&1")
    "▁█".encode(sys.stdout.encoding or "utf-8")
except Exception:
    UTF = False

BOX = ("┌", "─", "┐", "│", "└", "┘") if UTF else ("+", "-", "+", "|", "+", "+")
LAMPA = {"ok": "🟢" if UTF else "[OK]", "nincs": "🔴" if UTF else "[!!]",
         "vár": "🟡" if UTF else "[..]"}


def wlen(szoveg: str) -> int:
    """Hány cellát foglal a szöveg a terminálban?

    A `len()` nem elég: az emoji (🟢) és a kelet-ázsiai karakterek KÉT
    cellát foglalnak, ezért a keret jobb széle elcsúszna. A kombináló
    jelek (ékezet-ráolvasztás) viszont nullát.
    """
    import unicodedata
    n = 0
    for c in szoveg:
        if unicodedata.combining(c):
            continue
        n += 2 if unicodedata.east_asian_width(c) in ("W", "F") else 1
    return n


def wljust(szoveg: str, szeles: int) -> str:
    """Balra igazítás CELLÁRA számolva; ha nem fér, csonkoljuk."""
    h = wlen(szoveg)
    if h <= szeles:
        return szoveg + " " * (szeles - h)
    ki, n = "", 0
    for c in szoveg:
        cw = wlen(c)
        if n + cw > szeles - 1:
            break
        ki += c
        n += cw
    return ki + "…" + " " * max(0, szeles - n - 1)


def wcenter(szoveg: str, szeles: int) -> str:
    h = wlen(szoveg)
    if h >= szeles:
        return wljust(szoveg, szeles)
    bal = (szeles - h) // 2
    return " " * bal + szoveg + " " * (szeles - h - bal)


def panel(cimsor, sorok, szeles=74):
    """Keretezett panel — a szélesség CELLÁBAN értendő, nem karakterben."""
    ki = [BOX[0] + BOX[1] * (szeles - 2) + BOX[2]]
    ki.append(BOX[3] + wcenter(cimsor, szeles - 2) + BOX[3])
    ki.append(BOX[3] + BOX[1] * (szeles - 2) + BOX[3])
    for s in sorok:
        ki.append(BOX[3] + " " + wljust(s, szeles - 4) + " " + BOX[3])
    ki.append(BOX[4] + BOX[1] * (szeles - 2) + BOX[5])
    return "\n".join(ki)


def savszint(szazalek, szeles=24):
    """Százalék-sáv; a szám mindig ott van mellette."""
    tele = int(max(0, min(100, szazalek)) / 100 * szeles)
    if UTF:
        return "█" * tele + "░" * (szeles - tele)
    return "#" * tele + "-" * (szeles - tele)


def naplo(*reszek):
    print(datetime.now().strftime("[%H:%M:%S] ") + " ".join(str(r) for r in reszek), flush=True)


# ═══════════════════════════════════════════════════════════════════════════
# KÖRNYEZET
# ═══════════════════════════════════════════════════════════════════════════

def ytdlp_verzio():
    try:
        import yt_dlp
        return getattr(yt_dlp.version, "__version__", "ismeretlen")
    except Exception:
        return None


def ffmpeg_ut():
    """Hol van ffmpeg? Három helyen keressük, ebben a sorrendben:
    a rendszer útvonalán, a letöltő saját mappájában, és a pipből
    telepíthető imageio-ffmpeg csomagban (ez utóbbi a legegyszerűbb
    pótlás, ha a gépen nincs ffmpeg)."""
    p = shutil.which("ffmpeg")
    if p:
        return p
    for nev in ("ffmpeg.exe", "ffmpeg"):
        c = SAJAT_MAPPA / nev
        if c.is_file():
            return str(c)
    try:
        import imageio_ffmpeg
        u = imageio_ffmpeg.get_ffmpeg_exe()
        if u and Path(u).is_file():
            return u
    except Exception:
        pass
    return None


def ffmpeg_van():
    return ffmpeg_ut() is not None


def ytdlp_frissites():
    """pip install -U yt-dlp — ez a leggyakoribb megoldás a hibás letöltésre."""
    naplo("yt-dlp frissítése…")
    try:
        r = subprocess.run([sys.executable, "-m", "pip", "install", "-U", "yt-dlp"],
                           capture_output=True, text=True, timeout=300, check=False)
        ok = r.returncode == 0
        naplo(("frissítve" if ok else "a frissítés nem sikerült") +
              " — a hatályba lépéshez indítsd újra a letöltőt")
        return ok, (r.stdout or r.stderr or "")[-400:]
    except Exception as e:
        return False, str(e)


# ═══════════════════════════════════════════════════════════════════════════
# URL-JAVÍTÁSOK (az AvideoZ-ből)
# ═══════════════════════════════════════════════════════════════════════════

def javit_url(url: str) -> str:
    m = re.search(r"facebook\.com/reel/(\d+)", url)
    if m:
        return f"https://www.facebook.com/watch/?v={m.group(1)}"
    if "dropbox.com" in url.lower():
        url = re.sub(r"[&?]dl=0", "&dl=1", url)
        if "dl=" not in url:
            url += "&dl=1" if "?" in url else "?dl=1"
    return url


# ═══════════════════════════════════════════════════════════════════════════
# MUNKÁK
# ═══════════════════════════════════════════════════════════════════════════

class Megszakitva(Exception):
    """A felhasználó megszakította a letöltést."""


munkak = {}          # id -> dict
munkak_zar = threading.Lock()


def uj_munka(url, mod):
    mid = uuid.uuid4().hex[:12]
    with munkak_zar:
        munkak[mid] = {
            "id": mid, "url": url, "mod": mod, "allapot": "fut",
            "szazalek": 0.0, "uzenet": "indul…", "cim": url,
            "fajlok": [], "mappa": str(LETOLTES_DIR / mid), "megszakit": False,
            "kezdet": time.time(),
        }
    return mid


def munka(mid):
    with munkak_zar:
        return dict(munkak.get(mid) or {})


def frissit(mid, **mezok):
    with munkak_zar:
        if mid in munkak:
            munkak[mid].update(mezok)


def ydl_opciok(mid, mod, kimenet):
    """A négy üzemmód beállításai. A lassítás a rate-limit ellen véd."""
    kozos = {
        "outtmpl": os.path.join(kimenet, "%(title).80s_%(id)s.%(ext)s"),
        "restrictfilenames": True,
        "noprogress": True,
        "no_color": True,
        "quiet": True,
        "no_warnings": True,
        "ignoreerrors": False,
        "sleep_interval": 2,
        "max_sleep_interval": 6,
        "sleep_interval_requests": 1,
        "progress_hooks": [lambda d: horog(mid, d)],
    }
    ff = ffmpeg_ut()
    if ff and not shutil.which("ffmpeg"):
        kozos["ffmpeg_location"] = ff      # saját mappából vagy csomagból
    feliratok = {
        "writesubtitles": True, "writeautomaticsub": True,
        "subtitleslangs": ["hu", "en"], "subtitlesformat": "srt",
    }
    if mod == "hang":
        kozos.update({
            "format": "bestaudio/best",
            "postprocessors": [{"key": "FFmpegExtractAudio",
                                "preferredcodec": "mp3", "preferredquality": "192"}],
        })
    elif mod == "felirat":
        kozos.update({"skip_download": True})
        kozos.update(feliratok)
    else:
        kozos.update({"format": "bestvideo+bestaudio/best",
                      "merge_output_format": "mp4"})
        if mod == "video_felirat":
            kozos.update(feliratok)
    return kozos


def horog(mid, d):
    """yt-dlp haladás → a munka állapota."""
    if munka(mid).get("megszakit"):
        raise Megszakitva
    if d.get("status") == "downloading":
        teljes = d.get("total_bytes") or d.get("total_bytes_estimate") or 0
        kesz = d.get("downloaded_bytes") or 0
        szaz = (kesz / teljes * 100) if teljes else 0
        frissit(mid, szazalek=round(szaz, 1),
                uzenet=(d.get("_speed_str") or "").strip() or "letöltés…")
    elif d.get("status") == "finished":
        frissit(mid, szazalek=100.0, uzenet="feldolgozás…")


def futtat(mid):
    import yt_dlp
    m = munka(mid)
    kimenet = m["mappa"]
    os.makedirs(kimenet, exist_ok=True)
    url = javit_url(m["url"])
    naplo(f"indul: {url}  [{m['mod']}]")
    try:
        with yt_dlp.YoutubeDL(ydl_opciok(mid, m["mod"], kimenet)) as ydl:
            info = ydl.extract_info(url, download=True)
        cim = (info or {}).get("title") or url
        fajlok = []
        for p in sorted(Path(kimenet).iterdir()):
            if p.is_file() and not p.name.endswith((".part", ".ytdl")):
                fajlok.append({"nev": p.name, "meret": p.stat().st_size})
        if not fajlok:
            frissit(mid, allapot="hiba", uzenet="nem keletkezett fájl")
            naplo(LAMPA["nincs"], "nem keletkezett fájl:", url)
            return
        frissit(mid, allapot="kesz", szazalek=100.0, cim=cim, fajlok=fajlok,
                uzenet=f"{len(fajlok)} fájl")
        ossz = sum(f["meret"] for f in fajlok)
        naplo(LAMPA["ok"], f"kész: {cim}  ({len(fajlok)} fájl, {ossz/1048576:.1f} MB, "
                          f"{time.time()-m['kezdet']:.0f} mp)")
    except Exception as e:
        uz = str(e)[:200]
        frissit(mid, allapot="hiba", uzenet=uz)
        naplo(LAMPA["nincs"], "hiba:", uz)
        if "Sign in to confirm" in uz or "HTTP Error 403" in uz:
            naplo("   tipp: próbáld a yt-dlp frissítését (a Verkliben: Kapcsolat ellenőrzése → Frissítés)")


# ═══════════════════════════════════════════════════════════════════════════
# HTTP
# ═══════════════════════════════════════════════════════════════════════════

class Kezelo(BaseHTTPRequestHandler):
    server_version = "VerkliLetolto/" + VERZIO

    def log_message(self, *a):
        pass    # a saját naplónkat használjuk

    # — segédek —
    # — védelem —
    def sajat_eredet(self):
        """A kérés a saját felületünkről jön-e? (Origin nélküli kérés is jó:
        navigáció, curl, saját lap azonos eredetről.)"""
        o = self.headers.get("Origin")
        if not o:
            return True
        return o in (f"http://{HOST}:{PORT}", f"http://localhost:{PORT}",
                     f"http://[::1]:{PORT}")

    def jo_token(self):
        fejlecben = self.headers.get("X-Verkli-Token")
        if fejlecben and secrets.compare_digest(fejlecben, TOKEN):
            return True
        from urllib.parse import parse_qs
        q = parse_qs(urlparse(self.path).query).get("token", [""])[0]
        return bool(q) and secrets.compare_digest(q, TOKEN)

    def engedelyezett(self):
        """Érdemi művelethez saját eredet ÉS érvényes token kell."""
        if not self.sajat_eredet():
            naplo(LAMPA["nincs"], "idegen eredetű kérés elutasítva:",
                  self.headers.get("Origin"), self.path)
            return False
        if not self.jo_token():
            naplo(LAMPA["nincs"], "token nélküli kérés elutasítva:", self.path)
            return False
        return True

    def fejlec(self, kod=200, tipus="application/json; charset=utf-8", extra=None,
               nyilt=False):
        self.send_response(kod)
        self.send_header("Content-Type", tipus)
        # CORS csak a nyilvános végpontra; minden másra a saját eredet kell
        o = self.headers.get("Origin")
        if nyilt:
            self.send_header("Access-Control-Allow-Origin", "*")
        elif o and self.sajat_eredet():
            self.send_header("Access-Control-Allow-Origin", o)
            self.send_header("Vary", "Origin")
        self.send_header("Access-Control-Allow-Headers", "Content-Type, X-Verkli-Token")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
        self.send_header("Cache-Control", "no-store")
        for k, v in (extra or {}).items():
            self.send_header(k, v)
        self.end_headers()

    def tiltva(self):
        self.fejlec(403)
        self.wfile.write(json.dumps({"hiba": "nincs jogosultság"}).encode("utf-8"))

    def json_valasz(self, adat, kod=200):
        self.fejlec(kod)
        self.wfile.write(json.dumps(adat, ensure_ascii=False).encode("utf-8"))

    def do_OPTIONS(self):
        nyilt = urlparse(self.path).path == "/api/pong"
        self.fejlec(204, "text/plain", nyilt=nyilt)

    def do_GET(self):
        ut = urlparse(self.path).path

        # nyilvános: csak annyit mond, hogy fut — ez kell a webes Verkli
        # „átlépés a helyi példányra" felajánlásához
        if ut == "/api/pong":
            self.fejlec(200, nyilt=True)
            self.wfile.write(json.dumps({"verkli": True, "verzio": VERZIO})
                             .encode("utf-8"))
            return

        if ut == "/api/allapot":
            if not self.engedelyezett():
                self.tiltva()
                return
            self.json_valasz({
                "verkli": True, "verzio": VERZIO,
                "ytdlp": ytdlp_verzio(), "ffmpeg": ffmpeg_van(),
                "mappa": str(LETOLTES_DIR),
            })
            return

        m = re.match(r"^/api/allapot/([0-9a-f]+)$", ut)
        if m:
            if not self.engedelyezett():
                self.tiltva()
                return
            j = munka(m.group(1))
            if not j:
                self.json_valasz({"hiba": "nincs ilyen munka"}, 404)
                return
            self.json_valasz({k: j[k] for k in
                              ("id", "allapot", "szazalek", "uzenet", "cim", "fajlok")})
            return

        # — a Verkli felülete a saját mappából —
        if ut in ("/", "/index.html"):
            f = verkli_html()
            if not f:
                self.fejlec(404, "text/html; charset=utf-8")
                self.wfile.write("<h1>Nincs Verkli HTML a letöltő mellett.</h1>"
                                 .encode("utf-8"))
                return
            adat = f.read_bytes()
            # A token CSAK ebbe a lapba kerül bele — így az általunk kiadott
            # felület tud dolgozni, idegen lap viszont nem.
            jel = b"<head>"
            if jel in adat[:4000]:
                belovo = ('<script>window.VERKLI_TOKEN=' +
                          json.dumps(TOKEN) + ';</script>').encode("utf-8")
                adat = adat.replace(jel, jel + belovo, 1)
            self.fejlec(200, "text/html; charset=utf-8",
                        {"Content-Length": str(len(adat))})
            self.wfile.write(adat)
            return

        if re.match(r"^/[\w.\-]+\.(html|css|js|png|jpg|svg|ico|py|bat)$", ut):
            cel = (SAJAT_MAPPA / unquote(ut.lstrip("/"))).resolve()
            if str(cel).startswith(str(SAJAT_MAPPA)) and cel.is_file():
                tipus = mimetypes.guess_type(cel.name)[0] or "application/octet-stream"
                adat = cel.read_bytes()
                self.fejlec(200, tipus, {"Content-Length": str(len(adat))})
                self.wfile.write(adat)
                return

        m = re.match(r"^/api/fajl/([0-9a-f]+)/(.+)$", ut)
        if m:
            if not self.engedelyezett():
                self.tiltva()
                return
            j = munka(m.group(1))
            nev = unquote(m.group(2))
            if not j:
                self.json_valasz({"hiba": "nincs ilyen munka"}, 404)
                return
            # csak a munka saját mappájából, könyvtár-kilépés nélkül
            cel = (Path(j["mappa"]) / nev).resolve()
            if not str(cel).startswith(str(Path(j["mappa"]).resolve())) or not cel.is_file():
                self.json_valasz({"hiba": "nincs ilyen fájl"}, 404)
                return
            tipus = mimetypes.guess_type(cel.name)[0] or "application/octet-stream"
            self.fejlec(200, tipus, {"Content-Length": str(cel.stat().st_size)})
            with open(cel, "rb") as f:
                shutil.copyfileobj(f, self.wfile)
            return

        self.json_valasz({"hiba": "ismeretlen cím"}, 404)

    def do_POST(self):
        ut = urlparse(self.path).path
        if not self.engedelyezett():      # minden módosító művelet védett
            self.tiltva()
            return
        hossz = int(self.headers.get("Content-Length") or 0)
        try:
            test = json.loads(self.rfile.read(hossz) or b"{}")
        except Exception:
            test = {}

        if ut == "/api/letoltes":
            url = (test.get("url") or "").strip()
            mod = test.get("mod") or "video"
            if not re.match(r"^https?://", url):
                self.json_valasz({"hiba": "hibás cím"}, 400)
                return
            if not ytdlp_verzio():
                self.json_valasz({"hiba": "a yt-dlp nincs telepítve"}, 500)
                return
            mid = uj_munka(url, mod)
            threading.Thread(target=futtat, args=(mid,), daemon=True).start()
            self.json_valasz({"id": mid})
            return

        m = re.match(r"^/api/megszakit/([0-9a-f]+)$", ut)
        if m:
            frissit(m.group(1), megszakit=True, allapot="hiba", uzenet="megszakítva")
            naplo("megszakítva:", m.group(1))
            self.json_valasz({"ok": True})
            return

        if ut == "/api/frissites":
            ok, kimenet = ytdlp_frissites()
            self.json_valasz({"ok": ok, "kimenet": kimenet, "ytdlp": ytdlp_verzio()})
            return

        self.json_valasz({"hiba": "ismeretlen cím"}, 404)


# ═══════════════════════════════════════════════════════════════════════════
# INDULÁS
# ═══════════════════════════════════════════════════════════════════════════

def build_szam(tartalom: bytes) -> str | None:
    """A felület build-száma a KV_BUILD konstansból (pl. 'Verkli b47')."""
    m = re.search(rb"KV_BUILD\s*=\s*'([^']{1,40})'", tartalom)
    return m.group(1).decode("utf-8", "ignore") if m else None


def helyi_build() -> str | None:
    f = verkli_html()
    if not f:
        return None
    return build_szam(f.read_bytes())


def frissites_ellenorzes():
    """Van-e újabb Verkli felület a szerveren? Ha igen, lehozzuk.

    A cím mutathat közvetlenül a felületre (ilyenkor a build-számot a
    fájlból olvassuk ki), vagy egy manifestre. Hálózat nélkül csendben
    kimarad — a letöltő enélkül is működik.
    """
    if not FRISSITES or not FRISSITES_URL:
        return None
    import urllib.request
    try:
        with urllib.request.urlopen(FRISSITES_URL, timeout=8) as r:
            adat = r.read()

        # — manifest-ág —
        if FRISSITES_URL.rstrip("/").lower().endswith(".json"):
            j = json.loads(adat.decode("utf-8"))
            nev = j.get("fajl")
            if not nev or not re.match(r"^verkli[\w.\-]*\.html$", nev):
                return None
            if (SAJAT_MAPPA / nev).exists():
                return None
            cim = FRISSITES_URL.rsplit("/", 1)[0] + "/" + nev
            with urllib.request.urlopen(cim, timeout=30) as r:
                adat = r.read()
        # — közvetlen felület-ág: a letöltött lap MAGA a friss felület —
        else:
            if b"<html" not in adat[:2000].lower():
                naplo("a frissítési cím nem HTML-t adott, kihagyva")
                return None
            build = build_szam(adat)
            if not build:
                naplo("a letöltött lapban nincs build-szám, kihagyva")
                return None
            if build == helyi_build():
                return None                      # már ez van nálunk
            nev = re.sub(r"\W+", "_", build).strip("_").lower() + ".html"

        if len(adat) < 10000:
            naplo(LAMPA["nincs"], "a letöltött fájl túl kicsi, eldobva")
            return None
        if verkli_html() is None:
            naplo("nincs helyi felület — lehozzuk a szerverről")
        (SAJAT_MAPPA / nev).write_bytes(adat)
        naplo(LAMPA["ok"], "frissítve:", nev, f"({len(adat)/1024:.0f} kB)")
        return nev
    except Exception as e:
        naplo("frissítés-ellenőrzés kimaradt:", str(e)[:80])
        return None


def main():
    frissites_ellenorzes()
    yv = ytdlp_verzio()
    ffu = ffmpeg_ut()
    ff = ffu is not None
    os.makedirs(LETOLTES_DIR, exist_ok=True)

    html = verkli_html()
    sorok = [
        f"NYISD MEG:  http://{HOST}:{PORT}",
        f"felület:  {html.name if html else 'nincs verkli*.html a mappában!'}",
        f"letöltés: {LETOLTES_DIR}",
        "",
        f"{LAMPA['ok'] if yv else LAMPA['nincs']} yt-dlp   {yv or 'NINCS TELEPÍTVE  →  pip install -U yt-dlp'}",
        f"{LAMPA['ok'] if ff else LAMPA['vár']} ffmpeg   "
        f"{(Path(ffu).name + ' (' + ('rendszer' if shutil.which('ffmpeg') else 'saját') + ')') if ff else 'hiányzik — MP3 es jo minoseg nem lesz'}",
        "",
        "A böngésző magától megnyílik. A Letöltés fülön zöld pötty a jó jel.",
        "Csak az innen megnyitott felület vezérelheti — idegen lap nem.",
        "Innen a munkamappa és a pendrive-másolás is működik.",
        "Leállítás: Ctrl+C",
    ]
    print()
    print(panel(f"VERKLI LETÖLTŐ {VERZIO}  ·  TZlab", sorok))
    print()

    if not yv:
        naplo("A yt-dlp hiányzik. Telepítés:  pip install -U yt-dlp")

    try:
        szerver = ThreadingHTTPServer((HOST, PORT), Kezelo)
    except OSError as e:
        if getattr(e, "errno", None) in (48, 98, 10048):
            print(panel("MÁR FUT", [
                f"A {PORT}-es kapu foglalt — alighanem már fut egy Verkli letöltő.",
                "",
                "Nézd meg a tálcán vagy a másik ablakban. Ha nem találod,",
                "zárd be az összes fekete ablakot, és indítsd újra.",
            ]))
        else:
            print(panel("NEM INDULT EL", [str(e)]))
        sys.exit(1)

    try:
        szerver.serve_forever()
    except KeyboardInterrupt:
        print()
        kesz = sum(1 for m in munkak.values() if m["allapot"] == "kesz")
        rossz = sum(1 for m in munkak.values() if m["allapot"] == "hiba")
        print(panel("ÖSSZEGZÉS", [
            f"letöltött tételek: {kesz}",
            f"sikertelen:        {rossz}",
            f"fájlok helye:      {LETOLTES_DIR}",
        ]))
        naplo("leállítva.")


if __name__ == "__main__":
    main()
