How to Dynamically Update Twitter Cover Image to Show Latest Followers Using PHP GD and TwitterOAuth

erikaheidi

Erika Heidi

Posted on June 2, 2021

How to Dynamically Update Twitter Cover Image to Show Latest Followers Using PHP GD and TwitterOAuth

A week ago, I came across this tweet that made me so intrigued:

It made me remember a couple other times in my career where I was very excited and actually almost obsessed to figure out how something was made. The first time (long ago) that happened was when I saw this "magazine cover tool" where you uploaded a photo and generated a magazine cover. That led me to a whole world of discoveries and my first successful project that allowed me to leave the job I had at the time to live off Google Adsense. Yes, it was a fake magazine cover generator online :) made with PHP and GD.

The other occasion when I felt as motivated to find out how they did something was when I found this tool to keep track of who unfollowed you on Twitter. Again, that led me to a whole world of discoveries, open source contributions, and lots of digging in the Twitter API.

So when I saw that tweet from Tony, I realized this would be basically the cover generator + Twitter API tinkering combined. I got very excited to implement it! Took about a week, couple hours each day, because I had small chunks of code here and there that I could reuse and repurpose.

This is the final result, you can also check it out live:

Twitter Header Image with recent followers and featured github sponsors

What makes me excited about this little project is that the possibilities are ENDLESS. You can showcase whatever you want that has a dynamic output and can be programmatically obtained (like, via an API). The same approach can be used to tinker with your profile image.

In this tutorial, we'll build a simpler version of that dynamic header that shows your latest 5 Twitter followers. The dynacover GitHub repository contains a few other templates that you may want to experiment with, later.

Ok, enough talk. Let's get to work!

Before Getting Started

Before moving along, make sure you have the following available to you:

  • A registered application on dev.twitter.com with read and write access.
  • Obtain the following 4 tokens from the application settings page:
    • Consumer / API Token (App token)
    • Consumer / API Secret (App secret)
    • Access Key (User token)
    • Access Secret (User secret)
  • A working PHP development environment in the command line with the following dependencies installed:
    • php-cli 7.4+
    • ext-gd
    • ext-json
    • ext-curl
    • Composer
    • A PNG image to serve as cover, which will be pasted on top of all layers. The image must be 1500x500 and have transparent placeholders for the avatar images, like this one that you are free to use and modify. In case you create a custom banner with placeholders in different locations, you'll also need the size and coordinates of each placeholder.

You are encouraged to use a source code editor such as Visual Studio Code to facilitate working within a PHP project.

For this demo, we'll create a command-line application using Minicli, a minimalist framework for command line applications in PHP. We'll use the TwitterOAuth library to communicate with the Twitter API.

If you'd prefer to follow along with the code from an existing repository, or if you just want to try this demo out, you can check erikaheidi/dynacover on GitHub. To match the code in this guide, download version 0.1, since the main branch will change over time.

1. Bootstrapping the Application

Start by bootstrapping a small command-line app using Minicli. Here, we'll call it dynacover.

composer create-project --prefer-dist minicli/application dynacover
Enter fullscreen mode Exit fullscreen mode
Creating a "minicli/application" project at "./dynacover"
Installing minicli/application (2.0.1)
  - Installing minicli/application (2.0.1): Extracting archive
