Intent Chaining in ASK game - node.js

I'm creating a game in ASK (Alexa Skills Kit) where a Alexa will ask the product between two random numbers in a range specified by the user. When the user answers, Alexa should tell them if the answer is correct or incorrect (UserAnswerIntentHandler), and then ask a new question (GamePlayIntentHandler).
const GamePlayIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'GamePlayIntent';
},
async handle(handlerInput){
const attributesManager = handlerInput.attributesManager;
const attributes = await attributesManager.getSessionAttributes() || {};
var lowestNum = attributes.lowestNum
var highestNum = attributes.highestNum
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min)
}
var randomOne = getRandomIntInclusive(lowestNum, highestNum)
var randomTwo = getRandomIntInclusive(lowestNum, highestNum)
attributes.randomOne = randomOne
attributes.randomTwo = randomTwo
const speakOutput = `What is ${randomOne} times ${randomTwo}?` //Here, alexa is asking for the product between two random numbers in a range the user previously specified.
return handlerInput.responseBuilder
.addElicitSlotDirective('answer', {
name: 'UserAnswer',
confirmationStatus: 'NONE'
})
.speak(speakOutput)
.reprompt(`Sorry, I didn't get that. What is ${randomOne} times ${randomTwo}?`)
.getResponse();
}
}
const UserAnswerIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'UserAnswerIntent';
},
async handle(handlerInput) {
const {requestEnvelope} = handlerInput
const {intent} = requestEnvelope.request
const attributesManager = handlerInput.attributesManager;
const attributes = await attributesManager.getSessionAttributes() || {};
var userAnswer = parseInt(Alexa.getSlotValue(requestEnvelope, 'answer'))
const randomOne = attributes.randomOne
const randomTwo = attributes.randomTwo
var correctAnswer = randomOne * randomTwo
var points = 0
var speakOutput = ""
if (userAnswer > 0){
if (userAnswer === correctAnswer){
points +=1
speakOutput = `That's correct! You now have ${points} points.`
return handlerInput.responseBuilder
.speak(speakOutput)
.addDelegateDirective({
name: 'GamePlayIntent',
confirmationStatus: 'NONE'
})
.getResponse();
}//If the answer is correct, the user gets a point and Alexa should immediatley ask the next question
else{
speakOutput = `Your answer is incorrect. The correct answer is ${correctAnswer}. You currently have ${points} points.`
return handlerInput.responseBuilder
.speak(speakOutput)
.addDelegateDirective({
name: 'GamePlayIntent',
confirmationStatus: 'NONE'
})
.getResponse();
}//If the answer is incorrect, Alexa will give the correct answer and total points and immediatley go to the next question
}
}
}
What's happening right now is that Alexa isn't telling the user if their answer is correct or incorrect, and is immediately asking the next question.
Is there any way to fix this or create a loop such that as soon as the user answers, Alexa can evaluate the answer, and then immediately ask the next question?

When the GamePlayIntent is triggered, Alexa will ask a question then get the answer provided by the user.
The answer should then trigger UserAnswerIntent.
At this point, UserAnswerIntentHandler is triggered and goes to the handle(handlerInput) where you have your logic to verify the answer.
Once done, Alexa gives the correct answer then chain with the new question in the same message string.
You can build you string message by having one part for verifying the answer and a second part for the next question, you concatenate the strings together and return it in the same responseBuilder .speak(), followed by a repromt to make sure the user is giving an answer.
Once the user's input is captured, it will again triggered the corresponding intent to verify the new answer.
Another way would be to ask the user if they would like to have a new question through YesIntent/NoIntent.
If the answer is yes then you redirect them to the GamePlayIntent using
return GamePlayIntentHandler.handle(handlerInput);
If the answer is no then you add a goodbye message and close the session or so depending on the skill functionality/logic.
Here are some sample skills (1, 2) I found that you can get guided by, one includes questions/answers handled in a different way.

Related

Helping Finding A friend bot

