1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

discordBOTの改良をした話

Last updated at Posted at 2024-04-07

皆さん、こんにちは。takazonessです。
先日、discordの音楽BOTを作りましたが、中身を少し改良したので、そのコードを記事として記しておこうと思います。

https://qiita.com/takazoness/items/04d0d15ab3eff09389c2
↑これの改良版

改良点

・日本語化
・スキップ機能の追加
・キュー機能の追加
・ストリーミング化(mp3形式にて)

コード

※ストリーミング化を行いました。なおキュー機能に関して動作はしますが、曲が終了する1分ほど前になぜか曲が終了し次の曲へ移行します。
2024年5月12日にコードを変更しました。

discordbot.py
import asyncio
import discord
import yt_dlp as youtube_dl
from discord.ext import commands
import os

# エラーについてのメッセージを非表示にする
youtube_dl.utils.bug_reports_message = lambda: ''

ytdl_format_options = {
    'format': 'bestaudio/best',
    'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
    'restrictfilenames': True,
    'noplaylist': True,
    'nocheckcertificate': True,
    'ignoreerrors': False,
    'logtostderr': False,
    'quiet': True,
    'default_search': 'auto',
    'source_address': '0.0.0.0', 
    'reconnect': 1,
    'reconnect_streamed': 1,
    'reconnect_delay_max': 5,
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }]
}

ffmpeg_options = {
    'options': '-vn'
}

ytdl = youtube_dl.YoutubeDL(ytdl_format_options)

class YTDLSource(discord.PCMVolumeTransformer):
    def __init__(self, source, *, data, volume=0.5, ctx):
        super().__init__(source, volume)

        self.data = data
        self.title = data.get('title')
        self.url = data.get('url')
        self.ctx = ctx  # ctx を保持
    
    @classmethod
    async def from_url(cls, url, *, loop=None, stream=True, ctx):
        loop = loop or asyncio.get_event_loop()
        data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))

        if 'entries' in data:
            data = data['entries'][0]

        filename = data['url'] if stream else ytdl.prepare_filename(data)
        return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data, ctx=ctx, volume=0.5)

class Music(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.song_queue = asyncio.Queue()
        self.current_song = None
        self.loop_task = None

    @commands.command(name='yt', aliases=['p'])
    async def yt(self, ctx, *, url):
        """曲をキューに追加します"""
        async with ctx.typing():
            player = await YTDLSource.from_url(url, loop=self.bot.loop, ctx=ctx, stream=True)

        await self.song_queue.put(player)
        await ctx.send(f'{player.title} をキューに追加しました。')

        if not self.loop_task or self.loop_task.done():
            self.loop_task = self.bot.loop.create_task(self.play_queue())

    async def play_queue(self):
        while True:
            if self.current_song is None:
                if self.song_queue.empty():
                    return
                self.current_song = await self.song_queue.get()
                ctx = self.current_song.ctx
                if ctx.voice_client is None:
                    if ctx.author.voice:
                        await ctx.author.voice.channel.connect()
                    else:
                        await ctx.send("ボイスチャンネルに接続されていません。")
                        return

                ctx.voice_client.play(self.current_song, after=lambda e: self.bot.loop.create_task(self.play_next(ctx, e)) if e else None)
                await ctx.send(f'再生中: {self.current_song.title}')
                self.current_song.is_playing_next = False

            elif self.current_song.ctx.voice_client.is_playing():
                await asyncio.sleep(1)

            else:  # 再生が終了した場合のみ次の曲へ移行
                await self.play_next(self.current_song.ctx, None)

            await asyncio.sleep(1)

    async def play_next(self, ctx, error):
        if error:
            print(f'Player error: {error}')
        else:
            self.current_song.is_playing_next = True
            while self.current_song.ctx.voice_client.is_playing():
                await asyncio.sleep(1)

            self.current_song = None
            if not self.song_queue.empty():
                await ctx.send('次の曲に移ります。')
                await self.play_queue()

    @commands.command(name='skip', aliases=['s'])
    async def skip(self, ctx):
        """次の曲に移ります"""
        if not ctx.voice_client.is_playing():
            return await ctx.send("再生中の曲がありません。")

        ctx.voice_client.stop()
        self.current_song = None

    @commands.command(name='volume', aliases=['v'])
    async def volume(self, ctx, volume: int):
        """ボリュームを変更します"""

        if ctx.voice_client is None:
            return await ctx.send("ボイスチャンネルに接続されていません。")

        ctx.voice_client.source.volume = volume / 100
        await ctx.send(f"ボリュームを {volume}% に変更しました。")

    @commands.command(name='stop', aliases=['dc'])
    async def stop(self, ctx):
        """BOTをボイスチャンネルから切断します"""

        await ctx.voice_client.disconnect()

    @commands.command(name='queue', aliases=['q'])
    async def queue_list(self, ctx):
        """キューの曲一覧を表示します"""
        if self.song_queue.empty():
            await ctx.send("キューは空です。")
        else:
            queue_list = "キュー:\n"
            queue_songs = list(self.song_queue._queue)  # キューの内容をリストに変換
            for song in queue_songs:
                queue_list += f"- {song.title}\n"
            await ctx.send(queue_list)

    @yt.before_invoke
    @skip.before_invoke
    @volume.before_invoke
    @stop.before_invoke
    @queue_list.before_invoke
    async def ensure_voice(self, ctx):
        if ctx.voice_client is None:
            if ctx.author.voice:
                await ctx.author.voice.channel.connect()
            else:
                await ctx.send("ボイスチャンネルに接続されていません。")
                raise commands.CommandError("ボイスチャンネルに接続されていません。")

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

bot = commands.Bot(
    command_prefix=commands.when_mentioned_or("!"),
    description='簡単な音楽BOTの例',
    intents=intents,
)

@bot.event
async def on_ready():
    print(f'{bot.user} (ID: {bot.user.id}) でログインしました')
    print('------')

async def main():
    async with bot:
        await bot.add_cog(Music(bot))
        await bot.start('')
        
asyncio.run(main()) 
1
4
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
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?