issues with Claudia.js text responses and Alexa - node.js

I'm working on a Claudia.js bot that can be reached through Slack, FB messenger, and as an Alexa skill. Supposedly in claudia, you can return plain text and the framework will give it back to the "frontend" correctly... What I have currently have here works fine with FB and Slack, but when I access it through the Alexa "Service Simulator" I always get "The response is invalid." Here is the lambda that uses Claudia.js. Basically it gets messages from the client and then shunts them to another Lambda that is the "AI". Alexa seems to be choking on line 67. Ideas?
const promiseDelay = require('promise-delay');
// const aws = require('aws-sdk');
// const lambda = new aws.Lambda();
const lambda = require('aws-lambda-invoke');
const botBuilder = require('claudia-bot-builder');
const stackTrace = require('stack-trace');
//const slackDelayedReply = botBuilder.slackDelayedReply;
const getIntentName = alexaPayload =>
alexaPayload &&
alexaPayload.request &&
alexaPayload.request.type === 'IntentRequest' &&
alexaPayload.request.intent &&
alexaPayload.request.intent.name;
const api = botBuilder((message, apiRequest) => {
console.log = console.log.bind(null, '[LOG]');
console.info = console.info.bind(null, '[INFO]');
console.error = console.error.bind(null, '[ERROR]');
console.warn = console.warn.bind(null, '[WARN]');
console.info(message, apiRequest);
console.log(apiRequest.body);
const requestData = {
'user-id': {
type: message.type,
ID: message.sender
},
epoch: 1484771343.01,
'payload-type': 'luis',
facets: {},
utterance: 'Seek Showtimes',
payload: {
query: 'Seek Showtime',
topScoringIntent: {
intent: 'SeekShowtime',
score: 1.0
},
intents: [{
intent: 'SeekShowtime',
score: 1
}],
entities: []
}
};
if (message.text) {
return new Promise((resolve, reject) => {
lambda.raw.invoke({
FunctionName: 'ca2',
Payload: JSON.stringify(requestData),
}, (err, done) => {
if (err) {
const trace = stackTrace.parse(err);
console.warn(err);
console.error(trace);
return reject(err);
}
resolve(done);
});
}).then((result) => { // the initial response
const payload = JSON.parse(result.Payload);
console.log(payload.utterance);
return payload.utterance;
}).catch((error) => {
const trace = stackTrace.parse(error);
console.warn(error);
console.error(trace);
return 'Could not setup';
});
} else if (getIntentName(apiRequest.body) === 'ExitApp') {
return {
response: {
outputSpeech: {
type: 'PlainText',
text: 'Bye from Bot!'
},
shouldEndSession: true
}
};
} else {
return {};
}
},
{ platforms: ['facebook', 'slackSlashCommand', 'alexa'] }
);
module.exports = api;
Update -- even if I hardcode a plain text string response or use Alexa Message Builder I still get "The response is invalid." as the Service Response is coming back "undefined."
Looking at logs, as soon as the response is returned (for parsing by botBuilder and a pass to Alexa) this error occurs [TypeError: Cannot read property 'replace' of undefined]
Another update:
If I replace return payload.utterance with something like
if (type === 'alexa-skill') {
Console.warn('trying to contact alexa');
return "Hi from Alexa";
}
The problem persists.
Here is where the Json Request comes in, no problem:
2017-04-27T18:06:30.552Z 3d70c273-2b74-11e7-a1c8-bf3fec00cbff STORE Map { "user-id": Map { "type": "alexa-skill", "ID": "amzn1.ask.account.AF6FUNJDSHGCXPVSAO5HUSRYFBD3SPCJJLILC4HLPS3K3L4AOWIMXPS4ZDDCXQ3ZVIV5L4FOMYD23PWZXEIAKYQBVXIQTPE2WW2PMBIXQIY3TUATXADCVNYO7NYUR2B45EU5GRIWBFHQIPLQVDQZMXD7IYVGTKAV3OWPHROCPR7XIUGNSJEAGQZJOMULSKT5HYSNUNJONASE34Y" }, "epoch": 1484771343.01, "payload-type": "luis", "utterance": "when is Logan playing", "payload": Map { "query": "when is Logan playing" } }
Here is the response I get back from the other lambda (the payload):
017-04-27T18:06:32.513Z 3d70c273-2b74-11e7-a1c8-bf3fec00cbff [LOG] mnlpData { StatusCode: 200,
Payload: '{"utterance": "To find movies playing near you, I need to know where you are. Please tell me your zip code.", "AskLocation": 1, "context": {"updated": 1493316392.162429, "user_id": "TEST_ID_TUES_14", "sessions": [{"intents": ["SeekShowtime", "SeekShowtime"], "facet-delta": {}, "facets": {"ity.location": {"ity.zip": "", "ity.code": "", "ity.theatre-name": ""}, "ity.movie": {"ity.title": "", "ity.code": ""}, "ity.time": [], "ity.date": []}, "modes": ["", "SHOWTIME_SWITCH", "AskLocation", "SHOWTIME_SWITCH", "AskLocation"]}], "created": 1493316379.950335, "mode_process_count": 2, "user-id": {"type": "alexa-skill", "ID": "amzn1.ask.account.AF6FUNJDSHGCXPVSAO5HUSRYFBD3SPCJJLILC4HLPS3K3L4AOWIMXPS4ZDDCXQ3ZVIV5L4FOMYD23PWZXEIAKYQBVXIQTPE2WW2PMBIXQIY3TUATXADCVNYO7NYUR2B45EU5GRIWBFHQIPLQVDQZMXD7IYVGTKAV3OWPHROCPR7XIUGNSJEAGQZJOMULSKT5HYSNUNJONASE34Y"}, "utterance": ["To find movies playing near you, I need to know where you are. Please tell me your zip code."]}}' }
then:
2017-04-27T18:06:32.514Z 3d70c273-2b74-11e7-a1c8-bf3fec00cbff [WARN] trying to contact alexa
and then the error:
2017-04-27T18:06:32.514Z 3d70c273-2b74-11e7-a1c8-bf3fec00cbff [TypeError: Cannot read property 'replace' of undefined]

