Which code pattern to use for Bot builder sdk 4 nodejs? - node.js

I am looking at the code sample, 14.nlp-with-dispatch which makes use of Dispatch, LUIS, and QnA Maker. Before looking at this code I had downloaded a NodeJS sample from the page Use multiple LUIS and QnA models, which looked like:
const { BotFrameworkAdapter, BotStateSet, ConversationState, MemoryStorage, TurnContext, UserState } = require('botbuilder');
const { LuisRecognizer, QnAMaker } = require('botbuilder-ai');
const { DialogSet } = require('botbuilder-dialogs');
const restify = require('restify');
// Create server
let server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log(`${server.name} listening to ${server.url}`);
});
// Create adapter
const adapter = new BotFrameworkAdapter({
appId: '',
appPassword: ''
});
const dispatcher = new LuisRecognizer({
appId: '',
subscriptionKey: '',
serviceEndpoint: '',
verbose: true
});
//LUIS homeautomatio app
const homeAutomation = new LuisRecognizer({
appId: '',
subscriptionKey: '',
serviceEndpoint: '',
verbose: true
});
// LUIS `weather app`
const weather = new LuisRecognizer({
appId: '',
subscriptionKey: '',
serviceEndpoint: '',
verbose: true
});
// The QnA
const faq = new QnAMaker(
{
knowledgeBaseId: '',
endpointKey: '',
host: ''
},
{
answerBeforeNext: true
}
);
// Add state middleware
const storage = new MemoryStorage();
const convoState = new ConversationState(storage);
const userState = new UserState(storage);
adapter.use(new BotStateSet(convoState, userState));
// Register some dialogs for usage with the LUIS apps that are being dispatched to
const dialogs = new DialogSet();
// Helper function to retrieve specific entities from LUIS results
function findEntities(entityName, entityResults) {
let entities = []
if (entityName in entityResults) {
entityResults[entityName].forEach(entity => {
entities.push(entity);
});
}
return entities.length > 0 ? entities : undefined;
}
// Setup dialogs
dialogs.add('HomeAutomation_TurnOn', [
async (dialogContext, args) => {
const devices = findEntities('HomeAutomation_Device', args.entities);
const operations = findEntities('HomeAutomation_Operation', args.entities);
const state = convoState.get(dialogContext.context);
state.homeAutomationTurnOn = state.homeAutomationTurnOn ? state.homeAutomationTurnOn + 1 : 1;
await dialogContext.context.sendActivity(`${state.homeAutomationTurnOn}: You reached the "HomeAutomation_TurnOn" dialog.`);
if (devices) {
await dialogContext.context.sendActivity(`Found these "HomeAutomation_Device" entities:\n${devices.join(', ')}`);
}
if (operations) {
await dialogContext.context.sendActivity(`Found these "HomeAutomation_Operation" entities:\n${operations.join(', ')}`);
}
await dialogContext.end();
}
]);
dialogs.add('Weather_GetCondition', [
async (dialogContext, args) => {
const locations = findEntities('Weather_Location', args.entities);
const state = convoState.get(dialogContext.context);
state.weatherGetCondition = state.weatherGetCondition ? state.weatherGetCondition + 1 : 1;
await dialogContext.context.sendActivity(`${state.weatherGetCondition}: You reached the "Weather_GetCondition" dialog.`);
if (locations) {
await dialogContext.context.sendActivity(`Found these "Weather_Location" entities:\n${locations.join(', ')}`);
}
await dialogContext.end();
}
]);
adapter.use(dispatcher);
// Listen for incoming Activities
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (context) => {
if (context.activity.type === 'message') {
//the dialog set requires use of a state property accessor to access the dialog state
const state = convoState.get(context);
const dc = dialogs.createContext(context, state);
// Retrieve the LUIS results from our dispatcher LUIS application
const luisResults = dispatcher.get(context);
// Extract the top intent from LUIS and use it to select which LUIS application to dispatch to
const topIntent = LuisRecognizer.topIntent(luisResults);
const isMessage = context.activity.type === 'message';
if (isMessage) {
switch (topIntent) {
case 'l_homeautomation':
//Call luis.
const homeAutoResults = await homeAutomation.recognize(context);
const topHomeAutoIntent = LuisRecognizer.topIntent(homeAutoResults);
// topHomeAutoIntent = HomeAutomation_TurnOn
await dc.begin(topHomeAutoIntent, homeAutoResults);
break;
case 'l_weather':
const weatherResults = await weather.recognize(context);
const topWeatherIntent = LuisRecognizer.topIntent(weatherResults);
await dc.begin(topWeatherIntent, weatherResults);
break;
case 'q_FAQ':
await faq.answer(context);
break;
default:
await dc.begin('None');
}
}
if (!context.responded) {
await dc.continue();
if (!context.responded && isMessage) {
await dc.context.sendActivity(`Hi! I'm the LUIS dispatch bot. Say something and LUIS will decide how the message should be routed.`);
}
}
}
});
});
The above code looks very different from what's in the 14.nlp-with-dispatch sample when it comes to designing, defining and triggering dialogs.
Have new coding patterns been introduced. If so, which should be follow and which are still supported?

