Show users that reacted to a message with a ReactionCollector - javascript

I'm trying to make a "vote" command where you can see who voted. This is my current code:
message.channel.send("React with 🗡 to vote").then(Msent =>
Msent.react('🗡')).then(Msent => {
const collector = Msent.message.createReactionCollector((reaction, user) => reaction.emoji.name === '🗡', {
time: 15000
});
collector.on('collect', r => {
if (r.emoji.name === '🗡') {
message.channel.send("someone voted!");
}
});
collector.on('end', collected => {
message.channel.send(`${collected.size} users voted`);
});
})
I've been trying to replace "someone voted!" with the name of the user who voted: how can I do this?
Another thing is that collected.size is 0 if no one reacted, but its always 1 no matter if 1 or 5 users reacted: what am I doing wrong there?

I've been trying to replace "someone voted!" with the name of the user who voted: how can I do this?
You can't get the user from the 'collect' event handler because the parameter is a MessageReaction: you can only get all the users that reacted.
The only thing you can do is modify the filter to something like this:
const filter = (reaction, user) => {
if (reaction.emoji.name === '🗡') {
message.channel.send(`${user} voted!`); // This will mention the user
message.channel.send(`${user.tag} voted!`); // This will only say their name
return true;
} else return false;
}
const collector = Msent.createReactionCollector(filter, { time: 15000 });
collected.size is 0 if no one reacted, but its always 1 no matter if 1 or 5 users reacted: what am I doing wrong there?
The problem is that collected contains the collected reactions, not the users, and since your filter accepts only one emoji its size will only be 0 or 1.
To get the number of users you need to check for MessageReaction.users.size:
collector.on('end', collected => {
let n_users = 0;
if (collected.size > 0) { // Check if there was any vote
let reaction = collected.first(); // Take the first (and only) 'type' of reaction
n_users = reaction.users.size; // Get the number of users that used it
}
message.channel.send(`${n_users} users voted`);
});

Related

Global array returns empty with values from .push

I have an interaction handler that shows a name of a user stored in mongodb one at a time from a click on a button component. There are two buttons, first user and second user, some sort of pagination. My problem is that the users array that received values from push on the first user button returns empty when called on the second user button.
The array does receive values just fine, but it really is empty when I check the value of users on console on the second button interaction (returns []).
So my question is how can I use the global array that received values from .push on my other interaction check conditions?
client.on("interactionCreate", async (interaction) => {
let page = 0;
let users = [];
if (interaction.customId === "firstUserBtn") {
db.collection("users")
.find()
.forEach((user) => users.push(user))
.then(() => {
const name = users[page].name;
console.log(name);
});
}
if (interaction.customId === "secondUserBtn") {
page += 1;
const name = users[page].name;
console.log(name);
}
});
Global array returns empty with values from .push
The users array isn't global, it's a local created every time the client.on callback is run. That means that the array that gets values added to it when interaction.customId === "firstUserBtn" is a different array than the one you're looking at later when interaction.customId === "secondUserBtn".
If you want to use the same array, move users out of the callback (you probably want to move page as well):
let users = []; // <==============================================
let page = 0; // <==============================================
client.on("interactionCreate", async (interaction) => {
if (interaction.customId === "firstUserBtn") {
db.collection("users")
.find()
.forEach((user) => users.push(user))
.then(() => {
const name = users[page].name;
console.log(name);
});
}
if (interaction.customId === "secondUserBtn") {
page += 1;
const name = users[page].name;
console.log(name);
}
});

Giving multiple prompts over DM's

