How to use a loop inside message.react() inside async function discord.js - node.js

I am making a message that allows the user to target another user via a Discord reaction for a game I'm making compatible with Discord. I am trying to make this work for any number of players without specifying each possible amount of players. I have been using a loop to try and make this work. I just want the bot to add a reaction of 1, 2 and 3 as options for a game that has 3 total players (or users) and display the correct 1, 2 or 3 emoji I have specified in reaction_numbers below. (Those emoji's are just blue squares with the number in them that I know work with Discord reactions)
I get an error of (node:10988) UnhandledPromiseRejectionWarning: TypeError: Emoji must be a string or Emoji/ReactionEmoji
var reaction_numbers = ["\u0030\u20E3","\u0031\u20E3","\u0032\u20E3","\u0033\u20E3","\u0034\u20E3","\u0035\u20E3", "\u0036\u20E3","\u0037\u20E3","\u0038\u20E3","\u0039\u20E3"]
var PlayerListMessage = [] <<< Gets list of players and arranges them
for (let i = 0; i < playerUserArray.length; i++) {
PlayerListMessage.push(`${i+1}: ${playerUserArray[i]}\n`)
}
async function QuestionPlayerToTarget(){
let msg = await message.author.send(`Which player activated the card you would like to negate?\n${PlayerListMessage.join("")}\nPlease select only one player.`)
for (var i of playerUserArray){
await msg.react(reaction_numbers[i+(1)]) <<< Error happens here.
}
const filter = (reaction, user) => {
return [reaction_numbers[1], reaction_numbers[2], reaction_numbers[3], reaction_numbers[4], reaction_numbers[5], reaction_numbers[6]].includes(reaction.emoji.name) && user.id === message.author.id;
};
const reply = await msg.awaitReactions(filter, { max: 1 })
.catch(console.error);
const targetPlayer = reply.first()
return targetPlayer
}
var targetPlayer = await QuestionPlayerToTarget()
console.log(targetPlayer)
Any ideas on how to make this loop add reactions for the exact number of players in the game? Thanks in advance for the help!

Next time please comment with # not <<<
As the error says: Emoji must be a string or Emoji/ReactionEmoji > string or emoji.
How to use msg.react()
So you can either use msg.react("emoji") or msg.react(msg.guild.emojis.get("emojiid")).
The first option is for already existing emojis, like numbers in blue squares.
So in Discord you can put a \ before you post an emoji and you are getting this emoji as string.
The second option is for guild-emojis, emojis, which are only available in this guild.
In your Case
use http://getemoji.com/ and search for "one" and you'll get 1️⃣. You can use this symbol for msg.react("1️⃣").
So just put this in the list reaction_numbers and so on...

I wasn't too far off in my attempts. I ended up changing the loop type from a "for of" loop to a standard for loop and it worked as expected. Code changes as follows.
for (let i = 1; i < playerUserArray.length; i++) {
await msg.react(reaction_numbers[i])
}
var reaction_numbers = ["\u0031\u20E3","\u0032\u20E3","\u0033\u20E3","\u0034\u20E3","\u0035\u20E3", "\u0036\u20E3","\u0037\u20E3","\u0038\u20E3","\u0039\u20E3", "\u0030\u20E3"]
I also took the first value of reaction_numbers and put it at the end of the list in order to make index value 0 = emoji of the number 1 with the blue square.

Related

Node.js (discord.js) code randomly stops working in the middle of nothing

