# 百家号发布 Skill

## 触发词
`发百家号` — 用户说"发百家号"并提供文章内容时自动触发

## 流程概述
1. 启动 Chrome 调试模式（端口 9222）
2. **CDP 直接导航** 到百家号编辑器（`Target.createTarget`）
3. 检测登录状态（如未登录需人工扫码）
4. 填标题（Lexical 编辑器）
5. 填正文（UEditor `setContent`）
6. 存草稿

## 关键配置

### 环境（V2.0 统一配置）
- **CDP 端口**: `9222`（与微信公众号 Skill 保持一致）
- **用户数据目录**: `C:\ChromeDebug`（与微信公众号 Skill 一致）
- **图片目录**: `D:\wxpic\`（自动取最新修改的 jpg/png）
- **PAGE_ID**: 动态获取（`GET http://127.0.0.1:9222/json`）

### 为什么使用 9222 端口？
统一使用 9222 端口可以确保 Skill 在任何电脑上都能运行：
- 不依赖特定 AI 代理（WorkBuddy/QClaw）
- 不依赖特定浏览器配置
- 只要安装了 Chrome，就能运行本 Skill

## 脚本文件

| 脚本 | 用途 |
|------|------|
| `start_chrome_bjh.py` | 启动 Chrome 调试模式（端口 9222） |
| `bjh_navigate_cdp.py` | **CDP 直接导航** 到百家号编辑器 |
| `bjh_fill_article_v6.py` | 填写标题、正文，存草稿 |
| `batch_bjh.py` | 一键批量发布（调用上述全部脚本） |

## CDP 直接导航说明

### 什么是 CDP 直接导航？
CDP（Chrome DevTools Protocol）直接导航是指通过 Chrome 的调试协议直接创建新标签页并导航到目标 URL，**不依赖外部浏览器操作**。

### 实现方式
```python
# 使用 Target.createTarget 方法直接创建新标签页
call_method(sock, 'Target.createTarget', {
    'url': 'https://baijiahao.baidu.com/builder/rc/edit?type=news',
    'width': 1280,
    'height': 900
})
```

### 优势
1. **纯代码实现** - 无需模拟鼠标点击、等待页面跳转
2. **稳定可靠** - 不受页面元素变化影响
3. **跨平台兼容** - 只要 Chrome 在调试模式运行，就能工作
4. **快速** - 直接创建标签页，无需等待页面加载动画

## 使用方式

### 方式一：一键批量发布（推荐）
```bash
cd baijiahao_skill/scripts
python batch_bjh.py
```

### 方式二：分步执行
```bash
cd baijiahao_skill/scripts

# Step 1: 启动 Chrome
python start_chrome_bjh.py

# Step 2: CDP 导航到百家号编辑器
python bjh_navigate_cdp.py

# Step 3: 人工登录（如需要）
# 在 Chrome 中手动登录百家号

# Step 4: 填写文章并存草稿
python bjh_fill_article_v6.py
```

## 人工介入点
- **扫码登录** - 如果百家号会话过期，需要人工扫码登录
- **正文图片** - 目前需用户手动在编辑器中插入正文图片（百家号 CDN 认证限制）

### 随机延时
- 各步骤之间加 **1-3 秒**随机延时（模拟人工操作）
- 存草稿前等待 **5-10 分钟**随机延时

### 百家号导航
1. `https://baijiahao.baidu.com/` — 首页
2. `._4f4cb3a3b81b55a5-button` — "发布作品"按钮（直达图文编辑器，无需再点"发布图文"）
3. 可能有成就弹窗，需先关闭

### 编辑器元素
| 功能 | 定位方式 |
|------|---------|
| 标题 | `div._9ddb7e475b559749-editor[contenteditable=true]`（Lexical 编辑器） |
| 正文 | UEditor：`UE_V2.instants['ueditorInstant0']`（**key 需动态获取**） |
| 摘要 | `textarea[placeholder="请输入摘要"]` |
| 封面 | `div._73a3a52aab7e3a36-content`（React 组件，点击后动态创建 file input） |
| 存草稿 | `button` 文字="存草稿" |

## 技术要点

