Get Horror Movie Suggestions with Twilio and the TMDB API

October 10, 2022
Written by
Dainyl Cua
Twilion
Reviewed by

horrormovies

The month of October brings along many festivities—dressing up in costumes, going trick-or-treating, stopping by a pumpkin patch, or visiting a festival! For those who enjoy a good scare, maybe check out a haunted house and settle down at home with a nice, spooky movie. But what if you don’t know what to watch? Searching through the thousands of movies on IMDB can take a lot of time, and sometimes your favorite streaming service isn’t recommending the movies for you.

With the help of TMDB’s API and both Twilio’s Programmable Messaging API and Functions, you can set up your own horror movie recommendation bot after doing a bit of coding.

Prerequisites

For this tutorial, you will need:

Set up your developer environment

Before you can write your code, you will first need to get your API key from TMDB. Create an account on the TMDB signup page and then verify your account by clicking the blue button labeled Activate My Account in the verification email you receive. Log in to your account and then request an API key by clicking the link under Request an API Key on this page.

Register as a Developer, then read through and accept the terms of usage. Fill out the API request form completely and then click submit. Once you’ve done so, you should land on a page that contains your API Key.

tmdb api setup

For this tutorial, you will be using the API Read Access Token located below the highlighted header in the image above. This token will need to be sent in the request header of every request to verify your identity.

With that, you’ve now gathered all you need to start searching for movies! Head over to your Twilio Console and get ready to code.

Set up your Twilio Function

From your Twilio Console, click the Explore Products label on the left-hand sidebar. Then, click on Developer tools in the menu on the left, and then click on the Function and Assets card label. Click on the blue button labeled Create Service, enter a suitable Service Name (I named mine horror-movies), and then hit the blue button labeled Next. You should end up on a page like the one below.

Twilio Function - Empty function

Click the blue button labeled Add on the top of the screen, and rename the path to /main.

Next, click on the button labeled Environment Variables under the Settings header in the bottom left of the screen. Copy and paste your TMDB v4 API Read Access Token into the Value input box. Then, click the Key input box and enter TMDB_TOKEN, then click the button labeled Add.

Twilio Function - Environment Variables tab open with TMDB_TOKEN variable set to a hidden Value

Finally, click on Dependencies, located below Environment Variables. You will need to add the node-fetch module, specifically version 2.x.

Enter node-fetch in the Module input box and enter 2.x in the Value input box. Then, click the button labeled Add.

Twilio Function - Dependencies tab with the node-fetch module imported on version 2.x

You’re now ready to start implementing your code.

Write your code

The code will consist of four main parts:

  • Give the user instructions on how to get movie recommendations
  • Prompt the user for movie genres
  • Search for movies using the TMDB API
  • Provide details for a selected movie

Give the user instructions and prompt the user for movie genres

Click the /main path and then, in the provided code editor, replace the prewritten code with the following:

js 
exports.handler = async function(context, event, callback) {
   // Initialize MessagingResponse
  let response = new Twilio.Response();
  let twiml = new Twilio.twiml.MessagingResponse();

  // Initialize stage
  let stage = event.request.cookies.stage || "intro";

  // Set response
  response
        .setBody(twiml.toString())
        .appendHeader('Content-Type', 'text/xml')

  return callback(null, response);
};

This code changes the function into an asynchronous function and initializes both the response and twiml variables that you will be using to respond back to the user. The stage variable tracks the user’s progress through the application and will be stored in a cookie that will persist on the server as the user sends text messages.

If there are no cookies (i.e. the user has texted the number for the first time or cookies have been cleared prior to texting), then stage is set to “intro”. The response variable then has the text message body set using the .setBody() function and the proper header is set using the .appendHeader() function. Finally, the response variable is sent through the callback function.

Next, you will need to create and send the messages that will guide your users through the movie recommendation bot.

In-between the initialization of the stage variable and the line where you set the response (highlighted in the code block above), copy and paste the following lines of code:

// Message decision tree
switch(stage) {
  case "intro":
    twiml.message("Welcome to the horror movie recommender powered by Twilio SMS and TMDB!\n\nWhat genre of horror movie would you like to see today? (e.g. science fiction, fantasy, mystery, thriller, etc.)\n\n\nIf you would like to see all types of horror movies, respond with \"next\".")
    response
      .setBody(twiml.toString())
      .appendHeader('Content-Type', 'text/xml')
      .setCookie("stage", "search")
  
    return callback(null, response);

  case "search":
    twiml.redirect({
      method: "POST"
    }, "/search")
  
  case "details":
    twiml.redirect({
      method: "POST"
    }, "/details")
}

There will be two other paths besides the /main path in this application: the /search path and the /details path.

Users will be sent to the corresponding path depending on the value of the stage variable thanks to the switch statement.

