モチベ
-
高校とか大学の頃の写真が最近見ると面白い
-
けど、毎日見つけてくんのめんどくさい
-
毎日定時に写真を貼ってくれるBotが欲しい
-
AWSをいい加減触り始めたい
-
サーバ立てるんめんどくさい&挫折率高め
-
こんな記事を発見
-
Lambdaでサーバー構築が簡単に!!!
tl;dr
目指すものはBotを含むグループ・ルームにおいて、ある特定の単語が出た場合、S3に配置してある画像からランダムに画像を1枚取得し、Botが写真を返信すること
- LINEサーバからLambdaに発火
- RDSからURLを取得
- LINEサーバへURLを含んだPOSTを送る
- アプリにS3の画像が投稿される
てな流れですかねえ
また、LINEBotを作る上でネックになる、送信元識別子
の取得も合わせてできるように実装していきます
構成図(完成予定)
この記事ではその第一弾として、とりあえずLambdaを使って、疎通確認・色々弄ってみたいと思います
構成図(現状)
LINEのBotアカウント取得
まずはこちらで、事業者アカウントを登録しましょう。
職種などなど聞かれますが、だいたい適当でok(細かいところは覚えてない、、なんせアカウント作ったのは去年なので,,,)
筆者の職種は美容系にしました
そうすると、「アカウントリスト」画面のページ下部に「ビジネスアカウントを作成する」があるのでこちらへGO
すると、アカウントが提供するサービスの種類を聞かれますが、ここはMessagingAPIの下部の小さな「Developer Trialを始める」を選択しましょう
こうすると、アカウントのプランがDeveloperTrialになります。一般的なフリープランとの大きな違いは、
developper | フリー | |
---|---|---|
pushAPI | OK | NG |
追加可能友達数 | 50 | 制限なし |
趣味で作るぐらいなら、DeveloperTrialで十分ですね
アカウント名・画像・職種などを入力して、申し込みます
その後、この部分忘れがちですが、LINE@MANAGERに移動してください
すると、こんな画面に出くわすはずです(アカウント設定>MessagingAPI設定)
そのまま進んでいきましょう
これで、晴れて、Botアカウント作成が完了しました
ここで、LINEサーバとLambdaをつなげるための、CHANNEL_ACCESS_TOKENをメモしておきましょう
Lambda関数作成 & Lambda => LINEサーバへの接続設定
次に、Lambdaでサーバーレスなサーバーを作って、Lambda => LINEサーバへの接続設定も併せて行っていきたいと思います。
AWS Lambda を使用すれば、サーバーのプロビジョニングや管理なしでコードを実行できます。課金は実際に使用したコンピューティング時間に対してのみ発生し、コードが実行されていないときには料金も発生しません。Lambda を使用すれば、実質どのようなタイプのアプリケーションやバックエンドサービスでも管理を必要とせずに実行できます。コードさえアップロードすれば、高可用性を実現しながらコードを実行およびスケーリングするために必要なことは、すべて Lambda により行われます。コードは、他の AWS サービスから自動的にトリガーするよう設定することも、ウェブやモバイルアプリケーションから直接呼び出すよう設定することもできます。
- AWSのConsoleから
Lambda
を選択 関数の作成
- 設計図:
一から作成
- トリガー:
APIGateway
- API名:{任意},セキュリティ:
オープン
,デプロイされるステージ:prod
- 名前:{任意},説明:{任意}:ランタイム:
python3.6
今回は、python3.6
を使います(筆者が最近好んでるのでw)
実は、LINE のMessageinAPIを使って、特定のユーザー・グループ・ルームに対して何かしようとすると、送信先識別子というのがつきまとってきます。これは、
Webhookイベントの送信元ユーザ、グループ、及びトークルームの識別子を、送信先識別子として指定できます。
LINEアプリで使用されているLINE IDは指定できません。
とのことらしいです。botを開発しているユーザーの自分の送信先識別子はLINEDevelopersに書いてあるにしても、他のユーザー・グループ・ルームの識別子は全くわかりません。
なので、まず始めにLambdaで実装するコードは、Botに対して送信して来たグループ・ユーザーの送信先識別子を返すということにします
以下、私が作ったsampleコードです
import urllib.request, json, os, ast
def lambda_handler(event, context):
# LINEサーバへ叩く準備
url = "https://api.line.me/v2/bot/message/push"
method = "POST"
headers = {
'Authorization': os.getenv("CHANNEL_ACCESS_TOKEN"),
'Content-Type': 'application/json'
}
# 文字列をdict型へparse
apiParam = ast.literal_eval(event['body'])
source = apiParam['events'][0]['source']
sourceList = []
for key, value in source.items():
sourceList.append({"type": "text", "text": key + "#" + value})
obj = {
# ルームからきたrequestはルームへ返す。他は、発信者へ返す。
"to": source["roomId"] if "roomId" in source else source["userId"],
"messages": sourceList
}
json_data = json.dumps(obj).encode("utf-8")
# httpリクエストを準備してPOST
request = urllib.request.Request(url, data=json_data, method=method, headers=headers)
with urllib.request.urlopen(request) as response:
response_body = response.read().decode("utf-8")
return 0
ハンドラ:index.lambda_handler
ロール:カスタムロールの作成
=> ここで、ポリシーテンプレートにS3オブジェクトの読み取り専用アクセス権限
を加えてください。他は適当に作りましょう。
また、環境変数部分には以下を設定
CHANNEL_ACCESSS_TOKEN:{先ほどLINEの画面でメモしたCHANNEL_ACCESS_TOKEN}
これを貼って保存
しましょう
※ここで、テスト
をしても、実際のLINEサーバからのレスポンスではないので、正しくテストできません。
LINEサーバ => Lambdaへの接続設定
最後に、LINEサーバからLambdaへの接続設定を行なっていきます
先ほど作成したLambdaのトリガー項目から、(下図参照)
URLを拾って来ます、筆者はMethod:AMYのため、このURLをGETでもPOSTでも叩くと、Lambdaに書かれたコードが実行されるということです
では、この発火するためのURLをLINEサーバに設定します。
LINEDevelopersにログインし、Webhook URL
に設定します
この際に、必ず、ポート番号443をつけましょう。弾かれるみたいです(試してはいない)
これで、晴れてLINEBotの完成です
LINEDevelopersのQRコードから友達登録して、適当なルーム・1to1で何か送信してみましょう
こんな感じで表示されるはずです
こいつらが、送信先識別子です、この場合、私自身の識別子と、送信したルームの識別子が表示されています
tips1 外部ライブラリは使えないの?
Lambdaでは、コードを書いて乗っけるだけなので、初期状態では基本的なライブラリしか使えません。
なので、LambdaからAPIを叩くときには、urllib2が使えずに、urllibをごり押しで使いました
実はzip化したコードとライブラリ一式をアップロードすると、ライブラリがつかえますが、それは次回紹介します
tips2 なんでもう一回parseしてるの?
LINE API Referenceをざっと読むと、
Webhookのrequestは以下のように書いてありました。
{
"events": [
{
"replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
"type": "message",
"timestamp": 1462629479859,
"source": {
"type": "user",
"userId": "U206d25c2ea6bd87c17655609a1c37cb8"
},
"message": {
"id": "325708",
"type": "text",
"text": "Hello, world"
}
}
]
}
対して、Lambdaの引数は、以下
def lambda_handler(event, context):
引数event
の中にこいつらが含まれています。
{
~~~~~略~~~~~~~
"body": "{"events":[{"type":"message","replyToken":"cd8477b1e2844fb7ac535ba8cfb7e604","source":{"userId":"Ue49159d045ba254087865fb1c09ce0e7","type":"user"},"timestamp":1504100774524,"message":{"type":"text","id":"6623088374597","text":"fewgvlkwgw"}}]}"
~~~~~略~~~~~~~
}
なななんと、文字列で格納されているんですね、、、dict
型だと思って、どハマりした、、、、
なので、
apiParam = ast.literal_eval(event['body'])
というふうに、parseしてるんです
所感
- サーバレスだからどハマりする要素が少ない
- せっかくLINE developersアカウントでpushAPIが使えるのに使ってない
- pythonの無駄を削ぎ落とした感
-
||
がor
とか、phpでいうarray_key_exist()
がpythonではA in B
などから感じられる
-
次回予告
次回は
RDS&S3連動編ということで、「プリーズ」と送ったら、RDS(S3)の中からランダムな写真をresponseするものを作っていきます