DEV Community

Cover image for Make a Node.js Twitter Bot: Bogus Definition
Ryan
Ryan

Posted on • Edited on

Make a Node.js Twitter Bot: Bogus Definition

Wassup wassup, I'm glad you're here! We'll be discussing how to create a Twitter bot. A Twitter bot is an account that is connected to a hosted app that uses the Twitter API in order to make requests for that Twitter bot, such as tweeting, retweeting, liking, and more. Bots are a fun way to improve your coding skills through creativity, there are a bunch of different APIs available that you can use to make your bot do all sorts of things. Throughout the article, I'll be referencing the setup of one of my bots, @BogusDefinition. This bot tweets out random words with definitions that seem correct, but actually aren't.

You can find all the code used in this article on GitHub.


Contents

There are three main sections to this article. Following the article will walk you through the entire setup of a Twitter bot - from setting up your bot's app with Twitter, to hosting the bot with its upgraded functionality on a live server:

  • Hello, Twitter Bot
  • Making the Bot Smarter
  • Deploying the Bot

Hello, Twitter Bot

Creating the App

The first thing that you want to do is create the Twitter account for your bot. After you've got the account set up, you can head on over to the Twitter's Application Management page. You should see something like this, click on "Create New App".

TwitterApps

It will prompt you for some information, such as name, description, and website. For the website, you can use your GitHub account's URL - or some other URL that you feel is valid. Check the "Developer Agreement" checkbox, and click "Create your Twitter application".

Your Keys & Access Tokens

In order for your Node.js app to be authenticated when requesting from the Twitter API, you've got to include keys and access tokens. These keys and access tokens work a lot like a username and password, they allow your Node.js app to "login" to the Twitter account. On your Twitter app's page, you should see an "Application Settings" tab, with a "Consumer Key" section.

ApplicationSettings

Click on the highlighted link, "manage keys and access tokens". You'll see the following page. Copy down the Consumer Key (API Key), and Consumer Secret (API Secret). Then go ahead and click "Create my access token" at the bottom. Copy the Access Token, and Access Token Secret.

KeysAndTokens

The Bot's First Tweet

Now that you have your keys and access tokens, you can start developing the code for your bot. Create a new folder on your computer for your bot's code. Inside of the folder, create a file called env.js. This file should be at the root of your project and is going to contain your environment variables, which will include the keys and access tokens you just created. :) env.js should look like this:

    process.env['TWITTER_CONSUMER_KEY'] = '1mFxGkYQPEuxqbtHd55cb89';
    process.env['TWITTER_CONSUMER_SECRET'] = 'IK1ytDHhtzF4AUvh5fkmoaBC1mTvmWT18WjAhl3Y9bQv5y8o';
    process.env['TWITTER_ACCESS_TOKEN'] = '2057253019-IpiUHS9FuJqPQSQbaChVHvEza1J08QZlJYY3Z';
    process.env['TWITTER_ACCESS_TOKEN_SECRET'] = 'jHj81H3qYUOrOSXNg6RMVAPaKgMv6pz0ogs1YWeJ7pa';
Enter fullscreen mode Exit fullscreen mode

Be sure to use your own keys and access tokens!

The next thing you need to do is create a file called index.js. This file is going to be the main file for your bot, it will start your bot. First, I'll break this file down by each section, then show a shot of its entire content.

You want to set up your required Node packages at the top of the file. The packages that we are using are Express, Request, and Twit. Express allows us to set up a minimalist web framework. Request will give us the capability to make easy HTTP requests in order to make some API calls. Last but not least, Twit will make it easy for us to access the Twitter API:

var express = require('express');
var twit = require('twit');
var request = require('request');

Enter fullscreen mode Exit fullscreen mode

The next bit is pretty simple, it creates the Express app and sets the port:

var app = express();
app.set('port', process.env.PORT || 5000);
Enter fullscreen mode Exit fullscreen mode

The next part is where you're going to use the the env.js variables, these are the variables that let our app receive authentication when connecting to Twitter. When initializing your bot with the Twit package, you've got to pass your keys and tokens as parameters like this:

