AWS(Lambda, API Gateway)でLINEの天気予報botを作ってみる

### 完成物
郵便番号or地名を入れると、そのエリアの天気予報が返ってくるLINE bot
![天気予報bot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567568/47c56762-c1b2-c36b-106b-f100aaf83cde.gif)

### 今回使うもの
- LINE Messaging API   
- Amazon API Gateway
- AWS Lambda（言語：node.js）
- OpenWeatherMap API

## LINE botの作成
まずはLINE botを準備しましょう。

①[このページ](https://account.line.biz/login?redirectUri=https%3A%2F%2Fdevelopers.line.biz%2Fconsole%2F)の「LINEアカウントでログイン」から、自分のLINEアカウントのアドレス、パスワードでログインします。

②プロバイダーを好きな名前で作成します。

③チャネル選択画面が出てくるので、「Messaging API」を選択します。

④必須項目を入力し「作成」を押します。
　チャネル名はLINE botの名前になります（この場合は「天気予報bot」）

⑤チャネル一覧が出てくるので、作成したチャネルを選択します。
　「Messaging API設定」タブ→画面下部のチャネルアクセストークンをコピーしておいてください。

この後の設定で必要になるので、この画面は開いたままにしておきましょう。

## OpenWeatherMap APIの準備
①[このページ](https://home.openweathermap.org/users/sign_up)からアカウントを作成します。
②[このページ](https://home.openweathermap.org/api_keys)にアクセスし、API Keyをコピーしておいてください。



## AWS Lambdaの作成
次はいよいよLambda関数の作成です。

①[このページ](https://ap-northeast-1.console.aws.amazon.com/lambda/home?region=ap-northeast-1#/functions)から、node.jsの新しいLambda関数を作成します。

②ローカルで、以下の通りにモジュールをインストールします。

```言語:
$ npm install @line/bot-sdk
```
③上記で作成されたnode_modulesフォルダと同じ階層にindex.jsを作成し、以下のコードを貼り付けて保存します。

```javascript:index.js
const line = require('@line/bot-sdk');
const https = require('https');

let event;
const research_mode = 0; //0:郵便番号検索、1:地名検索

exports.handler = (_event, _context) => {
    let url;
    event = _event;
    
    let userMessage = JSON.parse(event.body).events[0].message.text;
    
    if (typeof userMessage === "string") {
        
        if (research_mode === 0) {
            if (userMessage.length !== 8) {
                //ハイフンが入力されなかった場合はハイフンを入れる
                let _userMessage = userMessage.slice(0, 3) + "-" + userMessage.slice(3, 8); 
                userMessage = _userMessage;
            }
            url = 'https://api.openweathermap.org/data/2.5/forecast?&units=metric&zip=' + userMessage + ',jp&APPID=' + process.env.API_KEY;
        } else {
            url = 'https://api.openweathermap.org/data/2.5/forecast?&units=metric&q=' + userMessage + ',jp&APPID=' + process.env.API_KEY;
        }
        
        main(url);
    }
};

function main(url) {
    
    let result;
    
    https.get(url, (res) => {
        res.on('data', (chunk) => {
            result = JSON.parse(chunk);
        })
       .on('end', (res) => {
            let message;   
                    
            if (result.cod === "200") {
                let contents = []; //6つの天気情報を格納する配列
                
                let header = {
                    "type": "text",
                    "text": result.list[3].dt_txt.slice(0, 10), //日付
                    "size": "md",
                    "color": "#555555",
                    "flex": 0
                }
                contents.push(header);
            
                for (let i = 3; i < 10; i++) {
                    let _contents = {};
                    _contents = {
                            "type": "box",
                            "layout": "baseline",
                            "contents": [
                                {
                                    "type": "text",
                                    "text": result.list[i].dt_txt.slice(11, 16), //時刻
                                    "size": "sm",
                                    "color": "#555555",
                                    "flex": 0
                                },
                                {
                                    "type": "text",
                                    "text": return_japanese_weather(result.list[i].weather[0].main), //天気
                                    "size": "sm",
                                    "color": "#111111",
                                    "align": "end"
                                },
                                {
                                    "type": "icon",
                                    "url": "https://openweathermap.org/img/w/" + result.list[i].weather[0].icon + ".png", //アイコン画像
                                    "size": "xl"
                                },
                                {
                                    "type": "text",
                                    "text":  String(Math.round(result.list[i].main.temp)) + '℃',  //気温
                                    "size": "sm",
                                    "color": "#111111",
                                    "align": "end"
                                }
                            ]
                        }
                    contents.push(_contents);
                }
                
                message = {
                    "type": "flex",
                    "altText": result.city.name + 'の天気予報',
                    "contents": {
                        "type": "bubble",
                        "styles": {
                            "footer": {
                                "separator": true
                            }
                        },
                        "body": {
                            "type": "box",
                            "layout": "vertical",
                            "contents": [
                                {
                                    "type": "text",
                                    "text": "天気予報",
                                    "weight": "bold",
                                    "size": "xxl",
                                    "margin": "md"
                                },
                                {
                                    "type": "text",
                                    "text": result.city.name,　//地名
                                    "size": "md",
                                    "color": "#DC143C",
                                    "wrap": true
                                },  
                                {
                                    "type": "separator",
                                    "margin": "xxl"
                                },
                                {
                                    "type": "box",
                                    "layout": "vertical",
                                    "margin": "xxl",
                                    "spacing": "sm",
                                    "contents": contents
                                }
                            ]
                        }
                    }
                };
            } else {  //該当する天気情報を取得できなかった場合
                message = {
                    type: "text",
                    text: research_mode === 0 ? "該当する郵便番号の天気予報を取得できませんでした。" : "該当する地名の天気予報を取得できませんでした。"
                };
            }
            //メッセージ返信
            const client = new line.Client({
               channelAccessToken: process.env.ACCESSTOKEN
            });
            client.replyMessage(JSON.parse(event.body).events[0].replyToken, message)
            .catch((err) => { console.log(err) });
        });
    });
};

function return_japanese_weather(weather_eng) {
    let weather_jpn = {
        "Thunderstorm" : "雷雨",
        "Drizzle" : "霧雨",
        "Rain" : "雨",
        "Snow" : "雪",
        "Clear" : "晴れ",
        "Clouds" : "曇り",
        "Mist" : "霧",
        "Fog" : "濃霧",
        "Haze" : "靄",
        "Dust" : "砂塵",
        "Sand" : "砂嵐",
        "Squall" : "スコール",
        "Tornado" : "竜巻"
    }
    
    return weather_jpn[weather_eng] === "" ? weather_eng : weather_jpn[weather_eng]; //該当するものがない場合はそのまま返す
}
```
④index.jsとnode_modulesフォルダをzipファイルに圧縮します。
　index.zip
　　┗node_modules
　　┗index.js
この構造になるよう注意

⑤Lambdaの関数コードの「アクション」→「.zipファイルをアップロード」から、上記のzipファイルをアップロードします。

⑥環境変数を設定します。

- ACCESSTOKEN：LINE botで取得したチャネルアクセストークン   
- API_KEY：OpenWeatherMap APIで取得したAPI Key
![env_variable.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567568/8fab9e5b-8599-eb7a-7984-04fa01e27e64.png)

※コードを修正した場合は、必ず最後に「デプロイ」を押しましょう

## Amazon API Gatewayの作成
最後はAPI Gatewayの作成です。

①[このページ](https://ap-northeast-1.console.aws.amazon.com/apigateway/main/apis?region=ap-northeast-1)から新しいAPIを作成します。(APIタイプ：REST API)

②作成後に表示されるリソースの画面で、「アクション」→「メソッドの作成」から”/”リソースに「POST」メソッドを作成します。

③以下を設定し保存します。

- 統合タイプ：Lambda関数
- Lambda プロキシ統合の使用：チェック
- Lambda リージョン：ap-northeast-1
- Lambda関数：先ほど作ったLambda関数名

④Lambda 関数に権限を追加する　→　OKをクリック

⑤「アクション」→「APIのデプロイ」からAPIをデプロイします。ステージ名は何でもいいです。

⑥画面上部に表示されるURLをLINE botに登録します。
　先程開いたままの画面に戻り、「Webhook URL」に貼り付けます。

![webhook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567568/6e10a5fb-abc0-4a98-b26c-076b72875362.png)


これで完成です:relaxed:！

## まとめ
今回は郵便番号検索にしていますが、コードのresearch_modeを1に変えれば地名検索に切り替わります。
また、天気予報情報は今回3時間おきの情報ですが、現在の天気など様々種類がありますので[こちら](https://openweathermap.org/api)で見てみてください。

返ってくるFlexMessageも、[ここ](https://developers.line.biz/ja/docs/messaging-api/flex-message-elements/#component)を参考に文字の大きさなど色々変えることができます。