跳至主要內容

Gemini 串接 LINE Bot 教學:用 GAS 免伺服器做 AI 助理

作者:Clone發布日期:修改日期:分類:LINE 應用閱讀時間:約 14 分鐘

Gemini 串接 LINE 教學示意圖,AI 生成,標示 LINE 傳訊、Apps Script 接收、Gemini 判斷、試算表儲存四個環節
目錄
  1. Gemini 串接 LINE 的架構
  2. 事前準備:LINE 官方帳號、Gemini 金鑰與試算表
  3. 部署程式碼:四個檔案
  4. 設定屬性、部署與 Webhook
  5. 設定時間觸發器:提醒與晨報
  6. 費用與限制
  7. 什麼時候該從 GAS 換掉?
  8. 常見問題
  9. Gemini 串接 LINE 要花錢嗎?
  10. Gemini 串接 LINE 要用哪個模型?
  11. LINE Bot 一直回「系統暫時繁忙」怎麼辦?
  12. 用 GAS 做 LINE Bot 能撐多少人?
  13. 用 GAS 接 LINE Webhook 安全嗎?
  14. 本文引用資料來源

Gemini 串接 LINE 可以不架伺服器:用 Google Apps Script(GAS)當 LINE Webhook 的接收端,收到訊息就呼叫 Gemini API 判斷要做什麼,資料存在 Google 試算表,再用 LINE 回覆。這篇做的是一個自己用的 AI 行程助理,用口語就能新增行程、待辦與備忘。完整程式碼放在 GitHub 的 line-bot-gemini-gas,下面講每一段在做什麼,以及新帳號要注意的模型、費用與限制。

Gemini 串接 LINE 的架構

環節用什麼做什麼
傳訊LINE Messaging API使用者傳訊息,LINE 把事件送到 Webhook
接收Google Apps Script 網路應用程式doPost 收到事件,分派給對應的處理函式
判斷Gemini API把口語訊息轉成 JSON:要新增行程、待辦、備忘,還是查詢
儲存Google 試算表「行事曆」「待辦清單」「備忘錄」「用戶資料」四張工作表
回覆LINE 回覆/推播查詢結果用 Flex Message 卡片回覆;提醒與晨報用推播

Gemini 串接 LINE 架構示意圖,AI 生成,展示 LINE 訊息經 Apps Script 交給 Gemini 判斷、寫入試算表後回覆的流程

做好之後,用口語輸入,Gemini 判斷意圖後寫入對應的工作表;查詢行程時回覆卡片,可以直接按按鈕完成或刪除:

LINE Bot 批量新增行程截圖,實際畫面,展示一句話裡的多個行程由 Gemini 拆開寫入

LINE Bot 新增待辦截圖,實際畫面,展示待辦事項由 Gemini 自動分類

LINE Bot 今日行程卡片截圖,實際畫面,展示 Flex Message 上的完成與刪除按鈕

事前準備:LINE 官方帳號、Gemini 金鑰與試算表

  1. LINE 官方帳號:LINE 自 2024-09-04 起不能在 Developers Console 直接建立 Messaging API channel。先建立 LINE 官方帳號,到 LINE Official Account Manager「設定」→「Messaging API」啟用,再到 LINE Developers Console 的 Messaging API 分頁發行 Channel access token
  2. 關閉自動回應:到 LINE Official Account Manager 的「設定」→「回應設定」,把「自動回應訊息」與「加入好友的歡迎訊息」關掉。這兩項建立時預設開啟,不關的話每則訊息會被官方帳號的預設回應再回一次
  3. Gemini:到 Google AI Studio 建立 API 金鑰
  4. 試算表:開一份新的 Google 試算表,選單「擴充功能」→「Apps Script」

兩把金鑰都不要寫進程式碼,下一步放進 Apps Script 的「指令碼屬性」。

部署程式碼:四個檔案

把 GitHub 上的 4 個檔案分別貼進 Apps Script:

