如何解决discord.py中‘str’对象无‘is_paused’属性的错误?
错误现象
执行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"))
问题分析
- 命令参数错误:
play命令定义时额外添加了self参数,discord.py会把用户输入的查询字符串赋值给这个self参数,导致self变成字符串类型,访问self.is_paused自然报错。 - 命令与Cog分离:所有音乐相关命令都定义在
music_cog类外部,无法正确访问类内部的is_paused、music_queue等属性,逻辑完全错误。 - 缺失依赖导入:代码中使用了
YoutubeDL但未导入,运行时会触发额外错误。 - 未注册Cog:注释掉了
client.add_cog(music_cog(client)),导致music_cog的逻辑从未被加载。
修复方案
核心修复步骤
- 将所有音乐命令移入
music_cog类内部,作为类方法,使用@commands.command装饰器。 - 移除
play命令中多余的self参数,类方法的self已指向Cog实例。 - 补充
YoutubeDL的导入(推荐用yt_dlp替代旧版youtube-dl)。 - 取消注释注册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

