Install
openclaw skills install bili-sunflower-publishPublish HTML or Markdown content to Bilibili — supports both 专栏 (article) and 小站 (tribee) targets. Handles login check, title input, content injection via ed...
openclaw skills install bili-sunflower-publishUnified publish pipeline for Bilibili: HTML/Markdown file → editor → publish. Uses window.editor API directly — no system clipboard dependency.
Supports two targets:
member.bilibili.combilibili.com/bubbleopenclaw profile (Playwright-managed browser).html and .md / .markdown filesarticle (专栏) or tribee (小站) — infer from user intentNavigate to https://member.bilibili.com/york/read-editor and take a snapshot.
"请输入标题(建议30字以内)"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:
If not logged in, stop and tell the user to log in manually in the openclaw browser profile, then retry.
Run the preprocess script matching the file type. The script handles H1 extraction, heading promotion, image inlining, and (for HTML) whitespace cleanup.
.html filespython3 {SKILL_DIR}/scripts/preprocess_html.py <input.html> [output-dir]
Output (stdout, 3 lines):
title=<extracted title>
path=<processed html file path>
bytes=<html byte size>
Parse title and path from the output. The processed HTML at path is ready for injection in Phase 3.
.md / .markdown filespython3 {SKILL_DIR}/scripts/preprocess_md.py <input.md> [output-dir]
Output (stdout, 3 lines):
title=<extracted title>
path=<processed md file path>
bytes=<md byte size>
Parse title and path from the output. The processed Markdown at path is ready for injection in Phase 3.
If the user provided a title explicitly, use it instead of the script-extracted one.
After resolving, validate:
output, untitled, temp, or clearly unrelated to the content): read the first few hundred chars of body, generate 2-3 title suggestions, let user chooseDo NOT silently use a bad title. Wait for user selection before proceeding.
Enter the title: click the title textbox and type.
Both editors share the same rich-text editor component (window.editor).
Use the preprocessed content from Phase 2 and inject it into the editor.
IMPORTANT — JS string escaping:
Content (HTML or Markdown) often contains characters that break JS syntax (backticks, quotes, newlines, backslashes, etc.). You must use the method below to safely construct the JS code. Do NOT paste raw content directly into a JS variable assignment.
Follow these steps exactly for both HTML and Markdown injection:
path=...)python3 -c "import json,sys; print(json.dumps(open(sys.argv[1]).read()))" <processed-file-path>
This outputs a double-quoted JSON string with all special characters escaped (newlines, quotes, backslashes, etc.), e.g. "line1\nline2\"quoted\"".
ESCAPED_JSON_STRINGESCAPED_JSON_STRING into the template (see 3a-HTML / 3a-MD below). It already includes its own surrounding double quotes, so place it directly after JSON.parse( with no additional quotes.(function () {
const html = JSON.parse(ESCAPED_JSON_STRING);
const clipboardData = new DataTransfer();
clipboardData.setData('text/html', html);
clipboardData.setData('text/plain', '');
const pasteEvent = new ClipboardEvent('paste', {
bubbles: true,
cancelable: true,
clipboardData,
});
window.editor.view.dom.dispatchEvent(pasteEvent);
})();
Note: If the HTML content is large (>50KB), split it into chunks and paste sequentially.
(function () {
const markdown = JSON.parse(ESCAPED_JSON_STRING);
window.editor.commands.importMarkdown({
content: markdown,
replaceContent: true,
});
})();
Evaluate JS to confirm content was inserted:
(function () {
const e = window.editor.view.dom;
return { chars: e.textContent.length, first80: e.textContent.substring(0, 80) };
})();
Expected: chars > 100 and first80 matches the article opening.
Editor bottom has a "发布设置" panel (usually already visible) with defaults that work out of the box:
If the user has specific publishing preferences, adjust before publishing:
| 设置 | 操作 | 说明 |
|---|---|---|
| 可见范围 | 选 radio: "所有人可见" / "仅自己可见" | 仅自己可见不支持分享和商业推广 |
| 自定义封面 | 勾选 checkbox → 上传图片 | 不设则自动抓正文开头文字 |
| 评论开关 | 勾选/取消 checkbox | 关闭后无法评论 |
| 精选评论 | 勾选 checkbox | 开启后需手动筛选评论才展示 |
| 定时发布 | 勾选 checkbox → 选择时间 | 范围: 当前+2h ~ 7天内,北京时间 |
| 创作声明-原创 | 勾选 checkbox | 声明原创,禁止转载 |
| 创作声明-AI | 勾选 checkbox | 标识使用 AI 合成技术 |
| 话题 | 点击"添加话题"按钮 | 可选 |
| 文集 | 点击"选择文集"按钮 | 可选 |
Steps:
Bottom bar appears after content is entered, with defaults that work out of the box:
If the user has specific preferences, adjust before publishing:
| 设置 | 操作 | 说明 |
|---|---|---|
| 分区 | 点击"选择分区"下拉 → 选择分区 | 部分小站可能必选 |
| 同步至动态 | 取消勾选 checkbox | 默认开启,取消后不同步到动态 |
When the user did not specify a 分区:
If the user did specify a 分区, use it directly (skip auto-selection).
Steps:
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.