Discord音乐Bot执行$play命令触发AttributeError: 'NoneType'无play属性报错
问题排查与修复方案
异常根因定位
报错AttributeError: 'NoneType' object has no attribute 'play'的直接原因是**play命令作用域内的voice变量始终为None**,问题出在代码的作用域逻辑缺陷:
- 首次执行play命令时,Bot尚未加入任何语音频道,
get(client.voice_clients, guild=ctx.guild)返回None赋值给初始的voice变量 - 调用
join()函数时,函数内部在自己的局部作用域新建了语音连接对象,但没有将这个连接对象返回给play命令的外层作用域 - 外层作用域的voice变量仍然是初始的
None,直接调用voice.play()自然触发方法不存在的报错。
同步需要修复的潜在隐患
当前代码还有几处会导致运行报错的问题,需要同步修正:
search()函数定义的入参为query,但函数内部全部使用了未定义的arg变量,运行时会直接抛出NameError- 未判断命令发起者是否处于语音频道,用户不在频道时执行
join会触发空指针异常 - YoutubeDL配置中
noplaylist参数传入了字符串'True',实际该配置需要布尔值True才能生效 - 原逻辑直接取
info['formats'][0]['url'],该格式通常不是最佳音频流,会导致播放异常
具体修复操作
- 修改
join()函数,在完成语音连接/频道切换后,将最终的语音客户端对象返回 - 在play命令中调用
join()时,用返回值重新赋值voice变量,覆盖初始的None值 - 统一
search()函数的参数名,将所有未定义的arg替换为query - 增加前置校验:如果用户未加入语音频道,直接返回提示终止命令
- 修正YoutubeDL参数类型,直接使用ytdl提取的最佳音频流地址
修复后可运行的代码示例
import requests import discord from discord.ext import commands from discord import FFmpegPCMAudio from yt_dlp import YoutubeDL # 替换为你自己的Bot Token token = "你的Bot Token" client = commands.Bot(command_prefix='$') @client.event async def on_ready(): print('ready') def search(query): with YoutubeDL({'format': 'bestaudio', 'noplaylist': True}) as ydl: try: requests.get(query) info = ydl.extract_info(query, download=False) except: info = ydl.extract_info(f"ytsearch:{query}", download=False)['entries'][0] return info, info['url'] async def join(ctx, voice): channel = ctx.author.voice.channel if voice and voice.is_connected(): await voice.move_to(channel) else: voice = await channel.connect() # 返回最终的语音连接对象 return voice @client.command(pass_context=True) async def play(ctx, *, query): FFMPEG_OPTS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'} # 前置校验用户是否在语音频道 if not ctx.author.voice or not ctx.author.voice.channel: await ctx.send('你需要先加入一个语音频道才能播放音乐') return # 获取当前服务器已有的bot语音连接 voice = discord.utils.get(client.voice_clients, guild=ctx.guild) # 用join返回的对象更新voice变量 voice = await join(ctx, voice) if not voice: await ctx.send('无法连接到你的语音频道') return video, source = search(query) await ctx.send(f'正在播放: {video.get("title", "未知曲目")}') voice.play(FFmpegPCMAudio(source, **FFMPEG_OPTS), after=lambda e: print('done', e)) client.run(token)
内容的提问来源于stack exchange,提问作者pepega
相关产品推荐
相关产品推荐

