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

如何解决discord.py中‘str’对象无‘is_paused’属性的错误?

解决discord.py音乐机器人play命令的AttributeError错误

错误现象

执行play命令时触发AttributeError,提示'str' object has no attribute 'is_paused',完整错误回溯如下:

[2023-02-19 05:14:12] [ERROR ] discord.ext.commands.bot: 忽略play命令中的异常
回溯(最近的调用最后):
文件 "C:\Users\toto\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\ext\commands\core.py",第229行,在wrapped中
ret = await coro(*args, **kwargs)
文件 "C:\Users\toto\PycharmProjects\pythonProject\music_bot-main\main.py",第101行,在play中
elif self.is_paused:
AttributeError: 'str' object has no attribute 'is_paused'

上述异常是导致以下异常的直接原因:

回溯(最近的调用最后):
文件 "C:\Users\toto\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\ext\commands\bot.py",第1349行,在invoke中
await ctx.command.invoke(ctx)
文件 "C:\Users\toto\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\ext\commands\core.py",第1023行,在invoke中
await injected(*ctx.args, **ctx.kwargs) # type: ignore
文件 "C:\Users\toto\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\ext\commands\core.py",第238行,在wrapped中
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'str' object has no attribute 'is_paused'

原问题代码

import discord
from discord.ext import commands
import os
import asyncio

# 导入所有cog模块
# from help_cog import help_cog
# from music_cog import music_cog

intents = discord.Intents.all()
client = commands.Bot(command_prefix='.', intents=intents)

@client.event
async def on_ready():
    print(f'Logged in as {client.user} (ID: {client.user.id})')
    print('------')


# 移除默认help命令,以便自定义
client.remove_command('help')


# 向机器人注册类
# bot.add_cog(help_cog(bot))
# client.add_cog(music_cog(client))
# 机器人代码
class music_cog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

        # 所有音乐相关配置
        self.is_playing = False
        self.is_paused = False

        # 二维数组,存储[歌曲, 频道]
        self.music_queue = []
        self.YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist': 'True'}
        self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
                               'options': '-vn'}

        self.vc = None

    # 在YouTube上搜索内容
    def search_yt(self, item):
        with YoutubeDL(self.YDL_OPTIONS) as ydl:
            try:
                info = ydl.extract_info("ytsearch:%s" % item, download=False)['entries'][0]
            except Exception:
                return False

        return {'source': info['formats'][0]['url'], 'title': info['title']}

    def play_next(self):
        if len(self.music_queue) > 0:
            self.is_playing = True

            # 获取第一个链接
            m_url = self.music_queue[0][0]['source']

            # 移除正在播放的首个元素
            self.music_queue.pop(0)

            self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
        else:
            self.is_playing = False

    # 循环检查播放状态
    async def play_music(self, ctx):
        if len(self.music_queue) > 0:
            self.is_playing = True

            m_url = self.music_queue[0][0]['source']

            # 若未连接语音频道则尝试连接
            if self.vc == None or not self.vc.is_connected():
                self.vc = await self.music_queue[0][1].connect()

                # 处理连接失败情况
                if self.vc == None:
                    await ctx.send("Could not connect to the voice channel")
                    return
            else:
                await self.vc.move_to(self.music_queue[0][1])

            # 移除正在播放的首个元素
            self.music_queue.pop(0)

            self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
        else:
            self.is_playing = False


@client.command(name="play", aliases=["p", "playing"], help="播放YouTube上选定的歌曲")
async def play(ctx, self, *args):
    query = " ".join(args)

    voice_channel = ctx.author.voice.channel
    if voice_channel is None:
        # 需要连接语音频道,机器人才能知道前往位置
        await ctx.send("请连接到语音频道!")
    elif self.is_paused:
        self.vc.resume()
    else:
        song = self.search_yt(query)
        if type(song) == type(True):
            await ctx.send(
                "无法下载该歌曲。请尝试其他关键词,可能是播放列表或直播格式导致。")
        else:
            await ctx.send("歌曲已添加到队列")
            self.music_queue.append([song, voice_channel])

            if self.is_playing == False:
                await self.play_music(ctx)


@client.command(name="pause", help="暂停当前播放的歌曲")
async def pause(self, ctx, *args):
    if self.is_playing:
        self.is_playing = False
        self.is_paused = True
        self.vc.pause()
    elif self.is_paused:
        self.is_paused = False
        self.is_playing = True
        self.vc.resume()


@client.command(name="resume", aliases=["r"], help="恢复机器人的音乐播放")
async def resume(self, ctx, *args):
    if self.is_paused:
        self.is_paused = False
        self.is_playing = True
        self.vc.resume()


@client.command(name="skip", aliases=["s"], help="跳过当前播放的歌曲")
async def skip(self, ctx):
    if self.vc != None and self.vc:
        self.vc.stop()
        # 若队列中有歌曲则尝试播放下一首
        await self.play_music(ctx)


@client.command(name="queue", aliases=["q"], help="显示当前队列中的歌曲")
async def queue(self, ctx):
    retval = ""
    for i in range(0, len(self.music_queue)):
        # 最多显示队列中的5首歌曲
        if (i > 4): break
        retval += self.music_queue[i][0]['title'] + "\n"

    if retval != "":
        await ctx.send(retval)
    else:
        await ctx.send("队列中无音乐")


@client.command(name="clear", aliases=["c", "bin"], help="停止音乐并清空队列")
async def clear(self, ctx):
    if self.vc != None and self.is_playing:
        self.vc.stop()
    self.music_queue = []
    await ctx.send("音乐队列已清空")


@client.command(name="leave", aliases=["disconnect", "l", "d"], help="将机器人移出语音频道")
async def dc(self, ctx):
    self.is_playing = False
    self.is_paused = False
    await self.vc.disconnect()


