下面兩格已沿用你剛才寫的答案;這次不必重填。這個交接只在本次頁面使用,讀取後不再保存。
備開工前確認
| 你需要 | 說明 |
|---|---|
| 你自己的 LINE 帳號 | 手機上正在用的那個即可 |
| 你自己的 Google 帳號 | 一般 Gmail 帳號即可 |
| 一支裝了 LINE 的手機 | 最後測試要用它收第一個 AI 回覆 |
| 一段不被打斷的時間 | 照著做一次到底;中斷也沒關係,平台上做完的都會留著 |
板安裝進度板
勾了會自動更新;進度只存在本頁開著的期間(刻意設計)。
1建立官方帳號
- 開 LINE 台灣的「免費開設帳號」頁(LINE 官方網站),按頁面中間綠色的「免費開設帳號」鍵。
- 接著會到「LINE Business ID」頁:還沒有的選「使用LINE帳號註冊」,用你手機 LINE 的帳號登入;已經有的選下方「登入」。(頁面可切繁體中文。)
- 填帳號名稱、行業別等基本資料,送出。若畫面提供「未認證帳號」選項,可先依畫面完成;帳號可用性、驗證與功能狀態以 LINE 當下顯示為準。
已經有官方帳號的人,直接到步驟 2。
2啟用 Messaging API
這一步會把「能接程式的通道」打開。全程在中文介面。
- 開 LINE Official Account Manager,選你的帳號。
- 右上「設定」→ 左邊「Messaging API」。
- 按綠色「啟用Messaging API」。
- 「選擇服務提供者」:選「建立服務提供者」取個名(例:你的店名)→「同意」。已有的話選既有那個。
- 「隱私權政策及服務條款」兩欄都是選填,空著直接「確定」。




3複製 Channel ID 與 Channel secret
沿用剛才的 Messaging API 頁面,複製這個官方帳號的兩個設定。程式會自動向 LINE 取得短期權杖,不需要按 Issue。
- 在 LINE Manager 的「設定 → Messaging API」複製 Channel ID,貼進下方第一格。
- 複製同帳號的 Channel secret,貼進第二格。若 Manager 未顯示,從頁面連結進 LINE Developers,選同一個 channel 的 Basic settings;核對 Channel ID 後複製 Channel secret。
4取得 Gemini API Key
- 用你的 Google 帳號開 aistudio.google.com/apikey。
- 按右上「Create API key」。
- 幫金鑰取個名(隨意)、專案用它預設給的 →「Create key」。
- 「API key details」視窗出現金鑰——按複製鈕,貼進下面這格。



