LINEBotで、「バス時刻」を知らせてくれるアプリを作りました

#（１）アプリ作成の背景
・引っ越してから、通勤にあたりバスを使うようになった。
・バスの時刻表をiphoneで撮影していちいち”写真”から確認している。
・通勤ルートは、【自宅　↔︎ （バス）↔︎ 最寄駅　↔︎ （電車）↔︎ 会社の駅】で、特に帰りは最寄駅に着いたらすぐにバスに乗りたい。
・バスの時刻を確認するたびに行う次の動作が面倒。（iphoneで写真アプリ起動）→ （写真アプリから撮影したバス時刻を探す）
・もっと効率良くバスの時刻を知りたい。
・勉強中のpython、LINEBot等を使って、生活の役に立つものを作りたい。
・勉強と割り切ってはいるものの、人のマネをしたアプリ作成はそろそろ飽きてきた。
・以上が、今回のアプリ作成の背景です。
（注意）ソースは初心者なので汚いです（ご容赦ください）。
（注意）csvファイルから時刻表を取り込んで、ローカル環境でテストした後にHerokuにデプロイしています（もっと効率的なやり方があるかもしれません）

↓↓↓↓↓↓↓（完成イメージ）↓↓↓↓↓↓↓↓
”行き”とLINEBotに発信すると、バスのイメージと、自宅最寄りのバス停から駅までのバス時刻、加えてその次のバス時刻を返します。”帰り”と発信すると、最寄り駅から自宅最寄りのバス停へのバス時刻を返します。
<img width="337" alt="スクリーンショット 2020-03-29 14.10.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/4dad7b4e-01a1-7fbc-74d2-ed165c2d0f7e.png">


#（２）この記事で書かないことと、記事の構成
###①この記事で書かないこと
・LINEBotのチャンネル作成方法
・Herokuへのデプロイ方法詳細
###②記事の構成
（１）アプリ作成の背景
（２）この記事で書かないことと、記事の構成
（３）環境構築
（４）ローカル環境で動作確認
（５）Herokuを使いLINEBotに組み込む

#（３）環境構築
・Mac
・python3
・sqlite3
・Postgresql
・Heroku
・Flask

まず、デスクトップにディレクトリlinebot_jikokuhyouを作成して、ディレクトリ内に仮想環境を構築して起動します。

```.py
python3 -m venv .
source bin/activate
```

#（４）ローカル環境で動作確認
まずは、ローカル環境でテストします。
データベースはsqlを使います。
あらかじめ用意したcsvファイルをsqlに取り込み、Flaskでローカル上で正常にプログラムが動作するか検証します。
###①作業ディレクトリと、作業ファイルを準備する
以下のようにディレクトリとファイルを用意します。
iki.csvと、kaeri.csvは自分で用意したファイルを使います（後述）。
上記以外のファイルは空ファイルとして作成します。

 ```
linebot_jikokuhyou
├csv_kakou.py
├csv_to_sql.py
├local_main.py
├jikoku_main.py
├assets
│  ├database.py
│  ├models.py
│  ├__ini__.py
│  ├iki.csv（自分で用意したcsvファイル）
│  └kaeri.csv（自分で用意したcsvファイル）
│ 
└templates
   ├test.html
   └test2.html
```
####（補足説明）
・以下の時刻表をcsvファイルで用意しました（サンプルです）。
（↓↓↓↓↓↓自宅近くの停留所から最寄駅までのバス時刻表） 
<img width="149" alt="スクリーンショット 2020-03-28 23.24.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/63640c4d-60f0-fdbc-4e26-4720bd1dc2b5.png">
（↓↓↓↓↓↓最寄駅から自宅近くの停留所までのバス時刻表） 
<img width="147" alt="スクリーンショット 2020-03-28 23.24.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/7e9c4622-87fb-127d-7a20-ad021db00141.png">
###②jikoku.csvファイルを作成して、実行する
・自宅から最寄駅までの時刻表iki.csvと、最寄駅から自宅までの時刻表kaeri.csvを加工して、jikoku.csvを作成します。
・jikoku.csvを作成するためのファイルcsv_kakou.pyを作成します。
・以下を実行すると、iki.csvとkaeri.csvを加工してまとめたファイルjikoku.csvがassetsディレクトリ内にできます。
・まずは、csvの加工処理が終わりました。

