Communityコーディング&開発github.com

X記事パブリッシャー

Markdown記事をX(Twitter)記事エディターに適切なフォーマットで公開します。Markdownファイル/URLをX記事に公開したい場合や、「Xに公開」「Twitterに記事を投稿」「X記事」などと言及された場合、またはX Premium記事公開の支援を求められた場合に使用します。カバー画像のアップロードとMarkdownからリッチテキストへの自動変換を処理します。

X記事パブリッシャー とは?

このスキルは、Markdown形式で書かれた記事をX(旧Twitter)の記事エディターに公開するための自動化ツールです。ユーザーが指定したMarkdownファイルを解析し、タイトル、カバー画像、本文のリッチテキスト、記事内画像、区切り線などの構造化データを抽出します。Playwright MCPを使用したブラウザ自動化により、Xの記事作成ページにアクセスし、カバー画像のアップロード、タイトルの入力、リッチテキストの貼り付け、画像や区切り線の挿入を自動で行います。主な特徴として、MarkdownのHTMLへの変換による書式維持、Pythonスクリプトによるクリップボード操作と画像圧縮、テーブルやMermaid図のPNG画像への変換、そしてblock_indexに基づく正確な画像・区切り線の配置が挙げられます。最終的には記事を下書きとして保存し、自動公開は行いません。このスキルはClaude Code環境で動作し、X Premium Plusの購読が必要です。

対応✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/wshuyi/x-article-publisher-skill/tree/main/skills/x-article-publisher

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

プレビュー

スキル README より

Table
Diagram

ドキュメント

X記事パブリッシャー は何をしますか?

MarkdownコンテンツをX(Twitter)記事エディターに公開し、リッチテキスト変換で書式を保持します。

前提条件

  • ブラウザ自動化のためのPlaywright MCP
  • Premium PlusサブスクリプションでXにログイン済みのユーザー
  • 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をシステムクリップボードにコピー(クロスプラットフォーム):

# 画像のコピー(オプションで圧縮)
python copy_to_clipboard.py image /path/to/image.jpg [--quality 80]

# リッチテキスト貼り付け用のHTMLコピー
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

# テーブルを一時ファイルに抽出し、変換
python ~/.claude/skills/x-article-publisher/scripts/table_to_image.py /tmp/table.md /tmp/table.png
# Markdown内のテーブルを次のように置換: ![Table](https://raw.githubusercontent.com/wshuyi/x-article-publisher-skill/main/tmp/table.png)

Mermaid図 → PNG

# Mermaidブロックを.mmdファイルに抽出し、変換
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.png -b white -s 2
# Mermaidブロックを次のように置換: ![Diagram](https://raw.githubusercontent.com/wshuyi/x-article-publisher-skill/main/tmp/diagram.png)

区切り線 (---)

区切り線は parse_markdown.py によって自動的に検出され、dividers 配列に出力されます。X記事の 挿入 > 区切り線 メニューから挿入する必要があります(HTMLの <hr> タグはXでは無視されます)。

ワークフロー

戦略: 「先文後図後分割線」(テキスト優先、画像次、区切り線最後)

画像と区切り線を含む記事の場合、最初にすべてのテキストコンテンツを貼り付け、その後、block_indexを使用して正しい位置に画像と区切り線を挿入します。

  1. (オプション) 前処理: テーブル/Mermaid図を画像に変換
  2. PythonスクリプトでMarkdownを解析 → title, images, block_index付きdividers, HTMLを取得
  3. X記事エディターに移動
  4. カバー画像をアップロード(最初の画像)
  5. タイトルを入力
  6. HTMLをクリップボードにコピー(Python)→ Cmd+Vで貼り付け
  7. block_indexで指定された位置にコンテンツ画像を挿入
  8. block_indexで指定された位置に区切り線を挿入(挿入 > 区切り線メニュー経由)
  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. 独立した操作の並列実行

2つの操作に依存関係がない場合、同じメッセージ内で複数のツールを並列に呼び出すことができます:

✅ 並列可能:
- タイトルの入力 (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": "記事タイトル",
  "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": "コンテキスト..."},
    {"path": "/path/to/img3.jpg", "original_path": "/md/dir/assets/img3.jpg", "exists": true, "block_index": 12, "after_text": "別のコンテキスト..."}
  ],
  "html": "<p>コンテンツ...</p><h2>セクション</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をシステムクリップボードにコピーし、貼り付け:

# HTMLをクリップボードにコピー
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. 画像をクリップボードにコピー(圧縮あり)
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. after_text を含む段落をクリック
browser_click: element="対象テキストを含む段落", ref=<paragraph_ref>

# 4. **重要**: Endキーを押してカーソルを行末に移動
#    このステップは非常に重要!段落内のリンクを誤ってクリックし、位置がずれるのを防ぐ
browser_press_key: End

# 5. 画像を貼り付け
browser_press_key: Meta+v

# 6. アップロード完了を待機(textGoneのみ使用、timeパラメータは不要)
browser_wait_for textGone="メディアをアップロード中"

なぜEndキーが必要か?

