members map with a limit per page - javascript

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.

Related

Discord js send messages to people who did not answer

I would like to make a command that sends a message to the players of a specific role who did not respond (with a reaction) to the last message in a channel
let channel = client.channels.cache.get(channelID);
var listUserResponce = new Array();
var lastMessageToKeep = channel.messages.fetch({limit: 1}).then(messages => {
messages.first().reactions.cache.forEach(async(reaction) => {
//get user who react with specific emoji
if(reaction._emoji.name === emojiAcceptedName || reaction._emoji.name === emojiDeclineName || reaction._emoji.name === emojiMaybeName){
let list = await reaction.users.fetch();
for(var [key,value] of list){
listUserResponce.push(value)
}
}
});
return messages.first();
})
//get user from role
message.guild.roles.fetch(roleId).then(role => {
role.members.forEach(member => {
//send a message to the users of the group who are not present in the list,
if(!listUserResponce.includes(member.user)){
lastMessageToKeep.then(function(result){
member.user.send(msgVote+result.url)
})
}
})
})
The listUserResponce is empty.
I am a beginner in node.js, I don't understand how to retrieve and keep the info. I tried with Promise.resolve but it doesn't work.
I tried to use the same method as for the lastmessage but I can't return the list of users. It is empty outside the foreach
Can someone help me / point me to the right way ?
I use discord v13.9
I found a solution, I post it here if it can help other people
let channel = client.channels.cache.get(channelID);
listUserResponse= new Array();
//use async in then for waiting Promise.all
channel.messages.fetch({limit: 1}).then(async messages => {
//waiting all async function
await Promise.all(messages.first().reactions.cache.map(async(reaction) => {
if(reaction._emoji.name === emojiAcceptedName || reaction._emoji.name === emojiDeclineName || reaction._emoji.name === emojiMaybeName){
let list = await reaction.users.fetch();
for(var [key,value] of list){
listUserResponse.push(value)
}
}
}));
message.guild.roles.fetch(roleId).then(role => {
role.members.forEach(member => {
if(!listUserResponse.includes(member.user)){
member.user.send(msgVote+messages.first().url)
}
})
})
})

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))){
};
});

How to update 'bot.users.size' in bot status without restarting

So basically I want my bot to have a dynamic status that changes every 5 seconds and displays how many users my bot is currently helping. The changing status part is not the issue, the problem is the users helped count does not change without restarting my bot, which is not ideal considering it is hosted and on many servers, no point restarting all the time just for the user count to be accurate.
What I have tried is by including a interval timer that will update the userCount variable in an attempt to make it accurate to how many users it is helping. What seems to be happening is when a user joins the server, the variable will update accordingly and display +1 in relation to the previous number of users helped. But, when a user leaves, it does not subtract one from the count, instead, it just leaves it at the previous count. I included console.log(userCount) just to make it easier to see the number every 10 seconds in the console, instead of having to wait for the bot's status to change to the proper one.
bot.on('ready', function() {
let userCount = bot.users.size
setInterval(() => {
userCount = bot.users.size
console.log(userCount)
}, 10000);
setInterval(async () => {
let statuslist = [
'blah',
"blah'",
'blah ' + ` ${userCount} Users`
];
const random = Math.floor(Math.random() * statuslist.length);
try {
await bot.user.setPresence({
game: {
name: `${statuslist[random]}`,
type: "Playing"
},
status: "online"
});
} catch (error) {
console.error(error);
}
}, 5000);
console.log("Logged in as " + bot.user.username);
});
I feel as though this isn't a library issue, because I am not very confident in my ability to create code that checks for changes. Ideally, it will display the accurate number based on users joining/leaving servers the bot is on, as well as guilds inviting/removing the bot from the guild. I am not sure if I should use events for this, and even if I was to, I do not know how I could.
But, when a user leaves, it does not subtract one from the count, instead, it just leaves it at the previous count.
This is expected of client.users. It's a collection of all the users the client has cached at some point, so it shouldn't remove any. I think what you're looking for is this...
bot.on('ready', () => {
setInterval(async () => {
let users = 0;
for (let g of bot.guilds.array()) users += (g.members.size - 1));
await bot.user.setActivity(`${users} user${users !== 1 ? 's' : ''}`, {type: 'WATCHING'})
.catch(err => console.error());
}, 15000);
});
Note that this will count users over again if they're in 2 guilds. If you'd rather have a precise count, at the cost of memory, you could use...
bot.on('ready', () => {
setInterval(() => {
let guilds = bot.guilds.array();
let users = [];
for (var i = 0; i < guilds.length; i++) {
let members = guilds[i].members.array();
for (var i = 0; i < members.length; i++) {
if (members[i].user.id !== bot.user.id && users.indexOf(members[i].user.id) === -1) users.push(members[i].user.id);
}
}
bot.user.setActivity(`${users.length} user${users.length !== 1 ? 's' : ''}`, {type: 'WATCHING'})
.catch(err => console.error());
});
});
You have 2 setIntervals, combine them!
That should solve your issue.
You could also use the guildMemberAdd / guildMemberRemove event.
bot.on('ready', function() {
setInterval(async () => {
const statuslist = [
'blah',
'blah_1',
` ${bot.users.size} Users`,
];
console.log(statuslist);
const random = Math.floor(Math.random() * statuslist.length);
try {
await bot.user.setPresence({
game: {
name: `${statuslist[random]}`,
type: 'Playing',
},
status: 'online',
});
}
catch (error) {
console.error(error);
}
}, 15000);
console.log('Logged in as ' + bot.user.username);
});

