Ryan
Posted on October 12, 2017
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".
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.
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.
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';
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');
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);
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.
});
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!');
});
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'));
});
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'));
});
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.
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;
});
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!");
});
});
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'));
});
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);
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);
});
};
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 therequest
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
}
});
};
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);
});
};
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!");
});
}
}
}
}
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);
}
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();
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'));
});
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.
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:
- Bogus Definition: Tweets bogus definitions.
- Just Doing Junk: Retweets people who are just doing things.
- Spots Of Midnight: Tweets the spots of midnight around the world.
Posted on October 12, 2017
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.