# -*- coding: utf-8 -*-
"""深度搜索百家号页面中的所有 file inputs（包括 iframe/shadow DOM）"""
import sys, socket, json, time
sys.stdout.reconfigure(encoding="utf-8")

HOST = "localhost"
PORT = 28800
PAGE_ID = "70039F329097C14448996F189E3A42A1"

def make_ws():
    import urllib.parse
    u = "ws://%s:%s/devtools/page/%s" % (HOST, PORT, PAGE_ID)
    p = urllib.parse.urlparse(u)
    s = socket.create_connection((p.hostname, p.port or 80), timeout=15)
    req = (
        "GET %s HTTP/1.1\r\nHost: %s:%s\r\nUpgrade: websocket\r\n"
        "Connection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
        "Sec-WebSocket-Version: 13\r\n\r\n"
    ) % (p.path, p.hostname, p.port)
    s.send(req.encode())
    resp = b""
    while b"\r\n\r\n" not in resp:
        resp += s.recv(4096)
    return s

def ws_send(sock, data):
    data = data.encode("utf-8")
    n = len(data)
    m = b"\x01\x02\x03\x04"
    if n <= 125:
        h = bytes([0x81, n | 0x80]) + m
    elif n <= 65535:
        h = bytes([0x81, 0xFE]) + n.to_bytes(2, "big") + m
    else:
        h = bytes([0x81, 0xFF]) + n.to_bytes(8, "big") + m
    sock.send(h + bytes(data[i] ^ m[i % 4] for i in range(n)))

def ws_recv(sock):
    h = b""
    while len(h) < 2:
        h += sock.recv(2 - len(h))
    n = h[1] & 0x7F
    if n == 126:
        nb = b""
        while len(nb) < 2:
            nb += sock.recv(2 - len(nb))
        n = int.from_bytes(nb, "big")
    elif n == 127:
        nb = b""
        while len(nb) < 8:
            nb += sock.recv(8 - len(nb))
        n = int.from_bytes(nb, "big")
    body = b""
    while len(body) < n:
        body += sock.recv(n - len(body))
    return body.decode("utf-8")

_mid = [0]

def cdp(sock, method, params=None):
    if params is None:
        params = {}
    _mid[0] += 1
    ws_send(sock, json.dumps({"id": _mid[0], "method": method, "params": params}))
    sock.settimeout(20)
    for _ in range(300):
        try:
            r = json.loads(ws_recv(sock))
            if r.get("id") == _mid[0]:
                return r
        except Exception:
            pass
    return None

sock = make_ws()
print("[OK] Connected\n")

# 全面搜索
print("[1] 全面搜索 file inputs...")
r = cdp(sock, "Runtime.evaluate", {
    "expression": """
    (function() {
        var results = [];
        
        // 主文档
        var main = document.querySelectorAll('input');
        results.push('Main doc inputs: ' + main.length);
        for (var i = 0; i < main.length; i++) {
            if (main[i].type === 'file' || main[i].accept) {
                results.push('  [' + i + '] type=' + main[i].type + ' accept=' + (main[i].accept || '') + ' id=' + (main[i].id || '') + ' name=' + (main[i].name || ''));
            }
        }
        
        // iframes
        var iframes = document.querySelectorAll('iframe');
        results.push('Iframes: ' + iframes.length);
        for (var f = 0; f < iframes.length; f++) {
            results.push('  iframe[' + f + '] src=' + (iframes[f].src || '').substring(0, 60));
            try {
                var idoc = iframes[f].contentDocument;
                var iinputs = idoc.querySelectorAll('input');
                results.push('    inputs in iframe: ' + iinputs.length);
                for (var j = 0; j < iinputs.length; j++) {
                    results.push('    [' + j + '] type=' + iinputs[j].type + ' accept=' + (iinputs[j].accept || ''));
                }
                // 也搜索 iframe 中的 div/label 里的隐藏 inputs
                var allEls = idoc.querySelectorAll('*');
                for (var el of allEls) {
                    if (el.shadowRoot) {
                        var si = el.shadowRoot.querySelectorAll('input[type=file]');
                        if (si.length) results.push('    shadow input: ' + si.length);
                    }
                }
            } catch(e) {
                results.push('    ERROR: ' + e.message);
            }
        }
        
        // shadow DOM
        var allEls = document.querySelectorAll('*');
        var shadowCount = 0;
        for (var el of allEls) {
            if (el.shadowRoot) {
                shadowCount++;
                var si = el.shadowRoot.querySelectorAll('input');
                if (si.length > 0) {
                    results.push('Shadow ' + el.tagName + '#' + (el.id || '') + ': ' + si.length + ' inputs');
                }
            }
        }
        results.push('Shadow roots: ' + shadowCount);
        
        return results.join('\\n');
    })()
    """,
    "returnByValue": True
})
val = r.get("result", {}).get("result", {}).get("value", "") if r else ""
print(val)

# 用 CDP 获取所有 frame 的 tree
print("\n[2] CDP Page.getFrameTree...")
r = cdp(sock, "Page.getFrameTree", {})
print("  结果:", json.dumps(r, ensure_ascii=False)[:500] if r else "None")

# 也看看"Choose File"按钮周围的 DOM 结构
print("\n[3] Choose File 按钮周围 DOM...")
r = cdp(sock, "Runtime.evaluate", {
    "expression": """
    (function() {
        // 找所有按钮文字含 "Choose File" 或 "选择封面" 的
        var btns = document.querySelectorAll('button, div, span, label, a');
        var results = [];
        for (var btn of btns) {
            var text = (btn.textContent || '').trim();
            if (text.indexOf('Choose File') > -1 || text.indexOf('选择封面') > -1 || text.indexOf('封') > -1) {
                if (text.length < 30) {
                    // 获取其 DOM 路径
                    var path = '';
                    var el = btn;
                    while (el && el !== document.body) {
                        var tag = el.tagName || '';
                        var id = el.id ? '#' + el.id : '';
                        var cls = el.className && typeof el.className === 'string' ? '.' + el.className.split(' ')[0] : '';
                        path = tag + id + cls + '>' + path;
                        el = el.parentElement;
                    }
                    // 找子元素中的 file input
                    var fileInputs = btn.querySelectorAll('input[type=file]');
                    var parentInputs = 0;
                    var p = btn.parentElement;
                    while (p && parentInputs < 5) {
                        parentInputs += p.querySelectorAll('input[type=file]').length;
                        p = p.parentElement;
                    }
                    results.push({
                        text: text.substring(0, 20),
                        tag: btn.tagName,
                        path: path.substring(0, 80),
                        childFileInputs: fileInputs.length,
                        ancestorFileInputs: parentInputs,
                        hasOnclick: !!btn.onclick || !!btn.getAttribute('onclick')
                    });
                }
            }
        }
        return JSON.stringify(results, null, 2);
    })()
    """,
    "returnByValue": True
})
val = r.get("result", {}).get("result", {}).get("value", "") if r else ""
print(val[:600])

sock.close()
print("\nDone")