Created project in /home/erika/Projects/dynacover
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking minicli/minicli (2.2.1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing minicli/minicli (2.2.1): Extracting archive
Generating autoload files
1 package you are using is looking for funding.
Use the `composer fund` command to find out more!

Enter fullscreen mode Exit fullscreen mode

This will bootstrap de application. Enter the new directory and have a look at the files. Here's a quick overview of the directory structure in a new Minicli app:

cd dynacover
Enter fullscreen mode Exit fullscreen mode
.
├── app
│   └── Command
├── composer.json
├── composer.lock
├── LICENSE
├── minicli
├── README.md
└── vendor
    ├── autoload.php
    ├── composer
    └── minicli
Enter fullscreen mode Exit fullscreen mode

The app/Command directory is where all commands are defined. We'll work on that in the next step.

Next, include abraham/twitteroauth as dependency via Composer:

composer require abraham/twitteroauth
Enter fullscreen mode Exit fullscreen mode

You can also rename your script to match the application name:

mv minicli dynacover
Enter fullscreen mode Exit fullscreen mode

To test that everything works as expected, run:

php dynacover help
Enter fullscreen mode Exit fullscreen mode

You should see the following output:

Available Commands

help
└──table
└──test

Enter fullscreen mode Exit fullscreen mode

These commands are implemented at the app/Command/Help/ directory as example commands that you may want to check out. In the next step, we'll start creating new commands.

2. Obtaining New Followers with TwitterOAuth Library

Before writing the code that will dynamically generate the image, first make sure you can obtain your latest followers using the TwitterOAuth library and your application / user keys.

We'll create a simple command to list your latest followers.

First, however, we need to set up the Twitter tokens in such a way that you can obtain them from the command controllers. The easiest way to do so is to create a separate file with the credentials and import them into the app's configuration. You should also make sure this file is included within your .gitignore in case you are using version control, so that it is not committed along with the application code.

Storing Twitter Credentials Safely

Create a new file named credentials.php in the root of the application folder. This file should return an array with the application's sensitive data.

Copy the following content to your credentials.php file and replace all the keys with your own Twitter credentials:

#credentials.php
<?php

return [
    'twitter_consumer_key' => 'APP_CONSUMER_KEY',
    'twitter_consumer_secret' => 'APP_CONSUMER_SECRET',
    'twitter_user_token' => 'USER_ACCESS_TOKEN',
    'twitter_token_secret' => 'USER_ACCESS_TOKEN_SECRET',
];
Enter fullscreen mode Exit fullscreen mode

To make sure this file isn't included within version control, you should add a line to your .gitignore file. You can do this with the following command, which will append a new line to that file:

echo "credentials.php" >> .gitignore
Enter fullscreen mode Exit fullscreen mode

Then, edit the dynacover script to merge this data into the main config. This is how you can do it:

#dynacover
#!/usr/bin/php
<?php

if (php_sapi_name() !== 'cli') {
    exit;
}

require __DIR__ . '/vendor/autoload.php';

use Minicli\App;

$config = [
    'app_path' => __DIR__ . '/app/Command'
];

if (is_file(__DIR__ . '/credentials.php')) {
    $config = array_merge($config, include __DIR__ . '/credentials.php');
}

$app = new App($config);

$app->runCommand($argv);

Enter fullscreen mode Exit fullscreen mode

Now you should be able to obtain these credentials from any command controller.

Creating New Command to Fetch Followers

With the credentials in place, you can start working with the Twitter API. Create a new directory within app/Command called Fetch.

mkdir app/Command/Fetch
Enter fullscreen mode Exit fullscreen mode

Create a new file at app/Command/Fetch/FollowersController.php using your preferred code editor. This is a class that needs to extend from the Minicli\Command\CommandController and implement a method named handle, which will handle the command when it's invoked from the prompt.

From the handle method, you'll obtain the needed credentials from the $this->getApp()->config object, accessible as magic properties:

$api_token = $this->getApp()->config->twitter_consumer_key;
$api_secret = $this->getApp()->config->twitter_consumer_secret;
$access_token = $this->getApp()->config->twitter_user_token;
$token_secret = $this->getApp()->config->twitter_token_secret;
Enter fullscreen mode Exit fullscreen mode

Then, using the TwitterOAuth library, you can create a request to obtain your latest followers.

$client = new TwitterOAuth($api_token, $api_secret, $access_token, $token_secret);
$followers = $client->get('/followers/list', [
    'skip_status' => true,
    'count' => 10
]);
Enter fullscreen mode Exit fullscreen mode

The following code contains the full implementation, also listing the results as output at the end:

#app/Command/Fetch/FollowersController.php
<?php

namespace App\Command\Fetch;

use Minicli\Command\CommandController;
use Abraham\TwitterOAuth\TwitterOAuth;

class FollowersController extends CommandController
{
    public function handle()
    {
        $api_token = $this->getApp()->config->twitter_consumer_key;
        $api_secret = $this->getApp()->config->twitter_consumer_secret;
        $access_token = $this->getApp()->config->twitter_user_token;
        $token_secret = $this->getApp()->config->twitter_token_secret;

        $client = new TwitterOAuth($api_token, $api_secret, $access_token, $token_secret);
        $followers = $client->get('/followers/list', [
            'skip_status' => true,
            'count' => 10
        ]);

        $this->getPrinter()->info("Latest Followers", true);

        foreach ($followers->users as $follower) {
            $this->getPrinter()->info($follower->screen_name);
        }

        $this->getPrinter()->info("Finished.");
        return 0;
    }
}
Enter fullscreen mode Exit fullscreen mode

You can now run this command with:

php dynacover fetch followers
Enter fullscreen mode Exit fullscreen mode

If everything worked as expected, you should see a list with your 10 most recent new followers, like this:

screenshot terminal list recent followers

With this data we can start building the dynamic cover image.

3. Generating a Cover with User Avatars

You'll now build a new command to generate the cover image. We'll need the base image that will be pasted on top of the avatar images to give the best effect. I used Canva to create mine, but you can use any image / photo editor able to work with PNGs and transparency.

For this demo, we'll use this example image. After creating this on Canva, I opened the image on Gimp and used the circle selection tool to select where I want the profile images to go, then hit "delete" to delete that portion of the image and make it transparent:

Creating transparent placeholders on Gimp

Our coordinates will be based on that image.

Setting Up the Example Cover Image

Create a new folder inside the app directory called Resources to place this cover image.

mkdir app/Resources
cp ~/Downloads/cover_basic.png app/Resources/twitter_cover.png
Enter fullscreen mode Exit fullscreen mode

Creating A Cover Generate Command

mkdir app/Command/Cover
Enter fullscreen mode Exit fullscreen mode

Create a new command controller in that folder named GenerateController, so it will be reachable as dynacover cover generate later on. You can place the following bootstrap code in it:

<?php

namespace App\Command\Cover;

use Minicli\Command\CommandController;

class GenerateController extends CommandController
{
    public function handle()
    {
        // TODO: Implement handle() method.
    }
}
Enter fullscreen mode Exit fullscreen mode

Here are the things we'll need to do now:

1) Get Latest Followers (5);
3) Download their avatars to a temporary location;
4) Create a new GD image resource using imagecreatetruecolor, sized 1500x500;
5) Loop through an array with the right coordinates to paste the avatar images, and use imagecopyresampled to place those images in the blank GD resource;
6) Finalize with the PNG image on top;
7) Write the image to a fixed location that we can use later on to upload to Twitter.

