時間割や提出物などを教えてくれえるLINE BOT作ってみた

プログラミング

どうも、なおです。

今回は僕がクラスメートのために作ったLINE BOTの紹介と少しだけ作り方を解説します。

用いたプログラミング言語はPythonです。現在PythonでLINE BOTを開発していて困ったことがある人は、僕のTwitterのDMにて相談していただけたら、分かる範囲でお答えしようと思います。

デモンストレーションと主な機能

まずはこちらの動画をご覧ください

字幕が早すぎて見にくですが、雰囲気だけ分かってくれたら良いかな。

LINE BOTは基本的にメッセージを送ったら、それに対して返信します。

メイン機能としては「時間割テスト範囲表(小テスト/定期テスト)・提出物行事予定表」の4つです。

あとBOTの使い方を教えてくれる機能質問や機能の追加を依頼できる機能もあります。

管理者用の機能としてコマンドが用意されています。コマンドで時間割・テスト範囲表・提出物・行事予定表の変更ができます。なので急な時間割変更にも対応できるわけです。また提出物も毎日のように変わっていくので、コマンドがあると変更が楽です。

コードの構造

Pythonファイルは2つのみです。

(1)メッセージの送信に対する処理を行うファイル

(2)データベースからデータを取得したり、データを更新したりするファイル

今回はデータベースの代わりにGoogleスプレッドシートを利用しました。理由としては管理が楽だからです。

Googleスプレッドシートを利用することで1週間の時間割や提出物などが一目で分かります。あと開発が楽…(本音)

LINE BOTの作り方

ここからは超ざっくりとしたLINE BOTの作り方を紹介していきます。

LINE Developersに登録

まずは開発言語に関わらずLINE Developersに登録しましょう。

次にプロバイダーおよびチャンネルを作成しましょう。利用するAPIは「Messaging API」です。

作成したらチャンネルの管理画面でチャンネルアクセストークンチャンネルシークレットを取得しましょう。

Pythonファイルを作成

次にソースコードを書いていくので、必要なライブラリをインストールしておきましょう。

pip install flask
pip install line-bot-sdk

Pythonファイルを作成し下記のコードをコピペしましょう。

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('YOUR_CHANNEL_ACCESS_TOKEN')
handler = WebhookHandler('YOUR_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'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))


if __name__ == "__main__":
    app.run()

15行目と16行目に先程取得したチャンネルアクセストークンチャンネルシークレットを書いてください。

ngrokでサーバを起動

次はngrokというソフトを使ってサーバを起動しますので、まだインストールしていない人はこちらからインストールしてください。

インストールが完了したzipファイルを解凍してwindowsならコマンドプロンプト、macならターミナルで解凍したファイルまで移動してください。そしたら「ngrok http 5000」でサーバを起動してください。

そのウィンドウは必ずそのままにしておいてください

あとは先程作成したPythonファイルを起動しましょう。

ngrokのウィンドウに表示される「https://」から始まるURLをLINE Developersの管理画面のWebhook URLという部分に貼り付けて検証を押す。「成功」と表示されたらOK

オウム返しBOTの完成

LINE Developersの管理画面に表示されるQRコードからLINEの友だち追加をすると、オウム返しBOTが完成しているはず??

送信したメッセージがそのまま返ってくると思います。

参考動画

ここまでの説明は大変分かりにくかったと思うので、こちらの動画を見ると良いでしょう。

コード解説

ここからは僕が作成したLINE BOTのソースコードを超ざっくりと解説していきます。ファイルの処理は下記の通りです。

(main.py)メッセージの送信に対する処理を行うファイル

(message.py)データベースからデータを取得したり、データを更新したりするファイル

時間割

時間割は曜日ごとに返信内容が異なるので、コードが少し複雑で大きいです。

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
  #1
    weekday = datetime.date.today().weekday()
    now_time = int(datetime.datetime.now().strftime('%H'))
    
  #2
    now_time += 9
    if now_time > 24:
        now_time -= 24
        weekday += 1
        if weekday > 6:
            weekday = 0

  #3
    message = event.message.text
    if message == '時間割':
        message_content = schedule(weekday, now_time)
        line_bot_api.reply_message(
            event.reply_token,
            messages=message_content
        )

