diff --git a/commands/play.js b/commands/play.js index 7dc0cc2..4332ad2 100644 --- a/commands/play.js +++ b/commands/play.js @@ -3,7 +3,7 @@ const { v4: uuidv4 } = require('uuid'); const path = require('path'); const { EmbedBuilder } = require('discord.js'); const fs = require('fs'); -const { exec, spawn, execSync } = require('child_process'); +const { exec, execSync } = require('child_process'); const MAX_RETRIES = 3; const RETRY_DELAY = 1000; @@ -45,31 +45,27 @@ module.exports = { const searchQuery = args.join(' '); const voiceChannel = message.member.voice.channel; - console.log(`Received command: play ${searchQuery}`); - console.log(`Voice channel: ${voiceChannel ? voiceChannel.name : 'None'}`); - if (!voiceChannel) { - console.error('User is not in a voice channel.'); return message.reply('You need to be in a voice channel to play music!'); } - let title, tempFilePath, videoUrl = null, thumbnailUrl = null; + const isPlaylist = searchQuery.includes("list="); let loadingMessage; - try { - const getDuration = (filePath) => { - try { - const output = execSync(`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${filePath}"`).toString().trim(); - const durationSeconds = parseFloat(output); - const minutes = Math.floor(durationSeconds / 60); - const seconds = Math.floor(durationSeconds % 60); - return `${minutes}:${seconds.toString().padStart(2, '0')}`; - } catch (error) { - console.error('Error getting track duration:', error); - return 'Unknown'; - } - }; + const getDuration = (filePath) => { + try { + const output = execSync(`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${filePath}"`).toString().trim(); + const durationSeconds = parseFloat(output); + const minutes = Math.floor(durationSeconds / 60); + const seconds = Math.floor(durationSeconds % 60); + return `${minutes}:${seconds.toString().padStart(2, '0')}`; + } catch (error) { + console.error('Error getting track duration:', error); + return 'Unknown'; + } + }; + try { if (message.attachments.size > 0) { const attachment = message.attachments.first(); const attachmentName = attachment.name.toLowerCase(); @@ -78,15 +74,10 @@ module.exports = { title = attachment.name; tempFilePath = path.join(__dirname, '../utils/tmp', attachment.name); - console.log(`Attachment received: ${title}`); - console.log(`Downloading attachment to: ${tempFilePath}`); - const response = await fetch(attachment.url); const buffer = await response.buffer(); fs.writeFileSync(tempFilePath, buffer); - console.log(`Downloaded and saved: ${tempFilePath}`); - const duration = getDuration(tempFilePath); const embed = new EmbedBuilder() @@ -98,7 +89,6 @@ module.exports = { message.channel.send({ embeds: [embed] }); - console.log('Adding to queue and attempting to play.'); addToQueue(message.guild.id, tempFilePath, title, voiceChannel, null, message.author.username, message.author.displayAvatarURL(), null); playNextInQueue(message.guild.id); return; @@ -107,9 +97,6 @@ module.exports = { const convertedFileName = `${uuidv4()}.mp3`; tempFilePath = path.join(__dirname, '../utils/tmp', convertedFileName); - console.log(`Attachment received: ${title}`); - console.log(`Converting and downloading attachment to: ${tempFilePath}`); - const response = await fetch(attachment.url); const buffer = await response.buffer(); const tempVideoPath = path.join(__dirname, '../utils/tmp', attachment.name); @@ -117,12 +104,10 @@ module.exports = { spawnFFmpegProcess(['-i', tempVideoPath, '-f', 'mp3', '-ab', '192000', '-vn', tempFilePath], (err) => { if (err) { - console.error('Error converting file:', err); message.reply('Failed to convert the video file.'); return; } - console.log(`Converted and saved: ${tempFilePath}`); fs.unlinkSync(tempVideoPath); const duration = getDuration(tempFilePath); @@ -136,14 +121,12 @@ module.exports = { message.channel.send({ embeds: [embed] }); - console.log('Adding to queue and attempting to play.'); addToQueue(message.guild.id, tempFilePath, title, voiceChannel, null, message.author.username, message.author.displayAvatarURL(), null); playNextInQueue(message.guild.id); }); return; } else { - console.error('Attachment is not a supported media file.'); return message.reply('Only MP3, MP4, WEBM, and MOV files are supported for uploads.'); } } @@ -153,16 +136,11 @@ module.exports = { title = path.basename(searchQuery.split('?')[0]); tempFilePath = path.join(__dirname, '../utils/tmp', `${uuidv4()}_${title}`); - console.log(`MP3 link received: ${searchQuery}`); - console.log(`Downloading MP3 to: ${tempFilePath}`); - const response = await fetch(searchQuery); if (!response.ok) throw new Error('Failed to download MP3 file.'); const buffer = await response.buffer(); fs.writeFileSync(tempFilePath, buffer); - console.log(`Downloaded and saved: ${tempFilePath}`); - const duration = getDuration(tempFilePath); const embed = new EmbedBuilder() @@ -174,7 +152,6 @@ module.exports = { message.channel.send({ embeds: [embed] }); - console.log('Adding to queue and attempting to play.'); addToQueue(message.guild.id, tempFilePath, title, voiceChannel, null, message.author.username, message.author.displayAvatarURL(), null); playNextInQueue(message.guild.id); return; @@ -185,9 +162,6 @@ module.exports = { const convertedFileName = `${uuidv4()}.${fileExtension}`; tempFilePath = path.join(__dirname, '../utils/tmp', convertedFileName); - console.log(`Discord media link received: ${searchQuery}`); - console.log(`Downloading media from Discord to: ${tempFilePath}`); - const response = await fetch(searchQuery); if (!response.ok) throw new Error('Failed to download media file from Discord.'); const buffer = await response.buffer(); @@ -198,12 +172,10 @@ module.exports = { spawnFFmpegProcess(['-i', tempVideoPath, '-f', 'mp3', '-ab', '192000', '-vn', tempFilePath], (err) => { if (err) { - console.error('Error converting file:', err); message.reply('Failed to convert the video file.'); return; } - console.log(`Converted and saved: ${tempFilePath}`); fs.unlinkSync(tempVideoPath); const duration = getDuration(tempFilePath); @@ -217,14 +189,12 @@ module.exports = { message.channel.send({ embeds: [embed] }); - console.log('Adding to queue and attempting to play.'); addToQueue(message.guild.id, tempFilePath, title, voiceChannel, null, message.author.username, message.author.displayAvatarURL(), null); playNextInQueue(message.guild.id); }); } else { fs.writeFileSync(tempFilePath, buffer); - console.log(`Downloaded and saved: ${tempFilePath}`); const duration = getDuration(tempFilePath); @@ -237,18 +207,34 @@ module.exports = { message.channel.send({ embeds: [embed] }); - console.log('Adding to queue and attempting to play.'); addToQueue(message.guild.id, tempFilePath, title, voiceChannel, null, message.author.username, message.author.displayAvatarURL(), null); playNextInQueue(message.guild.id); } return; - } else { - console.log(`YouTube link received: ${searchQuery}`); - videoUrl = searchQuery; + } else if (isPlaylist) { + loadingMessage = await message.channel.send(`Loading playlist...`); - exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --print-json --skip-download ${searchQuery}`, async (error, stdout, stderr) => { + exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --flat-playlist --print-json "${searchQuery}"`, async (error, stdout) => { + if (error) { + message.reply('Failed to retrieve playlist info.'); + return; + } + + const playlist = stdout.split('\n').filter(Boolean).map(line => JSON.parse(line)); + if (loadingMessage) await loadingMessage.delete(); + + message.channel.send(`Adding ${playlist.length} songs to the queue...`); + + for (const video of playlist) { + const videoUrl = `https://www.youtube.com/watch?v=${video.id}`; + await addVideoToQueue(videoUrl, message, voiceChannel, false); + } + + playNextInQueue(message.guild.id); + }); + } else { + exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --print-json --skip-download "${searchQuery}"`, async (error, stdout) => { if (error) { - console.error(`Error getting video info: ${error}`); message.reply('Failed to retrieve video info.'); return; } @@ -264,15 +250,12 @@ module.exports = { tempFilePath = path.join(__dirname, '../utils/tmp', `${uuidv4()}.mp3`); - exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --format bestaudio --output "${tempFilePath}" ${searchQuery}`, async (error, stdout, stderr) => { + exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --format bestaudio --output "${tempFilePath}" "${searchQuery}"`, async (error, stdout, stderr) => { if (error) { - console.error(`Error downloading file: ${error}`); message.reply('Failed to download audio file.'); return; } - console.log(`Downloaded and saved: ${tempFilePath}`); - const duration = getDuration(tempFilePath); if (loadingMessage) { @@ -289,60 +272,24 @@ module.exports = { message.channel.send({ embeds: [embed] }); - console.log('Adding to queue and attempting to play.'); - addToQueue(message.guild.id, tempFilePath, title, voiceChannel, videoUrl, message.author.username, message.author.displayAvatarURL(), thumbnailUrl); + addToQueue(message.guild.id, tempFilePath, title, voiceChannel, searchQuery, message.author.username, message.author.displayAvatarURL(), thumbnailUrl); playNextInQueue(message.guild.id); }); }); } } else { loadingMessage = await message.channel.send(`Searching for: **${searchQuery}**...`); - - console.log(`Performing YouTube search: ${searchQuery}`); - exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --dump-single-json "ytsearch:${searchQuery}"`, (error, stdout, stderr) => { + exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --dump-single-json "ytsearch:${searchQuery}"`, async (error, stdout) => { if (error) { - console.error(`Error searching: ${error}`); message.reply('Failed to search for video.'); return; } const info = JSON.parse(stdout); - const url = info.entries[0].webpage_url; - title = info.entries[0].title; - thumbnailUrl = info.entries[0].thumbnail; + const videoUrl = info.entries[0].webpage_url; + await addVideoToQueue(videoUrl, message, voiceChannel, true); - tempFilePath = path.join(__dirname, '../utils/tmp', `${uuidv4()}.mp3`); - console.log(`Downloading file to: ${tempFilePath}`); - - exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --format bestaudio --output "${tempFilePath}" ${url}`, async (error, stdout, stderr) => { - if (error) { - console.error(`Error downloading file: ${error}`); - message.reply('Failed to download audio file.'); - return; - } - - console.log(`Downloaded and saved: ${tempFilePath}`); - - const duration = getDuration(tempFilePath); - - if (loadingMessage) { - await loadingMessage.delete(); - } - - const embed = new EmbedBuilder() - .setColor('#0099ff') - .setTitle('Added To Queue') - .setDescription(`**${title}** (${duration})`) - .setThumbnail(thumbnailUrl) - .setFooter({ text: `Requested by ${message.author.username}`, iconURL: message.author.displayAvatarURL() }) - .setTimestamp(); - - message.channel.send({ embeds: [embed] }); - - console.log('Adding to queue and attempting to play.'); - addToQueue(message.guild.id, tempFilePath, title, voiceChannel, url, message.author.username, message.author.displayAvatarURL(), thumbnailUrl); - playNextInQueue(message.guild.id); - }); + if (loadingMessage) await loadingMessage.delete(); }); } @@ -354,6 +301,43 @@ module.exports = { }, }; +async function addVideoToQueue(videoUrl, message, voiceChannel, sendEmbed = true) { + const tempFilePath = path.join(__dirname, '../utils/tmp', `${uuidv4()}.mp3`); + + exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --print-json --skip-download "${videoUrl}"`, async (error, stdout) => { + if (error) { + message.reply('Failed to retrieve video info.'); + return; + } + + const info = JSON.parse(stdout); + const title = info.title; + const thumbnailUrl = info.thumbnail; + + exec(`yt-dlp --cookies ${path.join(__dirname, '../cookies.txt')} --format bestaudio --output "${tempFilePath}" "${videoUrl}"`, async (error) => { + if (error) { + message.reply('Failed to download audio file.'); + return; + } + + if (sendEmbed) { + const embed = new EmbedBuilder() + .setColor('#0099ff') + .setTitle('Added To Queue') + .setDescription(`**${title}**`) + .setThumbnail(thumbnailUrl) + .setFooter({ text: `Requested by ${message.author.username}`, iconURL: message.author.displayAvatarURL() }) + .setTimestamp(); + + message.channel.send({ embeds: [embed] }); + } + + addToQueue(message.guild.id, tempFilePath, title, voiceChannel, videoUrl, message.author.username, message.author.displayAvatarURL(), thumbnailUrl); + playNextInQueue(message.guild.id); + }); + }); +} + function isValidURL(string) { try { new URL(string);