1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】学習時間記録Discord Bot作成(Heroku Postgres連携Ver)

Posted at

概要

Discord Botを改修した。
以前学習時間記録Discord Botを作成した。
特定のボイスチャンネルを監視し、ユーザ入退室時間を記録、滞在時間を計算して通知するbotである。

上記ではプロセス上の変数に情報を保存していたが入室時刻はDBに保存してデータが消失しないようする。

方法

Bot作成

以下の記事を参考にDiscord Botを作成する。

DB作成

HerokuにDBを作成する。

Botのコード追加・修正

(コード全文は一番下に掲載)

PythonからHerkouDB接続

PythonでHeroku上のDBに接続する例は以下の記事を参考にした。
以下の記事ではコード内にDB接続情報を埋め込んでいる。
DB接続情報の確認はHerokuの[Setting]→[Database Credentials]から確認できる。
(上記DB作成の記事にあったHerokuCLIの接続情報と同じ場所)

追加したDB接続用コードは以下。

  • DB接続情報は以前のDiscord Bot Tokenと同じでheroku上の環境変数に登録している
  • executeで実行するSQL分のパラメータは個数に関係なくリスト(orタプル)にしないといけないらしい:参考
  • INSERT, DELETE後はcommitを忘れない
import psycopg2

def sqlInit():
    # 設定値
    users = getenv('HEROKU_POSTGRESQL_USERS')         # DBにアクセスするユーザー名(適宜変更)
    dbnames = getenv('HEROKU_POSTGRESQL_DBNAMES')     # 接続するデータベース名(適宜変更)
    passwords = getenv('HEROKU_POSTGRESQL_PASSWORDS') # DBにアクセスするユーザーのパスワード(適宜変更)
    host = getenv('HEROKU_POSTGRESQL_HOST')           # DBが稼働しているホスト名(適宜変更)
    port = getenv('HEROKU_POSTGRESQL_PORT')           # DBが稼働しているポート番号(適宜変更)

    # PostgreSQLへ接続
    conn = psycopg2.connect("user=" + users +" dbname=" + dbnames +" password=" + passwords, host=host, port=port)
    return conn

def sqlExit(conn):
    # PostgreSQLの接続を終了
    conn.close()
    return

def INSERT(conn, id, time):
    cur = conn.cursor()
    cur.execute("INSERT INTO pretime (id, time) VALUES (%s, %s)", [id, time])
    conn.commit()
    cur.close()
    return

def SELECT(conn, id):
    cur = conn.cursor()
    cur.execute("SELECT * FROM pretime WHERE id = %s", [id])
    result = cur.fetchone()
    cur.close()
    return result

def DELETE(conn, id):
    cur = conn.cursor()
    cur.execute("DELETE FROM pretime WHERE id = %s", [id])
    conn.commit()
    cur.close()
    return

イベント処理

改修したコードは以下。

  • SEND_CHANNEL_ID, WATCH_CHANNEL_IDも環境変数化した
  • 以前はプログラム内変数に保存していた情報をDB保存にした
import discord
import datetime
from os import getenv

client = discord.Client()
SEND_CHANNEL_ID = int(getenv('SEND_CHANNEL_ID'))   # 通知を送信するチャンネルID
WATCH_CHANNEL_ID = int(getenv('WATCH_CHANNEL_ID')) # 入退室を監視するチャンネルID
DIFF_JST_FROM_UTC = 9

@client.event
async def on_ready():
    print('Logged in as')
    print(client.user.name)
    print(client.user.id)
    print('------')

@client.event
async def on_voice_state_update(member, before, after):

    # 入室時
    if before.channel is None:
        if after.channel.id != WATCH_CHANNEL_ID:
            return

        # 入室時刻を取得
        dt_now = datetime.datetime.utcnow() + datetime.timedelta(hours=DIFF_JST_FROM_UTC)
        dt_now_text = dt_now.strftime('%Y-%m-%d %H:%M:%S')

        # 入室者ID, 入室時刻をDBに保存
        try:
            conn = sqlInit()
            INSERT(conn, str(member.id), str(dt_now_text))
            sqlExit(conn)
            send_text = f'**{member.name} ({member.nick})** \n  入室: {dt_now_text}'
        except Exception as e:
            send_text = f'{e}'

        # 入室時は特に通知はしない(HerokuLogには出力する)
        print(send_text)

    # 退室時
    elif after.channel is None:
        if before.channel.id != WATCH_CHANNEL_ID:
            return

        # 退室時刻を取得
        dt_now = datetime.datetime.utcnow() + datetime.timedelta(hours=DIFF_JST_FROM_UTC)
        dt_now_text = dt_now.strftime('%Y-%m-%d %H:%M:%S')

        # 退室者IDを用いてDBから入室時刻を取得
        # 滞在時間を計算
        try:
            conn = sqlInit()
            result = SELECT(conn, str(member.id))
            if result is None:
                print('error: result is None')
                sqlExit(conn)
                return
            DELETE(conn, str(member.id))
            sqlExit(conn)

            dt_now_before = result[1]
            dt_now_before_text = dt_now_before.strftime('%Y-%m-%d %H:%M:%S')

            time_text = getDurationTimeText(dt_now_before, dt_now)
            send_text = f'**{member.name} ({member.nick})** \n  入室: {dt_now_before_text}\n  退室: {dt_now_text}\n  勉強時間: {time_text}'
        except Exception as e:
            send_text = f'{e}'

        print(send_text)

        # 指定のテキストチャンネルに通知
        send_channel = client.get_channel(SEND_CHANNEL_ID)
        await send_channel.send(send_text)

