I'm making a bot for the mattermost server of my company (like a discord bot), and for my bot commands, I created one file per command.
The idea is that to send a command, the user must send a message like "!giveChannelPerms arg1 arg 2 ...". The bot will parse the message to identify the command (in this case !giveChannelPerms) and execute the code related to the command.
The problem is that for each command, I have to require() the file and make an if {} else if {} else if {} ... to find the command, as you can see with the code below.
const giveChannelPerms = require('../cmd/giveChannelPerms');
const removeChannelPerms = require('../cmd/removeChannelPerms');
[...]
if (cmd == "!giveChannelPerms") {
giveChannelPerms.giveChannelPerms(post, args, db, obj);
} else if (cmd == "!removeChannelPerms") {
removeChannelPerms.removeChannelPerms(post, args, db, obj);
}
This code is good if we only have 2 commands for our bot, but the more commands I create, the more require() and if {} else if {} will be big.
Isn't there a more "optimized" way to do what I'm trying to do? I had thought of doing something like C function pointers but I have no idea how to do it.
If you want less require and reduce if elses, I recommend you to create a file importing your commands and returning an associated map
const { giveChannelPerms } = require('../cmd/giveChannelPerms');
const { removeChannelPerms } = require('../cmd/removeChannelPerms');
const cmdMap = new Map();
cmdMap.set('!giveChannelPerms', giveChannelPerms)
cmdMap.set('!removeChannelPerms', removeChannelPerms)
export default cmdMap
Then you will be able to import it only once and use it without conditions in your file :
// Imported multiples functions in one require
const commands = require('../cmd/commands');
// Will execute function associated to cmd string without conditions
commands.get(cmd)(post, args, db, obj);
Borrowing from https://stackoverflow.com/a/38397640/932256 try:
const commandFiles = {
giveChannelPerms: require('../cmd/giveChannelPerms'),
removeChannelPerms: require('../cmd/removeChannelPerms')
};
const commandList = Object.keys(commandFiles);
if (cmd.match(new RegExp('^!(' + commandList.join('|') + ')$'))) {
let baseCommand = cmd.replace('!', '');
commandFiles[baseCommand][baseCommand](post, args, db, obj);
}
Now all you need to do is add your commands to the commandFiles object.
Maybe something like this would work
[...]
require('../cmd/giveChannelPerms').then(res => {
if (res) {
res.giveChannelPerms(post, args, db, obj);
} else {
require('../cmd/removeChannelPerms').then(rslt => {
if (rslt) rslt.removeChannelPerms(post, args, db, obj);
})
}
});
I think you are looking for below two solution, hope this helps
More elegant way to manage your imports -
For this you can create a index file (index.js) which will import all your commands from different file and export all commands from one place which is index.js
Simplify your if else condition - from your code snippet, it seems like you need to evaluate against each command as the code then executed is different for each command. As there is no escape from, switch-case will provide you better code readability than if-else
const Cmds = require('../cmd/index');
// or you use import as below
//const {giveChannelPerms, removeChannelPerms} = require('../cmd/index')
switch(cmd)
case: '!giveChannelPerms'
Cmds.giveChannelPerms.giveChannelPerms(post, args, db, obj);
case: '!removeChannelPerms'
Cmds.removeChannelPerms.removeChannelPerms(post, args, db, obj);
defualt:
// do-somthing
}
Related
I'm a very new discord.js coder. I have been trying to use this code in a different file, \modules\verifyForm.js, and it always comes up with client is not defined. I've had quite a large look around and it always comes up with something that is too outdated, something very advanced or something that just don't work.
I have a separate main file in \index.js in which this piece of code works. Client is defined in that file or course, but because you can't have two instances I can't re-define it. Am I just being dumb or is there a better way to do this. Would a collection or using exports help?
const { ActionRowBuilder, Events, InteractionType, ModalBuilder, TextInputBuilder, TextInputStyle, } = require('discord.js');
client.on(Events.InteractionCreate, async (interaction) => {
if (interaction.isButton()) {
if (interaction.customId === 'verification-button') {
const modal = new ModalBuilder()
.setCustomId('verification-modal')
.setTitle('Verify yourself')
.addComponents([
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('verification-input')
.setLabel('Answer')
.setStyle(TextInputStyle.Short)
.setMinLength(0)
.setMaxLength(512)
.setPlaceholder('ABCDEF')
.setRequired(true),
),
]);
await interaction.showModal(modal);
}
}
if (interaction.type === InteractionType.ModalSubmit) {
if (interaction.customId === 'verification-modal') {
const response =
interaction.fields.getTextInputValue('verification-input');
interaction.channel(`Yay, your answer is submitted: "${response}"`);
}
}
});
I do have a separate embed with the button attached.
If you do have a way to get around using client or if you have a way to fix the error, thanks.
make the client a global variable so you can use it everywhere, basically global.client = new Client()
I'm trying to save a value of the server being "Down" or "Up", it seems to be working fine, however, when I clear the console, then run the bot again, it looks to have reset the database fully, is there a way to make the database, work like a normal database would? (Save after restart)
I'm having a check to see if the server is up, then don't allow it to run the command and say it's already online.
I wasn't like this before, and I haven't used quick.db for a while, so sorry if I missed anything.
Code:
// Main requirements
const { ROLE_SSUPIN, CHANNEL_SSU, CHANNEL_ROLESEL, BOT_PREFIX } = require('../../config.json')
const commandName = 'ssu'
// Optional Requirements
const { MessageEmbed } = require('discord.js')
const { QuickDB } = require('quick.db');
const db = new QuickDB();
// Main Code
module.exports = async client => {
client.on('messageCreate', async message => {
if (message.author.bot) return;
if (!message.content.toLowerCase().startsWith(BOT_PREFIX)) return;
const command = message.content.split(' ')
const args = message.content.substring(1).split(' ')
if (command.toString().toLowerCase().replace(BOT_PREFIX, '').startsWith(commandName)) {
const ssuStatus = await db.get("ssu.status")
await db.set("ssu.status", "down");
console.log(ssuStatus)
if (!message.member.roles.cache.get('998320362234323026')) return message.reply("Only staff with `SSU Permissions` can use this command!")//.then(m => {setTimeout( function() { m.delete(), message.delete() }, 10000)});
if (!message.channel.id == CHANNEL_SSU) return message.reply(`Please use this command in <#${CHANNEL_SSU}>.`)
if (ssuStatus == 'up') return message.reply(`Server Status is already set to Online. Say \`${BOT_PREFIX}Server Status Set Offline\` if you believe this is a mistake.`)
message.channel.send(`
Start up done.
`)
.then(await db.set("ssu.status", "up"))
}
})
}
I had "await db.set("ssu.status", "down");" in the start of each function, so it was reseting it back to "Down", I didn't mean for it do that, if you have the same issue, make sure that you don't have anything that sets the database value in the start, as it might be the reason it's getting reset and you think it's wiping.
I'm trying to get it so that when a user says a command like '!help' or '!commands' it returns the help message, in order to save space in my code and not use 2 cases, how can I make this into 1?
client.on('message', message => {
let args = message.content.substring(prefix.length).split(" ");
switch (args[0]) {
case ('help' || 'commands'):
I'm not sure what to do, it responds to "help" but not to commands. Any ideas?
The switch statement has a feature where you can create an empty case. If the case returns true, it will simply execute the next case statement with code.
switch ('someString') {
case 'someString':
case 'someOtherString': {
console.log('This will still execute');
break;
};
};
What you should not do, is use a switch to handle your commands in the first place.
What you should do is use a command handler. That way you can export all your commands into seperate files and use something called aliases.
Start with creating a commands folder in the same directory as your index.js. Each file needs to be a .js file with the following content.
module.exports = {
name: 'your command name', // needs to be completly lowercase
aliases: ["all", "of", "your", "aliases"],
description: 'Your description',
execute: (message, args) => {
// the rest of your code
}
}
Next you need to add some things to your index.js file.
Require the file system module fs and Discord. Create two new Collections.
const fs = require('fs');
const Discord = require('discord.js');
client.commands = new Discord.Collection();
client.aliases = new Discord.Collection();
Next you need to add all of the names and aliases to your two new Collections.
// Read all files in the commands folder and that ends in .js
const commands = fs.readdirSync('./commands/').filter(file => file.endsWith('.js'));
// Loop over the commands, and add all of them to a collection
// If there's no name found, prevent it from returning an error
for (let file of commands) {
const command = require(`./commands/${file}`);
// Check if the command has both a name and a description
if (command.name && command.description) {
client.commands.set(command.name, command);
} else {
console.log("A file is missing something")
}
// check if there is an alias and if that alias is an array
if (command.aliases && Array.isArray(command.aliases))
command.aliases.forEach(alias => client.aliases.set(alias, command.name));
};
Now that we added all of our commands to the collection we need build our command handler in client.on('message', message {...}).
client.on('message', message => {
// check if the message comes through a DM
//console.log(message.guild)
if (message.guild === null) {
return message.reply("Hey there, no reason to DM me anything. I won't answer anyway :wink:");
}
// check if the author is a bot
if (message.author.bot) return;
// set a prefix and check if the message starts with it
const prefix = "!";
if (!message.content.startsWith(prefix)) {
return;
}
// slice off the prefix and convert the rest of the message into an array
const args = message.content.slice(prefix.length).trim().split(/ +/g);
// convert all arguments to lowercase
const cmd = args.shift().toLowerCase();
// check if there is a message after the prefix
if (cmd.length === 0) return;
// look for the specified command in the collection of commands
let command = client.commands.get(cmd);
// If no command is found check the aliases
if (!command) command = client.commands.get(client.aliases.get(cmd));
// if there is no command we return with an error message
if (!command) return message.reply(`\`${prefix + cmd}\` doesn't exist!`);
// finally run the command
command.execute(message, args);
});
This is a guide without the aliases key.
Oh? Hi there!
So, I had a little problem recently.
I'm probably too dumb to realize how to do this, but here it is. I need some sort of way to actually read JSON files. I am requiring them, but I have no idea how to actually use them.
Here is my JSON file:
{
"help": "Displays this message"
}
And my code here:
const cmdh = require("../cmdhandle");
const Discord = require("discord.js");
const util = require("../../util");
const json = require("../help.json");
module.exports = {
aliases: ["?"],
execute:function(msg, cmd) {
// Insert code here so that "def" is a list of elements and definitions in the json file
msg.channel.send(util.genEmbed("Help", def, "#ff5c5c"));
}
}
Edit: ;p i was an idiot back then, looks like this was wayyy easier than i thought lol
You've already loaded the JSON with const json = require("../help.json"), now all you have to do to use the stuff in it is to type json.help or json["help"].
If you want a list of the definitions and elements, you can use the Object.keys and Object.values functions, or Object.entries for key-value pairs.
const cmdh = require("../cmdhandle");
const Discord = require("discord.js");
const util = require("../../util");
const json = require("../help.json");
module.exports = {
aliases: ["?"],
execute:function(msg, cmd) {
// Get all entires
Object.entries(json).forEach(([key, msg]) => {
msg.channel.send(util.genEmbed(key, msg, "#ff5c5c"));
})
}
}
I've got a script that synchronously installs non-built-in modules at startup that looks like this
const cp = require('child_process')
function requireOrInstall (module) {
try {
require.resolve(module)
} catch (e) {
console.log(`Could not resolve "${module}"\nInstalling`)
cp.execSync(`npm install ${module}`)
console.log(`"${module}" has been installed`)
}
console.log(`Requiring "${module}"`)
try {
return require(module)
} catch (e) {
console.log(require.cache)
console.log(e)
}
}
const http = require('http')
const path = require('path')
const fs = require('fs')
const ffp = requireOrInstall('find-free-port')
const express = requireOrInstall('express')
const socket = requireOrInstall('socket.io')
// List goes on...
When I uninstall modules, they get installed successfully when I start the server again, which is what I want. However, the script starts throwing Cannot find module errors when I uninstall the first or first two modules of the list that use the function requireOrInstall. That's right, the errors only occur when the script has to install either the first or the first two modules, not when only the second module needs installing.
In this example, the error will be thrown when I uninstall find-free-port, unless I move its require at least one spot down ¯\_(• _ •)_/¯
I've also tried adding a delay directly after the synchronous install to give it a little more breathing time with the following two lines:
var until = new Date().getTime() + 1000
while (new Date().getTime() < until) {}
The pause was there. It didn't fix anything.
#velocityzen came with the idea to check the cache, which I've now added to the script. It doesn't show anything out of the ordinary.
#vaughan's comment on another question noted that this exact error occurs when requiring a module twice. I've changed the script to use require.resolve(), but the error still remains.
Does anybody know what could be causing this?
Edit
Since the question has been answered, I'm posting the one-liner (139 characters!). It doesn't globally define child_modules, has no last try-catch and doesn't log anything in the console:
const req=async m=>{let r=require;try{r.resolve(m)}catch(e){r('child_process').execSync('npm i '+m);await setImmediate(()=>{})}return r(m)}
The name of the function is req() and can be used like in #alex-rokabilis' answer.
It seems that the require operation after an npm install needs a certain delay.
Also the problem is worse in windows, it will always fail if the module needs to be npm installed.
It's like at a specific event snapshot is already known what modules can be required and what cannot. Probably that's why require.cache was mentioned in the comments. Nevertheless I suggest you to check the 2 following solutions.
1) Use a delay
const cp = require("child_process");
const requireOrInstall = async module => {
try {
require.resolve(module);
} catch (e) {
console.log(`Could not resolve "${module}"\nInstalling`);
cp.execSync(`npm install ${module}`);
// Use one of the two awaits below
// The first one waits 1000 milliseconds
// The other waits until the next event cycle
// Both work
await new Promise(resolve => setTimeout(() => resolve(), 1000));
await new Promise(resolve => setImmediate(() => resolve()));
console.log(`"${module}" has been installed`);
}
console.log(`Requiring "${module}"`);
try {
return require(module);
} catch (e) {
console.log(require.cache);
console.log(e);
}
}
const main = async() => {
const http = require("http");
const path = require("path");
const fs = require("fs");
const ffp = await requireOrInstall("find-free-port");
const express = await requireOrInstall("express");
const socket = await requireOrInstall("socket.io");
}
main();
await always needs a promise to work with, but it's not needed to explicitly create one as await will wrap whatever it is waiting for in a promise if it isn't handed one.
2) Use a cluster
const cp = require("child_process");
function requireOrInstall(module) {
try {
require.resolve(module);
} catch (e) {
console.log(`Could not resolve "${module}"\nInstalling`);
cp.execSync(`npm install ${module}`);
console.log(`"${module}" has been installed`);
}
console.log(`Requiring "${module}"`);
try {
return require(module);
} catch (e) {
console.log(require.cache);
console.log(e);
process.exit(1007);
}
}
const cluster = require("cluster");
if (cluster.isMaster) {
cluster.fork();
cluster.on("exit", (worker, code, signal) => {
if (code === 1007) {
cluster.fork();
}
});
} else if (cluster.isWorker) {
// The real work here for the worker
const http = require("http");
const path = require("path");
const fs = require("fs");
const ffp = requireOrInstall("find-free-port");
const express = requireOrInstall("express");
const socket = requireOrInstall("socket.io");
process.exit(0);
}
The idea here is to re-run the process in case of a missing module. This way we fully reproduce a manual npm install so as you guess it works! Also it seems more synchronous rather the first option, but a bit more complex.
I think your best option is either:
(ugly) to install package globally, instead of locally
(best solution ?) to define YOUR new 'package repository installation', when installing, AND when requiring
First, you may consider using the npm-programmatic package.
Then, you may define your repository path with something like:
const PATH='/tmp/myNodeModuleRepository';
Then, replace your installation instruction with something like:
const npm = require('npm-programmatic');
npm.install(`${module}`, {
cwd: PATH,
save:true
}
Eventually, replace your failback require instruction, with something like:
return require(module, { paths: [ PATH ] });
If it is still not working, you may update the require.cache variable, for instance to invalide a module, you can do something like:
delete require.cache[process.cwd() + 'node_modules/bluebird/js/release/bluebird.js'];
You may need to update it manually, to add information about your new module, before loading it.
cp.execSync is an async call so try check if the module is installed in it's call back function. I have tried it, installation is clean now:
const cp = require('child_process')
function requireOrInstall (module) {
try {
require.resolve(module)
} catch (e) {
console.log(`Could not resolve "${module}"\nInstalling`)
cp.execSync(`npm install ${module}`, () => {
console.log(`"${module}" has been installed`)
try {
return require(module)
} catch (e) {
console.log(require.cache)
console.log(e)
}
})
}
console.log(`Requiring "${module}"`)
}
const http = require('http')
const path = require('path')
const fs = require('fs')
const ffp = requireOrInstall('find-free-port')
const express = requireOrInstall('express')
const socket = requireOrInstall('socket.io')
When node_modules not available yet :
When node_modules available already: