You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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)

问题根源

  1. 原队列仅存储音频源(source),未保存歌曲标题,导致后续播放时无法获取歌曲名发送提示
  2. check_queue函数仅负责取出队列中的音频源并播放,没有处理发送「Now playing」提示的逻辑
  3. 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)

修改说明

  1. 队列结构调整:将原队列存储的单一音频源改为(title, source)元组,保留歌曲标题信息
  2. check_queue函数增强:
    • 取出队列中的标题和音频源
    • 使用asyncio.run_coroutine_threadsafe在同步回调中执行异步消息发送(因为after回调运行在非asyncio事件循环线程,不能直接用await)
    • 递归调用自身处理下一首队列歌曲
  3. 修复标题获取逻辑:在搜索分支中先处理entries,避免原代码可能出现的标题获取错误
  4. 简化队列判断:用queue.get(guild_id)替代原queue[id] != {},更简洁且避免KeyError

内容的提问来源于stack exchange,提问作者iflookscouldkill

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 06:17:03