Socket
Socket
Sign inDemoInstall

node-wit

Package Overview
Dependencies
6
Maintainers
5
Versions
30
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.3.2 to 4.0.0

examples/basic.js

20

CHANGES.md

@@ -0,4 +1,22 @@

## v4.0.0
After a lot of internal dogfooding and bot building, we decided to change the API in a backwards-incompatible way. The changes are described below and aim to simplify user code and accommodate upcoming features.
We moved to a Promise-based API, instead of callbacks. This makes the code simpler and the error-handling more straight-forward. It's also inline with where JS is going with standards like `fetch()` and `async/await` that are based on Promises.
See `./examples` to see how to use the new API.
### Breaking changes
- `say` renamed to `send` to reflect that it deals with more than just text
- Removed built-in actions `merge` and `error`
- Actions signature simplified with `request` and `response` arguments
- Actions need to return promises and do not receive the `cb` parameter anymore
- INFO level replaces LOG level
- configuration is now done when instantiating the `Wit` object, instead of using env vars
## v3.3.2
- allows for targetting a specific API version, by setting `WIT_API_VERSION`
- allows for overriding API version, by setting `WIT_API_VERSION`

@@ -5,0 +23,0 @@ ## v3.3.1

77

examples/joke.js
'use strict';
// Joke example
// See https://wit.ai/patapizza/example-joke
let Wit = null;
try {
// if running from repo
Wit = require('../').Wit;
} catch (e) {
Wit = require('node-wit').Wit;
}
// When not cloning the `node-wit` repo, replace the `require` like so:
// const Wit = require('node-wit').Wit;
const Wit = require('../').Wit;
const token = (() => {
const accessToken = (() => {
if (process.argv.length !== 3) {
console.log('usage: node examples/joke.js <wit-token>');
console.log('usage: node examples/joke.js <wit-access-token>');
process.exit(1);

@@ -18,2 +19,5 @@ }

// Joke example
// See https://wit.ai/patapizza/example-joke
const allJokes = {

@@ -46,31 +50,40 @@ chuck: [

const actions = {
say(sessionId, context, message, cb) {
console.log(message);
cb();
send(request, response) {
console.log('sending...', JSON.stringify(response));
return Promise.resolve();
},
merge(sessionId, context, entities, message, cb) {
delete context.joke;
const category = firstEntityValue(entities, 'category');
if (category) {
context.cat = category;
}
const sentiment = firstEntityValue(entities, 'sentiment');
if (sentiment) {
context.ack = sentiment === 'positive' ? 'Glad you liked it.' : 'Hmm.';
} else {
delete context.ack;
}
cb(context);
merge({entities, context, message, sessionId}) {
return new Promise(function(resolve, reject) {
delete context.joke;
const category = firstEntityValue(entities, 'category');
if (category) {
context.cat = category;
}
const sentiment = firstEntityValue(entities, 'sentiment');
if (sentiment) {
context.ack = sentiment === 'positive' ? 'Glad you liked it.' : 'Hmm.';
} else {
delete context.ack;
}
return resolve(context);
});
},
error(sessionId, context, error) {
console.log(error.message);
['select-joke']({entities, context}) {
return new Promise(function(resolve, reject) {
// const category = firstEntityValue(entities, 'category') || 'default';
// const sentiment = firstEntityValue(entities, 'sentiment');
// if (sentiment) {
// context.ack = sentiment === 'positive' ? 'Glad you liked it.' : 'Hmm.';
// } else {
// delete context.ack;
// }
const jokes = allJokes[context.cat || 'default'];
context.joke = jokes[Math.floor(Math.random() * jokes.length)];
return resolve(context);
});
},
['select-joke'](sessionId, context, cb) {
const jokes = allJokes[context.cat || 'default'];
context.joke = jokes[Math.floor(Math.random() * jokes.length)];
cb(context);
},
};
const client = new Wit(token, actions);
const client = new Wit({accessToken, actions});
client.interactive();

@@ -9,16 +9,25 @@ 'use strict';

//
// 1. npm install body-parser express request
// 1. npm install body-parser express request
// 2. Download and install ngrok from https://ngrok.com/download
// 3. ./ngrok http 8445
// 4. WIT_TOKEN=your_access_token FB_PAGE_ID=your_page_id FB_PAGE_TOKEN=your_page_token FB_VERIFY_TOKEN=verify_token node examples/messenger.js
// 5. Subscribe your page to the Webhooks using verify_token and `https://<your_ngrok_io>/fb` as callback URL.
// 4. WIT_TOKEN=your_access_token FB_APP_SECRET=your_app_secret FB_PAGE_TOKEN=your_page_token node examples/messenger.js
// 5. Subscribe your page to the Webhooks using verify_token and `https://<your_ngrok_io>/webhook` as callback URL.
// 6. Talk to your bot on Messenger!
const bodyParser = require('body-parser');
const crypto = require('crypto');
const express = require('express');
const fetch = require('node-fetch');
const request = require('request');
// When not cloning the `node-wit` repo, replace the `require` like so:
// const Wit = require('node-wit').Wit;
const Wit = require('../').Wit;
let Wit = null;
let log = null;
try {
// if running from repo
Wit = require('../').Wit;
log = require('../').log;
} catch (e) {
Wit = require('node-wit').Wit;
log = require('node-wit').log;
}

@@ -32,12 +41,17 @@ // Webserver parameter

// Messenger API parameters
const FB_PAGE_ID = process.env.FB_PAGE_ID && Number(process.env.FB_PAGE_ID);
if (!FB_PAGE_ID) {
throw new Error('missing FB_PAGE_ID');
}
const FB_PAGE_ID = process.env.FB_PAGE_ID;
if (!FB_PAGE_ID) { throw new Error('missing FB_PAGE_ID') }
const FB_PAGE_TOKEN = process.env.FB_PAGE_TOKEN;
if (!FB_PAGE_TOKEN) {
throw new Error('missing FB_PAGE_TOKEN');
}
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
if (!FB_PAGE_TOKEN) { throw new Error('missing FB_PAGE_TOKEN') }
const FB_APP_SECRET = process.env.FB_APP_SECRET;
if (!FB_APP_SECRET) { throw new Error('missing FB_APP_SECRET') }
let FB_VERIFY_TOKEN = null;
crypto.randomBytes(8, (err, buff) => {
if (err) throw err;
FB_VERIFY_TOKEN = buff.toString('hex');
console.log(`/webhook will accept the Verify Token "${FB_VERIFY_TOKEN}"`);
});
// ----------------------------------------------------------------------------
// Messenger API specific code

@@ -47,45 +61,24 @@

// 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);
const fbMessage = (id, text) => {
const body = JSON.stringify({
recipient: { id },
message: { text },
});
const qs = 'access_token=' + encodeURIComponent(FB_PAGE_TOKEN);
return fetch('https://graph.facebook.com/me/messages?' + qs, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body,
})
.then(rsp => rsp.json())
.then(json => {
if (json.error && json.error.message) {
throw new Error(json.error.message);
}
return json;
});
};
// 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

@@ -117,3 +110,3 @@

const actions = {
say(sessionId, context, message, cb) {
send({sessionId}, {text}) {
// Our bot has something to say!

@@ -125,27 +118,19 @@ // Let's retrieve the Facebook user whose session belongs to

// 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();
// We return a promise to let our bot know when we're done sending
return fbMessage(recipientId, text)
.then(() => null)
.catch((err) => {
console.error(
'Oops! An error occurred while forwarding the response to',
recipientId,
':',
err.stack || err
);
});
} else {
console.log('Oops! Couldn\'t find user for session:', sessionId);
console.error('Oops! Couldn\'t find user for session:', sessionId);
// Giving the wheel back to our bot
cb();
return Promise.resolve()
}
},
merge(sessionId, context, entities, message, cb) {
cb(context);
},
error(sessionId, context, error) {
console.log(error.message);
},
// You should implement your custom actions here

@@ -156,15 +141,20 @@ // See https://wit.ai/docs/quickstart

// Setting up our bot
const wit = new Wit(WIT_TOKEN, actions);
const wit = new Wit({
accessToken: WIT_TOKEN,
actions,
logger: new log.Logger(log.INFO)
});
// Starting our webserver and putting it all together
const app = express();
app.set('port', PORT);
app.listen(app.get('port'));
app.use(bodyParser.json());
app.use(({method, url}, rsp, next) => {
rsp.on('finish', () => {
console.log(`${rsp.statusCode} ${method} ${url}`);
});
next();
});
app.use(bodyParser.json({ verify: verifyRequestSignature }));
// Webhook setup
app.get('/fb', (req, res) => {
if (!FB_VERIFY_TOKEN) {
throw new Error('missing FB_VERIFY_TOKEN');
}
app.get('/webhook', (req, res) => {
if (req.query['hub.mode'] === 'subscribe' &&

@@ -179,57 +169,61 @@ req.query['hub.verify_token'] === FB_VERIFY_TOKEN) {

// Message handler
app.post('/fb', (req, res) => {
// Parsing the Messenger API response
const messaging = getFirstMessagingEntry(req.body);
if (messaging && messaging.message && messaging.recipient.id === FB_PAGE_ID) {
// Yay! We got a new message!
app.post('/webhook', (req, res) => {
// Parse the Messenger payload
// See the Webhook reference
// https://developers.facebook.com/docs/messenger-platform/webhook-reference
const data = req.body;
// We retrieve the Facebook user ID of the sender
const sender = messaging.sender.id;
if (data.object === 'page') {
data.entry.forEach(entry => {
entry.messaging.forEach(event => {
if (event.message) {
// Yay! We got a new message!
// We retrieve the Facebook user ID of the sender
const sender = event.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 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;
// We retrieve the message content
const {text, attachments} = event.message;
if (atts) {
// We received an attachment
if (attachments) {
// We received an attachment
// Let's reply with an automatic message
fbMessage(sender, 'Sorry I can only process text messages for now.')
.catch(console.error);
} else if (text) {
// We received a text message
// 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
text, // the user's message
sessions[sessionId].context // the user's current session state
).then((context) => {
// Our bot did everything it has to do.
// Now it's waiting for further messages to proceed.
console.log('Waiting for next user messages');
// 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];
// }
// 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;
// Updating the user's current session state
sessions[sessionId].context = context;
})
.catch((err) => {
console.error('Oops! Got an error from Wit: ', err.stack || err);
})
}
} else {
console.log('received event', JSON.stringify(event));
}
);
}
});
});
}

@@ -239,1 +233,33 @@ res.sendStatus(200);

/*
* Verify that the callback came from Facebook. Using the App Secret from
* the App Dashboard, we can verify the signature that is sent with each
* callback in the x-hub-signature field, located in the header.
*
* https://developers.facebook.com/docs/graph-api/webhooks#setup
*
*/
function verifyRequestSignature(req, res, buf) {
var signature = req.headers["x-hub-signature"];
if (!signature) {
// For testing, let's log an error. In production, you should throw an
// error.
console.error("Couldn't validate the signature.");
} else {
var elements = signature.split('=');
var method = elements[0];
var signatureHash = elements[1];
var expectedHash = crypto.createHmac('sha1', FB_APP_SECRET)
.update(buf)
.digest('hex');
if (signatureHash != expectedHash) {
throw new Error("Couldn't validate the request signature.");
}
}
}
app.listen(PORT);
console.log('Listening on :' + PORT + '...');
'use strict';
// Quickstart example
// See https://wit.ai/l5t/Quickstart
let Wit = null;
try {
// if running from repo
Wit = require('../').Wit;
} catch (e) {
Wit = require('node-wit').Wit;
}
// When not cloning the `node-wit` repo, replace the `require` like so:
// const Wit = require('node-wit').Wit;
const Wit = require('../').Wit;
const token = (() => {
const accessToken = (() => {
if (process.argv.length !== 3) {
console.log('usage: node examples/quickstart.js <wit-token>');
console.log('usage: node examples/quickstart.js <wit-access-token>');
process.exit(1);

@@ -18,2 +19,5 @@ }

// Quickstart example
// See https://wit.ai/ar7hur/quickstart
const firstEntityValue = (entities, entity) => {

@@ -32,26 +36,25 @@ const val = entities && entities[entity] &&

const actions = {
say(sessionId, context, message, cb) {
console.log(message);
cb();
send(request, response) {
const {sessionId, context, entities} = request;
const {text, quickreplies} = response;
return new Promise(function(resolve, reject) {
console.log('sending...', JSON.stringify(response));
return resolve();
});
},
merge(sessionId, context, entities, message, cb) {
// Retrieve the location entity and store it into a context field
const loc = firstEntityValue(entities, 'location');
if (loc) {
context.loc = loc;
}
cb(context);
getForecast({context, entities}) {
return new Promise(function(resolve, reject) {
var location = firstEntityValue(entities, 'location')
if (location) {
context.forecast = 'sunny in ' + location; // we should call a weather API here
} else {
context.missingLocation = true;
delete context.forecast;
}
return resolve(context);
});
},
error(sessionId, context, error) {
console.log(error.message);
},
['fetch-weather'](sessionId, context, cb) {
// Here should go the api call, e.g.:
// context.forecast = apiCall(context.loc)
context.forecast = 'sunny';
cb(context);
},
};
const client = new Wit(token, actions);
const client = new Wit({accessToken, actions});
client.interactive();
module.exports = {
Logger: require('./lib/logger.js').Logger,
logLevels: require('./lib/logger.js').logLevels,
log: require('./lib/log.js'),
Wit: require('./lib/wit.js').Wit,
}

@@ -6,20 +6,164 @@ 'use strict';

const uuid = require('node-uuid');
const Logger = require('./logger').Logger;
const logLevels = require('./logger').logLevels;
const log = require('./log');
const DEFAULT_API_VERSION = '20160516';
const DEFAULT_MAX_STEPS = 5;
const CALLBACK_TIMEOUT_MS = 10000;
const DEFAULT_WIT_URL = 'https://api.wit.ai';
const learnMore = 'Learn more at https://wit.ai/docs/quickstart';
let l = new Logger(logLevels.LOG);
function Wit(opts) {
if (!(this instanceof Wit)) {
return new Wit(opts);
}
const makeWitResponseHandler = (endpoint, l, cb) => {
const error = err => {
l.error('[' + endpoint + '] Error: ' + err);
if (cb) {
cb(err);
const {
accessToken, apiVersion, actions, headers, logger, witURL
} = this.config = Object.freeze(validate(opts));
this.message = (message, context) => {
let qs = 'q=' + encodeURIComponent(message);
if (context) {
qs += '&context=' + encodeURIComponent(JSON.stringify(context));
}
const method = 'GET';
const fullURL = witURL + '/message?' + qs
const handler = makeWitResponseHandler(logger, 'message');
logger.debug(method, fullURL);
return fetch(fullURL, {
method,
headers,
})
.then(response => Promise.all([response.json(), response.status]))
.then(handler)
;
};
this.converse = (sessionId, message, context) => {
let qs = 'session_id=' + encodeURIComponent(sessionId);
if (message) {
qs += '&q=' + encodeURIComponent(message);
}
const method = 'POST';
const fullURL = witURL + '/converse?' + qs;
const handler = makeWitResponseHandler(logger, 'converse');
logger.debug(method, fullURL);
return fetch(fullURL, {
method,
headers,
body: JSON.stringify(context),
})
.then(response => Promise.all([response.json(), response.status]))
.then(handler)
;
};
const continueRunActions = (sessionId, message, prevContext, i) => {
return (json) => {
if (i < 0) {
logger.warn('Max steps reached, stopping.');
return prevContext;
}
if (!json.type) {
throw new Error('Couldn\'t find type in Wit response');
}
logger.debug('Context: ' + JSON.stringify(prevContext));
logger.debug('Response type: ' + json.type);
// backwards-cpmpatibility with API version 20160516
if (json.type === 'merge') {
json.type = 'action';
json.action = 'merge';
}
if (json.type === 'error') {
throw new Error('Oops, I don\'t know what to do.');
}
if (json.type === 'stop') {
return prevContext;
}
const request = {
sessionId,
context: clone(prevContext),
text: message,
entities: json.entities,
};
if (json.type === 'msg') {
throwIfActionMissing(actions, 'send');
const response = {
text: json.msg,
quickreplies: json.quickreplies,
};
return actions.send(request, response).then((ctx) => {
if (ctx) {
throw new Error('Cannot update context after \'send\' action');
}
return this.converse(sessionId, null, prevContext).then(
continueRunActions(sessionId, message, prevContext, i - 1)
);
});
} else if (json.type === 'action') {
const action = json.action;
throwIfActionMissing(actions, action);
return actions[action](request).then((ctx) => {
const nextContext = ctx || {};
return this.converse(sessionId, null, nextContext).then(
continueRunActions(sessionId, message, nextContext, i - 1)
);
});
} else {
logger.debug('unknown response type', json);
throw new Error('unknown response type ' + json.type);
}
}
};
this.runActions = (sessionId, message, context, maxSteps) => {
if (!actions) throwMustHaveActions();
const steps = maxSteps ? maxSteps : DEFAULT_MAX_STEPS;
return this.converse(sessionId, message, context).then(
continueRunActions(sessionId, message, context, steps)
);
};
this.interactive = (initContext, maxSteps) => {
if (!actions) throwMustHaveActions();
let context = typeof initContext === 'object' ? initContext : {};
const sessionId = uuid.v1();
const steps = maxSteps ? maxSteps : DEFAULT_MAX_STEPS;
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.setPrompt('> ');
const prompt = () => {
rl.prompt();
rl.write(null, {ctrl: true, name: 'e'});
};
prompt();
rl.on('line', (line) => {
line = line.trim();
if (!line) {
return prompt();
}
this.runActions(sessionId, line, context, steps)
.then((ctx) => {
context = ctx;
prompt();
})
.catch(logger.error)
});
};
};
const makeWitResponseHandler = (logger, endpoint) => {
return rsp => {
const error = err => {
logger.error('[' + endpoint + '] Error: ' + err);
throw err;
};
if (rsp instanceof Error) {

@@ -29,4 +173,3 @@ return error(rsp);

const json = rsp[0];
const status = rsp[1];
const [json, status] = rsp;

@@ -40,60 +183,71 @@ if (json instanceof Error) {

if (err) {
return error(err)
return error(err);
}
l.debug('[' + endpoint + '] Response: ' + JSON.stringify(json));
if (cb) {
cb(null, json);
}
logger.debug('[' + endpoint + '] Response: ' + JSON.stringify(json));
return json;
}
};
const validateActions = (actions) => {
const learnMore = 'Learn more at https://wit.ai/docs/quickstart';
if (typeof actions !== 'object') {
throw new Error('The second parameter should be an Object.');
const throwMustHaveActions = () => {
throw new Error('You must provide the `actions` parameter to be able to use runActions. ' + learnMore)
};
const throwIfActionMissing = (actions, action) => {
if (!actions[action]) {
throw new Error('No \'' + action + '\' action found.');
}
if (!actions.say) {
throw new Error('The \'say\' action is missing. ' + learnMore);
};
const validate = (opts) => {
if (!opts.accessToken) {
throw new Error('Could not find access token, learn more at https://wit.ai/docs');
}
if (!actions.merge) {
throw new Error('The \'merge\' action is missing. ' + learnMore);
opts.witURL = opts.witURL || DEFAULT_WIT_URL;
opts.apiVersion = opts.apiVersion || DEFAULT_API_VERSION;
opts.headers = opts.headers || {
'Authorization': 'Bearer ' + opts.accessToken,
'Accept': 'application/vnd.wit.' + opts.apiVersion + '+json',
'Content-Type': 'application/json',
};
opts.logger = opts.logger || new log.Logger(log.INFO);
if (opts.actions) {
opts.actions = validateActions(opts.logger, opts.actions);
}
if (!actions.error) {
throw new Error('The \'error\' action is missing. ' + learnMore);
return opts;
};
const validateActions = (logger, actions) => {
if (typeof actions !== 'object') {
throw new Error('Actions should be an object. ' + learnMore);
}
if (!actions.send) {
throw new Error('The \'send\' action is missing. ' + learnMore);
}
Object.keys(actions).forEach(key => {
if (typeof actions[key] !== 'function') {
l.warn('The \'' + key + '\' action should be a function.');
logger.warn('The \'' + key + '\' action should be a function.');
}
if (key === 'say' && actions.say.length !== 4) {
l.warn('The \'say\' action should accept 4 arguments: sessionId, context, message, callback. ' + learnMore);
} else if (key === 'merge' && actions.merge.length !== 5) {
l.warn('The \'merge\' action should accept 5 arguments: sessionId, context, entities, message, callback. ' + learnMore);
} else if (key === 'error' && actions.error.length !== 3) {
l.warn('The \'error\' action should accept 3 arguments: sessionId, context, error. ' + learnMore);
} else if (key !== 'say' && key !== 'merge' && key !== 'error' && actions[key].length !== 3) {
l.warn('The \'' + key + '\' action should accept 3 arguments: sessionId, context, callback. ' + learnMore);
if (key === 'say' && actions[key].length > 2 ||
key === 'merge' && actions[key].length > 2 ||
key === 'error' && actions[key].length > 2
) {
logger.warn('The \'' + key + '\' action has been deprecated. ' + learnMore);
}
if (key === 'send') {
if (actions[key].length !== 2) {
logger.warn('The \'send\' action should accept 2 arguments: request and response. ' + learnMore);
}
} else if (actions[key].length !== 1) {
logger.warn('The \'' + key + '\' action should accept 1 argument: request. ' + learnMore);
}
});
return actions;
};
const makeCallbackTimeout = (ms) => {
return setTimeout(() => {
l.warn('I didn\'t get the callback after ' + (ms / 1000) + ' seconds. Did you forget to call me back?');
}, ms);
};
const cbIfActionMissing = (actions, action, cb) => {
if (!actions.hasOwnProperty(action)) {
if (cb) {
cb('No \'' + action + '\' action found.');
}
return true;
}
return false;
};
const clone = (obj) => {

@@ -115,203 +269,4 @@ if (obj !== null && typeof obj === 'object') {

const Wit = function(token, actions, logger) {
const baseURL = process.env.WIT_URL || 'https://api.wit.ai';
const version = process.env.WIT_API_VERSION || DEFAULT_API_VERSION;
const headers = {
'Authorization': 'Bearer ' + token,
'Accept': 'application/vnd.wit.' + version + '+json',
'Content-Type': 'application/json',
};
if (logger) {
l = logger;
}
this.actions = validateActions(actions);
this.message = (message, context, cb) => {
if (typeof context === 'function') {
cb = context;
context = undefined;
}
let qs = 'q=' + encodeURIComponent(message);
if (context) {
qs += '&context=' + encodeURIComponent(JSON.stringify(context));
}
const handler = makeWitResponseHandler('message', l, cb);
fetch(baseURL + '/message?' + qs, {
method: 'GET',
headers: headers,
})
.then(response => Promise.all([response.json(), response.status]))
.then(handler)
.catch(handler)
;
};
this.converse = (sessionId, message, context, cb) => {
const handler = makeWitResponseHandler('converse', l, cb);
let qs = 'session_id=' + sessionId;
if (message) {
qs += '&q=' + encodeURIComponent(message);
}
fetch(baseURL + '/converse?' + qs, {
method: 'POST',
headers: headers,
body: JSON.stringify(context),
})
.then(response => Promise.all([response.json(), response.status]))
.then(handler)
.catch(handler)
;
};
const makeCallback = (i, sessionId, message, context, cb) => {
let timeoutID;
const makeActionCallback = () => {
timeoutID = makeCallbackTimeout(CALLBACK_TIMEOUT_MS);
return (newContext) => {
if (timeoutID) {
clearTimeout(timeoutID);
timeoutID = null;
}
const context = newContext || {};
l.debug('Context\': ' + JSON.stringify(context));
if (i <= 0) {
l.warn('Max steps reached, halting.');
if (cb) {
cb(null, context);
}
return;
}
// Retrieving action sequence
this.converse(
sessionId,
null,
context,
makeCallback(--i, sessionId, message, context, cb).bind(this)
);
};
};
const makeSayCallback = () => {
timeoutID = makeCallbackTimeout(CALLBACK_TIMEOUT_MS);
return function() {
if (arguments.length > 0) {
throw new Error('The \'say\' callback should not have any arguments!');
}
if (timeoutID) {
clearTimeout(timeoutID);
timeoutID = null;
}
if (i <= 0) {
l.warn('Max steps reached, halting.');
if (cb) {
cb(null, context);
}
return;
}
// Retrieving action sequence
this.converse(
sessionId,
null,
context,
makeCallback(--i, sessionId, message, context, cb).bind(this)
);
};
};
return (error, json) => {
l.debug('Context: ' + JSON.stringify(context));
error = error || !json.type && 'Couldn\'t find type in Wit response';
if (error) {
if (cb) {
cb(error);
}
return;
}
var clonedContext = clone(context);
if (json.type === 'stop') {
// End of turn
if (cb) {
cb(null, context);
}
return;
} else if (json.type === 'msg') {
if (cbIfActionMissing(this.actions, 'say', cb)) {
return;
}
l.log('Executing say with message: ' + json.msg);
this.actions.say(sessionId, clonedContext, json.msg, makeSayCallback().bind(this));
} else if (json.type === 'merge') {
if (cbIfActionMissing(this.actions, 'merge', cb)) {
return;
}
l.log('Executing merge action');
this.actions.merge(sessionId, clonedContext, json.entities, message, makeActionCallback());
} else if (json.type === 'action') {
const action = json.action;
if (cbIfActionMissing(this.actions, action, cb)) {
return;
}
l.log('Executing action: ' + action);
this.actions[action](sessionId, clonedContext, makeActionCallback());
} else { // error
if (cbIfActionMissing(this.actions, 'error', cb)) {
return;
}
l.log('Executing error action');
this.actions.error(sessionId, clonedContext, new Error('Oops, I don\'t know what to do.'));
return;
}
};
};
this.runActions = (sessionId, message, context, cb, maxSteps) => {
const steps = maxSteps ? maxSteps : DEFAULT_MAX_STEPS;
this.converse(
sessionId,
message,
context,
makeCallback(steps, sessionId, message, context, cb).bind(this)
);
};
this.interactive = (initContext, maxSteps) => {
const sessionId = uuid.v1();
this.context = typeof initContext === 'object' ? initContext : {};
const steps = maxSteps ? maxSteps : DEFAULT_MAX_STEPS;
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
this.rl.setPrompt('> ');
this.rl.prompt();
this.rl.write(null, {ctrl: true, name: 'e'});
this.rl.on('line', ((line) => {
const msg = line.trim();
this.runActions(
sessionId,
msg,
this.context,
(error, context) => {
if (error) {
l.error(error);
} else {
this.context = context;
}
this.rl.prompt();
this.rl.write(null, {ctrl: true, name: 'e'});
},
steps
);
}).bind(this));
};
};
module.exports = {
Wit: Wit,
Wit,
};
{
"name": "node-wit",
"version": "3.3.2",
"version": "4.0.0",
"description": "Wit.ai Node.js SDK",

@@ -19,7 +19,10 @@ "keywords": [

"repository": "https://github.com/wit-ai/node-wit",
"author": "Julien Odent <julien@wit.ai>",
"author": "The Wit Team <help@wit.ai>",
"dependencies": {
"node-fetch": "^1.5.1",
"node-uuid": "^1.4.7"
},
"engines": {
"node" : ">=4.0.0"
}
}

@@ -18,3 +18,6 @@ # Wit Node.js SDK [![npm](https://img.shields.io/npm/v/node-wit.svg)](https://www.npmjs.com/package/node-wit)

```bash
node examples/template.js <your_token>
# Node.js <= 6.x.x, add the flag --harmony_destructuring
node --harmony_destructuring examples/basic.js <MY_TOKEN>
# Node.js >= v6.x.x
node examples/basic.js <MY_TOKEN>
```

@@ -24,44 +27,6 @@

## API
### Messenger integration example
### Version change
See `examples/messenger.js` for a thoroughly documented tutorial.
On 2016, May 11th, the /message API was updated to reflect the new Bot Engine model: intent are now entities.
We updated the SDK to the latest version: 20160516.
You can target a specific version by setting the env variable `WIT_API_VERSION`.
```json
{
"msg_id" : "e86468e5-b9e8-4645-95ce-b41a66fda88d",
"_text" : "hello",
"entities" : {
"intent" : [ {
"confidence" : 0.9753469589149633,
"value" : "greetings"
} ]
}
}
```
Version prior to 20160511 will return the old format:
```json
{
"msg_id" : "722fc79b-725c-4ca1-8029-b7f57ff88f54",
"_text" : "hello",
"outcomes" : [ {
"_text" : "hello",
"confidence" : null,
"intent" : "default_intent",
"entities" : {
"intent" : [ {
"confidence" : 0.9753469589149633,
"value" : "greetings"
} ]
}
} ],
"WARNING" : "DEPRECATED"
}
```
### Overview

@@ -78,50 +43,50 @@

The Wit constructor takes the following parameters:
* `token` - the access token of your Wit instance
* `actions` - the object with your actions
* `accessToken` - the access token of your Wit instance
* `actions` - (optional if only using `.message()`) the object with your actions
* `logger` - (optional) the object handling the logging.
* `apiVersion` - (optional) the API version to use instead of the recommended one
The `actions` object has action names as properties, and action implementations as values.
You need to provide at least an implementation for the special actions `say`, `merge` and `error`.
The `actions` object has action names as properties, and action functions as values.
Action implementations must return Promises (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
You must provide at least an implementation for the special action `send`.
A minimal `actions` object looks like this:
```js
const actions = {
say(sessionId, context, message, cb) {
console.log(message);
cb();
},
merge(sessionId, context, entities, message, cb) {
cb(context);
},
error(sessionId, context, error) {
console.log(error.message);
},
};
```
* `send` takes 2 parameters: `request` and `response`
* custom actions take 1 parameter: `request`
A custom action takes the following parameters:
* `sessionId` - a unique identifier describing the user session
* `context` - the object representing the session state
* `cb(context)` - a callback function to fire at the end of your action with the updated context.
#### Request
* `sessionId` (string) - a unique identifier describing the user session
* `context` (object) - the object representing the session state
* `text` (string) - the text message sent by your end-user
* `entities` (object) - the entities extracted by Wit's NLU
Example:
```js
const Wit = require('node-wit').Wit;
const client = new Wit(token, actions);
```
#### Response
* `text` (string) - The text your bot needs to send to the user (as described in your Wit.ai Stories)
* `quickreplies`
The `logger` object should implement the methods `debug`, `log`, `warn` and `error`.
All methods take a single parameter `message`.
The `logger` object should implement the methods `debug`, `info`, `warn` and `error`.
They can receive an arbitrary number of parameters to log.
For convenience, we provide a `Logger` class, taking a log level parameter
For convenience, we provide a `Logger`, taking a log level parameter (provided as `logLevels`).
The following levels are defined: `DEBUG`, `LOG`, `WARN`, `ERROR`.
Example:
```js
const Logger = require('node-wit').Logger;
const levels = require('node-wit').logLevels;
const Wit = require('node-wit').Wit;
const {Wit, log} = require('node-wit');
const logger = new Logger(levels.DEBUG);
const client = new Wit(token, actions, logger);
const client = new Wit({
accessToken: MY_TOKEN,
actions: {
send(request, response) {
return new Promise(function(resolve, reject) {
console.log(JSON.stringify(response));
return resolve();
});
},
myAction({sessionId, context, text, entities}) {
console.log(`Session ${sessionId} received ${text}`);
console.log(`The current context is ${JSON.stringify(context)}`);
console.log(`Wit extracted ${JSON.stringify(entities)}`);
return Promise.resolve(context);
}
},
logger: new log.Logger(log.DEBUG) // optional
});
```

@@ -136,14 +101,11 @@

* `context` - (optional) the object representing the session state
* `cb(error, data)` - a callback function with the JSON response
Example:
```js
const context = {};
client.message('what is the weather in London?', context, (error, data) => {
if (error) {
console.log('Oops! Got an error: ' + error);
} else {
console.log('Yay, got Wit.ai response: ' + JSON.stringify(data));
}
});
const client = new Wit({accessToken: 'MY_TOKEN'});
client.message('what is the weather in London?', {})
.then((data) => {
console.log('Yay, got Wit.ai response: ' + JSON.stringify(data));
})
.catch(console.error);
```

@@ -159,25 +121,24 @@

* `context` - the object representing the session state
* `cb(error, context)` - a callback function with the updated context
* `maxSteps` - (optional) the maximum number of actions to execute (defaults to 5)
Example:
```js
const session = 'my-user-session-42';
const sessionId = 'my-user-session-42';
const context0 = {};
client.runActions(session, 'what is the weather in London?', context0, (e, context1) => {
if (e) {
console.log('Oops! Got an error: ' + e);
return;
}
client.runActions(sessionId, 'what is the weather in London?', context0)
.then((context1) => {
console.log('The session state is now: ' + JSON.stringify(context1));
client.runActions(session, 'and in Brussels?', context1, (e, context2) => {
if (e) {
console.log('Oops! Got an error: ' + e);
return;
}
console.log('The session state is now: ' + JSON.stringify(context2));
});
return client.runActions(sessionId, 'and in Brussels?', context1);
})
.then((context2) => {
console.log('The session state is now: ' + JSON.stringify(context2));
})
.catch((e) => {
console.log('Oops! Got an error: ' + e);
});
```
See `./examples/messenger.js` for a full-fledged example
### converse

@@ -191,13 +152,10 @@

* `context` - the object representing the session state
* `cb(error, data)` - a callback function with the JSON response
Example:
```js
client.converse('my-user-session-42', 'what is the weather in London?', {}, (error, data) => {
if (error) {
console.log('Oops! Got an error: ' + error);
} else {
console.log('Yay, got Wit.ai response: ' + JSON.stringify(data));
}
});
client.converse('my-user-session-42', 'what is the weather in London?', {})
.then((data) => {
console.log('Yay, got Wit.ai response: ' + JSON.stringify(data));
})
.catch(console.error);
```

@@ -217,42 +175,40 @@

## Messenger integration example
## Changing the API version
This quickstart assumes that you have:
* a [Wit.ai bot setup](https://wit.ai/docs/quickstart);
* a [Messenger Platform setup](https://developers.facebook.com/docs/messenger-platform/quickstart).
On 2016, May 11th, the /message API was updated to reflect the new Bot Engine model: intent are now entities.
We updated the SDK to the latest version: 20160516.
You can target a specific version by passing the `apiVersion` parameter when creating the `Wit` object.
### Install dependencies
```bash
npm install body-parser express node-fetch
```json
{
"msg_id" : "e86468e5-b9e8-4645-95ce-b41a66fda88d",
"_text" : "hello",
"entities" : {
"intent" : [ {
"confidence" : 0.9753469589149633,
"value" : "greetings"
} ]
}
}
```
### Download and install ngrok
Version prior to 20160511 will return the old format:
From [here](https://ngrok.com/download).
### Run ngrok
```bash
./ngrok http 8445
```json
{
"msg_id" : "722fc79b-725c-4ca1-8029-b7f57ff88f54",
"_text" : "hello",
"outcomes" : [ {
"_text" : "hello",
"confidence" : null,
"intent" : "default_intent",
"entities" : {
"intent" : [ {
"confidence" : 0.9753469589149633,
"value" : "greetings"
} ]
}
} ],
"WARNING" : "DEPRECATED"
}
```
This will provide `your_ngrok_domain` (the `Forwarding` line).
### Run the example
```bash
export WIT_TOKEN=your_access_token
export FB_PAGE_ID=your_page_id
export FB_PAGE_TOKEN=your_page_token
export FB_VERIFY_TOKEN=any_token
node examples/messenger.js
```
### Subscribe your page to Messenger Webhooks
Using your `FB_VERIFY_TOKEN` and `https://<your_ngrok_domain>/fb` as callback URL.
See the [Messenger Platform docs](https://developers.facebook.com/docs/messenger-platform/quickstart).
### Talk to your bot on Messenger!
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc