AWS Cloud9でオウム返しLINE Botを作る


## はじめに

AWS Cloud9 上で`SAM Local`を使い、オウム返しするLINE Botを作るメモ

## 構成図

こんなの作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/fcf8b256-e677-df6d-abae-fa8b53835c8e.png)


## 手順の概要

- [LINE Developers] プロバイダー作成
- [LINE Developers] 作成したプロバイダーにチャネルを設定
- [LINE Developers] 作成したチャネルで2つの情報を取得
    - チャネルアクセストークン
    - チャネルシークレット
- [AWS] Cloud9環境を用意する（東京リージョン: ap-northeast-1）
- [AWS-Cloud9] Lambdaを作成する
    - Function name
    - Application name
- [AWS] Lambdaスクリプト更新
- [AWS] Lambdaに環境変数を設定
    - LINE_CHANNEL_SECRET       (チャネルシークレット)
    - LINE_CHANNEL_ACCESS_TOKEN (チャネルアクセストークン)
- [AWS] API GatewayのURL呼び出し を確認
- [LINE Developers] チャネルの設定更新
    - Webhook URL （← API GatewayのURL呼び出し）
    - Webhookの利用 無効->有効
    - 応答メッセージ 有効->無効

## 参考

- [LINE Messaging API × AWS Lambdaによる簡易応答システム #2-appx1: 署名検証](https://qrunch.net/@hanar/entries/7VV6aDdzeByXPamr?ref=qrunch)
    - 主にLambdaスクリプトを参考にさせて頂きました。

## 1.[LINE Developers] プロバイダー作成

[LINE Developers](https://developers.line.biz/ja/)でプロバイダーを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/029636e0-6db2-e4f4-7d57-6220ce26c550.png)

## 2.[LINE Developers] 作成したプロバイダーにチャネルを設定

先ほど作成したプロバイダーにチャネルを設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/e94779a0-b4c4-49e9-21b1-02591f1e7dac.png)

Messaging APIを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/295bcbc6-40c2-aeeb-21a0-dd7e033cc602.png)

## 3.[LINE Developers] 作成したチャネルで2つの情報を取得

チャネルシークレット、チャネルアクセストークンの情報を取得します。

チャネルシークレットの情報
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/6c23456e-1274-95a5-edea-c18cf606d8d4.png)


チャネルアクセストークンの情報は、発行ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/0580b189-fc25-3f2b-7d39-ebaa139658e6.png)



## 4.[AWS] Cloud9環境を用意する

AWSコンソールで、Cloud9を作ります。東京リージョン(ap-northeast-1)で作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/33624c67-74ca-b3df-6349-3862c5072c9f.png)

## 5.[AWS-Cloud9] Lambdaを作成する

SAM Localを使いLambdaを作成します。Cloud9の右側にある`λ` -> `λ+`とクリックし、新規作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/6cc62b0e-4eec-69c8-54ba-09fad8509519.png)

赤枠の欄に、任意の文字列を設定し`Next`ボタンをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/adcd5237-51c3-e8b9-e422-759e28cb5914.png)

Pythonを選択しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/5ea6b437-f8d0-583d-f7d0-c64cfd2608a0.png)

API Gatewayもまとめて作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/c7865747-757d-dfbc-a57d-2802e359540f.png)

デフォルト設定にしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/acb36d47-66ae-5875-86f3-5e89e34a92da.png)

`Finish`ボタンをクリックすると作成が開始されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/4e5638c7-3043-ae51-e504-e28e7a3a999e.png)

作成が完了すると、このような画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/10cccf3c-6e6e-04a8-87c5-23615dcbb0db.png)


## ※余談※ [AWS] 作成したAWSリソース確認

これまでで作成したAWSリソースは、CloudFormationスタックで確認できます。2つのスタックが作られていて、最初のスタック(aws-cluod9-xxx)は、Cloud9を作成したときのスタック、次のスタック(cloud9-app1)は、Lambdaを作ったときのスタックです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/9acbe17f-9f0c-5693-5c35-48d53b19aed6.png)

Lambdaを作ったときのスタックのリソースタブで作成されたAWSリソースを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/8e8673b0-7b08-4734-5e99-1b7045e03978.png)


## 6.[AWS] Lambdaスクリプト更新

今あるLambdaスクリプトを全て削除し、以下のスクリプトをコピーします。

