From 6ccf0be796b63293f2dc4a8634fa6f834c2dd55a Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 23 Feb 2020 19:24:05 +0100 Subject: Calculate seconds as n_hours * seconds_in_hour This is a lot more readable than just 86400. --- bot/seasons/season.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/season.py b/bot/seasons/season.py index e7b7a69c..8fc68491 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -386,7 +386,7 @@ class SeasonManager(commands.Cog): while True: await asyncio.sleep(self.sleep_time) # Sleep until midnight - self.sleep_time = 86400 # Next time, sleep for 24 hours. + self.sleep_time = 24 * 3600 # Next time, sleep for 24 hours # If the season has changed, load it. new_season = get_season(date=datetime.datetime.utcnow()) -- cgit v1.2.3 From fe74128512b83e5a90fcfb5c655d9bc9fceab56a Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 23 Feb 2020 19:55:44 +0100 Subject: Add icon cycle frequency constant --- bot/constants.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index f0656926..52a4aa20 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -69,6 +69,7 @@ class Client(NamedTuple): token = environ.get("SEASONALBOT_TOKEN") debug = environ.get("SEASONALBOT_DEBUG", "").lower() == "true" season_override = environ.get("SEASON_OVERRIDE") + icon_cycle_frequency = 3 # N days to wait between cycling server icons within a single season class Colours: -- cgit v1.2.3 From 9330b404316cb00379f6eb5074b7ace842cedc8b Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 23 Feb 2020 20:09:26 +0100 Subject: Cycle icons within season in configured interval only The `load_seasons` task now has an internal state, which increments for every day. Before cycling the icon within the same season, first check whether we've waited the configured amount of days since the last cycle. Entering a new season, or changing the icon, resets the state to 0. This allows us to slow down the rate at which we cycle icons, addressing a bug where the icon wasn't loading for some users. --- bot/seasons/season.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/season.py b/bot/seasons/season.py index 8fc68491..763a08d2 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -383,18 +383,29 @@ class SeasonManager(commands.Cog): """Asynchronous timer loop to check for a new season every midnight.""" await self.bot.wait_until_ready() await self.season.load() + days_since_icon_change = 0 while True: await asyncio.sleep(self.sleep_time) # Sleep until midnight self.sleep_time = 24 * 3600 # Next time, sleep for 24 hours + days_since_icon_change += 1 + log.debug(f"Days since last icon change: {days_since_icon_change}") + # If the season has changed, load it. new_season = get_season(date=datetime.datetime.utcnow()) if new_season.name != self.season.name: self.season = new_season await self.season.load() + days_since_icon_change = 0 # Start counting afresh for the new season + + # Otherwise we check whether it's time for an icon cycle within the current season else: - await self.season.change_server_icon() + if days_since_icon_change == Client.icon_cycle_frequency: + await self.season.change_server_icon() + days_since_icon_change = 0 + else: + log.debug(f"Waiting {Client.icon_cycle_frequency - days_since_icon_change} more days to cycle icon") @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.command(name="season") -- cgit v1.2.3 From 829d32c75ae67d42da884dafc7bfc9476e9f086d Mon Sep 17 00:00:00 2001 From: ks123 Date: Mon, 24 Feb 2020 18:48:09 +0200 Subject: Added .games command with all it's subcommands, added IGDB token requirement to constants.py. --- bot/constants.py | 1 + bot/seasons/evergreen/game.py | 338 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 bot/seasons/evergreen/game.py (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index f0656926..cb3228b6 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -134,6 +134,7 @@ class Tokens(NamedTuple): omdb = environ.get("OMDB_API_KEY") youtube = environ.get("YOUTUBE_API_KEY") tmdb = environ.get("TMDB_API_KEY") + igdb = environ.get("IGDB_API_KEY") # Default role combinations diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py new file mode 100644 index 00000000..d378c34e --- /dev/null +++ b/bot/seasons/evergreen/game.py @@ -0,0 +1,338 @@ +import asyncio +import random +from datetime import datetime +from enum import IntEnum +from typing import Any, Dict, List, Tuple + +from aiohttp import ClientSession +from discord import Embed +from discord.ext.commands import Bot, Cog, Context, group + +from bot.constants import Tokens +from bot.pagination import ImagePaginator, LinePaginator + +# Base URL of IGDB API +BASE_URL = "https://api-v3.igdb.com/" +IMAGE_BASE_URL = "https://images.igdb.com/igdb/image/upload/" + +HEADERS = { + "user-key": Tokens.igdb, + "Accept": "application/json" +} + + +class GameStatus(IntEnum): + """Game statuses in IGDB API.""" + + Released = 0 + Alpha = 2 + Beta = 3 + Early = 4 + Offline = 5 + Cancelled = 6 + Rumored = 7 + + +class AgeRatingCategories(IntEnum): + """IGDB API Age Rating categories IDs.""" + + ESRB = 1 + PEGI = 2 + + +class AgeRatings(IntEnum): + """PEGI/ESRB ratings IGDB API IDs.""" + + Three = 1 + Seven = 2 + Twelve = 3 + Sixteen = 4 + Eighteen = 5 + RP = 6 + EC = 7 + E = 8 + E10 = 9 + T = 10 + M = 11 + AO = 12 + + +class Games(Cog): + """Games Cog contains commands that collect data from IGDB.""" + + def __init__(self, bot: Bot): + self.bot = bot + self.http_session: ClientSession = bot.http_session + + # Initialize genres + asyncio.get_event_loop().create_task(self._get_genres()) + + async def _get_genres(self) -> None: + """Create genres variable for games command.""" + body = "fields name; limit 100;" + async with self.http_session.get(BASE_URL + "genres", data=body, headers=HEADERS) as resp: + result = await resp.json() + + genres = {genre['name'].capitalize(): genre['id'] for genre in result} + + self.genres = {} + + # Manual check genres, replace sentences with words + for genre in genres: + if genre == "Role-playing (rpg)": + self.genres["Role-playing"] = genres[genre] + self.genres["Rpg"] = genres[genre] + elif genre == "Turn-based strategy (tbs)": + self.genres["Turn-based-strategy"] = genres[genre] + self.genres["Tbs"] = genres[genre] + elif genre == "Real time strategy (rts)": + self.genres["Real-time-strategy"] = genres[genre] + self.genres["Rts"] = genres[genre] + elif genre == "Hack and slash/beat 'em up": + self.genres["Hack-and-slash"] = genres[genre] + else: + self.genres[genre] = genres[genre] + + @group(name='games', aliases=['game'], invoke_without_command=True) + async def games(self, ctx: Context, genre: str = "", amount: int = 5) -> None: + """ + Get random game(s) by genre from IGDB. Use .movies genres command to get all available genres. + + Also support amount parameter, what max is 25 and min 1, default 5. Use quotes ("") for genres with multiple + words. + """ + # Capitalize genre for check + genre = genre.capitalize() + + # Check for amounts, max is 25 and min 1 + if amount > 25: + await ctx.send("You can't get more than 25 games at once.") + return + elif amount < 1: + await ctx.send("You can't get less than 1 game.") + return + + # Get games listing, if genre don't exist, show help. + try: + games = await self.get_games_list(self.http_session, amount, self.genres[genre], + offset=random.randint(0, 150)) + except KeyError: + await ctx.send_help('games') + return + + # Create pages and paginate + pages = await self.get_pages(games) + + await ImagePaginator.paginate(pages, ctx, Embed(title=f'Random {genre} Games')) + + @games.command(name='top', aliases=['t']) + async def top(self, ctx: Context, amount: int = 10) -> None: + """ + Get current Top games in IGDB. + + Support amount parameter. Max is 25, min is 1. + """ + if amount > 25: + await ctx.send("You can't get more than top 25 games.") + return + elif amount < 1: + await ctx.send("You can't get less than 1 top game.") + return + + games = await self.get_games_list(self.http_session, amount, sort='total_rating desc', + additional_body="where total_rating >= 90; sort total_rating_count desc;") + + pages = await self.get_pages(games) + await ImagePaginator.paginate(pages, ctx, Embed(title=f'Top {amount} Games')) + + @games.command(name='genres', aliases=['genre', 'g']) + async def genres(self, ctx: Context) -> None: + """Get all available genres.""" + await ctx.send(f"Currently available genres: {', '.join(f'`{genre}`' for genre in self.genres)}") + + @games.command(name='search', aliases=['s']) + async def search(self, ctx: Context, *, search: str) -> None: + """Find games by name.""" + lines = await self.search_games(self.http_session, search) + + await LinePaginator.paginate((line for line in lines), ctx, Embed(title=f'Game Search Results: {search}')) + + @games.command(name='company', aliases=['companies']) + async def company(self, ctx: Context, amount: int = 5) -> None: + """ + Get random Game Companies companies from IGDB API. + + Support amount parameter. Max is 25, min is 1. + """ + if amount > 25: + await ctx.send("You can't get more than 25 companies at once.") + return + elif amount < 1: + await ctx.send("You can't get less than 1 company.") + return + + companies = await self.get_companies_list(self.http_session, amount, random.randint(0, 150)) + pages = await self.get_company_pages(companies) + + await ImagePaginator.paginate(pages, ctx, Embed(title='Random Game Companies')) + + async def get_games_list(self, + client: ClientSession, + limit: int, + genre: str = None, + sort: str = None, + additional_body: str = "", + offset: int = 0) \ + -> List[Dict[str, Any]]: + """Get Games List from IGDB API.""" + # Create body of IGDB API request, define fields, sorting, offset, limit and genre + body = "fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, " + body += "status, involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, " + body += "total_rating_count;\n" + + body += f"sort {sort};\n" if sort else '' + body += f"offset {offset};\n" + body += f"limit {limit};\n" + + body += f"where genres = ({genre});" if genre else '' + body += additional_body + + # Do request to IGDB API, create headers, URL, define body, return result + async with client.get( + url=BASE_URL + "games", + data=body, + headers=HEADERS + ) as resp: + return await resp.json() + + async def get_pages(self, data: List[Dict[str, Any]]) -> List[Tuple[str, str]]: + """Generate all game pages, do additional requests to IGDB API.""" + pages = [] + [pages.append(await self.create_page(game)) for game in data] + return pages + + async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: + """Create content of Game Page.""" + # Create page content variable, what will be returned + page = "" + + # If game have cover, generate URL of Cover, if not, let url empty + if 'cover' in data: + url = f"{IMAGE_BASE_URL}t_cover_big/{data['cover']['image_id']}.jpg" + else: + url = "" + + # Add title with hyperlink and check for storyline + page += f"**[{data['name']}]({data['url']})**\n" + page += data['summary'] + "\n\n" if 'summary' in data else "\n" + + # Add release date if key is in game information + if 'first_release_date' in data: + page += f"**Release Date:** {datetime.utcfromtimestamp(data['first_release_date']).date()}\n" + + # Add other information + page += f"**Rating:** {'{0:.2f}'.format(data['total_rating']) if 'total_rating' in data else '?'}/100 " + page += f":star: (based on {data['total_rating_count'] if 'total_rating_count' in data else '?'})\n" + + page += f"**Platforms:** " + page += f"{', '.join(pf['name'] for pf in data['platforms']) if 'platforms' in data else '?'}\n" + + page += f"**Status:** {GameStatus(data['status']).name if 'status' in data else '?'}\n" + + if 'age_ratings' in data: + rating = f"""{', '.join(AgeRatingCategories(age['category']).name + ' ' + AgeRatings(age['rating']).name + for age in data['age_ratings'])}""" + page += f"**Age Ratings:** {rating}\n" + + if 'involved_companies' in data: + companies = ', '.join(co['company']['name'] for co in data['involved_companies']) + else: + companies = "?" + page += f"**Made by:** {companies}\n" + + page += "\n" + page += data['storyline'] if 'storyline' in data else '' + + return page, url + + async def search_games(self, client: ClientSession, search: str) -> List[str]: + """Search game from IGDB API by string, return listing of pages.""" + lines = [] + + # Define request body of IGDB API request and do request + body = f"""fields name, url, storyline, total_rating, total_rating_count; +search "{search}"; +limit 50;""" + + async with client.get( + url=BASE_URL + "games", + data=body, + headers=HEADERS) as resp: + data = await resp.json() + + # Loop over games, format them to good format, make line and append this to total lines + for game in data: + line = "" + + # Add games name and rating, also attach URL to title + line += f"**[{game['name']}]({game['url']})**\n" + line += f"""{'{0:.2f}'.format(game['total_rating'] if 'total_rating' in game.keys() else 0)}/100 :star: (by { + game['total_rating_count'] if 'total_rating_count' in game.keys() else '?'} users)""" + + lines.append(line) + + return lines + + async def get_companies_list(self, client: ClientSession, limit: int, offset: int = 0) -> List[Dict[str, Any]]: + """Get random Game Companies from IGDB API.""" + # Create request body, define included fields, limit and offset, do request + body = f"""fields name, url, start_date, logo.image_id, developed.name, published.name, description; +limit {limit}; +offset {offset};""" + + async with client.get( + url=BASE_URL + "companies", + data=body, + headers=HEADERS + ) as resp: + return await resp.json() + + async def get_company_pages(self, data: List[Dict[str, Any]]) -> List[Tuple[str, str]]: + """Get all good formatted pages about Game Companies.""" + pages = [] + + # Loop over companies, add them to pages listing and return pages + [pages.append(await self.create_company_page(co)) for co in data] + + return pages + + async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: + """Create good formatted Game Company page.""" + page = "" + + # Generate URL of company logo + url = f"{IMAGE_BASE_URL}t_logo_med/{data['logo']['image_id'] if 'logo' in data.keys() else ''}.png" + + # Add name and description of company, attach URL to title + page += f"[{data['name']}]({data['url']})\n" + page += data['description'] + "\n\n" if 'description' in data.keys() else '\n' + + # Add other information + if 'start_date' in data.keys(): + founded = datetime.utcfromtimestamp(data['start_date']).date() + else: + founded = "?" + page += f"**Founded:** {founded}\n" + + page += "**Developed:** " + page += f"{', '.join(game['name'] for game in data['developed']) if 'developed' in data.keys() else '?'}\n" + + page += "**Published:** " + page += f"{', '.join(game['name'] for game in data['published']) if 'published' in data.keys() else '?'}" + + return page, url + + +def setup(bot: Bot) -> None: + """Add/Load Games cog.""" + bot.add_cog(Games(bot)) -- cgit v1.2.3 From 21ea3e012f29dd9a645b1678461e6444e5efb5b0 Mon Sep 17 00:00:00 2001 From: Karlis S <45097959+ks129@users.noreply.github.com> Date: Tue, 25 Feb 2020 12:08:30 +0200 Subject: Remove keys() from total_rating count (Games Cog) Co-Authored-By: Thomas Petersson --- bot/seasons/evergreen/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index d378c34e..5f6846c2 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -277,7 +277,7 @@ limit 50;""" # Add games name and rating, also attach URL to title line += f"**[{game['name']}]({game['url']})**\n" line += f"""{'{0:.2f}'.format(game['total_rating'] if 'total_rating' in game.keys() else 0)}/100 :star: (by { - game['total_rating_count'] if 'total_rating_count' in game.keys() else '?'} users)""" + game['total_rating_count'] if 'total_rating_count' in game else '?'} users)""" lines.append(line) -- cgit v1.2.3 From bd5a4d00a7e6ee3cccc8db0654d681737a71bbad Mon Sep 17 00:00:00 2001 From: Karlis S <45097959+ks129@users.noreply.github.com> Date: Tue, 25 Feb 2020 12:08:30 +0200 Subject: Added .games command with all it's subcommands, added IGDB token requirement to constants.py. --- bot/seasons/evergreen/game.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 5f6846c2..2f701fd6 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -121,7 +121,7 @@ class Games(Cog): return # Create pages and paginate - pages = await self.get_pages(games) + pages = [await self.create_page(game) for game in games] await ImagePaginator.paginate(pages, ctx, Embed(title=f'Random {genre} Games')) @@ -172,7 +172,7 @@ class Games(Cog): return companies = await self.get_companies_list(self.http_session, amount, random.randint(0, 150)) - pages = await self.get_company_pages(companies) + pages = [await self.create_company_page(co) for co in companies] await ImagePaginator.paginate(pages, ctx, Embed(title='Random Game Companies')) @@ -205,12 +205,6 @@ class Games(Cog): ) as resp: return await resp.json() - async def get_pages(self, data: List[Dict[str, Any]]) -> List[Tuple[str, str]]: - """Generate all game pages, do additional requests to IGDB API.""" - pages = [] - [pages.append(await self.create_page(game)) for game in data] - return pages - async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create content of Game Page.""" # Create page content variable, what will be returned @@ -240,8 +234,8 @@ class Games(Cog): page += f"**Status:** {GameStatus(data['status']).name if 'status' in data else '?'}\n" if 'age_ratings' in data: - rating = f"""{', '.join(AgeRatingCategories(age['category']).name + ' ' + AgeRatings(age['rating']).name - for age in data['age_ratings'])}""" + rating = ', '.join(f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" + for age in data['age_ratings']) page += f"**Age Ratings:** {rating}\n" if 'involved_companies' in data: @@ -297,15 +291,6 @@ offset {offset};""" ) as resp: return await resp.json() - async def get_company_pages(self, data: List[Dict[str, Any]]) -> List[Tuple[str, str]]: - """Get all good formatted pages about Game Companies.""" - pages = [] - - # Loop over companies, add them to pages listing and return pages - [pages.append(await self.create_company_page(co)) for co in data] - - return pages - async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create good formatted Game Company page.""" page = "" -- cgit v1.2.3 From f22eaa7148030303e8cbf27ab92c6011d423e8b7 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Tue, 25 Feb 2020 10:12:54 -0500 Subject: Update devlog channel constant The log channels have become one --- bot/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 52a4aa20..006cf77f 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -35,7 +35,7 @@ class Channels(NamedTuple): bot = 267659945086812160 checkpoint_test = 422077681434099723 devalerts = 460181980097675264 - devlog = int(environ.get("CHANNEL_DEVLOG", 548438471685963776)) + devlog = int(environ.get("CHANNEL_DEVLOG", 622895325144940554)) devtest = 414574275865870337 help_0 = 303906576991780866 help_1 = 303906556754395136 -- cgit v1.2.3 From c1bc07f5676ba96a2f4fd458fb0c8c19e4eccda7 Mon Sep 17 00:00:00 2001 From: ks123 Date: Fri, 28 Feb 2020 12:55:19 +0200 Subject: (Games Cog): Moved layouts, request bodies and URLs to Templates. Added token check on load. Other small code improvisations. --- bot/seasons/evergreen/game.py | 375 ++++++++++++++++++++++++------------------ 1 file changed, 213 insertions(+), 162 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 2f701fd6..b52c1b93 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -1,25 +1,104 @@ -import asyncio +import difflib +import logging import random -from datetime import datetime +import textwrap +from datetime import datetime as dt from enum import IntEnum -from typing import Any, Dict, List, Tuple +from string import Template +from typing import Any, Dict, List, Optional, Tuple from aiohttp import ClientSession from discord import Embed -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import SeasonalBot from bot.constants import Tokens from bot.pagination import ImagePaginator, LinePaginator # Base URL of IGDB API -BASE_URL = "https://api-v3.igdb.com/" -IMAGE_BASE_URL = "https://images.igdb.com/igdb/image/upload/" +BASE_URL = "https://api-v3.igdb.com" HEADERS = { "user-key": Tokens.igdb, "Accept": "application/json" } +logger = logging.getLogger(__name__) + +# --------- +# TEMPLATES +# --------- + +# Body templates +# Request body template for get_games_list +GAMES_LIST_BODY = Template( + textwrap.dedent(""" + fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, status, + involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, total_rating_count; + ${sort} ${limit} ${offset} ${genre} ${additional} + """) +) + +# Request body template for get_companies_list +COMPANIES_LIST_BODY = Template( + textwrap.dedent(""" + fields name, url, start_date, logo.image_id, developed.name, published.name, description; + offset ${offset}; + limit ${limit}; + """) +) + +# Request body template for games search +SEARCH_BODY = Template('fields name, url, storyline, total_rating, total_rating_count; limit 50; search "${term}";') + +# Pages templates +# Game embed layout +GAME_PAGE = Template( + textwrap.dedent(""" + **[${name}](${url})** + ${description} + **Release Date:** ${release_date} + **Rating:** ${rating}/100 :star: (based on ${rating_count} ratings) + **Platforms:** ${platforms} + **Status:** ${status} + **Age Ratings:** ${age_ratings} + **Made by:** ${made_by} + + ${storyline} + """) +) + +# .games company command page layout +COMPANY_PAGE = Template( + textwrap.dedent(""" + **[${name}](${url})** + ${description} + **Founded:** ${founded} + **Developed:** ${developed} + **Published:** ${published} + """) +) + +# For .games search command line layout +GAME_SEARCH_LINE = Template( + textwrap.dedent(""" + **[${name}](${url})** + ${rating}/100 :star: (based on ${rating_count} ratings) + """) +) + +# URL templates +COVER_URL = Template("https://images.igdb.com/igdb/image/upload/t_cover_big/${image_id}.jpg") +LOGO_URL = Template("https://images.igdb.com/igdb/image/upload/t_logo_med/${image_id}.png") + +# Create aliases for complex genre names +ALIASES = { + "Role-playing (rpg)": ["Role-playing", "Rpg"], + "Turn-based strategy (tbs)": ["Turn-based-strategy", "Tbs"], + "Real time strategy (rts)": ["Real-time-strategy", "Rts"], + "Hack and slash/beat 'em up": ["Hack-and-slash"] +} + class GameStatus(IntEnum): """Game statuses in IGDB API.""" @@ -60,264 +139,236 @@ class AgeRatings(IntEnum): class Games(Cog): """Games Cog contains commands that collect data from IGDB.""" - def __init__(self, bot: Bot): + def __init__(self, bot: SeasonalBot): self.bot = bot self.http_session: ClientSession = bot.http_session # Initialize genres - asyncio.get_event_loop().create_task(self._get_genres()) + bot.loop.create_task(self._get_genres()) async def _get_genres(self) -> None: """Create genres variable for games command.""" body = "fields name; limit 100;" - async with self.http_session.get(BASE_URL + "genres", data=body, headers=HEADERS) as resp: + async with self.http_session.get(f"{BASE_URL}/genres", data=body, headers=HEADERS) as resp: result = await resp.json() - genres = {genre['name'].capitalize(): genre['id'] for genre in result} + genres = {genre["name"].capitalize(): genre["id"] for genre in result} self.genres = {} - # Manual check genres, replace sentences with words + # Replace complex names with names from ALIASES for genre in genres: - if genre == "Role-playing (rpg)": - self.genres["Role-playing"] = genres[genre] - self.genres["Rpg"] = genres[genre] - elif genre == "Turn-based strategy (tbs)": - self.genres["Turn-based-strategy"] = genres[genre] - self.genres["Tbs"] = genres[genre] - elif genre == "Real time strategy (rts)": - self.genres["Real-time-strategy"] = genres[genre] - self.genres["Rts"] = genres[genre] - elif genre == "Hack and slash/beat 'em up": - self.genres["Hack-and-slash"] = genres[genre] + if genre in ALIASES: + for alias in ALIASES[genre]: + self.genres[alias] = genres[genre] else: self.genres[genre] = genres[genre] - @group(name='games', aliases=['game'], invoke_without_command=True) - async def games(self, ctx: Context, genre: str = "", amount: int = 5) -> None: + @group(name="games", aliases=["game"], invoke_without_command=True) + async def games(self, ctx: Context, genre: Optional[str] = None, amount: int = 5) -> None: """ - Get random game(s) by genre from IGDB. Use .movies genres command to get all available genres. + Get random game(s) by genre from IGDB. Use .games genres command to get all available genres. Also support amount parameter, what max is 25 and min 1, default 5. Use quotes ("") for genres with multiple words. """ + # When user didn't specified genre, send help message + if genre is None: + await ctx.send_help("games") + return + # Capitalize genre for check genre = genre.capitalize() # Check for amounts, max is 25 and min 1 - if amount > 25: - await ctx.send("You can't get more than 25 games at once.") - return - elif amount < 1: - await ctx.send("You can't get less than 1 game.") + if not 1 <= amount <= 25: + await ctx.send("Your provided amount is out of range. Our minimum is 1 and maximum 25.") return - # Get games listing, if genre don't exist, show help. + # Get games listing, if genre don't exist, show error message with possibilities. try: - games = await self.get_games_list(self.http_session, amount, self.genres[genre], + games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) except KeyError: - await ctx.send_help('games') + possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres)) + await ctx.send(f"Invalid genre `{genre}`. {f'Maybe you meant `{possibilities}`?' if possibilities else ''}") return # Create pages and paginate pages = [await self.create_page(game) for game in games] - await ImagePaginator.paginate(pages, ctx, Embed(title=f'Random {genre} Games')) + await ImagePaginator.paginate(pages, ctx, Embed(title=f"Random {genre} Games")) - @games.command(name='top', aliases=['t']) + @games.command(name="top", aliases=["t"]) async def top(self, ctx: Context, amount: int = 10) -> None: """ Get current Top games in IGDB. Support amount parameter. Max is 25, min is 1. """ - if amount > 25: - await ctx.send("You can't get more than top 25 games.") - return - elif amount < 1: - await ctx.send("You can't get less than 1 top game.") + if not 1 <= amount <= 25: + await ctx.send("Your provided amount is out of range. Our minimum is 1 and maximum 25.") return - games = await self.get_games_list(self.http_session, amount, sort='total_rating desc', + games = await self.get_games_list(amount, sort="total_rating desc", additional_body="where total_rating >= 90; sort total_rating_count desc;") - pages = await self.get_pages(games) - await ImagePaginator.paginate(pages, ctx, Embed(title=f'Top {amount} Games')) + pages = [await self.create_page(game) for game in games] + await ImagePaginator.paginate(pages, ctx, Embed(title=f"Top {amount} Games")) - @games.command(name='genres', aliases=['genre', 'g']) + @games.command(name="genres", aliases=["genre", "g"]) async def genres(self, ctx: Context) -> None: """Get all available genres.""" await ctx.send(f"Currently available genres: {', '.join(f'`{genre}`' for genre in self.genres)}") - @games.command(name='search', aliases=['s']) - async def search(self, ctx: Context, *, search: str) -> None: + @games.command(name="search", aliases=["s"]) + async def search(self, ctx: Context, *, search_term: str) -> None: """Find games by name.""" - lines = await self.search_games(self.http_session, search) + lines = await self.search_games(search_term) - await LinePaginator.paginate((line for line in lines), ctx, Embed(title=f'Game Search Results: {search}')) + await LinePaginator.paginate((line for line in lines), ctx, Embed(title=f"Game Search Results: {search_term}")) - @games.command(name='company', aliases=['companies']) + @games.command(name="company", aliases=["companies"]) async def company(self, ctx: Context, amount: int = 5) -> None: """ Get random Game Companies companies from IGDB API. Support amount parameter. Max is 25, min is 1. """ - if amount > 25: - await ctx.send("You can't get more than 25 companies at once.") - return - elif amount < 1: - await ctx.send("You can't get less than 1 company.") + if not 1 <= amount <= 25: + await ctx.send("Your provided amount is out of range. Our minimum is 1 and maximum 25.") return - companies = await self.get_companies_list(self.http_session, amount, random.randint(0, 150)) + companies = await self.get_companies_list(amount, random.randint(0, 150)) pages = [await self.create_company_page(co) for co in companies] - await ImagePaginator.paginate(pages, ctx, Embed(title='Random Game Companies')) + await ImagePaginator.paginate(pages, ctx, Embed(title="Random Game Companies")) async def get_games_list(self, - client: ClientSession, - limit: int, - genre: str = None, - sort: str = None, + amount: int, + genre: Optional[str] = None, + sort: Optional[str] = None, additional_body: str = "", - offset: int = 0) \ - -> List[Dict[str, Any]]: - """Get Games List from IGDB API.""" - # Create body of IGDB API request, define fields, sorting, offset, limit and genre - body = "fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, " - body += "status, involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, " - body += "total_rating_count;\n" - - body += f"sort {sort};\n" if sort else '' - body += f"offset {offset};\n" - body += f"limit {limit};\n" + offset: int = 0 + ) -> List[Dict[str, Any]]: + """ + Get list of games from IGDB API by parameters that is provided. - body += f"where genres = ({genre});" if genre else '' - body += additional_body + Amount param show how much movies this get, genre is genre ID and at least one genre in game must this when + provided. Sort is sorting by specific field and direction, ex. total_rating desc/asc (total_rating is field, + desc/asc is direction). Additional_body is field where you can pass extra search parameters. Offset show start + position in API. + """ + # Create body of IGDB API request, define fields, sorting, offset, limit and genre + params = { + "sort": f"sort {sort};" if sort else "", + "limit": f"limit {amount};", + "offset": f"offset {offset};" if offset else "", + "genre": f"where genres = ({genre});" if genre else "", + "additional": additional_body + } + body = GAMES_LIST_BODY.substitute(params) # Do request to IGDB API, create headers, URL, define body, return result - async with client.get( - url=BASE_URL + "games", - data=body, - headers=HEADERS - ) as resp: + async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=HEADERS) as resp: return await resp.json() async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create content of Game Page.""" - # Create page content variable, what will be returned - page = "" - - # If game have cover, generate URL of Cover, if not, let url empty - if 'cover' in data: - url = f"{IMAGE_BASE_URL}t_cover_big/{data['cover']['image_id']}.jpg" - else: - url = "" - - # Add title with hyperlink and check for storyline - page += f"**[{data['name']}]({data['url']})**\n" - page += data['summary'] + "\n\n" if 'summary' in data else "\n" - - # Add release date if key is in game information - if 'first_release_date' in data: - page += f"**Release Date:** {datetime.utcfromtimestamp(data['first_release_date']).date()}\n" - - # Add other information - page += f"**Rating:** {'{0:.2f}'.format(data['total_rating']) if 'total_rating' in data else '?'}/100 " - page += f":star: (based on {data['total_rating_count'] if 'total_rating_count' in data else '?'})\n" - - page += f"**Platforms:** " - page += f"{', '.join(pf['name'] for pf in data['platforms']) if 'platforms' in data else '?'}\n" - - page += f"**Status:** {GameStatus(data['status']).name if 'status' in data else '?'}\n" - - if 'age_ratings' in data: - rating = ', '.join(f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" - for age in data['age_ratings']) - page += f"**Age Ratings:** {rating}\n" - - if 'involved_companies' in data: - companies = ', '.join(co['company']['name'] for co in data['involved_companies']) - else: - companies = "?" - page += f"**Made by:** {companies}\n" - - page += "\n" - page += data['storyline'] if 'storyline' in data else '' + # Create cover image URL from template + url = COVER_URL.substitute({"image_id": data["cover"]["image_id"] if "cover" in data else ""}) + + # Get release date separately with checking + release_date = dt.utcfromtimestamp(data["first_release_date"]).date() if "first_release_date" in data else "?" + + # Create Age Ratings value + rating = ", ".join(f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" + for age in data["age_ratings"]) if "age_ratings" in data else "?" + + companies = ", ".join(comp["company"]["name"] for comp in data["involved_companies"]) \ + if "involved_companies" in data else "?" + + # Create formatting for template page + formatting = { + "name": data["name"], + "url": data["url"], + "description": f"{data['summary']}\n\n" if "summary" in data else "\n", + "release_date": release_date, + "rating": round(data["total_rating"] if "total_rating" in data else 0, 2), + "rating_count": data["total_rating_count"] if "total_rating_count" in data else "?", + "platforms": ", ".join(platform["name"] for platform in data["platforms"]) if "platforms" in data else "?", + "status": GameStatus(data["status"]).name if "status" in data else "?", + "age_ratings": rating, + "made_by": companies, + "storyline": data["storyline"] if "storyline" in data else "" + } + page = GAME_PAGE.substitute(formatting) return page, url - async def search_games(self, client: ClientSession, search: str) -> List[str]: + async def search_games(self, search_term: str) -> List[str]: """Search game from IGDB API by string, return listing of pages.""" lines = [] # Define request body of IGDB API request and do request - body = f"""fields name, url, storyline, total_rating, total_rating_count; -search "{search}"; -limit 50;""" - - async with client.get( - url=BASE_URL + "games", - data=body, - headers=HEADERS) as resp: + body = SEARCH_BODY.substitute({"term": search_term}) + + async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=HEADERS) as resp: data = await resp.json() # Loop over games, format them to good format, make line and append this to total lines for game in data: - line = "" - - # Add games name and rating, also attach URL to title - line += f"**[{game['name']}]({game['url']})**\n" - line += f"""{'{0:.2f}'.format(game['total_rating'] if 'total_rating' in game.keys() else 0)}/100 :star: (by { - game['total_rating_count'] if 'total_rating_count' in game else '?'} users)""" - + formatting = { + "name": game["name"], + "url": game["url"], + "rating": round(game["total_rating"] if "total_rating" in game else 0, 2), + "rating_count": game["total_rating_count"] if "total_rating" in game else "?" + } + line = GAME_SEARCH_LINE.substitute(formatting) lines.append(line) return lines - async def get_companies_list(self, client: ClientSession, limit: int, offset: int = 0) -> List[Dict[str, Any]]: + async def get_companies_list(self, limit: int, offset: int = 0) -> List[Dict[str, Any]]: """Get random Game Companies from IGDB API.""" - # Create request body, define included fields, limit and offset, do request - body = f"""fields name, url, start_date, logo.image_id, developed.name, published.name, description; -limit {limit}; -offset {offset};""" - - async with client.get( - url=BASE_URL + "companies", - data=body, - headers=HEADERS - ) as resp: + # Create request body from template + body = COMPANIES_LIST_BODY.substitute({ + "limit": limit, + "offset": offset + }) + + async with self.http_session.get(url=f"{BASE_URL}/companies", data=body, headers=HEADERS) as resp: return await resp.json() async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create good formatted Game Company page.""" - page = "" - # Generate URL of company logo - url = f"{IMAGE_BASE_URL}t_logo_med/{data['logo']['image_id'] if 'logo' in data.keys() else ''}.png" - - # Add name and description of company, attach URL to title - page += f"[{data['name']}]({data['url']})\n" - page += data['description'] + "\n\n" if 'description' in data.keys() else '\n' + url = LOGO_URL.substitute({"image_id": data["logo"]["image_id"] if "logo" in data else ""}) - # Add other information - if 'start_date' in data.keys(): - founded = datetime.utcfromtimestamp(data['start_date']).date() - else: - founded = "?" - page += f"**Founded:** {founded}\n" + # Try to get found date of company + founded = dt.utcfromtimestamp(data["start_date"]).date() if "start_date" in data else "?" - page += "**Developed:** " - page += f"{', '.join(game['name'] for game in data['developed']) if 'developed' in data.keys() else '?'}\n" + # Generate list of games, that company have developed or published + developed = ", ".join(game["name"] for game in data["developed"]) if "developed" in data else "?" + published = ", ".join(game["name"] for game in data["published"]) if "published" in data else "?" - page += "**Published:** " - page += f"{', '.join(game['name'] for game in data['published']) if 'published' in data.keys() else '?'}" + formatting = { + "name": data["name"], + "url": data["url"], + "description": f"{data['description']}\n\n" if "description" in data else "\n", + "founded": founded, + "developed": developed, + "published": published + } + page = COMPANY_PAGE.substitute(formatting) return page, url -def setup(bot: Bot) -> None: +def setup(bot: SeasonalBot) -> None: """Add/Load Games cog.""" + # Check does IGDB API key exist, if not, log warning and don't load cog + if not Tokens.igdb: + logger.warning("No IGDB API key. Not loading Games cog.") + return bot.add_cog(Games(bot)) -- cgit v1.2.3 From 10b63edcefa3dc53c833c0ce383b003823f7fa7c Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Sun, 1 Mar 2020 18:57:39 -0500 Subject: Strip references to seasonalbot chat Redirect any output to seasonalbot commands, where relevant --- bot/constants.py | 1 - bot/seasons/easter/__init__.py | 2 +- bot/seasons/easter/egg_facts.py | 2 +- bot/seasons/evergreen/issues.py | 5 ++--- bot/seasons/halloween/candy_collection.py | 8 ++++---- bot/seasons/halloween/halloween_facts.py | 2 +- bot/seasons/pride/__init__.py | 2 +- bot/seasons/pride/pride_facts.py | 2 +- bot/seasons/valentines/be_my_valentine.py | 2 +- docker-compose.yml | 1 - 10 files changed, 12 insertions(+), 15 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 006cf77f..d406bbd0 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -52,7 +52,6 @@ class Channels(NamedTuple): off_topic_2 = 463035268514185226 python = 267624335836053506 reddit = 458224812528238616 - seasonalbot_chat = int(environ.get("CHANNEL_SEASONALBOT_CHAT", 542272993192050698)) seasonalbot_commands = int(environ.get("CHANNEL_SEASONALBOT_COMMANDS", 607247579608121354)) seasonalbot_voice = int(environ.get("CHANNEL_SEASONALBOT_VOICE", 606259004230074378)) staff_lounge = 464905259261755392 diff --git a/bot/seasons/easter/__init__.py b/bot/seasons/easter/__init__.py index 1d77b6a6..dd60bf5c 100644 --- a/bot/seasons/easter/__init__.py +++ b/bot/seasons/easter/__init__.py @@ -16,7 +16,7 @@ class Easter(SeasonBase): • You may see stuff like an Easter themed esoteric challenge, a celebration of Earth Day, or Easter-related micro-events for you to join. Stay tuned! - If you'd like to contribute, head on over to <#542272993192050698> and we will help you get + If you'd like to contribute, head on over to <#635950537262759947> and we will help you get started. It doesn't matter if you're new to open source or Python, if you'd like to help, we will find you a task and teach you what you need to know. """ diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 9e6fb1cb..e66e25a3 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -34,7 +34,7 @@ class EasterFacts(commands.Cog): async def send_egg_fact_daily(self) -> None: """A background task that sends an easter egg fact in the event channel everyday.""" - channel = self.bot.get_channel(Channels.seasonalbot_chat) + channel = self.bot.get_channel(Channels.seasonalbot_commands) while True: embed = self.make_embed() await channel.send(embed=embed) diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index c7501a5d..fba5b174 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -3,11 +3,10 @@ import logging import discord from discord.ext import commands -from bot.constants import Channels, Colours, Emojis, WHITELISTED_CHANNELS +from bot.constants import Colours, Emojis, WHITELISTED_CHANNELS from bot.decorators import override_in_channel log = logging.getLogger(__name__) -ISSUE_WHITELIST = WHITELISTED_CHANNELS + (Channels.seasonalbot_chat,) BAD_RESPONSE = { 404: "Issue/pull request not located! Please enter a valid number!", @@ -22,7 +21,7 @@ class Issues(commands.Cog): self.bot = bot @commands.command(aliases=("pr",)) - @override_in_channel(ISSUE_WHITELIST) + @override_in_channel(WHITELISTED_CHANNELS) async def issue( self, ctx: commands.Context, number: int, repository: str = "seasonalbot", user: str = "python-discord" ) -> None: diff --git a/bot/seasons/halloween/candy_collection.py b/bot/seasons/halloween/candy_collection.py index 64da7ced..490609dd 100644 --- a/bot/seasons/halloween/candy_collection.py +++ b/bot/seasons/halloween/candy_collection.py @@ -41,7 +41,7 @@ class CandyCollection(commands.Cog): if message.author.bot: return # ensure it's hacktober channel - if message.channel.id != Channels.seasonalbot_chat: + if message.channel.id != Channels.seasonalbot_commands: return # do random check for skull first as it has the lower chance @@ -64,7 +64,7 @@ class CandyCollection(commands.Cog): return # check to ensure it is in correct channel - if message.channel.id != Channels.seasonalbot_chat: + if message.channel.id != Channels.seasonalbot_commands: return # if its not a candy or skull, and it is one of 10 most recent messages, @@ -124,7 +124,7 @@ class CandyCollection(commands.Cog): ten_recent = [] recent_msg_id = max( message.id for message in self.bot._connection._messages - if message.channel.id == Channels.seasonalbot_chat + if message.channel.id == Channels.seasonalbot_commands ) channel = await self.hacktober_channel() @@ -155,7 +155,7 @@ class CandyCollection(commands.Cog): async def hacktober_channel(self) -> discord.TextChannel: """Get #hacktoberbot channel from its ID.""" - return self.bot.get_channel(id=Channels.seasonalbot_chat) + return self.bot.get_channel(id=Channels.seasonalbot_commands) async def remove_reactions(self, reaction: discord.Reaction) -> None: """Remove all candy/skull reactions.""" diff --git a/bot/seasons/halloween/halloween_facts.py b/bot/seasons/halloween/halloween_facts.py index f8610bd3..94730d9e 100644 --- a/bot/seasons/halloween/halloween_facts.py +++ b/bot/seasons/halloween/halloween_facts.py @@ -40,7 +40,7 @@ class HalloweenFacts(commands.Cog): @commands.Cog.listener() async def on_ready(self) -> None: """Get event Channel object and initialize fact task loop.""" - self.channel = self.bot.get_channel(Channels.seasonalbot_chat) + self.channel = self.bot.get_channel(Channels.seasonalbot_commands) self.bot.loop.create_task(self._fact_publisher_task()) def random_fact(self) -> Tuple[int, str]: diff --git a/bot/seasons/pride/__init__.py b/bot/seasons/pride/__init__.py index 75e90b2a..08df2fa1 100644 --- a/bot/seasons/pride/__init__.py +++ b/bot/seasons/pride/__init__.py @@ -16,7 +16,7 @@ class Pride(SeasonBase): • [Pride issues are now available for SeasonalBot on the repo](https://git.io/pythonpride). • You may see Pride-themed esoteric challenges and other microevents. - If you'd like to contribute, head on over to <#542272993192050698> and we will help you get + If you'd like to contribute, head on over to <#635950537262759947> and we will help you get started. It doesn't matter if you're new to open source or Python, if you'd like to help, we will find you a task and teach you what you need to know. """ diff --git a/bot/seasons/pride/pride_facts.py b/bot/seasons/pride/pride_facts.py index b705bfb4..5c19dfd0 100644 --- a/bot/seasons/pride/pride_facts.py +++ b/bot/seasons/pride/pride_facts.py @@ -33,7 +33,7 @@ class PrideFacts(commands.Cog): async def send_pride_fact_daily(self) -> None: """Background task to post the daily pride fact every day.""" - channel = self.bot.get_channel(Channels.seasonalbot_chat) + channel = self.bot.get_channel(Channels.seasonalbot_commands) while True: await self.send_select_fact(channel, datetime.utcnow()) await asyncio.sleep(24 * 60 * 60) diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index a073e1bd..de97cb4e 100644 --- a/bot/seasons/valentines/be_my_valentine.py +++ b/bot/seasons/valentines/be_my_valentine.py @@ -96,7 +96,7 @@ class BeMyValentine(commands.Cog): emoji_1, emoji_2 = self.random_emoji() lovefest_role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) - channel = self.bot.get_channel(Channels.seasonalbot_chat) + channel = self.bot.get_channel(Channels.seasonalbot_commands) valentine, title = self.valentine_check(valentine_type) if user is None: diff --git a/docker-compose.yml b/docker-compose.yml index f2f4b056..30e8a109 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,6 @@ services: # - SEASONALBOT_ADMIN_ROLE_ID= # - CHANNEL_ANNOUNCEMENTS= # - CHANNEL_DEVLOG= - # - CHANNEL_SEASONALBOT_CHAT= # - SEASON_OVERRIDE= volumes: -- cgit v1.2.3 From b0fb34d553663fd71121a005a9b6f7651da8e90a Mon Sep 17 00:00:00 2001 From: ks123 Date: Mon, 2 Mar 2020 17:46:11 +0200 Subject: (Games Cog): Fixed and added content to docstrings. --- bot/seasons/evergreen/game.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index b52c1b93..46fdc689 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -254,7 +254,7 @@ class Games(Cog): """ Get list of games from IGDB API by parameters that is provided. - Amount param show how much movies this get, genre is genre ID and at least one genre in game must this when + Amount param show how much games this get, genre is genre ID and at least one genre in game must this when provided. Sort is sorting by specific field and direction, ex. total_rating desc/asc (total_rating is field, desc/asc is direction). Additional_body is field where you can pass extra search parameters. Offset show start position in API. @@ -330,7 +330,12 @@ class Games(Cog): return lines async def get_companies_list(self, limit: int, offset: int = 0) -> List[Dict[str, Any]]: - """Get random Game Companies from IGDB API.""" + """ + Get random Game Companies from IGDB API. + + Limit is parameter, that show how much movies this should return, offset show in which position should API start + returning results. + """ # Create request body from template body = COMPANIES_LIST_BODY.substitute({ "limit": limit, -- cgit v1.2.3 From d4bbf9a7a548341c77f55039e4a7689e1f1ee6ac Mon Sep 17 00:00:00 2001 From: ks123 Date: Mon, 2 Mar 2020 17:51:02 +0200 Subject: (Games Cog): Added comments about offsets, use keyword parameters for get_companies_list. --- bot/seasons/evergreen/game.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 46fdc689..08068c23 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -186,6 +186,8 @@ class Games(Cog): return # Get games listing, if genre don't exist, show error message with possibilities. + # Offset must be random, due otherwise we will get always same result (offset show in which position should + # API start returning result) try: games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) @@ -239,7 +241,9 @@ class Games(Cog): await ctx.send("Your provided amount is out of range. Our minimum is 1 and maximum 25.") return - companies = await self.get_companies_list(amount, random.randint(0, 150)) + # Get companies listing. Provide limit for limiting how much companies will be returned. Get random offset to + # get (almost) every time different companies (offset show in which position should API start returning result) + companies = await self.get_companies_list(limit=amount, offset=random.randint(0, 150)) pages = [await self.create_company_page(co) for co in companies] await ImagePaginator.paginate(pages, ctx, Embed(title="Random Game Companies")) -- cgit v1.2.3 From 9e0662a1adbbcf291d3b935dd08dbf7cc68a6e16 Mon Sep 17 00:00:00 2001 From: ks123 Date: Mon, 2 Mar 2020 18:44:40 +0200 Subject: (Games Cog): Fixed companies list generating code (.games command). --- bot/seasons/evergreen/game.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 08068c23..b2114da8 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -289,8 +289,7 @@ class Games(Cog): rating = ", ".join(f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" for age in data["age_ratings"]) if "age_ratings" in data else "?" - companies = ", ".join(comp["company"]["name"] for comp in data["involved_companies"]) \ - if "involved_companies" in data else "?" + companies = [c["company"]["name"] for c in data["involved_companies"]] if "involved_companies" in data else "?" # Create formatting for template page formatting = { @@ -303,7 +302,7 @@ class Games(Cog): "platforms": ", ".join(platform["name"] for platform in data["platforms"]) if "platforms" in data else "?", "status": GameStatus(data["status"]).name if "status" in data else "?", "age_ratings": rating, - "made_by": companies, + "made_by": ", ".join(companies), "storyline": data["storyline"] if "storyline" in data else "" } page = GAME_PAGE.substitute(formatting) -- cgit v1.2.3 From c5ec3e129cad08a3b67cc396e445d92bdfac560e Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 09:19:55 +0200 Subject: (Games Cog): Fixed get_games_list calling formatting at L192 --- bot/seasons/evergreen/game.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index b2114da8..7a7dc02f 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -189,8 +189,7 @@ class Games(Cog): # Offset must be random, due otherwise we will get always same result (offset show in which position should # API start returning result) try: - games = await self.get_games_list(amount, self.genres[genre], - offset=random.randint(0, 150)) + games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) except KeyError: possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres)) await ctx.send(f"Invalid genre `{genre}`. {f'Maybe you meant `{possibilities}`?' if possibilities else ''}") -- cgit v1.2.3 From d89c37462b1917ea45dfa082a7b7b1daa09e4baa Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 09:22:41 +0200 Subject: (Games Cog): Fixed _get_genres function looping over genres (started using dict.items()) --- bot/seasons/evergreen/game.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 7a7dc02f..8f55a1f3 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -157,12 +157,12 @@ class Games(Cog): self.genres = {} # Replace complex names with names from ALIASES - for genre in genres: - if genre in ALIASES: - for alias in ALIASES[genre]: - self.genres[alias] = genres[genre] + for genre_name, genre in genres.items(): + if genre_name in ALIASES: + for alias in ALIASES[genre_name]: + self.genres[alias] = genre else: - self.genres[genre] = genres[genre] + self.genres[genre_name] = genre @group(name="games", aliases=["game"], invoke_without_command=True) async def games(self, ctx: Context, genre: Optional[str] = None, amount: int = 5) -> None: -- cgit v1.2.3 From 2fe0cdef64e6d6e49f583f9fd02c2f5f8b0a25ed Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 09:28:44 +0200 Subject: (Games Cog): Created task for fetching genres (every hour) --- bot/seasons/evergreen/game.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 8f55a1f3..727985a3 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Tuple from aiohttp import ClientSession from discord import Embed +from discord.ext import tasks from discord.ext.commands import Cog, Context, group from bot.bot import SeasonalBot @@ -143,8 +144,12 @@ class Games(Cog): self.bot = bot self.http_session: ClientSession = bot.http_session - # Initialize genres - bot.loop.create_task(self._get_genres()) + self.refresh_genres_task.start() + + @tasks.loop(hours=1.0) + async def refresh_genres_task(self) -> None: + """Refresh genres in every hour.""" + await self._get_genres() async def _get_genres(self) -> None: """Create genres variable for games command.""" -- cgit v1.2.3 From 69941d874d708defaa20e30fe4afada37b72ad2a Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 09:54:15 +0200 Subject: (Games Cog): Added .games refresh|r command for refreshing genres. --- bot/seasons/evergreen/game.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 727985a3..de8d0109 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -13,7 +13,8 @@ from discord.ext import tasks from discord.ext.commands import Cog, Context, group from bot.bot import SeasonalBot -from bot.constants import Tokens +from bot.constants import STAFF_ROLES, Tokens +from bot.decorators import with_role from bot.pagination import ImagePaginator, LinePaginator # Base URL of IGDB API @@ -252,6 +253,17 @@ class Games(Cog): await ImagePaginator.paginate(pages, ctx, Embed(title="Random Game Companies")) + @with_role(*STAFF_ROLES) + @games.command(name="refresh", aliases=["r"]) + async def refresh_genres_command(self, ctx: Context) -> None: + """Refresh .games command genres.""" + try: + await self._get_genres() + except Exception as e: + await ctx.send(f"There was error while refreshing genres: `{e}`") + return + await ctx.send("Successfully refreshed genres.") + async def get_games_list(self, amount: int, genre: Optional[str] = None, -- cgit v1.2.3 From ae500f85bd664bd59d818866a89b3533bc9e94b1 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 10:06:48 +0200 Subject: (Games Cog): Added try block to genres refresh task. --- bot/seasons/evergreen/game.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index de8d0109..21c3b5d7 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -150,7 +150,12 @@ class Games(Cog): @tasks.loop(hours=1.0) async def refresh_genres_task(self) -> None: """Refresh genres in every hour.""" - await self._get_genres() + try: + await self._get_genres() + except Exception as e: + logger.warning(f"There was error while refreshing genres: {e}") + return + logger.info("Successfully refreshed genres.") async def _get_genres(self) -> None: """Create genres variable for games command.""" -- cgit v1.2.3 From 918c11fcba1a47f78ce17eb7278da294d95e314c Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 13:36:16 +0200 Subject: (Games Cog): Stop refreshing genres task when Cog unload --- bot/seasons/evergreen/game.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 21c3b5d7..97df2bdb 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -157,6 +157,11 @@ class Games(Cog): return logger.info("Successfully refreshed genres.") + def cog_unload(self) -> None: + """Cancel genres refreshing start when unloading Cog.""" + self.refresh_genres_task.cancel() + logger.info("Successfully stopped Genres Refreshing task.") + async def _get_genres(self) -> None: """Create genres variable for games command.""" body = "fields name; limit 100;" -- cgit v1.2.3 From 75c3024209f62c327f002cefed9b148e31b98479 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 13:49:55 +0200 Subject: (Games Cog): Remove too much empty lines in .games search command, simplify lines. --- bot/seasons/evergreen/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 97df2bdb..7774484e 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -243,7 +243,7 @@ class Games(Cog): """Find games by name.""" lines = await self.search_games(search_term) - await LinePaginator.paginate((line for line in lines), ctx, Embed(title=f"Game Search Results: {search_term}")) + await LinePaginator.paginate(lines, ctx, Embed(title=f"Game Search Results: {search_term}"), empty=False) @games.command(name="company", aliases=["companies"]) async def company(self, ctx: Context, amount: int = 5) -> None: -- cgit v1.2.3 From 4b2cc3910e535158626722a0168a7b2202b0d826 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 14:40:07 +0200 Subject: (Games Cog): Replaced - with space in genre aliases, added multiword genres support for .games command, modified docstring to explain this and added str.title() to embed title genre showing. --- bot/seasons/evergreen/game.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 7774484e..957e1195 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -95,10 +95,10 @@ LOGO_URL = Template("https://images.igdb.com/igdb/image/upload/t_logo_med/${imag # Create aliases for complex genre names ALIASES = { - "Role-playing (rpg)": ["Role-playing", "Rpg"], - "Turn-based strategy (tbs)": ["Turn-based-strategy", "Tbs"], - "Real time strategy (rts)": ["Real-time-strategy", "Rts"], - "Hack and slash/beat 'em up": ["Hack-and-slash"] + "Role-playing (rpg)": ["Role playing", "Rpg"], + "Turn-based strategy (tbs)": ["Turn based strategy", "Tbs"], + "Real time strategy (rts)": ["Real time strategy", "Rts"], + "Hack and slash/beat 'em up": ["Hack and slash"] } @@ -181,12 +181,13 @@ class Games(Cog): self.genres[genre_name] = genre @group(name="games", aliases=["game"], invoke_without_command=True) - async def games(self, ctx: Context, genre: Optional[str] = None, amount: int = 5) -> None: + async def games(self, ctx: Context, amount: Optional[int] = 5, *, genre: Optional[str] = None) -> None: """ Get random game(s) by genre from IGDB. Use .games genres command to get all available genres. - Also support amount parameter, what max is 25 and min 1, default 5. Use quotes ("") for genres with multiple - words. + Also support amount parameter, what max is 25 and min 1, default 5. Supported formats: + - .games + - .games """ # When user didn't specified genre, send help message if genre is None: @@ -194,7 +195,7 @@ class Games(Cog): return # Capitalize genre for check - genre = genre.capitalize() + genre = "".join(genre).capitalize() # Check for amounts, max is 25 and min 1 if not 1 <= amount <= 25: @@ -214,7 +215,7 @@ class Games(Cog): # Create pages and paginate pages = [await self.create_page(game) for game in games] - await ImagePaginator.paginate(pages, ctx, Embed(title=f"Random {genre} Games")) + await ImagePaginator.paginate(pages, ctx, Embed(title=f"Random {genre.title()} Games")) @games.command(name="top", aliases=["t"]) async def top(self, ctx: Context, amount: int = 10) -> None: -- cgit v1.2.3 From ea8484858d849212d8ce1ae191bc1d1178859cc2 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 15:09:54 +0200 Subject: (Games Cog): Moved self.genres to __init__ and added type hints. Added lower `difflib.get_close_matches` cutoff from 0.6 (default) to 0.4. --- bot/seasons/evergreen/game.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 957e1195..65598c17 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -145,6 +145,8 @@ class Games(Cog): self.bot = bot self.http_session: ClientSession = bot.http_session + self.genres: Dict[str, int] = {} + self.refresh_genres_task.start() @tasks.loop(hours=1.0) @@ -170,8 +172,6 @@ class Games(Cog): genres = {genre["name"].capitalize(): genre["id"] for genre in result} - self.genres = {} - # Replace complex names with names from ALIASES for genre_name, genre in genres.items(): if genre_name in ALIASES: @@ -208,7 +208,7 @@ class Games(Cog): try: games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) except KeyError: - possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres)) + possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres, cutoff=0.4)) await ctx.send(f"Invalid genre `{genre}`. {f'Maybe you meant `{possibilities}`?' if possibilities else ''}") return -- cgit v1.2.3 From 3b2926e50368c8cb547b7d93741401268980cb48 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 15:43:58 +0200 Subject: (Games Cog): Moved `string.Template` to `str.format()`, applied changes everywhere. --- bot/seasons/evergreen/game.py | 88 ++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 51 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 65598c17..bb48123d 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -1,10 +1,8 @@ import difflib import logging import random -import textwrap from datetime import datetime as dt from enum import IntEnum -from string import Template from typing import Any, Dict, List, Optional, Tuple from aiohttp import ClientSession @@ -33,65 +31,53 @@ logger = logging.getLogger(__name__) # Body templates # Request body template for get_games_list -GAMES_LIST_BODY = Template( - textwrap.dedent(""" - fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, status, - involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, total_rating_count; - ${sort} ${limit} ${offset} ${genre} ${additional} - """) +GAMES_LIST_BODY = ( + "fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, status," + "involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, total_rating_count;" + "{sort} {limit} {offset} {genre} {additional}" ) # Request body template for get_companies_list -COMPANIES_LIST_BODY = Template( - textwrap.dedent(""" - fields name, url, start_date, logo.image_id, developed.name, published.name, description; - offset ${offset}; - limit ${limit}; - """) +COMPANIES_LIST_BODY = ( + "fields name, url, start_date, logo.image_id, developed.name, published.name, description;" + "offset {offset}; limit {limit};" ) # Request body template for games search -SEARCH_BODY = Template('fields name, url, storyline, total_rating, total_rating_count; limit 50; search "${term}";') +SEARCH_BODY = 'fields name, url, storyline, total_rating, total_rating_count; limit 50; search "{term}";' # Pages templates # Game embed layout -GAME_PAGE = Template( - textwrap.dedent(""" - **[${name}](${url})** - ${description} - **Release Date:** ${release_date} - **Rating:** ${rating}/100 :star: (based on ${rating_count} ratings) - **Platforms:** ${platforms} - **Status:** ${status} - **Age Ratings:** ${age_ratings} - **Made by:** ${made_by} - - ${storyline} - """) +GAME_PAGE = ( + "**[{name}]({url})**\n" + "{description}" + "**Release Date:** {release_date}\n" + "**Rating:** {rating}/100 :star: (based on {rating_count} ratings)\n" + "**Platforms:** {platforms}\n" + "**Status:** {status}\n" + "**Age Ratings:** {age_ratings}\n" + "**Made by:** {made_by}\n\n" + "{storyline}" ) # .games company command page layout -COMPANY_PAGE = Template( - textwrap.dedent(""" - **[${name}](${url})** - ${description} - **Founded:** ${founded} - **Developed:** ${developed} - **Published:** ${published} - """) +COMPANY_PAGE = ( + "**[{name}]({url})**\n" + "{description}" + "**Founded:** {founded}\n" + "**Developed:** {developed}\n" + "**Published:** {published}" ) # For .games search command line layout -GAME_SEARCH_LINE = Template( - textwrap.dedent(""" - **[${name}](${url})** - ${rating}/100 :star: (based on ${rating_count} ratings) - """) +GAME_SEARCH_LINE = ( + "**[{name}]({url})**\n" + "{rating}/100 :star: (based on {rating_count} ratings)" ) # URL templates -COVER_URL = Template("https://images.igdb.com/igdb/image/upload/t_cover_big/${image_id}.jpg") -LOGO_URL = Template("https://images.igdb.com/igdb/image/upload/t_logo_med/${image_id}.png") +COVER_URL = "https://images.igdb.com/igdb/image/upload/t_cover_big/{image_id}.jpg" +LOGO_URL = "https://images.igdb.com/igdb/image/upload/t_logo_med/{image_id}.png" # Create aliases for complex genre names ALIASES = { @@ -298,7 +284,7 @@ class Games(Cog): "genre": f"where genres = ({genre});" if genre else "", "additional": additional_body } - body = GAMES_LIST_BODY.substitute(params) + body = GAMES_LIST_BODY.format(**params) # Do request to IGDB API, create headers, URL, define body, return result async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=HEADERS) as resp: @@ -307,7 +293,7 @@ class Games(Cog): async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create content of Game Page.""" # Create cover image URL from template - url = COVER_URL.substitute({"image_id": data["cover"]["image_id"] if "cover" in data else ""}) + url = COVER_URL.format(**{"image_id": data["cover"]["image_id"] if "cover" in data else ""}) # Get release date separately with checking release_date = dt.utcfromtimestamp(data["first_release_date"]).date() if "first_release_date" in data else "?" @@ -332,7 +318,7 @@ class Games(Cog): "made_by": ", ".join(companies), "storyline": data["storyline"] if "storyline" in data else "" } - page = GAME_PAGE.substitute(formatting) + page = GAME_PAGE.format(**formatting) return page, url @@ -341,7 +327,7 @@ class Games(Cog): lines = [] # Define request body of IGDB API request and do request - body = SEARCH_BODY.substitute({"term": search_term}) + body = SEARCH_BODY.format(**{"term": search_term}) async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=HEADERS) as resp: data = await resp.json() @@ -354,7 +340,7 @@ class Games(Cog): "rating": round(game["total_rating"] if "total_rating" in game else 0, 2), "rating_count": game["total_rating_count"] if "total_rating" in game else "?" } - line = GAME_SEARCH_LINE.substitute(formatting) + line = GAME_SEARCH_LINE.format(**formatting) lines.append(line) return lines @@ -367,7 +353,7 @@ class Games(Cog): returning results. """ # Create request body from template - body = COMPANIES_LIST_BODY.substitute({ + body = COMPANIES_LIST_BODY.format(**{ "limit": limit, "offset": offset }) @@ -378,7 +364,7 @@ class Games(Cog): async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create good formatted Game Company page.""" # Generate URL of company logo - url = LOGO_URL.substitute({"image_id": data["logo"]["image_id"] if "logo" in data else ""}) + url = LOGO_URL.format(**{"image_id": data["logo"]["image_id"] if "logo" in data else ""}) # Try to get found date of company founded = dt.utcfromtimestamp(data["start_date"]).date() if "start_date" in data else "?" @@ -395,7 +381,7 @@ class Games(Cog): "developed": developed, "published": published } - page = COMPANY_PAGE.substitute(formatting) + page = COMPANY_PAGE.format(**formatting) return page, url -- cgit v1.2.3 From 6c1ce462cc8594c224cc8fe78e36557a6be44b9c Mon Sep 17 00:00:00 2001 From: Karlis S <45097959+ks129@users.noreply.github.com> Date: Wed, 4 Mar 2020 19:04:50 +0200 Subject: (Games Cog): Added space between game search result + removed cutoff in get_close_matches. --- bot/seasons/evergreen/game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index bb48123d..e6700937 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -72,7 +72,7 @@ COMPANY_PAGE = ( # For .games search command line layout GAME_SEARCH_LINE = ( "**[{name}]({url})**\n" - "{rating}/100 :star: (based on {rating_count} ratings)" + "{rating}/100 :star: (based on {rating_count} ratings)\n" ) # URL templates @@ -194,7 +194,7 @@ class Games(Cog): try: games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) except KeyError: - possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres, cutoff=0.4)) + possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres)) await ctx.send(f"Invalid genre `{genre}`. {f'Maybe you meant `{possibilities}`?' if possibilities else ''}") return -- cgit v1.2.3 From bbe81ebe0c27ea65fec1170648976d4fcd8c3f3c Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 4 Mar 2020 17:47:54 -0800 Subject: Update dependencies The pipfile will need to be re-locked to add a dependency so may as well re-pin some dependencies to reflect the more recent versions that will be used. discord.py was pinned to a patch version instead of a minor version to be on the safe side. Notable updates: * discord.py -> 1.3.2 * flake8-annotation -> 2.0 * pre-commit -> 2.1 --- Pipfile | 15 +- Pipfile.lock | 568 +++++++++++++++-------------- bot/seasons/evergreen/snakes/snakes_cog.py | 4 +- bot/seasons/halloween/hacktoberstats.py | 12 +- bot/seasons/valentines/be_my_valentine.py | 6 +- tox.ini | 2 +- 6 files changed, 317 insertions(+), 290 deletions(-) (limited to 'bot') diff --git a/Pipfile b/Pipfile index c066958e..be17343f 100644 --- a/Pipfile +++ b/Pipfile @@ -7,21 +7,22 @@ name = "pypi" aiodns = "~=2.0" arrow = "~=0.14" beautifulsoup4 = "~=4.8" -discord-py = "~=1.2" +discord-py = "~=1.3.2" fuzzywuzzy = "~=0.17" pillow = "~=6.2" pytz = "~=2019.2" [dev-packages] flake8 = "~=3.7" -flake8-annotations = "~=1.1" -flake8-bugbear = "~=19.8" -flake8-docstrings = "~=1.4" +flake8-annotations = "~=2.0" +flake8-bugbear = "~=20.1" +flake8-docstrings = "~=1.5" flake8-import-order = "~=0.18" -flake8-string-format = "~=0.2" -flake8-tidy-imports = "~=2.0" +flake8-string-format = "~=0.3" +flake8-tidy-imports = "~=4.0" flake8-todo = "~=0.7" -pre-commit = "~=1.18" +pep8-naming = "~=0.9" +pre-commit = "~=2.1" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 3252f36f..659a046c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "40f23ea08504def8d3d5f56379820221088d93e9bf81d739850dc97ea8a4b7dc" + "sha256": "4cd9801f890f8087b7a46b239264e6b09d4c29c35223118add96bed0af22b475" }, "pipfile-spec": 6, "requires": { @@ -26,38 +26,28 @@ }, "aiohttp": { "hashes": [ - "sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55", - "sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed", - "sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10", - "sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5", - "sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1", - "sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939", - "sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390", - "sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa", - "sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc", - "sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5", - "sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d", - "sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf", - "sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6", - "sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72", - "sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12", - "sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366", - "sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4", - "sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300", - "sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d", - "sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303", - "sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6", - "sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889" - ], - "version": "==3.5.4" + "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", + "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", + "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", + "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", + "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", + "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", + "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", + "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", + "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", + "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", + "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", + "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965" + ], + "version": "==3.6.2" }, "arrow": { "hashes": [ - "sha256:01a16d8a93eddf86a29237f32ae36b29c27f047e79312eb4df5d55fd5a2b3183", - "sha256:e1a318a4c0b787833ae46302c02488b6eeef413c6a13324b3261ad320f21ec1e" + "sha256:5390e464e2c5f76971b60ffa7ee29c598c7501a294bc9f5e6dadcb251a5d027b", + "sha256:70729bcc831da496ca3cb4b7e89472c8e2d27d398908155e0796179f6d2d41ee" ], "index": "pypi", - "version": "==0.15.4" + "version": "==0.15.5" }, "async-timeout": { "hashes": [ @@ -75,50 +65,45 @@ }, "beautifulsoup4": { "hashes": [ - "sha256:5279c36b4b2ec2cb4298d723791467e3000e5384a43ea0cdf5d45207c7e97169", - "sha256:6135db2ba678168c07950f9a16c4031822c6f4aec75a65e0a97bc5ca09789931", - "sha256:dcdef580e18a76d54002088602eba453eec38ebbcafafeaabd8cab12b6155d57" + "sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a", + "sha256:9fbb4d6e48ecd30bcacc5b63b94088192dcda178513b2ae3c394229f8911b887", + "sha256:e1505eeed31b0f4ce2dbb3bc8eb256c04cc2b3b72af7d551a4ab6efd5cbe5dae" ], "index": "pypi", - "version": "==4.8.1" + "version": "==4.8.2" }, "cffi": { "hashes": [ - "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", - "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", - "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", - "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", - "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", - "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", - "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", - "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", - "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", - "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", - "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", - "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", - "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", - "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", - "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", - "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", - "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", - "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", - "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", - "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", - "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", - "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", - "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", - "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", - "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", - "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", - "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", - "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", - "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", - "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", - "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", - "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", - "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" - ], - "version": "==1.13.2" + "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", + "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", + "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", + "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", + "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", + "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", + "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", + "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", + "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", + "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", + "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", + "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", + "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", + "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", + "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", + "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", + "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", + "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", + "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", + "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", + "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", + "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", + "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", + "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", + "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", + "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", + "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", + "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" + ], + "version": "==1.14.0" }, "chardet": { "hashes": [ @@ -129,107 +114,124 @@ }, "discord-py": { "hashes": [ - "sha256:7c843b523bb011062b453864e75c7b675a03faf573c58d14c9f096e85984329d" + "sha256:7424be26b07b37ecad4404d9383d685995a0e0b3df3f9c645bdd3a4d977b83b4" ], "index": "pypi", - "version": "==1.2.5" + "version": "==1.3.2" }, "fuzzywuzzy": { "hashes": [ - "sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254", - "sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62" + "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8", + "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993" ], "index": "pypi", - "version": "==0.17.0" + "version": "==0.18.0" }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" }, "multidict": { "hashes": [ - "sha256:07f9a6bf75ad675d53956b2c6a2d4ef2fa63132f33ecc99e9c24cf93beb0d10b", - "sha256:0ffe4d4d28cbe9801952bfb52a8095dd9ffecebd93f84bdf973c76300de783c5", - "sha256:1b605272c558e4c659dbaf0fb32a53bfede44121bcf77b356e6e906867b958b7", - "sha256:205a011e636d885af6dd0029e41e3514a46e05bb2a43251a619a6e8348b96fc0", - "sha256:250632316295f2311e1ed43e6b26a63b0216b866b45c11441886ac1543ca96e1", - "sha256:2bc9c2579312c68a3552ee816311c8da76412e6f6a9cf33b15152e385a572d2a", - "sha256:318aadf1cfb6741c555c7dd83d94f746dc95989f4f106b25b8a83dfb547f2756", - "sha256:42cdd649741a14b0602bf15985cad0dd4696a380081a3319cd1ead46fd0f0fab", - "sha256:5159c4975931a1a78bf6602bbebaa366747fce0a56cb2111f44789d2c45e379f", - "sha256:87e26d8b89127c25659e962c61a4c655ec7445d19150daea0759516884ecb8b4", - "sha256:891b7e142885e17a894d9d22b0349b92bb2da4769b4e675665d0331c08719be5", - "sha256:8d919034420378132d074bf89df148d0193e9780c9fe7c0e495e895b8af4d8a2", - "sha256:9c890978e2b37dd0dc1bd952da9a5d9f245d4807bee33e3517e4119c48d66f8c", - "sha256:a37433ce8cdb35fc9e6e47e1606fa1bfd6d70440879038dca7d8dd023197eaa9", - "sha256:c626029841ada34c030b94a00c573a0c7575fe66489cde148785b6535397d675", - "sha256:cfec9d001a83dc73580143f3c77e898cf7ad78b27bb5e64dbe9652668fcafec7", - "sha256:efaf1b18ea6c1f577b1371c0159edbe4749558bfe983e13aa24d0a0c01e1ad7b" - ], - "version": "==4.6.1" + "sha256:317f96bc0950d249e96d8d29ab556d01dd38888fbe68324f46fd834b430169f1", + "sha256:42f56542166040b4474c0c608ed051732033cd821126493cf25b6c276df7dd35", + "sha256:4b7df040fb5fe826d689204f9b544af469593fb3ff3a069a6ad3409f742f5928", + "sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969", + "sha256:620b37c3fea181dab09267cd5a84b0f23fa043beb8bc50d8474dd9694de1fa6e", + "sha256:6e6fef114741c4d7ca46da8449038ec8b1e880bbe68674c01ceeb1ac8a648e78", + "sha256:7774e9f6c9af3f12f296131453f7b81dabb7ebdb948483362f5afcaac8a826f1", + "sha256:85cb26c38c96f76b7ff38b86c9d560dea10cf3459bb5f4caf72fc1bb932c7136", + "sha256:a326f4240123a2ac66bb163eeba99578e9d63a8654a59f4688a79198f9aa10f8", + "sha256:ae402f43604e3b2bc41e8ea8b8526c7fa7139ed76b0d64fc48e28125925275b2", + "sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e", + "sha256:b51249fdd2923739cd3efc95a3d6c363b67bbf779208e9f37fd5e68540d1a4d4", + "sha256:bb519becc46275c594410c6c28a8a0adc66fe24fef154a9addea54c1adb006f5", + "sha256:c2c37185fb0af79d5c117b8d2764f4321eeb12ba8c141a95d0aa8c2c1d0a11dd", + "sha256:dc561313279f9d05a3d0ffa89cd15ae477528ea37aa9795c4654588a3287a9ab", + "sha256:e439c9a10a95cb32abd708bb8be83b2134fa93790a4fb0535ca36db3dda94d20", + "sha256:fc3b4adc2ee8474cb3cd2a155305d5f8eda0a9c91320f83e55748e1fcb68f8e3" + ], + "version": "==4.7.5" }, "pillow": { "hashes": [ - "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", - "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", - "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", - "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", - "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", - "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", - "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", - "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", - "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", - "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", - "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", - "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", - "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", - "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", - "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", - "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", - "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", - "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", - "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", - "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", - "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", - "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", - "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9", - "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1", - "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a", - "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96", - "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132", - "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a", - "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5", - "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0" + "sha256:00e0bbe9923adc5cc38a8da7d87d4ce16cde53b8d3bba8886cb928e84522d963", + "sha256:03457e439d073770d88afdd90318382084732a5b98b0eb6f49454746dbaae701", + "sha256:0d5c99f80068f13231ac206bd9b2e80ea357f5cf9ae0fa97fab21e32d5b61065", + "sha256:1a3bc8e1db5af40a81535a62a591fafdb30a8a1b319798ea8052aa65ef8f06d2", + "sha256:2b4a94be53dff02af90760c10a2e3634c3c7703410f38c98154d5ce71fe63d20", + "sha256:3ba7d8f1d962780f86aa747fef0baf3211b80cb13310fff0c375da879c0656d4", + "sha256:3e81485cec47c24f5fb27acb485a4fc97376b2b332ed633867dc68ac3077998c", + "sha256:43ef1cff7ee57f9c8c8e6fa02a62eae9fa23a7e34418c7ce88c0e3fe09d1fb38", + "sha256:4adc3302df4faf77c63ab3a83e1a3e34b94a6a992084f4aa1cb236d1deaf4b39", + "sha256:535e8e0e02c9f1fc2e307256149d6ee8ad3aa9a6e24144b7b6e6fb6126cb0e99", + "sha256:5ccfcb0a34ad9b77ad247c231edb781763198f405a5c8dc1b642449af821fb7f", + "sha256:5dcbbaa3a24d091a64560d3c439a8962866a79a033d40eb1a75f1b3413bfc2bc", + "sha256:6e2a7e74d1a626b817ecb7a28c433b471a395c010b2a1f511f976e9ea4363e64", + "sha256:82859575005408af81b3e9171ae326ff56a69af5439d3fc20e8cb76cd51c8246", + "sha256:834dd023b7f987d6b700ad93dc818098d7eb046bd445e9992b3093c6f9d7a95f", + "sha256:87ef0eca169f7f0bc050b22f05c7e174a65c36d584428431e802c0165c5856ea", + "sha256:900de1fdc93764be13f6b39dc0dd0207d9ff441d87ad7c6e97e49b81987dc0f3", + "sha256:92b83b380f9181cacc994f4c983d95a9c8b00b50bf786c66d235716b526a3332", + "sha256:aa1b0297e352007ec781a33f026afbb062a9a9895bb103c8f49af434b1666880", + "sha256:aa4792ab056f51b49e7d59ce5733155e10a918baf8ce50f64405db23d5627fa2", + "sha256:b72c39585f1837d946bd1a829a4820ccf86e361f28cbf60f5d646f06318b61e2", + "sha256:bb7861e4618a0c06c40a2e509c1bea207eea5fd4320d486e314e00745a402ca5", + "sha256:bc149dab804291a18e1186536519e5e122a2ac1316cb80f506e855a500b1cdd4", + "sha256:c424d35a5259be559b64490d0fd9e03fba81f1ce8e5b66e0a59de97547351d80", + "sha256:cbd5647097dc55e501f459dbac7f1d0402225636deeb9e0a98a8d2df649fc19d", + "sha256:ccf16fe444cc43800eeacd4f4769971200982200a71b1368f49410d0eb769543", + "sha256:d3a98444a00b4643b22b0685dbf9e0ddcaf4ebfd4ea23f84f228adf5a0765bb2", + "sha256:d6b4dc325170bee04ca8292bbd556c6f5398d52c6149ca881e67daf62215426f", + "sha256:db9ff0c251ed066d367f53b64827cc9e18ccea001b986d08c265e53625dab950", + "sha256:e3a797a079ce289e59dbd7eac9ca3bf682d52687f718686857281475b7ca8e6a" ], "index": "pypi", - "version": "==6.2.1" + "version": "==6.2.2" }, "pycares": { "hashes": [ - "sha256:2ca080db265ea238dc45f997f94effb62b979a617569889e265c26a839ed6305", - "sha256:6f79c6afb6ce603009db2042fddc2e348ad093ece9784cbe2daa809499871a23", - "sha256:70918d06eb0603016d37092a5f2c0228509eb4e6c5a3faacb4184f6ab7be7650", - "sha256:755187d28d24a9ea63aa2b4c0638be31d65fbf7f0ce16d41261b9f8cb55a1b99", - "sha256:7baa4b1f2146eb8423ff8303ebde3a20fb444a60db761fba0430d104fe35ddbf", - "sha256:90b27d4df86395f465a171386bc341098d6d47b65944df46518814ae298f6cc6", - "sha256:9e090dd6b2afa65cb51c133883b2bf2240fd0f717b130b0048714b33fb0f47ce", - "sha256:a11b7d63c3718775f6e805d6464cb10943780395ab042c7e5a0a7a9f612735dd", - "sha256:b253f5dcaa0ac7076b79388a3ac80dd8f3bd979108f813baade40d3a9b8bf0bd", - "sha256:c7f4f65e44ba35e35ad3febc844270665bba21cfb0fb7d749434e705b556e087", - "sha256:cdb342e6a254f035bd976d95807a2184038fc088d957a5104dcaab8be602c093", - "sha256:cf08e164f8bfb83b9fe633feb56f2754fae6baefcea663593794fa0518f8f98c", - "sha256:df9bc694cf03673878ea8ce674082c5acd134991d64d6c306d4bd61c0c1df98f" - ], - "version": "==3.0.0" + "sha256:050f00b39ed77ea8a4e555f09417d4b1a6b5baa24bb9531a3e15d003d2319b3f", + "sha256:0a24d2e580a8eb567140d7b69f12cb7de90c836bd7b6488ec69394d308605ac3", + "sha256:0c5bd1f6f885a219d5e972788d6eef7b8043b55c3375a845e5399638436e0bba", + "sha256:11c628402cc8fc8ef461076d4e47f88afc1f8609989ebbff0dbffcd54c97239f", + "sha256:18dfd4fd300f570d6c4536c1d987b7b7673b2a9d14346592c5d6ed716df0d104", + "sha256:1917b82494907a4a342db420bc4dd5bac355a5fa3984c35ba9bf51422b020b48", + "sha256:1b90fa00a89564df059fb18e796458864cc4e00cb55e364dbf921997266b7c55", + "sha256:1d8d177c40567de78108a7835170f570ab04f09084bfd32df9919c0eaec47aa1", + "sha256:236286f81664658b32c141c8e79d20afc3d54f6e2e49dfc8b702026be7265855", + "sha256:2e4f74677542737fb5af4ea9a2e415ec5ab31aa67e7b8c3c969fdb15c069f679", + "sha256:48a7750f04e69e1f304f4332b755728067e7c4b1abe2760bba1cacd9ff7a847a", + "sha256:7d86e62b700b21401ffe7fd1bbfe91e08489416fecae99c6570ab023c6896022", + "sha256:7e2d7effd08d2e5a3cb95d98a7286ebab71ab2fbce84fa93cc2dd56caf7240dd", + "sha256:81edb016d9e43dde7473bc3999c29cdfee3a6b67308fed1ea21049f458e83ae0", + "sha256:96c90e11b4a4c7c0b8ff5aaaae969c5035493136586043ff301979aae0623941", + "sha256:9a0a1845f8cb2e62332bca0aaa9ad5494603ac43fb60d510a61d5b5b170d7216", + "sha256:a05bbfdfd41f8410a905a818f329afe7510cbd9ee65c60f8860a72b6c64ce5dc", + "sha256:a5089fd660f0b0d228b14cdaa110d0d311edfa5a63f800618dbf1321dcaef66b", + "sha256:c457a709e6f2befea7e2996c991eda6d79705dd075f6521593ba6ebc1485b811", + "sha256:c5cb72644b04e5e5abfb1e10a0e7eb75da6684ea0e60871652f348e412cf3b11", + "sha256:cce46dd4717debfd2aab79d6d7f0cbdf6b1e982dc4d9bebad81658d59ede07c2", + "sha256:cfdd1f90bcf373b00f4b2c55ea47868616fe2f779f792fc913fa82a3d64ffe43", + "sha256:d88a279cbc5af613f73e86e19b3f63850f7a2e2736e249c51995dedcc830b1bb", + "sha256:eba9a9227438da5e78fc8eee32f32eb35d9a50cf0a0bd937eb6275c7cc3015fe", + "sha256:eee7b6a5f5b5af050cb7d66ab28179287b416f06d15a8974ac831437fec51336", + "sha256:f41ac1c858687e53242828c9f59c2e7b0b95dbcd5bdd09c7e5d3c48b0f89a25a", + "sha256:f8deaefefc3a589058df1b177275f79233e8b0eeee6734cf4336d80164ecd022", + "sha256:fa78e919f3bd7d6d075db262aa41079b4c02da315c6043c6f43881e2ebcdd623", + "sha256:fadb97d2e02dabdc15a0091591a972a938850d79ddde23d385d813c1731983f0" + ], + "version": "==3.1.1" }, "pycparser": { "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], - "version": "==2.19" + "version": "==2.20" }, "python-dateutil": { "hashes": [ @@ -248,74 +250,75 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "soupsieve": { "hashes": [ - "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5", - "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda" + "sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae", + "sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69" ], - "version": "==1.9.5" + "version": "==2.0" }, "websockets": { "hashes": [ - "sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136", - "sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6", - "sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1", - "sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538", - "sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4", - "sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908", - "sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0", - "sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d", - "sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c", - "sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d", - "sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c", - "sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb", - "sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf", - "sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e", - "sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96", - "sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584", - "sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484", - "sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d", - "sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559", - "sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff", - "sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454" - ], - "version": "==6.0" + "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", + "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", + "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", + "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", + "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", + "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", + "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", + "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", + "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", + "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", + "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", + "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", + "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", + "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", + "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", + "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", + "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", + "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", + "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", + "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", + "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", + "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" + ], + "version": "==8.1" }, "yarl": { "hashes": [ - "sha256:031e8f56cf085d3b3df6b6bce756369ea7052b82d35ea07b6045f209c819e0e5", - "sha256:074958fe4578ef3a3d0bdaf96bbc25e4c4db82b7ff523594776fcf3d3f16c531", - "sha256:2db667ee21f620b446a54a793e467714fc5a446fcc82d93a47e8bde01d69afab", - "sha256:326f2dbaaa17b858ae86f261ae73a266fd820a561fc5142cee9d0fc58448fbd7", - "sha256:32a3885f542f74d0f4f87057050c6b45529ebd79d0639f56582e741521575bfe", - "sha256:56126ef061b913c3eefecace3404ca88917265d0550b8e32bbbeab29e5c830bf", - "sha256:589ac1e82add13fbdedc04eb0a83400db728e5f1af2bd273392088ca90de7062", - "sha256:6076bce2ecc6ebf6c92919d77762f80f4c9c6ecc9c1fbaa16567ec59ad7d6f1d", - "sha256:63be649c535d18ab6230efbc06a07f7779cd4336a687672defe70c025349a47b", - "sha256:6642cbc92eaffa586180f669adc772f5c34977e9e849e93f33dc142351e98c9c", - "sha256:6fa05a25f2280e78a514041d4609d39962e7d51525f2439db9ad7a2ae7aac163", - "sha256:7ed006a220422c33ff0889288be24db56ff0a3008ffe9eaead58a690715ad09b", - "sha256:80c9c213803b50899460cc355f47e66778c3c868f448b7b7de5b1f1858c82c2a", - "sha256:8bae18e2129850e76969b57869dacc72a66cccdbeebce1a28d7f3d439c21a7a3", - "sha256:ab112fba996a8f48f427e26969f2066d50080df0c24007a8cc6d7ae865e19013", - "sha256:b1c178ef813940c9a5cbad42ab7b8b76ac08b594b0a6bad91063c968e0466efc", - "sha256:d6eff151c3b23a56a5e4f496805619bc3bdf4f749f63a7a95ad50e8267c17475" - ], - "version": "==1.4.1" + "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce", + "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6", + "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce", + "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae", + "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d", + "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f", + "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b", + "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b", + "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb", + "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462", + "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea", + "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70", + "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1", + "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a", + "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b", + "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080", + "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2" + ], + "version": "==1.4.2" } }, "develop": { - "aspy.yaml": { + "appdirs": { "hashes": [ - "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc", - "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45" + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" ], - "version": "==1.3.0" + "version": "==1.4.3" }, "attrs": { "hashes": [ @@ -326,10 +329,16 @@ }, "cfgv": { "hashes": [ - "sha256:edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144", - "sha256:fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289" + "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53", + "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513" ], - "version": "==2.0.1" + "version": "==3.1.0" + }, + "distlib": { + "hashes": [ + "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" + ], + "version": "==0.3.0" }, "entrypoints": { "hashes": [ @@ -338,6 +347,13 @@ ], "version": "==0.3" }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" + }, "flake8": { "hashes": [ "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", @@ -348,19 +364,19 @@ }, "flake8-annotations": { "hashes": [ - "sha256:6ac7ca1e706307686b60af8043ff1db31dc2cfc1233c8210d67a3d9b8f364736", - "sha256:b51131007000d67217608fa028a35ff80aa400b474e5972f1f99c2cf9d26bd2e" + "sha256:a38b44d01abd480586a92a02a2b0a36231ec42dcc5e114de78fa5db016d8d3f9", + "sha256:d5b0e8704e4e7728b352fa1464e23539ff2341ba11cc153b536fa2cf921ee659" ], "index": "pypi", - "version": "==1.1.0" + "version": "==2.0.1" }, "flake8-bugbear": { "hashes": [ - "sha256:d8c466ea79d5020cb20bf9f11cf349026e09517a42264f313d3f6fddb83e0571", - "sha256:ded4d282778969b5ab5530ceba7aa1a9f1b86fa7618fc96a19a1d512331640f8" + "sha256:a3ddc03ec28ba2296fc6f89444d1c946a6b76460f859795b35b77d4920a51b63", + "sha256:bd02e4b009fb153fe6072c31c52aeab5b133d508095befb2ffcf3b41c4823162" ], "index": "pypi", - "version": "==19.8.0" + "version": "==20.1.4" }, "flake8-docstrings": { "hashes": [ @@ -378,21 +394,28 @@ "index": "pypi", "version": "==0.18.1" }, + "flake8-polyfill": { + "hashes": [ + "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9", + "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda" + ], + "version": "==1.0.2" + }, "flake8-string-format": { "hashes": [ - "sha256:68ea72a1a5b75e7018cae44d14f32473c798cf73d75cbaed86c6a9a907b770b2", - "sha256:774d56103d9242ed968897455ef49b7d6de272000cfa83de5814273a868832f1" + "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2", + "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af" ], "index": "pypi", - "version": "==0.2.3" + "version": "==0.3.0" }, "flake8-tidy-imports": { "hashes": [ - "sha256:1c476aabc6e8db26dc75278464a3a392dba0ea80562777c5f13fd5cdf2646154", - "sha256:b3f5b96affd0f57cacb6621ed28286ce67edaca807757b51227043ebf7b136a1" + "sha256:8aa34384b45137d4cf33f5818b8e7897dc903b1d1e10a503fa7dd193a9a710ba", + "sha256:b26461561bcc80e8012e46846630ecf0aaa59314f362a94cb7800dfdb32fa413" ], "index": "pypi", - "version": "==2.0.0" + "version": "==4.0.0" }, "flake8-todo": { "hashes": [ @@ -403,18 +426,18 @@ }, "identify": { "hashes": [ - "sha256:4f1fe9a59df4e80fcb0213086fcf502bc1765a01ea4fe8be48da3b65afd2a017", - "sha256:d8919589bd2a5f99c66302fec0ef9027b12ae150b0b0213999ad3f695fc7296e" + "sha256:1222b648251bdcb8deb240b294f450fbf704c7984e08baa92507e4ea10b436d5", + "sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96" ], - "version": "==1.4.7" + "version": "==1.4.11" }, "importlib-metadata": { "hashes": [ - "sha256:b044f07694ef14a6683b097ba56bd081dbc7cdc7c7fe46011e499dfecc082f21", - "sha256:e6ac600a142cf2db707b1998382cc7fc3b02befb7273876e01b8ad10b9652742" + "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", + "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" ], "markers": "python_version < '3.8'", - "version": "==1.1.0" + "version": "==1.5.0" }, "mccabe": { "hashes": [ @@ -423,26 +446,27 @@ ], "version": "==0.6.1" }, - "more-itertools": { + "nodeenv": { "hashes": [ - "sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2", - "sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45" + "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212" ], - "version": "==8.0.0" + "version": "==1.3.5" }, - "nodeenv": { + "pep8-naming": { "hashes": [ - "sha256:ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a" + "sha256:45f330db8fcfb0fba57458c77385e288e7a3be1d01e8ea4268263ef677ceea5f", + "sha256:a33d38177056321a167decd6ba70b890856ba5025f0a8eca6a3eda607da93caf" ], - "version": "==1.3.3" + "index": "pypi", + "version": "==0.9.1" }, "pre-commit": { "hashes": [ - "sha256:9f152687127ec90642a2cc3e4d9e1e6240c4eb153615cb02aa1ad41d331cbb6e", - "sha256:c2e4810d2d3102d354947907514a78c5d30424d299dc0fe48f5aa049826e9b50" + "sha256:09ebe467f43ce24377f8c2f200fe3cd2570d328eb2ce0568c8e96ce19da45fa6", + "sha256:f8d555e31e2051892c7f7b3ad9f620bd2c09271d87e9eedb2ad831737d6211eb" ], "index": "pypi", - "version": "==1.20.0" + "version": "==2.1.1" }, "pycodestyle": { "hashes": [ @@ -453,10 +477,10 @@ }, "pydocstyle": { "hashes": [ - "sha256:04c84e034ebb56eb6396c820442b8c4499ac5eb94a3bda88951ac3dc519b6058", - "sha256:66aff87ffe34b1e49bff2dd03a88ce6843be2f3346b0c9814410d34987fbab59" + "sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586", + "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5" ], - "version": "==4.0.1" + "version": "==5.0.2" }, "pyflakes": { "hashes": [ @@ -467,26 +491,26 @@ }, "pyyaml": { "hashes": [ - "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", - "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", - "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", - "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", - "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", - "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", - "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", - "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", - "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", - "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", - "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" + "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", + "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", + "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", + "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", + "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", + "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", + "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", + "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", + "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", + "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", + "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" ], - "version": "==5.2" + "version": "==5.3" }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "snowballstemmer": { "hashes": [ @@ -504,42 +528,44 @@ }, "typed-ast": { "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" - ], - "version": "==1.4.0" + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "markers": "python_version < '3.8'", + "version": "==1.4.1" }, "virtualenv": { "hashes": [ - "sha256:116655188441670978117d0ebb6451eb6a7526f9ae0796cc0dee6bd7356909b0", - "sha256:b57776b44f91511866594e477dd10e76a6eb44439cdd7f06dcd30ba4c5bd854f" + "sha256:0c04c7e8e0314470b4c2b43740ff68be1c62bb3fdef8309341ff1daea60d49d1", + "sha256:1f0369d068d9761b5c1ed7b44dad1ec124727eb10bc7f4aaefbba0cdca3bd924" ], - "version": "==16.7.8" + "version": "==20.0.8" }, "zipp": { "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==0.6.0" + "version": "==3.1.0" } } } diff --git a/bot/seasons/evergreen/snakes/snakes_cog.py b/bot/seasons/evergreen/snakes/snakes_cog.py index 1ed38f86..09f5e250 100644 --- a/bot/seasons/evergreen/snakes/snakes_cog.py +++ b/bot/seasons/evergreen/snakes/snakes_cog.py @@ -617,8 +617,8 @@ class Snakes(Cog): text_color=text_color, bg_color=bg_color ) - png_bytesIO = utils.frame_to_png_bytes(image_frame) - file = File(png_bytesIO, filename='snek.png') + png_bytes = utils.frame_to_png_bytes(image_frame) + file = File(png_bytes, filename='snek.png') await ctx.send(file=file) @snakes_group.command(name='get') diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index b7b4122d..fbadb7cc 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -121,8 +121,8 @@ class HacktoberStats(commands.Cog): """ if self.link_json.exists(): logging.info(f"Loading linked GitHub accounts from '{self.link_json}'") - with open(self.link_json, 'r') as fID: - linked_accounts = json.load(fID) + with open(self.link_json, 'r') as file: + linked_accounts = json.load(file) logging.info(f"Loaded {len(linked_accounts)} linked GitHub accounts from '{self.link_json}'") return linked_accounts @@ -143,8 +143,8 @@ class HacktoberStats(commands.Cog): } """ logging.info(f"Saving linked_accounts to '{self.link_json}'") - with open(self.link_json, 'w') as fID: - json.dump(self.linked_accounts, fID, default=str) + with open(self.link_json, 'w') as file: + json.dump(self.linked_accounts, file, default=str) logging.info(f"linked_accounts saved to '{self.link_json}'") async def get_stats(self, ctx: commands.Context, github_username: str) -> None: @@ -309,11 +309,11 @@ class HacktoberStats(commands.Cog): n contribution(s) to [shortname](url) ... """ - baseURL = "https://www.github.com/" + base_url = "https://www.github.com/" contributionstrs = [] for repo in stats['top5']: n = repo[1] - contributionstrs.append(f"{n} {HacktoberStats._contributionator(n)} to [{repo[0]}]({baseURL}{repo[0]})") + contributionstrs.append(f"{n} {HacktoberStats._contributionator(n)} to [{repo[0]}]({base_url}{repo[0]})") return "\n".join(contributionstrs) diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index de97cb4e..ab8ea290 100644 --- a/bot/seasons/valentines/be_my_valentine.py +++ b/bot/seasons/valentines/be_my_valentine.py @@ -202,9 +202,9 @@ class BeMyValentine(commands.Cog): @staticmethod def random_emoji() -> Tuple[str, str]: """Return two random emoji from the module-defined constants.""" - EMOJI_1 = random.choice(HEART_EMOJIS) - EMOJI_2 = random.choice(HEART_EMOJIS) - return EMOJI_1, EMOJI_2 + emoji_1 = random.choice(HEART_EMOJIS) + emoji_2 = random.choice(HEART_EMOJIS) + return emoji_1, emoji_2 def random_valentine(self) -> Tuple[str, str]: """Grabs a random poem or a compliment (any message).""" diff --git a/tox.ini b/tox.ini index ee898b0d..af87e6fc 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ ignore= # Docstring Content D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417 # Type Annotations - TYP002,TYP003,TYP101,TYP102,TYP204,TYP206 + ANN002,ANN003,ANN101,ANN102,ANN204,ANN206 exclude= __pycache__,.cache, venv,.venv, -- cgit v1.2.3 From a6237bb9dfd7e9c28421f0b37d92796454b9c122 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 4 Mar 2020 18:16:04 -0800 Subject: Add more pre-commit hooks Hooks added: * check-merge-conflict - checks for files with merge conflict strings * check-toml - attempts to load all toml files to verify syntax * check-yaml - attempts to load all yaml files to verify syntax * end-of-file-fixer - ensures files end in a newline and only a newline * mixed-line-ending - replaces mixed line endings with LF * trailing-whitespace - trims trailing whitespace * python-check-blanket-noqa - enforces that noqa annotations always occur with specific codes Changes made to comply with new hooks: * Remove trailing whitespaces * Convert some CRLF files to LF * Remove noqa and add missing type annotations * Add missing newlines at end of files See: python-discord/organisation#138 --- .gitignore | 2 +- .pre-commit-config.yaml | 23 +++- CONTRIBUTING.md | 4 +- Pipfile | 2 +- README.md | 2 +- bot/resources/advent_of_code/about.json | 2 +- bot/resources/easter/april_fools_vids.json | 2 +- .../evergreen/game_recs/chrono_trigger.json | 2 +- .../evergreen/game_recs/digimon_world.json | 2 +- bot/resources/evergreen/game_recs/doom_2.json | 2 +- bot/resources/evergreen/game_recs/skyrim.json | 2 +- bot/resources/evergreen/magic8ball.json | 2 +- bot/resources/evergreen/trivia_quiz.json | 2 +- bot/resources/halloween/candy_collection.json | 2 +- bot/resources/halloween/halloweenify.json | 2 +- bot/resources/halloween/spooky_rating.json | 2 +- bot/resources/pride/anthems.json | 2 +- bot/resources/pride/drag_queen_names.json | 2 +- bot/resources/pride/facts.json | 2 +- bot/resources/snakes/snake_idioms.json | 2 +- bot/resources/snakes/snake_names.json | 2 +- bot/resources/snakes/special_snakes.json | 2 +- bot/resources/valentines/date_ideas.json | 2 - bot/resources/valentines/love_matches.json | 2 +- bot/resources/valentines/pickup_lines.json | 2 +- bot/resources/valentines/valenstates.json | 2 +- bot/resources/valentines/valentine_facts.json | 2 +- bot/resources/valentines/zodiac_compatibility.json | 2 +- bot/seasons/christmas/adventofcode.py | 2 +- bot/seasons/evergreen/bookmark.py | 130 ++++++++++----------- bot/seasons/halloween/hacktoberstats.py | 2 +- 31 files changed, 113 insertions(+), 100 deletions(-) (limited to 'bot') diff --git a/.gitignore b/.gitignore index 8a21b668..d95585b7 100644 --- a/.gitignore +++ b/.gitignore @@ -113,5 +113,5 @@ venv.bak/ .idea/ .DS_Store -# vscode +# vscode .vscode/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 86035786..be57904e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,25 @@ repos: -- repo: local + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.5.0 hooks: - - id: flake8 + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.5.1 + hooks: + - id: python-check-blanket-noqa + - repo: local + hooks: + - id: flake8 name: Flake8 description: This hook runs flake8 within our project's pipenv environment. - entry: pipenv run lint + entry: pipenv run flake8 language: python types: [python] - require_serial: true \ No newline at end of file + require_serial: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd0c048f..ced5df70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ Seasonalbot utilizes [Pipenv](https://pipenv.readthedocs.io/en/latest/) for inst When pulling down changes from GitHub, remember to sync your environment using `pipenv sync --dev` to ensure you're using the most up-to-date versions the project's dependencies. ### Type Hinting -[PEP 484](https://www.python.org/dev/peps/pep-0484/) formally specifies type hints for Python functions, added to the Python Standard Library in version 3.5. Type hints are recognized by most modern code editing tools and provide useful insight into both the input and output types of a function, preventing the user from having to go through the codebase to determine these types. +[PEP 484](https://www.python.org/dev/peps/pep-0484/) formally specifies type hints for Python functions, added to the Python Standard Library in version 3.5. Type hints are recognized by most modern code editing tools and provide useful insight into both the input and output types of a function, preventing the user from having to go through the codebase to determine these types. For example: @@ -86,7 +86,7 @@ def foo(bar: int, baz: dict=None) -> bool: Does some things with some stuff. This function takes an index, `bar` and checks for its presence in the database `baz`, passed as a dictionary. - + Returns `False` if `baz` is not passed. """ ``` diff --git a/Pipfile b/Pipfile index be17343f..0c4b2353 100644 --- a/Pipfile +++ b/Pipfile @@ -29,5 +29,5 @@ python_version = "3.7" [scripts] start = "python -m bot" -lint = "flake8 bot" +lint = "pre-commit run --all-files" precommit = "pre-commit install" diff --git a/README.md b/README.md index f956cbaf..c1952286 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SeasonalBot +# SeasonalBot [![Build Status](https://dev.azure.com/python-discord/Python%20Discord/_apis/build/status/Seasonal%20Bot%20(Mainline))](https://dev.azure.com/python-discord/Python%20Discord/_build/latest?definitionId=3) [![Discord](https://img.shields.io/static/v1?label=Python%20Discord&logo=discord&message=%3E30k%20members&color=%237289DA&logoColor=white)](https://discord.gg/2B963hn) diff --git a/bot/resources/advent_of_code/about.json b/bot/resources/advent_of_code/about.json index b1d16a93..91ae6813 100644 --- a/bot/resources/advent_of_code/about.json +++ b/bot/resources/advent_of_code/about.json @@ -24,4 +24,4 @@ "value": "In addition to the global leaderboard, AoC also offers private leaderboards, where you can compete against a smaller group of friends!\n\nGet the join code using `.aoc join` and head over to AoC's [private leaderboard page](https://adventofcode.com/leaderboard/private) to join the PyDis private leaderboard!", "inline": false } -] \ No newline at end of file +] diff --git a/bot/resources/easter/april_fools_vids.json b/bot/resources/easter/april_fools_vids.json index dfc01b7b..5f0ba06e 100644 --- a/bot/resources/easter/april_fools_vids.json +++ b/bot/resources/easter/april_fools_vids.json @@ -122,4 +122,4 @@ } ] -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/game_recs/chrono_trigger.json b/bot/resources/evergreen/game_recs/chrono_trigger.json index 219c1e78..9720b977 100644 --- a/bot/resources/evergreen/game_recs/chrono_trigger.json +++ b/bot/resources/evergreen/game_recs/chrono_trigger.json @@ -4,4 +4,4 @@ "link": "https://rawg.io/games/chrono-trigger-1995", "image": "https://vignette.wikia.nocookie.net/chrono/images/2/24/Chrono_Trigger_cover.jpg", "author": "352635617709916161" -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/game_recs/digimon_world.json b/bot/resources/evergreen/game_recs/digimon_world.json index a2820f8e..c1cb4f37 100644 --- a/bot/resources/evergreen/game_recs/digimon_world.json +++ b/bot/resources/evergreen/game_recs/digimon_world.json @@ -4,4 +4,4 @@ "image": "https://www.mobygames.com/images/covers/l/437308-digimon-world-playstation-front-cover.jpg", "link": "https://rawg.io/games/digimon-world", "author": "352635617709916161" -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/game_recs/doom_2.json b/bot/resources/evergreen/game_recs/doom_2.json index e228e2b1..b60cc05f 100644 --- a/bot/resources/evergreen/game_recs/doom_2.json +++ b/bot/resources/evergreen/game_recs/doom_2.json @@ -4,4 +4,4 @@ "image": "https://upload.wikimedia.org/wikipedia/en/thumb/2/29/Doom_II_-_Hell_on_Earth_Coverart.png/220px-Doom_II_-_Hell_on_Earth_Coverart.png", "link": "https://rawg.io/games/doom-ii", "author": "352635617709916161" -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/game_recs/skyrim.json b/bot/resources/evergreen/game_recs/skyrim.json index 09f93563..ad86db31 100644 --- a/bot/resources/evergreen/game_recs/skyrim.json +++ b/bot/resources/evergreen/game_recs/skyrim.json @@ -4,4 +4,4 @@ "image": "https://upload.wikimedia.org/wikipedia/en/1/15/The_Elder_Scrolls_V_Skyrim_cover.png", "link": "https://rawg.io/games/the-elder-scrolls-v-skyrim", "author": "352635617709916161" -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/magic8ball.json b/bot/resources/evergreen/magic8ball.json index 6fe86950..f5f1df62 100644 --- a/bot/resources/evergreen/magic8ball.json +++ b/bot/resources/evergreen/magic8ball.json @@ -19,4 +19,4 @@ "My sources say no", "Outlook not so good", "Very doubtful" -] \ No newline at end of file +] diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json index 48ee2ce4..6100ca62 100644 --- a/bot/resources/evergreen/trivia_quiz.json +++ b/bot/resources/evergreen/trivia_quiz.json @@ -29,7 +29,7 @@ "hints": ["The game was released in 1990.", "It was released on the SNES."], "question": "What was the first game Yoshi appeared in?", "answer": "Super Mario World" - } + } ], "general":[ { diff --git a/bot/resources/halloween/candy_collection.json b/bot/resources/halloween/candy_collection.json index bebcd4fc..9aa78a5f 100644 --- a/bot/resources/halloween/candy_collection.json +++ b/bot/resources/halloween/candy_collection.json @@ -1 +1 @@ -{"msg_reacted": [{"reaction": "\ud83c\udf6c", "msg_id": 514442189359546375, "won": true, "user_reacted": 95872159741644800}, {"reaction": "\ud83c\udf6c", "msg_id": 514442502460276740, "won": true, "user_reacted": 178876748224659457}], "records": [{"userid": 95872159741644800, "record": 1}, {"userid": 178876748224659457, "record": 1}]} \ No newline at end of file +{"msg_reacted": [{"reaction": "\ud83c\udf6c", "msg_id": 514442189359546375, "won": true, "user_reacted": 95872159741644800}, {"reaction": "\ud83c\udf6c", "msg_id": 514442502460276740, "won": true, "user_reacted": 178876748224659457}], "records": [{"userid": 95872159741644800, "record": 1}, {"userid": 178876748224659457, "record": 1}]} diff --git a/bot/resources/halloween/halloweenify.json b/bot/resources/halloween/halloweenify.json index 88c46bfc..af9204b2 100644 --- a/bot/resources/halloween/halloweenify.json +++ b/bot/resources/halloween/halloweenify.json @@ -79,4 +79,4 @@ "Pale Man": "https://i2.wp.com/macguff.in/wp-content/uploads/2016/10/Pans-Labyrinth-Movie-Header-Image.jpg?fit=630%2C400&ssl=1" } ] -} \ No newline at end of file +} diff --git a/bot/resources/halloween/spooky_rating.json b/bot/resources/halloween/spooky_rating.json index 1815befc..d9c8dcb7 100644 --- a/bot/resources/halloween/spooky_rating.json +++ b/bot/resources/halloween/spooky_rating.json @@ -44,4 +44,4 @@ "text": "You are the evillest creature in existence to scare anyone and everyone humanly possible. The reason your underlings are called mortals is that they die. With your help, they die a lot quicker. With all the evil power in the universe, you know what to do.", "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/devil.jpeg" } -} \ No newline at end of file +} diff --git a/bot/resources/pride/anthems.json b/bot/resources/pride/anthems.json index d80d7558..fd8e8b92 100644 --- a/bot/resources/pride/anthems.json +++ b/bot/resources/pride/anthems.json @@ -452,4 +452,4 @@ "pop" ] } -] \ No newline at end of file +] diff --git a/bot/resources/pride/drag_queen_names.json b/bot/resources/pride/drag_queen_names.json index f63cdec3..023dff36 100644 --- a/bot/resources/pride/drag_queen_names.json +++ b/bot/resources/pride/drag_queen_names.json @@ -246,4 +246,4 @@ "Vivian Foxx", "Vye Vacius", "Zahara Dessert" -] \ No newline at end of file +] diff --git a/bot/resources/pride/facts.json b/bot/resources/pride/facts.json index f6597201..2151f5ca 100644 --- a/bot/resources/pride/facts.json +++ b/bot/resources/pride/facts.json @@ -31,4 +31,4 @@ "As of 2019-10-02, there are 17 states in the United States of America where queer people can be fired for being queer. In most other states, there is minimal protection offered, often only for public employees.", "In 1985, an official Star Trek novel was published with scenes depicting Kirk and Spock as lovers. These parts were largely removed, which made the original into a collector's item." ] -} \ No newline at end of file +} diff --git a/bot/resources/snakes/snake_idioms.json b/bot/resources/snakes/snake_idioms.json index 37148c42..ecbeb6ff 100644 --- a/bot/resources/snakes/snake_idioms.json +++ b/bot/resources/snakes/snake_idioms.json @@ -272,4 +272,4 @@ { "idiom": "photosnek" } -] \ No newline at end of file +] diff --git a/bot/resources/snakes/snake_names.json b/bot/resources/snakes/snake_names.json index 8ba9dbd7..25832550 100644 --- a/bot/resources/snakes/snake_names.json +++ b/bot/resources/snakes/snake_names.json @@ -2167,4 +2167,4 @@ "name": "Titanboa", "scientific": "Titanoboa" } -] \ No newline at end of file +] diff --git a/bot/resources/snakes/special_snakes.json b/bot/resources/snakes/special_snakes.json index 8159f914..46214f66 100644 --- a/bot/resources/snakes/special_snakes.json +++ b/bot/resources/snakes/special_snakes.json @@ -13,4 +13,4 @@ "https://img.thrfun.com/img/080/349/spaghetti_dinner_l1.jpg" ] } -] \ No newline at end of file +] diff --git a/bot/resources/valentines/date_ideas.json b/bot/resources/valentines/date_ideas.json index 09d31067..995f14bb 100644 --- a/bot/resources/valentines/date_ideas.json +++ b/bot/resources/valentines/date_ideas.json @@ -123,5 +123,3 @@ } ] } - - diff --git a/bot/resources/valentines/love_matches.json b/bot/resources/valentines/love_matches.json index 8d50cd79..7df2dbda 100644 --- a/bot/resources/valentines/love_matches.json +++ b/bot/resources/valentines/love_matches.json @@ -55,4 +55,4 @@ ], "text": "You two will most likely have the perfect relationship. But don't think that this means you don't have to do anything for it to work. Talking to each other and spending time together is key, even in a seemingly perfect relationship." } -} \ No newline at end of file +} diff --git a/bot/resources/valentines/pickup_lines.json b/bot/resources/valentines/pickup_lines.json index a18d0840..eb01290f 100644 --- a/bot/resources/valentines/pickup_lines.json +++ b/bot/resources/valentines/pickup_lines.json @@ -94,4 +94,4 @@ "image": "https://upload.wikimedia.org/wikipedia/en/thumb/0/0c/The_Genie_Aladdin.png/220px-The_Genie_Aladdin.png" } ] -} \ No newline at end of file +} diff --git a/bot/resources/valentines/valenstates.json b/bot/resources/valentines/valenstates.json index 06cbb2e5..c58a5b7c 100644 --- a/bot/resources/valentines/valenstates.json +++ b/bot/resources/valentines/valenstates.json @@ -119,4 +119,4 @@ "text": "", "flag": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Flag_of_Wales_%281959%E2%80%93present%29.svg/1920px-Flag_of_Wales_%281959%E2%80%93present%29.svg.png" } -} \ No newline at end of file +} diff --git a/bot/resources/valentines/valentine_facts.json b/bot/resources/valentines/valentine_facts.json index d2ffa980..e6f826c3 100644 --- a/bot/resources/valentines/valentine_facts.json +++ b/bot/resources/valentines/valentine_facts.json @@ -21,4 +21,4 @@ "There's a form of cryptological communication called 'Floriography', in which you communicate through flowers. Meaning has been attributed to flowers for thousands of years, and some form of floriography has been practiced in traditional cultures throughout Europe, Asia, and Africa. Here are some meanings for roses you might want to take a look at, if you plan on gifting your loved one a bouquet of roses on Valentine's Day:\n\u200b\nRed: eternal love\nPink: young, developing love\nWhite: innocence, fervor, loyalty\nOrange: happiness, security\nViolet: love at first sight\nBlue: unfulfilled longing, quiet desire\nYellow: friendship, jealousy, envy, infidelity\nBlack: unfulfilled longing, quiet desire, grief, hatred, misfortune, death", "Traditionally, young girls in the U.S. and the U.K. believed they could tell what type of man they would marry depending on the type of bird they saw first on Valentine's Day. If they saw a blackbird, they would marry a clergyman, a robin redbreast indicated a sailor, and a goldfinch indicated a rich man. A sparrow meant they would marry a farmer, a bluebird indicated a happy man, and a crossbill meant an argumentative man. If they saw a dove, they would marry a good man, but seeing a woodpecker meant they would not marry at all." ] -} \ No newline at end of file +} diff --git a/bot/resources/valentines/zodiac_compatibility.json b/bot/resources/valentines/zodiac_compatibility.json index 4e337714..3971d40d 100644 --- a/bot/resources/valentines/zodiac_compatibility.json +++ b/bot/resources/valentines/zodiac_compatibility.json @@ -259,4 +259,4 @@ } ] -} \ No newline at end of file +} diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py index f2ec83df..8caf43bd 100644 --- a/bot/seasons/christmas/adventofcode.py +++ b/bot/seasons/christmas/adventofcode.py @@ -364,7 +364,7 @@ class AdventOfCode(commands.Cog): aoc_embed.set_footer(text="Last Updated") await ctx.send( - content=f"Here's the current global Top {number_of_people_to_display}! {Emojis.christmas_tree*3}\n\n{table}", # noqa + f"Here's the current global Top {number_of_people_to_display}! {Emojis.christmas_tree*3}\n\n{table}", embed=aoc_embed, ) diff --git a/bot/seasons/evergreen/bookmark.py b/bot/seasons/evergreen/bookmark.py index 7bdd362c..bd7d5c11 100644 --- a/bot/seasons/evergreen/bookmark.py +++ b/bot/seasons/evergreen/bookmark.py @@ -1,65 +1,65 @@ -import logging -import random - -import discord -from discord.ext import commands - -from bot.constants import Colours, ERROR_REPLIES, Emojis, bookmark_icon_url - -log = logging.getLogger(__name__) - - -class Bookmark(commands.Cog): - """Creates personal bookmarks by relaying a message link to the user's DMs.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.command(name="bookmark", aliases=("bm", "pin")) - async def bookmark( - self, - ctx: commands.Context, - target_message: discord.Message, - *, - title: str = "Bookmark" - ) -> None: - """Send the author a link to `target_message` via DMs.""" - # Prevent users from bookmarking a message in a channel they don't have access to - permissions = ctx.author.permissions_in(target_message.channel) - if not permissions.read_messages: - log.info(f"{ctx.author} tried to bookmark a message in #{target_message.channel} but has no permissions") - embed = discord.Embed( - title=random.choice(ERROR_REPLIES), - color=Colours.soft_red, - description="You don't have permission to view this channel." - ) - await ctx.send(embed=embed) - return - - embed = discord.Embed( - title=title, - colour=Colours.soft_green, - description=target_message.content - ) - embed.add_field(name="Wanna give it a visit?", value=f"[Visit original message]({target_message.jump_url})") - embed.set_author(name=target_message.author, icon_url=target_message.author.avatar_url) - embed.set_thumbnail(url=bookmark_icon_url) - - try: - await ctx.author.send(embed=embed) - except discord.Forbidden: - error_embed = discord.Embed( - title=random.choice(ERROR_REPLIES), - description=f"{ctx.author.mention}, please enable your DMs to receive the bookmark", - colour=Colours.soft_red - ) - await ctx.send(embed=error_embed) - else: - log.info(f"{ctx.author} bookmarked {target_message.jump_url} with title '{title}'") - await ctx.message.add_reaction(Emojis.envelope) - - -def setup(bot: commands.Bot) -> None: - """Load the Bookmark cog.""" - bot.add_cog(Bookmark(bot)) - log.info("Bookmark cog loaded") +import logging +import random + +import discord +from discord.ext import commands + +from bot.constants import Colours, ERROR_REPLIES, Emojis, bookmark_icon_url + +log = logging.getLogger(__name__) + + +class Bookmark(commands.Cog): + """Creates personal bookmarks by relaying a message link to the user's DMs.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.command(name="bookmark", aliases=("bm", "pin")) + async def bookmark( + self, + ctx: commands.Context, + target_message: discord.Message, + *, + title: str = "Bookmark" + ) -> None: + """Send the author a link to `target_message` via DMs.""" + # Prevent users from bookmarking a message in a channel they don't have access to + permissions = ctx.author.permissions_in(target_message.channel) + if not permissions.read_messages: + log.info(f"{ctx.author} tried to bookmark a message in #{target_message.channel} but has no permissions") + embed = discord.Embed( + title=random.choice(ERROR_REPLIES), + color=Colours.soft_red, + description="You don't have permission to view this channel." + ) + await ctx.send(embed=embed) + return + + embed = discord.Embed( + title=title, + colour=Colours.soft_green, + description=target_message.content + ) + embed.add_field(name="Wanna give it a visit?", value=f"[Visit original message]({target_message.jump_url})") + embed.set_author(name=target_message.author, icon_url=target_message.author.avatar_url) + embed.set_thumbnail(url=bookmark_icon_url) + + try: + await ctx.author.send(embed=embed) + except discord.Forbidden: + error_embed = discord.Embed( + title=random.choice(ERROR_REPLIES), + description=f"{ctx.author.mention}, please enable your DMs to receive the bookmark", + colour=Colours.soft_red + ) + await ctx.send(embed=error_embed) + else: + log.info(f"{ctx.author} bookmarked {target_message.jump_url} with title '{title}'") + await ctx.message.add_reaction(Emojis.envelope) + + +def setup(bot: commands.Bot) -> None: + """Load the Bookmark cog.""" + bot.add_cog(Bookmark(bot)) + log.info("Bookmark cog loaded") diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index fbadb7cc..d61e048b 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -334,7 +334,7 @@ class HacktoberStats(commands.Cog): return author_id, author_mention -def setup(bot): # Noqa +def setup(bot: commands.Bot) -> None: """Hacktoberstats Cog load.""" bot.add_cog(HacktoberStats(bot)) log.info("HacktoberStats cog loaded") -- cgit v1.2.3