<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Haider Ali Punjabi</title>
    <description>The latest articles on DEV Community by Haider Ali Punjabi (@haideralipunjabi).</description>
    <link>https://dev.to/haideralipunjabi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F185385%2F608faca3-e4e7-4d84-afce-2bb485a83dc3.png</url>
      <title>DEV Community: Haider Ali Punjabi</title>
      <link>https://dev.to/haideralipunjabi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haideralipunjabi"/>
    <language>en</language>
    <item>
      <title>Creating a secure Wordle using Serverless Functions</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Tue, 01 Feb 2022 08:33:26 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/creating-a-secure-wordle-using-serverless-functions-28di</link>
      <guid>https://dev.to/haideralipunjabi/creating-a-secure-wordle-using-serverless-functions-28di</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.powerlanguage.co.uk/wordle/"&gt;Wordle&lt;/a&gt; has been viral on Twitter for the past few weeks. It's a really simple game and has been really successful. But, &lt;a href="https://reichel.dev/blog/reverse-engineering-wordle.html"&gt;people were able to reverse engineer it&lt;/a&gt; and find out what the words are going to be in the future. I wanted to try and make one that can't be reverse-engineered that way. I did make one, but since there are hundreds of "How to make Wordle" tutorials out there, I will focus only on the "how to make it a bit more secure" part. I will use Serverless Functions for it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Serverless Functions can cost a bit if your Wordle becomes viral.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's wrong with Wordle?
&lt;/h2&gt;

&lt;p&gt;I won't say anything is wrong with &lt;a href="https://www.powerlanguage.co.uk/wordle/"&gt;Wordle&lt;/a&gt;. It's just a choice that its developer made. I am sure if &lt;a href="https://www.powerlanguage.co.uk"&gt;Josh Wardle&lt;/a&gt; wanted, he could have made it more secure, and if I was in his shoes, I would have also made it as he has.&lt;/p&gt;

&lt;p&gt;Why? Because using Serverless Functions is costly if the traffic is too much.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://reichel.dev/"&gt;Robert Reichel &lt;/a&gt;wrote a &lt;a href="https://reichel.dev/blog/reverse-engineering-wordle.html"&gt;good article on Reverse Engineering Wordle&lt;/a&gt; which explains how &lt;a href="//powerlanguage.co.uk/wordle/"&gt;Josh Wardle's Wordle&lt;/a&gt; determines the words on the client-side.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At this point, we've done enough digging to know how Wordle is choosing the word of the day. We know that Wordle uses a client-side date-based algorithm to determine which word to use from a static wordlist. Each day is predictable so long as we have all of the code pieced together&lt;/p&gt;

&lt;p&gt;-- &lt;a href="https://reichel.dev/blog/reverse-engineering-wordle.html"&gt;Reverse Engineering Wordle | Robert Reichel&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What do I mean by secure?
&lt;/h2&gt;

&lt;p&gt;By "secure", I mean that no one would be able to know what tomorrow's (or the day after, or any day in the future) word is. One can always know what today's word is by playing the game once, seeing the word and playing it again in a different browser. Or if you know how to, you can send a request to the API and it will tell you today's word. What will this do? This will prevent bots like "&lt;a href="https://www.thegamer.com/wordle-twitter-bot-the-wordlinator-spoler/"&gt;The Wordlinator&lt;/a&gt;" that spoil the game for others.&lt;/p&gt;

&lt;p&gt;Also, another advantage of using this method is. No matter where in the world you are playing from, everyone will get the new word at the same time because the selection will be based on the clock of the server, and not of the client. Many Wordle games have this issue where some people start getting the new word earlier than the rest of the world because it is 12:00 am of the new day for them, and the rest of the world is still on the previous day.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to do it?
&lt;/h2&gt;

&lt;p&gt;I won't write about how to make the entire Wordle game, but just the API  / Serverless Functions Part.  It can be deployed on any platform you like. I deployed mine on&lt;a href="https://nextjs.org/"&gt; Next.js&lt;/a&gt; and &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A list of words from which each day's word will be chosen. It's better if this is a subset of a larger list of words that decide which word is accepted and which isn't. The word acceptance logic and the large list can be client-side. (It would be better as it will reduce the load on API and may save money). The smaller list of words is never loaded on the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Logic:
&lt;/h3&gt;

&lt;p&gt;The logic for it is pretty simple. We will make an API Route that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loads the list of words from which we select a word each day&lt;/li&gt;
&lt;li&gt;Calculates the number of days since some fixed data (e.g, the day the app/game is launched).&lt;/li&gt;
&lt;li&gt;Selecting and responding with the word from the list of words using the calculated difference.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Code:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Next.js API route support: https://nextjs.org/docs/api-routes/introduction  &lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;luxon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GameData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/interfaces&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;gameWords&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../data/selected.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// The list of words&lt;/span&gt;

&lt;span class="c1"&gt;// Function to calculate the difference between today and and a fixed date  &lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getIndex&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;31/01/2022&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dd/mm/yyyy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;setZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTC+5:30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;startOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;setZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTC+5:30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;startOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;days&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;days&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GameData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getIndex&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;  
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="na"&gt;word&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gameWords&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  
    &lt;span class="p"&gt;});&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, if you want, you can make another API Endpoint that returns the time left for next word.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;luxon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;setZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTC+5:30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;startOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these two APIs, you can make a Wordle game that is a bit more secure. &lt;/p&gt;

&lt;h2&gt;
  
  
  References:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.powerlanguage.co.uk/wordle/"&gt;Josh Wardle's Wordle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reichel.dev/blog/reverse-engineering-wordle.html"&gt;Reverse Engineering Wordle - Robert Reichel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/api-routes/introduction"&gt;Next.js API Routes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.haideralipunjabi.com/posts/creating-a-secure-wordle-using-next.js-api-routes-and-vercel-serverless-functions/"&gt;Read it on my blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.buymeacoffee.com/HAliPunjabi"&gt;Buy Me a Coffee&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>wordle</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Dynamic Twitter Header using Python</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Fri, 10 Sep 2021 11:06:23 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/dynamic-twitter-header-using-python-4hkm</link>
      <guid>https://dev.to/haideralipunjabi/dynamic-twitter-header-using-python-4hkm</guid>
      <description>&lt;p&gt;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 &lt;a href="https://twitter.com/Prathkum" rel="noopener noreferrer"&gt;@Prathkum&lt;/a&gt;, he is a CSS Wizard and one of the inspirations behind my header projects).&lt;/p&gt;

&lt;p&gt;Since "Made With CSS" wasn't possible, I decided to take a different route. &lt;strong&gt;Python&lt;/strong&gt;. 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 &lt;em&gt;Twitter Header Experiments&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The second one like I said, was a cryptocurrency joke which updated the USD rates every minute. (I have that &lt;a href="https://www.reddit.com/user/dJones176/comments/npoq3f/a_boy_asked_his_bitcoininvesting_dad_for_1/" rel="noopener noreferrer"&gt;same joke on Reddit&lt;/a&gt;). I used this header for a while but later changed it to some static image.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1435219303465324548-71" src="https://platform.twitter.com/embed/Tweet.html?id=1435219303465324548"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1435219303465324548-71');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1435219303465324548&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Before starting the actual tutorial, you can check my header on &lt;a href="https://twitter.com/HAliPunjabi" rel="noopener noreferrer"&gt;my Twitter (@HAliPunjabi)&lt;/a&gt; and the &lt;a href="https://twitter.com/HAliPunjabi/status/1435219303465324548" rel="noopener noreferrer"&gt;related Tweet &lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite - Twitter Developer Account and App
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://developer.twitter.com/en/apply-for-access" rel="noopener noreferrer"&gt;Twitter Developer Account&lt;/a&gt; and apply for access. It shouldn't take too long to get approved.&lt;/li&gt;
&lt;li&gt;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 &lt;code&gt;CONSUMER_KEY&lt;/code&gt; and &lt;code&gt;CONSUMER_SECRET&lt;/code&gt; from now on.&lt;/li&gt;
&lt;li&gt;Change the App Permissions to &lt;code&gt;Read and Write&lt;/code&gt; (or &lt;code&gt;Read and Write and Direct Messages&lt;/code&gt; if your script needs it).&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Keys and Tokens&lt;/strong&gt; section and generate Access Token and Secret. Copy these as well and save them somewhere. I will refer to them as &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; and &lt;code&gt;ACCESS_TOKEN_SECRET&lt;/code&gt; from now on.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Writing the script
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a directory where you will store the code&lt;/li&gt;
&lt;li&gt;Create a file &lt;code&gt;.env&lt;/code&gt; that will store our Keys and Tokens. This is how its contents should look
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;ACCESS_TOKEN&amp;gt;
&lt;span class="nv"&gt;ACCESS_TOKEN_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;ACCESS_TOKEN_SECRET&amp;gt;
&lt;span class="nv"&gt;CONSUMER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;CONSUMER_KEY&amp;gt;
&lt;span class="nv"&gt;CONSUMER_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;CONSUMER_KEY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Create a directory called &lt;code&gt;fonts&lt;/code&gt; and download &lt;code&gt;SourceCodePro-Regular.ttf&lt;/code&gt; from &lt;a href="https://fonts.google.com/specimen/Source+Code+Pro" rel="noopener noreferrer"&gt;Google Fonts&lt;/a&gt; into it&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/haideralipunjabi" rel="noopener noreferrer"&gt;
        haideralipunjabi
      &lt;/a&gt; / &lt;a href="https://github.com/haideralipunjabi/twitter-header-script" rel="noopener noreferrer"&gt;
        twitter-header-script
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Dynamic Twitter Header using Python
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Twitter-Header-Script&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Python Script to generate a Twitter Header with Like / Retweet Statistics of a Tweet, and 3 latest followers&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/haideralipunjabi/twitter-header-script.github/header.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhaideralipunjabi%2Ftwitter-header-script.github%2Fheader.png" alt="header.png"&gt;&lt;/a&gt;
Sample Generated Image&lt;/p&gt;
&lt;p&gt;Also, I am going to be using this as &lt;a href="https://twitter.com/HAliPunjabi" rel="nofollow noopener noreferrer"&gt;my header on Twitter&lt;/a&gt; for a while, using &lt;a href="https://twitter.com/HAliPunjabi/status/1435219303465324548" rel="nofollow noopener noreferrer"&gt;this tweet&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Download the requirements
&lt;code&gt;pip install -r requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a directory called fonts and download SourceCodePro-Regular.ttf from &lt;a href="https://fonts.google.com/specimen/Source+Code+Pro" rel="nofollow noopener noreferrer"&gt;Google Fonts&lt;/a&gt; into it&lt;/li&gt;
&lt;li&gt;Create a Twitter app from &lt;a href="https://developer.twitter.com/" rel="nofollow noopener noreferrer"&gt;Twitter Developer Dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Rename &lt;code&gt;.env.sample&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt; and fill it with appropriate values from your Twitter App&lt;/li&gt;
&lt;li&gt;Change the &lt;code&gt;PINNED_TWEET_ID&lt;/code&gt; variable in &lt;code&gt;main.py&lt;/code&gt; to the ID of the tweet you want to use&lt;/li&gt;
&lt;li&gt;Run the script to generate the header and upload it&lt;/li&gt;
&lt;li&gt;Run it at regular intervals (e.g, using cronjobs) to make sure the header is always updated&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/haideralipunjabi/twitter-header-script" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;&lt;br&gt;
The whole code is &lt;a href="https://github.com/haideralipunjabi/twitter-header-script" rel="noopener noreferrer"&gt;available on Github&lt;/a&gt;, so I will explain only the important parts of it.

