如何让discord.py音乐机器人播放队列歌曲时发送当前播放提示?
Discord音乐机器人队列播放提示修复方案
问题描述
我基于discord.py开发的Discord音乐机器人合并了队列(queue)与播放(play)命令,仅需使用play命令即可添加歌曲至队列。当前存在问题:机器人仅在播放第一首歌曲时发送「Now playing: (歌曲名)」提示,后续队列歌曲自动播放时无消息,希望每首新队列歌曲播放时都发送该提示。原代码如下:
queue = {} @commands.command(pass_context = True) async def play(self, ctx, *, info): def check_queue(ctx, id): if queue[id] != {}: voice_client = ctx.guild.voice_client source = queue[id].pop(0) voice_client.play(source, after=lambda x=0: check_queue(ctx, ctx.message.guild.id)) try: voice_channel = ctx.author.voice.channel # checks if user is in voice channel channel = ctx.message.author.voice.channel VOICE_CHANNELS[channel.id] = ctx.channel guild_id = ctx.message.guild.id except AttributeError: return await ctx.send("Dush! Please Join a Channel.") # user is not in a voice channel voice_client = ctx.guild.voice_client if not voice_client: await voice_channel.connect() voice_client = discord.utils.get(self.bot.voice_clients, guild=ctx.guild) loop = asyncio.get_event_loop() try: data = await loop.run_in_executor(None, lambda: ytdl.extract_info(info, download=False)) title = data["title"] # get title song = data["url"] # get url if "entries" in data: # checks for playlist data = data["entries"][0] # if its a playlist, we get the first item except Exception as e: data = await loop.run_in_executor(None, lambda: ytdl.extract_info("ytsearch:" + info, download=False)) song = data["formats"][0]["url"] title = data["formats"][0]["title"] if 'entries' in data: data = data['entries'][0] try: source = discord.FFmpegPCMAudio(source=song,**ffmpeg_options, executable="ffmpeg") if voice_client.is_playing(): if guild_id in queue: queue[guild_id].append(source) else: queue[guild_id] = [source] if len(queue) >= 1: await ctx.send(f"{title} added to queue.") else: voice_client.play(source, after=lambda x=0: check_queue(ctx, ctx.message.guild.id)) await ctx.send(f'**Now playing:** {title}') except Exception as e: print(e)
问题根源
- 原队列仅存储音频源(
source),未保存歌曲标题,导致后续播放时无法获取歌曲名发送提示 check_queue函数仅负责取出队列中的音频源并播放,没有处理发送「Now playing」提示的逻辑after回调是同步函数,不能直接在其中使用await发送异步消息
修改后的代码
import asyncio import discord from discord.ext import commands # 队列改为存储(标题, 音频源)的元组列表 queue = {} @commands.command(pass_context=True) async def play(self, ctx, *, info): def check_queue(ctx, guild_id): if queue.get(guild_id): voice_client = ctx.guild.voice_client # 取出标题和音频源 title, source = queue[guild_id].pop(0) # 在同步回调中执行异步消息发送,需要用run_coroutine_threadsafe asyncio.run_coroutine_threadsafe( ctx.send(f'**Now playing:** {title}'), self.bot.loop ) # 继续播放下一首,递归调用check_queue voice_client.play(source, after=lambda x=0: check_queue(ctx, guild_id)) try: voice_channel = ctx.author.voice.channel channel = ctx.message.author.voice.channel VOICE_CHANNELS[channel.id] = ctx.channel guild_id = ctx.message.guild.id except AttributeError: return await ctx.send("Dush! Please Join a Channel.") voice_client = ctx.guild.voice_client if not voice_client: await voice_channel.connect() voice_client = discord.utils.get(self.bot.voice_clients, guild=ctx.guild) loop = asyncio.get_event_loop() try: data = await loop.run_in_executor(None, lambda: ytdl.extract_info(info, download=False)) title = data["title"] song = data["url"] if "entries" in data: data = data["entries"][0] except Exception as e: data = await loop.run_in_executor(None, lambda: ytdl.extract_info("ytsearch:" + info, download=False)) # 修复此处的title获取逻辑,原代码可能出错,应该从entries里取 if 'entries' in data: data = data['entries'][0] song = data["formats"][0]["url"] title = data["title"] try: source = discord.FFmpegPCMAudio(source=song, **ffmpeg_options, executable="ffmpeg") if voice_client.is_playing(): # 将标题和音频源一起加入队列 if guild_id in queue: queue[guild_id].append((title, source)) else: queue[guild_id] = [(title, source)] await ctx.send(f"{title} added to queue.") else: voice_client.play(source, after=lambda x=0: check_queue(ctx, guild_id)) await ctx.send(f'**Now playing:** {title}') except Exception as e: print(e)
修改说明
- 队列结构调整:将原队列存储的单一音频源改为
(title, source)元组,保留歌曲标题信息 - check_queue函数增强:
- 取出队列中的标题和音频源
- 使用
asyncio.run_coroutine_threadsafe在同步回调中执行异步消息发送(因为after回调运行在非asyncio事件循环线程,不能直接用await) - 递归调用自身处理下一首队列歌曲
- 修复标题获取逻辑:在搜索分支中先处理entries,避免原代码可能出现的标题获取错误
- 简化队列判断:用
queue.get(guild_id)替代原queue[id] != {},更简洁且避免KeyError
内容的提问来源于stack exchange,提问作者iflookscouldkill
相关产品推荐
相关产品推荐

