標籤

python

Day13 - 試玩 python SDK

前言

以前從花落落的文件中找到一個方法,像是使用 push message 我都會笨笨的 call API 的方法去執行功能,殊不知 SDK 早就包好了,然後邊做邊罵自己為什麼要解這些 JSON (拍桌) 到了現在比較會看文件,才知道原來以前是自己雷自己 (捶心肝)...接下來就試玩幾個我覺得常會用到的 method 好了不廢話了,來簡單介紹一下一些常用的方法 😎

get_profile

回傳使用者的個人狀態,可以透過每次的 reply 去拿到使用者的資訊,一般使用的話都話把它存下來,等之後有介接其他服務時方便使用 userId 去判斷使用者。

profile = line_bot_api.get_profile(event['events'][0]['source']['userId'])

try:
    with Database() as db, db.connect() as conn, conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
        cur.execute(
            f"INSERT INTO users(id, name, picture) VALUES ('{id}', '{name}', '{picture}')")
except:
    pass # 這邊簡單處理相同 id 的問題(id我有設 Private key 哦!)
state = f"Hello 👉 `{profile.display_name}` 👈"
line_bot_api.reply_message(token, [
  TextSendMessage(text=state),
  ImageSendMessage(original_content_url=profile.picture_url, preview_image_url=profile.picture_url)
  ])

這邊搭配 ImageSendMessage 來幫忙發送圖片,他主要是透過 original_content_url 以及 preview_image_url 這兩個參數來幫忙轉成圖片

  • original 就是讓使用者看到的圖片
  • preview 在還沒載入時的樣子

push_message

既然都取到使用者的 id 了,下一步就是推訊息給使用者啦!

建立一個 GET 的路由在 /webhook 上,收到 query string 的 msg 的值並找到所有使用者的 id 並逐一推送訊息。

line_bot_api = LineBotApi(os.getenv('LINE_CHANNEL_TOKEN'))
handler = WebhookHandler(os.getenv('LINE_CHANNEL_SECRET_KEY'))

def get(self):
    msg = request.args.get('msg')
    with Database() as db, db.connect() as conn, conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
        cur.execute("SELECT * FROM users")
        fetch = cur.fetchall()
    for user in fetch:
        self.line_bot_api.push_message(
            user['id'], TextSendMessage(text=msg))
    return {'message': msg}, 200


Reply Mode

LocationSendMessage

這邊直接拿 SDK 的範例來用,若是有在做商家功能的機器人可以使用這個方式讓使用者接收到商家訊息~

self.line_bot_api.reply_message(token, LocationSendMessage(
    title='my location',
    address='Tokyo',
    latitude=35.65910807942215,
    longitude=139.70372892916203
))

StickerSendMessage

這個方法可以搭配使用,讓整個回應看起來跟真實吧!

不知道清單能用什麼可以參考

self.line_bot_api.reply_message(token, StickerSendMessage(package_id='1',sticker_id='1'))

Button Template

buttons_template_message = TemplateSendMessage(
    alt_text='Buttons template',
    template=ButtonsTemplate(
        thumbnail_image_url=f'{picture}.jpg',
        title='Menu',
        text='Please select',
        actions=[
            PostbackAction(
                label='postback',
                display_text='postback text',
                data='action=buy&itemid=1'
            ),
            MessageAction(
                label='message',
                text='message text'
            ),
            URIAction(
                label='uri',
                uri='http://example.com/'
            )
        ]
    )
)
self.line_bot_api.reply_message(token, buttons_template_message)

結論

簡單玩了幾個 API,大概就這幾個比較常搭配著使用,用過之後才知道其實有好多 API 我都還不是很清楚用法 🤣 而 LINE 最近更新了他們文件的功能,多了一個Try的按鈕提供使用者可以線上直接測 API,只是像是 push messages 這個就剛好沒有 😓,但至少有很多 API 不用通靈去測試,直接拿著 Token 在網頁上測試嚕! Doc Sample

Code is here

參考

LINE develop page

繼續閱讀

Day6 - 建立一個使用 Query string 來幫忙發送的 LINE Notify

繼上一篇我們已經可以讓使用者註冊 Notify 並將 token 放入資料庫,接著就帶各位使用 query string 的方式讓你的 Notify 可以送訊息給所有註冊過的 Notify。

