├── Procfile ├── config └── default.json ├── CHANGELOG.md ├── package.json ├── LICENSE ├── README.md └── app.js /Procfile: -------------------------------------------------------------------------------- 1 | web: node app.js -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "appSecret": "", 3 | "pageAccessToken" : "", 4 | "validationToken": "", 5 | "serverURL": ".herokuapp.com)", 6 | "wayloKey": "" 7 | } 8 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "waylo-api-client", 3 | "version": "1.0.0", 4 | "description": "Build a hotel booking chat bot in <10 minutes", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js", 8 | "lint": "jshint --exclude node_modules .", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/waylo-api/HotelBot.git" 14 | }, 15 | "author": "Waylo (RoamAmore Inc.)", 16 | "license": "ISC", 17 | "dependencies": { 18 | "body-parser": "^1.15.0", 19 | "config": "^1.20.4", 20 | "ejs": "^2.4.2", 21 | "express": "^4.13.4", 22 | "request": "^2.79.0" 23 | }, 24 | "engines": { 25 | "node": "~4.1.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-present, RoamAmore, 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 RoamAmore Inc.. 6 | 7 | Since this 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HotelBot built using the Waylo API 2 | 3 | You can clone this repo to create your own hotel booking Facebook Messenger chatbot in 10 minutes. The hotel bookings are provided by [Waylo API](http://dev.thewaylo.com) and you earn a share of the revenue. 4 | 5 | If you want to test out a HotelBot first, try [Waylo Bot](https://m.me/thewaylo) 6 | 7 | # Build Your Hotel Booking Bot and Monetize 8 | 9 | Creating this version will give you a Facebook Messenger chat bot. 10 | 11 | ![Demo](http://i.imgur.com/I9MgSI8.gif) 12 | 13 | You can read our [FAQ](http://dev.thewaylo.com/faq.html) to learn more about the monetization. The average earnings per booking from your bot is ~ USD 10. 14 | 15 | # How is this possible in 10 minutes? 16 | 17 | This is an example of bot to bot communcation. You only set up a simple FB bot with a button. Waylo's API takes care of the Natural Language Understanding, getting access to inventory and handling payments. 18 | 19 | # Requirements 20 | 21 | Edit 5 parameters in config/default.json. 22 | 23 | ``` 24 | { 25 | "appSecret": "", 26 | "pageAccessToken" : "", 27 | "validationToken": "", 28 | "serverURL": ".herokuapp.com)", 29 | "wayloKey": "" 30 | } 31 | ``` 32 | 33 | If you have set up a few bots, create a FB app, page and head over to [Waylo Developer site](http://dev.thewaylo.com) and create your API key. You should be good to go. If not, you can follow the steps outlined here. 34 | 35 | You can see the API request [Here](https://github.com/waylo-api/HotelBot/blob/master/app.js#L348-375) 36 | 37 | You would need node, npm and git installed to follow along. 38 | 39 | ## Get Started 40 | 41 | ### *Build the server* 42 | 43 | 1. Create an app on [heroku.com](https://www.heroku.com/) or your favorite server 44 | 45 | ![Heroku](http://nicelydone.club/wp-content/uploads/2016/08/nicelydone-heroku-create.png) 46 | Take note of the server URL. 47 | 48 | 2. Create a new folder Hotelbot and clone this repo 49 | 50 | ``` 51 | git clone https://github.com/waylo-api/HotelBot.git 52 | ``` 53 | 54 | 3. Add heroku remote 55 | 56 | ``` 57 | git remote add heroku git@heroku.com:.git 58 | ``` 59 | 60 | where is the name of your Heroku app in Step 1. 61 | 62 | 4. Install dependencies 63 | 64 | ``` 65 | npm install 66 | ``` 67 | 5. Add config parameters 68 | 69 | Edit config/default.json and add the following parameters 70 | 71 | ``` 72 | "validationToken": "", 73 | "serverURL": "", 74 | ``` 75 | 76 | Add any string as your validation token. 77 | "serverURL" is the server URL in Step 1. Leave others blank for now. 78 | 79 | 6. Commit all code to Heroku 80 | 81 | ``` 82 | git add . 83 | git commit -am "first commit" 84 | git push heroku master 85 | ``` 86 | 87 | ### *Setup the Facebook App* 88 | 89 | 1. Create or configure a Facebook App here https://developers.facebook.com/apps/. Take note of the App Secret in the App Dashboard. 90 | 91 | ![App Dashboard](http://i.imgur.com/l5ly27B.jpg) 92 | 93 | 2. In the app go to Messenger tab (or add product and then add Messenger) and click Setup Webhook. Here you will put in the URL of your Heroku server(Typically https://.herokuapp.com/webhook) and a token in Step 5 of *Build the server*. Make sure to check all the subscription fields. 94 | 95 | 3. [Create a Facebook Page](https://www.facebook.com/pages/create/) 96 | 97 | 4. Get a Page Access Token and save this somewhere. 98 | 99 | ![Page Access Token](https://abhaykashyap.com/media/ckeditor/2016/11/30/fb_token_generation.png) 100 | 101 | 5. [Subscribe your page to the App](http://imgur.com/a/PPL5t). 102 | 103 | 6. Add config parameters 104 | 105 | Edit config/default.json and add the following parameters 106 | 107 | ``` 108 | "appSecret": , 109 | "pageAccessToken" : , 110 | ``` 111 | 112 | "appSecret" is your FB App Secret in Step 1 of "Setup the Facebook App". "pageAccessToken" is the Page Access Token you noted in the previous step. 113 | 114 | 115 | ### *Get Waylo Hotel API key* 116 | 117 | 1. First, sign up for a free account at [Waylo developer account](http://dev.thewaylo.com) 118 | 119 | You'll need the Page Access Token in Step 4 of *Setup the Facebook App* 120 | 121 | 2. Note the API key and add config parameters 122 | 123 | Edit config/default.json and add the following parameters 124 | 125 | ``` 126 | "wayloKey": 127 | ``` 128 | 3. Commit all changes to Heroku 129 | 130 | ``` 131 | git add . 132 | git commit -am "Waylo API first commit" 133 | git push heroku master 134 | ``` 135 | 4. You should be all set. Open your Facebook page and start chatting with your new hotel booking bot! Congrats you have just enabled bot monetization. 136 | 137 | 138 | ## License 139 | 140 | See the LICENSE file in the root directory of this source tree. Feel free to use and modify the code. 141 | 142 | 143 | #BAB - Build Awesome Bots 144 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-present, RoamAmore, Inc. 3 | * All rights reserved. 4 | 5 | * Based on https://github.com/fbsamples/messenger-platform-samples 6 | * 7 | * This source code is licensed under the license found in the 8 | * LICENSE file in the root directory of this source tree. 9 | * 10 | */ 11 | 12 | /* jshint node: true, devel: true */ 13 | 'use strict'; 14 | 15 | const 16 | bodyParser = require('body-parser'), 17 | config = require('config'), 18 | crypto = require('crypto'), 19 | express = require('express'), 20 | https = require('https'), 21 | request = require('request'); 22 | 23 | var app = express(); 24 | app.set('port', process.env.PORT || 5000); 25 | app.set('view engine', 'ejs'); 26 | app.use(bodyParser.json({ verify: verifyRequestSignature })); 27 | app.use(express.static('public')); 28 | 29 | var jsonParser = bodyParser.json(); 30 | 31 | /* 32 | * Be sure to setup your config values before running this code. You can 33 | * set them using environment variables or modifying the config file in /config. 34 | * 35 | */ 36 | 37 | // App Secret can be retrieved from the App Dashboard 38 | const APP_SECRET = (process.env.MESSENGER_APP_SECRET) ? 39 | process.env.MESSENGER_APP_SECRET : 40 | config.get('appSecret'); 41 | 42 | // Arbitrary value used to validate a webhook 43 | const VALIDATION_TOKEN = (process.env.MESSENGER_VALIDATION_TOKEN) ? 44 | (process.env.MESSENGER_VALIDATION_TOKEN) : 45 | config.get('validationToken'); 46 | 47 | // Generate a page access token for your page from the App Dashboard 48 | const PAGE_ACCESS_TOKEN = (process.env.MESSENGER_PAGE_ACCESS_TOKEN) ? 49 | (process.env.MESSENGER_PAGE_ACCESS_TOKEN) : 50 | config.get('pageAccessToken'); 51 | 52 | // URL where the app is running (include protocol). Used to point to scripts and 53 | // assets located at this address. 54 | const SERVER_URL = (process.env.SERVER_URL) ? 55 | (process.env.SERVER_URL) : 56 | config.get('serverURL'); 57 | 58 | if (!(APP_SECRET && VALIDATION_TOKEN && PAGE_ACCESS_TOKEN && SERVER_URL)) { 59 | console.error("Missing config values"); 60 | process.exit(1); 61 | } 62 | 63 | const WAYLO_API_KEY = (process.env.WAYLO_API_KEY) ? 64 | (process.env.WAYLO_API_KEY) : 65 | config.get('wayloKey'); 66 | 67 | /*************** Pause the bot. Waylo takes over ******************************/ 68 | 69 | app.get('/pause', function(req, res) { 70 | console.log("pause"); 71 | res.send(200); 72 | 73 | }); 74 | 75 | var pausedUsers = {}; // Implement in the database to make it more robust 76 | app.post('/pause', jsonParser, function (req, res) { 77 | // app.post('/pause', function (req, res) { 78 | console.log("paused"); 79 | // console.log(req.query); 80 | const userId = req.query.userId; 81 | const paused = (req.query.paused === "true"); 82 | // console.log(userId); 83 | // console.log(paused); 84 | pausedUsers[userId] = paused; 85 | res.send("ok") 86 | }) 87 | 88 | /* Messenger validation token */ 89 | 90 | app.get('/webhook', function(req, res) { 91 | if (req.query['hub.mode'] === 'subscribe' && 92 | req.query['hub.verify_token'] === VALIDATION_TOKEN) { 93 | console.log("Validating webhook"); 94 | res.status(200).send(req.query['hub.challenge']); 95 | } else { 96 | console.error("Failed validation. Make sure the validation tokens match."); 97 | res.sendStatus(403); 98 | } 99 | }); 100 | 101 | 102 | /* 103 | * All callbacks for Messenger are POST-ed. They will be sent to the same 104 | * webhook. Be sure to subscribe your app to your page to receive callbacks 105 | * for your page. 106 | * https://developers.facebook.com/docs/messenger-platform/product-overview/setup#subscribe_app 107 | * 108 | */ 109 | app.post('/webhook', function (req, res) { 110 | var data = req.body; 111 | apitrack(req.body); 112 | // Make sure this is a page subscription 113 | if (data.object == 'page') { 114 | // Iterate over each entry 115 | // There may be multiple if batched 116 | data.entry.forEach(function(pageEntry) { 117 | var pageID = pageEntry.id; 118 | var timeOfEvent = pageEntry.time; 119 | 120 | // Iterate over each messaging event 121 | pageEntry.messaging.forEach(function(messagingEvent) { 122 | 123 | const senderID= messagingEvent.sender.id; 124 | 125 | if(!pausedUsers[senderID]){ 126 | if (messagingEvent.message) { 127 | receivedMessage(messagingEvent); 128 | } else if (messagingEvent.postback) { 129 | receivedPostback(messagingEvent); 130 | } else { 131 | console.log("Webhook received unknown messagingEvent: ", messagingEvent); 132 | } 133 | } 134 | }); 135 | }); 136 | 137 | // Assume all went well. 138 | // 139 | // You must send back a 200, within 20 seconds, to let us know you've 140 | // successfully received the callback. Otherwise, the request will time out. 141 | res.sendStatus(200); 142 | } 143 | }); 144 | 145 | 146 | 147 | /* 148 | * Verify that the callback came from Facebook. Using the App Secret from 149 | * the App Dashboard, we can verify the signature that is sent with each 150 | * callback in the x-hub-signature field, located in the header. 151 | * 152 | * https://developers.facebook.com/docs/graph-api/webhooks#setup 153 | * 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 | 187 | /* 188 | * Message Event 189 | * 190 | * This event is called when a message is sent to your page. The 'message' 191 | * object format can vary depending on the kind of message that was received. 192 | * Read more at https://developers.facebook.com/docs/messenger-platform/webhook-reference/message-received 193 | * 194 | * For this example, we're going to echo any text that we get. If we get some 195 | * special keywords ('button', 'generic', 'receipt'), then we'll send back 196 | * examples of those bubbles to illustrate the special message bubbles we've 197 | * created. If we receive a message with an attachment (image, video, audio), 198 | * then we'll simply confirm that we've received the attachment. 199 | * 200 | */ 201 | 202 | function receivedMessage(event) { 203 | var senderID = event.sender.id; 204 | var recipientID = event.recipient.id; 205 | var timeOfMessage = event.timestamp; 206 | var message = event.message; 207 | 208 | console.log("Received message for user %d and page %d at %d with message:", 209 | senderID, recipientID, timeOfMessage); 210 | // console.log(JSON.stringify(message)); 211 | 212 | var isEcho = message.is_echo; 213 | var messageId = message.mid; 214 | var appId = message.app_id; 215 | var metadata = message.metadata; 216 | 217 | // You may get a text or attachment but not both 218 | var messageText = message.text; 219 | var messageAttachments = message.attachments; 220 | var quickReply = message.quick_reply; 221 | 222 | if (isEcho) { 223 | // Just logging message echoes to console 224 | console.log("Received echo for message %s and app %d with metadata %s", 225 | messageId, appId, metadata); 226 | return; 227 | } else if (quickReply) { 228 | var quickReplyPayload = quickReply.payload; 229 | console.log("Quick reply for message %s with payload %s", 230 | messageId, quickReplyPayload); 231 | 232 | // sendTextMessage(senderID, "Quick reply tapped"); 233 | return; 234 | } 235 | 236 | if (messageText) { 237 | 238 | var buttonData = { 239 | recipient: { 240 | id: senderID 241 | }, 242 | message: { 243 | attachment: { 244 | type: "template", 245 | payload: { 246 | template_type: "button", 247 | text: 'Looking to book a hotel?', 248 | buttons:[ 249 | { 250 | type: "postback", 251 | title: "Book hotel", 252 | payload: "pause=hotel" 253 | 254 | } 255 | 256 | ] 257 | } 258 | } 259 | } 260 | }; 261 | 262 | callSendAPI(buttonData); 263 | 264 | } else if (messageAttachments) { 265 | sendTextMessage(senderID, ":)"); 266 | } 267 | } 268 | 269 | 270 | /* 271 | * Postback Event 272 | * 273 | * This event is called when a postback is tapped on a Structured Message. 274 | * https://developers.facebook.com/docs/messenger-platform/webhook-reference/postback-received 275 | * 276 | */ 277 | function receivedPostback(event) { 278 | var senderID = event.sender.id; 279 | var recipientID = event.recipient.id; 280 | var timeOfPostback = event.timestamp; 281 | 282 | // The 'payload' param is a developer-defined field which is set in a postback 283 | // button for Structured Messages. 284 | var payload = event.postback.payload; 285 | 286 | console.log("Received postback for user %d and page %d with payload '%s' " + 287 | "at %d", senderID, recipientID, payload, timeOfPostback); 288 | 289 | // When a postback is called, we'll send a message back to the sender to 290 | // let them know it was successful 291 | // sendTextMessage(senderID, "Postback called"); 292 | if (payload === 'pause=hotel') { 293 | sendTextMessage(senderID,'Transferring to Waylo for your hotel booking. Return anytime by typing "quit"') 294 | } 295 | } 296 | 297 | /* 298 | * Send a text message using the Send API. 299 | * 300 | */ 301 | function sendTextMessage(recipientId, messageText) { 302 | var messageData = { 303 | recipient: { 304 | id: recipientId 305 | }, 306 | message: { 307 | text: messageText, 308 | metadata: "DEVELOPER_DEFINED_METADATA" 309 | } 310 | }; 311 | 312 | callSendAPI(messageData); 313 | } 314 | 315 | 316 | /* 317 | * Call the Send API. The message data goes in the body. If successful, we'll 318 | * get the message id in a response 319 | * 320 | */ 321 | function callSendAPI(messageData) { 322 | request({ 323 | uri: 'https://graph.facebook.com/v2.6/me/messages', 324 | qs: { access_token: PAGE_ACCESS_TOKEN }, 325 | method: 'POST', 326 | json: messageData 327 | 328 | }, function (error, response, body) { 329 | if (!error && response.statusCode == 200) { 330 | var recipientId = body.recipient_id; 331 | var messageId = body.message_id; 332 | 333 | if (messageId) { 334 | console.log("Successfully sent message with id %s to recipient %s", 335 | messageId, recipientId); 336 | } else { 337 | console.log("Successfully called Send API for recipient %s", 338 | recipientId); 339 | } 340 | } else { 341 | console.error("Failed calling Send API", response.statusCode, response.statusMessage, body.error); 342 | } 343 | }); 344 | } 345 | 346 | /************Call Waylo's API***********************/ 347 | 348 | function apitrack(message){ 349 | 350 | var userid = message.entry[0].id===message.entry[0].messaging[0].sender.id? message.entry[0].messaging[0].recipient.id : message.entry[0].messaging[0].sender.id; 351 | var pausedState = pausedUsers[userid] ? pausedUsers[userid] : false; 352 | var options = { method: 'POST', 353 | // url: 'https://wayloapi.herokuapp.com/webhook', 354 | // url: 'https://apiwaylo.herokuapp.com/facebook/v1/messages', 355 | url: 'https://api.thewaylo.com/facebook/v1/messages', 356 | qs: { 357 | apikey: WAYLO_API_KEY, 358 | paused: pausedState 359 | // userid: userid 360 | }, 361 | headers: 362 | {'content-type': 'application/json' }, 363 | body: message, 364 | json: true }; 365 | 366 | request(options, function(error, response, body){ 367 | 368 | if(error) { 369 | console.log(error) 370 | } else { 371 | console.log(response.statusCode, body) 372 | } 373 | }); 374 | 375 | } 376 | 377 | // Start server 378 | // Webhooks must be available via SSL with a certificate signed by a valid 379 | // certificate authority. 380 | app.listen(app.get('port'), function() { 381 | console.log('Node app is running on port', app.get('port')); 382 | }); 383 | 384 | module.exports = app; 385 | 386 | --------------------------------------------------------------------------------