Communitygithub.com

Twitter 技能

透過 Chrome 電腦操作模式,將文字和圖片發佈到 X (Twitter)。

Twitter 技能 是什麼?

此技能提供了一套完整的 Twitter (X) 自動化操作流程,允許 AI 代理透過混合執行模式發佈推文、刪除推文以及匯出推文。它首先嘗試使用官方的 X API 進行快速且穩定的文字發佈;如果 API 調用失敗(例如因配額限制)或未配置 API 憑證,則會自動回退至 Chrome 瀏覽器自動化模式來模擬使用者操作。對於環境中缺乏電腦操作工具的情況,該技能也提供了 CLI Bridge 模式,可透過平台特定的腳本(如 macOS 的 AppleScript、Linux 的 bash、Windows 的 PowerShell)來執行 JavaScript 與瀏覽器互動。此外,它也包含了詳細的 DOM 操作指南,用於處理 X 的 React/Draft.js 編輯器,並支援圖片附件(最多四張)。最後,該技能還包含了刪除推文和匯出推文的完整工作流程,讓 AI 代理能全面管理使用者的 Twitter 帳號。

相容平台✓Claude Code~Codex CLI~Cursor✓Antigravity
npx skills add hankunpeng/hkp-skills

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Twitter 技能 是做什麼的?

透過 Chrome 電腦操作模式,將文字和圖片發佈到 X (Twitter)。

腳本目錄

重要:所有腳本都位在此技能的 scripts/ 子目錄中。

代理執行說明:

  1. 確定此 SKILL.md 檔案所在的目錄路徑為 {baseDir}
  2. 腳本路徑:
    • 剪貼簿輔助工具:{baseDir}/scripts/copy-to-clipboard.ts
    • 推文匯出/擷取工具:{baseDir}/scripts/export-tweets.ts
    • 官方 X API 輔助工具:{baseDir}/scripts/x-api.ts
  3. 解析 ${BUN_X} 執行環境:若已安裝 bun → bun;若 npx 可用 → npx -y bun;否則建議安裝 bun
  4. 將本文檔中的所有 {baseDir} 和 ${BUN_X} 替換為實際值

執行模式

此技能採用混合執行模式:

  1. 優先使用 API(建議用於文字發佈):嘗試使用官方 X API 腳本 (x-api.ts) 發佈推文。此方式快速、穩定,且不消耗瀏覽器資源。
  2. 瀏覽器備援:若 API 調用失敗(例如每月 1500 則推文的免費方案配額已達上限、遭遇速率限制),或者 subscription.yaml 中未配置 API 憑證,則自動回退至Chrome 電腦操作模式 / CLI Bridge 來模擬瀏覽器操作。

前置需求

  • 使用 API 模式:請在你的本地端 subscription.yaml 檔案中配置 X API 憑證(OAuth 1.0a 使用者上下文權杖),此檔案已被 .gitignore 忽略:
    x_api:
      api_key: "YOUR_API_KEY"
      api_key_secret: "YOUR_API_KEY_SECRET"
      access_token: "YOUR_ACCESS_TOKEN"
      access_token_secret: "YOUR_ACCESS_TOKEN_SECRET"
    
  • 使用瀏覽器備援模式:需已安裝 Google Chrome、在 Chrome 中登入 X (Twitter),並(在 macOS 上)授予必要的無障礙功能權限。

一般發文工作流程(文字與圖片)

執行發佈時:

  1. 開始代理回合,調用 get_app_state(或等效工具)來檢查 Google Chrome 的狀態。
  2. 開啟或將 Google Chrome 導航至 https://x.com/compose/post。
  3. 找到推文編輯器輸入框。
  4. 使用電腦操作的鍵盤輸入,在編輯器中輸入貼文文字。
  5. 若有圖片需要附加(最多 4 張): 對於每張圖片: a. 執行剪貼簿輔助腳本,將圖片複製到剪貼簿:
    ${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts image /absolute/path/to/image.png
    
    b. 使用貼上快捷鍵(macOS 為 super+v,Windows/Linux 為 control+v)將圖片貼到編輯器中。 c. 等待 2-3 秒,直到 X 完成媒體上傳。
  6. 發佈安全:在未取得當前對話中使用者明確的最終確認前,切勿點擊 Publish、Post 或任何等效按鈕來發佈推文。
  7. 使用者確認後,點擊 Post 按鈕進行發佈。
  8. 發佈後,關閉編輯器視窗,以免 UI 停留在撰寫對話框。使用關閉按鈕或 Escape 鍵:
    • DOM 選擇器:[data-testid="app-bar-close"] 或 [aria-label="Close"]
    • 備援方案:觸發一個 Escape 鍵按下事件
  9. 自動重新整理動態(選擇性):若使用者有其他分頁開啟個人檔案頁面(例如 x.com/[username])或首頁動態(x.com/home),請重新整理它們以使新推文立即顯示。
var closeBtn = document.querySelector('[data-testid="app-bar-close"], [aria-label="Close"]');
if (closeBtn) { closeBtn.click(); }
else { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true })); }