I would recommend using the example set out by the github repo 14. NLP-with-Dispatch. The code in the tutorial you linked is rather simplified, so it can be compressed to a single file. It is only meant to show the how the routing for multiple LUIS and/or QnA models can be used. The Github sample is more robust, showing further customization.

Related

BotFramework TypeError: Cannot perform 'get' on a proxy that has been revoked

I am trying to develop a MS Teams bot that sends content to students module(unit) wise. I have created 3 classes:
methods.js = Contains all the methods for sending texts, attachments etc.
teamBot.js = Captures a specific keyword from the users and based on that executes a function.
test.js = Connects the bot with Airtable and sends the content accordingly
I am facing Cannot perform 'get' on a proxy that has been revoked error. I figured it might be because of the context. I am passing context as a parameter, which I feel might not be the correct way, how can I achieve the result, and retain the context between files.
teamsBot.js
const test = require("./test");
class TeamsBot extends TeamsActivityHandler {
constructor() {
super();
// record the likeCount
this.likeCountObj = { likeCount: 0 };
this.onMessage(async (context, next) => {
console.log("Running with Message Activity.");
let txt = context.activity.text;
// const removedMentionText = TurnContext.removeRecipientMention(context.activity);
// if (removedMentionText) {
// // Remove the line break
// txt = removedMentionText.toLowerCase().replace(/\n|\r/g, "").trim();
// }
// Trigger command by IM text
switch (txt) {
case "Begin": {
await test.sendModuleContent(context)
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
// Listen to MembersAdded event, view https://learn.microsoft.com/en-us/microsoftteams/platform/resources/bot-v3/bots-notifications for more events
this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded;
for (let cnt = 0; cnt < membersAdded.length; cnt++) {
if (membersAdded[cnt].id) {
const card = cardTools.AdaptiveCards.declareWithoutData(rawWelcomeCard).render();
await context.sendActivity({ attachments: [CardFactory.adaptiveCard(card)] });
break;
}
}
await next();
});
}
test.js
const ms = require('./methods')
async function sendModuleContent(context) {
data = module_text //fetched from Airtable
await ms.sendText(context, data)
}
methods.js
const {TeamsActivityHandler, ActivityHandler, MessageFactory } = require('botbuilder');
async function sendText(context, text){
console.log("Sending text")
await context.sendActivity(text);
}
Refer this: TypeError: Cannot perform 'get' on a proxy that has been revoked
make the following changes to test.js
const {
TurnContext
} = require("botbuilder");
var conversationReferences = {};
var adapter;
async function sendModuleContent(context) {
data = module_text //fetched from Airtable
const currentUser = context.activity.from.id;
conversationReferences[currentUser] = TurnContext.getConversationReference(context.activity);
adapter = context.adapter;
await adapter.continueConversation(conversationReferences[currentUser], async turnContext => {
await turnContext.sendActivity(data);
});
}

KustoAuthenticationError when ingesting data into azure data explorer using azure-kusto-ingest nodejs client

when i run this code it says KustoAuthenticationError: Failed to get cloud
info for cluster https://clusterName.kusto.windows.net
The appId is the Application (client) ID,
appKey is the value of the client secret
and authorityId is the Directory (tenant) ID
I got this code from: https://github.com/Azure/azure-kusto-node/blob/master/packages/azure-kusto-ingest/example.js
code:
const IngestClient = require("azure-kusto-ingest").IngestClient;
const IngestStatusQueues = require("azure-kusto-ingest").IngestStatusQueues;
const IngestionProps = require("azure-kusto-ingest").IngestionProperties;
const KustoConnectionStringBuilder = require("azure-kusto-data").KustoConnectionStringBuilder;
const { DataFormat, JsonColumnMapping, IngestionMappingKind, CompressionType, ReportLevel, ReportMethod } = require("azure-kusto-ingest");
const { BlobDescriptor, StreamDescriptor } = require("azure-kusto-ingest").IngestionDescriptors;
const StreamingIngestClient = require("azure-kusto-ingest").StreamingIngestClient;
// const fs = require("fs");
const clusterName = "clusterName";
const authorityId = "authorityId";
const appId = "appId"
const appKey = "appKey"
var d = new Date();
var dateTimeNow = d.toJSON().slice(0,19).replace('T',':');
var data = {"Id" : "1314444525", "Temperature" : 32, "Battery" : 89, "Humidity" : 94, "Time" : dateTimeNow};
var s = require('stream');
var stream = new s.Readable();
stream.push(JSON.stringify(data));
stream.push(null);
// Streaming ingest client
const props2 = new IngestionProps({
database: "DBname",
table: "Tablename",
format: DataFormat.JSON,
ingestionMappingReference: "Pre-defined mapping name",
});
const streamingIngestClient = new StreamingIngestClient(
KustoConnectionStringBuilder.withAadApplicationKeyAuthentication(`https://${clusterName}.kusto.windows.net`, appId, appKey, authorityId),
props2
);
startStreamingIngestion();
async function startStreamingIngestion() {
try {
await streamingIngestClient.ingestFromStream(stream, props2);
console.log("Ingestion done");
await waitForStatus();
} catch (err) {
console.log(err);
}
}
async function waitForStatus(numberOFIngestions = 1) {
while ((await statusQueues.failure.isEmpty()) && (await statusQueues.success.isEmpty())) {
console.log("Waiting for status...");
await sleep(1000);
}
const failures = await statusQueues.failure.pop(numberOFIngestions);
for (let failure of failures) {
console.log('Failed: ${JSON.stringify(failure)}');
}
const successes = await statusQueues.success.pop(numberOFIngestions);
for (let success of successes) {
console.log('Succeeded: ${JSON.stringify(success)}');
}
}
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
This usually means you don’t have access to your ADX cluster’s endpoint. Verify whether your ADX cluster allows requests over the public network and that your Azure AD App doesn't have any outgoing network restrictions. In addition, make sure to grant 'ingestor' permission on your ADX database to your Azure AD App.

Google Cloud Function returns vague 'Connection error' when trying to connect to Google CloudSQL (mysql) instance

I was trying out this codelab code snippet by Google. I wanted to alter it in a way so that the Vision API metadata would be stored in a relational MySQL using the CloudSQL service - following their examples on how to connect Cloud Functions with CloudSQL.
The code I ended up deploys but upon triggering the function (by uploading a new image) I get a vague 'connection error' in the logs with no more information. This is my code at this moment:
const vision = require('#google-cloud/vision');
const Storage = require('#google-cloud/storage');
const client = new vision.ImageAnnotatorClient();
const winston = require('winston');
const {LoggingWinston} = require('#google-cloud/logging-winston');
const loggingWinston = new LoggingWinston();
const logger = winston.createLogger({
level: 'info',
transports: [new winston.transports.Console(), loggingWinston],
});
const createUnixSocketPool = async (config) => {
const dbSocketPath = "/cloudsql"
return await mysql.createPool({
user: 'root',
password: 'mypassword',
database: 'mydatabase',
socketPath: `${dbSocketPath}/cs-03-282615:europe-west1:mydatabase`,
...config
});
}
const createPool = async () => {
const config = {
connectionLimit: 5,
connectTimeout: 10000,
acquireTimeout: 10000,
waitForConnections: true,
queueLimit: 0,
}
return await createUnixSocketPool(config);
};
let pool;
const poolPromise = createPool()
.then(async (pool) => {
return pool;
})
.catch((err) => {
logger.error(err);
process.exit(1)
});
exports.vision_analysis = async (event, context, pool) => {
console.log(`Event: ${JSON.stringify(event)}`);
const filename = event.name;
const filebucket = event.bucket;
console.log(`New picture uploaded ${filename} in ${filebucket}`);
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
// invoking the Vision API
const [response] = await client.annotateImage(request);
console.log(`Raw vision output for: ${filename}: ${JSON.stringify(response)}`);
if (response.error === null) {
// listing the labels found in the picture
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
console.log(`Labels: ${labels.join(', ')}`);
// retrieving the dominant color of the picture
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
console.log(`Colors: ${colorHex}`);
// determining if the picture is safe to show
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"].every(k =>
!['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
console.log(`Safe? ${isSafe}`);
if (isSafe) {
const pool = await poolPromise();
const stmt = 'INSERT INTO sc_03_metadata_schema (labels, color, created) VALUES (?, ?, ?)';
await pool.query(stmt, [labels, colorHex, NOW()]);
console.log("Stored metadata in CloudSQL");
}
} else {
throw new Error(`Vision API error: code ${response.error.code}, message: "${response.error.message}"`);
}
};
function decColorToHex(r, g, b) {
return '#' + Number(r).toString(16).padStart(2, '0') +
Number(g).toString(16).padStart(2, '0') +
Number(b).toString(16).padStart(2, '0');
}
The full error is as such:
Error Logs
I understand you wish to connect to your Cloud SQL MySQL instance via a Cloud Function. From referencing the public documentation Connecting from Cloud Functions to Cloud SQL it seems that “process.env.DB_SOCKET_PATH || “ was not included when instantiating your dbSocketPath.
In regards to the vague error message, this is a known issue, one of which is currently being investigated by the Cloud Functions specialists. I would suggest that you take a look at the Public Tracker and click the star icon to subscribe to the issue for further updates and click the bell icon to be notified of updates via email. We do not have an ETA on the resolution of this issue at this time.

TEAMS bot. "TypeError: source.on is not a function" in 'createConversation' method

I have a TEAMS node.js bot running locally (with ngrok). I receive messages from TEAMS client and echo works
context.sendActivity(`You said '${context.activity.text}'`);
I need to send a proactive message, but i get an error creating conversation pbject.
My code:
...
await BotConnector.MicrosoftAppCredentials.trustServiceUrl(sServiceUrl);
var credentials = new BotConnector.MicrosoftAppCredentials({
appId: "XXXXXXXXXXXX",
appPassword: "YYYYYYYYYYYYY"
});
var connectorClient = new BotConnector.ConnectorClient(credentials, { baseUri: sServiceUrl });
const parameters = {
members: [{ id: sUserId }],
isGroup: false,
channelData:
{
tenant: {
id: sTenantId
}
}
};
// Here I get the error: "TypeError: source.on is not a function"
var conversationResource = await connectorClient.conversations.createConversation(parameters);
await connectorClient.conversations.sendToConversation(conversationResource.id, {
type: "message",
from: { id: credentials.appId },
recipient: { id: sUserId },
text: 'This a message from Bot Connector Client (NodeJS)'
});
String values are correct and I have a valid connectorClient.
Thanks in advance,
Diego
I think you are close but just need to make a couple modifications. It's hard to tell because I can't see all of your code.
Here, I add the Teams middleware to the adapter in index.js and am making the call that triggers the proactive message from within a waterfall step in mainDialog.js. The location of the code shouldn't matter as long as you can pass context in.
Also, with regards to your message construction, sending the text alone is sufficient. The recipient is already specified in the paremeters with the rest of the activity properties assigned via the connector. There is no need to reconstruct the activity (unless you really need to).
Lastly, be sure to trust the serviceUrl. I do this via the onTurn method in mainBot.js.
Hope of help!
index.js
const teams = require('botbuilder-teams');
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
})
.use(new teams.TeamsMiddleware())
mainDialog.js
const { ConnectorClient, MicrosoftAppCredentials } = require('botframework-connector');
async teamsProactiveMessage ( stepContext ) {
const credentials = new MicrosoftAppCredentials(process.env.MicrosoftAppId, process.env.MicrosoftAppPassword);
const connector = new ConnectorClient(credentials, { baseUri: stepContext.context.activity.serviceUrl });
const roster = await connector.conversations.getConversationMembers(stepContext.context.activity.conversation.id);
const parameters = {
members: [
{
id: roster[0].id
}
],
channelData: {
tenant: {
id: roster[0].tenantId
}
}
};
const conversationResource = await connector.conversations.createConversation(parameters);
const message = MessageFactory.text('This is a proactive message');
await connector.conversations.sendToConversation(conversationResource.id, message);
return stepContext.next();
}
mainBot.js
this.onTurn(async (context, next) => {
if (context.activity.channelId === 'msteams') {
MicrosoftAppCredentials.trustServiceUrl(context.activity.serviceUrl);
}
await next();
});
I created my bot with Yeoman, so I had a running example which can respond to an incoming message.
https://learn.microsoft.com/es-es/azure/bot-service/javascript/bot-builder-javascript-quickstart?view=azure-bot-service-4.0
With that, I can respond to an incoming message and it works
My complete code:
index.js: // Created automatically, not modified
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const dotenv = require('dotenv');
const path = require('path');
const restify = require('restify');
// Import required bot services.
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
const { BotFrameworkAdapter } = require('botbuilder');
// This bot's main dialog.
const { MyBot } = require('./bot');
// Import required bot configuration.
const ENV_FILE = path.join(__dirname, '.env');
dotenv.config({ path: ENV_FILE });
// Create HTTP server
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, () => {
console.log(`\n${ server.name } listening to ${ server.url }`);
console.log(`\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator`);
console.log(`\nTo test your bot, see: https://aka.ms/debug-with-emulator`);
});
// Create adapter.
// See https://aka.ms/about-bot-adapter to learn more about how bots work.
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
// Catch-all for errors.
adapter.onTurnError = async (context, error) => {
// This check writes out errors to console log .vs. app insights.
console.error(`\n [onTurnError]: ${ error }`);
// Send a message to the user
await context.sendActivity(`Oops. Something went wrong!`);
};
// Create the main dialog.
const myBot = new MyBot();
// Listen for incoming requests.
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (context) => {
// Route to main dialog.
await myBot.run(context);
});
});
My bot file. bot.js // created automatically, modified to add proactive messages
const { ConnectorClient, MicrosoftAppCredentials, BotConnector } = require('botframework-connector');
const { ActivityHandler } = require('botbuilder');
class MyBot extends ActivityHandler {
constructor() {
super();
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
this.onMessage(async (context, next) => {
//await context.sendActivity(`You said '${context.activity.text}'`); // It works
var activity = context.activity;
await MicrosoftAppCredentials.trustServiceUrl(activity.serviceUrl);
var connectorClient = new ConnectorClient(credentials, { baseUri: activity.serviceUrl });
const parameters = {
members: [{ id: activity.from.id }],
isGroup: false,
channelData:
{
tenant: {
id: activity.conversation.tenantId
}
}
};
var conversationResource = await connectorClient.conversations.createConversation(parameters);
});
}
module.exports.MyBot = MyBot;
In 'createConversation' is where I get the error:
[onTurnError]: TypeError: source.on is not a function
This code will be in a method, to call it when required, I put it in 'onMessage' just to simplify
I think my code is like yours... But I'm afraid I am missing something or doing sth wrong...
Thanks for your help,
Diego
I have a stack trace of the error, and it seems to be related to 'delayed stream' node module. I add as a response since it is too long for a comment:
(node:28248) UnhandledPromiseRejectionWarning: TypeError: source.on is not a function
at Function.DelayedStream.create ([Bot Project Path]\node_modules\delayed-stream\lib\delayed_stream.js:33:10)
at FormData.CombinedStream.append ([Bot Project Path]\node_modules\combined-stream\lib\combined_stream.js:45:37)
at FormData.append ([Bot Project Path]\node_modules\form-data\lib\form_data.js:74:3)
at MicrosoftAppCredentials.refreshToken ([Bot Project Path]\node_modules\botframework-connector\lib\auth\microsoftAppCredentials.js:127:20)
at MicrosoftAppCredentials.getToken ([Bot Project Path]\node_modules\botframework-connector\lib\auth\microsoftAppCredentials.js:91:32)
at MicrosoftAppCredentials.signRequest ([Bot Project Path]\node_modules\botframework-connector\lib\auth\microsoftAppCredentials.js:71:38)
at SigningPolicy.signRequest ([Bot Project Path]\node_modules\#azure\ms-rest-js\dist\msRest.node.js:2980:44)
at SigningPolicy.sendRequest ([Bot Project Path]\node_modules\#azure\ms-rest-js\dist\msRest.node.js:2984:21)
at ConnectorClient.ServiceClient.sendRequest ([Bot Project Path]\node_modules\#azure\ms-rest-js\dist\msRest.node.js:3230:29)
at ConnectorClient.ServiceClient.sendOperationRequest ([Bot Project Path]\node_modules\#azure\ms-rest-js\dist\msRest.node.js:3352:27)
I found my problem. I must set credentials/conector/trust in the correct order:
credentials = new MicrosoftAppCredentials(process.env.MicrosoftAppId, process.env.MicrosoftAppPassword);
connectorClient = new ConnectorClient(credentials, { baseUri: activity.serviceUrl });
MicrosoftAppCredentials.trustServiceUrl(activity.serviceUrl);

Node.js TypeError: Wit is not a constructor

How to solve "Wit is not a constructor" error coming from Node.js while executing code given by node-wit and wit.ai documentation.
// Setting up our bot
const wit = new Wit(WIT_TOKEN, actions);
I tried all the ways by upgrading and downgrading npm/node versions, but no luck.
Update: Please find the index.js source I used,
Do I need to change anything in this?
module.exports = {
Logger: require('./lib/logger.js').Logger,
logLevels: require('./lib/logger.js').logLevels,
Wit: require('./lib/wit.js').Wit,
}
'use strict';
var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
const Logger = require('node-wit').Logger;
const levels = require('node-wit').logLevels;
var app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.listen((process.env.PORT || 3000));
//const Wit = require('node-wit').Wit;
const WIT_TOKEN = process.env.WIT_TOKEN;
const FB_PAGE_TOKEN = process.env.FB_PAGE_TOKEN;
const Wit = require('node-wit').Wit;
// Server frontpage
app.get('/', function (req, res) {
debugger;
res.send('This is TestBot Server');
});
// Messenger API specific code
// See the Send API reference
// https://developers.facebook.com/docs/messenger-platform/send-api-reference
const fbReq = request.defaults({
uri: 'https://graph.facebook.com/me/messages',
method: 'POST',
json: true,
qs: { access_token: FB_PAGE_TOKEN },
headers: {'Content-Type': 'application/json'},
});
const fbMessage = (recipientId, msg, cb) => {
const opts = {
form: {
recipient: {
id: recipientId,
},
message: {
text: msg,
},
},
};
fbReq(opts, (err, resp, data) => {
if (cb) {
cb(err || data.error && data.error.message, data);
}
});
};
// See the Webhook reference
// https://developers.facebook.com/docs/messenger-platform/webhook-reference
const getFirstMessagingEntry = (body) => {
const val = body.object == 'page' &&
body.entry &&
Array.isArray(body.entry) &&
body.entry.length > 0 &&
body.entry[0] &&
body.entry[0].id === FB_PAGE_ID &&
body.entry[0].messaging &&
Array.isArray(body.entry[0].messaging) &&
body.entry[0].messaging.length > 0 &&
body.entry[0].messaging[0]
;
return val || null;
};
// Wit.ai bot specific code
// This will contain all user sessions.
// Each session has an entry:
// sessionId -> {fbid: facebookUserId, context: sessionState}
const sessions = {};
const findOrCreateSession = (fbid) => {
var sessionId;
// Let's see if we already have a session for the user fbid
Object.keys(sessions).forEach(k => {
if (sessions[k].fbid === fbid) {
// Yep, got it!
sessionId = k;
}
});
if (!sessionId) {
// No session found for user fbid, let's create a new one
sessionId = new Date().toISOString();
sessions[sessionId] = {fbid: fbid, context: {}};
}
return sessionId;
};
// Our bot actions
const actions = {
say(sessionId, context, message, cb) {
// Our bot has something to say!
// Let's retrieve the Facebook user whose session belongs to
const recipientId = sessions[sessionId].fbid;
if (recipientId) {
// Yay, we found our recipient!
// Let's forward our bot response to her.
fbMessage(recipientId, message, (err, data) => {
if (err) {
console.log(
'Oops! An error occurred while forwarding the response to',
recipientId,
':',
err
);
}
// Let's give the wheel back to our bot
cb();
});
} else {
console.log('Oops! Couldn\'t find user for session:', sessionId);
// Giving the wheel back to our bot
cb();
}
},
merge(sessionId, context, entities, message, cb) {
cb(context);
},
error(sessionId, context, error) {
console.log(error.message);
},
// You should implement your custom actions here
// See https://wit.ai/docs/quickstart
};
const wit = new Wit(WIT_TOKEN, actions);
// Message handler
app.post('/webhook', (req, res) => {
// Parsing the Messenger API response
// Setting up our bot
//const wit = new Wit(WIT_TOKEN, actions);
const messaging = getFirstMessagingEntry(req.body);
if (messaging && messaging.message && messaging.message.text) {
// Yay! We got a new message!
// We retrieve the Facebook user ID of the sender
const sender = messaging.sender.id;
// We retrieve the user's current session, or create one if it doesn't exist
// This is needed for our bot to figure out the conversation history
const sessionId = findOrCreateSession(sender);
// We retrieve the message content
const msg = messaging.message.text;
const atts = messaging.message.attachments;
if (atts) {
// We received an attachment
// Let's reply with an automatic message
fbMessage(
sender,
'Sorry I can only process text messages for now.'
);
} else if (msg) {
// We received a text message
// Let's forward the message to the Wit.ai Bot Engine
// This will run all actions until our bot has nothing left to do
wit.runActions(
sessionId, // the user's current session
msg, // the user's message
sessions[sessionId].context, // the user's current session state
(error, context) => {
if (error) {
console.log('Oops! Got an error from Wit:', error);
} else {
// Our bot did everything it has to do.
// Now it's waiting for further messages to proceed.
console.log('Waiting for futher messages.');
// Based on the session state, you might want to reset the session.
// This depends heavily on the business logic of your bot.
// Example:
// if (context['done']) {
// delete sessions[sessionId];
// }
// Updating the user's current session state
sessions[sessionId].context = context;
}
}
);
}
}
res.sendStatus(200);
});
There are two typical causes of your issue, either forgetting to require your module or forgetting to npm install it. Check if you:
Forgot to require('node-wit') and obtain the constructor from the returned object:
const Wit = require('node-wit').Wit
Properly required Wit but forgot to npm install node-wit
For everyone who are using messenger.js as your index.js use this:
const Wit = require('./lib/wit');
const log = require('./lib/log');
Please check your node_modules directory for node-wit package.
If node-wit is present then please require it before trying to create its instance.
const {Wit} = require('node-wit');
witHandler = new Wit({
accessToken: accessToken
});

Resources