I have a question about a music bot that I learned from CodeLyon. His video on making this music bot is here: https://www.youtube.com/watch?v=riyHsgI2IDs&list=PLbbLC0BLaGjpyzN1rg-gK4dUqbn8eJQq4&index=20
If you are lazy and you don't want to watch the entire video, here's a summary:
I have a command handler, named command_handler.js. I think this file is for filtering the commands, but I'm not sure. It's just a command handler. If you really want it (I don't know why, though), the code is here:
const fs = require('fs');
module.exports = (client, Discord) =>{
const command_files = fs.readdirSync('./commands/').filter(file => file.endsWith('.js'))
for(const file of command_files){
const command = require(`../commands/${file}`);
if(command.name){
client.commands.set(command.name, command);
} else {
continue;
}
}
}
A slightly more important file is the message.js file. This one runs all the commands. Here is the code, but, like the command_handler.js, I don't know why you would need it.
require('dotenv').config();
//create cooldowns map
const cooldowns = new Map();
module.exports = (Discord, client, message) => {
const prefix = process.env.PREFIX;
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).split(/ +/)
const cmd = args.shift().toLowerCase();
const command = client.commands.get(cmd) || client.commands.find(a => a.aliases && a.aliases.includes(cmd));
//If cooldowns map doesn't have a command.name key then create one.
if(!cooldowns.has(command.name)){
cooldowns.set(command.name, new Discord.Collection());
}
const current_time = Date.now();
const time_stamps = cooldowns.get(command.name);
const cooldown_amount = (command.cooldown) * 1000;
//If time_stamps has a key with the author's id then check the expiration time to send a message to a user.
if(time_stamps.has(message.author.id)){
const expiration_time = time_stamps.get(message.author.id) + cooldown_amount;
if(current_time < expiration_time){
const time_left = (expiration_time - current_time) / 1000;
return message.reply(`Please wait ${time_left.toFixed(1)} more seconds before using ${command.name}`);
}
}
//If the author's id is not in time_stamps then add them with the current time.
time_stamps.set(message.author.id, current_time);
//Delete the user's id once the cooldown is over.
setTimeout(() => time_stamps.delete(message.author.id), cooldown_amount);
try{
command.execute(client, message, args, cmd, Discord);
} catch (err){
message.reply("There was an error trying to execute this command!");
console.log(err);
}
}
Now comes the most important file: the one that plays the song. It is activated with the play keyword, or the aliases skip and stop. The file is named play.js. Here is the code:
const ytdl = require('ytdl-core');
const ytSearch = require('yt-search');
//Global queue for your bot. Every server will have a key and value pair in this map. { guild.id, queue_constructor{} }
const queue = new Map();
module.exports = {
name: 'play',
aliases: ['skip', 'stop'], //We are using aliases to run the skip and stop command follow this tutorial if lost: https://www.youtube.com/watch?v=QBUJ3cdofqc
cooldown: 0,
description: 'Advanced music bot',
async execute(client, message,args, cmd, Discord){
//Checking for the voicechannel and permissions (you can add more permissions if you like).
const voice_channel = message.member.voice.channel;
if (!voice_channel) return message.channel.send('You need to be in a channel to execute this command!');
const permissions = voice_channel.permissionsFor(message.client.user);
if (!permissions.has('CONNECT')) return message.channel.send('You dont have the correct permissions');
if (!permissions.has('SPEAK')) return message.channel.send('You dont have the correct permissions');
//This is our server queue. We are getting this server queue from the global queue.
const server_queue = queue.get(message.guild.id);
//If the user has used the play command
if (cmd === 'play'){
if (!args.length) return message.channel.send('You need to send the second argument!');
let song = {};
//If the first argument is a link. Set the song object to have two keys. Title and URl.
if (ytdl.validateURL(args[0])) {
const song_info = await ytdl.getInfo(args[0]);
song = { title: song_info.videoDetails.title, url: song_info.videoDetails.video_url }
} else {
//If there was no link, we use keywords to search for a video. Set the song object to have two keys. Title and URl.
const video_finder = async (query) =>{
const video_result = await ytSearch(query);
return (video_result.videos.length > 1) ? video_result.videos[0] : null;
}
const video = await video_finder(args.join(' '));
if (video){
song = { title: video.title, url: video.url }
} else {
message.channel.send('Error finding video.');
}
}
//If the server queue does not exist (which doesn't for the first video queued) then create a constructor to be added to our global queue.
if (!server_queue){
const queue_constructor = {
voice_channel: voice_channel,
text_channel: message.channel,
connection: null,
songs: []
}
//Add our key and value pair into the global queue. We then use this to get our server queue.
queue.set(message.guild.id, queue_constructor);
queue_constructor.songs.push(song);
//Establish a connection and play the song with the vide_player function.
try {
const connection = await voice_channel.join();
queue_constructor.connection = connection;
video_player(message.guild, queue_constructor.songs[0]);
} catch (err) {
queue.delete(message.guild.id);
message.channel.send('There was an error connecting!');
throw err;
}
} else{
server_queue.songs.push(song);
return message.channel.send(`???? **${song.title}** added to queue!`);
}
}
else if(cmd === 'skip') skip_song(message, server_queue);
else if(cmd === 'stop') stop_song(message, server_queue);
}
}
const video_player = async (guild, song) => {
const song_queue = queue.get(guild.id);
//If no song is left in the server queue. Leave the voice channel and delete the key and value pair from the global queue.
if (!song) {
song_queue.voice_channel.leave();
queue.delete(guild.id);
return;
}
const stream = ytdl(song.url, { filter: 'audioonly' });
song_queue.connection.play(stream, { seek: 0, volume: 0.5 })
.on('finish', () => {
song_queue.songs.shift();
video_player(guild, song_queue.songs[0]);
});
await song_queue.text_channel.send(`???? Now playing **${song.title}**`)
}
const skip_song = (message, server_queue) => {
if (!message.member.voice.channel) return message.channel.send('You need to be in a channel to execute this command!');
if(!server_queue){
return message.channel.send(`There are no songs in queue ????`);
}
server_queue.connection.dispatcher.end();
}
const stop_song = (message, server_queue) => {
if (!message.member.voice.channel) return message.channel.send('You need to be in a channel to execute this command!');
server_queue.songs = [];
server_queue.connection.dispatcher.end();
}
There are some emojis in some of the strings. I apologize if you cannot see them, but they are not very important.
You see, my prefix is >>. The problem is that when I type >>play, and then any song, in the Discord chat, no matter what song it is, the bot pops into the voice channel for about one fifth of second before it pops back out again. I can't even listen to the song. Just so to let you know, I am in the voice channel. Is there a fix? If not, is there a workaround?
I hope I have described enough of my problem. If you need me to add something here, can you please tell me in the comments? If I need to, somehow, restart my bot, or do something with my Discord server, or change some of the settings, or download some packages, please tell me. I need the answers to be uttermost precise. Help is greatly appreciated!
Codelyon's tutorial playlists for 2021: https://www.youtube.com/playlist?list=PLbbLC0BLaGjpyzN1rg-gK4dUqbn8eJQq4