discord.js - Random user ID from reactions under message - node.js

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)

Related

Best way to organize firebase writes on update trigger

There may be more than one correct answer to this question, but here's my issue: I have a user document in firebase with many fields that can be updated and which interact in different ways. If one field is updated, it may require a change to another field on the backend. Is it better to have a whole bunch of if statements each with their own write action if the condition is met or, or do single write at the end of the function for all the fields that might change. If a field does not change, I would have to write its original value back to itself. That seems clunky, but so does the other option. Am I missing a third option? What is the best practice here?
Here's an example of what I'm talking about. Updating fields one at a time is what I have now, which looks like this:
export const userUpdate = functions.firestore
.document("users/{userID}")
.onUpdate(async (change) => {
const beforeData = change.before.data();
const afterData = change.after.data();
// user levels up and gets more HP
if(beforeData.userLevel != afterData.userLevel){
const newMaxHP = 15 + 5 * afterData.userLevel;
change.after.ref.update({
maxHp: newMaxHP
})
}
//update user rating
if (beforeData.numberOfRatings != afterData.numberOfRatings) {
const newRating = placerRating(beforeData.userRating, beforeData.numberOfRatings, afterData.latestRating);
change.after.ref.update({
userRating: newRating
})
}
//replenish user funds from zero
if (afterData.money == 0){
change.after.ref.update({
money: 20
})
}
If I did it all in a single write, the if statements would assign a value to a variable, but not update the firestore document. Each if statement would include an else statement assigning the variable to the field's original value. There would be a single write at the end like this:
change.after.ref.update({
maxHp: newMaxHP,
userRating: newRating,
money: 20
})
I hope that helps.
[edit to add follow-up question about updating a map value]
#Dharmaraj's answer works great, but I'm struggling to apply it when updating a map value. BTW - I'm using Typescript.
Before using #Dharmaraj's solution, I was doing this:
admin.firestore().collection("users").doc(lastPlayerAttacker).update({
"equipped.weapon.usesLeft": admin.firestore.FieldValue.increment(-1)
});
Using the update object, I'm trying it like this, but I get the error "Object is of type 'unknown'"
const lastPlayerUpdates:{[key:string]:unknown} = {};
lastPlayerUpdates.equipped.weapon.usesLeft = admin.firestore.FieldValue.increment(-1);
admin.firestore().collection("users").doc(lastPlayerAttacker).update(lastPlayerUpdates);
Any advice on how to fix it?
Every time you call update(), you are being charged for 1 write operation. It'll be best to accumulate all updated fields in an object and then update the document only once as it'll be more efficient too. Try refactoring the code as shown below:
export const userUpdate = functions.firestore
.document("users/{userID}")
.onUpdate(async (change) => {
const beforeData = change.before.data();
const afterData = change.after.data();
const updatedData = {};
// user levels up and gets more HP
if (beforeData.userLevel != afterData.userLevel) {
const newMaxHP = 15 + 5 * afterData.userLevel;
updatedData.maxHp = newMaxHP;
}
//update user rating
if (beforeData.numberOfRatings != afterData.numberOfRatings) {
const newRating = placerRating(beforeData.userRating, beforeData.numberOfRatings, afterData.latestRating);
updatedData.userRating = newRating;
}
//replenish user funds from zero
if (afterData.money == 0) {
updatedData.money = 20;
}
await change.after.ref.update(updatedData);
console.log("Data updated");
return null;
})

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'] // {}

Make //repeat command correct length node & discord.js

Currently for my discord bot I am trying to make a repeat command for me and my friends but the issue is since I'm new to discord.js and node I don't know any good alternatives to "startswith", what this means is while the "//repeat #user" command works, "//repeattesttext #user" also does the same thing. Is there any way to prevent this? Here is my code:
if (!msg.guild) return;
if (msg.content.startsWith('//repeat')) {
if (msg.member.roles.cache.has("744347114255155201")) {
if (!active4) {
const user = msg.mentions.users.first();
if (user) {
const memb = msg.guild.member(user);
if (memb) {
if (active4) {
active4 = false;
msg.channel.send("Repeat Deactivated.")
} else {
id2 = memb.id
active4 = true
msg.channel.send("Repeat Activated")
}
}
}
} else {
active4 = false
msg.channel.send("Repeat Deacivated.")
}
return
} else {
msg.channel.send("You don't have RBLX permissions.")
return
}
}
Any help would be appreciated and since I'm new if you can please explain how your code works. Even if you can't I'll still be grateful for an answer of any kind!
Instead of checking if the string starts with //repeat, you should check if the first word is equal to that.
This can be achieving by separating the msg.content in an array containing its words.
msg.content.split(' ') gives that array.
msg.content.split(' ')[0] === '//repeat' is the if statement you are looking for.

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.

Data validation before next dialog waterfall step in Bot Framework

Have simple waterfall dialog:
SendMessageDialog = [
function (session) {
builder.Prompts.time(session, "Enter dates?");
},
function (session, results) {
session.conversationData.start = builder.EntityRecognizer.resolveTime([results.response]).toISOString();
if(typeof results.response.resolution.end != "undefined")
session.conversationData.end = results.response.resolution.end.toISOString();
}
];
Bot successfully recognizes time in different formats, and if format is invalid makes default prompt to user proposing to re-enter data like:
I didn't understand. Please choose an option from the list.
In Prompts option I can only change this default retryPrompt message. What if I need additional validation like:
User enters a date, but the date isn't valid because of business
logic (in the past, unavailable)
User enters a location, so need to check against list of available locations (perhaps from an api call)
Check number range after Prompts.number()
etc
Is there is an easy way to add additional validation to retry same waterfall step and ask user to re-enter data? How to implement this? Is there a workable code for BotBuilder 3.9?
There are some examples exist to make some validations with LUIS API calls, however they work only on next waterfall step. Goal not to go to the next step until correct data entered - is it possible? Thanks!
Right after the question was asked, had found how-to Create custom prompts to validate input:
Result code:
[
function (session) {
// Call start/end time prompt dialog
session.beginDialog("DatePromptDialog");
},
...
]
DatePromptDialog = [
function (session, args) {
var options = { retryPrompt: "I didn’t recognize dates you entered. Please try again using format: start - end dates" };
if (args && args.reprompt && args.endTimeMissed) {
builder.Prompts.time(session, "Please specify both start - end times:", options);
} else if (args && args.reprompt && args.dateInPast){
builder.Prompts.time(session, "That date seems to be in the past! Please enter a valid date.", options);
} else {
builder.Prompts.time(session, "Enter dates?", options);
}
},
function (session, results) {
var args = {};
delete session.conversationData.start; // Clear previous values
delete session.conversationData.end;
// Get start time
session.conversationData.start = builder.EntityRecognizer.resolveTime([results.response]).toISOString();
// Get duration end time if available
if(typeof results.response.resolution.end != "undefined")
session.conversationData.end = results.response.resolution.end.toISOString();
else {
args.endTimeMissed = true;
args.reprompt = true;
}
// Convert dates from string
var currDate = new Date(); // Current date
var startDate = new Date(session.conversationData.start);
var endDate = new Date(session.conversationData.end);
if(startDate < currDate || endDate < currDate) {
args.dateInPast = true;
args.reprompt = true;
}
if (args.reprompt) {
// Repeat the dialog
session.replaceDialog('DatePromptDialog', args);
} else {
// Success
session.endDialog();
}
}
];

Resources