２時間でできるサーバーレスLINEBot(送信元識別子取得編)

## モチベ
- 高校とか大学の頃の写真が最近見ると面白い
- けど、毎日見つけてくんのめんどくさい
 - `毎日定時に写真を貼ってくれるBotが欲しい`

- AWSをいい加減触り始めたい
- サーバ立てるんめんどくさい&挫折率高め
 - [こんな記事](http://qiita.com/kooohei/items/650c331f95f83072f4d6)を発見
 - Lambdaでサーバー構築が簡単に！！！

## tl;dr
目指すものは**Botを含むグループ・ルームにおいて、ある特定の単語が出た場合、S3に配置してある画像からランダムに画像を１枚取得し、Botが写真を返信する**こと

1. LINEサーバからLambdaに発火
2. RDSからURLを取得
3. LINEサーバへURLを含んだPOSTを送る
4. アプリにS3の画像が投稿される

てな流れですかねえ

また、LINEBotを作る上でネックになる、`送信元識別子`の取得も合わせてできるように実装していきます

### 構成図（完成予定）
![Untitled Diagram.png](https://qiita-image-store.s3.amazonaws.com/0/158393/c9f79358-b5ae-8faf-ed84-cc8136be9185.png)


この記事ではその第一弾として、とりあえずLambdaを使って、疎通確認・色々弄ってみたいと思います

### 構成図（現状）
![Untitled Diagram-2.png](https://qiita-image-store.s3.amazonaws.com/0/158393/1282b63e-5094-f877-98ef-3c8e9a66f770.png)



## LINEのBotアカウント取得

https://business.line.me/ja/

まずはこちらで、事業者アカウントを登録しましょう。

職種などなど聞かれますが、だいたい適当でok(細かいところは覚えてない、、なんせアカウント作ったのは去年なので,,,)
筆者の職種は美容系にしました

そうすると、「アカウントリスト」画面のページ下部に「ビジネスアカウントを作成する」があるのでこちらへGO
<img width="1047" alt="スクリーンショット 2017-08-31 0.37.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/81f30d73-36bb-2a62-934e-0d1e292e82b6.png">

すると、アカウントが提供するサービスの種類を聞かれますが、ここは**MessagingAPI**の下部の小さな「Developer Trialを始める」を選択しましょう

<img width="1003" alt="apapapap.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/c7a0cf7c-1b19-642e-4edf-5bd7d7a0ff92.png">

こうすると、アカウントのプランがDeveloperTrialになります。一般的なフリープランとの大きな違いは、

| |developper|フリー|
|:---|:----|:--|
|pushAPI|OK|NG|
|追加可能友達数|50|制限なし|

趣味で作るぐらいなら、DeveloperTrialで十分ですね

アカウント名・画像・職種などを入力して、申し込みます
その後、この部分忘れがちですが、**LINE@MANAGER**に移動してください

すると、こんな画面に出くわすはずです（アカウント設定>MessagingAPI設定）
<img width="1384" alt="スクリーンショット 2017-08-31 0.52.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/a7b89f74-a4ce-241e-7261-161e88dec7cb.png">

そのまま進んでいきましょう
これで、晴れて、Botアカウント作成が完了しました


ここで、LINEサーバとLambdaをつなげるための、**CHANNEL_ACCESS_TOKEN**をメモしておきましょう
<img width="1300" alt="スクリーンショット 2017-09-01 14.26.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/59fcfe51-601a-b1e6-4d8c-0be2fccb0161.png">



## Lambda関数作成 & Lambda => LINEサーバへの接続設定

次に、Lambdaでサーバーレスなサーバーを作って、Lambda => LINEサーバへの接続設定も併せて行っていきたいと思います。

>AWS Lambda を使用すれば、サーバーのプロビジョニングや管理なしでコードを実行できます。課金は実際に使用したコンピューティング時間に対してのみ発生し、コードが実行されていないときには料金も発生しません。Lambda を使用すれば、実質どのようなタイプのアプリケーションやバックエンドサービスでも管理を必要とせずに実行できます。コードさえアップロードすれば、高可用性を実現しながらコードを実行およびスケーリングするために必要なことは、すべて Lambda により行われます。コードは、他の AWS サービスから自動的にトリガーするよう設定することも、ウェブやモバイルアプリケーションから直接呼び出すよう設定することもできます。

1. AWSのConsoleから`Lambda`を選択
2. `関数の作成`
3. 設計図：`一から作成`
4. トリガー：`APIGateway`
5. API名：{任意},セキュリティ：`オープン`,デプロイされるステージ：`prod`
6. 名前：{任意},説明：{任意}：ランタイム：`python3.6`

今回は、`python3.6`を使います(筆者が最近好んでるのでw)

実は、LINE のMessageinAPIを使って、特定のユーザー・グループ・ルームに対して何かしようとすると、送信先識別子というのがつきまとってきます。これは、

>Webhookイベントの送信元ユーザ、グループ、及びトークルームの識別子を、送信先識別子として指定できます。
LINEアプリで使用されているLINE IDは指定できません。

とのことらしいです。botを開発しているユーザーの自分の送信先識別子はLINEDevelopersに書いてあるにしても、他のユーザー・グループ・ルームの識別子は全くわかりません。

なので、まず始めにLambdaで実装するコードは、**Botに対して送信して来たグループ・ユーザーの送信先識別子を返す**ということにします

以下、私が作ったsampleコードです

```py3

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のトリガー項目から、(下図参照)

<img width="1262" alt="スクリーンショット 2017-09-02 22.17.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/d3f789ba-5525-4bf7-6331-a6d13a5f89b7.png">

URLを拾って来ます、筆者はMethod:AMYのため、このURLをGETでもPOSTでも叩くと、Lambdaに書かれたコードが実行されるということです


では、この発火するためのURLをLINEサーバに設定します。


LINEDevelopersにログインし、`Webhook URL`に設定します

<img width="1397" alt="スクリーンショット 2017-09-02 22.22.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/8831f26e-4ef1-500b-b84f-a37f4a573d6d.png">


この際に、必ず、ポート番号443をつけましょう。弾かれるみたいです（試してはいない）

これで、晴れてLINEBotの完成です

LINEDevelopersのQRコードから友達登録して、適当なルーム・1to1で何か送信してみましょう
こんな感じで表示されるはずです
こいつらが、送信先識別子です、この場合、私自身の識別子と、送信したルームの識別子が表示されています

<img width="1076" alt="スクリーンショット 2017-09-02 22.56.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/35e3563b-4c23-cfc1-020a-ab1295529fa7.png">



## tips1 外部ライブラリは使えないの？

Lambdaでは、コードを書いて乗っけるだけなので、初期状態では基本的なライブラリしか使えません。
なので、LambdaからAPIを叩くときには、urllib2が使えずに、urllibをごり押しで使いました
実はzip化したコードとライブラリ一式をアップロードすると、ライブラリがつかえますが、それは次回紹介します

## tips2 なんでもう一回parseしてるの？

[LINE API Reference](https://devdocs.line.me/ja/)をざっと読むと、
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するものを作っていきます