├── .gitignore ├── Procfile ├── README.md ├── app.json ├── config.sample.json ├── example_scripts ├── direct_messages │ ├── send-direct-message-buttons.js │ └── send-direct-message.js ├── webhook_management │ ├── add-subscription.js │ ├── create-webhook-config.js │ ├── delete-subscription.js │ ├── delete-webhook-config.js │ ├── get-webhook-config.js │ ├── trigger-crc-request.js │ └── update-wehbhook-url.js └── welcome_messages │ ├── create-welcome-message.js │ ├── delete-default-welcome-message.js │ └── set-default-welcome-message.js ├── index.js ├── message-processor.js ├── messages ├── README.md ├── config.js ├── default_welcome_message.js ├── feature_buttons.js ├── feature_location_sharing.js ├── feature_location_sharing_response.js ├── feature_quick_reply_input.js ├── feature_quick_reply_input_response.js ├── feature_quick_reply_options.js ├── feature_quick_reply_options_response.js ├── fragment_demo_features_options.js ├── messages.js └── package.json ├── package.json ├── public ├── javascript │ └── main.js └── stylesheets │ └── main.css ├── security.js ├── test.js ├── twitter.js └── views ├── pages └── index.ejs └── partials └── header.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.json 3 | .DS_Store -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twitter Webhook Boilerplate Node 2 | 3 | Starter web app for consuming events via Account Activity API (beta). 4 | 5 | **Note:** This application is only compatible with the DM-only beta version of the Account Activity API. The DM-only beta will end on August 16, 2018 and this app will no longer be fully funcional. Please see [this project](https://github.com/twitterdev/account-activity-dashboard) for updated example code to get started with the Account Activity API. 6 | 7 | ## Dependencies 8 | 9 | * A Twitter app created on [apps.twitter.com](https://apps.twitter.com/) 10 | * [Node.js](https://nodejs.org) 11 | * [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) (optional) 12 | 13 | ## Create and configure a Twitter app 14 | 15 | 1. Create a Twitter app on [apps.twitter.com](https://apps.twitter.com/) 16 | 17 | 2. On the **Permissions** tab > **Access** section > enable **Read, Write and Access direct messages**. 18 | 19 | 3. On the **Keys and Access Tokens** tab > **Your Access Token** section > click **Create my access token** button. 20 | 21 | 4. On the **Keys and Access Tokens** tab, take note of the `consumer key`, `consumer secret`, `access token` and `access token secret`. 22 | 23 | ## Setup & run the Node.js web app 24 | 25 | 1. Clone this repository: 26 | 27 | ``` 28 | git clone https://github.com/twitterdev/twitter-webhook-boilerplate-node.git 29 | ``` 30 | 31 | 2. Install Node.js dependencies: 32 | 33 | ``` 34 | npm install 35 | ``` 36 | 37 | 3. Create a new `config.json` file based on `config.sample.json` and fill in your Twitter keys and tokens. 38 | 39 | 4. Run locally: 40 | 41 | ``` 42 | node index 43 | ``` 44 | 45 | 5. Deploy app. To deploy to Heroku see "Deploy to Heroku" instructions below. 46 | 47 | Take note of your webhook URL. For example: 48 | ``` 49 | https://your.app.domain/webhooks/twitter 50 | ``` 51 | 52 | ## Configure webhook to receive events via the API 53 | 54 | 1. Create webhook config. Update `WEBHOOK_URL` in source code. 55 | 56 | ``` 57 | node example_scripts/webhook_management/create-webhook-config.js 58 | ``` 59 | Take note of returned `webhook_id`. 60 | 61 | 2. Add user subscription. Update `WEBHOOK_ID` in source code. 62 | 63 | ``` 64 | node example_scripts/webhook_management/add-subscription.js 65 | ``` 66 | Subscription will be created for user the context provided by the access tokens. 67 | 68 | 3. Test configuration by sending a DM to or from the subscribed account. You should receive a message event on your deployed webhook app. 69 | 70 | ## Example Scripts 71 | 72 | See the example scripts in the `example_scripts` directory to: 73 | 74 | * Send Direct Messages. 75 | * Manage webhook configs and subscriptions. 76 | * Setup Welcome Message deeplinks and defaults. 77 | 78 | ## Deploy to Heroku (optional) 79 | 80 | 1. Init Heroku app. 81 | 82 | ``` 83 | heroku create 84 | ``` 85 | 86 | 2. Run locally. 87 | 88 | ``` 89 | heroku local 90 | ``` 91 | 92 | 3. Configure environment variables. Set up an environment variable for every property on config.json. See Heroku documentation on [Configuration and Config Vars](https://devcenter.heroku.com/articles/config-vars). 93 | 94 | 4. Deploy to Heroku. 95 | 96 | ``` 97 | git push heroku master 98 | ``` 99 | 100 | **Note:** The free tier of Heroku will put your app to sleep after 30 minutes. On cold start, you app will have very high latency which may result in a CRC failure that deactivates your webhook. To trigger a CRC request and re-validate, run the following script with your `WEBHOOK_ID`: 101 | 102 | ``` 103 | node example_scripts/webhook_management/trigger-crc-request.js 104 | ``` 105 | 106 | 107 | ## Documentation 108 | * [Direct Message API](https://developer.twitter.com/en/docs/direct-messages/api-features) 109 | * [Account Activity API (webhook)](https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/overview) 110 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Twitter Webhook Boilerplate", 3 | "description": "A barebones Node.js app using Express 4 for Twitter DMs and webhooks.", 4 | "repository": "", 5 | "logo": "", 6 | "keywords": ["node", "express", "static"], 7 | "image": "" 8 | } 9 | -------------------------------------------------------------------------------- /config.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "TWITTER_CONSUMER_KEY": "", 3 | "TWITTER_CONSUMER_SECRET": "", 4 | "TWITTER_ACCESS_TOKEN": "", 5 | "TWITTER_ACCESS_TOKEN_SECRET": "" 6 | } -------------------------------------------------------------------------------- /example_scripts/direct_messages/send-direct-message-buttons.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | // direct message request body 17 | var dm_params = { 18 | "event": { 19 | "type": "message_create", 20 | "message_create": { 21 | "target": { 22 | "recipient_id": "4534871" 23 | }, 24 | "message_data": { 25 | "text": "Flight SF8020 from San Francisco to Montreal is ahead of schedule and will land in approximately 15 minutes. Can we help with anything else?", 26 | "ctas": [ 27 | { 28 | "type": "web_url", 29 | "label": "See flight details", 30 | "url": "https://dev.twitter.com" 31 | }, 32 | { 33 | "type": "web_url", 34 | "label": "Map it", 35 | "url": "https://dev.twitter.com" 36 | }, 37 | { 38 | "type": "web_url", 39 | "label": "Visit MyAirline.domain", 40 | "url": "https://dev.twitter.com" 41 | } 42 | ] 43 | } 44 | } 45 | } 46 | } 47 | 48 | // request options 49 | var request_options = { 50 | url: 'https://api.twitter.com/1.1/direct_messages/events/new.json', 51 | oauth: twitter_oauth, 52 | json: true, 53 | headers: { 54 | 'content-type': 'application/json' 55 | }, 56 | body: dm_params 57 | } 58 | 59 | // POST request to send Direct Message 60 | request.post(request_options, function (error, response, body) { 61 | console.log(body) 62 | }) -------------------------------------------------------------------------------- /example_scripts/direct_messages/send-direct-message.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | // direct message request body 17 | var dm_params = { 18 | "event": { 19 | "type": "message_create", 20 | "message_create": { 21 | "target": { 22 | "recipient_id": "4534871" 23 | }, 24 | "message_data": { 25 | "text": "What color bird is your fav?", 26 | "quick_reply": { 27 | "type": "options", 28 | "options": [ 29 | { 30 | "label": "Red Bird", 31 | "description": "A description about the red bird.", 32 | "metadata": "external_id_1" 33 | }, 34 | { 35 | "label": "Blue Bird", 36 | "description": "A description about the blue bird.", 37 | "metadata": "external_id_2" 38 | }, 39 | { 40 | "label": "Black Bird", 41 | "description": "A description about the black bird.", 42 | "metadata": "external_id_3" 43 | }, 44 | { 45 | "label": "White Bird", 46 | "description": "A description about the white bird.", 47 | "metadata": "external_id_4" 48 | } 49 | ] 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | // request options 57 | var request_options = { 58 | url: 'https://api.twitter.com/1.1/direct_messages/events/new.json', 59 | oauth: twitter_oauth, 60 | json: true, 61 | headers: { 62 | 'content-type': 'application/json' 63 | }, 64 | body: dm_params 65 | } 66 | 67 | // POST request to send Direct Message 68 | request.post(request_options, function (error, response, body) { 69 | console.log(body) 70 | }) -------------------------------------------------------------------------------- /example_scripts/webhook_management/add-subscription.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | var WEBHOOK_ID = 'your-webhook-id' 17 | 18 | 19 | // request options 20 | var request_options = { 21 | url: 'https://api.twitter.com/1.1/account_activity/webhooks/' + WEBHOOK_ID + '/subscriptions.json', 22 | oauth: twitter_oauth 23 | } 24 | 25 | // POST request to create webhook config 26 | request.post(request_options, function (error, response, body) { 27 | 28 | if (response.statusCode == 204) { 29 | console.log('Subscription added.') 30 | } else { 31 | console.log('User has not authorized your app.') 32 | } 33 | }) -------------------------------------------------------------------------------- /example_scripts/webhook_management/create-webhook-config.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | var WEBHOOK_URL = 'https://your-webhook-url' 17 | 18 | 19 | // request options 20 | var request_options = { 21 | url: 'https://api.twitter.com/1.1/account_activity/webhooks.json', 22 | oauth: twitter_oauth, 23 | headers: { 24 | 'Content-type': 'application/x-www-form-urlencoded' 25 | }, 26 | form: { 27 | url: WEBHOOK_URL 28 | } 29 | } 30 | 31 | // POST request to create webhook config 32 | request.post(request_options, function (error, response, body) { 33 | console.log(body) 34 | }) -------------------------------------------------------------------------------- /example_scripts/webhook_management/delete-subscription.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | var WEBHOOK_ID = 'your-webhook-id' 17 | 18 | 19 | // request options 20 | var request_options = { 21 | url: 'https://api.twitter.com/1.1/account_activity/webhooks/' + WEBHOOK_ID + '/subscriptions.json', 22 | oauth: twitter_oauth 23 | } 24 | 25 | // POST request to create webhook config 26 | request.delete(request_options, function (error, response, body) { 27 | 28 | if (response.statusCode == 204) { 29 | console.log('Subscription removed.') 30 | } else { 31 | console.log('User has not authorized your app or subscription not found.') 32 | } 33 | }) -------------------------------------------------------------------------------- /example_scripts/webhook_management/delete-webhook-config.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | var WEBHOOK_URL = 'https://8f2a1ddc.ngrok.io' 17 | 18 | 19 | function getConfig() { 20 | // request options 21 | var request_options = { 22 | url: 'https://api.twitter.com/1.1/account_activity/webhooks.json', 23 | oauth: twitter_oauth 24 | } 25 | 26 | // GET request to retreive webhook config 27 | request.get(request_options, function (error, response, body) { 28 | var configs = JSON.parse(body) 29 | 30 | if (configs.length > 0) { 31 | current_config = configs[0] 32 | console.log(current_config) 33 | removeSubscription(current_config.id); 34 | } else { 35 | console.log('App does not have a webhook config.') 36 | createConfig() 37 | } 38 | }) 39 | } 40 | 41 | 42 | function removeSubscription(config_id) { 43 | // request options 44 | var request_options = { 45 | url: 'https://api.twitter.com/1.1/account_activity/webhooks/' + config_id + '/subscriptions.json', 46 | oauth: twitter_oauth 47 | } 48 | 49 | // POST request to create webhook config 50 | request.delete(request_options, function (error, response, body) { 51 | 52 | if (response.statusCode == 204) { 53 | console.log('Subscription removed.') 54 | deleteConfig(config_id) 55 | } else { 56 | console.log('User has not authorized your app or subscription not found.') 57 | deleteConfig(config_id) 58 | } 59 | }) 60 | } 61 | 62 | 63 | function deleteConfig(config_id) { 64 | // request options 65 | var request_options = { 66 | url: 'https://api.twitter.com/1.1/account_activity/webhooks/' + config_id + '.json', 67 | oauth: twitter_oauth 68 | } 69 | 70 | // GET request to retreive webhook config 71 | request.del(request_options, function (error, response, body) { 72 | if (error || response.statusCode != 204) { 73 | console.log('Error deleting webhook config.') 74 | return 75 | } else { 76 | console.log('Webhook config deleted.') 77 | } 78 | }) 79 | } 80 | 81 | 82 | getConfig() -------------------------------------------------------------------------------- /example_scripts/webhook_management/get-webhook-config.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | 17 | // request options 18 | var request_options = { 19 | url: 'https://api.twitter.com/1.1/account_activity/webhooks.json', 20 | oauth: twitter_oauth 21 | } 22 | 23 | // GET request to retreive webhook config 24 | request.get(request_options, function (error, response, body) { 25 | console.log(body) 26 | }) -------------------------------------------------------------------------------- /example_scripts/webhook_management/trigger-crc-request.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | var WEBHOOK_ID = 'your-webhook-id' 17 | 18 | 19 | // request options 20 | var request_options = { 21 | url: 'https://api.twitter.com/1.1/account_activity/webhooks/' + WEBHOOK_ID + '.json', 22 | oauth: twitter_oauth 23 | } 24 | 25 | // POST request to create webhook config 26 | request.put(request_options, function (error, response, body) { 27 | console.log(response.statusCode) 28 | 29 | if (response.statusCode == 429) { 30 | console.log('Rate limit exceeded.') 31 | } 32 | else if (response.statusCode == 214) { 33 | console.log('Webhook URL does not meet the requirements.') 34 | } 35 | else if (response.statusCode == 204) { 36 | console.log('CRC request successful and webhook status set to valid.') 37 | } 38 | }) -------------------------------------------------------------------------------- /example_scripts/webhook_management/update-wehbhook-url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UPDATE WEBHOOK STEPS 3 | * 1. Retreive current webhook config 4 | * 2. Remove subscription for user on current webhook config 5 | * 3. Delete current webhook config 6 | * 4. Create new webhook config with new URL 7 | * 5. Add subscription for user 8 | */ 9 | 10 | 11 | var nconf = require('nconf') 12 | var request = require('request') 13 | 14 | 15 | // load config 16 | nconf.file({ file: 'config.json' }).env() 17 | 18 | // twitter authentication 19 | var twitter_oauth = { 20 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 21 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 22 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 23 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 24 | } 25 | 26 | var WEBHOOK_URL = 'https://your-webhook-url' 27 | 28 | 29 | /** 30 | * Retrieves webhook configuration 31 | */ 32 | function getConfig() { 33 | // request options 34 | var request_options = { 35 | url: 'https://api.twitter.com/1.1/account_activity/webhooks.json', 36 | oauth: twitter_oauth 37 | } 38 | 39 | // GET request to retreive webhook config 40 | request.get(request_options, function (error, response, body) { 41 | var configs = JSON.parse(body) 42 | 43 | if (configs.length > 0) { 44 | current_config = configs[0] 45 | console.log(current_config) 46 | removeSubscription(current_config.id); 47 | } else { 48 | console.log('App does not have a webhook config.') 49 | createConfig() 50 | } 51 | }) 52 | } 53 | 54 | 55 | /** 56 | * Removes existing subscription 57 | * for user context of provided access tokens 58 | */ 59 | function removeSubscription(config_id) { 60 | // request options 61 | var request_options = { 62 | url: 'https://api.twitter.com/1.1/account_activity/webhooks/' + config_id + '/subscriptions.json', 63 | oauth: twitter_oauth 64 | } 65 | 66 | // DELETE request to remove subscription 67 | request.delete(request_options, function (error, response, body) { 68 | 69 | if (response.statusCode == 204) { 70 | console.log('Subscription removed.') 71 | deleteConfig(config_id) 72 | } else { 73 | console.log('User has not authorized your app or subscription not found.') 74 | deleteConfig(config_id) 75 | } 76 | }) 77 | } 78 | 79 | 80 | /** 81 | * Deletes webhook configuration 82 | */ 83 | function deleteConfig(config_id) { 84 | // request options 85 | var request_options = { 86 | url: 'https://api.twitter.com/1.1/account_activity/webhooks/' + config_id + '.json', 87 | oauth: twitter_oauth 88 | } 89 | 90 | // DELETE request to delete webhook config 91 | request.del(request_options, function (error, response, body) { 92 | if (error || response.statusCode != 204) { 93 | console.log('Error deleting webhook config.') 94 | return 95 | } else { 96 | console.log('Webhook config deleted.') 97 | createConfig() 98 | } 99 | }) 100 | } 101 | 102 | 103 | /** 104 | * Create new webhook configuration 105 | */ 106 | function createConfig() { 107 | // request options 108 | var request_options = { 109 | url: 'https://api.twitter.com/1.1/account_activity/webhooks.json', 110 | oauth: twitter_oauth, 111 | headers: { 112 | 'Content-type': 'application/x-www-form-urlencoded' 113 | }, 114 | form: { 115 | url: WEBHOOK_URL 116 | } 117 | } 118 | 119 | // POST request to create webhook config 120 | request.post(request_options, function (error, response, body) { 121 | if (response.statusCode != 200) { 122 | console.log('Error creating webhook config.') 123 | console.log(body) 124 | } else { 125 | var config = JSON.parse(body) 126 | console.log('Webhook config created.') 127 | addSubscription(config.id) 128 | } 129 | }) 130 | } 131 | 132 | 133 | /** 134 | * Adds webhook subscription subscription 135 | * for user context of provided access tokens 136 | */ 137 | function addSubscription(webhook_id) { 138 | // request options 139 | var request_options = { 140 | url: 'https://api.twitter.com/1.1/account_activity/webhooks/' + webhook_id + '/subscriptions.json', 141 | oauth: twitter_oauth 142 | } 143 | 144 | // POST request to create webhook config 145 | request.post(request_options, function (error, response, body) { 146 | 147 | if (response.statusCode == 204) { 148 | console.log('Subscription added.') 149 | } else { 150 | console.log('User has not authorized your app.') 151 | } 152 | }) 153 | } 154 | 155 | 156 | getConfig() -------------------------------------------------------------------------------- /example_scripts/welcome_messages/create-welcome-message.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | // welcome message params 17 | var wm_params = { 18 | "welcome_message": { 19 | "message_data": { 20 | "text": "Hi! What can I do for you today?", 21 | "quick_reply": { 22 | "type": "options", 23 | "options": [ 24 | { 25 | "label": "Order Status", 26 | "description": "Check the status of an order recently placed.", 27 | "metadata": "external_id_1" 28 | }, 29 | { 30 | "label": "Return", 31 | "description": "Return a product you received less than 30 days ago.", 32 | "metadata": "external_id_2" 33 | }, 34 | { 35 | "label": "Change Order", 36 | "description": "Update or cancel an order recently placed.", 37 | "metadata": "external_id_3" 38 | }, 39 | { 40 | "label": "Talk to a Human", 41 | "description": "Talk with a customer service agent.", 42 | "metadata": "external_id_4" 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | } 49 | 50 | // request options 51 | var request_options = { 52 | url: 'https://api.twitter.com/1.1/direct_messages/welcome_messages/new.json', 53 | oauth: twitter_oauth, 54 | json: true, 55 | headers: { 56 | 'content-type': 'application/json' 57 | }, 58 | body: wm_params 59 | } 60 | 61 | // POST request to create new welcome message 62 | request.post(request_options, function (error, response, body) { 63 | 64 | if (error) { 65 | console.log('Error creating welcome message.') 66 | console.log(error) 67 | return; 68 | } 69 | 70 | // get welcome message ID 71 | var welcome_message_id = body.welcome_message.id; 72 | 73 | console.log('Welcome Message created:', welcome_message_id) 74 | 75 | // update request options 76 | request_options = { 77 | url: 'https://api.twitter.com/1.1/account/verify_credentials.json', 78 | oauth: twitter_oauth 79 | } 80 | 81 | // get current user info 82 | request.get(request_options, function (error, response, body) { 83 | 84 | if (error) { 85 | console.log('Error retreiving user data.') 86 | console.log(error) 87 | return; 88 | } 89 | 90 | // get welcome message ID 91 | var user_id = JSON.parse(body).id_str; 92 | 93 | // construct deeplink to welcome message 94 | var wm_deeplink = 'https://twitter.com/messages/compose?recipient_id=' + user_id + '&welcome_message_id=' + welcome_message_id 95 | 96 | console.log('Use this link to deeplink to this Welecome Message:', wm_deeplink) 97 | }) 98 | }) -------------------------------------------------------------------------------- /example_scripts/welcome_messages/delete-default-welcome-message.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | 17 | // request options 18 | var request_options = { 19 | url: 'https://api.twitter.com/1.1/direct_messages/welcome_messages/rules/list.json', 20 | oauth: twitter_oauth 21 | } 22 | 23 | // GET request to retreive current welcome message rules 24 | request.get(request_options, function (error, response, body) { 25 | 26 | if (error) { 27 | console.log('Error creating welcome message.') 28 | console.log(error) 29 | return 30 | } 31 | 32 | var data = JSON.parse(body); 33 | 34 | // if welcome messages rule objects returned 35 | if (data.welcome_message_rules) { 36 | // get the ID of the rule 37 | var welcome_message_rule_id = data.welcome_message_rules[0].id 38 | 39 | console.log('Deleting current default welcome message:', welcome_message_rule_id) 40 | 41 | request_options = { 42 | url: 'https://api.twitter.com/1.1/direct_messages/welcome_messages/rules/destroy.json', 43 | oauth: twitter_oauth, 44 | qs: { 45 | id: welcome_message_rule_id 46 | } 47 | } 48 | 49 | // DELETE request to remove rule for default welcome message 50 | request.del(request_options, function (error, response, body) { 51 | if (error || response.statusCode != 204) { 52 | console.log('Error deleting welcome message rule.') 53 | console.log(error) 54 | return 55 | } else { 56 | console.log('Welcome message deleted.') 57 | } 58 | }); 59 | } 60 | 61 | // no welcome messages rule objects returned 62 | else { 63 | console.log('No default welcome message has been set.') 64 | } 65 | }) 66 | -------------------------------------------------------------------------------- /example_scripts/welcome_messages/set-default-welcome-message.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require("request") 3 | 4 | 5 | // load config 6 | nconf.file({ file: 'config.json' }).env() 7 | 8 | // twitter authentication 9 | var twitter_oauth = { 10 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 11 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 12 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 13 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 14 | } 15 | 16 | // welcome message request body 17 | var wm_params = { 18 | "welcome_message": { 19 | "message_data": { 20 | "text": "Hi! What can I help you with today?", 21 | "quick_reply": { 22 | "type": "options", 23 | "options": [ 24 | { 25 | "label": "Order Status", 26 | "description": "Check the status of an order recently placed.", 27 | "metadata": "external_id_1" 28 | }, 29 | { 30 | "label": "Return", 31 | "description": "Return a product you received less than 30 days ago.", 32 | "metadata": "external_id_2" 33 | }, 34 | { 35 | "label": "Change Order", 36 | "description": "Update or cancel an order recently placed.", 37 | "metadata": "external_id_3" 38 | }, 39 | { 40 | "label": "Talk to a Human", 41 | "description": "Talk with a customer service agent.", 42 | "metadata": "external_id_4" 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | } 49 | 50 | // request options 51 | var request_options = { 52 | url: 'https://api.twitter.com/1.1/direct_messages/welcome_messages/new.json', 53 | oauth: twitter_oauth, 54 | json: true, 55 | headers: { 56 | 'content-type': 'application/json' 57 | }, 58 | body: wm_params 59 | } 60 | 61 | // POST request to create new Welcome Message 62 | request.post(request_options, function (error, response, body) { 63 | 64 | if (error) { 65 | console.log('Error creating welcome message.') 66 | console.log(error) 67 | return 68 | } 69 | 70 | console.log(body); 71 | 72 | var wm_rule_params = { 73 | "welcome_message_rule": { 74 | "welcome_message_id": body.welcome_message.id 75 | } 76 | } 77 | 78 | request_options = { 79 | url: 'https://api.twitter.com/1.1/direct_messages/welcome_messages/rules/new.json', 80 | oauth: twitter_oauth, 81 | json: true, 82 | headers: { 83 | 'content-type': 'application/json' 84 | }, 85 | body: wm_rule_params 86 | } 87 | 88 | // POST request to set Welcome Message as default 89 | request.post(request_options, function (error, response, body) { 90 | 91 | if (error) { 92 | console.log('Error creating welcome message rule.') 93 | console.log(error) 94 | return; 95 | } 96 | 97 | console.log(body) 98 | }) 99 | }) 100 | 101 | 102 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var bodyParser = require('body-parser') 3 | var app = express() 4 | var security = require('./security') 5 | var message_processor = require('./message-processor') 6 | var twitter = require('./twitter') 7 | 8 | app.set('port', (process.env.PORT || 5000)); 9 | 10 | app.use(express.static(__dirname + '/public')) 11 | app.use(bodyParser.json()) 12 | app.use(bodyParser.urlencoded({ extended: true })) 13 | 14 | app.set('views', __dirname + '/views') 15 | app.set('view engine', 'ejs') 16 | 17 | 18 | console.log(twitter) 19 | 20 | 21 | /** 22 | * Serves the home page 23 | **/ 24 | app.get('/', function(request, response) { 25 | response.render('pages/index') 26 | }) 27 | 28 | 29 | /** 30 | * Receives challenge response check (CRC) 31 | **/ 32 | app.get('/webhooks/twitter', function(request, response) { 33 | 34 | var crc_token = request.query.crc_token 35 | 36 | if (crc_token) { 37 | var hash = security.get_challenge_response(crc_token, twitter.oauth.consumer_secret) 38 | 39 | response.status(200); 40 | response.send({ 41 | response_token: 'sha256=' + hash 42 | }) 43 | } else { 44 | response.status(400); 45 | response.send('Error: crc_token missing from request.') 46 | } 47 | }) 48 | 49 | 50 | /** 51 | * Receives DM events 52 | **/ 53 | app.post('/webhooks/twitter', function(request, response) { 54 | 55 | // replace this with your own bot logic 56 | message_processor.process(request.body) 57 | 58 | response.send('200 OK') 59 | }) 60 | 61 | 62 | app.listen(app.get('port'), function() { 63 | console.log('Node app is running on port', app.get('port')) 64 | }) 65 | 66 | 67 | -------------------------------------------------------------------------------- /message-processor.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var twitter = require('./twitter') 3 | var messages = require('./messages') 4 | 5 | var mp = {} 6 | 7 | 8 | /** 9 | * Processes incoming events 10 | * @param payload the incoming webhook json payload 11 | */ 12 | mp.process = function (payload) { 13 | 14 | // 1. Loop through all direct_message_events in payload 15 | // 2. Check if event is a message_create event 16 | // 3. Check if the message is incoming by ensuring the sender_id is not itself 17 | // 4. Process event 18 | 19 | // check for direct message events 20 | if(payload.direct_message_events) { 21 | 22 | // loop through each event 23 | _.forEach(payload.direct_message_events, function(message_event) { 24 | 25 | // check if event is a message_create event and if it is incoming by validating sender ID 26 | if(message_event.type == 'message_create' && message_event.message_create.sender_id !== twitter.user_id) { 27 | 28 | // process each event individually 29 | process_message_event(message_event) 30 | } 31 | }); 32 | } 33 | } 34 | 35 | 36 | /** 37 | * Processes a single message event 38 | * @param message_event a valid Twitter DM message_event 39 | */ 40 | function process_message_event (message_event) { 41 | 42 | // 1. Check for a quick_reply_response object 43 | // 2. Access the included metadata value 44 | // 3. Get appropriate response for metadata 45 | // 4. Send response 46 | 47 | console.log('Message recieved from:', message_event.message_create.sender_id) 48 | console.log(message_event.message_create.message_data) 49 | 50 | var metadata 51 | 52 | // check for quick reply response 53 | if(message_event.message_create.message_data.quick_reply_response) { 54 | 55 | // access the metadata of the quick reply response 56 | metadata = message_event.message_create.message_data.quick_reply_response.metadata 57 | } 58 | // user submitted free form messsage 59 | else { 60 | metadata = 'default_message' 61 | } 62 | 63 | 64 | // access sender of the message to reply to 65 | var sender_id = message_event.message_create.sender_id 66 | 67 | // retrieve response for provided metadata 68 | var message_to_send = messages.get(metadata, sender_id) 69 | 70 | mp.send_message(message_to_send) 71 | } 72 | 73 | 74 | /** 75 | * sends Direct Message with Twitter API 76 | * @param msg a valid message event to sent using POST direct_messages/events/new 77 | */ 78 | mp.send_message = function (msg) { 79 | twitter.send_direct_message(msg, function (error, response, body) { 80 | console.log(body) 81 | }) 82 | } 83 | 84 | 85 | module.exports = mp -------------------------------------------------------------------------------- /messages/README.md: -------------------------------------------------------------------------------- 1 | # Messages 2 | This simple bot maps metadata values from Quick Reply responses to messages the bot should respond with. If the bot cannot find a response for a provided metadata value or metadata is not provided, the bot will respond with a default message. 3 | 4 | ## Configuring 5 | 6 | Messages are configured in `config.js`. In this file you can set the default message (for when the bots cannot find a response) as well as define all of the message responses for the bot. 7 | 8 | ### Setting the Default Message 9 | 10 | When the bot cannot find a response for a message, it will respond with the message defined for `default_message`. This vaule shoud be a JSON representation of a [Direcet Message Event Object](https://dev.twitter.com/rest/direct-messages/direct-message-event). 11 | 12 | ### Adding a Response 13 | 14 | 1. Create a message module with the following strcuture in the `messages` folder. You may want to name the module file the same as the `metadata_trigger` property. 15 | 16 | **Module Definition** 17 | 18 | ~~~~ 19 | module.exports = { 20 | metadata_trigger: 'option_1', 21 | message_event: { 22 | "event": { 23 | "type": "message_create", 24 | "message_create": { 25 | "target": { 26 | "recipient_id": undefined 27 | }, 28 | "message_data": { 29 | "text": "You selected option #1!" 30 | } 31 | } 32 | } 33 | } 34 | } 35 | ~~~~ 36 | 37 | **Module Properties** 38 | 39 | | Property | Description | 40 | |:------------------|:------------------| 41 | | metadata_trigger | The metadata value on an incomming message that will trigger the message. | 42 | | message_event | A JSON representation of a [Direcet Message Event Object](https://dev.twitter.com/rest/direct-messages/direct-message-event). Set the `recipient_id` to `undefined`. | 43 | 44 | 2. Add the module to `config.js` in the `messages_files` array. Exclude the `.js` extension. 45 | 46 | 3. Chain this response to an existing message prompt by setting the metadata in a [Quick Reply](https://dev.twitter.com/rest/direct-messages/quick-replies) prompt, [Welcome Message](https://dev.twitter.com/rest/direct-messages/welcome-messages), etc. 47 | -------------------------------------------------------------------------------- /messages/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | // this message is used if the bot can't find a response 4 | default_message: { 5 | "event": { 6 | "type": "message_create", 7 | "message_create": { 8 | "target": { 9 | "recipient_id": undefined 10 | }, 11 | "message_data": { 12 | "text": "Sorry. My knwoledge of natural language is limited. To learn more about a feature, select an option below.", 13 | "quick_reply": require('./fragment_demo_features_options') 14 | } 15 | } 16 | } 17 | }, 18 | 19 | // all message responses 20 | messages_files: [ 21 | 'feature_quick_reply_input', 22 | 'feature_quick_reply_input_response', 23 | 'feature_quick_reply_options', 24 | 'feature_quick_reply_options_response', 25 | 'feature_buttons', 26 | 'feature_location_sharing', 27 | 'feature_location_sharing_response', 28 | ] 29 | } -------------------------------------------------------------------------------- /messages/default_welcome_message.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "welcome_message": { 3 | "message_data": { 4 | "text": "Select a feature to demo.", 5 | "quick_reply": require('./fragment_demo_features_options') 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /messages/feature_buttons.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | metadata_trigger: 'feature_buttons', 3 | message_event: { 4 | "event": { 5 | "type": "message_create", 6 | "message_create": { 7 | "target": { 8 | "recipient_id": undefined 9 | }, 10 | "message_data": { 11 | "text": "You can see buttons attached to this message! Some customer experiences require actions outside of Direct Messages. Create interactions that open webviews, Tweet compose windows and other external links. \n\nNotice that you can combine Buttons with a Quick Reply. Select another feature when you are ready.", 12 | "quick_reply": require('./fragment_demo_features_options'), 13 | "ctas": [ 14 | { 15 | "type": "web_url", 16 | "label": "Follow @TwitterDev", 17 | "url": "https://twitter.com/intent/follow?screen_name=twitterdev" 18 | }, 19 | { 20 | "type": "web_url", 21 | "label": "Visit dev.twitter.com", 22 | "url": "https://dev.twitter.com" 23 | }, 24 | { 25 | "type": "web_url", 26 | "label": "Learn more about Buttons", 27 | "url": "https://dev.twitter.com/rest/direct-messages/buttons" 28 | } 29 | ] 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /messages/feature_location_sharing.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | metadata_trigger: 'feature_location_sharing', 3 | message_event: { 4 | "event": { 5 | "type": "message_create", 6 | "message_create": { 7 | "target": { 8 | "recipient_id": undefined 9 | }, 10 | "message_data": { 11 | "text": "You can see a request to share your location attached to this message! Location sharing allows business to get essential context for delivering many customer experiences -- examples include an automated location finder bot, helping a human agent interpret complaints about mobile phone service, or personalizing news content.", 12 | "quick_reply": { 13 | "type": "location", 14 | "location": { 15 | "metadata": "feature_location_sharing_response" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /messages/feature_location_sharing_response.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | metadata_trigger: 'feature_location_sharing_response', 3 | message_event: { 4 | "event": { 5 | "type": "message_create", 6 | "message_create": { 7 | "target": { 8 | "recipient_id": undefined 9 | }, 10 | "message_data": { 11 | "text": "Select another feature or learn more about Location Sharing.", 12 | "quick_reply": require('./fragment_demo_features_options'), 13 | "ctas": [ 14 | { 15 | "type": "web_url", 16 | "label": "Learn more about Location Sharing", 17 | "url": "https://dev.twitter.com/rest/direct-messages/quick-replies/location" 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /messages/feature_quick_reply_input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | metadata_trigger: 'feature_quick_reply_input', 3 | message_event: { 4 | "event": { 5 | "type": "message_create", 6 | "message_create": { 7 | "target": { 8 | "recipient_id": undefined 9 | }, 10 | "message_data": { 11 | "text": "Normally the text field shows “Send a message” but with the text input version of quick replies you can modify this to give better instructions for what people should enter (ie Email address or Zip code). Enter anything to proceed.", 12 | "quick_reply": { 13 | "type": "text_input", 14 | "text_input": { 15 | "keyboard": "number", 16 | "label": "Enter any number you want.", 17 | "metadata": "feature_quick_reply_input_response" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /messages/feature_quick_reply_input_response.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | metadata_trigger: 'feature_quick_reply_input_response', 3 | message_event: { 4 | "event": { 5 | "type": "message_create", 6 | "message_create": { 7 | "target": { 8 | "recipient_id": undefined 9 | }, 10 | "message_data": { 11 | "text": "Select another feature or learn more about Quick Reply Text Input.", 12 | "quick_reply": require('./fragment_demo_features_options'), 13 | "ctas": [ 14 | { 15 | "type": "web_url", 16 | "label": "Learn more about Text Input", 17 | "url": "https://dev.twitter.com/rest/direct-messages/quick-replies/text-input" 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /messages/feature_quick_reply_options.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | metadata_trigger: 'feature_quick_reply_options', 3 | message_event: { 4 | "event": { 5 | "type": "message_create", 6 | "message_create": { 7 | "target": { 8 | "recipient_id": undefined 9 | }, 10 | "message_data": { 11 | "text": "You can see an example of quick replies (options list) below! Quick replies let businesses prompt people with the best ways to reply to a Direct Message, whether by choosing from a list of options or guiding users to enter specific text values. On mobile quick replies will replace the keyboard, and on desktop they display below the text field.", 12 | "quick_reply": { 13 | "type": "options", 14 | "options": [ 15 | { 16 | "label": "Option 1", 17 | "description": "A short description for option 1", 18 | "metadata": "feature_quick_reply_options_response" 19 | }, 20 | { 21 | "label": "Option 2", 22 | "description": "A short description for option 2", 23 | "metadata": "feature_quick_reply_options_response" 24 | }, 25 | { 26 | "label": "Option 3", 27 | "description": "A short description for option 3", 28 | "metadata": "feature_quick_reply_options_response" 29 | } 30 | ] 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /messages/feature_quick_reply_options_response.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | metadata_trigger: 'feature_quick_reply_options_response', 3 | message_event: { 4 | "event": { 5 | "type": "message_create", 6 | "message_create": { 7 | "target": { 8 | "recipient_id": undefined 9 | }, 10 | "message_data": { 11 | "text": "Select another feature or learn more about Quick Reply Options.", 12 | "quick_reply": require('./fragment_demo_features_options'), 13 | "ctas": [ 14 | { 15 | "type": "web_url", 16 | "label": "Learn more about Options", 17 | "url": "https://dev.twitter.com/rest/direct-messages/quick-replies/options" 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /messages/fragment_demo_features_options.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "type": "options", 3 | "options": [ 4 | { 5 | "label": "Quick Reply: Options", 6 | "description": "Prompt a user to select from list of predefined options.", 7 | "metadata": "feature_quick_reply_options" 8 | }, 9 | { 10 | "label": "Quick Reply: Text Input", 11 | "description": "Prompt a user with hint text and a restricted keyboard.", 12 | "metadata": "feature_quick_reply_input" 13 | }, 14 | { 15 | "label": "Location Sharing", 16 | "description": "Prompt a user to share their location with an interactive map.", 17 | "metadata": "feature_location_sharing" 18 | }, 19 | { 20 | "label": "Buttons", 21 | "description": "Prompt a user with buttons linked to URLs.", 22 | "metadata": "feature_buttons" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /messages/messages.js: -------------------------------------------------------------------------------- 1 | var config = require('./config') 2 | 3 | var message_events = [] 4 | var messages = {} 5 | 6 | 7 | /** 8 | * Adds a message 9 | * @param msg the message json object with metadata_trigger and message_event properties 10 | */ 11 | messages.add = function (msg) { 12 | 13 | if (message_events[msg.metadata_trigger]) { 14 | throw 'Message already added for trigger: ' + msg.metadata_trigger 15 | } 16 | 17 | message_events[msg.metadata_trigger] = msg.message_event 18 | } 19 | 20 | 21 | /** 22 | * Retrieves a message and sets the recipient_id 23 | * @param metadata_trigger the metadata string attached to a message the will trigger the message 24 | * @param recipient_id the user ID the message will be sent to 25 | * @return json 26 | */ 27 | messages.get = function (metadata_trigger, recipient_id) { 28 | 29 | var msg = config.default_message 30 | 31 | if (message_events[metadata_trigger]) { 32 | msg = message_events[metadata_trigger] 33 | } 34 | 35 | msg.event.message_create.target.recipient_id = recipient_id 36 | 37 | return msg 38 | } 39 | 40 | 41 | /** 42 | * Add all message files 43 | */ 44 | config.messages_files.forEach(function (file_name) { 45 | 46 | messages.add(require('./' + file_name + '.js')) 47 | }) 48 | 49 | 50 | /** 51 | * Add default message 52 | */ 53 | messages.add({ 54 | metadata_trigge: 'default_message', 55 | message_event: config.default_message 56 | }) 57 | 58 | 59 | module.exports = messages -------------------------------------------------------------------------------- /messages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "messages", 3 | "main" : "./messages.js" 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitter-webhook-boilerplate-node", 3 | "version": "0.2.5", 4 | "description": "", 5 | "engines": { 6 | "node": "5.9.1" 7 | }, 8 | "main": "index.js", 9 | "scripts": { 10 | "start": "node index.js" 11 | }, 12 | "dependencies": { 13 | "body-parser": "^1.16.1", 14 | "ejs": "^2.5.7", 15 | "express": "4.13.3", 16 | "lodash": "^4.17.4", 17 | "nconf": "^0.8.4", 18 | "request": "^2.79.0" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "" 23 | }, 24 | "keywords": [ 25 | "node", 26 | "heroku", 27 | "express" 28 | ], 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /public/javascript/main.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | })(); -------------------------------------------------------------------------------- /public/stylesheets/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 50px 0 0 0; 3 | } -------------------------------------------------------------------------------- /security.js: -------------------------------------------------------------------------------- 1 | crypto = require('crypto') 2 | 3 | var security = {} 4 | 5 | /** 6 | * Creates a HMAC SHA-256 hash created from the app TOKEN and 7 | * your app Consumer Secret. 8 | * @param token the token provided by the incoming GET request 9 | * @return string 10 | */ 11 | security.get_challenge_response = function(crc_token, consumer_secret) { 12 | 13 | hmac = crypto.createHmac('sha256', consumer_secret).update(crc_token).digest('base64') 14 | 15 | return hmac 16 | } 17 | 18 | 19 | module.exports = security -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var security = require('./security') 2 | 3 | SIGNATURE_HEADER = 'Pi321ShXoGo6Gn5fuT5vlUU0uZbx1Gq/pSuUkope2Lo=' 4 | 5 | token = security.get_challenge_response_noencode('crc_token=OWY4ODQ2ZjgtNDljMC00NDY2LWJhMGQtYTE4NzQ0MTM2OGFi&nonce=MTQ4Nzk3OTc0NDI1MA', 'fccV6Z7YvZdPz0IPRUCVGzk7KDe9QfGtMdVHo3kgyIUwWCtPjW'); 6 | 7 | console.log(token); 8 | console.log('sha256='+SIGNATURE_HEADER); -------------------------------------------------------------------------------- /twitter.js: -------------------------------------------------------------------------------- 1 | var nconf = require('nconf') 2 | var request = require('request') 3 | 4 | 5 | 6 | var twitter = {} 7 | 8 | 9 | // load config 10 | nconf.file({ file: 'config.json' }).env(); 11 | 12 | twitter.oauth = { 13 | consumer_key: nconf.get('TWITTER_CONSUMER_KEY'), 14 | consumer_secret: nconf.get('TWITTER_CONSUMER_SECRET'), 15 | token: nconf.get('TWITTER_ACCESS_TOKEN'), 16 | token_secret: nconf.get('TWITTER_ACCESS_TOKEN_SECRET') 17 | } 18 | 19 | 20 | /** 21 | * Sends a Twitter Direct message with POST direct_messages/events/new 22 | * @param message_event valid Direct Message event json 23 | * @param callback function to pass response to 24 | */ 25 | twitter.send_direct_message = function (message_event, callback) { 26 | 27 | 28 | console.log('sending message:', message_event.event.message_create.message_data) 29 | 30 | // request options 31 | var request_options = { 32 | url: 'https://api.twitter.com/1.1/direct_messages/events/new.json', 33 | oauth: twitter.oauth, 34 | json: true, 35 | headers: { 36 | 'content-type': 'application/json' 37 | }, 38 | body: message_event 39 | } 40 | 41 | // POST request to send Direct Message 42 | request.post(request_options, function (error, response, body) { 43 | if(callback) { 44 | callback(error, response, body) 45 | } 46 | }) 47 | } 48 | 49 | 50 | /** 51 | * Retieves user ID for access tokens in config 52 | * and adds it to twitter object 53 | */ 54 | function get_user_id() { 55 | 56 | request_options = { 57 | url: 'https://api.twitter.com/1.1/account/verify_credentials.json', 58 | oauth: twitter.oauth 59 | } 60 | 61 | // get current user info 62 | request.get(request_options, function (error, response, body) { 63 | 64 | if (error) { 65 | console.log('Error retreiving user data.') 66 | console.log(error) 67 | return; 68 | } 69 | 70 | var user_id = JSON.parse(body).id_str 71 | twitter.user_id = user_id 72 | }) 73 | } 74 | 75 | get_user_id() 76 | 77 | 78 | module.exports = twitter -------------------------------------------------------------------------------- /views/pages/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../partials/header.ejs %> 5 | 6 | 7 | 8 | 9 |
10 |
11 |

#helloworld

12 |

...

13 |

Button does nothing

14 |
15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /views/partials/header.ejs: -------------------------------------------------------------------------------- 1 | CRC Tester 2 | 3 | 4 | 5 | 6 | --------------------------------------------------------------------------------