node-facebook-messenger-api 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jshintrc +4 -0
- package/README.md +180 -0
- package/account-link-handler.js +176 -0
- package/example/public/index.html +8 -0
- package/example/server.js +87 -0
- package/messenger.js +768 -0
- package/node-facebook-messenger-api.js +15 -0
- package/package.json +37 -0
- package/screenshots/confirm-subscribe-page.png +0 -0
- package/screenshots/create-app.png +0 -0
- package/screenshots/create-page.png +0 -0
- package/screenshots/heroku-create.png +0 -0
- package/screenshots/page-access-token.png +0 -0
- package/screenshots/page-subscribed.png +0 -0
- package/screenshots/send-message-hi.png +0 -0
- package/screenshots/setup-messenger.png +0 -0
- package/screenshots/setup-webhooks.png +0 -0
- package/screenshots/subscribe-page.png +0 -0
- package/screenshots/webhook-config.png +0 -0
- package/test/analytics.test.js +157 -0
- package/webhook-handler.js +170 -0
package/.jshintrc
ADDED
package/README.md
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# Simple Node.js Facebook Messenger API #
|
2
|
+
|
3
|
+
[](https://www.bithound.io/github/perusworld/node-facebook-messenger-api)
|
4
|
+
[](https://www.bithound.io/github/perusworld/node-facebook-messenger-api/master/dependencies/npm)
|
5
|
+
[](https://www.bithound.io/github/perusworld/node-facebook-messenger-api)
|
6
|
+
|
7
|
+
Based on the [Messenger Platform Sample](https://github.com/fbsamples/messenger-platform-samples)
|
8
|
+
|
9
|
+
## Install ##
|
10
|
+
```bash
|
11
|
+
npm install github:perusworld/node-facebook-messenger-api --save
|
12
|
+
```
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
## Usage ##
|
17
|
+
See [Setup](https://developers.facebook.com/docs/messenger-platform/guides/setup) for more details about setting up the bot/page.
|
18
|
+
|
19
|
+
## Detailed Walkthrough/Example (messenger bot on heroku) ##
|
20
|
+
[Quickly write a facebook messenger bot using nodejs and host it on heroku](https://steemit.com/chatbot/@perusworld/quickly-write-a-facebook-messenger-bot-using-nodejs-and-host-it-on-heroku)
|
21
|
+
|
22
|
+
|
23
|
+
### Get User Profile - [User Profile API](https://developers.facebook.com/docs/messenger-platform/user-profile) ##
|
24
|
+
```javascript
|
25
|
+
var messengerapi = require('node-facebook-messenger-api').messenger();
|
26
|
+
var messenger = new messengerapi.Messenger({
|
27
|
+
appId:"",
|
28
|
+
pageId: "",
|
29
|
+
analyticsLogLevel: "",
|
30
|
+
appSecret: "",
|
31
|
+
pageAccessToken: "",
|
32
|
+
validationToken: ""
|
33
|
+
});
|
34
|
+
|
35
|
+
var pageScopedUserID = "...";
|
36
|
+
|
37
|
+
messenger.getUserProfile(pageScopedUserID, (err, resp) => {
|
38
|
+
if (err) {
|
39
|
+
console.error(recipientId, "Sorry, looks like the backend is down :-(");
|
40
|
+
} else {
|
41
|
+
console.log(JSON.parse(resp));
|
42
|
+
}
|
43
|
+
});
|
44
|
+
```
|
45
|
+
|
46
|
+
### Send Generic Template Message - [Generic Template](https://developers.facebook.com/docs/messenger-platform/send-api-reference/generic-template) ###
|
47
|
+
```javascript
|
48
|
+
messenger.sendGenericMessage(pageScopeUserID, [{
|
49
|
+
title: "Welcome to Peter\'s Hats",
|
50
|
+
image_url: "https://petersfancybrownhats.com/company_image.png",
|
51
|
+
subtitle: "We\'ve got the right hat for everyone.",
|
52
|
+
default_action: {
|
53
|
+
type: "web_url",
|
54
|
+
url: "https://peterssendreceiveapp.ngrok.io/view?item=103",
|
55
|
+
messenger_extensions: true,
|
56
|
+
webview_height_ratio: "tall",
|
57
|
+
fallback_url: "https://peterssendreceiveapp.ngrok.io/"
|
58
|
+
},
|
59
|
+
buttons: [{
|
60
|
+
type: "web_url",
|
61
|
+
url: "https://petersfancybrownhats.com",
|
62
|
+
title: "View Website"
|
63
|
+
}, {
|
64
|
+
type: "postback",
|
65
|
+
title: "Start Chatting",
|
66
|
+
payload: "DEVELOPER_DEFINED_PAYLOAD"
|
67
|
+
}]
|
68
|
+
}]);
|
69
|
+
```
|
70
|
+
|
71
|
+
### Using Webhook Handler to receive Facebook Messenger Events - [Webhook Reference](https://developers.facebook.com/docs/messenger-platform/webhook-reference) ###
|
72
|
+
The example folder contains a sample app
|
73
|
+
```bash
|
74
|
+
npm install
|
75
|
+
cd example
|
76
|
+
export MESSENGER_ANALYTICS_LOG_LEVEL = "2";
|
77
|
+
export MESSENGER_APP_ID = "--yours--";
|
78
|
+
export MESSENGER_PAGE_ID = "--yours--";
|
79
|
+
export MESSENGER_APP_SECRET="--yours--"
|
80
|
+
export MESSENGER_VALIDATION_TOKEN="--yours--"
|
81
|
+
export MESSENGER_PAGE_ACCESS_TOKEN="--yours--"
|
82
|
+
node server
|
83
|
+
```
|
84
|
+
Or use this in your existing code
|
85
|
+
```javascript
|
86
|
+
const
|
87
|
+
express = require('express');
|
88
|
+
|
89
|
+
var ignores = ['/some-url/to-ignore'];
|
90
|
+
var verifySignature = true;
|
91
|
+
|
92
|
+
var messengerapi = require('node-facebook-messenger-api').messenger();
|
93
|
+
var messenger = new messengerapi.Messenger({});
|
94
|
+
var webhookHandler = require('node-facebook-messenger-api').webhookHandler()(messenger, {
|
95
|
+
receivedAuthentication : function(event) {
|
96
|
+
console.log('receivedAuthentication', event);
|
97
|
+
},
|
98
|
+
handleMessage : function(event) {
|
99
|
+
console.log('handleMessage', event);
|
100
|
+
messenger.sendTextMessage(event.sender.id, JSON.stringify(event));
|
101
|
+
},
|
102
|
+
receivedDeliveryConfirmation : function(event) {
|
103
|
+
console.log('receivedDeliveryConfirmation', event);
|
104
|
+
},
|
105
|
+
receivedPostback : function(event) {
|
106
|
+
console.log('receivedPostback', event);
|
107
|
+
},
|
108
|
+
receivedMessageRead : function(event) {
|
109
|
+
console.log('receivedMessageRead', event);
|
110
|
+
},
|
111
|
+
doLinking : function(event) {
|
112
|
+
console.log('doLinking', event);
|
113
|
+
},
|
114
|
+
doUnlinking : function(event) {
|
115
|
+
console.log('doUnlinking', event);
|
116
|
+
}
|
117
|
+
},verifySignature, ignores, express.Router());
|
118
|
+
|
119
|
+
var app = express();
|
120
|
+
app.set('port', process.env.PORT || 3000);
|
121
|
+
app.use(express.static('public'));
|
122
|
+
|
123
|
+
app.use('/fb', webhookHandler);
|
124
|
+
app.listen(app.get('port'), function () {
|
125
|
+
console.log('Node app is running in http mode on port', app.get('port'));
|
126
|
+
});
|
127
|
+
```
|
128
|
+
#### Analytics Events ####
|
129
|
+
You can now send analytics events through the following API (See [App Events with Bots for Messenger - Logging Custom Events](https://developers.facebook.com/docs/app-events/bots-for-messenger#logging-custom-events) and [App Events API](https://developers.facebook.com/docs/marketing-api/app-event-api/v2.9) for more details about the event types)
|
130
|
+
|
131
|
+
For performace reasons if you would like to throttle the events that you would like to send/track, you can use the env variable
|
132
|
+
```bash
|
133
|
+
export MESSENGER_ANALYTICS_LOG_LEVEL = "2";
|
134
|
+
```
|
135
|
+
to control the log levels. Set that to
|
136
|
+
|
137
|
+
Level | Value | Description |
|
138
|
+
--- | --- | --- |
|
139
|
+
None | 99 | Don't send any analytics events |
|
140
|
+
Critical | 2 | Send only critical analytics events |
|
141
|
+
Verbose | 1 | Send all analytics events |
|
142
|
+
|
143
|
+
The asynchronous callback from the *analyticsEvent* call would be either
|
144
|
+
```json
|
145
|
+
{
|
146
|
+
"success": true
|
147
|
+
}
|
148
|
+
```
|
149
|
+
if the event was successfully accepted or
|
150
|
+
```json
|
151
|
+
{
|
152
|
+
"skip": true
|
153
|
+
}
|
154
|
+
```
|
155
|
+
if the event was skipped due lower log levels
|
156
|
+
|
157
|
+
- Custom Event
|
158
|
+
```javascript
|
159
|
+
messenger.analyticsEvent(messengerapi.ANALYTICS_LEVEL_VERBOSE, event.sender.id, () => {
|
160
|
+
return messenger.buildAnalyticsEvent("fb_mobile_verbose_event");
|
161
|
+
}, (err, resp) => {
|
162
|
+
messenger.sendTextMessage(event.sender.id, JSON.stringify(err ? err : resp));
|
163
|
+
});
|
164
|
+
```
|
165
|
+
|
166
|
+
- Standard Purchase Event
|
167
|
+
```javascript
|
168
|
+
messenger.analyticsEvent(messengerapi.ANALYTICS_LEVEL_CRITICAL, event.sender.id, () => {
|
169
|
+
return messenger.buildAnalyticsEvent("fb_mobile_purchase", { _valueToSum: 9.99, fb_currency: 'USD' });
|
170
|
+
}, (err, resp) => {
|
171
|
+
messenger.sendTextMessage(event.sender.id, JSON.stringify(err ? err : resp));
|
172
|
+
});
|
173
|
+
```
|
174
|
+
- Standard Add Cart Event Using quickAnalytics
|
175
|
+
```javascript
|
176
|
+
messenger.quickAnalytics(messengerapi.ANALYTICS_LEVEL_CRITICAL, event.sender.id, "fb_mobile_add_to_cart", {
|
177
|
+
fb_content_type: 'blah blah blah', fb_content_id: '123456789', _valueToSum: 9.99, fb_currency: 'USD'
|
178
|
+
});
|
179
|
+
```
|
180
|
+
|
@@ -0,0 +1,176 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
const async = require('async'),
|
4
|
+
crypto = require('crypto'),
|
5
|
+
error = require('debug')('error'),
|
6
|
+
debug = require('debug')('fb-accountlink');
|
7
|
+
|
8
|
+
function AccountLinkHandler(config) {
|
9
|
+
this.conf = config;
|
10
|
+
if (!this.conf.userIdField) {
|
11
|
+
this.conf.userIdField = 'userid';
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
AccountLinkHandler.prototype.getFbLinking = function (authCode, callback) {
|
16
|
+
this.conf.linkModel.getById(authCode, callback)
|
17
|
+
};
|
18
|
+
|
19
|
+
AccountLinkHandler.prototype.removeFbLinking = function (linking, callback) {
|
20
|
+
this.conf.linkModel.delete({
|
21
|
+
_id: linking._id
|
22
|
+
}, callback);
|
23
|
+
};
|
24
|
+
|
25
|
+
AccountLinkHandler.prototype.getFbUser = function (linking, callback) {
|
26
|
+
this.conf.userModel.getById(linking[this.conf.userIdField], callback);
|
27
|
+
};
|
28
|
+
|
29
|
+
AccountLinkHandler.prototype.addFbUser = function (linking, authCode, handler, callback) {
|
30
|
+
var obj = {
|
31
|
+
_id: linking[this.conf.userIdField],
|
32
|
+
authCode: authCode
|
33
|
+
};
|
34
|
+
if (handler) {
|
35
|
+
handler.updateFbUser(obj, linking);
|
36
|
+
}
|
37
|
+
this.conf.userModel.add(obj, callback);
|
38
|
+
};
|
39
|
+
|
40
|
+
AccountLinkHandler.prototype.updateFbUser = function (user, authCode, callback) {
|
41
|
+
user.authCode = authCode;
|
42
|
+
this.conf.userModel.update(user, callback);
|
43
|
+
};
|
44
|
+
|
45
|
+
AccountLinkHandler.prototype.removeFbUser = function (mapping, callback) {
|
46
|
+
this.conf.userModel.delete({
|
47
|
+
_id: mapping[this.conf.userIdField]
|
48
|
+
}, callback)
|
49
|
+
};
|
50
|
+
|
51
|
+
AccountLinkHandler.prototype.getFbUserMapping = function (senderID, callback) {
|
52
|
+
this.conf.mappingModel.getById(senderID, callback);
|
53
|
+
};
|
54
|
+
|
55
|
+
AccountLinkHandler.prototype.addFbUserMapping = function (senderID, recipientID, linking, status, callback) {
|
56
|
+
var obj = {
|
57
|
+
_id: senderID,
|
58
|
+
pageId: recipientID,
|
59
|
+
status: status
|
60
|
+
};
|
61
|
+
obj[this.conf.userIdField] = linking[this.conf.userIdField];
|
62
|
+
this.conf.mappingModel.add(obj, callback)
|
63
|
+
};
|
64
|
+
|
65
|
+
AccountLinkHandler.prototype.updateFbUserMapping = function (mapping, recipientID, linking, status, callback) {
|
66
|
+
mapping.status = status;
|
67
|
+
mapping.pageId = recipientID;
|
68
|
+
this.conf.mappingModel.update(mapping, callback)
|
69
|
+
};
|
70
|
+
|
71
|
+
AccountLinkHandler.prototype.updateFbUserMappingStatus = function (mapping, status, callback) {
|
72
|
+
mapping.status = status;
|
73
|
+
this.conf.mappingModel.update(mapping, callback)
|
74
|
+
};
|
75
|
+
|
76
|
+
//TODO: Transation commit/rollback
|
77
|
+
AccountLinkHandler.prototype.doLinking = function (event, callback, handler) {
|
78
|
+
var senderID = event.sender.id;
|
79
|
+
var recipientID = event.recipient.id;
|
80
|
+
var status = event.account_linking.status;
|
81
|
+
var authCode = event.account_linking.authorization_code;
|
82
|
+
|
83
|
+
var ctx = {};
|
84
|
+
var ptr = this;
|
85
|
+
async.waterfall([
|
86
|
+
function (callback) {
|
87
|
+
ptr.getFbLinking(authCode, callback)
|
88
|
+
},
|
89
|
+
function (linking, callback) {
|
90
|
+
if (null == linking) {
|
91
|
+
callback("No linking found", null);
|
92
|
+
} else {
|
93
|
+
ctx.linking = linking;
|
94
|
+
ptr.getFbUser(ctx.linking, callback);
|
95
|
+
}
|
96
|
+
},
|
97
|
+
function (user, callback) {
|
98
|
+
if (null == user) {
|
99
|
+
ptr.addFbUser(ctx.linking, authCode, handler, callback);
|
100
|
+
} else {
|
101
|
+
ptr.updateFbUser(user, authCode, callback)
|
102
|
+
}
|
103
|
+
},
|
104
|
+
function (user, callback) {
|
105
|
+
if (null == user) {
|
106
|
+
callback("No user found", null);
|
107
|
+
} else {
|
108
|
+
ctx.user = user;
|
109
|
+
ptr.getFbUserMapping(senderID, callback);
|
110
|
+
}
|
111
|
+
},
|
112
|
+
function (mapping, callback) {
|
113
|
+
if (null == mapping) {
|
114
|
+
ptr.addFbUserMapping(senderID, recipientID, ctx.linking, status, callback)
|
115
|
+
} else {
|
116
|
+
ptr.updateFbUserMapping(mapping, recipientID, ctx.linking, status, callback)
|
117
|
+
}
|
118
|
+
},
|
119
|
+
function (mapping, callback) {
|
120
|
+
if (null == mapping) {
|
121
|
+
callback("No mapping found", null);
|
122
|
+
} else {
|
123
|
+
ptr.removeFbLinking(ctx.linking, callback);
|
124
|
+
}
|
125
|
+
},
|
126
|
+
], function (err, deleted) {
|
127
|
+
if (err || !deleted) {
|
128
|
+
error('failed to process account linking for', senderID, recipientID, authCode, err);
|
129
|
+
callback('failed to process account linking', null);
|
130
|
+
} else {
|
131
|
+
debug('successfully setup account linking for', senderID, recipientID, authCode);
|
132
|
+
callback(null, senderID);
|
133
|
+
}
|
134
|
+
});
|
135
|
+
};
|
136
|
+
|
137
|
+
AccountLinkHandler.prototype.doUnlinking = function (event, callback) {
|
138
|
+
var senderID = event.sender.id;
|
139
|
+
var recipientID = event.recipient.id;
|
140
|
+
var status = event.account_linking.status;
|
141
|
+
|
142
|
+
var ptr = this;
|
143
|
+
var ctx = {};
|
144
|
+
async.waterfall([
|
145
|
+
function (callback) {
|
146
|
+
ptr.getFbUserMapping(senderID, callback);
|
147
|
+
},
|
148
|
+
function (mapping, callback) {
|
149
|
+
if (null == mapping) {
|
150
|
+
callback({
|
151
|
+
mapping: false
|
152
|
+
}, null);
|
153
|
+
} else {
|
154
|
+
ctx.mapping = mapping;
|
155
|
+
ptr.updateFbUserMappingStatus(mapping, status, callback);
|
156
|
+
}
|
157
|
+
},
|
158
|
+
function (mapping, callback) {
|
159
|
+
if (null == mapping) {
|
160
|
+
callback("No mapping found", null);
|
161
|
+
} else {
|
162
|
+
ptr.removeFbUser(ctx.mapping, callback);
|
163
|
+
}
|
164
|
+
}
|
165
|
+
], function (err, deleted) {
|
166
|
+
if (err || !deleted) {
|
167
|
+
error('failed to process account unlinking for', senderID, recipientID, err);
|
168
|
+
callback('failed to process account unlinking', null);
|
169
|
+
} else {
|
170
|
+
debug('successfully unlinked account for', senderID, recipientID);
|
171
|
+
callback(null, senderID);
|
172
|
+
}
|
173
|
+
});
|
174
|
+
};
|
175
|
+
|
176
|
+
module.exports.AccountLinkHandler = AccountLinkHandler;
|
@@ -0,0 +1,87 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const
|
4
|
+
express = require('express');
|
5
|
+
|
6
|
+
var ignores = ['/some-url/to-ignore'];
|
7
|
+
var verifySignature = true;
|
8
|
+
|
9
|
+
var messengerapi = require('../node-facebook-messenger-api').messenger();
|
10
|
+
var messenger = new messengerapi.Messenger({});
|
11
|
+
var webhookHandler = require('../node-facebook-messenger-api').webhookHandler()(messenger, {
|
12
|
+
receivedAuthentication: function (event) {
|
13
|
+
console.log('receivedAuthentication', event);
|
14
|
+
},
|
15
|
+
handleMessage: function (event) {
|
16
|
+
console.log('handleMessage', event);
|
17
|
+
if (event.message && event.message.text) {
|
18
|
+
switch (event.message.text) {
|
19
|
+
case 'profile':
|
20
|
+
messenger.getUserProfile(event.sender.id, (err, resp) => {
|
21
|
+
messenger.sendTextMessage(event.sender.id, JSON.stringify(err ? err : resp));
|
22
|
+
});
|
23
|
+
break;
|
24
|
+
case 'anverb':
|
25
|
+
messenger.analyticsEvent(messengerapi.ANALYTICS_LEVEL_VERBOSE, event.sender.id, () => {
|
26
|
+
return messenger.buildAnalyticsEvent("fb_mobile_verbose_event");
|
27
|
+
}, (err, resp) => {
|
28
|
+
messenger.sendTextMessage(event.sender.id, JSON.stringify(err ? err : resp));
|
29
|
+
});
|
30
|
+
break;
|
31
|
+
case 'ancrit':
|
32
|
+
messenger.analyticsEvent(messengerapi.ANALYTICS_LEVEL_CRITICAL, event.sender.id, () => {
|
33
|
+
return messenger.buildAnalyticsEvent("fb_mobile_critical_event");
|
34
|
+
}, (err, resp) => {
|
35
|
+
messenger.sendTextMessage(event.sender.id, JSON.stringify(err ? err : resp));
|
36
|
+
});
|
37
|
+
break;
|
38
|
+
case 'anusd':
|
39
|
+
messenger.analyticsEvent(messengerapi.ANALYTICS_LEVEL_CRITICAL, event.sender.id, () => {
|
40
|
+
return messenger.buildAnalyticsEvent("fb_mobile_purchase", { _valueToSum: 9.99, fb_currency: 'USD' });
|
41
|
+
}, (err, resp) => {
|
42
|
+
messenger.sendTextMessage(event.sender.id, JSON.stringify(err ? err : resp));
|
43
|
+
});
|
44
|
+
break;
|
45
|
+
case 'anquick':
|
46
|
+
messenger.quickAnalytics(messengerapi.ANALYTICS_LEVEL_CRITICAL, event.sender.id, "quick_analytics");
|
47
|
+
break;
|
48
|
+
case 'anitem':
|
49
|
+
messenger.quickAnalytics(messengerapi.ANALYTICS_LEVEL_CRITICAL, event.sender.id, "fb_mobile_add_to_cart", {
|
50
|
+
fb_content_type: 'blah blah blah', fb_content_id: '123456789', _valueToSum: 9.99, fb_currency: 'USD'
|
51
|
+
});
|
52
|
+
break;
|
53
|
+
default:
|
54
|
+
messenger.sendTextMessage(event.sender.id, JSON.stringify(event));
|
55
|
+
break;
|
56
|
+
}
|
57
|
+
} else {
|
58
|
+
messenger.sendTextMessage(event.sender.id, JSON.stringify(event));
|
59
|
+
}
|
60
|
+
},
|
61
|
+
receivedDeliveryConfirmation: function (event) {
|
62
|
+
console.log('receivedDeliveryConfirmation', event);
|
63
|
+
},
|
64
|
+
receivedPostback: function (event) {
|
65
|
+
console.log('receivedPostback', event);
|
66
|
+
},
|
67
|
+
receivedMessageRead: function (event) {
|
68
|
+
console.log('receivedMessageRead', event);
|
69
|
+
},
|
70
|
+
doLinking: function (event) {
|
71
|
+
console.log('doLinking', event);
|
72
|
+
},
|
73
|
+
doUnlinking: function (event) {
|
74
|
+
console.log('doUnlinking', event);
|
75
|
+
}
|
76
|
+
}, verifySignature, ignores, express.Router());
|
77
|
+
|
78
|
+
var app = express();
|
79
|
+
app.set('port', process.env.PORT || 3000);
|
80
|
+
app.use(express.static('public'));
|
81
|
+
|
82
|
+
app.use('/fb', webhookHandler);
|
83
|
+
app.listen(app.get('port'), function () {
|
84
|
+
console.log('Node app is running in http mode on port', app.get('port'));
|
85
|
+
});
|
86
|
+
|
87
|
+
|