Slack Bot を活用した研究室の出席管理システムを作ろう

研究室での出席状況を適切に把握して、学生が自らの活動状況を振り返ることができるようにすることは、学習効率や意欲向上の手助けになるかと思います。しかし、このような出席管理を教員 and/or 学生が手動で行うのは大変。負担が大きく、データの蓄積や分析も手間がかかります。

このノートでは、Slack の Bolt for Python を活用して、出席記録を自動化するシステムを構築する方法を紹介します。

システム概要

このシステムは、Slackを介して以下の機能を提供します:

  • 朝のおはようメッセージ:毎朝、Slackのラボイン・チャンネルにボットから「おはようメッセージ」が投稿されます。
  • リアクションによる記録:研究室に着いたときにこの「おはようメッセージ」にリアクションするだけで出席(ラボイン)が記録されます。
  • ヒートマップでの視覚化:夕方には、過去30日間のログをヒートマップ化したものが同チャンネルに表示され、全員が自分の出席状況を確認できます。

ステップ 1:Slack App の設定

アプリの作成とトークンの取得

Slack APIYour Apps にアクセスし、新しいアプリを作成(Create an App)。

From scratch を選びました。

アプリ名ワークスペース を設定。アプリ名は、最初にLabInとしましたが、後におはようボットに変更しました。

Basic InformationApp-Level Token を作成し、connections:writeauthorizations:read スコープを設定。

App-Level Token が発行されました。

OAuth & Permissions で以下のスコープを設定し、Slack Appに出席チェックやリアクション処理の権限を付与:

  • chat:write:メッセージ送信
  • files:write:ファイルアップロード
  • reactions:read:リアクションの読み取り
  • reactions:write:リアクションの追加
  • users:read:ユーザー情報取得

アプリをワークスペースにインストールし、Bot User OAuth Tokenを取得。

アクセス権限を確認して、許可します。

Bot User OAuth Toke が発行されました。

Socket ModeEnable にし、リアクション処理をリアルタイムで行えるように。

最後に、Display Informationでアプリ名をおはようボットに変更し、アイコンなどを設定。

チャンネルの設定

Slack の yamlab ワークスペースで、出席確認を行うチャンネル(例: 01_ラボイン)を作成し、アプリ(おはようボット)を追加します。

ステップ 2:Pythonスクリプトの実装

次に、Slack Botが動作するPythonスクリプトを作成します。Slackにメッセージを送信し、指定したリアクションが追加されたときにデータベースに記録します。

必要なライブラリのインストール

Python仮想環境を構築し、必要なライブラリをインストールします。

mkdir ~/opt/labin
cd ~/opt/labin
python -m venv venv
source venv/bin/activate
pip install slack-bolt slack-sdk schedule pandas matplotlib seaborn japanize-matplotlib

Bot の実装と設定

Slack Bot へのリアクションをトリガーに出席を記録するPythonスクリプトです。

import os
import sqlite3
import time
import schedule
import threading
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib
import seaborn as sns
from datetime import datetime, timedelta
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from slack_sdk.errors import SlackApiError
from slack_sdk import WebClient

# 環境変数からSlackのチャンネルIDとトークン情報を取得
CHANNEL_ID = os.getenv('CHANNEL_ID')
BOT_USER_OAUTH_TOKEN = os.getenv('BOT_USER_OAUTH_TOKEN')
APP_LEVEL_TOKEN = os.getenv('APP_LEVEL_TOKEN')

# リアクションに使う絵文字
CHECKIN_EMOJI = 'wave'

# Slack Boltアプリのインスタンス作成
app = App(token=BOT_USER_OAUTH_TOKEN)
client = WebClient(token=BOT_USER_OAUTH_TOKEN)

