wechat-channels-video · v0.2.0 · 2026-11-02 · sha256 8509b818fee22c11
wechat-channels-video v0.2.0B
Immutable. This exact content never changes and is served at /api/v1/blob/8509b818fee22c11.
---
name: wechat-channels-video
description: Publish a WeChat Channels video draft with Ego Lite via ego-browser — login-check first (sessions expire fast), upload video, fill description/short title/topics/location/cover, save draft only. Soft-fails if Channels is logged out and tells the human to scan QR inside Ego. Personal use. Use when the user asks to post to 视频号 / Channels assistant.
metadata:
version: "0.2.0"
requires:
bins:
- ego-browser
- node
ego:
min_skill_guide: "2.1.0"
---
# wechat-channels-video
用 Ego Lite 会话在 [视频号助手](https://channels.weixin.qq.com) **保存视频草稿**。不点「发表」(扫码发表留给人)。
Save a WeChat Channels **draft** through Ego Lite. Never click Publish — QR publish is human-only.
## 先装 Ego
先装 **Ego Lite**,保证 `ego-browser` 在 `PATH`。缺失则 soft-fail / 告知用户去装,**不要**硬调浏览器。
## 每次发布前:登录闸门(必做)
视频号登录态**过期很快**。Agent **在上传或填表之前**必须跑:
```bash
node scripts/check-login.mjs
# 需要把窗口留给人扫码时:
node scripts/check-login.mjs --keep
```
| 退出码 | 含义 | Agent 做什么 |
|--------|------|----------------|
| `0` | 已登录 | 把 `login_tip` 告诉用户,继续流程 |
| `2` | 未登录 / 无 ego-browser | 展示 `how_to_login` / `how_to_login_zh`,**停**。请人在 Ego 扫码后再跑 check-login |
| `1` | 其它错误 | 报告 JSON `error`,不要假装已发布 |
详见 `references/login.md`。不要另做 cookie-healthcheck 技能。
## 输入(通用,无本机产品路径)
调用方提供视频与文案字段,见 `references/inputs.md`。本技能**不**写死任何 `~/…` 业务目录。
最少需要:本机 `video` 路径、`description`、`short_title`。可选:`tags`、`cover`、位置/原创/活动。
## CLI 薄脚本(稳定部分)
`ego-browser nodejs` 不转发 shell argv;`check-login` 已用临时 entry + `ego-browser nodejs -e` 包好。
```bash
# 仓库根目录
node skills/wechat-channels-video/scripts/check-login.mjs
node skills/wechat-channels-video/scripts/check-login.mjs --keep
node skills/wechat-channels-video/scripts/serve-file.mjs --file /absolute/path/to/video.mp4 --port 18765
# 已安装的技能目录里也可以用相对路径
node scripts/check-login.mjs
node scripts/serve-file.mjs --file /absolute/path/to/video.mp4 --port 18765
```
- **check-login**:唯一登录硬闸门;结束默认 `finish({ keep: [] })`(`--keep` 除外)。
- **serve-file**:给页面 `fetch` 用的本机 CORS 文件服务;打印 `{ url, bytes, port }`,SIGINT 结束。大视频不要 base64。
**不**把整段发表 UI 打成一个巨型脚本:wujie、坐标点击、封面 `waitForFileChooser` 需要 Agent 看 snapshot/截图。脚本负责可判定的登录与传文件;表单仍按下面 Agent 步骤。
## ego-browser API 速查
每次 `ego-browser nodejs` 是新进程;TaskSpace / Page 跨调用持久,JS 变量不持久。
| API | 说明 |
| --- | --- |
| `taskSpace(name\|id)` | 一个用户目标一个 space;记下 `spaceId` 复用 |
| `task.page("p1")` | 首屏用 goto 复用,别乱开页 |
| `page.snapshot()` | 观察(能看穿 open shadow);**点击**在 wujie 内常被拦截 |
| `page.evaluate(fn, arg)` | 只收**一个** JSON 可序列化参数(长文案走这里) |
| `page.mouse.click(x, y)` | wujie 内弹层常用 |
| `page.waitForFileChooser()` | 封面唯一稳妥上传路径 |
| `task.handOff()` | 交给人扫码发表 / 当面登录 |
| `task.finish({ keep: [] })` | 收尾关 space;需人接着看时用 handOff,不要长期占机器 |
**关键**:snapshot 看得见 wujie shadow;ref/text 点击会报 intercepts pointer events。wujie 内动作用 `evaluate` 里 `.click()` 或坐标 `mouse.click`。
## Agent 流程(登录通过之后)
### 0. 路由到发表页
**不要**直接 goto `/platform/post/create`(懒加载白屏)。走 UI:
`首页 → 左栏「内容管理」→「视频」→ 右侧「发表视频」`
就绪:wujie 内文案出现「视频描述」「短标题」,且有隐藏 `input[type=file][accept*="video"]` 与「保存草稿」。
```js
const state = await page.evaluate(() => {
const app = document.querySelector('wujie-app');
if (!app?.shadowRoot) return { wujie: false };
const t = app.shadowRoot.textContent || '';
return {
wujie: true,
hasDesc: t.includes('视频描述'),
hasShort: t.includes('短标题'),
hasUpload: !!app.shadowRoot.querySelector('input[type=file][accept*="video"]'),
hasSave: t.includes('保存草稿'),
};
});
```
### 1. 上传视频
先起 `serve-file`(端口默认 18765),再在页面里 fetch + DataTransfer 注入隐藏 file input(视频组件未做 isTrusted 加固;封面做了):
```js
const r = await page.evaluate(async (p) => {
const root = document.querySelector('wujie-app').shadowRoot;
const input = root.querySelector('input[type=file][accept*="video"]');
const resp = await fetch(p.url);
const dt = new DataTransfer();
dt.items.add(new File([await resp.blob()], p.name, { type: 'video/mp4' }));
input.files = dt.files;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
return 'injected ' + dt.files[0].size;
}, { url: 'http://127.0.0.1:18765/video.mp4', name: 'video.mp4' });
```
验证只看 DOM:出现「转发/赞/评论」预览卡、进度稳定 100%。`performance` resource **抓不到**上传 POST。上传中途不要填表;wujie 短暂 null 时等一会再探,**不要刷新**。
### 1.5 检查点:先存草稿
上传完成后**立刻**点一次「保存草稿」,再填描述/短标题/位置,最后再存。避免中途翻车重传大文件。
### 2. 描述 + 话题
描述是 `.input-editor`(非 contenteditable)。`execCommand`;文案从 evaluate 第二参数传入:
```js
await page.evaluate((p) => {
const ed = document.querySelector('wujie-app').shadowRoot.querySelector('.input-editor');
ed.focus();
document.execCommand('selectAll', false, null);
document.execCommand('delete', false, null);
document.execCommand('insertText', false, p.desc);
}, { desc });
await page.evaluate((p) => {
const ed = document.querySelector('wujie-app').shadowRoot.querySelector('.input-editor');
ed.focus();
document.execCommand('insertParagraph');
document.execCommand('insertText', false, p.tags);
ed.blur();
}, { tags });
```
### 3. 短标题
真 `<input>`,必须 native setter;验证读 `input.value`(innerText 不含 input 值)。
### 4. 位置
默认常带城市。稳妥:`mouse.click` 打开 → snapshot/截图 → 点「不显示位置」→ **回读字段**确认。evaluate 坐标可能点到隐藏层。
### 5. 保存草稿(终点)
点「保存草稿」。成功信号:「已保存」+ 底部按钮变灰。再 goto 草稿箱核对。**草稿箱可见前不要离开编辑器**。
可选:声明原创、活动搜索(native setter 写搜索词;搜不到就「不参与活动」)。
### 6. 封面(可选)
唯一稳妥:`waitForFileChooser()` + 可信点击「上传封面」+ `chooser.setFiles(<绝对路径>)`。合成 `input.files` / OS 文件框方案已实测失败。确认按钮转圈结束、预览不再回退默认帧后再存草稿。
### 7. 收尾
- 停掉 `serve-file`(释放端口)。
- 需要人发表:草稿箱 `handOff()`。
- 否则 `finish({ keep: [] })`,**不要**长期占 space。
## 避坑清单
1. wujie shadow:查询从 `wujie-app.shadowRoot` 起。
2. 直链 create 白屏:必须 UI 路径进入发表页。
3. wujie 内 ref/text 点击被拦截 → evaluate 或坐标点击。
4. 长文案只经 evaluate 参数传入。
5. 描述用 execCommand;短标题用 native setter。
6. 位置两段式坐标 + 回读验证。
7. 封面只能 waitForFileChooser。
8. 上传验证只看 DOM;上传后先存草稿。
9. 草稿落盘前不导航离开。
10. 登录页「加载失败」是红鲱鱼;handOff 让人扫码。
11. **每次发布前 check-login**;exit 2 必须停。
12. 发表是人工终点;本技能只到草稿。
## 测试
```bash
npm test
```