First, if you notice the payload that is sent to you from the other lambda, you can see the utterance is an array, so you might have to pass the first element if present.
By looking at the code of the bot builder, my best bet is that the error you're getting is due to the fact your alexaAppName is undefined, and when passing it down to the responder that is encoded in base64, it's not possible to run a replace of this variable.
I would try to make sure my app name is correctly configured and what's given is a valid string as shown in the alexa claudia example.

If you haven't already, run claudia update --configure-alexa-skill and enter your bot's name, then use the url it gives in your alexa skill builder setup. Select HTTPS instead of lambda arn.
Currently, message.text is being passed as an empty string, which means that none of your log blocks are firing, and you are returning an empty object at the very bottom of your code. Test it yourself by replacing that empty object with a string, the alexa tester no longer complains.
So why is message.text an empty string? Claudia js populates the text field by concatenating all the slots that have been filled for your intent. If your intent has no slots, then claudia passes an empty string.
Add slots to your intent, make sure the skill is configured, and fix the logic statement issues.

Related

Hubspot Timeline event API Node.JS "TypeError: Cannot read properties of undefined (reading 'eventsApi')"

I'm trying to use the Hubspot Timeline event API to "create a single event" but I keep getting the error:
"TypeError: Cannot read properties of undefined (reading 'eventsApi')".
I'm using the code from here: https://developers.hubspot.com/docs/api/crm/timeline
With a working access token.
It works when I run it from within their page but I get the error when I try to run it from my own server(I'm using an actual access Token when I'm running it)
I'm thinking that is has something to do with the: #hubspot/api-client but I can't figure out why it doesn't work (it is installed: #hubspot/api-client": "^6.0.1-beta3" and I have tried older versions as well)
async function reportGenerated (){
const hubspot = require('#hubspot/api-client');
const hubspotClient = new hubspot.Client({"accessToken":"????"});
const tokens = {
"report-generated": "",
"yay1": "456",
"yay2": "2"
};
const extraData = {
"questions": [
{
"question": "Who's a good girl?",
"answer": "Bark!"
},
{
"question": "Do you wanna go on a walk?",
"answer": "Woof!"
}
]
};
const timelineIFrame = {
"linkLabel": "View Art3mis",
"headerLabel": "Art3mis dog",
"url": "https://my.petspot.com/pets/Art3mis",
"width": 600,
"height": 400
};
const TimelineEvent = { eventTemplateId: "1123354", email: "test#gmail.com", tokens, extraData, timelineIFrame };
try {
const apiResponse = await hubspotClient.crm.timeline.events.eventsApi.create(TimelineEvent);
console.log(JSON.stringify(apiResponse.body, null, 2));
} catch (e) {
e.message === 'HTTP request failed'
? console.error(JSON.stringify(e.response, null, 2))
: console.error(e)
}
}
reportGenerated ();
I figured it out. The path in the documentation is wrong.
Instead of:
hubspotClient.crm.timeline.events.eventsApi.create(TimelineEvent);
it should be
hubspotClient.crm.timeline.eventsApi.create(TimelineEvent);
So "events" needs to be removed

