ReactionCollector filter doesn't work with one of the emojis - node.js

if (cmd === "test") {
message.channel.send("Pick a reaction").then(async function(message) {
await message.react("🇳");
await message.react("🇧");
await message.react("🇴");
const filter = (reaction, user) => reaction.emoji.name === "🇳" || reaction.emoji.name === "🇧" || reaction.emoji.name === "🇴" && reaction.message.author.id === "266315409735548928";
let collector = message.createReactionCollector(filter, {
time: 15000
});
collector.on('collect', (reaction, collector) => {
switch (reaction.emoji.name) {
case "🇳":
message.channel.send("Picked N");
break;
case "🇧":
message.channel.send("Picked B");
break;
case "🇴":
message.channel.send("Picked O");
break;
}
});
collector.on('end', collected => {
return;
});
});
}
For some reason my code stopped working today: when I switch the 🇴 and 🇧 in the filter it does work but when I leave it as it is now, nothing happens when 🇴 is picked.
What is causing this problem?

The problem is not the 🇴 itself, but the way logical operators work. Let's say you have this expression:
first || second && third
That means that the program is first going to check first, then if that's true since there's a || after it, it will stop, otherwise, it will check for second. When it checks for second it doesn't stop if its true, because you put a && after it. That expression translates to this one:
first || (second && third)
Your code works with the first two reactions because it's not checking the id, while it does it in the third (and it happens not to be the same: false)
I suggest you to switch to this expression:
["🇳", "🇧", "🇴"].includes(reaction.emoji.name) && reaction.message.author.id == '266315409735548928';
This does the same thing but in a more compact way. If you still want to use the old way, write this:
(reaction.emoji.name === "🇳" || reaction.emoji.name === "🇧" || reaction.emoji.name === "🇴") && reaction.message.author.id === "266315409735548928";
Sidenote: since you wrote that it didn't work with the 🇴, it won't work with this code too because reaction.message.author.id == '266315409735548928' still returns false.
If you were trying to check if the reaction was added by you, you should use user.id == 'your id'.
I don't see the point in checking the ID of the message's author since the message was sent by the bot and the ReactionCollector works only with that message.

Related

Bot replying multiple times when triggered by reaction

Right now I am making a discord bot (in version 12 of discord.js).
It works like this:
Someone sends and insult
If the insult is included in a list (stored in insultes.json) the bot send a message and adds a reaction
If we add the same reaction the bot sends another message
The problem I'm facing is that if I keep adding the reaction the bot keeps replying 2, 3, 4 times and so on: every time (n) I check the reaction it replies with n+1 messages.
This is the code:
bot.on('message', message => {
const insulte = require('./insultes.json');
for (let p = 0; p < insulte.length; p++) {
// Check if the insult is in the list and make sure it's not from the bot itself
if (message.content.toLowerCase().includes(insulte[p]) && message.author.id !== "711337757435363468") {
message.channel.send("First message").then(messageReaction => {
messageReaction.react("➡️");
});
bot.on('messageReactionAdd', (reaction, user) => {
if (reaction.emoji.name === "➡️" && user.id !== "711337757435363468") {
message.channel.send("Additional message");
}
});
}
}
});
I think your problem comes from the fact that you're using bot.on('messageReactionAdd', ...: that means that every time you run that part of the code the code will add another listener that adds up to the ones that you used before.
Also, that code will trigger when a reaction to any message is added, not just the one you sent.
From your question, I don't understand if the bot is supposed to reply with one message every time you hit the reaction on that message, or just do it once and then ignore the message. I'll assume it's the latter.
Here's my take on this:
bot.on('message', message => {
const insults = require('./insultes.json')
if (insults.some(i => message.content.toLowerCase().includes(i)) && message.author.id !== "711337757435363468") {
message.channel.send("First message").then(myMsg=> {
myMsg.react("➡️");
let reactionFilter = (reaction, user) => reaction.emoji.name === '➡️' && user.id !== "711337757435363468"
myMsg.awaitReactions(reactionFilter, { max: 1 }).then(() => {
myMsg.channel.send('Additional message')
})
});
}
})
As you can see, I'm using Array.some() to check whether any insult is in the message, instead of a for loop. I'm using Message.awaitReactions() to fetch the first reaction and respond to that: after that, the bot will just ignore any other reaction on that message, but will still work on others.
Feel free to let me know if something is not clear or doesn't work :)

How can i check if a person has went online, offline, etc. in discord.js?

