實作:試算表記帳機器人
把前面學的合起來:使用者在 LINE 傳「記 午餐 120」,機器人把這筆寫進 Google 試算表;傳「今天」或「本月」查統計;每天晚上再主動推播當日花費。試算表就是你的資料庫,還能直接用圖表與樞紐分析。
準備試算表
-
新增一份試算表,把第一個工作表命名為 記帳,第一列填標題:
A B C D 時間 userId 項目 金額 -
網址
/d/後面那串是試算表 ID(見 繫結與獨立專案)。到 GAS 專案的 指令碼屬性 新增SHEET_ID,值就是這串 ID。 - GAS 專案設定 把時區改成
(GMT+08:00) 台北。「今天」是以專案時區的午夜起算,時區不對,晚上記的帳會算到別天。
完整程式
延續 建立機器人並接上 GAS 的 doPost、callLine、reply、push、replyText。把 handleEvent 與 handleText 換成下面的版本,並加入其餘函式:
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 集是 帳號準備與訊息傳送。