Firebase. Error "Request contains an invalid argument."

Use on backend side following libraries (from package.json).
"firebase": "^8.3.3",
"firebase-admin": "^9.6.0",
Try to send multicast message to multiple users.
import * as admin from 'firebase-admin';
const createNotificationMessage = (tokens: string[], data?: { [key: string]: string }): admin.messaging.MulticastMessage => {
return {
data,
tokens,
apns: {
payload: {
aps: {
contentAvailable: true,
},
},
},
};
};
const sendMulticast = (payload: admin.messaging.MulticastMessage) =>
admin.messaging().sendMulticast(payload);
const sendNotifications = async () => {
try {
const data = getData(); // here we get main data
const userTokens = getTokens(); // here we get tokens
await sendMulticast(createNotificationMessage(userTokens, data));
} catch (error) {
console.log(error);
}
}
I put 4 tokens to message before sending. But I got this error message in response
[{"success":false,"error":{"code":"messaging/invalid-argument","message":"Request contains an invalid argument."}},{"success":false,"error":{"code":"messaging/invalid-argument","message":"Request contains an invalid argument."}},{"success":false,"error":{"code":"messaging/invalid-argument","message":"Request contains an invalid argument."}},{"success":false,"error":{"code":"messaging/invalid-argument","message":"Request contains an invalid argument."}}]
What I tried to do:
Send messages through method send one by one. Result: the same error on every message
Tried to set header apns-priority to 5. The same error
Tried to set custom properties in aps object - content-available, content_available. The same error
Delete apns property from payload. Works well and there is no errors but I need silent notifications in iOS applications that's why option contentAvailable is required.
One note: this code worked well till 9 April 2021.
After full day search the reason of this errors, I found a solution for my problem.
const createNotificationMessage = (tokens: string[], data?: { [key: string]: string }): admin.messaging.MulticastMessage => {
return {
data,
tokens,
apns: {
payload: {
aps: {
contentAvailable: true,
badge : 0
},
},
},
};
};
Don't know why firebase shows an error because according to official website, parameter badge is optional string.

AWS PUT request met with "Provided key element does not match schema."