5開 GAS 專案,貼程式碼
這項免費安裝服務全程免費,不需要信用卡。 Google Apps Script(GAS)是 Google 的腳本執行服務,可以讓一小段程式有自己的網址;consumer 帳戶有官方 quota 與限制,超過時會停止執行或回報服務不可用,不會因此自動付款。本包只用它承接你自己的小工作;免費不等於無限量。
- 用你的 Google 帳號開 script.google.com → 按「新專案」。
- 編輯區全選(
Ctrl+A)→ Delete 全部刪掉。 - 按下面「複製整段程式碼」→ 貼進空白編輯區 →
Ctrl+S儲存。要整段貼:從第一行/**到最後一個}。
已複製 ✓
/** 愛智理- AI 助理免費安裝服務:LINE × Gemini 十步教學 */
function cfg_(name) {
return PropertiesService.getScriptProperties().getProperty(name);
}
/** 秘密只留在自己的指令碼屬性,不寫進程式碼或執行紀錄。 */
function importSetup_() {
var props = PropertiesService.getScriptProperties();
var raw = props.getProperty('SETUP_INPUT');
if (!raw) return;
var data;
try { data = JSON.parse(raw); } catch (_) { throw new Error('設定格式不完整:回第6步重新複製整份設定'); }
if (!data || typeof data !== 'object' || Array.isArray(data)) throw new Error('設定格式錯誤');
var required = ['LINE_CHANNEL_ID', 'LINE_CHANNEL_SECRET', 'GEMINI_API_KEY', 'KNOWLEDGE'];
var allowed = required.concat(['PROMPT', 'GEMINI_MODEL']);
if (Object.keys(data).some(function(k) { return allowed.indexOf(k) < 0; })) throw new Error('設定含未知欄位');
required.forEach(function(k) {
if (typeof data[k] !== 'string' || !data[k].trim()) throw new Error('缺少設定:' + k);
});
if (!/^\d+$/.test(data.LINE_CHANNEL_ID.trim())) throw new Error('Channel ID 應只有數字');
var clean = {};
allowed.forEach(function(k) {
if (data[k] !== undefined && typeof data[k] !== 'string') throw new Error('設定型態錯誤:' + k);
clean[k] = (data[k] || '').trim();
});
props.setProperties(clean, false);
props.deleteProperty('SETUP_INPUT');
console.log('設定已匯入;暫存設定已清除');
}
/** 只快取到到期前60秒。快取遺失時重發,不沿用失效憑證。 */
function lineToken_() {
var id = cfg_('LINE_CHANNEL_ID');
var secret = cfg_('LINE_CHANNEL_SECRET');
if (!id || !secret) throw new Error('LINE 設定缺少 Channel ID 或 Channel secret');
var digest = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, id + '\n' + secret);
var key = 'fs_line_' + Utilities.base64EncodeWebSafe(digest);
var cache = CacheService.getScriptCache();
var token = cache.get(key);
if (token) return token;
var res;
try {
res = UrlFetchApp.fetch('https://api.line.me/oauth2/v3/token', {
method: 'post', contentType: 'application/x-www-form-urlencoded',
payload: { grant_type: 'client_credentials', client_id: id, client_secret: secret },
muteHttpExceptions: true
});
} catch (_) { throw new Error('LINE 權杖連線失敗'); }
if (res.getResponseCode() !== 200) throw new Error('LINE 權杖 HTTP ' + res.getResponseCode());
var data;
try { data = JSON.parse(res.getContentText()); } catch (_) { throw new Error('LINE 權杖回應格式錯誤'); }
if (!data.access_token || typeof data.access_token !== 'string' || !isFinite(data.expires_in) || Number(data.expires_in) <= 60)
throw new Error('LINE 權杖回應不完整');
cache.put(key, data.access_token, Math.min(840, Math.floor(Number(data.expires_in) - 60)));
return data.access_token;
}
function ok_() {
return ContentService.createTextOutput(JSON.stringify({ ok: true })).setMimeType(ContentService.MimeType.JSON);
}
function doPost(e) {
try {
if (!e || !e.postData || !e.postData.contents) return ok_();
var body;
try { body = JSON.parse(e.postData.contents); } catch (_) { throw new Error('Webhook 內容格式錯誤'); }
var events = body.events || [];
if (!Array.isArray(events)) throw new Error('Webhook events 格式錯誤');
events.forEach(function(ev) {
if (ev.type !== 'message' || !ev.message || ev.message.type !== 'text' || !ev.replyToken) return;
var answer;
try { answer = askGemini_(ev.message.text); }
catch (err) {
console.error('Gemini 失敗:' + err.message);
answer = '抱歉,我這邊暫時有點問題,請稍等一下由真人回覆你。';
}
replyToLine_(ev.replyToken, answer);
});
} catch (err) { console.error('doPost 失敗:' + err.message); }
return ok_();
}
function askGemini_(userText) {
var key = cfg_('GEMINI_API_KEY');
if (!key) throw new Error('Gemini 設定缺少 API Key');
var model = cfg_('GEMINI_MODEL') || 'gemini-3.5-flash-lite';
var persona = cfg_('PROMPT') || '你是這家店的客服助理。用繁體中文,簡短、有禮貌地回答。';
var facts = cfg_('KNOWLEDGE') || '';
var systemText = '【你可以依據的資料】\n' + (facts || '(目前沒有提供資料)') +
'\n\n【預設風格;下面你的身分可以修改風格】\n回答控制在三句話以內。\n' +
'\n【你的身分與說話方式】\n' + persona +
'\n\n【安全底線;請最優先遵守】\n' +
'1. 只根據上面的資料回答;資料裡沒有的,就說你不知道。\n' +
'2. 遇到價格變動、退款、客訴,或你不確定的事,回答「這部分我幫你轉給真人確認」。\n' +
'3. 不要承諾我們做不到的事。';
var res;
try {
res = UrlFetchApp.fetch('https://generativelanguage.googleapis.com/v1beta/models/' + encodeURIComponent(model) + ':generateContent', {
method: 'post', contentType: 'application/json', headers: { 'x-goog-api-key': key },
payload: JSON.stringify({systemInstruction: {parts: [{text: systemText}]},
contents: [{role: 'user', parts: [{text: userText}]}], generationConfig: {maxOutputTokens: 1024}}),
muteHttpExceptions: true
});
} catch (_) { throw new Error('Gemini 連線失敗'); }
if (res.getResponseCode() !== 200) throw new Error('Gemini HTTP ' + res.getResponseCode());
var data;
try { data = JSON.parse(res.getContentText()); } catch (_) { throw new Error('Gemini 回應格式錯誤'); }
var parts = data.candidates && data.candidates[0] && data.candidates[0].content && data.candidates[0].content.parts;
var out = parts && parts.filter(function(p) { return p.text && !p.thought; }).map(function(p) { return p.text; }).join('');
if (!out) throw new Error('Gemini 沒有文字回覆:請檢查模型與安全限制');
return out;
}
function replyToLine_(replyToken, text) {
var res;
var token = lineToken_();
try {
res = UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
method: 'post', contentType: 'application/json', headers: {Authorization: 'Bearer ' + token},
payload: JSON.stringify({replyToken: replyToken, messages: [{type: 'text', text: String(text).slice(0, 4900)}]}),
muteHttpExceptions: true
});
} catch (_) { throw new Error('LINE 回覆連線失敗'); }
if (res.getResponseCode() !== 200) throw new Error('LINE 回覆 HTTP ' + res.getResponseCode());
}
/** 先檢查設定,再驗證 LINE 與 Gemini;失敗即停,不印秘密或模型回覆。 */
function checkSetup() {
importSetup_();
['LINE_CHANNEL_ID', 'LINE_CHANNEL_SECRET', 'GEMINI_API_KEY', 'KNOWLEDGE'].forEach(function(k) {
if (!cfg_(k)) throw new Error('缺少設定:' + k + ',回第6步');
});
console.log('設定:完整');
var token = lineToken_();
var res;
try {
res = UrlFetchApp.fetch('https://api.line.me/v2/bot/info', {headers: {Authorization: 'Bearer ' + token}, muteHttpExceptions: true});
} catch (_) { throw new Error('LINE 帳號檢查連線失敗'); }
if (res.getResponseCode() !== 200) throw new Error('LINE 帳號檢查 HTTP ' + res.getResponseCode());
console.log('LINE:連線成功');
askGemini_('請問你們幾點開?');
console.log('Gemini:連線成功');
console.log('下一步:部署並用手機收第一個 AI 回覆;本檢查尚未驗證 Webhook 回程');
}