&lt;ul&gt;
&lt;li&gt;Variables and Constants to be used later on in the code
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Load environment variables from .env file
&lt;/span&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Location of this file
&lt;/span&gt;&lt;span class="n"&gt;parent_directory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Colors to be used in the header
&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#000000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BACKGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#FFFFFF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Fonts to be used in the header
&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fonts/SourceCodePro-Regular.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUBTITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fonts/SourceCodePro-Regular.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUMBERS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fonts/SourceCodePro-Regular.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOOTER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fonts/SourceCodePro-Regular.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# ID of the Tweet to be used
&lt;/span&gt;&lt;span class="n"&gt;PINNED_TWEET_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1435219303465324548&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Authentication to Twitter
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Authenticate to Twitter and return the API object
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_twitter_api&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tweepy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OAuthHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONSUMER_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONSUMER_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_access_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACCESS_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACCESS_TOKEN_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tweepy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;API&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Fetching required data
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fetch the status and followers data
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_status_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;followers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;followers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skip_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# 3 Latest Followers
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;likes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;favorite_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retweets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retweet_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;followers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;screen_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;photo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile_image_url_https&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;follower&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;followers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;        &lt;span class="c1"&gt;# The username and profile picture only
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;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.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Draw the Header
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Create a new Image 1500 x 500 with background color
&lt;/span&gt;    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BACKGROUND&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Draw variable for drawing on the image
&lt;/span&gt;    &lt;span class="c1"&gt;# The rectangle which contains the test "My pinned tweet has"
&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;680&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Text - My Pinned Tweet has
&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;355&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My pinned tweet has&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Likes and Likes Count
&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;370&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LIKES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;likes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
           &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUMBERS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Retweets and Retweets Count
&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;370&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RETWEETS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retweets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
           &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUMBERS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Text - Latest Followers
&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Latest Followers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUBTITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Keeping track of widest line of text
&lt;/span&gt;    &lt;span class="n"&gt;max_width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Latest Followers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUBTITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Drawing the followers text and image
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;follower&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;followers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;username_width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUBTITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;username_width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# 50 = Width of image (30) + gap between image and text (20)
&lt;/span&gt;            &lt;span class="n"&gt;max_width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;username_width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
               &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUBTITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Download image
&lt;/span&gt;        &lt;span class="n"&gt;profile_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;photo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;# Resize image
&lt;/span&gt;        &lt;span class="n"&gt;profile_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;profile_image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;# Paste Image
&lt;/span&gt;        &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paste&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile_image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username_width&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Draw rectangle around followers
&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1300&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1300&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Footer Text
&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiline_text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;750&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;465&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Like / Retweet my pinned tweet to see my header update&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Check pinned thread for more details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOREGROUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOOTER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Save the image
&lt;/span&gt;    &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Driver Code
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Driver Code
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_twitter_api&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;     &lt;span class="c1"&gt;# Get the Authenticated Api
&lt;/span&gt;    &lt;span class="c1"&gt;# Draw the header using data of PINNED_TWEET_ID
&lt;/span&gt;    &lt;span class="nf"&gt;draw_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_status_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PINNED_TWEET_ID&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_profile_banner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;parent_directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;header.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Upload the header
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Execution
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Related Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.twitter.com/en/docs" rel="noopener noreferrer"&gt;Twitter Developer Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.deveshb.me/create-a-real-time-twitter-banner" rel="noopener noreferrer"&gt;Create a real-time Twitter banner! (NodeJS) - Devesh B&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/Prathkum" rel="noopener noreferrer"&gt;Prathkum on Twitter for Awesome CSS Headers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.buymeacoffee.com/HAliPunjabi" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;
&lt;/h2&gt;

</description>
      <category>python</category>
      <category>twitter</category>
      <category>programming</category>
      <category>automation</category>
    </item>
    <item>
      <title>Testing PWAs on mobile devices during development</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Mon, 21 Dec 2020 11:05:08 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/testing-pwas-on-mobile-devices-during-development-22mm</link>
      <guid>https://dev.to/haideralipunjabi/testing-pwas-on-mobile-devices-during-development-22mm</guid>
      <description>&lt;h2&gt;
  
  
  PWA? What is that?
&lt;/h2&gt;

&lt;p&gt;Progressive Web Applications (PWAs) are web applications (duh!) that use emerging web APIs and modern technologies to make the web app behave similar to a native application on any platform / operating system. They can be installed on your phone or PC, and even be distributed through App Stores.&lt;/p&gt;

&lt;p&gt;I will link some articles related to PWAs at the end.&lt;/p&gt;

&lt;p&gt;The capability to be installed on a mobile device is very important for a PWA, and to test those mobile-specific features even more important. PWAs can only be served over &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts" rel="noopener noreferrer"&gt;Secure Contexts&lt;/a&gt; (&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/HTTPS" rel="noopener noreferrer"&gt;HTTPS&lt;/a&gt;). It isn't a big deal today due to services like &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let's Encrypt&lt;/a&gt;, which provide free SSL Certificates. The HTTPS restriction isn't a problem but makes testing the PWA difficult. Most browsers treat localhost as a Secure Context, and testing PWA on your own machine isn't a problem. The problem arises when you access your web app from your phone (over local network), and it won't work because usually, you won't have an SSL Certificate for debugging on your own machine. One of the solutions to this problem is to get an SSL certificate, but it can be a complicated process for those not having experience with such stuff.&lt;/p&gt;

&lt;p&gt;In this blog post, I will explain two methods which will help you test your PWAs on your phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chrome Port Forwarding
&lt;/h2&gt;

&lt;p&gt;Host a site on your machine, access the content from an Android Device (over Android Debugging Bridge). By using Port Forwarding, the browser on your mobile will be able to access the site on its own localhost, thus in a Secure Context.&lt;/p&gt;

