【Python】ほぼコピペで作れる!discord.pyでおやすみBot を作ってみた

【PYthon】discord.pyでおやすみBotを作ってみた

こんにちは。梅雨入りしたこともあってジメジメした日が多いですが、皆様いかがお過ごしでしょうか私は元気です。

バックエンド
エンジニア

こっちゃん

やりたいこと多くて身体が足りてません。この中に人体錬成できる方いらっしゃいませんか。

バックエンド
エンジニア

こっちゃん

もう寝るつもりだったのに、友達とのおしゃべりが楽しくてついつい長話をしてしまう…
皆さんそんな経験を1度はしたことがあるのではないでしょうか?(あると信じてます)

自制したいけど楽しくてなかなかやめられない…意思もよわよわなのでやめられない…
なのでプログラムに頼ることにしました。

今回はどれだけ会話が盛り上がっていても、設定した時間になったら強制的に通話を終了させるBotをdiscord.pyで作成しました。

Pythonを書く前に

Botの作成についてはこちらの記事をご参照ください。

チャンネルIDを取得

まずBotを使用するサーバーのチャンネルIDを取得します。

このIDを使用してユーザーを退出させるボイスチャンネルを指定します。

①botを使用するサーバーのボイスチャンネルを右クリックします。

②「チャンネルIDをコピー」をクリックしてメモ帳に控えておきます。

botを使用するサーバーのボイスチャンネルを右クリック

チャンネルIDはできればコード上で取得したかったのですが、
どうもやり方が分からず泣く泣く画面上で取得しています。

ざっくりとした仕様

  • スラッシュコマンドで処理する
  • 入力形式は「/YYYY-MM-DD hh:mm」
  • ユーザーが指定する時間になったら、ボイスチャンネルに参加しているすべてのユーザーを強制退出させる
  • ボイスチャンネルに参加者がいない場合は何もしない

細かい仕様はコードの説明で書きます。

〜環境〜
・Python 3.10.7
・pip 22.2.2
・discord.py 2.4.0

※環境構築は終っている前提の記事です。

Pythonを書いていく

まずは全体のコードをドン。こんな感じです。

import discord
import config
import asyncio
from datetime import datetime as dt

# ⓵チャンネルID
CHANNEL_ID = 1122334455667788990

intents = discord.Intents.all()
client = discord.Client(intents=intents)

# ⓶Bot起動時のメッセージ
@client.event
async def on_ready():
    for channel in client.get_all_channels():
        if isinstance(channel, discord.TextChannel):
            await channel.send('今回は何時に終わるシュコ?終りたい時間を入力するシュコ!(例:/2024-01-01 01:00)')

# メッセージの検知
@client.event
async def on_message(message):
    # ⓷/(スラッシュ)コマンドの入力に対して処理をする
    if message.content.startswith('/'):
        # ボイスチャンネルを取得
        channel = client.get_channel(CHANNEL_ID)

        # ⓸ボイスチャンネルに参加しているユーザーがいるか
        if len(channel.voice_states.keys()) > 0:
            userSetTime = message.content.lstrip(message.content[0])
            try:
                # ⓹ユーザーの入力を datetime型に変換する
                endTime = dt.strptime(userSetTime, '%Y-%m-%d %H:%M')

                # ⓹現在日時を取得
                dt_now = dt.now().strftime("%Y-%m-%d %H:%M:%S")
                now = dt.strptime(dt_now, '%Y-%m-%d %H:%M:%S')

                # ⓹ユーザーの入力時間と現在時間の差分を計算する
                diffTime = 0
                if endTime > now:
                    diffTime = endTime - now
                else:
                    await message.channel.send('未来の日時を入力するシュコ!')

                # ⓺残り時間を秒に変換して、非同期処理をさせる
                timeLimit = diffTime.total_seconds()
                await message.channel.send(userSetTime + '了解シュコ!')
                await asyncio.sleep(timeLimit)
				
                # ⓺ボイスチャンネルに参加しているユーザーを強制退出させる
                for voiceChannel in channel.guild.voice_channels:
                    for vcMember in voiceChannel.members:
                        await vcMember.move_to(None)
                await message.channel.send('みんなお休みシュコ~!')
               	return

            
            # 指定の入力形式以外で入力された場合の処理
            except ValueError:
                await message.channel.send('/YYYY-MM-DD hh:mm の形式で半角で入力するシュコ!(例:/2024-01-01 01:00)')
        else:
            await message.channel.send('ボイスチャンネルに参加するシュコ!')
            return

