I am working on a Discord bot, and trying to improve my already functioning command handler.
I have a folder, and every file is an extra command. I want to expand the system, so I have alias name for the same command, e.g. I want my clearchat command to function with /clearchat or with /cc, but I dont want to create just another file and copy the code. This is what I have:
// I left out the other imports etc.
client.commands = new Discord.Collection();
// Reading commands-folder
const commandFiles = fs.readdirSync("./commands/").filter(file => file.endsWith(".js"));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
client.commands.set(command.name, command);
}
client.on("message", msg => {
if (msg.content.startsWith(config.prefix) && !msg.author.bot && msg.guild) {
const args = msg.content.slice(config.prefix.length).split(" ");
const command = args.shift().toLowerCase();
if (client.commands.find(f => f.name === command)) {
client.commands.get(command).execute(client, msg, args);
}
}
});
and then a command file inside the commands-folder:
module.exports = {
name: "clearchat",
execute(client, msg, args) {
if (msg.member.hasPermission("ADMINISTRATOR")) {
msg.channel.messages.fetch({limit: 99}).then(messages => {
msg.channel.bulkDelete(messages);
});
}
}
}
(I know it only deletes 100 messages max, I am fine with that)
I image something in changing a few lines in my client.on("message) function, and just having to write in the clearchat.js file a line like name: ["clearchat", "cc", ...] where I can go writing as much aliases as I want.
Thanks in advance!
First, you'll have to create an array with the aliases in your command.
module.exports = {
name: "clearchat",
aliases: ["cc"],
execute(client, msg, args) {
}
}
Then, the same you did with commands, create a Collection for the aliases.
client.aliases = new Discord.Collection()
And finally, bind the alias to the command:
if (command.aliases) {
command.aliases.forEach(alias => {
client.aliases.set(alias, command)
})
}
Now, when you want to execute a command, you'll have to check if it has an alias.
const commandName = "testcommand" // This should be the user's input.
const command = client.commands.get(commandName) || client.aliases.get(commandName); // This will return the command and you can proceed by running the execute method.
fs.readdir(`./commands/`, (error, files) => {
if (error) {return console.log("Error while trying to get the commmands.");};
files.forEach(file => {
const command = require(`./commands/${file}`);
const commandName = file.split(".")[0];
client.commands.set(commandName, command);
if (command.aliases) {
command.aliases.forEach(alias => {
client.aliases.set(alias, command);
});
};
});
});
Related
So i want my bot on discord to have custom replies for example:
user: Hi
bot: Hello
user: ping
bot: pong
I want to make my bot do this but i'm using an event handler for all the commands i'm using and I don't want to use If(message.content ==... message.channel.send ".." in the messagecreate.js or the index.js file because I'm planning to use multiple messages like so and I'd like my code to be relatively clean and organized
so if there is any way I can move the 'if' statements to another messagereplies.js file and run it from there, please let me know
This is my messagecreat.js file
require('dotenv').config();
module.exports = async (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 (command) command.execute(client, message, args, Discord);
and this is my event handler
const fs = require('fs');
module.exports = (client, Discord) => {
const load_dir = (dirs) => {
const event_files = fs.readdirSync(`./events/${dirs}`).filter(file => file.endsWith('.js'))
for (const file of event_files) {
const event = require(`../events/${dirs}/${file}`);
const event_name = file.split('.')[0];
client.on(event_name, event.bind(null, Discord, client))
}
}
['client', 'guild'].forEach(e => load_dir(e));
}
Basically I just want a block in the messagecreate.js file or event_handler.js file that executes another file in another folder if a message is sent
Create a json file call it whatever and inside of it put your trigger phrase and its response like so, for this example, I'm calling it catchPhrase.json
{
"hi": "hello",
"ping": "pong",
"here": "there",
"nowhere": "everywhere",
"master": "slave",
"mac": "pc"
}
Then in your event listener you only need one if statement
require('dotenv').config();
module.exports = async (Discord, client, message) => {
const prefix = (process.env.PREFIX);
const catchPhrase = require('path/to/catchPhrase.json')
if (catchPhrase[message.content]) return message.reply(`${catchPhrase[message.content]}`)
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 (command) command.execute(client, message, args, Discord);
}
I'm getting this error message on heroku and I think I'm getting it cause of Procfile.
I'm using Worker at the moment, but I'm trying to figure out how to have heroku access both index.js and ping.js. Unless I'm reading the error message completely wrong and this could be a different issue. Any help is appreciated!
EDIT:
Here's my code for index.js
const Discord = require('discord.js');
const music = require('#koenie06/discord.js-music');
const fs = require('fs');
const { dir } = require('console');
const bot = new Discord.Client({
shards: "auto",
intents: [
Discord.Intents.FLAGS.GUILDS,
Discord.Intents.FLAGS.GUILD_MESSAGES,
Discord.Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Discord.Intents.FLAGS.DIRECT_MESSAGES,
Discord.Intents.FLAGS.GUILD_VOICE_STATES
]
});
bot.commands = new Discord.Collection();
bot.aliases = new Discord.Collection();
//Command handler and aliases
fs.readdirSync('./commands/').forEach(dir => {
//in the commands folder, we gonna check for the category
fs.readdir(`./commands/${dir}`, (err, files) => {
//console log error(catch error)
if(err)throw err;
//checking if the files ends with .js if its a javascript file
var jsFiles = files.filter(f => f.split('.').pop() === 'js');
//if there is no commands in the file it will return
if(jsFiles.length <= 0) {
console.log("Can't find any commands");
return;
}
jsFiles.forEach(file => {
//console the loaded commands
var fileGet = require(`./commands/${dir}/${file}`);
console.log(`[COMMAND HANDLER] - File ${file} was loaded`);
//gonna let the commands run
try {
bot.commands.set(fileGet.help.name, fileGet);
// it search in the commands folder if there is any aliases
fileGet.help.aliases.forEach(alias => {
bot.aliases.set(alias, fileGet.help.name);
})
} catch(err) {
//catch error in console
return console.log(err);
}
})
})
})
/**
* ECHO STUFF
*/
//slash command to echo
bot.on('ready', async () => {
bot.user.setPresence({ activities: [{ name: "Tedi", type: "WATCHING"}] });
console.log("bye");
const data = {
name: 'echo',
description: 'Echo your text',
options: [{
name: 'text',
type: 'STRING',
description: 'The user input',
required: true,
}],
};
const command = await bot.guilds.cache.get('872986148681703444')?.commands.create(data);
})
bot.on('messageCreate', async message => {
if(message.author.bot || message.channel.type == 'DM') return
let prefix = '~'
let messageArray = message.content.split(' ');
let cmd = messsageArray[0];
let args = messageArray.slice(1);
//it will make the cmd work with his original name and his aliases
let commands = bot.commands.get(cmd.slice(prefix.length)) || bot.commands.get(bot.aliases.get(cmd.slice(prefix.length)));
if(commands) {
if(!message.content.startsWith(prefix)) return
commands.run(bot, message, args, prefix);
}
})
//interactionCreate for echo slash command
bot.on('interactionCreate', async interaction => {
/**
* isButton() used to check if its a button
* isCommand() used to check if its a slash command
* isSelectMenu() used to check if its a dropdown menu
* isMessageComponent()
*/
if(interaction.isCommand()) {
if(interaction.commandName === 'echo') {
const text = interaction.options.getString('text');
await interaction.reply({ content: text, ephemeral: false}); //if ephemeral if true, it would make the slash command private
}
}
})
bot.login(process.env.token);
Here is my ping.js
const Discord = require("discord.js");
module.exports.run = async (Client, message, args, prefix) => {
message.channel.send("pong")
}
module.exports.help = {
name: "ping",
aliases: ["p"]
}
This error is not because of Heroku, it's basically because there is a file in your command handler that doesn't have a name while handling it, as example like this code over here:
const Discord = require("discord.js");
module.exports.run = async (Client, message, args, prefix) => {
message.channel.send("pong")
}
module.exports.help = {
// here the name isn't included
aliases: ["p"]
}
// so just check if you have a file without a name while handling it and put a name, and if you don't want aliases make it `aliases: []`
This command handler should be like thisCommands Folder commands > Subfolder e.g. Moderation > kick.jsThats how it works, also thank you for watching my videos, I'm UltraX :)
In this line of code I am trying to send private message, to initial message author. For some unknown reason there is an error:
(node:17560) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'send' of undefined
Here is this line of code:
const appStart = await message.author.send(questions[collectCounter++]);
Here is whole file:
const Discord = require("discord.js");
const {Client, Message, MessageEmbed} = require("discord.js");
module.exports = {
name: 'apply',
/**
*
* #param {Client} client
* #param {Messafe} message
* #param {String[]} args
*/
run : async(client, message, args) => {
const questions = [
"What is your in-game name?",
"Do you accept the rules?",
"How old are you?",
"Where are you from?",
"How long have you been playing Minecraft?",
"Where did you find the server?",
"Have you ever been banned from another server on Minecraft? If yes, what was the reason?",
"Why should we accept you?",
]
let collectCounter = 0;
let endCounter = 0;
const filter = (m) => m.author.id === message.author.id;
const appStart = await message.author.send(questions[collectCounter++]);
const channel = appStart.channel;
const collector = channel.createMessageCollector(filter);
collector.on("collect", () => {
if(collectCounter < questions.length) {
channel.send(questions[collectedCounter++]);
} else {
channel.send("Your application has been sent!");
collector.stop("fulfilled");
}
});
const appsChannel = client.channel.cache.get('863534867668009004');
collector.on('end', (collected, reason) => {
if(reason === 'fulfilled') {
let index = 1;
const mappedResponses = collected.map((msg) => {
return `&{index++}) &{questions[endCounter++]}\n-> ${msg.content}`
})
.join('\n\n');
}
appsChannel.send(message.channel.send('New Application!', mappedResponses));
});
},
};
This is my index.js:
const fs = require("fs");
const Discord = require("discord.js");
const client = new Discord.Client();
client.commands = new Discord.Collection();
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
const BOT_ID = "863550035923697674";
const prefix = "!";
client.on("ready", () => {
console.log(`Logged in as ${client.user.tag}!`);
client.user.setActivity("ur cheat files...", {
type: "WATCHING",
url: "https://discord.gg/bzYXx8t3fE"
});
});
for(const file of commandFiles) {
const command = require(`./commands/${file}`);
client.commands.set(command.name, command);
}
client.on("message", message => {
let userID = message.author.id;
if (userID == BOT_ID) {
return;
}
const version = "Wersja bota 0.2";
///const args = message.content;
const args = message.content.slice(prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
if(!client.commands.has(command)) return;
try{
client.commands.get(command).run(message, args);
}catch(error){
console.error(error);
message.reply('**There was an issue executing that command!**');
}
});
client.login("tokenishere");
Thanks for help!
This just means that message is not what you think it is. In your command handler, you executed the command with the arguments message and args (order matters). However, in the command file, you expected the parameters to be client, message, and then args.
This means, that in your command file, client actually refers to the message, message actually refers to the args, and the third parameter doesn't exist.
To fix this problem, you can modify the names and order of the parameters in either file.
This is an easy fix... as #Lioness100 mentioned your parameters are wrong and im here to show a code example
locate this line of code in index.js :
client.commands.get(command).run(message, args);
// and change it to
client.commands.get(command).run(message, args, client);
after wards go to your "File" and fine this line of code :
run : async(client, message, args) => {
// And change it to
run : async(message, args) => {
Your problem was that you were executing wrong parameters in Your "File" so you just needed to change your parameters from client, message, args to message, args, client
This is a common mistake
I have a command handler in my main.js file and alias handler is in it too - But when I try to run a command with the alias I've set in module.exports.config it does not respond.
This is my command handler:
fs.readdir(`./commands/`, (error, files) => {
if (error) {return console.log("Error while trying to get the commmands.");};
files.forEach(file => {
const command = require(`./commands/${file}`);
const commandName = file.split(".")[0];
client.commands.set(commandName, command);
if (command.aliases) {
command.aliases.forEach(alias => {
client.aliases.set(alias, command.name)
});
};
});
});
client.on("message", async message => {
if(message.author.bot || message.channel.type === "dm") return;
let prefix = (";");
let messageArray = message.content.split(" ");
let cmd = messageArray[0].toLowerCase();
let args = messageArray.slice(1)
if(!message.content.startsWith(prefix)) return;
let commandfile = client.commands.get(cmd.slice(prefix.length)) || client.commands.get(client.aliases.get(cmd.slice(prefix.length)))
if(commandfile) commandfile.run(client,message,args)
})
And this is my module.exports.config:
module.exports.config = {
name: "eval",
description: "Eval a code",
aliases: ['debug']
};
You don't need to create a new collection for the aliases. You could use collection's .find() method and check if there is an alias with the command name.
So, inside fs.readdir you only set client.commands and when there is an incoming message, you can check if there is an exact command name (using client.commands.get) and if there is not, you check if the command has an aliases property. If it has, you can check if the aliases array includes the command.
Also, if you use cmd.slice(prefix.length) in more than one place and you don't use cmd anywhere, then you probably need to remove the prefix from the command.
The following should work as expected:
fs.readdir(`./commands/`, (error, files) => {
if (error) {
console.log(error);
return console.log('Error while trying to get the commmands.');
}
files.forEach((file) => {
const command = require(`./commands/${file}`);
const commandName = file.split('.')[0];
client.commands.set(commandName, command);
});
});
client.on('message', async (message) => {
let prefix = ';';
if (
message.author.bot ||
message.channel.type === 'dm' ||
!message.content.startsWith(prefix)
)
return;
const args = message.content.slice(prefix.length).trim().split(/ +/);
const commandName = args.shift().toLowerCase();
const command =
client.commands.get(commandName) ||
client.commands.find((cmd) => cmd.config?.aliases?.includes(commandName));
if (!command) return;
try {
command.run(client, message, args);
} catch (error) {
console.log('There was an error trying to run that command:');
console.log(error);
}
});
How do I get this code to work for sub folders (folders inside the main 'commands' folder)? Here is some of my code.
Index.js:
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
client.commands.set(command.name, command);
}
client.on('message', message => {
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).trim().split(/ +/);
const command = args.shift().toLowerCase();
if (!client.commands.has(command)) return;
try {
client.commands.get(command).execute(message, args, client);
} catch (error) {
console.error(error);
const errembed = new Discord.MessageEmbed()
.setColor('#009ACD')
.setDescription('There was an error trying to execute that command!')
message.channel.send(errembed);
}
});
Examplecommand.js:
const Discord = require('discord.js')
module.exports = {
name: 'command name',
description: 'command description',
execute(message, args) {
//code here
}
}
I don't know a way to get this to work in sub-folders with an elegant solution, so I think this is what you should do:
client.subFolderCommands = new Discord.Collection();
const subFolderFiles = fs.readdirSync('./commands/subfolder').filter(file => file.endsWith('.js'));
for (var i = 0; i < subFolderFiles.length; i++) {
const command = require(`./commands/subfolder/${subFolderFiles[i]}`);
client.subFolderCommands.set(command.name, command);
}
If you know a better solution, please tell me!
What you can do, is make the readdirSync method return the files and/or directories as directory entry (Dirent). You can do this by setting the option withFileTypes to true in the fs.readdirSync method. Then you can iterate over the results and if an entry is a subdirectory, read all the files/directories from that folder. You can check this with the isDirectory() function.
Here's an example that could work:
function readFilesFromPath(pathString) {
const directoryEntries = fs.readdirSync(pathString, {withFileTypes: true});
return directoryEntries.reduce((filteredEntries, dirEnt) => {
if (dirEnt.isDirectory()) {
// If the entry is a directory, call this function again
// but now add the directory name to the path string.
filteredEntries.push(...readFilesFromPath(`${pathString}/${dirEnt.name}`))
} else if (dirEnt.isFile()) {
// Check if the entry is a file instead. And if so, check
// if the file name ends with `.js`.
if (dirEnt.name.endsWith('.js') {
// Add the file to the command file array.
filteredEntries.push(`${pathString}/${dirEnt.name}`);
}
}
return filteredEntries;
}, []);
}
// Call the read files function with the root folder of the commands and
// store all the file paths in the constant.
const commandFilePaths = readFilesFromPath('./commands');
// Loop over the array of file paths and set the command on the client.
commandFilePaths.forEach((filePath) => {
const command = require(filePath);
client.commands.set(command.name, command);
});
This is my command handler with subfolder support.
Just replace first 5 lines with these
const commandFolders = fs.readdirSync('./commands');
for (const folder of commandFolders) {
const commandFiles = fs.readdirSync(`./commands/${folder}`).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const command = require(`./commands/${folder}/${file}`);
client.commands.set(command.name, command);
}
}