&lt;p&gt;Most of the tutorials on internet use Wired ADB, which discourages many people from using this method. I will be using Wireless ADB, which isn't very difficult to setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  ADB over Network
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhejc09z4sn9sh347jcpw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhejc09z4sn9sh347jcpw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your Phone Settings &amp;gt; System &amp;gt; Developer Options (This might vary in different phones, so if it is not the same in your's, look it up on the internet)&lt;/li&gt;
&lt;li&gt;Turn on Android Debugging and ADB over Network.&lt;/li&gt;
&lt;li&gt;Note the IP Address and Port shown under ADB over Network&lt;/li&gt;
&lt;li&gt;Install &lt;a href=""&gt;ADB&lt;/a&gt; on your computer&lt;/li&gt;
&lt;li&gt;Go to your command-line / command prompt and enter &lt;code&gt;adb connect &amp;lt;ip-address&amp;gt;:&amp;lt;port&amp;gt;&lt;/code&gt; (Use the IP Address and Port from Step 3)&lt;/li&gt;
&lt;li&gt;When connecting for the first time, you will need to authorize the connection on your phone.&lt;/li&gt;
&lt;li&gt;Your device should be connected to your PC over WiFi.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Using Chrome Port Forwarding
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Make sure your development server is running on your PC&lt;/li&gt;
&lt;li&gt;Go to &lt;a href="https://dev.tochrome://inspect/#devices"&gt;&lt;code&gt;chrome://inspect/#devices&lt;/code&gt;&lt;/a&gt;. You should see your device along with a Connected status indicator&lt;/li&gt;
&lt;li&gt;Enable Port Forwarding&lt;/li&gt;
&lt;li&gt;Click Add Rule&lt;/li&gt;
&lt;li&gt;In the Device Port, enter the port number on which you want to access the site on your device&lt;/li&gt;
&lt;li&gt;In the Local Address field, enter the address and port of your development server. (e.g, localhost:1313)&lt;/li&gt;
&lt;li&gt;Click Add&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.haideralipunjabi.com%2Fuploads%2Fmaim-1591337806.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.haideralipunjabi.com%2Fuploads%2Fmaim-1591337806.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Port Forwarding should be setup and you will be able to access your server on your Android Device on localhost:port (which you set in Step 5.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/web/tools/chrome-devtools/remote-debugging/local-server" rel="noopener noreferrer"&gt;More Detailed Tutorial&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros vs Cons
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster Deployment and Testing&lt;/li&gt;
&lt;li&gt;Already using ADB and Chrome, which will also be used if you want to use the Dev Tools on your mobile device&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can only be used for Android Devices&lt;/li&gt;
&lt;li&gt;The devices need to be on the same network&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Netlify Dev
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.netlify.com/products/dev/" rel="noopener noreferrer"&gt;Netlify Dev&lt;/a&gt; is part of the &lt;a href="https://docs.netlify.com/cli/get-started" rel="noopener noreferrer"&gt;netlify-cli&lt;/a&gt;, and is a great choice if you are already using netlify-cli, or need to showcase your work live without deploying it to production. Netlify Dev also allows you to use many features from the Netlify Ecosystem like Netlify Functions, Custom Headers, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;First of all, you will need to setup netlify-cli and authorize it. You will need an account on Netlify for it.&lt;/p&gt;

&lt;p&gt;&lt;a href=""&gt;Here's a link to a detailed tutorial&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install netlify-cli
&lt;code&gt;npm install netlify-cli -g&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authenticate&lt;br&gt;
&lt;code&gt;netlify login&lt;/code&gt;&lt;br&gt;
Authorize it in the browser window that opens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setup your repository using&lt;br&gt;
a. Automated Setup (if your repository is on Github)&lt;br&gt;
&lt;code&gt;netlify init&lt;/code&gt;&lt;br&gt;
b. Manual (for other Git Providers, or if you wise to do it like this)&lt;br&gt;
&lt;code&gt;netlify init --manual&lt;/code&gt;&lt;br&gt;
You can do a lot of stuff with this cli, but I will skip directly to Netlify Dev&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To start a Netlify Dev server, you can use &lt;code&gt;netlify dev&lt;/code&gt; , but since we want to Start a Public Live Session, we will use&lt;br&gt;
&lt;code&gt;netlify dev --live&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will get a URL that looks like &lt;code&gt;https://clever-cray-2aa156-6639f3.netlify.live/&lt;/code&gt;. This URL will be accessible by everyone on internet.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;netlify-cli has many more features you should read about in the &lt;a href="https://docs.netlify.com/cli/get-started/#netlify-dev" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing your PWA on your mobile
&lt;/h2&gt;

&lt;p&gt;Now that you can access the PWA on your mobile, you need to find a way to test it properly. The Dev Tools available in PC Browsers help in debugging and testing, but unfortunately, I have yet to find a similar feature on phone. Fortunately, Chrome provides a solution by allowing us access to the Dev Tools for a page on mobile devices using ADB.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setup ADB, as shown in the Chrome Port Forwarding&lt;/li&gt;
&lt;li&gt;In &lt;a href="https://dev.tochrome://inspect/#devices"&gt;&lt;code&gt;chrome://inspect/#devices&lt;/code&gt;&lt;/a&gt;, you will see a list of all pages opened on your mobile device.&lt;/li&gt;
&lt;li&gt;Click on the inspect button to launch the Dev Tools for that page&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.haideralipunjabi.com%2Fuploads%2Fmaim-1591337816.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.haideralipunjabi.com%2Fuploads%2Fmaim-1591337816.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps" rel="noopener noreferrer"&gt;Progressive Web Apps -Mozilla&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.netlify.com/products/dev/" rel="noopener noreferrer"&gt;Netlify Dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/tools/chrome-devtools/remote-debugging/local-server" rel="noopener noreferrer"&gt;Access Local Servers - Google Tools for Web Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews" rel="noopener noreferrer"&gt;Remote Debugging WebView&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connect with me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/HAliPunjabi" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.buymeacoffee.com/HAliPunjabi" rel="noopener noreferrer"&gt;Buy Me A Coffee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/haideralipunjabi" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>pwa</category>
    </item>
    <item>
      <title>Friendly reminder: String.replaceAll is only supported on 83.7% of devices</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Sun, 13 Dec 2020 16:35:26 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/friendly-reminder-string-replaceall-is-only-supported-on-83-7-of-devices-93o</link>
      <guid>https://dev.to/haideralipunjabi/friendly-reminder-string-replaceall-is-only-supported-on-83-7-of-devices-93o</guid>
      <description>&lt;p&gt;I just found out after one of my friends couldn't access my NextJS web app. Finished my free trial of &lt;a href="https://www.browserstack.com/"&gt;BrowserStack&lt;/a&gt; on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://caniuse.com/?search=replaceAll"&gt;Can I Use replaceAll ?&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Badges for the terminal - Python</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Mon, 24 Aug 2020 09:00:23 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/badges-for-the-terminal-python-53ln</link>
      <guid>https://dev.to/haideralipunjabi/badges-for-the-terminal-python-53ln</guid>
      <description>&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;I've just ported the &lt;a href="https://github.com/nombrekeff/cli-badges"&gt;quirky node-js library cli-badges&lt;/a&gt; over to Python. The author of that library: &lt;/p&gt;
&lt;div class="ltag__user ltag__user__id__187971"&gt;
  
    .ltag__user__id__187971 .follow-action-button {
      background-color: #000000 !important;
      color: #ffffff !important;
      border-color: #000000 !important;
    }
  
    &lt;a href="/nombrekeff" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WSaJs7tF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--hX7RfmcL--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/187971/a5359a24-b652-46be-8898-2c5df32aa6e0.png" alt="nombrekeff image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/nombrekeff"&gt;Manolo Edge&lt;/a&gt;
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/nombrekeff"&gt;I don't know... I just code and they pay me!&lt;/a&gt;
    &lt;/div&gt;
    &lt;p class="ltag__user__social"&gt;
        &lt;a href="https://github.com/nombrekeff" rel="noopener"&gt;
          &lt;img class="icon-img" alt="github logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--C74Jn3f1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo.svg"&gt;nombrekeff
        &lt;/a&gt;
        &lt;a href="https://manoloedge.com/" rel="noopener"&gt;
          &lt;img class="icon-img" alt="external link icon" src="https://res.cloudinary.com/practicaldev/image/fetch/s--WsHTbjfA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/link.svg"&gt;https://manoloedge.com/
        &lt;/a&gt;
    &lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
Post about the NodeJS Library&lt;br&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/nombrekeff" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WSaJs7tF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--hX7RfmcL--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/187971/a5359a24-b652-46be-8898-2c5df32aa6e0.png" alt="nombrekeff image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/nombrekeff/badges-for-the-terminal-36b" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Badges for the terminal&lt;/h2&gt;
      &lt;h3&gt;Manolo Edge ・ Aug 22 ・ 1 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#showdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Python Port that I made: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/haideralipunjabi"&gt;
        haideralipunjabi
      &lt;/a&gt; / &lt;a href="https://github.com/haideralipunjabi/cli-badges"&gt;
        cli-badges
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Quirky little python package for generating badges for your cli apps.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://camo.githubusercontent.com/3f9a3274bcf8cd453de9b0d2a7244dc7848549c9/68747470733a2f2f76656374722e636f6d2f6b657266662f6464626d76795a6d6d2e7376673f77696474683d363030266865696768743d3330302673656c6563743d614e624b7863695068"&gt;&lt;img src="https://camo.githubusercontent.com/3f9a3274bcf8cd453de9b0d2a7244dc7848549c9/68747470733a2f2f76656374722e636f6d2f6b657266662f6464626d76795a6d6d2e7376673f77696474683d363030266865696768743d3330302673656c6563743d614e624b7863695068" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Quirky little python library for generating badges for your cli apps.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://camo.githubusercontent.com/52cb009aa7eca7d35696fc4d15b7fad4951984d4/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73697a652f686169646572616c6970756e6a6162692f636c692d6261646765732f636c695f6261646765732f636c695f6261646765732e70793f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/52cb009aa7eca7d35696fc4d15b7fad4951984d4/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73697a652f686169646572616c6970756e6a6162692f636c692d6261646765732f636c695f6261646765732f636c695f6261646765732e70793f7374796c653d666c61742d737175617265" alt="GitHub file size in bytes"&gt;&lt;/a&gt;
&lt;a href="https://badge.fury.io/py/cli-badges" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/fcd9366e7807c56fb2f87bd5cd260f0cd153b3bc/68747470733a2f2f62616467652e667572792e696f2f70792f636c692d6261646765732e737667" alt="PyPI version"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Inspired &amp;amp; Python Port of &lt;em&gt;&lt;a href="https://github.com/nombrekeff/cli-badges"&gt;cli-badges - nombrekeff&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
Getting Started&lt;/h2&gt;
&lt;h3&gt;
Installing&lt;/h3&gt;
&lt;p&gt;As usual, you need to install from PIP:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pip install cli-badges
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;
Usage&lt;/h3&gt;
&lt;p&gt;This is a simple example, using badges to display test results:&lt;/p&gt;
&lt;div class="highlight highlight-source-python"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;cli_badges&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;badge&lt;/span&gt;
&lt;span class="pl-s1"&gt;failedBadge&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;badge&lt;/span&gt;(&lt;span class="pl-s"&gt;"failed"&lt;/span&gt;,&lt;span class="pl-s"&gt;'2'&lt;/span&gt;,&lt;span class="pl-s1"&gt;messagebg&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;'red'&lt;/span&gt;)
&lt;span class="pl-s1"&gt;skippedBadge&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;badge&lt;/span&gt;(&lt;span class="pl-s"&gt;'skipped'&lt;/span&gt;, &lt;span class="pl-s"&gt;'1'&lt;/span&gt;, &lt;span class="pl-s1"&gt;messagebg&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;'yellow'&lt;/span&gt;,&lt;span class="pl-s1"&gt;messagecolor&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;'black'&lt;/span&gt;)
&lt;span class="pl-s1"&gt;successBadge&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;badge&lt;/span&gt;(&lt;span class="pl-s"&gt;'success'&lt;/span&gt;,&lt;span class="pl-s"&gt;'8'&lt;/span&gt;, &lt;span class="pl-s1"&gt;messagebg&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;'green'&lt;/span&gt;,&lt;span class="pl-s1"&gt;messagecolor&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;'black'&lt;/span&gt;)
&lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;failedBadge&lt;/span&gt;, &lt;span class="pl-s1"&gt;successBadge&lt;/span&gt;, &lt;span class="pl-s1"&gt;skippedBadge&lt;/span&gt;)&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above would output something similar to the terminal:&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/basic-output-example.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hSOTAdwk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/basic-output-example.png" alt="output-example"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You could also create a donate badge with a link (&lt;a href="https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/#links"&gt;if supported&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight highlight-source-python"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;cli_badges&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;badge&lt;/span&gt;
&lt;span class="pl-s1"&gt;donateBadge&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;badge&lt;/span&gt;(&lt;span class="pl-s"&gt;'❤️ donate'&lt;/span&gt;, &lt;span class="pl-s"&gt;'ko-fi'&lt;/span&gt;, &lt;span class="pl-s1"&gt;messagelink&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;'https://ko-fi.com/logginjs'&lt;/span&gt;);
&lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;donateBadge&lt;/span&gt;)&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/donate-output-example.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--evjhMFv6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/donate-output-example.png" alt="donate-output-example.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can also only…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/haideralipunjabi/cli-badges"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Installing
&lt;/h3&gt;

&lt;p&gt;As usual, you need to install from PIP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install cli-badges
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;This is a simple example, using badges to display test results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;cli_badges&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;

&lt;span class="n"&gt;failedBadge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;messagebg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;skippedBadge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'skipped'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messagebg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'yellow'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;messagecolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'black'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;successBadge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;'8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messagebg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;messagecolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'black'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failedBadge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;successBadge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skippedBadge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The above would output something similar to the terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hSOTAdwk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/basic-output-example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hSOTAdwk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/basic-output-example.png" alt="output-example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could also create a donate badge with a link (if supported):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;cli_badges&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;

&lt;span class="n"&gt;donateBadge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'❤️ donate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'ko-fi'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messagelink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'https://ko-fi.com/logginjs'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;donateBadge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--evjhMFv6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/donate-output-example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--evjhMFv6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/donate-output-example.png" alt="donate-output-example.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also only show the label:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;cli_badges&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;

&lt;span class="n"&gt;onlyLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'❤️ donate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onlyLabel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LQBI0WZX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/onlylabel-output-example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LQBI0WZX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/onlylabel-output-example.png" alt="onlylabel-output-example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Example output is a mock, console output will vary slightly from terminal to terminal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Badge Structure
&lt;/h2&gt;

&lt;p&gt;A badge is conformed of a label and a message &lt;code&gt;&amp;lt;label&amp;gt;:&amp;lt;message&amp;gt;&lt;/code&gt;. Each segment can be customized, by changing bg color, text color and style.&lt;/p&gt;

&lt;h2&gt;
  
  
  Available Options
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;label&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;''&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;message&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;''&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;messagebg&lt;/td&gt;
&lt;td&gt;Color&lt;/td&gt;
&lt;td&gt;blue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;labelbg&lt;/td&gt;
&lt;td&gt;Color&lt;/td&gt;
&lt;td&gt;dark_gray&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;messagecolor&lt;/td&gt;
&lt;td&gt;Color&lt;/td&gt;
&lt;td&gt;white&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;labelcolor&lt;/td&gt;
&lt;td&gt;Color&lt;/td&gt;
&lt;td&gt;white&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;labelwidth&lt;/td&gt;
&lt;td&gt;Integer&lt;/td&gt;
&lt;td&gt;label length + 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;messagewidth&lt;/td&gt;
&lt;td&gt;Integer&lt;/td&gt;
&lt;td&gt;label length + 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;labelstyles&lt;/td&gt;
&lt;td&gt;Array of Styles&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;messagestyles&lt;/td&gt;
&lt;td&gt;Array of Styles&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;labellink&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;''&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;messagelink&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;''&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Colors
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cli-badges&lt;/code&gt; uses &lt;a href="https://pypi.org/project/colored/"&gt;&lt;code&gt;colored&lt;/code&gt;&lt;/a&gt; internally for managing colors, you can check the list of available colors there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Styles
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cli-badges&lt;/code&gt; uses &lt;a href="https://pypi.org/project/colored/"&gt;&lt;code&gt;colored&lt;/code&gt;&lt;/a&gt; internally for managing styles, you can check the list of available styles there.&lt;/p&gt;

&lt;h4&gt;
  
  
  Available Styles
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;bold&lt;/li&gt;
&lt;li&gt;dim&lt;/li&gt;
&lt;li&gt;underlined&lt;/li&gt;
&lt;li&gt;reverse&lt;/li&gt;
&lt;li&gt;hidden&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;You can output badges with a link attached to it, that can be clicked in some terminals. &lt;code&gt;labellink&lt;/code&gt; option will add the link to the label, while &lt;code&gt;messagelink&lt;/code&gt; option will add the link to the message.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  ⚠︎ cli-badges will only output link if its supported by your terminal.
&lt;/h4&gt;

