How I created a Reddit bot with Laravel
So I’m really into gaming (obviously) and especially like the “Photo mode” a lot of games provide these days. Plenty of these in-game pictures are shared on Reddit, which is great, but I was somewhat frustrated that most of them are scattered over dozens of subs. That’s why I came up with the idea to create a dedicated sub that centralizes all these pieces of art: r/GamePhotoModePorn.
I wasn’t going to do this manually of course, so I started looking into the possibility of creating a bot that would do all the hard work for me.
The boring part: research
The first thing I had to find out was if the Reddit submissions containing pictures had similarities or patterns. Developers thrive on patterns as they make our life easier. I started to scan several subs and noticed two patterns:
- Some subs had dedicated “flairs” for in-game pictures
- Others didn’t, but a lot of the submission titles contained the word “screenshot”
Bearing these two things in mind I started looking at the Reddit API docs, which are… confusing, to say the least. What I understood from it, was that I could fetch the submissions that met the above two conditions by implementing the following steps:
- Creating a Reddit application
- Authenticating via OAuth2 with the credentials provided by the application
- Use the corresponding API calls to fetch the data I need
Ok, cool, let’s get going!
Well… No. I’m mainly a Drupal developer but I knew from the start that Drupal would not be the best framework to create this bot. It has too much clutter and all I needed was a simple database layer accompanied with some kind of HTTP client. As Drupal uses a lot of Symfony components, Symfony would be the obvious winner. It allows you to pull together the packages you need and start developing right away, BUT…
I’m always in for learning something new, so I started looking at the cool kid on the block, Laravel:
The PHP Framework for Web Artisans
as they like to call it. I kind of liked the first tests I ran with it. Also, the speed with which you can start new projects made me decide to go with Laravel.
The fun part: programming and putting it all together
Figuring out the API calls
First I had to figure out the requests’ and responses’ structure of the Reddit API. After messing around for a few hours I came up with five requests I would need to fetch and cross-post the required data.
- Search subreddits that have a dedicated flair:
2. Search subreddits that do not have a flair but need searching on the title:
3. Search subreddits where just a list of all newest submissions is required:
4. Cross-post a submission to r/GamePhotoModePorn:
5. Automatically update the submission which contains some info and numbers about r/GamePhotoModePorn:
Creating a client with Guzzle
With great API calls, comes a great HTTP client a wise man once said… So that’s what I set out to do. Laravel ships with Guzzle, which is the most common library to build your HTTP request with.
I created a class that handles all the API calls and runs them through one method request(). Here’s a snippet:
When you look closely, you’ll see all the cool stuff happens in the request() method. A lot of API calls cannot be executed as an anonymous user, that’s why every single one is provided by a Bearer token to authenticate the call:
When the token is fetched, it is used in the Authorization header to execute the actual call:
Furthermore, every method returns a structured object e.g. a Submission object when crossPost() is called.
At this point, I had a class that would fetch the bot the data it needed to start cross-posting to r/GamePhotoModePorn. To make sure it did not cross-post the same submission multiple times, I had to keep track of all the cross-posts made. As you might guess, I used Laravel models and eloquent to achieve this.
Keeping track of cross-posts
I created a simple model with Artisan to keep track of all the cross-posts:
php artisan make:model RedditCrossPost
Every time the bot cross-posts a submission to r/GamePhotoModePorn it tracks the post in the database:
This prevents cross-posting the same submission multiple times.
A structured way to define the subreddits to scan
To “feed” the bot with the subreddits to scan, I’ve set up a simple but effective structure.
I started with defining 3 query type objects (one for every different search I mentioned in “Figuring out the API calls”):
Each query instance implements the same interface. This interface allows the bot to determine the search query string for a specific query type:
This is a snippet of the FlairQuery class which searches for submissions with a specific flair attached:
Next, I defined a “Subreddit” class which defines a... subreddit obviously. The class contains the name, query type(s) to use, and the flair it has to be tagged with:
And last but not least, I’ve set up an array of subreddits for the bot to scan:
At this point, I was set up to tie it all together.
I decided to run the bot every 15 minutes, frequently enough to generate new content, but not too much for Reddit to consider it as spam. Laravel allows you to do this with scheduled tasks.
I started by adding the required Command class and implementing the handle() method. Because of the way I set up my code I could easily iterate over the subreddits and execute the corresponding API calls for each of them:
Next, I added the command to my schedule:
$schedule->command('reddit:run-bot')->cron('*/15 * * * *')
And I made sure the schedule is executed every minute by a Linux cronjob:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
HOORAY! My bot is currently up and running and doing a very good job at centralizing a lot of in-game screenshots. The time I have to spend to manually moderate it is close to zero, which means I have accomplished what I had set out to do from the beginning:
To create a bot that would do all the hard work for me
There are multiple reasons why I decided to share the approach I took to solve this “issue”. For one, I was surprised by how little decent documentation I was able to find about Reddit API and PHP implementations. Maybe this post can help some of you with the problems you (will) face. I’m also very critical of my own work, so please, if you have any suggestions or feedback on how I could improve, let me know!
Feel free to join r/GamePhotoModePorn :)