```.py:csv_kakou.py
#iki.csvを加工する処理
list = []
with open('assets/iki.csv',encoding='utf-8')as f:
    #行ごとに読み込む処理
    for i in f:
        columns = i.rstrip()
        list.append(columns)
list2 = []
for i in list:
    columns2 = i.split(',')
    for ii in range(len(columns2)):
        if ii != 0:
            list2.append(columns2[0]+'時'+columns2[ii]+'分')
list2.pop(0)
num = 1
with open('assets/jikoku.csv','w',encoding='utf-8')as f:
    go_or_come = '行き'
    for time in list2:
        f.write(str(num) +','+time+','+str(go_or_come)+'\n')
        num+=1

#kaeri.csvを加工する処理
list = []
with open('assets/kaeri.csv',encoding='utf-8')as f:
    #行ごとに読み込む処理
    for i in f:
        columns = i.rstrip()
        list.append(columns)
list2 = []
for i in list:
    columns2 = i.split(',')
    for ii in range(len(columns2)):
        if ii != 0:
            list2.append(columns2[0]+'時'+columns2[ii]+'分')
list2.pop(0)
with open('assets/jikoku.csv','a',encoding='utf-8')as f:
    go_or_come = '帰り'
    for time in list2:
        f.write(str(num) +','+time+','+str(go_or_come)+'\n')
        num+=1
```

・↓↓↓↓↓↓↓ assetsディレクトリに作成されたjikoku.csvは以下の通りとなります（一部抜粋）。全部で64レコードとなりました。
<img width="202" alt="スクリーンショット 2020-03-28 23.58.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/6dfbd30c-88b9-3622-868e-1a97a023b60e.png">
<img width="197" alt="スクリーンショット 2020-03-29 0.06.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/f347b024-c21c-2d8a-01e4-c2269b67981f.png">
###③assetsディレクトリにdatabase.pyと、models.pyのファイルを作成する
assetsディレクトリにそれぞれを作成します。

```.py:database.py
#coding: utf-8


from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session,sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import datetime
import os

database_file = os.path.join(os.path.abspath(os.path.dirname(__file__)),'data.db')

engine = create_engine('sqlite:///' + database_file,convert_unicode=True,echo=True)
db_session = scoped_session(
                sessionmaker(
                    autocommit = False,
                    autoflush = False,
                    bind = engine
                )
            )
Base = declarative_base()
Base.query = db_session.query_property()

def init_db():
    #assetsフォルダのmodelsをインポート
    import assets.models
    Base.metadata.create_all(bind=engine)
```

```.py:models.py
#coding: utf-8

from sqlalchemy import Column,Integer,String,Boolean,DateTime,Date,Text
from assets.database import Base
from datetime import datetime as dt

#データベースのテーブル情報
class Data(Base):
    #テーブルnameの設定,dataというnameに設定
    __tablename__ = "data"
    #Column情報を設定、uniqueはFalseとする（同じ値でも認めるという意味）
    #主キーは行を検索する時に必要、通常は設定しておく
    id = Column(Integer,primary_key=True)
    #行き、帰り、どちらかなのかを判定
    go_or_come = Column(Text,unique=False)
    #主キーとは別にナンバリング
    num = Column(Integer,unique=False)
    #時刻表の時刻
    time = Column(Text,unique=False)
    #timestamp
    timestamp = Column(DateTime,unique=False)

    #初期化する
    def __init__(self,go_or_come=None,num=0,time=None,timestamp=None):
        self.go_or_come = go_or_come
        self.num = num
        self.time = time
        self.timestamp = timestamp
```
###④csv_to_sql.pyのファイルを作成する
・csvデータを読み込み、sqlデータに書き込むファイルcsv_to_sql.pyを作成します。

