newbie node.js bot integration with database lookup (looking for best practice) - node.js

OK, new to Node.js and botframework. I built first bot using Azure site and downloaded the code. Chose the Luis integrated bot template.
I understand (finally) the event driven model of node.js and the concept of callbacks.
I have the code snippet below. When Luis finds an intent of "Help" it triggers this function. In turn, I have database calls to lookup the entity. Within the database I have an entity, response (if entity is bottle, answer "Recycle").
I have that code working too.
Below the first block is a function handleHelpRequester which is the callback function, I have that working as well. Where I am a little stuck (best practice) is that in this callback function I want to send something to the session object (session.send toward the bottom of the function).
Since I don't create the session object (directly) I'm not sure on the options.
Should I pass the session object to the database function, then pass it back?
Should I create a global variable and set it to the session object (I'm concerned that if multiple people are using this bot then this approach won't work).
I'm open for suggestions.
Thanks
.matches('Help', (session, args) => {
var entities = args.entities;
var itype = builder.EntityRecognizer.findEntity(args.entities, 'ItemTypes');
var respondToUser = '';
var msg = 'Initialized';
// if there is an entity provided, perform a lookup of the entity.
if (itype.entity !== null) {
//Use session.sendTyping so the user thinks something is happening rather than being ignored while we do the lookup in SharePoint.
session.sendTyping();
//perform lookup
respondToUser = sp.lookupEntity(itype.entity, handleHelpRequest);
};
})
function handleHelpRequest(err, respondToUser) {
var msg = 'uninitialized';
if (err = 'success') {
console.log('Respond to user from matches:'.concat(respondToUser));
//return from lookup
if (respondToUser === 'No match found') {
msg = 'I think you are asking for help, but I don\'t understand what you need help with.';
}
else {
msg = 'I can help you with that, \'%s\'.', respondToUser;
}
console.log(msg);
session.send(msg);
//The following two lines are for debugging
session.send('How may I assist you? ' + JSON.stringify(args));
session.send('Value of entity you said: \'%s\'.', itype.entity);
}
else {
console.log('an error occurred');
}
}

If you want to have access to the session object, then pass it as a parameter to your helper function.
Example:
function handleHelpRequest(session, err, respondToUser) {
// do session stuff
}

Related

How to call a handler from another handler deploying an Alexa Skill?

Hello everyone,
i am working on an Alexa Skill at the moment and I have the problem, that I need to call a handler from another one.
I read several posts but there was no clear answer to my problem. I have a handler "AskForEmployeeInformation_Handler" which deals with several intents which answer several questions about a person. But if the user only says the surname or name instead of the full name then Alexa prompts out if there are persons which match to the given name. The user should now say one of the proposed names. While saying this name another handler "SetName_Handler" is called, which sets the chosen name as a session attribute. Now I want to call in the return statement the "AskForEmployeeInformation_Handler" again because it includes the whole logic to prompt out the wanted informations.
I saw some solutions to call the handler like you can see in the following code and the logic was executed but Alexa did not say anything and do not prompt the answer to the user.
Do you have an idea what I can do to solve the problem?
Thanks for your help.
const SetName_Handler = {
canHandle(handlerInput){
const request = handlerInput.requestEnvelope.request;
let sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
return sessionAttributes.event === 'AskForEmployee_Name' || sessionAttributes.event === 'AskForPhoneNumber_Name' || sessionAttributes.event === 'AskForRole_Name';
},
handle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
const responseBuilder = handlerInput.responseBuilder;
let sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
let say = '';
let slotValues = getSlotValues(request.intent.slots);
console.log('***** slotValues: ' + JSON.stringify(slotValues, null, 2));
//save person for the session - maybe for further questions
sessionAttributes['employeeSurname'] = slotValues.employeeSurname.resolved;
sessionAttributes['employeeName'] = slotValues.employeeName.resolved;
handlerInput.attributesManager.setSessionAttributes(sessionAttributes);
return AskForEmployeeInformation_Handler.handle(handlerInput;
}
};
I think your best bet is to separate the logic out of your handlers. I have never seen the need to call another Intent's handle method from another, and I find that this would add complications and odd errors.
The flat IntentHandler way of doing things is a recipe for disaster once your skill needs to know about session states and context (for example, what the user was doing previous to this IntentHandler being invoked).
Instead, you might find the Dialog Interface way of confirming slots to be exactly what you are looking for: https://developer.amazon.com/en-US/docs/alexa/custom-skills/dialog-interface-reference.html
Check out this article for a good overview of why/how to manage state and context in Alexa apps: https://medium.com/hackernoon/lessons-learned-moving-from-web-to-voice-development-35daa1d301db

