DEV Community

Cover image for Playing Breaking Bad Quotes From a Phone Call Using Node.JS and Twilio βš—οΈ
Gregory Gaines
Gregory Gaines

Posted on

Playing Breaking Bad Quotes From a Phone Call Using Node.JS and Twilio βš—οΈ

Howdy Reader πŸ‘‹πŸ½

Have you ever been in a situation where you wanted to hear quotes from your favorite show but didn't have a way to? I recently watched Breaking Bad and Walter's and Jesse's iconic voices still bounce around in my head.

Today, I'll be teaching you how to set up a number that automatically plays Breaking Bad quotes when called, or you can use it for something lame like setting up an automated support line for a product, startup, or whatever capitalistic endeavor you have in mind. πŸ₯±

Call this number for a Demo: +1(318) 490-4496


Prerequisites βœ‹πŸ½

  • Basic JavaScript Knowledge.
  • A Twilio Account
  • A recent Node.js installation.

What is Twilio? πŸ“±

Twilio is a company that provides APIs for various communicating needs like phone calls, text messaging, or P2P video streaming.

We are using the Programmable Voice API that exposes functionality for playing a voice or audio.

Project Design πŸ—οΈ

Let's go over the design!

A user first calls our number, then Twilio calls an endpoint on our express server which tells Twilio "Hey, answer the call and return this audio". Twilio says ok, then plays the audio back to the caller.

Project Setup

Let's start cooking our project.

Twilio API Keys πŸ”‘

Let's kick off by getting our Twilio API keys, we'll need them later for testing. If you don't have a Twilio account, here’s the sign-up page (don't worry it's not an affiliate link). Don't forget to generate a free phone number.

The console should show two tokens connected to your account info, the Account SID and Auth Token.

Adding Environment Variables 🀫

One of the packages we installed was dotenv. Dotenv allows us to define a .env file for variables or sensitive keys (depending on who you ask) that are loaded into process.env.

Open the .env file and add your Twilio API keys and your generated phone number.

TWILIO_ACCOUNT_SID=<YOU_ACCOUNT_SID>
TWILIO_AUTH_TOKEN=<YOUR_AUTH_TOKEN>
TWILIO_NUMBER=<YOUR_GENERATED_TWILIO_NUMBER> # Ex. +14045555555
Enter fullscreen mode Exit fullscreen mode

Creating Express.js Server

To handle Twilio asking "what should I do?", we need an Express.js server. Create an empty directory and init an empty npm project and set whatever configs you want except change "entry" to app.js.

npm init
Enter fullscreen mode Exit fullscreen mode

Then run the NPM command below to install our required packages.

npm install express dotenv twilio ngrok
Enter fullscreen mode Exit fullscreen mode

Create the below folder structure.

β”œβ”€β”€ public/ - Public static files
β”œβ”€β”€ routes/ - HTTP routes
    └── voice.js - Twilio API endpoints
β”œβ”€β”€ .env
β”œβ”€β”€ app.js - HTTP server definition
β”œβ”€β”€ package.json
β”œβ”€β”€ package-lock.json
Enter fullscreen mode Exit fullscreen mode

Let's add some server code to app.js!

const express = require('express');
const path = require('path');
const http = require('http');

// Pull environment variables 
require('dotenv').config();

// Init Express
const app = express();
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(express.static(path.join(__dirname, 'public')));

// Set port
const port = '3000';
app.set('port', port);

// Create HTTP server
const server = http.createServer(app);

// Start server on port
server.listen(port, () => {
  console.log("Started server");
});

module.exports = app;
Enter fullscreen mode Exit fullscreen mode

Run this command to start the server.

node app.js
Enter fullscreen mode Exit fullscreen mode

Adding Audio Files 🎀

As per our requirements, let's gather some audio files to play when someone calls our number. You already know I'm doing Breaking Bad quotes but you can use whatever you want, I'm not your boss.

I used this site to download Breaking Bad quotes.

After getting your audio files, place them in the public folder. If you don't want your audio files exposed publicly, you can create another folder and relocate them.

Create Audio Response Endpoint

When someone calls our Twilio number, we direct Twilio to our POST endpoint that tells Twilio how to respond to the caller using TwiML, Twilios markup language for commands.

Navigate to the routes/voice.js file. We are adding an POST endpoint named /voice which randomly selects a quote, then creates and returns a TwiML command telling Twilio to play this file.

const express = require('express');
const VoiceResponse = require('twilio').twiml.VoiceResponse;
const fs = require('fs');

// Create router
const router = express.Router();