(Edited to incorporate comments)
So I apologize in advance for the long question. I don't know how else to ask it.
I'm trying to finish up a full-stack web app using React, Node, and DynamoDB. POST and GET requests are working fine, but I'm stuck on PUT. My mock PUT request works fine, but once I try it from the front end in React, I get the error mentioned in the title. I'll show the back end code first, then the mock update, and then the front end.
import handler from "./libs/handler-lib";
import dynamoDb from "./libs/dynamodb-lib";
export const main = handler(async (event, context) => {
const data = JSON.parse(event.body);
const params = {
TableName: process.env.tableName,
Key: {
userId: event.requestContext.identity.cognitoIdentityId,
activityId: event.pathParameters.activityId
},
UpdateExpression: "SET title = :title, activityType = :activityType, activityRoutine = :activityRoutine, activityComment = :activityComment",
ExpressionAttributeValues: {
":title": data.title || null,
":activityType": data.activityType || null,
// ":activityRoutine": data.activityRoutine == '' ? "None" : data.activityRoutine,
// ":activityComment": data.activityComment == '' ? "None" : data.activityComment
":activityRoutine": data.activityRoutine || null,
":activityComment": data.activityComment || null
},
ReturnValues: "ALL_NEW"
};
await dynamoDb.update(params);
return { status: true };
This mock update event works without issue:
{
"body": "{\"title\":\"test\",\"activityType\":\"testing\",\"activityRoutine\":\"\",\"activityComment\":\"\"}",
"pathParameters": {
"activityId": "long-alphanumeric-id"
},
"requestContext": {
"identity": {
"cognitoIdentityId": "us-east-and-so-on"
}
}
}
But this code, which produces the exact same Javascript object as the mock, is not okay with AWS:
function saveActivity(activity) {
try {
return API.put("activities", `/activities/${id}`, {
body: activity
});
} catch(e) {
console.log("saveActivity error:", e);
}
}
async function handleSubmit(event) {
event.preventDefault();
setIsLoading(true)
try {
await saveActivity({
title: title, activityType: activityType, activityRoutine: activityRoutine, activityComment: activityComment
// "key": {userId: userId, activityId: activityId}
// "pathParameters": {"id": activityId},
// "requestContext": {"identity": {"cognitoIdentityId": userId}}
});
} catch(e) {
console.log(e)
setIsLoading(false)
}
}
If anyone needs to see more of the code, I'm happy to share, but I figured this question is already getting very long. Any code you see commented out has been tried before without success.
I'd also be happy if someone could point me in the right direction as far as the AWS documentation is concerned. I've been going off of a tutorial and modifying it where need be.
Any help is appreciated!

How to intergrate lex with lambda in amazon aws?

I have a simple bot with the following logic.
Bot: select one of the following item your interested
(response card)
-ecommerce
-travel etc
Human : clicks eg travel
Bot: response card
-marketing
-digital
Here is what I have in my lambda function
'use strict';
exports.handler = (event, context, callback) => {
const sessionAttributes = event.sessionAttributes;
const slots = event.currentIntent.slots;
const videommerceType = slots.videommerceType;
// predefined list of available pizza
const validData = ['ecommerce', 'startup', 'lead generation', 'crm', 'travel'];
// negative check: if valid slot value is not obtained, inform lex that user is expected
// respond with a slot value
if (videommerceType && !(videommerceType === "") && validData.indexOf(videommerceType.toLowerCase()) === -1) {
let response = {
sessionAttributes: event.sessionAttributes,
dialogAction: {
type : "'ElicitSlot",
message: {
contentType: "PlainText or SSML",
content: "Message to convey to the user. For example, Thanks, your pizza has been ordered."
},
responseCard: {
version: "1",
contentType: "application/vnd.amazonaws.card.generic",
genericAttachments: [{
title: "card-title",
subTitle: "card-sub-title",
imageUrl: "URL of the image to be shown",
attachmentLinkUrl: "URL of the attachment to be associated with the card",
buttons: [{
text: "button-text",
value: "Value sent to server on button click"
}]
}]
}
}
}
callback(null, response);
}
let response = {
sessionAttributes: sessionAttributes,
dialogAction: {
type: "Delegate",
slots: event.currentIntent.slots
}
}
callback(null, response);
};
Unfortunately this not working, on lex bot I get the following error
Intent videommerceIntent is ReadyForFulfillment: name:jim videommerceType:ecommerce
What is wrong with my code? any help or similar working demo would be appreciated , thanks

First alexa skill

I am trying to develop my first Alexa skill using Node.js, and every time I try to test it I get "There was a problem with the requested skill's response".
I am trying create a random restaurant generator. Pretty simple its an array of restaurants, a random index is selected, and Alexa says the restaurant. I don't know where I went wrong I have uploaded my .json and .js files if anyone can help i'd really appreciate it.
index.js:
const Alexa = require('alexa-sdk');
const APP_ID = 'amzn1.ask.skill.9350e65b-fb41-48ce-9930-98b5156eb63c';
const handlers = {
'LaunchRequest': function () {
this.emit('randomRestaurantGeneratorIntent');
},
'randomRestaurantGeneratorIntent': function () {
var randomResturant;
var foodArray = ['IHOP', 'Dennys', 'burger king'];
randomResturant = foodArray[Math.floor(Math.random() * foodArray.length)];
this.response.speak(randomResturant);
this.emit(':responseReady');
},
'AMAZON.HelpIntent': function () {
const say = 'You can say what did I learn, or, you can say exit... How can I help you?';
this.response.speak(say).listen(say);
this.emit(':responseReady');
},
'AMAZON.CancelIntent': function () {
this.response.speak('Bye!');
this.emit(':responseReady');
},
'AMAZON.StopIntent': function () {
this.response.speak('Bye!');
this.emit(':responseReady');
}
};
exports.handler = function (event, context, callback) {
const alexa = Alexa.handler(event, context, callback);
alexa.APP_ID = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
};
randomResturantGeneratorIntent.JSON:
{
"interactionModel": {
"languageModel": {
"invocationName": "random restaurant generator",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "randomRestaurantGeneratorIntent",
"slots": [],
"samples": [
"Launch Random Restaurant Generator "
]
}
],
"types": []
}
}
}
Thank you
Try this way to render responses.
var speechOutput = 'Your response here';
var reprompt = "How can I help?";
this.response.speak(speechOutput);
this.response.listen(reprompt);
this.emit(":responseReady");
Try this function in inline editor for your first skill. and try to test with open random restaurant generator,
/**
* Called when the user launches the skill without specifying what they want.
*/
function onLaunch(launchRequest, session, callback) {
console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
// Dispatch to your skill's launch.
getWelcomeResponse(callback);
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: '1.0',
sessionAttributes,
response: speechletResponse,
};
}
function getWelcomeResponse(callback) {
// If we wanted to initialize the session to have some attributes we could add those here.
const sessionAttributes = {};
const cardTitle = 'Welcome';
const speechOutput = 'Welcome to Your First Alexa Skill';
// If the user either does not reply to the welcome message or says something that is not
// understood, they will be prompted again with this text.
const repromptText = 'Please tell me What do you want to know?';
const shouldEndSession = false;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: 'PlainText',
text: output,
},
//For testing purpose only
// card: {
// type: 'Simple',
// title: `SessionSpeechlet - ${title}`,
// content: `SessionSpeechlet - ${output}`,
// },
reprompt: {
outputSpeech: {
type: 'PlainText',
text: repromptText,
},
},
shouldEndSession,
};
}
exports.handler = (event, context, callback) => {
try {
console.log(`event.session.application.applicationId=${event.session.application.applicationId}`);
if (event.request.type === 'LaunchRequest') {
onLaunch(event.request,
event.session,
(sessionAttributes, speechletResponse) => {
callback(null, buildResponse(sessionAttributes, speechletResponse));
});
}
}
catch (err) {
callback(err);
}
};
I’ve been using lambda for two years and it’s terrible to debug and deploy for me until I started to use aws cloud9.
I suggest that you use aws cloud9 which is a cloud IDE for writing, running and debugging code. You could run the lambda function as local environment.
Check their website to get more information. It’s time consuming, but totally worth it, especially if you want to develop an Alexa skill.
Most of the times you get that error for 2 things:
You don't have the trigger "Alexa Skill Kit" in your lambda function. If you don't have it, you can add one in the designer tab of the configuration of the lambda function.
You don't have the neccesary modules in your lambda function. You can add them locally with "npm install ask-sdk-core" and then upload the folder.
Use this way:
var speechOutput = 'Your response here';
var reprompt = "How can I help?";
this.response.speak(speechOutput);
this.response.listen(reprompt);
this.emit(":responseReady");

Resources