# -*- coding: utf-8 -*-
"""
protect_apk.py - FINAL VERSION
Full flow: Login -> Upload APK -> Poll until done -> Download protected APK
Usage: python protect_apk.py
"""
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

import requests, urllib3, re, time, os, json
urllib3.disable_warnings()

# ── CONFIG ────────────────────────────────────────────────
USERNAME = "slex"
PASSWORD = "pppp0000"
BASE     = "https://dexshellx.com"
APK_PATH = "apk.apk"
OUTPUT   = "protected_result.apk"

# Filters
FILTERS = "!/\ncom/\n_COROUTINE/\nteacher/**"

POLL_INTERVAL = 15   # seconds between status checks
MAX_WAIT      = 900  # 15 minutes max

BASE_HEADERS = {
    "User-Agent":      "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "Accept":          "text/html,application/xhtml+xml,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
}

# ─────────────────────────────────────────────────────────
def log(msg):
    print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)

s = requests.Session()
s.verify = False

# ── STEP 1: Homepage → JSESSIONID ────────────────────────
log("STEP 1: Getting session cookie...")
r = s.get(BASE + "/", headers=BASE_HEADERS, timeout=20)
log(f"  HTTP {r.status_code} | URL: {r.url} | Cookie: {dict(s.cookies)}")

# ── STEP 2: Login ────────────────────────────────────────
log("STEP 2: Logging in...")
s.get(BASE + "/dex/login", headers=BASE_HEADERS, timeout=20)

login_h = {**BASE_HEADERS,
    "Content-Type":   "application/x-www-form-urlencoded",
    "Origin":         BASE,
    "Referer":        BASE + "/dex/login",
    "Sec-Fetch-Site": "same-origin",
    "Sec-Fetch-Mode": "navigate",
}
r2 = s.post(BASE + "/dex/login",
            data={"user": USERNAME, "pass": PASSWORD, "language": "en_US"},
            headers=login_h, timeout=20, allow_redirects=True)
log(f"  HTTP {r2.status_code} | URL: {r2.url}")

if "/login" in r2.url:
    log("  [FAIL] Login failed! Check credentials.")
    sys.exit(1)
log("  [OK] Logged in!")

# ── STEP 3: Upload APK ───────────────────────────────────
log("STEP 3: Uploading APK...")
log(f"  File: {APK_PATH} ({os.path.getsize(APK_PATH):,} bytes)")

protect_url = BASE + "/dex/protection/options"
upload_h = {
    "User-Agent":      BASE_HEADERS["User-Agent"],
    "Accept":          "text/html,application/xhtml+xml,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Origin":          BASE,
    "Referer":         BASE + "/dex/protection/options?mode=standard",
    "Sec-Fetch-Site":  "same-origin",
    "Sec-Fetch-Mode":  "navigate",
    "Sec-Fetch-Dest":  "document",
}

t0 = time.time()
with open(APK_PATH, "rb") as apk:
    files = {"apkFile": ("apk.apk", apk, "application/vnd.android.package-archive")}
    data = {
        # Hidden fields
        "mode":          "standard",
        "userModeValue": "false",   # "No" = app STOPS if root/runtime checks fail
        "signatureAlias": "android",
        # Filter textareas — same value for all 3 (nativeLibFilters intentionally blank)
        "stringFilters":    FILTERS,   # String Encryption filters
        "hideFilters":      FILTERS,   # Hide Access filters
        "classFilters":     FILTERS,   # Class Encryption filters
        # All checkboxes (CORRECT names from form inspection)
        "optimize":          "on",
        "stripLogging":      "on",
        "crashHandler":      "on",
        "webViewSupport":    "on",
        "manifestMangling":  "on",
        "assets":            "on",
        "res":               "on",
        "nameObf":           "on",
        "root":              "on",
        "strings":           "on",
        "annotationEnc":     "on",
        "stringEnc":         "on",
        "hideAccess":        "on",
        "classEnc":          "on",
        "jniObf":            "on",
        "nativeLib":         "on",
        "antiAssinatura":    "on",
        "antiEmulator":      "on",
        "antiXposed":        "on",
        "antiManual":        "on",
        "antiDev":           "on",
        "uiProtection":      "on",
        "runtimeChecks":     "on",
    }
    r3 = s.post(protect_url, headers=upload_h, files=files, data=data,
                timeout=120, allow_redirects=True)

upload_time = round(time.time() - t0, 1)
log(f"  HTTP {r3.status_code} | Time: {upload_time}s | URL: {r3.url}")

# Extract process ID from redirect URL
m = re.search(r'process/detail\?id=(\d+)', r3.url)
if not m:
    m = re.search(r'data-process-id=["\'](\d+)["\']', r3.text)
if not m:
    log("  [FAIL] No process ID found in response!")
    with open("fail_response.html", "w", encoding="utf-8", errors="replace") as f:
        f.write(r3.text)
    log("  Saved fail_response.html for debug")
    sys.exit(1)