問題: 段落にリンクが含まれている場合(例: [リンクテキスト](https://raw.githubusercontent.com/wshuyi/x-article-publisher-skill/main/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記事はこれを無視します)。X記事の「挿入」メニューから挿入する必要があります。

操作手順

各区切り線(dividers 配列から)について、block_index の逆順で:

# 1. block_index の位置にあるブロック要素をクリック
browser_click on the element at position block_index in the editor

# 2. 挿入メニューを開く(メディア追加ボタン)
browser_click on "挿入" or "メディアを追加" button

# 3. 区切り線メニューアイテムをクリック
browser_click on "区切り線" or "Divider" menuitem

# カーソル位置に区切り線が挿入される

画像との挿入順序

最初にすべての画像を挿入し、次にすべての区切り線を挿入することをお勧めします。どちらも block_index が大きい順に:

  1. すべての画像を挿入(最大のblock_indexから開始)
  2. すべての区切り線を挿入(最大のblock_indexから開始)

ステップ7: 下書きとして保存

  1. コンテンツが貼り付けられたことを確認(文字数インジケーターを確認)
  2. 下書きは自動保存されるか、必要に応じて「保存」ボタンをクリック
  3. 「プレビュー」をクリックして書式を確認
  4. 報告: 「下書きが保存されました。手動で確認して公開してください。」

重要なルール

  1. 絶対に公開しない - 下書きとしてのみ保存
  2. 最初の画像 = カバー画像 - 最初の画像をカバー画像としてアップロード
  3. リッチテキスト変換 - 貼り付け前に必ずMarkdownをHTMLに変換
  4. クリップボードAPIを使用 - 適切な書式設定のためにクリップボード経由で貼り付け
  5. block_indexによる位置決め - 画像と区切り線の正確な配置にblock_indexを使用
  6. 逆順挿入 - 画像と区切り線はblock_indexが大きい順に挿入
  7. H1タイトルの処理 - H1はタイトルとしてのみ使用され、本文には含めない
  8. 区切り線はメニュー経由 - Markdownの --- は「挿入 > 区切り線」メニューから挿入する必要がある(HTMLの <hr> は無視される)

サポートされる書式

要素サポート備考
H2 (##)ネイティブセクション見出し
太字 (**)ネイティブ強調
斜体 (*)ネイティブ強調
リンク ([](https://raw.githubusercontent.com/wshuyi/x-article-publisher-skill/main/url))ネイティブハイパーリンク
順序付きリストネイティブ1. 2. 3.
順序なしリストネイティブ- 箇条書き
ブロック引用 (>)ネイティブ引用テキスト
コードブロック変換→ ブロック引用
テーブル変換→ PNG画像(table_to_image.pyを使用)
Mermaid変換→ PNG画像(mmdcを使用)
区切り線 (---)メニュー挿入→ 挿入 > 区切り線

実行例

ユーザー: "/path/to/article.md をXに公開して"

# Step 1: 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 は人間による確認用として保持される

なぜブラウザ内JavaScriptではなくPythonなのか?

  1. ローカル処理の方が信頼性が高い: Pythonはブラウザのサンドボックス制限を受けずに、直接OSのクリップボードを操作
  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"
# その後、再度移動

画像の位置ずれ

画像の挿入位置が正しくない場合(特にリンクを含む段落をクリックした場合):

原因: 段落をクリックした際に誤ってリンクをトリガーし、カーソル位置がずれる

解決策: クリック後、必ずEndキーを押してカーソルを行末に移動する

# 正しいフロー
1. browser_click で対象段落をクリック
2. browser_press_key: End        # 重要!
3. browser_press_key: Meta+v     # 画像を貼り付け
4. browser_wait_for textGone="メディアをアップロード中"

画像パスが見つからない

Markdown内の相対パス画像が見つからない場合(例: ./assets/image.png が実際には別の場所にある):

自動検索により、スクリプトは複数の場所を検索します:

  • Markdownファイルからの相対パス
  • 元のパス(Markdownが移動された場合)
  • 絶対パスとして直接

検索後も見つからない場合、exists: false とマークされ、アップロードはスキップされます。

クリップボードの問題

macOSでクリップボード操作が失敗する場合:

# クリップボードデーモンが実行中か確認
pbpaste | head -n5
# 空の場合は手動で何かをコピーしてテスト

# 権限の問題が疑われる場合:
python -c "import subprocess; subprocess.run(['pbcopy'], input='test', text=True)"

補足

X記事の制限事項

  1. 最大画像数: 記事あたり最大25枚の画像
  2. 画像サイズ: 各画像は5MB以下
  3. カバー画像: アスペクト比3:2を推奨(900x600ピクセル以上)
  4. 文字数制限: 25000文字(記事全体)
  5. 下書きの保存期間: 無制限(ただし、長期間未公開の下書きは削除される可能性あり)

将来の拡張可能性

  • スケジュール公開: X APIがサポートすれば、下書き保存後の自動スケジュール公開
  • 複数画像の一括アップロード: 現在は1枚ずつだが、将来的にバッチ処理
  • テンプレートサポート: よく使う記事テンプレートの保存と適用

関連スキル