Updating your Twitter cover photo for a new follower

Akash Shrivastava
17 min readJun 19, 2021

Recently I saw a blog where a user used Grafana dashboard with Prometheus and hooked it to TwitterAPIs to update his cover photo with a graph of every minute of his follower count. Although it doesn’t solve any problem, it is quite fun to see yourself as a graph spike on someone’s Twitter cover photo. Things like these are fun to make and then it gives a good amount of pleasure seeing people reacting to it and finding it fun, every developer would love some serotonin in his life of fixing random bugs. This gave me the idea to use the TwitterAPIs for something related to this, so I wrote a script that updates my cover photo with the name and photo of the new follower. That’s just it, that’s all that the script does and this blog covers how I made it. It’s very simple though, and at the end of the blog you will say “Could have done it myself”, but since you didn’t here is a blog to tell you how to.

Twitter APIs

If you don’t know about Twitter APIs, this section is for you. If you do know about it and already have a Twitter Developer account, you can skip to the next section.

TwitterAPIs are a way to connect to your Twitter account without using the Twitter app or website. They provide endpoints for you so you can write some lines of code and then boom, your code now runs your Twitter account. In brief, it is a step towards Robots controlling the world, starting with your Twitter profile. Jokes apart, you can use TwitterAPI to automate various parts of your Twitter profile, like sending a message to every new follower, tweeting a “Good Morning” message with a random picture from Google every morning at 8 am like your family groups in Whatsapp, or you can create a bot that scans the current trending tweets and then uses the tweets as context to create a new tweet on the same topic and tweet it with the hashtags. It’s a very powerful tool, and it has been there for a very long. Since now you know what is it, let’s continue and make a Twitter Developer account.

Note: Without a Developer account you cannot use the TwitterAPIs, so that’s the basic requirement, and sometimes it might take an hour or so to get activated, so remember to save this post in case you take a break 😉.

Setting up Developer Account

Twitter Developer account allows you to use the TwitterAPI, the process is simple, just requires you to answer some questions.

Step 1: Visit https://developer.twitter.com/en/apply-for-access and click on “Apply for a developer account”

Step 2: Log in with your Twitter account

Step 3: Select your Use case, if you are just testing things, select Hobbyist and then select Exploring the API, otherwise select as per your need.

Twitter API Use Cases Selection

Step 4: Enter your details as requested and press next

Step 5: Answer the questions as per your need.

For this question, you can go on lines of that you are just getting started with Twitter APIs and is currently planning a small project where you will update your cover photo whenever someone follows you and that you further plan to do more in this fun automation way moving forward.

Other than this, you do not require to analyze tweets or interact with other users using APIs or display twitter’s content anywhere else, so you can mention that you do not intend to do any of these. Explain them in details, it will be easier for them to review your application.

Step 5: Confirm the data you entered in the form and then accept the Terms of Twitter and click on Submit Application

Terms Page

Great, you are done with the process. Keep checking your email, Twitter with mail you if they want any further clarification, just explain to them thoroughly why you want to use their API. After either a few minutes or 1–2 hour you will receive an email that your developer account is active and after that, you can proceed to the next step.

The email confirming that the Twitter Developer account is active

Creating an App in Developer Account

Now you have a developer account, we can now create an app to start using the APIs.

First, go to the Developer portal by using this link, or if you are already there then cool, let’s move on.

Now, go to the Projects and Apps section from the sidebar and click on the “Create App” button in the “Standalone Apps” section.

Enter your app name and then do next, here you will see some keys, like the screenshot below

API Key and Secret

Save the API key and API Secret key into a notepad or somewhere safe, we will be using this later.

Now, click on App settings and then go to the Keys and tokens panel. You will find an Authentication Tokens section, inside that click on Generate for Access Tokens and Secret, it will open up a popup, showing you your access token and secret, save them with the API key we saved earlier. If you didn’t understand, the screenshots below will help you.

Keys and Tokens to Generate Access Token and Secrets
Generated Access Token and Secret

Now, we have the required access to the TwitterAPI, we can move ahead with our programming part.

Setting up Code Environment

I have made this project using Python. Why? you would ask. One is that I have been working with python a lot so it’s my go-to language for any fast development. Also there tweepy, a python package for TwitterAPI. Tweepy makes it easy for you to work with TwitterAPIs, you just have to call some functions, pass in variables, and then you have a python object with the information you wanted. So, now we have selected a programming language, let’s get on with setting up the coding environment. Don’t worry, it’s not that difficult (if you are a seasoned programmer, this line will seem useless to you, but since I wrote this for everyone, a seasoned programmer or someone who have never tried programming)

