HerokuとDjango(Python)、LINE Messaging APIでBotを作ってみる
はじめに
前回、HerokuにDjangoアプリをデプロイまでを書きました。
目的はLINE Messaging APIでBotを作成することだったので、続いてLINE Botの作成手順をまとめておきます。
(ネットで見つけたサンプルを解析しながら作業をしました。先駆者のKosuke-Szk氏、itdadao氏、他皆様に感謝!)
また、LINE Messaging APIのアカウント作成・設定に関しては省略します。
この記事の実行環境は下記のとおりです。
- ホストOS:Windows7 x64
- 仮想化ソフト:VirtualBox 5.0.16
- ゲストOS:Ubuntu 16.04.1 amd64
- Python:2.7.12
Botアプリのベース作成
前回作成したDjangoアプリhellodjangoにhellobotという名前でアプリを追加します。
下記のコマンドを実行するとアプリの必要なファイルが自動生成されます。
(venv) yosuke@yosuke-vm:~/hellodjango$ python manage.py startapp hellobot (venv) yosuke@yosuke-vm:~/hellodjango$
生成されたファイルの一覧を見てみます。
(venv) yosuke@yosuke-vm:~/hellodjango$ cd hellobot/ (venv) yosuke@yosuke-vm:~/hellodjango/hellobot$ ls -l total 24 -rw-rw-r-- 1 yosuke yosuke 63 11月 2 14:46 admin.py -rw-rw-r-- 1 yosuke yosuke 132 11月 2 14:46 apps.py -rw-rw-r-- 1 yosuke yosuke 0 11月 2 14:46 __init__.py drwxrwxr-x 2 yosuke yosuke 4096 11月 2 14:46 migrations -rw-rw-r-- 1 yosuke yosuke 98 11月 2 14:46 models.py -rw-rw-r-- 1 yosuke yosuke 60 11月 2 14:46 tests.py -rw-rw-r-- 1 yosuke yosuke 63 11月 2 14:46 views.py (venv) yosuke@yosuke-vm:~/hellodjango/hellobot$
サンプルコードをみると、views.pyにcallbackという関数が追加されており、ここでLINEからの受信メッセージの解析と応答メッセージの処理を行っているようです。
また、Djangoのリファレンスにも、
「ビュー関数、あるいは単に ビュー とは、簡単にいえばウェブリクエストを引数 にとり、ウェブレスポンスを返す関数です。」
という記載があったので、views.pyはリクエストを受ける処理を追加する場所のようです。
ルーティング(GET/POSTリクエスト受信時の呼び出し先設定)
処理を書く場所はわかりましたが、リクエスト受信時にこの処理を呼び出すために呼び出し先を設定(ルーティング)する必要があります。
これは、hellodjangoディレクトリにあるurls.pyに対して追記することで可能なようです。
hellodjango/urls.pyだけでルーティングを完結させることもできるようですが、先ほど作成したhellobotにも別にurls.pyを作って、hellobotだけのルーティング設定を書くこともできるようだったので、この分けて書く方法を試してみました。
hellodjango/urls.py(追記)
"""hellodjango URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.10/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import include, url from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^hellobot/', include('hellobot.urls')), #この行を追加 ]
hellobot/urls.py(新規作成)
from django.conf.urls import include, url from . import views urlpatterns = [ url('^callback/', views.callback), ]
ここまで書いたら、ローカルサーバを起動(foreman start)します。
localhost:5000/callback/を表示してみます。
問題がなければ、gitリポジトリへcommit後、Herokuにpushします。
リクエスト受信時処理の追加
見つけたサンプル実装(echoback)から、処理を抜き出します。
views.py
#from django.shortcuts import render # ## Create your views here. # from django.conf import settings from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.views.decorators.csrf import csrf_exempt from linebot import LineBotApi, WebhookParser from linebot.exceptions import InvalidSignatureError, LineBotApiError from linebot.models import MessageEvent, TextSendMessage line_bot_api = LineBotApi(settings.LINE_CHANNEL_ACCESS_TOKEN) parser = WebhookParser(settings.LINE_CHANNEL_SECRET) @csrf_exempt def callback(request): if request.method == 'POST': signature = request.META['HTTP_X_LINE_SIGNATURE'] body = request.body.decode('utf-8') try: events = parser.parse(body, signature) except InvalidSignatureError: return HttpResponseForbidden() except LineBotApiError: return HttpResponseBadRequest() for event in events: if isinstance(event, MessageEvent): line_bot_api.reply_message( event.reply_token, TextSendMessage(text=event.message.text) ) return HttpResponse() else: return HttpResponseBadRequest()
requestを分解してeventsを取得し、イベント(event)の種類がMessageEventなら、返信を作成するというようなことをしているようです。
eventはmessageを持ち、このmessageが持つtextが実際にLINEアプリに表示される本文のようです。
CHANNEL_ACCESS_TOKENとLINE_CHANNEL_SECRET
LINEのSDKで通信を行うために必要なCHANNEL_ACCESS_TOKENとLINE_CHANNEL_SECRETを定義します。
サンプルコードでは、settings.pyにHerokuサーバから定義値を取得するための関数が記載されていましたが、
Herokuへの定義方法がわからなかったので、一先ずsettings.pyに即値代入する形で追記しました。
さて、最終的なgitリポジトリに対する変更は下記のようになります。
(venv) yosuke@yosuke-vm:~/hellodjango$ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: hellobot/__init__.py new file: hellobot/admin.py new file: hellobot/apps.py new file: hellobot/migrations/__init__.py new file: hellobot/models.py new file: hellobot/tests.py new file: hellobot/urls.py new file: hellobot/views.py modified: hellodjango/settings.py modified: hellodjango/urls.py modified: requirements.txt (venv) yosuke@yosuke-vm:~/hellodjango$
new file:となっているもので処理を追加したファイルとその内容は
・views.py:リクエスト受信時に実行する処理
・urls.py:リクエスト受信時のルーティング設定
です。
modifled:となっているファイルとその内容は
・settings.py:LINE_CHANNEL_ACCESS_TOKENとLINE_CHANNEL_SECRETの定義を追加
・urls.py:リクエスト受信内容のうち、botアプリ向けのルーティング追加
・requirements.txt:LINE SDKの使用を宣言
です。
以上の変更をpushした後、LINE Botに対してメッセージを送ってみます。
自分の書いた内容がそのまま返ってきました!
SDKサンプルの動作確認(メッセージタイプ)
LINE Messaging APIについて調べていると、メッセージタイプというものが存在することがわかりました。
また、このあたりでLINEがサンプルコードを公開していることと、サンプルはDjangoとは違うFlaskというフレームワークを使用していることを知りました…
SDKサンプルコードから「Confirm」「Buttons」を貼り付けます。
for event in events: if isinstance(event, MessageEvent): text = event.message.text if text == 'confirm': confirm_template = ConfirmTemplate(text='Do it?', actions=[ MessageTemplateAction(label='Yes', text='Yes!'), MessageTemplateAction(label='No', text='No!'), ]) template_message = TemplateSendMessage( alt_text='Confirm alt text', template=confirm_template) line_bot_api.reply_message( event.reply_token, template_message ) elif text == 'buttons': buttons_template = ButtonsTemplate( title='My buttons sample', text='Hello, my buttons', actions=[ URITemplateAction( label='Go to line.me', uri='https://line.me'), PostbackTemplateAction(label='ping', data='ping'), PostbackTemplateAction( label='ping with text', data='ping', text='ping'), MessageTemplateAction(label='Translate Rice', text='米') ]) template_message = TemplateSendMessage( alt_text='Buttons alt text', template=buttons_template) line_bot_api.reply_message(event.reply_token, template_message) else: line_bot_api.reply_message( event.reply_token, TextSendMessage(text=event.message.text) ) return HttpResponse()
foreman startで実行してみて動作していれば、gitリポジトリへコードの変更を反映し、Herokuへpushします。
LINE Botに対して「confirm」を送信すると、選択肢が表示されます。
Yesボタンを押すと、「Yes!」というメッセージを自動で送信し、Noボタンを押すと、「No!」とメッセージを自動送信しました。
これは、コードに記載のあったtextに対応しています。
次に、LINE Botに対して「buttons」と送信します。今度も選択肢が表示されます。
このうち、「ping」を押してみますが、特に変化はありません。
「ping with text」を押せば、「ping」とメッセージを自動送信します。
コード上、該当しそうな箇所を見てみると、「 ping」ボタンを押した際にdataを送信していそうだとわかります。
herokuのログを表示します。
yosuke@yosuke-vm:~/hellodjango/hellobot$ heroku logs --tail <略> 2016-11-06T13:37:09.926367+00:00 heroku[router]: at=info method=POST path="/hellobot/callback/" host=shrouded-mesa-25264.herokuapp.com request_id=6fbbb303-3cb9-42b8-8d5f-fc89301bbb97 fwd="203.104.146.154" dyno=web.1 connect=1ms service=223ms status=200 bytes=202 2016-11-06T13:37:11.151169+00:00 heroku[router]: at=info method=POST path="/hellobot/callback/" host=shrouded-mesa-25264.herokuapp.com request_id=3a246a48-be31-4245-b5f7-4f1b529c82f0 fwd="203.104.146.154" dyno=web.1 connect=1ms service=229ms status=200 bytes=202 2016-11-06T13:37:12.465064+00:00 heroku[router]: at=info method=POST path="/hellobot/callback/" host=shrouded-mesa-25264.herokuapp.com request_id=2ff2b34c-8bff-44b5-b80e-a3cec794e9be fwd="203.104.146.154" dyno=web.1 connect=1ms service=237ms status=200 bytes=202
「ping」ボタンを押してみます。
<略> 2016-11-06T13:37:09.926367+00:00 heroku[router]: at=info method=POST path="/hellobot/callback/" host=shrouded-mesa-25264.herokuapp.com request_id=6fbbb303-3cb9-42b8-8d5f-fc89301bbb97 fwd="203.104.146.154" dyno=web.1 connect=1ms service=223ms status=200 bytes=202 2016-11-06T13:37:11.151169+00:00 heroku[router]: at=info method=POST path="/hellobot/callback/" host=shrouded-mesa-25264.herokuapp.com request_id=3a246a48-be31-4245-b5f7-4f1b529c82f0 fwd="203.104.146.154" dyno=web.1 connect=1ms service=229ms status=200 bytes=202 2016-11-06T13:37:12.465064+00:00 heroku[router]: at=info method=POST path="/hellobot/callback/" host=shrouded-mesa-25264.herokuapp.com request_id=2ff2b34c-8bff-44b5-b80e-a3cec794e9be fwd="203.104.146.154" dyno=web.1 connect=1ms service=237ms status=200 bytes=202 2016-11-06T13:38:23.200287+00:00 heroku[router]: at=info method=POST path="/hellobot/callback/" host=shrouded-mesa-25264.herokuapp.com request_id=cbc249ac-d7ca-45d2-82aa-c9e0352651e7 fwd="203.104.146.154" dyno=web.1 connect=1ms service=217ms status=200 bytes=202
ログが増えたので、通信は行われているようです。
コード内部のdataが怪しいと思い、調べていると、pipのページにPostbackEventの階層構造が示されていました。
PostbackEvent type timestamp source: Source reply_token postback: Postback data
これを元に、pushbackのdataを取得できるか確認してみます。
取得eventを解析するfor文では、eventがMessageEventであれば、返信するような処理をしていました。
ここに取得したのがPostbackEventの場合の処理を追加します。
postbackのdataが'ping'であれば、「ping postback received!」というメッセージを表示させ、それ以外の場合はechobackと同様です。
for event in events: if isinstance(event, MessageEvent): <略> elif isinstance(event, PostbackEvent): data = event.postback.data if data == 'ping': line_bot_api.reply_message( event.reply_token, TextSendMessage(text='ping postback received!') ) else: line_bot_api.reply_message( event.reply_token, TextSendMessage(text=data) ) return HttpResponse()
結果、pushbackのdataを取得することができました!
さいごに
ネットのサンプルコードから、Botの作成方法とLINE Messaging APIの使い方がわかりました!
特にpostbackを使うことで、LINE Bot越しに色々なモノを制御するということが僕でもできそうだと感じました。
次はArduinoやRaspberry Piと連携させてみたいと思います!!
参考Webページ
DjangoでLINE Botのサンプル:ものすごく参考にした!
LINE Messaging APIとPythonを使ってChatbotを作ってみた - Qiita
echobackするBotのDjangoサンプルプロジェクト:ものすごく参考にした!
line_echobot: A django implementation of Line Bot-IT大道
HerokuとLINE Messaging APIの設定:ものすごく参考にした!
HerokuとGoでLINEの Messaging API環境を作ってみた - Qiita
Djangoのチュートリアル
はじめての Django アプリ作成、その 1 | Django documentation | Django
urlsの仕組み
Django urlsってなに? · workshop_tutorialJP
pipのLINE SDKリファレンス:EventとMessageの構造が参考になった!!
line-bot-sdk 1.0.2 : Python Package Index
メッセージタイプに関する簡単な紹介
LINE、 「Messaging API」正式提供開始-Botアプリケーションの開発が可能に - Feedmaticブログ
メッセージタイプの実装例:コードはPHPだけれど、confirmの理解に役立った!
PHP版の公式SDKを使ってLINE Messaging APIベースのBotを作ってみた - Qiita