| name | publish-facebook |
| description | 用 Playwright 在 Facebook 個人 timeline / 粉專發一篇含圖貼文。 |
共通規則見 CLAUDE.md「共通行為規則」章節,本檔只描述 FB 特有的步驟。
每步結束都要呼叫 scripts/log-click.sh(格式見 CLAUDE.md「Click recording」)。
Input
caption:貼文文字
hashtags:list of string(接在 caption 結尾)
local_image_path:本地圖檔絕對路徑
draft_id / run_id:caller(/publish-now)傳入,用於 click-log
Output
{ "post_url": "https://..." }
Step ID 鎖定表(log --step 值必須一字不差用以下、不准自取)
| step_id | 必要 | 用途 |
|---|
1_navigate | 必 | 進粉專首頁(讀 socials.facebook.url) |
2_switch_to_page_if_needed | 條件 | 切換到粉專管理身分(沒「立即切換」就 log 跳過) |
3_click_create_post | 必 | 點「在想些什麼?」開 composer |
4_type_caption | 必 | 輸入 caption + hashtags |
5_click_add_photo | 必 | 點「相片/影片」 |
6_file_upload | 必 | 上傳檔案 |
7_wait_preview | 必 | 等預覽載入完成 |
7b_stage_continue_1 | Reel-only | Reel 三段式第 1 個「繼續」(圖片貼文不會出現,照樣 log skip) |
7c_stage_continue_2 | Reel-only | Reel 三段式第 2 個「繼續」 |
8_click_publish | 必 | 點「發佈」 |
8b_dismiss_post_publish_dialog | 必 | 處理 WhatsApp 推銷 dialog(見下方坑點,發出去後必處理) |
9_extract_post_url | 必 | 抓貼文 URL |
為什麼鎖定:跨平台規則見 OPERATING_RULES §9。簡言之,click-log 統計分析需要 step_id 穩定才看得出 pattern。
「條件 / Reel-only」步驟若當下不適用,仍要 log 一筆 --ok true --ms 0 --args '{"reason":"skipped_not_applicable"}' 占位,不要省略。
流程細節
1_navigate — 讀 config/brand.yaml.socials.facebook.url,browser_navigate 過去
2_switch_to_page_if_needed — 用 browser_snapshot 看是否有「立即切換」按鈕(FB 會顯示「切換為 Cuite Bellie 的粉絲專頁並開始管理。」橫幅)。有就 browser_click、等頁面重載;沒有代表已是粉專管理身分,跳過(log 一筆 --ok true --ms 0 標記略過)。這步是粉專身分必要條件 — 沒切換 composer 會以個人身分發文。
3_click_create_post — 點「在想些什麼?」開 composer
4_type_caption — 文字框輸入 caption + "\n\n" + hashtags.join(" ")
5_click_add_photo — 點「相片/影片」
6_file_upload — browser_file_upload 傳 local_image_path
7_wait_preview — browser_wait_for 等預覽載入完
7b_stage_continue_1 — 若 Reel 流程出現「繼續」按鈕(Stage A → B)就點;圖片貼文無此按鈕、log skip
7c_stage_continue_2 — 同上、Stage B → C 的「繼續」;圖片貼文 log skip
8_click_publish — 點「發佈」(用 name: '發佈', exact: true)
8b_dismiss_post_publish_dialog — 點「稍後再說」處理 WhatsApp 推銷 dialog(見下方坑點),沒跳就 log skip
9_extract_post_url — 抓新貼文永久連結。雙策略:Business Suite 優先、polling 為 fallback:
Strategy 1(主):Meta Business Suite 已發佈 tab
- 讀
config/brand.yaml.socials.facebook.asset_id
- 沒有
asset_id 欄位 → 跳到 Strategy 2
browser_navigate 到 https://business.facebook.com/latest/posts/published_posts?asset_id=<asset_id>
- 等表格載入(找 tablist 含「已發佈」/「已排定發佈」/「草稿」)
- 若有公告 banner(如「你現在可以批量上傳連續短片」)擋住操作 → 點 banner 上的 X 關掉
- 找最上面那一筆 row、確認是這次發的(時間戳在最近 3 分鐘內 + caption 前 30 字符合)
- 抓 permalink:row 的標題 cell 不可點擊,要走「下拉式功能表」:
- 點 row 上「開啟下拉式功能表」按鈕(每筆 row 都有)
- 在彈出 menu 找「在 Facebook 查看貼文」項
- 抓 href(格式
https://www.facebook.com/<page_id>/posts/pfbid... 或 https://www.facebook.com/reel/<id>/)
- 不要真的點開(會跳分頁、增加狀態)— 從 anchor
getAttribute('href') 直接讀就好
- 抓到 URL → 回
{"post_url": "<url>"}、log step 9 --ok true --args '{"strategy": "business_suite"}'
Strategy 2(fallback):polling 粉專 timeline
只在 Strategy 1 失敗(asset_id 沒設、Business Suite 進不去、找不到符合的 row)時跑:
browser_navigate 回粉專 URL
- 每 10 秒 snapshot 一次粉專首頁、找新出現的
/reel/<id>/ 或 /posts/pfbid...,up to 90 秒
- 抓到 → 回
{"post_url": "<url>"}、log --ok true --error "fallback_via_polling"
兩個策略都失敗 → log --ok false --error "post not visible in Business Suite or timeline within 90s"、回 {"post_url": null, "error": "url_extraction_failed"}、caller 收到要明確告訴使用者「貼文可能已發出但 URL 抓不到,請手動確認 Business Suite」,不要假裝成功。
Dry-run 模式
當 caller 傳入 mode: dry_run:
- 圖片貼文:跑 step 1 ~ step 7(含 caption + 圖片上傳 + 預覽載入);跳過 step 8
8_click_publish、step 9
- 影片 / Reel:跑到 Stage C「Reel 設定」開到底為止;不點底部的「發佈」按鈕(不踩到 WhatsApp dialog 那一段)
- 收尾:
- 點 dialog 左上「返回」回到 Stage B → A,或直接點 X 關閉 composer
- 若跳「捨棄變更?」確認 → 點「捨棄」
browser_close
- 回
{"status": "dry_run_ok", "stages_passed": [...], "abort_at_step": "<step name>"}
- 若中途某步抓不到按鈕或 dialog 異常 → 回
{"status": "dry_run_failed", "step_failed": "<step>", "error": "<原因>"}
dry-run 不寫 click-log、不寫 reports/posts/ 報告。
因為 dry-run 不點最後的「發佈」,所以 WhatsApp 推銷 dialog 不會出現 — 那段邏輯靠 dry-run 驗不到,要靠真實 publish 才驗得到。
FB 特有的坑
以下是已知 case,非 exhaustive list。中間步驟遇到沒列出的 dialog / 元素 drift / 載入慢 → 自己 reasoning 解(OPERATING_RULES §4「過程雜訊 vs 終點驗收」),不要 surface 給使用者。只有終點驗收(post URL、登入態、素材)才老實回 error。
點完「發佈」會跳的 interstitial dialog(必處理,否則 publish 默默失敗)
WhatsApp 推銷 dialog(2026-05-05 確認)— 點完 step 8 發佈後 1-2 秒內會跳出:
- 標題:「讓用戶輕鬆與你聯絡」
- 描述:「新增 WhatsApp 按鈕,讓用戶可以直接從你的貼文傳送訊息給你。」
- 按鈕 A:「新增 WhatsApp 按鈕」 ← 絕對不能點(會中斷 publish 並進入粉專功能設定)
- 按鈕 B:「稍後再說」 ← 點這個
處理方式(即 8b_dismiss_post_publish_dialog,step 8 之後、step 9 之前必做):
browser_wait_for time=2
browser_snapshot 看當前 dialog
- 若看到「稍後再說」按鈕 →
browser_click 點它,log 8b --ok true --args '{"role":"button","name":"稍後再說"}'
- 若看到「新增 WhatsApp 按鈕」單獨存在卻沒看到「稍後再說」 → 試
browser_press_key Escape 關 dialog(禁止點「新增 WhatsApp 按鈕」),log 8b 標 method=key
- 沒跳 dialog → log
8b --ok true --ms 0 --args '{"reason":"skipped_no_dialog"}'
為什麼重要:背景的 Reel 設定 dialog 會顯示「發佈中」status,但若 WhatsApp dialog 沒處理乾淨,FB 在某個內部超時後會把 publish 當作取消,前端不會出 error toast、Meta Business Suite 「已發佈」/ 「草稿」/ 「已排定發佈」三個 tab 都不會出現該貼文。這就是 publish-facebook 過去看似成功實則沒發出去的根因。
密碼洩漏 dialog(FB 偶爾跳)— 點 Not now / 稍後再說 關閉後繼續。同樣不算 step、不 log。
其他
- step 2「立即切換」按鈕位置隨 FB UI 改版浮動 — 用 accessible name 抓("立即切換" / "Switch Now"),不要寫死 selector
- 切換到粉專身分後,FB 的個人通知、訊息列也會改成粉專視角;publish 完成後不需要切回個人,瀏覽器關了下次再開仍然從個人身分啟動
- step 8 發佈按鈕用
name: '發佈', exact: true — 因為「加強推廣貼文」action 的 nested text 也含「發佈」二字,不 exact 會誤點
- step 9 Business Suite 偶爾跳 onboarding banner(例:「你現在可以批量上傳連續短片」、「全新觀眾衡量指標」) → 用 X 關閉、不算 step、不 log。新版本可能改文案,用「關閉」accessible name 抓較穩
Reel 三段式 dialog(影片自動轉 Reel 時)
上傳影片後,FB 不會留在「建立貼文」單一 dialog,會分三段:
- Stage A「建立貼文」— 顯示「你的連續短片沒有問題,可以發佈了!」+ 「繼續」鈕(disabled 直到上傳到 100%)
- Stage B「編輯 Reel」— 修剪/字幕/音訊/逐字稿(4 個可展開選項)+ 右側預覽 + 「繼續」鈕
- Stage C「Reel 設定」— Post audience / 混搭 / 標註協作 / 排程 / 分享到社團 / 限時動態 / 加強推廣 + 底部「儲存」(草稿) + 「發佈」(正式發佈)
caption 在 Stage A 輸入後會自動帶到 Stage B/C,不要重輸。三段都用預設值點「繼續」/「發佈」即可,不要展開或修改任何欄位。