First up, since we are using python it is good to set up a virtual environment, that will make handling packages easy. Why do we need it? When you have multiple python projects, it will become difficult to manage all the packages you will be using, and also which version goes with which project. It’s not compulsory to set it up, but it’s a good practice to do so. Good for us that python has many virtual environment packages as well( don’t worry you don’t need to manage the versioning of this package 😊), I will be using python-venv, you are free to use any other of your choice.

Let’s make a folder for our project and then create a virtual environment inside it. Open your terminal (for Linux, macOS users), or CMD (for windows), just search for it if you don’t know the shortcut.

mkdir twitterapi
cd twitterapi
python -m venv env

Now we have the virtual environment, we will activate it and install the packages we will be using. Now always remember to activate your virtual env before continuing with your project.

Note: Few of the commands that I will be using will only work for Linux, so I will add a Windows section below it as well. For Macs? they use the same terminal, so the Linux commands will work for Macs as well. (It’s just Windows is different 😢)

Linux/Mac

source env/bin/activate

Windows

cd env
./Scripts/activate
cd ../

To install the packages (Windows, Linux, Mac)

pip install tweepy, pillow

Let me just tell you about the packages

  1. Tweepy: tweepy is a python package for using the TwitterAPI, I have explained it above
  2. Pillow: pillow is a python imaging library, used to open, manipulate, save different types of images.

That’s it we have our environment setup, now open your code editor and let’s start writing some code. Finally!

Testing the API

So before we actually make the final project, let’s first test the API and verify that it’s running fine, and our keys are proper.

Create a file named auth.py and let’s write the code to test the API

