【AWS】PythonによるLINE Bot構築

# 【AWS】PythonによるLINE Bot構築
　LINEは日本人口の70~80%が利用しており、生活に必要不可欠な通信インフラと言っても差し支えないほど、若い世代から高齢の世代まで日常的に利用している。その中でサービスを展開している企業やお店が、LINE公式アカウントを通じて、顧客とコミュニケーションを可能にするサービスがMessaging APIである。今回は、LINE Messaging APIを用いて、LINE公式アカウントとAWS上に作成したWebアプリと連携することで、LINE Botを作成する方法を紹介する。

## 1. LINE Developersにログインして、Messaging API用のチャネルを作成する
下記LINE Developersのサイトのコンソールにアクセスして、自身のLINEアカウントなどでログインする。

https://developers.line.biz/ja/

次に、コンソール画面からLINEアプリ作成のため、Messaging API用のチャネルを作成する。

1. Create a new Channelを押す
1. Messaging APIを選択する
1. Company or owner's country or regionでJapanを選択する
1. Channel nameを決め、Channel descriptionに適当に書き入れる
1. Category、Subcategoryで任意のものを選択して、Createを押す

作成したチャネルから、下記のAPI Keyを生成して、後程使うため控えておく。

 - Channel secret
 - Channel access token 

<img width="600" alt=LINEdevelopers1.PNG src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/19692ac9-c140-21ad-8604-99826f88df3c.png>

<img width="800" alt=LINEdevelopers12.PNG src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/bf228760-a58c-a157-72cd-335e069cc47e.png>

## 2. AWS Lambda関数作成
下記の手順に従ってAWS Lambda関数の作成を行う。

1. AWSマネジメントコンソールからLambdaを選択する
1. 関数の作成を押す
1. 一から作成を選択する
1. 関数名を適当な名前にして、ランタイムにPython 3.9を選択する
1. 実行ロールを適切なものを選択し、関数を作成を押す

<img width="800" alt=lambda1.PNG src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/c062272d-0677-f6a6-5023-da8d3140d2c9.png>

## 3. AWS API Gatewayでトリガーとエンドポイント作成
下記の手順に従ってAWS API Gatewayの作成を行う。

1. 先ほど作成したLambdaの関数のページへ行く
1. 関数の概要の中のトリガーを追加を押す
1. トリガーを選択ではAPI GateWay、Create a new APIを選択し、APIタイプは「HTTP」を選択する
1. セキュリティはOpenを選択する
1. 追加ボタンを押す
1. 設定タブのトリガーの中にあるAPI GateWayのAPIエンドポイントをコピーしておく

<img width="800" alt=APIgateway.PNG src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/bc6abe0e-c098-3cc0-9618-9933738d1add.png>

## 4. Line DevelopersでMessaging APIにWebhook URLを登録する

1. Messageing API設定の「Webhook URL」に先ほどコピーしたAPI GateWayのAPIエンドポイントを追記する
1. verifyを押して「Success」と表示されることを確認する

<img width="600" alt=Linedevelop3.PNG src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/943a5ed8-2069-3eb3-6b99-488bce95f223.png>


## 5. Lambdaの関数の環境設定をする

1. Lambdaの関数のページへ行く
1. 設定->環境変数で先ほどLINE Developersで取得したAPI Keyを登録する

```
LINE_CHANNEL_ACCESS_TOKEN = ****
LINE_CHANNEL_SECRET = ****
```

<img width="800" alt=lambda3.PNG src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/f700b8e7-d570-2719-54a5-7f06dcb8485e.png>

## 6. LINE botのPythonアプリを作成する
 下記のPythonコードを先ほど作成したLambdaの関数のランタイム設定の******.lambda_handlerの名前で保存する。※デフォルトではlambda_functionとなっているはず。

 次にPythonコードを保存したフォルダで必要なPython moduleをInstallする。Install後、PythonライブラリファイルとPythonコードをzip形式で適当なファイル名で圧縮する。

```
pip install -t . linebot
```


