From 69659794e5290f07c980b2a255de06bbfc893c24 Mon Sep 17 00:00:00 2001 From: oddluck <39967334+oddluck@users.noreply.github.com> Date: Tue, 10 Mar 2020 20:44:32 +0000 Subject: [PATCH] Corona: abbreviations, api fallback, etc. --- Corona/LICENSE | 14 -- Corona/README.md | 13 +- Corona/__init__.py | 3 +- Corona/config.py | 6 +- Corona/plugin.py | 454 ++++++++++++++++++++++++++++++++++------ Corona/requirements.txt | 1 + 6 files changed, 414 insertions(+), 77 deletions(-) delete mode 100644 Corona/LICENSE create mode 100644 Corona/requirements.txt diff --git a/Corona/LICENSE b/Corona/LICENSE deleted file mode 100644 index ee7d6a5..0000000 --- a/Corona/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - Version 2, December 2004 - - Copyright (C) 2004 Sam Hocevar - - Everyone is permitted to copy and distribute verbatim or modified - copies of this license document, and changing it is allowed as long - as the name is changed. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. You just DO WHAT THE FUCK YOU WANT TO. - diff --git a/Corona/README.md b/Corona/README.md index b929321..6a811b9 100644 --- a/Corona/README.md +++ b/Corona/README.md @@ -1,2 +1,11 @@ -# Corona -Limnoria coronavirus plugin +Return the latest Coronavirus (COVID-19) statistics globally or by country/state. + +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=T8E56M6SP9JH2) + +`config plugins.corona.template` - Configure the template for replies + +Template default: `\x02$location: \x0307$confirmed\x03 infected, \x0304$dead\x03 dead ($ratio), \x0309$recovered\x03 recovered.` + +`config plugins.corona.countryFirst` - Country name abbreviations take precedence over USA state name abbreviations when `True` + +countryFirst default: `False` diff --git a/Corona/__init__.py b/Corona/__init__.py index 34a9276..f7bf736 100644 --- a/Corona/__init__.py +++ b/Corona/__init__.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2020, Hoaas +# Copyright (c) 2020, oddluck # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -40,7 +41,7 @@ import imp __version__ = "2020.02.24+git" # XXX Replace this with an appropriate author or supybot.Author instance. -__author__ = supybot.Author('Hoaas', 'Hoaas', '') +__author__ = supybot.Author('oddluck', 'oddluck', 'oddluck@riseup.net') __maintainer__ = {} # This is a dictionary mapping supybot.Author instances to lists of diff --git a/Corona/config.py b/Corona/config.py index ed9351e..478fb4a 100644 --- a/Corona/config.py +++ b/Corona/config.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2020, Hoaas +# Copyright (c) 2020, oddluck # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -50,4 +51,7 @@ def configure(advanced): Corona = conf.registerPlugin('Corona') conf.registerChannelValue(Corona, 'template', - registry.String("\x02$location: \x0307$confirmed\x03 infected, \x0304$dead\x03 dead ($ratio), \x0309$recovered\x03 recovered.", _("""Template for replies"""))) + registry.String("\x02$location: \x0307$confirmed\x03 infected, \x0304$dead\x03 dead ($ratio), \x0309$recovered\x03 recovered.", _("""Template for replies"""))) + +conf.registerChannelValue(Corona, 'countryFirst', + registry.Boolean(False, _("Give preference to country name abbreviations over USA state name abbreviations"))) diff --git a/Corona/plugin.py b/Corona/plugin.py index f49e850..607e666 100644 --- a/Corona/plugin.py +++ b/Corona/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2020, Hoaas +# Copyright (c) 2020, oddluck # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -28,8 +29,9 @@ ### import json -import urllib - +import requests +import csv +from datetime import date, timedelta from supybot import utils, plugins, ircutils, callbacks, log from supybot.commands import * try: @@ -41,82 +43,416 @@ except ImportError: _ = lambda x: x +CC = { + "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, PLURINATIONAL STATE OF", + "BQ": "BONAIRE, SINT EUSTATIUS AND SABA", + "BA": "BOSNIA AND HERZEGOVINA", + "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 (KEELING) ISLANDS", + "CO": "COLOMBIA", + "KM": "COMOROS", + "CG": "CONGO", + "CD": "CONGO, THE DEMOCRATIC REPUBLIC OF THE", + "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 (MALVINAS)", + "FO": "FAROE ISLANDS", + "FJ": "FIJI", + "FI": "FINLAND", + "FR": "FRANCE", + "GF": "FRENCH GUIANA", + "PF": "FRENCH 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": "HOLY SEE (VATICAN CITY STATE)", + "HN": "HONDURAS", + "HK": "HONG KONG", + "HU": "HUNGARY", + "IS": "ICELAND", + "IN": "INDIA", + "ID": "INDONESIA", + "IR": "IRAN, ISLAMIC REPUBLIC OF", + "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": "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF", + "KR": "KOREA, REPUBLIC OF", + "KW": "KUWAIT", + "KG": "KYRGYZSTAN", + "LA": "LAO PEOPLE'S DEMOCRATIC REPUBLIC", + "LV": "LATVIA", + "LB": "LEBANON", + "LS": "LESOTHO", + "LR": "LIBERIA", + "LY": "LIBYA", + "LI": "LIECHTENSTEIN", + "LT": "LITHUANIA", + "LU": "LUXEMBOURG", + "MO": "MACAO", + "MK": "MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF", + "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, FEDERATED STATES OF", + "MD": "MOLDOVA, REPUBLIC OF", + "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, ASCENSION AND TRISTAN DA CUNHA", + "KN": "SAINT KITTS AND NEVIS", + "LC": "SAINT LUCIA", + "MF": "SAINT MARTIN (FRENCH PART)", + "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 (DUTCH PART)", + "SK": "SLOVAKIA", + "SI": "SLOVENIA", + "SB": "SOLOMON ISLANDS", + "SO": "SOMALIA", + "ZA": "SOUTH AFRICA", + "GS": "SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS", + "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, UNITED REPUBLIC OF", + "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": "UNITED KINGDOM", + "US": "UNITED STATES", + "UM": "UNITED STATES MINOR OUTLYING ISLANDS", + "UY": "URUGUAY", + "UZ": "UZBEKISTAN", + "VU": "VANUATU", + "VE": "VENEZUELA, BOLIVARIAN REPUBLIC OF", + "VN": "VIET NAM", + "VG": "VIRGIN ISLANDS, BRITISH", + "VI": "VIRGIN ISLANDS, U.S.", + "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 - url = "https://services1.arcgis.com/0MSEUqKaxRlEPj5g/arcgis/rest/services/ncov_cases/FeatureServer/1/query?f=json&where=Confirmed>0&outFields=*" - @wrap([optional('text')]) def corona(self, irc, msg, args, search): """ Displays Coronavirus stats """ - + git = api = False + url = "https://services1.arcgis.com/0MSEUqKaxRlEPj5g/arcgis/rest/services/ncov_cases/FeatureServer/1/query?f=json&where=Confirmed>0&outFields=*" try: - data = utils.web.getUrl(self.url).decode() - data = json.loads(data) - except utils.web.Error as e: - log.debug('Corona: error retrieving data from {0}: {1}'.format(self.url, e)) - return - - features = data.get('features') - if not features: - log.debug("Corona: Error retrieving features data.") - return - + data = requests.get(url, timeout=10) + data.raise_for_status() + except (requests.exceptions.RequestException, requests.exceptions.HTTPError) as e: + log.debug('Corona: error retrieving data from API: {0}'.format(e)) + 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) + data = requests.get(url, timeout=10) + data.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) + data = requests.get(url, timeout=10) + except (requests.exceptions.RequestException, requests.exceptions.HTTPError) as e: + log.debug('Corona: error retrieving data for yesterday: {0}'.format(e)) + return + else: + git = True + data = csv.DictReader(data.iter_lines(decode_unicode = True)) + else: + git = True + data = csv.DictReader(data.iter_lines(decode_unicode = True)) + else: + api = True + data = json.loads(data.content) + data = data.get('features') + if not data: + log.debug("Corona: Error retrieving features data from API.") + return total_confirmed = total_deaths = total_recovered = 0 confirmed = deaths = recovered = 0 location = 'Global' - - for region in features: - r = region.get('attributes') - + for region in data: + if api: + r = region.get('attributes') + else: + r = region if search: - region = r.get('Country_Region') - state = r.get('Province_State') + 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 and search.lower() != 'us' and search.lower() != 'uk': + if self.registryValue("countryFirst", msg.channel): + try: + search = CC[search.upper()] + except KeyError: + try: + search = states[search.upper()] + except KeyError: + pass + else: + try: + search = states[search.upper()] + except KeyError: + try: + search = CC[search.upper()] + except KeyError: + pass if 'china' in search.lower(): search = 'mainland china' + elif 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 += r.get('Confirmed') - deaths += r.get('Deaths') - recovered += r.get('Recovered') + 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: - if search.lower() == state.lower(): - location = state - confirmed += r.get('Confirmed') - deaths += r.get('Deaths') - recovered += r.get('Recovered') - local_ratio_dead = "{0:.1%}".format(deaths/confirmed) - else: - state = state.split(',', 1) - if search.lower() == state[0].lower().strip(): - location = r.get('Province_State') - confirmed += r.get('Confirmed') - deaths += r.get('Deaths') - recovered += r.get('Recovered') - local_ratio_dead = "{0:.1%}".format(deaths/confirmed) - elif len(state) > 1 and search.lower() == state[1].lower().strip(): - location = state[1].strip() - confirmed += r.get('Confirmed') - deaths += r.get('Deaths') - recovered += r.get('Recovered') - local_ratio_dead = "{0:.1%}".format(deaths/confirmed) - elif search.lower().replace('county', '').strip() == state[0].lower().replace('county', '').strip(): - location = r.get('Province_State') - confirmed += r.get('Confirmed') - deaths += r.get('Deaths') - recovered += r.get('Recovered') - local_ratio_dead = "{0:.1%}".format(deaths/confirmed) - - total_confirmed += r.get('Confirmed') - total_deaths += r.get('Deaths') - total_recovered += r.get('Recovered') - + 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) @@ -130,7 +466,7 @@ class Corona(callbacks.Plugin): template = template.replace("$dead", str(deaths)) template = template.replace("$recovered", str(recovered)) template = template.replace("$ratio", local_ratio_dead) - irc.reply(template) Class = Corona + diff --git a/Corona/requirements.txt b/Corona/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/Corona/requirements.txt @@ -0,0 +1 @@ +requests