├── node
├── public
│ ├── assets
│ │ ├── test.txt
│ │ ├── like.png
│ │ ├── rift.png
│ │ ├── riftsq.png
│ │ ├── sample.mp3
│ │ ├── touch.png
│ │ ├── gearvrsq.png
│ │ ├── allofus480.mov
│ │ └── instagram_logo.gif
│ └── index.html
├── .jshintrc
├── app.js
├── config
│ └── default.json
├── .babelrc
├── CHANGELOG.md
├── package.json
├── views
│ └── authorize.ejs
├── responder.js
├── README.md
├── responders.js
├── response.js
└── node_app.js
├── .gitignore
├── README.md
├── LICENSE
└── CONTRIBUTING.md
/node/public/assets/test.txt:
--------------------------------------------------------------------------------
1 | 1234567890
--------------------------------------------------------------------------------
/node/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esversion": 6
3 | }
4 |
--------------------------------------------------------------------------------
/node/app.js:
--------------------------------------------------------------------------------
1 | require('babel-register');
2 | require('./node_app.js');
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | *.log
3 | pids
4 | node_modules
5 | .npm
6 | *.dat
7 |
--------------------------------------------------------------------------------
/node/public/assets/like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zog/fb-messenger-bot/master/node/public/assets/like.png
--------------------------------------------------------------------------------
/node/public/assets/rift.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zog/fb-messenger-bot/master/node/public/assets/rift.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Messenger Platform Sample
2 |
3 | Q&D proof-of-concept for a FB Messenger bot. Still way to go !
4 |
5 |
--------------------------------------------------------------------------------
/node/public/assets/riftsq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zog/fb-messenger-bot/master/node/public/assets/riftsq.png
--------------------------------------------------------------------------------
/node/public/assets/sample.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zog/fb-messenger-bot/master/node/public/assets/sample.mp3
--------------------------------------------------------------------------------
/node/public/assets/touch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zog/fb-messenger-bot/master/node/public/assets/touch.png
--------------------------------------------------------------------------------
/node/public/assets/gearvrsq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zog/fb-messenger-bot/master/node/public/assets/gearvrsq.png
--------------------------------------------------------------------------------
/node/public/assets/allofus480.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zog/fb-messenger-bot/master/node/public/assets/allofus480.mov
--------------------------------------------------------------------------------
/node/public/assets/instagram_logo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zog/fb-messenger-bot/master/node/public/assets/instagram_logo.gif
--------------------------------------------------------------------------------
/node/config/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "appSecret": "",
3 | "pageAccessToken": "",
4 | "validationToken": "",
5 | "serverURL": ""
6 | }
7 |
--------------------------------------------------------------------------------
/node/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "es2015",
5 | {
6 | "modules": false
7 | }
8 | ]
9 | ],
10 | "plugins": [
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/node/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## July 8, 2016
4 |
5 | * Implemented v1.1 features including: quick replies, message echoes, message reads, sending read receipts, sending typing indicators, sending gifs/videos/audio/files, adding metadata to sent messages, phone number button type
6 |
7 | ## May 11, 2016
8 |
9 | * Initial launch of the sample app including all features launched at F8
--------------------------------------------------------------------------------
/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "messenger-get-started",
3 | "version": "1.0.0",
4 | "description": "Get started example for Messenger Platform",
5 | "main": "app.js",
6 | "scripts": {
7 | "start": "node app.js",
8 | "dev": "nodemon app.js",
9 | "lint": "jshint --exclude node_modules .",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/fbsamples/messenger-platform-samples.git"
15 | },
16 | "author": "Facebook",
17 | "license": "ISC",
18 | "dependencies": {
19 | "babel-register": "^6.24.1",
20 | "babel-preset-es2015": "^6.22.0",
21 | "body-parser": "^1.15.0",
22 | "config": "^1.20.4",
23 | "ejs": "^2.4.2",
24 | "express": "^4.13.4",
25 | "request": "^2.72.0"
26 | },
27 | "engines": {
28 | "node": "~4.1.2"
29 | },
30 | "devDependencies": {
31 | "babel-preset-es2015": "^6.24.1"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/node/views/authorize.ejs:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | OAuth Test
13 |
14 |
17 |
18 |
19 |
20 | Login
21 |
22 |
23 | This should be a login page. If the user successfully logs, they should
24 | be redirect to this link:
Complete Account Link
25 |
26 |
27 |
28 | Account linking token: <%= accountLinkingToken %>
29 |
30 |
31 |
32 | Redirect URI: <%= redirectURI %>
33 |
34 |
35 |
36 | Redirect URI Successful: <%= redirectURISuccess %>
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
2 |
3 | You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
4 | copy, modify, and distribute this software in source code or binary form for use
5 | in connection with the web services and APIs provided by Facebook.
6 |
7 | As with any software that integrates with the Facebook platform, your use of
8 | this software is subject to the Facebook Developer Principles and Policies
9 | [http://developers.facebook.com/policy/]. This copyright notice shall be
10 | included in all copies or substantial portions of the software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
14 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
16 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
17 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 |
--------------------------------------------------------------------------------
/node/responder.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const response = require('./response')
4 |
5 | class Responder {
6 | constructor(test, callback, opts){
7 | this.test = test
8 | this.callback = callback
9 | this.opts = opts
10 | }
11 |
12 | match(event, message){
13 | return this.test(event, message)
14 | }
15 |
16 | answer(event, message, senderID){
17 | return this.callback(event, message, senderID)
18 | }
19 | }
20 |
21 | Responder.candidates = {}
22 |
23 | Responder.register = (test, callback, opts=null) => {
24 | if(opts === null){
25 | opts = {}
26 | }
27 | console.log(`registering ${test}`)
28 | const weight = opts.weight || 0
29 | const candidate = new Responder(test, callback, opts)
30 | if(Responder.candidates[weight] === undefined){
31 | Responder.candidates[weight] = []
32 | }
33 | Responder.candidates[weight].push(candidate)
34 | }
35 |
36 | Responder.respond = (event) => {
37 | const senderID = event.sender.id
38 | const message = event.message
39 | for(let index in Responder.candidates){
40 | let candidatesList = Responder.candidates[index]
41 | for(let candidate of candidatesList){
42 | if(candidate.match(event, message)){
43 | const _response = new response.Response(senderID)
44 | return candidate.answer(event, message, _response)
45 | }
46 | }
47 | }
48 | }
49 |
50 | exports.Responder = Responder
51 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to messenger-platform-samples
2 | We want to make contributing to this project as easy and transparent as
3 | possible.
4 |
5 | ## Pull Requests
6 | We actively welcome your pull requests.
7 |
8 | 1. Fork the repo and create your branch from `master`.
9 | 2. If you've added code that should be tested, add tests.
10 | 3. If you've changed APIs, update the documentation.
11 | 4. Ensure the test suite passes.
12 | 5. Make sure your code lints.
13 | 6. If you haven't already, complete the Contributor License Agreement ("CLA").
14 |
15 | ## Contributor License Agreement ("CLA")
16 | In order to accept your pull request, we need you to submit a CLA. You only need
17 | to do this once to work on any of Facebook's open source projects.
18 |
19 | Complete your CLA here:
20 |
21 | ## Issues
22 | We use GitHub issues to track public bugs. Please ensure your description is
23 | clear and has sufficient instructions to be able to reproduce the issue.
24 |
25 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
26 | disclosure of security bugs. In those cases, please go through the process
27 | outlined on that page and do not file a public issue.
28 |
29 | ## Coding Style
30 | * 2 spaces for indentation rather than tabs
31 | * 80 character line length
32 |
33 | ## License
34 | By contributing to, you agree that your contributions will be licensed under the LICENSE file in the root directory of this source tree.
35 |
--------------------------------------------------------------------------------
/node/public/index.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | Messenger Demo
13 |
14 |
15 |
32 |
33 | Messenger Demo
34 |
35 |
36 |
The "Send to Messenger" plugin will trigger an authentication callback to your webhook.
37 |
38 |
44 |
45 |
46 |
47 |
48 |
The "Message Us" plugin takes the user directly to Messenger and into a thread with your Page.
49 |
50 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/node/README.md:
--------------------------------------------------------------------------------
1 | # Messenger Platform Sample -- node.js
2 |
3 | This project is an example server for Messenger Platform built in Node.js. With this app, you can send it messages and it will echo them back to you. You can also see examples of the different types of Structured Messages.
4 |
5 | It contains the following functionality:
6 |
7 | * Webhook (specifically for Messenger Platform events)
8 | * Send API
9 | * Web Plugins
10 | * Messenger Platform v1.1 features
11 |
12 | Follow the [walk-through](https://developers.facebook.com/docs/messenger-platform/quickstart) to learn about this project in more detail.
13 |
14 | ## Setup
15 |
16 | Set the values in `config/default.json` before running the sample. Descriptions of each parameter can be found in `app.js`. Alternatively, you can set the corresponding environment variables as defined in `app.js`.
17 |
18 | Replace values for `APP_ID` and `PAGE_ID` in `public/index.html`.
19 |
20 | ## Run
21 |
22 | You can start the server by running `npm start`. However, the webhook must be at a public URL that the Facebook servers can reach. Therefore, running the server locally on your machine will not work.
23 |
24 | You can run this example on a cloud service provider like Heroku, Google Cloud Platform or AWS. Note that webhooks must have a valid SSL certificate, signed by a certificate authority. Read more about setting up SSL for a [Webhook](https://developers.facebook.com/docs/graph-api/webhooks#setup).
25 |
26 | ## Webhook
27 |
28 | All webhook code is in `app.js`. It is routed to `/webhook`. This project handles callbacks for authentication, messages, delivery confirmation and postbacks. More details are available at the [reference docs](https://developers.facebook.com/docs/messenger-platform/webhook-reference).
29 |
30 | ## "Send to Messenger" and "Message Us" Plugin
31 |
32 | An example of the "Send to Messenger" plugin and "Message Us" plugin are located at `index.html`. The "Send to Messenger" plugin can be used to trigger an authentication event. More details are available at the [reference docs](https://developers.facebook.com/docs/messenger-platform/plugin-reference).
33 |
34 | ## License
35 |
36 | See the LICENSE file in the root directory of this source tree. Feel free to useand modify the code.
--------------------------------------------------------------------------------
/node/responders.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const responder = require('./responder').Responder
4 | const response = require('./response').Response
5 |
6 | /// ------------------
7 | /// REGISTER RESPONDERS
8 | /// ------------------
9 |
10 |
11 | responder.register(function(event, message){
12 | let out = false
13 | const txt = message.text
14 | out = out || txt.match(/hello/i)
15 | out = out || txt.match(/salut/i)
16 | out = out || txt.match(/bonjour/i)
17 | out = out || txt.match(/hi/i)
18 | return out
19 | }, function(event, message, response){
20 | response.getUserInfo().then(()=>{ response.sendTextMessage(`Bonjour ${response.user.first_name}`) })
21 | })
22 |
23 | responder.register(function(event, message){
24 | let out = false
25 | const txt = message.text
26 | out = out || txt.match(/tronche/i)
27 | out = out || txt.match(/gueule/i)
28 | out = out || txt.match(/ma photo/i)
29 | return out
30 | }, function(event, message, response){
31 | response.getUserInfo().then(()=>{ response.sendImageMessage(response.user.profile_pic) })
32 | })
33 |
34 | responder.register(function(event, message){
35 | let out = false
36 | const txt = message.text
37 | out = out || txt.match(/(c|ç)a va.*pas/i)
38 | out = out || txt.match(/je vais vas.*pas/i)
39 | return out
40 | }, function(event, message, response){
41 | response.getUserInfo().then(()=>{ response.sendTextMessage(`Tu m'en vois désolé`) })
42 | })
43 |
44 | responder.register(function(event, message){
45 | let out = false
46 | const txt = message.text
47 | out = out || txt.match(/(c|ç)a va/i)
48 | out = out || txt.match(/je vais vas.*?/i)
49 | return out
50 | }, function(event, message, response){
51 | response.getUserInfo().then(()=>{ response.sendTextMessage(`Tu m'en vois ravi`) })
52 | })
53 |
54 | responder.register(function(event, message){
55 | let out = false
56 | const txt = message.text
57 | out = out || txt.match(/(c|ç)a va.*?/i)
58 | out = out || txt.match(/tu vas.*?/i)
59 | return out
60 | }, function(event, message, response){
61 | response.getUserInfo().then(()=>{ response.sendTextMessage(`Moi ca va bien, merci. Et toi ?`) })
62 | })
63 |
64 |
65 | responder.register(function(event, message){
66 | return true
67 | }, function(event, message, response){
68 | response.sendTextMessage(message.text)
69 | }, {weight: 10000})
70 |
71 |
--------------------------------------------------------------------------------
/node/response.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const
4 | request = require('request'),
5 | config = require('config')
6 |
7 | const PAGE_ACCESS_TOKEN = (process.env.MESSENGER_PAGE_ACCESS_TOKEN) ?
8 | (process.env.MESSENGER_PAGE_ACCESS_TOKEN) :
9 | config.get('pageAccessToken');
10 |
11 |
12 | class User{
13 |
14 | }
15 |
16 | User.cache = {}
17 |
18 | User.get = (id) => {
19 | return new Promise((resolve, reject)=>{
20 | const cached = User.cache[id]
21 | if(cached !== undefined){
22 | resolve(cached)
23 | return
24 | }
25 | request({
26 | uri: `https://graph.facebook.com/v2.6/${id}`,
27 | qs: { access_token: PAGE_ACCESS_TOKEN, fields: 'first_name,last_name,profile_pic,locale,timezone,gender' },
28 | method: 'GET'
29 |
30 | }, (error, response, body) => {
31 | if (!error && response.statusCode == 200) {
32 | User.cache[id] = body
33 | resolve(body)
34 | } else {
35 | reject("Failed calling Profile API", response.statusCode, response.statusMessage, body.error);
36 | }
37 | })
38 | })
39 | }
40 |
41 | class Response{
42 | constructor(recipientId){
43 | this.recipientId = recipientId
44 | }
45 |
46 | getUserInfo(){
47 | return User.get(this.recipientId).then((data)=>{
48 | let user = JSON.parse(data)
49 | this.user = user
50 | })
51 | }
52 |
53 | /*
54 | * Send a text message using the Send API.
55 | *
56 | */
57 |
58 | sendTextMessage(messageText) {
59 | var messageData = {
60 | recipient: {
61 | id: this.recipientId
62 | },
63 | message: {
64 | text: messageText,
65 | metadata: "DEVELOPER_DEFINED_METADATA"
66 | }
67 | };
68 |
69 | this.callSendAPI(messageData);
70 | }
71 |
72 | /*
73 | * Send an image using the Send API.
74 | *
75 | */
76 | sendImageMessage(url) {
77 | var messageData = {
78 | recipient: {
79 | id: this.recipientId
80 | },
81 | message: {
82 | attachment: {
83 | type: "image",
84 | payload: {
85 | url: url
86 | }
87 | }
88 | }
89 | };
90 |
91 | this.callSendAPI(messageData);
92 | }
93 |
94 | /*
95 | * Call the Send API. The message data goes in the body. If successful, we'll
96 | * get the message id in a response
97 | *
98 | */
99 | callSendAPI(messageData) {
100 | request({
101 | uri: 'https://graph.facebook.com/v2.6/me/messages',
102 | qs: { access_token: PAGE_ACCESS_TOKEN },
103 | method: 'POST',
104 | json: messageData
105 |
106 | }, function (error, response, body) {
107 | if (!error && response.statusCode == 200) {
108 | var recipientId = body.recipient_id;
109 | var messageId = body.message_id;
110 |
111 | if (messageId) {
112 | console.log("Successfully sent message with id %s to recipient %s",
113 | messageId, recipientId);
114 | } else {
115 | console.log("Successfully called Send API for recipient %s",
116 | recipientId);
117 | }
118 | } else {
119 | console.error("Failed calling Send API", response.statusCode, response.statusMessage, body.error);
120 | }
121 | });
122 | }
123 |
124 | }
125 |
126 | exports.Response = Response
127 |
--------------------------------------------------------------------------------
/node/node_app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 |
10 | /* jshint node: true, devel: true */
11 | 'use strict';
12 |
13 | const
14 | bodyParser = require('body-parser'),
15 | config = require('config'),
16 | crypto = require('crypto'),
17 | express = require('express'),
18 | https = require('https'),
19 | request = require('request'),
20 | responder = require('./responder').Responder
21 |
22 | require('./responders')
23 |
24 | var app = express();
25 | app.set('port', process.env.PORT || 5000);
26 | app.set('view engine', 'ejs');
27 | app.use(bodyParser.json({ verify: verifyRequestSignature }));
28 | app.use(express.static('public'));
29 |
30 | /*
31 | * Be sure to setup your config values before running this code. You can
32 | * set them using environment variables or modifying the config file in /config.
33 | *
34 | */
35 |
36 | // App Secret can be retrieved from the App Dashboard
37 | const APP_SECRET = (process.env.MESSENGER_APP_SECRET) ?
38 | process.env.MESSENGER_APP_SECRET :
39 | config.get('appSecret');
40 |
41 | // Arbitrary value used to validate a webhook
42 | const VALIDATION_TOKEN = (process.env.MESSENGER_VALIDATION_TOKEN) ?
43 | (process.env.MESSENGER_VALIDATION_TOKEN) :
44 | config.get('validationToken');
45 |
46 | // Generate a page access token for your page from the App Dashboard
47 | const PAGE_ACCESS_TOKEN = (process.env.MESSENGER_PAGE_ACCESS_TOKEN) ?
48 | (process.env.MESSENGER_PAGE_ACCESS_TOKEN) :
49 | config.get('pageAccessToken');
50 |
51 | // URL where the app is running (include protocol). Used to point to scripts and
52 | // assets located at this address.
53 | const SERVER_URL = (process.env.SERVER_URL) ?
54 | (process.env.SERVER_URL) :
55 | config.get('serverURL');
56 |
57 | if (!(APP_SECRET && VALIDATION_TOKEN && PAGE_ACCESS_TOKEN && SERVER_URL)) {
58 | console.error("Missing config values");
59 | process.exit(1);
60 | }
61 |
62 | /*
63 | * Use your own validation token. Check that the token used in the Webhook
64 | * setup is the same token used here.
65 | *
66 | */
67 | app.get('/webhook', function(req, res) {
68 | if (req.query['hub.mode'] === 'subscribe' &&
69 | req.query['hub.verify_token'] === VALIDATION_TOKEN) {
70 | console.log("Validating webhook");
71 | res.status(200).send(req.query['hub.challenge']);
72 | } else {
73 | console.error("Failed validation. Make sure the validation tokens match.");
74 | res.sendStatus(403);
75 | }
76 | });
77 |
78 |
79 | /*
80 | * All callbacks for Messenger are POST-ed. They will be sent to the same
81 | * webhook. Be sure to subscribe your app to your page to receive callbacks
82 | * for your page.
83 | * https://developers.facebook.com/docs/messenger-platform/product-overview/setup#subscribe_app
84 | *
85 | */
86 | app.post('/webhook', function (req, res) {
87 | var data = req.body;
88 |
89 | // Make sure this is a page subscription
90 | if (data.object == 'page') {
91 | // Iterate over each entry
92 | // There may be multiple if batched
93 | data.entry.forEach(function(pageEntry) {
94 | var pageID = pageEntry.id;
95 | var timeOfEvent = pageEntry.time;
96 |
97 | // Iterate over each messaging event
98 | pageEntry.messaging.forEach(function(messagingEvent) {
99 | if (messagingEvent.optin) {
100 | receivedAuthentication(messagingEvent);
101 | } else if (messagingEvent.message) {
102 | receivedMessage(messagingEvent);
103 | } else if (messagingEvent.delivery) {
104 | receivedDeliveryConfirmation(messagingEvent);
105 | } else if (messagingEvent.postback) {
106 | receivedPostback(messagingEvent);
107 | } else if (messagingEvent.read) {
108 | receivedMessageRead(messagingEvent);
109 | } else if (messagingEvent.account_linking) {
110 | receivedAccountLink(messagingEvent);
111 | } else {
112 | console.log("Webhook received unknown messagingEvent: ", messagingEvent);
113 | }
114 | });
115 | });
116 |
117 | // Assume all went well.
118 | //
119 | // You must send back a 200, within 20 seconds, to let us know you've
120 | // successfully received the callback. Otherwise, the request will time out.
121 | res.sendStatus(200);
122 | }
123 | });
124 |
125 | /*
126 | * This path is used for account linking. The account linking call-to-action
127 | * (sendAccountLinking) is pointed to this URL.
128 | *
129 | */
130 | app.get('/authorize', function(req, res) {
131 | var accountLinkingToken = req.query.account_linking_token;
132 | var redirectURI = req.query.redirect_uri;
133 |
134 | // Authorization Code should be generated per user by the developer. This will
135 | // be passed to the Account Linking callback.
136 | var authCode = "1234567890";
137 |
138 | // Redirect users to this URI on successful login
139 | var redirectURISuccess = redirectURI + "&authorization_code=" + authCode;
140 |
141 | res.render('authorize', {
142 | accountLinkingToken: accountLinkingToken,
143 | redirectURI: redirectURI,
144 | redirectURISuccess: redirectURISuccess
145 | });
146 | });
147 |
148 | /*
149 | * Verify that the callback came from Facebook. Using the App Secret from
150 | * the App Dashboard, we can verify the signature that is sent with each
151 | * callback in the x-hub-signature field, located in the header.
152 | *
153 | * https://developers.facebook.com/docs/graph-api/webhooks#setup
154 | *
155 | */
156 | function verifyRequestSignature(req, res, buf) {
157 | var signature = req.headers["x-hub-signature"];
158 |
159 | if (!signature) {
160 | // For testing, let's log an error. In production, you should throw an
161 | // error.
162 | console.error("Couldn't validate the signature.");
163 | } else {
164 | var elements = signature.split('=');
165 | var method = elements[0];
166 | var signatureHash = elements[1];
167 |
168 | var expectedHash = crypto.createHmac('sha1', APP_SECRET)
169 | .update(buf)
170 | .digest('hex');
171 |
172 | if (signatureHash != expectedHash) {
173 | throw new Error("Couldn't validate the request signature.");
174 | }
175 | }
176 | }
177 |
178 | /*
179 | * Authorization Event
180 | *
181 | * The value for 'optin.ref' is defined in the entry point. For the "Send to
182 | * Messenger" plugin, it is the 'data-ref' field. Read more at
183 | * https://developers.facebook.com/docs/messenger-platform/webhook-reference/authentication
184 | *
185 | */
186 | function receivedAuthentication(event) {
187 | var senderID = event.sender.id;
188 | var recipientID = event.recipient.id;
189 | var timeOfAuth = event.timestamp;
190 |
191 | // The 'ref' field is set in the 'Send to Messenger' plugin, in the 'data-ref'
192 | // The developer can set this to an arbitrary value to associate the
193 | // authentication callback with the 'Send to Messenger' click event. This is
194 | // a way to do account linking when the user clicks the 'Send to Messenger'
195 | // plugin.
196 | var passThroughParam = event.optin.ref;
197 |
198 | console.log("Received authentication for user %d and page %d with pass " +
199 | "through param '%s' at %d", senderID, recipientID, passThroughParam,
200 | timeOfAuth);
201 |
202 | // When an authentication is received, we'll send a message back to the sender
203 | // to let them know it was successful.
204 | sendTextMessage(senderID, "Authentication successful");
205 | }
206 |
207 | /*
208 | * Message Event
209 | *
210 | * This event is called when a message is sent to your page. The 'message'
211 | * object format can vary depending on the kind of message that was received.
212 | * Read more at https://developers.facebook.com/docs/messenger-platform/webhook-reference/message-received
213 | *
214 | * For this example, we're going to echo any text that we get. If we get some
215 | * special keywords ('button', 'generic', 'receipt'), then we'll send back
216 | * examples of those bubbles to illustrate the special message bubbles we've
217 | * created. If we receive a message with an attachment (image, video, audio),
218 | * then we'll simply confirm that we've received the attachment.
219 | *
220 | */
221 | function receivedMessage(event) {
222 | var senderID = event.sender.id;
223 | var recipientID = event.recipient.id;
224 | var timeOfMessage = event.timestamp;
225 | var message = event.message;
226 |
227 | console.log("Received message for user %d and page %d at %d with message:",
228 | senderID, recipientID, timeOfMessage);
229 | console.log(JSON.stringify(message));
230 |
231 | var isEcho = message.is_echo;
232 | var messageId = message.mid;
233 | var appId = message.app_id;
234 | var metadata = message.metadata;
235 |
236 | // You may get a text or attachment but not both
237 | var messageText = message.text;
238 | var messageAttachments = message.attachments;
239 | var quickReply = message.quick_reply;
240 |
241 | if (isEcho) {
242 | // Just logging message echoes to console
243 | console.log("Received echo for message %s and app %d with metadata %s",
244 | messageId, appId, metadata);
245 | return;
246 | } else if (quickReply) {
247 | var quickReplyPayload = quickReply.payload;
248 | console.log("Quick reply for message %s with payload %s",
249 | messageId, quickReplyPayload);
250 |
251 | sendTextMessage(senderID, "Quick reply tapped");
252 | return;
253 | }
254 |
255 | responder.respond(event)
256 | // if (messageText) {
257 |
258 | // // If we receive a text message, check to see if it matches any special
259 | // // keywords and send back the corresponding example. Otherwise, just echo
260 | // // the text we received.
261 |
262 |
263 | // switch (messageText) {
264 | // case 'image':
265 | // sendImageMessage(senderID);
266 | // break;
267 |
268 | // case 'gif':
269 | // sendGifMessage(senderID);
270 | // break;
271 |
272 | // case 'audio':
273 | // sendAudioMessage(senderID);
274 | // break;
275 |
276 | // case 'video':
277 | // sendVideoMessage(senderID);
278 | // break;
279 |
280 | // case 'file':
281 | // sendFileMessage(senderID);
282 | // break;
283 |
284 | // case 'button':
285 | // sendButtonMessage(senderID);
286 | // break;
287 |
288 | // case 'generic':
289 | // sendGenericMessage(senderID);
290 | // break;
291 |
292 | // case 'receipt':
293 | // sendReceiptMessage(senderID);
294 | // break;
295 |
296 | // case 'quick reply':
297 | // sendQuickReply(senderID);
298 | // break;
299 |
300 | // case 'read receipt':
301 | // sendReadReceipt(senderID);
302 | // break;
303 |
304 | // case 'typing on':
305 | // sendTypingOn(senderID);
306 | // break;
307 |
308 | // case 'typing off':
309 | // sendTypingOff(senderID);
310 | // break;
311 |
312 | // case 'account linking':
313 | // sendAccountLinking(senderID);
314 | // break;
315 |
316 | // default:
317 | // sendTextMessage(senderID, messageText);
318 | // }
319 | // } else if (messageAttachments) {
320 | // sendTextMessage(senderID, "Message with attachment received");
321 | // }
322 | }
323 |
324 |
325 | /*
326 | * Delivery Confirmation Event
327 | *
328 | * This event is sent to confirm the delivery of a message. Read more about
329 | * these fields at https://developers.facebook.com/docs/messenger-platform/webhook-reference/message-delivered
330 | *
331 | */
332 | function receivedDeliveryConfirmation(event) {
333 | var senderID = event.sender.id;
334 | var recipientID = event.recipient.id;
335 | var delivery = event.delivery;
336 | var messageIDs = delivery.mids;
337 | var watermark = delivery.watermark;
338 | var sequenceNumber = delivery.seq;
339 |
340 | if (messageIDs) {
341 | messageIDs.forEach(function(messageID) {
342 | console.log("Received delivery confirmation for message ID: %s",
343 | messageID);
344 | });
345 | }
346 |
347 | console.log("All message before %d were delivered.", watermark);
348 | }
349 |
350 |
351 | /*
352 | * Postback Event
353 | *
354 | * This event is called when a postback is tapped on a Structured Message.
355 | * https://developers.facebook.com/docs/messenger-platform/webhook-reference/postback-received
356 | *
357 | */
358 | function receivedPostback(event) {
359 | var senderID = event.sender.id;
360 | var recipientID = event.recipient.id;
361 | var timeOfPostback = event.timestamp;
362 |
363 | // The 'payload' param is a developer-defined field which is set in a postback
364 | // button for Structured Messages.
365 | var payload = event.postback.payload;
366 |
367 | console.log("Received postback for user %d and page %d with payload '%s' " +
368 | "at %d", senderID, recipientID, payload, timeOfPostback);
369 |
370 | // When a postback is called, we'll send a message back to the sender to
371 | // let them know it was successful
372 | sendTextMessage(senderID, "Postback called");
373 | }
374 |
375 | /*
376 | * Message Read Event
377 | *
378 | * This event is called when a previously-sent message has been read.
379 | * https://developers.facebook.com/docs/messenger-platform/webhook-reference/message-read
380 | *
381 | */
382 | function receivedMessageRead(event) {
383 | var senderID = event.sender.id;
384 | var recipientID = event.recipient.id;
385 |
386 | // All messages before watermark (a timestamp) or sequence have been seen.
387 | var watermark = event.read.watermark;
388 | var sequenceNumber = event.read.seq;
389 |
390 | console.log("Received message read event for watermark %d and sequence " +
391 | "number %d", watermark, sequenceNumber);
392 | }
393 |
394 | /*
395 | * Account Link Event
396 | *
397 | * This event is called when the Link Account or UnLink Account action has been
398 | * tapped.
399 | * https://developers.facebook.com/docs/messenger-platform/webhook-reference/account-linking
400 | *
401 | */
402 | function receivedAccountLink(event) {
403 | var senderID = event.sender.id;
404 | var recipientID = event.recipient.id;
405 |
406 | var status = event.account_linking.status;
407 | var authCode = event.account_linking.authorization_code;
408 |
409 | console.log("Received account link event with for user %d with status %s " +
410 | "and auth code %s ", senderID, status, authCode);
411 | }
412 |
413 | /*
414 | * Send an image using the Send API.
415 | *
416 | */
417 | function sendImageMessage(recipientId) {
418 | var messageData = {
419 | recipient: {
420 | id: recipientId
421 | },
422 | message: {
423 | attachment: {
424 | type: "image",
425 | payload: {
426 | url: SERVER_URL + "/assets/rift.png"
427 | }
428 | }
429 | }
430 | };
431 |
432 | callSendAPI(messageData);
433 | }
434 |
435 | /*
436 | * Send a Gif using the Send API.
437 | *
438 | */
439 | function sendGifMessage(recipientId) {
440 | var messageData = {
441 | recipient: {
442 | id: recipientId
443 | },
444 | message: {
445 | attachment: {
446 | type: "image",
447 | payload: {
448 | url: SERVER_URL + "/assets/instagram_logo.gif"
449 | }
450 | }
451 | }
452 | };
453 |
454 | callSendAPI(messageData);
455 | }
456 |
457 | /*
458 | * Send audio using the Send API.
459 | *
460 | */
461 | function sendAudioMessage(recipientId) {
462 | var messageData = {
463 | recipient: {
464 | id: recipientId
465 | },
466 | message: {
467 | attachment: {
468 | type: "audio",
469 | payload: {
470 | url: SERVER_URL + "/assets/sample.mp3"
471 | }
472 | }
473 | }
474 | };
475 |
476 | callSendAPI(messageData);
477 | }
478 |
479 | /*
480 | * Send a video using the Send API.
481 | *
482 | */
483 | function sendVideoMessage(recipientId) {
484 | var messageData = {
485 | recipient: {
486 | id: recipientId
487 | },
488 | message: {
489 | attachment: {
490 | type: "video",
491 | payload: {
492 | url: SERVER_URL + "/assets/allofus480.mov"
493 | }
494 | }
495 | }
496 | };
497 |
498 | callSendAPI(messageData);
499 | }
500 |
501 | /*
502 | * Send a file using the Send API.
503 | *
504 | */
505 | function sendFileMessage(recipientId) {
506 | var messageData = {
507 | recipient: {
508 | id: recipientId
509 | },
510 | message: {
511 | attachment: {
512 | type: "file",
513 | payload: {
514 | url: SERVER_URL + "/assets/test.txt"
515 | }
516 | }
517 | }
518 | };
519 |
520 | callSendAPI(messageData);
521 | }
522 |
523 |
524 | /*
525 | * Send a button message using the Send API.
526 | *
527 | */
528 | function sendButtonMessage(recipientId) {
529 | var messageData = {
530 | recipient: {
531 | id: recipientId
532 | },
533 | message: {
534 | attachment: {
535 | type: "template",
536 | payload: {
537 | template_type: "button",
538 | text: "This is test text",
539 | buttons:[{
540 | type: "web_url",
541 | url: "https://www.oculus.com/en-us/rift/",
542 | title: "Open Web URL"
543 | }, {
544 | type: "postback",
545 | title: "Trigger Postback",
546 | payload: "DEVELOPER_DEFINED_PAYLOAD"
547 | }, {
548 | type: "phone_number",
549 | title: "Call Phone Number",
550 | payload: "+16505551234"
551 | }]
552 | }
553 | }
554 | }
555 | };
556 |
557 | callSendAPI(messageData);
558 | }
559 |
560 | /*
561 | * Send a Structured Message (Generic Message type) using the Send API.
562 | *
563 | */
564 | function sendGenericMessage(recipientId) {
565 | var messageData = {
566 | recipient: {
567 | id: recipientId
568 | },
569 | message: {
570 | attachment: {
571 | type: "template",
572 | payload: {
573 | template_type: "generic",
574 | elements: [{
575 | title: "rift",
576 | subtitle: "Next-generation virtual reality",
577 | item_url: "https://www.oculus.com/en-us/rift/",
578 | image_url: SERVER_URL + "/assets/rift.png",
579 | buttons: [{
580 | type: "web_url",
581 | url: "https://www.oculus.com/en-us/rift/",
582 | title: "Open Web URL"
583 | }, {
584 | type: "postback",
585 | title: "Call Postback",
586 | payload: "Payload for first bubble",
587 | }],
588 | }, {
589 | title: "touch",
590 | subtitle: "Your Hands, Now in VR",
591 | item_url: "https://www.oculus.com/en-us/touch/",
592 | image_url: SERVER_URL + "/assets/touch.png",
593 | buttons: [{
594 | type: "web_url",
595 | url: "https://www.oculus.com/en-us/touch/",
596 | title: "Open Web URL"
597 | }, {
598 | type: "postback",
599 | title: "Call Postback",
600 | payload: "Payload for second bubble",
601 | }]
602 | }]
603 | }
604 | }
605 | }
606 | };
607 |
608 | callSendAPI(messageData);
609 | }
610 |
611 | /*
612 | * Send a receipt message using the Send API.
613 | *
614 | */
615 | function sendReceiptMessage(recipientId) {
616 | // Generate a random receipt ID as the API requires a unique ID
617 | var receiptId = "order" + Math.floor(Math.random()*1000);
618 |
619 | var messageData = {
620 | recipient: {
621 | id: recipientId
622 | },
623 | message:{
624 | attachment: {
625 | type: "template",
626 | payload: {
627 | template_type: "receipt",
628 | recipient_name: "Peter Chang",
629 | order_number: receiptId,
630 | currency: "USD",
631 | payment_method: "Visa 1234",
632 | timestamp: "1428444852",
633 | elements: [{
634 | title: "Oculus Rift",
635 | subtitle: "Includes: headset, sensor, remote",
636 | quantity: 1,
637 | price: 599.00,
638 | currency: "USD",
639 | image_url: SERVER_URL + "/assets/riftsq.png"
640 | }, {
641 | title: "Samsung Gear VR",
642 | subtitle: "Frost White",
643 | quantity: 1,
644 | price: 99.99,
645 | currency: "USD",
646 | image_url: SERVER_URL + "/assets/gearvrsq.png"
647 | }],
648 | address: {
649 | street_1: "1 Hacker Way",
650 | street_2: "",
651 | city: "Menlo Park",
652 | postal_code: "94025",
653 | state: "CA",
654 | country: "US"
655 | },
656 | summary: {
657 | subtotal: 698.99,
658 | shipping_cost: 20.00,
659 | total_tax: 57.67,
660 | total_cost: 626.66
661 | },
662 | adjustments: [{
663 | name: "New Customer Discount",
664 | amount: -50
665 | }, {
666 | name: "$100 Off Coupon",
667 | amount: -100
668 | }]
669 | }
670 | }
671 | }
672 | };
673 |
674 | callSendAPI(messageData);
675 | }
676 |
677 | /*
678 | * Send a message with Quick Reply buttons.
679 | *
680 | */
681 | function sendQuickReply(recipientId) {
682 | var messageData = {
683 | recipient: {
684 | id: recipientId
685 | },
686 | message: {
687 | text: "What's your favorite movie genre?",
688 | quick_replies: [
689 | {
690 | "content_type":"text",
691 | "title":"Action",
692 | "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_ACTION"
693 | },
694 | {
695 | "content_type":"text",
696 | "title":"Comedy",
697 | "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_COMEDY"
698 | },
699 | {
700 | "content_type":"text",
701 | "title":"Drama",
702 | "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_DRAMA"
703 | }
704 | ]
705 | }
706 | };
707 |
708 | callSendAPI(messageData);
709 | }
710 |
711 | /*
712 | * Send a read receipt to indicate the message has been read
713 | *
714 | */
715 | function sendReadReceipt(recipientId) {
716 | console.log("Sending a read receipt to mark message as seen");
717 |
718 | var messageData = {
719 | recipient: {
720 | id: recipientId
721 | },
722 | sender_action: "mark_seen"
723 | };
724 |
725 | callSendAPI(messageData);
726 | }
727 |
728 | /*
729 | * Turn typing indicator on
730 | *
731 | */
732 | function sendTypingOn(recipientId) {
733 | console.log("Turning typing indicator on");
734 |
735 | var messageData = {
736 | recipient: {
737 | id: recipientId
738 | },
739 | sender_action: "typing_on"
740 | };
741 |
742 | callSendAPI(messageData);
743 | }
744 |
745 | /*
746 | * Turn typing indicator off
747 | *
748 | */
749 | function sendTypingOff(recipientId) {
750 | console.log("Turning typing indicator off");
751 |
752 | var messageData = {
753 | recipient: {
754 | id: recipientId
755 | },
756 | sender_action: "typing_off"
757 | };
758 |
759 | callSendAPI(messageData);
760 | }
761 |
762 | /*
763 | * Send a message with the account linking call-to-action
764 | *
765 | */
766 | function sendAccountLinking(recipientId) {
767 | var messageData = {
768 | recipient: {
769 | id: recipientId
770 | },
771 | message: {
772 | attachment: {
773 | type: "template",
774 | payload: {
775 | template_type: "button",
776 | text: "Welcome. Link your account.",
777 | buttons:[{
778 | type: "account_link",
779 | url: SERVER_URL + "/authorize"
780 | }]
781 | }
782 | }
783 | }
784 | };
785 |
786 | callSendAPI(messageData);
787 | }
788 |
789 | /*
790 | * Call the Send API. The message data goes in the body. If successful, we'll
791 | * get the message id in a response
792 | *
793 | */
794 | function callSendAPI(messageData) {
795 | request({
796 | uri: 'https://graph.facebook.com/v2.6/me/messages',
797 | qs: { access_token: PAGE_ACCESS_TOKEN },
798 | method: 'POST',
799 | json: messageData
800 |
801 | }, function (error, response, body) {
802 | if (!error && response.statusCode == 200) {
803 | var recipientId = body.recipient_id;
804 | var messageId = body.message_id;
805 |
806 | if (messageId) {
807 | console.log("Successfully sent message with id %s to recipient %s",
808 | messageId, recipientId);
809 | } else {
810 | console.log("Successfully called Send API for recipient %s",
811 | recipientId);
812 | }
813 | } else {
814 | console.error("Failed calling Send API", response.statusCode, response.statusMessage, body.error);
815 | }
816 | });
817 | }
818 |
819 | // Start server
820 | // Webhooks must be available via SSL with a certificate signed by a valid
821 | // certificate authority.
822 | app.listen(app.get('port'), function() {
823 | console.log('Node app is running on port', app.get('port'));
824 | });
825 |
826 | module.exports = app;
827 |
828 |
--------------------------------------------------------------------------------