&lt;p&gt;See &lt;a href="https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda"&gt;this&lt;/a&gt; for information on supported terminals&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'with'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'link'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;labellink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'https://link.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messagelink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'https://link2.com'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WlMSqy4P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/withlink-output-example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WlMSqy4P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/haideralipunjabi/cli-badges/master/withlink-output-example.png" alt="withlink-output-example"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>python</category>
    </item>
    <item>
      <title>Redesigning My Website - Automation, Custom SSG &amp; Optimisations</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Sun, 02 Aug 2020 06:46:35 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/redesigning-my-website-automation-custom-ssg-optimisations-3h85</link>
      <guid>https://dev.to/haideralipunjabi/redesigning-my-website-automation-custom-ssg-optimisations-3h85</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;a href="https://haideralipunjabi.com"&gt;Link to the redesigned website&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Old Design&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Over the years, my portfolio website has gone through many changes. I always liked my website as simple as possible, with the possibility of editing my resume without opening the code. I have always preferred using HTML/CSS &amp;amp; JS to make websites if possible. I will, of course, use a proper SSG if the project needs it (mostly Hugo for blogs). I am also not against using some framework like React or Vue, but I only use them in projects with a huge number of components.&lt;/p&gt;

&lt;p&gt;Also, although I know CSS, the quality of any design I make from scratch has an inverse relation with the number of components. Thus, I prefer using premade themes as a base and then customizing them to my needs.&lt;/p&gt;

&lt;p&gt;The older version of my website used &lt;a href="https://html5up.net/astral"&gt;Astral&lt;/a&gt; theme from &lt;a href="https://html5up.net/"&gt;HTML5UP&lt;/a&gt;. I had customized it to some extent (not a lot, the theme was great but I never spent enough time on it). I hosted my resume on &lt;a href="https://gist.github.com/"&gt;Github Gists&lt;/a&gt; and embedded it on the website. After some minor CSS changes, it looked as if it was part of the website itself.&lt;/p&gt;

&lt;p&gt;There were a number of problems in that design, and I had to eventually change it someday. Among the problems, the things that I hated the most were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extremely Heavy: The design loaded a lot of resources (Font Awesome, etc) that made it extremely slow to load.&lt;/li&gt;
&lt;li&gt;No CMS: I never got the time to integrate the website with a CMS, and I always had to edit the code to make any change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I was always planning to fix these two issues, as soon as some inspiration struck me for the new design.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Inspiration for the New Design&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The inspiration came when &lt;a href="https://www.florin-pop.com/"&gt;Florin Pop&lt;/a&gt; did a live stream on Twitch where he did a developer portfolio review. Among those portfolios, I found a design that I liked a lot. The said design was of &lt;a href="https://github.com/dev-caspertheghost"&gt;CasperTheGhost&lt;/a&gt;’s &lt;a href="https://caspertheghost.me/"&gt;portfolio website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What I liked about his website was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited Colors: It had only 3 colours, one purplish for the background and grey and a white for the text&lt;/li&gt;
&lt;li&gt;Repeatable design: All the sections of the website had the same base design. Adding or removing sections won’t destroy the design of the website.&lt;/li&gt;
&lt;li&gt;Lightweight: Very few external resources are used, and I could remove whatever I didn’t want.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Before proceeding further, you should check out the&lt;/em&gt; &lt;a href="https://haideralipunjabi.com/"&gt;&lt;em&gt;website&lt;/em&gt;&lt;/a&gt; &lt;em&gt;because I am going to mention different parts of it&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Planning&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;After deciding on the design, I spent some time deciding how to make the website work. If I did minimal changes to the source, I would have to do a lot of changes manually and keep doing them manually in the future. I knew that I was going to add a variety of stuff to the portfolio which can change frequently, so having a CMS was a must. I decided that the CMS would make store all the data in JSON files somewhere in the code and I would load the data into HTML files.&lt;/p&gt;

&lt;p&gt;For the loading part, I had two choices. One, I could use JavaScript and its fetch() method to load the JSON data. The advantage of this method is that no building or generating is required, and the files could be used as-is. The disadvantage, of course, was the effect on performance and that the website would have almost nothing that isn’t loaded via JavaScript. I had used this method a lot in the past but wasn’t too fond of it due to its disadvantages.&lt;/p&gt;

&lt;p&gt;On the other hand, I could use a Static Site Generator that would put the data into the HTML files during the build process. The client will only need to download the HTML file which already has all the data in it. The advantage, of course, would be the increase in performance, but I don’t like using Static Site Generators for such a simple task. Static Site Generators are usually made for generating much bigger and complicated sites (many are specially tuned for generating a blogging website from markdown files).&lt;/p&gt;

&lt;p&gt;I finally decided on writing a Python Script that would act as my custom Static Site Generator. &lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja2&lt;/a&gt; is a great Python module for generating files from templates. I had already used similar scripts in other projects of mine. This is a great way to add a common part (header, footer, navigation bar, etc) of code to a multi-paged website. I was also able to generate a multilingual web app using a similar technique.&lt;/p&gt;

&lt;p&gt;Deciding to use a Python Script allowed me to make a lot of stuff editable from the CMS (Name, Open Graph Information, Colors, etc).&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The CMS&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Before starting the work on the website, I had to decide what CMS I was going to use. I have previously used both &lt;a href="https://forestry.io/"&gt;Forestry&lt;/a&gt; and &lt;a href="https://www.netlifycms.org/"&gt;Netlify CMS&lt;/a&gt;, but only for &lt;a href="https://gohugo.io/"&gt;Hugo Blogs&lt;/a&gt;. I wanted to use &lt;a href="https://www.netlifycms.org/"&gt;Netlify CMS&lt;/a&gt; because I could access it from &lt;em&gt;mydomain.com/admin&lt;/em&gt; (I forgot how to access &lt;a href="https://forestry.io/"&gt;Forestry&lt;/a&gt; CMS once). No matter how much I tried, I couldn’t get it to work for editing single data files. I switched to &lt;a href="https://forestry.io/"&gt;Forestry&lt;/a&gt; and setup the required front matter and data files.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Structure of Data&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Since the whole website works on this data, I think it would be appropriate to define its structure first.&lt;/p&gt;

&lt;p&gt;At the time of writing this, I have 5 different JSON files for various sections of the website.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Settings.json
This contained the settings for the website (colours, google analytics code, etc), Open Graph Details, and some stuff that is used in the landing section (Name, Subtitle, Social Media Links, etc)&lt;/li&gt;
&lt;li&gt;backpack.json, foss-contributions.json, timeline.json, projects.json
Each of these contained an array of items to be used in different sections. You can see the actual structure in the code below. For the icons, I was originally using the Font Awesome class names but changed it to the path to increase performance. (More on this later)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;backpack.json&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assets/svgs/brands/python.svg"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;foss-contributions.json&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"link"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/processing/p5.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;timeline.json&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Polybar Module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"June 2020"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;p&amp;gt;&amp;lt;a rel=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;noopener noreferrer&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; href=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/HackeSta/polybar-speedtest&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;polybar-speedtest&amp;lt;/a&amp;gt; - speedtest.net module for Polybar&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;projects.json&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tweet2Pic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"buttons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Play Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"link"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://play.google.com/store/apps/details?id=org.hackesta.tweet2pic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assets/svgs/brands/google-play.svg"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tweet2Pic is a small tool, which can be used to share your Twitter Tweets in the form of an image."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"assets/svgs/brands/android.svg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"assets/svgs/brands/java.svg"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Data from the Internet&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Apart from the data which I am entering via the CMS, there were some sections that required the script to fetch data from different sources on the internet. Most importantly, I use the &lt;a href="https://docs.github.com/en/rest"&gt;Github API&lt;/a&gt; data for the Open Source section, which lists the number of repositories (source) I have, the numbers of stars they have, and the number of forks I have. I use the &lt;a href="https://docs.github.com/en/rest"&gt;Github API&lt;/a&gt; for the “Projects I Contribute to” Section as well. In its data file (foss-contributions.json), I am storing only the repository name of the items. The script then uses &lt;a href="https://docs.github.com/en/rest"&gt;Github API&lt;/a&gt; to fetch the description, number of stars and forks of each project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_github_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GITHUB_API_URL&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;source_repos_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"fork"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;stargazers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"stargazers_count"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;forks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"forks"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;most_popular&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'stargazers_count'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))[:&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
            &lt;span class="s"&gt;"repo_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;source_repos_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"stargazers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stargazers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"forks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"most_popular"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;most_popular&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_foss_contributions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;contributions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FOSS_CONTRIBUTIONS_DATA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="s"&gt;"contributions"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;contributions_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;contribution&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;contributions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;api_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contribution&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"link"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"github.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"api.github.com/repos"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;contributions_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"contributions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contributions_data&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I also configured &lt;a href="https://blog.haideralipunjabi.com"&gt;my blog&lt;/a&gt; to output a JSON listing recent blog posts along with their featured/open graph image. The script fetches this JSON file, then downloads the data for four recent posts, along with the images. The images are then resized to a smaller size and stored in a folder. Downloading the images, resizing them to a smaller size and converting them to WEBP format really helped in maintaining the performance of the website.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_blog_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BLOG_API_URL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rm -rf assets/img/blogs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mkdir assets/img/blogs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;filein&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;f'assets/img/blogs/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;fileout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filein&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rsplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".webp"&lt;/span&gt;
        &lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;filein&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fileout&lt;/span&gt;
        &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filein&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BLOG_IMAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ANTIALIAS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Optimizations&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I wanted my website to be as lightweight as possible. To make sure I am doing everything right, I used &lt;a href="https://www.webpagetest.org/lighthouse"&gt;the Lighthouse Test&lt;/a&gt; to check for errors, and get ideas for optimizations. Apart from issues that were fixable easily (missing attributes, missing icons, etc), the most important part of the optimization was to reduce the number &amp;amp; size of requests. As I already had minimal JS, I focused on optimizing the CSS files and reducing the number of requests.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Optimizing CSS&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Two Node Packages, &lt;a href="https://purgecss.com/"&gt;PurgeCSS&lt;/a&gt; (to delete unused CSS, although there wasn’t much to delete) and &lt;a href="https://postcss.org/"&gt;PostCSS&lt;/a&gt; + &lt;a href="https://cssnano.co/"&gt;cssnano&lt;/a&gt; (to minify and bundle the CSS) were more than enough to optimize the files. I added both of them to the Travis Build Process to automate it.&lt;/p&gt;

&lt;p&gt;Also, Netlify does provide a feature to minify and bundle resources, which should also work similarly.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Optimizing Font Awesome&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Apart from other places in the website, the “My Backpack” section heavily used Font Awesome. Font Awesome files are huge in size and each icon had a request associated to it. To overcome this, I stopped using Font Awesome the normal way (adding fa* fa-* classes to elements) and took advantage of using &lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja2&lt;/a&gt; to import the SVG icons into the HTML wherever required. After generating the final page using &lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja2&lt;/a&gt;, all the SVGs are present as inline SVGs and are loaded as part of the HTML document. This removed the size of the helper script (approx 1.4mb) and removed a good number of requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GThiBnl4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ez2hCDyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GThiBnl4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ez2hCDyh.png" alt="Screenshot of My Backpack Section"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Optimizing Favicons&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;I originally used another Python script to generate Favicons but they were being requested numerous times during the page load. After some research, I came across this &lt;a href="https://realfavicongenerator.net/"&gt;favicon generator site&lt;/a&gt; which generated beautiful icons and reduced the number of requests to just 2 requests.&lt;/p&gt;

