Skip to content

Commit e79f86d

Browse files
committed
Allow certain roles to run AoC commands in November
1 parent 1a67d49 commit e79f86d

3 files changed

Lines changed: 59 additions & 15 deletions

File tree

bot/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ class _Roles(EnvConfig, env_prefix="ROLE_"):
178178
admins: int = 267628507062992896
179179
advent_of_code: int = 518565788744024082
180180
code_jam_event_team: int = 787816728474288181
181+
core_devs: int = 587606783669829632
181182
events_lead: int = 778361735739998228
182183
event_runner: int = 940911658799333408
183184
summer_aoc: int = 988801794668908655

bot/exts/advent_of_code/_cog.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636

3737
AOC_REDIRECT = (Channels.advent_of_code_commands, Channels.sir_lancebot_playground, Channels.bot_commands)
3838

39+
EARLY_ACCESS_ROLES = (Roles.admins, Roles.events_lead, Roles.core_devs)
40+
3941

4042
class AdventOfCode(commands.Cog):
4143
"""Advent of Code festivities! Ho Ho Ho!"""
@@ -326,6 +328,7 @@ async def aoc_unlink_account(self, ctx: commands.Context) -> None:
326328
await ctx.reply("You don't have an Advent of Code account linked.")
327329

328330
@in_month(Month.DECEMBER, Month.JANUARY, Month.FEBRUARY)
331+
@in_month(Month.NOVEMBER, roles=EARLY_ACCESS_ROLES)
329332
@adventofcode_group.command(
330333
name="dayandstar",
331334
aliases=("daynstar", "daystar"),
@@ -364,6 +367,7 @@ async def aoc_day_and_star_leaderboard(
364367
await message.edit(view=None)
365368

366369
@in_month(Month.DECEMBER, Month.JANUARY, Month.FEBRUARY)
370+
@in_month(Month.NOVEMBER, roles=EARLY_ACCESS_ROLES)
367371
@adventofcode_group.command(
368372
name="leaderboard",
369373
aliases=("board", "lb"),
@@ -445,6 +449,7 @@ async def aoc_global_leaderboard(self, ctx: commands.Context) -> None:
445449
await ctx.send(embed=self.cached_no_global)
446450

447451
@in_month(Month.DECEMBER, Month.JANUARY, Month.FEBRUARY)
452+
@in_month(Month.NOVEMBER, roles=EARLY_ACCESS_ROLES)
448453
@adventofcode_group.command(
449454
name="stats",
450455
aliases=("dailystats", "ds"),

bot/utils/decorators.py

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections.abc import Callable, Container
44

55
from discord.ext import commands
6-
from discord.ext.commands import Command, Context
6+
from discord.ext.commands import CheckFailure, Command, Context
77
from pydis_core.utils import logging
88

99
from bot.constants import Channels, Month
@@ -69,28 +69,64 @@ async def guarded_listener(*args, **kwargs) -> None:
6969
return decorator
7070

7171

72-
def in_month_command(*allowed_months: Month) -> Callable:
72+
def in_month_command(*allowed_months: Month, roles: tuple[int, ...] = ()) -> Callable:
7373
"""
74-
Check whether the command was invoked in one of `enabled_months`.
74+
Check whether the command was invoked in one of `allowed_months`.
75+
76+
The check can be limited to certain roles.
77+
To enable the supplied months for everyone, don't set a value for `roles`.
78+
79+
If a command is decorated several times with this, it only needs to pass one of the checks.
7580
7681
Uses the current UTC month at the time of running the predicate.
7782
"""
7883
async def predicate(ctx: Context) -> bool:
79-
current_month = resolve_current_month()
80-
can_run = current_month in allowed_months
81-
82-
log.debug(
83-
f"Command '{ctx.command}' is locked to months {human_months(allowed_months)}. "
84-
f"Invoking it in month {current_month!s} is {'allowed' if can_run else 'disallowed'}."
85-
)
86-
if can_run:
84+
command = ctx.command
85+
if "month_checks" not in command.extras:
86+
log.debug(f"No month checks found for command {command}.")
8787
return True
88-
raise InMonthCheckFailure(f"Command can only be used in {human_months(allowed_months)}")
8988

90-
return commands.check(predicate)
89+
everyone_error = None
90+
privileged_user = False
91+
allowed_months_for_user = set()
92+
current_month = resolve_current_month()
93+
for checked_roles, checked_months in command.extras["month_checks"].items():
94+
if checked_roles:
95+
uroles = ctx.author.roles
96+
if not uroles or not (set(checked_roles) & set(r.id for r in uroles)):
97+
log.debug(f"Month check for roles {checked_roles} doesn't apply to {ctx.author}.")
98+
continue
99+
100+
if current_month in checked_months:
101+
log.debug(f"Month check for roles {checked_roles} passed for {ctx.author}.")
102+
return True
103+
104+
log.debug(f"Month check for roles {checked_roles} didn't pass for {ctx.author}.")
105+
if not checked_roles:
106+
everyone_error = InMonthCheckFailure(f"Command can only be used in {human_months(checked_months)}")
107+
else:
108+
privileged_user = True
109+
allowed_months_for_user |= set(checked_months)
110+
111+
if privileged_user:
112+
allowed_months_for_user = sorted(allowed_months_for_user)
113+
raise InMonthCheckFailure(f"You can run this command only in {human_months(allowed_months_for_user)}")
114+
if everyone_error:
115+
raise everyone_error
116+
raise CheckFailure("You cannot run this command.")
117+
118+
def decorator(func: Command) -> Command:
119+
if "month_checks" in func.extras:
120+
func.extras["month_checks"][roles] = allowed_months
121+
return func
122+
123+
func.extras["month_checks"] = {roles: allowed_months}
124+
return commands.check(predicate)(func)
125+
126+
return decorator
91127

92128

93-
def in_month(*allowed_months: Month) -> Callable:
129+
def in_month(*allowed_months: Month, roles: tuple[int, ...] = ()) -> Callable:
94130
"""
95131
Universal decorator for season-locking commands and listeners alike.
96132
@@ -112,10 +148,12 @@ def decorator(callable_: Callable) -> Callable:
112148
# Functions decorated as commands are turned into instances of `Command`
113149
if isinstance(callable_, Command):
114150
log.debug(f"Command {callable_.qualified_name} will be locked to {human_months(allowed_months)}")
115-
actual_deco = in_month_command(*allowed_months)
151+
actual_deco = in_month_command(*allowed_months, roles=roles)
116152

117153
# D.py will assign this attribute when `callable_` is registered as a listener
118154
elif hasattr(callable_, "__cog_listener__"):
155+
if roles is not None:
156+
raise ValueError("Role restrictions are not available for listeners.")
119157
log.debug(f"Listener {callable_.__qualname__} will be locked to {human_months(allowed_months)}")
120158
actual_deco = in_month_listener(*allowed_months)
121159

0 commit comments

Comments
 (0)