if(process.env.TWITTER_CONSUMER_KEY == undefined){
  require('./env.js');
}
var bot = new twit({
  consumer_key:         process.env.TWITTER_CONSUMER_KEY,
  consumer_secret:      process.env.TWITTER_CONSUMER_SECRET,
  access_token:         process.env.TWITTER_ACCESS_TOKEN,
  access_token_secret:  process.env.TWITTER_ACCESS_TOKEN_SECRET,
  timeout_ms:           60*1000,  // optional HTTP request timeout to apply to all requests. 
});
Enter fullscreen mode Exit fullscreen mode

The if-statement is used in order to figure out whether the application is running locally or on a server. If TWITTER_CONSUMER_KEY is defined, the app knows it's running on a server that has the definition already. If it is not defined, then the app knows it needs to include the env.js file that we have locally. This env.js file will not be a part of the GitHub repo.

Taking a look at the Twit documentation you can see that you're able to post a tweet quite easily. There is a .post function that you are able to call. Make your bot tweet "Beep boop bop!" with this block:

bot.post('statuses/update', { status: 'Beep boop bop!' }, function(err, data, response) {
  console.log('Success!');
});
Enter fullscreen mode Exit fullscreen mode

Almost there! You just need to tell your app what port to listen to:

app.listen(app.get('port'), function() {
  console.log('Bot is running on port', app.get('port'));
});
Enter fullscreen mode Exit fullscreen mode

Your entire index.js file should look like this:

var express = require('express');
var twit = require('twit');
var request = require('request');

var app = express();
app.set('port', process.env.PORT || 5000);

if(process.env.TWITTER_CONSUMER_KEY == undefined){
  require('./env.js');
}
var bot = new twit({
  consumer_key:         process.env.TWITTER_CONSUMER_KEY,
  consumer_secret:      process.env.TWITTER_CONSUMER_SECRET,
  access_token:         process.env.TWITTER_ACCESS_TOKEN,
  access_token_secret:  process.env.TWITTER_ACCESS_TOKEN_SECRET,
  timeout_ms:           60*1000,  // optional HTTP request timeout to apply to all requests. 
});

bot.post('statuses/update', { status: 'Beep boop bop!' }, function(err, data, response) {
  console.log('Success!');
});

app.listen(app.get('port'), function() {
  console.log('Bot is running on port', app.get('port'));
});

Enter fullscreen mode Exit fullscreen mode

Running Your Bot

Before you're able to run your bot locally, you've got to install --save each of the Node packages that are being used. So, from your app's root folder, run the commands npm install --save express, npm install --save request, npm install --save twit. If you haven't run a Node.js application locally before, you can give my article Node.js + Express Server Setup a glance.

Okay, cool! Your bot is ready to send its first tweet! Start up your bot with node index.js, and you should see "Success!" on the command-line. Check out your bot's Twitter profile, and you'll see that it tweeted "Beep boop bop!"


Making the Bot Smarter

It's dope that you've got the bot tweeting, but there's more to do now! This is where the fun part starts. Your bot can't actually get smarter, but we can make people believe it is with some simple API calls. ;) Bogus Definition is a bot that tweets a random word, with a definition that sounds kind of right, but isn't. This is accomplished by requesting a random word from Wordnik, then a rhyming word of that random word, then a definition of that rhyming word, and then finally combining the random word with the rhyming word's definition.

Tweet -- "randomWord: rhyming word's definition"

In this section, I'll show you how I did that.

API Calls

The Request package is going to make it easy for us, too! Let's kick things off by tweeting out a random word. You might ask, how are we going to get a random word? Well, check out the Wordnik API. Under words, there's a randomWord endpoint that we can access with an HTTP request, and it will give us a random word. In order to do that, we need to use request. You can get the request URL by clicking the "Try it out!" button under the Wordnik endpoint section.

TryItOut

Here's the randomWord request:

randomWord = 'http://api.wordnik.com:80/v4/words.json/randomWord?hasDictionaryDef=true&minCorpusCount=0&maxCorpusCount=-1&minDictionaryCount=1&maxDictionaryCount=-1&minLength=5&maxLength=-1&api_key=' + process.env.WORDNIK_KEY;
  request(randomWord, function (error, response, body) {
    randomWord = JSON.parse(body).word;
  });
Enter fullscreen mode Exit fullscreen mode

