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 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 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