process_id = m.group(1)
log(f"  [OK] Process created! ID: {process_id}")

# Extract download token from initial page
apk_download_url = None
m_dl = re.search(r'href=["\'](/dex/process/download\?id=' + process_id + r'&(?:amp;)?type=apk&(?:amp;)?token=[^"\']+)["\']', r3.text)
if m_dl:
    token_url = m_dl.group(1).replace('&amp;', '&')
    apk_download_url = BASE + token_url
    log(f"  Download URL captured: {token_url[:80]}...")

# ── STEP 4: Poll status until DONE / ERROR ───────────────
log("STEP 4: Polling for completion...")
status_url  = BASE + f"/dex/process/detail/data?id={process_id}"
detail_url  = BASE + f"/dex/process/detail?id={process_id}"
status_h    = {**BASE_HEADERS, "Accept": "application/json, text/plain, */*", "Referer": detail_url}

start = time.time()
last_status = ""

while True:
    elapsed = int(time.time() - start)
    if elapsed > MAX_WAIT:
        log(f"  [TIMEOUT] Waited {MAX_WAIT}s — giving up.")
        sys.exit(1)

    try:
        # Try JSON status endpoint first
        rs = s.get(status_url, headers=status_h, timeout=20)
        status = "UNKNOWN"

        if rs.headers.get("Content-Type", "").startswith("application/json"):
            try:
                jd = rs.json()
                status = jd.get("status", jd.get("state", "UNKNOWN")).upper()
                if status != last_status:
                    log(f"  [{elapsed}s] Status: {status} | JSON: {json.dumps(jd)[:200]}")
                    last_status = status
            except Exception:
                pass
        else:
            # Fallback: parse HTML for status
            sm = re.search(r'data-status=["\']([^"\']+)["\']', rs.text)
            if sm:
                status = sm.group(1).upper()
            if status != last_status:
                log(f"  [{elapsed}s] Status: {status} (from HTML)")
                last_status = status

        if status in ("DONE", "SUCCESS", "FINISHED", "COMPLETED"):
            log(f"  [OK] Processing complete after {elapsed}s!")
            break
        elif status in ("ERROR", "FAILED", "FAILURE"):
            log(f"  [FAIL] Processing failed! Status: {status}")
            # Try to get download URL from detail page anyway
            rdp = s.get(detail_url, headers=BASE_HEADERS, timeout=20)
            m_err = re.search(r'href=["\'](/dex/process/download\?id=' + process_id + r'&(?:amp;)?type=error[^"\']*)["\']', rdp.text)
            if m_err:
                err_url = BASE + m_err.group(1).replace('&amp;', '&')
                re_dl = s.get(err_url, headers=BASE_HEADERS, timeout=30)
                with open("error_log.txt", "wb") as ef:
                    ef.write(re_dl.content)
                log("  Error log saved to: error_log.txt")
            sys.exit(1)

    except Exception as e:
        log(f"  [WARN] Poll error: {e}")

    time.sleep(POLL_INTERVAL)

# ── STEP 5: Download protected APK ───────────────────────
log("STEP 5: Downloading protected APK...")

# Get fresh download URL from detail page
rdp = s.get(detail_url, headers=BASE_HEADERS, timeout=20)
m_dl2 = re.search(r'href=["\'](/dex/process/download\?id=' + process_id + r'&(?:amp;)?type=apk&(?:amp;)?token=[^"\']+)["\']', rdp.text)

if m_dl2:
    apk_download_url = BASE + m_dl2.group(1).replace('&amp;', '&')
    log(f"  Download URL: {apk_download_url[:100]}...")
elif not apk_download_url:
    log("  [FAIL] Could not find download URL!")
    with open("detail_page.html", "w", encoding="utf-8", errors="replace") as f:
        f.write(rdp.text)
    log("  Saved detail_page.html for debug")
    sys.exit(1)

dl_h = {**BASE_HEADERS, "Referer": detail_url, "Accept": "application/octet-stream,*/*"}
rd = s.get(apk_download_url, headers=dl_h, timeout=120, stream=True)
log(f"  Download HTTP {rd.status_code} | Content-Type: {rd.headers.get('Content-Type','?')}")

total = 0
with open(OUTPUT, "wb") as out:
    for chunk in rd.iter_content(chunk_size=65536):
        if chunk:
            out.write(chunk)
            total += len(chunk)

log(f"  [OK] Saved: {OUTPUT} ({total:,} bytes)")
log("")
log("=" * 55)
log("  PROTECTION COMPLETE!")
log(f"  Input:  {APK_PATH} ({os.path.getsize(APK_PATH):,} bytes)")
log(f"  Output: {OUTPUT}  ({total:,} bytes)")
log("=" * 55)
