├── .gitignore ├── corona_bot.py ├── corona_india_data.json ├── readme.md └── slack_client.py /.gitignore: -------------------------------------------------------------------------------- 1 | auth.py 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | 140 | # Cython debug symbols 141 | cython_debug/ 142 | 143 | # static files generated from Django application using `collectstatic` 144 | media 145 | static 146 | -------------------------------------------------------------------------------- /corona_bot.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import requests 4 | import argparse 5 | import logging 6 | from bs4 import BeautifulSoup 7 | from tabulate import tabulate 8 | from slack_client import slacker 9 | 10 | FORMAT = '[%(asctime)-15s] %(message)s' 11 | logging.basicConfig(format=FORMAT, level=logging.DEBUG, filename='bot.log', filemode='a') 12 | 13 | URL = 'https://www.mohfw.gov.in/' 14 | SHORT_HEADERS = ['Sno', 'State','In','Fr','Cd','Dt'] 15 | FILE_NAME = 'corona_india_data.json' 16 | extract_contents = lambda row: [x.text.replace('\n', '') for x in row] 17 | 18 | 19 | def save(x): 20 | with open(FILE_NAME, 'w') as f: 21 | json.dump(x, f) 22 | 23 | 24 | def load(): 25 | res = {} 26 | with open(FILE_NAME, 'r') as f: 27 | res = json.load(f) 28 | return res 29 | 30 | 31 | if __name__ == '__main__': 32 | 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument('--states', default=',') 35 | args = parser.parse_args() 36 | interested_states = args.states.split(',') 37 | 38 | current_time = datetime.datetime.now().strftime('%d/%m/%Y %H:%M') 39 | info = [] 40 | 41 | try: 42 | response = requests.get(URL).content 43 | soup = BeautifulSoup(response, 'html.parser') 44 | header = extract_contents(soup.tr.find_all('th')) 45 | 46 | stats = [] 47 | all_rows = soup.find_all('tr') 48 | for row in all_rows: 49 | stat = extract_contents(row.find_all('td')) 50 | if stat: 51 | if len(stat) == 5: 52 | # last row 53 | stat = ['', *stat] 54 | stats.append(stat) 55 | elif any([s.lower() in stat[1].lower() for s in interested_states]): 56 | stats.append(stat) 57 | 58 | past_data = load() 59 | cur_data = {x[1]: {current_time: x[2:]} for x in stats} 60 | 61 | changed = False 62 | 63 | for state in cur_data: 64 | if state not in past_data: 65 | # new state has emerged 66 | info.append(f'NEW_STATE {state} got corona virus: {cur_data[state][current_time]}') 67 | past_data[state] = {} 68 | changed = True 69 | else: 70 | past = past_data[state]['latest'] 71 | cur = cur_data[state][current_time] 72 | if past != cur: 73 | changed = True 74 | info.append(f'Change for {state}: {past}->{cur}') 75 | 76 | events_info = '' 77 | for event in info: 78 | logging.warning(event) 79 | events_info += '\n - ' + event.replace("'", "") 80 | 81 | if changed: 82 | # override the latest one now 83 | for state in cur_data: 84 | past_data[state]['latest'] = cur_data[state][current_time] 85 | past_data[state][current_time] = cur_data[state][current_time] 86 | save(past_data) 87 | 88 | table = tabulate(stats, headers=SHORT_HEADERS, tablefmt='psql') 89 | slack_text = f'Please find CoronaVirus Summary for India below:\n{events_info}\n```{table}```' 90 | slacker()(slack_text) 91 | except Exception as e: 92 | logging.exception('oops, corono script failed.') 93 | slacker()(f'Exception occured: [{e}]') 94 | -------------------------------------------------------------------------------- /corona_india_data.json: -------------------------------------------------------------------------------- 1 | {"Andhra Pradesh": {"19/03/2020 06:14": ["1", "0", "0", "0"], "latest": ["1", "0", "0", "0"]}, "Delhi": {"19/03/2020 06:14": ["11", "1", "2", "1"], "latest": ["11", "1", "2", "1"]}, "Haryana": {"19/03/2020 06:14": ["3", "14", "0", "0"], "latest": ["3", "14", "0", "0"]}, "Karnataka": {"19/03/2020 06:14": ["14", "0", "0", "1"], "latest": ["14", "0", "0", "1"]}, "Kerala": {"19/03/2020 06:14": ["25", "2", "3", "0"], "latest": ["25", "2", "3", "0"]}, "Maharashtra": {"19/03/2020 06:14": ["42", "3", "0", "1"], "latest": ["42", "3", "0", "1"]}, "Odisha": {"19/03/2020 06:14": ["1", "0", "0", "0"], "latest": ["1", "0", "0", "0"]}, "Pondicherry": {"19/03/2020 06:14": ["1", "0", "0", "0"], "latest": ["1", "0", "0", "0"]}, "Punjab": {"19/03/2020 06:14": ["1", "0", "0", "0"], "latest": ["1", "0", "0", "0"]}, "Rajasthan": {"19/03/2020 06:14": ["5", "2", "3", "0"], "latest": ["5", "2", "3", "0"]}, "Tamil Nadu": {"19/03/2020 06:14": ["2", "0", "1", "0"], "latest": ["2", "0", "1", "0"]}, "Telengana": {"19/03/2020 06:14": ["4", "2", "1", "0"], "latest": ["4", "2", "1", "0"]}, "Union Territory of Chandigarh": {"19/03/2020 06:14": ["1", "0", "0", "0"], "latest": ["1", "0", "0", "0"]}, "Union Territory of Jammu and Kashmir": {"19/03/2020 06:14": ["4", "0", "0", "0"], "latest": ["4", "0", "0", "0"]}, "Union Territory of Ladakh": {"19/03/2020 06:14": ["8", "0", "0", "0"], "latest": ["8", "0", "0", "0"]}, "Uttar Pradesh": {"19/03/2020 06:14": ["16", "1", "5", "0"], "latest": ["16", "1", "5", "0"]}, "Uttarakhand": {"19/03/2020 06:14": ["1", "0", "0", "0"], "latest": ["1", "0", "0", "0"]}, "West Bengal": {"19/03/2020 06:14": ["1", "0", "0", "0"], "latest": ["1", "0", "0", "0"]}, "Total number of confirmed cases in India": {"19/03/2020 06:14": ["141", "25", "15", "3"], "latest": ["141", "25", "15", "3"]}} -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # CoronaVirus Bot for India (Covid19) 2 | 3 | ![Corona SlackBot by rachitiitr](https://i.ibb.co/8BmwQDs/corona-slack-bot.png) 4 | 5 | ## YouTube Demo 6 | - Check the demo for how this works on YouTube [here](http://bit.ly/2UpI7ga). 7 | 8 | ## Features 9 | - Sit back and relax - the coronavirus updates will come to you. 10 | - Get Slack notifications (picture below) 11 | - New Corona Virus cases happening in India 12 | - How many Indian nationals have Corona Virus per State? 13 | - How many deaths happened per State? 14 | - The new States entering the corona zone like Chattisgarh 15 | - Too many updates? Subscribe only to the states that you want. 16 | - Its reliable - the source of data is official Government site ([here](https://mohfw.gov.in/)) 17 | - Its ROBUST! 18 | - What if script fails? What if the Govt website changes format? 19 | - You get Slack notifications about the exceptions too. 20 | - You have log files (check `bot.log`) too, to evaluate what went wrong 21 | - Don't like a feature? Change it! Raise a Pull Request too 😉 22 | 23 | 24 | ## Installation 25 | - You need Python 26 | - You need a Slack account + Slack Webhook to send slack notifications to your account 27 | - Install dependencies by running 28 | ```bash 29 | pip install tabulate 30 | pip install requests 31 | pip install beautifulsoup4 32 | ``` 33 | - Clone this repo and create auth.py 34 | ```bash 35 | git clone https://github.com/rachitiitr/coronovirus-bot-tracker.git 36 | cd coronovirus-bot-tracker 37 | touch auth.py 38 | ``` 39 | - Write your Slack Webhook into auth.py 40 | ```python 41 | DEFAULT_SLACK_WEBHOOK = 'https://hooks.slack.com/services/' 42 | ``` 43 | - Setup the cron job to receive updates whenever something changes 44 | ```bash 45 | crontab -e # opens an editor like vim or nano 46 | # now write the following to run the bot every 5 mins 47 | */5 * * * * cd $PATH_TO_CLONE_DIR; python3 corona_bot.py --states 'haryana,maharashtra' 48 | # to receive updates for all states, ignore the --states flag 49 | ``` 50 | -------------------------------------------------------------------------------- /slack_client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import logging 4 | from auth import DEFAULT_SLACK_WEBHOOK 5 | 6 | HEADERS = { 7 | 'Content-type': 'application/json' 8 | } 9 | 10 | 11 | def slacker(webhook_url=DEFAULT_SLACK_WEBHOOK): 12 | def slackit(msg): 13 | logging.info('Sending {msg} to slack'.format(msg=msg)) 14 | payload = { 'text': msg } 15 | return requests.post(webhook_url, headers=HEADERS, data=json.dumps(payload)) 16 | return slackit 17 | --------------------------------------------------------------------------------