aboutsummaryrefslogtreecommitdiffstats
path: root/bot/seasons
diff options
context:
space:
mode:
Diffstat (limited to 'bot/seasons')
-rw-r--r--bot/seasons/christmas/adventofcode.py2
-rw-r--r--bot/seasons/easter/__init__.py2
-rw-r--r--bot/seasons/easter/egg_facts.py2
-rw-r--r--bot/seasons/evergreen/bookmark.py130
-rw-r--r--bot/seasons/evergreen/game.py395
-rw-r--r--bot/seasons/evergreen/issues.py5
-rw-r--r--bot/seasons/evergreen/snakes/snakes_cog.py4
-rw-r--r--bot/seasons/halloween/candy_collection.py8
-rw-r--r--bot/seasons/halloween/hacktoberstats.py14
-rw-r--r--bot/seasons/halloween/halloween_facts.py2
-rw-r--r--bot/seasons/pride/__init__.py2
-rw-r--r--bot/seasons/pride/pride_facts.py2
-rw-r--r--bot/seasons/season.py15
-rw-r--r--bot/seasons/valentines/be_my_valentine.py8
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)."""