So currently, I am making a discord bot in Replit, discord.js. I want to make economy system, and the code is "cutted" in the middle, like there was written "return", but there isn't.
else if (type === "cashadd") {
let target = message.mentions.users.first();
const amount = args.join(" ");
if (!target) return message.channel.send("Please metion someone!")
______________________ <- here the code is "cutted"
if (!amount) return message.channel.send("Please specify the amount of money you want to send!")
if(isNaN(amount)) return message.channel.send("please enter a real number")
let userBalance = await db.get(`wallet_${target}`)
await db.set(`wallet_${target}`, userBalance + amount)
message.channel.send(`You sent ${amount} money to ${target}`)
}
full code: https://pastebin.com/eaStV20P
(I am using command handled from Imagine gaming play if it is somehow useful)
I tried to put an extra like of code saying "it works", I was thinking if it says it. It didn't.
I would assume this is because you never spliced the arguments const type = args.splice(integer).join(" ");
Just using const type = args.join(" "); just adds a space in between each argument. Here's an example:
let args = ["Testing","Lol","Xd"] (this is just an example of what args would be, in your case, it would be the entirety of the command arguments)
Using const type = args.join(" "); would return string "Testing Lol Xd", as you can see by using console.log(type)
Using const type = args.splice(integer).join(" ") would remove all arguments prior to the integer provided, so if you did const type = args.splice(1).join(" "); this would return as "Lol Xd", since 0 is the very first argument in the string, "Lol" would be the second argument in the string, so it would be integer 1, and so on.
So, if you want it to work how you want it to, try using args.splice(integer).join(" ");
An example for your code would be, Assuming your command is "-eco", and "balance" being everything else inside of the array,
if you typed "-eco balance" in a channel, args would be something along the lines of;
args = ["-eco", "balance"]; "balance" string being the type constant/variable you are looking for,
Actual code for this example:
const type = args.splice(1).join(" ");
if(type.toLowerCase === "balance") {
...
} else if(...) { ...

How to make a new embed when the first embed reaches the description limit (4096)

I have added the logs case for my warn slash command, but I have a problem.. that problem is that if the embed description reaches the limit, i get an error and thats not what I want.
So basically, I want the a new embed to be created as like a "second page", and I can use my pagination function to help with navigating between pages and so on. I just don't exactly know how to do that or how to get started.
I am asking for some assistance here because my goal is to have a functional "warning logs" embed with buttons to navigate through the pages if there are more than one like most users will have.
case "logs": {
const buttonPages = require("../../functions/pagination");
const user = interaction.options.getUser("user");
const userWarnings = await warnSchema.find({ Guild: interaction.guild.id, User: user.id });
if (!userWarnings?.length) return interaction.reply({ content: `\`${user.tag}\` does not have any warnings.`, ephemeral: true });
const embedDescription = userWarnings.map((warn) => {
const moderator = interaction.guild.members.cache.get(warn.Moderator);
return [
`<:CL_Shield:937188831227183135> Warn ID: ${warn.id}`,
`<:CL_ReplyContinued:909444370221137930> Moderator: ${moderator || "unknown"}`,
`<:CL_ReplyContinued:909444370221137930> User: ${user}`,
`<:CL_ReplyContinued:909444370221137930> Reason: \`${warn.Reason}\``,
`<:CL_Reply:909436090413363252> Date: ${warn.Date}`,
].join("\n");
}).join("\n\n");
const embed = new EmbedBuilder()
.setTitle(`${user.tag}'s warnings`)
.setDescription(embedDescription)
.setColor("#2f3136");
//const pages = [embed];
//buttonPages(interaction, pages);
await interaction.reply({ embeds: [embed] });
}
What you're trying to do is called pagination. Code is below, but here is how it works:
First what you've got to do is determine how many different embeds it'll be. We can do that with description.length/4096, but we need to round up so we'd use Math.ceil. The Array().keys() allows us to get a list of all the numbers up to it to iterate over. For example Array(3).keys() would give us [0, 1, 2] so we can iterate over it.
We then need to select which part of the description we want to send. We want to start at (i*4096) since all previous embeds have 4096 characters, and then we want it to be 4096 characters long so we simply end it at (i*4096)+4096).
You also need to consider that we cannot always use interaction.reply as interactions can only be replied to once, so we must use interaction.channel.send to send them all to the channel. But, we will want some sort of response to the interaction, so we send the first one as a response to the interaction, and all following ones to the channel.
Here's the code:
for (const i of Array(Math.ceil(embedDescription.length/4096)).keys()) {
const embed = new EmbedBuilder().setDescription(embedDescription.substring((i*4096), (i*4096)+4096))
if(i === 0) await interaction.reply({embeds: [embed]})
else await interaction.channel.send({embeds: [embed]})
}

