├── .env.example ├── .github └── CODE_OF_CONDUCT.md ├── README.md ├── docs ├── img │ ├── bot-hello-world.png │ ├── cover.gif │ ├── create-app.png │ ├── create-bot-user.png │ ├── fb-config-module.png │ ├── fb-create-project.png │ ├── interactive-messages-request.png │ ├── slack-auth-tokens.png │ ├── slack-event-subscriptions-2.png │ ├── slack-event-subscriptions.png │ ├── slack-initial-permission-scopes.png │ ├── slack-manage-distribution.png │ ├── slack-permission-scopes.png │ ├── sms-via-slack.gif │ ├── twilio-buy-number.png │ ├── twilio-config.gif │ └── twilio-set-webhook.png ├── section-01.md ├── section-02.md ├── section-03.md ├── section-04.md ├── section-05.md └── section-06.md ├── index.js └── package.json /.env.example: -------------------------------------------------------------------------------- 1 | SLACK_CLIENT_ID= 2 | SLACK_CLIENT_SECRET= 3 | SLACK_VERIFICATION_TOKEN= 4 | SLACK_BOT_TOKEN= 5 | SLACK_AUTH_TOKEN= 6 | 7 | TWILIO_SID= 8 | TWILIO_AUTH_TOKEN= 9 | 10 | FB_API_KEY= 11 | FB_AUTH_DOMAIN= 12 | FB_DB_URL= 13 | FB_PROJECT_ID= 14 | FB_STORAGE_BUCKET= 15 | FB_MESSAGING_SENDER_ID= -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Introduction 4 | 5 | Diversity and inclusion make our community strong. We encourage participation from the most varied and diverse backgrounds possible and want to be very clear about where we stand. 6 | 7 | Our goal is to maintain a safe, helpful and friendly community for everyone, regardless of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other defining characteristic. 8 | 9 | This code and related procedures also apply to unacceptable behavior occurring outside the scope of community activities, in all community venues (online and in-person) as well as in all one-on-one communications, and anywhere such behavior has the potential to adversely affect the safety and well-being of community members. 10 | 11 | For more information on our code of conduct, please visit [https://slackhq.github.io/code-of-conduct](https://slackhq.github.io/code-of-conduct) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TalkBot: Using Threads to Build a Twilio-powered Slack Bot 2 | 3 | This tutorial walks through building Talkbot 🤖, an example application written in Node that allows you to direct incoming Twilio SMS messages to a Slack channel. We'll use the Slack Events and Web API, plug into Twilio and Firebase, utilize Slack threads, and listen/respond to message reactions. 4 | 5 | If you don't want to develop the app locally, you can 6 | 7 | [![Remix on Glitch](https://cdn.glitch.com/2703baf2-b643-4da7-ab91-7ee2a2d00b5b%2Fremix-button.svg)](https://glitch.com/edit/#!/remix/talkbot) 8 | > 💡 *If you're going to use Glitch, you'll still have to go through the setup to generate tokens for your `.env` and connect the app to Firebase and Twilio. But instead of using an ngrok URL, you'll be able to use your Glitch URL.* 9 | 10 | Let's get started! 🎉👩‍💻 11 | 12 | ![TalkBot Overview](docs/img/cover.gif) 13 | 14 | ### This tutorial will cover: 15 | * Setting up a Slack application and bot user, then installing it onto your team 16 | * Using Twilio to send and receive SMS from your Slack app 17 | * Integrating Firebase and threads to organize conversations across SMS and Slack 18 | * Listening for and responding to Slack events 19 | 20 | ### Technologies we'll be using: 21 | * [Slack Developer Kit for Node.js](https://github.com/slackapi/node-slack-sdk) 22 | * [Slack Events API Module](https://github.com/slackapi/node-slack-events-api) 23 | * [Twilio](https://www.twilio.com/docs) - *Cloud communications platform* 24 | * [Firebase](https://firebase.google.com) - *Database solution* 25 | * [Ngrok](https://api.slack.com/tutorials/tunneling-with-ngrok) - *Secure tunneling* 26 | * Body Parser - *Node.js body parsing middleware* 27 | * Express - *Web framework for Node* 28 | 29 | ## Getting Started 30 | 31 | This tutorial is split up into 6 different sections. If you get stuck on any section at any time, you can look at the code in this repository to help you out. We've also included some resources and helpful links below for further reading. 32 | 33 | * **Section 00: Overview and Introduction** 👈 34 | * [Section 01: Setting up your Slack Bot](docs/section-01.md) 35 | * [Section 02: Integrating your Bot with Twilio](docs/section-02.md) 36 | * [Section 03: Adding Threads to Your Conversations](docs/section-03.md) 37 | * [Section 04: Responding to Message Events](docs/section-04.md) 38 | * [Section 05: Responding to SMS via Slack](docs/section-05.md) 39 | * [Section 06: Adding OAuth Configuration](docs/section-06.md) 40 | 41 | ## Getting Help 42 | 43 | If you're having trouble getting through the tutorial, or a section of the tutorial, make sure to glance through the [Slack API documentation](https://api.slack.com). The Slack Developer community is also a great resource to plug into. If you can't find anything by Googling or looking on [Stack Overflow](https://stackoverflow.com/questions/tagged/slack-api), try asking the [Dev4Slack](http://dev4slack.xoxco.com) Slack team. 44 | 45 | You can also view and remix the working project on Glitch 46 | 47 | [![Remix on Glitch](https://cdn.glitch.com/2703baf2-b643-4da7-ab91-7ee2a2d00b5b%2Fremix-button.svg)](https://glitch.com/edit/#!/remix/talkbot) 48 | -------------------------------------------------------------------------------- /docs/img/bot-hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/bot-hello-world.png -------------------------------------------------------------------------------- /docs/img/cover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/cover.gif -------------------------------------------------------------------------------- /docs/img/create-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/create-app.png -------------------------------------------------------------------------------- /docs/img/create-bot-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/create-bot-user.png -------------------------------------------------------------------------------- /docs/img/fb-config-module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/fb-config-module.png -------------------------------------------------------------------------------- /docs/img/fb-create-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/fb-create-project.png -------------------------------------------------------------------------------- /docs/img/interactive-messages-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/interactive-messages-request.png -------------------------------------------------------------------------------- /docs/img/slack-auth-tokens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/slack-auth-tokens.png -------------------------------------------------------------------------------- /docs/img/slack-event-subscriptions-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/slack-event-subscriptions-2.png -------------------------------------------------------------------------------- /docs/img/slack-event-subscriptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/slack-event-subscriptions.png -------------------------------------------------------------------------------- /docs/img/slack-initial-permission-scopes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/slack-initial-permission-scopes.png -------------------------------------------------------------------------------- /docs/img/slack-manage-distribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/slack-manage-distribution.png -------------------------------------------------------------------------------- /docs/img/slack-permission-scopes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/slack-permission-scopes.png -------------------------------------------------------------------------------- /docs/img/sms-via-slack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/sms-via-slack.gif -------------------------------------------------------------------------------- /docs/img/twilio-buy-number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/twilio-buy-number.png -------------------------------------------------------------------------------- /docs/img/twilio-config.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/twilio-config.gif -------------------------------------------------------------------------------- /docs/img/twilio-set-webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/javascript-twilio-talkbot/dbb83e90a2cfef9794ac88d5590ec1eeeff37cd9/docs/img/twilio-set-webhook.png -------------------------------------------------------------------------------- /docs/section-01.md: -------------------------------------------------------------------------------- 1 | # Section 01: Setting up your Slack Bot 🤖 2 | 3 | * [Section 00: Overview and Introduction](../README.md) 4 | * **Section 01: Setting up your Slack Bot** 👈 5 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 6 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 7 | * [Section 04: Responding to Message Events](section-04.md) 8 | * [Section 05: Responding to SMS via Slack](section-05.md) 9 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) 10 | 11 | Before we dig into Twilio integrations and making our bot useful, we have to set up and configure an app with proper access permissions. Slack has a few different APIs you can use for bot-building, but we'll be using the Events and Web APIs. We can easily acess and use these APIs with the [Slack SDK](https://github.com/slackapi/node-slack-sdk) and the [Slack Events API module](https://github.com/slackapi/node-slack-events-api) 12 | 13 | > 💡 *If you want to learn about the differences between the Slack APIs, you should [check out the API documentation](https://api.slack.com/slack-apps).* 14 | 15 | ## Starting the Code 16 | To start, we're going to create a new node project and install some project dependencies. Create a new folder to hold your app and, inside of that folder, initialize your project. 17 | 18 | ```sh 19 | $ npm init 20 | ``` 21 | 22 | > 💡 *If you don't have Node.js and NPM up and running, take a look at [NPM's tutorial](https://docs.npmjs.com/getting-started/installing-node)* 23 | 24 | Now, we're going to create an `index.js` file in the root of our project. This is the entry file for our application, where our bot's logic and routing will take place. Below, I've added some setup code that you can copy. You can see that we're going to have to install some dependencies. 25 | 26 | ```js 27 | /** 28 | * Dependencies: 29 | * Dotenv, Express, BodyParser, Slack Web Client, Slack Events API 30 | */ 31 | 32 | require('dotenv').config(); 33 | 34 | const express = require('express'); 35 | const bodyParser = require('body-parser'); 36 | const WebClient = require('@slack/client').WebClient; 37 | const createSlackEventAdapter = require('@slack/events-api').createSlackEventAdapter; 38 | 39 | ``` 40 | 41 | > 💡 Body parser is middleware for express, which allows us to parse and handle requests easily. To learn more, [check out their documentation](https://github.com/expressjs/body-parser). 42 | 43 | To install the necessary dependencies, use the following command in your project folder: 44 | 45 | ```sh 46 | $ npm install express dotenv body-parser @slack/client @slack/events-api --save 47 | ``` 48 | 49 | > 🔑 *If you don't want to install the dependencies manually, you can just replace the `dependencies` in your project's `package.json` with the one in this repo, then run `npm install`.* 50 | 51 | ## Hooking it up to Slack 🔌 52 | Now let's create an app on Slack and add the API credentials to our Node project. To create a new app, go to [the Slack developer site](https://api.slack.com/apps) and sign in to the team that you'll be testing your app on. 53 | 54 | Go ahead and click `Create New App`. This will bring up a dialog box, where you can name your app. Make sure to select the team you want to test your app on from the dropdown menu, under `Slack Development Team`. Don't worry, we talk about installing your app onto other teams in [section 6](section-06.md). After you're finished, click the `Create App` button. 55 | 56 | ![Create new Slack application](img/create-app.png) 57 | 58 | We'll need your App Credentials for using OAuth later on. Let's grab the Client ID and Client secret from `App Credentials` in `Basic Information`. 59 | 60 | Create a new file, `.env` in your Node project. This is going to hold all of the private configurations for your app. We'll add your Client ID, Client Secret, and Verification Token here, along with other tokens you'll need later. Paste your credentials and give them environmental variable names. We'll read them into our application using the `dotenv` package we installed. 61 | 62 | ``` 63 | SLACK_CLIENT_ID= 64 | SLACK_CLIENT_SECRET= 65 | SLACK_VERIFICATION_TOKEN= 66 | ``` 67 | 68 | Now, let's add a Bot User to our app that will be able to send our Twilio messages to a Slack channel. Click on `Bot Users` > `Add a Bot User` and give your bot a default username. 69 | 70 | ![Create a bot user](img/create-bot-user.png) 71 | 72 | After you add a bot user to your app, you'll want to install it onto your team. Click `Install App` and give the app permission to join your team. After it joins, you'll be able to access its authorization token. Under your app's setting page, click `Install App` again, and copy the OAuth Access Token and the Bot User OAuth Access Token that appeared. 73 | 74 | ![Accessing the Bot User OAuth Access Token](img/slack-auth-tokens.png) 75 | 76 | Now we'll add those tokens to our `.env` file as well. 77 | 78 | ``` 79 | SLACK_BOT_TOKEN=xoxb- 80 | SLACK_AUTH_TOKEN=xoxp- 81 | ``` 82 | 83 | > 🔑 *In this repository, there's an `.env.example` file. It has labels for all of the tokens and credentials you'll need throughout this tutorial.* 84 | 85 | To retrieve and reference your tokens in the code, we'll add get our tokens in `index.js`. We'll use these tokens later when sending Slack messages. 86 | 87 | ```js 88 | // Retrieve bot token from dotenv file 89 | const bot_token = process.env.SLACK_BOT_TOKEN || ''; 90 | // Authorization token 91 | const auth_token = process.env.SLACK_AUTH_TOKEN || ''; 92 | // Verification token for Events Adapter 93 | const slackEvents = createSlackEventAdapter(process.env.SLACK_VERIFICATION_TOKEN); 94 | ``` 95 | > 💡 *You can see the last one, `slackEvents`, actually passing the Verification Token into a method. This will allow us to verify that the events we receive are from Slack. We'll use this more in [Section 04](section-04.md).* 96 | 97 | ## Configure Slack Clients 98 | Next, we're going to use the `Client ID` and `Client Secret` to initialize two web clients. We need two because we'll need to make API calls on behalf of the bot user we set up. Using clients will make it simpler to send messages and use auth endpoints. In `index.js`, you'll want to add the following *after* you declare your Express app: 99 | 100 | ```js 101 | // Slack web client 102 | const web = new WebClient(auth_token); 103 | const bot = new WebClient(bot_token); 104 | ``` 105 | 106 | ## Adding Permission Scopes 107 | 108 | Permission scopes are what allow you app to access certain information about whatever team your app is installed on. They allow access to different objects (`files`, `search`, `chat`, and `reactions`) with multiple scopes rather than giving an app full access to a team's information. 109 | 110 | On the same page you got the tokens, scroll to `Permission Scopes`. Since we'll need to add a bot user, access public channels, and send messages with our bot, we'll subscribe to the `bot`, `channels:read`, and `chat:write:bot` scopes. 111 | 112 | ![Permission scopes](img/slack-initial-permission-scopes.png) 113 | 114 | > 💡 *You can learn more about how Slack uses permission scopes [in the documentation](https://api.slack.com/docs/oauth-scopes)* 115 | 116 | Since we added new permission scopes, we'll have to reinstall the app onto our team. Afer you reinstall it, you should have the necessary permission scopes for the app. Now let's make your bot say hello world! 🤖 117 | 118 | ## Sending Messages 119 | 120 | To make sure our bot is wired up properly, let's send a message. Copy the following code into your `index.js`. 121 | 122 | ```js 123 | // Post message to Slack 124 | web.chat.postMessage('#general', 'Hello, world!', function(err, info) { 125 | if (err) console.log(err); 126 | }); 127 | ``` 128 | 129 | > 🔑 *We aren't going to be using this code at any other point in the tutorial. After you test that everything is working, you can delete this code.* 130 | 131 | You should now be able to run your bot! 132 | 133 | Go to your project's root folder and run your bot by starting your Node project. 134 | 135 | ```sh 136 | $ node index.js 137 | ``` 138 | 139 | You should see your bot's post in `#general`. 140 | 141 | ![Sample Hello World in Slack](img/bot-hello-world.png) 142 | 143 | > ⚠️ *Make sure you add your bot to `#general` in your team. You can do this easily by typing `/invite @` in `#general` in the Slack team you installed your app on.* 144 | 145 | ## Adding SMS to our Application 146 | In Section 2, we're going to add functionality to our application by integrating Twilio, which will allow us to receive and send SMS messages to Slack. 147 | 148 | * [Section 00: Overview and Introduction](../README.md) 149 | * **Section 01: Setting up your Slack Bot** 👈 150 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 151 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 152 | * [Section 04: Responding to Message Events](section-04.md) 153 | * [Section 05: Responding to SMS via Slack](section-05.md) 154 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) 155 | 156 | -------------------------------------------------------------------------------- /docs/section-02.md: -------------------------------------------------------------------------------- 1 | # Section 02: Integrating your Bot with Twilio 📞 2 | 3 | * [Section 00: Overview and Introduction](../README.md) 4 | * [Section 01: Setting up your Slack Bot](section-01.md) 5 | * **Section 02: Integrating your Bot with Twilio** 👈 6 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 7 | * [Section 04: Responding to Message Events](section-04.md) 8 | * [Section 05: Responding to SMS via Slack](section-05.md) 9 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) 10 | 11 | In [Section 01](section-01.md), we set up a Slack bot, configured it, and added it to our team. By the end of this section, you'll be able to text a Twilio number we set up and forward that message to Slack using your bot! However, before we can send messages, we have to do some Twilio setup and configuration. 12 | 13 | > 💡 *Twilio is a service that allows you to easily build apps that use voice, video, messaging, and authentication. Although we'll only be using the messaging API from Twilio for this project, you can dig into their other uses and APIs in [Twilio's documentation](https://www.twilio.com/docs).* 14 | 15 | ## Setting up your Twilio App 16 | 17 | We'll start by setting up and configuring a Twilio phone number associated with our app. We'll use this number to forward SMS texts to our Slack team. Start by [logging into Twilio](https://www.twilio.com/login) (or sign up if you don't have an account). 18 | 19 | Once you're logged in, you'll want to go to [Buy a Number](https://www.twilio.com/console/phone-numbers/search) under [Phone Numbers](https://www.twilio.com/console/phone-numbers). Here, you'll search for an available phone number. It doesn't matter where your phone number is as long as it has SMS capabilities enabled. 20 | 21 | ![Buy a phone number on Twilio](img/twilio-buy-number.png) 22 | 23 | > 💡 *Under Twilio's free trial, you can only obtain one phone number. So if you need more than one number, you'll have to upgrade your account.* 24 | 25 | Now that we have a Twilio phone number, we need authorize our application. Go to [Twilio's console](https://www.twilio.com/console), and copy your Account SID and Auth Token into the `.env` we created in [Section 1](section-01.md). We'll use these credentials later when we are sending messages using Twilio from Slack. 26 | 27 | ``` 28 | TWILIO_SID= 29 | TWILIO_AUTH_TOKEN= 30 | ``` 31 | 32 | While we're in our code, we want to install the Twilio SDK using NPM. Install it using 33 | 34 | ```sh 35 | $ npm install twilio --save 36 | ``` 37 | 38 | and add it to your project like we did the other dependencies: 39 | 40 | ```js 41 | var twilio = require('twilio'); 42 | ``` 43 | 44 | ## Configuring your Express Server with Ngrok 45 | 46 | Next, we'll need to configure a server so Twilio can send us the SMS messages sent to our Twilio number. To do this, we'll use Ngrok. If you've never used Ngrok before, [read through our tutorial on tunneling with Ngrok](https://api.slack.com/tutorials/tunneling-with-ngrok). 47 | 48 | After you understand Ngrok, set up the express server and add some basic routing in our `index.js`: 49 | 50 | ```js 51 | // Creates our express app 52 | const app = express(); 53 | // Use BodyParser for app 54 | app.use(bodyParser.urlencoded({extended: true})); 55 | app.use(bodyParser.json()); 56 | // Slack web client 57 | const web = new WebClient(auth_token); 58 | const bot = new WebClient(bot_token); 59 | 60 | // The port we'll be using for our Express server 61 | const PORT = 4390; 62 | 63 | // The channel we'll send TalkBot messages to 64 | const channel = '#general' 65 | 66 | // Starts our server 67 | app.listen(PORT, function() { 68 | console.log('TalkBot is listening on port ' + PORT); 69 | }); 70 | 71 | // Handles incoming SMS to Twilio number 72 | app.post('/sms', function(req, res) { 73 | // Gets message from Twilio req 74 | let msg = req.body.Body ? req.body.Body : ''; 75 | // Gets phone number from sender without leading plus sign 76 | let num = req.body.From ? req.body.From.slice(1) : ''; 77 | 78 | // Sends message to Slack - in format from 79 | sendMessage(msg + ' from ' + num, channel, num); 80 | }); 81 | 82 | function sendMessage(text, channel, num) { 83 | // Send message using Slack Web Client 84 | web.chat.postMessage(channel, text, function(err, info) { 85 | if (err) { 86 | console.log(err); 87 | } 88 | }); 89 | } 90 | ``` 91 | 92 | In the above code, we created an express server with `express()`. Then, after our web clients, we set up an HTTP post route at `/sms` that will receive all of the messages sent to our Twilio phone number we set up earlier. We're taking the text sent to our number, storing it in a `msg` variable, and then sending it via the Web API we set up in [Section 01](section-01.md). 93 | 94 | The `sendMessage()` function uses the same `postMessage` call we used in [section 01](section-01.md). All of the available calls for the Web API can be found [in the Web API documentation](https://api.slack.com/methods). 95 | 96 | > 💡 *We'll use the `num` parameter in `sendMessage()` in [section 03](section-03.md). Don't worry about it now.* 97 | 98 | Great! Now let's start an Ngrok server on the port we defined, `4390`. We can do this via command line by running: 99 | 100 | ```sh 101 | $ ngrok http 4390 102 | ``` 103 | 104 | > 🔑 *The port number here doesn't matter, as long as you are consistent between Ngrok and your application.* 105 | 106 | Copy the forwarding address of your Ngrok server and add it to your Twilio number's incoming message webhook. This can be done by going to the configuration page for your active number and pasting your Ngrok url to the `A Message Comes In` option under Messaging and click `Save`. 107 | 108 | ![Add webhook address to Twilio number](img/twilio-set-webhook.png) 109 | 110 | > ⚠️ *Ensure that you add `/sms` to the end of your url so that the messages are forwarding to the proper route in our node application.* 111 | 112 | ## Testing it Out 113 | We should now be able to text our new Twilio number, and our message should go to `#general` in the Slack team that our app was added to 🤙 114 | 115 | ![Twilio set up](img/twilio-config.gif) 116 | 117 | ## Making Threads Work 118 | In [Section 3](section-03.md), we're going to be configuring Firebase and adding threads to our application. This will allow us to keep track of multiple people texting our Twilio number in a smart and intuitive way. 119 | 120 | * [Section 00: Overview and Introduction](../README.md) 121 | * [Section 01: Setting up your Slack Bot](section-01.md) 122 | * **Section 02: Integrating your Bot with Twilio** 👈 123 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 124 | * [Section 04: Responding to Message Events](section-04.md) 125 | * [Section 05: Responding to SMS via Slack](section-05.md) 126 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) -------------------------------------------------------------------------------- /docs/section-03.md: -------------------------------------------------------------------------------- 1 | # Section 03: Adding Threads to Your Conversations 💬 2 | 3 | * [Section 00: Overview and Introduction](../README.md) 4 | * [Section 01: Setting up your Slack Bot](section-01.md) 5 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 6 | * **Section 03: Adding Threads to Your Conversations** 👈 7 | * [Section 04: Responding to Message Events](section-04.md) 8 | * [Section 05: Responding to SMS via Slack](section-05.md) 9 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) 10 | 11 | In [Section 2](section-02.md), we configured a Twilio number to forward incoming messages using a webhook, which allowed us to send messages to Slack. In this section, we're going to make those messages useful by organizing them into threads. To do this, we'll configure an *extremely* simple database using [Firebase](https://firebase.google.com). 12 | 13 | Using Firebase will allow us to store and reference phone numbers that have texted our Twilio number in the past, which allows us to add new messages to Slack threads. We'll also use it to store a configured channel ID (so we aren't just posting to `#general`). Let's dig in 🤓 14 | 15 | ## Setting up Firebase 16 | To start, we'll set up Firebase. This will allow us to differentiate between users who are messaging a Twilio number for the first time versus someone who is following up (which we'll add to an existing thread). We can achieve this model by storing user data in simple key-value pairs: 17 | 18 | > **phone-number** : *message-id* 19 | 20 | This structure will allow us to reference a phone number's original message ID, which we'll use to add their new messages under a thread of the original. Later, when we mark a message as resolved using emoji reactions, we'll delete the user's key-value pair from the database. 21 | 22 | We'll also store a channel ID to reference which channel the user wants TalkBot running in. 23 | 24 | Let's start by setting up our database. Go to the [Firebase console](https://console.firebase.google.com) and click `Add project`. Type in the name of your app and then click `Create Project`. This will generate the necessary tokens to access our database. 25 | 26 | ![Create a Firebase app](img/fb-create-project.png) 27 | 28 | Firebase makes it really easy to access the generated config information. Click on `Overview` in the top left, and then `Add Firebase to your web app`. Copy the token values in the config JSON object and add them all to the `.env` file we created earlier. 29 | 30 | ![Firebase configuration module](img/fb-config-module.png) 31 | 32 | > 💡 *Since firebase gives you the configuration information in JSON format, we'll have to copy just the values assign them names in our `.env`, which we'll use to access our tokens from our code. Keeping our tokens in our `.env` will make sure they are safe* 🔐 33 | 34 | Finally, we're going to change the database rules. Although this shouldn't be done for public applications, we're going to make our database access public to make it easier for development. To do this, go to `Database` > `Rules`. Here, we'll set read and write to `true` (or *public*): 35 | 36 | ```js 37 | // These rules give anyone, even people who are not users of your app, 38 | // read and write access to your database 39 | { 40 | "rules": { 41 | ".read": true, 42 | ".write": true 43 | } 44 | } 45 | ``` 46 | 47 | Now we'll add Firebase to our Node project. Add the following near the top of `index.js`: 48 | 49 | ```js 50 | // Add firebase database 51 | var firebase = require('firebase'); 52 | ``` 53 | 54 | then install firebase via NPM: 55 | 56 | ```sh 57 | $ npm install firebase --save 58 | ``` 59 | 60 | We'll then add the config from our `.env`. Under our app configuration and initialization, let's add in the config struct that will allow us to reference our Firebase database. This is the same config from the Firebase console. 61 | 62 | ```js 63 | var config = { 64 | apiKey: process.env.FB_API_KEY || '', 65 | authDomain: process.env.FB_AUTH_DOMAIN || '', 66 | databaseURL: process.env.FB_DB_URL || '', 67 | projectId: process.env.FB_PROJECT_ID || '', 68 | storageBucket: process.env.FB_STORAGE_BUCKET || '', 69 | messagingSenderId: process.env.FB_MESSAGING_SENDER_ID || '' 70 | }; 71 | ``` 72 | 73 | Under that, we can actually initialize and reference our database: 74 | 75 | ```js 76 | // Firebase initialization 77 | var db = firebase.initializeApp(config).database(); 78 | ``` 79 | 80 | > 💡 *This configuration and initialization is taken from Firebase's documentation. To better understand what's happening here, [check out their docs](https://firebase.google.com/docs/database).* 81 | 82 | ## Adding Some Helper Functions 83 | 84 | I wrote some helper functions that we can use to get/write data to/from Firebase. Go ahead and copy these (*or write your own 🎉*) to the bottom of your `index.js`: 85 | 86 | ```js 87 | /** 88 | * Firebase Access Methods: 89 | * Channel manipulation, user manipulation, and user retrieval 90 | */ 91 | 92 | // Update channel 93 | function updateChannel(id) { 94 | db.ref('channel/id').set(id); 95 | } 96 | 97 | // Get channel 98 | function getChannel() { 99 | const ref = db.ref('channel/id'); 100 | ref.on('value', (snapshot) => { 101 | channel = snapshot.val(); 102 | }) 103 | } 104 | 105 | // Create user in Firebase 106 | function createUser(num, id) { 107 | db.ref('users/' + num).set(id); 108 | } 109 | 110 | // Delete user in Firebase 111 | function deleteUser(num) { 112 | db.ref('users/').child(num).remove(); 113 | } 114 | 115 | // Get number from message ID in Firebase 116 | function getNum(id) { 117 | return db.ref('users/').orderByValue().equalTo(id).once('value').then((snapshot) => { 118 | if (snapshot.val()) return Object.keys(snapshot.val())[0]; 119 | return null; 120 | }); 121 | } 122 | 123 | // Get thread ID from phone number in Firebase 124 | function getID(num) { 125 | return db.ref('users/').child(num).once('value').then((snapshot) => { 126 | return snapshot.val(); 127 | }); 128 | } 129 | ``` 130 | 131 | > 🔑 There are four basic functions to create/delete users in our database, and retrieve information using ID and phone number. We're storing all of our key/value pairs under the `users/` path in our database. `getNum()` and `getID()` both have callbacks since we need to wait until their data gets back from Firebase before it's used in our application. 132 | 133 | ## Threading it All Together 134 | 135 | Now that we've set up firebase configuration, we can easily retrieve and store our data. Let's go back to our `app.post('/sms')` method in `index.js`. We're going to use our `getID()` method we created to check if a user that messages our Twilio number already exists in our database. 136 | 137 | If it does exist, we'll send their message to the thread of their original message. If it doesn't exist, we'll send their message, retrieve their message id, and create a user with their phone number and message id. 138 | 139 | ```js 140 | // Handles incoming SMS to Twilio number 141 | app.post('/sms', function(req, res) { 142 | // Gets message from Twilio req 143 | let msg = req.body.Body ? req.body.Body : ''; 144 | // Gets phone number from sender without leading plus sign 145 | let num = req.body.From ? req.body.From.slice(1) : ''; 146 | 147 | getID(num) 148 | .then((id) => { 149 | if (id) { // User exists in database 150 | sendThread(msg, channel, id); 151 | } else { // User doesn't exist -- send message and create user 152 | sendMessage(msg, channel, num); 153 | } 154 | }) 155 | .catch(console.error); 156 | }); 157 | ``` 158 | 159 | You can see in the above code that we are fetching the message and phone number of sender from the Twilio request. We'll then check if the user has an ID in our Firebase database using `getID()`. If they do, we'll send this information to Slack in a thread of their original message. If they don't, we'll send it in a new message, and add the user to the database. We'll need to modify the `sendMessage()` method to call `createUser()` inside of it, and create the `sendThread()` method: 160 | 161 | ```js 162 | function sendMessage(text, channel, num) { 163 | // Send message using Slack Web Client 164 | web.chat.postMessage(channel, text, function(err, info) { 165 | if (err) { 166 | console.log(err); 167 | } else { 168 | if (num) { 169 | // Create user in database 170 | createUser(num, info.ts); 171 | } 172 | } 173 | }); 174 | } 175 | 176 | function sendThread(text, channel, id) { 177 | // Send message using Slack Web Client 178 | var msg = { 179 | thread_ts: id 180 | }; 181 | 182 | web.chat.postMessage(channel, text, msg, function(err, info) { 183 | if (err) console.log(err); 184 | }); 185 | } 186 | ``` 187 | 188 | Now, if you send a message to your Twilio number, you'll see it appear in Slack. If you send another one, it'll appear in a thread under your original message. Wow 😲 189 | 190 | ## Adding Reactions and Deleting Users 191 | In [Section 4](section-04.md), we'll be adding event listening, so you can mark a user as *complete*, which will delete that phone number from your database. 192 | 193 | * [Section 00: Overview and Introduction](../README.md) 194 | * [Section 01: Setting up your Slack Bot](section-01.md) 195 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 196 | * **Section 03: Adding Threads to Your Conversations** 👈 197 | * [Section 04: Responding to Message Events](section-04.md) 198 | * [Section 05: Responding to SMS via Slack](section-05.md) 199 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) -------------------------------------------------------------------------------- /docs/section-04.md: -------------------------------------------------------------------------------- 1 | # Section 04: Responding to Message Events 2 | 3 | * [Section 00: Overview and Introduction](../README.md) 4 | * [Section 01: Setting up your Slack Bot](section-01.md) 5 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 6 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 7 | * **Section 04: Responding to Message Events** 👈 8 | * [Section 05: Responding to SMS via Slack](section-05.md) 9 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) 10 | 11 | In [Section 03](section-03.md), we added threading capabilities to our application, which allowed us to organize conversations in a more intuitive, non-intrusive way. Now, we're going to add the ability to mark users as *complete* so if they message us again, their message will be sent as a new message instead of in an old thread. 12 | 13 | We'll accomplish this by using the Events API to listen for message reactions. When someone reacts to a message with ✅, we'll delete that user from the database. 14 | 15 | ## Subscribing to Reaction Events 16 | The first thing we'll have to do is subscribe to reaction events. This is really easy with the Slack Events API. Earlier, we included the Events API Module, so we'll use that to easily acess event subscriptions. Below your initialization of your Express app, add the following snippet: 17 | 18 | ```js 19 | // Slack events client 20 | app.use('/events', slackEvents.expressMiddleware()); 21 | ``` 22 | 23 | > 💡 *This is going to use the Slack Events API module to parse your `/events` endpoint (which is where we'll send the events). After this initial set up, it becomes easy to add single event handlers.* 24 | 25 | Save and start your node program. 26 | 27 | ```sh 28 | $ node index.js 29 | ``` 30 | 31 | Now go to [Your Slack Apps](https://api.slack.com/apps). Click on your app, then on the left sidebar, go to `Event Subscriptions` (under Features). 32 | 33 | We're going to enable events, and enter the request URL we just set up. Remember to include the URL to your Ngrok server before `/events` (in my case, the full URL is `http://6668728a.ngrok.io/events`). Once we enter the url, it'll send the [URL Verification event](https://api.slack.com/events/url_verification) to our route (which the Events API module handles). 34 | 35 | ![Add Slack event subscription](img/slack-event-subscriptions.png) 36 | 37 | > 💡 *You can learn more about the URL verification and why it's necessary in the [Events API documentation](https://api.slack.com/events-api).* 38 | 39 | Now we can use the Events API! 👏 40 | 41 | ## Deleting User on Reaction Event 42 | Now that we have an event listener set up, we'll use our Events API module to react to events. 43 | 44 | Let's set up a method to listen for a `reaction_added` event: 45 | 46 | ```js 47 | slackEvents.on('reaction_added', (event) => { 48 | // Do something when a reaction is added to a message 49 | }); 50 | ``` 51 | 52 | Now, we're going to check if the reaction is a ✅ emoji. Emojis have a label that you can view in your team settings page that we can use to listen to a specific emoji (*in this case* ✅*, or `white_check_mark`*). 53 | 54 | If it is the ✅ emoji, we're going to lookup the phone number associated with the given message ID. The message ID is passed to us in the request body (as the parameter `event.item.ts`). Once we find the user associated with the message with reaction, we'll delete that user from our database using our `deleteUser()` method. 55 | 56 | ```js 57 | slackEvents.on('reaction_added', (event) => { 58 | // Check for white check mark emoji 59 | if (event.reaction == 'white_check_mark') { 60 | getNum(event.item.ts) 61 | .then((num) => {deleteUser(num)}) 62 | .catch(console.error); 63 | } 64 | }); 65 | ``` 66 | 67 | ## Messaging using Twilio from Slack 68 | Our app is almost done. It receives messages via Twilio, and organizes those messages using threads. When we're done with a user, we can mark it as done using a ✅ reaction. One feature we're missing is responding to users that message our Slack. We'll configure responding with SMS via Slack in [Section 05](section-05.md). 69 | 70 | * [Section 00: Overview and Introduction](../README.md) 71 | * [Section 01: Setting up your Slack Bot](section-01.md) 72 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 73 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 74 | * **Section 04: Responding to Message Events** 👈 75 | * [Section 05: Responding to SMS via Slack](section-05.md) 76 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) -------------------------------------------------------------------------------- /docs/section-05.md: -------------------------------------------------------------------------------- 1 | # Section 05: Responding to SMS via Slack 2 | 3 | * [Section 00: Overview and Introduction](../README.md) 4 | * [Section 01: Setting up your Slack Bot](section-01.md) 5 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 6 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 7 | * [Section 04: Responding to Message Events](section-04.md) 8 | * **Section 05: Responding to SMS via Slack** 👈 9 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) 10 | 11 | In [Section 04](section-04.md), we added user deletion on emoji reaction. Next, we're going to add is the ability to send SMS messages back to the user using a trigger `@talkbot ` in an existing, active thread. We're going to achieve this by again using *event triggers*. 12 | 13 | ## Setting up Message Listener 14 | First, just like we did for listening with reactions, we need to set up an event listener for all messages. Go to [Your Slack Apps](https://api.slack.com/apps). Click on your app, then on the left sidebar, go to `Event Subscriptions` (under Features). 15 | 16 | Under your `reaction_added` event, you'll click `Add Bot User Event`. Choose the `message.channels`, and click `Save Changes`. 17 | 18 | ![Add message event listener](img/slack-event-subscriptions-2.png) 19 | 20 | > 💡 *The new Bot Event will use the same path as our `reaction_added` event. We'll be able to see which is which in the request body.* 21 | 22 | ## Using the Message Listener 23 | To start using our new message listener, we need to access our bot's Slack ID. We'll do this in our `app.listen()` method. Define a new variable, `botID`, that we can use later with our message listener. 24 | 25 | ```js 26 | // Holds channel ID 27 | let channel; 28 | // Holds bot user ID 29 | let botID; 30 | // The port we'll be using for our Express server 31 | const PORT = 4390; 32 | 33 | // Starts our server 34 | app.listen(PORT, function() { 35 | console.log('TalkBot is listening on port ' + PORT); 36 | 37 | // Get userID for bot 38 | bot.auth.test() 39 | .then((info) => { 40 | if (info.user_id) { 41 | botID = info.user_id; 42 | } 43 | }) 44 | .catch(console.error) 45 | }); 46 | ``` 47 | 48 | Above, we're using the `auth.test` endpoint to retrieve the proper user ID for the user bot. 49 | 50 | Now, we can add a new event listener to check for messages with the `@talkbot ` format. The following code receives incoming messages from the Events API module. It then checks if the `parent_user_id` (the thread's parent) is our bot, and finally if the message starts with the trigger `<@botID>`. 51 | 52 | ```js 53 | slackEvents.on('message', (event) => { 54 | let trigger = '<@' + botID + '>'; 55 | if (event.thread_ts && event.text.startsWith(trigger)) { 56 | // Send message 57 | } 58 | }); 59 | ``` 60 | 61 | > 🔑 *Our trigger is `<@bot-id>` instead of `@bot-name` because Slack automatically tags the user based on their ID when someone mentions them in Slack. So that means when we see `@bot-name hello` in Slack, the text being passed in the request body is `<@bot-id> hello`* 62 | 63 | ## Sending an SMS via Twilio 64 | Finally, we are going to connect all of the loose ends to send our first SMS 📱! First, let's prepare the Twilio client. We're going to use the tokens we added to our `.env` back in [Section 02](section-02.md). Near the top of `index.js`, require `twilio` and use a variable to hold the Twilio SID and Authorization token. 65 | 66 | ```js 67 | // Require Twilio 68 | const twilio = require('twilio'); 69 | 70 | // Twilio client authorization 71 | const twilio_sid = process.env.TWILIO_SID || ''; 72 | const twilio_auth = process.env.TWILIO_AUTH_TOKEN || ''; 73 | ``` 74 | 75 | Once you save `index.js`, install the Twilio SDK via command line. 76 | 77 | ```sh 78 | $ npm install twilio --save 79 | ``` 80 | 81 | Below that, we'll initialize the Twilio client. 82 | 83 | 84 | ```js 85 | // Initialize Twilio client using our Twilio SID and Authorization token 86 | const twilioClient = twilio(twilio_sid, twilio_auth); 87 | ``` 88 | 89 | Now, we're going to create a new function `sendSMS()` that uses the Twilio client we intialized to send an SMS. We'll use our `getNum()` method to make sure a number exists using a message ID. If it does exist in our database, we'll send that message a number. 90 | 91 | ```js 92 | function sendSMS(msg, id) { 93 | getNum(id) 94 | .then((num) => { 95 | if (num) { 96 | twilioClient.messages.create({ 97 | to: num, 98 | // from: your Twilio phone number 99 | from: '+14155555555', 100 | body: msg 101 | }); 102 | } 103 | }) 104 | .catch(console.error) 105 | } 106 | ``` 107 | 108 | Finally, going back to our message listener, we'll call `sendSMS()` in our `if` statement from earlier. We've sanitized the message to uninclude the trigger, and fetched the thread ID from the event trigger. 109 | 110 | ```js 111 | slackEvents.on('message', (event) => { 112 | let trigger = '<@' + botID + '>'; 113 | if (event.thread_ts && event.text.startsWith(trigger)) { 114 | let msg = event.text.replace(trigger, ''); 115 | sendSMS(msg, event.thread_ts); 116 | } 117 | }); 118 | ``` 119 | 120 | Now, you should be able to send SMS via Slack after the phone number is added to the database. 121 | 122 | ![SMS using Slack](img/sms-via-slack.gif) 123 | 124 | # Adding Message Menus and Distributing 125 | Overall, our app is functional now. If you aren't interested in configuring OAuth for distribution or adding the ability for a default channel picker, you're done! 🎉 126 | 127 | For everyone else...the last thing we'll do is set up the application for distribution. This involves setting up OAuth and prompting the installer via DM and message menus to configure TalkBot for their team. 128 | 129 | * [Section 00: Overview and Introduction](../README.md) 130 | * [Section 01: Setting up your Slack Bot](section-01.md) 131 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 132 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 133 | * [Section 04: Responding to Message Events](section-04.md) 134 | * **Section 05: Responding to SMS via Slack** 👈 135 | * [Section 06: Adding Onboarding and Message Menus](section-06.md) -------------------------------------------------------------------------------- /docs/section-06.md: -------------------------------------------------------------------------------- 1 | # Section 06: Adding Onboarding and Message Menus 2 | 3 | * [Section 00: Overview and Introduction](../README.md) 4 | * [Section 01: Setting up your Slack Bot](section-01.md) 5 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 6 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 7 | * [Section 04: Responding to Message Events](section-04.md) 8 | * [Section 05: Responding to SMS via Slack](section-05.md) 9 | * **Section 06: Adding Onboarding and Message Menus** 👈 10 | 11 | In [Section 05](section-05.md), we added the ability to respond with SMS from within Slack. Although not necessary for functionality, we're going to finish TalkBot by adding message menus to prompt the user for a default channel, and configure OAuth for app installation. To start, let's set up an Auth Redirect URL and set up an Add to Slack button for our app. 12 | 13 | ## Adding OAuth 14 | To start, go to [Your Slack Apps](https://api.slack.com/apps). Click on your app, then on the left sidebar, click `OAuth & Permissions`. Add a redirect URL with your Ngrok server at a `/auth` endpoint. For me, it'd be: 15 | 16 | > http://6668728a.ngrok.io/auth 17 | 18 | Next, on the same page, add additional permission scopes to our application. We'll add `im:read` and `im:write` so we can directly message users on install. 19 | 20 | ![Adding permission scopes](img/slack-permission-scopes.png) 21 | 22 | Let's set up the `/auth` endpoint we just pointed to. Go to your `index.js` and add the following code: 23 | 24 | ```js 25 | app.get('/auth', function(req, res) { 26 | web.oauth.access(process.env.SLACK_CLIENT_ID, process.env.SLACK_CLIENT_SECRET, req.query.code, function(err, info) { 27 | if (err) console.log(err); 28 | if (!err) { 29 | initBot(info.user_id); 30 | 31 | res.redirect('http://slack.com'); 32 | } 33 | }); 34 | }); 35 | ``` 36 | 37 | This looks pretty similar to the other calls we've been making using the Web API, but for authentication. You can see we're retrieving the user that added the bot in the callback, then passing it to a function we'll create called `initBot()`. On install, the user is redirected to [http://slack.com](http://slack.com). 38 | 39 | Let's make the `initBot()` method. This is going to open a direct message (or IM) with the user that added the bot in order to figure out what channel our bot should be posting in. We'll save this channel in our Firebase database to use throughout our application. 40 | 41 | Copy the following code below our `/auth` endpoint: 42 | 43 | ```js 44 | function initBot(id) { 45 | // Open IM 46 | web.im.open(id, function(err, info) { 47 | if (err) console.log(err); 48 | }); 49 | } 50 | ``` 51 | 52 | ## Adding Message Menus 53 | 54 | The last thing we need to do for this part is configure interactive messages. First, go to your app. Go to your [app page](https://api.slack.com/apps) and select your app. On the sidebar, click `Interactive Messages`. Set up an endpoint at `/select` (for me this will be `http://6668728a.ngrok.io/select`. This will handle interactions with the message menu we're going to create. 55 | 56 | ![Interactive Messages Request URL](img/interactive-messages-request.png) 57 | 58 | Now, go to your `index.js` and create the `/select` endpoint: 59 | 60 | ```js 61 | app.post('/select', function(req, res) { 62 | let body = JSON.parse(req.body.payload); 63 | let id = body.actions[0].selected_options[0].value; 64 | res.json({text: 'Great, I\'ll send incoming messages there', replace_original: false}); 65 | 66 | // Update Channel 67 | updateChannel(id); 68 | }); 69 | ``` 70 | 71 | This is going to receieve the channel that is selected in our message menu. We'll update the channel by passing the new channel ID into `updateChannel()`, which saves it in our Firebase database. Now let's create the actual message menu. 72 | 73 | Create a new method, `installConfig()`. This is going to send a DM to the user. Add the following code: 74 | 75 | ```js 76 | function installConfig(id) { 77 | let text = 'Hey there! I\'m TalkBot, a Slack bot you can use to send and receive messages from Twilio. Pick which channel you want me to send messages to. After you select the channel, make sure to invite me using the command `/invite @talkbot` in your selected channel.'; 78 | let msg = { 79 | response_type: 'in_channel', 80 | attachments: [{ 81 | fallback: 'Upgrade your Slack client to use messages like these.', 82 | color: '#ed2e3b', 83 | attachment_type: 'default', 84 | callback_id: 'simple_select', 85 | actions: [{ 86 | name: 'channels_list', 87 | text: 'Which channel should I post to?', 88 | type: 'select', 89 | data_source: 'channels' 90 | }] 91 | }] 92 | }; 93 | 94 | // Send initial IM to user 95 | web.chat.postMessage(id, text, msg, function(err, info) { 96 | if (err) console.log(err); 97 | }); 98 | } 99 | ``` 100 | 101 | > 💡 *This code is modified from the samples in the [message menu documentation](https://api.slack.com/docs/message-menus).* 102 | 103 | The structure of our request is the same as earlier ones, like `postMessage`. But this an added `attachments` which we use to attach a channel picker. This is indicated in the `data_source` of our attachment actions. This can be overwhleming the first time you're using message menus, so be sure to [read through the documentation](https://api.slack.com/docs/message-menus) to understand what's being sent and received. 104 | 105 | We need to call this in our `initBot()` method, so the user that installs your application receieves the direct message on install. Change the method to call our `installConfig()` method: 106 | 107 | ```js 108 | function initBot(id) { 109 | // Open IM 110 | web.im.open(id, function(err, info) { 111 | if (err) console.log(err); 112 | 113 | installConfig(info.channel.id); 114 | }); 115 | } 116 | ``` 117 | 118 | One last thing we should do is call `getChannel()` in our startup function. This will make sure the channel we should be posting to stays up to date if the app restarts. 119 | 120 | ```js 121 | // Starts our server 122 | app.listen(PORT, function() { 123 | console.log('TalkBot is listening on port ' + PORT); 124 | 125 | // Get userID for bot 126 | bot.auth.test() 127 | .then((info) => { 128 | if (info.user_id) { 129 | botID = info.user_id; 130 | } 131 | }) 132 | .catch(console.error) 133 | 134 | // Update channel 135 | getChannel(); 136 | }); 137 | ``` 138 | 139 | ## Using Install to Slack button 140 | 141 | We have our authentication on login all setup. Now let's use a Slack button to test our app. Go to your app page and click the `Manage Distribution` tab on the left side. You can share this on whatever website you want. We can test our app by copying the shareable URL. 142 | 143 | ![Slack button generator](img/slack-manage-distribution.png) 144 | 145 | If you select a channel from the drop down, you'll be able to see a channel ID added to Firebase. Your app now communicates and reacts to installation on a team. If you continue development, you should look into saving the user's configuration information on authentication. 146 | 147 | # Conclusion 148 | Over 6 sections, we've created a Slack bot that listens to and responds to SMS messages using the Twilio SDK. We've learned how to utilize the Events and Web APIs, include Firebase and Twilio in our Slack applications, and utilize event listeners to add threads and interactivity to our bot. Naturally, what we covered was pretty bare bones, but hopefully it gave you a good overview into designing Slack bots and utilizing threads. 149 | 150 | I'd love feedback on this tutorial. You can file an issue, submit a PR, or [message me on GitHub](http://github.com/shanedewael). Happy bot buidling 🤖🎉 151 | 152 | * [Section 00: Overview and Introduction](../README.md) 153 | * [Section 01: Setting up your Slack Bot](section-01.md) 154 | * [Section 02: Integrating your Bot with Twilio](section-02.md) 155 | * [Section 03: Adding Threads to Your Conversations](section-03.md) 156 | * [Section 04: Responding to Message Events](section-04.md) 157 | * [Section 05: Responding to SMS via Slack](section-05.md) 158 | * **Section 06: Adding Onboarding and Message Menus** 👈 159 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies: 3 | * Dotenv, Express, BodyParser, Slack Web Client, Slack Events API 4 | */ 5 | 6 | require('dotenv').config(); 7 | 8 | const express = require('express'); 9 | const firebase = require('firebase'); 10 | const twilio = require('twilio'); 11 | const bodyParser = require('body-parser'); 12 | const WebClient = require('@slack/client').WebClient; 13 | const createSlackEventAdapter = require('@slack/events-api').createSlackEventAdapter; 14 | 15 | 16 | /** 17 | * Tokens: 18 | * Slack, Firebase, Twilio 19 | */ 20 | 21 | // Retrieve bot token from dotenv file 22 | const bot_token = process.env.SLACK_BOT_TOKEN || ''; 23 | // Authorization token 24 | const auth_token = process.env.SLACK_AUTH_TOKEN || ''; 25 | // Verification token for Events Adapter 26 | const slackEvents = createSlackEventAdapter(process.env.SLACK_VERIFICATION_TOKEN); 27 | // Twilio client authorization 28 | const twilio_sid = process.env.TWILIO_SID || ''; 29 | const twilio_auth = process.env.TWILIO_AUTH_TOKEN || ''; 30 | 31 | 32 | /** 33 | * App Configuration: 34 | * Express, BodyParser, Firebase, Events API, Web Clients, Twilio 35 | */ 36 | 37 | // Creates our express app 38 | const app = express(); 39 | // Use BodyParser for app 40 | app.use(bodyParser.urlencoded({extended: true})); 41 | app.use(bodyParser.json()); 42 | // Slack events client 43 | app.use('/events', slackEvents.expressMiddleware()); 44 | // Slack web client 45 | const web = new WebClient(auth_token); 46 | const bot = new WebClient(bot_token); 47 | // Firebase configuration 48 | const config = { 49 | apiKey: process.env.FB_API_KEY || '', 50 | authDomain: process.env.FB_AUTH_DOMAIN || '', 51 | databaseURL: process.env.FB_DB_URL || '', 52 | projectId: process.env.FB_PROJECT_ID || '', 53 | storageBucket: process.env.FB_STORAGE_BUCKET || '', 54 | messagingSenderId: process.env.FB_MESSAGING_SENDER_ID || '' 55 | }; 56 | // Firebase initialization 57 | const db = firebase.initializeApp(config).database(); 58 | // Initialize Twilio client using our Twilio SID and Authorization token 59 | const twilioClient = twilio(twilio_sid, twilio_auth); 60 | 61 | 62 | /** 63 | * App Variables 64 | * Channel ID, Bot User ID, Port 65 | */ 66 | 67 | // Holds channel ID 68 | let channel = '#general' 69 | // Holds bot user ID 70 | let botID; 71 | // The port we'll be using for our Express server 72 | const PORT = 4390; 73 | 74 | // Starts our server 75 | app.listen(PORT, function() { 76 | console.log('TalkBot is listening on port ' + PORT); 77 | 78 | // Get userID for bot 79 | bot.auth.test() 80 | .then((info) => { 81 | if (info.user_id) { 82 | botID = info.user_id; 83 | } 84 | }) 85 | .catch(console.error) 86 | 87 | // Update channel 88 | getChannel(); 89 | }); 90 | 91 | 92 | /** 93 | * Slack Event Listeners 94 | * Reaction added and message receieved 95 | */ 96 | 97 | slackEvents.on('reaction_added', (event) => { 98 | // Check for white check mark emoji 99 | if (event.reaction == 'white_check_mark') { 100 | getNum(event.item.ts) 101 | .then((num) => {deleteUser(num)}) 102 | .catch(console.error); 103 | } 104 | }); 105 | 106 | slackEvents.on('message', (event) => { 107 | let trigger = '<@' + botID + '>'; 108 | if (event.thread_ts && event.text.startsWith(trigger)) { 109 | let msg = event.text.replace(trigger, ''); 110 | sendSMS(msg, event.thread_ts); 111 | } 112 | }); 113 | 114 | // Handles incoming SMS to Twilio number 115 | app.post('/sms', function(req, res) { 116 | // Gets message from Twilio req 117 | let msg = req.body.Body ? req.body.Body : ''; 118 | // Gets phone number from sender without leading plus sign 119 | let num = req.body.From ? req.body.From.slice(1) : ''; 120 | 121 | getID(num) 122 | .then((id) => { 123 | if (id) { // User exists in database 124 | sendThread(msg, channel, id); 125 | } else { // User doesn't exist -- send message and create user 126 | sendMessage(msg, channel, num); 127 | } 128 | }) 129 | .catch(console.error); 130 | }); 131 | 132 | app.post('/select', function(req, res) { 133 | let body = JSON.parse(req.body.payload); 134 | let id = body.actions[0].selected_options[0].value; 135 | res.json({text: 'Great, I\'ll send incoming messages there', replace_original: false}); 136 | 137 | // Update Channel 138 | updateChannel(id); 139 | }); 140 | 141 | app.get('/auth', function(req, res) { 142 | web.oauth.access(process.env.SLACK_CLIENT_ID, process.env.SLACK_CLIENT_SECRET, req.query.code, function(err, info) { 143 | if (err) console.log(err); 144 | if (!err) { 145 | initBot(info.user_id); 146 | 147 | res.redirect('http://slack.com'); 148 | } 149 | }); 150 | }); 151 | 152 | function sendSMS(msg, id) { 153 | getNum(id) 154 | .then((num) => { 155 | if (num) { 156 | twilioClient.messages.create({ 157 | to: num, 158 | // from: your Twilio phone number 159 | from: '+14155555555', 160 | body: msg 161 | }); 162 | } 163 | }) 164 | .catch(console.error) 165 | } 166 | 167 | function sendMessage(text, channel, num) { 168 | // Send message using Slack Web Client 169 | web.chat.postMessage(channel, text, function(err, info) { 170 | if (err) { 171 | console.log(err); 172 | } else { 173 | if (num) { 174 | // Create user in database 175 | createUser(num, info.ts); 176 | } 177 | } 178 | }); 179 | } 180 | 181 | function sendThread(text, channel, id) { 182 | // Send message using Slack Web Client 183 | var msg = { 184 | thread_ts: id 185 | }; 186 | 187 | web.chat.postMessage(channel, text, msg, function(err, info) { 188 | if (err) console.log(err); 189 | }); 190 | } 191 | 192 | function initBot(id) { 193 | // Open IM 194 | web.im.open(id, function(err, info) { 195 | if (err) console.log(err); 196 | 197 | installConfig(info.channel.id); 198 | }); 199 | } 200 | 201 | function installConfig(id) { 202 | let text = 'Hey there! I\'m TalkBot, a Slack bot you can use to send and receive messages from Twilio. Pick which channel you want me to send messages to. After you select the channel, make sure to invite me using the command `/invite @talkbot` in your selected channel.'; 203 | let msg = { 204 | response_type: 'in_channel', 205 | attachments: [{ 206 | fallback: 'Upgrade your Slack client to use messages like these.', 207 | color: '#ed2e3b', 208 | attachment_type: 'default', 209 | callback_id: 'simple_select', 210 | actions: [{ 211 | name: 'channels_list', 212 | text: 'Which channel should I post to?', 213 | type: 'select', 214 | data_source: 'channels' 215 | }] 216 | }] 217 | }; 218 | 219 | // Send initial IM to user 220 | web.chat.postMessage(id, text, msg, function(err, info) { 221 | if (err) console.log(err); 222 | }); 223 | } 224 | 225 | /** 226 | * Firebase Access Methods: 227 | * Channel manipulation, user manipulation, and user retrieval 228 | */ 229 | 230 | // Update channel 231 | function updateChannel(id) { 232 | db.ref('channel/id').set(id); 233 | } 234 | 235 | // Get channel 236 | function getChannel() { 237 | const ref = db.ref('channel/id'); 238 | ref.on('value', (snapshot) => { 239 | channel = snapshot.val(); 240 | }) 241 | } 242 | 243 | // Create user in Firebase 244 | function createUser(num, id) { 245 | db.ref('users/' + num).set(id); 246 | } 247 | 248 | // Delete user in Firebase 249 | function deleteUser(num) { 250 | db.ref('users/').child(num).remove(); 251 | } 252 | 253 | // Get number from message ID in Firebase 254 | function getNum(id) { 255 | return db.ref('users/').orderByValue().equalTo(id).once('value').then((snapshot) => { 256 | if (snapshot.val()) return Object.keys(snapshot.val())[0]; 257 | return null; 258 | }); 259 | } 260 | 261 | // Get thread ID from phone number in Firebase 262 | function getID(num) { 263 | return db.ref('users/').child(num).once('value').then((snapshot) => { 264 | return snapshot.val(); 265 | }); 266 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "talkbot", 3 | "version": "1.0.0", 4 | "description": "Building a Twilio-powered Slack bot with Node.js", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/shanedewael/TalkBot.git" 15 | }, 16 | "author": "Shane DeWael", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/shanedewael/TalkBot/issues" 20 | }, 21 | "homepage": "https://github.com/shanedewael/TalkBot#readme", 22 | "dependencies": { 23 | "@slack/client": "^3.10.0", 24 | "@slack/events-api": "^1.0.1", 25 | "body-parser": "^1.17.2", 26 | "dotenv": "^4.0.0", 27 | "express": "^4.15.3", 28 | "firebase": "^4.1.2", 29 | "twilio": "^3.3.0" 30 | } 31 | } 32 | --------------------------------------------------------------------------------