Cowin vaccination slots notifier INDIA (without using the Cowin public API)

GUIDE TO SETUP NOTIFICATION FOR VACCINATION IN COWIN INDIA WITHOUT DELAY

This approach mimics any user perfectly. It doesn't have any drawbacks of the other approaches on the internet and it has zero delay factor.

Cowin is the portal for vaccination in India.

As of now, in India slots are very scarce and this has kicked off a frenzy in figuring out different ways to quickly book slots.
The slots on the Cowin website are pretty tough to get by.

This guide doesn't want to widen the gap of accessibility to the vaccine to the more tech-oriented and others. but could be used as a case study in developing this idea in other portals.

Most of the procedures laid by other techies have been revolving around the use of public Cowin API. But the Cowin public API described at https://apisetu.gov.in/public/api/cowin is slowed by 30 minutes. And the API calls are restricted to 100 per minute which actually defeats the whole point of the use case.

The approach, I have used doesn't deal with these APIs. I have used selenium driver which is a chrome test automation software to run the website automatically and check for slots.

This approach removes all the restrictions of API because it perfectly mimics how a user will interact with the website using any web browser.

it will log in automatically and constantly search for slots in a specified Pincode and start beeping if slots are available. you will just need to select that slots and fill captcha and submit.

Running without worrying about the geeky stuff

to run without knowing any geeky stuff, you just need to follow these basic steps:

  1. download APK and python program from https://github.com/eyanshu1997/cowin-api
  2. download selenium web driver from https://chromedriver.chromium.org/downloads. place it in the same folder as the python program
  3. Install all the dependencies using pip
  4. Install the android app on your phone. ( you need to provide permission to the app manually. I haven't written any facility to that asks it automatically)
  5. click show string on the app to get the string.
  6. If you want the email notification set up a new email with less app secure turned off and fill in the variables.
  7. If you don't want an email notification just comment line no 109.
  8. run the python program using three args: string from APK, phone no, pin code to search for.
  9. The program will run continuously and will start beeping whenever there is a slot available. no need to log in or anything else it saves that time. just click on that slot fill the captcha and your slot is booked.

The GEEKY STUFF!!

Imports required

import sys
from firebase import Firebase
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import NoSuchElementException
import datetime
import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import os
if len( sys.argv)<4:
print(“enter argument displayed on app as phone 2 no as 2 and pin as 3”)
exit()

setting up the mail for sending notifications on slots. (optional)

you need to set up a dummy email that will be used to send notifications. we need to enable a less secure app login for that to work. https://myaccount.google.com/lesssecureapps.

Enable this we will be using this account to send emails from. This is the part that deals with the email.

sender_email = 
password =
receiver_email=

fill these variables according to the account

def send_mail(mess):port = 465 
smtp_server = "smtp.gmail.com"
message = MIMEMultipart("alternative")
message["Subject"] = "covid vacine slots- chrome program"
message["From"] = sender_email
message["To"] = receiver_email
part1 = MIMEText(mess, "plain")
message.attach(part1)
context = ssl.create_default_context()
with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message.as_string())

Set up Firebase for the OTP

you need to set up a new app in the firebase console.
to add your python program to the firebase database add a web app in the Firebase console for the project. you can get config from there.
you need to copy that config and set the firebase config as a variable here.

this part of the code connects and defines the firebase database object

db=None
def firebase():
config={
"apiKey": "AIzaSyCSt9KRzomwUvi_vmuR3_M3VSQPuiwZbE8",
"authDomain": "cowin-e58d4.firebaseapp.com",
"databaseURL": "https://cowin-e58d4-default-rtdb.firebaseio.com",
"projectId": "cowin-e58d4",
"storageBucket": "cowin-e58d4.appspot.com",
"messagingSenderId": "668386027081",
"appId": "1:668386027081:web:30a32e9fd327a57db6c198"
}
firebase=Firebase(config)
db=firebase.database()

Setting up the chrome driver for interaction with the website.

