This is how I built a Twitter bot that posts random cat posts from Reddit.
Tags: ktor, bot, serverside, cronjob
Published: 2022-10-28 13:56:12

### ⭐Introduction

- I've always wanted to build a [bot](https://twitter.com/catktorbot) that posts something independently with the data.

- In this case, I've included data from a few subreddits where people post cat images, like r/Catswithjobs, r/catpictures, and r/SupermodelCats.

- Now, using the data from the above subreddits, I've built this bot that posts to Twitter through a cron-job service and also returns an API where the posted data by the bot can be retrieved.

### 👨🏻‍💻 Tech Stack

#### I've used the following tech stack to build this bot and API:

- [Ktor](https://ktor.io/) - for building the bot, API, and posting respective data to the database.

- [Ktor-Client](https://ktor.io/docs/getting-started-ktor-client.html) - for retrieving JSON data from subreddits(s).

- [MongoDB](https://www.mongodb.com/) - for storing previous posts that are posted by the bot, which can be retrieved by the API.

- [KMongo](https://github.com/Litote/kmongo) - for chit-chatting with MongoDB.

- [Twitter4j](https://github.com/Twitter4J/Twitter4J) - for interacting with the Twitter API so that the bot can post the data to Twitter.

- [Apache Commons IO](https://commons.apache.org/proper/commons-io/) - converts the URL into a file (so that the bot can tweet the image URL as an image that was retrieved from the subreddit).

- [Zeplo](https://www.zeplo.io/) - for triggering the bot endpoint automatically every third hour; In other words, zeplo is used as a cron-job(s) service(if that makes sense in this context).

- [Ktor Plugins:ktor-api-key](https://github.com/LukasForst/ktor-plugins) - It is a simple authentication provider for Ktor that verifies the presence of the API key from the header.

- [Render](https://render.com/) - for deploying the project using [Docker](https://www.docker.com/).

### 🤖Building The Bot

![catworking](https://media.tenor.com/6cYTV2P3oDYAAAAC/working-hahacc.gif)

#### This is what the visual of the bot looks like:

![botWorking.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666944932122/U6vq-EQ5Z.png)

- The implementation of this bot was pretty simple and clear to me. As I don't want to get the data only from a specific subreddit from the above-mentioned.

- I've added the URLs of these subreddits to the database(MongoDB) so that I can retrieve any random subreddit's URL for further usage:

```
val kMongo = KMongo.createClient(System.getenv("MONGODB_URL"))
    val collectionData = kMongo.getDatabase(System.getenv("DB_NAME")).getCollection(System.getenv("SUBREDDIT_URLS"))
    val listOfSubredditURLs = mutableListOf<String>()
    collectionData.find("""{url:{${MongoOperator.regex}:/www.reddit.com/i}}""").toList().forEach { document ->
        listOfSubredditURLs.add(document.getString("url"))
    }
    val pickedSubredditURL = listOfSubredditURLs.random()
```
#### 🧐Filtering the data retrieved from the random subreddit

![bot](https://media.tenor.com/seONvVZIk4gAAAAC/robot-nintendo.gif)

- As I only wanted to post an image with a respective title to it, which will be retrieved from the above code, I don't want to make posts that contain videos or others that could interpret the bot. So I've filtered the random data which I'll get from the above code:

```
val fetchDataFromURL = ktorClient.get(pickedSubredditURL).body<RedditData>()
    val fetchedDataFromURL = mutableListOf<Children>()
    for(children in fetchDataFromURL.data.children){
       if(!children.data.is_video && children.data.url.contains(regex = Regex("/i.redd.it")) && !children.data.over_18){
           fetchedDataFromURL.add(children)
       }
    }
    return fetchedDataFromURL.random()
```
#### 🐤Posting the filtered data to Twitter and the database(MongoDB)

- I've used a library known as [Twitter4j](https://github.com/Twitter4J/Twitter4J), which will post the given data to Twitter using the Twitter API.

- Now, here's where the problem arrives(it has a solution😎🤝🏻). The image of the random post, which is retrieved from the subreddit, changes every time, and upon that, the image file is allocated to Reddit's database, which can be retrieved only using HTTP/HTTPS. But the library that I'm using takes media input from local storage using the `File` API.

- The solution for this was simply to use the `Apache Commons IO` library in which one of the classes/objects can convert any URL into a file

```
val refFile = File("/src/main/assets/ref.jpg")
val refURL = Url(fetchedTweetData.data.url).toURI().toURL()
FileUtils.copyURLToFile(refURL, refFile)
```
##### If you actually notice the `refFile` variable, I haven't added the correct path; which simply replaces the image URL into a temporary file (which doesn't even exist)🤭

- Now that I have converted the URL into a file, I've passed the `refFile` which contains the `File` that will be converted into the respective image's file:

```
val statusUpdate = StatusUpdate.of(tweetTitle).media(refFile)
twitterBuilder.v1().tweets().updateStatus(statusUpdate)
```
- Now the above code will post the respective data to Twitter.

- As I mentioned, I also want to save the posted data in a database that can be retrieved by the API.

- I've created a function called [`addNewPostToDB()`](https://github.com/sakethpathike/catktorbot/blob/6928ed2fa8f1eeecd9cbc7cc8706f85a68d81358/src/main/kotlin/com/sakethh/api/AddNewPostToDB.kt#L8) that will post the respective data to the database. [`addNewPostToDB()`](https://github.com/sakethpathike/catktorbot/blob/6928ed2fa8f1eeecd9cbc7cc8706f85a68d81358/src/main/kotlin/com/sakethh/api/AddNewPostToDB.kt#L8) function is placed in the [`postNewTweet()`](https://github.com/sakethpathike/catktorbot/blob/6928ed2fa8f1eeecd9cbc7cc8706f85a68d81358/src/main/kotlin/com/sakethh/bot/twitter/PostNewTweet.kt#L15) function so that both adding the data to the database as well as posting to Twitter happens with a single trigger.

### 🚀 Automating the Bot

- Now that I have to automate things, I've used [**Zeplo**](https://www.zeplo.io/) which will trigger the endpoint of the bot [`postNewTweet()`](https://github.com/sakethpathike/catktorbot/blob/6928ed2fa8f1eeecd9cbc7cc8706f85a68d81358/src/main/kotlin/com/sakethh/bot/twitter/PostNewTweet.kt#L15) function every third hour.

- The best part about [**Zeplo**](https://www.zeplo.io/) is that it tries to connect to the server if it fails due to timeout.

#### Well, that's all. The bot is live now on [Twitter](https://twitter.com/catktorbot)🥳

### 🌐Building The API

#### This is what the visual of the API looks like:

![apiWorking.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666945007797/WC_g_ZxtG.png)

- Building the API was pretty simple compared to the bot, as the data will be automatically added to the database when the bot posts data from a subreddit.

- When the endpoint of the API gets triggered, it will collect the data from the database and return it in the JSON format. It's as simple as that.

#### Endpoints:

- I've added two endpoints for accessing the [API](https://catktorapi.onrender.com/).


1.  **/previousPosts** will return the previously posted data from the database.
2. */previousPosts*/**{DATE}** will return data based on the date provided in the URL parameter.

##### 📃You can checkout API docs from [here](https://documenter.getpostman.com/view/24098668/2s8YCjBr9o).

The source code of this project is public and open-source under MIT License:

{% embed https://github.com/sakethpathike/catktorbot %}