作ったWebアプリをLINEbotにしてAWS lambdaで実装

# はじめに
[こちら](https://qiita.com/YuuN0808/items/422493dda9956eae9f61)の記事で紹介している自作Webアプリが今のままだと実用性がなさすぎるのでLINEbotにしてみました。

LINEbotを実装しようと思うとHerokuが定番？な感じですが去年AWSのソリューションアーキテクトアソシエイトを取得したこともあり、せっかくなのでAWS使おうと思いlambdaで実装。




以下参考文献になります。lambdaにデプロイするときとかの勉強させて頂きました。

https://qiita.com/konikoni428/items/fd1ab5993bc5526726bb

https://qiita.com/grapefruit1030/items/727bedb2867d90c7d1a6


# メインのコード
```python
import os
import sys
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    LineBotApiError, InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage
)
import urllib.parse
import json
import requests
import random
import logging


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

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)
    

line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)
    



def lambda_handler(event, context):
    
    if "x-line-signature" in event["headers"]:
        signature = event["headers"]["x-line-signature"]
    elif "X-Line-Signature" in event["headers"]:
        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"}
    
    
    def Budget_to_Code(budget):
        budget = int(budget)
        if budget <= 500:
            code = "B009"
        elif 500<budget<=1000:
            code = "B010"
        elif 1000<budget<=1500:
            code = "B011"
        elif 1500<budget<=2000:
            code = "B001"
        elif 2000<budget<=3000:
            code = "B002"
        elif 3000<budget<=4000:
            code = "B003"
        elif 4000<budget<=5000:
            code = "B008"
        elif 5000<budget<=7000:
            code = "B004"
        elif 7000<budget<=10000:
            code = "B005"
        elif 10000<budget<=15000:
            code = "B006"
        elif 15000<budget<=20000:
            code = "B012"
        elif 20000<budget<=30000:
            code = "B013"
        elif budget>=30000:
            code = "B014"
        return code
    
    def RailAPI(station, pref):
        station_url =urllib.parse.quote(station)
        pref_url =urllib.parse.quote(pref)
        api='http://express.heartrails.com/api/json?method=getStations&name={station_name}&prefecture={pref_name}'
        url=api.format(station_name=station_url, pref_name=pref_url)
        response=requests.get(url)
        result_list = json.loads(response.text)['response']['station']
        lng=result_list[0]['x']
        lat=result_list[0]['y']
        return lat, lng
    
    
    def HotpepperAPI(lat, lng, free_drink=0, code=None):
        api_key=""　#APIキー 
        if code:
            api = "http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?" \
                    "key={key}&lat={lat}&lng={lng}&budget={code}&free_drink={free_drink}&range=3&count=200&order=1&format=json"
        else:
            api = "http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?" \
                    "key={key}&lat={lat}&lng={lng}&free_drink={free_drink}&range=3&count=200&order=1&format=json"
        url=api.format(key=api_key,lat=lat, lng=lng, code=code, free_drink=free_drink)
        response = requests.get(url)
        result_list = json.loads(response.text)['results']['shop']
        shop_datas=[]
        for shop_data in result_list:
            shop_datas.append([shop_data["name"],shop_data["address"],shop_data["urls"]['pc'],shop_data["free_drink"], shop_data["budget"]['average'],shop_data["open"]])
        shop_datas = random.sample(shop_datas, 3)
        return shop_datas


    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
 
        #テキストメッセージを取得
        text = event.message.text.split()
        
        if len(text) == 4:
            pref = text[0]
            station = text[1]
            budget = text[2]
            drink = 1
            
            #予算のコード変換
            budget = Budget_to_Code(budget)
            #apiで検索
            lat, lng = RailAPI(station, pref)
            shop_datas = HotpepperAPI(lat, lng, free_drink=drink, code=budget)
            
        elif len(text) == 3:
            pref = text[0]
            station = text[1]
            budget= text[2]
            
            budget = Budget_to_Code(budget)
            lat, lng = RailAPI(station, pref)
            shop_datas = HotpepperAPI(lat, lng, code=budget)
            
        elif len(text) == 2:
            pref = text[0]
            station = text[1]
            
            lat, lng = RailAPI(station, pref)
            shop_datas = HotpepperAPI(lat, lng)
        else:
            #形式に合っていない場合はエラー
            with open("explanation.txt", "r") as f:
                text = f.read()
                
            line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text=text)
            )
            return
        
        shop1 = shop_datas[0]
        shop2 = shop_datas[1]
        shop3 = shop_datas[2]    
        
        #店舗情報を返信
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=f"{shop1[0]}\n住所:{shop1[1]}\nURL:{shop1[2]}\n{shop2[0]}\n住所:{shop2[1]}\nURL:{shop2[2]}\n{shop3[0]}\n住所:{shop3[1]}\nURL:{shop3[2]}\n")
        )
        
        
    try:
        handler.handle(body, signature)
    except LineBotApiError as e:
        logger.error("Got exception from LINE Messaging API: %s\n" % e.message)
        for m in e.error.details:
            logger.error("  %s: %s" % (m.property, m.message))
        return error_json
    except InvalidSignatureError:
        return error_json

    return ok_json

```

コードの中身はほぼ変えてません。flaskの要素をとっぱらってlambdaの関数の中に入れただけみたいな、、、

explanation.txtには形式外の入力に対する返答のコピペをいれてます。

LINEbotのシークレットキーとアクセスキーはAWSでlambdaに環境変数として渡してありますが、Hotpepperの方は変えてません(コードの変更めんどくさいから環境変数にした方がいい)


# AWSの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2496393/faff2c1a-1443-69d0-43e6-02167c3d93cf.png)


構成としてはこれだけです。めっちゃ単純。
１．Lambda(test_function)を作成する
２．Lambdaにコードとコードに必要なライブラリが入ったzipファイルをアップロードする
３．トリガーにAPI Gatewayを作成
４．作成されたエンドポイントURLをMessaging APIのWebhook URLに登録

これだけです！

具体的なやり方は色々な方が記事にされてるので省略します。


# 実際に作ったLINEbot
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2496393/37c3a292-c053-c019-2125-e21f9b4e9959.png)

形式外の入力時
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2496393/9574742e-f66c-1aee-5272-83c8c57febba.png)


入力の制限は多々ありますがとりあえず動いてよかったです。


# 感想
思ったより簡単に作れました。ただわかってはいましたが、AWSの資格をとるのと実際に実装するのは別物ですね、、、
もっと勉強したいと思います。

１つ疑問に思ったのが、今回API GatewayのAPIタイプはHTTPにしたのですが、この場合だとWebhookの特徴でもある双方向はできないってことかな？多分そうですよね。
アプリの仕様上HTTPでまったく問題ないので今回はこれでよかったですが、もっと理解を深めないとなと実感しました。

4月に応用情報技術者を受けるので一旦開発は休憩します。

落ち着いたら競馬予想ＡＩ作りたいなーーー