首先我們進入我們已經建立過的 controller/notify_controller.py,會看到 class 下有我們之前建立的 post method,這時我們就在前面加入 get method,並加入以下的 code,讓這個 class 看起來比較有順序(潔癖)

from flask import request

class NotifyController(Resource):

    def get(self):
            msg = request.args.get('msg')
            with Database() as db, db.connect() as conn:
                with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
                    cur.execute(
                        f"SELECT token FROM notify")
                    fetch = cur.fetchall()
            for f in fetch:
                headers = {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Authorization': f"Bearer {f['token']}"
                }
                payload = {'message': msg}

                r = requests.post(
                    'https://notify-api.line.me/api/notify', data=payload, headers=headers)
            return {'result': 'ok'}, 200

    def post(self):
        ...

這次使用的套件比上次多 import 一個 flask 底下的 request (注意沒有 s 哦),這個主要是讓我們可以抓到網址問號後面接的參數,這邊我設定打 API 的人會打一個 msg 的參數。

接著就是使用進 DB 撈我們的 token 們,並用一個迴圈來跑

再來是設定 headers 以及 payload

headers 的部分參考 Notify 的文件,如下圖 我們需要設定 Content-Type 以及 Authorization,需要注意的是 Authorization 是使用 Bearer 格式(參考),他中間是有加空白鍵的,這邊很多朋友都會錯在這裡,千萬要記得檢查這邊!

接著是要送出去的東西是要用什麼送出,本篇只用 message 做範例,若需要使用到其他的功能,像是圖片、貼圖等等的可參考以下的圖片

接著我們就可以使用sls deploy來部署我們的程式啦

等等! 這邊有個需要注意的地方是,照著這次的範例使用的話會需要用 docker 來幫忙跑編譯,因為 psycopg2-binary 這個套件如果不是在 Linux 的環境下會需要透過 docker 幫忙編譯完再丟過去 這部分需要在serverless.yml下填入參數

custom:
  pythonRequirements:
    dockerizePip: true

如此一來當前環境若不是 Linux 的話他就會使用 docker 來幫忙,記得 docker 要開哦 🙏

當然最簡單的方法就是把它直接抓下來放在專案中,只是這樣就會比較髒一點,可以從這邊抓 -> 參考

這邊可以先在環境變數上加上SLS_DEBUG=* 接著在加上 sls deploy 後面加上--verbose可以看到 serverless 到底都在背地裡做了什麼事情 🤣

接著我們就使用 Postman 來幫我們送字串出去,參考下圖,當回傳 ok 就成功了 🎉 你的 Notify 應該要回你了~

結論

這邊帶大家做一個簡單的應用,一般來說我覺得放在 query string 讓其他 API 來呼叫的時候帶參數來就可以直接用是很方便的,不用再特地自己寫方法去呼叫 LINE Notify 來幫我們送,反正 AWS Lambda 有一百萬次的請求不怕 🤣

繼續閱讀

Day5 - 做一個與 LINE Notify 連動的服務

建立 LINE Notify 服務

第一步先到 LINE Notify 管力登入服務去建立一個服務 接著按下登入服務 把該填的填一填,要注意的是 Callback Url 這邊先暫時先打 localhost:5500,等頁面上傳後再回來改 domain


建立完成之後就到剛剛填的信箱去收信點下網址並啟動服務 這樣第一步就建立完成嚕!

動工

接著上次的透過上次的專案並使用 flask_restful 來改造成 Restful 格式的專案(後面開發會比較輕鬆) 因為我們會讓前端呼叫,CORS 設定成*比較輕鬆,若是有要設定跨域問題的在這邊設定哦 👏 加入套件到 requirements.txt

flask==1.0.2
Flask-RESTful==0.3.6
Flask-Cors==3.0.6
requests==2.22.0

透過使用 flask_restful 幫忙轉發並管理路由 將 api.py 改為以下的 code

from flask import Flask
from flask_restful import Api
from flask_cors import CORS
from controller.notify_controller import NotifyController

app = Flask(__name__)
CORS(app, resources={r"*": {"origins": "*", "supports_credentials": True}})
api = Api(app)

api.add_resource(NotifyController, '/notify')

if __name__ == '__main__':
    app.run(debug=True)

這邊我使用 Postgresql,並在專案底下建立一個 lib/ 的資料夾,裡頭放著名為 db.py (這裡還是要記得建立 init.py 哦!) 這邊只簡單建立 Context Manager 讓連線可以被我呼叫。 db.py:

