跳轉至

建立 2026-09-21 更新 2026-09-21

實作:試算表記帳機器人

把前面學的合起來:使用者在 LINE 傳「記 午餐 120」,機器人把這筆寫進 Google 試算表;傳「今天」或「本月」查統計;每天晚上再主動推播當日花費。試算表就是你的資料庫,還能直接用圖表與樞紐分析。

準備試算表

  1. 新增一份試算表,把第一個工作表命名為 記帳,第一列填標題:

    A B C D
    時間 userId 項目 金額
  2. 網址 /d/ 後面那串是試算表 ID(見 繫結與獨立專案)。到 GAS 專案的 指令碼屬性 新增 SHEET_ID,值就是這串 ID。

  3. GAS 專案設定 把時區改成 (GMT+08:00) 台北。「今天」是以專案時區的午夜起算,時區不對,晚上記的帳會算到別天。

完整程式

延續 建立機器人並接上 GASdoPostcallLinereplypushreplyText。把 handleEventhandleText 換成下面的版本,並加入其餘函式:

const HELP_TEXT = "指令:\n記 午餐 120\n今天\n本月";

function handleEvent(event) {
  if (event.type === "follow") {
    return replyText(event.replyToken, "歡迎使用記帳機器人。\n" + HELP_TEXT);
  }
  if (event.type !== "message" || event.message.type !== "text") return;
  handleText(event);
}

function handleText(event) {
  const userId = event.source.userId;
  const [cmd, item, amount] = event.message.text.trim().split(/\s+/);

  if (cmd === "記") {
    const n = Number(amount);
    if (!item || !Number.isFinite(n)) {
      return replyText(event.replyToken, "格式:記 午餐 120");
    }
    addExpense(userId, item, n);
    return replyText(event.replyToken, "已記錄:" + item + " " + n + " 元");
  }
  if (cmd === "今天") {
    return replyText(event.replyToken, "今天共花費 " + sumSince(userId, startOfToday()) + " 元");
  }
  if (cmd === "本月") {
    return replyText(event.replyToken, "本月共花費 " + sumSince(userId, startOfMonth()) + " 元");
  }
  replyText(event.replyToken, HELP_TEXT);
}

// ---- 試算表存取 ----

function getSheet() {
  const id = PropertiesService.getScriptProperties().getProperty("SHEET_ID");
  return SpreadsheetApp.openById(id).getSheetByName("記帳");
}

function addExpense(userId, item, amount) {
  const lock = LockService.getScriptLock();
  lock.waitLock(10000); // 多人同時記帳時排隊寫入
  try {
    getSheet().appendRow([new Date(), userId, item, amount]);
  } finally {
    lock.releaseLock();
  }
}

function sumSince(userId, since) {
  const rows = getSheet().getDataRange().getValues().slice(1); // 去掉標題列
  return rows
    .filter((r) => r[1] === userId && r[0] >= since)
    .reduce((sum, r) => sum + Number(r[3]), 0);
}

function startOfToday() {
  const d = new Date();
  d.setHours(0, 0, 0, 0);
  return d;
}

function startOfMonth() {
  const d = new Date();
  return new Date(d.getFullYear(), d.getMonth(), 1);
}

重點說明:

  • 每個人只看得到自己的帳:寫入時存 userId,查詢時用 r[1] === userId 過濾。多人共用同一個機器人也不會混在一起。
  • 一次讀整張表,再在記憶體裡過濾getDataRange().getValues() 只往返一次。不要對每一列呼叫 getRange,這個原則見 效能與執行限制
  • LockService:兩個人同時傳訊息時,兩次執行會同時寫入。鎖讓它們排隊,一個做完換下一個。
  • 輸入檢查Number.isFinite(n) 擋掉「記 午餐 abc」這種輸入,並回應正確格式,而不是默默寫入 NaN
  • 改完程式別忘了 發新版本

每天晚上自動推播

再加一個函式,只推播給「今天有記帳的人」,這樣省下 Push 額度:

function pushDailySummary() {
  const since = startOfToday();
  const rows = getSheet().getDataRange().getValues().slice(1);
  const totals = {};

  rows
    .filter((r) => r[0] >= since)
    .forEach((r) => {
      totals[r[1]] = (totals[r[1]] || 0) + Number(r[3]);
    });

  Object.keys(totals).forEach((userId) => {
    push(userId, [{ type: "text", text: "今天共花費 " + totals[userId] + " 元" }]);
  });
}

接著到編輯器左側時鐘 觸發條件 → 新增觸發條件:函式選 pushDailySummary、事件來源選 時間驅動、每日、晚上 9 點到 10 點。設定方式見 觸發條件

算一下額度

Push 計入每月免費則數。假設有 10 位使用者,每天各推 1 則,一個月就是約 300 則,會超過輕用量方案的免費額度。額度細節見 上線注意事項

延伸練習

  • 加「刪」指令:找到該使用者最後一列,用 deleteRow 刪掉。刪除前記得也加鎖。
  • 快速回覆 做「今天/本月」按鈕。
  • 記錄時加上分類(記 午餐 120 餐飲),再用試算表的樞紐分析看各類別佔比。
  • 每月 1 號推播上個月總結,改用月份觸發。

推薦影音

用 GAS 做 LINE Bot:試算表與家計簿

頻道:まりんとすふぃあ。「用 GAS 做 LINE Bot」系列的第 3 集,主題是把 LINE 的訊息寫進試算表、做成家計簿(記帳)機器人,和本頁做的東西一樣。日文影片,但畫面示範完整,搭配本頁程式對照即可。系列第 1 集是 帳號準備與訊息傳送