&lt;h4&gt;
  
  
  Final Score
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WdpCqmHb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/4cweeeVh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WdpCqmHb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/4cweeeVh.png" alt="Final Lighthouse Score"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dev-caspertheghost"&gt;CasperTheGhost&lt;/a&gt;’s &lt;a href="https://caspertheghost.me/"&gt;portfolio website&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.florin-pop.com/"&gt;Florin Pop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://html5up.net/"&gt;HTML5UP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forestry.io/"&gt;Forestry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.netlifycms.org/"&gt;Netlify CMS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.webpagetest.org/lighthouse"&gt;Webpagetest - Lighthouse Test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/"&gt;web.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://blog.haideralipunjabi.com/posts/redesigning-my-website-automation-custom-ssg-optimisations/"&gt;Read this on my blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>css</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Automating Android Games with Python &amp; Pytesseract: Sudoku</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Wed, 24 Jun 2020 16:45:36 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/automating-android-games-with-python-pytesseract-sudoku-ojp</link>
      <guid>https://dev.to/haideralipunjabi/automating-android-games-with-python-pytesseract-sudoku-ojp</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;I made a Python Script to Automate a Sudoku Game on Android after watching &lt;a href="https://www.youtube.com/channel/UCrUL8K81R4VBzm-KOYwrcxQ" rel="noopener noreferrer"&gt;Engineer Man's Videos on Youtube&lt;/a&gt; doing the same for different games.&lt;/p&gt;

&lt;p&gt;The script can be divided into 5 parts&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connecting to an Android device using ADB, and getting the screenshot of the game from it&lt;/li&gt;
&lt;li&gt;Using &lt;a href="https://pypi.org/project/Pillow/" rel="noopener noreferrer"&gt;Pillow&lt;/a&gt; to process the screenshot for &lt;a href="https://pypi.org/project/pytesseract/" rel="noopener noreferrer"&gt;pytesseract&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Using &lt;a href="https://pypi.org/project/pytesseract/" rel="noopener noreferrer"&gt;pytesseract&lt;/a&gt; to extract the Sudoku Game Grid to a 2D List in Python.&lt;/li&gt;
&lt;li&gt;Solving the Sudoku Game&lt;/li&gt;
&lt;li&gt;Sending the solved input to your Android Device using Python&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Out of the 5, I will be focusing mostly on 2,3 &amp;amp; 5 as 1 &amp;amp; 4 are topics that have been extensively covered.&lt;/p&gt;

&lt;p&gt;Link to the game I automated: &lt;a href="https://play.google.com/store/apps/details?id=com.quarzo.sudoku" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.quarzo.sudoku&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The complete code is available on the following repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/haideralipunjabi/sudoku_automate" rel="noopener noreferrer"&gt;Github: haideralipunjabi/sudoku_automate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also watch the script in action on:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/fGY1nQzzGUc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Libraries Used
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/Pillow/" rel="noopener noreferrer"&gt;Pillow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/pure-python-adb/" rel="noopener noreferrer"&gt;pure-python-adb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/pytesseract/" rel="noopener noreferrer"&gt;pytesseract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/progress/" rel="noopener noreferrer"&gt;progress&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tutorial
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1 (a). Using ADB to Connect to your Device
&lt;/h4&gt;

&lt;p&gt;Most of the tutorials on internet use Wired ADB, which discourages many people from using this method. I will be using Wireless ADB, which isn't very difficult to setup.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your Phone Settings &amp;gt; System &amp;gt; Developer Options (This might vary in different phones, so if it is not the same in your's, look it up on the internet)&lt;/li&gt;
&lt;li&gt;Turn on Android Debugging and ADB over Network.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FzjUIKeF.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FzjUIKeF.png" alt="ADB Over Network"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Note the IP Address and Port shown under ADB over Network&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://developer.android.com/studio/command-line/adb" rel="noopener noreferrer"&gt;ADB&lt;/a&gt; on your computer&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to your command-line / command prompt and enter&lt;/p&gt;

&lt;p&gt;adb connect &amp;lt;ip-address&amp;gt;:&amp;lt;port&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use the IP Address and Port from Step 3&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When connecting for the first time, you will need to authorize the connection on your phone.&lt;/li&gt;
&lt;li&gt;Your device should be connected to your PC over WiFi.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  1 (b). Using ADB with Python (&lt;a href="https://pypi.org/project/pure-python-adb/" rel="noopener noreferrer"&gt;pure-python-adb&lt;/a&gt;)
&lt;/h4&gt;

&lt;p&gt;You can define the following function to connect to  the first ADB device connected to your computer using Python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ppadb.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connect_device&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;adb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5037&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No Devices Attached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will be using this function later to return an instance of &lt;code&gt;ppadb.device.Device&lt;/code&gt; which will be used to take a screenshot, and send input to your device.&lt;/p&gt;

&lt;h4&gt;
  
  
  1 (c). Taking a Screenshot and saving it
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/pure-python-adb/" rel="noopener noreferrer"&gt;pure-python-adb&lt;/a&gt; makes it very easy to capture a screenshot of your device. The &lt;code&gt;screencap&lt;/code&gt; function is all that you need to get the screenshot. Use Pythons File IO to save it to &lt;code&gt;screen.png\&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;take_screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screencap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;screen.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FHrtXJUo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FHrtXJUo.png" alt="Screenshot of Sudoku"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Processing the screenshot with &lt;a href="https://pypi.org/project/Pillow/" rel="noopener noreferrer"&gt;Pillow&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;In the captured screenshot, the accuracy of any OCR will be very low. To increase accuracy, I used &lt;a href="https://pypi.org/project/Pillow/" rel="noopener noreferrer"&gt;Pillow&lt;/a&gt; to process the screenshot so that it only shows the numbers in black color on a white background.&lt;/p&gt;

&lt;p&gt;To do that, we first convert the image to grayscale (or single channel) using &lt;code&gt;image.convert('L')&lt;/code&gt;. This will make the convert the colors to shades of greys (0-255).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FIEF2xXo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FIEF2xXo.png" alt="Grayscale Screenshot of Sudoku"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this, we need the numbers (which are the darkest, or very near to black) in black color, and the rest in white. For this, we use &lt;code&gt;image.point()&lt;/code&gt;  so that all the greys &amp;gt; 50  become white (255) and the rest (numbers) become 0. I also increased the Contrast and Sharpness a bit to be on the safer side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F1rkOOfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F1rkOOfg.png" alt="Processed Screenshot of Sudoku"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageEnhance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contrast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageEnhance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sharpness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.  Extracting the numbers from the image using &lt;a href="https://pypi.org/project/pytesseract/" rel="noopener noreferrer"&gt;pytesseract&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Using &lt;a href="https://pypi.org/project/pytesseract/" rel="noopener noreferrer"&gt;pytesseract&lt;/a&gt; on the whole image might give us the numbers, but it won't tell us in which box the number was present. So, I use &lt;a href="https://pypi.org/project/Pillow/" rel="noopener noreferrer"&gt;Pillow&lt;/a&gt; to crop each box and then use &lt;a href="https://pypi.org/project/pytesseract/" rel="noopener noreferrer"&gt;pytesseract&lt;/a&gt; on the cropped images. Before using &lt;a href="https://pypi.org/project/pytesseract/" rel="noopener noreferrer"&gt;pytesseract&lt;/a&gt;, I defined some functions to give me the coordinates of each box and to give me a cropped image of each box.&lt;/p&gt;

&lt;p&gt;Since Sudoku has a 9x9 grid, I use two for loops from 0 to 8 to loop over each box. The &lt;a href="https://pypi.org/project/pytesseract/" rel="noopener noreferrer"&gt;pytesseract&lt;/a&gt; wasn't accurate enough on the default configuration and I had to pass the config &lt;code&gt;--psm 10 --oem 0&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;--psm&lt;/code&gt; argument defines the Page Segmentation Method. &lt;code&gt;10&lt;/code&gt; stands for &lt;code&gt;Treat the image as a single character&lt;/code&gt;. This seemed most appropriate since I am passing cropped images of each box.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;--oem&lt;/code&gt; argument defines the OCR Engine Mode. &lt;code&gt;0&lt;/code&gt; stands for &lt;code&gt;Legacy Engine Only&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following function will extract the numbers from the passed &lt;code&gt;image&lt;/code&gt; and return a 9x9 2D List with the numbers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_grid_from_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;digit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytesseract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;get_box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--psm 10 --oem 0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;     &lt;span class="c1"&gt;# If pytesseract returned a digit
&lt;/span&gt;                &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Solving the Sudoku Game
&lt;/h4&gt;

&lt;p&gt;Now that we have the 9x9 Sudoku, we need to solve it. Solving Sudoku is a topic that has been covered a lot, and I also copied this code from &lt;a href="https://www.geeksforgeeks.org/" rel="noopener noreferrer"&gt;geeksforgeeks.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.geeksforgeeks.org/sudoku-backtracking-7/" rel="noopener noreferrer"&gt;Here's the geekforgeeks article on Sudoku&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Sending the solved input to your Android Device using Python
&lt;/h4&gt;

&lt;p&gt;To send the input, I first filtered out the input from the solved Sudoku Grid,i.e, only send the values which were missing. I used the &lt;code&gt;get_coords&lt;/code&gt; function from earlier to get the coords of each box and then calculated their centres. I sent a touch at that centre using ADB, and then sent over the solution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;automate_game&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org_grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;solved_grid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;org_grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="c1"&gt;# If the box was blank in the game
&lt;/span&gt;                &lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# Calculating the center of the box (to select it)
&lt;/span&gt;                &lt;span class="n"&gt;solution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solved_grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input touchscreen swipe &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input text &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;solution&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Running the code
&lt;/h4&gt;

&lt;p&gt;All the code that I wrote is in functions and they are called one by one. Note that the grid that I get in step 3 isn't passed directly to step 4. I use &lt;code&gt;deepcopy&lt;/code&gt; to create a copy of it, so that I can compare the solved grid with the unsolved/original one in step 5.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Connect the device using ADB
&lt;/span&gt;    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_device&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Take Screenshot of the screen and save it in screen.png
&lt;/span&gt;    &lt;span class="n"&gt;adb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take_screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;screen.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# Process the image for OCR
&lt;/span&gt;    &lt;span class="n"&gt;org_grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_grid_from_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# Convert the Image to 2D list using OCR / Pytesseract
&lt;/span&gt;    &lt;span class="n"&gt;solved_grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deepcopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org_grid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# Deepcopy is used to prevent the function from modifying the original sudoku game
&lt;/span&gt;    &lt;span class="nf"&gt;solve_sudoku&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solved_grid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;automate_game&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org_grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;solved_grid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# Input the solved game into your device
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/channel/UCrUL8K81R4VBzm-KOYwrcxQ" rel="noopener noreferrer"&gt;Engineer Man's Youtube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai-facets.org/tesseract-ocr-best-practices/" rel="noopener noreferrer"&gt;Tesseract OCR Best Practices - ai-facets.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/studio/command-line/adb#wireless" rel="noopener noreferrer"&gt;ADB - Connect over Wi-Fi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>android</category>
      <category>game</category>
    </item>
    <item>
      <title>The Elder Scrolls V: Skyrim Special Edition - Analysis of Dialogues</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Thu, 04 Jun 2020 13:19:57 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/the-elder-scrolls-v-skyrim-special-edition-analysis-of-dialogues-2d1c</link>
      <guid>https://dev.to/haideralipunjabi/the-elder-scrolls-v-skyrim-special-edition-analysis-of-dialogues-2d1c</guid>
      <description>&lt;p&gt;&lt;a href="https://blog.haideralipunjabi.com/posts/the-elder-scrolls-v-skyrim-special-edition-analysis-of-dialogues/"&gt;Read this on my blog&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;After &lt;a href="https://blog.haideralipunjabi.com/posts/harry-potter-books-fanfiction-an-analysis-of-words/"&gt;my last visualisation of Harry Potter data&lt;/a&gt;, I decided to use some other data to create word clouds. I am a huge Skyrim fan and always wanted to learn how to use xEdit Scripts. As a result, here I am with another Word Cloud.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: A lot of the code is shared between&lt;/em&gt; &lt;a href="https://blog.haideralipunjabi.com/posts/harry-potter-books-fanfiction-an-analysis-of-words/"&gt;&lt;em&gt;my previous&lt;/em&gt;&lt;/a&gt; &lt;em&gt;project and this one&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Visualisations
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gZ1mXQlo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/odnJWMwh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gZ1mXQlo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/odnJWMwh.png" alt="WordCloud of Most Occurring Words in Dialogues of Skyrim Special Edition"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5q_-Nipt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ca71mquh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5q_-Nipt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ca71mquh.png" alt="Graph of DLC contribution towards dialogues"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the data
&lt;/h3&gt;