6一次貼入設定
這頁把 ID、secret、API Key 和工作資料合成一份設定。只貼一列,避免逐格拼名稱。秘密保存在你自己的 Apps Script;第 7 步會自動拆開並刪掉暫存列。
選填:語氣與模型
- Apps Script 左邊「專案設定」(齒輪)→「指令碼屬性」→「新增指令碼屬性」。
- 按「複製設定名稱」,貼到名稱欄;按「複製整份設定」,貼到值欄。
- 按「儲存指令碼屬性」。
已複製 ✓
這份設定含秘密,只能貼在自己的專案設定值欄;不要貼進程式碼、聊天或截圖。貼好後複製其他文字覆蓋剪貼簿,並清除剪貼簿歷史中的秘密。
SETUP_INPUT,值是一整份設定。不要自行拆開或修改。
7執行 checkSetup 驗證連線
- 回編輯畫面,上方下拉選單選「checkSetup」→ 按「執行」。
- 第一次執行會跳授權:「審查權限」→ 選你的 Google 帳號 → 確認專案名稱就是你剛建立的專案;若出現「這個應用程式未經 Google 驗證」(這程式就是你自己剛貼的)→ 點「進階」→「前往(你的專案名稱)(不安全)」→「繼續/允許」。
只授權你剛建立的自己的程式。若帳戶政策禁止授權,不要改建 OAuth 應用;先停止這條路徑並確認帳戶政策。


8部署,拿到網址
- 右上「部署」→「新增部署作業」。
- 「選取類型」旁的齒輪 → 選「網頁應用程式」。
- 兩格設定:「執行身分」=「我(你的 email)」;「誰可以存取」=「所有人」。
- 按「部署」。
- 完成視窗最下面「網頁應用程式」的網址按「複製」。
https://script.google.com/macros/s/… 網址已複製。可以順手貼進下面這格備用。交給 LINE 的網址必須是
/exec 結尾——結尾不是 /exec(例如網址裡有 library)=複製到別格了,回完成視窗拿「網頁應用程式」那串。


9回 Manager 接上線
- 回步驟 2 那頁(Manager → 設定 → Messaging API)。
- 把剛複製的網址貼進「Webhook網址」那格 → 按「儲存」。
- 左邊「回應設定」→ 把「Webhook」開成綠色。
同頁的「自動回應訊息」「加入好友的歡迎訊息」「聊天」開不開由你決定——它們跟機器人回覆不衝突(2026-08 實測)。想讓客人只收到 AI 的回答,就把自動回應關掉。注意:自動回應開著時,你測試收到的可能是罐頭回應而非 AI——測試時以回覆內容判斷。


10手機實測:收你的第一個 AI 回覆
用手機把自己的官方帳號加好友(Manager 的「加入好友工具」可取得 QR code),照順序傳兩句。第一次測試先用步驟 6 的範例四行,通了再換自己的內容。
已複製 ✓
Google/LINE 的介面、配額與政策可能變動;使用自己的帳號逐步確認。完成判定以手機實際收到 AI 回覆為準。