#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
============================================================
 Ency Grabber Desktop — سحب ملفات DevUploads (أداة مكتبية)
============================================================
 الاستخدام:
   python3 devupload_grabber.py https://devuploads.com/xxxxxxx
   python3 devupload_grabber.py URL --out ./downloads
   python3 devupload_grabber.py URL --cookie "cf_clearance=..." --ua "Mozilla/5.0 ..."

 الميزات:
   • يمر السلسلة كاملة: صفحة الملف ← صفحة المقال ← توليد الرابط ← تحميل الملف
   • بصمة متصفح حقيقية (Safari/Firefox TLS) عبر curl_cffi — تعبر معظم فحوص Cloudflare
   • إذا ظهر تحدي Cloudflare: مرّر كوكيز متصفحك (--cookie) من نفس الجهاز وستعمل
   • يعمل على ويندوز/لينكس/ماك — يتطلب: pip install curl_cffi
============================================================
"""
import argparse
import os
import re
import sys
import time

try:
    from curl_cffi import requests as cr
except ImportError:
    print("❌ ينقصcurl_cffi — ثبّته أولاً:  pip install curl_cffi")
    sys.exit(1)

IMPERSONATE_CANDIDATES = ["safari184", "safari180", "firefox135"]
UA_SAFARI = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 "
             "(KHTML, like Gecko) Version/17.4 Safari/605.1.15")
BAD_RE = re.compile(
    r"(clickadu|propeller|doubleclick|googlesynd|adservice|facebook|twitter|t\.me"
    r"|telegram|google\.|bing\.|youtube|exoclick|juicyads|adskeeper|otieu|publify)", re.I)
FILE_RE = re.compile(r"/d/|/download/|/files?/", re.I)
EXT_RE = re.compile(
    r"\.(apk|xapk|apkm|zip|rar|7z|exe|ipa|pdf|mp3|mp4|mkv|avi|epub|m4b|iso|tar|gz"
    r"|txt|jpg|jpeg|png|webm|doc|docx)([?#]|$)", re.I)

WAIT_SECONDS = 20
HTTP_TIMEOUT = 90


def c(text, code):
    """ألوان بسيطة للطرفية"""
    if not sys.stdout.isatty():
        return text
    return f"\033[{code}m{text}\033[0m"


def info(msg):  print(c("  • ", "36") + msg)
def okmsg(msg): print(c("  ✅ ", "32") + msg)
def errmsg(msg): print(c("  ❌ ", "31") + msg)
def step(msg):  print(c(f"━━ {msg}", "1;33"))


def absolute(u, base):
    if re.match(r"^https?://", u, re.I):
        return u
    from urllib.parse import urljoin
    return urljoin(base, u)


def is_challenge(status, body):
    if status in (403, 503, 429):
        low = body[:6000].lower()
        return ("just a moment" in low or "challenge-platform" in low
                or "cf-browser-verification" in low)
    return False


def find_direct_link(html, base):
    # 1) meta refresh
    m = re.search(r'<meta[^>]+http-equiv=["\']?refresh["\']?[^>]+content=["\']?[^"\'>]*url=([^"\'>]+)', html, re.I)
    if m:
        u = absolute(m.group(1).strip().strip("'\""), base)
        if u and not BAD_RE.search(u):
            return u
    # 2) توجيهات JS
    for p in (r"location(?:\.href)?\s*=\s*[\"']([^\"']+)[\"']",
              r"location\.replace\s*\(\s*[\"']([^\"']+)[\"']\s*\)",
              r"location\.assign\s*\(\s*[\"']([^\"']+)[\"']\s*\)"):
        m = re.search(p, html, re.I)
        if m:
            u = absolute(m.group(1), base)
            if u and not BAD_RE.search(u) and (FILE_RE.search(u) or EXT_RE.search(u)):
                return u
    # 3) روابط <a>
    for m in re.finditer(r'<a[^>]+href=["\']([^"\']+)["\']', html, re.I):
        u = absolute(m.group(1), base)
        if u and not BAD_RE.search(u) and (FILE_RE.search(u) or EXT_RE.search(u)):
            return u
    # 4) أي رابط /d/
    m = re.search(r"https?://[^\s\"'<>]+/d/[^\s\"'<>]+", html, re.I)
    if m and not BAD_RE.search(m.group(0)):
        return m.group(0)
    return None


def grab(link, out_dir, cookie=None, ua=None, wait=WAIT_SECONDS):
    os.makedirs(out_dir, exist_ok=True)
    jar = cr.Cookies()
    if cookie:
        # حقن كوكيز متصفح المستخدم (مفيد عند ظهور تحدي Cloudflare)
        domain = re.match(r"https?://([^/]+)", link).group(1)
        for part in cookie.split(";"):
            if "=" in part:
                k, v = part.strip().split("=", 1)
                jar.set(k, v, domain=domain)

    imp = ua  # placeholder to avoid lint confusion
    s = None
    last_err = None
    for cand in IMPERSONATE_CANDIDATES:
        try:
            s = cr.Session(impersonate=cand, cookies=jar)
            info(f"تجربة بصمة المتصفح: {cand}")
            r = s.get(link, timeout=HTTP_TIMEOUT, allow_redirects=True)
            if not is_challenge(r.status_code, r.text):
                break
            last_err = "challenge"
            info("ظهر تحقق Cloudflare بهذه البصمة — نجرّب أخرى…")
            s = None
        except Exception as e:
            last_err = str(e)
            s = None
    if s is None:
        errmsg("تعذر فتح صفحة الملف دون تحدي Cloudflare.")
        print()
        print(c("الحل المضمون:", "1;33"))
        print("  1) افتح رابط الملف في متصفحك وانتظر حتى تتحقق الصفحة")
        print("  2) انسخ كوكي cf_clearance من أدوات المطور (F12 → Application → Cookies)")
        print("  3) أعد تشغيل الأمر مع:  --cookie \"cf_clearance=...\" --ua \"نفس User-Agent متصفحك\"")
        print("  أو استخدم سكريبت المتصفح مع grab.php — يعمل دائماً")
        return 2

    # ---- الخطوة 1: صفحة الملف ----
    step("الخطوة 1/4: فتح صفحة الملف")
    r1 = s.get(link, timeout=HTTP_TIMEOUT,
               headers={"Accept": "text/html,application/xhtml+xml,*/*;q=0.8",
                        "Accept-Language": "en-US,en;q=0.9"})
    if is_challenge(r1.status_code, r1.text):
        errmsg("Cloudflare يمنع صفحة الملف من هذا الجهاز — استخدم --cookie كما في الشرح أعلاه")
        return 2
    if r1.status_code != 200:
        errmsg(f"صفحة الملف أعادت كود {r1.status_code}")
        return 1
    html = r1.text
    m = re.search(r'<form id="downloadpage".*?</form>', html, re.S)
    if not m:
        if "file not found" in html.lower():
            errmsg("الملف غير موجود أو حُذف من DevUploads")
        else:
            errmsg("لم يُعثر على نموذج الملف — تأكد أن الرابط صحيح")
        return 1
    fields = {}
    for tag in re.findall(r"<input[^>]+>", m.group(0)):
        nm = re.search(r'name=["\']([^"\']+)["\']', tag)
        vl = re.search(r'value=["\']([^"\']*)["\']', tag)
        if nm:
            fields[nm.group(1)] = vl.group(1) if vl else ""
    title = fields.get("title", "")
    tm = re.search(r"var\s+filename\s*=\s*['\"]([^'\"]+)['\"]", html)
    if tm:
        title = tm.group(1)
    size = fields.get("size", "")
    okmsg(f"الملف: {title or '؟'} ({size or '؟'})")

    site_id = 3 if re.search(r"\.(apk|xapk)\s*['\"]?$", title or "", re.I) else 2
    fields["ransite"] = str(site_id)
    article = ""
    lm = re.search(r"links:\s*\[(.*?)\]\s*,\s*id:\s*" + str(site_id), html, re.S)
    if lm:
        am = re.search(r"[\"']?(https?://[^\"',\s]+)[\"']?", lm.group(1))
        if am:
            article = am.group(1)
    if not article:
        errmsg("لم يُعثر على رابط صفحة التوليد في الصفحة")
        return 1

    # ---- الخطوة 2: صفحة المقال ----
    step("الخطوة 2/4: إرسال بيانات الملف لصفحة التوليد")
    r2 = s.post(article, data=fields, timeout=HTTP_TIMEOUT,
                headers={"Referer": link, "Origin": re.match(r"(https?://[^/]+)", link).group(1),
                         "Accept": "text/html,application/xhtml+xml,*/*;q=0.8"})
    if is_challenge(r2.status_code, r2.text):
        errmsg("Cloudflare يمنع صفحة التوليد — استخدم --cookie أو سكريبت المتصفح")
        return 2
    f2m = re.search(r'<form[^>]+id="dlform"[^>]*>(.*?)</form>', r2.text, re.S)
    if not f2m:
        errmsg("صفحة التوليد تغيّر تنسيقها")
        return 1
    act = re.search(r'<form[^>]+action=["\']([^"\']+)["\']', f2m.group(0), re.I)
    post_url = absolute(act.group(1), str(r2.url)) if act else link
    f2 = {}
    for tag in re.findall(r"<input[^>]+>", f2m.group(1)):
        nm = re.search(r'name=["\']([^"\']+)["\']', tag)
        vl = re.search(r'value=["\']([^"\']*)["\']', tag)
        if nm:
            f2[nm.group(1)] = vl.group(1) if vl else ""

    # ---- الخطوة 3: انتظار + توليد الرابط ----
    step(f"الخطوة 3/4: انتظار {wait} ثانية (مثل عدّاد الموقع) ثم توليد الرابط")
    for i in range(wait, 0, -1):
        print(f"\r     ⏱️  {i:3d} ثانية", end="", flush=True)
        time.sleep(1)
    print("\r" + " " * 30 + "\r", end="")
    # الترويسات مثل المتصفح تماماً: النموذج يقع في صفحة المقال
    adom = re.match(r"(https?://[^/]+)", article).group(1)
    r3 = s.post(post_url, data=f2, timeout=HTTP_TIMEOUT,
                headers={"Referer": article, "Origin": adom,
                         "Accept": "text/html,application/xhtml+xml,*/*;q=0.8"})
    if is_challenge(r3.status_code, r3.text):
        errmsg("Cloudflare يمنع طلب توليد الرابط (403) من هذا الجهاز.")
        print()
        print(c("لماذا؟ حماية الموقع تشدّد الحماية على خطوة التوليد تحديداً.", "90"))
        print(c("الحل المضمون 100%: سكريبت المتصفح مع grab.php — الخطوة المحمية تعمل من متصفحك أنت.", "1;33"))
        print(c("أو: مرر كوكيز متصفحك: --cookie \"cf_clearance=...\" --ua \"...\"", "1;33"))
        return 2
    direct = find_direct_link(r3.text, post_url)
    if not direct:
        errmsg("لم يُعثر على الرابط المباشر في الاستجابة")
        return 1
    okmsg(f"الرابط المباشر: {direct}")

    # ---- الخطوة 4: تحميل الملف ----
    step("الخطوة 4/4: تحميل الملف إلى جهازك")
    name = title or os.path.basename(absolute(direct, post_url).split("?")[0]) or "file"
    name = re.sub(r"[^A-Za-z0-9._()\- ]+", "_", name).strip() or "file"
    dest = os.path.join(out_dir, name)
    i = 1
    while os.path.exists(dest):
        root, ext = os.path.splitext(name)
        dest = os.path.join(out_dir, f"{root}({i}){ext}")
        i += 1
    tmp = dest + ".part"
    resp = s.get(direct, stream=True, timeout=0,
                 headers={"Referer": str(r3.url)})
    total = int(resp.headers.get("content-length", 0) or 0)
    done = 0
    with open(tmp, "wb") as f:
        for chunk in resp.iter_content(256 * 1024):
            f.write(chunk)
            done += len(chunk)
            if total:
                pct = done * 100 // total
                print(f"\r     ⬇️  {done/1048576:.1f}/{total/1048576:.1f} MB ({pct}%)", end="", flush=True)
    print()
    resp.close()
    os.replace(tmp, dest)
    okmsg(f"تم الحفظ: {dest} ({os.path.getsize(dest)/1048576:.1f} MB)")
    return 0


def main():
    ap = argparse.ArgumentParser(description="Ency Grabber Desktop — سحب ملفات DevUploads")
    ap.add_argument("url", help="رابط صفحة الملف في devuploads.com")
    ap.add_argument("--out", default="./downloads", help="مجلد الحفظ (افتراضياً ./downloads)")
    ap.add_argument("--cookie", default="", help='كوكيز المتصفح "cf_clearance=..." عند ظهور التحدي')
    ap.add_argument("--ua", default="", help="User-Agent نفسه متصفحك (مع --cookie)")
    ap.add_argument("--wait", type=int, default=WAIT_SECONDS, help="ثواني الانتظار قبل التوليد")
    a = ap.parse_args()
    if not re.match(r"^https?://", a.url, re.I):
        errmsg("رابط غير صالح")
        return 1
    print(c("━━━━━━━━━━ Ency Grabber Desktop ━━━━━━━━━━", "1;35"))
    return grab(a.url, a.out, a.cookie or None, a.ua or None, a.wait)


if __name__ == "__main__":
    sys.exit(main())
