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?

discord.pyでメンバー一括ミュート&解除bot~ポモドーロ添え~

Last updated at Posted at 2024-05-24

もくりヘビーユーザーだった。
哀しい事にもくりは旅立ってしまった。
ので疑似集中モードを作ってdiscordに導入した。
けどものすごく右往左往したので書き記すことにした。

botの中身のみの記事になります。
discordのbotそのものの作成方法については扱いませんので、下記を参考に作成してください。

Botアカウント作成(公式ドキュメント)

やりたいこと

  • ボイチャに参加しているメンバーをコマンド一つで全員サーバーミュートにする
  • ポモドーロタイマーの機能を持たせ、集中時間は全員ミュート&休憩時間はミュート解除になるようにする
  • ボイチャに途中参加したメンバーを最初からサーバーミュートにする
  • ミュートとミュート解除のタイミングで好きな音を流したい
  • Pythonで作りたい

用意したもの

  • Windows 11
  • Python 3.12.3
  • discord.py 2.3.2
  • ffmpeg(音を流すのに必要)
  • 流したい音声ファイル

できたもの

mute.py

import discord
from discord.ext import commands
import asyncio

intents = discord.Intents.all()
intents.message_content = True
intents.members = True

bot = commands.Bot(
    command_prefix="$",
    case_insensitive=True,
    intents=intents
)

@bot.event
async def on_ready():
    print(f'{bot.user}がログインしました')
    new_activity = f"集中モード稼働中" 
    await bot.change_presence(activity=discord.Game(new_activity))
    await bot.tree.sync()

@bot.event
async def on_voice_state_update(member, before, after):
    if before.channel != after.channel:
       memberlist = []
       if before.channel is None:
           for member in after.channel.members:
               memberlist.append(member)
               
           if memberlist[0].voice.mute == True:
               await member.edit(mute=True)
           else:
               await member.edit(mute=False)
       else:
            print(f'{member.name}が抜けたで')
            if member.bot:
                return
            else:
                await member.kick( reason = None )
                print(f'{member.name}のサーバから消したで')

@bot.hybrid_command(name="go",description="一斉ミュートコマンド")
async def test(ctx, times: int, count: int ,brk: int):
    await ctx.author.voice.channel.connect()
    voice_channel = ctx.author.voice.channel
    a = times * 60
    b = brk * 60

    for i in range(count):
        for member in voice_channel.members:
            await member.edit(mute=True)
            
        await ctx.channel.send("ミュートにしました\n" + str(i+1) + "回目の作業開始\n" + str(times) +"分後にお知らせします。")
        await asyncio.sleep(a)

        for member in voice_channel.members:
            await member.edit(mute=False)
        
        voice_client = ctx.guild.voice_client
        music = "turnapage.mp3"
        ffmpeg_audio_source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(music),volume=0.2)
        voice_client.play(ffmpeg_audio_source)
        
        if i != count-1:
            await ctx.channel.send( str(brk) + "分休憩しましょう")
            await asyncio.sleep(b)
            music_end = "pon_01.mp3"
            ffmpeg_audio_source_end = discord.FFmpegPCMAudio(music_end)
            voice_client.play(ffmpeg_audio_source_end)
            await ctx.channel.send( str(brk) + "分休憩終わりです。また頑張りましょう。")
            await asyncio.sleep(15)
        else:
            await asyncio.sleep(46)
            await ctx.channel.send( str(count) + "周終わりました")
    else:
        await ctx.guild.voice_client.disconnect()

bot.run('ここにBOTのトークン')

実行はコマンドプロンプトを起動して

$ python mute.py

でOK
あとはbotを招待したサーバのボイチャで

Ex.25分集中で4周、休憩時間は5分

# 通常コマンド
$go 25 4 5

or

# スラッシュコマンド
/go 25 4 5

を叩けば開始される。
私は一つのサーバにチャンネル複数作ってないのでチャンネルの指定も何もないけど、いっぱいある人はif ctx.channel.id != チャンネルIDとかで指定しておいた方が良いとは思う。
トークンは直接書かない方がいいんだけど自分用でしか使う予定がないのでまあいいかという具合。

やりたいこと各所

少しずつ書いて、最後に全部組み合わせた。

メンバー全員をミュート/ミュート解除する

