全部技能 / 内容创作 / x-article-publisher
内容创作 · wshuyi/x-article-publisher-skill

x-article-publisher

Publish Markdown articles to X (Twitter) Articles editor with proper formatting. Use when user wants to publish a Markdown file/URL to X Articles, or mentions "publish to X", "post article to Twitter", "X article", or wants help with X Premium article publishing. Handles cover image upload and converts Markdown to rich text automatically.

风险提醒:橙色 · 评估后使用AI 侦查报告
作者 wshuyiGitHub wshuyi/x-article-publisher-skill ↗Stars 867许可 MIT(仓库根 LICENSE:Copyright (c) 2024 wshuyi;gh api spdx=MIT)commit eb3197d1d8
agent 宿主通常会约束 skill 执行权限;风险提醒为 AI 侦查观点,不构成质量或安全保证。第三方 skill 仅作拆解与展示,安装使用风险自负,版权归原作者。

1实现原理 · 为什么它能做到

三段式管线:Markdown 文件先被本地 Python 脚本解析成结构化数据(JSON + 供粘贴的 HTML),再由宿主提供的 Playwright MCP 浏览器工具把内容灌进 X Articles 编辑器,最后只落成草稿;脚本层与浏览器层职责分离,脚本完全不碰网络。

README.md
Markdown File ↓ Python parsing Structured Data (title, images with block_index, HTML) ↓ Playwright MCP X Articles Editor (browser automation) ↓ Draft Saved (never auto-publishes)
注:README 的开篇架构图就是这份 skill 的全部机制:解析、投放、止于草稿三步,没有服务端、没有 X API。

块级切分是全部定位能力的地基:split_into_blocks 逐行扫描,把代码块(用 ___CODE_BLOCK_START/END 包裹,含未闭合兜底)、'---' 分割线、'#'/'>' 起始的标题与引用、独占一行的图片各自切成独立 block,其余连续行合并成段落块。

skills/x-article-publisher/scripts/parse_markdown.py
def split_into_blocks(markdown: str) -> list[str]: """Split markdown into logical blocks (paragraphs, headers, quotes, code blocks, etc.)."""
注:纯标准库正则/状态机,无 markdown 解析库;块序即后续 block_index 的坐标系。

图片与分割线在解析阶段就被从正文里剥离:extract_images_and_dividers 逐个 block 判断 ___DIVIDER___ 或图片正则,记录 block_index = 已收集 clean block 的数量,并取前一块最后一行前 80 字符作 after_text;剥离后的正文才去做 HTML 转换。

skills/x-article-publisher/scripts/parse_markdown.py
block_index = len(clean_blocks) after_text = "" if clean_blocks: prev_block = clean_blocks[-1].strip() lines = [l for l in prev_block.split('\n') if l.strip()] after_text = lines[-1][:80] if lines else ""
注:索引是『插到这个块之后』的语义;after_text 只作人工核验用。

标题抽取有明确的优先级与去重规则:extract_title 依次尝试首个 H1、H2、首个非图片非空行;若标题来自 H1,则把那一行从 Markdown 中删除,避免正文里再出现一次标题。

skills/x-article-publisher/scripts/parse_markdown.py
# Remove H1 title line from markdown to avoid duplication
注:对应 SKILL.md Critical Rule 7『H1 只当标题不进正文』。

富文本转换不是用 markdown 库,而是一串顺序敏感的正则替换:代码块先被转成 <blockquote>(因为 X 不支持 <pre><code>),随后 H2/H3→h2/h3、**→strong、*→em、[text](url)→a、>→blockquote、- 与 1.→li 并包进 ul,最后按空行切段、段内换行转 <br> 并用 <p> 包裹。

skills/x-article-publisher/scripts/parse_markdown.py
# Convert to blockquote format since X Articles doesn't support <pre><code>
注:顺序很关键:代码块标记先处理,否则块内的 * 和 * 会被当成强调语法。

进剪贴板走操作系统原生剪贴板 API 而非第三方库:macOS 分支用 pyobjc 的 NSPasteboard,先 clearContents 再 setData_forType_ 写 HTML 类型,并同时 setString_forType_ 写一份纯文本副本,所以 Cmd+V 到 X 编辑器能保留 H2/粗体/链接等富文本。