檔案用途
Code.gs進入點 doPost、訊息分派、提醒與報表
GeminiAPI.gs呼叫 Gemini,把訊息轉成 JSON 指令
Database.gs試算表的讀寫
LineAPI.gs回覆與推播、Flex Message 卡片

進入點只做一件事:拆開 LINE 送來的事件,文字訊息交給 handleMessage,新好友交給 handleNewUser。

JavaScript
function doPost(e) {
  try {
    const events = JSON.parse(e.postData.contents).events;
    events.forEach(event => {
      if (event.type === 'message' && event.message.type === 'text') {
        handleMessage(event);
      } else if (event.type === 'follow') {
        handleNewUser(event);
      }
    });
  } catch (error) {
    Logger.log('Error: ' + error);
  }
  return HtmlService.createHtmlOutput('OK');
}

GeminiAPI.gs 的模型寫在開頭一個常數,金鑰放在請求標頭而不是網址,網址會留在記錄裡:

JavaScript
const GEMINI_MODEL = 'gemini-3.5-flash-lite';

// …(略:讀取金鑰、組 payload)

const url = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent`;
const options = {
  method: 'POST',
  contentType: 'application/json',
  headers: { 'x-goog-api-key': apiKey },
  payload: JSON.stringify(payload),
  muteHttpExceptions: true
};

新帳號要用新模型。 Google 官方模型頁(2026-10-13 更新)寫明,2.5 系列只開放給過去用過的帳號,「For any new projects, use our latest models: 3.5 Flash-Lite or 3.8 Flash」。原本寫死 gemini-2.5-flash 的程式,新帳號照做只會回「系統暫時繁忙」;範例改成 3.5 Flash-Lite,要更準的語意判斷再換 3.8 Flash。模型會再更新,比較見〈Gemini 模型介紹〉。

回覆用的是 LINE 的 Reply API,帶上事件裡的回覆權杖:

JavaScript
function replyToLine(replyToken, message) {
  const accessToken = PropertiesService.getScriptProperties().getProperty('LINE_CHANNEL_ACCESS_TOKEN');
  const url = 'https://api.line.me/v2/bot/message/reply';
  
  const payload = {
    replyToken: replyToken,
    messages: [{
      type: 'text',
      text: message
    }]
  };
  
  const options = {
    method: 'post',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + accessToken
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };
  
  try {
    UrlFetchApp.fetch(url, options);
  } catch (error) {
    Logger.log('Reply Error: ' + error);
  }
}

設定屬性、部署與 Webhook

  1. 指令碼屬性:Apps Script 左側「專案設定」→「指令碼屬性」,新增 LINE_CHANNEL_ACCESS_TOKEN 與 GEMINI_API_KEY
  2. 時區:同一頁把時區設成「(GMT+08:00) 台北」,試算表的「檔案」→「設定」也設成台北。只貼 4 個檔案不會帶入 repo 的設定檔;寫進試算表的時間與提醒、晨報都依時區換算,兩邊不一致時間就會錯位
  3. 建立工作表:在編輯器選擇函式 ensureDataExists 按「執行」,依提示授權,會建好四張工作表與欄位
  4. 部署:「部署」→「新增部署作業」→ 網路應用程式;執行身分「我」,存取權「所有人」,LINE 伺服器才呼叫得到。複製結尾是 /exec 的網址
  5. Webhook:回到 LINE Developers Console 的 Messaging API 分頁,貼上網址、開啟「Use webhook」,按「Verify」確認成功

之後每次改程式,都要到「部署」→「管理部署作業」→ 編輯 → 版本選「新版本」,/exec 才會跑新的程式碼。

設定時間觸發器:提醒與晨報

提醒與報表要靠觸發器才會自動執行。Apps Script 左側「觸發條件」→「新增觸發條件」:

函式頻率
checkReminders每 10 分鐘(程式在行程前 30 分鐘內推播提醒)
dailyReport每天早上
weeklyReport每週一早上

費用與限制

免費的部分要注意的地方
Gemini API3.5 Flash-Lite、3.8 Flash 都有免費層免費層的內容會被 Google 用來改進產品,官方提醒不要送出個人資料;3.8 Flash 付費價 2027-01-01 起調高一倍
Apps Script一般 Google 帳號就能執行每次執行最多 6 分鐘、觸發器每天合計 90 分鐘、URL Fetch 每天 2 萬次
LINE回覆訊息不計入則數提醒、晨報、週報是推播,會計入官方帳號方案的訊息則數

費用細節見〈LINE 機器人製作費用差在哪?〉。另外三個限制要先知道:

  • 所有好友共用同一份資料:四張工作表沒有記錄是哪個使用者,任何加好友並傳過訊息的人,都查得到全部行程與備忘,也會收到所有人的提醒。拿到官方帳號 ID 或 QR code 的人都加得到好友,要擋外人,可以在 handleMessage 開頭比對自己的 userId,不是本人就不處理。
  • 無法驗證 LINE 簽章:LINE 建議用 X-Line-Signature 標頭驗證 Webhook 來源,但 Apps Script 的 doPost(e) 事件物件不含請求標頭,所以這套做法沒有簽章驗證。部署網址不要公開。
  • 回覆權杖有時限:LINE 文件寫明回覆權杖要在收到事件後一分鐘內使用,而且時限可能不經通知變更;Gemini 想太久,就只能改用推播。

GAS 版 LINE Bot 限制示意圖,AI 生成,展示所有人共用資料、無法驗證簽章、執行時間有上限三個限制

什麼時候該從 GAS 換掉?

這套做法適合給自己用,或拿來驗證想法。要給多人或員工每天用,就會撞到前面的牆:資料要依使用者分開、要驗證簽章與綁定身分、執行時間與配額不夠、多人同時寫入產生重複編號。這時通常不是再優化 GAS 能解決,而是後端該換地方。商用版怎麼接 AI 自動回覆、處理推播額度、身分綁定與權限,寫在 LINE 串接系統開發;想先評估,可以寫信描述現在的用法,我們回一份現況判讀,這一步不收費。

常見問題

Gemini 串接 LINE 要花錢嗎?

小量可以免費起步:Gemini API 有免費層、LINE 的回覆訊息不計入則數,Apps Script 用一般 Google 帳號就能跑。但免費層的內容會被 Google 用來改進產品,官方也提醒不要送出個人資料;提醒與晨報用的是推播,會計入官方帳號的訊息則數。

Gemini 串接 LINE 要用哪個模型?

Google 官方模型頁(2026-10-13 更新)建議新專案用 3.5 Flash-Lite 或 3.8 Flash;2.5 系列只開放給過去用過的帳號。範例預設 3.5 Flash-Lite,要換就改 GeminiAPI.gs 開頭的 GEMINI_MODEL,再部署新版本才會生效。

LINE Bot 一直回「系統暫時繁忙」怎麼辦?

多半是金鑰填錯、模型名稱是新帳號用不了的舊版,或改了程式卻沒部署新版本;金鑰沒填的話回的是「系統設定錯誤」。到 Apps Script 左側「執行項目」,看記錄裡 Gemini 回傳的 HTTP 狀態碼與錯誤內容。

用 GAS 做 LINE Bot 能撐多少人?

這份範例是單人設計:所有加好友的人共用同一份行程與備忘,也會收到彼此的提醒。一般 Google 帳號每次執行最多 6 分鐘、觸發器每天合計 90 分鐘、URL Fetch 每天 2 萬次;多人同時寫入還可能產生重複編號。給多人用,就要改成依使用者分開資料,或換後端。

用 GAS 接 LINE Webhook 安全嗎?

金鑰放在指令碼屬性,不寫進程式碼。但 GAS 的 doPost 拿不到請求標頭,無法驗證 LINE 的簽章,所以部署網址不要公開;這份範例也沒有依使用者分開資料,官方帳號不要給外人加好友。正式商用要改用能驗證簽章、分開資料的後端。

本文引用資料來源

RELATED

< NEED_HELP />

想把這篇的做法用在自己的系統上?

寫信講現況,我們回你從哪一步開始。 收到後先回覆能不能做。

// WAITING_FOR_INPUT...