aboutsummaryrefslogtreecommitdiffstats
path: root/bot
diff options
context:
space:
mode:
authorGravatar Mark <[email protected]>2019-12-03 22:17:55 -0800
committerGravatar GitHub <[email protected]>2019-12-03 22:17:55 -0800
commit8e21889d68be318c662a464ecaaeaf0bc1864424 (patch)
tree15dc5c0860b2180d682f096df061360f468a5897 /bot
parentReadd user agent to request header (diff)
parentMerge pull request #308 from python-discord/dependabot/pip/pillow-6.2.0 (diff)
Merge branch 'master' into exclude-draft-prs
Diffstat (limited to 'bot')
-rw-r--r--bot/constants.py14
-rw-r--r--bot/resources/advent_of_code/about.json2
-rw-r--r--bot/resources/halloween/monster.json41
-rw-r--r--bot/seasons/christmas/__init__.py9
-rw-r--r--bot/seasons/christmas/adventofcode.py40
-rw-r--r--bot/seasons/easter/easter_riddle.py2
-rw-r--r--bot/seasons/easter/egg_decorating.py2
-rw-r--r--bot/seasons/evergreen/__init__.py12
-rw-r--r--bot/seasons/evergreen/issues.py73
-rw-r--r--bot/seasons/halloween/hacktober-issue-finder.py2
-rw-r--r--bot/seasons/halloween/hacktoberstats.py2
-rw-r--r--bot/seasons/halloween/monsterbio.py56
-rw-r--r--bot/seasons/halloween/timeleft.py2
-rw-r--r--bot/seasons/season.py9
14 files changed, 218 insertions, 48 deletions
diff --git a/bot/constants.py b/bot/constants.py
index 0d4321c8..c09d8369 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -1,6 +1,7 @@
import logging
from os import environ
from typing import NamedTuple
+from datetime import datetime
__all__ = (
"AdventOfCode", "Channels", "Client", "Colours", "Emojis", "Hacktoberfest", "Roles", "Tokens",
@@ -13,15 +14,16 @@ log = logging.getLogger(__name__)
class AdventOfCode:
leaderboard_cache_age_threshold_seconds = 3600
- leaderboard_id = 363275
+ leaderboard_id = 631135
leaderboard_join_code = str(environ.get("AOC_JOIN_CODE", None))
leaderboard_max_displayed_members = 10
- year = 2018
+ year = int(environ.get("AOC_YEAR", datetime.utcnow().year))
role_id = int(environ.get("AOC_ROLE_ID", 518565788744024082))
class Channels(NamedTuple):
admins = 365960823622991872
+ advent_of_code = 517745814039166986
announcements = int(environ.get("CHANNEL_ANNOUNCEMENTS", 354619224620138496))
big_brother_logs = 468507907357409333
bot = 267659945086812160
@@ -72,12 +74,14 @@ class Colours:
soft_green = 0x68c290
soft_red = 0xcd6d6d
yellow = 0xf9f586
+ purple = 0xb734eb
class Emojis:
star = "\u2B50"
christmas_tree = "\U0001F384"
check = "\u2611"
+ envelope = "\U0001F4E8"
terning1 = "<:terning1:431249668983488527>"
terning2 = "<:terning2:462339216987127808>"
@@ -86,6 +90,12 @@ class Emojis:
terning5 = "<:terning5:431249716328792064>"
terning6 = "<:terning6:431249726705369098>"
+ issue = "<:IssueOpen:629695470327037963>"
+ issue_closed = "<:IssueClosed:629695470570307614>"
+ pull_request = "<:PROpen:629695470175780875>"
+ pull_request_closed = "<:PRClosed:629695470519713818>"
+ merge = "<:PRMerged:629695470570176522>"
+
class Lovefest:
role_id = int(environ.get("LOVEFEST_ROLE_ID", 542431903886606399))
diff --git a/bot/resources/advent_of_code/about.json b/bot/resources/advent_of_code/about.json
index 4abf9145..b1d16a93 100644
--- a/bot/resources/advent_of_code/about.json
+++ b/bot/resources/advent_of_code/about.json
@@ -16,7 +16,7 @@
},
{
"name": "How does scoring work?",
- "value": "Getting a star first is worth 100 points, second is 99, and so on down to 1 point at 100th place.\n\nCheck out AoC's [global leaderboard](https://adventofcode.com/2018/leaderboard) to see who's leading this year's event!",
+ "value": "Getting a star first is worth 100 points, second is 99, and so on down to 1 point at 100th place.\n\nCheck out AoC's [global leaderboard](https://adventofcode.com/leaderboard) to see who's leading this year's event!",
"inline": false
},
{
diff --git a/bot/resources/halloween/monster.json b/bot/resources/halloween/monster.json
new file mode 100644
index 00000000..5958dc9c
--- /dev/null
+++ b/bot/resources/halloween/monster.json
@@ -0,0 +1,41 @@
+{
+ "monster_type": [
+ ["El", "Go", "Ma", "Nya", "Wo", "Hom", "Shar", "Gronn", "Grom", "Blar"],
+ ["gaf", "mot", "phi", "zyme", "qur", "tile", "pim"],
+ ["yam", "ja", "rok", "pym", "el"],
+ ["ya", "tor", "tir", "tyre", "pam"]
+ ],
+ "scientist_first_name": ["Ellis", "Elliot", "Rick", "Laurent", "Morgan", "Sophia", "Oak"],
+ "scientist_last_name": ["E. M.", "E. T.", "Smith", "Schimm", "Schiftner", "Smile", "Tomson", "Thompson", "Huffson", "Argor", "Lephtain", "S. M.", "A. R.", "P. G."],
+ "verb": [
+ "discovered", "created", "found"
+ ],
+ "adjective": [
+ "ferocious", "spectacular", "incredible", "terrifying"
+ ],
+ "physical_adjective": [
+ "springy", "rubbery", "bouncy", "tough", "notched", "chipped"
+ ],
+ "color": [
+ "blue", "green", "teal", "black", "pure white", "obsidian black", "purple", "bright red", "bright yellow"
+ ],
+ "attribute": [
+ "horns", "teeth", "shell", "fur", "bones", "exoskeleton", "spikes"
+ ],
+ "ability": [
+ "breathe fire", "devour dreams", "lift thousand-pound weights", "devour metal", "chew up diamonds", "create diamonds", "create gemstones", "breathe icy cold air", "spit poison", "live forever"
+ ],
+ "ingredients": [
+ "witch's eye", "frog legs", "slime", "true love's kiss", "a lock of golden hair", "the skin of a snake", "a never-melting chunk of ice"
+ ],
+ "time": [
+ "dusk", "dawn", "mid-day", "midnight on a full moon", "midnight on Halloween night", "the time of a solar eclipse", "the time of a lunar eclipse."
+ ],
+ "year": [
+ "1996", "1594", "1330", "1700"
+ ],
+ "biography_text": [
+ {"scientist_first_name": 1, "scientist_last_name": 1, "verb": 1, "adjective": 1, "attribute": 1, "ability": 1, "color": 1, "year": 1, "time": 1, "physical_adjective": 1, "text": "Your name is {monster_name}, a member of the {adjective} species {monster_species}. The first {monster_species} was {verb} by {scientist_first_name} {scientist_last_name} in {year} at {time}. The species {monster_species} is known for its {physical_adjective} {color} {attribute}. It is said to even be able to {ability}!"},
+ {"scientist_first_name": 1, "scientist_last_name": 1, "adjective": 1, "attribute": 1, "physical_adjective": 1, "ingredients": 2, "time": 1, "ability": 1, "verb": 1, "color": 1, "year": 1, "text": "The {monster_species} is an {adjective} species, and you, {monster_name}, are no exception. {monster_species} is famed for its {physical_adjective} {attribute}. Whispers say that when brewed with {ingredients[0]} and {ingredients[1]} at {time}, a foul, {color} brew will be produced, granting it's drinker the ability to {ability}! This species was {verb} by {scientist_first_name} {scientist_last_name} in {year}."}
+ ]
+}
diff --git a/bot/seasons/christmas/__init__.py b/bot/seasons/christmas/__init__.py
index ae93800e..4287efb7 100644
--- a/bot/seasons/christmas/__init__.py
+++ b/bot/seasons/christmas/__init__.py
@@ -1,3 +1,5 @@
+import datetime
+
from bot.constants import Colours
from bot.seasons import SeasonBase
@@ -22,5 +24,10 @@ class Christmas(SeasonBase):
colour = Colours.dark_green
icon = (
- "/logos/logo_seasonal/christmas/festive.png",
+ "/logos/logo_seasonal/christmas/2019/festive_512.gif",
)
+
+ @classmethod
+ def end(cls) -> datetime.datetime:
+ """Overload the `SeasonBase` method to account for the event ending in the next year."""
+ return datetime.datetime.strptime(f"{cls.end_date}/{cls.current_year() + 1}", cls.date_format)
diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py
index 513c1020..71da8d94 100644
--- a/bot/seasons/christmas/adventofcode.py
+++ b/bot/seasons/christmas/adventofcode.py
@@ -13,7 +13,7 @@ from bs4 import BeautifulSoup
from discord.ext import commands
from pytz import timezone
-from bot.constants import AdventOfCode as AocConfig, Channels, Colours, Emojis, Tokens
+from bot.constants import AdventOfCode as AocConfig, Channels, Colours, Emojis, Tokens, WHITELISTED_CHANNELS
from bot.decorators import override_in_channel
log = logging.getLogger(__name__)
@@ -24,6 +24,8 @@ AOC_SESSION_COOKIE = {"session": Tokens.aoc_session_cookie}
EST = timezone("EST")
COUNTDOWN_STEP = 60 * 5
+AOC_WHITELIST = WHITELISTED_CHANNELS + (Channels.advent_of_code,)
+
def is_in_advent() -> bool:
"""Utility function to check if we are between December 1st and December 25th."""
@@ -126,7 +128,7 @@ class AdventOfCode(commands.Cog):
self.status_task = asyncio.ensure_future(self.bot.loop.create_task(status_coro))
@commands.group(name="adventofcode", aliases=("aoc",), invoke_without_command=True)
- @override_in_channel()
+ @override_in_channel(AOC_WHITELIST)
async def adventofcode_group(self, ctx: commands.Context) -> None:
"""All of the Advent of Code commands."""
await ctx.send_help(ctx.command)
@@ -136,6 +138,7 @@ class AdventOfCode(commands.Cog):
aliases=("sub", "notifications", "notify", "notifs"),
brief="Notifications for new days"
)
+ @override_in_channel(AOC_WHITELIST)
async def aoc_subscribe(self, ctx: commands.Context) -> None:
"""Assign the role for notifications about new days being ready."""
role = ctx.guild.get_role(AocConfig.role_id)
@@ -150,6 +153,7 @@ class AdventOfCode(commands.Cog):
f"If you don't want them any more, run `{unsubscribe_command}` instead.")
@adventofcode_group.command(name="unsubscribe", aliases=("unsub",), brief="Notifications for new days")
+ @override_in_channel(AOC_WHITELIST)
async def aoc_unsubscribe(self, ctx: commands.Context) -> None:
"""Remove the role for notifications about new days being ready."""
role = ctx.guild.get_role(AocConfig.role_id)
@@ -161,14 +165,26 @@ class AdventOfCode(commands.Cog):
await ctx.send("Hey, you don't even get any notifications about new Advent of Code tasks currently anyway.")
@adventofcode_group.command(name="countdown", aliases=("count", "c"), brief="Return time left until next day")
+ @override_in_channel(AOC_WHITELIST)
async def aoc_countdown(self, ctx: commands.Context) -> None:
"""Return time left until next day."""
if not is_in_advent():
datetime_now = datetime.now(EST)
- december_first = datetime(datetime_now.year + 1, 12, 1, tzinfo=EST)
- delta = december_first - datetime_now
+
+ # Calculate the delta to this & next year's December 1st to see which one is closest and not in the past
+ this_year = datetime(datetime_now.year, 12, 1, tzinfo=EST)
+ next_year = datetime(datetime_now.year + 1, 12, 1, tzinfo=EST)
+ deltas = (dec_first - datetime_now for dec_first in (this_year, next_year))
+ delta = min(delta for delta in deltas if delta >= timedelta()) # timedelta() gives 0 duration delta
+
+ # Add a finer timedelta if there's less than a day left
+ if delta.days == 0:
+ delta_str = f"approximately {delta.seconds // 3600} hours"
+ else:
+ delta_str = f"{delta.days} days"
+
await ctx.send(f"The Advent of Code event is not currently running. "
- f"The next event will start in {delta.days} days.")
+ f"The next event will start in {delta_str}.")
return
tomorrow, time_left = time_left_to_aoc_midnight()
@@ -178,11 +194,13 @@ class AdventOfCode(commands.Cog):
await ctx.send(f"There are {hours} hours and {minutes} minutes left until day {tomorrow.day}.")
@adventofcode_group.command(name="about", aliases=("ab", "info"), brief="Learn about Advent of Code")
+ @override_in_channel(AOC_WHITELIST)
async def about_aoc(self, ctx: commands.Context) -> None:
"""Respond with an explanation of all things Advent of Code."""
await ctx.send("", embed=self.cached_about_aoc)
- @adventofcode_group.command(name="join", aliases=("j",), brief="Learn how to join PyDis' private AoC leaderboard")
+ @adventofcode_group.command(name="join", aliases=("j",), brief="Learn how to join the leaderboard (via DM)")
+ @override_in_channel(AOC_WHITELIST)
async def join_leaderboard(self, ctx: commands.Context) -> None:
"""DM the user the information for joining the PyDis AoC private leaderboard."""
author = ctx.message.author
@@ -197,12 +215,15 @@ class AdventOfCode(commands.Cog):
except discord.errors.Forbidden:
log.debug(f"{author.name} ({author.id}) has disabled DMs from server members")
await ctx.send(f":x: {author.mention}, please (temporarily) enable DMs to receive the join code")
+ else:
+ await ctx.message.add_reaction(Emojis.envelope)
@adventofcode_group.command(
name="leaderboard",
aliases=("board", "lb"),
brief="Get a snapshot of the PyDis private AoC leaderboard",
)
+ @override_in_channel(AOC_WHITELIST)
async def aoc_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10) -> None:
"""
Pull the top number_of_people_to_display members from the PyDis leaderboard and post an embed.
@@ -244,6 +265,7 @@ class AdventOfCode(commands.Cog):
aliases=("dailystats", "ds"),
brief="Get daily statistics for the PyDis private leaderboard"
)
+ @override_in_channel(AOC_WHITELIST)
async def private_leaderboard_daily_stats(self, ctx: commands.Context) -> None:
"""
Respond with a table of the daily completion statistics for the PyDis private leaderboard.
@@ -287,6 +309,7 @@ class AdventOfCode(commands.Cog):
aliases=("globalboard", "gb"),
brief="Get a snapshot of the global AoC leaderboard",
)
+ @override_in_channel(AOC_WHITELIST)
async def global_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10) -> None:
"""
Pull the top number_of_people_to_display members from the global AoC leaderboard and post an embed.
@@ -397,6 +420,11 @@ class AdventOfCode(commands.Cog):
else:
self.cached_private_leaderboard = await AocPrivateLeaderboard.from_url()
+ def cog_unload(self) -> None:
+ """Cancel season-related tasks on cog unload."""
+ self.countdown_task.cancel()
+ self.status_task.cancel()
+
class AocMember:
"""Object representing the Advent of Code user."""
diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py
index 4b98b204..f5b1aac7 100644
--- a/bot/seasons/easter/easter_riddle.py
+++ b/bot/seasons/easter/easter_riddle.py
@@ -83,7 +83,7 @@ class EasterRiddle(commands.Cog):
self.current_channel = None
@commands.Cog.listener()
- async def on_message(self, message: discord.Messaged) -> None:
+ async def on_message(self, message: discord.Message) -> None:
"""If a non-bot user enters a correct answer, their username gets added to self.winners."""
if self.current_channel != message.channel:
return
diff --git a/bot/seasons/easter/egg_decorating.py b/bot/seasons/easter/egg_decorating.py
index 51f52264..23df95f1 100644
--- a/bot/seasons/easter/egg_decorating.py
+++ b/bot/seasons/easter/egg_decorating.py
@@ -46,7 +46,7 @@ class EggDecorating(commands.Cog):
@commands.command(aliases=["decorateegg"])
async def eggdecorate(
self, ctx: commands.Context, *colours: Union[discord.Colour, str]
- ) -> Union[Image, discord.Message]:
+ ) -> Union[Image.Image, discord.Message]:
"""
Picks a random egg design and decorates it using the given colours.
diff --git a/bot/seasons/evergreen/__init__.py b/bot/seasons/evergreen/__init__.py
index b95f3528..b3d0dc63 100644
--- a/bot/seasons/evergreen/__init__.py
+++ b/bot/seasons/evergreen/__init__.py
@@ -6,8 +6,12 @@ class Evergreen(SeasonBase):
bot_icon = "/logos/logo_seasonal/evergreen/logo_evergreen.png"
icon = (
- "/logos/logo_animated/heartbeat/heartbeat.gif",
- "/logos/logo_animated/spinner/spinner.gif",
- "/logos/logo_animated/tongues/tongues.gif",
- "/logos/logo_animated/winky/winky.gif",
+ "/logos/logo_animated/heartbeat/heartbeat_512.gif",
+ "/logos/logo_animated/spinner/spinner_512.gif",
+ "/logos/logo_animated/tongues/tongues_512.gif",
+ "/logos/logo_animated/winky/winky_512.gif",
+ "/logos/logo_animated/jumper/jumper_512.gif",
+ "/logos/logo_animated/apple/apple_512.gif",
+ "/logos/logo_animated/blinky/blinky_512.gif",
+ "/logos/logo_animated/runner/runner_512.gif",
)
diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py
index 438ab475..c7501a5d 100644
--- a/bot/seasons/evergreen/issues.py
+++ b/bot/seasons/evergreen/issues.py
@@ -3,10 +3,16 @@ import logging
import discord
from discord.ext import commands
-from bot.constants import Colours
+from bot.constants import Channels, 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!",
+ 403: "Rate limit has been hit! Please try again later!"
+}
class Issues(commands.Cog):
@@ -15,43 +21,58 @@ class Issues(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
- @commands.command(aliases=("issues",))
- @override_in_channel()
+ @commands.command(aliases=("pr",))
+ @override_in_channel(ISSUE_WHITELIST)
async def issue(
self, ctx: commands.Context, number: int, repository: str = "seasonalbot", user: str = "python-discord"
) -> None:
"""Command to retrieve issues from a GitHub repository."""
- api_url = f"https://api.github.com/repos/{user}/{repository}/issues/{number}"
- failed_status = {
- 404: f"Issue #{number} doesn't exist in the repository {user}/{repository}.",
- 403: f"Rate limit exceeded. Please wait a while before trying again!"
- }
+ url = f"https://api.github.com/repos/{user}/{repository}/issues/{number}"
+ merge_url = f"https://api.github.com/repos/{user}/{repository}/pulls/{number}/merge"
- async with self.bot.http_session.get(api_url) as r:
+ log.trace(f"Querying GH issues API: {url}")
+ async with self.bot.http_session.get(url) as r:
json_data = await r.json()
- response_code = r.status
-
- if response_code in failed_status:
- return await ctx.send(failed_status[response_code])
- repo_url = f"https://github.com/{user}/{repository}"
- issue_embed = discord.Embed(colour=Colours.bright_green)
- issue_embed.add_field(name="Repository", value=f"[{user}/{repository}]({repo_url})", inline=False)
- issue_embed.add_field(name="Issue Number", value=f"#{number}", inline=False)
- issue_embed.add_field(name="Status", value=json_data["state"].title())
- issue_embed.add_field(name="Link", value=json_data["html_url"], inline=False)
+ if r.status in BAD_RESPONSE:
+ log.warning(f"Received response {r.status} from: {url}")
+ return await ctx.send(f"[{str(r.status)}] {BAD_RESPONSE.get(r.status)}")
- description = json_data["body"]
- if len(description) > 1024:
- placeholder = " [...]"
- description = f"{description[:1024 - len(placeholder)]}{placeholder}"
+ # The initial API request is made to the issues API endpoint, which will return information
+ # if the issue or PR is present. However, the scope of information returned for PRs differs
+ # from issues: if the 'issues' key is present in the response then we can pull the data we
+ # need from the initial API call.
+ if "issues" in json_data.get("html_url"):
+ if json_data.get("state") == "open":
+ icon_url = Emojis.issue
+ else:
+ icon_url = Emojis.issue_closed
- issue_embed.add_field(name="Description", value=description, inline=False)
+ # If the 'issues' key is not contained in the API response and there is no error code, then
+ # we know that a PR has been requested and a call to the pulls API endpoint is necessary
+ # to get the desired information for the PR.
+ else:
+ log.trace(f"PR provided, querying GH pulls API for additional information: {merge_url}")
+ async with self.bot.http_session.get(merge_url) as m:
+ if json_data.get("state") == "open":
+ icon_url = Emojis.pull_request
+ # When the status is 204 this means that the state of the PR is merged
+ elif m.status == 204:
+ icon_url = Emojis.merge
+ else:
+ icon_url = Emojis.pull_request_closed
- await ctx.send(embed=issue_embed)
+ issue_url = json_data.get("html_url")
+ description_text = f"[{repository}] #{number} {json_data.get('title')}"
+ resp = discord.Embed(
+ colour=Colours.bright_green,
+ description=f"{icon_url} [{description_text}]({issue_url})"
+ )
+ resp.set_author(name="GitHub", url=issue_url)
+ await ctx.send(embed=resp)
def setup(bot: commands.Bot) -> None:
- """Github Issues Cog Load."""
+ """Cog Retrieves Issues From Github."""
bot.add_cog(Issues(bot))
log.info("Issues cog loaded")
diff --git a/bot/seasons/halloween/hacktober-issue-finder.py b/bot/seasons/halloween/hacktober-issue-finder.py
index 2f1d6ac7..10732374 100644
--- a/bot/seasons/halloween/hacktober-issue-finder.py
+++ b/bot/seasons/halloween/hacktober-issue-finder.py
@@ -58,7 +58,7 @@ class HacktoberIssues(commands.Cog):
else:
url = URL
if self.cache_normal is not None:
- page = random.randint(1, min(1000, self.cache_normal["total_count"]))
+ page = random.randint(1, min(1000, self.cache_normal["total_count"]) // 100)
url += f"&page={page}"
log.debug(f"making api request to url: {url}")
diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py
index ffcb6eaf..b7b4122d 100644
--- a/bot/seasons/halloween/hacktoberstats.py
+++ b/bot/seasons/halloween/hacktoberstats.py
@@ -58,6 +58,7 @@ class HacktoberStats(commands.Cog):
await self.get_stats(ctx, github_username)
@hacktoberstats_group.command(name="link")
+ @override_in_channel(HACKTOBER_WHITELIST)
async def link_user(self, ctx: commands.Context, github_username: str = None) -> None:
"""
Link the invoking user's Github github_username to their Discord ID.
@@ -91,6 +92,7 @@ class HacktoberStats(commands.Cog):
await ctx.send(f"{author_mention}, a GitHub username is required to link your account")
@hacktoberstats_group.command(name="unlink")
+ @override_in_channel(HACKTOBER_WHITELIST)
async def unlink_user(self, ctx: commands.Context) -> None:
"""Remove the invoking user's account link from the log."""
author_id, author_mention = HacktoberStats._author_mention_from_context(ctx)
diff --git a/bot/seasons/halloween/monsterbio.py b/bot/seasons/halloween/monsterbio.py
new file mode 100644
index 00000000..bfa8a026
--- /dev/null
+++ b/bot/seasons/halloween/monsterbio.py
@@ -0,0 +1,56 @@
+import json
+import logging
+import random
+from pathlib import Path
+
+import discord
+from discord.ext import commands
+
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+with open(Path("bot/resources/halloween/monster.json"), "r", encoding="utf8") as f:
+ TEXT_OPTIONS = json.load(f) # Data for a mad-lib style generation of text
+
+
+class MonsterBio(commands.Cog):
+ """A cog that generates a spooky monster biography."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ def generate_name(self, seeded_random: random.Random) -> str:
+ """Generates a name (for either monster species or monster name)."""
+ n_candidate_strings = seeded_random.randint(2, len(TEXT_OPTIONS["monster_type"]))
+ return "".join(seeded_random.choice(TEXT_OPTIONS["monster_type"][i]) for i in range(n_candidate_strings))
+
+ @commands.command(brief="Sends your monster bio!")
+ async def monsterbio(self, ctx: commands.Context) -> None:
+ """Sends a description of a monster."""
+ seeded_random = random.Random(ctx.message.author.id) # Seed a local Random instance rather than the system one
+
+ name = self.generate_name(seeded_random)
+ species = self.generate_name(seeded_random)
+ biography_text = seeded_random.choice(TEXT_OPTIONS["biography_text"])
+ words = {"monster_name": name, "monster_species": species}
+ for key, value in biography_text.items():
+ if key == "text":
+ continue
+
+ options = seeded_random.sample(TEXT_OPTIONS[key], value)
+ words[key] = ' '.join(options)
+
+ embed = discord.Embed(
+ title=f"{name}'s Biography",
+ color=seeded_random.choice([Colours.orange, Colours.purple]),
+ description=biography_text["text"].format_map(words),
+ )
+
+ await ctx.send(embed=embed)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Monster bio Cog load."""
+ bot.add_cog(MonsterBio(bot))
+ log.info("MonsterBio cog loaded.")
diff --git a/bot/seasons/halloween/timeleft.py b/bot/seasons/halloween/timeleft.py
index 77767baa..8cb3f4f6 100644
--- a/bot/seasons/halloween/timeleft.py
+++ b/bot/seasons/halloween/timeleft.py
@@ -25,7 +25,7 @@ class TimeLeft(commands.Cog):
year = now.year
if now.month > 10:
year += 1
- end = datetime(year, 10, 31, 11, 59, 59)
+ end = datetime(year, 11, 1, 11, 59, 59)
start = datetime(year, 10, 1)
return now, end, start
diff --git a/bot/seasons/season.py b/bot/seasons/season.py
index 3546fda6..e7b7a69c 100644
--- a/bot/seasons/season.py
+++ b/bot/seasons/season.py
@@ -79,6 +79,7 @@ class SeasonBase:
start_date: Optional[str] = None
end_date: Optional[str] = None
+ should_announce: bool = False
colour: Optional[int] = None
icon: Tuple[str, ...] = ("/logos/logo_full/logo_full.png",)
@@ -268,11 +269,11 @@ class SeasonBase:
"""
Announces a change in season in the announcement channel.
- It will skip the announcement if the current active season is the "evergreen" default season.
+ Auto-announcement is configured by the `should_announce` `SeasonBase` attribute
"""
- # Don't actually announce if reverting to normal season
- if self.name in ("evergreen", "wildcard", "halloween"):
- log.debug(f"Season Changed: {self.name}")
+ # Short circuit if the season had disabled automatic announcements
+ if not self.should_announce:
+ log.debug(f"Season changed without announcement: {self.name}")
return
guild = bot.get_guild(Client.guild)