Using conditional operators with QnAMaker - operators aren't routing correctly - node.js

I'm having difficulty figuring out what most likely is a simple issue, which relates to a 'if then else' problem in my code (NodeJS, Bot Framework v4).
I can't quite figure out why the relevant card isn't being shown depending on the number of semi-colons it finds in the response string from QnAMaker.
When testing with the Bot Framework emulator, it only returns one response type, whether that's plain text or one Rich Card no matter how many semi-colons are in the response.
I've tried to see if it's the length of the string it's having problems with by parsing the number value in the length statement. Didn't make a difference sadly. Notably if I use any other conditional operator such as '===' for example, it breaks the response completely.
const { ActivityTypes, CardFactory } = require('botbuilder');
const { WelcomeCard } = require('./dialogs/welcome');
// const { HeroCard } = require('./dialogs/welcome');
// const { VideoCard } = require('./dialogs/welcome');
class MyBot {
/**
*
* #param {TurnContext} on turn context object.
*/
constructor(qnaServices) {
this.qnaServices = qnaServices;
}
async onTurn(turnContext) {
if (turnContext.activity.type === ActivityTypes.Message) {
for (let i = 0; i < this.qnaServices.length; i++) {
// Perform a call to the QnA Maker service to retrieve matching Question and Answer pairs.
const qnaResults = await this.qnaServices[i].getAnswers(turnContext);
const qnaCard = qnaResults.includes(';');
// If an answer was received from QnA Maker, send the answer back to the user and exit.
if (qnaCard.toString().split(';').length < 3) {
await turnContext.sendActivity(qnaResults[0].answer);
await turnContext.sendActivity({
text: 'Hero Card',
attachments: [CardFactory.heroCard(HeroCard)]
});
} else if (qnaCard.toString().split(';').length > 3) {
await turnContext.sendActivity(qnaResults[0].answer);
await turnContext.sendActivity({
text: 'Video Card',
attachments: [CardFactory.videoCard(VideoCard)]
});
} else if (qnaCard.toString().split(';').length === 0) {
await turnContext.sendActivity(qnaResults[0].answer);
return;
}
}
// If no answers were returned from QnA Maker, reply with help.
await turnContext.sendActivity('No QnA Maker answers were found.');
} else {
await turnContext.sendActivity(`[${ turnContext.activity.type } event detected]`);
} if (turnContext.activity.type === ActivityTypes.ConversationUpdate) {
// Handle ConversationUpdate activity type, which is used to indicates new members add to
// the conversation.
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types
// Do we have any new members added to the conversation?
if (turnContext.activity.membersAdded.length !== 0) {
// Iterate over all new members added to the conversation
for (var idx in turnContext.activity.membersAdded) {
// Greet anyone that was not the target (recipient) of this message
// the 'bot' is the recipient for events from the channel,
// context.activity.membersAdded == context.activity.recipient.Id indicates the
// bot was added to the conversation.
if (turnContext.activity.membersAdded[idx].id !== turnContext.activity.recipient.id) {
// Welcome user.
// When activity type is "conversationUpdate" and the member joining the conversation is the bot
// we will send our Welcome Adaptive Card. This will only be sent once, when the Bot joins conversation
// To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
const welcomeCard = CardFactory.adaptiveCard(WelcomeCard);
await turnContext.sendActivity({ attachments: [welcomeCard] });
}
}
}
}
}
}
module.exports.MyBot = MyBot;
Ideally, what I'm hoping to see is if I ask a question which has 3 semi-colons in the response, it outputs a Hero Card. If it has more than 3, then a Video Card and if it doesn't have either, a text response.