Show users that reacted to a message with a ReactionCollector

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`);
});

Limit number of records in firebase

Every minute I have a script that push a new record in my firebase database.
What i want is delete the last records when length of the list reach a fixed value.
I have been through the doc and other post and the thing I have found so far is something like that :
// Max number of lines of the chat history.
const MAX_ARDUINO = 10;
exports.arduinoResponseLength = functions.database.ref('/arduinoResponse/{res}').onWrite(event => {
const parentRef = event.data.ref.parent;
return parentRef.once('value').then(snapshot => {
if (snapshot.numChildren() >= MAX_ARDUINO) {
let childCount = 0;
let updates = {};
snapshot.forEach(function(child) {
if (++childCount <= snapshot.numChildren() - MAX_ARDUINO) {
updates[child.key] = null;
}
});
// Update the parent. This effectively removes the extra children.
return parentRef.update(updates);
}
});
});
The problem is : onWrite seems to download all the related data every time it is triggered.
This is a pretty good process when the list is not so long. But I have like 4000 records, and every month it seems that I screw up my firebase download quota with that.
Does anyone would know how to handle this kind of situation ?
Ok so at the end I came with 3 functions. One update the number of arduino records, one totally recount it if the counter is missing. The last one use the counter to make a query using the limitToFirst filter so it retrieve only the relevant data to remove.
It is actually a combination of those two example provided by Firebase :
https://github.com/firebase/functions-samples/tree/master/limit-children
https://github.com/firebase/functions-samples/tree/master/child-count
Here is my final result
const MAX_ARDUINO = 1500;
exports.deleteOldArduino = functions.database.ref('/arduinoResponse/{resId}/timestamp').onWrite(event => {
const collectionRef = event.data.ref.parent.parent;
const countRef = collectionRef.parent.child('arduinoResCount');
return countRef.once('value').then(snapCount => {
return collectionRef.limitToFirst(snapCount.val() - MAX_ARDUINO).transaction(snapshot => {
snapshot = null;
return snapshot;
})
});
});
exports.trackArduinoLength = functions.database.ref('/arduinoResponse/{resId}/timestamp').onWrite(event => {
const collectionRef = event.data.ref.parent.parent;
const countRef = collectionRef.parent.child('arduinoResCount');
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
}).then(() => {
console.log('Counter updated.');
});
});
exports.recountArduino = functions.database.ref('/arduinoResCount').onWrite(event => {
if (!event.data.exists()) {
const counterRef = event.data.ref;
const collectionRef = counterRef.parent.child('arduinoResponse');
// Return the promise from counterRef.set() so our function
// waits for this async event to complete before it exits.
return collectionRef.once('value')
.then(arduinoRes => counterRef.set(arduinoRes.numChildren()));
}
});
I have not tested it yet but soon I will post my result !
I also heard that one day Firebase will add a "size" query, that is definitely missing in my opinion.

Categories

Resources