```.py:csv_to_sql.py
from assets.database import db_session
from assets.models import Data

#初期化処理
from assets.database import init_db
init_db()

#csvからsqlに書き込む処理
with open('assets/jikoku.csv',encoding='utf-8')as f:
    for i in f:
        columns = i.rstrip().split(',')
        num = int(columns[0])#numはmodels.pyでint型として定義しているのでint型にした
        time = columns[1]
        go_or_come = columns[2]
        row = Data(num=num,time=time,go_or_come=go_or_come)
        db_session.add(row)
        db_session.commit()
```

####（補足説明）
・init_db()で、sqlを初期化します。
・de_session.addした後に、db_session.commitすることで、sqlに書き込みます。
・sqlにちゃんと書き込まれたかを確認します。assetsディレクトリに移動して以下を入力し、sqliteモードとします。

```
sqlite3 data.db
```

sqliteで以下を入力すると、

```
select * from data;
```

以下が出力され、sqlにデータが書き込まれていることを確認できました。
<img width="290" alt="スクリーンショット 2020-03-29 1.49.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/6beafa3e-7fbb-032f-d314-ff47a7f46534.png">





###⑤jikoku.pyファイルを作成する
・sqlデータベースに保存されている時刻から、指定した時刻を取得するファイルです。
・引数に’行き'、または'帰り'が代入されると、現在時刻から直近のバス時刻と、次のバス時刻をsqlから抽出して返り値として返します。

```.py:jikoku.py
from assets.database import db_session
from assets.models import Data
import datetime


def jikoku_search(route):
    #sqlを読み込む
    data = db_session.query(Data.num,Data.time,Data.go_or_come,).all()

    #現在日時を取得（datetime型)
    date_todaytime = datetime.datetime.today()
    #上記をstr型に変換
    str_todaytime = date_todaytime.strftime('%Y年%m月%d日%H時%M分')
    #現在日時のうち、●年●月●日のみ取得(date型)
    date = datetime.date.today()
    #上記をstr型に変換
    str_date = date.strftime('%Y年%m月%d日')

    #変数を設定
    bustime = ''
    next_bustime = ''

    #routeは行きと帰りを分類
    route = route
    #sqlから直近のバスの出発時刻と、次のバスの出発時刻を抽出
    for i in data:
        str_sql = i[1]
        #sqrの時刻に、現在日時の●年●月●日を付け加えて”日時”にする
        str_sql_rr = str_date + str_sql
        #上記をdatetime型に変換
        date_sql_rr = datetime.datetime.strptime(str_sql_rr,'%Y年%m月%d日%H時%M分')
        #現在日時と比較して直近で出発するバス日時を取得
        if date_sql_rr > date_todaytime and i[2]== route:#go_or_comeがrouteと合致するなら以下を実行
            #直近のバスの出発日時と現在日時の差分を取得
            date_sabun = date_sql_rr-date_todaytime
            #datetimeの差分はtimedelta型となる。timedelta型はstrftimeでstr型にできないため、str()でstr型とした
            #timedelta型は、0:00:00となっており、かつ差分は時刻表から1時間以内のため、スライスで”分”を抽出
            if str(date_sabun)[0:1] == "0":
                bustime = '次のバスは、'+str_sql_rr+'に出発します。' + 'あと' + str(date_sabun)[2:4] + '分です。'
            else:
                bustime = 'その次のバスは、'+str_sql_rr+'に出発します。' + 'あと'+ str(date_sabun)[0:1] + '時間' + str(date_sabun)[2:4] + '分です。'

            #次のバスの出発日時のnumを取得
            next_num = i[0]
            #次のバスの出発時刻を取得（直近バスはあるが、次のバスが終電を超えてる場合の処理）
            try:
                _next_bustime = db_session.query(Data.num,Data.time,Data.go_or_come).all()[next_num].time
                #次のバスの出発時刻に現在日時の●年●月●日を付け加えて”日時”にする
                next_bustime = str_date + _next_bustime+'に出発します。'
            except:
                next_bustime="終電を超えています。"

            #バスの時刻を取得したらfor文を抜ける処理
            break

        #直近のバス、次のバスともに終電が終わっている場合の処理
        else:
            bustime="次のバスは終電を超えています。"
            next_bustime="その次のバスも終電を超えています。"


    return bustime,next_bustime
```
###⑥local_main.pyを作成する
・ローカル環境でjikoku.pyが正常に作動するか検証するために、local_main.pyを作成します。
・まず、test.htmlを表示するとともに、sqlデータベースを読み込みます。
・test.htmlでは’行き’と’帰り’を指定するので、’行き’の場合と、’帰り’の場合の引数をそれぞれjikoku.pyのjikoku_searchメソッドに代入し、返り値をtest2.htmlに返します。