If you want to try and implement that on your own as a challenge, go ahead! I encourage you to try. If you just want to see it working, then you can copy the following controller code to your own GenerateController.php file.

#app/Command/Cover/GenerateController.php
<?php

namespace App\Command\Cover;

use Abraham\TwitterOAuth\TwitterOAuth;
use Minicli\Command\CommandController;

class GenerateController extends CommandController
{
    public function handle()
    {
        $api_token = $this->getApp()->config->twitter_consumer_key;
        $api_secret = $this->getApp()->config->twitter_consumer_secret;
        $access_token = $this->getApp()->config->twitter_user_token;
        $token_secret = $this->getApp()->config->twitter_token_secret;

        $client = new TwitterOAuth($api_token, $api_secret, $access_token, $token_secret);
        $followers = $client->get('/followers/list', [
            'skip_status' => true,
            'count' => 5
        ]);

        if (!isset($followers->users)) {
            $this->getPrinter()->error("An error occurred.");
            return 1;
        }

        $users = $followers->users;
        $placeholders = $this->getPlaceholders();

        $cover_final = imagecreatetruecolor(1500, 500);

        foreach ($placeholders as $key => $placeholder) {
            $follower = $users[$key];
            $this->getPrinter()->info("Adding user to banner: $follower->screen_name");

            $path = $this->downloadAvatar($follower->profile_image_url);
            $resource = $this->getResource($path);
            $info = getimagesize($path);

            if ($resource) {
                imagecopyresampled(
                    $cover_final,
                    $resource,
                    $placeholder['pos_x'],
                    $placeholder['pos_y'],
                    0,
                    0,
                    $placeholder['width'],
                    $placeholder['height'],
                    $info[0],
                    $info[1]
                );
            }
        }

        //now finish with the cover image on top
        $cover = imagecreatefrompng(__DIR__ . '/../../Resources/cover_template.png');

        if ($cover) {
            imagecopyresized(
                $cover_final,
                $cover,
                0,
                0,
                0,
                0,
                1500,
                500,
                1500,
                500
            );
        }

        $save_path = __DIR__ . '/../../../latest_header.png';
        imagepng($cover_final, $save_path);
        $this->getPrinter()->info("Finished generating cover at $save_path.");

        return 0;
    }

    public function getPlaceholders(): array
    {
        return [
            [
                'pos_x' => 486,
                'pos_y' => 272,
                'width' => 130,
                'height' => 130
            ],[
                'pos_x' => 670,
                'pos_y' => 272,
                'width' => 130,
                'height' => 130
            ],[
                'pos_x' => 859,
                'pos_y' => 272,
                'width' => 130,
                'height' => 130
            ],[
                'pos_x' => 1049,
                'pos_y' => 272,
                'width' => 130,
                'height' => 130
            ],[
                'pos_x' => 1236,
                'pos_y' => 272,
                'width' => 130,
                'height' => 130
            ]
        ];
    }