# token にDiscordのデベロッパサイトで取得したトークンを入れてください
token = getenv('DISCORD_BOT_TOKEN')
client.run(token)

コード全文

import discord
import datetime
from os import getenv
import psycopg2

def sqlInit():
    # 設定値
    users = getenv('HEROKU_POSTGRESQL_USERS')         # DBにアクセスするユーザー名(適宜変更)
    dbnames = getenv('HEROKU_POSTGRESQL_DBNAMES')     # 接続するデータベース名(適宜変更)
    passwords = getenv('HEROKU_POSTGRESQL_PASSWORDS') # DBにアクセスするユーザーのパスワード(適宜変更)
    host = getenv('HEROKU_POSTGRESQL_HOST')           # DBが稼働しているホスト名(適宜変更)
    port = getenv('HEROKU_POSTGRESQL_PORT')           # DBが稼働しているポート番号(適宜変更)

    # PostgreSQLへ接続
    conn = psycopg2.connect("user=" + users +" dbname=" + dbnames +" password=" + passwords, host=host, port=port)
    return conn

def sqlExit(conn):
    # PostgreSQLの接続を終了
    conn.close()
    return

def INSERT(conn, id, time):
    cur = conn.cursor()
    cur.execute("INSERT INTO pretime (id, time) VALUES (%s, %s)", [id, time])
    conn.commit()
    cur.close()
    return

def SELECT(conn, id):
    cur = conn.cursor()
    cur.execute("SELECT * FROM pretime WHERE id = %s", [id])
    result = cur.fetchone()
    cur.close()
    return result

def DELETE(conn, id):
    cur = conn.cursor()
    cur.execute("DELETE FROM pretime WHERE id = %s", [id])
    conn.commit()
    cur.close()
    return

def getDurationTimeText(dt_now_before, dt_now):
    duration_time = dt_now_before - dt_now
    duration_time_adjust = int(duration_time.total_seconds()) * -1
    hours = duration_time_adjust // 3600
    remain = duration_time_adjust - (hours * 3600)
    minutes = remain // 60
    seconds = remain - (minutes * 60)
    time_text = '{:02}:{:02}:{:02}'.format(int(hours), int(minutes), int(seconds))
    return time_text

client = discord.Client()
SEND_CHANNEL_ID = int(getenv('SEND_CHANNEL_ID'))   # 通知を送信するチャンネルID
WATCH_CHANNEL_ID = int(getenv('WATCH_CHANNEL_ID')) # 入退室を監視するチャンネルID
DIFF_JST_FROM_UTC = 9

@client.event
async def on_ready():
    print('Logged in as')
    print(client.user.name)
    print(client.user.id)
    print('------')

@client.event
async def on_voice_state_update(member, before, after):

    # 入室時
    if before.channel is None:
        if after.channel.id != WATCH_CHANNEL_ID:
            return

        # 入室時刻を取得
        dt_now = datetime.datetime.utcnow() + datetime.timedelta(hours=DIFF_JST_FROM_UTC)
        dt_now_text = dt_now.strftime('%Y-%m-%d %H:%M:%S')

        # 入室者ID, 入室時刻をDBに保存
        try:
            conn = sqlInit()
            INSERT(conn, str(member.id), str(dt_now_text))
            sqlExit(conn)
            send_text = f'**{member.name} ({member.nick})** \n  入室: {dt_now_text}'
        except Exception as e:
            send_text = f'{e}'

        # 入室時は特に通知はしない(HerokuLogには出力する)
        print(send_text)

    # 退室時
    elif after.channel is None:
        if before.channel.id != WATCH_CHANNEL_ID:
            return

        # 退室時刻を取得
        dt_now = datetime.datetime.utcnow() + datetime.timedelta(hours=DIFF_JST_FROM_UTC)
        dt_now_text = dt_now.strftime('%Y-%m-%d %H:%M:%S')

        # 退室者IDを用いてDBから入室時刻を取得
        # 滞在時間を計算
        try:
            conn = sqlInit()
            result = SELECT(conn, str(member.id))
            if result is None:
                print('error: result is None')
                sqlExit(conn)
                return
            DELETE(conn, str(member.id))
            sqlExit(conn)

            dt_now_before = result[1]
            dt_now_before_text = dt_now_before.strftime('%Y-%m-%d %H:%M:%S')

            time_text = getDurationTimeText(dt_now_before, dt_now)
            send_text = f'**{member.name} ({member.nick})** \n  入室: {dt_now_before_text}\n  退室: {dt_now_text}\n  勉強時間: {time_text}'
        except Exception as e:
            send_text = f'{e}'

        print(send_text)

        # 指定のテキストチャンネルに通知
        send_channel = client.get_channel(SEND_CHANNEL_ID)
        await send_channel.send(send_text)

# token にDiscordのデベロッパサイトで取得したトークンを入れてください
token = getenv('DISCORD_BOT_TOKEN')
client.run(token)
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?