### # Copyright (c) 2020, Hoaas # Copyright (c) 2020, oddluck # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import json import requests import csv from datetime import date, timedelta from supybot import utils, plugins, ircutils, callbacks, log from supybot.commands import * try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('Corona') except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = lambda x: x countries = { "AF": "AFGHANISTAN", "AX": "ÅLAND ISLANDS", "AL": "ALBANIA", "DZ": "ALGERIA", "AS": "AMERICAN SAMOA", "AD": "ANDORRA", "AO": "ANGOLA", "AI": "ANGUILLA", "AQ": "ANTARCTICA", "AG": "ANTIGUA AND BARBUDA", "AR": "ARGENTINA", "AM": "ARMENIA", "AW": "ARUBA", "AU": "AUSTRALIA", "AT": "AUSTRIA", "AZ": "AZERBAIJAN", "BS": "BAHAMAS", "BH": "BAHRAIN", "BD": "BANGLADESH", "BB": "BARBADOS", "BY": "BELARUS", "BE": "BELGIUM", "BZ": "BELIZE", "BJ": "BENIN", "BM": "BERMUDA", "BT": "BHUTAN", "BO": "BOLIVIA", "BQ": "BONAIRE", "BA": "BOSNIA", "BW": "BOTSWANA", "BV": "BOUVET ISLAND", "BR": "BRAZIL", "IO": "BRITISH INDIAN OCEAN TERRITORY", "BN": "BRUNEI DARUSSALAM", "BG": "BULGARIA", "BF": "BURKINA FASO", "BI": "BURUNDI", "KH": "CAMBODIA", "CM": "CAMEROON", "CA": "CANADA", "CV": "CAPE VERDE", "KY": "CAYMAN ISLANDS", "CF": "CENTRAL AFRICAN REPUBLIC", "TD": "CHAD", "CL": "CHILE", "CN": "CHINA", "CX": "CHRISTMAS ISLAND", "CC": "COCOS ISLANDS", "CO": "COLOMBIA", "KM": "COMOROS", "CG": "CONGO", "CD": "CONGO", "CK": "COOK ISLANDS", "CR": "COSTA RICA", "CI": "CÔTE D'IVOIRE", "HR": "CROATIA", "CU": "CUBA", "CW": "CURAÇAO", "CY": "CYPRUS", "CZ": "CZECH REPUBLIC", "DK": "DENMARK", "DJ": "DJIBOUTI", "DM": "DOMINICA", "DO": "DOMINICAN REPUBLIC", "EC": "ECUADOR", "EG": "EGYPT", "SV": "EL SALVADOR", "GQ": "EQUATORIAL GUINEA", "ER": "ERITREA", "EE": "ESTONIA", "ET": "ETHIOPIA", "FK": "FALKLAND ISLANDS", "FO": "FAROE ISLANDS", "FJ": "FIJI", "FI": "FINLAND", "FR": "FRANCE", "GF": "GUIANA", "PF": "POLYNESIA", "TF": "FRENCH SOUTHERN TERRITORIES", "GA": "GABON", "GM": "GAMBIA", "GE": "GEORGIA", "DE": "GERMANY", "GH": "GHANA", "GI": "GIBRALTAR", "GR": "GREECE", "GL": "GREENLAND", "GD": "GRENADA", "GP": "GUADELOUPE", "GU": "GUAM", "GT": "GUATEMALA", "GG": "GUERNSEY", "GN": "GUINEA", "GW": "GUINEA-BISSAU", "GY": "GUYANA", "HT": "HAITI", "HM": "HEARD ISLAND AND MCDONALD ISLANDS", "VA": "VATICAN CITY", "HN": "HONDURAS", "HK": "HONG KONG", "HU": "HUNGARY", "IS": "ICELAND", "IN": "INDIA", "ID": "INDONESIA", "IR": "IRAN", "IQ": "IRAQ", "IE": "IRELAND", "IM": "ISLE OF MAN", "IL": "ISRAEL", "IT": "ITALY", "JM": "JAMAICA", "JP": "JAPAN", "JE": "JERSEY", "JO": "JORDAN", "KZ": "KAZAKHSTAN", "KE": "KENYA", "KI": "KIRIBATI", "KP": "NORTH KOREA", "KR": "SOUTH KOREA", "KW": "KUWAIT", "KG": "KYRGYZSTAN", "LA": "LAOS", "LV": "LATVIA", "LB": "LEBANON", "LS": "LESOTHO", "LR": "LIBERIA", "LY": "LIBYA", "LI": "LIECHTENSTEIN", "LT": "LITHUANIA", "LU": "LUXEMBOURG", "MO": "MACAO", "MK": "MACEDONIA", "MG": "MADAGASCAR", "MW": "MALAWI", "MY": "MALAYSIA", "MV": "MALDIVES", "ML": "MALI", "MT": "MALTA", "MH": "MARSHALL ISLANDS", "MQ": "MARTINIQUE", "MR": "MAURITANIA", "MU": "MAURITIUS", "YT": "MAYOTTE", "MX": "MEXICO", "FM": "MICRONESIA", "MD": "MOLDOVA", "MC": "MONACO", "MN": "MONGOLIA", "ME": "MONTENEGRO", "MS": "MONTSERRAT", "MA": "MOROCCO", "MZ": "MOZAMBIQUE", "MM": "MYANMAR", "NA": "NAMIBIA", "NR": "NAURU", "NP": "NEPAL", "NL": "NETHERLANDS", "NC": "NEW CALEDONIA", "NZ": "NEW ZEALAND", "NI": "NICARAGUA", "NE": "NIGER", "NG": "NIGERIA", "NU": "NIUE", "NF": "NORFOLK ISLAND", "MP": "NORTHERN MARIANA ISLANDS", "NO": "NORWAY", "OM": "OMAN", "PK": "PAKISTAN", "PW": "PALAU", "PS": "PALESTINE, STATE OF", "PA": "PANAMA", "PG": "PAPUA NEW GUINEA", "PY": "PARAGUAY", "PE": "PERU", "PH": "PHILIPPINES", "PN": "PITCAIRN", "PL": "POLAND", "PT": "PORTUGAL", "PR": "PUERTO RICO", "QA": "QATAR", "RE": "RÉUNION", "RO": "ROMANIA", "RU": "RUSSIAN FEDERATION", "RW": "RWANDA", "BL": "SAINT BARTHÉLEMY", "SH": "SAINT HELENA", "KN": "SAINT KITTS AND NEVIS", "LC": "SAINT LUCIA", "MF": "SAINT MARTIN", "PM": "SAINT PIERRE AND MIQUELON", "VC": "SAINT VINCENT AND THE GRENADINES", "WS": "SAMOA", "SM": "SAN MARINO", "ST": "SAO TOME AND PRINCIPE", "SA": "SAUDI ARABIA", "SN": "SENEGAL", "RS": "SERBIA", "SC": "SEYCHELLES", "SL": "SIERRA LEONE", "SG": "SINGAPORE", "SX": "SINT MAARTEN", "SK": "SLOVAKIA", "SI": "SLOVENIA", "SB": "SOLOMON ISLANDS", "SO": "SOMALIA", "ZA": "SOUTH AFRICA", "GS": "SOUTH GEORGIA", "SS": "SOUTH SUDAN", "ES": "SPAIN", "LK": "SRI LANKA", "SD": "SUDAN", "SR": "SURINAME", "SJ": "SVALBARD AND JAN MAYEN", "SZ": "SWAZILAND", "SE": "SWEDEN", "CH": "SWITZERLAND", "SY": "SYRIAN ARAB REPUBLIC", "TW": "TAIWAN, PROVINCE OF CHINA", "TJ": "TAJIKISTAN", "TZ": "TANZANIA", "TH": "THAILAND", "TL": "TIMOR-LESTE", "TG": "TOGO", "TK": "TOKELAU", "TO": "TONGA", "TT": "TRINIDAD AND TOBAGO", "TN": "TUNISIA", "TR": "TURKEY", "TM": "TURKMENISTAN", "TC": "TURKS AND CAICOS ISLANDS", "TV": "TUVALU", "UG": "UGANDA", "UA": "UKRAINE", "AE": "UNITED ARAB EMIRATES", "GB": "UK", "US": "US", "UM": "UNITED STATES MINOR OUTLYING ISLANDS", "UY": "URUGUAY", "UZ": "UZBEKISTAN", "VU": "VANUATU", "VE": "VENEZUELA", "VN": "VIET NAM", "VG": "VIRGIN ISLANDS", "VI": "VIRGIN ISLANDS", "WF": "WALLIS AND FUTUNA", "EH": "WESTERN SAHARA", "YE": "YEMEN", "ZM": "ZAMBIA", "ZW": "ZIMBABWE", } states = { 'AK': 'ALASKA', 'AL': 'ALABAMA', 'AR': 'ARKANSAS', 'AS': 'AMERICAN SAMOA', 'AZ': 'ARIZONA', 'CA': 'CALIFORNIA', 'CO': 'COLORADO', 'CT': 'CONNECTICUT', 'DC': 'DISTRICT OF COLUMBIA', 'DE': 'DELAWARE', 'FL': 'FLORIDA', 'GA': 'GEORGIA', 'GU': 'GUAM', 'HI': 'HAWAII', 'IA': 'IOWA', 'ID': 'IDAHO', 'IL': 'ILLINOIS', 'IN': 'INDIANA', 'KS': 'KANSAS', 'KY': 'KENTUCKY', 'LA': 'LOUISIANA', 'MA': 'MASSACHUSETTS', 'MD': 'MARYLAND', 'ME': 'MAINE', 'MI': 'MICHIGAN', 'MN': 'MINNESOTA', 'MO': 'MISSOURI', 'MP': 'NORTHERN MARIANA ISLANDS', 'MS': 'MISSISSIPPI', 'MT': 'MONTANA', 'NA': 'NATIONAL', 'NC': 'NORTH CAROLINA', 'ND': 'NORTH DAKOTA', 'NE': 'NEBRASKA', 'NH': 'NEW HAMPSHIRE', 'NJ': 'NEW JERSEY', 'NM': 'NEW MEXICO', 'NV': 'NEVADA', 'NY': 'NEW YORK', 'OH': 'OHIO', 'OK': 'OKLAHOMA', 'OR': 'OREGON', 'PA': 'PENNSYLVANIA', 'PR': 'PUERTO RICO', 'RI': 'RHODE ISLAND', 'SC': 'SOUTH CAROLINA', 'SD': 'SOUTH DAKOTA', 'TN': 'TENNESSEE', 'TX': 'TEXAS', 'UT': 'UTAH', 'VA': 'VIRGINIA', 'VI': 'VIRGIN ISLANDS', 'VT': 'VERMONT', 'WA': 'WASHINGTON', 'WI': 'WISCONSIN', 'WV': 'WEST VIRGINIA', 'WY': 'WYOMING' } class Corona(callbacks.Plugin): """Displays current stats of the Coronavirus outbreak""" threaded = True def getCSV(self): data = None try: day = date.today().strftime('%m-%d-%Y') url = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/{0}.csv".format(day) r = requests.get(url, timeout=10) r.raise_for_status() except (requests.exceptions.RequestException, requests.exceptions.HTTPError) as e: log.debug('Corona: error retrieving data for today: {0}'.format(e)) try: day = date.today() - timedelta(days=1) day = day.strftime('%m-%d-%Y') url = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/{0}.csv".format(day) r = requests.get(url, timeout=10) r.raise_for_status() except (requests.exceptions.RequestException, requests.exceptions.HTTPError) as e: log.debug('Corona: error retrieving data for yesterday: {0}'.format(e)) else: data = csv.DictReader(r.iter_lines(decode_unicode = True)) else: data = csv.DictReader(r.iter_lines(decode_unicode = True)) return data def getAPI(self): data = None url = "https://services1.arcgis.com/0MSEUqKaxRlEPj5g/arcgis/rest/services/ncov_cases/FeatureServer/1/query?f=json&where=Confirmed>0&outFields=*" try: r = requests.get(url, timeout=10) r.raise_for_status() except (requests.exceptions.RequestException, requests.exceptions.HTTPError) as e: log.debug('Corona: error retrieving data from API: {0}'.format(e)) else: r = json.loads(r.content.decode()) data = r.get('features') if not data: log.debug("Corona: Error retrieving features data from API.") return data @wrap([optional('text')]) def corona(self, irc, msg, args, search): """[region] Displays Coronavirus statistics. Add a region name to search for country/state specific results. Accepts full country/state names or ISO 3166-1 alpha-2 (two character) country abbreviations and US Postal (two character) state abbreviations. Invalid region names or search terms without data return global results. """ git = api = False data = self.getAPI() if data: api = True else: data = self.getCSV() if data: git = True if not data: irc.reply("Error. Unable to access database.") return total_confirmed = total_deaths = total_recovered = 0 confirmed = deaths = recovered = 0 location = 'Global' for region in data: if api: r = region.get('attributes') else: r = region if search: if api: region = r.get('Country_Region') state = r.get('Province_State') else: region = r.get('Country/Region') state = r.get('Province/State') if len(search) == 2: if self.registryValue("countryFirst", msg.channel): try: search = countries[search.upper()] except KeyError: try: search = states[search.upper()] except KeyError: pass else: try: search = states[search.upper()] except KeyError: try: search = countries[search.upper()] except KeyError: pass if search.lower() == 'usa' or 'united states' in search.lower(): search = 'us' elif 'united kingdom' in search.lower(): search = 'uk' if region and search.lower() == region.lower(): location = region confirmed += int(r.get('Confirmed')) deaths += int(r.get('Deaths')) recovered += int(r.get('Recovered')) local_ratio_dead = "{0:.1%}".format(deaths/confirmed) elif state and search.lower() == state.lower(): location = state confirmed += int(r.get('Confirmed')) deaths += int(r.get('Deaths')) recovered += int(r.get('Recovered')) local_ratio_dead = "{0:.1%}".format(deaths/confirmed) total_confirmed += int(r.get('Confirmed')) total_deaths += int(r.get('Deaths')) total_recovered += int(r.get('Recovered')) ratio_dead = "{0:.1%}".format(total_deaths/total_confirmed) template = self.registryValue("template", msg.channel) if location == 'Global': template = template.replace("$location", location) template = template.replace("$confirmed", str(total_confirmed)) template = template.replace("$dead", str(total_deaths)) template = template.replace("$recovered", str(total_recovered)) template = template.replace("$ratio", ratio_dead) else: template = template.replace("$location", location) template = template.replace("$confirmed", str(confirmed)) template = template.replace("$dead", str(deaths)) template = template.replace("$recovered", str(recovered)) template = template.replace("$ratio", local_ratio_dead) irc.reply(template) Class = Corona