skills/x-article-publisher/scripts/copy_to_clipboard.py
pasteboard.setData_forType_(ns_data, NSPasteboardTypeHTML) # Also set plain text version pasteboard.setString_forType_(html, NSPasteboardTypeString)
注:Windows 对应实现是 clip-util 的 Clipboard()["html"];两条分支都未使用 pyperclip。

图片入剪贴板前可选压缩并用平台格式封装:compress_image 用 Pillow 打开图、必要时转 RGB、thumbnail 限制到 (2000,2000) 后按 JPEG quality 写入 BytesIO;Windows 分支还要存成 BMP 并裁掉 14 字节 BITMAPFILEHEADER,再把剩下的 DIB 数据塞进 CF_DIB。

skills/x-article-publisher/scripts/copy_to_clipboard.py
data = output.getvalue()[14:] # Skip BITMAPFILEHEADER
注:这句注释解释了为什么 Windows 路径必须手工去掉 BMP 文件头——剪贴板只认裸 DIB。

表格转图完全自绘:table_to_image.py 自己解析 Markdown 表格(识别 :--- 对齐标记),用 Pillow ImageDraw 以 textbbox 量列宽、画圆角外框/表头底色/行分隔线,并按 macOS(sfns/pingfang/stheiti)→Linux(dejavu)→Windows(arial/msyh) 的顺序逐个尝试 truetype 字体,全失败才回退位图字体。

skills/x-article-publisher/scripts/table_to_image.py
for path in font_paths: try: return ImageFont.truetype(path, size) except (OSError, IOError): continue
注:字体候选里含 PingFang 与微软雅黑(C:/Windows/Fonts/msyh.ttc),说明作者刻意照顾中文表格。

2核心能力

01Markdown 块级解析并输出 block_index 定位数据(标题 / 封面图 / 内容图 / 分割线 / total_blocks)
02Markdown→HTML 富文本转换(H2/H3、粗体、斜体、链接、引用、有序/无序列表、代码块降级为引用块)
03系统剪贴板写入富文本(macOS NSPasteboard HTML+String;Windows clip-util html)
04系统剪贴板写入图片并可选压缩(--quality 触发 JPEG 压缩,--max-width/--max-height 限制 2000×2000)
05Markdown 表格转 PNG(含表头底色、圆角边框、列对齐、--scale 高清倍率)
06分割线(---)检测与独立索引,供 agent 通过 X 的 Insert > Divider 菜单插入
07图片路径回退搜索:原地找不到时按文件名依次搜索 ~/Downloads、~/Desktop、~/Pictures,并在 stderr 报告替换
08标题与前置元数据处理:YAML frontmatter 自动跳过,H1 提为标题并从正文移除

3外部依赖

类型依赖
clipython3(解释器,脚本 shebang 与文档命令均为 python3/python)
climmdc(@mermaid-js/mermaid-cli,Mermaid→PNG,需 npm 全局安装)
clipkill(排障步骤里杀 Playwright MCP 相关进程)
packagePillow(image 压缩、BMP/DIB 封装、表格自绘)
packagepyobjc-framework-Cocoa(macOS AppKit/Foundation,NSPasteboard)
packagepywin32(win32clipboard/CF_DIB)与 clip-util(Windows HTML 剪贴板)
networkPlaywright MCP(宿主浏览器自动化工具集 browser_navigate/browser_click/browser_press_key 等;skill 不自带浏览器驱动)
networkX Articles 编辑器(被自动化访问的站点,读取用户已登录会话)
apiX (Twitter) Premium Plus 订阅(Articles 功能的准入前提,非 API 调用)

4风险提醒 风险提醒:橙色 · 评估后使用