ドキュメントと先駆者の記事を参考に、ギルドミュート(サーバーミュート)するedit(mute='True)と、ボイチャに参加中のメンバーを巡る for member in hoge.members:を組み合わせる事でいけた。

@bot.hybrid_command(name="go",description="一斉ミュートコマンド")
async def test(ctx):

    voice_channel = ctx.author.voice.channel
    # コマンド送信者のボイチャを指定
    
    for member in voice_channel.members:
        await member.edit(mute=True) # チャンネルの各参加者をミュートする

    await ctx.channel.send("ミュートにしました\n〇分後にお知らせします。")
    await asyncio.sleep(a) # ミュート解除までの秒数を指定(ex.25分=1500)

    for member in voice_channel.members:
        await member.edit(mute=False)

これだけで事足りるとは思わなかった。

ポモドーロタイマーの機能を持たせ、集中時間は全員ミュート&休憩時間はミュート解除になるようにする

asyncio.sleep(sec)に加えてfor i in range(int)を使用した。
またそれにプラスして、コマンド送信時にオプションで「集中時間」「何周やるか」「休憩時間」も指定できるようにした。

@bot.hybrid_command(name="go",description="一斉ミュートコマンド")
-async def test(ctx):
+async def test(ctx, times: int, count: int ,brk: int):
    voice_channel = ctx.author.voice.channel
+   a = times * 60
+   b = brk * 60
    # 秒数に直す

    for i in range(count): # count周する
        for member in voice_channel.members:
            await member.edit(mute=True)
        
-       await ctx.channel.send("ミュートにしました\n〇分後にお知らせします。")
+       await ctx.channel.send("ミュートにしました\n" + str(i+1) + "回目の作業開始\n" + str(times) +"分後にお知らせします。")
        await asyncio.sleep(a)

        for member in voice_channel.members:
            await member.edit(mute=False)
               
+       if i != count-1:
+           await ctx.channel.send( str(brk) + "分休憩しましょう")
+           await asyncio.sleep(b)

+           await ctx.channel.send( str(brk) + "分休憩終わりです。また頑張りましょう。")
+       else:
+           await ctx.channel.send( str(count) + "周終わりました")
+   else:
+       await ctx.channel.send("おつ!")

周回して、指定した回数分より少なければループ、回数分になったらループを抜ける旨をメッセージで送れるようにした。

数字として受け取った値をメッセージに出す場合はstr(hoge)のように文字列にする事を忘れないこと

ボイチャに途中参加したメンバーを最初からサーバーミュートにする

これは別のイベントでの記述になる。

@bot.event
async def on_voice_state_update(member, before, after):
    if before.channel != after.channel:
        if before.channel is None: 
            await member.edit(mute=True)

on_voice_state_update(member, before, after)で、ボイチャの状態変化をキャッチする。ここではメンバーと変化する以前、変化した後をキャッチするように指定している。
if before.channel is None: await member.edit(mute=True)としているが、これは「前に居らんかったmemberをミュートにしろ」という指示になる。
ここのmemberには参加したユーザーが入っているので注意。

ただ問題があって、ゲスト招待にした場合、この箇所も含めた前述のミュート機能そのものがダメになった。
ので、通常のサーバ(チャンネル)招待にして、ボイチャから退出したらゲスト招待の時と同様にキックする方法を取った。それで変更後がこれ。

@bot.event
async def on_voice_state_update(member, before, after):
    if before.channel != after.channel:
+       memberlist = []
       if before.channel is None:
+           for member in after.channel.members:
+               memberlist.append(member)

+          if memberlist[0].voice.mute == True:
              await member.edit(mute=True)
+          else:
+              await member.edit(mute=False)
+      else:
+           if member.bot:
+               return
+           else:
+               await member.kick( reason = None )

if memberlist[0].voice.mute == True:で「今ボイチャに参加してるやつで一番最初のやつ(大体私本人かBOTのはず)がサーバーミュート状態だったら」という形にしてる。作業時間中にボイチャに参加した人をミュートにするための処理がこの後のmember.edit(mute=**)にあたる。
ボイチャから抜けた際に、抜けた対象をmember.kick( reason = None )でキックする事により、これでゲスト招待と同じようになる。ただしBOTをキックさせるわけにはいかないので、if member.bot:は抜けたのがBOT本体だった場合にBOTをキックしないようにするため。タイマー終わったらBOTは出て行っちゃうからその防止。

ミュートとミュート解除のタイミングで好きな音を流したい

これはffmpegが別途必要になるので入れて、かつBOTをボイチャに参加させる。

@bot.hybrid_command(name="go",description="一斉ミュートコマンド")
async def test(ctx, times: int, count: int ,brk: int):
    await ctx.author.voice.channel.connect()
+   voice_channel = ctx.author.voice.channel
    a = times * 60
    b = brk * 60

    for i in range(count):
        for member in voice_channel.members:
            await member.edit(mute=True)
            
        await ctx.channel.send("ミュートにしました\n" + str(i+1) + "回目の作業開始\n" + str(times) +"分後にお知らせします。")
        await asyncio.sleep(a)

        for member in voice_channel.members:
            await member.edit(mute=False)
        
+       voice_client = ctx.guild.voice_client
+       music = "turnapage.mp3"
+       ffmpeg_audio_source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(music),volume=0.2)
+       voice_client.play(ffmpeg_audio_source)
        
        if i != count-1:
            await ctx.channel.send( str(brk) + "分休憩しましょう")
            await asyncio.sleep(b)
+           music_end = "pon_01.mp3"
+           ffmpeg_audio_source_end = discord.FFmpegPCMAudio(music_end)
+           voice_client.play(ffmpeg_audio_source_end)
            await ctx.channel.send( str(brk) + "分休憩終わりです。また頑張りましょう。")
+           await asyncio.sleep(15)
        else:
+           await asyncio.sleep(46)
            await ctx.channel.send( str(count) + "周終わりました")
    else:
-       await ctx.channel.send("おつ!")
+       await ctx.guild.voice_client.disconnect()

鳴らす音についてはパスで指定すること。多分なんでもいける。
turnapage.mp3の方はもともとの音量が大きいので、discord.PCMVolumeTransformer()でボリュームを調整している。

これで自前の環境でなら疑似集中モードを実装することができた。
もし可能なら作業時間残り何分のようなカウントダウンタイマーも付けたいけどちょっと難しそう。

もくりありがとう

参考

感謝してもしきれない ありがとうございます

discord.py公式ドキュメント
https://discordpy.readthedocs.io/ja/latest/index.html
https://discordpy.readthedocs.io/ja/latest/api.html
https://discordpy.readthedocs.io/ja/latest/ext/commands/index.html

discord.py入門
https://qiita.com/sizumita/items/9d44ae7d1ce007391699

Pythonで実用Discord Bot(discordpy解説)
https://qiita.com/1ntegrale9/items/9d570ef8175cf178468f

discord.py - commandsフレームワークへの移行
https://zenn.dev/mnonamer/articles/272f53a1d300f9

discord.pyでボイスチャンネルにいるメンバーを取得する
https://qiita.com/NightWatchWife/items/569d9a5d4cb7d094d1f3

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?