```python:lambda_function.py
import os
import sys
import logging
import requests
import urllib
import urllib.request
import json

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.models import (MessageEvent, TextMessage, TextSendMessage)
from linebot.models import RichMenu, RichMenuArea, RichMenuBounds, RichMenuSize,URIAction
from linebot.models import CameraAction, CameraRollAction
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
    SourceUser, SourceGroup, SourceRoom,
    TemplateSendMessage, ConfirmTemplate, MessageTemplateAction,
    ButtonsTemplate, ImageCarouselTemplate, ImageCarouselColumn, URITemplateAction,
    PostbackTemplateAction, DatetimePickerTemplateAction,
    CarouselTemplate, CarouselColumn, PostbackEvent,
    StickerMessage, StickerSendMessage, LocationMessage, LocationSendMessage, ImageSendMessage, VideoSendMessage,
    ImageMessage, VideoMessage, AudioMessage, FileMessage,
    UnfollowEvent, FollowEvent, JoinEvent, LeaveEvent, BeaconEvent, FlexSendMessage
)
from linebot.models.actions import PostbackAction

from linebot.exceptions import (LineBotApiError, InvalidSignatureError)

logger = logging.getLogger()
logger.setLevel(logging.ERROR)

#LINEBOTと接続するための記述
#環境変数からLINEBotのチャンネルアクセストークンとシークレットを読み込む
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)

#無いならエラー
if channel_secret is None:
    logger.error('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    logger.error('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

#apiとhandlerの生成（チャンネルアクセストークンとシークレットを渡す）
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

#Lambdaのメインの動作
def lambda_handler(event, context):

#認証用のx-line-signatureヘッダー
    signature = event["headers"]["x-line-signature"]
    body = event["body"]

#リターン値の設定
    ok_json = {"isBase64Encoded": False,
               "statusCode": 200,
               "headers": {},
               "body": ""}
    error_json = {"isBase64Encoded": False,
                  "statusCode": 500,
                  "headers": {},
                  "body": "Error"}


    #以下でWebhookから送られてきたイベントをどのように処理するかを記述する
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        send_message = event.message.text
        rep = talkapi(send_message)
        display_name = 'None'
        if isinstance(event.source, SourceUser):
            profile = line_bot_api.get_profile(event.source.user_id)
            user_id = event.source.user_id
            display_name = profile.display_name
        else: print("user profile can't not use")
        if send_message == "今日のドリンクメニュー" and isinstance(event.source, SourceUser):
            profile = line_bot_api.get_profile(event.source.user_id)
            tmpname = profile.display_name
            line_bot_api.reply_message(
                event.reply_token,
                (ImageSendMessage(original_content_url="https://maindepository.s3.ap-northeast-1.amazonaws.com/cafe_menu.png",
                                  preview_image_url="https://maindepository.s3.ap-northeast-1.amazonaws.com/cafe_menu.png")))
        elif send_message == "お問い合わせ" and isinstance(event.source, SourceUser):
            bubble_string = """
                {
                  "type": "bubble",
                  "header": {
                    "type": "box",
                    "layout": "vertical",
                    "contents": [
                      {
                        "type": "text",
                        "text": "お問い合わせ",
                        "weight": "bold",
                        "align": "center",
                        "color": "#ffffff"
                      },
                      {
                        "type": "text",
                        "text": "お困りの状況に該当するものをお選びください。",
                        "wrap": true,
                        "color": "#ffffff"
                      }
                    ],
                    "backgroundColor": "#00CC62"
                  },
                  "body": {
                    "type": "box",
                    "layout": "vertical",
                    "contents": [
                      {
                        "type": "box",
                        "layout": "vertical",
                        "contents": [
                          {
                            "type": "text",
                            "text": "何時から開いていますか？",
                            "align": "center",
                            "color": "#42659a"
                          }
                        ],
                        "action": {
                          "type": "postback",
                          "label": "question",
                          "data": "action=question&id=1",
                          "displayText": "何時から開いていますか？"
                        }
                      },
                      {
                        "type": "box",
                        "layout": "vertical",
                        "contents": [
                          {
                            "type": "text",
                            "text": "店舗はどこにありますか？",
                            "color": "#42659a",
                            "align": "center"
                          }
                        ],
                        "margin": "12px",
                        "action": {
                          "type": "postback",
                          "label": "question",
                          "data": "action=question&id=2",
                          "displayText": "店舗はどこにありますか？"
                        }
                      },
                      {
                        "type": "box",
                        "layout": "vertical",
                        "contents": [
                          {
                            "type": "text",
                            "text": "駐車場はありますか？",
                            "color": "#42659a",
                            "align": "center"
                          }
                        ],
                        "margin": "12px",
                        "action": {
                          "type": "postback",
                          "label": "question",
                          "data": "action=question&id=3",
                          "displayText": "駐車場はありますか？"
                        }
                      },
                      {
                        "type": "box",
                        "layout": "vertical",
                        "contents": [
                          {
                            "type": "text",
                            "text": "テイクアウトやデリバリーはありますか？",
                            "align": "center",
                            "color": "#42659a"
                          }
                        ],
                        "margin": "12px",
                        "action": {
                          "type": "postback",
                          "label": "question",
                          "data": "action=question&id=4",
                          "displayText": "テイクアウトやデリバリーはありますか？"
                        }
                      }
                    ]
                  }
                }
            """
            message = FlexSendMessage(alt_text="お問い合わせ", contents=json.loads(bubble_string))
            line_bot_api.reply_message(
                event.reply_token,
                message
            )
    
    @handler.add(PostbackEvent)
    def handle_postback(event):
        if event.postback.data == 'action=question&id=1':
            line_bot_api.reply_message(
                event.reply_token,
                (TextSendMessage(text='平日は8:30~17:00、祝休日は8:30~21:00まで開いています。'))
            )
        elif event.postback.data == 'action=question&id=2':
            line_bot_api.reply_message(
                event.reply_token,
                (TextSendMessage(text='下記リンクからGoogle Mapで確認できます。\nhttps://goo.gl/maps/m2KbyY6RA8QepLaa8'))
            )
        elif event.postback.data == 'action=question&id=3':
            line_bot_api.reply_message(
                event.reply_token,
                (TextSendMessage(text='はい、車6台が停めれる駐車場がございます。また、駐輪場もございます。'))
            )
        elif event.postback.data == 'action=question&id=4':
            line_bot_api.reply_message(
                event.reply_token,
                (TextSendMessage(text='申し訳ございません。デリバリーもテイクアウトも今はやっておりません'))
            )        

    @handler.add(FollowEvent)
    def handle_follow(event):
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text='登録して頂きありがとうございます！Cafe BORCELLEのLINE公式アカウントです。\n\n下記Menuから今日のドリンクメニューやクーポン、お問い合わせ、ホームページのリンクがご確認頂けます。\n\n自動会話botと連携しており、チャットで話しかけると返答が返ってきます。\n\nぜひご活用頂ければ幸いです。'))
    
#例外処理としての動作
    try:
        handler.handle(body, signature)
    except LineBotApiError as e:
        logger.error("Got exception from LINE Messaging API: %s\n" % e.handle_message)
        for m in e.error.details:
            logger.error("  %s: %s" % (m.property, m.handle_message))
        return error_json
    except InvalidSignatureError:
        return error_json

    return ok_json
```

## 7. LINE botのPythonコードをLambdaへアップロードする

1. 先ほど作成したLambdaの関数のページへ行く
1. 右側のアップロード元をクリックする
1. .zipファイルを選択する
1. 先ほど作成したPythonコードを選択する
1. 保存を押す

## 7. 動作確認する
LINE developersのWebhookl URLのverifyを押して、successが出るか試してみる。もしエラーが起きたら、正常にHTTP通信ができていないので、Lambdaの関数ページのモニタリング->ログでエラーを確認する。


## まとめ
Herokuが2022/11/28~有料になったため、別のPaaSへの移管として、今回AWSでLINE Botを作ってみた。公開アプリに関してはAWS LambdaとAPI Gatewayで簡単に作成できた。認証を加えたい場合などはIAMなどで、認証キーの作成などを行う必要があると思う。