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

Discord.py音乐机器人报错AttributeError: 'NoneType'无play属性求助

Discord音乐机器人播放时报错AttributeError: 'NoneType' object has no attribute 'play'

问题描述

我是编程新手,正在开发Discord音乐机器人,已经实现机器人加入、离开语音频道等功能,但播放音乐时出现错误AttributeError: 'NoneType' object has no attribute 'play'。我尝试查阅资料仍无法解决,此前已修复其他错误,现寻求帮助。相关代码如下:

@bot.command(name='playy', help='This command pauses the song')
async def playyt(ctx, url):
    song_there = os.path.isfile("song.mp3")
    try:
        if song_there:
            os.remove("song.mp3")
    except PermissionError:
        em8 = discord.Embed(title = "Music Is Currently Playing", description = 'Please wait for the current playing music to end or use %leave <:_Paimon6:827074349450133524>.\nMusic provided by {ctx.author.mention} <:_Paimon6:827074349450133524>',color = ctx.author.color)
        await ctx.send(embed = em8)
        return

    voiceChannel = discord.utils.get(ctx.guild.voice_channels)
    await voiceChannel.connect()
    voice = discord.utils.get(client.voice_clients, guild=ctx.guild)
    em6 = discord.Embed(title = "Downloading Youtube Music", description = f'{url}\n\nPlease wait for paimon to setup the music you provide.\nMusic provided by {ctx.author.mention} <:_Paimon6:827074349450133524>',color = ctx.author.color)
    await ctx.send(embed = em6, delete_after = 2)
    await ctx.message.delete()

    ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
            'preferredquality': '196',
        }],
    }
    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
        ydl.download([url])
    for file in os.listdir("./"):
        if file.endswith(".mp3"):
            os.rename(file, "song.mp3")
    voice.play(discord.FFmpegPCMAudio("song.mp3"))
    em1 = discord.Embed(title = "Now Listening Youtube Music", description = f'{url}\n\nPlease use %leave first to change music.\nMusic provided by {ctx.author.mention} <:_Paimon6:827074349450133524>',color = ctx.author.color)

    videoID = url.split("watch?v=")[1].split("&")[0]

    em1.set_thumbnail(url = f'https://img.youtube.com/vi/{videoID}/default.jpg'.format(videoID = videoID))
    await ctx.send(embed = em1)

问题分析与修复

核心错误原因

报错的直接原因是voice变量为None,导致调用voice.play()时触发AttributeError。问题出在这一行:

voice = discord.utils.get(client.voice_clients, guild=ctx.guild)

你的命令是用@bot.command定义的,但这里却用了client来获取语音客户端,而不是bot。如果你的机器人实例是bot,那么应该用bot.voice_clients,否则找不到对应的语音客户端,voice就会是None。

优化修复方案

除了修正client为bot,还有几个潜在问题需要处理,让代码更稳定:

  1. 获取用户所在的语音频道:原代码discord.utils.get(ctx.guild.voice_channels)会随机获取一个服务器语音频道,应该改为获取命令发起者所在的频道,同时处理用户不在频道的情况。
  2. 直接使用连接后的VoiceClient对象:调用await voiceChannel.connect()会返回对应的VoiceClient,无需再用discord.utils.get获取,更可靠。
  3. 增加文件存在判断:下载并重命名文件后,要确保song.mp3存在再播放。
  4. 处理FFmpeg路径问题:如果系统找不到FFmpeg可执行文件,需要指定其路径。

修复后的代码

import os
import discord
import youtube_dl

@bot.command(name='playy', help='This command plays the song from YouTube')
async def playyt(ctx, url):
    song_path = "song.mp3"
    song_there = os.path.isfile(song_path)
    
    # 处理已存在的音频文件
    try:
        if song_there:
            os.remove(song_path)
    except PermissionError:
        em8 = discord.Embed(
            title="Music Is Currently Playing",
            description=f'Please wait for the current playing music to end or use %leave <:_Paimon6:827074349450133524>.\nMusic provided by {ctx.author.mention} <:_Paimon6:827074349450133524>',
            color=ctx.author.color
        )
        await ctx.send(embed=em8)
        return
    
    # 检查用户是否在语音频道
    if not ctx.author.voice:
        em_error = discord.Embed(
            title="Error",
            description="You need to be in a voice channel to use this command!",
            color=discord.Color.red()
        )
        await ctx.send(embed=em_error)
        return
    
    # 连接到用户所在的语音频道
    voice_channel = ctx.author.voice.channel
    voice = await voice_channel.connect()
    
    # 发送下载提示
    em6 = discord.Embed(
        title="Downloading Youtube Music",
        description=f'{url}\n\nPlease wait for paimon to setup the music you provide.\nMusic provided by {ctx.author.mention} <:_Paimon6:827074349450133524>',
        color=ctx.author.color
    )
    await ctx.send(embed=em6, delete_after=2)
    await ctx.message.delete()
    
    # 下载音频
    ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
            'preferredquality': '196',
        }],
        'outtmpl': '%(title)s.%(ext)s',
    }
    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
        ydl.download([url])
    
    # 重命名下载的文件
    downloaded_file = None
    for file in os.listdir("./"):
        if file.endswith(".mp3"):
            downloaded_file = file
            break
    if not downloaded_file:
        em_error = discord.Embed(
            title="Download Failed",
            description="Failed to download or convert the audio file!",
            color=discord.Color.red()
        )
        await ctx.send(embed=em_error)
        await voice.disconnect()
        return
    
    os.rename(downloaded_file, song_path)
    
    # 播放音频(如果FFmpeg不在系统PATH,需要指定executable参数,比如executable="C:/ffmpeg/bin/ffmpeg.exe")
    voice.play(discord.FFmpegPCMAudio(song_path))
    
    # 发送播放提示
    em1 = discord.Embed(
        title="Now Listening Youtube Music",
        description=f'{url}\n\nPlease use %leave first to change music.\nMusic provided by {ctx.author.mention} <:_Paimon6:827074349450133524>',
        color=ctx.author.color
    )
    
    # 提取视频ID并设置缩略图
    try:
        if "watch?v=" in url:
            videoID = url.split("watch?v=")[1].split("&")[0]
        elif "/vi/" in url:
            videoID = url.split("/vi/")[1].split("/")[0]
        else:
            videoID = None
        
        if videoID:
            em1.set_thumbnail(url=f'https://img.youtube.com/vi/{videoID}/default.jpg')
    except IndexError:
        pass  # 忽略视频ID提取失败的情况
    
    await ctx.send(embed=em1)

关键修改说明

  • 直接使用voice_channel.connect()返回的voice对象,彻底避免None的问题。
  • 增加了用户是否在语音频道的检查,避免机器人连接到错误的频道。
  • 优化了文件下载和重命名的逻辑,增加了失败处理。
  • 增强了视频ID的提取逻辑,避免因URL格式问题报错。
  • 注释了FFmpeg路径的配置说明,解决可能的播放失败问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:18:09