aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Janine vN <[email protected]>2021-12-03 21:06:10 -0500
committerGravatar Janine vN <[email protected]>2021-12-03 21:51:56 -0500
commita28abf1b286d841b8f27aba5d31696a9f658f952 (patch)
treecddca97a2845221a880fcfbc5c36c91c7e3e98fa
parentMake aoc_name a keyword arguemnt to accept spaces (diff)
parentMerge pull request #963 from python-discord/aoc-lb-multiword (diff)
Merge branch 'main' into aoc-link
-rw-r--r--bot/constants.py10
-rw-r--r--bot/exts/core/internal_eval/_internal_eval.py6
-rw-r--r--bot/exts/events/advent_of_code/_cog.py28
-rw-r--r--bot/exts/fun/snakes/_utils.py25
-rw-r--r--bot/exts/fun/trivia_quiz.py4
-rw-r--r--bot/exts/holidays/hanukkah/hanukkah_embed.py84
-rw-r--r--bot/utils/pagination.py10
7 files changed, 76 insertions, 91 deletions
diff --git a/bot/constants.py b/bot/constants.py
index 33bc8b61..f4b1cab0 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -282,9 +282,9 @@ if Client.month_override is not None:
class Roles(NamedTuple):
- owner = 267627879762755584
- admin = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896))
- moderator = 267629731250176001
+ owners = 267627879762755584
+ admins = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896))
+ moderation_team = 267629731250176001
helpers = int(environ.get("ROLE_HELPERS", 267630620367257601))
core_developers = 587606783669829632
everyone = int(environ.get("BOT_GUILD", 267624335836053506))
@@ -334,8 +334,8 @@ class Reddit:
# Default role combinations
-MODERATION_ROLES = Roles.moderator, Roles.admin, Roles.owner
-STAFF_ROLES = Roles.helpers, Roles.moderator, Roles.admin, Roles.owner
+MODERATION_ROLES = {Roles.moderation_team, Roles.admins, Roles.owners}
+STAFF_ROLES = {Roles.helpers, Roles.moderation_team, Roles.admins, Roles.owners}
# Whitelisted channels
WHITELISTED_CHANNELS = (
diff --git a/bot/exts/core/internal_eval/_internal_eval.py b/bot/exts/core/internal_eval/_internal_eval.py
index 12a860fa..5b5461f0 100644
--- a/bot/exts/core/internal_eval/_internal_eval.py
+++ b/bot/exts/core/internal_eval/_internal_eval.py
@@ -147,14 +147,14 @@ class InternalEval(commands.Cog):
await self._send_output(ctx, eval_context.format_output())
@commands.group(name="internal", aliases=("int",))
- @with_role(Roles.admin)
+ @with_role(Roles.admins)
async def internal_group(self, ctx: commands.Context) -> None:
"""Internal commands. Top secret!"""
if not ctx.invoked_subcommand:
await invoke_help_command(ctx)
@internal_group.command(name="eval", aliases=("e",))
- @with_role(Roles.admin)
+ @with_role(Roles.admins)
async def eval(self, ctx: commands.Context, *, code: str) -> None:
"""Run eval in a REPL-like format."""
if match := list(FORMATTED_CODE_REGEX.finditer(code)):
@@ -173,7 +173,7 @@ class InternalEval(commands.Cog):
await self._eval(ctx, code)
@internal_group.command(name="reset", aliases=("clear", "exit", "r", "c"))
- @with_role(Roles.admin)
+ @with_role(Roles.admins)
async def reset(self, ctx: commands.Context) -> None:
"""Reset the context and locals of the eval session."""
self.locals = {}
diff --git a/bot/exts/events/advent_of_code/_cog.py b/bot/exts/events/advent_of_code/_cog.py
index 55fd0ac6..16176c69 100644
--- a/bot/exts/events/advent_of_code/_cog.py
+++ b/bot/exts/events/advent_of_code/_cog.py
@@ -59,6 +59,7 @@ class AdventOfCode(commands.Cog):
if not ctx.invoked_subcommand:
await invoke_help_command(ctx)
+ @commands.guild_only()
@adventofcode_group.command(
name="subscribe",
aliases=("sub", "notifications", "notify", "notifs"),
@@ -88,6 +89,7 @@ class AdventOfCode(commands.Cog):
)
@in_month(Month.DECEMBER)
+ @commands.guild_only()
@adventofcode_group.command(name="unsubscribe", aliases=("unsub",), brief="Notifications for new days")
@whitelist_override(channels=AOC_WHITELIST)
async def aoc_unsubscribe(self, ctx: commands.Context) -> None:
@@ -131,6 +133,7 @@ class AdventOfCode(commands.Cog):
"""Respond with an explanation of all things Advent of Code."""
await ctx.send(embed=self.cached_about_aoc)
+ @commands.guild_only()
@adventofcode_group.command(name="join", aliases=("j",), brief="Learn how to join the leaderboard (via DM)")
@whitelist_override(channels=AOC_WHITELIST)
async def join_leaderboard(self, ctx: commands.Context) -> None:
@@ -294,20 +297,23 @@ class AdventOfCode(commands.Cog):
brief="Get a snapshot of the PyDis private AoC leaderboard",
)
@whitelist_override(channels=AOC_WHITELIST_RESTRICTED)
- async def aoc_leaderboard(
- self,
- ctx: commands.Context,
- self_placement_name: Optional[str] = None,
- ) -> None:
+ async def aoc_leaderboard(self, ctx: commands.Context, *, aoc_name: Optional[str] = None) -> None:
"""
Get the current top scorers of the Python Discord Leaderboard.
- Additionally you can specify a `self_placement_name`
- that will append the specified profile's personal stats to the top of the leaderboard
+ Additionally you can specify an `aoc_name` that will append the
+ specified profile's personal stats to the top of the leaderboard
"""
+ # Strip quotes from the AoC username if needed (e.g. "My Name" -> My Name)
+ # This is to keep compatibility with those already used to wrapping the AoC name in quotes
+ # Note: only strips one layer of quotes to allow names with quotes at the start and end
+ # e.g. ""My Name"" -> "My Name"
+ if aoc_name and aoc_name.startswith('"') and aoc_name.endswith('"'):
+ aoc_name = aoc_name[1:-1]
+
async with ctx.typing():
try:
- leaderboard = await _helpers.fetch_leaderboard(self_placement_name=self_placement_name)
+ leaderboard = await _helpers.fetch_leaderboard(self_placement_name=aoc_name)
except _helpers.FetchingLeaderboardFailedError:
await ctx.send(":x: Unable to fetch leaderboard!")
return
@@ -315,10 +321,10 @@ class AdventOfCode(commands.Cog):
number_of_participants = leaderboard["number_of_participants"]
top_count = min(AocConfig.leaderboard_displayed_members, number_of_participants)
- self_placement_header = "(and your personal stats compared to the top 10)" if self_placement_name else ""
+ self_placement_header = "(and your personal stats compared to the top 10)" if aoc_name else ""
header = f"Here's our current top {top_count}{self_placement_header}! {Emojis.christmas_tree * 3}"
table = "```\n" \
- f"{leaderboard['placement_leaderboard'] if self_placement_name else leaderboard['top_leaderboard']}" \
+ f"{leaderboard['placement_leaderboard'] if aoc_name else leaderboard['top_leaderboard']}" \
"\n```"
info_embed = _helpers.get_summary_embed(leaderboard)
@@ -372,7 +378,7 @@ class AdventOfCode(commands.Cog):
info_embed = _helpers.get_summary_embed(leaderboard)
await ctx.send(f"```\n{table}\n```", embed=info_embed)
- @with_role(Roles.admin)
+ @with_role(Roles.admins)
@adventofcode_group.command(
name="refresh",
aliases=("fetch",),
diff --git a/bot/exts/fun/snakes/_utils.py b/bot/exts/fun/snakes/_utils.py
index de51339d..182fa9d9 100644
--- a/bot/exts/fun/snakes/_utils.py
+++ b/bot/exts/fun/snakes/_utils.py
@@ -6,13 +6,14 @@ import math
import random
from itertools import product
from pathlib import Path
+from typing import Union
from PIL import Image
from PIL.ImageDraw import ImageDraw
-from discord import File, Member, Reaction
+from discord import File, Member, Reaction, User
from discord.ext.commands import Cog, Context
-from bot.constants import Roles
+from bot.constants import MODERATION_ROLES
SNAKE_RESOURCES = Path("bot/resources/fun/snakes").absolute()
@@ -395,7 +396,7 @@ class SnakeAndLaddersGame:
Listen for reactions until players have joined, and the game has been started.
"""
- def startup_event_check(reaction_: Reaction, user_: Member) -> bool:
+ def startup_event_check(reaction_: Reaction, user_: Union[User, Member]) -> bool:
"""Make sure that this reaction is what we want to operate on."""
return (
all((
@@ -460,7 +461,7 @@ class SnakeAndLaddersGame:
await self.cancel_game()
return # We're done, no reactions for the last 5 minutes
- async def _add_player(self, user: Member) -> None:
+ async def _add_player(self, user: Union[User, Member]) -> None:
"""Add player to game."""
self.players.append(user)
self.player_tiles[user.id] = 1
@@ -469,7 +470,7 @@ class SnakeAndLaddersGame:
im = Image.open(io.BytesIO(avatar_bytes)).resize((BOARD_PLAYER_SIZE, BOARD_PLAYER_SIZE))
self.avatar_images[user.id] = im
- async def player_join(self, user: Member) -> None:
+ async def player_join(self, user: Union[User, Member]) -> None:
"""
Handle players joining the game.
@@ -495,7 +496,7 @@ class SnakeAndLaddersGame:
delete_after=10
)
- async def player_leave(self, user: Member) -> bool:
+ async def player_leave(self, user: Union[User, Member]) -> bool:
"""
Handle players leaving the game.
@@ -530,7 +531,7 @@ class SnakeAndLaddersGame:
await self.channel.send("**Snakes and Ladders**: Game has been canceled.")
self._destruct()
- async def start_game(self, user: Member) -> None:
+ async def start_game(self, user: Union[User, Member]) -> None:
"""
Allow the game author to begin the game.
@@ -551,7 +552,7 @@ class SnakeAndLaddersGame:
async def start_round(self) -> None:
"""Begin the round."""
- def game_event_check(reaction_: Reaction, user_: Member) -> bool:
+ def game_event_check(reaction_: Reaction, user_: Union[User, Member]) -> bool:
"""Make sure that this reaction is what we want to operate on."""
return (
all((
@@ -644,7 +645,7 @@ class SnakeAndLaddersGame:
if not is_surrendered:
await self._complete_round()
- async def player_roll(self, user: Member) -> None:
+ async def player_roll(self, user: Union[User, Member]) -> None:
"""Handle the player's roll."""
if user.id not in self.player_tiles:
await self.channel.send(user.mention + " You are not in the match.", delete_after=10)
@@ -691,7 +692,7 @@ class SnakeAndLaddersGame:
await self.channel.send("**Snakes and Ladders**: " + winner.mention + " has won the game! :tada:")
self._destruct()
- def _check_winner(self) -> Member:
+ def _check_winner(self) -> Union[User, Member]:
"""Return a winning member if we're in the post-round state and there's a winner."""
if self.state != "post_round":
return None
@@ -716,6 +717,6 @@ class SnakeAndLaddersGame:
return x_level, y_level
@staticmethod
- def _is_moderator(user: Member) -> bool:
+ def _is_moderator(user: Union[User, Member]) -> bool:
"""Return True if the user is a Moderator."""
- return any(Roles.moderator == role.id for role in user.roles)
+ return any(role.id in MODERATION_ROLES for role in getattr(user, 'roles', []))
diff --git a/bot/exts/fun/trivia_quiz.py b/bot/exts/fun/trivia_quiz.py
index 712c8a12..4a1cec5b 100644
--- a/bot/exts/fun/trivia_quiz.py
+++ b/bot/exts/fun/trivia_quiz.py
@@ -16,7 +16,7 @@ from discord.ext import commands, tasks
from rapidfuzz import fuzz
from bot.bot import Bot
-from bot.constants import Client, Colours, NEGATIVE_REPLIES, Roles
+from bot.constants import Client, Colours, MODERATION_ROLES, NEGATIVE_REPLIES
logger = logging.getLogger(__name__)
@@ -550,7 +550,7 @@ class TriviaQuiz(commands.Cog):
if self.game_status[ctx.channel.id]:
# Check if the author is the game starter or a moderator.
if ctx.author == self.game_owners[ctx.channel.id] or any(
- Roles.moderator == role.id for role in ctx.author.roles
+ role.id in MODERATION_ROLES for role in getattr(ctx.author, 'roles', [])
):
self.game_status[ctx.channel.id] = False
del self.game_owners[ctx.channel.id]
diff --git a/bot/exts/holidays/hanukkah/hanukkah_embed.py b/bot/exts/holidays/hanukkah/hanukkah_embed.py
index ac3eab7b..5767f91e 100644
--- a/bot/exts/holidays/hanukkah/hanukkah_embed.py
+++ b/bot/exts/holidays/hanukkah/hanukkah_embed.py
@@ -21,45 +21,41 @@ class HanukkahEmbed(commands.Cog):
def __init__(self, bot: Bot):
self.bot = bot
- self.hanukkah_days = []
- self.hanukkah_months = []
- self.hanukkah_years = []
+ self.hanukkah_dates: list[datetime.date] = []
- async def get_hanukkah_dates(self) -> list[str]:
+ def _parse_time_to_datetime(self, date: list[str]) -> datetime.datetime:
+ """Format the times provided by the api to datetime forms."""
+ try:
+ return datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%S%z")
+ except ValueError:
+ # there is a possibility of an event not having a time, just a day
+ # to catch this, we try again without time information
+ return datetime.datetime.strptime(date, "%Y-%m-%d")
+
+ async def fetch_hanukkah_dates(self) -> list[datetime.date]:
"""Gets the dates for hanukkah festival."""
- hanukkah_dates = []
+ # clear the datetime objects to prevent a memory link
+ self.hanukkah_dates = []
async with self.bot.http_session.get(HEBCAL_URL) as response:
json_data = await response.json()
festivals = json_data["items"]
for festival in festivals:
if festival["title"].startswith("Chanukah"):
date = festival["date"]
- hanukkah_dates.append(date)
- return hanukkah_dates
+ self.hanukkah_dates.append(self._parse_time_to_datetime(date).date())
+ return self.hanukkah_dates
@in_month(Month.NOVEMBER, Month.DECEMBER)
@commands.command(name="hanukkah", aliases=("chanukah",))
async def hanukkah_festival(self, ctx: commands.Context) -> None:
"""Tells you about the Hanukkah Festivaltime of festival, festival day, etc)."""
- hanukkah_dates = await self.get_hanukkah_dates()
- self.hanukkah_dates_split(hanukkah_dates)
- hanukkah_start_day = int(self.hanukkah_days[0])
- hanukkah_start_month = int(self.hanukkah_months[0])
- hanukkah_start_year = int(self.hanukkah_years[0])
- hanukkah_end_day = int(self.hanukkah_days[8])
- hanukkah_end_month = int(self.hanukkah_months[8])
- hanukkah_end_year = int(self.hanukkah_years[8])
-
- hanukkah_start = datetime.date(hanukkah_start_year, hanukkah_start_month, hanukkah_start_day)
- hanukkah_end = datetime.date(hanukkah_end_year, hanukkah_end_month, hanukkah_end_day)
+ hanukkah_dates = await self.fetch_hanukkah_dates()
+ start_day = hanukkah_dates[0]
+ end_day = hanukkah_dates[-1]
today = datetime.date.today()
- # today = datetime.date(2019, 12, 24) (for testing)
- day = str(today.day)
- month = str(today.month)
- year = str(today.year)
embed = Embed(title="Hanukkah", colour=Colours.blue)
- if day in self.hanukkah_days and month in self.hanukkah_months and year in self.hanukkah_years:
- if int(day) == hanukkah_start_day:
+ if start_day <= today <= end_day:
+ if start_day == today:
now = datetime.datetime.utcnow()
hours = now.hour + 4 # using only hours
hanukkah_start_hour = 18
@@ -77,35 +73,27 @@ class HanukkahEmbed(commands.Cog):
)
await ctx.send(embed=embed)
return
- festival_day = self.hanukkah_days.index(day)
+ festival_day = hanukkah_dates.index(today)
number_suffixes = ["st", "nd", "rd", "th"]
suffix = number_suffixes[festival_day - 1 if festival_day <= 3 else 3]
message = ":menorah:" * festival_day
- embed.description = f"It is the {festival_day}{suffix} day of Hanukkah!\n{message}"
- await ctx.send(embed=embed)
+ embed.description = (
+ f"It is the {festival_day}{suffix} day of Hanukkah!\n{message}"
+ )
+ elif today < start_day:
+ format_start = start_day.strftime("%d of %B")
+ embed.description = (
+ "Hanukkah has not started yet. "
+ f"Hanukkah will start at sundown on {format_start}."
+ )
else:
- if today < hanukkah_start:
- festival_starting_month = hanukkah_start.strftime("%B")
- embed.description = (
- f"Hanukkah has not started yet. "
- f"Hanukkah will start at sundown on {hanukkah_start_day}th "
- f"of {festival_starting_month}."
- )
- else:
- festival_end_month = hanukkah_end.strftime("%B")
- embed.description = (
- f"Looks like you missed Hanukkah!"
- f"Hanukkah ended on {hanukkah_end_day}th of {festival_end_month}."
- )
-
- await ctx.send(embed=embed)
+ format_end = end_day.strftime("%d of %B")
+ embed.description = (
+ "Looks like you missed Hanukkah! "
+ f"Hanukkah ended on {format_end}."
+ )
- def hanukkah_dates_split(self, hanukkah_dates: list[str]) -> None:
- """We are splitting the dates for hanukkah into days, months and years."""
- for date in hanukkah_dates:
- self.hanukkah_days.append(date[8:10])
- self.hanukkah_months.append(date[5:7])
- self.hanukkah_years.append(date[0:4])
+ await ctx.send(embed=embed)
def setup(bot: Bot) -> None:
diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py
index 013ef9e7..188b279f 100644
--- a/bot/utils/pagination.py
+++ b/bot/utils/pagination.py
@@ -211,8 +211,6 @@ class LinePaginator(Paginator):
log.debug(f"Got first page reaction - changing to page 1/{len(paginator.pages)}")
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
if footer_text:
embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})")
@@ -226,8 +224,6 @@ class LinePaginator(Paginator):
log.debug(f"Got last page reaction - changing to page {current_page + 1}/{len(paginator.pages)}")
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
if footer_text:
embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})")
@@ -245,8 +241,6 @@ class LinePaginator(Paginator):
current_page -= 1
log.debug(f"Got previous page reaction - changing to page {current_page + 1}/{len(paginator.pages)}")
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
if footer_text:
@@ -266,8 +260,6 @@ class LinePaginator(Paginator):
current_page += 1
log.debug(f"Got next page reaction - changing to page {current_page + 1}/{len(paginator.pages)}")
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
if footer_text:
@@ -428,8 +420,6 @@ class ImagePaginator(Paginator):
reaction_type = "next"
# Magic happens here, after page and reaction_type is set
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
image = paginator.images[current_page] or EmptyEmbed