import tweepy# Setting up the api keys and secrets
consumer_key = <your_api_key>
consumer_secret = <your_api_secret>
access_token = <your_access_token>
access_token_secret = <your_access_token_secret>
# Creating a OAuth object
auth = tweepy.OAuthHandler(consumer_key=consumer_key, consumer_secret=consumer_secret)
# Setting the access token to authorise the API calls
auth.set_access_token(access_token, access_token_secret)
# Creating the API object we will be using to access the APIs
api = tweepy.API(auth)
try:
api.verify_credentials()
print("Yay! It worked, credentials verified"
except:
print("Nay, something went wrong, maybe you didn't enter the proper keys")

Now let’s run this program and see if the API is working. Inside your terminal/CMD enter this command

python auth.py

If your output is

Yay! It worked, credentials verified

then congratulations, we can now move ahead with what the title of this article said. Ah! yes finally.

If it says

Nay, something went wrong, maybe you didn't enter the proper keys

then, don’t worry, you might have not entered the correct keys, re-paste the keys and run again, if it still doesn’t work, or something else pops up on the screen, you can comment down and I will help you out with getting you back on track, or you can message me, my social media links will be at the end of this article. Otherwise, you can always do a google search yourself.

Magic Code

We are going to break the code into modules, so then later we can add more fun ways to this code only without all the initial setup.

This is how our directory structure will look like

Folder Structure for the Project

I know, seems a very complicated folder structure for a very simple and small project, but I tried to divide the code into different modules so that I can just keep on adding more things to this like easily. You don’t have to worry about this, we will go through it one-by-one.

First, let’s create this directory structure and all the files. Run the following commands

mkdir components data generated static utils
mkdir generated/images
mkdir static/fonts static/images
touch components/__init__.py components/update_banner.py
touch data/last_update.json
touch utils/__init__.py utils/env_utils.py utils/image_utils.py
touch auth.py env.json main.py settings.py

Now we have the skeleton of our project ready.

Note: __init__.py is a special file that makes python read a directory as a module, so we can import our code from different directories just like we import other packages or modules

Authentication

Let’s start with authentication first, our strategy here is to save our keys and tokens on an env.json file and then read from there to access the API.

We will start with the env.json file and saving our tokens and keys inside it

{
"CONSUMER_KEY":<your_api_key>
"CONSUMER_SECRET":<your_api_secret>
"ACCESS_TOKEN":<your_access_token>
"ACCESS_SECRET":<your_access_token_secret>
}

Another requirement is the settings.py file, which we will be using to store our static variables like the directories path etc. Add this code in the settings.py

import osBASE_DIR = os.path.dirname(__file__)# To get the file path in base dir
def base(filename):
return os.path.join(BASE_DIR, filename)
# To get the file path in static dir
def static(filename):
return os.path.join(BASE_DIR, 'static', filename)
# To get the file path in data dir
def data(filename):
return os.path.join(BASE_DIR, 'data', filename)
# To get the file path in generated dir
def generated(filename):
return os.path.join(BASE_DIR, 'generated', filename)

We will create a function inside env_utils.py to read the env.json file and give us a python array with the keys.

import jsonimport settingsdef getEnv():
environ_secrets = []
# Opening env.json and fetching the tokens and keys
f = open(settings.base("env.json"))
env = json.load(f)
f.close()

environ_secrets.append(env.get("CONSUMER_KEY", ""))
environ_secrets.append(env.get("CONSUMER_SECRET", ""))
environ_secrets.append(env.get("ACCESS_TOKEN", ""))
environ_secrets.append(env.get("ACCESS_SECRET", ""))

return environ_secrets

Now let's create the auth.py file to authenticate and get the API object

import tweepyfrom utils.env_util import getEnvdef authenticate():
# Getting the tokens and keys
environ_secrets = getEnv()
# Setting up variables
consumer_key = environ_secrets[0]
consumer_secret = environ_secrets[1]
access_token = environ_secrets[2]
access_token_secret = environ_secrets[3]
# Creating Auth object
auth = tweepy.OAuthHandler(consumer_key=consumer_key, consumer_secret=consumer_secret)
# Setting access token to access API
auth.set_access_token(access_token, access_token_secret)
# Getting the API object to make API calls
api = tweepy.API(auth)
# Verifying API and returning the API object
try:
api.verify_credentials()
print("Verified")
return api
except:
print("Wrong Credentials")
return None

Now let’s run our authenticate() function to see that it’s working fine

python -c "import auth; auth.authenticate()"

If you got “Verified” or “Wrong Credentials”, the function is working fine, just in case of “Wrong Credentials” your API keys and tokens are not correct, check the env.json and verify that. If you are getting an error, then something went wrong with your code and now you have to debug it.

I will assume everything went right for you too and move ahead with the code.

Image Manipulation Automation

Now that our authentication setup is done, it’s time to make the function that will create the cover photo for us.

import urllib.request
from PIL import Image, ImageFont, ImageDraw
import settings# Downloading an image from a url and saving it as per needs
def download_image(url, filename):
urllib.request.urlretrieve(url, settings.generated(f"images/{filename}.jpg"))
# The function which creates the cover photo for us
def create_image(screen_name, profile_photo):

download_image(profile_photo, "user_image")
user_image = Image.open(settings.generated("images/user_image.jpg"))
welcome_image = Image.open(settings.static("images/welcome.jpg"))
text = str(screen_name)
image_editable = ImageDraw.Draw(welcome_image)
# Setting font size
fontsize = 1 # starting font size
img_fraction = 0.40 # how much percentage width should the text
font = ImageFont.truetype(settings.static("fonts/playfair.ttf"), fontsize)
while font.getsize(text)[0] < img_fraction*welcome_image.size[0]:
# iterate until the text size is just larger than the criteria
fontsize += 1
font = ImageFont.truetype(settings.static("fonts/playfair.ttf"), fontsize)
# Write the text and paste the user image to our cover image
image_editable.text((490, 180), text, (0, 0, 0), font=font)
user_image = user_image.resize((90, 90))
welcome_image.paste(user_image, (390, 185))
welcome_image.save( settings.generated("images/new_welcome_banner.jpg"))

Well, let’s go through this code part by part to understand what we are doing actually.

# Downloading an image from a url and saving it as per needs
def download_image(url, filename):
urllib.request.urlretrieve(url,
settings.generated(f"images/{filename}.jpg"))

This function will download an image file from a URL and save it to the generated/images folder. We will be using this to download the user image from his Twitter profile

def create_image(screen_name, profile_photo):

The create_image function requires two arguments, screen name and the link of the profile picture of the user (in our case the new follower)

download_image(profile_photo, "user_image")
user_image = Image.open(
settings.generated("images/user_image.jpg"))
welcome_image = Image.open(
settings.static("images/welcome.jpg"))
text = str(screen_name)
image_editable = ImageDraw.Draw(welcome_image)

Now we are downloading the user image, opening it and our welcome image using the PIL (pillow library we installed earlier) open() function and then making our image editable using the ImageDraw module. For the welcome image, I have used this image, but you are free to use any image, just save it as welcome.jpg inside the static/images directory

# Setting font size
fontsize = 1 # starting font size
img_fraction = 0.40 # how much percentage width should the text
font = ImageFont.truetype(settings.static("fonts/playfair.ttf"), fontsize)
while font.getsize(text)[0] < img_fraction*welcome_image.size[0]:
# iterate until the text size is just larger than the criteria
fontsize += 1
font = ImageFont.truetype(
settings.static("fonts/playfair.ttf"), fontsize)

This part of the code basically sets the font size for the text according to the image size and how much width we want the text to be of the image. This saves us from manually changing the font size for different images or when someone has a long username. It will automatically get adjusted in size to fit the image perfectly. For the font I have used Playfair light, you can use any font you want, just change the name accordingly and put it inside the static/fonts directory.

# Write the text and paste the user image to our cover image
image_editable.text((490, 180), text, (0, 0, 0), font=font)
user_image = user_image.resize((90, 90))
welcome_image.paste(user_image, (390, 185))
welcome_image.save( settings.generated("images/new_welcome_banner.jpg"))

This is the final part, we have added the text to the image and then pasted the user image onto our main image and saved the composed image into the generated/images directory. The text() functions here takes four arguments, the first one is the position in x and y coordinates (x,y), you can set them accordingly as per your need, I will show you below how to find the best position manually. The next argument is the text we want to write on the image, following it is the colour of the text in RGB format (you can search for RGB code for your favourite colour and paste it here). The last argument is the font we will be using. The resize function resizes our user_image.py.

Similarly in the paste() function, we have two arguments, the first one is the image we want to paste and the second is the coordinates of the image.

Now, let’s test our function and see what image it’s creating. For that, we will create a test.py inside our root directory.

touch test.py

Paste this inside test.py

from image_util import create_imagecreate_image('akashshrivastava', 'https://imgur.com/download/KY1zb2j/avatar%20female')

Then run the test.py file

python test.py

If it ran without any error, you will see “new_welcome_banner.jpg” inside generated/images directory. Open it and check that the placement of the text and image is proper. You can change the (x,y) coordinates and also the size in resize() to adjust the image properly.

Note: If you are following the tutorial completely, and are using the same image, the output will be perfect for you, and you can move ahead without changing anything.

Updating Cover Photo

So, we have authenticated properly, our create_image is making the correct image, now it’s time to fetch our followers list and update the banner if there is a new follower.

The API.followers() function returns the list of followers with their data such as screen_name, profile_photo_url etc. in the latest follower first sorted manner. So before we make a function to update the cover only for new followers, let’s just update our cover for the latest follower first

Inside components/update_banner.py, paste this

from utils.image_util import create_image
import settings
from auth import authenticate
api = authenticate()
# Getting the followers list
followers = api.followers()
create_image(
followers[0].screen_name,followers[0].profile_image_url_https)
api.update_profile_banner(
settings.generated("images/new_welcome_banner.jpg"))

Let’s run this and see if our cover is getting updated.

python components/update_banner.py

Go to Twitter and check if your cover got updated. I will assume that it did, if it did not, or the code didn’t run properly, you can always comment down, contact me or just search on google

Now we will change the update_banner.py to keep the data of the latest follower and only update the cover if there is a new follower, otherwise, change back to our default picture after some time (in my case I have kept it to 5 minutes). Remove everything from update_banner.py and paste this code into it.

import json
import settings
from datetime import datetime, timedelta
from utils.image_util import create_imagedef updateBannerForNewFollower(api): # Getting the followers list
followers = api.followers()

# Getting the last updated data
with open(settings.data("last_update.json"), "r") as f:
last_update = json.load(f)
# Latest update from the api call
current_update = {
"id": followers[0].id_str,
"time": datetime.now()
}
# Deserializing the time from the json into datetime format
last_updated_time = datetime.strptime(
last_update.get("time"), "%Y-%m-%d %H:%M:%S.%f")

# Checking if there has been a new follower or not
# Updating the banner if there hasn't been any new follower in
# last 5 minutes or updating the banner with the new follower
# if there is a new follower
if last_update["id"] == current_update["id"]:
if datetime.now() - last_updated_time > timedelta(minutes=5)):
print("No update")
api.update_profile_banner(
settings.static("images/default_banner.jpg"))
else:
create_image(followers[0].screen_name,
followers[0].profile_image_url_https)
api.update_profile_banner(settings.generated(
"images/new_welcome_banner.jpg"))
# Saving the latest data back into the json file
with open(settings.data("last_update.json"), "w") as f:
json.dump(current_update, f, default=str)