&lt;p&gt;I don't like opening my Windows installation (I have a dual boot setup, and use Manjaro mainly), and looked around the internet for some sort of data dump of Skyrim Dialogues. Unfortunately, I couldn't find any and then decided to extract the data myself. I had recently formatted my Windows Partition so had to reinstall the game. It also provided the benefit that no mods would pollute the data. (I had over 150 mods before the format). I downloaded the latest &lt;a href="http://tes5edit.github.io/"&gt;xEdit&lt;/a&gt; and used the &lt;code&gt;Export dialogues.pas&lt;/code&gt; script that comes with it to export all the dialogues. (It took me 22:05 minutes).&lt;/p&gt;

&lt;p&gt;I am going to look into other data I can extract this way, and maybe make some other stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Processing the data
&lt;/h3&gt;

&lt;p&gt;In the CSV, there were two columns of data I was interested in &lt;strong&gt;RESPONSE TEXT&lt;/strong&gt; &amp;amp; &lt;strong&gt;TOPIC TEXT&lt;/strong&gt;. Response Text was the larger one, with over 40k unique dialogues. Topic Text had only around 5.5K unique dialogues and also needed some additional processing. Topic Text contained some game constants such as &lt;code&gt;RoomCost&lt;/code&gt; , &lt;code&gt;HorseCost&lt;/code&gt;, and other prices, which had to be filtered out. I did all that in &lt;code&gt;csv_to_json.py&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Counting the Words
&lt;/h3&gt;

&lt;p&gt;Like the &lt;a href=""&gt;previous visualisation&lt;/a&gt;,  I used &lt;a href=""&gt;nltk'&lt;/a&gt;s stopwords corpus, along with a modified version of &lt;a href="https://github.com/first20hours/google-10000-english"&gt;20k most common words by Google&lt;/a&gt;. Interestingly, the modifications I did for Harry Potter were valid for Skyrim as well, because there is no dialogue with names like Harry, Ron, Arthur, etc and they share words like vampires, magic, etc.&lt;/p&gt;

&lt;p&gt;I counted both RESPONSE TEXT &amp;amp; TOPIC TEXT data separately and then merged them into a single file count.json&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Additional Tip:&lt;/em&gt; &lt;a href="https://pypi.org/project/progress/"&gt;&lt;em&gt;progress&lt;/em&gt;&lt;/a&gt; &lt;em&gt;is a great Python Package to show progress in your scripts.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Making the WordCloud
&lt;/h3&gt;

&lt;p&gt;I used pretty much the same process as the &lt;a href="https://blog.haideralipunjabi.com/posts/harry-potter-books-fanfiction-an-analysis-of-words/"&gt;last visualisation&lt;/a&gt;. I changed the maximum font size to depict the variation properly and used a custom font this time.&lt;/p&gt;

&lt;p&gt;To make the WordCloud, I used the &lt;a href="https://amueller.github.io/word_cloud/"&gt;wordcloud&lt;/a&gt; package. For the mask, I used &lt;a href="https://www.nexusmods.com/skyrim/mods/68054"&gt;Skyrim Logo Vector&lt;/a&gt;. For the font, I used &lt;a href="https://www.nexusmods.com/skyrimspecialedition/mods/386"&gt;Sovngarde&lt;/a&gt; font.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making the Graph
&lt;/h3&gt;

&lt;p&gt;I initially planned on making a set of graphs from the data, but wasn't able to due to two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Some of the data was weird. Argneir had the highest dialogue count due to dialogues of many NPCs (including General Tullius, I think) is assigned to him.&lt;/li&gt;
&lt;li&gt;Some of the data doesn't; produce interesting visualisations. Nords have the highest dialogue count, and after the difference between the first few races and the remaining is so huge that a lot of the races aren't visible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since I had already made this, I thought of sharing it here, in case someone is interested in the image or its code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Plans
&lt;/h3&gt;

&lt;p&gt;I will look into making custom scripts (if someone already has them, do share it with me) to extract other interesting data from Skyrim and see what I can do with them.&lt;/p&gt;

&lt;h3&gt;
  
  
  References:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/haideralipunjabi/skyrim-dialogueanalysis"&gt;Link to Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nltk.org"&gt;Natural Language Toolkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://amueller.github.io/word_cloud/"&gt;wordcloud&lt;/a&gt; package&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nexusmods.com/skyrim/mods/68054"&gt;Skyrim Logo Vector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nexusmods.com/skyrimspecialedition/mods/386"&gt;Sovngarde&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://tes5edit.github.io/"&gt;xEdit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.haideralipunjabi.com/posts/harry-potter-books-fanfiction-an-analysis-of-words/"&gt;Harry Potter Books &amp;amp; Fanfiction - An Analysis of Words&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>showdev</category>
      <category>visualisation</category>
      <category>graphic</category>
      <category>python</category>
    </item>
    <item>
      <title>Harry Potter Books &amp; Fanfiction - An Analysis of Words</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Mon, 01 Jun 2020 16:07:58 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/harry-potter-books-fanfiction-an-analysis-of-words-1hi7</link>
      <guid>https://dev.to/haideralipunjabi/harry-potter-books-fanfiction-an-analysis-of-words-1hi7</guid>
      <description>&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;On 28th May, &lt;a href="https://www.reddit.com/r/dataisbeautiful/comments/gs4me1/oc_word_cloud_comparison_between_user_comments_on/"&gt;a post on r/dataisbeautiful&lt;/a&gt; inspired me to learn how to make Word Clouds myself. Being a huge Harry Potter fan, the data I was going to use was obvious. Using the Books seemed too simple so I decided to scrape 250 stories from &lt;a href="http://fanfiction.net/"&gt;Fanfiction.net&lt;/a&gt; , and make a Word Cloud from that data. I posted &lt;a href="https://www.reddit.com/r/dataisbeautiful/comments/gtxzx8/oc_frequently_occurring_words_in_top_250_harry/"&gt;my first attempt on r/dataisbeautiful&lt;/a&gt;, and based on the feedback I received, I decided to write this blog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/haideralipunjabi/harrypotter-analysis"&gt;The entire source code (except the data files &amp;amp; output files) is available here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 1
&lt;/h3&gt;

&lt;p&gt;There are many approaches I could have taken to prepare the data. I decided to download the stories first and then do the processing on the local files due to my slow &amp;amp; unreliable internet.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V76ZTM6L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/out_final1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V76ZTM6L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/out_final1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Scraping Fanfiction
&lt;/h4&gt;

&lt;p&gt;I used simple Python + BeautifulSoup combination to scrape the stories form  &lt;a href="http://fanfiction.net/"&gt;Fanfiction.net&lt;/a&gt;. I sorted the stories based on their Favorite Count, and filtered them to stories having more than 100k words. (&lt;a href="https://www.fanfiction.net/book/Harry-Potter/?&amp;amp;srt=4&amp;amp;r=10&amp;amp;len=100&amp;amp;p=1"&gt;Link to the URL&lt;/a&gt;). I scraped first 10 pages, (each page has 25 stories) resulting in 250 stories. It took me a total of 10 hours (7 on one day, and 3 on the next) to scrape all the stories.&lt;/p&gt;

&lt;h4&gt;
  
  
  Processing the Data
&lt;/h4&gt;

&lt;p&gt;Taking hints from the &lt;a href="https://www.reddit.com/r/dataisbeautiful/comments/gs4me1/oc_word_cloud_comparison_between_user_comments_on/"&gt;original post&lt;/a&gt;, I used nltk to tokenize the stories, and removed the common words from the nltk English Stopwords Corpus. This was my first attempt at doing anything like this, and the process was taking 3-4 minutes per story initially. After some optimization, I was able to reduce the time to 1-2 minutes per story. I talked to a friend about the problem, and he suggested me to try multiprocessing. After adding multiprocessing, I had the idea of distributing the load over two CPUs (my laptop and a Raspberry Pi 4B). I copied the script and 25% of the stories over to the Pi and started the job.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Additional Tip:&lt;/em&gt; &lt;a href="https://www.geeksforgeeks.org/screen-command-in-linux-with-examples/"&gt;&lt;em&gt;screen&lt;/em&gt;&lt;/a&gt; &lt;em&gt;is a good utility to do long jobs over SSH&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It took me an hour to the processing. I didn't want to do the processing again if I needed to remove some more words so I decided to store the word frequency data into json files. (Really helpful in future)&lt;/p&gt;

&lt;h4&gt;
  
  
  Making the Word Cloud
&lt;/h4&gt;

&lt;p&gt;I took a look at &lt;a href="https://github.com/amueller/word_cloud"&gt;wordcloud Python Package&lt;/a&gt; and copied the code from its examples to generate the word cloud.&lt;/p&gt;

&lt;p&gt;To make the mask image, I downloaded some images from the Internet and used Inkscape to fix them.&lt;/p&gt;

&lt;h4&gt;
  
  
  Feedback
&lt;/h4&gt;

&lt;p&gt;After posting the first attempt over at Reddit &amp;amp; Twitter, I received a lot of feedback. Common among them were the queries about why is Daphne more frequent and why is Ron less frequent (I will answer both later), suggestions to remove more words to focus it more on Harry Potter related words, and to show some other visualisations, especially ones comparing the books and fanfiction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 2
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Finding more stopwords
&lt;/h4&gt;

&lt;p&gt;In my first attempt, I used the nltk English stopwords corpus, which is just 179 words. I searched for a bigger list and ended up using a customised 20,000 most common words list from &lt;a href="https://github.com/first20hours/google-10000-english"&gt;google-10000-english repository&lt;/a&gt;. What were the customisations? I had to remove some words (like magic, magical, wand, wards, vampire, etc) and some names (Harry, Ron, Fred, Arthur, etc) from the 20k list so that they aren't removed from my analysis. Storing the results of the processing from my first attempt into json files saved me from spending another hour of processing. I just removed the necessary keys from each data file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Harry Potter Canon Books
&lt;/h4&gt;

&lt;p&gt;I also downloaded the text versions of the 7 books from somewhere on the Internet, sanitised them a bit, and applied the same process as the fanfiction stories to generate their data. Using that data, I was able to compare the occurrence of some words in fanfiction vs canon. Since I had the data and the code, I decided to make their corresponding word clouds as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visualisations from Attempt 2
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Frequently Occurring Words in Top 250 Fanfictions
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jNuH33c4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/fics.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jNuH33c4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/fics.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Average Frequency of Occurrence of Words per book or story
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Top 20 Most Occurring Words in Fanfiction Stories&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BZxd75RK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/graph1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BZxd75RK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/graph1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Top 20 Most Occurring Words in Harry Potter Books (excluding Top 20 from Fanfiction Stories)&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3pRBcxyv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/graph2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3pRBcxyv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/graph2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Code to generate the above visualisations
&lt;/h4&gt;