```.py:local_main.py
from flask import Flask,request,render_template
from assets.database import db_session
from assets.models import Data
import jikoku_main as jm

app = Flask(__name__)

@app.route('/')
def test():
    #sqlから読み込む
    data = db_session.query(Data.num,Data.time,Data.go_or_come,).all()
    return render_template('test.html',data=data)

@app.route('/iki')
def test2():
    result1,result2 = jm.jikoku_search('行き')
    return render_template('test2.html',bustime=result1,next_bustime=result2)

@app.route('/kaeri')
def test3():
    result1,result2 = jm.jikoku_search('帰り')
    return render_template('test2.html',bustime=result1,next_bustime=result2)

if __name__ == '__main__':
    app.run(debug=True)
```
###⑦templatesディレクトリにtest.htmlと、test2.htmlを作成する
templatesディレクトリに、以下の通り、それぞれを作成する。

```.html:test.html
<!DOCTYPE html>
<html lang='ja'>
  <head>
      <meta charset='utf-8'>
      <title>Jikokuhyou</title>
      <style>body{padding:10px;}</style>
  </head>


  <body>
    <form action='/iki' method='get'>
      <button type='submit'>行き</button>
    </form>
    <form action='/kaeri' method='get'>
      <button type='submit'>帰り</button>
    </form>
  </body>

</html>
```

```.html:test2.html
<!DOCTYPE html>
<html lang='ja'>
  <head>
      <meta charset='utf-8'>
      <title>Jikokuhyou</title>
      <style>body{padding:10px;}</style>
  </head>


  <body>
    {{'直近のバスは、'+bustime}} <br>{{'次のバスは、'+next_bustime}}
    <form action='/' method='get'>
      <button type='submit'>戻る</button>
    </form>
  </body>
</html>
```

###⑧assetsディレクトリ内に\__init__ファイルを作成する
・\__init__は、app.pyからdatabase.pyやmodels.pyをモジュールとして読み込むために必要なファイル。中身は何も記述しません。

```.py:__init__.py
```
###⑨ローカル環境で検証する
・最初に、csv_to_sql.pyを実行して、データベースの初期化と、csvからsqlへの読み込み、data.dbを作成します。
・次に、local_main.pyを実行してFlaskを起動し、ブラウザで確認します。
・↓↓↓↓↓↓↓（ブラウザ確認）”行き”と”帰り”のいずれかのボタンを押します。
<img width="52" alt="スクリーンショット 2020-03-29 0.46.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/608fca33-e29c-efba-03de-6b782969f5bd.png">
・↓↓↓↓↓↓↓ （ブラウザ確認）ちゃんと表示されました（現在時刻は2時13分）
<img width="569" alt="スクリーンショット 2020-03-29 2.11.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/514bb104-91c0-770b-18f8-f4c1a4280b4c.png">

###ここまでで、ローカル環境でバス時刻表をちゃんと抽出できるようになりました。

#（５）Herokuを使いLINEBotに組み込む
・ローカル環境で検証できたので、いよいよHerokuを使いLINEBot化します。
###①作業ディレクトリと、作業ファイルを準備する
・以下のようにディレクトリとファイルを追加します。
・参考までに上記までで作成されたdata.dbと、jikoku.csvも追記しておきます。

```
linebot_jikokuhyou
├csv_kakou.py
├csv_to_sql.py
├local_main.py
├jikoku_main.py
├main.py（追加）
├requirments.txt（追加）
├runtime.txt（追加）
├Procfile（追加）
├assets
│  ├database.py
│  ├models.py
│  ├data.db（これまでで作成されたファイル）
│  ├__ini__.py
│  ├jikoku.csv（これまでで作成されたファイル）
│  ├iki.csv
│  └kaeri.csv
│ 
└templates
   ├test.html
   └test2.html
```
###②main.pyを作成する
・まず、line-sdkを使って、バス時刻を通知するファイルmain.pyを作成します。
・jikoku_mainモジュールをインポートして、jikoku_mainのjikoku_searchメソッドを使います。

```.py:main.py
from flask import Flask, request, abort
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,LocationMessage,ImageSendMessage
)
import os
import jikoku_main as jm


app = Flask(__name__)

YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]
line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)

@app.route("/")
def hello_world():
    return "hello world!"


@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)
    return 'OK'



@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    if '行き' in event.message.text:
        result1,result2 = jm.jikoku_search('行き')
        line_bot_api.reply_message(
            event.reply_token,
            [
            ImageSendMessage(original_content_url='https://www.photolibrary.jp/mhd5/img237/450-2012011014412960119.jpg',
            preview_image_url='https://www.photolibrary.jp/mhd5/img237/450-2012011014412960119.jpg'),
            TextSendMessage(text=result1),
            TextSendMessage(text=result2)
            ]
        )


    if '帰り' in event.message.text:
        result1,result2 = jm.jikoku_search('帰り')
        line_bot_api.reply_message(
            event.reply_token,
            [
            ImageSendMessage
(original_content_url='https://www.photolibrary.jp/mhd5/img237/450-2012011014412960119.jpg',
            preview_image_url='https://www.photolibrary.jp/mhd5/img237/450-2012011014412960119.jpg'),
            TextSendMessage(text=result1),
            TextSendMessage(text=result2)
            ]
        )



if __name__ == "__main__":
    port = int(os.getenv("PORT", 5000))
    app.run(host="0.0.0.0", port=port)
```

####（補足説明）
・LINEBotの全体的な構造は[LINE BOTを使ってみよう（CallBackプログラム：受信編）](https://it-engineer-info.com/language/php/1192/)を参考にさせていただきました。
・LINEBotでテキストを複数送信する場合は、リストを使います。
・LINEBotで画像を送信する場合は、https型で、かつjpegに限定されます。以下のサイトを参考にさせていただきました。
[【Python入門】LineAPIを使って公式アカウントから画像と文章を送信する](https://meriek.com/programing/python/bitcoin3/)
[python line bot imagemap 画像送信](http://hungrykirby.hatenablog.com/entry/2017/11/19/145149)
[line-bot-sdk-pythonを使ってみた](https://keinumata.hatenablog.com/entry/2018/05/08/122348)
・バスのイラストは、[こちら](https://www.photolibrary.jp/img237/60119_1766741.html)を使わせていただきました。
###③database.pyを修正する
・Herokuのpostgresqlを使用するにあたりdatabase.pyを修正します。
・具体的には、environというHeroku上の環境変数を見に行ってDATABASE_URLというデータベースを取得する処理を記述します。
・environには接続先のURLがセットされます。また、orをつけることで、ローカル環境上はsqliteをデータベースとして参照することとしました。
・herokuに接続されている場合はpostgresqlのurlを参照して、接続されていない場合はsqlを参照に行くとなります。

```.py:database.py
#coding: utf-8

#database.py/sqliteなど、どのデータベースを使うのか初期設定を扱うファイル
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session,sessionmaker
from sqlalchemy.ext.declarative import declarative_base

import datetime
import os

#data_dbという名前で、database.pyのある場所に（os.path.dirname(__file__)）、絶対パスで（os.path.abspath）、data_dbを保存するpathを保存する。
database_file = os.path.join(os.path.abspath(os.path.dirname(__file__)),'data.db')

#データベースsqliteを使って（engin)、database_fileに保存されているdata_dbを使う、またechoで実行の際にsqliteを出す（echo=True)
#engine = create_engine('sqlite:///' + database_file,convert_unicode=True,echo=True)
engine = create_engine(os.environ.get('DATABASE_URL') or 'sqlite:///' + database_file,convert_unicode=True,echo=True)
db_session = scoped_session(
                sessionmaker(
                    autocommit = False,
                    autoflush = False,
                    bind = engine
                )
            )

#declarative_baseのインスタンス生成する
Base = declarative_base()
Base.query = db_session.query_property()


#データベースの初期化をする関数
def init_db():
    #assetsフォルダのmodelsをインポート
    import assets.models
    Base.metadata.create_all(bind=engine)

```

###④Herokuに環境変数を設定する
・まず、LINE Develpersへアクセスして登録し、チャンネルを新規作成します（説明は割愛します）。
・チャンネルを作成したら、LINE Developersに記載されている「アクセストークン文字列」と「チャンネルシークレットの文字列」をコピーします。
・Herokuにアクセスしてアプリケーションを新規作成します。
・gitを初期化して、Herokuと紐つけます。
・Herokuの環境変数に先ほどの「アクセストークン文字列」と「チャンネルシークレットの文字列」を設定します。
・例えば、heroku config:set YOUR_CHANNEL_ACCESS_TOKEN="チャネルアクセストークンの文字列" -a （アプリ名）とする

```
heroku config:set YOUR_CHANNEL_ACCESS_TOKEN="チャネルアクセストークンの文字列" -a （アプリ名）
heroku config:set YOUR_CHANNEL_SECRET="チャネルシークレットの文字列" -a （アプリ名）
```

heroku上に環境変数がちゃんとセットされたか確認します。

```
heroku config
```

###⑤その他必要なファイルを作成する
・Procfile、runtime.txt、requirements.txtを作成します。
・runtime.txtは、自身のpythonのバージョンを確認の上、作成します。

```.txt:runtime.txt
python-3.8.2
```

```Profile
web: python main.py
```

requirements.txtは以下をターミナルで入力して記述します。

```
pip freeze > requirements.txt
```

###⑤Herokuへpush、デプロイする
・以下の手順でデプロイします。
・commit名は、the-firstとしました。

```
git add .
git commit -m'the-first'
git push heroku master
Heroku open
```

↓↓↓↓↓ Heroku openしてブラウザで確認すると、"hello world！"が表示されれば、無事デプロイ完了。
<img width="94" alt="スクリーンショット 2020-03-29 1.34.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/671f4290-5b6a-b4d5-039e-799521166208.png">

###⑥postgresqlにデータベースを書き込む
・Herokuのデータベースpostgresqlを設定し、データベースにcsvデータを書き込みます。
・Herokuアプリのresourceから、postgresqlを設定します。
<img width="1302" alt="スクリーンショット 2020-03-29 2.49.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/14391294-b207-f0e2-2487-311dc5595a6a.png">

・bashコマンドを実行し、Heroku環境でコマンドが打てるようにします。
・その後、csv_to_sql.pyを実行します。
・こうすることで、postgresqlを初期化し、csvのデータをpostgresqlに書き込みます。

```
heroku run bash
```

```
python3 csv_to_sql.py
```

ちゃんと、書き込まれているか確認します。
以下のコマンドを入力します。

```
heroku pg:psql
```

```
テーブル内のデータを一覧するコマンド
select * from （テーブル名）;
```

以下が出力され、ちゃんとpostgresqlに書き込まれているのを確認できました。
<img width="486" alt="スクリーンショット 2020-03-29 1.45.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/a4c6a993-8e0b-4167-b62d-4ec308727347.png">

###⑦LINE DevelopersのWebhookを設定
LINE DevelopersのWebhookにURLを設定し、Webhookの利用をオンにします（詳細は割愛します）。
友達登録して、LINEBotを立ち上げれば完成です。
<img width="337" alt="スクリーンショット 2020-03-29 14.10.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/f01493c2-0eaa-6ead-e0a0-6306d3aa24a6.png">