#!/usr/bin/env python3.12
"""Unduh folder publik MEGA (public folder link) tanpa akun.

CATATAN PENTING (dari uji 18 Sep 2026):
- Untuk LINK FOLDER publik, kunci node tiap anak dienkripsi dengan KUNCI LINK (bukan kunci induk).
- Kunci AES node = 4 int pertama XOR 4 int kedua (decrypt_key lalu XOR pasangan).
- List isi folder: POST https://g.api.mega.co.nz/cs?id=0&n=<handle>
  body [{"a":"f","c":1,"r":1,"ca":1,"p":"<handle>"}]  (tanpa sid, cukup ikut di URL)
Usage:
  python3.12 mega_public_pull.py list
  python3.12 mega_public_pull.py download [dest_dir]
"""
import os, sys, json
import requests
from mega.crypto import (base64_to_a32, base64_url_decode, decrypt_attr,
                         decrypt_key, a32_to_str)
from Crypto.Cipher import AES
from Crypto.Util import Counter

HANDLE = "HRMAlYzK"
KEY = "IJeN5r2FcnfkWLt7w4ZfzQ"
API = "https://g.api.mega.co.nz/cs"
DEST_DEFAULT = "/home/mantapman/.hermes/profiles/atun/workspace/tsc_stock_baru"


def aes_key_from(material):
    """material = daftar int a32 hasil decrypt_key; AES key = 4 pertama XOR 4 kedua."""
    if len(material) >= 8:
        return (material[0] ^ material[4], material[1] ^ material[5],
                material[2] ^ material[6], material[3] ^ material[7])
    return tuple(material[:4])


def list_tree():
    r = requests.post(f"{API}?id=0&n={HANDLE}",
                      data=json.dumps([{"a": "f", "c": 1, "r": 1, "ca": 1, "p": HANDLE}]),
                      timeout=90)
    r.raise_for_status()
    data = r.json()
    nodes = data[0]["f"] if isinstance(data, list) else data["f"]
    link_key = tuple(base64_to_a32(KEY))
    mats = {}          # handle -> material a32 (4 atau 8 int) hasil dekripsi 'k'
    for n in nodes:
        parts = [p.split(":", 1)[1] for p in (n.get("k") or "").split("/") if ":" in p]
        for part in parts:
            for base_key in (link_key, mats.get(n.get("p"))):
                if base_key is None:
                    continue
                try:
                    mat = decrypt_key(base64_to_a32(part), base_key)
                    nm = decrypt_attr(base64_url_decode(n.get("a", "")), aes_key_from(mat))
                    if nm and "n" in nm:
                        mats[n["h"]] = mat
                        break
                except Exception:
                    continue
            if n["h"] in mats:
                break
    out = []
    for n in nodes:
        mat = mats.get(n["h"])
        name = None
        if mat:
            try:
                name = decrypt_attr(base64_url_decode(n.get("a", "")), aes_key_from(mat)).get("n")
            except Exception:
                name = None
        out.append({"h": n["h"], "p": n.get("p"), "type": n.get("t"),
                    "size": n.get("s"), "name": name, "mat": list(mat) if mat else None})
    return out


def download_file(handle, mat, dest, name):
    r = requests.post(f"{API}?id=0&n={handle}",
                      data=json.dumps([{"a": "g", "g": 1, "p": handle}]), timeout=90)
    url = r.json()[0]["g"]
    if len(mat) >= 8:
        aes_key = a32_to_str(aes_key_from(mat))
        iv = (mat[4], mat[5])
    else:
        aes_key = a32_to_str(mat[:4])
        iv = (0, 0)
    ctr = Counter.new(128, initial_value=((iv[0] << 32) + iv[1]) << 64)
    cipher = AES.new(aes_key, AES.MODE_CTR, counter=ctr)
    path = os.path.join(dest, name)
    with requests.get(url, stream=True, timeout=900) as resp:
        resp.raise_for_status()
        with open(path, "wb") as f:
            for chunk in resp.iter_content(chunk_size=1 << 20):
                if chunk:
                    f.write(cipher.decrypt(chunk))
    return path


def main():
    mode = sys.argv[1] if len(sys.argv) > 1 else "list"
    dest = sys.argv[2] if len(sys.argv) > 2 else DEST_DEFAULT
    items = list_tree()
    files = [i for i in items if i["type"] == 0]
    print(f"{len(items)} node, {len(files)} file:")
    for it in items:
        print(f"  {'DIR ' if it['type'] == 1 else 'FILE'} | {it['size']} | {it['name']} | {it['h']}")
    json.dump([{k: v for k, v in i.items() if k != 'mat'} for i in items],
              open("/home/mantapman/.hermes/profiles/atun/workspace/mega_folder_list.json", "w"),
              indent=1, ensure_ascii=False)
    if mode == "download":
        os.makedirs(dest, exist_ok=True)
        for it in files:
            if not it["name"] or not it["mat"]:
                print("skip (gagal dekripsi):", it["h"])
                continue
            if os.path.exists(os.path.join(dest, it["name"])):
                print("skip ada:", it["name"])
                continue
            print("unduh:", it["name"], flush=True)
            try:
                download_file(it["h"], it["mat"], dest, it["name"])
            except Exception as e:
                print("  GAGAL:", type(e).__name__, e, flush=True)
        print("\nhasil:", sorted(os.listdir(dest)))


if __name__ == "__main__":
    main()
