← knym.net

Claude in ChromeでXのスレッド予約を壊れずに回す

·8 min read
Claude in ChromeClaude CodeX自動化

物販ブログの X アカウントで、商品紹介をスレッドで予約投稿している。1本目に感想と写真、2本目に価格とアフィリエイトリンク、という2本立てだ。X の API は使っていない。自分の Chrome を Claude in Chrome で動かしてもらって、普段の画面から予約している。

今日は、Claude Code に下書きを作ってもらって中身を確かめたあと、これだけ送った。

Welleg予約して

Claude は X の作成画面を開き、画像2枚を付けて、2本の本文を入れ、11月6日 12:37 に予約し、予約一覧で確認するところまで進めた。ブラウザへの指示は7回で、1回も入れ直していない。

最初からこうだったわけではない。9月に7本続けて予約してもらったときは、往復が80回ほどかかっていた。

座標クリックとタイピングで壊れたこと

Claude がこれまでにつまずいて、そのたびにメモに残していたのは、こんなことだった。

いちばん怖いのは最後の件だ。X は投稿したあとで直せない。予約一覧の表示がおかしいのに気づかなければ、違う商品の写真のまま公開されるところだった。

いまの手順

壊れた操作を1つずつ JS に置き換えていった結果、いまはこの順で動いている。

1. モーダルを起点に要素を取る

/compose/post を開くと、作成モーダルは #layers の中にできる。要素はいつもここから取る。

const L = document.getElementById('layers');
const ta0 = L.querySelector('[data-testid="tweetTextarea_0"]');

画像は Claude in Chrome の file_upload で、モーダルの中の file input に渡す。

2. 本文は paste イベントで入れる

キーボードもシステムのクリップボードも使わない。DataTransfer に文字を入れて、paste イベントを入力欄に投げる。X の入力欄は貼り付けとして受け取るので、改行もそのまま入る。

const paste = (el, text) => {
  el.focus();
  const dt = new DataTransfer();
  dt.setData('text/plain', text);
  el.dispatchEvent(new ClipboardEvent('paste', {
    clipboardData: dt, bubbles: true, cancelable: true,
  }));
};
paste(ta0, '1本目の本文\n\n2段落目');

入ったかどうかは、段落ごとの要素を読んで確かめる。

[...L.querySelectorAll('[data-testid="tweetTextarea_0"] [data-block="true"]')]
  .map(b => b.textContent);
// ["1本目の本文", "", "2段落目"]

3. ボタンは React の onClick を直接呼ぶ

「ポストを追加」「予約」「確認する」「予約設定」は、座標でクリックせずに、要素に付いている React の props から onClick を呼ぶ。

const press = (b) => {
  const pk = Object.keys(b).find(k => k.startsWith('__reactProps'));
  b[pk].onClick({
    preventDefault() {}, stopPropagation() {},
    currentTarget: b, target: b, nativeEvent: {}, type: 'click',
  });
};
press(L.querySelector('[data-testid="addButton"]'));      // ポストを追加
paste(L.querySelector('[data-testid="tweetTextarea_1"]'), '2本目\nhttps://a.r10.to/xxxxxx ad');
press(L.querySelector('[data-testid="scheduleOption"]')); // 予約

4. 日時の select はネイティブの setter で変える

予約画面には、月、日、年、時、分、午前か午後かを選ぶ select が6つ並ぶ。s.value = '11' と代入しただけでは React の state が変わらないので、HTMLSelectElement のネイティブの setter を使ってから change を投げる。

const vals = ['11', '6', '2026', '12', '37', 'pm'];
const setter = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value').set;
[...L.querySelectorAll('select')].slice(0, 6).forEach((s, i) => {
  setter.call(s, vals[i]);
  s.dispatchEvent(new Event('change', { bubbles: true }));
});

select の id は、予約を編集し直すと別の番号に変わる。なので id ではなく並び順で取っている。

5. 確定して、一覧で確かめる

「確認する」を押すと、モーダルに「2026年11月6日(金)の午後0:37に送信されます」と出る。ここで本文と添付の枚数をもう一度読み、tweetButton(予約設定)を押す。最後に /compose/post/unsent/scheduled を開き、一覧に同じ日時の予約があるかを見る。

気をつけていること

やってみて

画面を人間のように操作してもらうより、ページの中で何が起きているかに合わせて JS で触ってもらうほうが、ずっと壊れにくかった。自分の役目は、下書きを読んで「予約して」と言うことと、つまずいたときに何が起きたかを一緒に見ることだけになった。手順は Claude がつまずくたびに自分でメモへ書き足していて、次のセッションはそのメモを読んでから始めている。