LINEbotで勤怠管理 業務効率化

今回はLINEbotを使って勤怠管理をしていきます！
具体的には、LINEで「出勤」「退勤」と入力すると、指定のGoogleスプレッドシートに自動でその時の時刻が書き込まれるようにします。
今回は主要なところだけ説明していくので、詳しく知りたい方は適宜自分でお調べください。
[![Image from Gyazo](https://i.gyazo.com/d3ef3bb2be2c84977968837900c1f7da.gif)](https://gyazo.com/d3ef3bb2be2c84977968837900c1f7da)
[![Image from Gyazo](https://i.gyazo.com/53f05372d2d53083ad9f365cabe6d0e9.gif)](https://gyazo.com/53f05372d2d53083ad9f365cabe6d0e9)


#はじめに
##必要なファイルの作成など
こちらは解説すると大変長くなってしまうので下記のYoutubeの動画に沿って行ってください
（最初の15分くらいで大丈夫です）難しいことは何もないので手順をまねていればOK👌
https://youtu.be/MHf6dideSqg

ここからはコードの解説などを行っていきます
#サンプルコード

```python:python.py
import pandas as pd
from datetime import datetime, timedelta, timezone
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import os

# タイムゾーンの生成
JST = timezone(timedelta(hours=+9), 'JST')

def auth():
    SP_CREDENTIAL_FILE = 'secret.json'
    SP_SCOPE = [
        'https://spreadsheets.google.com/feeds',
        'https://www.googleapis.com/auth/drive'
    ]

    SP_SHEET_KEY = 'SP_SHEET_KEY'
    SP_SHEET = 'SP_SHEET'

    credentials = ServiceAccountCredentials.from_json_keyfile_name(SP_CREDENTIAL_FILE, SP_SCOPE)
    gc = gspread.authorize(credentials)

    worksheet = gc.open_by_key(SP_SHEET_KEY).worksheet(SP_SHEET)
    return worksheet

# 出勤
def punch_in():
    worksheet = auth()
    df = pd.DataFrame(worksheet.get_all_records())

    timestamp = datetime.now(JST)
    date = timestamp.strftime('%Y/%m/%d')
    punch_in = timestamp.strftime('%H:%M')

    df = df.append({'日付': date, '出勤時間': punch_in, '退勤時間': '00:00'}, ignore_index=True)
    worksheet.update([df.columns.values.tolist()] + df.values.tolist())

# 退勤
def punch_out():
    worksheet = auth()
    df = pd.DataFrame(worksheet.get_all_records())

    timestamp = datetime.now(JST)
    punch_out = timestamp.strftime('%H:%M')

    df.iloc[-1, 2] = punch_out
    worksheet.update([df.columns.values.tolist()] + df.values.tolist())

from flask import Flask, request, abort

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

app = Flask(__name__)

line_bot_api = LineBotApi(os.getenv('YOUR_CHANNEL_ACCESS_TOKEN'))
handler = WebhookHandler(os.getenv('YOUR_CHANNEL_SECRET'))

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

@app.route("/callback", methods=["GET", "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'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    if event.message.text == '出勤':
        punch_in()
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text="出勤登録完了しました！今日もがんばりましょう！"))
    elif event.message.text == '退勤':
        punch_out()
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text="退勤登録完了しました！お疲れ様でした"))
    else:
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text="こちらは出退勤を管理するBotです。"))


if __name__ == "__main__":
    port = os.getenv("PORT")
    app.run(host="0.0.0.0", port=port)
```
#ライブラリ
分かりやすいよう二つに分けて書きましたが、上はGoogleスプレッドシートを操作するもの。下はLINEbotで必要なものです。気になるものがあれば適宜調べてください。

```python
import pandas as pd
from datetime import datetime, timedelta, timezone
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import os
```
```python
from flask import Flask, request, abort

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

#googleスプレッドシートへの入力
ここからは、このauth関数を完成させている前提で進めます。まだの方は[こちら](https://youtu.be/MHf6dideSqg)を見て完成させてください。（最初の15分程度）

```python
def auth():
    SP_CREDENTIAL_FILE = 'secret.json'
    SP_SCOPE = [
        'https://spreadsheets.google.com/feeds',
        'https://www.googleapis.com/auth/drive'
    ]

    SP_SHEET_KEY = 'SP_SHEET_KEY'
    SP_SHEET = 'SP_SHEET'

    credentials = ServiceAccountCredentials.from_json_keyfile_name(SP_CREDENTIAL_FILE, SP_SCOPE)
    gc = gspread.authorize(credentials)

    worksheet = gc.open_by_key(SP_SHEET_KEY).worksheet(SP_SHEET)
    return worksheet
```
##出勤時間の入力
まず、タイムゾーンを作成しておきます。

```python
# タイムゾーンの生成
JST = timezone(timedelta(hours=+9), 'JST')
```
こちらが出勤時間を入力するものです。流れとしては、指定したシートをworksheetとし、現在の時間を取得。そしてworksheetをアップデートという形です。
今回は日付と出勤時間を入力、退勤時間は00:00にしておきました。

```python
# 出勤
def punch_in():
    worksheet = auth()
    df = pd.DataFrame(worksheet.get_all_records())

    timestamp = datetime.now(JST)
    date = timestamp.strftime('%Y/%m/%d')
    punch_in = timestamp.strftime('%H:%M')

    df = df.append({'日付': date, '出勤時間': punch_in, '退勤時間': '00:00'}, ignore_index=True)
    worksheet.update([df.columns.values.tolist()] + df.values.tolist())
```

##退勤時間の入力
流れは出勤と全く同じです。

```python
#退勤
def punch_out():
    worksheet = auth()
    df = pd.DataFrame(worksheet.get_all_records())

    timestamp = datetime.now(JST)
    punch_out = timestamp.strftime('%H:%M')

    df.iloc[-1, 2] = punch_out
    worksheet.update([df.columns.values.tolist()] + df.values.tolist())
```
しかし、退勤の場合は一箇所変更を加えればいいだけなので、
``` df.iloc[-1, 2] = punch_out```としています。これは-1行（すなはち、最終行）の2列目に値を入れるという意味です。

ここまででGoogleスプレッドシートへの入力は終わりです。

#LINEbotの作成
ここからはLINEbotの作成に入ります。
[こちらの動画](https://youtu.be/MHf6dideSqg)の35〜40分の部分を見ていただいてLINE Messaging APIの登録をしてください。

#応答メッセージ設定
こちらで返答するメッセージを指定します。```event.message.text```で送られてきたメッセージを取得し、if文で条件分岐。

```python
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    if event.message.text == '出勤':
        punch_in() #出勤の関数
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text="出勤登録完了しました！今日もがんばりましょう！"))
    elif event.message.text == '退勤':
        punch_out() #退勤の関数
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text="退勤登録完了しました！お疲れ様でした"))
    else: #その他のメッセージが送られてきたとき
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text="こちらは出退勤を管理するBotです。"))