CLI Bridge(無電腦操作工具)

當環境缺乏電腦操作的鍵盤/滑鼠工具時,請使用平台特定的方法來開啟 Chrome 並將 JavaScript 注入頁面。

macOS (AppleScript)

開啟撰寫頁面:

open -a "Google Chrome" "https://x.com/compose/post"

在 Chrome 中執行 JavaScript — 先將 JS 寫入暫存檔案(避免 shell 跳脫問題),再透過 AppleScript 執行:

cat > /tmp/tweet.js << 'EOF'
(function() { /* your code */ })();
EOF

osascript -e '
tell application "Google Chrome"
    activate
    set js to read "/tmp/tweet.js"
    set result to execute front window'"'"'s active tab javascript js
    return result
end tell'

模式:始終先將 JS 內容寫入暫存檔案。不要嘗試在 osascript -e 中內嵌 JS — 引號/跳脫衝突會導致解析錯誤。

發佈後關閉編輯器(否則視窗會保持開啟):

var closeBtn = document.querySelector('[data-testid="app-bar-close"], [aria-label="Close"]');
if (closeBtn) { closeBtn.click(); }
else { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true })); }

重新整理個人檔案/首頁分頁以立即反映新貼文(選擇性):

osascript -e '
tell application "Google Chrome"
    tell window 1
        set tabList to every tab whose URL contains "x.com/hankunpeng" or URL contains "x.com/home"
        repeat with t in tabList
            reload t
        end repeat
    end tell
end tell'

Linux

google-chrome "https://x.com/compose/post"
# 使用 chrome-remote-interface 或類似 CDP 客戶端來執行 JS

Windows

Start-Process "chrome" "https://x.com/compose/post"
# 透過 --remote-debugging-port 或 PowerShell 自動化使用 CDP

JavaScript / DOM 自動化指南(備援)

如果無法直接使用電腦操作的鍵盤/滑鼠事件,或者你正在瀏覽器腳本/CDP 模式下執行,請遵循以下指南來與 X 的 React/Draft.js 編輯器互動:

  1. 多重編輯器偵測: X 頁面經常包含多個撰寫文字區塊(例如首頁動態中的內嵌編輯器和已啟用的模式對話框)。請始終查詢所有編輯器:

    var els = document.querySelectorAll('[data-testid="tweetTextarea_0"]');
    var el = els.length > 1 ? els[els.length - 1] : els[0];
    

    始終鎖定已啟用的模式編輯器(通常是列表中的最後一個元素)。

  2. 綁定選取範圍與焦點: 在插入文字之前,你必須先點擊該元素以觸發 Draft.js 的選取範圍綁定,然後設定焦點:

    el.click();
    el.focus();
    
  3. 保留編輯器結構:

    • 請勿對空的編輯器使用 el.innerHTML = '' 或 document.execCommand('delete')。清除 DOM 節點會破壞 Draft.js 的內部包裝結構(例如 public-DraftStyleDefault-block span),這會導致 React 組件崩潰,並使發文按鈕永久處於禁用狀態。
    • 只需直接對空的且已聚焦的編輯器使用 document.execCommand('insertText', false, text)。
  4. 觸發 React 狀態更新: 插入文字後,觸發一個可冒泡的 input 事件來通知 React:

    el.dispatchEvent(new Event('input', { bubbles: true }));
    
  5. 找到正確的發文按鈕: 按鈕的 testid (tweetButtonInline 和 tweetButton) 可能會根據上下文互換。始終掃描可見且已啟用的按鈕:

    var btns = document.querySelectorAll('[data-testid="tweetButtonInline"], [data-testid="tweetButton"]');
    var activeBtn = Array.from(btns).find(function(btn) {
        var isVisible = btn.offsetWidth > 0 && btn.offsetHeight > 0;
        var isDisabled = btn.disabled || btn.getAttribute('aria-disabled') === 'true';
        return isVisible && !isDisabled;
    });
     if (activeBtn) activeBtn.click();
    