download selenium web driver for chrome from https://chromedriver.chromium.org/downloads. we will use this binary in chrome to run the code.
we will, for now, assume that OTP will be automatically updated by our android app into a firebase database where we will be fetching that in our python program to input into the site.

the code of the python program is as follow:

driver = webdriver.Chrome('./chromedriver')
#driver.get("https://selfregistration.cowin.gov.in/")
def reset():
global driver
print("resetting ")
driver.close()
driver = webdriver.Chrome('./chromedriver')
main()

def find_center(pi):
#time.sleep(100)
pin=driver.find_element_by_id("mat-input-2")
pin.clear()
pin.send_keys(pi)
time.sleep(1)
but=driver.find_element_by_class_name("pin-search-btn")
but.click()
time.sleep(2)
try:
ch18=driver.find_element_by_id("c1")
driver.execute_script("arguments[0].click()",ch18)
time.sleep(2)
except Exception:
print("age buttn not found")
#logout()
reset()
try:
ls=driver.find_elements_by_class_name("slot-available-main")
#print(ls.text)
#print(l.text)
for l in ls:
#print(l.text)
#continue;
if (str(l.text)).strip()=="":
continue
print("center ")
x=str(l.text)
lines=x.split("\n")
i=0
for l in lines:
#print(str(i)+" "+l)
i+=1
if l.strip() != "Booked" and l.strip() != "NA" and l.strip() != "COVAXIN" and l.strip() != "Age 18+" :
print("checking "+l)
if int(l.strip())>4:
print("found "+ l)
i=180
while i>0:
n = os.fork()
n = os.fork()
if n>0:
os.system('play -nq -t alsa synth {} sine {}'.format(duration, freq))
i=i-1
time.sleep(1)
else:
send_mail(l)
exit()
except NoSuchElementException:
print("no slots")

def login():
global current
current=time.time()
try:
driver.get("https://selfregistration.cowin.gov.in/")
time.sleep(1)
except Exception:
print("net error")
reset()
return
curr=datetime.datetime.today()
cu=curr.strftime('%d-%m-%y %H:%M')
curr=datetime.datetime.strptime(cu,'%d-%m-%y %H:%M')
try:
ph=driver.find_element_by_id("mat-input-0")
ph.send_keys(sys.argv[2])
except Exception:
print("phone no input not found")
reset()
return
try:
su=driver.find_element_by_class_name("login-btn")
su.click()
time.sleep(3)
except Exception:
print("logn button not found")
reset()
return
otp=driver.find_element_by_class_name("otp-field")
res=db.child(sys.argv[1]).get()
print(res.val())
ot=res.val()
tim,otpmsg=ot.split("#")
dobj = datetime.datetime.strptime(tim, '%d-%m-%Y %H:%M:%S')
dob=dobj.strftime('%d-%m-%y %H:%M')
dobj=datetime.datetime.strptime(dob,'%d-%m-%y %H:%M')
print(dobj)
print(curr)
print(dobj<curr)
#if dobj<curr:
#print(cu)
st=time.time()
while dobj<curr :
time.sleep(1)
res=db.child(sys.argv[1]).get()
if time.time()-st<10:
print(res.val())
ot=res.val()
tim,otpmsg=ot.split("#")
dobj = datetime.datetime.strptime(tim, '%d-%m-%Y %H:%M:%S')
if time.time()-st>190:
try:
print("resending")
resend=driver.find_element_by_class_name("resend")
resend.click()
st=time.time()
except Exception:
print("not found going to main")
reset()
return
print(ot)
otpfrommsg=otpmsg.split()[6]
otpf=otpfrommsg[:-1]
print(otpf)
otp.send_keys(otpf)
sub=driver.find_element_by_class_name("button-solid")
sub.click()
time.sleep(3)

try:
sche=driver.find_element_by_class_name("m-lablename")
sche.click()
time.sleep(0.5)
except Exception:
print("schedule now button not found")
reset()
return



