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

Discord音乐机器人下载YouTube歌曲时文件0字节且无法完成

问题

我正在开发一款可播放本地目录歌曲的Discord音乐机器人,添加从YouTube下载歌曲到本地目录的命令后,执行命令会创建0字节的文件,且下载进程永不结束。

代码示例

require('dotenv').config();
const { Client, GatewayIntentBits, Events } = require('discord.js');
const { joinVoiceChannel, createAudioPlayer, createAudioResource, AudioPlayerStatus, getVoiceConnection } = require('@discordjs/voice');
const path = require('path');
const fs = require('fs');
const ytdl = require('ytdl-core');

// Criar cliente do Discord
const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildVoiceStates,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent
    ]
});

// Quando o bot estiver pronto
client.once(Events.ClientReady, () => {
    console.log(`${client.user.tag} está online!`);
});

client.on(Events.MessageCreate, async (message) => {
    if (!message.guild || message.author.bot) return;

    if (message.content.startsWith('!baixarmusica')) {
        const args = message.content.split(' ');
        const url = args[1];

        if (!ytdl.validateURL(url)) {
            return message.channel.send('Por favor, envie um link válido do YouTube.');
        }

        const info = await ytdl.getInfo(url);
        const title = info.videoDetails.title.replace(/[^\w\s]/gi, ''); // Nome do arquivo sem caracteres especiais

        const outputPath = path.resolve(__dirname, 'music', `${title}.mp3`);

        message.channel.send(`Baixando: **${title}**...`);

        try {
            // Stream de download e conversão para MP3 usando ffmpeg
            const stream = ytdl(url, { filter: 'audioonly' });

            // Salvando o áudio como arquivo .mp3 na pasta "music"
            const audioFile = fs.createWriteStream(outputPath);

            stream.pipe(audioFile);

            audioFile.on('finish', () => {
                message.channel.send(`Música **${title}** foi baixada e salva na pasta **music**.`);
            });

            audioFile.on('error', (err) => {
                console.error('Erro ao salvar o arquivo:', err);
                message.channel.send('Ocorreu um erro ao baixar a música.');
            });

        } catch (error) {
            console.error('Erro ao baixar a música:', error);
            message.channel.send('Houve um erro ao processar o link do YouTube.');
        }
    }
});

解决方法

1. 更新依赖并添加必要配置

YouTube频繁更新API策略,旧版本ytdl-core可能无法获取有效流。先执行命令更新依赖:

npm update ytdl-core

2. 修改下载逻辑,替换流处理方式

原生pipe无法妥善处理流错误和结束状态,改用stream/promises的pipeline方法,同时添加请求头模拟浏览器访问,避免被YouTube拦截:

require('dotenv').config();
const { Client, GatewayIntentBits, Events } = require('discord.js');
const { joinVoiceChannel, createAudioPlayer, createAudioResource, AudioPlayerStatus, getVoiceConnection } = require('@discordjs/voice');
const path = require('path');
const fs = require('fs');
const { pipeline } = require('stream/promises'); // 引入pipeline处理流
const ytdl = require('ytdl-core');

const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildVoiceStates,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent
    ]
});

client.once(Events.ClientReady, () => {
    console.log(`${client.user.tag} está online!`);
});

client.on(Events.MessageCreate, async (message) => {
    if (!message.guild || message.author.bot) return;

    if (message.content.startsWith('!baixarmusica')) {
        const args = message.content.split(' ');
        const url = args[1];

        if (!ytdl.validateURL(url)) {
            return message.channel.send('Por favor, envie um link válido do YouTube.');
        }

        try {
            const info = await ytdl.getInfo(url);
            const title = info.videoDetails.title.replace(/[^\w\s]/gi, '');
            const outputDir = path.resolve(__dirname, 'music');
            
            // 确保目标目录存在
            if (!fs.existsSync(outputDir)) {
                fs.mkdirSync(outputDir, { recursive: true });
            }
            const outputPath = path.join(outputDir, `${title}.mp3`);

            message.channel.send(`Baixando: **${title}**...`);

            // 配置ytdl参数,模拟浏览器请求并指定最高音频质量
            const stream = ytdl(url, {
                filter: 'audioonly',
                quality: 'highestaudio',
                requestOptions: {
                    headers: {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
                    }
                }
            });

            // 监听下载流错误,及时清理空文件
            stream.on('error', (err) => {
                console.error('下载流错误:', err);
                message.channel.send('下载过程中出现错误,请重试。');
                if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath);
            });

            // 使用pipeline处理流,自动处理结束和错误
            await pipeline(stream, fs.createWriteStream(outputPath));

            message.channel.send(`Música **${title}** foi baixada e salva na pasta **music**.`);

        } catch (error) {
            console.error('处理错误:', error);
            message.channel.send('处理YouTube链接时出现错误。');
        }
    }
});

3. 关键优化点

  • 用pipeline替代pipe:自动处理流的销毁、错误捕获,避免进程挂起
  • 添加浏览器请求头:绕过YouTube的反爬限制
  • 确保目录存在:避免因目录缺失导致写入失败
  • 监听下载流错误:及时捕获下载阶段的问题并清理无效文件

内容的提问来源于stack exchange,提问作者Gabriel Porto Terruya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 05:35:23