# -*- coding: utf-8 -*-
"""
百家号文章填写脚本（CDP 自动化）
===================================
功能：
  - 自动填写标题
  - 插入文首图片（从 D:\wxpic 取最新图片）
  - 逐段插入正文
  - 保存为草稿

调试端口：9223
"""
import sys, socket, json, time, urllib.request, base64, os, glob, random
sys.stdout.reconfigure(encoding='utf-8')

HOST = "localhost"
PORT = 9223

# ── 配置区（每次发布前修改这里）────────────────────────────────────────────
TITLE = '请在这里填写文章标题'

PARAGRAPHS = [
    ('normal', '这里是第一段正文内容。'),
    ('normal', '这里是第二段正文内容。'),
]
# ── 配置区结束 ───────────────────────────────────────────────────────────────

def rand_sleep(min_sec, max_sec):
    t = random.uniform(min_sec, max_sec)
    time.sleep(t)
    return t

def get_latest_image():
    image_dir = r'D:\wxpic'
    exts = ['*.jpg', '*.jpeg', '*.png', '*.gif', '*.bmp', '*.webp']
    all_images = []
    for ext in exts:
        all_images.extend(glob.glob(os.path.join(image_dir, ext)))
    if not all_images:
        raise FileNotFoundError(f"在 {image_dir} 目录下没有找到图片文件")
    latest = max(all_images, key=os.path.getmtime)
    return latest

IMAGE_PATH = get_latest_image()
print(f"[INFO] 使用最新图片: {IMAGE_PATH}")

def format_paragraph(style, text):
    if style == 'normal':
        return '　　' + text
    elif style == 'heading':
        return text
    elif style == 'divider':
        return '————————————————————'
    return text

# ── CDP helpers ──────────────────────────────────────────────────────────────
def get_bjh_page_id():
    """获取百家号编辑页面的 ID"""
    pages = json.loads(urllib.request.urlopen(f'http://{HOST}:{PORT}/json').read())
    for p in pages:
        url = p.get('url', '')
        # 百家号编辑页 URL 特征
        if 'baijiahao.baidu.com' in url and ('edit' in url or 'builder' in url):
            return p['id'], url
    return None, None

def ws_connect(page_id):
    import urllib.parse
    ws_url = f"ws://{HOST}:{PORT}/devtools/page/{page_id}"
    parsed = urllib.parse.urlparse(ws_url)
    sock = socket.create_connection((parsed.hostname, parsed.port or 80), timeout=15)
    key = 'dGhlIHNhbXBsZSBub25jZQ=='
    handshake = (
        f'GET {parsed.path} HTTP/1.1\r\n'
        f'Host: {parsed.hostname}:{parsed.port}\r\n'
        'Upgrade: websocket\r\nConnection: Upgrade\r\n'
        f'Sec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n'
    )
    sock.send(handshake.encode())
    resp = b''
    while b'\r\n\r\n' not in resp:
        resp += sock.recv(4096)
    return sock

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

def recv_frame(sock):
    def rx(n):
        buf = b''
        while len(buf) < n:
            buf += sock.recv(n - len(buf))
        return buf
    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')

_msg_id = 0
def call(sock, expr, timeout=15):
    global _msg_id
    _msg_id += 1
    mid = _msg_id
    send_frame(sock, json.dumps({
        'id': mid,
        'method': 'Runtime.evaluate',
        'params': {'expression': expr, 'returnByValue': True}
    }))
    sock.settimeout(timeout)
    while True:
        raw = recv_frame(sock)
        obj = json.loads(raw)
        if obj.get('id') == mid:
            res = obj.get('result', {}).get('result', {})
            return res.get('value', res.get('description', ''))