&lt;h3&gt;
  
  
  Important Results
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Who is Daphne? Why is she so popular in Fanfiction?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Hermione’s name was called. Trembling, she left the chamber with Anthony Goldstein, Gregory Goyle, and &lt;strong&gt;Daphne Greengrass&lt;/strong&gt;. Students who had already been tested did not return afterward, so Harry and Ron had no idea how Hermione had done.&lt;/p&gt;

&lt;p&gt;-&amp;gt; Harry Potter and the Order of the Phoenix&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Daphne Greengrass is an almost non-entity in canon, and a blank slate for fanfiction writers. In canon &amp;amp; most fanfictions, she is the sister of Astoria Greengrass (another almost non-entity) who becomes the wife of Draco Malfoy. In fanfictions, she is usually a Slytherin due to her ambitions &amp;amp; cunningness &amp;amp; not because of being a Pureblood Supermasict. Her family is depicted as Light or Grey, and support "Lord Potter". She is a popular pairing in Independent Harry stories.&lt;/p&gt;

&lt;p&gt;Her being a blank slate character-wise is a boon for writers who want to write an OC without explicitly mentioning it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=4mSD-GAmz1I"&gt;Video explaining Daphne Greengrass and her popularity&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What happened to Ron?
&lt;/h4&gt;

&lt;p&gt;Ron is an almost opposite of Daphne. JKR wrote Ron in such a beautiful manner that many fanfiction writers are unable to write a good Ron. In canon, Ron is flawed but is also very funny, brave and loyal to his friends. In fanfictions, especially where Harry is very different to canon (Independent, Super-Powered, Lord Potter, etc), Harry usually ignores Ron (if diverging before Hogwarts) or the author does a lot of Ron bashing to justify Harry breaking up their friendship.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus Visualisations
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The 7 Canon Books:
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Philosopher's Stone&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K28bXFfP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K28bXFfP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book1.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
   I tried to use an image of 9 3/4 . The word Quirrell and Griphook are frequent in this book and will lose their frequency in future books.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chamber of Secrets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GayMGft6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GayMGft6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book2.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
   I used an image of Dobby the Free Elf. You will words like Dobby, Lockhart (I hate that guy), Polyjuice, Parseltongue, Mandrakes, Mudbloods making an appearance in this book.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prisoner of Azkaban&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QPMObgZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QPMObgZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book3.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
   I used an image of Prongs (James' Marauder Nickname, and Animagus form. Harry's Patronus) for this book. Words like Lupin, Sirius, Pettigrew, dementors, Crookshanks, Patronus start appearing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goblet of Fire&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zl1BBAUo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zl1BBAUo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book4.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I tried to use an Image of the Triwizard Trophy. Words like Cedric, Beauxbatons, Crouch,  Durmstrang start appearing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order of the Phoenix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uhuyabvE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uhuyabvE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book5.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tried to use an image of a Phoenix. Umbridge is very popular in this book.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Half Blood Prince&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4cnhHHK2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4cnhHHK2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book6.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used an image of the Half Blood Prince for this book. Apart from the usual, Slughorn is the most common word in this book.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deathly Hallows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ftwyWKSe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ftwyWKSe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.haideralipunjabi.com/uploads/book7.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used an image of the Deathly Hallows for this. You will see "wand" becomes very used due to "Elder wand". Hallows, Cloak, Wandmaker appear. Also, Griphook is back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Plans
&lt;/h3&gt;

&lt;p&gt;I am planning to scrape AO3 in the future to do some more analysis. I might also create some other Word Clouds from other popular books.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href=""&gt;wordcloud Python Package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nltk.org/"&gt;nltk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/HPfanfiction"&gt;r/HPFanfiction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4Rp5bdBBEeM"&gt;Snape, Snape, Severus Snape - Music Video&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://blog.haideralipunjabi.com/posts/harry-potter-books-fanfiction-an-analysis-of-words/"&gt;Read this on my blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>visualisations</category>
      <category>data</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Receive Netlify Notification on Telegram</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Sun, 17 May 2020 12:44:47 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/receive-netlify-notification-on-telegram-2d77</link>
      <guid>https://dev.to/haideralipunjabi/receive-netlify-notification-on-telegram-2d77</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://netlify.com"&gt;Netlify&lt;/a&gt; is one of the worlds largest hosting providers for JAMStack Websites. All of my websites are hosted on it. Some of them use manual deploys as &lt;a href="https://blog.haideralipunjabi.com/posts/using-ci-to-update-static-websites/"&gt;they are deployed via CI&lt;/a&gt;. I have disabled auto-publishing for some of the websites because I like to preview them before publishing.  Since a CI is used during deploy, the time taken for the website to deploy on Netlify is random. I wanted to be notified whenever a deploy was ready to be published and I tried many different techniques for it. Initially, I used &lt;a href="https://docs.netlify.com/site-deploys/notifications/"&gt;Outgoing Notifications&lt;/a&gt; with &lt;a href="https://ifttt.com"&gt;IFTTT&lt;/a&gt;. With that, I was able to receive a notification on my mobile whenever the deploy was ready. It worked fine, but I wanted to receive a notification on my PC, not my mobile, which will open the deploys page of the website when I click on it.&lt;/p&gt;

