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
相关产品推荐
相关产品推荐