刪除推文工作流程

執行刪除時:

  1. 開啟或將 Google Chrome 導航至使用者的個人檔案頁面 (https://x.com/[username]) 或直接推文 URL (https://x.com/[username]/status/[tweetId])。
  2. 搜尋包含要刪除文字的目標推文 <article> 容器。
  3. 點擊推文上的選項選單按鈕:
    • DOM 選擇器:[data-testid="caret"]
  4. 等待 1-2 秒,然後點擊「Delete」選單項目:
    • DOM 選擇器:文字包含「Delete」或「删除」的 [role="menuitem"] 元素。
  5. 等待 1-2 秒,然後點擊對話框中的確認刪除按鈕:
    • DOM 選擇器:[data-testid="confirmationSheetConfirm"](或備援方案:任何文字包含「Delete」或「删除」的對話框按鈕)。

CLI Bridge 範例 (macOS)

使用與發文相同的暫存檔案 + AppleScript 模式。將 TWEET_TEXT_HERE 替換為目標推文的內容。

步驟 1 — 找到推文並點擊選項選單:

cat > /tmp/del-1.js << 'EOF'
(function() {
  var articles = document.querySelectorAll('article');
  var target = null;
  var needle = 'TWEET_TEXT_HERE';
  for (var i = 0; i < articles.length; i++) {
    var textEl = articles[i].querySelector('[data-testid="tweetText"]');
    if (textEl && textEl.textContent.trim().indexOf(needle) !== -1) {
      target = articles[i];
      break;
    }
  }
  if (!target) return 'ERROR: tweet not found';
  var caret = target.querySelector('[data-testid="caret"]');
  if (!caret) return 'ERROR: caret not found';
  caret.click();
  return 'OK: caret clicked';
})();
EOF

osascript -e '
tell application "Google Chrome"
    set js to read "/tmp/del-1.js"
    set result to execute front window'"'"'s active tab javascript js
    return result
end tell'

步驟 2 — 點擊「Delete」選單項目(在步驟 1 後等待 1-2 秒):

cat > /tmp/del-2.js << 'EOF'
(function() {
  var items = document.querySelectorAll('[role="menuitem"]');
  for (var i = 0; i < items.length; i++) {
    var txt = items[i].textContent.trim();
    if (txt === 'Delete' || txt === '删除') {
      var clickTarget = items[i].querySelector('[role="menuitem"]') || items[i];
      clickTarget.click();
      return 'OK: delete clicked (index ' + i + ')';
    }
  }
  var first = document.querySelectorAll('[role="menuitem"]')[0];
  if (first) { first.click(); return 'OK: first menuitem clicked'; }
  return 'ERROR: no menuitems';
})();
EOF

osascript -e '
tell application "Google Chrome"
    set js to read "/tmp/del-2.js"
    set result to execute front window'"'"'s active tab javascript js
    return result
end tell'

步驟 3 — 確認刪除(在步驟 2 後等待 1-2 秒):

cat > /tmp/del-3.js << 'EOF'
(function() {
  var confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
  if (!confirmBtn) {
    var buttons = document.querySelectorAll('[role="button"], button');
    for (var i = 0; i < buttons.length; i++) {
      var txt = buttons[i].textContent.trim();
      if (txt === 'Delete' || txt === '删除') {
        confirmBtn = buttons[i];
        break;
      }
    }
  }
  if (!confirmBtn) return 'ERROR: confirm button not found';
  confirmBtn.click();
  return 'OK: confirm clicked';
})();
EOF

osascript -e '
tell application "Google Chrome"
    set js to read "/tmp/del-3.js"
    set result to execute front window'"'"'s active tab javascript js
    return result
end tell'

匯出推文工作流程

若要從個人檔案頁面匯出/儲存所有或經過篩選的推文:

  1. 執行匯出腳本:
    ${BUN_X} {baseDir}/scripts/export-tweets.ts [startDate] [endDate]
    
    • 選擇性日期篩選:你可以提供 startDate(例如 2026-06-01)和 endDate(例如 2026-06-30)來依日期範圍篩選輸出。如果省略,則會匯出所有擷取的推文。
    • 輸出檔案:推文將儲存在 /Users/alex/twitter/twitter.yaml 中,其中推文 URL 為鍵,推文文字內容為值。

相關技能