def screenshot(sock, filename):
    global _msg_id
    _msg_id += 1
    mid = _msg_id
    send_frame(sock, json.dumps({
        'id': mid,
        'method': 'Page.captureScreenshot',
        'params': {'format': 'png'}
    }))
    sock.settimeout(15)
    while True:
        raw = recv_frame(sock)
        obj = json.loads(raw)
        if obj.get('id') == mid:
            data = obj.get('result', {}).get('data', '')
            if data:
                script_dir = os.path.dirname(os.path.abspath(__file__))
                path = os.path.join(script_dir, filename)
                with open(path, 'wb') as f:
                    f.write(base64.b64decode(data))
                print(f"[截图] 已保存: {path}")
            return

# ── 诊断：探测页面结构 ────────────────────────────────────────────────────────
def diagnose_page(sock):
    """探测百家号编辑器的页面结构"""
    print("\n[诊断] 探测页面结构...")

    # 探测标题输入框
    r = call(sock, """
(function(){
    var selectors = [
        'input[placeholder*="标题"]',
        'input[class*="title"]',
        'textarea[placeholder*="标题"]',
        '.title-input input',
        '.article-title input',
        '[data-placeholder*="标题"]',
        'input[maxlength="64"]',
        'input[maxlength="100"]',
    ];
    for(var sel of selectors){
        var el = document.querySelector(sel);
        if(el) return 'FOUND title: ' + sel + ' | placeholder=' + (el.placeholder||'') + ' | class=' + el.className.substring(0,50);
    }
    // 列出所有 input
    var inputs = document.querySelectorAll('input');
    var info = [];
    for(var inp of inputs){
        info.push('input[' + (inp.placeholder||'') + '][' + inp.className.substring(0,30) + ']');
    }
    return 'NO title found. inputs: ' + info.slice(0,5).join(' | ');
})()
""")
    print(f"  标题框: {r}")

    # 探测编辑器
    r = call(sock, """
(function(){
    var selectors = [
        '.ProseMirror',
        '[contenteditable="true"]',
        '.ql-editor',
        '.editor-content',
        '.article-editor',
        '#editor',
        '.content-editor',
        '[data-placeholder*="正文"]',
        '[data-placeholder*="内容"]',
    ];
    for(var sel of selectors){
        var el = document.querySelector(sel);
        if(el) return 'FOUND editor: ' + sel + ' | class=' + el.className.substring(0,50);
    }
    // 列出所有 contenteditable
    var ces = document.querySelectorAll('[contenteditable]');
    var info = [];
    for(var ce of ces){
        info.push('[' + ce.tagName + '][' + ce.className.substring(0,30) + ']');
    }
    return 'NO editor found. contenteditable: ' + info.slice(0,5).join(' | ');
})()
""")
    print(f"  编辑器: {r}")

    # 探测保存/草稿按钮
    r = call(sock, """
(function(){
    var all = document.querySelectorAll('button, a, span, div');
    var found = [];
    for(var el of all){
        var t = (el.innerText || '').trim();
        if(t && t.length < 15 && (
            t.includes('草稿') || t.includes('保存') || t.includes('发布') || t.includes('存稿')
        )){
            var s = window.getComputedStyle(el);
            if(s.display !== 'none' && s.visibility !== 'hidden'){
                found.push(el.tagName + '[' + el.className.substring(0,20) + ']: ' + t);
            }
        }
    }
    return found.slice(0,10).join(' | ') || '未找到相关按钮';
})()
""")
    print(f"  按钮: {r}")

    return r

# ── 主流程 ────────────────────────────────────────────────────────────────────
page_id, page_url = get_bjh_page_id()
if not page_id:
    print("[ERROR] 未找到百家号编辑页面")
    print("[提示] 请先运行 start_chrome_bjh.py 启动 Chrome，并确保百家号编辑页已打开")
    sys.exit(1)
print(f"[OK] 找到百家号编辑页面: {page_url[:80]}")

sock = ws_connect(page_id)
print("[OK] WebSocket 已连接")
time.sleep(1)

# 先截图看当前状态
screenshot(sock, "bjh_before.png")

# 诊断页面结构
diagnose_page(sock)

print("\n[INFO] 诊断完成，请查看 bjh_before.png 和上方输出")
print("[INFO] 根据诊断结果，将在 bjh_fill_article.py 中填写内容")

sock.close()
