[LINE bot] NowとHerokuで常時起動化させる

# この記事の内容
* LINEボットの完成形、常時稼働化のやり方
* そもそもモヤついていたところを自分なりにまとめてみた




## 完成品
（いじってたらなぜかアイコンが黒く怪しくなってしまった・・・どうやって戻すんだろう）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/fc3431f0-d077-665a-a2b3-ed1cfd354801.png)


猿よりは賢いアプリを作った。（ただし、人間ほど完璧な回答はできない。）

* ？を付けて聞くと文章で返します
* ？がない時はwikipediaのリンクを返します
* お猿さんが知らない単語のときは、、無視します。。


## QRコード(お友達になってあげてね！)

※このアプリはこの後紹介するHerokuを使ってます。
![smartmonky.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/9bf51304-89d1-1710-6aec-bcd961b1cd79.png)



# 作っていて思ったこと・・・
#### そもそも`express`,`ngrok`,`now`,`heroku`ってそれぞれなんだろう。普通のサーバーと何が違うのだろう。
この辺りがモヤつくポイントだと思うので、自分なりの理解でまとめます。

## さくらサーバー（phpを使う）
以前、レンタルサーバーにphpファイルを置いて、そこでwebhookを受けて処理をするように作り替えた。
[常に動くLINEBOTにお引っ越し（レンタルサーバ＋PHP編）](https://qiita.com/shima-07/items/fe7257efcec2f9a7f359)

こんなイメージ。
![レンタルサーバ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/4c176f9f-af94-cf81-af31-8b535b337123.png)


## Express
Node.jsで利用できるwebアプリケーションフレームワーク。

``` .js
const express = require('express');
const app = express();
```
requireでExpressを利用可能にし、expressのアプリケーションオブジェクトをappに格納する。
そうすることで、`app.post()`postリクエスト時の処理や、`app.listen()`サーバー起動などの処理が簡単に記述できる。

Expressでローカル環境にアプリケーションを構築することができるが、ローカル環境は基本的に外部に公開しないのでLINEbotからの外部とのやりとりができない。
![express.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/944bda56-7618-346c-ee1e-c454ee25e12a.png)



## ngrok
Expressを用いて作った手元のlocalhostにグローバルからアクセスできるようにするトンネリングツール。
設定等は[こちら](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d#3-ngrok%E3%81%A7%E3%83%88%E3%83%B3%E3%83%8D%E3%83%AA%E3%83%B3%E3%82%B0)を参照。
ngrokのサブドメインを取得して、その/webhookに対してアクセスすることでngrokを通して、

``` .js
app.post('/webhook', line.middleware(config), (req, res) => {....}
```
の処理を呼ぶことができる。
ただし、PCを閉じてしまうとこのngrokサーバも終了してしまい、常時動かすには不便。
![ngrok.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/6c54d679-c181-e499-95c6-381bc9c922bf.png)


## now
とてもデプロイが簡単なPaas。
サーバにアクセスされるとnow.jsonの設定の通り処理が実行される。
設定等は[こちら](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d#4-now%E3%81%A7%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4)を参照。
![now.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/0f40f278-ccb5-d934-cba2-7b1e00fafbfc.png)


## Heroku
Paas。Gitを使ってデプロイする必要がある。
今回公開しているアプリはHerokuを使っています。なので手順を下記に記載する。

初期化

``` 
npm init -y
```

必要なライブラリインストール

``` 
npm i body-parser express
```

`.gitignore `ファイルの作成

``` 
# Dependency directories
node_modules/

# Optional npm cache directory
.npm
```

1番上の階層に`Procfile`の作成

```
web: node index.js
```

#### git

初期化

```
git init
```

herokuアプリを追加する。アプリケーションネームはお好きな名前を決める。

```
heroku create アプリケーションネーム
```


add -> commit -> pushをする

```
git add --a
git commit -m "commit"
git push heroku master
```



# Nowで常時化
Nowを使って常時動くようにします。先述の通りNowはGitも不要で非常に簡単です。

## 準備
下記にあるようにngrokで動いていたモノが、nowでは正しい挙動をしない時があったので、[前回記事](https://qiita.com/shima-07/items/2322598ca5a40cfee47b)から少し修正をした。
[LINE BOTで天気を返すサンプルがngrokで動いてnowで動かない件](https://qiita.com/n0bisuke/items/fb19ed3cd0138135ae69)


## コード

``` linebot-now.js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;
// 追加
const axios = require('axios');

const config = {
    channelSecret: 'XXXXXXXXXXX',
    channelAccessToken: 'XXXXXXXXXXX'
};

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
    // LINE上で入力された
    console.log(req.body.events[0].message.text);
    

    //ここのif文はdeveloper consoleの"接続確認"用なので後で削除して問題ないです。
    if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
        res.send('Hello LINE BOT!(POST)');
        console.log('疎通確認用');
        return; 
    }


    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  let mes = ''
// console.log(event.message.text);
    if(event.message.text.indexOf('？') > -1){
        // ？を含んでいる場合にはwikiで検索したものを出して、含んでない場合はurlを返す
    var str = event.message.text;
    var result = str.split( '？' ).join( '' ); //？を取り除く処理
    mes = result + 'の説明：'; //wikiのbodyの前の一言

    await client.replyMessage(event.replyToken, {
      type: 'text',
      // text: event.message.text //実際に返信の言葉を入れる箇所
      text : mes 
    });
    // このreturnはポイント
    return getBody(event.source.userId,result,mes); //wiki APIで取得できたらプッシュメッセージ

  }else{
    var result = event.message.text;
    mes = result + 'のURL：'; //wikiのurlの前の一言
    await client.replyMessage(event.replyToken, {
      type: 'text',
      // text: event.message.text //実際に返信の言葉を入れる箇所
      text : mes 
    });
    // このreturnはポイント
    return getUrl(event.source.userId,result); //wiki APIで取得できたらプッシュメッセージ
  }
}

    const getBody = async (userId,word,message) => {

    const res = await axios.get('http://wikipedia.simpleapi.net/api?keyword='+ encodeURIComponent(word) + '&output=json');
    const item = res.data;
    console.log(item); 

    await client.pushMessage(userId, {
        type: 'text',
        text: item[0].body,
    });
}

    const getUrl = async (userId,word) => {
    const res = await axios.get('http://wikipedia.simpleapi.net/api?keyword='+ encodeURIComponent(word) + '&output=json');
    const item = res.data;
    // console.log(item); 
    await client.pushMessage(userId, {
        type: 'text',
        text: item[0].url,
    });
}

(process.env.NOW_REGION) ? module.exports = app : app.listen(PORT);
console.log(`Server running at ${PORT}`);

```

# Herokuで常時化

## Herokuとは
参考
[Herokuってなんなの？](https://qiita.com/okasir4444/items/b9b1e70fdbbbd5794f1b)

## ソースコード
``` linebot-heroku.js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;
// 追加
const axios = require('axios');

const config = {
    channelSecret: 'XXXXXXXX',
    channelAccessToken: 'XXXXXXXX'
};

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {

    //ここのif文はdeveloper consoleの"接続確認"用なので後で削除して問題ないです。
    if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
        res.send('Hello LINE BOT!(POST)');
        console.log('疎通確認用');
        return; 
    }

    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  let mes = ''
// console.log(event.message.text);
    if(event.message.text.indexOf('？') > -1){
        // ？を含んでいる場合にはwikiで検索したものを出して、含んでない場合はurlを返す
    var str = event.message.text;
    var result = str.split( '？' ).join( '' ); //？を取り除く処理
    mes = result + 'の説明：'; //wikiのbodyの前の一言
    getBody(event.source.userId,result); //wiki APIで取得できたらプッシュメッセージ

  }else{
    var result = event.message.text;
    mes = result + 'のURL：'; //wikiのurlの前の一言
    getUrl(event.source.userId,result); //wiki APIで取得できたらプッシュメッセージ
  }

  return client.replyMessage(event.replyToken, {
    type: 'text',
    text : mes 
});
}

const getBody = async (userId,word) => {
    const res = await axios.get('http://wikipedia.simpleapi.net/api?keyword='+ encodeURIComponent(word) + '&output=json');
    const item = res.data;
    // console.log(item); 

    await client.pushMessage(userId, {
        type: 'text',
        text: item[0].body,
    });
}
const getUrl = async (userId,word) => {
    const res = await axios.get('http://wikipedia.simpleapi.net/api?keyword='+ encodeURIComponent(word) + '&output=json');
    const item = res.data;
    // console.log(item); 
    await client.pushMessage(userId, {
        type: 'text',
        text: item[0].url,
    });
}

app.listen(process.env.PORT || 8080);
// console.log(`Server running at ${PORT}`);

```

# どんどん試そう
レンタルサーバを除いて、無料なのでお好きややり方でどんどん試していきましょう！