def logout():
print("logout")
logou=driver.find_element_by_class_name("logout-text")
logou.click()
time.sleep(3)

current=time.time()
def main():
login()
while(1):
print(str(datetime.datetime.now().strftime('%d-%m-%y %H:%M:%S')))
if time.time()-current>13*60:
logout()
login()
find_center(sys.argv[3])
firebase()
main()

you can check out the full program at https://github.com/eyanshu1997/cowin-api/blob/main/cowin_book.py

It takes in three arguments

  1. string displayed in the android app
  2. phone no
  3. Pincode to search from

Android app

You can directly use my app if you are using the firebase configs showed above by installing the APK from https://github.com/eyanshu1997/cowin-api

You might need to provide permission for SMS to the app.
In case you want you can use your own app and define the things you like.

the android studio project is located here: https://github.com/eyanshu1997/Cowin_latest

The android app has three classes.

Cont

This is actually the activity I have been using to save the variables to share between the different activities.

package com.eyanshu.cowin_latest;

import android.app.Application;
import android.content.Context;

public class cont extends Application {
private static Context mContext;
public static String deviceId;
public static Context getContext() {
return mContext;
}

public static void setContext(Context mCont) {
mContext = mCont;
}
}

Main activity

This is the main activity of the program.

package com.eyanshu.cowin_latest;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.view.View;

import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cont.setContext(getApplicationContext());
//String permission = android.Manifest.permission.RECEIVE_SMS;
//int res = getApplicationContext().checkCallingOrSelfPermission(permission);
//TextView textView=findViewById(R.id.text_id);
cont.deviceId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
//cont.deviceId="heelo";
//textView.setText(cont.deviceId);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}
}

SMS receiver

This activity listens to incoming messages and uploads them to the firebase for access to the python program.

package com.eyanshu.cowin_latest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.widget.Toast;

import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class SmsReceiver extends BroadcastReceiver{

private SharedPreferences preferences;

@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub

if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")){
// Toast.makeText(cont.getContext(),"messgae recieved",Toast.LENGTH_LONG).show();
Bundle bundle = intent.getExtras(); //---get the SMS message passed in---
SmsMessage[] msgs = null;
String msg_from;
if (bundle != null){
//---retrieve the SMS message received---
try{
Object[] pdus = (Object[]) bundle.get("pdus");
msgs = new SmsMessage[pdus.length];
for(int i=0; i<msgs.length; i++){
msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
msg_from = msgs[i].getOriginatingAddress();
String msgBody = msgs[i].getMessageBody();
SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
String date = dateFormat.format(Calendar.getInstance().getTime());// Write a message to the database
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference(cont.deviceId);

myRef.setValue(date+"#"+msgBody,new DatabaseReference.CompletionListener() {
public void onComplete(DatabaseError error, DatabaseReference ref) {
Toast.makeText(cont.getContext(),"Value:"+date+"#"+msgBody,Toast.LENGTH_SHORT).show(); }});
}

}catch(Exception e){
// Log.d("Exception caught",e.getMessage());
}
}
}
}
}

First fragment

I have used this activity to display the string that will be the argument to the python program to recognize the device that saved the OTP

package com.eyanshu.cowin_latest;

import android.os.Bundle;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;

public class FirstFragment extends Fragment {
TextView textView;

@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState
) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false);
}

public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
textView=view.findViewById(R.id.text_id);
view.findViewById(R.id.button_first).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
textView.setText(cont.deviceId);
}
});
}
}

Android manifest

we need several permissions for our app we define them here

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eyanshu.cowin_latest">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".SmsReceiver">
<intent-filter android:priority="2147483647">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_SMS"/>

</manifest>

Conclusion

There might be a lot of ways we might update the app we could edit it to provide notification whenever the slots are available. we could send the captcha and send the solution from the phone itself.

There is a lot of scope for changes it depends a lot on the user requirement. But I was successfully able to book a slot using this in my place where only 100 slots open for the whole state every day.

Hope it helps people!