How to make a new embed when the first embed reaches the description limit (4096) - node.js

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]})
}

Related

Move data in Waterfall-Dialog. Bot Framework SDK

I'm using Bot Framework SDK with nodejs to implement a disamibuation flow.
I want that if two intents predicted by Luis are close to each other, ask the user from which of them are the one they want. I have done the validator but, I have a problem with the flow.
It is a waterfall Dialog with 3 steps:
FirstStep: Calls Orchestrator and Luis to get intents and entities. It pass the data with return await step.next({...})
Disamiguation Step: Checks if it is necessary to disambiguate, and, in that case, prompts the options. If not, it pass the data like the first step.
Answer step: If it has a disambiguation flag in the data it receives in step.result, it prompts the answer acordingly with the user response. Elsewhere, it uses the data in step.result that comes from the first step.
The problem is that, when it prompts user to say the intent, I lost the data of the FirstStep since I cannot use step.next({...})
¿How can I maintain both the data from the first step and the user answer in the prompt?
Here are the basic code:
async firstStep(step) {
logger.info(`FinalAnswer Dialog: firstStep`);
let model_dispatch = await this.bot.get_intent_dispatch(step.context);
let result = await this.bot.dispatchToTopIntentAsync(step.context, model_dispatch.model)
// model_dispatch = orchestrator_model
// result = {topIntent: String, entities: Array, disamibiguation: Array}
return await step.next({ model_dispatch: model_dispatch, result: result})
}
async disambiguationStep(step) {
logger.info(`FinalAnswer Dialog: disambiguationStep`);
if (step.result.result.disambiguation) {
logger.info("We need to disambiguate")
let disambiguation_options = step.result.result.disambiguation
const message_text = "What do you need";
const data = [
{
"title": "TEXT",
"value": disambiguation_option[0]
},
{
"title": "TEXT",
"value": disambiguation_option[1]
},
]
let buttons = data.map(function (d) {
return {
type: ActionTypes.PostBack,
title: d.title,
value: d.value
}
});
const msg = MessageFactory.suggestedActions(buttons, message_text);
return await step.prompt(TEXT_PROMPT, { prompt: msg });
return step.next(step.result) //not working
}
else {
logger.info("We dont desambiguate")
return step.next(step.result)
}
}
async answerStep(step) {
logger.info(`FinalAnswer Dialog: answerStep`);
let model_dispatch = step.result.model_dispatch
let result = step.result.result
//Show answer
return await step.endDialog();
}
You can use the step dictionary to store your values. The complex dialogs sample on GitHub is excellent for demonstrating this. https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/javascript_nodejs/43.complex-dialog/dialogs/topLevelDialog.js
You can save data in the context with whatever name you want:
step.values['nameProperty'] = {}
This will be accessible within the entire execution context of the waterfall dialog:
const data = step.values['nameProperty'] // {}

How to check if an element is in the document with playwright?

I want to test if an element had been rendered. So I want expect that if is present. Is there a command for this?
await page.goto(‘<http://localhost:3000/>');
const logo = await page.$(‘.logo’)
// expect(logo.toBeInDocument())
If you query one element with page.$(), you can simply use:
const logo = await page.$('.logo');
if (logo) {
}
Similarly if you query multiple elements with page.$$():
const logo = await page.$$('.logo');
if (logo) {
}
Since this example returns (after awaiting) an array of element handles, you can also use property length in the condition:
const logo = await page.$$('.logo');
if (logo.length) {
}
The key in all these examples is to await the promise that page.$() and page.$$() return.
Since the use of ElementHandle (page.$(), page.$$()) is discouraged by the Playwright Team, you could use the Locator object and the count() method:
expect(await page.locator('data-testid=exists').count()).toBeTruthy();
expect(await page.locator('data-testid=doesnt-exist').count()).toBeFalsy();
If you want to check if the element is rendered (I assume you mean visible) you could use the toBeVisible assertion:
await expect(page.locator('data-testid=is-visible')).toBeVisible();

How do I get the userid of second mention in a command

So I'm trying to make a command where someone can do -kill #firstUser #secondUser. This will increase the first user's kills by 1 and add a role to the second user mentioned. I can access the the first user mentioned by doing const firstUser = message.mentions.users.first(); but I'm not sure how to do the same for the second user.
I've tried accessing the message.mentions.users collection and converting it to an array (and trying to access that) but I can't get it to work.
const firstUser = message.mentions.users.get(0);
const secondUser = message.mentions.users.get(1);
How do I get the user class from a message with multiple mentions?
And what I found was, it returns an object, not a mention, and as you can't send an object, it will return as an empty message error.
So to send a mention, you send:
// Getting the first and second users
const allMentioned = message.mentions.users.array()
// First User `[0]`
const firstUser = allMentioned[0];
// Second User `[1]`
const secondUser = allMentioned[1];
// And so on and so forth...
// Add `<#` to the begginning of the id and `>` to the end of it to make a mention.
const mentionSecondUser = "<#" + secondUser.id + ">";
// Sending a message using the fetched property
message.channel.send(`Hey ${mentionSecondUser}, or whatever.`);
Alternatively, you can try using the other fetched properties using the following format, received from getting the property, say secondUser:
User {
id: '<secondUser's id>',
username: '<secondUser's username>',
bot: <true if secondUser a bot>,
discriminator: '<secondUser's discriminator>',
avatar: '<secondUser's avatarId>',
lastMessageID: <secondUser's lastMessageId>,
lastMessageChannelID: <secondUser's lastMessageChannelId>,
flags: UserFlags { bitfield: 0 }
}
An example of this is in the picture showed above.
You can use:
message.mentions.users.array()[1]
To get the second user in a message. Appropriately, use [2] for the third, [3] for the fourth, and so on.

How to use a loop inside message.react() inside async function discord.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.

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.

Resources