#1:現在の曜日と時刻を取得しています。曜日は0~6で取得され時刻は単位が(時)だけ取得されます。(例) 火曜日 3時18分 = 曜日:1 / 時刻:3

#2:これはHeroku(サーバ)にデプロイするために必要なコードです。Herokuのサーバはおおらくヨーロッパにあるため日本とは9時間の時差が発生します。なのでヨーロッパ時間を日本時間に変換するため時刻+9しているわけです。また時間は24までなので、もし+9の結果が25以上の場合は曜日を繰り上げするようにしています。

#3:まずユーザーから送られてきたメッセージをmessageに代入しています。もし「時間割」と送られてきたら、scheduleという関数を呼び出して第1引数に曜日番号第2引数に時刻を渡しています。最後にその返り値をユーザーに返信しています。

scheduleは時間割を取得するための関数で、これはmessage.pyに記載されています。

def schedule(weekday, now_time):
    #月曜日
    if weekday == 0:
        if now_time < 9:
            monday = worksheet.cell(2,1).value
            message_content = TextSendMessage(
                text=monday
            )
            return message_content
        #火曜日
        else:
            tuesday = worksheet.cell(2,2).value
            message_content = TextSendMessage(
                text=tuesday
            )
            return message_content
        
    elif weekday == 1:
        if now_time < 9:
            tuesday = worksheet.cell(2,2).value
            message_content = TextSendMessage(
                text=tuesday
            )
            return message_content
        #水曜日
        else:
            wednesday = worksheet.cell(2,3).value
            message_content = TextSendMessage(
                text=wednesday
            )
            return message_content

    elif weekday == 2:
        if now_time < 9:
            wednesday = worksheet.cell(2,3).value
            message_content = TextSendMessage(
                text=wednesday
            )
            return message_content
        #木曜日
        else:
            thursday = worksheet.cell(2,4).value
            message_content = TextSendMessage(
                text=thursday
            )
            return message_content
    elif weekday == 3:
        if now_time < 9:
            thursday = worksheet.cell(2,4).value
            message_content = TextSendMessage(
                text=thursday
            )
            return message_content
        #金曜日
        else:
            friday = worksheet.cell(2,5).value
            message_content = TextSendMessage(
                text=friday
            )
            return message_content

    elif weekday == 4:
        if now_time < 9:
            friday = worksheet.cell(2,5).value
            message_content = TextSendMessage(
                text=friday
            )
            return message_content
        #月曜日
        else:
            monday = worksheet.cell(2,1).value
            message_content = TextSendMessage(
                text=monday
            )
            return message_content

    elif weekday == 5:
        monday = worksheet.cell(2,1).value
        message_content = TextSendMessage(
            text=monday
        )
        return message_content

    elif weekday == 6:
        monday = worksheet.cell(2,1).value
        message_content = TextSendMessage(
            text=monday
        )
        return message_content

まず条件式の構造を説明します。

平日各曜日の朝9時まではその曜日の時間割が表示され、9時以降は明日の時間割が表示されます。

午前9時未満 午前9時以降
月曜日 月曜日の時間割 火曜日の時間割
火曜日 火曜日の時間割 水曜日の時間割
水曜日 水曜日の時間割 木曜日の時間割
木曜日 木曜日の時間割 金曜日の時間割
金曜日 金曜日の時間割 月曜日の時間割
土曜日 月曜日の時間割 月曜日の時間割
日曜日 月曜日の時間割 月曜日の時間割

コードは長く見えますが、これらの条件式をひたすら書いているだけです。

そして各条件式の処理としては「Googleスプレッドシートから時間割を取得して返り値として返す」って感じですね。

Googleスプレッドシートの処理はこんな感じ。

import gspread
from oauth2client.service_account import ServiceAccountCredentials

scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name('jsonファイル名', scope)
gc = gspread.authorize(credentials)
SPREADSHEET_KEY = 'スプレッドシートID'
worksheet = gc.open_by_key(SPREADSHEET_KEY).sheet1

詳しくはこちらの記事を見てください。

テスト範囲表

仕組みとしては時間割とほとんど同じです。

def scope():
    message_content = TemplateSendMessage(
        alt_text="範囲",
        template=ButtonsTemplate(
            title="何の範囲を確認しますか?",
            text="定期考査の範囲表は考査1週間前に更新します",            
            image_size="cover",
            actions=[
                PostbackAction(
                label='漢字テスト',
                text='漢字テスト',
                data='kanji'
                ),
                PostbackAction(
                label='ブライトステージ',
                text='ブライトステージ',
                data='brightstage'
                ),
                PostbackAction(
                label='定期テスト',
                text='定期テスト',
                data='tesuto'
                )
            ]
        )
    )
    return message_content

これはテンプレートメッセージというもので、選択画面を表示させることができます。

重要なのは9行目からです。PostbackActionというのは内部的なデータを渡せる選択ボタンで、main.pyではこのdataを取得して条件分岐しています

漢字テストが押された場合、python内ではkanjiというデータが送られていることになります。

#1
elif message == '範囲表':
  message_content = scope()
 line_bot_api.reply_message(
   event.reply_token,
   messages=message_content
  )

#2
@handler.add(PostbackEvent)
def postback(event):
    postback = event.postback.data
    #漢字
    if postback == 'kanji':
        message_content = kanji()
    elif postback == 'brightstage':
        message_content = brightstage()
    elif postback == 'tesuto':
        #テスト期間 ON/OFF
        active = check_tesuto_onoff()
        if active == '1':
            message_content = tesuto()
        elif active == '0':
            message_content = TextSendMessage(text='現在はテスト期間ではありません')

    line_bot_api.reply_message(
        event.reply_token,
        messages=message_content
    )

#1は時間割のコードの続きです。

#2は先程解説したデータの処理をしています。postbackにそのデータが代入されています。先程の例で言うと漢字テストはデータとしてkanjiが送られますので14行目が実行されます。これもまたmessage.pyで定義されている関数を実行しています。

def kanji():
    message_content = ImageSendMessage(
        original_content_url=worksheet.cell(6,2).value,
        preview_image_url=worksheet.cell(6,3).value
    )
    return message_content

これが漢字テストが押された場合に実行される関数です。

こちらはImageSendMessageつまり画像を返り値としています。original_content_urlは送られてきた画像をタップして全画面表示した際に表示される画像のこと、preview_image_urlはトーク画面で表示される画像のことです。つまりoriginal_content_urlはYouTubeの動画本編preview_image_urlはYouTubeのサムネイルって感じです。

これらの画像はURLで指定する必要があります。なのでGoogle Driveなどを使う必要があります。

僕はGoogle Driveに画像をアップロードして、そのURLを指定しています。他にもDropboxなどでも良いらしいです。

漢字テスト以外にもブライトステージや定期テストに関しても同じような仕組みになっています。

コード全文

コードの全文はこちらをご覧ください。コードに関しては好きに使っていただいて構いませんし、改造してもらってもOKです。再配布だけは禁止しておきます!

GitHub | Classroom-Bot_1.0 developed by Nao-kun81

最後に

解説がつかれたので、今日はここで終わり。またやる気が出たら書くかもしれない(多分やらない)

今回も最後までご覧いただきありがとうございました。

それでは、また。

【おすすめのブログ | 重藤録】

重藤録はタブレットを中心に数多くのガジェットを紹介するブログです。是非ご覧ください。

【なおくんのSNS】

【Twitter】

Twitterやっていますので、ぜひフォローお願いします🙏

【note】

noteでもスマホやパソコンなどの記事を書いていますので、ぜひご覧ください。あとフォローもお願いします。

【案件の依頼やお問い合わせはこちら】

    プログラミング
    naokunをフォローする
    なおコンサルティング
    タイトルとURLをコピーしました