# データベース接続とテーブル作成
conn = sqlite3.connect("labin.db", check_same_thread=False)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS attendance (user_id TEXT, date TEXT, time TEXT)''')
conn.commit()

def send_attendance_message():
    try:
        response = app.client.chat_postMessage(
            channel=CHANNEL_ID,
            text=f"おはよう!ラボインしたら :{CHECKIN_EMOJI}: でリアクション"
        )
        app.client.reactions_add(
            channel=response['channel'],
            timestamp=response['ts'],
            name=CHECKIN_EMOJI
        )
    except SlackApiError as e:
        print(f"Error sending message: {e.response['error']}")

@app.event("reaction_added")
def handle_reaction_added(event, say):
    if event["reaction"] == CHECKIN_EMOJI:
        record_attendance(event["user"])

def record_attendance(user_id):
    date, checkin_time = time.strftime("%Y-%m-%d"), time.strftime("%H:%M:%S")
    c.execute("SELECT * FROM attendance WHERE user_id = ? AND date = ?", (user_id, date))
    if c.fetchone() is None:
        c.execute("INSERT INTO attendance (user_id, date, time) VALUES (?, ?, ?)", (user_id, date, checkin_time))
        conn.commit()
        print(f"Recorded attendance for user {user_id} at {checkin_time}")

def post_attendance_heatmap():
    end_date = datetime.today()
    start_date = end_date - timedelta(days=29)
    date_range = pd.date_range(start=start_date, end=end_date)
    date_labels = date_range.strftime("%m-%d")
    
    attendance_query = "SELECT user_id, date FROM attendance WHERE date BETWEEN ? AND ?"
    attendance_df = pd.read_sql_query(attendance_query, conn, params=(start_date.strftime("%Y-%m-%d"), end_date.strftime("%Y-%m-%d")))

    members_df = pd.read_csv("members.csv", header=None, names=["user_id", "fullname"])
    attendance_df = members_df.merge(attendance_df, on="user_id", how="left")
    attendance_df["date"] = pd.Categorical(attendance_df["date"], categories=date_range.strftime("%Y-%m-%d"), ordered=True)

    attendance_pivot = attendance_df.pivot_table(index="fullname", columns="date", aggfunc="size", fill_value=0, observed=False)
    attendance_pivot = attendance_pivot.reindex(index=members_df["fullname"], fill_value=0)

    plt.figure(figsize=(8, 8))
    sns.heatmap(attendance_pivot, cmap="Blues", cbar=False, linewidths=1)
    plt.xticks(ticks=range(len(date_labels)), labels=date_labels, rotation=45)
    plt.tight_layout()
    plt.savefig("labin.png")
    plt.close()

    try:
        client.files_upload_v2(
            channels=CHANNEL_ID,
            file="labin.png",
            title="過去30日間のラボイン",
            initial_comment="おつかれ! 💮"
        )
        print("Attendance heatmaps were posted on Slack.")
    except SlackApiError as e:
        print(f"Error uploading file: {e.response['error']}")

def run_schedule():
    while True:
        schedule.run_pending()
        time.sleep(1)

# スケジュール設定
schedule.every().day.at("06:00").do(send_attendance_message)
schedule.every().day.at("18:00").do(post_attendance_heatmap)

if __name__ == "__main__":
    threading.Thread(target=run_schedule).start()
    handler = SocketModeHandler(app, APP_LEVEL_TOKEN)
    handler.start()

ステップ3:スクリプトのデーモン化

systemdでスクリプトを常時起動できるようにします。

サービスファイルの作成

/etc/systemd/system/labin.service ファイルを作成し、以下の内容を記述します:

[Unit]
Description=LabIn

[Service]
WorkingDirectory=/home/yamnor/opt/labin
ExecStart=/home/yamnor/opt/labin/venv/bin/python labin.py
Restart=always

[Install]
WantedBy=multi-user.target

サービスの起動

サービスを起動し、自動起動を設定します。

sudo systemctl start labin
sudo systemctl enable labin

以上で、Slack Bot を使ったシンプルな出席管理システムの完成です。

ステップ4:動作の確認

毎朝6時に、自動的にSlackの出席確認チャンネルにおはようメッセージが投稿されます。メンバーは、研究室に着いたときに Slack アプリを開いて、このおはようメッセージにリアクションすることで、その日の出席を記録します。

夕方18時には、過去30日間の出席状況がヒートマップ形式でSlackに投稿され、出席パターンを一目で確認できます。

以上で、Slack上で出席データを視覚化し、学びの進捗を一目で確認できるようになりました。

さいごに

ヤマラボでは、個々の出席情報「だけ」で、学生たちの活動状況を厳しく評価するようなことはしないようにしています。このシステムは、より効率的な自己管理のため、また、研究室での活動をお互いにサポートしながら共に成長するための環境作りとして導入しました。

ラボインを習慣化することで、より一層の連帯感と研究への集中力が生まれることを期待しています。

Posted :