Building a Twitter API v2 Bot in 15 minutes

Count von Bitcoin

While browsing Twitter today, two tweets from my favorite accounts showed up in a row. One, from CMS Holdings, was his trademark "THIRTY THOUSAND UNITED STATES DOLLARS PER BITCOIN" meme. The next, from Count von Count, showed The Count from Sesame Street counting up to another number.

And it clicked. How hard would it be to build a twitter bot to tweet the current Bitcoin price in Count von Count's voice?

TLDR: It took 15 minutes.

Check out the bot here

Modern web development is amazing for projects like these. With npm, node, and typescript, you can knock something out in no time.

Here's how I did it.

Step 1. Create a node + Typescript app

First up, create a new node project with typescript support. Thanks to sane defaults we can just fire these commands off and think nothing of it.

mkdir count-von-count-bot
cd count-von-count-bot
git init
echo "node_modules\n.env" > .gitignore
npm init -y
npm i -D ts-node @types/node
npx tsc --init

Awesome, we've now got the shell for our bot.

Step 2. Get the Twitter credentials

Next up we have to get Twitter credentials for our bot. This step probably took the longest to figure out since I don't use the Twitter API Dashboard reguarly, especially not since they released API v2.

To get a Bot account, do the following:

  1. Visit the Twitter Developer Dashboard and click "Sign Up" in the top right.
  2. Tap "Hobbyist" and "Making a Bot".

You'll have to fill out a Bot application, which should be auto-approved.

Once you're finished, you'll be given an API Key and API Secret. Put these into your .env file like so:

TWITTER_API_KEY="<your-api-key>"
TWITTER_API_SECRET="<your-api-secret>"

Step 3. Enable write access from the Bot

This is the step that was most opaque. Once you're in the twitter developer dashboard, click into your app from the sidebar under "Projects & Apps"

On the bottom of this page, find "User authentication settings", and click "Edit"

Once there, enable "OAuth 1.0a" access, and select "Read and Write".

For callback URL and website URL, just fill in any valid URLs, since this will be a private app.

Step 4. Get Credentials for your user

On the app page, click "Keys and tokens", click "Generate" for "Access Token and Secret".

Copy those values into your .env file like so:

TWITTER_ACCESS_TOKEN="<your-access-token>"
TWITTER_ACCESS_TOKEN_SECRET="<your-access-token-secret>"

Once you're done, you should see that these tokens were "Created with Read and Write permissions".

Step 5. On to the code

The code part is pretty simple. NPM offers a bunch of high quality, maintained packages for this project.

First, lets build a script that will pull in the latest Bitcoin price from CoinGecko. CoinGecko has an easy-to-use API which we'll be using here.

We'll be making basic HTTP requests to this endpoint, so let's add fetch to node

npm i cross-fetch

And build an index.ts file with the following:

import fetch from "cross-fetch"

const fetchBitcoinPrice = async () => {
  const resp = await fetch(
    "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=USD"
  )
  const json = await resp.json()

  return json.bitcoin.usd
}

const main = async () => {
  const price = await fetchBitcoinPrice()

  console.log(price)
}

main()
  .then(() => {
    process.exit(0)
  })
  .catch((e) => {
    console.error(e)
    process.exit(1)
  })

And update our package.json to have a script for this:

{
  // ...
  "scripts": {
    "tweet": "ts-node index.ts"
  }
  // ...
}

Step 6. Try it out

npm run tweet

You should see something like:

29642

💥 You have the bitcoin price showing up.

One of the beauties here is that it's very low risk if this API request fails. If we don't tweet at some point, who cares? In a real world scenario, you'd want some graceful error handling, but we don't need it here.

Step 7. Make it words

Ok, next up, how do we take this number and make it into words? Hmm... let's try searching npm for number to words.

Bingo.

Let's add it to our project.

npm i number-to-words

And add it to our index.ts file:

 import fetch from "cross-fetch"
+import { toWords } from "number-to-words"

 const fetchBitcoinPrice = async () => {
   const resp = await fetch(
     "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=USD"
   )
   const json = await resp.json()

   return json.bitcoin.usd
 }

 const main = async () => {
   const price = await fetchBitcoinPrice()
+  const words = toWords(price)

-  console.log(price)
+  console.log(words)
 }

 main()
   .then(() => {
     process.exit(0)
   })
   .catch((e) => {
     console.error(e)
     process.exit(1)
   })

And run it again:

npm run tweet

You should see something like:

twenty-nine thousand, six hundred forty-two

Step 8. Make it a tweet

Now how do we make it Tweet with Twitter API v2. npm gives us twitter-api-v2 as a widely used package, lets give it a shot.

For this, we'll need our API secrets, so let's load from .env using the dotenv package.

npm i dotenv twitter-api-v2

And hook it up to our app:

+import "dotenv/config"
 import fetch from "cross-fetch"
 import { toWords } from "number-to-words"
+import { TwitterApi } from "twitter-api-v2"

 const fetchBitcoinPrice = async () => {
   const resp = await fetch(
     "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=USD"
   )
   const json = await resp.json()

   return json.bitcoin.usd
 }

+const tweet = async (message: string) => {
+  const appKey = process.env.TWITTER_API_KEY;
+  const appSecret = process.env.TWITTER_API_SECRET;
+  const accessToken = process.env.TWITTER_ACCESS_TOKEN;
+  const accessSecret = process.env.TWITTER_ACCESS_TOKEN_SECRET;

+  if (!appKey || !appSecret || !accessToken || !accessSecret) {
+    throw new Error("key or secret is not set");
+  }

+  const twitterClient = new TwitterApi({
+    appKey,
+    appSecret,
+    accessToken,
+    accessSecret,
+  });

+  await twitterClient.v2.tweet(message);
+};

 const main = async () => {
   const price = await fetchBitcoinPrice()
   const words = toWords(price)

-  console.log(words)
+  await tweet(words)
 }

 main()
   .then(() => {
     process.exit(0)
   })
   .catch((e) => {
     console.error(e)
     process.exit(1)
   })

OK, now we can tweet, lets give it a shot!

npm run tweet

And check your twitter account! 🎉

Step 9. Automate this

To automate this, we just need a cron job, ideally hosted on someone elses computer for free.

There are plenty of options here, like Render or Vercel, but we stuck to the OG: Heroku.

In Heroku, create a new app, add all of the environment variables from .env to the UI, and set up the "Heroku Scheduler" addon. Since this is not a web app, you'll never really hit your Dyno usage limits. So set up a bunch of times per day to run your script npm run tweet.

But! We have one caveat. Since we're running ts-node directly here, without pre-compiling our Typescript app, we need to move all of our devDependencies to dependencies since they will be used in "production" here. Your package.json should look like:

{
  "name": "count-von-bitcoin",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "tweet": "ts-node index.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@types/node": "^17.0.35",
    "@types/number-to-words": "^1.2.1",
    "cross-fetch": "^3.1.5",
    "dotenv": "^16.0.1",
    "number-to-words": "^1.2.4",
    "ts-node": "^10.8.0",
    "twitter-api-v2": "^1.12.2"
  }
}

Push your code to heroku and you're done!

Check out our working version of the Bot here: @CountVonBitcoin on Twitter

M2 Labs is an experienced team of full-stack crypto developers building dapps, smart contracts, and tools to help advance Web3. Get in touch with us at m2.xyz.