AWS初心者がLambdaでLINE BOTを作る

LINE BOTでオウム返しのBOTを作ります。
正直AWSを触ったことが無いので、よく分からず困ったので同じように困る人が出ないように
記事にすることにしました。

#言語・ツール選定

私はRaspberry pi & PHPでLine botを作ったことが有る。
内容としては位置情報を渡すと近くのコンビニを教えてくれるものだ。
しかし、Raspberry piでLine botを作ると、常にRaspberry piを起動しておく必要があり電力的に優しくなく、
サーバの構築や認証鍵の設定が面倒だった。
そこでサーバレスのLine Botを作ろうと思いました。
私が選択したのはLambda+Node.js。
選択肢としてGAS(Google App Script)もあったけれど今後の拡張性を考えた時、
ES6が簡単に使えない(変換すれば使えるが面倒な)のが嫌だと思ったのでやめました。

#環境構築
**LINE Developers**
https://developers.line.me/ja/
↑LineBotを作るためにアカウント取得する
**【公式】クラウドならアマゾン ウェブ サービス （AWS）**
https://aws.amazon.com/jp/
↑AWSの登録する(日本アカウントだと駄目？)

おわり

#使用するAWS
初心者なのでLambdaだけで作れると思ってました。
すると、そういう訳じゃなくいくつかサービスを利用しないといけないことが分かりました。

**Lambda**
サーバレス、コンピューティングサービス。
サーバ構築しなくてもイベントやトリガーで実行出来るサービス。
使用できる言語はNode.js、Java、C#、Python。
無料枠は
月1,000,000リクエスト
秒400GB 月間400,000GB
有料枠は
1,000,000リクエストごとに0.20USD(約22.6円)
他のサービスは別料金

**API Gateway**
API の作成、配布、保守、監視、保護が出来る。
Lambdaの関数を動作させる(キックする)ために使う。
料金はデータ送出量で変わる、使用する地域ごとに料金が違う。
無料期間が終わったら有料。

**IAM** (これはサービスではなく仕組み)
アクセスを安全に制御するための仕組み。権限(role)の設定をする。
Lambdaでログを取ったりするときに設定が必要。

**CloudWatch Logs**
ログを取るために使う。
無料枠Amazon CloudWatch でのメトリックス 10 個、アラーム 10 個、API リクエスト 1,000,000 件
まぁ、サービスがうまく稼働したら止めると思うので有料枠は気にしない。

#API Gatewayの設定
+APIの作成からAPI名とエンドポイントタイプを決める。

エンドポイントとは？
ゲートウェイ VPC エンドポイント??

```
Amazon API Gateway で、REST API およびカスタムドメインの作成時に 2 種類の API エンドポイントから選択できるようになりました。
リージョンの API エンドポイントは、REST API をデプロイするのと同じ AWS リージョンからアクセスできる新しいタイプのエンドポイントです。
これにより、API リクエストが REST API と同じリージョンから発信される場合に、リクエストのレイテンシーを減らすことができます。
さらに、独自の Amazon CloudFront ディストリビューションとリージョンの API エンドポイントの関連付けを選択できるようになりました。
API エンドポイントの 2 つ目のタイプは、エッジ最適化 API です。
エッジ最適化 API は、API ゲートウェイによって作成および管理される CloudFront ディストリビューション経由でアクセスするエンドポイントです。
以前は、エッジ最適化 API は、API ゲートウェイを使用して API を作成する際のデフォルトオプションでした。
```

つまり、どーいうことだ？
何か、ドメイン関係というのは分かった、
エンドポイントとは？
https://qiita.com/NagaokaKenichi/items/6298eb8960570c7ad2e9

なるほど。
URIの設定のことね。
んで、エンドポイントタイプの違いって具体的に何？

Regional:リージョン（地域が一緒なら早い）
Edge Optimized:エッジ最適化（経由して最適化する）
って感じかな？
今回はAPI GatewayもLambdaも同じハイオワにしたので、リージョンで良さそう。

API 名を決めないと何故かエラーが出る模様。訳が分からない。泣いた。
http://kubotti.hatenablog.com/entry/2017/01/13/161022

リソースで、ポストが出来るように設定しLumbdaの関数が完成したらAPIのデプロイをする。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/173560/f9befbdb-16f7-e789-889d-549634432b73.png)

設定で、ARNの設定をする。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/173560/52b5c1c6-1333-2fde-8c73-e2a8153f1fb6.png)
Lumbdaへのアクセスできる設定とログが取れるようアクセスを許す設定した物を割り当てる。
あ、後クライアント証明書も作った。

