Featured image of post Discord.py童貞がVCで音楽聞けるBotを作った

Discord.py童貞がVCで音楽聞けるBotを作った

ハハハ

# Discord.py

PythonでDiscordのあれこれができるライブラリです。
インストールは次のコマンドで。

1
pip install discord.py[voice]

今回はVCも使うので[voice]を追加しています。

# 初めてなので…

voiceの方を触るのは疎か、discord.py自体を初めて触るのでもしかしたら間違っているかもしれません。
無いなら作ればいいじゃないで調べつつやっております。

# 欲しい機能

  • URLから再生
  • 検索->再生
  • 一時停止
  • 自動退出

こんなものでしょうか。実はプレイリストもやりたかったけど耳鳴りでそこまで続かなかった。
そして最終的に以下のソフトをホストに導入しました。

  • Python(実行用)
  • yt-dlp(pip)
  • ffmpeg(apt)

これさえ入れておけばコードコピペ&トークンさえあれば動くはずです。

# コード

以下のコードは初心者が書いたものなので無駄があるかもしれません。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import discord
from discord.ext import commands
from yt_dlp import YoutubeDL
import os

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

bot = commands.Bot(command_prefix='!',intents=intents)

currentfile = None

def delete_file(path):
    """再生が終了した後にファイルを削除する関数"""
    try:
        os.remove(path)
        print(f"Deleted file: {path}")
    except Exception as e:
        print(f"Error deleting file {path}: {e}")

@bot.event
async def on_ready():
    print(f'ログインしました。Bot名: {bot.user.name}')

@bot.command()
async def search(ctx, *, search_query):
    """検索して一番上の結果のURLを渡す"""
    ydl_opts = {
        'format': 'bestaudio/best',
        'quiet': True,
        'extract_flat': 'in_playlist'
    }
    
    with YoutubeDL(ydl_opts) as ydl:
        info = ydl.extract_info(f"ytsearch:{search_query}", download=False)['entries'][0]
    
    if info:
        url = info['url']
        title = info.get('title', 'Unknown title')
        await ctx.send(f"検索結果: {title}\n{url}")
        await play(ctx, url)
    else:
        await ctx.send("検索結果が見つかりませんでした。")

@bot.command()
async def play(ctx, url):
    """URLから音声をダウンロードして再生する"""
    global currentfile
    if not ctx.message.author.voice:
        await ctx.send('先にボイスチャンネルに参加してください。')
        return

    channel = ctx.message.author.voice.channel
    if voice_client := ctx.guild.voice_client:
        await voice_client.move_to(channel)
    else:
        voice_client = await channel.connect()

    ydl_opts = {
        'format': 'bestaudio',
        'outtmpl': 'tmp/%(id)s.%(ext)s',  # ダウンロードするファイルのパスとフォーマット
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',  # 音声を抽出
            'preferredcodec': 'opus',
            'preferredquality': '96',  # 96kbpsのopusに変換
        }],
    }
    with YoutubeDL(ydl_opts) as ydl:
        info = ydl.extract_info(url, download=True)
        filename = ydl.prepare_filename(info)
        audio_path = os.path.join('tmp', f"{info['id']}.opus")  # ダウンロードしたファイルのパス
        currentfile = audio_path

    def after_play(error):
        delete_file(audio_path)
        currentfile = None


    voice_client.play(discord.FFmpegPCMAudio(audio_path), after=lambda e: after_play(e))

    await ctx.send(f'再生を開始します: {info["title"]}')


@bot.command()
async def stop(ctx):
    global currentfile
    """音声の再生を停止し、ボイスチャンネルから切断します。"""
    voice_client = ctx.guild.voice_client
    if voice_client and voice_client.is_playing():
        voice_client.stop()
        await ctx.send('再生を停止しました。')
        delete_file(currentfile)
        currentfile = None
    else:
        await ctx.send('現在、何も再生されていません。')

@bot.command()
async def pause(ctx):
    """一時停止/再開する"""
    voice_client = ctx.guild.voice_client
    if voice_client and voice_client.is_playing():
        voice_client.pause()
        await ctx.send('一時停止しました')
    elif voice_client and voice_client.is_paused():
        voice_client.resume()
        await ctx.send('再開しました')
    else:
        await ctx.send('再生されていません')

@bot.event
async def on_voice_state_update(member, before, after):
    """ボイスチャンネルにBOT以外がいなくなったら抜ける"""
    # メンバーがボイスチャンネルから離れたか、別のチャンネルに移動した場合に実行
    if before.channel is not None and (after.channel is None or after.channel != before.channel):
        # before.channelには、メンバーが離れたボイスチャンネルの情報が含まれています
        # チャンネル内のメンバー数を確認
        if len(before.channel.members) == 1:
            # ボット自身がそのチャンネルに接続しているか確認
            voice_client = discord.utils.get(bot.voice_clients, channel=before.channel)
            if voice_client is not None:
                # チャンネルから抜ける
                await voice_client.disconnect()
                print(f"{before.channel.name}から抜けました。")

@bot.command()
async def leave(ctx):
    await ctx.voice_client.disconnect()

bot.run('TOKEN')

うん、分かったかな?(わからない)

こうなる

使い方です。

1
2
3
4
5
!play url : URLを指定して再生します
!search keyword : YouTubeで検索して一番上の結果を再生します
!pause : 一時停止/再開
!stop : 再生を停止
!leave : BOTを退出させる

これだけ。コマンド実行してから再生されるまで動画の長さとプログラムを実行している環境で結構変わる。
私の環境では約10秒程度で再生される。

# 実装予定の機能

次に再生は実装したいな。!add url とかで。もしくは再生中ならリストに追加とか。
まぁやる気が続けば。

Licensed under CC BY-NC-SA 4.0
ねもう です。
Built with Hugo
Theme Stack designed by Jimmy