I've signed up for a Wordnik account, and added my Wordnik API key to my env.js file as WORDNIK_KEY. You can do this too here. After you sign up, you can find your API key under your account settings at the bottom of the page.

Now inside of that request call, we can tweet the word that we received. You'll notice this line JSON.parse(body).word. We are receiving body as a JSON-formatted text response, and parsing it to a JSON object. This body is the response from the server, which contains our random word, under the .word attribute. If you haven't heard of JSON, or parsing it, you can learn more here. It's very useful, so be sure you understand! After you've parsed your random word, you can tweet it by putting a .post call inside of your request callback (the second parameter of the request function call):

request(randomWord, function (error, response, body) {
    // When the request finishes, this post will be called
    bot.post('statuses/update', { status: randomWord}, function(err, data, response) {
        console.log("Success!");
    });
});
Enter fullscreen mode Exit fullscreen mode

Go ahead and run your bot with the command-line. Your index.js should look like this now:

var express = require('express');
var twit = require('twit');
var request = require('request');

var app = express();
app.set('port', process.env.PORT || 5000);

var bot = new twit({
  consumer_key:         process.env.TWITTER_CONSUMER_KEY,
  consumer_secret:      process.env.TWITTER_CONSUMER_SECRET,
  access_token:         process.env.TWITTER_ACCESS_TOKEN,
  access_token_secret:  process.env.TWITTER_ACCESS_TOKEN_SECRET,
  timeout_ms:           60*1000,  // optional HTTP request timeout to apply to all requests. 
});

randomWord = 'http://api.wordnik.com:80/v4/words.json/randomWord?hasDictionaryDef=true&minCorpusCount=0&maxCorpusCount=-1&minDictionaryCount=1&maxDictionaryCount=-1&minLength=5&maxLength=-1&api_key=' + process.env.WORDNIK_KEY;
request(randomWord, function (error, response, body) {
    // When the request finishes, this post will be called
    bot.post('statuses/update', { status: randomWord}, function(err, data, response) {
        console.log("Success!");
    });
});

app.listen(app.get('port'), function() {
  console.log('Bot is running on port', app.get('port'));
});
Enter fullscreen mode Exit fullscreen mode

Asynchronous Problem

Okay, you've got a random word, and now want a rhyming word. So, all you need to do is make another request with the randomWord variable, right? Kind of, but there's a problem. Making a request is an asynchronous call. This means that when we make the request, that our request gets sent to the API, and at the same time our code continues to run. The problem with this is that our code is not waiting for the API response before continuing. So, it's possible that our code will try to make the second API request for the rhyming word before the random word is even returned, which will result in an undefined return value. How do we fix this? If you want a solution better than callbacks, check out The Path to Conquering Async JavaScript.

Callbacks

There are multiple ways to solve the asynchronous problem depending on what exactly you're doing, you can check out async functions, and promises. I'm going to solve the problem using callbacks. A callback function is a function passed into another function as an argument, which is then called once the outer function is done executing.

Here's an example, first runThisFunctionFirst is called, and it gets passed runThisFunctionSecond as the parameter callback. So, runThisFunctionFirst will add 2 to the number parameter, and once it's finished doing that, it will call callback which is runThisFunctionSecond and pass it plusTwo. Then, runThisFunctionSecond will log the value after runThisFunctionFirst is done doing its work.

function runThisFunctionSecond(plusTwo) {
  console.log('The input number + 2 is: ' + plusTwo);
}

function runThisFunctionFirst(number, callback) {
  var plusTwo = number + 2;
  callback(plusTwo); // this runs theCallbackFunction because it was passed as an arguement
}

runThisFunctionFirst(number, runThisFunctionSecond);
Enter fullscreen mode Exit fullscreen mode

Bogus Definition Chain

So how is this going to solve our problem? Well, we can make a callback chain similar to this with three functions: getWord, getRhymingWord, and getRhymingDef. These will be called just like runThisFunctionFirst and runThisFunctionSecond, but we will have a third function that would be equivalent to runThisFunctionThird.

Let's go ahead and create some global variables that will hold our content, and create our first function:

// Global variables needed to create the tweet
var randomWord;
var rhymingWord;
var rhymingDef;

