├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jerry Wang (https://jw84.co) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 Creating your own Facebook Messenger bot 2 | 3 | ![Alt text](/demo/Demo.gif) 4 | 5 | Facebook recently opened up their Messenger platform to enable bots to converse with users through Facebook Apps and on Facebook Pages. 6 | 7 | You can read the [documentation](https://developers.facebook.com/docs/messenger-platform/quickstart) the Messenger team prepared but it's not very clear for beginners and intermediate hackers. 8 | 9 | So instead here is how to create your own messenger bot in 15 minutes. 10 | 11 | ## 🙌 Get set 12 | 13 | Messenger bots uses a web server to process messages it receives or to figure out what messages to send. You also need to have the bot be authenticated to speak with the web server and the bot approved by Facebook to speak with the public. 14 | 15 | You can also skip the whole thing by git cloning this repository, running npm install, and run a server somewhere. 16 | 17 | ### *Build the server* 18 | 19 | 1. Install the Heroku toolbelt from here https://toolbelt.heroku.com to launch, stop and monitor instances. Sign up for free at https://www.heroku.com if you don't have an account yet. 20 | 21 | 2. Install Node from here https://nodejs.org, this will be the server environment. Then open up Terminal or Command Line Prompt and make sure you've got the very most recent version of npm by installing it again: 22 | 23 | ``` 24 | sudo npm install npm -g 25 | ``` 26 | 27 | 3. Create a new folder somewhere and let's create a new Node project. Hit Enter to accept the defaults. 28 | 29 | ``` 30 | npm init 31 | ``` 32 | 33 | 4. Install the additional Node dependencies. Express is for the server, request is for sending out messages and body-parser is to process messages. 34 | 35 | ``` 36 | npm install express request body-parser --save 37 | ``` 38 | 39 | 5. Create an index.js file in the folder and copy this into it. We will start by authenticating the bot. 40 | 41 | ```javascript 42 | 'use strict' 43 | 44 | const express = require('express') 45 | const bodyParser = require('body-parser') 46 | const request = require('request') 47 | const app = express() 48 | 49 | app.set('port', (process.env.PORT || 5000)) 50 | 51 | // Process application/x-www-form-urlencoded 52 | app.use(bodyParser.urlencoded({extended: false})) 53 | 54 | // Process application/json 55 | app.use(bodyParser.json()) 56 | 57 | // Index route 58 | app.get('/', function (req, res) { 59 | res.send('Hello world, I am a chat bot') 60 | }) 61 | 62 | // for Facebook verification 63 | app.get('/webhook/', function (req, res) { 64 | if (req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') { 65 | res.send(req.query['hub.challenge']) 66 | } 67 | res.send('Error, wrong token') 68 | }) 69 | 70 | // Spin up the server 71 | app.listen(app.get('port'), function() { 72 | console.log('running on port', app.get('port')) 73 | }) 74 | ``` 75 | 76 | 6. Make a file called Procfile and copy this. This is so Heroku can know what file to run. 77 | 78 | ``` 79 | web: node index.js 80 | ``` 81 | 82 | 7. Commit all the code with Git then create a new Heroku instance and push the code to the cloud. 83 | 84 | ``` 85 | git init 86 | git add . 87 | git commit --message "hello world" 88 | heroku create 89 | git push heroku master 90 | ``` 91 | 92 | ### *Setup the Facebook App* 93 | 94 | 1. Create or configure a Facebook App or Page here https://developers.facebook.com/apps/ 95 | 96 | ![Alt text](/demo/shot1.jpg) 97 | 98 | 2. In the app go to Messenger tab then click Setup Webhook. Here you will put in the URL of your Heroku server and a token. Make sure to check all the subscription fields. 99 | 100 | ![Alt text](/demo/shot3.jpg) 101 | 102 | 3. Get a Page Access Token and save this somewhere. 103 | 104 | ![Alt text](/demo/shot2.jpg) 105 | 106 | 4. Go back to Terminal and type in this command to trigger the Facebook app to send messages. Remember to use the token you requested earlier. 107 | 108 | ```bash 109 | curl -X POST "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" 110 | ``` 111 | 112 | ### *Setup the bot* 113 | 114 | Now that Facebook and Heroku can talk to each other we can code out the bot. 115 | 116 | 1. Add an API endpoint to index.js to process messages. Remember to also include the token we got earlier. 117 | 118 | ```javascript 119 | app.post('/webhook/', function (req, res) { 120 | let messaging_events = req.body.entry[0].messaging 121 | for (let i = 0; i < messaging_events.length; i++) { 122 | let event = req.body.entry[0].messaging[i] 123 | let sender = event.sender.id 124 | if (event.message && event.message.text) { 125 | let text = event.message.text 126 | sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200)) 127 | } 128 | } 129 | res.sendStatus(200) 130 | }) 131 | 132 | const token = "" 133 | ``` 134 | 135 | **Optional, but recommended**: keep your app secrets out of version control! 136 | - On Heroku, its easy to create dynamic runtime variables (known as [config vars](https://devcenter.heroku.com/articles/config-vars)). This can be done in the Heroku dashboard UI for your app **or** from the command line: 137 | ![Alt text](/demo/config_vars.jpg) 138 | ```bash 139 | heroku config:set FB_PAGE_ACCESS_TOKEN=fake-access-token-dhsa09uji4mlkasdfsd 140 | 141 | # view 142 | heroku config 143 | ``` 144 | 145 | - For local development: create an [environmental variable](https://en.wikipedia.org/wiki/Environment_variable) in your current session or add to your shell config file. 146 | ```bash 147 | # create env variable for current shell session 148 | export FB_PAGE_ACCESS_TOKEN=fake-access-token-dhsa09uji4mlkasdfsd 149 | 150 | # alternatively, you can add this line to your shell config 151 | # export FB_PAGE_ACCESS_TOKEN=fake-access-token-dhsa09uji4mlkasdfsd 152 | 153 | echo $FB_PAGE_ACCESS_TOKEN 154 | ``` 155 | 156 | - `config var` access at runtime 157 | ``` javascript 158 | const token = process.env.FB_PAGE_ACCESS_TOKEN 159 | ``` 160 | 161 | 162 | 3. Add a function to echo back messages 163 | 164 | ```javascript 165 | function sendTextMessage(sender, text) { 166 | let messageData = { text:text } 167 | request({ 168 | url: 'https://graph.facebook.com/v2.6/me/messages', 169 | qs: {access_token:token}, 170 | method: 'POST', 171 | json: { 172 | recipient: {id:sender}, 173 | message: messageData, 174 | } 175 | }, function(error, response, body) { 176 | if (error) { 177 | console.log('Error sending messages: ', error) 178 | } else if (response.body.error) { 179 | console.log('Error: ', response.body.error) 180 | } 181 | }) 182 | } 183 | ``` 184 | 185 | 4. Commit the code again and push to Heroku 186 | 187 | ``` 188 | git add . 189 | git commit -m 'updated the bot to speak' 190 | git push heroku master 191 | ``` 192 | 193 | 5. Go to the Facebook Page and click on Message to start chatting! 194 | 195 | ![Alt text](/demo/shot4.jpg) 196 | 197 | ## ⚙ Customize what the bot says 198 | 199 | ### *Send a Structured Message* 200 | 201 | Facebook Messenger can send messages structured as cards or buttons. 202 | 203 | ![Alt text](/demo/shot5.jpg) 204 | 205 | 1. Copy the code below to index.js to send a test message back as two cards. 206 | 207 | ```javascript 208 | function sendGenericMessage(sender) { 209 | let messageData = { 210 | "attachment": { 211 | "type": "template", 212 | "payload": { 213 | "template_type": "generic", 214 | "elements": [{ 215 | "title": "First card", 216 | "subtitle": "Element #1 of an hscroll", 217 | "image_url": "http://messengerdemo.parseapp.com/img/rift.png", 218 | "buttons": [{ 219 | "type": "web_url", 220 | "url": "https://www.messenger.com", 221 | "title": "web url" 222 | }, { 223 | "type": "postback", 224 | "title": "Postback", 225 | "payload": "Payload for first element in a generic bubble", 226 | }], 227 | }, { 228 | "title": "Second card", 229 | "subtitle": "Element #2 of an hscroll", 230 | "image_url": "http://messengerdemo.parseapp.com/img/gearvr.png", 231 | "buttons": [{ 232 | "type": "postback", 233 | "title": "Postback", 234 | "payload": "Payload for second element in a generic bubble", 235 | }], 236 | }] 237 | } 238 | } 239 | } 240 | request({ 241 | url: 'https://graph.facebook.com/v2.6/me/messages', 242 | qs: {access_token:token}, 243 | method: 'POST', 244 | json: { 245 | recipient: {id:sender}, 246 | message: messageData, 247 | } 248 | }, function(error, response, body) { 249 | if (error) { 250 | console.log('Error sending messages: ', error) 251 | } else if (response.body.error) { 252 | console.log('Error: ', response.body.error) 253 | } 254 | }) 255 | } 256 | ``` 257 | 258 | 2. Update the webhook API to look for special messages to trigger the cards 259 | 260 | ```javascript 261 | app.post('/webhook/', function (req, res) { 262 | let messaging_events = req.body.entry[0].messaging 263 | for (let i = 0; i < messaging_events.length; i++) { 264 | let event = req.body.entry[0].messaging[i] 265 | let sender = event.sender.id 266 | if (event.message && event.message.text) { 267 | let text = event.message.text 268 | if (text === 'Generic') { 269 | sendGenericMessage(sender) 270 | continue 271 | } 272 | sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200)) 273 | } 274 | } 275 | res.sendStatus(200) 276 | }) 277 | ``` 278 | 279 | ### *Act on what the user messages* 280 | 281 | What happens when the user clicks on a message button or card though? Let's update the webhook API one more time to send back a postback function. 282 | 283 | ```javascript 284 | app.post('/webhook/', function (req, res) { 285 | let messaging_events = req.body.entry[0].messaging 286 | for (let i = 0; i < messaging_events.length; i++) { 287 | let event = req.body.entry[0].messaging[i] 288 | let sender = event.sender.id 289 | if (event.message && event.message.text) { 290 | let text = event.message.text 291 | if (text === 'Generic') { 292 | sendGenericMessage(sender) 293 | continue 294 | } 295 | sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200)) 296 | } 297 | if (event.postback) { 298 | let text = JSON.stringify(event.postback) 299 | sendTextMessage(sender, "Postback received: "+text.substring(0, 200), token) 300 | continue 301 | } 302 | } 303 | res.sendStatus(200) 304 | }) 305 | ``` 306 | 307 | Git add, commit, and push to Heroku again. 308 | 309 | Now when you chat with the bot and type 'Generic' you can see this. 310 | 311 | ![Alt text](/demo/shot6.jpg) 312 | 313 | ## 📡 How to share your bot 314 | 315 | ### *Add a chat button to your webpage* 316 | 317 | Go [here](https://developers.facebook.com/docs/messenger-platform/plugin-reference) to learn how to add a chat button your page. 318 | 319 | ### *Create a shortlink* 320 | 321 | You can use https://m.me/ to have someone start a chat. 322 | 323 | ## 💡 What's next? 324 | 325 | You can learn how to get your bot approved for public use [here](https://developers.facebook.com/docs/messenger-platform/app-review). 326 | 327 | You can also connect an AI brain to your bot [here](https://wit.ai) 328 | 329 | Read about all things chat bots with the ChatBots Magazine [here](https://medium.com/chat-bots) 330 | 331 | You can also design Messenger bots in Sketch with the [Bots UI Kit](https://bots.mockuuups.com)! 332 | 333 | ## How I can help 334 | 335 | I build and design bots all day. Email me for help! 336 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | //This is still work in progress 2 | /* 3 | Please report any bugs to nicomwaks@gmail.com 4 | 5 | i have added console.log on line 48 6 | 7 | 8 | 9 | 10 | */ 11 | 'use strict' 12 | 13 | const express = require('express') 14 | const bodyParser = require('body-parser') 15 | const request = require('request') 16 | const app = express() 17 | 18 | app.set('port', (process.env.PORT || 5000)) 19 | 20 | // parse application/x-www-form-urlencoded 21 | app.use(bodyParser.urlencoded({extended: false})) 22 | 23 | // parse application/json 24 | app.use(bodyParser.json()) 25 | 26 | // index 27 | app.get('/', function (req, res) { 28 | res.send('hello world i am a secret bot') 29 | }) 30 | 31 | // for facebook verification 32 | app.get('/webhook/', function (req, res) { 33 | if (req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') { 34 | res.send(req.query['hub.challenge']) 35 | } else { 36 | res.send('Error, wrong token') 37 | } 38 | }) 39 | 40 | // to post data 41 | app.post('/webhook/', function (req, res) { 42 | let messaging_events = req.body.entry[0].messaging 43 | for (let i = 0; i < messaging_events.length; i++) { 44 | let event = req.body.entry[0].messaging[i] 45 | let sender = event.sender.id 46 | if (event.message && event.message.text) { 47 | let text = event.message.text 48 | if (text === 'Generic'){ 49 | console.log("welcome to chatbot") 50 | //sendGenericMessage(sender) 51 | continue 52 | } 53 | sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200)) 54 | } 55 | if (event.postback) { 56 | let text = JSON.stringify(event.postback) 57 | sendTextMessage(sender, "Postback received: "+text.substring(0, 200), token) 58 | continue 59 | } 60 | } 61 | res.sendStatus(200) 62 | }) 63 | 64 | 65 | // recommended to inject access tokens as environmental variables, e.g. 66 | // const token = process.env.FB_PAGE_ACCESS_TOKEN 67 | const token = "" 68 | 69 | function sendTextMessage(sender, text) { 70 | let messageData = { text:text } 71 | 72 | request({ 73 | url: 'https://graph.facebook.com/v2.6/me/messages', 74 | qs: {access_token:token}, 75 | method: 'POST', 76 | json: { 77 | recipient: {id:sender}, 78 | message: messageData, 79 | } 80 | }, function(error, response, body) { 81 | if (error) { 82 | console.log('Error sending messages: ', error) 83 | } else if (response.body.error) { 84 | console.log('Error: ', response.body.error) 85 | } 86 | }) 87 | } 88 | 89 | function sendGenericMessage(sender) { 90 | let messageData = { 91 | "attachment": { 92 | "type": "template", 93 | "payload": { 94 | "template_type": "generic", 95 | "elements": [{ 96 | "title": "First card", 97 | "subtitle": "Element #1 of an hscroll", 98 | "image_url": "http://messengerdemo.parseapp.com/img/rift.png", 99 | "buttons": [{ 100 | "type": "web_url", 101 | "url": "https://www.messenger.com", 102 | "title": "web url" 103 | }, { 104 | "type": "postback", 105 | "title": "Postback", 106 | "payload": "Payload for first element in a generic bubble", 107 | }], 108 | }, { 109 | "title": "Second card", 110 | "subtitle": "Element #2 of an hscroll", 111 | "image_url": "http://messengerdemo.parseapp.com/img/gearvr.png", 112 | "buttons": [{ 113 | "type": "postback", 114 | "title": "Postback", 115 | "payload": "Payload for second element in a generic bubble", 116 | }], 117 | }] 118 | } 119 | } 120 | } 121 | request({ 122 | url: 'https://graph.facebook.com/v2.6/me/messages', 123 | qs: {access_token:token}, 124 | method: 'POST', 125 | json: { 126 | recipient: {id:sender}, 127 | message: messageData, 128 | } 129 | }, function(error, response, body) { 130 | if (error) { 131 | console.log('Error sending messages: ', error) 132 | } else if (response.body.error) { 133 | console.log('Error: ', response.body.error) 134 | } 135 | }) 136 | } 137 | 138 | // spin spin sugar 139 | app.listen(app.get('port'), function() { 140 | console.log('running on port', app.get('port')) 141 | }) 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretbots", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "body-parser": "^1.15.0", 13 | "express": "^4.13.4", 14 | "request": "^2.71.0" 15 | } 16 | } 17 | --------------------------------------------------------------------------------