&lt;p&gt;For a project of mine, I learnt how to make Telegram Bots. With that, I was able to create another bot that messaged me whenever a website was ready to deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Follow &lt;a href="https://core.telegram.org/bots/#3-how-do-i-create-a-bot"&gt;this tutorial to create the Telegram Bot&lt;/a&gt;. Note the API Token you receive. In the following steps, it will be referred to by &lt;strong&gt;$token&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The bot will need to know your chat id. To find that, we need to set up a temporary webhook. &lt;a href="https://webhook.site"&gt;Webhook.site&lt;/a&gt; is a great service to test webhooks online. Go to &lt;a href="https://webhook.site"&gt;Webhook.site&lt;/a&gt; and copy the unique URL. (Referred by &lt;strong&gt;$unique_url&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Open the following URL in your browser
&lt;a href="https://api.telegram.org/bot%24token/setWebhook?url=%24unique_url"&gt;https://api.telegram.org/bot$token/setWebhook?url=$unique_url&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now,  send a message to your bot from your Telegram account. In the &lt;a href="https://webhook.site"&gt;Webhook.site&lt;/a&gt; window, you will receive a new request which looks like:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"update_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;213123213&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;987654321&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"is_bot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"first_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"last_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"johndoe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"language_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nl"&gt;"chat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;987654321&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;-----&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;chat_id&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"first_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"last_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"johndoe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"private"&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1589716177&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jd"&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Note the chat["id"] from the previous step. It is the chat id you need. (Referred by &lt;strong&gt;$chatid&lt;/strong&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can delete the webhook now &lt;br&gt;
 &lt;a href="https://api.telegram.org/bot%24token/deleteWebhook"&gt;https://api.telegram.org/bot$token/deleteWebhook&lt;/a&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now, go to your website's settings on Netlify. In Build &amp;amp; Deploy &amp;gt; Deploy Notifications, add a new Outgoing Webhook.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the required event (&lt;a href="https://docs.netlify.com/site-deploys/notifications/"&gt;Details of all events&lt;/a&gt;), and the following in URL field.&lt;br&gt;
&lt;a href="https://api.telegram.org/bot%24token/sendMessage?chat_id=%24chatid&amp;amp;text=%24message"&gt;https://api.telegram.org/bot$token/sendMessage?chat_id=$chatid&amp;amp;text=$message&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;$message&lt;/strong&gt; is the message you want to receive. I send the name of the website, event and the link to the website's deploys page.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The set up is done. Now, you will receive a Telegram Message whenever a deploy is ready.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.netlify.com/site-deploys/notifications/"&gt;Netlify Deploy Notifications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://core.telegram.org/bots/"&gt;Telegram Bots&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://blog.haideralipunjabi.com/posts/receive-netlify-notification-on-telegram/"&gt;Read this on my blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>netlify</category>
      <category>telegram</category>
      <category>webhook</category>
    </item>
    <item>
      <title>Using CI to update Static Websites.</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Tue, 28 Apr 2020 09:41:55 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/using-ci-to-update-static-websites-3ikh</link>
      <guid>https://dev.to/haideralipunjabi/using-ci-to-update-static-websites-3ikh</guid>
      <description>&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;Sometimes, our websites require data that isn't updated in realtime but updated after every few hours, days or maybe even weeks. In such cases, requesting the data from some backend or API on every request will be a waste of resources. Client-side caching of data might help a bit but isn't the best solution. Also, you might end up reaching your API Requests limit.&lt;/p&gt;

&lt;p&gt;In July 2019, I made a project - &lt;a href="https://hpffrec.hackesta.org"&gt;HPFanfiction Recommender&lt;/a&gt; (&lt;a href="https://blog.haideralipunjabi.com/posts/making-hpfanfiction-recommender/"&gt;Read about it in a previous blog post&lt;/a&gt;), where my backend generates the required data and then triggers a &lt;a href="https://travis-ci.org/"&gt;Travis CI&lt;/a&gt; build which downloads the data and stores it in the repository. This method had a serious drawback. It used to commit 2 - 3 MB of files every 4 hours. As a result, when I tried to Pull the repo in February 2020, the size was huge. As a temporary fix, I added --amend to the Travis Commit command but I am not sure how well it worked.&lt;/p&gt;

&lt;p&gt;At the end of March 2020, I started a project - &lt;a href="//covidkashmir.org"&gt;covidkashmir.org&lt;/a&gt;, which also needed to make some changes to repo during CI but I didn't want those changes committed back to the repo, only deploy them to &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;. My initial idea was to download &lt;a href="https://docs.netlify.com/cli/get-started/"&gt;netlify-cli&lt;/a&gt; during the CI build and then use it to deploy the changes without committing them back. To my surprise, I found out about &lt;a href="https://docs.travis-ci.com/user/deployment-v2"&gt;Travis CI DPL v2&lt;/a&gt; and its &lt;a href="https://docs.travis-ci.com/user/deployment-v2/providers/netlify/"&gt;Netlify Drop Deployment&lt;/a&gt;. In a future blog, I will write the details about &lt;a href="//covidkashmir.org"&gt;covidkashmir.org&lt;/a&gt; 's requirements. In this blog, I will write about how to set up &lt;a href="https://docs.travis-ci.com/user/deployment-v2"&gt;Travis CI DPL v2&lt;/a&gt; and its &lt;a href="https://docs.travis-ci.com/user/deployment-v2/providers/netlify/"&gt;Netlify Drop Deployment&lt;/a&gt; for any static site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up &lt;a href="https://docs.travis-ci.com/user/deployment-v2"&gt;Travis CI DPL v2&lt;/a&gt; and its &lt;a href="https://docs.travis-ci.com/user/deployment-v2/providers/netlify/"&gt;Netlify Drop Deployment&lt;/a&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  In your code/repository
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;In your repository, add a &lt;code&gt;.travis.yml&lt;/code&gt; file which will act as the configuration file for Travis.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;.travis.yml&lt;/code&gt; as per your needs. (&lt;a href="https://docs.travis-ci.com/"&gt;Travis CI Documentation&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;In your &lt;code&gt;.travis.yml&lt;/code&gt; add the following lines to set up &lt;a href="https://docs.travis-ci.com/user/deployment-v2"&gt;Travis CI DPL v2&lt;/a&gt; and its &lt;a href="https://docs.travis-ci.com/user/deployment-v2/providers/netlify/"&gt;Netlify Drop Deployment&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;       &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;netlify&lt;/span&gt;
         &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$NETLIFY_SITE&lt;/span&gt;    &lt;span class="c1"&gt;# Add in Travis CI Settings&lt;/span&gt;
         &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$NETLIFY_AUTH&lt;/span&gt;    &lt;span class="c1"&gt;# Add in Travis CI Settings&lt;/span&gt;
         &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
         &lt;span class="na"&gt;edge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# opt in to dpl v2&lt;/span&gt;
         &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;     &lt;span class="c1"&gt;# for deploying to production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  On Netlify
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;For New Sites&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Deploy your site using &lt;a href="https://docs.netlify.com/cli/get-started/"&gt;netlify-cli&lt;/a&gt; or &lt;a href="https://app.netlify.com/drop"&gt;Neltify Drop&lt;/a&gt;, and don't link your site to Git&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For existing Sites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your site is already linked to a git repository, &lt;a href="https://community.netlify.com/c/Netlify-support/48"&gt;apply here to unlink it&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need an unlinked site because it will deploy twice (once without the changes from Travis) otherwise.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Sites Settings &amp;gt; General and note the API ID. We will need it later.&lt;/li&gt;
&lt;li&gt;Go to User Settings &amp;gt; Applications and create a new Personal Access Token. Note it as well.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;On Travis CI&lt;/strong&gt;
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Link Travis CI to your Repository&lt;/li&gt;
&lt;li&gt;Go to the repository's settings and add the following environment variables:

&lt;ol&gt;
&lt;li&gt;NETLIFY_SITE : API ID from Step 3 of the Previous Section&lt;/li&gt;
&lt;li&gt;NETLIFY_AUTH: Personal Access Token from Step 4 of the Previous Section&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, when you push your code, it will trigger the build on Travis, generate your data and deploy to Netlify.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://blog.haideralipunjabi.com/posts/using-ci-to-update-static-websites/"&gt;Read this on my blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>ci</category>
      <category>html</category>
      <category>netlify</category>
    </item>
    <item>
      <title>Adding View Count to your JAMstack Website with JavaScript and Google Analytics</title>
      <dc:creator>Haider Ali Punjabi</dc:creator>
      <pubDate>Fri, 14 Feb 2020 13:36:39 +0000</pubDate>
      <link>https://dev.to/haideralipunjabi/adding-view-count-to-your-jamstack-website-with-javascript-and-google-analytics-109l</link>
      <guid>https://dev.to/haideralipunjabi/adding-view-count-to-your-jamstack-website-with-javascript-and-google-analytics-109l</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As &lt;a href="https://jamstack.org/"&gt;JAMStack&lt;/a&gt; is becoming more and more popular,  many people (especially developers) are shifting their websites (and blogs) to &lt;a href="https://jamstack.org/"&gt;JAMStack&lt;/a&gt; and use some &lt;a href="https://www.staticgen.com/"&gt;Static Site Generator&lt;/a&gt; (Jekyll, Hugo, Nuxt, Next, Gatsby, etc).&lt;/p&gt;

&lt;p&gt;My blog is based on &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; and hosted on &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;. My previous post got more views than I expected and I spent the next two days checking my &lt;a href="https://analytics.google.com/analytics/web/"&gt;Analytics App&lt;/a&gt; to keep track of the views. Then it occurred to me that I should try and add a view counter to my blog. I post all my articles simultaneously to &lt;a href="https://dev.to/"&gt;dev.to&lt;/a&gt; and &lt;a href="https://medium.com/"&gt;Medium&lt;/a&gt;, and both of them tell me how many views my article has got.&lt;/p&gt;

&lt;p&gt;It took me around 4 hours to add the views counter, but it won’t take you as much time because I experimented with lots of stuff.&lt;/p&gt;

&lt;p&gt;My only goal was to have a counter with good accuracy which updates the values without rebuilding the entire site.&lt;/p&gt;

&lt;p&gt;After searching for a while, I concluded that using &lt;a href="https://analytics.google.com/analytics/web/"&gt;Google Analytics&lt;/a&gt; data instead of adding a new mechanism would be best.&lt;/p&gt;

&lt;p&gt;I spent around two hours messing with &lt;a href="https://developers.google.com/analytics/devguides/reporting/core/v3/reference"&gt;Google Core Reporting API v3&lt;/a&gt; because I wanted to avoid the  &lt;a href="https://developers.google.com/analytics/devguides/reporting/core/v4/"&gt;Google Analytics Reporting API v4’s&lt;/a&gt; API KEY requirement. When I was almost finishing up the changes, I read somewhere that v3 API would stop functioning this year.&lt;/p&gt;

&lt;p&gt;After wasting 2 hours on v3, I knew I had no choice other than using v4. While looking up stuff related to v4, I came across &lt;a href="https://developers.google.com/analytics/solutions/google-analytics-spreadsheet-add-on"&gt;Google Analytics Spreadsheet Add-on&lt;/a&gt;, which allows you to export &lt;a href="https://analytics.google.com/analytics/web/"&gt;Google Analytics&lt;/a&gt; data to a Google Spreadsheet. After a lot of experimenting, following multiple tutorials and numerous visits to &lt;a href="https://stackoverflow.com/"&gt;Stack Overflow&lt;/a&gt;, I was able to export my Analytics Data to  &lt;a href="https://docs.google.com/spreadsheets/u/0/"&gt;Google Sheets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then I had to figure out how to get this data to my website. I decided on using Hugo’s inbuilt functions and variables to achieve this (a very bad decision in hindsight). I spent another hour meddling through Hugo’s documentation to show the page view data on my website. After everything was done, and I was about to commit the changes, I decided to check the size of the data I was loading from &lt;a href="https://docs.google.com/spreadsheets/u/0/"&gt;Google Sheets&lt;/a&gt;. To my surprise and shock, I couldn’t find the entry for the CSV file in my browsers Network tab. Then it occurred to me that the data is being fetched during build, something which I wanted to avoid. Scraping all the changes, I decided to use JavaScript to make this work. Since my website doesn’t have jQuery, and I didn’t want to add it for such a small task, I wrote all the code in vanilla JavaScript (which I could have written better) and had it working finally.&lt;/p&gt;

&lt;p&gt;The following tutorial would only cover getting the data from &lt;a href="https://analytics.google.com/analytics/web/#/"&gt;Analytics&lt;/a&gt; to &lt;a href="https://docs.google.com/spreadsheets/u/0/"&gt;Google Sheets&lt;/a&gt; and fetching that data in vanilla JavaScript. I won’t show how to add that to your website as every website structure is different.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tutorial
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Part a) Setting Up Google Sheets + Analytics Add-On
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Follow &lt;a href="https://developers.google.com/analytics/solutions/google-analytics-spreadsheet-add-on"&gt;this guide&lt;/a&gt; to install the add-on to your Google Sheets.&lt;/li&gt;
&lt;li&gt;Select "Add-ons" &amp;gt; "Google Analytics" &amp;gt; "Create a New Report" from the menu bar.&lt;/li&gt;
&lt;li&gt;Name it something, and select your Analytics View.&lt;/li&gt;
&lt;li&gt;Under configuration options, choose Pageviews (ga:pageviews) as your metric.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Dimensions will depend on the structure of your website. Page Path Level 1 will only return the data for Pages and Folders in the root of your website. Page Path Level 2 will return data for Pages and Folders in some folder in your root. Use the following table to decide your dimension.&lt;/p&gt;



&lt;p&gt;My blog posts are present in the posts/  directory, so I decided to use both  Page Path Level 1 &amp;amp;  Page Path Level 2 dimensions.  By using both 1 &amp;amp; 2, I get both posts/ &amp;amp; my blog post URL.&lt;/p&gt;

&lt;p&gt;If you use the Page dimension instead of Page Path n, your data will also include Query Strings and the data would be divided for every unique URL.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under Segments, I selected All Users. but you could also use New Users to get unique views only.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T8kcra_4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ian03swjalz7ryhheqz8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T8kcra_4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ian03swjalz7ryhheqz8.png" alt="Create Report Final"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the Report and you will see your Configuration Sheet will be generated.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uQ8zVVeJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l2u2a2sv0v5dy8jhu6ta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uQ8zVVeJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l2u2a2sv0v5dy8jhu6ta.png" alt="Report Configuration"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change the Start Date to a date before your first blog post (in YYYY/MM/DD format), and change the End Date to 'today'&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You should remove the 1000 limit if you want to get the data for more than 1000 posts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create another empty sheet which will act as output. Share this sheet with Edit Permission enabled. Copy the Share Link.&lt;/li&gt;
&lt;li&gt;Paste the copied Share Link into the Configuration Sheet in the Spreadsheet URL Row.&lt;/li&gt;
&lt;li&gt;Select "Add-ons" &amp;gt; "Google Analytics" &amp;gt; "Run reports" from the menu bar.&lt;/li&gt;
&lt;li&gt;It will show you a Report Status Popup, which should tell you that your report completed successfully.&lt;/li&gt;
&lt;li&gt;Select "Add-ons" &amp;gt; "Google Analytics" &amp;gt; "Schedule Reports" from the menu bar.&lt;/li&gt;
&lt;li&gt;Select "Enable Reports to run automatically" and run it "every hour", and save it.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t51pDJtZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/j4s8ltntucka0642j93v.png" alt="Output Report"&gt;
&lt;/li&gt;
&lt;li&gt;Go back to your output sheet and go to "File" &amp;gt; "Publish to Web"&lt;/li&gt;
&lt;li&gt;Under "Published content &amp;amp; settings", select you Sheet Name and check "Automatically republish when changes are made". Then click "Start Publishing".&lt;/li&gt;
&lt;li&gt;Choose "Comma-separated values (.csv) as output format and copy the given link"&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Part b) Getting the data on your blog
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;On the page where you want to show the view data, add a JavaScript file (or edit an existing JavaScript File for that page).&lt;/li&gt;
&lt;li&gt;Use Fetch API to get the CSV data and parse it using CSVToArray function from this &lt;a href="https://stackoverflow.com/questions/1293147/javascript-code-to-parse-csv-data/1293163#1293163"&gt;StackOverflow Answer&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;URL which you copied in Step 18. of part A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;     
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CSVToArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="nx"&gt;viewMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; 
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
         &lt;span class="nx"&gt;viewMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;     

    &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;})&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;You might need to adjust the code according to your needs&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You will have a dictionary with your Post URLs as Key and their Views as values.&lt;/li&gt;
&lt;li&gt;Use them according to your website structure. (You can look at my &lt;a href="https://github.com/haideralipunjabi/blog-haideralipunjabi"&gt;blog's code&lt;/a&gt; if you need more help)&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/analytics/solutions/google-analytics-spreadsheet-add-on"&gt;Google Analytics Spreadsheet Add-on&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.staticgen.com/"&gt;StaticGen - A List of Static Site Generators for JAMstack Sites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/analytics/devguides/reporting/core/v4/"&gt;Google Analytics Reporting API v4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Read This On
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.haideralipunjabi.com/posts/adding-view-count-to-your-jamstack-website/"&gt;Haider Ali Punjabi's Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/hackesta/adding-view-count-to-your-jamstack-website-with-javascript-and-google-analytics-6df837abab4d"&gt;Medium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>hugo</category>
      <category>jamstack</category>
      <category>web</category>
    </item>
  </channel>
</rss>
