Bili Sunflower Publish
Publish HTML content to Bilibili — supports both 专栏 (article) and 小站 (tribee) targets. Handles login check, title input, HTML paste via clipboard, and direct...
bili-sunflower-publish
Unified publish pipeline for Bilibili: HTML file → editor → publish.
Supports two targets:
- 专栏 (Article): long-form articles on
member.bilibili.com - 小站 (Tribee): community posts on
bilibili.com/bubble
Prerequisites
- macOS (uses Swift NSPasteboard for HTML clipboard)
- Browser tool with
openclawprofile (Playwright-managed browser)
Input
- HTML file path (article body)
- Target type:
article(专栏) ortribee(小站) — infer from user intent - Title (optional — see Phase 2)
- For tribee: tribee name or ID (required)
- For tribee: 分区 (category, optional — user can specify or choose during publish)
Phase 1: Navigate & Login Check
Article mode
Navigate to https://member.bilibili.com/york/read-editor and take a snapshot.
- Logged in: editor loads with title textbox
"请输入标题(建议30字以内)" - Not logged in: redirects to login page
Tribee mode
Publish URL: https://www.bilibili.com/bubble/publish?tribee_id={id}&tribee_name={name}
Both tribee_id and tribee_name are required for the publish URL. Resolve missing params:
| User provides | Resolution |
|---|---|
| id + name | Direct → publish URL |
| id only | Navigate to https://www.bilibili.com/bubble/home/{id}, extract tribee name from the page, then → publish URL |
| name only | Search https://search.bilibili.com/all?keyword={name}, find the card linking to bilibili.com/bubble/home/{id}, extract {id}, then → publish URL |
After navigating to the publish URL:
- Logged in + member: editor loads with title and body area
- Not logged in: redirects to login page
- Not a member: may show join prompt — tell user to join the tribee first
If not logged in, stop and tell the user to log in manually in the openclaw browser profile, then retry.
Phase 2: Title Handling
Determine the title:
- If user provided a title → use it
- If not → use HTML filename (sans
.htmlextension)
After resolving, validate:
- Title > 50 characters: present 2-3 shortened alternatives, let user choose
- Title looks meaningless (e.g.
output,untitled,temp, or clearly unrelated to the HTML content): read the first few hundred chars of HTML body, generate 2-3 title suggestions, let user choose
Do NOT silently use a bad title. Wait for user selection before proceeding.
Enter the title: click the title textbox and type.
Phase 3: Paste Article Body
Both editors share the same rich-text editor component.
3a. Set HTML clipboard (macOS NSPasteboard)
swift {SKILL_DIR}/scripts/set_html_clipboard.swift <html-file>
Sets text/html + text/plain on the system clipboard.
3b. Focus editor body and paste
- Click the body contenteditable area
- Press
Meta+vto paste
Note: The openclaw Playwright browser profile on macOS correctly routes Meta+v through the system clipboard. This does NOT work with the Chrome extension relay profile.
3c. Verify paste
Evaluate JS to confirm content was pasted:
// DOM selector for the editor's contenteditable area
(function () {
const e = document.querySelector(".eva3-editor");
return { chars: e.textContent.length, first80: e.textContent.substring(0, 80) };
})();
Expected: chars > 100 and first80 matches the article opening.
Phase 4: Publish
Article mode
Editor bottom has a "发布设置" panel (usually already visible) with defaults that work out of the box:
- 可见范围: 所有人可见 (default)
- 封面: auto-generated from body text (default)
- 评论: enabled (default)
If the user has specific publishing preferences, adjust before publishing:
| 设置 | 操作 | 说明 |
|---|---|---|
| 可见范围 | 选 radio: "所有人可见" / "仅自己可见" | 仅自己可见不支持分享和商业推广 |
| 自定义封面 | 勾选 checkbox → 上传图片 | 不设则自动抓正文开头文字 |
| 评论开关 | 勾选/取消 checkbox | 关闭后无法评论 |
| 精选评论 | 勾选 checkbox | 开启后需手动筛选评论才展示 |
| 定时发布 | 勾选 checkbox → 选择时间 | 范围: 当前+2h ~ 7天内,北京时间 |
| 创作声明-原创 | 勾选 checkbox | 声明原创,禁止转载 |
| 创作声明-AI | 勾选 checkbox | 标识使用 AI 合成技术 |
| 话题 | 点击"添加话题"按钮 | 可选 |
| 文集 | 点击"选择文集"按钮 | 可选 |
Steps:
- Scroll down to confirm the "发布设置" panel is visible; if collapsed, click "发布设置" button to expand
- Apply any user-requested settings from the table above
- Click "发布" button (next to "保存为草稿", at the bottom right)
- If a confirmation dialog appears, confirm
- Verify publish succeeded (URL changes or success toast)
Tribee mode
Bottom bar appears after content is entered, with defaults that work out of the box:
- 分区: 未选择 (some tribees may require)
- 同步至动态: enabled (default)
If the user has specific preferences, adjust before publishing:
| 设置 | 操作 | 说明 |
|---|---|---|
| 分区 | 点击"选择分区"下拉 → 选择分区 | 部分小站可能必选 |
| 同步至动态 | 取消勾选 checkbox | 默认开启,取消后不同步到动态 |
分区自动选择(Tribee only)
When the user did not specify a 分区:
- Click "选择分区" dropdown to reveal available options
- Read the list of 分区 names from the dropdown (snapshot)
- Based on the article title + first ~200 chars of body content, pick the most relevant 分区:
- Match by keyword/topic similarity (e.g. tutorial content → "经验分享", questions → "提问求助")
- If no option is a clear fit, pick the most general/catch-all option
- If the tribee only has 1-2 generic options, just pick the best one without overthinking
- Click the chosen 分区
- Do not ask the user to confirm — just pick and proceed (they can always re-publish if wrong)
If the user did specify a 分区, use it directly (skip auto-selection).
Steps:
- Handle 分区 (user-specified or auto-selected per above)
- Apply any other user-requested settings
- Click "发布" button (blue, bottom right)
- Verify publish succeeded
Fallback: CDP WebSocket Injection
If Meta+v paste fails (clipboard not recognized), use direct CDP injection:
- Split HTML into ~1500-char chunks
- Connect via CDP WebSocket to inject chunks into
window.__hparray - Join and dispatch a synthetic ClipboardEvent with
text/html
// DOM selector for the editor's contenteditable area
// After all chunks pushed to window.__hp:
(function () {
const e = document.querySelector(".eva3-editor");
e.focus();
const html = window.__hp.join("");
const dt = new DataTransfer();
dt.setData("text/html", html);
const pe = new ClipboardEvent("paste", { bubbles: true, cancelable: true, clipboardData: dt });
e.dispatchEvent(pe);
return e.textContent.length;
})();
CDP WebSocket URL pattern: ws://127.0.0.1:18800/devtools/page/{targetId}
Locate the ws module dynamically:
node -e "console.log(require.resolve('ws', {paths:[require.resolve('openclaw')]}))"
Use Runtime.evaluate to inject each chunk. Keep each evaluate payload under 6KB.
Execution
Default: execute Phase 1 → 4 directly in the main session (supports user interaction for login/title).
Only delegate to subagent if the user explicitly requests it.
Download
ZIP package — ready to use
Skill Info
- Creator
- zangzhicong
- Downloads
- 72
- Published
- Mar 15, 2026
- Updated
- Mar 16, 2026