Twitter BOT to send joke and image (lambda + typescript)
Tags: twitter, lambda, typescript, serverless
Published: 2022-02-15 14:28:21


![Twitter Bot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvw451vsxc5c8omrre75.png)

In this blog post, I am going to show you how you can build your own twitter bot which will periodically send out tweet (joke) with images 

**How will it work?**
- We will get a random joke from https://jokeapi.dev/ API. It's free with some limitation on hit rate etc.
- We will get a random but relevant GIPHY image using https://developers.giphy.com/ API
- We will create and send a tweet using twitter api and `twitter-api-client` node package

 
![system design](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cdxup4tmamcnh9v9p16j.png)

**End Result will look like this** 
{% twitter 1493178651692646400 %}


**Prerequisite**

- You should have a twitter account to register with twitter's [developer portal](https://developer.twitter.com)
- AWS access key & secret key to deploy on AWS

**Step 1: Register with twitter's developer portal and get relevant keys**

Getting keys from twitter's developer account is pretty easy and straight forward process. You can get a step by step guide from this video - 

{% youtube q-sIXAKV4Tc %}

**Step 2: Register with giphy's developer account and get their access key**

Again, this is very easy and self explanatory (https://developers.giphy.com/), but in case you need some detailed information on this API and how to get its access keys, you can watch this video - 

{% youtube HRh6zHRwRLo %}

**Step 3: Now you have your twitter & giphy keys, so let's get started with writing the bot code**

We will be using the [serverless](https://www.serverless.com/) framework to write this lambda function.

> Install serverless globally

``` javascript
npm install -g serverless
```

> Create an typescript project from the serverless template library

```javascript
sls create --template aws-nodejs-typescript
```

> Install required node packages for this project

```javascript
npm install --save axios twitter-api-client
npm install --save-dev serverless-offline
```

**[axios](https://www.npmjs.com/package/axios):** Promise based HTTP client for the browser and node.js
**[twitter-api-client](https://www.npmjs.com/package/twitter-api-client)** Node.js client for Twitter API
**[serverless-offline](https://www.npmjs.com/package/serverless-offline)** This Serverless plugin emulates AWS λ and API Gateway on your local machine to speed up your development cycles.

> Define your function in serverless.ts like this 

```javascript
import type { AWS } from '@serverless/typescript';

const serverlessConfiguration: AWS = {
  service: 'twitter-bot',
  frameworkVersion: '2',
  custom: {
    webpack: {
      webpackConfig: './webpack.config.js',
      includeModules: true
    }
  },
  // Add the serverless-webpack plugin
  plugins: ['serverless-webpack', 'serverless-offline'],
  provider: {
    name: 'aws',
    runtime: 'nodejs14.x',
    apiGateway: {
      minimumCompressionSize: 1024,
    },
    environment: {
      AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
    },
  },
  functions: {
    twitterJokesBot: {
      handler: 'handler.twitterJokesBot',
      events: [
        {
          http: {
            method: 'get',
            path: 'twitterJokesBot',
          }
        }
      ]
    }
  }
}

module.exports = serverlessConfiguration;

```

Here we have defined a function named `twitterJokesBot` and it will be of type `GET`

**Make sure that you add your `serverless-offline` plugin to the list of plugins in serverless.ts**

> Define twitter-api-client 

```javascript
import { TwitterClient } from 'twitter-api-client';

export const twitterClient = new TwitterClient({
    apiKey: process.env.TWITTER_API_KEY,
    apiSecret: process.env.TWITTER_API_SECRET,
    accessToken: process.env.TWITTER_ACCESS_TOKEN,
    accessTokenSecret: process.env.TWITTER_ACCESS_SECRET,
});
```

**Make sure that you have added these keys and tokens into your environment**

> Define joke functions

 ```javascript
import Axios, { AxiosResponse } from "axios";

/**
 * Get a joke from jokeapi.dev. If the length of tweet string is greater than 280 character then recurse till you find one.
 * @returns string
 */
export const getJokeTweet = async () => {
  //List of hashtags for the tweet
  const hashTags =
    "@metacollective9 #jokes #javascript #programming #dev #linux #java #programming #python #reactjs #DataScience #infosec #gamedev #BigData #serverless";

  const footerText = "From https://jokeapi.dev";

  //Get a random joke from jokeapi
  let joke: AxiosResponse = await Axios.get(
    "https://v2.jokeapi.dev/joke/Programming?blacklistFlags=nsfw,religious,political,racist,sexist,explicit&type=single"
  );

  // \n will add line breaks to the tweet
  const tweet = `${joke.data.joke} \n\n ${hashTags} \n\n ${footerText}`;

  console.log(tweet.length);

  if (tweet.length <= 280) {
    return tweet;
  }

   return await getJokeTweet();
  
};

/**
 * Get a gif image from giphy.com
 * @returns image buffer
 */
export const getJokeImage = async () => {
  //get a giphy to go with this joke
  let giphy: AxiosResponse = await Axios.get(
    `https://api.giphy.com/v1/gifs/random?api_key=${process.env.GIPHY_API_KEY}&tag=bad+joke&rating=g`
  );

  //Get that giphy image as a buffer.
  const image: AxiosResponse = await Axios.get(giphy?.data.data.images.downsized_medium.url, {
    responseType: "arraybuffer",
  });

  return image;
};

 ```

There are two parts of this

- **`getJokeTweet`** is to get a random joke from `jokeapi.dev`, then adds your hashtags and other texts that you want to attach with this tweet. It also checks that it is not more than 280 characters (tweet character limit as of Feb 2022), if it is then recurse and gets another joke
**Lookout for `blacklistFlags` in the API call. This makes sure that you get a nice & clean joke**
- **`getJokeImage`**is to get a random giphy image using the giphy api keys we got earlier. You have to get it as an `arraybuffer` so that we can upload it to twitter as a `base64` image

> Put them all together in your `handler.ts`

```javascript
import { APIGatewayProxyHandler } from "aws-lambda";
import "source-map-support/register";
import { twitterClient } from "./src/util/twitterApiClient";
import { getJokeImage, getJokeTweet } from "./src/util/joke";
import { AxiosResponse } from "axios";
import { MediaUpload } from "twitter-api-client";
export const twitterJokesBot: APIGatewayProxyHandler = async (_event, _context) => {
  let tweet: string = "";

  try {
    //get Joke
    tweet = await getJokeTweet();

    const image: AxiosResponse = await getJokeImage();

    //Upload media to twitter
    const media: MediaUpload = await twitterClient.media.mediaUpload({
      media: Buffer.from(image.data, "binary").toString("base64"),
    });

    //Send a tweet with joke and media
    await twitterClient.tweets.statusesUpdate({
      status: tweet,
      media_ids: media.media_id_string, 
    });
  } catch (error) {
    console.log(error);
  }

  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        joke: tweet,
        length: tweet.length
      },
      null,
      2
    ),
  };
};
```

This is also a 2 step process
- First is to upload a media asset to your twitter account
- Then use the `media_id_string` from the response of uploaded media and send the tweet with it

> That's it, now you are all set to test it - 

![test giphy](https://media.giphy.com/media/3o6Mbbs879ozZ9Yic0/giphy.gif)

Run `sls offline --stage local` in your terminal and you should have your endpoint ready to test with like this


![sls offline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gvv8o9oxn1820qli9qoa.png)

:tada::tada::tada: Your bot is ready to launch. 

If you wish to deploy it to your AWS account, then set your AWS Access Key & AWS secret Key in the environment and run `sls deploy --stage dev`

You can get the entire example from this repository https://github.com/appletreeat56/twitter-bot