# **【AI 圖片文字記帳 Line Bot: 自動記帳寫入 Google Sheet】ft. n8n 藥單辨識模板**
:::info
- 參考模板:藥單辨識
- Line Bot 個人記帳助理
1. 設置 Line Bot
2. n8n Webhook
3. Swift message/image 分流處理資料
4. 提取去重使用欄位
5. 比對資料是否已存在
6. Switch 判斷
7. 改自動回應訊息/圖片
8. 正式部署
:::
<br/>
一開始在 threads 看到有人分享 n8n藥單辨識 [Extract Structured Data from Medical Documents with Google Gemini AI](https://n8n.io/workflows/5917-extract-structured-data-from-medical-documents-with-google-gemini-ai/) 的模板
好像可以把裡面的圖片辨識節點拿來用?
自己常常忘記記帳,月底想不起來刷卡外的花費,於是萌生了做一個可以口述文字/圖片辨識記帳機器人的想法
因為是要隨時回傳的,這次部署在 Zeabur
相關文章:
[【Zeabur 架設 n8n: 手把手教你做自動化 TSMC 產業分析助理】](https://hackmd.io/@workcata/S1rCw_y8lg)
[【LINE Bot + Google Sheets, 部署到 Reader】](https://hackmd.io/@workcata/HyygFyMmel)
另外想感謝 [Darrell TW](https://www.darrelltw.com/),原本寫的比較雜,他幫我 review 後簡化很多。他的網站分享蠻多n8n知識的,想研究可以挖寶一下
<br/>
## 參考模板:藥單辨識
先用 postman 測試 Zeabur 能不能收到圖片
找了張圖片上傳 [Postimages](https://postimages.org/)
Webhook
點 listen for event

到 Postman,選 POST + 貼上 test url
Header

Body

Send -> 回到 Webhook
確定有收到

<br/>
## Line Bot 個人記帳助理

### 1. 設置 Line Bot
登入 [Line Developers](https://developers.line.biz/zh-hant/)
創一個 provider -> create new channel 選 Messaging API -> 填寫 Channel 資訊





Line 有更新,現在需要進到 [LINE Official Account Manager](https://manager.line.biz/) 啟用 API
點進設好的 Provider -> 右上角齒輪 設定 -> Messaging API -> 啟用




創建成功

回到 line,會看到設好的 line bot

現在再回到 [Line Developers](https://developers.line.biz/zh-hant/) 重整
剛才的 Provider 就會有資料了

### 2. n8n Webhook
n9n 新增第一個節點
test 的網址複製下來 -> 按 listen for the test event

回到 [Line Developers](https://developers.line.biz/zh-hant/)
點 Messaging API settings
把 n8n Webhook test-url 貼上 -> verify
出現 success 代表有成功收到
PS 下方 use Webhook 要打開

點選 Listen for the test event (接下來都是在測試環境,傳任何訊息都要先點一次) -> line bot 發送訊息
Webhook 就會出現了
line bot 回覆的訊息先不管它,最後再來改

回到 n8n Webhook 節點有收到資料就可以繼續

### 3. Swift message/image 分流處理資料
新增分流節點
分出收到的訊息是文字 text / 圖片 image

#### - text_openai

設一個 set 提取 text

接 AI Agent
prompt
```=
分析出資訊:
先判斷是否為記帳相關明細、發票,若不是則不需要處理,直接停止所有流程。若是記帳相關,分析出六個資訊:
1. 日期(如果只提到'今天',用{{ $now.format('YYYY-MM-DD') }} ) 2. 通路 3. 通路類型(只能為:便利商店、個人用品店、量販超市、傳統市場、網路購物、藥局、五金百貨、餐廳小吃店、醫療院所、3C商場、航空客運、軟體儲值、加油交通儲值、線上課程、電信公司)4. 花費明細 5. 金額 6. 類別(只能為:家用、正餐飲食、飲料甜點、生活用品、美妝、衣服飾品、交通、娛樂、電信、醫療、3C、軟體、學習、旅遊)。若通路類型判斷為航空客運,類別一定為旅遊。若無法判斷或沒資訊,請填入'DN',每格都要有資料。請輸出為 JSON 格式,例如:{\"日期\": \"...\", \"通路\": \"...\", \"通路類型\": \"...\", \"花費明細\": \"...\", \"金額\": \"...\", \"類別\": \"...\"}"
```
下面 Chat Model 點開,放 api key + 選模型


設一個 Structured Output Parser,指定輸出格式

#### - image_openai

設一個 Https
回到 [Line Developers](https://developers.line.biz/zh-hant/)
點 Messaging API settings
滑到下面,產生一個 Channel access token 複製下來
Send Headers 打開
Name: Authorization
Value: Bearer Channel access token


之後一樣接 AI Agent
#### - image_gemini
PS 這裡只是想試試藥單辨識模板的做法,如果不想用 gemini,可以直接跳過這段,最後我的模板只接 AI Agent

設一個 Https
這裡改用 gemini
回到 [Line Developers](https://developers.line.biz/zh-hant/)
點 Messaging API settings
滑到下面,產生一個 Channel access token 複製下來
Send Headers 打開
Name: Authorization
Value: Bearer Channel access token


設一個 Extract from File
從檔案中擷取出特定的資料內

設一個 set (prepare for AI)
只提取裡面的 data

設一個 Https
image post 到gemini
```=
https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent
```

設一個 code 整理
請 gpt 幫我寫的
```=
return items.map(item => {
// 1. 提取 JSON 數據
if (item.json.candidates?.[0]?.content?.parts?.[0]?.text) {
const textContent = item.json.candidates[0].content.parts[0].text;
// 移除 Markdown 的 ```json\n 和 \n``` 標記
const jsonString = textContent.replace(/^```json\n/, '').replace(/\n```$/, '');
try {
const parsedData = JSON.parse(jsonString);
// 將解析後的數據合併到 item.json 中
item.json = { ...item.json, ...parsedData };
// 可選:刪除原始的 Gemini API 響應結構,如果你不再需要它
delete item.json.candidates;
delete item.json.usageMetadata;
delete item.json.modelVersion;
delete item.json.responseId;
} catch (e) {
console.error("Error parsing JSON from Gemini text content:", e);
// 處理解析錯誤,例如設置一個錯誤標誌或保留原始數據
}
}
// 2. 提取 replyToken (如果 Gemini API 的輸出中也包含 LINE 的 body/events 結構)
// 這通常發生在 Line Trigger 直接連接到 Gemini 節點時
if (item.json.body?.events?.[0]?.replyToken) {
item.json.replyToken = item.json.body.events[0].replyToken;
delete item.json.body; // 刪除原始的 body 結構
}
return item; // 返回處理後的項目
});
```

<br/>
### 4. 提取去重使用欄位
為了防止自己重複紀錄,這裡提取去重使用欄位,之後用來比對

<br/>
### 5. 比對資料是否已存在

寫入資料前,比對 google 去重使用欄位是不是一樣
一樣 -> 不寫入
不一樣 -> 寫入
設一個 google sheet

設一個 get rows in sheet
假設有資料,output 會讀到

接著要判斷,這次新資料的去重使用欄位,是否存在於 get rows in sheet 的去重使用欄位
一開始我直接用 if 判斷,發現不能這樣做,就先提取到 list,再做比對
之後 Darrell 教我使用了 Aggregate
設一個 Aggregate

設一個 merge_all 把要比對的兩份資料合起來
選 combine position

<br/>
### 6. Switch 判斷
我希望不管寫入或不寫入,都要回傳訊息,Line Bot 使用當個會話的 replytoken 來 reply
一開始我用 if 判斷,要回傳資料發現 Line Bot 的 replyToken 只能使用一次,但 n8n 會每個分支都跑過一遍,就改成用 userId POST, Darrell review 幫我改成 Switch,方便很多

邏輯是
```=
去重使用欄位沒有重複 -> 寫入,回傳"✅ 記帳成功 <明細> "
去重使用欄位 = 'DN-DN-DN-DN' -> 不寫入,回傳"不相關明細或圖片,不會計入"
(前面下的prompt,判斷不出來,所有欄位會是DN,去重使用欄會出現'DN-DN-DN-DN')
去重使用欄位 正則表達配對 '^.*-DN-DN-DN$' -> 不寫入,回傳"不相關明細或圖片,不會計入"
(我有時候手殘,打到一半就按到送出,去重使用欄會出現'2025-01-01-DN-DN-DN)
去重使用欄位 = '---' ->不寫入,回傳"不相關明細或圖片,不會計入"
(傳不相關照片時,有可能會全空值,去重使用欄會出現'---')
去重使用欄位重複 -> 不寫入,回傳"⚠️ 此筆資料已記錄過,不會重複記帳"
```


#### - ✅ 記帳成功 <明細>
設一個 google sheet

設一個 Https
url = https://api.line.me/v2/bot/message/reply
Send Headers 打開
Name: Authorization
Value: Bearer Channel access token
Name: Content-Type
Value: application/json
Body json
```=
{
"replyToken": "{{ $('Webhook').item.json.body.events[0].replyToken }}",
"messages": [
{
"type": "text",
"text": "✅ 記帳成功: {{ $('Merge_all').item.json['去重使用'] }}"
}
]
}
```


#### - 不相關明細或圖片,不會計入
設一個 Https
url = https://api.line.me/v2/bot/message/reply
Send Headers 打開
Name: Authorization
Value: Bearer Channel access token
Name: Content-Type
Value: application/json
Body json
```=
{
"replyToken": "{{ $('Webhook').item.json.body.events[0].replyToken }}",
"messages": [
{
"type": "text",
"text": "不相關明細或圖片,不會計入"
}
]
}
```


#### - ⚠️ 此筆資料已記錄過,不會重複記帳
設一個 Https
url = https://api.line.me/v2/bot/message/reply
Send Headers 打開
Name: Authorization
Value: Bearer Channel access token
Name: Content-Type
Value: application/json
Body json
```=
{
"replyToken": "{{ $('Webhook').item.json.body.events[0].replyToken }}",
"messages": [
{
"type": "text",
"text": "⚠️ 此筆資料已記錄過,不會重複記帳"
}
]
}
```


### 7.改自動回應訊息/圖片
進到 [LINE Official Account Manager](https://manager.line.biz/)
點頭像可以直接改


自動回應訊息


基本檔案 -> 改背景圖


### 8. 正式部署
回到 n8n
上面的 Active 打開

Webhook,複製 Production URL

回到 [Line Developers](https://developers.line.biz/zh-hant/)
點 Messaging API settings
改 n8n Webhook url -> verify
出現 success 代表有成功收到,部署成功

月底拉個樞紐分析圖表,就會很清楚花費佔比了
