第 5 章

發出第一條帖

用兩步式發佈把帖文送上 Threads,並把連結自動發成主帖下面的一則回覆。

Threads 的發佈是兩步式的,不能一次過:

1. 建立容器 —— 把文字交給 Threads,取得一個 creation_id
2. 發佈容器 —— 用那個 id 正式出街

中間需要稍等,讓 Threads 處理好容器。這是官方建議的做法。

1. 加入發佈程式碼

Code.gs 底部加入:

/* ========================= 發佈 ========================= */

var TAB = "Calendar";
var COL = { STATUS: 0, WHEN: 1, TEXT: 2, LINK: 3, POSTED: 4, URL: 5 };

function sheet_() {
  return SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TAB);
}

function toast_(msg) {
  SpreadsheetApp.getActiveSpreadsheet().toast(msg, "Threads", 8);
}

function token_() {
  var t = props_().getProperty("ACCESS_TOKEN");
  if (!t) throw new Error("尚未連接 Threads。");
  return t;
}

function userId_() {
  var u = props_().getProperty("USER_ID");
  if (!u) throw new Error("找不到 user id,請重新連接。");
  return u;
}

function query_(obj) {
  return Object.keys(obj)
    .map(function (k) {
      return encodeURIComponent(k) + "=" + encodeURIComponent(obj[k]);
    })
    .join("&");
}

/**
 * 兩步式發佈。傳入 replyToId 就會發成該帖的回覆。
 * 回傳新帖的 media id。
 */
function publishText_(text, replyToId) {
  var token = token_();
  var uid = userId_();

  var params = { media_type: "TEXT", text: text, access_token: token };
  if (replyToId) params.reply_to_id = replyToId;

  var createRes = UrlFetchApp.fetch(
    API + "/" + uid + "/threads?" + query_(params),
    { method: "post", muteHttpExceptions: true }
  );
  var created = JSON.parse(createRes.getContentText() || "{}");
  if (!created.id) {
    throw new Error("建立容器失敗:" + createRes.getContentText().slice(0, 200));
  }

  Utilities.sleep(5000); // 讓 Threads 處理容器

  var pubRes = UrlFetchApp.fetch(
    API + "/" + uid + "/threads_publish?" +
      query_({ creation_id: created.id, access_token: token }),
    { method: "post", muteHttpExceptions: true }
  );
  var published = JSON.parse(pubRes.getContentText() || "{}");
  if (!published.id) {
    throw new Error("發佈失敗:" + pubRes.getContentText().slice(0, 200));
  }
  return published.id;
}

/** 取得帖文的永久連結。 */
function permalink_(mediaId) {
  var res = UrlFetchApp.fetch(
    API + "/" + mediaId + "?fields=permalink&access_token=" +
      encodeURIComponent(token_()),
    { muteHttpExceptions: true }
  );
  return JSON.parse(res.getContentText() || "{}").permalink || "";
}

/**
 * 發主帖,再把連結發成自己的回覆。
 * 回覆失敗不會影響已發出的主帖 —— 主帖比連結重要。
 */
function publishRow_(text, link) {
  var mainId = publishText_(text, null);
  if (link) {
    Utilities.sleep(2000);
    try {
      publishText_(link, mainId);
    } catch (err) {
      Logger.log("回覆連結失敗(主帖已發出):" + err.message);
    }
  }
  return { id: mainId, url: permalink_(mainId) };
}

/** 立即發佈游標所在的那一行。 */
function postSelectedRow() {
  var sh = sheet_();
  var row = sh.getActiveCell().getRow();
  if (row < 2) {
    toast_("請先選擇一行資料。");
    return;
  }
  var values = sh.getRange(row, 1, 1, 6).getValues()[0];
  var text = String(values[COL.TEXT] || "").trim();
  if (!text) {
    toast_("這一行沒有內容。");
    return;
  }

  try {
    var res = publishRow_(text, String(values[COL.LINK] || "").trim());
    sh.getRange(row, COL.STATUS + 1).setValue("Posted");
    sh.getRange(row, COL.POSTED + 1).setValue(new Date());
    sh.getRange(row, COL.URL + 1).setValue(res.url);
    toast_("✓ 已發佈:" + res.url);
  } catch (err) {
    sh.getRange(row, COL.STATUS + 1).setValue("Error");
    sh.getRange(row, COL.URL + 1).setValue(String(err.message).slice(0, 300));
    toast_("✗ " + err.message);
  }
}

2. 把新功能加進選單

修改 onOpen:

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("Threads")
    .addItem("連接 Threads", "connectThreads")
    .addItem("測試連線", "testConnection")
    .addSeparator()
    .addItem("立即發佈選取的一行", "postSelectedRow")
    .addToUi();
}

3. 試發一條

  1. Calendar 第 2 行填入內容:
StatusPublish AtTextLink
Draft測試一下自動發帖。
  1. 儲存程式碼,回試算表重新整理
  2. 把游標放在第 2 行任何一格
  3. Threads → 立即發佈選取的一行

選單沒有更新? 改了 onOpen 之後,單純按重新整理經常不夠——試算表會沿用快取住的舊選單,你會看見改動前那幾項。可靠的做法是把分頁完全關掉,再從試算表首頁重新開檔,強迫它冷啟動。先回 Apps Script 確認程式碼真的存好了,再這樣做。

成功的話,Status 會變成 Posted,Post URL 會出現連結。開連結確認帖文真的在你的帳戶上。

這一步不需要重新部署。重新部署只在改動 doGet(回呼行為)時才必要;選單函式是直接執行的。

試算表頂部的自訂選單 Threads,展開後可見「立即發佈選取的一行」

4. 試一條帶連結的

再填一行,這次 Link 欄填一條網址。發佈後你會看到:主帖是純文字,連結是下面的一則回覆

常見卡關

現象原因
建立容器失敗 + 權限訊息threads_content_publish,或授權時未包含該權限
發佈失敗容器還沒處理好。把 Utilities.sleep(5000) 加大到 8000
主帖出了、回覆沒有正常的保護行為 —— 到左側「執行項目」看記錄
選單不見了,或仍是舊的項目onOpen 只在開檔時執行,而重新整理常常沿用快取。關掉分頁再重新開檔

這一章完成了什麼

  • ✅ 可以由試算表一鍵發帖
  • ✅ 連結自動發成回覆,主帖保持乾淨
  • ✅ 發佈結果與錯誤都寫回試算表

下一章讓它自己按時發佈,不需要你在場。

步驟最後驗證於 2026年7月20日