In the “intro” case, the user is greeted with a message telling them to respond with the genre of movie they wish to see. The response variable is then set similarly to the previous step, but this time the “stage” cookie (not the variable) is initialized and set to “search”.

Before moving on, be sure to hit the blue button labeled Save!

Search for movies using the TMDB API

Next, you will need to implement the code that will perform the search. Click the blue button labeled Add on the top of the screen, and rename the newly created path /search. Replace the preexisting code with the lines of code below:


const fetch = require("node-fetch")

exports.handler = async function(context, event, callback) {
  // Initialize MessagingResponse
  let response = new Twilio.Response();
  let twiml = new Twilio.twiml.MessagingResponse();

  // Get message and split the words
  const words = event.Body.toLowerCase().split(" ")

  // Initialize genre codes
  let codes = {
        "action": 28,
        "adventure": 12,
        "animation": 16,
        "comedy": 35,
        "crime": 80,
        "documentary": 99,
        "drama": 18,
        "family": 10751,
        "fantasy": 14,
        "history": 36,
        "horror": 27,
        "music": 10402,
        "mystery": 9648,
        "romance": 10749,
        "science": 878,
        "tv": 10770,
        "thriller": 53,
        "war": 10752,
        "western": 37
  };

  let query = "27";
  let genres = "horror";

  response
        .setBody(twiml.toString())
        .appendHeader('Content-Type', 'text/xml')
        .setCookie("stage", "details")
        .setCookie("ids", idArray.toString())

  return callback(null, response);
};

The first line of code will initialize the fetch variable which will utilize the node-fetch module. Then, the exported function is turned asynchronous and the response and twiml variables are initialized just like in the /main path.

The words variable is then initialized and set to the body of the text message which has been transformed into lower case and split into an array. The codes variable is then initialized with all the corresponding genre codes that you will use for the TMDB API.

The query and genres variables have been initialized and set to ensure that all searches will be for horror movies only. The response variable also has a new cookie attached that you will be implementing later - the “ids” cookie. The “ids” cookie will store the ids of the movies that were found in the search.

Above the setting of the response variable and below the genres variable, add the following code:

// Append to query and provide feedback
for(i=0; i<words.length; i++) {
  if(Object.keys(codes).includes(words[i])) {
    query += `,${codes[words[i]]}`
    genres += ` and ${words[i]}`
  }
}

twiml.message(`Now searching for trending ${genres} movies.`)

// Initialize message and necessary arrays
let fullMessage = "The top 10 trending horror movies on TMDB are:\n\n";
let movies = [];
let idArray = [];

Using the text message the user sends, the query and genres variables are added on, depending on what genres the user searches. If the user searches for multiple movie genres, then all of them will be included.

The user is then notified of the genres that will be included in the search criteria. Finally, the fullMessage, movies, and idArray variables are initialized. The fullMessage variable will be added on, similarly to the query and genres variables, once a fetch is performed. The movies and idArray variables will be populated once a fetch is performed as well.

You will now need to fetch the data utilizing the TMDB API. Add this final bit of code below the code you just added, but above the setting of the response variable:

