iT邦幫忙

0

《賴田捕手:追加篇》第 35 天:製造 Deploy to Heroku 按鈕

第 35 天:製造 Deploy to Heroku 按鈕

我打開信封,有張明信片在裡面。明信片封面是一個大大的 Heroku 標誌,背面則寫著:親愛的小艾,偉大的學者路德維希·希洛庫曾經這麼說過,當眼睛看到美麗的 APP 時,手就會想在鍵盤上為她做一個 Deploy to Heroku 按鈕。我多麼希望我也能為妳做出一個按鈕。

~節錄自《聊天機器人的歷史:如果不會,那就》

延伸自系列文 《從LINE BOT到資料視覺化:賴田捕手》

《賴田捕手:追加篇》:

今天您將知道:

  1. 設計 LINE Notify 訂閱流程
  2. 製造 Deploy to Heroku 快速按鈕
  3. 使用 Deploy to Heroku 快速按鈕

設計 LINE Notify 訂閱流程

第 34 天當中,我們大致了解了 LINE Notify 的運作方式,以及程式碼的實作細節。那麼今天就來介紹一下我們可以怎麼做,來達到將使用者與 LINE Notify 建立連結,並且透過 LINE Notify 發送訊息給指定使用者的這個功能。
要讓使用者與 LINE Notify 建立連結,我們首先要傳送一個授權網址 (Auth Link) 給使用者,而要產生這樣子的授權網址,我們需要 Client ID、Redirect URI、以及一個我們自行定義的 State,如圖一

https://ithelp.ithome.com.tw/upload/images/20210117/20120178hpdnRObiI9.png
圖一、LINE BOT 產生授權網址

可以自行定義的 State 非常重要,因為之後 LINE Notify 給出來的連結權杖 (Access Token) 並沒有關於使用者的任何資料,為了要知道哪一個連結權杖會對應到哪一個使用者,我們必須要用 State 來記錄。因此這邊就用 LINE 給出來的使用者代碼來當作 State:

def create_auth_link(user_id, client_id=client_id, redirect_uri=redirect_uri):
    
    data = {
        'response_type': 'code', 
        'client_id': client_id, 
        'redirect_uri': redirect_uri, 
        'scope': 'notify', 
        'state': user_id
    }
    query_str = urllib.parse.urlencode(data)
    
    return f'https://notify-bot.line.me/oauth/authorize?{query_str}'

這邊的user_id可以從event.source.user_id拿到,而client_idredirect_uri則是我們在 LINE Notify 上登錄服務的時候會拿到/使用的資料。至於 LINE BOT 該在什麼時候將這個授權網址發送給使用者,大家就可以自行設計。以我們目前想要做的雙色打光來說,如果要將雙色打光處理過後的圖片都由 LINE Notify 發送給使用者的話,那這個網址就必須放在使用者容易拿到的地方,或是每一次互動都發送這個網址,或是更貼心一點,只發送給沒有與 LINE Notify 建立連結的使用者。
當使用者進入授權網址,點擊同意並連結之後,我們的 LINE BOT 要能收到 LINE Notify 產生的 Code,以及授權網址本身的 State,如圖二

https://ithelp.ithome.com.tw/upload/images/20210117/20120178SkKzlmzJEG.png
圖二、LINE USER 同意並連結

這件事我們之前已經示範過該如何實作了,就不囉嗦,程式碼如下:

  • app/routes.py
from flask import request

@app.route("/callback/notify", methods=['GET'])
def callback_notify():
    
    assert request.headers['referer'] == 'https://notify-bot.line.me/'
    code = request.args.get('code')
    state = request.args.get('state')

    # 接下來要實作的程式碼    
    success = handle_subscribe(code, state)

    return '恭喜完成 LINE Notify 連動!請關閉此視窗。' 
  • 第六行:state = request.args.get('state')
    這邊這個state,由於我們刻意安排的結果,其實就是使用者代碼 (user_id)。

我們的 LINE BOT 拿到這個 Code,就可以向 LINE Notify 拿取最重要的連結權杖 (Access Token) 了 (圖三):

import json

def get_token(code, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri):
    url = 'https://notify-bot.line.me/oauth/token'
    headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
    data = {
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': redirect_uri,
        'client_id': client_id,
        'client_secret': client_secret
    }
    data = urllib.parse.urlencode(data).encode()
    req = urllib.request.Request(url, data=data, headers=headers)
    page = urllib.request.urlopen(req).read()
    
    res = json.loads(page.decode('utf-8'))
    return res['access_token']