import psycopg2
import psycopg2.extras

class Database():
    conns = []
    def __enter__(self):
        return self

    def connect(self):
        conn = psycopg2.connect(
            database=PG_DB,
            user=PG_USER,
            password=PG_PWD,
            host=PG_HOST,
            port=PG_PORT
        )
        self.conns.append(conn)
        return conn

    def __exit__(self, type, value, traceback):
        for conn in self.conns:
            conn.close()
        self.conns.clear()

新增一下資料夾名為 controller,建立 init.py & notify_controller.py,這邊搭配 with 做資源管理器, DB 部分則見黎一個 notify 表,然後一個 token 的欄位。

在這個 class 類別下方法只要輸入 def get/post/put/patch/delete 的函式就可以對應 CRUD 的功能了!

notify_controller.py code is:

from flask_restful import Resource, reqparse
import requests
import json
from lib.db import Database
import psycopg2.extras

class NotifyController(Resource):
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument('code', required=True, help='code can not be blank!')
        args = parser.parse_args()
        code = args['code']
        client = {'grant_type': 'authorization_code', 'code': code, 'redirect_uri': 'YOUR_REDIRECT_URI',
                  'client_id': 'YOUR_CLIENT_ID', 'client_secret': 'YOUR_CLIENT_SECRET'}
        r = requests.post(
            'https://notify-bot.line.me/oauth/token', data=client)
        req = json.loads(r.text)
        if req['status'] == 200:
            token = req['access_token']
            # Here is use PostgreSQL, you can change your love db
            with Database() as db:
                with db.connect() as conn:
                    with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
                        cur.execute(
                            f"INSERT INTO notify(token) VALUES ('{token}')")
            return {'access_token': req['access_token']}, 200
        else:
            return {'message': r.text}, 200

以下這三個要設定成你的當時輸入的字串

  • YOUR_REDIRECT_URI
  • YOUR_CLIENT_ID
  • YOUR_CLIENT_SECRET

若不清楚 Client_id & Client_secret 的話就回到 Notify 頁面並點選剛剛建立的服務就能看到 token 了

建立一個 views/ 的資料夾,並建立兩個檔案

  • notify_index.html

    這邊需要修改 client_id 以及 redirect_uri 這個檔案主要功能是讓使用者有個可以按按鈕觸發的地方,當然這只是個範例,實際上線可能就會直接 call API 導向 LINE Notify 頁面等等

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Link Notify</title>
  </head>
  <body>
    <button onclick="Auth();">點選這裡連結到LineNotify</button>

    <script>
      function Auth() {
        var URL = "https://notify-bot.line.me/oauth/authorize?";
        URL += "response_type=code";
        URL += "&client_id=YOUR_CLIENT_ID";
        URL += "&redirect_uri=YOUR_REDIRECT_URI";
        URL += "&scope=notify";
        URL += "&state=nostate";
        window.location.href = URL;
      }
    </script>
  </body>
</html>
  • notify_confirm.html

    修改 ajax 裡的 url,將網址換成之前部署的網址,若忘了可以在部署一次 sls deploy 當導回來的時候透過 ajax 來呼叫 serverless api 讓後端發 requests 給https://notify-bot.line.me/oauth/token來獲取access_token

因為這個路由有 CORS 的問題,若透過瀏覽器來的話會出錯,這邊則是讓後端去處理

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>message check</title>
  </head>
  <body>
    verify messate:
    <div class="message"></div>
  </body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>

  <script>
    var urlParams = new URLSearchParams(window.location.search);
    var notify_code = urlParams.get("code");
    var state = urlParams.get("state");
    $(function() {
      $.ajax({
        url: "https://SLS_URI.execute-api.us-east-2.amazonaws.com/dev/notify",
        type: "POST",
        dataType: "json",
        data: { code: notify_code },
        success: function(response) {
          console.log(response);
          $(".message").text(response["access_token"]);
        },
        error: function(xhr) {
          console.log(xhr);
          alert("Ajax request 發生錯誤");
        }
      });
    });
  </script>
</html>

接著就是測試程式碼是不是正常的,把這兩個檔案丟到 S3 上面


把 object url 複製到 LINE Notify 上的 callback url 欄位,這樣就可以實機測試了 🤣