function getWord(callback){
  randomWord = 'http://api.wordnik.com:80/v4/words.json/randomWord?hasDictionaryDef=true&minCorpusCount=0&maxCorpusCount=-1&minDictionaryCount=1&maxDictionaryCount=-1&minLength=5&maxLength=-1&api_key=' + process.env.WORDNIK_KEY;
  request(randomWord, function (error, response, body) {
    randomWord = JSON.parse(body).word;
    callback(randomWord);
  });
};
Enter fullscreen mode Exit fullscreen mode

Now, getWord will need to be passed a function getRhymingWord so that it can call it once it's finished making its request.

Notice that callback is being called within the second parameter of the request call, it needs to be there because the second parameter is called once the API returns a value.

You can see getRhymingWord needs two parameters: one for the randomWord that is returned by the request and another for the callback function, which will be getRhymingDef.

function getRhymingWord(randomWord, callback){
  rhymingWord = 'http://api.wordnik.com:80/v4/word.json/' + randomWord + '/relatedWords?useCanonical=false&relationshipTypes=rhyme&limitPerRelationshipType=10&api_key=' + process.env.WORDNIK_KEY;
    request(rhymingWord, function (error, response, body) {
    try{
      rhymingWord = JSON.parse(body)[0].words[0];
      callback(rhymingWord);
    }catch(err){
      sendTweet(); // The word didn't rhyme with anything... restart
    }
  });
};
Enter fullscreen mode Exit fullscreen mode

Now we can add the third function:

function getRhymingDef(rhymingWord, callback){
  rhymingDef = 'http://api.wordnik.com:80/v4/word.json/' + rhymingWord + '/definitions?limit=200&includeRelated=true&useCanonical=false&includeTags=false&api_key=' + process.env.WORDNIK_KEY;
  request(rhymingDef, function (error, response, body) {
    rhymingDef = JSON.parse(body)[0].text;
    callback(rhymingDef);
  });  
};
Enter fullscreen mode Exit fullscreen mode

We still aren't calling the functions yet, so let's create a function called sendTweet that will be called once our bot starts up. This function will implement the callback chain. It will work just like the example that we used. First it calls getWord and passes nowGetRhymingWord as the callback function. Then inside of getWord it calls callback which is nowGetRhymingWord and passes it nowGetRhymingDef, and so on:

