mcpField
X
x-article-publisher

x-article-publisher

github.com/wshuyi营销与增长872318 次安装40 周前更新
代码仓库

关于 x-article-publisher

x-article-publisher 是面向 Claude 技能工作流的 X Articles 发布辅助技能,可将 Markdown 转换为富文本并整理图片与分割线。它通过浏览器自动化保存草稿,不会自动发布。

主要功能

  1. 01解析 Markdown 标题、图片和分割线
  2. 02将 Markdown 转换为富文本 HTML
  3. 03通过剪贴板粘贴文字和图片
  4. 04将表格与 Mermaid 图表转为图片
  5. 05按位置插入图片和分割线
  6. 06保存草稿并预览排版

适用场景

  1. 01将 Markdown 文章整理到 X Articles
  2. 02为文章转换表格或 Mermaid 图表
  3. 03按正文位置插入封面图和内容图片

MCP 相关

依赖 Playwright MCP 执行 X Articles 编辑器的浏览器自动化操作。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshuyi/x-article-publisher-skill --skill x-article-publisher

安装后在 Claude Code 中输入/x-article-publisher确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

scripts/
copy_to_clipboard.py
parse_markdown.py
table_to_image.py

详细介绍

X 文章发布器

将 Markdown 内容发布到 X(Twitter)文章编辑器,并通过富文本转换保留格式。

前提条件

  • 用于浏览器自动化的 Playwright MCP
  • 用户已登录 X,并订阅 Premium Plus
  • Python 3.9+ 及依赖项:
    • macOS:pip install Pillow pyobjc-framework-Cocoa
    • Windows:pip install Pillow pywin32 clip-util
  • Mermaid 图表所需:npm install -g @mermaid-js/mermaid-cli

脚本

位于 ~/.claude/skills/x-article-publisher/scripts/:

parse_markdown.py

解析 Markdown 并提取结构化数据:

python parse_markdown.py <markdown_file> [--output json|html] [--html-only]

返回包含以下字段的 JSON:title、cover_image、content_images、dividers(含用于定位的 block_index)、html、total_blocks

copy_to_clipboard.py

将图片或 HTML 复制到系统剪贴板(跨平台):

# Copy image (with optional compression)
python copy_to_clipboard.py image /path/to/image.jpg [--quality 80]

# Copy HTML for rich text paste
python copy_to_clipboard.py html --file /path/to/content.html
table_to_image.py

将 Markdown 表格转换为 PNG 图片:

python table_to_image.py <input.md> <output.png> [--scale 2]

当 X 文章不支持原生表格渲染或需要统一样式时使用。

预处理(可选)

发布前,扫描 Markdown,查找需要转换的元素:

表格 → PNG
# Extract table to temp file, then convert
python ~/.claude/skills/x-article-publisher/scripts/table_to_image.py /tmp/table.md /tmp/table.png
# Replace table in markdown with: ![Table](/tmp/table.png)
Mermaid 图表 → PNG
# Extract mermaid block to .mmd file, then convert
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.png -b white -s 2
# Replace mermaid block with: ![Diagram](/tmp/diagram.png)
分隔线(---)

parse_markdown.py 会自动检测分隔线,并将其输出到 dividers 数组中。必须通过 X 文章的 Insert > Divider 菜单插入(X 会忽略 HTML <hr> 标签)。

工作流

策略:“先文后图后分割线”

对于包含图片和分隔线的文章,先粘贴所有文本内容,然后根据区块索引在正确位置插入图片和分隔线。

  1. **(可选)**预处理:将表格/Mermaid 图表转换为图片
  2. 使用 Python 脚本解析 Markdown → 获取标题、图片、含 block_index 的 dividers 和 HTML
  3. 前往 X 文章编辑器
  4. 上传封面图片(第一张图片)
  5. 填写标题
  6. 将 HTML 复制到剪贴板(Python)→ 使用 Cmd+V 粘贴
  7. 在 block_index 指定的位置插入正文图片
  8. 在 block_index 指定的位置插入分隔线(通过 Insert > Divider 菜单)
  9. 保存为草稿(绝不自动发布)

高效执行原则

目标:尽量缩短各项操作之间的等待时间,实现流畅的自动化体验。

1. 避免不必要的 browser_snapshot

大多数浏览器操作(click、type、press_key 等)都会在返回结果中包含页面状态。不要在每次操作后单独调用 browser_snapshot,直接使用操作返回的页面状态即可。

❌ 错误做法:
browser_click → browser_snapshot → 分析 → browser_click → browser_snapshot → ...

✅ 正确做法:
browser_click → 从返回结果中获取页面状态 → browser_click → ...
2. 避免不必要的 browser_wait_for

仅在以下情况下使用 browser_wait_for:

  • 等待图片上传完成(textGone="正在上传媒体")
  • 等待页面初始加载(极少数情况)