### 标题（Lexical 编辑器）
```js
const editor = document.querySelector('div._9ddb7e475b559749-editor[contenteditable=true]');
editor.scrollIntoView({block: 'center'});
editor.focus();
document.execCommand('selectAll', false, null);
document.execCommand('delete', false, null);
document.execCommand('insertText', false, '标题文字');
editor.dispatchEvent(new Event('input', {bubbles: true}));
```

### 正文（UEditor）— 用 CDP 脚本注入
```js
// 动态获取 key
const keys = Object.keys(UE_V2.instants);
const key = keys[0]; // 通常是 'ueditorInstant0'
// 分块注入 HTML 到 window.__BJH_HTML__，然后：
UE_V2.instants[key].execCommand('inserthtml', window.__BJH_HTML__);
```
**注意**：HTML 需先分块注入到 `window.__BJH_HTML__`（避免字符串过长），再一次性 `inserthtml`。

### 正文图片 — ⚠️ 需用户手动操作
百家号图片 CDN URL 需要登录态 cookie，通过 fetch 直接上传拿到的 `picproxy` URL 在预览/发布时会因为认证失败而显示异常。
UEditor 的 `execCommand('insertimage')` 和 `execCommand('inserthtml')` 对 img 标签会被百家号过滤。
**方案：暂停流程，通知用户手动在编辑器工具栏点击上传图片。**

### 摘要
```js
const textarea = document.querySelector('textarea[placeholder="请输入摘要"]');
textarea.focus();
document.execCommand('selectAll', false, null);
document.execCommand('delete', false, null);
document.execCommand('insertText', false, '摘要文字');
textarea.dispatchEvent(new Event('input', {bubbles: true}));
```

### 封面图（MutationObserver + CDP）
```js
// Step 1: 注册 MutationObserver
window.__newFileInput__ = null;
new MutationObserver((mutations) => {
  for (const m of mutations) {
    for (const node of m.addedNodes) {
      if (node.nodeType === 1) {
        if (node.tagName === 'INPUT' && node.type === 'file') window.__newFileInput__ = node;
        const inputs = node.querySelectorAll ? node.querySelectorAll('input[type=file]') : [];
        if (inputs.length > 0) window.__newFileInput__ = inputs[0];
      }
    }
  }
}).observe(document.body, {childList: true, subtree: true});

// Step 2: 点击封面区域
document.querySelector('._73a3a52aab7e3a36-content').click();

// Step 3: 等待 file input 创建（1.5秒）
// Step 4: CDP 获取 objectId
// Runtime.evaluate {expression: "window.__newFileInput__", returnByValue: false}
// Step 5: CDP 设置文件路径
// DOM.setFileInputFiles {objectId: "xxx", files: ["D:\\wxpic\\xxx.jpg"]}
```

### 确认封面裁剪弹窗
上传后弹出裁剪弹窗，点击"确定"按钮。

## 脚本文件
| 脚本 | 用途 |
|------|------|
| `bjh_inject_body.py` | CDP 注入正文到 UEditor（动态 PAGE_ID + 分块 HTML） |
| `bjh_quick_upload.py` | CDP 设置封面 file input 文件路径 |
| `bjh_body_img_upload.py` | 正文图片上传（备用，目前不可用） |

## 注意事项
- UEditor key 不固定，每次要 `Object.keys(UE_V2.instants)` 动态获取
- 百家号可能弹成就弹窗/提示弹窗，要先关闭
- 封面 file input 必须通过 MutationObserver 等待动态创建
- PAGE_ID 每次浏览器重启都会变，脚本需动态获取
- `存草稿`前需等待 5-10 分钟随机延时
- 正文图片必须由用户手动上传，自动化上传的图片 URL 在发布后无法正常显示

## 更新记录
- 2026-03-22: 初始探索
- 2026-03-23: 封面上传调通（MutationObserver + CDP DOM.setFileInputFiles）
- 2026-03-23: 全流程验证通过
- 2026-03-23: 正文图片确认需手动操作（picproxy URL 认证问题），流程改为自动填内容+手动插图片+自动存草稿
- 2026-03-24: **V2.0 重大更新**
  - 统一使用 9222 端口（与微信公众号 Skill 一致）
  - 新增 **CDP 直接导航** 模块（`bjh_navigate_cdp.py`）
  - 实现跨电脑兼容性（不依赖特定 AI 代理）
  - 更新批量发布脚本（`batch_bjh.py` v2.0）