My friend has a discord server called Find A Friend and he wants me to code a bot to type a command to ask a series of questions and put it in a channel. People can see if anybody interests them and becomes friends with them. I have already coded part of it.
One of the questions it asks is: What games do you like?
My friend wants to make it able that the bot searches the other answers to this same question and dm the user if they have a game in common. I want to do the same for another question: What are some shows, movies, and anime that you like?
Here is my code for the series of questions:
const { Client, Message, MessageEmbed } = require("discord.js")
module.exports = {
name: "faf",
/**
* #param {Client} client
* #param {Message} message
* #param {String[]} args
*/
run: async (client, message, args) => {
message.delete()
const questions = [
"What is your usernames for any games you have including discord (is fine if you dont put them all but you MUST include your discord)",
"What is your age (not required)",
"What is your gender",
"Do you talk in Voicechannel or through text?",
"What games do you like?",
"What are some shows, movies, and anime that you like",
"Where are you from (not required)",
"Please state anything else you want to say"
]
let collectCounter = 0;
let endCounter = 0;
const filter = (m) => m.author.id === message.author.id;
const appStart = await message.author.send(questions[collectCounter++]);
const channel = appStart.channel;
const collector = channel.createMessageCollector(filter);
collector.on("collect", () => {
if(collectCounter < questions.length) {
channel.send(questions[collectCounter++])
} else {
channel.send('Your Find A Friend thread has been sent, Good Luck!')
collector.stop("fulfilled");
}
});
const appsChannel = client.channels.cache.get("831292911437086760");
collector.on('end', (collected, reason) => {
if(reason === 'fulfilled') {
let index = 1;
const mappedResponses = collected
.map((msg) => {
return `${index++}) ${questions[endCounter++]}\n-> ${msg.content}`;
}).join('\n\n')
appsChannel.send(
new MessageEmbed()
.setAuthor(
message.author.tag,
message.author.displayAvatarURL({ dynamic: true })
)
.setTitle('Friend Thread')
.setDescription(mappedResponses)
.setColor('RED')
.setTimestamp()
)
}
});
},
};
TL;DR I currently have a message collector bot that records responses to questions. How can I make the bot recognize when two people share common interests, such as games, movies, shows, or anime, based on their responses?

How can I resolve this problem (node.js, discord.js)

if (!args[1]) return message.channel.send('You need to specify a person!')
if (!args[2]) return message.channel.send('Please specify a time for how long the ban council will last.')
var tim = args[2]
const sweden = message.mentions.users.first()
message.react('πŸ‘').then(() => message.react('πŸ‘Ž'))
const mappo = ['πŸ‘', 'πŸ‘Ž']
if (!args[1]) return message.channel.send('Please specify a person!')
if(message.guild){ //this will check if the command is being called from a server
const embad = new Discord.MessageEmbed()
.setTitle('Ban Council')
.addField('The Convicted:', `${sweden.tag}`)
.addField('Rules:', 'Vote on whether the convicted is guilty or not with the prompted reactions. The ban will end automatically in 5 seconds.')
message.channel.send(embad)
setTimeout(function(){
if(sweden){
const lyft = message.guild.member(sweden)
if(lyft){
if(message.reactions.cache.map(r => `${'πŸ‘'} ${r.users.cache.size}`)[0] > message.reactions.cache.map(r => `${'πŸ‘'} ${r.users.cache.size}`)[1]){
lyft.ban({ ression: 'Majority has exiled you from server. '}).then(() => {
message.reply(`The user ${december.tag} was banned as a result of a majority vote.`)
})
} else {
message.channel.send('The ban was cancelled.')
}
}else{
message.reply('The user is not in this server.')
}
}else{
message.reply('You need to specify a person!')
}
}, tim)
} else {
message.channel.send('Banning does not work here!')
}
It sends the "Ban cancelled" before it actually has the chance to take input. I've tried collectors, and it doesn't work because of the max: part, how can I resolve this problem? (Also it returns no errors)
This is in a case. (appending onto what feedback I got)
Firstly you should beautify your code before you post on stackoverflow, you could have removed the break keyword and just explain it was inside of a switch case
The reason it does not work is because you are checking the message's reaction, and not the embed that you send, so to fix this you need to assign a variable to message.channel.send(embad), but this is a promise so you need to await it, which requires an async function
lastly awaitReactions and createReactionCollector are probably better options,
So here's the new code:
(async () => {
if (!args[1]) return message.channel.send('You need to specify a person!');
if (!args[2]) return message.channel.send('Please specify a time for how long the ban council will last.')
if (!message.guild) return message.channel.send('Banning does not work here');
var tim = args[2]
const sweden = message.mentions.member.first()
const mappo = ['πŸ‘', 'πŸ‘Ž']
message.react('πŸ‘').then(() => message.react('πŸ‘Ž'))
const embad = new Discord.MessageEmbed()
.setTitle('Ban Council')
.addField('The Convicted:', `${sweden.tag}`)
.addField('Rules:', 'Vote on whether the convicted is guilty or not with the prompted reactions. The ban will end automatically in 5 seconds.')
const embedMessage = await message.channel.send(embad);
setTimeout(function () {
if (!sweden) return message.reply('You need to mention a person');
const filter = (reaction) => mappo.includes(reaction.emoji.name);
embad.awaitReactions(filter, { time: 5000 })
.then(collection => {
//not the most optimal way to do it
const emoji1 = collection.find(e => e.emoji.name === 'πŸ‘');
const emoji2 = collection.find(e => e.emoji.name === 'πŸ‘Ž');
//both default to 0
const upvotes = emoji1 && emoji1.users.size || 0;
const downvotes = emoji2 && emoji2.users.size || 0;
if (upvotes > downvotes) {
lyft.ban({ reason: 'Majority has exiled you from server. ' })
.then(() => message.reply(`The user ${december.tag} was banned as a result of a majority vote.`));
} else {
message.channel.send('The ban was cancelled.');
}
})
.catch(console.error);
}, tim);
})();
It's been around 8 months since I made this post, and I found the answer, and an even more effective way to do it. I won't post the entire code, as it's pretty long and not very neat. However, this is a much more effective way of counting reactions.
My Original Code was:
const upvotes = message.reactions.cache.map(r => ${'πŸ‘'} ${r.users.cache.size})[0]
const downvotes = message.reactions.cache.map(r => ${'πŸ‘Ž'} ${r.users.cache.size})[1]
It doesn't work very well either.
const [upvoteReaction, downvoteReaction] = message.reactions.cache.first(2);
const upvotes = upvoteReaction.users.cache.size;
const downvotes = downvoteReaction.users.cache.size;
It takes the number of reactions from the first two reactions on the message. Seeing as how the only reactions are thumbs up and thumbs down, it will get the numbers from both of them. From there, you can just compare the two.

How to get previous intent in dialogflow

I am facing one issue in this:
I have created 4 intents bookTable, bookingDate, bookingTime and people count intent.i have added intent chaining using input and output context.
Suppose I am on third intent right now which is β€œBooking Time” & when I will say β€œBook A Table” to google home, it will directly jump to the first intent.
I need solution to not jump to the first intent but if user will say any except required input, then it will call custom fallback intent and stuck on that particular intent.
Please provide me the solution for not jumping the intent to any other..
// welcome intent here
app.intent('DefaultWelcomeIntent', (conv) => {
conv.ask('What would you like me to do for you?. Book a table or Check your calendar?');
});
// Booktable intent
app.intent('BookTable', (conv) => {
conv.ask('Please provide a date?');
});
// bookingDate intent
app.intent('bookingDate', (conv, params) => {
var speech = new Speech();
const currentDate = moment().format("YYYY-MM-DD");
const bookingDate = moment(params.date).format("YYYY-MM-DD");
const displayBookingDate = moment(bookingDate).format("DD MMM YYYY");
let say = '';
if (bookingDate && moment(bookingDate).isSameOrAfter(currentDate)) {
speech.say(`OK, ${displayBookingDate}`)
.pause('300ms')
.say(`Please provide time?`);
const speechOutput = speech.toObject();
conv.ask(speechOutput.speech);
conv.data.restaurantData.date = bookingDate;
} else {
say = `Please provide today's or upcoming date.`;
conv.ask(say);
}
});
// bookingTime intent
app.intent('bookingTime', (conv, params) => {
var speech = new Speech();
const currentDateTime = moment().format("YYYY-MM-DD HH:mm");
const bookingDate = moment(conv.data.restaurantData.date).format("YYYY-MM-DD");
const bookingTime = moment(params.time).format("HH:mm");
const booking12HourTimeString = moment(bookingTime, ["HH:mm"]).format("hh:mm A");
const concateDateTime = moment(bookingDate + " " + bookingTime).format('YYYY-MM-DD HH:mm');
let say = '';
if(moment(concateDateTime).isSameOrAfter(currentDateTime)) {
speech.say(`OK, ${booking12HourTimeString}`)
.pause('300ms')
.say(`How many people?`);
const speechOutput = speech.toObject();
conv.ask(speechOutput.speech);
conv.data.restaurantData.time = bookingTime;
} else {
say = `please provide a valid booking time.`;
conv.ask(say);
}
});
// people count intent
app.intent('peopleCount', (conv, params) => {
let peopleCounts = parseInt(params.count);
let restaurantName = 'Dominos pizza';
let restaurantAddress = 'San Francisco, ca';
let bookingDate = conv.data.restaurantData.date;
let bookingTime = conv.data.restaurantData.time;
const booking12HourTimeString = moment(bookingTime, ["HH:mm"]).format("hh:mm A");
let bookingDisplayDate = moment(bookingDate).format("DD MMM YYYY");
if (peopleCounts > 0) {
conv.ask(`OK You would like to create a table for ${restaurantName} in ${restaurantAddress} on ${bookingDisplayDate} at ${booking12HourTimeString} for ${peopleCounts} people`);
} else {
conv.ask(`Please add people more than zero`);
}
});
app.intent('customFallback', (conv, params) => {
conv.ask(`I couldn't understand. Can you say that again?`);
});
As per my understanding from your questions which is really vague, the values from the previous intents in the current intent can be get with the help of the contexts.
If still there is any place of ambiguity from this answer, it would be helpful if you provide some kind of examples.
you can not directly get the previous intent name however you can identify intent based upon the "conversationToken" as follows.
function getFeedbackFunc(agent){
...
let convTokens= request.body.originalDetectIntentRequest.payload.conversation.conversationTo
ken;
console log("Original Intent :"+convTokens);
...
}
I got the output as:["awaiting_feedback","customer_phone","awaiting_contact","customer_name","phonepair-no-followup"]
so based on the output the original intent is related to the context "phonepair-no-followup" and you can select your intent accordingly.
Please provide your feedback if it helps.
Your use case is not very clear, but as per my understanding what you really need is contexts or follow-up intents. Using these you can chain the intents.
If you still want to get previous intents name, i think you should store that in cache so that you can get that. There is no provision in dialogflow to get that.

Alexa - How to write if-else statment using slots in node.js

I'm writing an Alexa application in node.js to answer product spec questions. For example, I'd like to be able to ask "What is the maximum capacity of (product)?" Currently, I have separate intents for each question for each product, which is resulting in incredibly messy code with a ton of handlers. What I'd like to do is just have one MaxCapacity intent for example, that uses slots (containing the different products) to assign the fact. I'm still relatively new at node.js, so I apologize if this is really sloppy. I know how to make new slots on the developer console, I just don't know how to code it on the backend. This is one of the handlers:
const GetNewFactHandler1 = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'LaunchRequest'
|| (request.type === 'IntentRequest'
&& request.intent.name === 'UMaxCapacityIntent');
*/ And if slot type == *product*, then: */
},
handle(handlerInput) {
const factArr = data;
const randomFact = factArr[1]; //Array with all the answers
const speechOutput = GET_FACT_MESSAGE + randomFact;
return handlerInput.responseBuilder
.speak(speechOutput)
.withSimpleCard(SKILL_NAME, randomFact)
.getResponse();
},
};
I hope that makes sense, but I'd be happy to explain further. Thank you!
If I understood correctly, you want to invoke this handler, when slot 'product' exists? If so, you can use 'getSlot' helper method. If slot exists in the request, it returns that slot, otherwise it results in null. So just add extra check to canHandle condition && getSlot(handlerInput.requestEnvelope, 'product') and that should be it.

How to design and pass information in multiple Amazon.NextIntent chains for Alexa / Amazon Echo

Hi currently thinking about a good design to manage multiple output chains in an Alexa skill. For example if I start with one intent called "yesterday" and another one called "today". I want to pass this information (is it "yesterday" I started the chain with or "today") to a NextIntent chain.
Whats the best way to pass information between intents?
Just found out here https://developer.amazon.com/de/blogs/alexa/post/f167aa0f-8abe-4602-b985-65118b3032ca/code-deep-dive-slots-and-session-attributes-in-the-ask-sdk-for-node-js how to do it.
I was searching for session attributes
These can be used like this
1) The first intent I called to start the chain:
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'BesterAutorIntent';
},
handle(handlerInput) {
try {
const speechOutput = "Ich kΓΆnnte dir sagen wer der beste Autor ist, aber ich muss erst HR fragen ob ich darf";
const attributes = handlerInput.attributesManager.getSessionAttributes();
attributes.kaesebrot = "kaesebrot"
handlerInput.attributesManager.setSessionAttributes(attributes)
return handlerInput.responseBuilder
.speak(speechOutput)
.reprompt()
.withSimpleCard(defaulttext.SKILL_NAME, speechOutput)
.getResponse();
} catch (error) {
console.error(error);
}
},
};
In there you can I set an attribute called kaesebrot to be a session attribute:
const attributes = handlerInput.attributesManager.getSessionAttributes();
attributes.kaesebrot = "kaesebrot"
handlerInput.attributesManager.setSessionAttributes(attributes)
Later in another function you can get it like this
let counter = 0;
const NextIntentHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest'
&& request.intent.name === 'AMAZON.NextIntent';
},
handle(handlerInput) {
try {
counter = counter + 1;
const attributes = handlerInput.attributesManager.getSessionAttributes();
speechOutput = counter + " TEST " + attributes.kaesebrot;
return handlerInput.responseBuilder
.speak(speechOutput)
.reprompt()
.withSimpleCard(defaulttext.SKILL_NAME, speechOutput)
.getResponse();
} catch (error) {
console.error(error);
}
},
};
Tobi's answer is good.
I am using sdk v1 and the code to keep attributes on dynamoDB is like this:
exports.handler = function( event, context, callback ) {
const alexa = Alexa.handler( event, context );
alexa.dynamoDBTableName = "alexaTable";
alexa.registerHandlers( handlers );
alexa.execute();
};
...
const handlers = {
...
"AMAZON.NextIntent": function () {
console.log( "AMAZON.NextIntent: " + this.attributes.index );
}
...
}
A dynamodb table "alexaTable" will be created the first time the lambda function is invoked and will store and retrieve the attributes automatically.
In the above example "index" is an attribute carried over from a previous intent.
Table schema consists of a key and a field.
Key: userId, alexa skill user id
Field: mapAttr, JSON structured attributes
but it is self-managed by the alexa-sdk.
This post can help further
https://developer.amazon.com/blogs/alexa/post/648c46a1-b491-49bc-902d-d05ecf5c65b4/tips-on-state-management-at-three-different-levels
SessionAttributes is a way to persist state across intents but there's a new feature jsut out of the oven to specifically chain intents and pass slot values.
It's called Intent Chaining and you can see more here:
https://developer.amazon.com/blogs/alexa/post/9ffdbddb-948a-4eff-8408-7e210282ed38/intent-chaining-for-alexa-skill
Intent chaining allows your skill code to start dialog management from any intent, including the LaunchRequest. You can chain into any of your custom intents as long as your interaction model has a dialog model.
For example, from YesterdayIntent, in the response builder you would do:
handlerInput.responseBuilder.addDelegateDirective({
name: 'TodayIntent',
confirmationStatus: 'NONE',
slots: {} // the slot values to pass go here
})

Resources