I'm not a js specialist, but I'm quite confused by the following:
const qnaCard = qnaResults.includes(';');
In Javascript, includes is the following (source):
The includes() method determines whether an array includes a certain
value among its entries, returning true or false as appropriate.
So here your qnaCard is true or false. But it looks like you are trying to use it as if it was containing the text:
if (qnaCard.toString().split(';').length < 3) {
...
You have to work on the object containing the answer: qnaResults[0].answer.

Related

How to get email of user who send message to bot in Circuit JavaScript SDK?

I'm creating a bot that will receive a messages in Circuit and then send it somewhere else with email of user that printed this message.
As example I use xlator-bot https://github.com/circuit/xlator-bot
this.receiveItem = function receiveItem(item) {
logger.info('[APP]: receiveItem');
if (item.type !== 'TEXT' || self.sentByMe(item)) {
logger.debug('[APP]: skip it is not text or I sent it');
return;
}
if (!item.text || !item.text.content) {
logger.info('[APP]: skip it does not have text');
return;
}
self.receiveText(htmlToText.fromString(item.text.content))
.then (function addResponseItem(responseItem){
logger.info('[APP]: addResponseItem');
var comment = {
convId: item.convId,
parentId: (item.parentItemId) ? item.parentItemId : item.itemId,
content: responseItem
};
return client.addTextItem(item.convId, comment);
})
.catch(function(e){
logger.error('[APP]:', e);
});
};
I want to get email of user who send item in this function as string variable. Can anyone suggest how can I do it?
The item has an attribute creatorId which is the userId of the sender. The API getUserById will return the user object that contains the emailAddress attribute.
See
https://circuitsandbox.net/sdk/classes/Item.html#property_creatorId and https://circuitsandbox.net/sdk/classes/Client.html#method_getUserById and
https://circuitsandbox.net/sdk/classes/User.html#property_emailAddress

How to tell Alexa to jump to a specific intent from LaunchRequest based on user input

I am quite new in Alexa development so please excuse my ignorance. The Alexa skill I am developing requires the following:
Users will awake the skill along with a question, e.g.
Alexa, ask marketing platform about result of last campaign
I am referring to https://developer.amazon.com/docs/custom-skills/understanding-how-users-invoke-custom-skills.html#cert-invoke-specific-request but not quite understand how to jump to a specific intent from LaunchRequest.
Where marketing platform is the skill invocation and result of last campaign is the utterance for skill intent named CampaignIntent.
There are more intents like this, which I want to call based on user's question, e.g.
Alexa, ask marketing platform to give me messaging details
I am using Lambda for the skill. At the moment it looks like the following:
exports.handler = (event, context, callback) => {
try {
if (event.request.type === 'LaunchRequest') {
var welcomeMessage = '<speak>';
welcomeMessage = welcomeMessage + 'Welcome to XYZ agency.';
welcomeMessage = welcomeMessage + '</speak>';
callback(null, buildResponse(welcomeMessage, false));
//How can I tell Alexa to jump to CampaignIntent?
}
else if (event.request.type === 'IntentRequest') {
const intentName = event.request.intent.name;
if (intentName === 'CampaignIntent') {
var ssmlConfirm = "<speak>";
ssmlConfirm = ssmlConfirm + 'Hello Auto.';
ssmlConfirm = ssmlConfirm + "</speak>";
callback(null, buildResponse(ssmlConfirm, true));
}
}
}
catch (e) {
context.fail(`Exception: ${e}`);
}
};
function buildResponse(response, shouldEndSession) {
return {
version: '1.0',
response: {
outputSpeech: {
type: 'SSML',
ssml: response,
},
shouldEndSession: shouldEndSession,
},
sessionAttributes: {},
};
}
CampaignIntent does not have any slot. It simply fetches records from a third party platform API.
I also referred https://stackoverflow.com/a/48032367/1496518 but did not understand how to achieve ...has a WHEN slot to elicit part.
The documentation you linked say, "Users can combine your invocation name with an action, command or question. This sends the service for your skill an IntentRequest with the specific intent that corresponds to the user's request."
If a user invokes your skill in this way, the intent you find in the first request of that user's session will be CampaignIntent (the IntentRequest you've defined) instead of LaunchRequest. There isn't any "jumping" you need to do on your end. The behavior will be the same with or without slot values.

How can I create an entity specific to a user?

I'm creating an action for Google Assistant with Dialogflow and actions-on-google-nodejs that accesses the GitKraken Glo API to add cards to people's boards. I'm authenticating my users with Account Linking. I want my users to be able to say things like Add a card to [board name] or Add a card. If a board name isn't given I want the action to prompt the user for it. How can I create a session entity that get's all the board names for the logged in user?
Sorry if this doesn't make much sense, I'm pretty new to Actions on
Google and Dialogflow. Feel free to ask questions for clarity.
There are a few things you'll need to do first to use a Session Entity:
The Entity Type needs to already exist. Session Entities update existing ones. The easiest way to do this is to create the Entity you want in the Dialogflow UI. It doesn't need to have any Entities in it, but having one as a default can be useful.
You need a Service Account for your project in Google Cloud that will do the update, and a secret key for this account.
Your life will be a lot easier if you use a library, such as the dialogflow-nodejs library.
In general, your code needs to do the following, typically when the user first starts the session (ie - in your Welcome Intent Handler):
Get the list of boards
Update the Session Entity Type, creating an Entity for each board. Doing this update involves:
Issuing a patch against the projects.agent.sessions.entityTypes method with a SessionEntityType for the Entity Type you're overriding.
The SessionEntityType will contain an array of Entities with the canonical name (likely the board name, or some unique identifier) and any aliases for it (at least the board name, possibly anything else, possibly including aliases like "the first one" or "the most recent one").
The README for the library includes links to sample code about how to do this using the nodejs library. Code that I have that does this work has a function like this:
function setSessionEntity( env, entityType ){
const config = envToConfig( env );
const client = new dialogflow.SessionEntityTypesClient( config );
let parent = env.dialogflow.parent;
if( entityType.displayName && !entityType.name ){
entityType.name = `${parent}/entityTypes/${entityType.displayName}`;
}
if( !entityType.entityOverrideMode ){
entityType.entityOverrideMode = 'ENTITY_OVERRIDE_MODE_OVERRIDE';
}
const request = {
parent: parent,
sessionEntityType: entityType
};
return client.createSessionEntityType( request );
}
conv.user.email
You can use conv.user object :
const Users = {};
app.intent('Get Signin', (conv, params, signin) => {
if (signin.status === 'OK') {
const email = conv.user.email
Users[email] = { };
conv.ask(`I got your email as ${email}. What do you want to do next?`)
} else {
conv.ask(`I won't be able to save your data, but what do you want to next?`)
}
})
app.intent('actions.intent.TEXT', (conv, input) => {
if (signin.status === 'OK') {
Users[conv.user.email] = {
lastinput: input
};
}
});
conv.id
Also with conv id is unique id for the current conversation.
// Create an app instance
const app = dialogflow()
// Register handlers for Dialogflow intents
const Users = {};
app.intent('Default Welcome Intent', conv => {
Users[conv.id] = {
conversationId: conv.id,
name: '1234'
};
})
app.intent('actions.intent.TEXT', (conv, input) => {
Users[conv.id] = {
lastinput: input
};
});
app.intent('Goodbye', conv => {
delete Users[conv.id];
})