issue with creating role and channel automatically from private message with bot

Not sure how the create channel and create role isn't working inside the following code, towards the bottom. (EDIT: Nothing is sent to the console and nothing happens regardng the code. It is like it is entirely ignored.) This is a snippet from code that User A challenges User B. User B is messaged, alerting them that a challenge has been issued to them via a Private Message. If the challenge is accepted, I want the bot to 1)Make a role specifically for User A and User B named "User A vs User B" 2) take User A and User B and put them both into that new role and 3) Make a battlefield named "User A vs User B" inside a specific category inside the server the bot is on.
I am unsure if the problem lies in how the bot is trying to make the role and channel in a sever while the bot is talking to the user in a private message instead of on the server. I thought putting the "server" variable as the server ID would help but it doesn't seem to do anything after the accept message.
// Awaits reply from user
if (message.channel.id === '541736552582086656') return target.send("Do you accept the challenge? Please reply with 'accept' or 'deny'.")
.then((newmsg) => {
newmsg.channel.awaitMessages(response => response.content, {
max: 1,
time: 150000,
errors: ['time'],
}).then((collected) => {
// Grabs the first (and only) message from the collection.
const reply = collected.first();
if (reply.content === 'accept'){
reply.channel.send(`You have ***accepted *** the challenge from ${challenger}. Please wait while your battlefield is made...`);
message.author.send(`${target} has accepted your challenge! Please wait while the channel is made for your brawl...`)
/// Problems start here
function createChannel(message){
var server = "SERVER ID";
var name = `${target} vs ${challenger}`;
message.guild.createRole({
role: {
name: `${target} vs ${challenger}`,
color: "#00fffa",
permissions: [] }
}).then(role => {
target.addRole(role, name)
challenger.addRole(role, name)
.catch(error => client.catch(error))
}).catch(error => client.catch(error))
server.createChannel(Name, name).then(
(channel) => {
channel.setParent("CATEGORY ID")
})
} // problems end here
} else if (reply.content === 'deny') {
reply.channel.send("You have ***denied *** the challenge.")
} else {
reply.channel.send("Your response wasn't valid.");
}
})
})
}
I have been wondering if I need to go about making the channel and role in a different way since it is trying to be made from a private message and not inside the server..
Thanks for any and all help! I also apologize if I'm using stack overflow too much for problems like this... You guys are great at helping me see different ways to do things and what I'm doing wrong, so I am learning, but I don't want to feel like I'm abusing it too much.
I think the problem is the fact that you create a function called createChannel with the code to create a rol and channel, but you never call said function.
You can either call the function after you've declared it or (which is in my opinion better) you can remove the following lines
function createChannel(message){
} // problems end here

args not returning expected LUIS result after implementing BotAuth

