自己目前利用下班時間,協助 NGO 組織做一些小工具,想辦法讓人力使用降到最低(畢竟大家都有正職),或做出一些有趣實用的東西提供給大家。
過去就有不少校友學長姐提及,有許多散落但很棒的活動資訊,都不一定會掌握到。因此就在想,或許能做一個彙整的平台,讓無論是哪個社團、組織舉辦的活動,(尤其是各個地區校友會散落各處,資訊較難集中)都能彙整進一個行事曆當中。
整理出痛點有二,及其解決方法:- 資訊散落,需要一個彙整檢視的地方 => 一個行事曆介面一次看清楚!
- 填寫活動很麻煩 => 直接丟活動文字即可,用 AI 幫忙分類、彙整!
選擇使用的服務
- LINE Messaging API,串接 LINE 官方帳號
- Google 試算表 + Google App Script:試算表是資料庫、App script 作為 API(對外接口)。
- 靜態網頁,這邊使用 Github 架站即可
- OpenAI 服務,需要申請 OPENAI_API_KEY
系統架構

架構非常簡單!
成果
行事曆頁面
下圖可左右滑動
LINE 官方帳號

開發細節
活動資訊資料庫
需先定義好可能未來會需要使用到的欄位,包含之後要給 GPT 整理的相關資訊。