discord.js - Random user ID from reactions under message

I want to get random user ID from users with reaction under some message, but almost always when I'm trying to get all users with reaction it returns No Winner even if I reacted
Code:
setTimeout(()=> {
// msg.reactions.removeAll
if(msg.reactions.cache.get("👍").users.cache.filter(user => !user.bot).size > 0) {
const winner = msg.reactions.cache.get("👍").users.cache.filter(user => !user.client).random().id
message.channel.send(`Winner: #<${winner}>`)
} else {
message.channel.send("No winner.")
}
}, time-Date.now())
I had to add
Intents.FLAGS.GUILD_MESSAGE_REACTIONS to my intents.
I modified your code and it's working. You get No winner result every time because of user.client. You should use user.bot.
Here's a code I modified:
setTimeout(()=> {
const reaction = message.reactions.cache.get("👍")
const reactionUsers = reaction.users.cache.filter(user => !user.bot)
if(reactionUsers.size > 0){
const winner = reactionUsers.random()
const winnerId = winner.id
message.channel.send(`Winner: <#${winnerId}>`)
} else {
message.channel.send(`No winner`)
}
}, 10000)
Change 1: I used user.bot instead of user.client
Change 2: I assigned all variables before using them (It's more readable for me).
Change 3: #<${winnerId}> is not correct. Use <#${winnerId}> instead. (# should be inside of <> tag)

How to split text depending on word count

I am trying to make a lyric project using discord.js, cheerio and the website called genius.com.
I have successfully found a way to scrape the lyrics from the website, I am onto the part where I need to split it because discord has a max word limit of 2000.
I can check how many characters/words are in the overall lyrics by doing lyrics.length, I just need to find a way to split the string and send both, in the future I might implement richEmbeds to make it more stylish but for now I'm focusing on the basics.
var request = require('request');
var cheerio = require('cheerio');
/*
This is a project for my discord bot, the reason for the 2000 word limit is because
discords character limit is currently set to 2000, this means that i will have to add
a function to split the lyrics and send each part
*/
//Define the URL that we are going to be scraping the data from
var UR_L = "https://genius.com/Josh-a-and-jake-hill-not-afraid-of-dying-lyrics";
//send a request to the website and return the contents of the website
request(UR_L, function(err, resp, body) {
//load the website using cheerio
$ = cheerio.load(body);
//define lyrics as the selector to text form
var lyrics = $('p').text();
if (lyrics.length > "2000" && lyrics.length < "4000") {
} else if (lyrics.length > "4000" && lyrics.length < "6000") {
} else {
//send the lyrics as one message
}
})
You can find a live version running here on repl.it.
You don't need to use any fancy function, that function is already built in discord.js: you can attach some options to a message, and MessageOptions.split is what you're searching for. When you want to send the text, do it like this:
channel.send(lyrics, { split: true });
If lyrics.length is greater that the limit, discord.js will cut your messages and send them one after the other, making it seem like it's only one.
channel is the TextChannel you want to send the messages to.
Discord has a 2000 characters limit not a 2000 words limit.
One solution to your problem could be this:
// This will result in an array with strings of max 2000 length
const lyricsArr = lyrics.match(/.{1,2000}/g);
lyricsArr.forEach(chunk => sendMessage(chunk))
Given the async nature of sending messages, you might want to look into modules like p-iteration to ensure the chunks arrive in the correct order.
That being said, there exists APIs for getting lyrics of songs, which I would recommend instead of scraping. See apiseeds lyrics API as an example.
UPDATE
const lyrics = 'These are my lyrics';
const lyricsArr = lyrics.match(/.{1,8}/g);
console.log(lyricsArr); // [ 'These ar', 'e my lyr', 'ics' ]
lyricsArr.forEach((chunk, i) => {
// Break if this is the last chunk.
if (i == lyricsArr.length -1) {
return;
}
// If last character is not a space, we split a word in two.
// Add additional non-wordbreaking symbols between the slashes (in the regex) if needed.
if (!chunk[chunk.length - 1].match(/[ ,.!]/)) {
const lastWord = chunk.match(/\s([^ .]+)$/)
lyricsArr[i + 1] = lastWord[1] + lyricsArr[i + 1];
lyricsArr[i] = lyricsArr[i].split(/\s[^ .]*$/)[0];
}
})
console.log(lyricsArr) // [ 'These', 'are my', 'lyrics' ]
Updated as per the comments.
This is some crude code that i did not spend much time on, but it does the job.
Some info when using this approach:
You need to add any symbols that should not be considered wordbreaking to the regex in the second if
This has not been tested thoroughly, so use at your own risk.
It will definitely break if you have a word in the lyrics longer than the chunk size. Since this is around 2000, I imagine it will not be problem.
This will no longer ensure that the chunk length is below the limit, so change the limit to around 1900 to be safe
You can use .split( ) Javascript function.
word_list = lyrics.split(" ")
And word_list.length to access the number of words in your message and word_list[0] to select the first word for instance.

baconjs: throttle consecutive events with criteria

I'm coding a messaging app with Node.js and I need to detect when the same user sends N consecutive messages in a group (to avoid spammers). I'm using a bacon.js Bus where I push the incoming messages from all users.
A message looks like this:
{
"text": "My message",
"user": { "id": 1, name: "Pep" }
}
And this is my working code so far:
const Bacon = require('baconjs')
const bus = new Bacon.Bus();
const CONSECUTIVE_MESSAGES = 5;
bus.slidingWindow(CONSECUTIVE_MESSAGES)
.filter((messages) => {
return messages.length === MAX_CONSECUTIVE_MESSAGES &&
_.uniqBy(messages, 'user.id').length === 1;
})
.onValue((messages) => {
console.log(`User ${__.last(messages).user.id}`);
});
// ... on every message
bus.push(message);
It creates a sliding window, to keep only the number on consecutive messages I want to detect. On every event, it filters the array to let the data flow to the next step only if all the messages in the window belong to the same user. Last, in the onValue, it takes the last message to get the user id.
The code looks quite dirty/complex to me:
The filter doesn't look very natural with streams. Is there a better way to emit an event when N consecutive events match some criteria? .
Is there a better way to receive just a single event with the user (instead of an array of messages) in the onValue function.
It doesn't really throttle. If a user sends N messages in one year, he or she shouldn't be detected. The stream should forget old events somehow.
Any ideas to improve it? I'm open to migrating it to rxjs if that helps.
Maybe start with
latestMsgsP = bus.slidingWindow(CONSECUTIVE_MESSAGES)
.map(msgs => msgs.filter(msg => msgAge(msg) < AGE_LIMIT))
See if we should be blockking someone
let blockedUserIdP = latestMsgsP.map(getUserToBlock)
Where you can use something shamelessly imperative such as
function getUserToBlock(msgs) {
if (msgs.length < CONSECUTIVE_MESSAGES) return
let prevUserId;
for (var i = 0; i < msgs.length; i++) {
let userId = msgs[i].user.id
if (prevUserId && prevUserId != userId) return
prevUserId = userId
}
return prevUserId
}
Consider mapping the property you’re interested in as early as possible, then the rest of the stream can be simpler. Also, equality checks on every item in the sliding window won’t scale well as you increase the threshold. Consider using scan instead, so you simply keep a count which resets when the current and previous values don’t match.
bus
.map('.user.id')
.scan([0], ([n, a], b) => [a === b ? n + 1 : 1, b])
.filter(([n]) => n >= MAX_CONSECUTIVE_MESSAGES)
.onValue(([count, userId]) => void console.log(`User ${userId}`));

Resources