├── 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 | --------------------------------------------------------------------------------