不要使用 browser_wait_for 等待按钮或输入框出现——页面加载完成后,它们会立即可用。

3. 并行执行独立操作

两个操作之间没有依赖关系时,可以在同一条消息中并行调用多个工具:

✅ 可以并行:
- 填写标题 (browser_type) + 复制HTML到剪贴板 (Bash)
- 解析Markdown生成JSON + 生成HTML文件

❌ 不能并行(有依赖):
- 必须先点击create才能上传封面图
- 必须先粘贴内容才能插入图片
4. 连续执行浏览器操作

每个浏览器操作返回的页面状态都包含所需的所有元素引用。直接使用这些引用执行下一步操作:

# 理想流程(每步直接执行,不额外等待):
browser_navigate → 从返回状态找create按钮 → browser_click(create)
→ 从返回状态找上传按钮 → browser_click(上传) → browser_file_upload
→ 从返回状态找应用按钮 → browser_click(应用)
→ 从返回状态找标题框 → browser_type(标题)
→ 点击编辑器 → browser_press_key(Meta+v)
→ ...
5. 提前完成准备工作

开始浏览器操作前,先完成所有准备工作:

  1. 解析 Markdown,获取 JSON 数据
  2. 将 HTML 文件生成到 /tmp/
  3. 记录 title、cover_image、content_images 等信息

这样,浏览器操作阶段便可连续执行,无需中途停下来处理数据。

第 1 步:解析 Markdown(Python)

使用 parse_markdown.py 提取所有结构化数据:

python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md

输出 JSON:

{
  "title": "Article Title",
  "cover_image": "/path/to/first-image.jpg",
  "cover_exists": true,
  "content_images": [
    {"path": "/path/to/img2.jpg", "original_path": "/md/dir/assets/img2.jpg", "exists": true, "block_index": 5, "after_text": "context..."},
    {"path": "/path/to/img3.jpg", "original_path": "/md/dir/assets/img3.jpg", "exists": true, "block_index": 12, "after_text": "another..."}
  ],
  "html": "<p>Content...</p><h2>Section</h2>...",
  "total_blocks": 45,
  "missing_images": 0
}

关键字段:

  • block_index:应在此索引处的区块元素之后插入图片(从 0 开始计数)
  • total_blocks:HTML 中区块元素的总数
  • after_text:仅供参考/调试,不用于定位
  • exists:是否找到了图片文件(若为 false,上传将失败)
  • original_path:从 Markdown 解析出的路径(自动搜索前)
  • path:实际使用的路径(若经过自动搜索,可能与 original_path 不同)
  • missing_images:在所有位置均未找到的图片数量

将 HTML 保存到临时文件,以便复制到剪贴板:

python parse_markdown.py article.md --html-only > /tmp/article_html.html

第 2 步:打开 X 文章编辑器

浏览器错误处理

如果遇到 Error: Browser is already in use 错误:

# 方案1:先关闭浏览器再重新打开
browser_close
browser_navigate: https://x.com/compose/articles

# 方案2:如果 browser_close 无效(锁定),提示用户手动关闭 Chrome

# 方案3:使用已有标签页,直接导航
browser_tabs action=list  # 查看现有标签
browser_navigate: https://x.com/compose/articles  # 在当前标签导航

最佳实践:每次开始前先使用 browser_tabs action=list 检查状态,避免创建多余的空白标签页。

前往编辑器
browser_navigate: https://x.com/compose/articles

重要:页面加载后会显示草稿列表,而不是编辑器。需要:

  1. 等待页面加载完成:使用 browser_snapshot 检查页面状态
  2. 立即点击 "create" 按钮:不要等待“添加标题”等编辑器元素,它们只有点击 create 后才会出现
  3. 等待编辑器加载:点击 create 后,等待编辑器元素出现
# 1. 导航到页面
browser_navigate: https://x.com/compose/articles

# 2. 获取页面快照,找到 create 按钮
browser_snapshot

# 3. 点击 create 按钮(通常 ref 类似 "create" 或带有 create 标签)
browser_click: element="create button", ref=<create_button_ref>

# 4. 现在编辑器应该打开了,可以继续上传封面图等操作

注意:不要使用 browser_wait_for text="添加标题" 等待页面加载,因为此文本只有点击 create 后才会出现,否则会导致超时。

如果需要登录,请提示用户手动登录。

步骤 3:上传封面图片

  1. 点击“添加照片或视频”按钮
  2. 使用 browser_file_upload 上传封面图片路径(来自 JSON 输出)
  3. 确认图片已上传

步骤 4:填写标题

  • 查找占位文本为“添加标题”的文本框
  • 使用 browser_type 输入标题(来自 JSON 输出)

步骤 5:粘贴文本内容(Python 剪贴板)

使用 Python 将 HTML 复制到系统剪贴板,然后粘贴:

# Copy HTML to clipboard
python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html

然后在浏览器中:

browser_click on editor textbox
browser_press_key: Meta+v

这样可以保留所有富文本格式(H2、粗体、链接、列表)。

步骤 6:插入内容图片(按文字搜索定位)

推荐方法:使用 after_text 文字搜索定位,比 block_index 更直观可靠。

定位原理

每张图片的 after_text 字段记录了它前一个段落的末尾文字(最多 80 个字符)。在编辑器中搜索包含该文字的段落,点击后插入图片。

操作步骤

对于每张内容图片(来自 content_images 数组),按 block_index 从大到小的顺序:

# 1. Copy image to clipboard (with compression)
python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py image /path/to/img.jpg --quality 85
# 2. 在 browser_snapshot 中搜索包含 after_text 的段落
#    找到该段落的 ref

# 3. Click the paragraph containing after_text
browser_click: element="paragraph with target text", ref=<paragraph_ref>

# 4. **关键步骤**: 按 End 键移动光标到行尾
#    这一步非常重要!避免点击到段落中的链接导致位置偏移
browser_press_key: End

# 5. Paste image
browser_press_key: Meta+v

# 6. Wait for upload (only use textGone, no time parameter)
browser_wait_for textGone="正在上传媒体"
为什么需要按 End 键?

问题:当段落包含链接时(如 [链接文字](url)),点击段落可能会:

  • 触发链接编辑弹窗
  • 将光标定位在链接内部而非段落末尾

解决方案:点击段落后立即按 End 键:

  • 确保光标移动到段落末尾
  • 避免链接干扰
  • 图片将正确插入在该段落之后
定位策略

在 browser_snapshot 返回的结构中,搜索 after_text 的关键词:

textbox [ref=editor]:
  generic [ref=p1]:
    - StaticText: "元旦假期我在家里翻手机相册..."  # 如果 after_text 包含这段文字,点击 p1
  heading [ref=h1]:
    - StaticText: "演示"
  generic [ref=p2]:
    - StaticText: "这东西到底有多省事儿?"
    - link [ref=link1]: "Claude Code"  # 注意:段落可能包含链接
  ...
反向插入示例

如果有 3 张图片,block_index 分别为 5、12、27:

  1. 先插入 block_index=27 的图片(搜索 after_text + 按 End + 粘贴)
  2. 再插入 block_index=12 的图片
  3. 最后插入 block_index=5 的图片

按从大到小的顺序插入可以避免位置偏移问题。

步骤 6.5:插入分割线(通过菜单)

重要:Markdown 中的 --- 分割线不能通过 HTML <hr> 标签粘贴(X Articles 会忽略它)。必须通过 X Articles 的“Insert”菜单插入。

操作步骤

对于每条分割线(来自 dividers 数组),按 block_index 从大到小的顺序:

# 1. Click the block element at block_index position
browser_click on the element at position block_index in the editor

# 2. Open Insert menu (Add Media button)
browser_click on "Insert" or "添加媒体" button

# 3. Click Divider menu item
browser_click on "Divider" or "分割线" menuitem

# Divider is inserted at cursor position
与图片的插入顺序

建议先插入所有图片,再插入所有分割线。两者都按 block_index 从大到小的顺序:

  1. 插入所有图片(从最大的 block_index 开始)
  2. 插入所有分割线(从最大的 block_index 开始)

步骤 7:保存草稿

  1. 确认内容已粘贴(检查字数指示器)
  2. 草稿会自动保存;如有需要,也可以点击“Save”按钮
  3. 点击“预览”以确认格式
  4. 报告:“草稿已保存。请检查并手动发布。”

关键规则

  1. 绝不发布——只保存草稿
  2. 第一张图片作为封面——将第一张图片上传为封面图片
  3. 富文本转换——粘贴前始终将 Markdown 转换为 HTML
  4. 使用剪贴板 API——通过剪贴板粘贴以确保格式正确
  5. 按块索引定位——使用 block_index 精确定位图片和分割线
  6. 按反向顺序插入——按 block_index 从大到小插入图片和分割线
  7. H1 标题处理——H1 仅用作标题,不包含在正文中
  8. 通过菜单插入分割线——Markdown --- 必须通过 Insert > Divider 菜单插入(HTML <hr> 会被忽略)

支持的格式

