diff --git a/cogs/Help.py b/cogs/Help.py index df21725..e8b89a0 100644 --- a/cogs/Help.py +++ b/cogs/Help.py @@ -26,7 +26,8 @@ async def help(self, ctx): colour=discord.Colour.red(), timestamp=datetime.utcnow() ) - embed.add_field(name='```.c stat ```', value='Show **Confirmed** (new cases), **Deaths** (new deaths), and **Recovered** \n React with 📈 for a linear graph or 📉 for a log graph \n **** - Country stats | **** - Global stats \n •For any country you may type the **full name** or **[ISO 3166-1 codes](https://en.wikipedia.org/wiki/ISO_3166-1)** \n __Example:__ **.c stat Italy** | **.c stat IT** | **.c stat ITA** \n •If the country or state\'s full name is two words, enclose them in **quotation marks** \n __Example:__ **.c stat "South Korea"** | **.c stat US "New York"** \n •If you would like stats on a specific **state (full name or abbreviated)** in the US, put it after the country name \n __Example:__ **.c stat US California** or **.c stat US CA**', inline=False) + embed.add_field(name='```.c stat ```', value='Show **Confirmed** (new cases), **Deaths** (new deaths), and **Recovered** \n React with 📈 for a linear graph or 📉 for a log graph \n •For any country you may type the **full name** or **[ISO 3166-1 codes](https://en.wikipedia.org/wiki/ISO_3166-1)** \n __Example:__ **.c stat Italy** | **.c stat IT** | **.c stat ITA** \n •If the country or state\'s full name is two words, enclose them in **quotation marks** \n __Example:__ **.c stat "South Korea"** | **.c stat US "New York"** \n •If you would like stats on a specific **state (full name or abbreviated)** in the US, put it after the country name \n __Example:__ **.c stat US California** or **.c stat US CA**', inline=False) + embed.add_field(name='```.c graph ```', value='Display graph for a single country or multiple countries, choose between graph type and case type | Same rules for country names apply \n __Example:__ **.c graph log deaths nl deu ita usa chn kor jpn esp**') embed.add_field(name='```.c reddit ```', value='Return posts of given category from [r/Coronavirus](https://www.reddit.com/r/Coronavirus/) \n Shows 5 posts at a time (up to first 15) Use ⬅️ and ➡️ to scroll through \n **** - `Hot` | `New` | `Top`', inline=False) embed.add_field(name='```.c info```', value='Return additional info about the bot such as server and user count', inline=False) embed.add_field(name='```.c support```', value='Return invite link to support server', inline=False) @@ -78,7 +79,7 @@ async def on_command_error(self, ctx, error): '''Triggers when wrong command or is inputted''' if isinstance(error, commands.CommandNotFound): message = ctx.message.content - # logger.info(f'Invalid command use \"{message}\"') + logger.info(f'Invalid command use \"{message}\"') elif isinstance(error, commands.CommandOnCooldown): message = 'To prevent spam, the command has been rate limited to 3 times every 10 seconds' logger.info(f'Rate limit reached by {ctx.message.author}({ctx.message.author.id}) in {ctx.message.guild}({ctx.message.guild.id})') @@ -86,11 +87,15 @@ async def on_command_error(self, ctx, error): @commands.command(name='reload', aliases=['r']) @commands.is_owner() - async def reload(self, ctx): - self.bot.unload() - self.bot.load() - logger.info('Cogs Reloaded') - await ctx.send('Cogs Reloaded') + async def reload(self, ctx, extension=None): + if extension is None: + self.bot.unload() + self.bot.load() + await ctx.send('Reloaded All') + else: + self.bot.unload_extension(f'cogs.{extension.title()}') + self.bot.load_extension(f'cogs.{extension.title()}') + await ctx.send(f'Reloaded {extension.title()}') def setup(bot): bot.add_cog(Help(bot)) diff --git a/cogs/Stats.py b/cogs/Stats.py index 7cad00b..37dc7dc 100644 --- a/cogs/Stats.py +++ b/cogs/Stats.py @@ -7,9 +7,10 @@ import logging import asyncio import gc +import requests from datetime import datetime from discord.ext import commands -from utils.codes import states, alt_names, alpha2, alpha3 +from utils.codes import states, alt_names, alpha2, alpha3, JHU_names logger = logging.getLogger('covid-19') @@ -26,10 +27,20 @@ def __init__(self, bot): deaths_df = pd.read_csv(deaths_url, error_bad_lines=False).dropna(axis=1, how='all') recovered_df = pd.read_csv(recovered_url, error_bad_lines=False).dropna(axis=1, how='all') - df_list = pd.read_html('https://www.worldometers.info/coronavirus/') - us_df_list = pd.read_html('https://www.worldometers.info/coronavirus/country/us/') + header = { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.75 Safari/537.36", + "X-Requested-With": "XMLHttpRequest" + } + + wom_url = 'https://www.worldometers.info/coronavirus/' + us_wom_url = 'https://www.worldometers.info/coronavirus/country/us/' + r = requests.get(wom_url, headers=header) + r1 = requests.get(us_wom_url, headers=header) + + df_list = pd.read_html(r.text) + us_df_list = pd.read_html(r1.text) df = df_list[0].replace(np.nan, 0).replace(',', '', regex=True) - us_df = us_df_list[0].replace(np.nan, 0) + us_df = us_df_list[0].replace(np.nan, 0).replace(',', '', regex=True) def getTotal(self, type): df_all = self.df[self.df['Country,Other'].str.match('Total:', na=False)][type].values[0] @@ -89,9 +100,17 @@ async def stat(self, ctx, location = 'ALL', state = ''): deaths = self.getState(state, 'TotalDeaths') new_deaths = self.getState(state, 'NewDeaths') active = self.getState(state, 'ActiveCases') + elif location == 'Canada': + confirmed = self.confirmed_df[self.confirmed_df['Province/State'].str.contains(state, na=False)].iloc[:,-1].sum() + prev_confirmed = self.confirmed_df[self.confirmed_df['Province/State'].str.contains(state, na=False)].iloc[:,-2].sum() + deaths = self.deaths_df[self.deaths_df['Province/State'].str.contains(state, na=False)].iloc[:,-1].sum() + prev_deaths = self.deaths_df[self.deaths_df['Province/State'].str.contains(state, na=False)].iloc[:,-2].sum() + recovered = self.recovered_df[self.recovered_df['Province/State'].str.contains(state, na=False)].iloc[:,-1].sum() + active = confirmed - deaths - recovered + new_confirmed = confirmed - prev_confirmed + new_deaths = deaths - prev_deaths else: await ctx.send('There is no available data for this location | Use **.c help** for more info on commands') - else: confirmed = self.getLocation(location, 'TotalCases') new_confirmed = self.getLocation(location, 'NewCases') @@ -123,9 +142,9 @@ async def stat(self, ctx, location = 'ALL', state = ''): recovery_rate = round((recovered/confirmed * 100), 2) if state: - description='**Vote** for me on <:dbl:689485017667469327> [TOP.GG](https://top.gg/bot/683462722368700526/vote) | **Support** me on <:Kofi:689483361785217299> [Ko-fi](https://ko-fi.com/picklejason)' + description='**Vote** <:dbl:689485017667469327> [TOP.GG](https://top.gg/bot/683462722368700526/vote) | **Donate** <:Kofi:689483361785217299> [Ko-fi](https://ko-fi.com/picklejason) | **Join** <:discord:689486285349715995> [Support Server](https://discord.gg/tVN2UTa)' else: - description='**Vote** for me on <:dbl:689485017667469327> [TOP.GG](https://top.gg/bot/683462722368700526/vote) | **Support** me on <:Kofi:689483361785217299> [Ko-fi](https://ko-fi.com/picklejason) \n React with 📈 for a **linear** graph or 📉 for a **log** graph' + description='**Vote** <:dbl:689485017667469327> [TOP.GG](https://top.gg/bot/683462722368700526/vote) | **Donate** <:Kofi:689483361785217299> [Ko-fi](https://ko-fi.com/picklejason) | **Join** <:discord:689486285349715995> [Support Server](https://discord.gg/tVN2UTa) \n React with 📈 for a **linear** graph or 📉 for a **log** graph' embed = discord.Embed( description=description, colour=discord.Colour.red(), @@ -137,6 +156,7 @@ async def stat(self, ctx, location = 'ALL', state = ''): embed.set_author(name=name, url='https://www.worldometers.info/coronavirus/country/us/', icon_url='https://images.discordapp.net/avatars/683462722368700526/70c1743a2d87a44116f857a88bb107e0.png?size=512') embed.add_field(name='<:activecases:689494177733410861> Active Cases', value=f'**{int(active)}**') embed.add_field(name='<:mortalityrate:689488380865544345> Mortality Rate', value=f'**{mortality_rate}%**') + else: embed.set_author(name=name, url='https://www.worldometers.info/coronavirus/', icon_url='https://images.discordapp.net/avatars/683462722368700526/70c1743a2d87a44116f857a88bb107e0.png?size=512') embed.add_field(name='<:recovered:689490988808274003> Recovered', value=f'**{int(recovered)}**') @@ -146,13 +166,6 @@ async def stat(self, ctx, location = 'ALL', state = ''): embed.set_footer(text='Data from Worldometer and Johns Hopkins CSSE') msg = await ctx.send(embed=embed) - if location == 'USA': - location = 'US' - elif location == 'S. Korea': - location = 'Korea, South' - elif location == 'UK': - location = 'United Kingdom' - #Graph reactions linear = '📈' log = '📉' @@ -168,6 +181,9 @@ def check(reaction, user): return False return check + if location in JHU_names: + location = JHU_names[location] + #Plot graph function async def plot(graph_type): @@ -237,9 +253,9 @@ async def plot(graph_type): while True: try: - react, self.user = await self.bot.wait_for('reaction_add', check=predicate(msg), timeout=45) + react, self.user = await self.bot.wait_for('reaction_add', check=predicate(msg), timeout=30) except asyncio.TimeoutError: - description='**Vote** for me on <:dbl:689485017667469327> [TOP.GG](https://top.gg/bot/683462722368700526/vote) | **Support** me on <:Kofi:689483361785217299> [Ko-fi](https://ko-fi.com/picklejason)' + description='**Vote** <:dbl:689485017667469327> [TOP.GG](https://top.gg/bot/683462722368700526/vote) | **Donate** <:Kofi:689483361785217299> [Ko-fi](https://ko-fi.com/picklejason) | **Join** <:discord:689486285349715995> [Support Server](https://discord.gg/tVN2UTa)' embed = discord.Embed( description=description, colour=discord.Colour.red(), @@ -253,14 +269,12 @@ async def plot(graph_type): embed.add_field(name='<:mortalityrate:689488380865544345> Mortality Rate', value=f'**{mortality_rate}%**') embed.add_field(name='<:recoveryrate:689492820125417521> Recovery Rate', value=f'**{recovery_rate}%**') embed.set_footer(text='Data from Worldometer and Johns Hopkins CSSE') - #embed.set_footer(text='Join the support server with \".c support\"') await msg.edit(embed=embed) await msg.remove_reaction(linear, self.bot.user) await msg.remove_reaction(log, self.bot.user) graph_type = '' if react.emoji == linear: - logger.info(f'Linear graph used for {state} {location}') graph_type = 'linear' await msg.remove_reaction(linear, self.user) await msg.remove_reaction(linear, self.bot.user) @@ -270,7 +284,6 @@ async def plot(graph_type): await ctx.send(file=image, embed=embed) elif react.emoji == log: - logger.info(f'Log graph used for {state} {location}') graph_type = 'log' await msg.remove_reaction(log, self.user) await msg.remove_reaction(log, self.bot.user) @@ -287,5 +300,144 @@ async def plot(graph_type): else: await ctx.send('There is no available data for this location | Use **.c help** for more info on commands') + @commands.command() + @commands.cooldown(3, 10, commands.BucketType.user) + async def graph(self, ctx, graph_type, type, *location): + + countries = [] + #Parameter formatting | Check if country code + for country in location: + if len(country) == 2 or len(country) == 3: + country = country.upper() + else: + country = country.title() + + if country in alpha2: + country = alpha2[country] + elif country in alpha3: + country = alpha3[country] + elif country in alt_names: + country = alt_names[country] + + if country in JHU_names: + country = JHU_names[country] + + countries.append(country) + + fig = plt.figure(dpi=150) + plt.style.use('dark_background') + + for country in countries: + if country in list(alpha2.values()) or country in list(JHU_names.values()): + if graph_type == 'linear': + if type == 'confirmed': + ax = self.confirmed_df[self.confirmed_df['Country/Region'].str.contains(country, na=False)].iloc[:,4:].sum().plot(label=country) + elif type == 'recovered': + ax = self.recovered_df[self.recovered_df['Country/Region'].str.contains(country, na=False)].iloc[:,4:].sum().plot(label=country) + elif type == 'deaths': + ax = self.deaths_df[self.deaths_df['Country/Region'].str.contains(country, na=False)].iloc[:,4:].sum().plot(label=country) + + elif graph_type == 'log': + if type == 'confirmed': + ax = self.confirmed_df[self.confirmed_df['Country/Region'].str.contains(country, na=False)].iloc[:,4:].sum().plot(label=country, logy=True) + elif type == 'recovered': + ax = self.recovered_df[self.recovered_df['Country/Region'].str.contains(country, na=False)].iloc[:,4:].sum().plot(label=country, logy=True) + elif type == 'deaths': + ax = self.deaths_df[self.deaths_df['Country/Region'].str.contains(country, na=False)].iloc[:,4:].sum().plot(label=country, logy=True) + else: + await ctx.send(f'{country} is not a valid location', delete_after=3) + + if graph_type == 'linear': + filename = './graphs/lineargraph.png' + ax.set_ylim(0) + plt.title(f'{type.title()} Linear Graph') + + elif graph_type == 'log': + filename = './graphs/loggraph.png' + ax.set_ylim(10**2) + plt.title(f'{type.title()} Logarithmic Graph') + plt.minorticks_off() + + ax.legend(loc='upper left', fancybox=True, facecolor='0.2') + ax.yaxis.grid() + ax.spines['top'].set_visible(False) + ax.spines['right'].set_visible(False) + ax.spines['left'].set_visible(False) + locs, _ = plt.yticks() + ylabels = [] + for l in locs: + lab = str(int(l)).replace('00000000', '00M').replace('0000000', '0M').replace('000000', 'M').replace('00000', '00K').replace('0000', '0K').replace('000', 'K') + if not ('K' in lab or 'M' in lab): + lab = '{:,}'.format(int(lab)) + ylabels.append(lab) + plt.yticks(locs, ylabels) + plt.savefig(filename, transparent=True) + plt.cla() + plt.close(fig) + plt.close('all') + gc.collect() + with open(filename, 'rb') as f: + file = io.BytesIO(f.read()) + image = discord.File(file, filename=f'{graph_type}graph.png') + # description='**Vote** <:dbl:689485017667469327> [TOP.GG](https://top.gg/bot/683462722368700526/vote) | **Donate** <:Kofi:689483361785217299> [Ko-fi](https://ko-fi.com/picklejason) | **Join** <:discord:689486285349715995> [Support Server](https://discord.gg/tVN2UTa)' + embed = discord.Embed( + # description=description, + colour=discord.Colour.red(), + timestamp=datetime.utcnow() + ) + + embed.set_image(url=f'attachment://{graph_type}graph.png') + embed.set_footer(text=f'Requested by {ctx.message.author}', icon_url=ctx.message.author.avatar_url) + await ctx.send(file=image, embed=embed) + + if os.path.exists(f'./graphs/{graph_type}graph.png'): + os.remove(f'./graphs/{graph_type}graph.png') + else: + pass + + @commands.command() + @commands.cooldown(3, 10, commands.BucketType.user) + async def vcset(self, ctx, channel: discord.VoiceChannel, *, location = 'All', state = ''): + + if len(location) == 2: + location = location.upper() + else: + location = location.title() + if len(state) == 2: + state = state.upper() + else: + state = state.title() + if location in alpha2: + location = alpha2[location] + elif location in alpha3: + location = alpha3[location] + elif location in alt_names: + location = alt_names[location] + if state in states: + state = states[state] + + while True: + #Check if data exists for location + if location == 'All' or location == 'Other' or self.confirmed_df['Country/Region'].str.contains(location).any(): + #Parse Data + if location == 'All': + confirmed = self.confirmed_df.iloc[:,-1].sum() + deaths = self.deaths_df.iloc[:,-1].sum() + # recovered = self.recovered_df.iloc[:,-1].sum() + elif location == 'Other': + confirmed = self.confirmed_df[~self.confirmed_df['Country/Region'].str.contains('China', na=False)].iloc[:,-1].sum() + deaths = self.deaths_df[~self.deaths_df['Country/Region'].str.contains('China', na=False)].iloc[:,-1].sum() + # recovered = self.recovered_df[~self.recovered_df['Country/Region'].str.contains('China', na=False)].iloc[:,-1].sum() + else: + confirmed = self.confirmed_df[self.confirmed_df['Country/Region'].str.match(location, na=False)].iloc[:,-1].sum() + deaths = self.deaths_df[self.deaths_df['Country/Region'].str.match(location, na=False)].iloc[:,-1].sum() + # recovered = self.recovered_df[self.recovered_df['Country/Region'].str.match(location, na=False)].iloc[:,-1].sum() + else: + await ctx.send('There is no available data for this location | Use **.c help** for more info on commands') + + await channel.edit(name=f'😷 {location}: {str(confirmed)}') + + await asyncio.sleep(86400) + def setup(bot): bot.add_cog(Stats(bot)) diff --git a/cogs/TopGG.py b/cogs/TopGG.py index 79c4492..d9eff18 100644 --- a/cogs/TopGG.py +++ b/cogs/TopGG.py @@ -11,8 +11,5 @@ def __init__(self, bot): self.token = config.dbl_token # set this to your DBL token self.dblpy = dbl.DBLClient(self.bot, self.token, autopost=True) # Autopost will post your guild count every 30 minutes - async def on_guild_post(): - print("Server count posted successfully") - def setup(bot): bot.add_cog(TopGG(bot)) diff --git a/covid-19.py b/covid-19.py index 73d0fbd..64cf845 100644 --- a/covid-19.py +++ b/covid-19.py @@ -51,7 +51,7 @@ async def on_ready(self): await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=f'{len(bot.guilds)} servers | .c help')) self.unload_extension('cogs.Stats') self.load_extension('cogs.Stats') - logger.info('Stats reloaded') + logger.info('Reloaded Stats') await asyncio.sleep(600) async def on_guild_join(self, guild: discord.Guild): @@ -61,11 +61,11 @@ async def on_guild_join(self, guild: discord.Guild): await channel.send(embed=embed_join) if general and general.permissions_for(guild.me).send_messages: embed = discord.Embed( - title='Coronavirus (COVID-19) Discord Bot', - description='Thanks for inviting me! | Use **.c help** for more info on commands \n •Please vote for me on [TOP.GG](https://top.gg/bot/683462722368700526/vote) <:dbl:689485017667469327>', + description='Thanks for inviting me! | Use **.c help** for more info on commands \n Please vote for me on <:dbl:689485017667469327> [TOP.GG](https://top.gg/bot/683462722368700526/vote) | Join the <:discord:689486285349715995> [Support Server](https://discord.gg/tVN2UTa)', colour=discord.Colour.red() ) - embed.add_field(name='Command Prefix', value='`.c` or `@mention`') + embed.set_author(name='Coronavirus (COVID-19)', url='https://discord.gg/tVN2UTa', icon_url='https://images.discordapp.net/avatars/683462722368700526/70c1743a2d87a44116f857a88bb107e0.png?size=512') + embed.add_field(name='Command Prefix', value='`.c ` or `@mention`') users = 0 for s in self.guilds: users += len(s.members) @@ -74,7 +74,7 @@ async def on_guild_join(self, guild: discord.Guild): embed.add_field(name='Bot Source Code', value='<:github:689501322969350158> [Github](https://github.com/picklejason/coronavirus-bot)') embed.add_field(name='Bot Invite', value='<:discord:689486285349715995> [Link](https://discordapp.com/api/oauth2/authorize?client_id=683462722368700526&permissions=59456&scope=bot)') embed.add_field(name='Donate', value='<:Kofi:689483361785217299> [Ko-fi](https://ko-fi.com/picklejason)') - embed.set_footer(text='Made by PickleJason#5293 | Feel free to message me for any issues or suggestions') + embed.set_footer(text='Made by PickleJason#5293') await general.send(embed=embed) async def on_guild_remove(self, guild: discord.Guild): diff --git a/utils/codes.py b/utils/codes.py index fb0b2f9..58ffb94 100644 --- a/utils/codes.py +++ b/utils/codes.py @@ -242,7 +242,7 @@ 'QA': 'Qatar', 'RE': 'Reunion', 'RO': 'Romania', -'RU': 'Russian Federation', +'RU': 'Russia', 'RW': 'Rwanda', 'BL': 'St. Barth', 'SH': 'Saint Helena, Ascension and Tristan da Cunha', @@ -502,7 +502,7 @@ 'QAT': 'Qatar', 'REU': 'Reunion', 'ROU': 'Romania', -'RUS': 'Russian Federation', +'RUS': 'Russia', 'RWA': 'Rwanda', 'SAU': 'Saudi Arabia', 'SDN': 'Sudan', @@ -561,14 +561,20 @@ 'YEM': 'Yemen', 'ZAF': 'South Africa', 'ZMB': 'Zambia', -'ZWE': 'Zimbabwe', +'ZWE': 'Zimbabwe' } #Other names for countries alt_names = { - 'Czech Republic': 'Czechia', - 'UK': 'UK', - 'South Korea': 'S. Korea', - 'Korea': 'S. Korea', - 'United States': 'USA' +'Czech Republic': 'Czechia', +'United Kingdom': 'UK', +'South Korea': 'S. Korea', +'Korea': 'S. Korea', +'United States': 'USA' +} + +JHU_names = { +'USA': 'US', +'S. Korea': 'Korea, South', +'UK': 'United Kingdom' }