---
# System prepended metadata

title: Linebot 進階 - 小專案課程3

---

# Linebot 進階 - 小專案課程3 記憶傳承人

## 建議事項
- 請至少對第一堂社課 + ngrok範例影片有一定印象
- 本地端運行 + ngrok : [Linebot 開發 – 測試環境建立以快速測試](/M__0hsU6SSq0IWTxOOffLg)

## 你可以先開的畫面
- [今天Fork的專案](https://github.com/bsbacon0966/GDG_linebot_W3)
- [Firebase](https://firebase.google.com/)

## 今日課程
目標：使用 Linebot 結合 Firebase 使你的機器人具有記憶效果
- 申請Firebase憑證
- 透過呼叫collection->document來進行讀、寫資料
- 一些設計Linebot可以有的小技巧

>[!Warning]**此課程的調性**
>Linebot + Firebase，之中的程式呼叫相對複雜很多，很可能一般0基礎開發的學員很難理解，請如果真的不知道該怎麼辦，呼叫助教或是ChatGPT來協助你

---
## 第0步 : 為何要結合Firebase
如果你有跟上前兩週的課程內容，應該會發現一件事：
**LineBot 自身並沒有記憶能力**，而且我們現階段做的作品都是程式判斷要回應甚麼而已，每一次的訊息事件，對 Bot 來說都是「獨立的」。

所以我們可以透過Firebase，來讓我們的Linebot有一個"集中資料庫"，儲存使用者輸入的資料。

---
## 第1步 : 申請Firebase憑證

- 搜尋[Firebase](https://firebase.google.com/)，並進到此畫面，點選下方"Get Started in Console"
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/rkb4K5Dyee.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>

- 點選"建立Firebase專案"
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/BJeqF5DJll.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>

- 輸入專案名稱，取隨意名字都可以，輸入完後點選"繼續"
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/rkGW5qv1ll.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>

- 這裡他會要你選擇"Google Analytics"帳號，點選"Default"即可，**只要你沒有在此寫你的信用卡帳戶，使用Firebase都是免費的**
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/BySQq5P1xl.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>

- 進入到專案後，點左側的 "建構->Firestore Database"
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/SkUnq9PJxl.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>

- 點選"建立資料庫"，讓他架設資料庫
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/B17eo5wJll.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>

- 進入後，他會詢問你"安全性規則"，先幫我點選"測試版模式"啟動他
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/ryt7j9wkle.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>

- 建立完後，看向左上角的"專案總覽 -> 專案設定"
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/BJi0oqwkel.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>

- 看到"專案設定 -> 服務帳戶"，往下滑，直到看到"產生新的私密金鑰"
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/r1oZhcwkxe.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/BJUG2qvygl.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>

- 這時，他會下載一個.json的檔案，**請將其改名成"firebase_key.json"**，這個就是連線到Firebase的憑證
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/Hy3_hcD1eg.png" style="width: 60%; border: 2px solid black; padding: 5px;" />
</div>

---
## 第2步 : 與程式專案結合(這裡以Codespace作範例)

>[!Important]Codespace可以做的，不代表本地端可以
>研究後確認，如果你在Codespce運行```python app.py```
>他會給你一個選項為"設為公用"，這樣他給你的"轉送網址"貼到Linebot的Webhook URL使可以運行的
>如果你使本地端運行程式碼，請回到上方看ngrok的架設
>![image](https://hackmd.io/_uploads/BkdteoP1el.png)

### 2-0 今天的範例Code
請對[此專案](https://github.com/bsbacon0966/GDG_linebot_W3)進行fork，此檔案與第一周的範例程式碼相近。

### 2-1 基本設置

- 將剛剛下載的json憑證貼上
- 將原有的```.env.example```改名成```.env```
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/H1dw-jDylg.png" style="width: 60%; border: 2px solid black; padding: 5px;" />
</div>

- 將.env檔案中的```LINE_TOKEN```和```LINE_SECRET```貼上你Linebot的Token與Secret資訊
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/ByvTWjDJxe.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>

- 將.gitignore中，新增```firebase_key.json```，這樣等等push回到Github上時，**==你的json憑證不會跟著上傳到公開場所==**
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/r1PJQjP1eg.png" style="width: 80%; border: 2px solid black; padding: 5px;" />
</div>


- 在終端機輸入```python app.py```，如果剛剛輸入的.env順利的話，他就會運行
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/HJdwMjwJlg.png" style="width: 70%; border: 2px solid black; padding: 5px;" />
</div>

- 運行時，他會跳通知說"可以設為公開"，請點選"公開"

<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/BypSQiv1le.png" style="width: 70%; border: 2px solid black; padding: 5px;" />
</div>

- 點選後，你會看到"轉送位址"，你可以將其複製下來，並貼到Linebot -> Webhook URL，就可以直接測試你新寫好的Linebot程式碼了!

<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/B1NKQswklg.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>

## 第3步 : 先使用看看Firebase

```python=
# Firebase 初始化
######################################################
cred = credentials.Certificate("firebase_key.json")  # 放你的金鑰路徑
firebase_admin.initialize_app(cred)
db = firestore.client()
######################################################
```

```python=
# 設置一個事件處理器來處理 TextMessage 事件
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event: Event):
    if event.message.type == "text":
        user_message = event.message.text  # 使用者的訊息
        app.logger.info(f"收到的訊息: {user_message}")

        # 存到 Firestore（範例結構: GDG/W3/records/{儲存訊息}）
        ###################################################################
        reply_text = "你說了：" + user_message
        line_bot_api.reply_message(reply_token, TextSendMessage(text=reply_text))
            
        doc_address = db.collection("GDG").document("W3")
        doc = doc_address.get()
        if doc.exists:
            doc_data = doc.to_dict()
            history = doc_data.get("record", [])
        else:
            history = []
        print(history)

        history.append(reply_text)

        doc_address.set({"record": history})
        ###################################################################
```

將程式碼貼上後，運行```python app.py```，即可開始運行!
回到剛剛創建的Firebase，如果有看到下圖的格式，那恭喜你的linebot有了資料庫。

<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/SymsoFOyeg.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>

## 第4步 : 看懂Firebase的程式碼

### 4-1 Firebase結構
<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/BJS3hFuJex.png" style="width: 80%; border: 2px solid black; padding: 5px;" />
</div>

>[!Note]通俗的記法
> Collection = 抽屜的標籤
> Document = 當前抽屜的文件名稱
> Document的標籤層 = 當前文件中，標註的變數名稱與資料


則我們如果要儲存資料，那我們就要:
- 先導引到目標Document
- 讀取Document中，目標變數名稱
- 將資料轉成.dict，讓Python得以看懂

``` python=
doc_address = db.collection("GDG").document("W3") #指向目標document地址
doc = doc_address.get() #取出地址中的資料
if doc.exists:
    doc_data = doc.to_dict()
    history = doc_data.get("record", []) #取出資料中，指定標籤的資料
else:
    history = []
print(history)
```

則剛剛送給Firebase的資料就全在```history```這個變數中

```python=
history.append(reply_text)
doc_address.set({"record": history})
```

我們可以將rely_text插入到history變數中，並且最後對document地址宣告set，把history的資訊推回到Firebase中。

### 4-2 有了記憶後，結合Gemini吧!

初始化設定Gemini
```python=
# Gemini 初始化
######################################################
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
model = genai.GenerativeModel("gemini-1.5-pro")
######################################################
```

請把當前格子的程式碼改成這樣
```python=
# 存到 Firestore（範例結構: GDG/W3_memory/records/{儲存訊息}）
###################################################################

doc_address = db.collection("GDG").document("W3_memory")
doc = doc_address.get()
if doc.exists:
    doc_data = doc.to_dict()
    history = doc_data.get("record", [])
else:
    history = []
print(history)
gemini_response = model.generate_content(f"之前談話歷史為{history}，請回答{user_message}")
gemini_response_text = gemini_response.text.strip()

line_bot_api.reply_message(reply_token, TextSendMessage(text=gemini_response_text))

history.append({
    "user": user_message,
    "gemini_reply": gemini_response_text
})

doc_address.set({"record": history})
###################################################################
```

上述程式碼的目標 : 將Gemini的回應與user的輸入包裝再一起，並且將其傳送到指定地址的document中。

<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/r1-OP9uygx.png" style="width: 50%; border: 2px solid black; padding: 5px;" />
</div>

只要有了資料庫，並且調用資料庫資料給Gemini看，就可以使"每次對答"都能夠記住之前說過甚麼。

## 第5步 : 當你開發到一定階段後
>[!Warning]接下來的步驟
>因為Render實在有點難理解我在想甚麼，所以如果我們要將作品推到Render上，有一個下下策，需要一定轉換

請將你原先的Firebase初始化改成:

```python=
# === 初始化 Firebase ===
firebase_cred_str = os.getenv("FIREBASE_KEY") #放在遠端得環境變數中
firebase_initialized = False
if firebase_cred_str:
    cred_dict = json.loads(firebase_cred_str)  # 將 JSON 字串轉回 dict
    cred = credentials.Certificate(cred_dict)
    initialize_app(cred)
    db = firestore.client()
    firebase_initialized = True
else:
    raise ValueError("未設定 FIREBASE_CREDENTIAL_JSON")
#########################################################
```

並且驅動在Github上面的程式碼`change.py`，他會將你的Firebase憑證轉格式，你應該會得到一條結果

<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/SJPrJpegle.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>

請把```FIREBASE_KEY=```後面的所有東西複製下來

<div style="text-align: center;">
  <img src="https://hackmd.io/_uploads/r1s5Jpxexe.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>

把剛剛那一串打進```ENVIRONMENT->FIREBASE_KEY```當中，正常來說就可以在雲端上跑了。

## 第6步 : 一些開發Linebot可以有的小知識
### 取得使用者 ID（userId）做身份追蹤

```python=
user_id = event.source.user_id
```
在def handle_message(event)，除了可以提取詢問的內容，也可以提取"詢問人的userID"，可以運來做身分認證。

### 取得使用者的基本資料
```python=
profile = line_bot_api.get_profile(user_id)
name = profile.display_name
```
可以利用user_id提取出該使用者的部分資訊，例如姓名。


(---有找到新的資料就會繼續更新---)