// Fetch data
try {
  const res = await fetch(`https://api.themoviedb.org/3/discover/movie?language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=${query}&with_watch_monetization_types=flatrate`,   {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${context.TMDB_TOKEN}`
  }
})
  const data = await res.json();
  movies = data.results.slice(0,10);
} catch(e) {
  twiml.message("An error occurred when discovering movies from TMDB.");
}  

// Append movies to message and push ids into array
for(i=0; i<movies.length; i++) {
  fullMessage += `${i+1}. ${movies[i].title} [${movies[i].vote_average}/10]\n`;
  idArray.push(movies[i].id);
}

fullMessage += `\nReply with the number of the movie you'd like to learn more about.`;

// Send message
twiml.message(fullMessage);

The API call is performed in a try-catch block for better error handling. Utilizing the node-fetch module, a GET request is performed on the movie discover endpoint of the TMDB API. The query value is passed in for the with_genres= parameter, and the TMDB_TOKEN environment variable you set earlier is used in the header for authorization.

The results of the fetch request in the res variable are then converted into JSON and stored in the data variable, then the first ten movies are stored in the movies variable you initialized earlier. In case there is an error when performing the API call, then the user will be sent the message “An error occurred when discovering movies from TMDB.”

Properties from the movies that are found (specifically the title and voting average) are then added onto the fullMessage variable. Their corresponding ids are stored into the idArray variable for use later when users wish to look up details about a movie. Finally, a message notifying the user they can learn more about a movie is added onto fullMessage and then the entire fullMessage variable is added onto the twiml variable.

Don’t forget to hit the Save button before moving on!

Provide details for a selected movie

Finally, you will need to perform another search that will show the details of whichever movie the user selects. Click the blue button labeled Add on the top of the screen, and rename the final path to /details.

Replace the preexisting code with the lines of code below:


const fetch = require("node-fetch")

exports.handler = async function(context, event, callback) {
  // Initialize MessagingResponse
  let response = new Twilio.Response();
  let twiml = new Twilio.twiml.MessagingResponse();

  // Get message
  const words = event.Body.toLowerCase()

  // Get ids & selected movie id
  const ids = event.request.cookies.ids.split(",")
 
 
  return callback(null, response);
};

Most of this code is similar to the /search path, except that the final portion of the code where you set the response variable is missing (you’ll see why shortly!). The only new portion is the initializing of the ids variable, which will pull from the ids cookie that you set in the last step.

The final pieces of code will look complicated, but don’t be intimidated! Copy and paste the following code into the highlighted line below the ids variable and above the return:

let movie = {};

if(parseInt(words) > 0 && parseInt(words) < 11) {
  let selectedId = ids[parseInt(words)-1];

  try {
    const res = await fetch(`https://api.themoviedb.org/3/movie/${selectedId}?language=en-US`, {
      method: "GET",
      headers: {
      "Authorization": `Bearer ${context.TMDB_TOKEN}`
      }
    });
    
    const data = await res.json();
    movie = data;
  } catch(e) {
    twiml.message("An error occurred when fetching movie details from TMDB.")
  }

  let genres = "Genres:";

  for(i=0; i<movie.genres.length; i++) {
    genres += ` ${movie.genres[i].name}`;

    if(i !== movie.genres.length-1) {
      genres += ',';
    }
  }

  // Send details
  twiml.message(`${movie.title}\n"${movie.tagline}"\n\n${genres}\nRuntime: ${movie.runtime} minutes\n[${movie.vote_average}/10]\n\n${movie.overview}\n\nGet more information on https://www.themoviedb.org/movie/${movie.id}\n\nReply with another number to see the details of the other movie. Type "finish" and text this number again to perform another search.`)

  response
    .setBody(twiml.toString())
    .appendHeader('Content-Type', 'text/xml')

} else if(words === "finish" || words === "finished") {
// If user replies "finish" or "finished", then quit and remove cookies
  twiml.message("Goodbye!");

  response
    .setBody(twiml.toString())
    .appendHeader('Content-Type', 'text/xml')
    .removeCookie("stage")
    .removeCookie("ids")

} else {
// If no valid input, send nothing
  response
    .setBody(twiml.toString())
    .appendHeader('Content-Type', 'text/xml')
}

To make this easier, you can break this code into three separate cases based on the user response:

  • A single number from 1 through 10
  • “finish” or “finished”
  • An invalid input

Regardless of case, the movie variable is initialized. If the user responds with a number from 1 through 10, then details on the selected movie will be searched and sent back to them. The id of the movie the user wants to know more about is stored in the selectedId variable and then used in the API call in the try-catch block, similar to the API call in the previous step.

The data received is then transformed into JSON and then used in the movie variable. The genres variable is initialized and formatted, then the movie’s genres are added onto it. Finally, the movie details are sent in a single message, a link to the movie’s page on TMDB is provided, and instructions to look at another movie or perform another search are provided. The response variable is then set.

If the user replies with “finish” or “finished”, then the bot sends “Goodbye!” and the “stage” and “ids” cookies are removed from the response variable. This allows the user to start back again from the beginning and perform another search if they wish.

Finally, if there is no valid input (not a number from 1 through 10 nor “finish” or “finished”), then nothing is sent back to the user (as the twiml variable has no text).

Now hit the Save button and then hit the blue button labeled Deploy All. After the code is built and deployed to Twilio’s servers (this may take a while), you can now add the Function to your Twilio number.

Add the Function to your Twilio number

Navigate to your Active Numbers page, and then click on the Twilio phone number that you are using for this tutorial.

Scroll down to the Messaging section. Under the heading that says A MESSAGE COMES IN, click the dropdown menu and select Function. Next, select your Service in the dropdown menu under the SERVICE header, then select ui in the dropdown menu under the ENVIRONMENT header, and finally the function path you named earlier under the FUNCTION PATH header.

Twilio Phone Number - Messaging functionality set up

After that, hit the blue button labeled Save at the bottom of the page. Your Twilio phone number should now be set up and you can now get some spooky recommendations!

Text messages from a Twilio phone number giving horror movie recommendations

Conclusion

Though this tutorial only searches for horror movies, you can take a look at the TMDB API documentation and see what else you can come up with. Perhaps you could even send movie recommendations via email by using Twilio SendGrid? Maybe utilize Twilio’s WhatsApp API in case that’s your primary form of communication? Regardless, I hope you have a great time during the spookiest month of the year!

Dancing skeleton GIF

I can’t wait to see what you build next!

Dainyl Cua is a Software Engineer who loves helping others code. They would love to talk at any time and help you out. They can be reached through LinkedIn.