如何让Python Discord音乐机器人的音乐队列对各服务器独立?
解决Discord音乐机器人多服务器状态共享问题
核心思路
你的问题根源在于所有服务器共享了music_cog实例里的全局变量。解决方法是按服务器ID单独存储每个服务器的音乐状态,用字典映射每个服务器到它的专属状态数据。
具体实现步骤
1. 封装单服务器状态类
先创建一个内部类,把单个服务器需要的所有音乐状态变量封装进去:
class music_cog(commands.Cog): # 定义单服务器的音乐状态容器 class GuildMusicState: def __init__(self): self.found_title = "" self.found_duration = "" self.current_title = "" self.current_duration = "" self.current_mention = "" self.is_playing = False self.is_paused = False self.music_queue = [] self.vc = None # 存储当前服务器的语音连接对象 def __init__(self, bot): self.bot = bot # 用字典存储每个服务器的状态,键为服务器ID self.guild_states = {}
2. 添加状态获取辅助方法
写一个快捷方法,根据服务器ID获取对应的状态,不存在则自动创建:
def get_guild_state(self, guild_id): if guild_id not in self.guild_states: self.guild_states[guild_id] = self.GuildMusicState() return self.guild_states[guild_id]
3. 修改点歌逻辑
点歌时,根据当前服务器ID获取专属队列,再添加歌曲:
@commands.command(name="play") async def play(self, ctx, *, query): # 获取当前服务器的状态实例 state = self.get_guild_state(ctx.guild.id) # ... 你的歌曲搜索逻辑,最终得到song和voice_channel ... state.music_queue.append([song, voice_channel]) # 后续其他状态操作也全部替换为state.xxx
4. 重构播放逻辑
播放歌曲时,同样针对当前服务器的状态进行操作,注意回调里要传入服务器ID:
def play_next(self, guild_id): state = self.get_guild_state(guild_id) if len(state.music_queue) > 0: state.is_playing = True # 获取当前服务器队列的首条歌曲 m_url = state.music_queue[0][0]['source'] # 弹出队列 state.music_queue.pop(0) # 播放歌曲,回调传递服务器ID以处理后续队列 state.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next(guild_id)) # 更新当前播放信息 state.current_title = state.found_title state.current_duration = state.found_duration else: state.is_playing = False # 在play命令中触发播放的部分 async def play(self, ctx, *, query): state = self.get_guild_state(ctx.guild.id) # ... 前面的搜索、入队逻辑 ... if not state.is_playing: # 连接语音频道并保存到当前服务器状态 state.vc = await voice_channel.connect() # 启动播放,传入当前服务器ID self.play_next(ctx.guild.id)
5. 同步修改其他操作
所有涉及状态的命令(暂停、查看队列等)都要改为操作对应服务器的状态:
@commands.command(name="pause") async def pause(self, ctx): state = self.get_guild_state(ctx.guild.id) if state.is_playing and not state.is_paused: state.vc.pause() state.is_paused = True await ctx.send("播放已暂停") @commands.command(name="queue") async def queue(self, ctx): state = self.get_guild_state(ctx.guild.id) if len(state.music_queue) == 0: await ctx.send("当前队列为空") else: queue_str = "当前播放队列:\n" for i, item in enumerate(state.music_queue): queue_str += f"{i+1}. {item[0]['title']} - {item[0]['duration']}\n" await ctx.send(queue_str)
关键注意事项
- 必须在
after回调中传递服务器ID,否则回调无法识别要处理哪个服务器的队列。 - 每个服务器的语音连接对象
vc要存在对应状态里,避免不同服务器的连接互相干扰。 - 可选优化:当机器人离开服务器时,从
self.guild_states中删除对应状态,节省内存。
内容的提问来源于stack exchange,提问作者bricel27
相关产品推荐
相关产品推荐