const channel = client.channels.cache.get('<channelid>');
const person1 = client.users.cache.get('<userid>');
const person = client.users.cache.get('<userid>');
client.on('message', message =>{
client.on('presenceUpdate', () =>{
if(person1.user.presence.status === 'dnd' || person1.user.presence.status === 'online'){
channelforstatus.send('person1 is now online');
}
else if(peron1.user.presence.status === 'offline' || person1.user.presence.status === 'idle'){
channel.send('person1 is offline');
}
client.on('message', message => {
client.on('presenceUpdate', () =>{
if(person.user.presence.status === 'dnd' || person.user.presence.status === 'online'){
channel.send('person is now on');
}
else if(person.user.presence.status === 'offline' || person.user.presence.status === 'idle'){
channel.send('person is now off');
}
});
});
});
});
This is what I've tried and the .send() the function is not working. I've looked everywhere and found nothing that could help me with this problem. I just need it so it checks every time if a specific person has went online, offline, etc. And sends a message to a specific channel.
First of all, one rule to abide with is that event listeners should always be in top level of your code and never nested. (Else you are subject to memory leaks and other issues like duplicated and unintended code execution).
client.on("message", (message) => {
...
});
client.on('presenceUpdate', (oldPresence, newPresence) => {
...
});
Now when looking at presenceUpdate event and Presence object documentation you can manage to see if a status evolved like that :
client.on('presenceUpdate', (oldPresence, newPresence) => {
let member = newPresence.member;
// User id of the user you're tracking status.
if (member.id === '<userId>') {
if (oldPresence.status !== newPresence.status) {
// Your specific channel to send a message in.
let channel = member.guild.channels.cache.get('<channelId>');
// You can also use member.guild.channels.resolve('<channelId>');
let text = "";
if (newPresence.status === "online") {
text = "Our special member is online!";
} else if (newPresence.status === "offline") {
text = "Oh no! Our special member is offline.";
}
// etc...
channel.send(text);
}
}
});
Be aware that presenceUpdate event is fire by EACH guild the user and bot share, meaning that if user status change and share two guilds with your bot, this code will be executed twice.
In case you use presence but get offline instead of the user being online I spent like.. 2 whole days looking for the answer so ill share it anywayz
Common mistakes on presence.status is forgetting to check these stuff at the the developer applications. which i have no idea what means
A screenshot
now on your message (command handler) function.. if you have one
message.guild.members.cache.get('userId').presence.status
or
${message.author.username} is now ${message.author.presence.status};
Ill update this if I found out how to presence all the users instead of just one
my first post... I SHALL REMEMBER THIS xD
To get the presence, you can use user.presence, which will get all kinds of info about the user, but you only need user.presence.clientStatus.desktop
so your code would, for example, be
bot.on('presenceUpdate', () =>{
let person1 = bot.users.cache.get('USERID')
console.log(person1.presence.clientStatus.desktop)
if(person1.presence.clientStatus.desktop === 'dnd' || person1.presence.clientStatus.desktop === 'online'){
channel.send('person1 is now online');
}
else if(person1.presence.clientStatus.desktop === 'offline' || person1.presence.clientStatus.desktop === 'idle'){
channel.send('person1 is offline');
}
})

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

I want to send a message to a specific user after someone joined into a specific channel

So, I want my Discord bot to send a message to a specific user whenever someone joins into a specific channel. But my code doesn't seem to work.
Does someone know how to fix it?
client.on('voiceStateUpdate', (oldMember, newMember) => {
let newUserChannel = newMember.voiceChannel
let oldUserChannel = oldMember.voiceChannel
if(oldUserChannel === undefined && newUserChannel !== undefined) {
if(voiceChannel.id === "530501827389685762"){
client.users.get("188984116618854400").send("Ein neuer Spieler ist im Aufnahmebereich!")
}
else {
return;
}
}
})
On line 6, you write:
if (voiceChannel.id ...
voiceChannel is not defined.
For future reference, include the error. Some style remarks:
1. the else and return statement are unnecessary if that's the end of your function
2. research truthy and falsy values in javascript, they can save you some time checking if channels are undefined.

How to check if the variable is moment object in NodeJS

I have a simple scenario where I want to test whether I am sending a string or a number or boolean value instead of a moment object and proceed accordingly.
According to the docs, there is a function called isMoment() which will tell whether the element is a moment object or not. Now this works fine in a browser I tested using a JSfiddle.
But same doesn't work in NodeJS for the same version 2.13.0
This line console.log(moment.isMoment("String")); outputs false in browser but in NodeJS outputs
Deprecation warning: moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.
Arguments: [object Object]
Error
at Function.createFromInputFallback (/home/oroborus/vehico-api-business/node_modules/moment/moment.js:271:105)
and execution stops. How do I proceed here?
I read the issue. What they are saying is right that you cannot convert date string reliably to moment and hence the deprecation warning but then how do I test if whatever i am sending if it's a moment object then proceed if not return without a proper message.
Here is the code
if (req.body.startDate === "" || req.body.endDate === "" || req.body.driverId === "") {
res.status(400).send(JSON.stringify({
message: "Empty strings not allowed"
}));
} else if (req.body.startDate === null || req.body.endDate === null || req.body.driverId === null) {
res.status(400).send(JSON.stringify({
message: "Null values are not allowed"
}));
} else if (moment.isMoment(req.body.startDate) || moment.isMoment(req.body.endDate)) {
console.log("This is not working :( );
res.status(400).send(JSON.stringify({
message: "Only moment objects are allowed"
}));
} else {
//The rest of the procedure
}
It has nothing to do with moment, your last condition is not good. moment is failing like it should because the else branch is executed when the input dates are NOT moment objects.
if (req.body.startDate === "" || req.body.endDate === "" || req.body.driverId === "") {
res.status(400).send(JSON.stringify({
message: "Empty strings not allowed"
}));
} else if (req.body.startDate === null || req.body.endDate === null || req.body.driverId === null) {
res.status(400).send(JSON.stringify({
message: "Null values are not allowed"
}));
// ------------ you forgot ! here
} else if (!moment.isMoment(req.body.startDate) || !moment.isMoment(req.body.endDate)) {
console.log("This is not working :( )";
res.status(400).send(JSON.stringify({
message: "Only moment objects are allowed"
}));
} else {
//The rest of the procedure
}
One of the answers I found out while reading the docs thoroughly(Sorry my bad I didn't do it earlier). There is a line here which says
If a string does not match any of the above formats and is not able to
be parsed with Date.parse, moment#isValid will return false.
moment("not a real date").isValid();
which after printing a warning in node returns false.

Resources