接著按下 index page 的按鈕應該就會看到連動成功(進資料庫應該也會被 insert 一筆)

到了這邊如果沒問題真的是恭喜你,我踩了好多坑 Orz

參考

資源管理器 flask CORS CORS

繼續閱讀

Day2 - 安裝並使用 wsgi 以及 flask 建立第一個 Serverless 的專案

透過 npm 全域安裝 serverless 指令

npm install serverless -g

因為我是用 python3 在 AWS 上實作

  • --template可以選擇你想要的模板 (參考)
  • name service 的名稱
  • --path 專案路徑,若找不到資料夾時會自動新增
serverless create --template aws-python3 --name aws-wsgi-flask --path aws-wsgi-flask

加入套件

接著後面會需要使用套件,而 python 這邊會認得 requirements.txt 這個檔案,因此我們需要用 serverless 的 plugin 幫我們處理,讓專案在 deploy 上去之後會透過 requirements.txt 去安裝套件。

而接下來我們會需要一個介面幫我們處理一些麻煩事,這邊就選用 wsgi 來處理。詳細參考

sls plugin install -n serverless-python-requirements
sls plugin install -n serverless-wsgi

接著去看 serverless.yml 檔案最後出現下面這樣就成功安裝了。

plugins:
  - serverless-python-requirements
  - serverless-wsgi

然後新增一個 setup.cfg 並加入以下內容:

[install]
prefix=

由於 AWS Lambda 上都會將套件安裝在本地端(Lambda 上),因此在執行 pip install -t . -r requirements.txt 時會需要 setup.cfg。

接著選用 Flask 框架作我們開發的

echo "Flask==1.0.2" > requirements.txt
pip install -r requirements.txt -—user

serverless-python-requirements 同樣也支持 pipenv 哦,如果想完全擋掉的話可以參考

更改設定檔

置換以下內容至 serverless.yml,讓 WSGI 去處理路由。

service: aws-wsgi-flask
provider:
  name: aws
  runtime: python3.7
functions:
  api:
    handler: wsgi_handler.handler
    events:
      - http: ANY /
      - http: ANY {proxy+}
custom:
  wsgi:
    app: api.app
plugins:
  - serverless-python-requirements
  - serverless-wsgi

測試 wsgi

因為我們不是單純只用 python 架設,所以要將 handler.py 替換成 api.py,並加入以下內容

from flask import Flask
app = Flask(__name__)

@app.route("/", methods=['GET'])
def handler():
    return 'hello world'

if __name__ == '__main__':
    app.run(debug=True)

到這邊基礎設施都弄好了,但總不能部署才測試吧,所以這時候我們就出動

serverless wsgi serve

透過 wsgi 去幫忙起一個 local server 供使用者可以測試,而且每次儲存都重新整理一次哦!詳細用法請參考

接著用 postman 來測試一下

燈燈,hello world 出來了~

最後也是最重要的一個步驟『部署』 只是說部署前需要有 AWS 的 secret key & token

建立 AWS 存取金鑰

首先要先有個 AWS 帳號(廢話) ,如果沒有綁定信用卡記得去綁定才能繼續,接下來就可以去就可以去建立鑰匙囉! 如上圖,按下紅色框框的部分,接著按箭頭指的按鈕,就會建立一個我們要的金鑰囉! 如上圖所示,可以下載 key,AWS 不會幫忙保存 Secret key,若這視窗關掉的話 key 就不見了,所以使用者可以自行下載檔案保存,以免哪天要部署的時候找不到 key。 接著就將這兩個參數加入環境變數中:

export AWS_ACCESS_KEY_ID=<your-key-here>
export AWS_SECRET_ACCESS_KEY=<your-secret-key-here>

或是藉由 serverless 指令來幫忙加入參數:

serverless config credentials --provider aws --key <your-key-here> --secret <your-secret-key-here>

這步驟很重要哦,不然 AWS 不認識你就不給你上傳了 ?

最後就是下 serverlesss deploy進行部署

這邊 AWS 幫忙建立一個含有 SSL 的 domain,可以直接對這個網址下

endpoints: ANY - https://4omvn4z7re.execute-api.us-east-1.amazonaws.com/dev
  ANY - https://4omvn4z7re.execute-api.us-east-1.amazonaws.com/dev/{proxy+}

接著把網址丟到瀏覽器上來試試看 出現 hello world就成功囉!

參考

繼續閱讀