# Bot起動
client.run('your token here')

実際の動作

このコード実行してBotが起動するとメッセージを送ってくれます!

次にボイスチャンネルから退出させてほしい時間を入力すると、
入力した時間にボイスチャンネルから退出させるようタイマーをセットします!

そして設定した時間になると、Botが自動的にボイスチャンネルから退出させてくれます!
処理が無事終わるとBotからメッセージが送られます!

コードの説明

①チャンネルID

ここには記事の初めにDiscordの画面から取得したボイスチャンネルのIDを入れます。

②Bot起動時のメッセージ

ここはユーザーにBotが起動したことを知らせるメッセージを書いています。

client.get_all_channels()でBotが参加しているすべてのチャンネルを取得しています。
しかしこれだとボイスチャンネルも取得してしまうので、
if isinstance(channel, discord.TextChannel):でテキストチャンネルを判定します。

discord.TextChannelというのが、iscord内で扱われているチャンネルのタイプですね。

※ボイスチャンネルではメッセージの送信(send())ができないため、
プログラム実行時にエラーとなってしまいます。

③/(スラッシュ)コマンドの入力に対して処理をする

/(スラッシュ)コマンドの判定は、message.content.startswith('/'):で行っています。
ユーザーが入力したメッセージの開始が /(スラッシュ)となっているか判定しています。

④ボイスチャンネルに参加しているユーザーがいるか

channel.voice_states.keys()でボイスチャンネルに参加しているユーザー取得しています。

⑤時間の取得と計算

「ユーザーの設定した時間になった」という判定は、
「現在時間 – ユーザーの設定した時間」を秒に変換して非同期処理をさせることで実現しています。
strptime()strftime()でごちゃごちゃと型を変換していますが、
すべて「現在時間 – ユーザーの設定した時間」をするためです。

ユーザーから現在より過去の日時が入力された場合は、未来の日時を入力するようメッセージを送信します。

⑥非同期処理

非同期処理await asyncio.sleep()に⑤で計算した値をセットすることで、
「〇秒後に処理を実行する」という処理をさせ、
あたかもユーザーの設定した時間に終了したように見せています。

そして設定した秒数が経つと、await vcMember.move_to(None)が実行され、
ボイスチャンネルに参加しているユーザーを順番に退出させていきます。
vcMember.move_to()はユーザーを他のボイスチャンネルに移動させる時にも使いますが、
ここにNoneをセットすることでボイスチャンネルから退出させることができます。

余談

他の実現方法として、task.loop()を使用したやり方があるみたいです。
こちらは〇秒ごとのループ実行によるもので、イメージとしてはcronが近いかもしれません。

私が非同期処理を選んだのは、単純にコードが少なそうだったのと、
task.loop()では細かな時間が設定できなくなると考えたからです。
まぁ今回秒の入力は受け付けてないんですがね
1秒ごとのループ実行にしてしまうと、今回の処理内容だと毎秒現在時間を取得して
ユーザーの設定時間と比較して…という処理が走ることになり、恐らく1秒以内に処理が終わらないため好ましくないと思い今回は採用しませんでした。

さいごに

いかがでしたでしょうか?
今回は、時間になったらボイスチャンネルの参加者を強制退出させるBotを作ってみました。
この記事を書いている時に、ああすればよかった、あれが足りてないとか
いろいろ気づいてしまったのですが、まぁ伸びしろということにしたいです。(笑)
それにdiscord.pyはまだまだビギナーなのでいい機会になりました。

今回のBotに限らず、こんなのあったら楽しそう便利そうって思ったものを自分で作れたら
きっと楽しいし嬉しいですよね。
そんなこんなで皆様も良きもの作りライフを。

そういえばBotのアイコンに使わせていただいたロージーが可愛いのでドアップで載せときますね。

ねむねむロージー

ロジカルスタジオではエンジニアを募集しています。

興味を持っていただけたら、是非採用サイトからご応募ください!