So I need a command to send 4 different messages to the user, each message with a new prompt, so for example "Prompt 1" and what ever the user responds will be pushed into an array called "config". I thought about using message collectors, but couldn't set it up to collect multiple answers.
Pseudo code:
let config = new Array();
message.author.send("Prompt 1");
config.push(collected.answer).then(
message.author.send("Prompt 2");
config.push(collected.answer).then(
ect...
)
You CAN use message collectors. However, you need to have it in a variable. Here is an example:
msg.author.send('some prompt').then(m => {
let i = 0;
var collector = m.channel.createMessageCollector(me =>
me.author.id === msg.author.id && me.channel === m.channel, {max: /*some number*/})
collector.on('collect', collected => {
if(collected.content === 'end') return collector.stop();
//basically if you want to stop all the prompts and do nothing
i +=1
if(i === 1) return collected.channel.send(/*something*/); //next prompt
if(i === 2) return collected.channel.send(/*something*/); //and so on until you get to the last prompt
})
collector.on('end', collectedMsgs => {
if(collectedMsgs.size < /*amount of prompts*/) {
return collectedMsgs.first().channel.send('Ended early, nothing was done.');
}
//some action you would do after all are finished
})
})
There may be some missing parentheses, you will have to add them.

members map with a limit per page

how could I make a member limit on a page? for example: only 10 members would appear on the first page, and to see the second page you would have to react with ⏩
const { MessageEmbed } = require('discord.js');
module.exports.run = async (client, message, args) => {
const role = message.mentions.roles.first() || message.guild.roles.cache.get(args[0]) || message.guild.roles.cache.find(r => r.name === args.slice(0).join(" "));
const embed = new MessageEmbed()
.setTitle(`Members with a role`)
.addFields(
{ name: 'alphabetical list', value: `\`\`\`fix\n${message.guild.roles.cache.get(role.id).members.map(m => m.user.tag.toUpperCase()).sort().join('\n') || 'none'}\`\`\``}
)
return message.channel.send(embed);
}
I would get the list of users as an array, then use slice to return a portion of the array. In your case I would do:
//Get a list of all user tags
const list = msg.guild.roles.cache.get(role.id).members.map(m => m.user.tag.toUpperCase()).sort();
//Let the user define the starting page
var pageNum = (parseInt(args[0]) * 10) - 10;
//Set a default option
if (!pageNum) {
pageNum = 0;
};
//Get 10 members, starting at the defined page
//Ex: if args[0] was "2", it would give you entries 10-19 of the array
var userList = list.slice(pageNum, pageNum + 9).join("\n");
Now that you can get users based off of a page number, you just need a way to set it! createReactionCollector is what you're looking for in this case. The discordjs.guide website has a great example of this that we can modify to fit our needs:
//Only respond to the two emojis, and only if the member who reacted is the message author
const filter = (reaction, user) => ["◀️", "▶️"].includes(reaction.emoji.name) && user.id === msg.author.id;
//Setting the time is generally a good thing to do, so that your bot isn't constantly waiting for new reactions
//It's set to 2 minutes in this case, which should be plenty of time
const collector = msg.createReactionCollector(filter, {
time: 120000
});
collector.on('collect', (reaction, user) => {
//Do stuff here
});
//We can just return when the reactor ends, send a message that the time is up, whatever we want!
collector.on('end', collected => {
return msg.channel.send("I'm done looking for reactions on the message!");
});
Now that we can get users and await reactions, we only need to put everything together. I would put the list retrieval in a seperate function that you can call easily:
//Initially take the page number from user input if requested
var page = parseInt(args[0]);
if (!page) {
page = 1;
};
//Send the message in a way that lets us edit it later
const listMsg = await msg.channel.send("This is what will be reacted to!");
//React in order
await listMsg.react("◀️");
await listMsg.react("▶️");
const filter = (reaction, user) => ["◀️", "▶️"].includes(reaction.emoji.name) && user.id === msg.author.id;
const collector = listMsg.createReactionCollector(filter, {
time: 120000
});
collector.on('collect', (reaction, user) => {
reaction.emoji.reaction.users.remove(user.id);
switch (reaction.emoji.name) {
case "◀️":
//Decrement the page number
--page;
//Make sure we don't go back too far
if (page < 1) {
page = 1;
};
listMsg.edit(getUsers(page));
break;
case "▶️":
//Increment the page number
++page;
listMsg.edit(getUsers(page));
break;
};
});
collector.on('end', collected => {
return msg.channel.send("I'm done looking for reactions on the message!");
});
function getUsers(n) {
const list = msg.guild.roles.cache.get(role.id).members.map(m => m.user.tag.toUpperCase()).sort();
//Take the page from the function params
var pageNum = (n * 10) - 10;
if (!pageNum) {
pageNum = 0;
};
return list.slice(pageNum, pageNum + 9).join("\n");
};
That's pretty much it! Obviously you'll have to tweak this to fit your own bot, but this code should be a great starting point.

Reason after blacklisting command Discord.js

I want to add a reason to my blacklists (with the command !blacklist {userid} {reason}) which are visible in the embeds below like .addField ("💬 Reason:", somecode) how can I fix this?
if (command === "blacklist") {
if(!config["allowed-users"].includes(message.member.id)) return;
const user = client.users.cache.get(args[0]);
if(!user) {
return message.channel.send("This user does not exist")
}
if(blacklist.has(user.id)) {
return message.channel.send("This user is already on the blacklist")
}
blacklist.set(user.id, 'blacklisted');
let set = db.fetch(`g_${message.guild.id}`);
var embed = new Discord.MessageEmbed()
.setTitle(":warning: Blacklisted :warning:")
.setColor('#fc5a03')
.addField("👮 Moderator:", message.author.tag)
.addField("👤 User:", user.username)
.addField("🆔 User ID:", user.id)
.addField("🕒 Blacklisted on:", message.createdAt)
.setFooter("© 2020 - 2021 GlobalChat", "https://cdn.discordapp.com/avatars/759021875962576916/cc32b2b08fdd52ae86294516d34532c5.png?size=128")
.setThumbnail(user.avatarURL({ dynamic:true }))
.addField("Unblacklist?", "Please contact <#267818548431290369> or <#331736522782932993>");
client.guilds.cache.forEach(g => {
try {
client.channels.cache.get(db.fetch(`g_${g.id}`)).send(embed);
} catch (e) {
return;
}
});
}
First you'll want to check if there is no reason, this can be simple done by checking, for both approaches, if the second argument is undefined, like so
if (args[1] === undefined) {
const reason = "No reason.";
}
This solution will work for both approaches, since if the second argument is undefined there can be no more after it
You could take reason as an argument.
Inside the command add
const reason = args[1];
OR if you wanted to have the rest of the blacklist args dedicated to the reason you could add something along the lines of
let reason = ""
for (let i = 1; i < args.length; i++) {
// It's very important that i starts as 1, so we do not take the first argument into account for the reason
reason += args[i];
}
And then you can add to the embed
.addField("💬 Reason:", reason);
If you went with the first approach, the blacklist command would work like this
!blacklist 012345678910111213 the_reason_here
// or
!blacklist 012345678910111213 reason
The limitation to this approach is that a multi word reason isn't very intuitive.
If you went with the second approach though, the blacklist command would work like this
!blacklist 012345678910111213 The reason the user was banned and it can go on and on and on as long as the writer wants
You'll want to fetch the reason in the same way that you fetched the user id, like this:
const reason = args[1];
After that, in order to make sure that the reason doesn't show as undefined, you'll want to add a check in the form of an if statement, like this:
if (!reason) {
reason = "No reason";
}
After that, add .addField("💬 Reason:", reason) in the position of fields you want it to be.
Your code should look something like this:
if (command === "blacklist") {
if (!config["allowed-users"].includes(message.member.id)) return;
const user = client.users.cache.get(args[0]);
const reason = args[1];
if (!user) {
return message.channel.send("This user does not exist")
}
if (blacklist.has(user.id)) {
return message.channel.send("This user is already on the blacklist")
}
if (!reason) {
reason = "No reason";
}
blacklist.set(user.id, 'blacklisted');
let set = db.fetch(`g_${message.guild.id}`);
var embed = new Discord.MessageEmbed()
.setTitle(":warning: Blacklisted :warning:")
.setColor('#fc5a03')
.addField("👮 Moderator:", message.author.tag)
.addField("👤 User:", user.username)
.addField("🆔 User ID:", user.id)
.addField("🕒 Blacklisted on:", message.createdAt)
.addField(("💬 Reason:", reason)
.setFooter("© 2020 - 2021 GlobalChat", "https://cdn.discordapp.com/avatars/759021875962576916/cc32b2b08fdd52ae86294516d34532c5.png?size=128")
.setThumbnail(user.avatarURL({
dynamic: true
}))
.addField("Unblacklist?", "Please contact <#267818548431290369> or <#331736522782932993>");
client.guilds.cache.forEach(g => {
try {
client.channels.cache.get(db.fetch(`g_${g.id}`)).send(embed);
} catch (e) {
return;
}
});
}

Search a given discord channel for all messages that satisfies the condition and delete

I was trying to make a code that will check all messages in a channel for messages that contain certain words, and delete them if it does contain them. So something like:
if(msg.content.startsWith(prefix+'clean') {
let check = msg.content.split(prefix+'clean')[1]; // Condition, in this case if it containts a certain string
msg.channel.fetchMessages().then(msgs => { // Get messages to check
let msglog = msgs.array() // Make an array with all the messages fetched
for(var i = 0; i < msglog.size; i++) { // Loop to check all messages in array
if (check in msglog[i]) {
// Code to delete that message
};
};
});
};
I am aware that this will not check the entire channel and it will only check the last 50 messages, but I do not know how to make it check the whole channel so this will do until I find out how to do that.
But what code would delete the message that passes the check? Or any different way I could approach this?
Edit:
It seems I was not clear enough, so let's say a channel has the following conversation:
Person A: Hi, guys!
Person B: Hi
Person C: Bye
Let's say I want to delete all the messages with "Hi" in it through my bot, how should I do this? Note: I do not with to delete a message right after it has been sent, I only want to delete it when I want to do so.
Well, this is how I solved my problem after I realised the 2 week limitation of fetchMessages()
else if(msg.content.startsWith(`${prefix}clean`}) { // Check for command
let check = msg.content.split(`${prefix}clean`)[1] // Defines a check
msg.channel.fetchMessages({ limit: 100 }).then(msgs => { // Fetches the last 100 messages of the channel were the command was given
const msgstodelete = msgs.filter(del => del.content.includes(check)) // Filters the messages according to the check
msg.delete() // Deletes the original message with the command
for (var i = 0; i<Array.from(msgstodelete.keys()).length; i++) {
msg.channel.fetchMessage(Array.from(msgstodelete.keys())[i]).then(deldel => deldel.delete())
} // Loop to delete all messages that passed the filter
})
}
The bulkDelete function delete given messages that are newer than two weeks.
if(msg.content.startsWith(prefix+'clean') {
let check = msg.content.split(prefix+'clean')[1]; // Condition, in this case if it containts a certain string
msg.channel.fetchMessages().then(msgs => { // Get messages to check
let msgDel = msgs.filter(msgss => msgss.content.includes(check)) // Finds all messages with 'check'
msg.channel.bulkDelete(msgDel) // Deletes all messages that got found
});
};
To delete the messages older than 2 weeks, you have to iterate through the messages manually to delete them:
async function deleteReturnLast(chan, option, prevMsg, cond) {
return chan.fetchMessages(option)
.then(async msgs => {
if (msgs.size === 0){
if (cond(prevMsg)) {
prevMsg.delete()
.then(d => console.log('last message deleted: ' + d.content))
.catch(err => console.log('ERR>>', err, prevMsg.content, option.before)); }
return prevMsg;
};
let last = msgs.last();
for (const[id, msg] of msgs) {
let tmp = (id === last.id) ? prevMsg : msg;
if (cond(tmp)) {
tmp.delete()
.then(d => console.log('Message deleted: ' + d.content))
.catch(err => console.log('ERR>>', err));
}
};
return last;
})
.catch(err => console.log('ERR>>', err));
}
function cond(msg) {
return !msg.content.includes('a');
}
client.on('message', async function(msg) {
let chan = msg.channel;
let last = chan.lastMessage;
while (last !== (last = await deleteReturnLast(chan, {limit: 2, before: last.id}, last, cond))){
};
});

Categories

Resources