1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
import logging
import typing as t
from bot.constants import Colours, Month
from bot.utils import resolve_current_month
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
if len(active_seasons) > 1:
log.warning(f"Multiple active season in month {current_month.name}")
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
|