Twitter Headers are a craze among the tweeps of Tech Twitter, especially the ones “Made With CSS”. Even though I am fairly comfortable with CSS, I lack the creativity to make a good CSS header with it, and near impossible to make anything on par with the CSS geniuses there. (Check out @Prathkum, he is a CSS Wizard and one of the inspirations behind my header projects).

Since “Made With CSS” wasn’t possible, I decided to take a different route. Python. Python is something I use daily, from automating simple tasks to making bots for my amusement. Given my love for Python, it was the obvious choice for my Twitter Header Experiments.

The first one I made was similar to the one I will explain in this blog. It displayed my Twitter Statistics (Followers, Following, No. of Tweets, Lists I am on, My Twitter Age, etc). Even though it was cool and something that I used for a month or so, I later changed it to a humourous cryptocurrency one.

The second one like I said, was a cryptocurrency joke which updated the USD rates every minute. (I have that same joke on Reddit). I used this header for a while but later changed it to some static image.

Then, a couple of days ago I saw a cool “Made With CSS” header and thought about making another dynamic one. I also realised that I hadn’t made a tutorial about it. So, here I am, killing two birds with one stone.

The Idea

The idea is really simple, make a Dynamic Twitter Header that display’s the likes and retweets of the Tweet announcing the header. Since the header will change when users interact with the Tweet, more users will interact with it, just to see the header update. I was expecting the likes/retweets on the tweet to increase after I publish this blog, but it has already become one of my top tweets while I am typing this.

Although I hadn’t planned it initially, after receiving feedback from what little audience I have on my Twitter, I also added the Latest 3 Followers to the header.

Demo

Before starting the actual tutorial, you can check my header on my Twitter (@HAliPunjabi) and the related Tweet

Prerequisite - Twitter Developer Account and App

Before writing the actual code, you are going to need a Twitter Developer Account and then create a Twitter App. Using the created App’s Keys and Tokens, you will be able to update your header and get the data from Twitter about your followers and likes/retweets on any tweet.

  1. Go to Twitter Developer Account and apply for access. It shouldn’t take too long to get approved.
  2. Go to Overview and Create a New App. Name the App whatever you want, and copy and save the API Key and Secret they will provide. I will refer to them as CONSUMER_KEY and CONSUMER_SECRET from now on.
  3. Change the App Permissions to Read and Write (or Read and Write and Direct Messages if your script needs it).
  4. Go to the Keys and Tokens section and generate Access Token and Secret. Copy these as well and save them somewhere. I will refer to them as ACCESS_TOKEN and ACCESS_TOKEN_SECRET from now on.

Writing the script

To make the tutorial (and the code) easy, I won’t be using any pre-made images but will generate everything from scratch using Python.

Setup

  1. Create a directory where you will store the code
  2. Create a file .env that will store our Keys and Tokens. This is how its contents should look
    1
       2
       3
       4
       
    ACCESS_TOKEN=<ACCESS_TOKEN>
       ACCESS_TOKEN_SECRET=<ACCESS_TOKEN_SECRET>
       CONSUMER_KEY=<CONSUMER_KEY>
       CONSUMER_SECRET=<CONSUMER_KEY>
  3. Create a directory called fonts and download SourceCodePro-Regular.ttf from Google Fonts into it

Code

