第 6 章
設定自動排程
用時間觸發器每 15 分鐘檢查一次,把到期的帖自動發佈,並讓長期權杖自動續期。
到這裡,發帖仍然要你自己按。這一章讓它自己跑。
關鍵在於 Apps Script 的時間觸發器:它在 Google 的伺服器上執行,不是在你的電腦上。所以你關掉瀏覽器、關掉電腦、去旅行,它照樣運作。
狀態流轉
Draft ──(你填時間並改成 Scheduled)──▶ Scheduled
│ 時間到
▼
Posted 或 Error
排程器只碰 Scheduled 而且時間已到的行。Draft 是安全的暫存區——寫到一半的帖不會意外出街。
1. 加入排程程式碼
在 Code.gs 底部加入:
/* ========================= 排程 ========================= */
var SCHED_MINUTES = 15;
var REFRESH_AFTER_DAYS = 40; // 長期權杖約 60 天到期,提早續
/**
* 觸發器的目標函式:發佈所有已到期的 Scheduled 行。
* 每一行獨立處理 —— 一行失敗不會擋住其他行。
*/
function runScheduler() {
refreshTokenIfNeeded_();
var sh = sheet_();
var data = sh.getDataRange().getValues();
var now = new Date();
var posted = 0;
for (var r = 1; r < data.length; r++) {
var row = data[r];
if (String(row[COL.STATUS] || "").trim().toLowerCase() !== "scheduled") continue;
var when = row[COL.WHEN];
if (!(when instanceof Date) || when > now) continue;
var text = String(row[COL.TEXT] || "").trim();
if (!text) continue;
try {
var res = publishRow_(text, String(row[COL.LINK] || "").trim());
sh.getRange(r + 1, COL.STATUS + 1).setValue("Posted");
sh.getRange(r + 1, COL.POSTED + 1).setValue(new Date());
sh.getRange(r + 1, COL.URL + 1).setValue(res.url);
posted++;
} catch (err) {
// 錯誤寫回試算表,否則它會靜靜地失敗而你永遠不知道。
sh.getRange(r + 1, COL.STATUS + 1).setValue("Error");
sh.getRange(r + 1, COL.URL + 1).setValue(String(err.message).slice(0, 300));
}
}
return posted;
}
/** 安裝(或重裝)每 15 分鐘的觸發器。重複執行是安全的。 */
function installScheduler() {
ScriptApp.getProjectTriggers().forEach(function (t) {
if (t.getHandlerFunction() === "runScheduler") ScriptApp.deleteTrigger(t);
});
ScriptApp.newTrigger("runScheduler")
.timeBased()
.everyMinutes(SCHED_MINUTES)
.create();
toast_("已安裝自動排程(每 " + SCHED_MINUTES + " 分鐘檢查一次)。");
}
function runSchedulerNow() {
toast_("排程器已執行,發佈了 " + runScheduler() + " 條。");
}
/* ====================== 權杖自動續期 ====================== */
/** 權杖超過 REFRESH_AFTER_DAYS 天就續期,避免 60 天後失效。 */
function refreshTokenIfNeeded_() {
var p = props_();
var at = Number(p.getProperty("TOKEN_AT") || 0);
if (at && (Date.now() - at) / 86400000 < REFRESH_AFTER_DAYS) return;
try {
var res = UrlFetchApp.fetch(
"https://graph.threads.net/refresh_access_token" +
"?grant_type=th_refresh_token&access_token=" +
encodeURIComponent(token_()),
{ muteHttpExceptions: true }
);
var data = JSON.parse(res.getContentText() || "{}");
if (data.access_token) {
p.setProperty("ACCESS_TOKEN", data.access_token);
p.setProperty("TOKEN_AT", String(Date.now()));
Logger.log("權杖已續期。");
}
} catch (err) {
Logger.log("權杖續期失敗:" + err.message);
}
}
2. 更新選單
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("Threads")
.addItem("連接 Threads", "connectThreads")
.addItem("測試連線", "testConnection")
.addSeparator()
.addItem("立即發佈選取的一行", "postSelectedRow")
.addItem("立即執行排程器", "runSchedulerNow")
.addSeparator()
.addItem("安裝自動排程", "installScheduler")
.addToUi();
}
3. 安裝觸發器
儲存 → 回試算表重新整理 → Threads → 安裝自動排程。
首次會要求授權(這次是「代你執行」的權限)。授權後,左側 ⏰ 觸發條件 應該會看到一條 runScheduler,每 15 分鐘執行。

這一步做不到,整套系統就不會自動運作。 很多人寫完程式碼卻忘了安裝觸發器,然後以為壞了。
4. ⚠️ 時區
Publish At 用的是試算表的時區,而排程器比較時間用的是指令碼專案的時區。兩者不一致就會早出或遲出。
- 試算表:檔案 → 設定 → 時區
- 指令碼:Apps Script 專案設定 → 時區
確保兩者相同(例如都設為香港時間)。
5. 試一次
在 Calendar 填一行:
| Status | Publish At | Text |
|---|---|---|
| Scheduled | (現在之後 2 分鐘) | 排程測試。 |
然後選 Threads → 立即執行排程器,不用等。它應該立刻說發佈了 0 條(還沒到時間)。等過了那個時間再執行一次,就會發佈。
確認手動執行沒問題後,就交給觸發器。
觸發器是近似的:每 15 分鐘檢查一次,所以實際發佈時間可能比
Publish At遲最多 15 分鐘。要更準就把SCHED_MINUTES調細,但沒必要——社交平台的發文時間本來就不需要精準到分鐘。
6. 日常用法
由現在開始,你的流程是:
- 平時把想到的內容寫進
Calendar,Status留Draft - 想出街的,填上
Publish At,Status改成Scheduled - 收工
這一章完成了什麼
- ✅ 每 15 分鐘自動檢查並發佈
- ✅ 錯誤寫回試算表,不會靜靜失敗
- ✅ 權杖自動續期,不會 60 天後突然停止
- ✅ 全程在 Google 伺服器上執行,不需要開電腦
最後一章:把成效數據抓回來,以及一份疑難排解清單。
步驟最後驗證於 2026年7月20日