var sendTweet = function(){
  getWord(nowGetRhymingWord);
  function nowGetRhymingWord(randomWord){
    getRhymingWord(randomWord, nowGetRhymingDef);
    function nowGetRhymingDef(rhymingWord){
      getRhymingDef(rhymingWord, nowReturnTweet);
      function nowReturnTweet(definition){
        var wordAndDef = capitalizeFirstLetter(randomWord) + ": " + rhymingDef;
        bot.post('statuses/update', { status: wordAndDef }, function(err, data, response) {
          console.log("Success!");
        });
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice there is a helper function capitalizeFirstLetter. I added this because the response from Wordnik is not capitalized when getting a word. The function looks like this:

function capitalizeFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
}
Enter fullscreen mode Exit fullscreen mode

Okay... but sendTweet still isn't being called! Let's call it, and set an interval for it to be called:

// Send tweet every 28 minutes, and on start
setInterval(function() {
  sendTweet();
}, 1700000);
sendTweet();
Enter fullscreen mode Exit fullscreen mode

So your entire index.js file should now look like this:

var express = require('express');
var twit = require('twit');
var request = require('request');

var app = express();
app.set('port', process.env.PORT || 5000);

if(process.env.TWITTER_CONSUMER_KEY == undefined){
  require('./env.js');
}

var bot = new twit({
  consumer_key:         process.env.TWITTER_CONSUMER_KEY,
  consumer_secret:      process.env.TWITTER_CONSUMER_SECRET,
  access_token:         process.env.TWITTER_ACCESS_TOKEN,
  access_token_secret:  process.env.TWITTER_ACCESS_TOKEN_SECRET,
  timeout_ms:           60*1000,  // optional HTTP request timeout to apply to all requests. 
})
///////////////////////////////////

// Global variables needed to create the tweet
var randomWord;
var rhymingWord;
var rhymingDef;

// Callback chain
var sendTweet = function(){
  getWord(nowGetRhymingWord);
  function nowGetRhymingWord(randomWord){
    getRhymingWord(randomWord, nowGetRhymingDef);
    function nowGetRhymingDef(rhymingWord){
      getRhymingDef(rhymingWord, nowReturnTweet);
      function nowReturnTweet(definition){
        var wordAndDef = capitalizeFirstLetter(randomWord) + ": " + rhymingDef;
        bot.post('statuses/update', { status: wordAndDef }, function(err, data, response) {
          console.log("Success!");
        });
      }
    }
  }
}

// Send tweet every 28 minutes, and on start
setInterval(function() {
  sendTweet();
}, 1700000);
sendTweet();

function getWord(callback){
  randomWord = 'http://api.wordnik.com:80/v4/words.json/randomWord?hasDictionaryDef=true&minCorpusCount=0&maxCorpusCount=-1&minDictionaryCount=1&maxDictionaryCount=-1&minLength=5&maxLength=-1&api_key=' + process.env.WORDNIK_KEY;
  request(randomWord, function (error, response, body) {
    randomWord = JSON.parse(body).word;
    callback(randomWord);
  });
};

function getRhymingWord(randomWord, callback){
  rhymingWord = 'http://api.wordnik.com:80/v4/word.json/' + randomWord + '/relatedWords?useCanonical=false&relationshipTypes=rhyme&limitPerRelationshipType=10&api_key=' + process.env.WORDNIK_KEY;
    request(rhymingWord, function (error, response, body) {
    try{
      rhymingWord = JSON.parse(body)[0].words[0];
      callback(rhymingWord);
    }catch(err){
      sendTweet(); // The word didn't rhyme with anything... restart
    }
  });
};

function getRhymingDef(rhymingWord, callback){
  rhymingDef = 'http://api.wordnik.com:80/v4/word.json/' + rhymingWord + '/definitions?limit=200&includeRelated=true&useCanonical=false&includeTags=false&api_key=' + process.env.WORDNIK_KEY;
  request(rhymingDef, function (error, response, body) {
    rhymingDef = JSON.parse(body)[0].text;
    callback(rhymingDef);
  });  
};

// Helper function for to capitalize the random word
function capitalizeFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
}

// Tells the Express app to listen to a port
app.listen(app.get('port'), function() {
  console.log('Node app is running on port', app.get('port'));
});
Enter fullscreen mode Exit fullscreen mode

Deploying the Bot

Hey hey! Your bot can now run from the command-line. But you don't want it to have to run on the command-line for its entire life, so let's deploy it to Heroku. If you haven't deployed a Heroku app before, check out my article Auto-Deploy a Node.js Server: Heroku + GitHub. It will give you all of the steps you need to get the code connected, but you will still need to get config variables going on your Heroku app. I'll talk about that next here.

When deploying your app, don't include env.js in your GitHub repo.

Heroku Config Vars

Remember making the env.js file? Well, Heroku doesn't have it because it's not included in your GitHub repo. We do this because we don't want the entire internet knowing your Twitter app's authentication information! If someone were to find those keys and tokens, they could go ahead and start using your bot for their own purposes. But there's good news, under your Heroku app's "Settings" tab, there is a "Config Variables" section. Click on "Reveal Config Vars", and here you can add all of the information that is inside your env.js file, and it will stay hidden within Heroku, out of sight from the rest of the internet. :)

Sleepy Bot Problem

Heroku will put your app to sleep if it hasn't been pinged in a while. That means, if the address of your Heroku app isn't visited, that your bot won't work anymore. You're in luck though, under the Heroku app's "Resources" tab, there is an "Add-ons" section. Here you can search for and add "Heroku Scheduler" to your app.

Scheduler

This allows you to schedule command-line calls of your choice. With this, you can have the scheduler call node index.js every day, hour, or 10 minutes to make your bot stay awake and tweet!


Review

Congratulations! You've successfully built and deployed a Node.js Twitter bot! You did this by using multiple Node packages, making multiple API calls using a callback chain, and deploying to Heroku. Now it's time to get creative with some other APIs to make your bot do different things. Here's a fun list of APIs that you can check out to inspire your bot's next function. :) Please comment below if you have any comments, questions, or concerns! I'll be happy to reply.


Other Twitter Bots

I've created some other bots, you can find them all at:

Top comments (0)