How to limit command to the server where the command was called? - node.js

I created a Discord bot which has some commands that generates riddels and riddels answers.
I discovered a bug that was triggered: if I invite the bot in two different servers and try to run one command in one of this two servers, it will run it in the second one and that's actually bad because I wanted each server to be independent. If the command is run on one server, it shouldn't be run on another.
I tried to save server id and tried to write some conditions with it but didn't work.
on the first server:
on the second server:
I want to make the command works per server not get shared in all of them
let CommandRunned = CommandRunnedWithSpace.trim().toLowerCase()
let argsWithOutSpace = receivedMess.content.toLowerCase().slice(mentionNumber).trim().split(' ');
const cmd = argsWithOutSpace.shift().toLowerCase();
const args = argsWithOutSpace.join(' ');
if (CommandRunned === 'startriddle') {
if (RiddleMap.get('check') === true) {
receivedMess.reply("Riddle Already Started (can't start two riddels at the sametime)");
} else {
currentCluesArray = [];
clueI = 0;
getRndNum = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let randomNum = getRndNum(0, RiddlesApi.Riddles.length - 1)
RiddlesApi.Riddles.filter((Riddel) => {
if (Riddel.id === randomNum) {
RiddleMessage = Riddel.Riddle
answer = Riddel.Answer
clues = Riddel.clues
}
})
StartRiddleModuel.startRiddle(receivedMess, RiddleMessage);
RiddleMap.set('check', true);
}
}
if (cmd === 'answer') {
if (answerCooldown.has(receivedMess.author.id)) {
receivedMess.delete();
receivedMess.reply('You have to wait ' + answercdseconds + ' seconds befor answering again')
} else {
if (RiddleMap.get('check') === true) {
if (args === answer) {
RiddleMap.set('check', false);
receivedMess.reply("Correct :hushed: ")
receivedMess.react('✅');
} else if (args === '') {
receivedMess.reply("Well you didnt enter anything.How you want me to know your answer then.")
receivedMess.react('💢');
} else {
receivedMess.reply("That wasnt the right answer try again :smirk: ")
receivedMess.react('❌');
}
answerCooldown.add(receivedMess.author.id);
} else {
receivedMess.reply("No Riddles did started");
}
}
}