wait for user input to reply on a facebook bot using Node.js

What I need to do is make my bot after replying to something wait for user response so that he can reply with a flow ending messege Example:
User: clicks the Get Started button
Bot: I can help you with the following (Payment) (option2) (option3)
User: clicks Payment
Bot: Can you please enter the bill number
User:3922509234
.............................
Bot:Ok thank you, your payment will be processed
this is how the facebook app comunicates with the bot
app.post('/webhook', function(request, response) {
var data = request.body;
if(data.object == 'page'){
data.entry.forEach(function(pageEntry){
pageEntry.messaging.forEach(function (messagingEvent) {
if(messagingEvent.message){
console.log("MESSAGE---------------------> ",messagingEvent);
var messageInfo = functions.reciveMessage(messagingEvent);
console.log("MESSAGEINFO ", messageInfo);
evaluateMessage(messageInfo);
}
if(messagingEvent.postback){
console.log("POSTBACK--------------------> ",messagingEvent);
var postBackInfo = functions.recivePostBack(messagingEvent);
evaluatePostBack(postBackInfo);
}
});
});
response.sendStatus(200);
}
});
This is where the user's input is evaluated for keywords
function evaluateMessage(messageInfo, type) {
if(functions.isWordContain(messageInfo.senderMessage,'Pagar')||
functions.isWordContain(messageInfo.senderMessage,'pagar')||
functions.isWordContain(messageInfo.senderMessage,'Factura')||
functions.isWordContain(messageInfo.senderMessage,'factura')){
var message = functions.sendTextMessage(messageInfo.senderId, "Por
favor ingresa el numero de tu contrato");
callSendAPI(message);
}
}
The messageInfo is check if contains the keyWord then returns the message to the user.
How do i make the bot wait for user input so that he can reply
Im using Node.Js with express not any bot building platform please help
I found an easy way of doing this though there might be a better way a made a global variable
var verifier = {
origin:null,
state:false
};
then when it enters the message validator and modifies the json with a sort of token and sets the state to true
function evaluateMessage(messageInfo) {
if (functions.isWordContain(messageInfo.senderMessage,'Pagar')||
functions.isWordContain(messageInfo.senderMessage,'pagar')||
functions.isWordContain(messageInfo.senderMessage,'Factura')||
functions.isWordContain(messageInfo.senderMessage,'factura')){
var message = functions.sendTextMessage(messageInfo.senderId, "Por favor
ingresa el numero de tu contrato");
verifier.origin = 'pagoFactura';
verifier.state = true;
callSendAPI(message);
}
}
later where the messages enter I just ask if that state is true and which is the origin and depending if its one or the other I send a message
app.post('/webhook', function(request, response) {
var data = request.body;
if(data.object == 'page'){
data.entry.forEach(function(pageEntry){
pageEntry.messaging.forEach(function (messagingEvent) {
if(messagingEvent.message){
if(verifier.state == false){
console.log("MESSAGE---------------------> ",messagingEvent);
var messageInfo = functions.reciveMessage(messagingEvent);
console.log("MESSAGEINFO ", messageInfo);
evaluateMessage(messageInfo);
}else {
console.log("verifierOrigin ", verifier.origin);
var messageInfo = functions.reciveMessage(messagingEvent);
switch (verifier.origin) {
case 'pagoFactura':
var message = functions.sendTextMessage(messageInfo.senderId,
"Gracias su pago se a procesado exitosamente");
callSendAPI(message);
break;
default:
}
}
}
if(messagingEvent.postback){
console.log("POSTBACK--------------------> ",messagingEvent);
var postBackInfo = functions.recivePostBack(messagingEvent);
evaluatePostBack(postBackInfo);
}
});
});
response.sendStatus(200);
}
});
If someone finds a better way please comment also I know this is not the best way this is just for testing purposes

Handle XMPP presence with Node

I'm using the node-xmpp module to connect to a XMPP server and join a group chat. Connecting to the server, setting the presence, joining the room and reading out messages works so far. But I want to receive the userlist of the room too.
The XMPP protocol requires to send a presence stanza when the client enters the room (http://xmpp.org/extensions/xep-0045.html#enter-pres). But how can I now parse it in node?
My code currently looks like this:
var xmpp = require('node-xmpp');
// Create the XMPP Client
var cl = new xmpp.Client({
jid: jid,
password: password,
reconnect: true
});
// Do things when online
cl.on('online', function() {
util.log("We're online!");
// Set client's presence
cl.send(new xmpp.Element('presence', { type: 'available' }).c('show').t('chat'));
cl.send(new xmpp.Element('presence', { to: room_jid+'/'+room_nick }).c('x', { xmlns: 'http://jabber.org/protocol/muc' }).c('history', {seconds: 1}));
// Send keepalive
setInterval(function() {
cl.send(' ');
}, 30000);
cl.on('stanza', function(stanza) {
// always log error stanzas
if (stanza.attrs.type == 'error') {
util.log('[error] ' + stanza);
return;
}
// ignore everything that isn't a room message
if (!stanza.is('message') || !stanza.attrs.type == 'chat') {
return;
}
var body = stanza.getChild('body');
// message without body is probably a topic change
if (!body) {
return;
}
// Extract username
var from, room, _ref;
_ref = stanza.attrs.from.split('/'), room = _ref[0], from = _ref[1];
var message = body.getText();
// Log topics and messages to the console
if(!from) {
util.log('Topic: ' + message);
} else {
util.log('[' + from + ']: ' + message);
}
});
});
I already tried triggering presence by using
if(stanza.is('presence')) {}
within the cl.on('stanza') part but it doesn't work.
UPDATE: I'm describing a new method now which doesn't require the client to send requests.
Background: When the client joins a group chat, the server returns presence stanzas which contain information about the connected users to the group chat.
cl.on('stanza', function(stanza) {
// always log error stanzas
if (stanza.attrs.type == 'error') {
util.log('[error] ' + stanza);
return;
}
if(stanza.is('presence')){
// We are only interested in stanzas with <x> in the payload or it will throw some errors
if(stanza.getChild('x') !== undefined) {
// Deciding what to do based on the xmlns attribute
var _presXmlns = stanza.getChild('x').attrs.xmlns;
switch(_presXmlns) {
// If someone is joining or leaving
case 'http://jabber.org/protocol/muc#user':
// Get the role of joiner/leaver
_presRole = stanza.getChild('x').getChild('item').attrs.role;
// Get the JID of joiner/leaver
_presJID = stanza.getChild('x').getChild('item').attrs.jid;
// Get the nick of joiner/leaver
_presNick = stanza.attrs.from.split('/')[1];
// If it's not none, this user must be joining or changing his nick
if(_presRole !== 'none') {
// We are now handling the data of joinging / nick changing users. I recommend to use an in-memory store like 'dirty' [https://github.com/felixge/node-dirty] to store information of the users currentliy in the group chat.
} else {
// We are now handling the data of leaving users
}
break;
}
return;
}
return;
}
OLD METHOD
I previously described a method how to query the server for current users in the group chat. By maintaining a store where all user traffic (joining, leaving, nick changing) is stored, this is no longer required. However you could still use it to make sure the data is consistent by issues like a presence stanza was not delivered to the client correctly. That's the reason it's still described below:
To request a list with users connected to the room, you need to perform the following actions:
First send a request to the server and ask for the user list:
cl.send(new xmpp.Element('iq', {from: jid, to: room_jid, type: 'get' }).c('query', { xmlns: 'http://jabber.org/protocol/disco#items' }));
then listen for iq-stanzas, parse them and populate an array with the data:
// Catching the requested user list
if(stanza.is('iq')){
// Fetching usernames from return data (data structure: http://xmpp.org/extensions/xep-0045.html#example-12)
var _items = stanza.getChild('query').getChildren('item');
var users = new Array();
for(var i = 0; i<_items.length; i++) {
// We are building an object here to add more data later
users[i] = new Object();
users[i]['name'] = _items[i].attrs.name;
}
console.log(util.inspect(users, {depth: null, colors: true}));
return;
}
This will provide you with a user list. To request unique JIDs you have to probe every user. To keep the list up to date, you should remove users when they leave and add + probe when they join.

Resources