```

#herokuへのデプロイに必要なファイル
次の3つのファイルを作成してください。なお、この3つのファイルの名前はこの通りにしてください。

```python:Procfile
web: python line-bot1.py
```
```python:requirements.txt
Flask
line-bot-sdk
pandas
gspread
oauth2client
```
```python:runtime.txt
python-3.9.4
```

herokuにデプロイするファイルはこの3つと先程のコードを書いたもの、加えて最初のYoutubeのほうで作成したjsonファイルの合計5つです。

#エラーの修正
サンプルコードはすでに修正したものですが、最初に紹介した動画の通りに進めるとエラーがでましたので報告しておきます。読み飛ばしていただいてもかまいません。
##Webhook URLの設定
LINEのbot作成中にWebhook URL（https://アプリケーション名.herokuapp.com/callback）を認証させますがその時に一つエラーが起きました。

```python:動画でのコード
@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'
```
修正したものがこちら

```python
@app.route("/callback", methods=["GET", "POST"]) #GETを追加
```

#まとめ
今回はLINEbotを使って出退勤の管理をできるようにしました。LINEbotを初めて作成したのですが、公式LINEみたいでなんだかワクワクします笑
細かい設定などは[こちら](https://youtu.be/MHf6dideSqg)の動画を参考にしてください。初心者向けにとても分かりやすく解説されているのでおすすめです！

それでは今回はこれで終わります。ありがとうございました！