```python:Lambdaスクリプト
# 環境変数
# LINE_CHANNEL_SECRET       チャネルシークレット
# LINE_CHANNEL_ACCESS_TOKEN チャネルアクセストークン

import json  
import os  
import logging  
import urllib.request
import base64  
import hashlib  
import hmac

# ログ出力の準備  
logger = logging.getLogger()  
logger.setLevel(logging.INFO)  

def lambda_handler(event, context):  
    # リクエスト内容をログ出力  
    logger.info(event)  

    ###
    # 環境変数からLINEチャネルシークレットを取得  
    channel_secret = os.environ['LINE_CHANNEL_SECRET']  
    # LINEチャネルシークレットを鍵として、HMAC-SHA256アルゴリズムを使用してリクエストボディのハッシュ値を算出  
    hash = hmac.new(channel_secret.encode('utf-8'), event['body'].encode('utf-8'), hashlib.sha256).digest()
    # ハッシュ値をBase64エンコード  
    signature = base64.b64encode(hash)

    # X-Line-Signatureを取得  
    xLineSignature = event['headers']['X-Line-Signature'].encode('utf-8')  
    # 署名の一致を検証し、不一致の場合はログ出力  
    if xLineSignature != signature:  
        logger.info('署名の不一致')  
        return {  
            'statusCode': 200,  
            'body': json.dumps('署名が正しくないみたいだよ。')  
        } 
    ###

    # 1. Webhookイベントの内容を抽出  
    body = json.loads(event['body'])  

    for event in body['events']:  
        # 応答用のメッセージオブジェクトのリストを定義  
        messages = []  
        # 2. Webhookイベントタイプがmessageであり、  
        if event['type'] == 'message':  
            # 3. メッセージタイプがtextの場合に、  
            if event['message']['type'] == 'text':  
                # 4. 受信したテキストの内容をメッセージオブジェクトとする  
                messages.append({  
                        'type': 'text',  
                        'text': event['message']['text']  
                    })  

                # 応答メッセージのリクエスト情報を定義  
                url = 'https://api.line.me/v2/bot/message/reply'  
                headers = {  
                    'Content-Type': 'application/json',  
                    # 環境変数からLINEチャネルアクセストークンを取得  
                    'Authorization': 'Bearer ' + os.environ['LINE_CHANNEL_ACCESS_TOKEN']  
                    }  
                data = {  
                    # 応答用トークンとメッセージオブジェクトを設定  
                    'replyToken': event['replyToken'],  
                    'messages': messages  
                }  
                request = urllib.request.Request(url, data = json.dumps(data).encode('utf-8'), method = 'POST', headers = headers)  
                with urllib.request.urlopen(request) as response:  
                    # レスポンス内容をログ出力  
                    logger.info(response.read().decode("utf-8"))  

    return {  
        'statusCode': 200,  
        'body': json.dumps('Hello from Lambda!')  
    }
```

lambdaスクリプトを更新したら、デプロイボタン(上向きの矢印アイコン)でデプロイします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/fa5c1c76-0024-77b7-1a11-4f2a78e06ab3.png)


## 7.[AWS] Lambdaに環境変数を設定

AWSコンソールでLambdaを開き、LINE Developersで作成したチャネルの `チャネルシークレット`と`チャネルアクセストークン`を環境変数として設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/e1572ec4-455e-7925-ca32-a3340c597264.png)

## 8.[AWS] API GatewayのURL呼び出しを確認

`API GatewayのURL呼び出し`を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/a16f84f0-a3f4-ba9a-54ed-6c41e6b6814c.png)

## 9.[LINE Developers] チャネルの設定更新

さきほど確認した `API GatewayのURL呼び出し` をLINE Developersで作成したチャネルのWebhook URLとして設定とWebhookの利用を有効にします。さらに、応答メッセージを無効にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/9cfcd572-bd66-6c03-e4da-8742dbee03f9.png)

これで『オウム返しのLINE Bot』の設定が完了です。

## [LINE Bot] 動作確認

作成したチャネルをQRコードから友達登録し、オウム返しするか確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/de6f56eb-96b3-6abc-5118-5b9e327c8b9d.png)

このように、オウム返しをすれば成功

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/dd1cb777-bb8a-caf7-9cf6-b0ab34080775.png" width="250" />

## ※補足※ [AWS] うまく動かないとき

CloudWatchのロググループでエラーが発生していないか確認するといいことあるかも

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/a9793676-7f59-a6d9-0d5a-cef4c8d99722.png)


![line.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/41c68f81-c35a-0d52-dd87-c318c11fe0f1.png)

今回は、これでおわり