    public function getResource($path)
    {
        $info = getimagesize($path);
        $extension = image_type_to_extension($info[2]);

        if (strtolower($extension) == '.png') {
            return imagecreatefrompng($path);
        }

        if (strtolower($extension) == '.jpeg' OR strtolower($extension) == '.jpg') {
            return imagecreatefromjpeg($path);
        }

        return null;
    }

    public function downloadAvatar($url): string
    {
        $file_contents = file_get_contents($url);

        $file_path = "/tmp/" . basename($url);

        $image = fopen($file_path, "w+");
        fwrite($image, $file_contents);
        fclose($image);

        return $file_path;
    }
}
Enter fullscreen mode Exit fullscreen mode

You can run the command with:

php dynacover cover generate
Enter fullscreen mode Exit fullscreen mode

Once it's finished, check the root of the application to see the resulting image. It should look similar to this:

Example Resulting Cover Image

Naturally, you should feel free to customize the image and the position of the placeholders. Just remember to adjust the coordinates accordingly. A few other cover templates are available at the Dynacover GitHub Repository.

Once you are happy with the generated cover, you can go ahead and implement a command to update your twitter cover.

4. Programmatically Updating Twitter Cover Image

The only thing missing now is the part where we actually update the cover image within our Twitter profile, using the API.

Create a new command controller inside app/Command/Cover called UpdateController.

The following controller will:

1) Call the dynacover cover generate command to make sure we have an updated image to upload
2) Post the new image to Twitter using the TwitterOAuth library and the /account/update_profile_banner endpoint.

#app/Command/Cover/UpdateController
<?php

namespace App\Command\Cover;

use Abraham\TwitterOAuth\TwitterOAuth;
use Minicli\Command\CommandController;

class UpdateController extends CommandController
{
    public function handle()
    {
        $banner_path = __DIR__ . '/../../../latest_header.png';
        $this->getPrinter()->info("Generating new cover...");
        $this->getApp()->runCommand(['dynacover', 'cover', 'generate']);

        $api_token = $this->getApp()->config->twitter_consumer_key;
        $api_secret = $this->getApp()->config->twitter_consumer_secret;
        $access_token = $this->getApp()->config->twitter_user_token;
        $token_secret = $this->getApp()->config->twitter_token_secret;

        $client = new TwitterOAuth($api_token, $api_secret, $access_token, $token_secret);

        $post = [
            'width' => 1500,
            'height' => 500,
            'offset_top' => 0,
            'offset_left' => 0,
            'banner' => base64_encode(file_get_contents($banner_path))
        ];

        $this->getPrinter()->info("Uploading cover to Twitter...");
        $client->post('/account/update_profile_banner', $post);

        $this->getPrinter()->info("Finished uploading new banner.");
        return 0;
    }
}
Enter fullscreen mode Exit fullscreen mode

Once you get this controller set up, you can run:

php dynacover cover update
Enter fullscreen mode Exit fullscreen mode

Then check your Twitter profile to verify that the new cover was uploaded.

5. Include Script on Crontab (Optional)

The last stap to automate your cover generation is to include the update script in Crontab or equivalent, to run at a certain fixed interval (every 5 or 10 minutes, for instance).

I have set mine with Crontab to run every 5 minutes, and this is how I've done it:

crontab -e
Enter fullscreen mode Exit fullscreen mode

This will open a text editor with the crontab for your user. The following line will execute the specified command every 5 minutes (*/5) and throw the output to /dev/null:

*/5 * * * * /usr/bin/php /home/erika/dynacover/dynacover cover update > /dev/null 2>&1
Enter fullscreen mode Exit fullscreen mode

Once you save and quit, the new crontab will be installed. Please notice that this should be set on a remote, live server, and using your local machine to run the scheduled script won't be as reliable. Always use full paths for both the php executable and your dynacover script.

Conclusion

In this tutorial, we saw how it is possible to programmatically generate Twitter cover / header images and upload them to your profile, so that you may show your latest followers.

Although the subject of this guide was Twitter followers and the Twitter profile header image, the same principles apply to generate images for other networks and using other APIs. Check the dynacover GitHub for some ideas.

The PHP GD library is very powerful, and this is just scratching the surface of fun things we can build with it!

I hope you have enjoyed this tutorial and please share your profiles once you have implemented it :)

💖 💪 🙅 🚩
erikaheidi
Erika Heidi

Posted on June 2, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related