diff options
Diffstat (limited to 'bot/seasons.py')
-rw-r--r-- | bot/seasons.py | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/bot/seasons.py b/bot/seasons.py new file mode 100644 index 00000000..55cfef3c --- /dev/null +++ b/bot/seasons.py @@ -0,0 +1,181 @@ +import logging +import typing as t + +from bot.constants import Colours, Month +from bot.utils import resolve_current_month +from bot.utils.exceptions import BrandingError + +log = logging.getLogger(__name__) + + +class SeasonBase: + """ + Base for Seasonal classes. + + This serves as the off-season fallback for when no specific + seasons are active. + + Seasons are 'registered' simply by inheriting from `SeasonBase`. + We discover them by calling `__subclasses__`. + """ + + season_name: str = "Evergreen" + bot_name: str = "SeasonalBot" + + colour: str = Colours.soft_green + description: str = "The default season!" + + branding_path: str = "seasonal/evergreen" + + months: t.Set[Month] = set(Month) + + +class Christmas(SeasonBase): + """Branding for December.""" + + season_name = "Festive season" + bot_name = "MerryBot" + + colour = Colours.soft_red + description = ( + "The time is here to get into the festive spirit! No matter who you are, where you are, " + "or what beliefs you may follow, we hope every one of you enjoy this festive season!" + ) + + branding_path = "seasonal/christmas" + + months = {Month.DECEMBER} + + +class Easter(SeasonBase): + """Branding for April.""" + + season_name = "Easter" + bot_name = "BunnyBot" + + colour = Colours.bright_green + description = ( + "Bunny here, bunny there, bunny everywhere! Here at Python Discord, we celebrate " + "our version of Easter during the entire month of April." + ) + + branding_path = "seasonal/easter" + + months = {Month.APRIL} + + +class Halloween(SeasonBase): + """Branding for October.""" + + season_name = "Halloween" + bot_name = "NeonBot" + + colour = Colours.orange + description = "Trick or treat?!" + + branding_path = "seasonal/halloween" + + months = {Month.OCTOBER} + + +class Pride(SeasonBase): + """Branding for June.""" + + season_name = "Pride" + bot_name = "ProudBot" + + colour = Colours.pink + description = ( + "The month of June is a special month for us at Python Discord. It is very important to us " + "that everyone feels welcome here, no matter their origin, identity or sexuality. During the " + "month of June, while some of you are participating in Pride festivals across the world, " + "we will be celebrating individuality and commemorating the history and challenges " + "of the LGBTQ+ community with a Pride event of our own!" + ) + + branding_path = "seasonal/pride" + + months = {Month.JUNE} + + +class Valentines(SeasonBase): + """Branding for February.""" + + season_name = "Valentines" + bot_name = "TenderBot" + + colour = Colours.pink + description = "Love is in the air!" + + branding_path = "seasonal/valentines" + + months = {Month.FEBRUARY} + + +class Wildcard(SeasonBase): + """Branding for August.""" + + season_name = "Wildcard" + bot_name = "RetroBot" + + colour = Colours.purple + description = "A season full of surprises!" + + months = {Month.AUGUST} + + +def get_all_seasons() -> t.List[t.Type[SeasonBase]]: + """Give all available season classes.""" + return [SeasonBase] + SeasonBase.__subclasses__() + + +def get_current_season() -> t.Type[SeasonBase]: + """Give active season, based on current UTC month.""" + current_month = resolve_current_month() + + active_seasons = tuple( + season + for season in SeasonBase.__subclasses__() + if current_month in season.months + ) + + if not active_seasons: + return SeasonBase + + return active_seasons[0] + + +def get_season(name: str) -> t.Optional[t.Type[SeasonBase]]: + """ + Give season such that its class name or its `season_name` attr match `name` (caseless). + + If no such season exists, return None. + """ + name = name.casefold() + + for season in get_all_seasons(): + matches = (season.__name__.casefold(), season.season_name.casefold()) + + if name in matches: + return season + + +def _validate_season_overlap() -> None: + """ + Raise BrandingError if there are any colliding seasons. + + This serves as a local test to ensure that seasons haven't been misconfigured. + """ + month_to_season = {} + + for season in SeasonBase.__subclasses__(): + for month in season.months: + colliding_season = month_to_season.get(month) + + if colliding_season: + raise BrandingError(f"Season {season} collides with {colliding_season} in {month.name}") + else: + month_to_season[month] = season + + +_validate_season_overlap() |