元素支持情况备注
H2 (##)原生支持章节标题
粗体 (**)原生支持强调
斜体 (*)原生支持强调
链接 ([](url))原生支持超链接
有序列表原生支持1. 2. 3.
无序列表原生支持- 项目符号
引用块 (>)原生支持引用文本
代码块已转换→ 引用块
表格已转换→ PNG 图片(使用 table_to_image.py)
Mermaid已转换→ PNG 图片(使用 mmdc)
分割线 (---)通过菜单插入→ Insert > Divider

流程示例

用户:“将 /path/to/article.md 发布到 X”

# Step 1: Parse Markdown
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md > /tmp/article.json
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md --html-only > /tmp/article_html.html
  1. 前往 https://x.com/compose/articles
  2. 上传封面图片(仅对封面使用 browser_file_upload)
  3. 填写标题(来自 JSON:title)
  4. 复制并粘贴 HTML:
    python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html
    
    然后:browser_press_key Meta+v
  5. 对每张内容图片,按 block_index 从大到小的顺序:
    python copy_to_clipboard.py image /path/to/img.jpg --quality 85
    
    • 点击 block_index 位置的块元素
    • browser_press_key Meta+v
    • 等待上传完成
  6. 在预览中确认
  7. “草稿已保存。请检查并手动发布。”

最佳实践

为什么使用 block_index,而不是文字匹配?
  1. 精确定位:不依赖文字内容,即使多处文字相似,也能正确定位
  2. 可靠性:索引是确定性的,不会因文字相似而混淆
  3. 便于调试:保留 after_text 供人工核验
为什么使用 Python,而不是浏览器内的 JavaScript?
  1. 本地处理更可靠:Python 直接操作系统剪贴板,不受浏览器沙盒限制
  2. 图片压缩:上传前压缩图片(--quality 85),减少上传时间
  3. 代码复用:脚本固定不变,无需每次重新编写转换逻辑
  4. 便于调试:脚本可单独测试,容易定位问题
等待策略

重要发现: Playwright MCP 的 browser_wait_for 实际行为是 先等待 time 秒,再检查条件,而非轮询!

// 实际执行的代码:
await new Promise(f => setTimeout(f, time * 1000));  // 先固定等待
await page.getByText("xxx").waitFor({ state: 'hidden' });  // 再检查

正确用法:

  • ✅ 只用 textGone,不设 time:让 Playwright 自己轮询等待
  • ✅ 只用 time:固定等待指定秒数
  • ❌ 同时用 textGone + time:会先等 time 秒再检查,浪费时间
# 推荐:只用 textGone,让它自动等待条件满足
browser_wait_for textGone="正在上传媒体"

# 或者:用 browser_snapshot 轮询检查状态
# 每次操作后检查返回的页面状态,无需额外等待
图片插入效率

每张图片的浏览器操作从5步减少到2步:

  • 旧: 点击 → 添加媒体 → 媒体 → 添加照片 → file_upload
  • 新: 点击段落 → Meta+v
封面图 vs 内容图
  • 封面图: 使用 browser_file_upload(因为有专门的上传按钮)
  • 内容图: 使用 Python 剪贴板 + 粘贴(更高效)

故障排除

MCP 连接问题

如果 Playwright MCP 工具不可用(报错 No such tool available 或 Not connected):

方案1:重新连接 MCP(推荐)

执行 /mcp 命令,选择 playwright,选择 Restart

方案2:清理残留进程后重连

# 杀掉所有残留的 playwright 进程
pkill -f "mcp-server-playwright"
pkill -f "@playwright/mcp"

# 然后执行 /mcp 重新连接

配置文件位置: ~/.claude/mcp_servers.json

浏览器错误处理

如果遇到 Error: Browser is already in use 错误:

# 方案1:先关闭浏览器再重新打开
browser_close
browser_navigate: https://x.com/compose/articles

# 方案2:杀掉 Chrome 进程
pkill -f "Chrome.*--remote-debugging"
# 然后重新 navigate
图片位置偏移

如果图片插入位置不正确(特别是点击含链接的段落时):

原因: 点击段落时可能误触链接,导致光标位置错误

解决方案: 点击后必须按 End 键移动光标到行尾

# 正确流程
1. browser_click 点击目标段落
2. browser_press_key: End        # 关键步骤!
3. browser_press_key: Meta+v     # 粘贴图片
4. browser_wait_for textGone="正在上传媒体"
图片路径找不到

如果 Markdown 中的相对路径图片找不到(如 ./assets/image.png 实际在其他位置):

自动搜索: parse_markdown.py 会自动在以下目录搜索同名文件:

  • ~/Downloads
  • ~/Desktop
  • ~/Pictures

stderr 输出示例:

[parse_markdown] Image not found at '/path/to/assets/img.png', using '/Users/xxx/Downloads/img.png' instead

JSON 字段说明:

  • original_path: Markdown 中指定的路径(解析后的绝对路径)
  • path: 实际使用的路径(如果自动搜索成功,会不同于 original_path)
  • exists: true 表示找到文件,false 表示未找到(上传会失败)

如果仍然找不到:

  1. 检查 JSON 输出中的 missing_images 字段
  2. 手动将图片复制到 Markdown 文件同目录的 assets/ 子目录
  3. 或修改 Markdown 中的图片路径为绝对路径

评论

支持 Markdown