# -*- coding: utf-8 -*-
"""探测 UE 实例存储方式"""
import sys, socket, json, time
sys.stdout.reconfigure(encoding='utf-8')
HOST = "localhost"; PORT = 28800
PAGE_ID = "05A421A861953C0FFB3712503B85EE82"

def 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\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-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 send(s, d):
    d = d.encode('utf-8'); n = len(d); 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
    s.send(h + bytes(d[i]^m[i%4] for i in range(n)))

def recv(s):
    def rx(n):
        b = b''
        while len(b) < n: b += s.recv(n-len(b))
        return b
    h = rx(2); n = h[1]&0x7f
    if n == 126: n = int.from_bytes(rx(2),'big')
    elif n == 127: n = int.from_bytes(rx(8),'big')
    return rx(n).decode('utf-8')

_mid = 0
def cdp(s, js, timeout=15):
    global _mid
    _mid += 1
    send(s, json.dumps({'id':_mid,'method':'Runtime.evaluate','params':{'expression':js,'returnByValue':True}}))
    s.settimeout(timeout)
    for _ in range(200):
        try:
            r = json.loads(recv(s))
            if r.get('id') == _mid:
                return r.get('result',{}).get('result',{}).get('value','')
        except: pass
    return ''

s = ws()
print("[OK] Connected")

# 1. UE_V2.instants 详细探测
r = cdp(s, "var v=window.UE_V2;var t=typeof v;var ins=t==='object'?JSON.stringify(Object.keys(v.instants||{})):'not_object';JSON.stringify({type:t,keys:Object.keys(v||{}).join(','),instantKeys:ins,prototype:Object.getOwnPropertyNames(Object.getPrototypeOf(v||{}).constructor||{}).join(',')})")
print("[UE_V2]", r)

# 2. getEditor 方法
r = cdp(s, "var out=[];if(window.UE_V2){var g=window.UE_V2.getEditor;out.push('getEditor:'+typeof g);for(var k in window.UE_V2){var v=window.UE_V2[k];if(v&&typeof v==='object'&&k!=='instants'){out.push(k+':typeof='+typeof v+',methods='+Object.keys(v).filter(function(m){return typeof v[m]==='function'}).join(','))}}}out.join(' | ')")
print("[UE_V2 methods]", r[:300] if r else 'empty')

# 3. 直接在 window 上找 UE 实例
r = cdp(s, "var out=[];for(var k in window){try{if(k.toLowerCase().indexOf('ue')>-1||k.toLowerCase().indexOf('editor')>-1||k.toLowerCase().indexOf('content')>-1){var v=window[k];if(v&&typeof v==='function'){out.push(k+':function')}}}catch(e){}}out.join(' | ')")
print("[window keys with UE]", r[:200] if r else 'empty')

# 4. 测试 getContent + execCommand + setContent
r = cdp(s, "var out=[];function findUE(obj,path){if(!obj||typeof obj!=='object')return;try{for(var k in obj){var v=obj[k];if(v&&v.constructor&&v.constructor.name==='Editor'){out.push({path:path+'.'+k,methods:Object.keys(v).filter(function(m){return typeof v[m]==='function'}).join(','),contentLen:(v.getContent||function(){return''})().length})}}if(obj.instants&&typeof obj.instants==='object'){var ks=Object.keys(obj.instants);for(var k of ks){findUE(obj.instants[k],path+'.instants.'+k)}}if(obj._instances){for(var k of Object.keys(obj._instances)){findUE(obj._instances[k],path+'._instances.'+k)}}}catch(e){}}findUE(window.UE_V2,'window.UE_V2');out.length?JSON.stringify(out):'no_editor_found'")
print("[editors]", r[:500] if r else 'empty')

s.close()
