diff options
Diffstat (limited to 'bot/seasons')
| -rw-r--r-- | bot/seasons/christmas/adventofcode.py | 2 | ||||
| -rw-r--r-- | bot/seasons/easter/__init__.py | 2 | ||||
| -rw-r--r-- | bot/seasons/easter/egg_facts.py | 2 | ||||
| -rw-r--r-- | bot/seasons/evergreen/bookmark.py | 130 | ||||
| -rw-r--r-- | bot/seasons/evergreen/game.py | 395 | ||||
| -rw-r--r-- | bot/seasons/evergreen/issues.py | 5 | ||||
| -rw-r--r-- | bot/seasons/evergreen/snakes/snakes_cog.py | 4 | ||||
| -rw-r--r-- | bot/seasons/halloween/candy_collection.py | 8 | ||||
| -rw-r--r-- | bot/seasons/halloween/hacktoberstats.py | 14 | ||||
| -rw-r--r-- | bot/seasons/halloween/halloween_facts.py | 2 | ||||
| -rw-r--r-- | bot/seasons/pride/__init__.py | 2 | ||||
| -rw-r--r-- | bot/seasons/pride/pride_facts.py | 2 | ||||
| -rw-r--r-- | bot/seasons/season.py | 15 | ||||
| -rw-r--r-- | bot/seasons/valentines/be_my_valentine.py | 8 |
14 files changed, 498 insertions, 93 deletions
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/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/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/evergreen/game.py b/bot/seasons/evergreen/game.py new file mode 100644 index 00000000..e6700937 --- /dev/null +++ b/bot/seasons/evergreen/game.py @@ -0,0 +1,395 @@ +import difflib +import logging +import random +from datetime import datetime as dt +from enum import IntEnum +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 +from bot.constants import STAFF_ROLES, Tokens +from bot.decorators import with_role +from bot.pagination import ImagePaginator, LinePaginator + +# Base URL of IGDB API +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 = ( + "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 = ( + "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 = 'fields name, url, storyline, total_rating, total_rating_count; limit 50; search "{term}";' + +# Pages templates +# Game embed layout +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 = ( + "**[{name}]({url})**\n" + "{description}" + "**Founded:** {founded}\n" + "**Developed:** {developed}\n" + "**Published:** {published}" +) + +# For .games search command line layout +GAME_SEARCH_LINE = ( + "**[{name}]({url})**\n" + "{rating}/100 :star: (based on {rating_count} ratings)\n" +) + +# URL templates +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 = { + "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.""" + + 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: SeasonalBot): + 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) + async def refresh_genres_task(self) -> None: + """Refresh genres in every hour.""" + 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.") + + 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;" + 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} + + # Replace complex names with names from ALIASES + 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_name] = genre + + @group(name="games", aliases=["game"], invoke_without_command=True) + 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. Supported formats: + - .games <genre> + - .games <amount> <genre> + """ + # When user didn't specified genre, send help message + if genre is None: + await ctx.send_help("games") + return + + # Capitalize genre for check + genre = "".join(genre).capitalize() + + # Check for amounts, max is 25 and min 1 + 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 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)) + 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 ''}") + 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.title()} 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 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(amount, sort="total_rating desc", + additional_body="where total_rating >= 90; sort total_rating_count desc;") + + 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"]) + 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_term: str) -> None: + """Find games by name.""" + lines = await self.search_games(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: + """ + Get random Game Companies companies from IGDB API. + + Support amount parameter. Max is 25, min is 1. + """ + if not 1 <= amount <= 25: + await ctx.send("Your provided amount is out of range. Our minimum is 1 and maximum 25.") + return + + # 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")) + + @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, + sort: Optional[str] = None, + additional_body: str = "", + offset: int = 0 + ) -> List[Dict[str, Any]]: + """ + Get list of games from IGDB API by parameters that is provided. + + 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. + """ + # 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.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: + return await resp.json() + + 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.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 "?" + + # 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 = [c["company"]["name"] for c 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": ", ".join(companies), + "storyline": data["storyline"] if "storyline" in data else "" + } + page = GAME_PAGE.format(**formatting) + + return page, url + + 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 = 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() + + # Loop over games, format them to good format, make line and append this to total lines + for game in data: + 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.format(**formatting) + lines.append(line) + + return lines + + async def get_companies_list(self, limit: int, offset: int = 0) -> List[Dict[str, Any]]: + """ + 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.format(**{ + "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.""" + # Generate URL of company logo + 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 "?" + + # 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 "?" + + 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.format(**formatting) + + return page, url + + +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)) 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/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/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/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index b7b4122d..d61e048b 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) @@ -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") 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/season.py b/bot/seasons/season.py index e7b7a69c..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 = 86400 # Next time, sleep for 24 hours. + 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") diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index a073e1bd..ab8ea290 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: @@ -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).""" |