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

如何为discord.py音乐机器人添加队列命令及管理功能

discord.py 音乐机器人队列功能实现方案

核心修改思路

  • 为每个服务器独立维护播放队列,避免跨服务器数据冲突
  • 新增播放结束回调函数,当前歌曲播放完成后自动触发下一首歌曲加载
  • 新增队列查看、删除、顺序调整专属命令

完整修改后代码

import os
import discord
import asyncio
from discord.ext import commands
from dotenv import load_dotenv
from keep_alive import keep_alive
from youtube_dl import YoutubeDL
from discord.utils import get

load_dotenv()
TOKEN = os.getenv('TOKEN')

bot = commands.Bot(command_prefix='!', case_insensitive=True)
# 全局队列存储:key为服务器ID,value为待播歌曲列表,单条数据格式为{"title": 歌曲名, "url": 音频流地址}
queues = {}
# 固定配置抽离为全局变量,避免重复初始化
YDL_OPTIONS = {'format': 'bestaudio/best', 'noplaylist':'True'}
FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'}

# 播放结束回调,触发切歌
def after_play(ctx, error):
    if error:
        print(f"播放出错: {error}")
    # 同步回调中转异步切歌逻辑
    fut = asyncio.run_coroutine_threadsafe(play_next(ctx), bot.loop)
    try:
        fut.result()
    except Exception as e:
        print(f"切歌出错: {e}")

# 播放队列下一首歌曲
async def play_next(ctx):
    guild_id = ctx.guild.id
    voice = get(bot.voice_clients, guild=ctx.guild)
    if guild_id in queues and len(queues[guild_id]) > 0:
        next_song = queues[guild_id].pop(0)
        source = await discord.FFmpegOpusAudio.from_probe(next_song['url'], **FFMPEG_OPTIONS)
        voice.play(source, after=lambda e: after_play(ctx, e))
        await ctx.send(f"现在播放: {next_song['title']}")
    else:
        await ctx.send("队列已全部播放完毕")

@bot.event
async def on_ready():
    print(f'{bot.user} has successfully connected to Discord!')

@bot.command(aliases=['p'])
async def play(ctx, url: str = None):
    guild_id = ctx.guild.id
    voice = get(bot.voice_clients, guild=ctx.guild)
    # 无链接参数时直接播放队列内歌曲
    if not url:
        if voice and (voice.is_paused() or not voice.is_playing()) and guild_id in queues and len(queues[guild_id])>0:
            await play_next(ctx)
            return
        await ctx.send("请提供歌曲链接")
        return
    # 校验用户是否在语音频道
    try:
        channel = ctx.message.author.voice.channel
    except AttributeError:
        await ctx.send('Bruh, join a voice channel')
        return None
    # 机器人未加入频道时自动加入
    if not voice:
        await channel.connect()
        await ctx.send("Music/Audio will begin shortly, unless no URL provided.")
    voice = get(bot.voice_clients, guild=ctx.guild)
    # 解析歌曲信息
    with YoutubeDL(YDL_OPTIONS) as ydl:
        info = ydl.extract_info(url, download=False)
        song_info = {
            "title": info['title'],
            "url": info['formats'][0]['url']
        }
    # 正在播放/暂停状态时加入队列
    if voice.is_playing() or voice.is_paused():
        if guild_id not in queues:
            queues[guild_id] = []
        queues[guild_id].append(song_info)
        await ctx.send(f"已将 {song_info['title']} 加入队列,当前队列第{len(queues[guild_id])}位")
        return
    # 空闲状态直接播放
    source = await discord.FFmpegOpusAudio.from_probe(song_info['url'], **FFMPEG_OPTIONS)
    voice.play(source, after=lambda e: after_play(ctx, e))
    await ctx.send(f"正在播放: {song_info['title']}")
          
@bot.command()
async def pause(ctx):
    voice = discord.utils.get(bot.voice_clients, guild=ctx.guild)
    if voice.is_playing():
      voice.pause()
      await ctx.send("Audio is paused.")
    else:
      await ctx.send("Currently no audio is playing.")

@bot.command()
async def resume(ctx):
    voice = discord.utils.get(bot.voice_clients, guild=ctx.guild)
    if voice.is_paused():
      voice.resume()
      await ctx.send("Resumed audio.")
    else:
      await ctx.send("The audio is not paused.")

@bot.command()
async def stop(ctx):
    voice = discord.utils.get(bot.voice_clients, guild=ctx.guild)
    if voice.is_playing() or voice.is_paused():
        voice.stop()
        await ctx.send("Stopped playback.")
    else:
        await ctx.send("当前没有正在播放的音频")

@bot.command(aliases=['l'])  
async def leave(ctx):
    voice = discord.utils.get(bot.voice_clients, guild=ctx.guild)
    guild_id = ctx.guild.id
    # 离开时清空对应服务器队列
    if guild_id in queues:
        del queues[guild_id]
    await voice.disconnect()
    await ctx.send("Left voice channel.")

# 队列查看命令
@bot.command(aliases=['q'])
async def queue(ctx):
    guild_id = ctx.guild.id
    if guild_id not in queues or len(queues[guild_id]) == 0:
        await ctx.send("当前队列为空")
        return
    queue_text = "当前待播队列:\n"
    for idx, song in enumerate(queues[guild_id], 1):
        queue_text += f"{idx}. {song['title']}\n"
    await ctx.send(queue_text)

# 队列歌曲删除命令
@bot.command(aliases=['rm'])
async def remove(ctx, index: int):
    guild_id = ctx.guild.id
    if guild_id not in queues or len(queues[guild_id]) == 0:
        await ctx.send("当前队列为空")
        return
    if index < 1 or index > len(queues[guild_id]):
        await ctx.send("无效的队列序号")
        return
    removed_song = queues[guild_id].pop(index-1)
    await ctx.send(f"已移除队列第{index}位歌曲:{removed_song['title']}")

# 队列歌曲顺序调整命令
@bot.command(aliases=['mv'])
async def move(ctx, origin_idx: int, target_idx: int):
    guild_id = ctx.guild.id
    if guild_id not in queues or len(queues[guild_id]) == 0:
        await ctx.send("当前队列为空")
        return
    max_idx = len(queues[guild_id])
    if origin_idx < 1 or origin_idx > max_idx or target_idx < 1 or target_idx > max_idx:
        await ctx.send("无效的队列序号")
        return
    song = queues[guild_id].pop(origin_idx-1)
    queues[guild_id].insert(target_idx-1, song)
    await ctx.send(f"已将歌曲 {song['title']} 从第{origin_idx}位移至第{target_idx}位")

keep_alive()
bot.run(TOKEN)

功能使用说明

  • 队列自动加入:正在播放歌曲时调用!p 歌曲链接,歌曲会自动加入待播队列,当前歌曲结束后自动按顺序播放
  • 队列查看:调用!q或!queue即可查看当前服务器全部待播歌曲及对应序号
  • 队列删除:调用!rm 序号即可删除指定位置的待播歌曲
  • 队列顺序调整:调用!mv 原序号 目标序号即可将指定歌曲移动到目标位置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 00:57:00