Twitter 技能 是做什麼的?
透過 Chrome 電腦操作模式,將文字和圖片發佈到 X (Twitter)。
腳本目錄
重要:所有腳本都位在此技能的 scripts/ 子目錄中。
代理執行說明:
- 確定此 SKILL.md 檔案所在的目錄路徑為
{baseDir} - 腳本路徑:
- 剪貼簿輔助工具:
{baseDir}/scripts/copy-to-clipboard.ts - 推文匯出/擷取工具:
{baseDir}/scripts/export-tweets.ts - 官方 X API 輔助工具:
{baseDir}/scripts/x-api.ts
- 剪貼簿輔助工具:
- 解析
${BUN_X}執行環境:若已安裝bun→bun;若npx可用 →npx -y bun;否則建議安裝 bun - 將本文檔中的所有
{baseDir}和${BUN_X}替換為實際值
執行模式
此技能採用混合執行模式:
- 優先使用 API(建議用於文字發佈):嘗試使用官方 X API 腳本 (
x-api.ts) 發佈推文。此方式快速、穩定,且不消耗瀏覽器資源。 - 瀏覽器備援:若 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 上)授予必要的無障礙功能權限。
一般發文工作流程(文字與圖片)
執行發佈時:
- 開始代理回合,調用
get_app_state(或等效工具)來檢查Google Chrome的狀態。 - 開啟或將 Google Chrome 導航至
https://x.com/compose/post。 - 找到推文編輯器輸入框。
- 使用電腦操作的鍵盤輸入,在編輯器中輸入貼文文字。
- 若有圖片需要附加(最多 4 張):
對於每張圖片:
a. 執行剪貼簿輔助腳本,將圖片複製到剪貼簿:
b. 使用貼上快捷鍵(macOS 為${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts image /absolute/path/to/image.pngsuper+v,Windows/Linux 為control+v)將圖片貼到編輯器中。 c. 等待 2-3 秒,直到 X 完成媒體上傳。 - 發佈安全:在未取得當前對話中使用者明確的最終確認前,切勿點擊
Publish、Post或任何等效按鈕來發佈推文。 - 使用者確認後,點擊
Post按鈕進行發佈。 - 發佈後,關閉編輯器視窗,以免 UI 停留在撰寫對話框。使用關閉按鈕或 Escape 鍵:
- DOM 選擇器:
[data-testid="app-bar-close"]或[aria-label="Close"] - 備援方案:觸發一個
Escape鍵按下事件
- DOM 選擇器:
- 自動重新整理動態(選擇性):若使用者有其他分頁開啟個人檔案頁面(例如
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 編輯器互動:
-
多重編輯器偵測: X 頁面經常包含多個撰寫文字區塊(例如首頁動態中的內嵌編輯器和已啟用的模式對話框)。請始終查詢所有編輯器:
var els = document.querySelectorAll('[data-testid="tweetTextarea_0"]'); var el = els.length > 1 ? els[els.length - 1] : els[0];始終鎖定已啟用的模式編輯器(通常是列表中的最後一個元素)。
-
綁定選取範圍與焦點: 在插入文字之前,你必須先點擊該元素以觸發 Draft.js 的選取範圍綁定,然後設定焦點:
el.click(); el.focus(); -
保留編輯器結構:
- 請勿對空的編輯器使用
el.innerHTML = ''或document.execCommand('delete')。清除 DOM 節點會破壞 Draft.js 的內部包裝結構(例如public-DraftStyleDefault-blockspan),這會導致 React 組件崩潰,並使發文按鈕永久處於禁用狀態。 - 只需直接對空的且已聚焦的編輯器使用
document.execCommand('insertText', false, text)。
- 請勿對空的編輯器使用
-
觸發 React 狀態更新: 插入文字後,觸發一個可冒泡的
input事件來通知 React:el.dispatchEvent(new Event('input', { bubbles: true })); -
找到正確的發文按鈕: 按鈕的 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();
刪除推文工作流程
執行刪除時:
- 開啟或將 Google Chrome 導航至使用者的個人檔案頁面 (
https://x.com/[username]) 或直接推文 URL (https://x.com/[username]/status/[tweetId])。 - 搜尋包含要刪除文字的目標推文
<article>容器。 - 點擊推文上的選項選單按鈕:
- DOM 選擇器:
[data-testid="caret"]
- DOM 選擇器:
- 等待 1-2 秒,然後點擊「Delete」選單項目:
- DOM 選擇器:文字包含「Delete」或「删除」的
[role="menuitem"]元素。
- DOM 選擇器:文字包含「Delete」或「删除」的
- 等待 1-2 秒,然後點擊對話框中的確認刪除按鈕:
- DOM 選擇器:
[data-testid="confirmationSheetConfirm"](或備援方案:任何文字包含「Delete」或「删除」的對話框按鈕)。
- DOM 選擇器:
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'
匯出推文工作流程
若要從個人檔案頁面匯出/儲存所有或經過篩選的推文:
- 執行匯出腳本:
${BUN_X} {baseDir}/scripts/export-tweets.ts [startDate] [endDate]- 選擇性日期篩選:你可以提供
startDate(例如2026-06-01)和endDate(例如2026-06-30)來依日期範圍篩選輸出。如果省略,則會匯出所有擷取的推文。 - 輸出檔案:推文將儲存在
/Users/alex/twitter/twitter.yaml中,其中推文 URL 為鍵,推文文字內容為值。
- 選擇性日期篩選:你可以提供