Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9207c70
Add subscription request
Apr 13, 2016
5404598
Docker support
Apr 13, 2016
cef02cf
Fix app.json
Apr 13, 2016
b058d58
Update README.md
Apr 13, 2016
22a3729
Update app.json
xVir Apr 14, 2016
b17753e
Update README.md
xVir Apr 14, 2016
3974bd6
Add lang parameter
Apr 14, 2016
8ed6577
Lang parameter not required
Apr 14, 2016
b7b606b
Change subscribe logic
Apr 14, 2016
96d828c
Change facebook data processing
Apr 15, 2016
e0b3000
Add logging
Apr 15, 2016
c1adcd5
Fix bug in data processing
Apr 15, 2016
2d78a2a
Update apiai lib
Apr 15, 2016
3a42320
add repository field
Apr 15, 2016
6591f69
Fix typo
Apr 21, 2016
a6c32fd
Add request source parameter for statistic purposes
Apr 22, 2016
13a8b94
Update apiai library
Apr 25, 2016
278b9d5
Add response splitting
Apr 25, 2016
2a1f087
Add note about languages
Apr 26, 2016
bef7de4
Add link to instructions to README
May 11, 2016
ae1cbe0
Update lib reference
May 16, 2016
99edaf6
Workaround for https://developers.facebook.com/bugs/578746852290927/
May 17, 2016
fea6c68
Merge pull request #1 from crcastle/patch-1
xVir May 17, 2016
d322bae
Merge pull request #2 from api-ai/master
xVir May 20, 2016
dae3806
Fix problem with long messages
May 20, 2016
fc09f5e
check for postback
Jun 29, 2016
33b00cc
check for postback
Jun 29, 2016
e139788
check for postback
Jun 29, 2016
9dede32
check for postback
Jun 29, 2016
4ea4ab9
check for postback
Jun 29, 2016
969f7a7
check for postback
Jun 29, 2016
4c98b2e
Merge branch 'master' of https://git.heroku.com/fb-edi
Jun 29, 2016
aa3fe7a
send displaytext to user
Jun 30, 2016
b38f159
handle arrays of fb messages
Jun 30, 2016
fef7577
handle arrays of fb messages
Jun 30, 2016
b60b158
Revert "send displaytext to user"
Jun 30, 2016
35bcae3
handle arrays of fb messages
Jun 30, 2016
a870b46
sender actions
Jul 6, 2016
c24ebe3
sender actions
Jul 6, 2016
959ff92
sender actions
Jul 6, 2016
a1a6e2d
cleanup
Jul 6, 2016
7f82467
sender actions
Jul 6, 2016
9563f7a
sender actions debug
Jul 7, 2016
52db40d
sender actions debug
Jul 7, 2016
773e5e6
sender actions debug
Jul 7, 2016
16bf946
sender actions debug
Jul 7, 2016
d0bfd9e
pause for action
Jul 7, 2016
9f23be1
Merge pull request #2 from joshwolf/master
xVir Jul 21, 2016
3be1e64
Format changes and using arrow functions
Jul 21, 2016
c9c194c
Update libs
Jul 21, 2016
e052e67
Changed Facebook protocol support
Jul 21, 2016
570de7d
Comment
Jul 21, 2016
5b971c3
Merge pull request #4 from api-ai/master
xVir Jul 21, 2016
090bec9
Add badge
xVir Sep 20, 2016
b0f9676
Update Node
xVir Sep 20, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea/
node_modules/
npm-debug.log
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules/
npm-debug.log
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM node:6

MAINTAINER xVir <[email protected]>

RUN mkdir -p /usr/app/src

WORKDIR /usr/app
COPY . /usr/app

EXPOSE 5000

