LINE Messaging APIを使って知りたい地域の天気をトーク画面に送信してみる

#はじめに
初アドベントカレンダーです！
大学時代から色々困った際にはQiitaを参考にしていたので、あまり知識がないのに投稿して良いのかと思いますが、多めに見てください、、

今回作成したものとしては、「LINE Message API」を使って知りたい地域の天気情報をLINE Botから送信してもらおうというものです。

- 使用した言語等
  - python 3.8.6
  - heroku
  - yahoo天気情報（今回は以前自分で[投稿した記事]()のデータを使用しました）
  - SQLite

#SQLiteにデータを格納しよう
DBの扱い方は多少できますがなかなか面倒だなと思っていました。ですが、意外にもSQLiteだと簡単にできました。
参考にさせていただいた記事は
[こちら1](https://qiita.com/mu-tech/items/4deacbd844cda61fa9e6)
[こちら2](https://qiita.com/mu-tech/items/4cfb22670232b671700d)

「こちら1」ではcsvファイルからデータをインポートする方法が、「こちら2」ではpythonファイルからSQLiteのDBを操作する方法がそれぞれわかりやすく説明されています。

「こちら2」のコードに以降必要となる処理を加えたものがこちらです。

```python
import sqlite3, pandas as pd

#DBのtableからデータを取得するメソッド
def select_table():
    dbname = 'dbファイル'

    conn = sqlite3.connect(dbname)
    cur = conn.cursor()
    
    #DBのデータを全件取得
    table = cur.execute('select * FROM yahooWeather;')
    data = table.fetchall()
    
    conn.commit()
    conn.close()

    #select結果をリストからデータフレームにして返却（以降で使用する処理）
    data_list = []
    for i in data:
        data_list.append(list(i))
    return pd.DataFrame(data_list,
                        columns = ["rural", "municipality", "URL", "RSS"])
```

とりあえず、テーブルのデータ全件使うのでselectしてデータフレームにしました。

次にLINE Messaging APIのソースコードです。
参考にさせていただいた記事が[こちら](https://qiita.com/shimajiri/items/cf7ccf69d184fdb2fb26)
丁寧にわかりやすくまとめられていますので、Herokuでのデプロイ設定・必要なファイルの作成等のLINE Botの作成方法などは非常に参考になりました。

```python
######LINEBot用インポート
from flask import Flask, request, abort
import os

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)
######

######自作メソッドファイル
import connectDB

######

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():
    #変数宣言
    global rural
    global municipality
    
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)
    
    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    #天気情報を送信した後の処理
    if rural and municipality:
        rural = ""
        municipality = ""
        
    return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    global rural
    global municipality
    global data_frame
    
    if not rural:
        #メッセージ内容を格納
        rural = event.message.text
        municipality_list = data_frame.query('rural == \"' + rural + '\"')["municipality"].values.tolist()

        line_bot_api.reply_message(
            event.reply_token,
            [TextSendMessage(text = "現在の地方：" + rural + "\n市区町村を入力してください"),
             TextSendMessage(text = "\n".join(municipality_list))
            ]
        )

    elif not municipality:
        #メッセージ内容を格納
        municipality = event.message.text
        # ruralとmunicipalityに一致した天気情報を送信
        targetDataDF = data_frame.query('municipality == \"' + municipality + '\"')
        targetIndex  = targetDataDF.index[0]
        targetURL    = targetDataDF.at[targetIndex, "URL"]
        targetRSS    = targetDataDF.at[targetIndex, "RSS"]
        # ruralとmunicipalityに一致した天気情報を送信
        line_bot_api.reply_message(
            event.reply_token,
            [TextSendMessage(text = targetURL),
             TextSendMessage(text = targetRSS)
            ]
        )

if __name__ == "__main__":
#    app.run()
    #DB読み込み
    data_frame = connectDB.select_table()
    #各データのリストを用意
    rural_list = []
    for i in data_frame:
        if not i[0] in rural_list:
            rural_list.append(i[0])

    #各変数設定
    rural = ""
    municipality = ""
    
    port = int(os.getenv("PORT"))
    app.run(host="0.0.0.0", port=port)

```

今回は受信したメッセージのオウム返しではなくて、ユーザーのメッセージ内容によって返信するメッセージ内容を変える必要があります。
どうやら@handler.add(MessageEvent, message=TextMessage)の内部でメッセージ内容を設定できるようなので、この部分をいじっていきましょう。

まずmain関数では先ほど作成したDBのテーブルを読み込み、data_frameを取得します。
このdata_frameの中身は

```

rural Municipality　URL	RSS
道北　稚内　https://weather.yahoo.co.jp/weather/jp/1a/1100.html　https://rss-weather.yahoo.co.jp/rss/days/1100.xml
道北　旭川　https://weather.yahoo.co.jp/weather/jp/1a/1200.html　https://rss-weather.yahoo.co.jp/rss/days/1200.xml
道北　留萌　https://weather.yahoo.co.jp/weather/jp/1a/1300.html　https://rss-weather.yahoo.co.jp/rss/days/1300.xml
道央　札幌　https://weather.yahoo.co.jp/weather/jp/1b/1400.html　https://rss-weather.yahoo.co.jp/rss/days/1400.xml
・
・
・
```

といった形で左の列から地方名、市区町村名、yahoo天気情報のURLとRSSが格納されています。
for文では地方名のみを被らないようにリストにしています。
またプログラム全体の変数としてruralとmunicipalityを宣言しておきます。

そして、ユーザーからメッセージが送信された時に呼び出されるのがcallback関数です。
LINEの認証などは省略します。try句のhandler.handle(body, signature)で今回実装したかった動作を実現します。処理自体は特に難しくなく、地方名が入っていない場合は、変数ruralに送信された地方名を代入。そして、返信メッセージ内容として入力された地方名と、その地方に当てはまる市区町村を送り返します。![IMG_2314.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701238/0f14f52c-095d-fb77-0247-cdf62992add7.png)

こんな感じです。この場合だとruralに「道北」というキーワードが代入されます。そして、市区町村の候補からメッセージを送ると、、、
![IMG_2315.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701238/b25b8a53-379c-0b35-4e64-97a5c069cf60.png)

「留萌」選択しました。確かに留萌地方のyahoo天気情報が返信されていますね。
これでやりたいことの第一歩は完成できたかと思われます。

#終わりに
ざっくりですが、LINEBotでyahoo天気情報をユーザーの入力から分岐して送信してもらうということは実装することができました。終わった感想としては、まだまだ改善・追加したいことがたくさんあるなという感じです。市区町村の選択をクイックリプライという方法にしたり、Botの説明を常に表示できるようにするなどを追加していきたいと思ってます。アドベントカレンダーに限らず、常に自分で必要なものを考えて作っていけるといいなと思える経験でした。
少々短いかもしれないですが今回はこれでおしまいです、どうもありがとうございました！