diff options
-rw-r--r-- | bot/constants.py | 1 | ||||
-rw-r--r-- | bot/seasons/christmas/__init__.py | 9 | ||||
-rw-r--r-- | bot/seasons/evergreen/__init__.py | 10 | ||||
-rw-r--r-- | bot/seasons/halloween/__init__.py | 9 | ||||
-rw-r--r-- | bot/seasons/season.py | 198 |
5 files changed, 170 insertions, 57 deletions
diff --git a/bot/constants.py b/bot/constants.py index 1294912a..5382d5f3 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -74,6 +74,7 @@ class Colours: class Emojis: star = "\u2B50" christmas_tree = u"\U0001F384" + check = "\u2611" class Tokens(NamedTuple): diff --git a/bot/seasons/christmas/__init__.py b/bot/seasons/christmas/__init__.py index cd5ce307..8eb78251 100644 --- a/bot/seasons/christmas/__init__.py +++ b/bot/seasons/christmas/__init__.py @@ -6,11 +6,4 @@ class Christmas(SeasonBase): start_date = "01/12" end_date = "31/12" bot_name = "Santabot" - - def __init__(self, bot): - self.bot = bot - - @property - def bot_avatar(self): - with open(self.avatar_path("christmas.png"), "rb") as avatar: - return bytearray(avatar.read()) + bot_avatar = "christmas.png" diff --git a/bot/seasons/evergreen/__init__.py b/bot/seasons/evergreen/__init__.py index e4367aaa..db5b5684 100644 --- a/bot/seasons/evergreen/__init__.py +++ b/bot/seasons/evergreen/__init__.py @@ -2,12 +2,4 @@ from bot.seasons import SeasonBase class Evergreen(SeasonBase): - bot_name = "SeasonalBot" - - def __init__(self, bot): - self.bot = bot - - @property - def bot_avatar(self): - with open(self.avatar_path("standard.png"), "rb") as avatar: - return bytearray(avatar.read()) + pass diff --git a/bot/seasons/halloween/__init__.py b/bot/seasons/halloween/__init__.py index 40b9ce90..4dd195fb 100644 --- a/bot/seasons/halloween/__init__.py +++ b/bot/seasons/halloween/__init__.py @@ -6,11 +6,4 @@ class Halloween(SeasonBase): start_date = "01/10" end_date = "31/10" bot_name = "Spookybot" - - def __init__(self, bot): - self.bot = bot - - @property - def bot_avatar(self): - with open(self.avatar_path("spooky.png"), "rb") as avatar: - return bytearray(avatar.read()) + bot_avatar = "spooky.png" diff --git a/bot/seasons/season.py b/bot/seasons/season.py index 591bbc2d..a86330e9 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -5,9 +5,10 @@ import logging import pkgutil from pathlib import Path +import discord from discord.ext import commands -from bot.constants import Client, Roles +from bot.constants import Client, Roles, bot from bot.decorators import with_role log = logging.getLogger(__name__) @@ -15,24 +16,23 @@ log = logging.getLogger(__name__) def get_seasons(): """ - Returns all the Season objects - located in bot/seasons/ + Returns all the Season objects located in bot/seasons/ """ seasons = [] - for module in pkgutil.iter_modules([Path('bot', 'seasons')]): + for module in pkgutil.iter_modules([Path("bot", "seasons")]): if module.ispkg: - seasons.append(module[1]) + seasons.append(module.name) return seasons def get_season_class(season_name): - season_lib = importlib.import_module(f'bot.seasons.{season_name}') + season_lib = importlib.import_module(f"bot.seasons.{season_name}") return getattr(season_lib, season_name.capitalize()) -def get_season(bot, season_name: str = None, date: datetime.date = None): +def get_season(season_name: str = None, date: datetime.datetime = None): """ Returns a Season object based on either a string or a date. """ @@ -52,25 +52,29 @@ def get_season(bot, season_name: str = None, date: datetime.date = None): if season_name: season_name = season_name.lower() if season_name not in seasons: - season_name = 'evergreen' + season_name = "evergreen" season_class = get_season_class(season_name) - return season_class(bot) + return season_class() # If not, we have to figure out if the date matches any of the seasons. - seasons.remove('evergreen') + seasons.remove("evergreen") for season_name in seasons: season_class = get_season_class(season_name) # check if date matches before returning an instance - if season_class.start() <= date <= season_class.end(): - return season_class(bot) + if season_class.is_between_dates(date): + return season_class() else: - evergreen_class = get_season_class('evergreen') - return evergreen_class(bot) + evergreen_class = get_season_class("evergreen") + return evergreen_class() class SeasonBase: name = None - date_format = "%d/%m-%Y" + date_format = "%d/%m/%Y" + start_date = None + end_date = None + bot_name: str = "SeasonalBot" + bot_avatar: str = "standard.png" @staticmethod def current_year(): @@ -78,23 +82,31 @@ class SeasonBase: @classmethod def start(cls): - return datetime.datetime.strptime(f"{cls.start_date}-{cls.current_year()}", cls.date_format).date() + if not cls.start_date: + return datetime.datetime.min + return datetime.datetime.strptime(f"{cls.start_date}/{cls.current_year()}", cls.date_format) @classmethod def end(cls): - return datetime.datetime.strptime(f"{cls.end_date}-{cls.current_year()}", cls.date_format).date() + if not cls.end_date: + return datetime.datetime.max + return datetime.datetime.strptime(f"{cls.end_date}/{cls.current_year()}", cls.date_format) - @staticmethod - def avatar_path(*path_segments): - return Path('bot', 'resources', 'avatars', *path_segments) + @classmethod + def is_between_dates(cls, date): + return cls.start() <= date <= cls.end() + + def get_avatar(self): + avatar_path = Path("bot", "resources", "avatars", self.bot_avatar) + with open(avatar_path, "rb") as avatar_file: + return bytearray(avatar_file.read()) async def load(self): """ - Loads in the bot name, the bot avatar, - and the extensions that are relevant to that season. + Loads in the bot name, the bot avatar, and the extensions that are relevant to that season. """ - guild = self.bot.get_guild(Client.guild) + guild = bot.get_guild(Client.guild) # Change only nickname if in debug mode due to ratelimits for user edits if Client.debug: @@ -102,13 +114,13 @@ class SeasonBase: log.debug(f"Changing nickname to {self.bot_name}") await guild.me.edit(nick=self.bot_name) else: - if self.bot.user.name != self.bot_name: + if bot.user.name != self.bot_name: # attempt to change user details log.debug(f"Changing username to {self.bot_name}") - await self.bot.user.edit(name=self.bot_name, avatar=self.bot_avatar) + await bot.user.edit(username=self.bot_name, avatar=self.get_avatar()) # fallback on nickname if failed due to ratelimit - if self.bot.user.name != self.bot_name: + if bot.user.name != self.bot_name: log.info(f"User details failed to change: Changing nickname to {self.bot_name}") await guild.me.edit(nick=self.bot_name) @@ -121,13 +133,13 @@ class SeasonBase: extensions = [] for ext_folder in {self.name, "evergreen"}: if ext_folder: - log.info(f'Start loading extensions from seasons/{ext_folder}/') - path = Path('bot', 'seasons', ext_folder) + log.info(f"Start loading extensions from seasons/{ext_folder}/") + path = Path("bot", "seasons", ext_folder) for ext_name in [i[1] for i in pkgutil.iter_modules([path])]: extensions.append(f"bot.seasons.{ext_folder}.{ext_name}") # Finally we can load all the cogs we've prepared. - self.bot.load_extensions(extensions) + bot.load_extensions(extensions) class SeasonManager: @@ -137,7 +149,7 @@ class SeasonManager: def __init__(self, bot): self.bot = bot - self.season = get_season(bot, date=datetime.date.today()) + self.season = get_season(date=datetime.datetime.utcnow()) self.season_task = bot.loop.create_task(self.load_seasons()) # Figure out number of seconds until a minute past midnight @@ -161,7 +173,7 @@ class SeasonManager: self.sleep_time = 86400 # next time, sleep for 24 hours. # If the season has changed, load it. - new_season = get_season(self.bot, date=datetime.date.today()) + new_season = get_season(date=datetime.datetime.utcnow()) if new_season != self.season: await self.season.load() @@ -172,9 +184,131 @@ class SeasonManager: Changes the currently active season on the bot. """ - self.season = get_season(self.bot, season_name=new_season) + self.season = get_season(season_name=new_season) await self.season.load() await ctx.send(f"Season changed to {new_season}.") + @with_role(Roles.moderator, Roles.admin, Roles.owner) + @commands.command(name="seasons") + async def show_seasons(self, ctx): + """ + Shows the available seasons and their dates. + """ + + # sort by start order, followed by lower duration + def season_key(season: SeasonBase): + return season.start(), season.end() - datetime.datetime.max + + current_season = self.season.name + + entries = [] + seasons = [get_season_class(s) for s in get_seasons()] + for season in sorted(seasons, key=season_key): + start = season.start_date + end = season.end_date + if start and not end: + period = f"From {start}" + elif end and not start: + period = f"Until {end}" + elif not end and not start: + period = f"Always" + else: + period = f"{start} to {end}" + + # bold period if current date matches season date range + is_current = season.is_between_dates(datetime.datetime.utcnow()) + pdec = "**" if is_current else "" + + # underline currently active season + is_active = current_season == season.name + sdec = "__" if is_active else "" + + forced_space = "\u200b " + entries.append( + f"**{sdec}{season.__name__}:{sdec}**\n" + f"{forced_space*3}{pdec}{period}{pdec}" + ) + + embed = discord.Embed(description="\n".join(entries), colour=ctx.guild.me.colour) + embed.set_author(name="Seasons") + await ctx.send(embed=embed) + + @with_role(Roles.moderator, Roles.admin, Roles.owner) + @commands.group() + async def refresh(self, ctx): + """ + Refreshes certain seasonal elements without reloading seasons. + """ + if not ctx.invoked_subcommand: + await ctx.invoke(bot.get_command("help"), "refresh") + + @refresh.command(name="avatar") + async def refresh_avatar(self, ctx): + """ + Re-applies the bot avatar for the currently loaded season. + """ + + # track old avatar hash for later comparison + old_avatar = bot.user.avatar + + # attempt the change + await bot.user.edit(avatar=self.season.get_avatar()) + + if bot.user.avatar != old_avatar: + log.debug(f"Avatar changed to {self.season.bot_avatar}") + colour = ctx.guild.me.colour + title = "Avatar Refreshed" + else: + log.debug(f"Changing avatar failed: {self.season.bot_avatar}") + colour = discord.Colour.red() + title = "Avatar Failed to Refresh" + + # report back details + season_name = type(self.season).__name__ + embed = discord.Embed( + description=f"**Season:** {season_name}\n**Avatar:** {self.season.bot_avatar}", + colour=colour + ) + embed.set_author(name=title) + embed.set_thumbnail(url=bot.user.avatar_url_as(format="png")) + await ctx.send(embed=embed) + + @refresh.command(name="username", aliases=("name",)) + async def refresh_username(self, ctx): + """ + Re-applies the bot username for the currently loaded season. + """ + + # track old username for later comparison + old_username = str(bot.user) + + # attempt the change + await bot.user.edit(username=self.season.bot_name) + + if str(bot.user) != old_username: + log.debug(f"Username changed to {self.season.bot_name}") + colour = ctx.guild.me.colour + title = "Username Refreshed" + changed_element = "Username" + new_name = str(bot.user) + else: + log.debug(f"Changing username failed: Changing nickname to {self.season.bot_name}") + new_name = self.season.bot_name + await ctx.guild.me.edit(nick=new_name) + colour = discord.Colour.red() + title = "Username Failed to Refresh" + changed_element = "Nickname" + + # report back details + season_name = type(self.season).__name__ + embed = discord.Embed( + description=f"**Season:** {season_name}\n" + f"**Old Username:** {old_username}\n" + f"**New {changed_element}:** {new_name}", + colour=colour + ) + embed.set_author(name=title) + await ctx.send(embed=embed) + def __unload(self): self.season_task.cancel() |