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

如何让discord.py音乐队列实现Guild隔离,避免跨服务器共享?

解决Discord音乐机器人的服务器队列隔离问题

问题背景

当前音乐机器人使用全局的queuee和songs列表存储队列,导致不同服务器(Guild)的队列互相共享,需要实现服务器级别的队列隔离,且不想使用数据库方案,寻求替代方法。

当前有问题的核心代码:

queuee = []
songs = []

@client.hybrid_command(
    name="play",
    with_app_command=True,
    description="Play a song in a voice channel with a url or keyword",
)
@commands.is_owner()
async def play(ctx, *, query):
    await ctx.send(f"Searching for {query.capitalize()}..")
    voice_channel = ctx.author.voice.channel
    vc = await voice_channel.connect()
    loop = asyncio.get_event_loop()
    data = await loop.run_in_executor(
        None, lambda: ytdl.extract_info(query, download=False)
    )
    if "entries" in data:
        video_format = data["entries"][0]["formats"][0]
    elif "formats" in data:
        video_format = data["formats"][0]
    song = video_format["url"]
    player = discord.FFmpegPCMAudio(song, **ffmpeg_options)
    vc.play(player, after=do_after)
    queuee.append(song)
    songs.append(query.capitalize())

替代数据库的解决方案

1. 使用字典映射服务器ID到队列(最简洁的内存方案)

把全局列表改成以**服务器ID(ctx.guild.id)**为键的字典,每个键对应该服务器的专属队列列表。这样不同服务器的队列完全隔离,数据存在内存中,重启机器人后会丢失,但适合不需要持久化的场景。

修改后的代码示例:

# 初始化字典,键是guild_id,值是对应服务器的音频URL队列
queues = {}
# 存储各服务器的歌曲名称列表
song_names = {}

@client.hybrid_command(
    name="play",
    with_app_command=True,
    description="Play a song in a voice channel with a url or keyword",
)
@commands.is_owner()
async def play(ctx, *, query):
    guild_id = ctx.guild.id
    # 若当前服务器无队列,初始化空列表
    if guild_id not in queues:
        queues[guild_id] = []
    if guild_id not in song_names:
        song_names[guild_id] = []
    
    await ctx.send(f"Searching for {query.capitalize()}..")
    voice_channel = ctx.author.voice.channel
    vc = await voice_channel.connect()
    loop = asyncio.get_event_loop()
    data = await loop.run_in_executor(
        None, lambda: ytdl.extract_info(query, download=False)
    )
    if "entries" in data:
        video_format = data["entries"][0]["formats"][0]
    elif "formats" in data:
        video_format = data["formats"][0]
    song = video_format["url"]
    player = discord.FFmpegPCMAudio(song, **ffmpeg_options)
    vc.play(player, after=do_after)
    
    # 将歌曲添加到当前服务器的专属队列
    queues[guild_id].append(song)
    song_names[guild_id].append(query.capitalize())

2. 封装成GuildPlayer类(更结构化的方案)

如果后续需要扩展更多服务器专属功能(比如当前播放状态、音量控制等),可以创建类封装每个服务器的播放器和队列,用字典管理这些实例,代码结构更清晰。

示例代码:

class GuildPlayer:
    def __init__(self):
        self.queue = []
        self.song_names = []
        self.vc = None  # 存储当前服务器的语音连接实例
        self.current_player = None

# 存储各服务器的播放器实例,键是guild_id
guild_players = {}

@client.hybrid_command(
    name="play",
    with_app_command=True,
    description="Play a song in a voice channel with a url or keyword",
)
@commands.is_owner()
async def play(ctx, *, query):
    guild_id = ctx.guild.id
    # 初始化当前服务器的播放器实例
    if guild_id not in guild_players:
        guild_players[guild_id] = GuildPlayer()
    
    player = guild_players[guild_id]
    await ctx.send(f"Searching for {query.capitalize()}..")
    voice_channel = ctx.author.voice.channel
    player.vc = await voice_channel.connect()
    loop = asyncio.get_event_loop()
    data = await loop.run_in_executor(
        None, lambda: ytdl.extract_info(query, download=False)
    )
    if "entries" in data:
        video_format = data["entries"][0]["formats"][0]
    elif "formats" in data:
        video_format = data["formats"][0]
    song = video_format["url"]
    player.current_player = discord.FFmpegPCMAudio(song, **ffmpeg_options)
    player.vc.play(player.current_player, after=do_after)
    
    # 添加到当前服务器的队列
    player.queue.append(song)
    player.song_names.append(query.capitalize())

3. 使用discord.py的Guild对象属性(可选)

可以给每个Guild对象动态添加自定义属性存储队列,但要注意discord.py的对象可能会被重新实例化,导致属性丢失,适合简单场景:

@client.hybrid_command(
    name="play",
    with_app_command=True,
    description="Play a song in a voice channel with a url or keyword",
)
@commands.is_owner()
async def play(ctx, *, query):
    guild = ctx.guild
    # 初始化队列属性
    if not hasattr(guild, 'queue'):
        setattr(guild, 'queue', [])
    if not hasattr(guild, 'song_names'):
        setattr(guild, 'song_names', [])
    
    await ctx.send(f"Searching for {query.capitalize()}..")
    voice_channel = ctx.author.voice.channel
    vc = await voice_channel.connect()
    loop = asyncio.get_event_loop()
    data = await loop.run_in_executor(
        None, lambda: ytdl.extract_info(query, download=False)
    )
    if "entries" in data:
        video_format = data["entries"][0]["formats"][0]
    elif "formats" in data:
        video_format = data["formats"][0]
    song = video_format["url"]
    player = discord.FFmpegPCMAudio(song, **ffmpeg_options)
    vc.play(player, after=do_after)
    
    guild.queue.append(song)
    guild.song_names.append(query.capitalize())

注意事项

  • 以上方案均为内存存储,机器人重启后队列数据会丢失,如果需要持久化,仍需使用数据库(如SQLite、MongoDB)或本地文件(如定期保存JSON)。
  • 回调函数do_after也要同步修改,确保从当前服务器的队列中取出下一首播放,避免跨服务器调用队列。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 00:16:11