Without having complete code it's pretty hard to point out which lines of code should be changed. However, the general idea is to compare guild ID each time you run the command. Under Message object, you can find guild ID like this:
msg.guild.id
When user requests a riddle, you should save the guild ID and then compare it with the ID from the answer command.
However, there's another solution which could better fit your needs: awaitMessages method from TextChannel class.
The most important benefit is that it's used per channel, so you don't need to add any logic like the one I described above.
Example from documentation should help you to properly incorporate it in your script (comments and minor changes mine):
// Here you need to create a filter to 'catch' only desired messages
// E.g. filter I use to check if my bot was pinged is as below
// message.mentions.users.some(mention => mention.id === client.user.id)
const filter = m => m.content.includes('answer');
// Here you specify max: 1 as you want to catch and respond to each message
channel.awaitMessages(filter, { max: 1, time: 60000, errors: ['time'] })
.then(collected => console.log('If correct command was sent, program enters here'))
.catch(collected => console.log('In case errors or timeout, put commands here'));
The only thing missing is to loop awaitMessages (so that it doesn't end after one message is found) and set separate timer so you know when the time for answering ends.

Related

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory(i have set the max-old-space-size already) discord.js node.js

I keep getting this error, I have set my max-old-space-size to 8192 or higher and I don't understand what is the problem.
I have tried various types of size too.
This is the main part of the code:
client.on("message", message =>{
let i = 1;
while (i < 10) {
task(i)
}
function task(i) {
setTimeout(function (){
util.statusBedrock('ip', { port: port, timeout: 3800})
.then((reponse) =>{
const Embed = new Discord.MessageEmbed()
.setColor('#62ff00')
.setTitle('Server Status')
.setAuthor('Made by ThatTheredstoner')
.addField(':green_circle: Online', reponse.version)
.addField('**Software/System**', reponse.software)
.addField('Server IP', 'ip')
.addField('Server Port', reponse.port)
.addField('Gamemode', 'Survival ')
.addField('**Latency**', reponse.roundTripLatency)
.addField('Online Player', reponse.onlinePlayers)
.addField('Player', reponse.players)
.addField('Max Players', reponse.maxPlayers)
.setTimestamp()
.setFooter('footer');
client.channels.cache.get('channel').messages.fetch('message').then((mesg)=>{
mesg.edit(Embed);
})
console.log(reponse);
})
.catch((error) => {
console.log(error)
const OEmbed = new Discord.MessageEmbed()
.setColor('#ff0000')
.setTitle('Server Status')
.setAuthor('Powered with Redstone')
.addField(':red_circle:', 'Offline')
.addField('Server IP', 'ip')
.setTimestamp()
.setFooter('footer');
client.channels.cache.get('channel id').messages.fetch('message id').then((mesg)=>{
mesg.edit(OEmbed);
})
})
}, 4000);
}
});
I swapped out the IDs and IPs for good measurement.
Not an answer to the ops question but since this is the top search result for that error message here's an answer for other people who may be using create-react-app.
To change the heap limit for react-scripts you need to do it in package.json by changing the build line to:
"build": "react-scripts --max_old_space_size=4096 build"
Attempts to set environment variables, add parameters to the node command all seem ineffective but changing it here in package.json works.
The problem is within your while loop. Variable i is never used or updated. Because of your logic in the while loop, variable i is always below 10 no matter what.
Because of the never ending loop, and every time someone posts a message, your while loop will stack until there is no memory left on your machine to execute your while() loop, thus it will collapse and crash your application.
My solution would be as followed: Put your async task outside of the message loop and make a trigger command for it, for example !update server info.
! My answer might divergent from your own code, I've made a simulation for an async task as following:
let isBusy = false;
const tasks = 10;
const asyncTask = (ms, i) => new Promise(resolve => setTimeout(() => {
console.log(`Task (${i}) finished in ${ ms / 1000 } seconds.`);
resolve(true);
}, ms));
client.on("message", async message => {
if(message.content === "!update server info") {
if(isBusy === false) {
isBusy = true;
let i = 0;
while(i++ < tasks) {
await asyncTask(Math.floor(Math.random() * 5000), i); // THE MATH CALCULATION ON THIS LINE IS TO SIMULATE AN ASYNC TASK! DO NOT USE IN YOUR CODE.
if(i >= tasks) isBusy = false;
}
}else{
// Send a message to the user if your tasks are still running or not.
}
}
});
Also in your case you're updating your message frequently, I wouldn't do that as it may result in a rateLimit error. I would update your message only once per 1 or 2 minutes.
! This answer might not ideal or best practice, but it should push you to the right direction and understand how while loops work.

so i was trying to make a ban command using discord.js V12 and i am always getting the same error no matter what

a ban command sound simple and i know i could just copy one on the internet but i wanted to make a more specific ban command with arguments like time and looks like this :
const client = new Discord.Client();
client.client;
//message is an array of strings from the message the user send
//and args is everything after the !ban so the mention is args[0]
module.exports = {
name : 'ban',
description : 'bans any user for as long as you want(unit in days)',
execute(message, args) {
if(!message.mentions.users.first()) {
return message.channel.send('please specify a target');
}
// target is who will be banned
const target = message.guild.members.cache.get(message.mentions.users.first().id);
// time is how long will the user be banned (in days) it's mutiplied by 1 to convert it from a stirng to NaN or a number parseInt() works too :)
const time = args[1] * 1;
// checks if there are any arguments
if(args.length === 1) {
message.channel.send('you have not input a reason and a time for the ban');
return;
}
else if(!isNaN(time)) {
// this is where the problem is
try {
target.send('you were banned, dummy accout'); // << this works
target.ban({ reason:args.slice(2).join(' '), days:time }); // << but this doesn't
/* (node:10484) UnhandledPromiseRejectionWarning: DiscordAPIError: Invalid Form Body
delete_message_days: int value should be less than or equal to 7. */
}
catch(error) {
// this code does not execute
console.error(error);
message.reply('there was an issue executing the command');
}
return;
}
// this one works as well only when i dont give a time for the ban (if the if statement above returns false )
else if(typeof args[1] === 'string') {
target.ban({ reason:args.slice(1).join(' ') });
return;
}
},
};
i already set up a command handler and every command i have works fine but on this one i keep getting this error
(node:10484) UnhandledPromiseRejectionWarning: DiscordAPIError: Invalid Form Body
delete_message_days: int value should be less than or equal to 7.
and funny enough, if i typed !ban #user and then anything less than 7 it works fine
Well in the documentation its pretty clear that the days needs to be a value between 0 and 7.If you want to delete older messages you would need to do it by manually fetching them and then using Message#delete() or TextChannel#bulkDelete().

How do you turn on and off a bots reactions?

I've been working on a bot (using js) that reacts to every message sent and I've been trying to give it on and off commands. Is there a way to have on and off commands when it comes to reactions from bots?
This is what I have:
top:
var emojissetting = 0
on / off (tried with changing the variable to 0 or 1):
if (msg === prefix + 'START'){
emojisetting = 1;
message.channel.send("Snoopy is now reading the chat. :book:");
}
if (msg === prefix + 'STOP'){
emojisetting = 0;
message.channel.send("Snoopy has stopped reading the chat. :blue_book:");
}
bottom:
bot.on('message', message => {
if(emojisetting = 0){
if (message) {
return;
}
}
if(emojisetting = 1){
if (message) {
emojirandom = emojisnop[Math.floor(Math.random() * emojisnop.length)]
message.react(emojirandom)
}
}
});
First of all, if your bot is going to run in more than one server, it's probably gonna get API banned because you would exceed the rate-limit of adding reactions to messages, if I remember correctly the limit is probably 1 reaction every 0.25 seconds.
Anyway, what you did is correct but it will only work for one server, because 'emojisetting' is a global variable of the bot. You could create an array of objects to store that setting for each server, for example:
emojisettings = [
{
guildID = 5365657435245;
enabled = true;
}
guildID = 3543265356345;
enabled = false;
}
]
In this example we stored two settings for two different servers, identified by their IDs. Also we used true and false instead of 0/1 because it's more ideal and easy to handle.
Then we will need to let users handle this setting with a command, for example:
if (msg === prefix + 'START' || msg === prefix + 'STOP'){
//Define if enabling or not the bot
let enabling = true;
if(msg === prefix + 'STOP') enabling = false;
//Let's get the guild ID from the msg
let guildID = msg.guild.ID;
//Find the corresponding object in the array of the settings
let index = emojisettings.findIndex(element => element.guildID === guildID);
//If we found the guild setting
if(index >-1){
emojisettings[index].enabled = enabling;
}
//If not found that means the guild isn't stored in the array, so we need to create a new object for it
else{
emojisettings = emojisettings.push({guildID = msg.guild.ID, enabled = enabling});
}
//Reply to the message
if(enabling) message.channel.send("Snoopy is now reading the chat. :book:");
else message.channel.send("Snoopy is not reading the chat. :book:");
}
Keep in mind that this settings will be lost if the bot gets stopped, so if the bot stops running all this settings will be lost, so if you want to keep this setting forever you will need a database, I would suggest you mongoDB, but there are many others and if your bot is not in many servers you can even use a simple json file even if not optimal and risky sometimes.
After that we can simply check if the setting is enabled and if so react to the message:
bot.on('message', message => {
//Check if message was received in a server and not in DMs
if(message.channel.type !== 'text') return;
//Check if the setting is enabled for the server where you received the message
let enabled = emojisettings.find(element => element.guildID === message.guild.ID).enabled;
//If setting is enabled, react to the message
if (enabled) {
emojirandom = emojisnop[Math.floor(Math.random() * emojisnop.length)]
message.react(emojirandom)
}
}
});

