commit eb75bb1a3284857d3a97630b762c1852c863629c Author: Wizzard Date: Sat Aug 17 11:11:10 2024 -0400 First push diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9be7ae2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +cookies.txt +config.json +package-lock.json \ No newline at end of file diff --git a/commands/play.js b/commands/play.js new file mode 100644 index 0000000..36e32b9 --- /dev/null +++ b/commands/play.js @@ -0,0 +1,63 @@ +const { addToQueue, playNextInQueue } = require('../utils/queueManager'); +const ytDlpExec = require('yt-dlp-exec'); +const { v4: uuidv4 } = require('uuid'); +const path = require('path'); + +module.exports = { + name: 'play', + description: 'Play a song from YouTube', + async execute(message, args) { + const searchQuery = args.join(' '); + const voiceChannel = message.member.voice.channel; + + if (!voiceChannel) { + return message.reply('You need to be in a voice channel to play music!'); + } + + if (!searchQuery) { + return message.reply('Please provide a YouTube link or a song name.'); + } + + let url; + if (isValidURL(searchQuery)) { + url = searchQuery; + } else { + try { + const searchResult = await ytDlpExec(`ytsearch:${searchQuery}`, { + dumpSingleJson: true, + noPlaylist: true, + format: 'bestaudio/best', + quiet: true, + }); + url = searchResult.webpage_url; + } catch (error) { + console.error('yt-dlp search error:', error); + return message.reply('Could not find the song. Please try again.'); + } + } + + const tempFilePath = path.join(__dirname, '../utils/tmp', `${uuidv4()}.mp3`); + try { + await ytDlpExec(url, { + cookies: path.join(__dirname, '../cookies.txt'), + format: 'bestaudio', + output: tempFilePath, + quiet: true, + }); + addToQueue(message.guild.id, tempFilePath); + playNextInQueue(message.guild.id, voiceChannel); + } catch (error) { + console.error('yt-dlp error:', error); + message.reply('Failed to download video with yt-dlp.'); + } + }, +}; + +function isValidURL(string) { + try { + new URL(string); + return true; + } catch (_) { + return false; + } +} \ No newline at end of file diff --git a/commands/queue.js b/commands/queue.js new file mode 100644 index 0000000..2cf3cd1 --- /dev/null +++ b/commands/queue.js @@ -0,0 +1,13 @@ +const { getQueue } = require('../utils/queueManager'); + +module.exports = { + name: 'queue', + execute(message) { + const queue = getQueue(message.guild.id); + if (queue.length === 0) { + return message.channel.send('The queue is empty!'); + } + const queueString = queue.map((track, index) => `${index + 1}. ${track.title}`).join('\n'); + message.channel.send(`Current queue:\n${queueString}`); + } +}; diff --git a/commands/skip.js b/commands/skip.js new file mode 100644 index 0000000..24cc98b --- /dev/null +++ b/commands/skip.js @@ -0,0 +1,9 @@ +const { skipTrack } = require('../utils/queueManager'); + +module.exports = { + name: 'skip', + execute(message) { + skipTrack(message.guild.id); + message.channel.send('Skipped the current track!'); + } +}; diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..52f453c --- /dev/null +++ b/config.json.example @@ -0,0 +1,5 @@ +{ + "prefix": "+", + "token": "token", + "cookie-file": "/home/user/dz-musicbot/cookies.txt" + } \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..c0595a0 --- /dev/null +++ b/index.js @@ -0,0 +1,53 @@ +const { Client, GatewayIntentBits } = require('discord.js'); +const fs = require('fs'); +const path = require('path'); +const { prefix, token } = require('./config.json'); + +const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] }); + +client.commands = new Map(); + +const tmpDir = path.join(__dirname, 'utils', 'tmp'); +if (!fs.existsSync(tmpDir)) { + fs.mkdirSync(tmpDir, { recursive: true }); +} + +fs.readdir(tmpDir, (err, files) => { + if (err) throw err; + + for (const file of files) { + if (file.endsWith('.mp3')) { + fs.unlink(path.join(tmpDir, file), err => { + if (err) throw err; + }); + } + } +}); + +const commandFiles = fs.readdirSync(path.join(__dirname, 'commands')).filter(file => file.endsWith('.js')); +for (const file of commandFiles) { + const command = require(`./commands/${file}`); + client.commands.set(command.name, command); +} + +client.once('ready', () => { + console.log(`Logged in as ${client.user.tag}!`); +}); + +client.on('messageCreate', async message => { + if (!message.content.startsWith(prefix) || message.author.bot) return; + + const args = message.content.slice(prefix.length).trim().split(/ +/); + const commandName = args.shift().toLowerCase(); + + if (!client.commands.has(commandName)) return; + + try { + await client.commands.get(commandName).execute(message, args); + } catch (error) { + console.error(error); + message.reply('There was an error trying to execute that command!'); + } +}); + +client.login(token); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c85bcbd --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "@discordjs/voice": "^0.17.0", + "discord.js": "^14.15.3", + "ffmpeg-static": "^5.2.0", + "libsodium-wrappers": "^0.7.15", + "opusscript": "^0.0.8", + "prism-media": "^1.3.5", + "uuid": "^10.0.0", + "yt-dlp-exec": "^1.0.2", + "ytdl-core": "^4.11.5" + } +} diff --git a/utils/queueManager.js b/utils/queueManager.js new file mode 100644 index 0000000..24a24c0 --- /dev/null +++ b/utils/queueManager.js @@ -0,0 +1,66 @@ +const { createAudioPlayer, createAudioResource, AudioPlayerStatus, joinVoiceChannel } = require('@discordjs/voice'); +const fs = require('fs'); + +const queueMap = new Map(); + +function addToQueue(guildId, filePath) { + if (!queueMap.has(guildId)) { + queueMap.set(guildId, []); + } + queueMap.get(guildId).push(filePath); +} + +function getQueue(guildId) { + return queueMap.get(guildId) || []; +} + +function playNextInQueue(guildId, voiceChannel) { + const queue = getQueue(guildId); + if (queue.length === 0) return false; + + const connection = joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: guildId, + adapterCreator: voiceChannel.guild.voiceAdapterCreator, + }); + + const audioPlayer = createAudioPlayer(); + + const filePath = queue.shift(); + + if (!fs.existsSync(filePath)) { + console.error('Audio file not found:', filePath); + return false; + } + + const resource = createAudioResource(filePath); + audioPlayer.play(resource); + connection.subscribe(audioPlayer); + + audioPlayer.on(AudioPlayerStatus.Idle, () => { + if (queue.length > 0) { + playNextInQueue(guildId, voiceChannel); + } else { + connection.destroy(); + } + fs.unlink(filePath, (err) => { + if (err) console.error('Error deleting file:', filePath, err); + }); + }); + + audioPlayer.on('error', (err) => { + console.error('AudioPlayer error:', err); + connection.destroy(); + }); + + return true; +} + +function skipTrack(guildId) { + const queue = getQueue(guildId); + if (queue.length > 0) { + queue.shift(); + } +} + +module.exports = { addToQueue, getQueue, playNextInQueue, skipTrack }; diff --git a/utils/tmp/cd09c5f0-201e-4025-979c-04e580f9c23a.mp3 b/utils/tmp/cd09c5f0-201e-4025-979c-04e580f9c23a.mp3 new file mode 100644 index 0000000..68b0108 Binary files /dev/null and b/utils/tmp/cd09c5f0-201e-4025-979c-04e580f9c23a.mp3 differ diff --git a/utils/yt-dlp.js b/utils/yt-dlp.js new file mode 100644 index 0000000..ce9ae0c --- /dev/null +++ b/utils/yt-dlp.js @@ -0,0 +1,26 @@ +const { exec } = require('child_process'); +const path = require('path'); +const { v4: uuidv4 } = require('uuid'); + +async function downloadVideo(searchQuery) { + const tempFilePath = path.join(__dirname, 'tmp', `${uuidv4()}.mp3`); + + const ytDlpArgs = [ + '--cookies', path.join(__dirname, '../cookies.txt'), + '--format', 'bestaudio', + '--output', tempFilePath, + searchQuery, + ]; + + return new Promise((resolve, reject) => { + exec(`yt-dlp ${ytDlpArgs.join(' ')}`, (error, stdout, stderr) => { + if (error) { + console.error('yt-dlp error:', stderr); + return reject(new Error('Failed to download video with yt-dlp')); + } + resolve(tempFilePath); + }); + }); +} + +module.exports = { downloadVideo };