#Lambdaの設定
![image.png](https://qiita-image-store.s3.amazonaws.com/0/173560/52766d1f-40bd-77cf-ca7a-7279006e5e3e.png)
実行ロールの設定でログ監視とAPIGatewayからのアクセスが可能なアカウントを設定する。

コードはこんな感じ

```js:index.js
'use strict';

const https = require('https');
const querystring = require('querystring');

exports.myHandler = (event, context, callback) => {
    console.log('EVENT:', event);
    var event_data = JSON.parse(event.body);
    console.log('EVENT:', JSON.stringify(event_data));
    const messageData = event_data.events && event_data.events[0];
    console.log("TEST:" + JSON.stringify(messageData.message.text));
    var id = messageData.source.userId;
    if(messageData.source.groupId != null && messageData.source.groupId.length > 0){ //グループからのメッセージ
        id = messageData.source.groupId;
    }
    
    if(!check(messageData.message.text)){
        callback(null, 'Success!');
        return;
    }
    var postData = JSON.stringify(
    {
        "messages": [{
            "type": "text",
            "text": getResponseMessage(messageData.message.text)
        }],
        "to": id
    });
        // @TODO KMSで管理
    var ChannelAccessToken = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; //チャンネル設定
    
    //リクエストヘッダ
    var options = {
        hostname: 'api.line.me',
        path: '/v2/bot/message/push',
        headers: {
            'Content-Type': 'application/json; charset=utf-8',
            'Content-Length': Buffer.byteLength(postData),
            'Authorization': 'Bearer ' + ChannelAccessToken
            },
        method: 'POST',
    };
    console.log(JSON.stringify(options));
    //APIリクエスト
    var req = https.request(options,  function(res){
        res.setEncoding('utf8');
        res.on('data', function (body) {
            console.log(body);
            context.succeed('handler complete');
        });
    }).on('error', function(e) {
        context.done('error', e);
        console.log(e);
    });
    
    req.on('error', function(e) {
        var message = "通知に失敗しました. LINEから次のエラーが返りました: " + e.message;
        console.error(message);
        context.fail(message);
    });
    
    req.write(postData);
    req.on('data', function (body) {
            console.log(body);
     });
    req.end();
    console.log("TEST:" + postData);
    callback(null, 'Success!');
};

var getResponseMessage = function(message) {
    message = message.toLowerCase();
    message = message.replace('@bot ', '');
    switch (message) {
        default:
            return message;
    }
};

var check = function(message) {
    message = message.toLowerCase();
    var pattern = '@bot ';
    return message.indexOf(pattern) === 0;
};
```

・↑ではセキュリティガバガバなので、Lineからのアクセスかどうか検証するためにChannel SecretのIDを見るプログラムと、ChannelAccessTokenは管理するものを入れたほうが良いと思います。
・replyでも良いんですが、処理に時間がかかると処理されないらしいので今後重い処理を入れることを考えてpushにしました。
(参照：https://developers.line.me/ja/docs/messaging-api/reference/)
・グループに突っ込んだ時に関係なく反応するのがうざかったので、`@bot`と頭につくと返すようにしました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/173560/9da3a8e4-bcf1-1590-de6d-4e8c3bfad371.png)
LINE DevelopersのChannelAccessTokenはここのやつをコピペ。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/173560/733e2827-30b6-69ed-4909-6aebd215ed89.png)
上のような形で登録する。(念のため443はつけたほうが良い)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/173560/4162563a-88f3-2165-77e3-bc04c02ee0eb.png)
↑のエラーが出ますが問題なく動きます。

#結果

![image.png](https://qiita-image-store.s3.amazonaws.com/0/173560/e700c7ff-a6e6-a1d0-e5aa-57e428b809af.png)
出来た。

#まとめ
AWSのいい勉強になった。
私のセキュリティ意識が低いことが分かった。
よくわからないところからAPIリクエストが飛んで来ていたので気をつけたい。
LineBotはちょっと前とはAPIの書き方が変わっていた＆単純にNode.jsに慣れていないというのもあって、ちょっと苦戦した。
今後は、飲み会幹事BOTなどにしていきたい。
たのしかった。

#参考
https://qiita.com/akihito_nagai/items/f284ef495da380f368cc
https://qiita.com/ykyk1218/items/232437bc92f13d02a8a2
https://qiita.com/daikiojm/items/c05882bedb82d7ed664e