How to make Discord.js reply to discord DM messages

I am trying to get discord.js to read DM messages on Discord to have a bot that will be a server/faction application bot made from scratch but I Have it to send the first part when you type %apply to the bot the problem comes when trying to get passed question 2 it keeps getting question 2 instead of going to question 3
I am trying to filter out the DM messages when they are not the same as the passed ones so I have several if commands
bot.on("message", function(message) {
if (message.author.equals(bot.user)) return;
if (message.content === "%apply") {
apply = "TRUE";
a0 = message.author.lastMessageID
message.author.sendMessage("```We need to ask some questions so we can know a litte bit about yourself```");
message.author.sendMessage("```Application Started - Type '#Cancel' to cancel the application```");
message.author.sendMessage("```Question 1: In-Game Name?```");
}
if ((message.guild === null) && (message.author.lastMessageID != a0) && (message.content != "%apply") && (apply === "TRUE")) {
a1 = message.author.lastMessageID;
message.author.sendMessage("```Question 2: Age?```");
}
if ((message.guild === null) && (message.author.lastMessageID != a1) && (message.author.lastMessageID != a0) && (apply === "TRUE")) {
a2 = message.author.lastMessageID;
message.author.sendMessage("```Question 3: Timezone? NA, AU, EU, NZ, or Other? (If other, describe your timezone)```");
}
if ((message.guild === null) && (message.author.lastMessageID != a2) && (message.author.lastMessageID != a1) && (message.author.lastMessageID != a0) && (apply === "TRUE")) {
a3 = message.author.lastMessageID;
message.author.sendMessage("```Question 4: Do you have schematica?```");
}
I expected it to go from question 1 to question 2 the question 3
Preface
Although #Gruntzy's answer is not wrong, there's another solution that's built into Discord.js and meant for these situations - TextChannel.awaitMessages(). You can use it in a system like shown below.
Sample Code
const questions = [ // ------------------------------------
"What's your IGN?", //
"How old are you?", // Define the questions you'd like the
"What time zone do you reside in?", // application to have in this array.
"Do you have Schematica?" //
]; // ------------------------------------
const applying = [];
bot.on("message", async message => {
if (message.author.bot) return;
if (message.content.toLowerCase() === "%apply") {
if (applying.includes(message.author.id)) return;
try {
console.log(`${message.author.tag} began applying.`);
applying.push(message.author.id);
await message.channel.send(":pencil: **Application started!** Type `#cancel` to exit.");
for (let i = 0, cancel = false; i < questions.length && cancel === false; i++) {
await message.channel.send(questions[i]);
await message.channel.awaitMessages(m => m.author.id === message.author.id, { max: 1, time: 300000, errors: ["time"] })
.then(collected => {
if (collected.first().content.toLowerCase() === "#cancel") {
await message.channel.send(":x: **Application cancelled.**");
applying.splice(applying.indexOf(message.author.id), 1);
cancel = true;
console.log(`${message.author.tag} cancelled their application.`);
}
}).catch(() => {
await message.channel.send(":hourglass: **Application timed out.**");
applying.splice(applying.indexOf(message.author.id), 1);
cancel = true;
console.log(`${message.author.tag} let their application time out.`);
});
}
await message.channel.send(":thumbsup: **You're all done!**");
console.log(`${message.author.tag} finished applying.`);
} catch(err) {
console.error(err);
}
}
});
Explanation
To make the code easier to understand, let's go through it step by step...
1. Preceding lines.
The questions array contains all the questions you'd like to be asked in the application. I've implemented an array for the sake of efficiency; a question can be added or removed by changing only one line, and the same code won't be copied and pasted over and over again.
The applying array will help us keep track of the users in the application process.
2. Message event.
I've implemented an arrow function (ES6) as the callback and declared it async so we can use await inside of it.
We prevent bots from triggering any commands.
We check case iNsEnSiTiVeLy whether or not we should run the command.
You'll notice that the proceeding code is wrapped in a try...catch statement. This allows us to catch any unhandled promise rejections (i.e. if TextChannel.send() throws an error) easily.
3. %apply command.
We add the user to the applying array.
We send the start message.
Notice the keyword await: it waits for the promise to be fulfilled before moving on.
We use a for loop to iterate through the questions array.
let i = 0, cancel = false declares i (the "counter" variable) and a cancel variable so we can stop the loop if the user wants to cancel the application or if it times out. We can't use break within our then() callback, so I've reverted to this method.
i < questions.length && cancel === false are our conditions to match before continuing with the next iteration - the counter must be within the range of the array, and cancel must still be false.
i++ increments the counter by 1.
Inside the loop, we send the question and then invoke TextChannel.awaitMessages(). Our first parameter is a filter that the message must pass through, and the second is the options.
In our then() callback, we check if the message was #cancel. If so, we send the cancellation message, remove the user from the array, and set cancel's value to true.
Our catch() method would be called if no message was provided in 5 minutes. So in our callback, we send the time out message, remove the user from the array, and set cancel's value to true.
Once the loop is complete, we send the completion message. At this point, you'd handle the application however you wish.
Your variables a0, ... , a3 are inside the "onMessage" scope, and are undefined everytime in your callback. So if you message is not %apply, you are stuck into the "Question 2" step
You should keep track of your users registration steps in a global variable, and read it to know what step you are into.
Here is a short example of how to do this.
Note that this is a really basic approach, and it might be better to use some in-memory database if you need to add more complex features to your apply procedure.
It also requires more controls, and I guess some other data storage to keep track of the user answers.
let userApplications = {}
bot.on("message", function(message) {
if (message.author.equals(bot.user)) return;
let authorId = message.author.id;
if (message.content === "%apply") {
console.log(`Apply begin for authorId ${authorId}`);
// User is not already in a registration process
if (!(authorId in userApplications)) {
userApplications[authorId] = { "step" : 1}
message.author.send("```We need to ask some questions so we can know a litte bit about yourself```");
message.author.send("```Application Started - Type '#Cancel' to cancel the application```");
message.author.send("```Question 1: In-Game Name?```");
}
} else {
if (message.channel.type === "dm" && authorId in userApplications) {
let authorApplication = userApplications[authorId];
if (authorApplication.step == 1 ) {
message.author.send("```Question 2: Age?```");
authorApplication.step ++;
}
else if (authorApplication.step == 2) {
message.author.send("```Question 3: Timezone? NA, AU, EU, NZ, or Other? (If other, describe your timezone)```");
authorApplication.step ++;
}
else if (authorApplication.step == 3) {
message.author.send("```Question 4: Do you have schematica?```");
authorApplication.step ++;
}
else if (authorApplication.step == 4) {
message.author.send("```Thanks for your registration. Type %apply to register again```");
delete userApplications[authorId];
}
}
}
});
some other quick notes :
sendMessage(msg) is deprecated in the discord.js api, send(msg) should be used now
to test if the message received is a dm, i think it's better to check message.channel.type rather than looking for a empty message.guildId

In a Firebase Database Cloud Function, how can you add/replace an item in a list

My database structure is:
- Scores
- r5rwuerpepdsoazdf (user UID)
- scoreString: "5,12"
- j6fdasdfsdfs08fdd
- scoreString: "1,4,7"
and I have a cloud function that updates the Score for a particular user UID when a value is written in another node (ScoreStacks):
exports.updateScoreString =
functions.database.ref('/ScoreStacks/{uid}/{levelKey}/Score')
.onWrite((dataSnapshot, context) => {
const score = dataSnapshot.after.val();
const scoreStringRef = dataSnapshot.after.ref.root.child('Scores/' + context.params.uid + '/scoreString');
return scoreStringRef.transaction(scoreString => {
if (!scoreString) // transactions return null first time
{
return scoreString;
}
return scoreStringWithAddedScore(scoreString, score);
});
});
This is all working fine apart from if the UID in the Scores node does not yet exist. Then nothing happens.
I guess that is fair enough because scoreStringRef is refering to a path that doesn't exist.
So how can I ensure a new entry is made in the Scores node if the UID does not exist yet? It all has to be done atomically.
It's hard to be sure without seeing scoreStringWithAddedScore, but I suspect you want this:
return scoreStringRef.transaction(scoreString => {
if (!scoreString) // transactions return null first time
{
scoreString = "";
}
return scoreStringWithAddedScore(scoreString, score);
});
A more concise (but somewhat harder to parse if you're new to this) version:
return scoreStringRef.transaction(scoreString => {
return scoreStringWithAddedScore(scoreString || "", score);
});

Resources