【集団健康管理】Pythonで検温データを集めてくれるLINEbotを作成

## 目次
- [0. 前回の記事はこちら](#0-前回の記事はこちら)
- [1. 作ろうと思ったきっかけ](#1-作ろうと思ったきっかけ)
- [2. 何ができるの](#2-何ができるの)
- [3. 作り方](#3-作り方)
    - [3.1. フォルダ構成について](#31-フォルダ構成について)
    - [3.2. サーバーについて](#32-サーバーについて)


## 0. 前回の記事はこちら
【集団健康管理】みんなの検温データを毎日教えてくれるシステム
https://qiita.com/KANEI/items/c4f190bb3a0861a0cc81

## 1. 作ろうと思ったきっかけ
前回と同じで、サークルの人々に毎日の検温を促して健康管理を行いたいっていうのと、それに伴うのデータの管理をさらに効率化したかったんです。

コロナ期の時には似たような LINEbot を使用していたらしいのですが、Herokuの有料化で使わなくなったみたいです......
今回の開発では旧bot ができたことを再現しようと取り組みました。

LINEbot と Python を使うことで、誰かが体温を送信すれば勝手にユーザー情報を取得し、自動で名前と体温を記録してくれるシステムを作ることができました！
（GASだとPythonほど簡単にユーザー情報を獲得できなかったため、今回は Python を使いました。）


## 2. 何ができるの？
LINEを追加するとユーザー登録の画面が出てくるので、名前を指示の通りに打ち込んで登録します。

![LINE_capture_749387556.138356.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894554/b9f8d2c3-bd5f-ca43-e5a9-aa37d7d052bf.jpeg)

そうすると連携するスプレッドシートに、新しくユーザー名とLINE名、そして登録した名前が追加されました！

![スクリーンショット 2024-09-30 20.08.47.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894554/c0e11fa0-da07-bb8b-214e-0cea3e096c61.jpeg)

これで登録は終了です！簡単ですね！！
（テスト次郎のアカウントはテストのために適当に打っただけなのでモザイクなしです）

LINEbotに体温を送信してみます。

![LINE_capture_749387525.540679.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894554/5b96c692-bb1e-7767-f30b-7b47a363ef0a.jpeg)

入力した体温を記録した旨を様々なバリエーションで返してくれます！！
また、37.5°以上の時は「お大事になさってください。」と心配してくれます。

連携しているスプレッドシートの方を見てみると...

![スクリーンショット 2024-09-30 20.08.33.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894554/d58bd006-515b-3cfc-9c63-c00701c02da2.jpeg)

送信した時間やユーザー名、登録した名前、LINE名、そして送った体温がちゃんと反映されています！！

もし数値以外のことを送ると、正しい値を打つように返信してきます。

![LINE_capture_749387510.487288.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894554/7927d6a0-c4d1-3ec8-fa8f-9df2114dfafc.jpeg)

雑談には向かないbotですね。

## 3. 作り方

今回は以下のように作成しました。
・予算：0円
・制作期間：（大学生の夏休みの）1週間程度

今回の作成にあたって、下記のサイトをめちゃくちゃ参考にさせていただきました！！

【初心者必見】Pythonを使ってオウム返しLINEbotを作ろう！!
https://zenn.dev/shinshinki/articles/3dc4a769a1364d

フォルダ構成や、コードの書き方、ngrokの使い方などをかなり真似させていただきました！！

### 3.1. フォルダ構成について
今回のフォルダ構成は下の通りです。
```:folder
LINEbot
├─ .venv
├─ .gitignore
├─ .ssh
├─ .env
└─ app.py
```

仮想空間.venv上で実行します。gspread、pandas、datetime、flaskのモジュールをインストールしておきます。

```:gitignore
__pycache__
.env
config
.venv
.ssh
```
linebot.jsonはGoogleのサービスアカウントから発行できるキーで、スプレッドシートのアクセスに使います。

スプレッドシートの取得の仕方は、OAuthクライアントIDからやるよりもサービスアカウントからやる方が、めちゃ簡単なのでおすすめです！！下のサイトなど参考にしてください！

https://note.com/s_t877/n/n3d1252623692

環境変数として、LINE Developers から取得できるチャネルシークレットとチャネルアクセストークンと、スプレッドシートのキーを書き込んでいきます。

```:.env
LINE_CHANNEL_SECRET = "******************"   
LINE_CHANNEL_ACCESS_TOKEN = "*****************"
SPREADSHEET_URL = "********************"
```

これを読み込んで、app.pyで動かします！

```python:app.py
import os
import gspread
import random
import datetime
import pandas as pd
from flask import Flask, request, abort

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


#Googleスプレッドシートの情報を得る.
# 認証してGoogleスプレッドシートにアクセス
gc = gspread.service_account(
    filename="linebot.json"
)
# スプレッドシートを開く (スプレッドシートのURLの最後にあるIDを使用)
sh = gc.open_by_key(os.getenv('SPREADSHEET_URL'))

#LINEbotを操作する.
app = Flask(__name__)

line_bot_api = LineBotApi(os.getenv('LINE_CHANNEL_ACCESS_TOKEN'))
handler = WebhookHandler(os.getenv('LINE_CHANNEL_SECRET'))    


@app.route("/callback", methods=['POST'])
def callback():
    # 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:
        print("Invalid signature. Please check your channel access token/channel secret.")
        abort(400)

    return 'OK'

# 反応の関数。
def generate_responce():
    responce_list = [
        "ですね！\n今日がいい日になりますように。",
        "で記入しました！\n健康第一ですよ〜！",
        "ですね！\n今日は演劇日和かも。",
        "ですね！\nたまにはホッと一息。🍵",
        "ですね！\nいってらっしゃい〜！",
        "ですね！\n今日はいいことがある予感🔮",
        "で記入しました！\nありがとうございます💡",
        "ですね！\n気合い入れていきましょ〜！⭐️",
        "デスネ。\nキニュウイタシマシタ🤖",
        "ですね！\nちゃんと記入できましたよ〜🖊️",
        "ですにゃん🐈",
        "ですね！\n素敵な一日になりますように！！",
    ]
    return random.choice(responce_list)


#メンバーシートの読み込み。
member_ws = sh.worksheet("members")

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    if event.reply_token == "00000000000000000000000000000000":
        return
    
    user_input = event.message.text

    #プロフィールの情報を取得。
    profile = line_bot_api.get_profile(event.source.user_id)
    user_id        = event.source.user_id # ユーザID 
    user_disp_name = profile.display_name # アカウント名   

    user_list = member_ws.get_all_values()
    user_df = pd.DataFrame(user_list,columns=['user_id', 'line_name','user_name'])

    #初期値の設定。
    num = 0

    for user,name in zip(user_df["user_id"],user_df["user_name"]):
        if user_id == user:
           user_name = name
           num = 1

    if num == 0:
        user_name = str(user_input)[3:]
        new_df = pd.DataFrame([{'user_id':str(user_id), 'line_name':str(user_disp_name),'user_name':user_name}])
        user_df = pd.concat([user_df,new_df])
        member_ws.insert_row([str(user_id),str(user_disp_name),user_name], 1)
    else:
        num = 0

    #JSTの時間を取得。
    now = datetime.datetime.now(
        datetime.timezone(datetime.timedelta(hours=9))
    )
        
    try:
        temperature = float(user_input)

        #スプレッドシートに書き込む
        ws = sh.worksheet("data")
        # 指定行に引数のリストの内容を1行追加
        ws.insert_row([str(now),str(user_id),str(user_name),str(user_disp_name),str(user_input)], 2)

        #LINEbotで返信

        if temperature >= 37.5:
            message = str(user_input) + "ですね。\nお大事になさってください。"
        else:
            message = user_input + generate_responce()

        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=message)
        )

    except:
        if "登録" in user_input:
            message = user_name + "さん\n名前の登録ありがとうございます。"
        else:
            message = "正しい値を入力してください。"
        
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=message)
        )

if __name__ == "__main__":
    app.run(host="localhost", port=8000)
```

githubにもあげてます！↓↓

https://github.com/KANEI/kenon-line-bot

### 3.2. サーバーについて

今回は無料で作るということもあり、ngrokで動かしました！
ローカルサーバーを立ち上げている状態じゃないと動かないので、毎日のbotの運用は難しそうです...！
日々の運用は前回のを使うとして、お金が貯まったらまたリベンジしたいものです！！

## 参考リンク
・【初心者必見】Pythonを使ってオウム返しLINEbotを作ろう！!
https://zenn.dev/shinshinki/articles/3dc4a769a1364d

・【スプレッドシート】Pythonからスプレッドシートを操作しよう！〜業務効率化〜 初心者向け
https://m.youtube.com/watch?v=KkDt3GMS00k

・gspreadﾗｲﾌﾞﾗﾘの使い方をまとめる
https://note.com/s_t877/n/n3d1252623692

・[Git] .gitignoreの仕様詳解
https://qiita.com/anqooqie/items/110957797b3d5280c44f

・【Messaging API #1】 クライアントのユーザID(user_id)とアカウント名(display_name)の取得方法 (python)
https://motojapan.hateblo.jp/entry/2017/07/08/010435