I have been creating a chat bot with MS Bot Framework in Nodejs and LUIS. I am recently trying to get certain information from the MS Graph API, and have (sort of) successfully implemented BotAuth and am able to get the information I want.
The issue I am facing now is that for the dialog that implements BotAuth, I am not able to get the usual args that comes with LUIS-intents triggered dialogs. Thus, I am not able to get any entities that the user might have entered. Other dialogs that do not implement BotAuth have no issues with this.
What I am getting now from args is:
{ response: undefined, resumed: 4 }
I am guessing that the issue lies with the [].concat part in this section:
bot.dialog('refreshSchDialog-oauth', [].concat(
ba.authenticate("aadv2"),
(session, args, skip) => {
let user = ba.profile(session, "aadv2");
session.endDialog(user.displayName);
session.userData.accessToken = user.accessToken;
session.userData.refreshToken = user.refreshToken;
console.log('args');
console.log(args);
if (user.accessToken) {
session.send('got leh');
// valid access token, check if luis has any entities (MV name)
// if there is, store conversationData and move to next dialog
if (args.entities) {
for (i = 0; i < args.entities.length; i++) {
if (args.entities[i].type == 'dbName') {
session.conversationData.mvName = args.entities[i].entity;
session.send(args.entities[i].entity);
}
}
}
session.beginDialog('refreshSchDialog');
} else {
// no valid access token
// TODO error message
}
}))
.triggerAction({
matches: 'refreshSchema',
intentThreshold: 0.3
});
May I know why the args is not returning the information from LUIS?
Looking at the BotAuth code it appears that the Auth dialog returns the user if properly authenticated or false if the dialog failed. It doesn’t copy over the args from LUIS. I would change your code so that the first function in your waterfall stores the LUIS data into session.dialogData, then call ba.authenticate and then use both results in your last waterfall step.

Messaging a user a bot does not know

I am using the Slack RTM node client and having a bit of an issue with DM's. Say a user joins the channel who has never DM'ed the bot before, the user types a command in the channel that the bot usually will respond to and by default the bot responds in a private message to the user. However, the bot cannot do this because the dataStore does not contain any DM data for this user. Code sample below...
rtm.on(RTM_EVENTS.MESSAGE, function (message) {
user = rtm.getUserById(message.user);
console.log(user); // It gets the user object fine
dm = rtm.getDMByName(user.name);
console.log(dm); // This is always undefined unless the user has DM'ed the bot previously
});
Is there a way around this? I can't seem to find anything in the docs or code to suggest there might be.
You can use the im.open method of the web API. Here's roughly how you'd do it with #slack/client (untested, apologies in advance!):
var webClient = new WebClient(token);
...
rtm.on(RTM_EVENTS.MESSAGE, function (message) {
var dm = rtm.getDMById(message.user);
if (dm) {
console.log(`Already open IM: ${dm}`);
// send a message or whatever you want to do here
} else {
webClient.im.open(message.user, function (err, result) {
var dm = result.channel.id;
console.log(`Newly opened IM: ${dm}`);
// send a message or whatever you want to do here
});
}
});

Overriding delete operation on Azure Mobile Services table

I'd like to override delete operation on my Azure Mobile Services table to make it more like update then real delete. I have additional column named IsDeleted and I'd like to set it's value to true when delete operation is executed.
I figured out, that what I need is:
fire my own 'update' inside del function,
delete current request.execute()
prepare and sent response by myself
That meens my del function should look like that:
function del(id, user, request) {
// execute update query to set 'isDeleted' - true
// return standard response
request.respond();
}
As you can see I'm missing the first part of the function - the update one. Could you help me writing it? I read Mobile Services server script reference but there is no info about making additional queries inside a server script function.
There are basically two ways to do that - using the tables object, and using the mssql object. The links point to the appropriate reference.
Using mssql (I didn't try it, you may need to update your SQL statement):
function del(id, user, request) {
var sql = 'UPDATE <yourTableName> SET isDeleted = true WHERE id = ?';
mssql.query(sql, [id], {
success: function() {
request.respond(statusCodes.OK);
}
});
}
Using tables (again, only tested in notepad):
function del(id, user, request) {
var table = tables.getTable('YourTableName');
table.where({ id: id }).read({
success: function(items) {
if (items.length === 0) {
request.respond(statusCodes.NOT_FOUND);
} else {
var item = items[0];
item.isDeleted = true;
table.update(item, {
success: function() {
request.respond(statusCodes.OK, item);
}
});
}
}
});
}
There is a Node.js driver for SQL Server that you might want to check out.
The script component of Mobile Services uses node.js. You might want to check out the session from AzureConf called Javascript, meet cloud

Resources