The whole code is available on Github, so I will explain only the important parts of it.

  1. Variables and Constants to be used later on in the code
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    # Load environment variables from .env file
    load_dotenv()
    
    # Location of this file
    parent_directory = os.path.dirname(os.path.abspath(__file__))
    
    # Colors to be used in the header
    COLORS = {
        "FOREGROUND": "#000000",
        "BACKGROUND": "#FFFFFF",
    }
    
    # Fonts to be used in the header
    FONTS = {
        "TITLE": ImageFont.truetype(os.path.join(parent_directory, "fonts/SourceCodePro-Regular.ttf"), 48),
        "SUBTITLE": ImageFont.truetype(os.path.join(parent_directory, "fonts/SourceCodePro-Regular.ttf"), 30),
        "NUMBERS": ImageFont.truetype(os.path.join(parent_directory, "fonts/SourceCodePro-Regular.ttf"),  144),
        "FOOTER": ImageFont.truetype(os.path.join(parent_directory, "fonts/SourceCodePro-Regular.ttf"),  24),
    }
    
    # ID of the Tweet to be used
    PINNED_TWEET_ID = "1435219303465324548"
  2. Authentication to Twitter
    1
       2
       3
       4
       5
       6
       7
       
    # Authenticate to Twitter and return the API object
       def get_twitter_api():
           auth = tweepy.OAuthHandler(
               os.getenv("CONSUMER_KEY"), os.getenv("CONSUMER_SECRET"))
           auth.set_access_token(
               os.getenv("ACCESS_TOKEN"), os.getenv("ACCESS_TOKEN_SECRET"))
           return tweepy.API(auth)
  3. Fetching required data
     1
        2
        3
        4
        5
        6
        7
        8
        9
       10
       11
       12
       13
       
    # Fetch the status and followers data
       def get_status_data(api, status_id):
           status = api.get_status(id=status_id)
           followers = api.get_followers(
               count=3, skip_status=True)    # 3 Latest Followers
           return {
               "likes": status.favorite_count,
               "retweets": status.retweet_count,
               "followers": [{
                   "username": follower.screen_name,
                   "photo": follower.profile_image_url_https
               } for follower in followers]        # The username and profile picture only
           }
  4. Drawing the header. The coordinates are mostly hardcoded around the 1500x500px dimension of the header. Only the followers’ images and the rectangle around them is calculated based on the width of usernames.
     1
        2
        3
        4
        5
        6
        7
        8
        9
       10
       11
       12
       13
       14
       15
       16
       17
       18
       19
       20
       21
       22
       23
       24
       25
       26
       27
       28
       29
       30
       31
       32
       33
       34
       35
       36
       37
       38
       39
       40
       41
       42
       43
       44
       45
       46
       47
       48
       49
       50
       51
       52
       53
       54
       55
       
    # Draw the Header
       def draw_header(data):
           # Create a new Image 1500 x 500 with background color
           img = Image.new('RGB', (1500, 500), color=COLORS['BACKGROUND'])
           d = ImageDraw.Draw(img)  # Draw variable for drawing on the image
           # The rectangle which contains the test "My pinned tweet has"
           d.rectangle((30, 30, 680, 110), None, COLORS["FOREGROUND"], 5)
           # Text - My Pinned Tweet has
           d.text((355, 70), "My pinned tweet has",
                  fill=COLORS["FOREGROUND"], font=FONTS["TITLE"], anchor="mm")
       
           # Likes and Likes Count
           d.text((500, 370), "LIKES",
                  fill=COLORS["FOREGROUND"], font=FONTS["TITLE"], anchor="mm")
           d.text((500, 250), str(data["likes"]),
                  fill=COLORS["FOREGROUND"], font=FONTS["NUMBERS"], anchor="mm")
           # Retweets and Retweets Count
           d.text((1000, 370), "RETWEETS",
                  fill=COLORS["FOREGROUND"], font=FONTS["TITLE"], anchor="mm")
           d.text((1000, 250), str(data["retweets"]),
                  fill=COLORS["FOREGROUND"], font=FONTS["NUMBERS"], anchor="mm")
       
           # Text - Latest Followers
           d.text((1300, 50), "Latest Followers",
                  fill=COLORS["FOREGROUND"], font=FONTS["SUBTITLE"], anchor="mm")
       
           # Keeping track of widest line of text
           max_width = d.textsize("Latest Followers", font=FONTS["SUBTITLE"])[0]
       
           # Drawing the followers text and image
           for idx, follower in enumerate(data["followers"]):
               username_width = d.textsize(
                   follower["username"], font=FONTS["SUBTITLE"])[0]
               if username_width + 50 > max_width:
                   # 50 = Width of image (30) + gap between image and text (20)
                   max_width = username_width + 50
               d.text((1320, 90 + 40*idx), follower["username"],
                      fill=COLORS["FOREGROUND"], font=FONTS["SUBTITLE"], anchor="mm")
               # Download image
               profile_image = Image.open(io.BytesIO(
                   requests.get(follower["photo"]).content))
               # Resize image
               profile_image = profile_image.resize((30, 30))
               # Paste Image
               img.paste(profile_image, (1280 - (username_width//2), 75 + 40*idx))
       
           # Draw rectangle around followers
           d.rectangle((1300 - (max_width/2) - 20, 30, 1300 +
                       (max_width/2) + 20, 210), None, COLORS["FOREGROUND"], 5)
       
           # Footer Text
           d.multiline_text((750, 465), "Like / Retweet my pinned tweet to see my header update\nCheck pinned thread for more details",
                            fill=COLORS["FOREGROUND"], font=FONTS["FOOTER"], align="center", anchor="ms")
           # Save the image
           img.save(os.path.join(parent_directory, "header.png"))
  5. Driver Code
    1
       2
       3
       4
       5
       6
       7
       
    # Driver Code
       if __name__ == "__main__":
           api = get_twitter_api()     # Get the Authenticated Api
           # Draw the header using data of PINNED_TWEET_ID
           draw_header(get_status_data(api, PINNED_TWEET_ID))
           api.update_profile_banner(os.path.join(
               parent_directory, 'header.png'))  # Upload the header

Execution

You can run this script to update the header. I will suggest keeping this as a cronjob, for at least every two minutes to get the best results. I am hosting this on my Raspberry Pi, but you could try hosting it on Heroku, or some other service.