https://ithelp.ithome.com.tw/upload/images/20210117/20120178i00rsF347c.png
圖三、LINE BOT 取得連結權杖

我們得好好地把這個連結權杖儲存起來。說道儲存資料,第一個想法當然就是 Heroku Postgress!我們的 Heroku Postgres 已經有一個表格user_dualtone_settings用來儲存使用者的雙色打光設定,現在我們可以再建立一個表格,用來儲存連結權杖 (當然,若大家想用相同的表格來儲存這些資料也完全 OK,畢竟都可以用user_id來當作表格的 Primary Key)。那麼,這個表格要儲存哪些資料呢:

  • app/custom_models/CallDatabase.py
def init_table():
    conn, cursor = access_database()
    postgres_table_query = "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname != 'pg_catalog' AND schemaname != 'information_schema'"
    cursor.execute(postgres_table_query)
    table_records = cursor.fetchall()
    table_records = [i[0] for i in table_records]
    print('CallDatabase: table_records', table_records)

    # 用來儲存連結權杖的表格
    if 'notify_subscription' not in table_records:
        create_table_query = """CREATE TABLE notify_subscription (
            user_id VARCHAR ( 50 ) PRIMARY KEY,
            access_token VARCHAR ( 50 ) NOT NULL,
            subscribe BOOLEAN NOT NULL
        );"""

        cursor.execute(create_table_query)
        conn.commit()

    return True

關於user_dualtone_settings的相關程式碼之前介紹過了,這邊我們就略過不看。我們將用來儲存連結權杖的表格命名為notify_subscription,裡面需要放入這些資料:

CREATE TABLE notify_subscription (
    user_id VARCHAR ( 50 ) PRIMARY KEY,
    access_token VARCHAR ( 50 ) NOT NULL,
    subscribe BOOLEAN NOT NULL
);
  • 第一行:user_id VARCHAR ( 50 ) PRIMARY KEY,
    我們用user_id當作 Primary Key。

  • 第二行:access_token VARCHAR ( 50 ) NOT NULL,
    並且存入access_token連結權杖。

  • 第三行:subscribe BOOLEAN NOT NULL
    最後一個則是這個連結是否仍然有效的布林值。若仍然有效,布林值為真。若已經失效,布林值為假。為什麼會有有效跟無效的差別呢?因為使用者隨時可以解除 LINE Notify 的連結。這個時候我們的 LINE BOT 並不會收到任何通知,只有在嘗試用授權連結發送訊息給使用者的時候,發送失敗,才會知道原來使用者已經斷開連結了。因此可以用一個欄位來追蹤連結是否有效。

有了這個表格,當 LINE BOT 從 LINE Notify 拿到連結權杖之後,就可以存進來了 (圖四):

  • app/custom_models/CallDatabase.py
def notify_subscribe(user_id, access_token, subscribe):
    conn, cursor = access_database()

    if notify_get_token(user_id):
        postgres_delete_query = "DELETE from notify_subscription where user_id = %s"
        cursor.execute(postgres_delete_query, (user_id, ))
        conn.commit()

    table_columns = '(user_id, access_token, subscribe)'
    postgres_insert_query = f"INSERT INTO notify_subscription {table_columns} VALUES (%s,%s,%s)"

    record = (user_id, access_token, subscribe)
    
    cursor.execute(postgres_insert_query, record)
    conn.commit()

    cursor.close()
    conn.close()
    
    return record

回頭看看我們在app/routes.py當中,callback_notify這個函式裡答應要實作的handle_subscribe

def handle_subscribe(code, user_id):
    access_token = get_token(code)
    _ = CallDatabase.notify_subscribe(user_id, access_token, True)

    return True

https://ithelp.ithome.com.tw/upload/images/20210117/20120178APKNAiTKDc.png
圖四、Heroku Postgres 存入連結權杖

有沒有覺得整個流程都配合起來的感覺了呢?
最後,當我們需要透過 LINE Notify 發送訊息給使用者的時候 (圖五):

def send_message(user_id, access_token, im_url):

    url = 'https://notify-api.line.me/api/notify'
    headers = {"Authorization": "Bearer "+ access_token}
    data = {
        'message': '✌您點的雙色打光已送達!', 
        'imageThumbnail': im_url, 
        'imageFullsize': im_url
    }
    data = urllib.parse.urlencode(data).encode()
    req = urllib.request.Request(url, data=data, headers=headers)
    
    try:
        page = urllib.request.urlopen(req).read()
        print('Notify Success!')

    except:
        _ = CallDatabase.notify_subscribe(user_id, access_token, False)
        print('Notify Fail. User Unsubscribe.')

https://ithelp.ithome.com.tw/upload/images/20210117/201201786AXuEnKxh9.png
圖五、Heroku Postgres 讀取連結權杖

這麼一來,我們的 LINE BOT 就可以透過 LINE Notify 在需要的時候主動推送訊息給指定使用者,而不需要擔心預算爆表的問題了!

製造 Deploy to Heroku 快速按鈕

經過扎實的五個星期,我們的雙色打光 LINE BOT 終於完工了,草泥馬們一定很開心,可以看著最愛的雙色打光圖片提振精神了。
但也許不是每位草泥馬飼育家都像我們一樣也身兼 LINE BOT 設計師。畢竟飼養草泥馬本身就是一件艱困且耗時的工作,要是再要求每一位飼育家都能夠略懂 Python,且了解該怎麼實作 LINE BOT,似乎有點太苛刻了。怎麼辦呢?難道那些飼育家的草泥馬,就永遠跟雙色打光的圖片無緣了嗎?
答案是否定的。
感謝 Heroku 推出的一個很棒的功能:【Deploy to Heroku 按鈕】,讓我們可以把自己設計出來的 APP 很快地分享給有興趣的人,讓大家不用重複相同的工作,每一次都重新開始。只要把自己的專案放到 Github 上,再加上一個 Deploy to Heroku 按鈕,那麼下次想要寫出一模一樣的內容,只要按下這個按鈕,不用寫任何一行程式碼,你也可以擁有雙色打光 LINE BOT。是不是也很想知道該怎麼做出這個按鈕呢?那麼我們現在就來看看吧!
首先要把我們用來架構 Heroku APP 的專案都搬到 Github 上。舉例來說,以下是我們至今為止建立起的所有檔案內容:

D:\appendix>tree /f
Folder PATH listing
Volume serial number is 9C33-6XXD
D:.
│   runtime.txt
│   requirements.txt
│   Procfile
│   Alma.py
│
└───app
    │   __init__.py
    │   models_for_line.py
    │   routes.py 
    │
    └───custom_models
            AlmaTalks.py
            AlmaRenders.py
            AlmaNotify.py
            CallDatabase.py

我們要把這些檔案內容,按照該有的檔案架構,上傳到我們的 Github 當中獨立的資源庫 (repository) 裡。也就是在 Github 當中建立一個參考的資源庫。完成以後,只要在該資源庫的主目錄底下,增加一個app.json檔案,就算是做好準備工作了。這個app.json有點像是配置檔,根據該檔案的內容,Deploy to Heroku 按鈕才知道佈署新的 APP 時,該注意或該添加哪些擴充元件。那該怎麼寫這個app.json呢:

{
  "name": "你-APP-的專案名稱",
  "description": "你-APP-的專案描述",
  "repository": "你-APP-的資源庫網址",
  "env": {
    "CHANNEL_ACCESS_TOKEN": "your channel access token",
    "CHANNEL_SECRET": "your channel secret",
    "YOUR_HEROKU_APP_NAME": "your Heroku App name", 
    "NOTIFY_CLIENT_ID": "your Notify client ID", 
    "NOTIFY_CLIENT_SECRET": "your Notify client secret"
  },
  "addons": ["heroku-postgresql:hobby-dev"],
  "success_url": "/"
}
  • 第二行:"name": "你-APP-的專案名稱",
    我們 APP 的專案的名稱,最多 30 字。可填可不填。

  • 第三行:"description": "你-APP-的專案描述",
    用來詳細解釋我們這個 APP 的用途,或是任何你想填入的話。可填可不填。

  • 第四行:"repository": "你-APP-的資源庫網址",
    註記我們 APP 的專案是放在哪一個資源庫當中。可填可不填。

  • 第五行:"env": {
    這個就重要了。這代表建立我們的 APP,需要用到的環境變數。記得把所有要用到的環境變數名稱放進來,這樣可以簡化整個佈署的流程。以我們目前的雙色打光 LINE BOT 來說,需要用到的環境變數就包括用來驗證 LINE BOT 身分的CHANNEL_ACCESS_TOKENCHANNEL_SECRET,用來驗證 LINE Notify 身分的NOTIFY_CLIENT_IDNOTIFY_CLIENT_SECRET,以及方便我們路由做處理的YOUR_HEROKU_APP_NAME

  • 第十二行:"addons": ["heroku-postgresql:hobby-dev"],
    這個也很重要,表示我們這個 APP 需要用到哪些擴充元件。記得把所有要用到的擴充元件都放進來,方法是用陣列 (Array) 的方式把所有需要的擴充元件列下來。以我們目前的雙色打光 LINE BOT 來說,只需要用到 Heroku Postgres,那陣列當中就放進這個元素,並且用:hooby-dev表示我們要用的方案名稱。

  • 第十三行:"success_url": "/"
    當透過 Deploy to Heroku 按鈕成功發布這個 APP 之後,會被重新導向的網址。可填可不填。

有沒有發現很多都是可填可不填呢?不過記得要把envaddons這兩個項目寫對。關於app.json更詳細的說明,可以參考 Heroku 的官方文件

都準備好了以後,就可以在存放我們 APP 詳細內容的 Github 資源庫當中,在README.md這個說明文件裡添加 Deploy to Heroku 按鈕了。使用方式如下:

  • README.md
# 詳細表示法
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/你-Github-帳號/你-Github-資源庫)