RUN npm install
CMD ["npm", "start"]
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,39 @@
# api-ai-facebook
Facebook bot sources for api.ai integration
[![](https://images.microbadger.com/badges/image/xvir/api-ai-facebook.svg)](https://microbadger.com/images/xvir/api-ai-facebook "Get your own image badge on microbadger.com")

[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
Facebook bot sources for Api.ai integration

## Deploy with Heroku
Follow [these instructions](https://docs.api.ai/docs/facebook-integration#hosting-fb-messenger-bot-with-heroku).
Then,
[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)

## Deploy with Docker

```bash
docker run -it --name fb_bot \
-p <your_desired_port>:5000 \
-e APIAI_ACCESS_TOKEN="API.AI client access token" \
-e FB_PAGE_ACCESS_TOKEN="Facebook Page Access Token" \
-e FB_VERIFY_TOKEN="Facebook Verify Token" \
-e APIAI_LANG="en" \
xvir/api-ai-facebook
```

## Note about languages:
When you deploy the app manually to Heroku, the APIAI_LANG not filled with a value.
You need to provide language parameter according to your agent settings in the form of two-letters code.

* "en"
* "ru"
* "de"
* "pt"
* "pt-BR"
* "es"
* "fr"
* "it"
* "ja"
* "ko"
* "zh-CN"
* "zh-HK"
* "zh-TW"
11 changes: 8 additions & 3 deletions app.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "API.AI Facebook integration",
"description": "Api.ai Facebook integration allows you to create Facebook bots with natural language understanding based on Api.ai technology.",
"repository": "https://github.com/xVir/api-ai-slack-bot",
"repository": "https://github.com/api-ai/api-ai-facebook",
"logo": "http://xvir.github.io/img/apiai.png",
"keywords": ["api.ai", "slack", "natural language"],
"keywords": ["api.ai", "facebook", "natural language"],
"env": {
"APIAI_ACCESS_TOKEN": {
"description": "Client access token for Api.ai",
Expand All @@ -16,9 +16,14 @@
"FB_PAGE_ACCESS_TOKEN": {
"description": "Page Access Token",
"value": ""
},
"APIAI_LANG": {
"description": "Agent language",
"value": "",
"required": false
}
},
"engines": {
"node": "5.9.0"
"node": "5.10.1"
}
}
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "api-ai-facebook",
"version": "1.0.0",
"description": "API.AI Slack Bot",
"description": "API.AI Facebook Bot",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js",
Expand All @@ -10,11 +10,17 @@
"author": "Danil Skachkov <[email protected]> (https://api.ai)",
"license": "ISC",
"dependencies": {
"apiai": "^1.0.8",
"body-parser": "^1.15.0",
"express": "4.13.3",
"apiai": "2.0.5",
"async": "^2.0.0",
"body-parser": "^1.15.2",
"express": "^4.13.3",
"html-entities": "^1.2.0",
"json-bigint": "^0.2.0",
"node-uuid": "^1.4.7",
"request": "^2.71.0"
"request": "^2.73.0"
},
"repository": {
"type": "git",
"url": "https://github.com/api-ai/api-ai-facebook"
}
}
176 changes: 147 additions & 29 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@ const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('node-uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;

const apiAiService = apiai(APIAI_ACCESS_TOKEN, "deprecated", {});
const apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
const sessionIds = new Map();

function processEvent(event) {
var sender = event.sender.id;
var sender = event.sender.id.toString();

if (event.message && event.message.text) {
var text = event.message.text;
if ((event.message && event.message.text) || (event.postback && event.postback.payload)) {
var text = event.message ? event.message.text : event.postback.payload;
// Handle a text message from this sender

if (!sessionIds.has(sender)) {
Expand All @@ -35,18 +38,42 @@ function processEvent(event) {
apiaiRequest.on('response', (response) => {
if (isDefined(response.result)) {
let responseText = response.result.fulfillment.speech;
let responseData = response.result.data;
let responseData = response.result.fulfillment.data;
let action = response.result.action;

if (isDefined(responseData)) {
try {
let responseObject = JSON.parse(responseData);
sendFBMessage(sender, responseObject.facebook);
} catch (err) {
sendFBMessage(sender, err.message);
if (isDefined(responseData) && isDefined(responseData.facebook)) {
if (!Array.isArray(responseData.facebook)) {
try {
console.log('Response as formatted message');
sendFBMessage(sender, responseData.facebook);
} catch (err) {
sendFBMessage(sender, {text: err.message});
}
} else {
responseData.facebook.forEach((facebookMessage) => {
try {
if (facebookMessage.sender_action) {
console.log('Response as sender action');
sendFBSenderAction(sender, facebookMessage.sender_action);
}
else {
console.log('Response as formatted message');
sendFBMessage(sender, facebookMessage);
}
} catch (err) {
sendFBMessage(sender, {text: err.message});
}
});
}
} else if (isDefined(responseText)) {
sendFBMessage(sender, {text: responseText});
console.log('Response as text message');
// facebook API limit for text length is 320,
// so we must split message if needed
var splittedText = splitResponse(responseText);

async.eachSeries(splittedText, (textPart, callback) => {
sendFBMessage(sender, {text: textPart}, callback);
});
}

}
Expand All @@ -57,7 +84,43 @@ function processEvent(event) {
}
}

function sendFBMessage(sender, messageData) {
function splitResponse(str) {
if (str.length <= 320) {
return [str];
}

return chunkString(str, 300);
}

function chunkString(s, len) {
var curr = len, prev = 0;

var output = [];

while (s[curr]) {
if (s[curr++] == ' ') {
output.push(s.substring(prev, curr));
prev = curr;
curr += len;
}
else {
var currReverse = curr;
do {
if (s.substring(currReverse - 1, currReverse) == ' ') {
output.push(s.substring(prev, currReverse));
prev = currReverse;
curr = currReverse + len;
break;
}
currReverse--;
} while (currReverse > prev)
}
}
output.push(s.substr(prev));
return output;
}

function sendFBMessage(sender, messageData, callback) {
request({
url: 'https://graph.facebook.com/v2.6/me/messages',
qs: {access_token: FB_PAGE_ACCESS_TOKEN},
Expand All @@ -66,15 +129,56 @@ function sendFBMessage(sender, messageData) {
recipient: {id: sender},
message: messageData
}
}, function (error, response, body) {
}, (error, response, body) => {
if (error) {
console.log('Error sending message: ', error);
} else if (response.body.error) {
console.log('Error: ', response.body.error);
}

if (callback) {
callback();
}
});
}

function sendFBSenderAction(sender, action, callback) {
setTimeout(() => {
request({
url: 'https://graph.facebook.com/v2.6/me/messages',
qs: {access_token: FB_PAGE_ACCESS_TOKEN},
method: 'POST',
json: {
recipient: {id: sender},
sender_action: action
}
}, (error, response, body) => {
if (error) {
console.log('Error sending action: ', error);
} else if (response.body.error) {
console.log('Error: ', response.body.error);
}
if (callback) {
callback();
}
});
}, 1000);
}

function doSubscribeRequest() {
request({
method: 'POST',
uri: "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" + FB_PAGE_ACCESS_TOKEN
},
(error, response, body) => {
if (error) {
console.error('Error while subscription: ', error);
} else {
console.log('Subscription result: ', response.body);
}
});
}

function isDefined(obj) {
if (typeof obj == 'undefined') {
return false;
Expand All @@ -88,28 +192,40 @@ function isDefined(obj) {
}

const app = express();
app.use(bodyParser.json());
app.all('*', function (req, res, next) {
// res.header("Access-Control-Allow-Origin", '*');
// res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, content-type, accept");
next();
});

app.get('/webhook/', function (req, res) {
app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
if (req.query['hub.verify_token'] == FB_VERIFY_TOKEN) {
res.send(req.query['hub.challenge']);

setTimeout(() => {
doSubscribeRequest();
}, 3000);
} else {
res.send('Error, wrong validation token');
}
});

app.post('/webhook/', function (req, res) {
try{
var messaging_events = req.body.entry[0].messaging;
for (var i = 0; i < messaging_events.length; i++) {
var event = req.body.entry[0].messaging[i];
processEvent(event);
app.post('/webhook/', (req, res) => {
try {
var data = JSONbig.parse(req.body);

if (data.entry) {
let entries = data.entry;
entries.forEach((entry) => {
let messaging_events = entry.messaging;
if (messaging_events) {
messaging_events.forEach((event) => {
if (event.message && !event.message.is_echo ||
event.postback && event.postback.payload) {
processEvent(event);
}
});
}
});
}

return res.status(200).json({
status: "ok"
});
Expand All @@ -122,6 +238,8 @@ app.post('/webhook/', function (req, res) {

});

app.listen(REST_PORT, function () {
app.listen(REST_PORT, () => {
console.log('Rest service ready on port ' + REST_PORT);
});
});

doSubscribeRequest();