Let’s go through the code to understand what is happening

def updateBannerForNewFollower(api):

Our updateBannerForNewFollower() function takes the API object as the argument which we will provide through our main function (we will get to that after this).

# Getting the followers list
followers = api.followers()
# Getting the last updated data
with open(settings.data("last_update.json"), "r") as f:
last_update = json.load(f)
# Latest update from the api call
current_update = {
"id": followers[0].id_str,
"time": datetime.now()
}

We are getting the followers list by calling the api and then loading the last updated latest follower we have saved in our JSON file. We are then creating a python dictionary object with the data from the current API call.

# Deserializing the time from the json into datetime format
last_updated_time = datetime.strptime(
last_update.get("time"), "%Y-%m-%d %H:%M:%S.%f")

Since the time object we read from the JSON is in string format, we have to convert it into a python datetime object to use it, so we call the strptime function which takes a string argument, followed by the format argument, and then gives us a datetime object.

if last_update["id"] == current_update["id"]:
if datetime.now() - last_updated_time > timedelta(minutes=5):
print("No update")
api.update_profile_banner(
settings.static("images/default_banner.jpg"))

In the if clause, we are checking if the last updated user id and the newly fetched user id is the same or not, if it’s the same that means there is no new follower, so we will check if there has been any new follower from the x minutes (here I have chosen x to be 5 minutes, you can change according to your desire). If both the conditions are satisfied, then we update the cover with the default picture. If the initial condition fails, which means there is a new follower we move to the else clause.

else:
create_image(followers[0].screen_name,
followers[0].profile_image_url_https)
api.update_profile_banner(settings.generated(
"images/new_welcome_banner.jpg"))
# Saving the latest data back into the json file
with open(settings.data("last_update.json"), "w") as f:
json.dump(current_update, f, default=str)

First, we create the image using our create_image function passing the new follower’s screen name and profile image URL. After that, we update our cover photo with the new image we just created. Finally, we save the current data into the last_update.json to mention that this follower’s cover photo has been posted.

That’s our update_banner.py file, most of the logic for the code is done.

Before we move ahead we will need to create a dummy last_update.json file so that our code doesn’t break, paste this inside data/last_update.json

{
"id": "test",
"time": "2021-06-17 01:25:50.482099",
}

For the final part, we have to create the main.py which will actually run our code by combining everything, so let's go ahead and do this

import sysfrom auth import authenticate
from components.update_banner import updateBannerForNewFollower
def main(arg):
api = authenticate()
if arg == "follower-banner":
updateBannerForNewFollower(api)
else:
print("Not allowed")
if __name__ == '__main__':
if len(sys.argv) < 2:
main("follower-banner")
else:
main(sys.argv[1])

The main function calls the authenticate function to get the API object, and then according to the argument passed to it, calls the appropriate function. Right now there is only one function updateBannerForNewFollower(), so it doesn’t make much sense, but as you keep adding more, you just need to add them here and then you can just pass argument in terminal to run your desired part of code. So, this completes the project, we have everything set up. Now let’s run this once to test that everything is working fine

python main.py

If everything goes well, you can go ahead and ask a friend of yours to follow you and then run your code again using the command we used above, and see your cover photo getting updated.

Since you won’t be manually running this always, it is good to either create a scheduled task on your computer (if you keep your computer running always, or most of the time) or you can run this on a server using AWS, Azure, etc. I will cover the deployment part in another article, although it’s a very easy process. Until then, take care and I hope you enjoyed the article.

You can check out the live version on my Twitter profile (link below). Don’t unfollow after following 😉. You will have to wait 1 minute after following.

The complete code is present in my Github Repository, you are welcome to push any updates there if you think something can be improved, just create a PR and we can discuss.

Thank you for reading

Akash Shrivastava

Software Engineer at ChaosNative and a final year CSE student

Linkedin | Github | Instagram | Twitter

--

--

Akash Shrivastava

Software Engineer at Harness | Contributor to LitmusChaos | CSE Graduate | Backend Developer | Go | Kubernetes | Django | Python