风险提醒:橙色 · 评估后使用
  • SKILL.md 内部策略自相矛盾,可能直接导致图片插错位置 — Step 6 明写『使用 after_text 文字搜索定位,比 block_index 更直观可靠』,Critical Rules 第 5 条却要求用 block_index,README 又称文本匹配已被替换;agent 若按 Step 6 执行就退回到作者自己承认不稳定的方法。
  • 官方描述夸大输入能力(URL) — frontmatter 承诺 'publish a Markdown file/URL',实现只有本地文件路径;若 agent 自行补 URL 抓取,会把不受控网页内容带进剪贴板与 X 编辑器。
  • 剪贴板被清空覆写,且图片可能来自用户个人目录 — copy_to_clipboard.py 调用 clearContents()/EmptyClipboard() 覆盖用户剪贴板;SEARCH_DIRS 会在 ~/Downloads、~/Desktop、~/Pictures 按同名回退找图并上传到 X,存在误传私人图片的隐私风险。
  • 『绝不发布』只是提示词约束,非技术强制 — 仓库中没有禁用发布按钮或校验状态的代码,全部依赖 SKILL.md 文本;在用户已登录的 X 会话里,agent 的操作权限并未被代码层收窄。
  • 依赖未锁版本、平台声明不一致、安装路径硬编码 — 无 requirements.txt/package.json,pip 与 `npm install -g @mermaid-js/mermaid-cli` 均无版本固定;plugin.json platforms 仅 macos 而 README 说 Windows 可用、Linux 未完成;SKILL.md 硬编码 ~/.claude/skills/x-article-publisher/scripts/ 路径。
  • 文档指示读取用户配置文件与杀进程 — docs/GUIDE.md 给出 `cat ~/.claude/settings.json | grep playwright`(该文件可能含 API key)与 `pkill -f "mcp-server-playwright"`,属于对用户环境有实际副作用的排障动作。
橙档。三条源码证据:① 明文依赖用户已登录的账号会话——SKILL.md 写 'If login needed, prompt user to log in manually.',docs/GUIDE.md 3.4 注意事项写 '2. **已登录 X**:确保浏览器中已登录你的 X 账号',即自动化全程运行在用户的 X 身份下,属对 cookie/会话能力的实质使用(skill 自身不读取);② 依赖第三方包与全局 npm 包且无版本锁定——plugin.json 写 python 依赖 Pillow/pyobjc-framework-Cocoa,SKILL.md 写 'npm install -g @mermaid-js/mermaid-cli',仓库内无 requirements.txt;③ 会改写用户环境——copy_to_clipboard.py 的 'pasteboard.clearContents()' 与 'win32clipboard.EmptyClipboard()' 清空并覆盖剪贴板,table_to_image.py 的 "img.save(output, 'PNG')" 写文件,docs/GUIDE.md 还给出读取 ~/.claude/settings.json 与 pkill 杀进程的指令。未定红档:脚本无 eval/exec/subprocess/os.system,无任意代码执行面;无 TLS 校验降级、无反反爬绕过、无批量定向采集;唯一网络目的地是 X 官方域名,且由宿主 MCP 浏览器发起。

5第二遍独立确认

  • [ok] 全部 evidence.quote 逐字存在性(Python 子串校验,覆盖 SKILL.md/两个 README/GUIDE/三个脚本/plugin.json/LICENSE) — 所有引用均为原文精确子串,未发现 paraphrase 冒充 quote;含反斜杠的正则引用按源码原样保留。
  • [ok] 网络调用反查(requests|urllib|httpx|socket|aiohttp|curl|wget|api.x.com|api.twitter) — 仅 parse_markdown.py 的 `import urllib.parse` 与 `urllib.parse.unquote(img_path)` 命中,用途是 URL 解码文件名;无任何 HTTP 客户端。
  • [discrepancy] 凭证/密钥反查(token|api_key|environ|getenv|.env|cookie|oauth|Bearer) — 脚本层零命中,确认无凭证读取;但文档层 docs/GUIDE.md 指示 `cat ~/.claude/settings.json | grep playwright`,会读取可能含 API key 的 Claude Code 配置文件——第一遍未计入,已在 security.credential_reads 补记。
  • [ok] 隐藏文件与额外可执行体反查(find 全清单 + .gitignore) — 仓库仅 13 个非 .git 条目:3 个 py 脚本、SKILL.md、GUIDE.md、两个 README、plugin.json、LICENSE、.gitignore、skills/、docs/、.claude-plugin/;无隐藏脚本、无二进制、无混淆内容。
  • [ok] 外部依赖调用点复核(Pillow / pyobjc / pywin32 / clip-util / mmdc / pkill / Playwright MCP) — Pillow 在 copy_to_clipboard.py 与 table_to_image.py 的 import 行可定位;mmdc 与 pkill 只出现在 SKILL.md 文档指令中,仓库无脚本调用它们;Playwright MCP 只在 plugin.json 与 SKILL.md 工具名中出现,skill 不自带驱动。
  • [discrepancy] 功能声明 vs 实际能力:frontmatter 宣称 'publish a Markdown file/URL to X Articles' — 夸大:parse_markdown.py 只有 `with open(filepath, 'r', encoding='utf-8') as f:` 与 `os.path.exists(args.file)`,仅接受本地文件,全仓库无 URL 抓取分支。若 agent 照字面接受 URL 输入,会自行引入不受控的网页内容。
  • [discrepancy] 定位策略一致性:SKILL.md Step 6 推荐 after_text 文本搜索 vs Critical Rules 第 5 条与 README 声称的 block_index — SKILL.md 自身矛盾:Step 6 标题写 '(Text Search Positioning)' 且正文称『推荐方法: 使用 after_text 文字搜索定位,比 block_index 更直观可靠』,而同一文件 Critical Rules 写『5. Block index positioning - Use block_index for precise image/divider placement』,README 又称 v1.1 已用 block_index 替换文本匹配。agent 执行时选错策略会导致图片错位。
  • [discrepancy] 版本一致性:plugin.json version 与 README/changelog 版本号、日期 — plugin.json 为 version 1.1.0,README 顶部却写 v1.2.0;changelog 条目为 v1.2.0 (2025-01)、v1.1.0 (2025-12)、v1.0.0 (2025-12),v1.2.0 的月份早于 v1.1.0,与 pin commit 日期 2026-01-25 也对不上,疑为文档笔误。