@client.command()
async def bonjour(ctx):
    await ctx.send("Bonjour")


# 使用令牌启动机器人

client.run(os.getenv("TOKEN"))

问题分析

  1. 命令参数错误:play命令定义时额外添加了self参数,discord.py会把用户输入的查询字符串赋值给这个self参数,导致self变成字符串类型,访问self.is_paused自然报错。
  2. 命令与Cog分离:所有音乐相关命令都定义在music_cog类外部,无法正确访问类内部的is_paused、music_queue等属性,逻辑完全错误。
  3. 缺失依赖导入:代码中使用了YoutubeDL但未导入,运行时会触发额外错误。
  4. 未注册Cog:注释掉了client.add_cog(music_cog(client)),导致music_cog的逻辑从未被加载。

修复方案

核心修复步骤

  1. 将所有音乐命令移入music_cog类内部,作为类方法,使用@commands.command装饰器。
  2. 移除play命令中多余的self参数,类方法的self已指向Cog实例。
  3. 补充YoutubeDL的导入(推荐用yt_dlp替代旧版youtube-dl)。
  4. 取消注释注册Cog的代码,加载音乐逻辑。

修复后的完整代码

import discord
from discord.ext import commands
import os
import asyncio
from yt_dlp import YoutubeDL  # 补充导入依赖

intents = discord.Intents.all()
client = commands.Bot(command_prefix='.', intents=intents)

@client.event
async def on_ready():
    print(f'Logged in as {client.user} (ID: {client.user.id})')
    print('------')

# 移除默认help命令
client.remove_command('help')

class music_cog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.is_playing = False
        self.is_paused = False
        self.music_queue = []
        self.YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist': 'True'}
        self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
                               'options': '-vn'}
        self.vc = None

    def search_yt(self, item):
        with YoutubeDL(self.YDL_OPTIONS) as ydl:
            try:
                info = ydl.extract_info(f"ytsearch:{item}", download=False)['entries'][0]
            except Exception:
                return False
        return {'source': info['formats'][0]['url'], 'title': info['title']}

    def play_next(self):
        if len(self.music_queue) > 0:
            self.is_playing = True
            m_url = self.music_queue[0][0]['source']
            self.music_queue.pop(0)
            self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
        else:
            self.is_playing = False

    async def play_music(self, ctx):
        if len(self.music_queue) > 0:
            self.is_playing = True
            m_url = self.music_queue[0][0]['source']
            if self.vc is None or not self.vc.is_connected():
                self.vc = await self.music_queue[0][1].connect()
                if self.vc is None:
                    await ctx.send("无法连接到语音频道")
                    return
            else:
                await self.vc.move_to(self.music_queue[0][1])
            self.music_queue.pop(0)
            self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
        else:
            self.is_playing = False

    # 音乐命令移入类内部
    @commands.command(name="play", aliases=["p", "playing"], help="播放YouTube上选定的歌曲")
    async def play(self, ctx, *args):
        query = " ".join(args)
        voice_channel = ctx.author.voice.channel
        if voice_channel is None:
            await ctx.send("请连接到语音频道!")
        elif self.is_paused:
            self.vc.resume()
        else:
            song = self.search_yt(query)
            if isinstance(song, bool):
                await ctx.send("无法下载该歌曲。请尝试其他关键词,可能是播放列表或直播格式导致。")
            else:
                await ctx.send("歌曲已添加到队列")
                self.music_queue.append([song, voice_channel])
                if not self.is_playing:
                    await self.play_music(ctx)

    @commands.command(name="pause", help="暂停当前播放的歌曲")
    async def pause(self, ctx):
        if self.is_playing:
            self.is_playing = False
            self.is_paused = True
            self.vc.pause()
        elif self.is_paused:
            self.is_paused = False
            self.is_playing = True
            self.vc.resume()

    @commands.command(name="resume", aliases=["r"], help="恢复机器人的音乐播放")
    async def resume(self, ctx):
        if self.is_paused:
            self.is_paused = False
            self.is_playing = True
            self.vc.resume()

    @commands.command(name="skip", aliases=["s"], help="跳过当前播放的歌曲")
    async def skip(self, ctx):
        if self.vc is not None and self.vc.is_connected():
            self.vc.stop()
            await self.play_music(ctx)

    @commands.command(name="queue", aliases=["q"], help="显示当前队列中的歌曲")
    async def queue(self, ctx):
        retval = ""
        for i in range(min(len(self.music_queue), 5)):
            retval += self.music_queue[i][0]['title'] + "\n"
        if retval:
            await ctx.send(retval)
        else:
            await ctx.send("队列中无音乐")

    @commands.command(name="clear", aliases=["c", "bin"], help="停止音乐并清空队列")
    async def clear(self, ctx):
        if self.vc is not None and self.is_playing:
            self.vc.stop()
        self.music_queue = []
        await ctx.send("音乐队列已清空")

    @commands.command(name="leave", aliases=["disconnect", "l", "d"], help="将机器人移出语音频道")
    async def dc(self, ctx):
        self.is_playing = False
        self.is_paused = False
        if self.vc is not None and self.vc.is_connected():
            await self.vc.disconnect()
            self.vc = None

@client.command()
async def bonjour(ctx):
    await ctx.send("Bonjour")

# 注册音乐Cog
client.add_cog(music_cog(client))

# 启动机器人
client.run(os.getenv("TOKEN"))

额外说明

  • 安装依赖:执行pip install yt-dlp discord.py[voice]以获取必要的语音和视频解析支持。
  • 运行前需设置环境变量TOKEN为你的Discord机器人令牌。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 09:51:54