LoginSignup
0
0

discordBOTの改良をした話

Last updated at Posted at 2024-04-07

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

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

改良点

・日本語化
・スキップ機能の追加
・キュー機能の追加
・ストリーミング化

コード

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

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,
    'default_search': 'auto',
    'source_address': '0.0.0.0',  # IPv6アドレスが問題を起こすことがあるため、IPv4にバインド
}

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 or (self.current_song.ctx.voice_client and not self.current_song.ctx.voice_client.is_playing()):
                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: print(f'Player error: {e}') if e else None)
                await ctx.send(f'再生中: {self.current_song.title}')

            await asyncio.sleep(1)

    @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())

まとめ

ちょっとは使い勝手よくなったかな~って思います!
他にも改良したらまたコードを記していきます。

0
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
0
0