6结论

  • 零官方 API 依赖,绕开 X 开发者账号与 OAuth 审批
  • 唯一不可逆动作(发布)被显式排除在自动化外
  • 位置定位是确定性的,不靠文本模糊匹配
  • 核心解析脚本零第三方依赖,易审计易复用
  • 表格转图自绘,中文表格可用
  • 适合:适合已在 X 开通 Premium Plus、习惯用 Markdown 写作并愿意让 agent 接管浏览器操作的个人:把一篇含标题/图片/引用/列表的中英文文章从 Markdown 变成 X Articles 草稿,格式与图片位置由脚本计算、人工只做最后预览与发布。也适合作为『本地脚本做确定性转换 + MCP 浏览器做投递』这种半自动管线的参考实现。macOS 体验最完整(plugin.json 只声明 macos),Windows 的富文本剪贴板质量取决于 clip-util。
    不适合:不适合期望一键全自动发布的场景(设计上永远止步草稿);不适合 Linux 用户(copy_to_clipboard 显式报 Unsupported platform,README 自认未完成);不适合没有 X Premium Plus 的账号;不适合含大量复杂嵌套语法/原生表格/数学公式的文章(表格与 Mermaid 需先人工转成图片,代码块会被降级成引用块);不适合把外部不可信 Markdown 直接喂入并要求高保真的场景(无 HTML 转义、定位策略文档自相矛盾);也不适合需要审计发布链路的团队——剪贴板与浏览器会话层面的操作无法留痕或回放。
    安装 agent 直装可复制
    ① 本站镜像 更新 2026-09-15
    方式 A · 人下载镜像包下载 x-article-publisher.tar.gz
    sha256: 61755757ff05c8f7…
    方式 B · JSON 格式安装指南,复制给 agent
    安装指南
    agent 读 JSON 指南后会自动从本站下载安装,无需更多说明。
    ② 上游 GitHub · 原始来源
    能访问 GitHub?直接去上游安装(实时版,可能已更新)GitHub 原始 ↗
    本页镜像锁定 commit eb3197d1d8;上游为实时仓库。
    来源信息 GitHub 原始
    作者 / 仓库wshuyi / wshuyi/x-article-publisher-skill
    Stars867
    最近推送2026-01-25
    本 skill commiteb3197d1d8
    许可MIT(仓库根 LICENSE:Copyright (c) 2024 wshuyi;gh api spdx=MIT)
    本站信息
    收录日期2026-09-06
    分类内容创作
    侦查报告AI 侦查 · 2 遍 · 2026-09-06
    本站镜像与 GitHub 原始是不同来源:本站锁定 commit 快照经 /r2 分发;GitHub 为实时上游,内容可能已更新。
    同分类邻近