# 簡易表示法
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)

詳細表示法跟簡易表示法的差別,在於詳細表示法多了?template=https://github.com/你-Github-帳號/你-Github-資源庫這個參數,因此這樣子的按鈕可以在 Github 資源庫的README.md以外的地方使用。而簡易表示法,因為沒有特別指明是參照哪一個資源庫,所以只能在 Github 資源庫的README.md裡面使用。其他更詳細的資料,可以參考 Heroku 的官方文件

使用 Deploy to Heroku 快速按鈕

因為我們這個 APP 跟 LINE BOT 以及 LINE Notify 有關,要真的能使用這個 APP,請一起準備好 LINE BOT 的CHANNEL_ACCESS_TOKENCHANNEL_SECRET,以及 LINE Notify 服務的NOTIFY_CLIENT_IDNOTIFY_CLIENT_SECRET。最後,當然,你要有一個 Heroku 帳號。都準備好了以後,就讓我們來試用看看吧!

Deploy

點擊 Deploy to Heroku 按鈕之後,會帶到Heroku 的產生新 APP 頁面上,如圖六

https://ithelp.ithome.com.tw/upload/images/20210117/20120178nRSRUIgyM9.png
圖六、Create new APP

接著就看到我們在app.json填寫的環境變數,這邊會引導使用者將環境變數一個一個填好,如圖七

https://ithelp.ithome.com.tw/upload/images/20210117/20120178vaUmQtT1rX.png
圖七、Config Vars

填完之後,按下 Deploy,就開始佈署新 APP 了,如圖八

https://ithelp.ithome.com.tw/upload/images/20210117/20120178dXCdzRA3h9.png
圖八、Build APP

等一陣子之後,看到佈署成功的畫面,如圖九

https://ithelp.ithome.com.tw/upload/images/20210117/20120178VIkQujdtTi.png
圖九、Deploy to Heroku

記得要改 LINE BOT 的 Webhook URL (圖十),以及 LINE Notify 的 Callback URL (圖十一)。

https://ithelp.ithome.com.tw/upload/images/20210117/20120178o3mUzZMnQR.png
圖十、Webhook URL

https://ithelp.ithome.com.tw/upload/images/20210117/20120178wZAh8mk8pD.png
圖十一、Callback URL

最後讓我們一起試一試吧:

https://ithelp.ithome.com.tw/upload/images/20210117/20120178dlnKMg2zOS.jpg
圖十二、今晚我想來點雙色打光

https://ithelp.ithome.com.tw/upload/images/20210117/20120178D2CF2IOCTX.jpg
圖十三、Hello from Alma!

耶,《賴田捕手:追加篇》的全部內容就到這邊結束了,感謝大家的閱讀。如果有興趣,想要了解更多,比如說怎麼將相似的 LINE BOT 佈署到 AWS 上,歡迎參考《LINE Bot by Python 全攻略》。非常感謝 iT 邦幫忙跟博碩文化,沒有他們的幫忙,就不會有這本書。Happy Coding!


尚未有邦友留言

立即登入留言