如何为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
相关产品推荐
相关产品推荐

