intelligo
Version:
AI Chatbot Framework for Node.js
419 lines • 15.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const express = require("express");
const eventemitter3_1 = require("eventemitter3");
const body_parser_1 = require("body-parser");
const crypto_1 = require("crypto");
const request = require("request");
const intelligo = require("intelligo.js");
class MessengerBot extends eventemitter3_1.EventEmitter {
/**
* @param params
* @constructor
*/
constructor(params) {
super();
if (!params || (params && (!params.PAGE_ACCESS_TOKEN || !params.VALIDATION_TOKEN || !params.APP_SECRET))) {
throw new Error('You need to specify an PAGE_ACCESS_TOKEN, VALIDATION_TOKEN and APP_SECRET');
}
this.PAGE_ACCESS_TOKEN = params.PAGE_ACCESS_TOKEN;
this.VALIDATION_TOKEN = params.VALIDATION_TOKEN;
this.APP_SECRET = params.APP_SECRET;
this.FB_URL = params.FB_URL || 'https://graph.facebook.com/v3.1/';
this.app = params.app || express();
this.webhook = params.webhook || '/webhook';
this.app.use(body_parser_1.json({ verify: this.verifyRequestSignature.bind(this) }));
this.intelligoClassifier;
}
learn(data) {
// Repeat multiple levels
const TextClassifier = intelligo.classifiers.multilabel.BinaryRelevance.bind(0, {
binaryClassifierType: intelligo.classifiers.Winnow.bind(0, { retrain_count: 10 }),
});
const WordExtractor = (input, features) => {
return input.split(' ').map((word) => {
return (features[word] = 1);
});
};
this.intelligoClassifier = new intelligo.classifiers.EnhancedClassifier({
classifierType: TextClassifier,
featureExtractor: WordExtractor,
});
this.intelligoClassifier.trainBatch(data);
}
answer(question) {
const result = this.intelligoClassifier.classify(question);
return result;
}
initWebhook() {
/*
* Use your own validation token. Check that the token used in the Webhook
* setup is the same token used here.
*/
this.app.get(this.webhook, (req, res) => {
if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === this.VALIDATION_TOKEN) {
console.log('Validating webhook');
res.status(200).send(req.query['hub.challenge']);
}
else {
console.error('Failed validation. Make sure the validation tokens match.');
res.sendStatus(403);
}
});
/*
* All callbacks for Messenger are POST-ed. They will be sent to the same
* webhook. Be sure to subscribe your app to your page to receive callbacks
* for your page.
* https://developers.facebook.com/docs/messenger-platform/product-overview/setup#subscribe_app
*/
this.app.post(this.webhook, (req, res) => {
var data = req.body;
if (data.object === 'page') {
for (const pageEntry of data.entry) {
for (const messagingEvent of pageEntry.messaging) {
this.handleEvent(messagingEvent);
}
}
res.sendStatus(200);
}
});
}
// Iterate over each messaging event
handleEvent(event) {
if (event.optin) {
let optin = event.optin.ref;
this.emit('optin', event.sender.id, event, optin);
}
else if (typeof event.message === 'string') {
this.emit('message', event);
}
else if (event.message && !event.message.is_echo) {
this.emit('message', event);
}
else if (event.message && event.message.attachment) {
this.emit('attachment', event.sender.id, event.message.attachment, event.message.url, event.message.quickReplies);
}
else if (event.delivery) {
let mids = event.delivery.mids;
this.emit('delivery', event.sender.id, event, mids);
}
else if (event.read) {
let recipient = event.recipient.id;
this.emit('read', event.sender.id, recipient, event.read);
}
else if (event.postback || (event.message && !event.message.is_echo && event.message.quick_reply)) {
let postback = (event.postback && event.postback.payload) || event.message.quick_reply.payload;
let ref = event.postback && event.postback.referral && event.postback.referral.ref;
this.emit('postback', event.sender.id, postback);
}
else if (event.referral) {
let ref = event.referral.ref;
this.emit('referral', event.sender.id, event, ref);
}
else if (event.account_linking) {
let link = event.account_linking;
this.emit('account_link', event.sender.id, event, link);
}
else {
console.error('Invalid format for message.');
}
}
/*
* 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
*
*/
verifyRequestSignature(req, res, buf) {
const 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 {
const elements = signature.split('='), method = elements[0], signatureHash = elements[1];
const expectedHash = crypto_1.createHmac('sha1', this.APP_SECRET)
.update(buf)
.digest('hex');
if (signatureHash != expectedHash) {
throw new Error("Couldn't validate the request signature.");
}
}
}
addGreeting(text) {
request({
url: `${this.FB_URL}me/thread_settings`,
qs: { access_token: this.PAGE_ACCESS_TOKEN },
method: 'POST',
json: {
setting_type: 'greeting',
greeting: {
text: text,
},
},
}, function (error, response, body) {
if (error) {
console.log('Error sending message: ', error);
}
else if (response.body.error) {
console.log('Error: ', response.body.error);
}
});
}
addGetStartedButton() {
request({
url: `${this.FB_URL}me/messenger_profile`,
qs: { access_token: this.PAGE_ACCESS_TOKEN },
method: 'POST',
json: {
get_started: {
payload: 'GET_STARTED_PAYLOAD',
},
},
}, function (error, response, body) {
if (error) {
console.log('Error sending messages: ', error);
}
else if (response.body.error) {
console.log('Error: ', response.body.error);
}
});
}
addPersistentMenu(persistent_menu) {
request({
url: `${this.FB_URL}me/messenger_profile`,
qs: { access_token: this.PAGE_ACCESS_TOKEN },
method: 'POST',
json: {
persistent_menu: persistent_menu,
},
}, function (error, response, body) {
if (error) {
console.log('Error sending message: ', error);
}
else if (response.body.error) {
console.log('Error: ', response.body.error);
}
});
}
removePersistentMenu() {
request({
url: `${this.FB_URL}me/thread_settings`,
qs: { access_token: this.PAGE_ACCESS_TOKEN },
method: 'POST',
json: {
setting_type: 'call_to_actions',
thread_state: 'existing_thread',
call_to_actions: [],
},
}, function (error, response, body) {
console.log(response);
if (error) {
console.log('Error sending messages: ', error);
}
else if (response.body.error) {
console.log('Error: ', response.body.error);
}
});
}
/**
* @param {Recipient|Object} recipientId Recipient object or ID.
* @param {String} messageText
*/
sendTextMessage(recipientId, messageText) {
this.callSendAPI({
recipient: {
id: recipientId,
},
message: {
text: messageText,
},
});
}
/**
* @param {Recipient|String} recipientId
* @param {String} type Must be 'image', 'audio', 'video' or 'file'.
* @param {String} url URL of the attachment.
*/
sendAttachment(recipientId, type, url) {
this.callSendAPI({
recipient: {
id: recipientId,
},
message: {
attachment: {
type: type,
payload: {
url: url,
},
},
},
});
}
/**
* @param {Recipient|String} recipientId
* @param {String} url URL of the attachment.
*/
sendFileMessage(recipientId, url) {
this.sendAttachment(recipientId, 'file', url);
}
/**
* @param {Recipient|String} recipientId
* @param {String} url URL of the attachment.
*/
sendImageMessage(recipientId, url) {
this.sendAttachment(recipientId, 'image', url);
}
/**
* @param {Recipient|String} recipientId
* @param {String} url URL of the attachment.
*/
sendVideoMessage(recipientId, url) {
this.sendAttachment(recipientId, 'video', url);
}
/**
* @param {Recipient|String} recipientId
* @param {String} url URL of the attachment.
*/
sendAudioMessage(recipientId, url) {
this.sendAttachment(recipientId, 'audio', url);
}
/**
* @param {Recipient|String} recipientId
* @param {Array.<Element>} elements
*/
sendGenericMessage(recipientId, elements) {
var messageData = {
recipient: {
id: recipientId,
},
message: {
attachment: {
type: 'template',
payload: {
template_type: 'generic',
elements: elements,
},
},
},
};
this.callSendAPI(messageData);
}
sendButtonMessage(recipientId, text, buttons) {
var messageData = {
recipient: {
id: recipientId,
},
message: {
attachment: {
type: 'template',
payload: {
template_type: 'button',
text: text,
buttons: buttons,
},
},
},
};
this.callSendAPI(messageData);
}
callSendAPI(messageData) {
request({
uri: `${this.FB_URL}me/messages`,
qs: { access_token: this.PAGE_ACCESS_TOKEN },
method: 'POST',
json: messageData,
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
const recipientId = body.recipient_id, messageId = body.message_id;
if (messageId) {
console.log('Successfully sent message with id %s to recipient %s', messageId, recipientId);
}
else {
console.log('Successfully called Send API for recipient %s', recipientId);
}
}
else {
console.error('Failed calling Send API', response.statusCode, response.statusMessage, body.error);
}
});
}
/**
* @param {Recipient|String} recipientId
* @param greetings
* @param text
*/
sendWelcome(recipientId, greetings, text) {
// this (aka "the context") is a special keyword inside each function and its value only depends on how the function was called,
// not how/when/where it was defined. It is not affected by lexical scopes, like other variables
const self = this;
request({
url: `${this.FB_URL}` + recipientId + `?access_token=` + this.PAGE_ACCESS_TOKEN,
}, function (error, response, body) {
if (error || response.statusCode != 200)
return;
const fbProfileBody = JSON.parse(body), userName = fbProfileBody['first_name'], randomGreeting = greetings[self.getRandomNumber(0, greetings.length - 1)], welcomeMsg = `${randomGreeting} ${userName} ${text}`;
self.sendTextMessage(recipientId, welcomeMsg);
});
}
/*
* Postback Event
*
* This event is called when a postback is tapped on a Structured Message.
* https://developers.facebook.com/docs/messenger-platform/webhook-reference/postback-received
*
*/
receivedPostback(event) {
const senderID = event.sender.id, recipientID = event.recipient.id, timeOfPostback = event.timestamp, payload = event.postback.payload;
console.log("Received postback for user %d and page %d with payload '%s' " + 'at %d', senderID, recipientID, payload, timeOfPostback);
this.sendTextMessage(senderID, 'Postback called');
}
/*
* Send a read receipt to indicate the message has been read
*
*/
sendReadReceipt(recipientId) {
this.callSendAPI({
recipient: {
id: recipientId,
},
sender_action: 'mark_seen',
});
}
/**
* @param {Recipient|Object} recipientId Recipient object or ID.
*/
sendTypingOn(recipientId) {
this.callSendAPI({
recipient: {
id: recipientId,
},
sender_action: 'typing_on',
});
}
/**
* @param {Recipient|Object} recipientId Recipient object or ID.
*/
sendTypingOff(recipientId) {
this.callSendAPI({
recipient: {
id: recipientId,
},
sender_action: 'typing_off',
});
}
getRandomNumber(minimum, maxmimum) {
return Math.floor(Math.exp(Math.random() * Math.log(maxmimum - minimum + 1))) + minimum;
}
randomIntFromInterval(min, max) {
return this.getRandomNumber(min, max);
}
textMatches(message, matchString) {
return message.toLowerCase().indexOf(matchString) != -1;
}
}
exports.MessengerBot = MessengerBot;
//# sourceMappingURL=messenger.js.map
;