// Create a 'POST' endpoint named '/voice'
router.post('/voice', (req, res) => {

  // Get all the files in the /public folder 
  // then filter for only .mp3 files.
  const audioFiles = fs.readdirSync('./public').filter((file) => {
    return file.match(new RegExp('.*\.(mp3)', 'ig'));
  });

  // Choose a random .mp3
  const randomAudioFile = audioFiles[Math.floor(Math.random() * audioFiles.length)];

  // Create a voice response
  const voiceResponse = new VoiceResponse();

  // Add a pause because the audio starts too quickly
  // and the person calling might miss the beginning.
  voiceResponse.pause({
    length: 1,
  });

  // Generate a TwiML command that says 
  // "Play this audio file once".
  voiceResponse.play({
    loop: 1
  }, randomAudioFile);

  // Send response to Twilio
  res.type('text/xml')
    .status(200).send(voiceResponse.toString());
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Now attach the route to our server by editing app.js.

const voiceRouter = require('./routes/voice');
app.use(voiceRouter);
Enter fullscreen mode Exit fullscreen mode

Make sure to restart your server on code changes.

Testing '/voice' Endpoint πŸ”¬

Before attaching our endpoint to Twilio, we'd better test it first unless you're a bad programmer then by all means skip this section. Just tell me what your product is so I know NOT to add my debit card to it 🀣.

To check if the endpoint path works, use an HTTP client like Postman to send a POST request to localhost:3000/voice. You should see something like this:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
    <Pause length="1"/>
    <Play loop="1">who_knocks.mp3</Play>
</Response>
Enter fullscreen mode Exit fullscreen mode

To test the functionality of our endpoint, we create a Twilio function to call a number using the command from the /voice endpoint. If your Twilio account isn't activated, the only number you can call is the one you created your account with.

Before we can do that, Twilio can't run commands that run on localhost. Doesn't matter if we're testing or not. Luckily, there is a tool called Ngrok, a reverse proxy that exposes our localhost:3000 express server to the internet like it's an actual server!

Navigate to the app.js file. In the server.listen command's callback, add code to connect our express server to the internet then restart the server.

const ngrok = require('ngrok');

// Listen on port
server.listen(port, () => {
  console.log("Started server");

  // Create a public url for our
  // `localhost:3000` express server.
  ngrok.connect({
    addr: 3000, // Our localhost port
  }).then((ngrokUrl) => {
    console.log("Connected to url: " + ngrokUrl);
  });
});
Enter fullscreen mode Exit fullscreen mode

You should see something like:

Connected to url: https://xxxx-xx-xx-xxx-xx.ngrok.io
Enter fullscreen mode Exit fullscreen mode

Now we create the Twilio code to test our command! Create a Twilio client using the TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN environment variables we added earlier.

const twilio = require("twilio");

// Listen on port
server.listen(port, () => {
  console.log("Started server");

   // Create Twilio client
  const twilioClient =
     new twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
...
Enter fullscreen mode Exit fullscreen mode

With our Twilio client, we send a command to call a phone number using the command from the /voice endpoint.

// Listen on port
server.listen(port, () => {
  console.log("Started server");

  // Create a public url for our
  // `localhost:3000` express server.
  ngrok.connect({
    addr: 3000,
  }).then((ngrokUrl) => {
    console.log("Connected to url: " + ngrokUrl);

    // Append voice endpoint to ngrokUrl
    const voiceUrl = `${ngrokUrl}/voice`;

    // Create Twilio client
    const twilioClient = new twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

    // Call a phone number using the
    // command from the /voice endpoint.
    twilioClient.calls.create({
      to: '<TARGET_PHONE_NUMBER>', // Target number to call
      from: process.env.TWILIO_NUMBER, // Your generated Twilio number
      url: voiceUrl, // The ngrok url that points to the /voice endpoint
      method: 'POST'
    }).then((call) => {
      console.log(call); // Print call log
    }).catch((err) => {
      console.log("Error making call " + err); // Print error
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

After a few seconds, you should get a call playing the selected audio file!! Pretty cool right? 😊

Connect /voice Endpoint to Twilio πŸͺ›

It's time for the moment of truth! We are going to connect our /voice endpoint to Twilio. Login to your Twilio account and navigate to the console. Click 'Explore Products' in the left sidebar.

Twilio Sidebar

Scroll down and click 'Phone Numbers'.

Phone Numbers

Select your Twilio number.

Twilio Number

Scroll down to 'Voice & Fax' and add your ngrok url that points to the /voice endpoint as a webhook for 'A CALL COMES IN'.

Webhook

Call your Twilio phone number and your audio will play right into your ear!!! πŸ‘πŸΎπŸ‘πŸΎπŸ‘πŸΎ

Final Thoughts πŸ’­

Who says T.V rots your brain? Because of Breaking Bad, we learned about system design, creating a server, how to use environment variables, the importance of testing, and most importantly how to cook... code πŸ˜‰. Hopefully, you've enjoyed the read and taken something from it.

I'm Gregory Gaines, a goofy software engineer who's trying to write good articles. If you want more content, follow me on Twitter at @GregoryAGaines.

Now go create something great! If you create a Twilio app or need some help hit me up on Twitter (@GregoryAGaines) and we can talk about it.

Discussion (3)

Collapse
femolacaster profile image
femolacaster

I am the man who knocks!

Collapse
jonrandy profile image
Jon Randy

Have you ever been in a situation where you wanted to hear quotes from your favorite show but didn't have a way to?

Oh yes, frequently

Collapse
gregorygaines profile image
Gregory Gaines Author

You got options. Time to start coding!