除此之外,其實我覺得也可以請 GPT 斷詞關鍵字,這樣在之後行事曆介面上會變得更豐富易讀。
這份「資料庫」,同時會是 LINE 官方帳號傳送活動資訊的存放位置,也是前端靜態行事曆頁面取資料時,也會在這邊取得資料。同樣都使用同一份 Google Sheet,同樣都將 Google App Script 當作 API 使用,串接前後端。
App Script
這裡基本上就要處理所有前後端的互動、資料處理等功能。其中特別想提的是 GPT 的寫法,這邊想使用 Json Schema 確保 AI 看完活動文字以後,吐出來的欄位都會是固定的。
// 使用GPT API提取結構化數據
function extractDataWithGPT(text) {
// 動態取得當前年份
const currentYear = new Date().getFullYear();
const requestBody = {
'model': 'gpt-4o',
'messages': [
{
'role': 'system',
'content': `你是一位專門從非結構化文字中提取活動資訊的專家。請從使用者提供的文字中提取活動相關資訊並依照指定格式輸出。其中,如果使用者沒有給明確年份,年份時間不確定的話一律用${currentYear}年份。此外,如果是港湖幫、GIVERS CLUB,需在活動名稱就提及,格式為「港湖幫活動:<實際活動名稱或演講講題>」、「GIVERS CLUB:<實際活動名稱或演講講題>」。記得,GIVERS CLUB 跟 港湖幫 都屬於校友會`
},
{
'role': 'user',
'content': text
}
],
'functions': [
{
'name': 'extract_calendar_event',
'description': '從文字中提取行事曆活動資訊,注意,其他無法歸類的活動的詳細內容描述放「活動內容」中,完全用本文文字,勿自行詮釋說法',
'parameters': {
'type': 'object',
'properties': {
'開始時間': { 'type': 'string', 'description': `活動開始時間,盡量使用YYYY/MM/DD HH:MM格式,年份時間不確定的話一律用${currentYear}年份` },
'結束時間': { 'type': 'string', 'description': '活動結束時間,盡量使用YYYY/MM/DD HH:MM格式' },
'活動名稱': { 'type': 'string', 'description': '活動的名稱' },
'活動地點': { 'type': 'string', 'description': '活動實際舉辦的確切地點' },
'活動地點分類': { 'type': 'string', 'description': '活動地點的分類,呈現台灣的縣市名稱即可' },
'分類': { 'type': 'string', 'description': '活動的分類,僅分為"校友會", "其他活動"。' },
'活動單位': { 'type': 'string', 'description': '舉辦活動的單位' },
'活動相關連結': { 'type': 'string', 'description': '活動的相關網頁連結' },
'活動性質': { 'type': 'string', 'description': '活動的性質,僅能是 "遊玩" "演講" "課程" "餐會" "其他",根據活動內容選擇最適合的一項' },
'活動內容': { 'type': 'string', 'description': '其他無法歸類的活動的詳細內容描述放這邊,請完全用本文文字,勿自行詮釋' }
},
'required': ['開始時間', '活動名稱']
}}],
'function_call': { 'name': 'extract_calendar_event' }
};
const options = {
'method': 'post',
'headers': {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
'payload': JSON.stringify(requestBody),
'muteHttpExceptions': true
};
const response = UrlFetchApp.fetch('https://api.openai.com/v1/chat/completions', options);
const responseData = JSON.parse(response.getContentText());
if (responseData.error) {
throw new Error(`GPT API錯誤: ${responseData.error.message}`);
}
// 從回應中取出函數調用參數
const functionCall = responseData.choices[0].message.function_call;
if (!functionCall || functionCall.name !== 'extract_calendar_event') {
throw new Error('GPT未能正確提取資訊');
}
return JSON.parse(functionCall.arguments);
}
上面的程式碼也是請 Cursor 生的,所以不用擔心寫不出來。我下給 Cursor 的 Prompt 是直接從 OpenAI 官網指定我要 Structural Output 的寫法:
我想要做一個app script 結合 LINE BOT 跟 gpt 的應用 使用者會先針對 行事曆總表 這個 sheet 抓欄位 欄位從A到I分別為 開始時間 結束時間 活動名稱 活動地點 活動地點分類 分類 活動單位 活動相關連結 活動內容 使用者會在 LINE 上傳一堆繁體中文文字 要先從 LINE 傳到 Google App script 中 並透過 GPT 處理文字 把文字資訊轉成 json json key 就是欄位名稱的對應 gpt範例如下 <這邊附上官網範例寫法 我使用 curl 的範例>
完成過後,將相關的內容填入 sheet 中,最後回傳給使用者「新增成功」並簡述新增的活動內容 功能都寫在同一個 code 當中
LINE Bot
LINE 這邊相對單純,純粹接 Messaging API 後,就可以使用了!
開發踩過的雷
App Script 中寫了很多 Logger.log() 偵錯,但在偵測介面看不到任何訊息跳出
(這在上一篇文有提到,這邊重貼)
是的!這真的超級麻煩,你不知道哪裡錯...真的是能憑空猜測...。詢問 AI,AI 也只能亂猜,猜對還可以,猜錯,他會印出更多的 Logger.log 然後提醒你這個使用者去看偵測介面(console),但重點是就是沒有訊息跳出啊!
幾週前也看到 保哥 也發現類似的狀況,這串很多大神,值得留個連結在這。

當然,如果有 GCP,可以直接將 App script 專案綁定去 GCP,可以從 GCP 內看到程式 log。
個人用戶如我,後來決定「多寫一些 test function」,然後一一確認每個 Function 的功能,或許會比較不會「這麼憑空除錯」。

有了這個概念,我開發速度有加倍了!
分類模型用什麼好?
以 GPT 非推理模型而言,個人認為 gpt-4o 仍然最佳(gpt-4.1 不好,很囉唆),如果有預算考量,可以使用 gpt-4o-mini,但就會相對不準確。你需要允許容錯空間。
AI 的 API 很花錢...,難道讓群組的人都要付費ㄇ ??
確實...如果不太能接受花太多錢,可以用小模型如「gpt-4o-mini」、「gpt-4.1-mini」或是更小的「gpt-4.1-nano」。
不然...其實 OpenAI 有提供開發者一個方案:只要你勾選願意將資料提供給 OpenAI 做利用,他們會給使用者每日一定的免費額度(實測基本上每天都用不完...,偶爾一天才花0.3~0.4美金)。
怎麼「詮釋」畫面?
如果過去沒有程式經驗,在 Vibe Coding 過程中,可以用白話詮釋沒錯,但有時候並不會很精準。我個人推薦可以去看 Bootstrap 的中文網站,提供各種元件的「講法」,讓你 LLM 對話更精準。
(Bootstrap 就是一個乖乖牌格式,並沒有到好看,但可以拿來「溝通」)
如我常用的「麵包屑(breadthumb)」、「彈出視窗(modal)」、「吐司(Toasts)」在此次都有用來溝通。
一些未來優化的方向
「誰」更新活動資訊?怎麼更新?
現在解決了「不一一打欄位、一一按送出」的痛點,只要貼文字就好。但仍然要「貼文字」的動作。解決方法大概有二:
- 交給社群力量:開放這個 LINE 官方帳號,有人想新增資料,就直接填寫。但壞處就是不確定資料品質、大家丟上來的活動是否適合。
- 交給爬蟲:爬特定的社團、或 LINE 群組,定期丟入資訊。壞處當然仍是爬臉書困難、爬 LINE 也有點麻煩,或許需要加一層 AI 判斷哪些是「活動資訊」哪些不是,但這樣會讓 AI 監測所有群組談論內容,可能不是大家都喜歡這樣。
也因此,目前暫時打算使用一兩位管理者,仍然是手動貼文字訊息,但至少這樣已經很快了!