Communitygithub.com

terryso/wechat-channels-video

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.

wechat-channels-video 是什么?

wechat-channels-video is a Claude Code agent skill that 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.

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/terryso/ego-skills/tree/main/skills/wechat-channels-video

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

wechat-channels-video 是做什么的?

Use an Ego Lite session to save a video draft in Channels Assistant. Never click Publish — QR publish is reserved for humans.

Install Ego First

Install Ego Lite first and ensure ego-browser is in PATH. If missing, soft-fail and tell the user to install it — do not directly launch a browser.

Before Each Publish: Login Gate (Mandatory)

The WeChat Channels login session expires quickly. The agent must run the following before uploading or filling in forms:

node scripts/check-login.mjs
# If the window needs to be left for human QR scanning:
node scripts/check-login.mjs --keep
Exit CodeMeaningAgent Action
0Logged inInform the user of login_tip, proceed with the workflow
2Not logged in / no ego-browserDisplay how_to_login / how_to_login_zh, stop. Ask the human to scan the QR code in Ego, then run check-login again
1Other errorReport the JSON error, do not pretend the video was published

See references/login.md for details. Do not create a separate cookie-healthcheck skill.

Input (Generic, No Local Product Paths)

The caller provides the video and text fields — see references/inputs.md. This skill does not hard-code any ~/… business directories.

Minimum required: local video path, description, short_title. Optional: tags, cover, location/originality/activity.

CLI Thin Scripts (Stable Part)

ego-browser nodejs does not forward shell argv; check-login is already wrapped with a temp entry + ego-browser nodejs -e.

# From repo root
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

# Also usable with relative paths from the installed skill directory
node scripts/check-login.mjs
node scripts/serve-file.mjs --file /absolute/path/to/video.mp4 --port 18765
  • check-login: The only login hard gate; ends with finish({ keep: [] }) by default (except with --keep).
  • serve-file: A local CORS file server for the page to fetch; prints { url, bytes, port }, ends on SIGINT. Do not base64-encode large videos.

Do not bundle the entire publish UI into a single giant script: wujie, coordinate clicks, and cover waitForFileChooser require the agent to inspect snapshots/screenshots. Scripts handle deterministic login and file transfer; the form is filled by the agent steps below.

ego-browser API Quick Reference

Each ego-browser nodejs is a new process; TaskSpace / Page persists across calls, but JS variables do not.

APIDescription
taskSpace(name|id)One space per user goal; record the spaceId for reuse
task.page("p1")Reuse the first page via goto; do not open pages unnecessarily
page.snapshot()Inspect (can see through open shadows); clicks are often intercepted inside wujie
page.evaluate(fn, arg)Accepts one JSON-serializable argument (long text goes here)
page.mouse.click(x, y)Commonly used inside wujie popup layers
page.waitForFileChooser()The only safe upload path for covers
task.handOff()Hand off to human for QR code publish / login
task.finish({ keep: [] })Clean up and close the space; use handOff if the human needs to continue viewing, do not occupy the machine long-term

Key: snapshot can see wujie shadow; ref/text clicks will report intercepted pointer events. For actions inside wujie, use .click() inside evaluate or coordinate mouse.click.

Agent Workflow (After Login Passes)

0. Navigate to the Publish Page

Do not go directly to /platform/post/create (lazy load white screen). Follow the UI path:

Home → Left sidebar "Content Management" → "Videos" → Right side "Publish Video"

Ready: wujie inner text shows "视频描述" (video description) and "短标题" (short title), and there is a hidden input[type=file][accept*="video"] and a "保存草稿" (save draft) button.

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. Upload Video

First, start serve-file (default port 18765), then fetch the video inside the page and inject it into the hidden file input using DataTransfer (the video component is not isTrusted-protected; the cover is):

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' });

Validation: check the DOM for the preview card showing "转发/赞/评论" and a stable progress bar at 100%. The performance resource list will not capture the upload POST. Do not fill any form fields during upload; if wujie becomes temporarily null, wait a moment and try again — do not refresh.

1.5 Checkpoint: Save Draft First

Immediately after upload finishes, click "保存草稿" once, then fill in the description/short title/location, and finally save again. This avoids having to re-upload a large file if something goes wrong midway.

2. Description + Topics

The description uses .input-editor (not contenteditable). Use execCommand; pass the text as the second argument to evaluate:

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. Short Title

A real <input>, must use native setter; validate by reading input.value (innerText does not contain input values).

4. Location

A city is usually auto-filled. Safe approach: mouse.click to open → snapshot/screenshot → click "不显示位置" (do not show location) → re-read the field to confirm. Evaluating coordinates might click on a hidden layer.

5. Save Draft (End Point)

Click "保存草稿". Success signal: "已保存" (saved) + the bottom button turns gray. Then goto the drafts folder to verify. Do not navigate away from the editor before the draft is visible in the drafts folder.

Optional: declare originality, search for activities (use native setter for the search term; if not found, use "不参与活动" (do not participate)).

6. Cover (Optional)

The only reliable method: waitForFileChooser() + trusted click on "上传封面" (upload cover) + chooser.setFiles(<absolute path>). The synthetic input.files / OS file dialog approach has been tested and failed. Wait for the confirmation button's spinner to stop and the preview no longer reverts to the default frame, then save the draft.

7. Cleanup

  • Stop serve-file (release the port).
  • If the human needs to publish: use handOff() in the drafts folder.
  • Otherwise, finish({ keep: [] }), do not occupy the space long-term.

Pitfall Checklist

  1. wujie shadow: Start queries from wujie-app.shadowRoot.
  2. Direct create link white screen: Must use the UI path to enter the publish page.
  3. ref/text clicks inside wujie are intercepted → use evaluate or coordinate click.
  4. Long text only passes through evaluate parameters.
  5. Description uses execCommand; short title uses native setter.
  6. Location uses two-step coordinates + re-read verification.
  7. Cover can only use waitForFileChooser.
  8. Upload validation checks the DOM only; save draft right after upload.
  9. Do not navigate away before the draft is saved.
  10. "加载失败" (load failed) on the login page is a red herring; handOff lets the human scan the QR code.
  11. Run check-login before each publish; exit code 2 must stop the workflow.
  12. Publishing is a human endpoint; this skill only goes up to draft.

Testing

npm test

相关技能