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
- macOS:
- 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内のテーブルを次のように置換: 
Mermaid図 → PNG
# Mermaidブロックを.mmdファイルに抽出し、変換
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.png -b white -s 2
# Mermaidブロックを次のように置換: 
区切り線 (---)
区切り線は parse_markdown.py によって自動的に検出され、dividers 配列に出力されます。X記事の 挿入 > 区切り線 メニューから挿入する必要があります(HTMLの <hr> タグはXでは無視されます)。
ワークフロー
戦略: 「先文後図後分割線」(テキスト優先、画像次、区切り線最後)
画像と区切り線を含む記事の場合、最初にすべてのテキストコンテンツを貼り付け、その後、block_indexを使用して正しい位置に画像と区切り線を挿入します。
- (オプション) 前処理: テーブル/Mermaid図を画像に変換
- PythonスクリプトでMarkdownを解析 → title, images, block_index付きdividers, HTMLを取得
- X記事エディターに移動
- カバー画像をアップロード(最初の画像)
- タイトルを入力
- HTMLをクリップボードにコピー(Python)→ Cmd+Vで貼り付け
- block_indexで指定された位置にコンテンツ画像を挿入
- block_indexで指定された位置に区切り線を挿入(挿入 > 区切り線メニュー経由)
- 下書きとして保存(絶対に自動公開しない)
効率的実行原則
目標: 操作間の待ち時間を最小化し、スムーズな自動化体験を実現します。
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. 事前準備を先に行う
ブラウザ操作を開始する前に、すべての準備を完了します:
- Markdownを解析してJSONデータを取得
- HTMLファイルを /tmp/ に生成
- 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
重要: ページが読み込まれると下書きリストが表示され、エディターは表示されません。次の手順が必要です:
- ページ読み込み完了を待機:
browser_snapshotでページ状態を確認 - すぐに "create" ボタンをクリック: "タイトルを追加" などのエディター要素は、createをクリックするまで表示されないため、これらを待たない
- エディター読み込みを待機: 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: カバー画像のアップロード
- "写真またはビデオを追加" ボタンをクリック
- browser_file_upload でカバー画像のパス(JSON出力から)を指定
- 画像がアップロードされたことを確認
ステップ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 の場合:
- 最初に block_index=27 の画像を挿入(after_text検索 + End + 貼り付け)
- 次に block_index=12 の画像を挿入
- 最後に 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 が大きい順に:
- すべての画像を挿入(最大のblock_indexから開始)
- すべての区切り線を挿入(最大のblock_indexから開始)
ステップ7: 下書きとして保存
- コンテンツが貼り付けられたことを確認(文字数インジケーターを確認)
- 下書きは自動保存されるか、必要に応じて「保存」ボタンをクリック
- 「プレビュー」をクリックして書式を確認
- 報告: 「下書きが保存されました。手動で確認して公開してください。」
重要なルール
- 絶対に公開しない - 下書きとしてのみ保存
- 最初の画像 = カバー画像 - 最初の画像をカバー画像としてアップロード
- リッチテキスト変換 - 貼り付け前に必ずMarkdownをHTMLに変換
- クリップボードAPIを使用 - 適切な書式設定のためにクリップボード経由で貼り付け
- block_indexによる位置決め - 画像と区切り線の正確な配置にblock_indexを使用
- 逆順挿入 - 画像と区切り線はblock_indexが大きい順に挿入
- H1タイトルの処理 - H1はタイトルとしてのみ使用され、本文には含めない
- 区切り線はメニュー経由 - 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
- https://x.com/compose/articles に移動
- カバー画像をアップロード(カバーのみbrowser_file_upload)
- タイトルを入力(JSONの
titleから) - HTMLをコピーして貼り付け:
続いて: browser_press_key Meta+vpython ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html - 各コンテンツ画像について、block_index の逆順で:
python copy_to_clipboard.py image /path/to/img.jpg --quality 85block_indexの位置にあるブロック要素をクリック- browser_press_key Meta+v
- アップロード完了まで待機
- プレビューで確認
- 「下書きが保存されました。手動で確認して公開してください。」
ベストプラクティス
なぜblock_indexなのか、テキストマッチングではないのか?
- 正確な位置決め: テキストコンテンツに依存せず、似たようなテキストが複数ある場合でも正しく位置決め可能
- 信頼性: インデックスは決定的であり、テキストの類似性で混乱しない
- デバッグが容易:
after_textは人間による確認用として保持される
なぜブラウザ内JavaScriptではなくPythonなのか?
- ローカル処理の方が信頼性が高い: Pythonはブラウザのサンドボックス制限を受けずに、直接OSのクリップボードを操作
- 画像圧縮: アップロード前に画像を圧縮(--quality 85)、アップロード時間を短縮
- コードの再利用: スクリプトは固定されており、毎回変換ロジックを再作成する必要なし
- デバッグが容易: スクリプトは個別にテスト可能で、問題を特定しやすい
待機戦略
重要な発見: 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記事の制限事項
- 最大画像数: 記事あたり最大25枚の画像
- 画像サイズ: 各画像は5MB以下
- カバー画像: アスペクト比3:2を推奨(900x600ピクセル以上)
- 文字数制限: 25000文字(記事全体)
- 下書きの保存期間: 無制限(ただし、長期間未公開の下書きは削除される可能性あり)
将来の拡張可能性
- スケジュール公開: X APIがサポートすれば、下書き保存後の自動スケジュール公開
- 複数画像の一括アップロード: 現在は1枚ずつだが、将来的にバッチ処理
- テンプレートサポート: よく使う記事テンプレートの保存と適用

