aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Chris Lovering <[email protected]>2021-11-30 11:31:30 +0000
committerGravatar Chris Lovering <[email protected]>2021-11-30 11:51:21 +0000
commit7f22abfd3ec443cf0925f2c6e609be681c723799 (patch)
treedf540a0fcc5cdd44db49c9a387b37d991ab493e1
parentDelete the subscribe message after 5 minutes (diff)
Allow roles to be assignable over multiple months
This includes a refactor to use a dataclass for clearer implementation. Along with that, this changes the roles so that they're always available, but un-assignable roles are in red and give a different error.
-rw-r--r--bot/exts/info/subscribe.py95
1 files changed, 72 insertions, 23 deletions
diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py
index 17bb24dca..9b96e7ab2 100644
--- a/bot/exts/info/subscribe.py
+++ b/bot/exts/info/subscribe.py
@@ -1,3 +1,7 @@
+import calendar
+import typing as t
+from dataclasses import dataclass
+
import arrow
import discord
from discord.ext import commands
@@ -9,15 +13,44 @@ from bot.decorators import in_whitelist
from bot.log import get_logger
from bot.utils import checks, members, scheduling
-# Tuple of tuples, where each inner tuple is a role id and a month number.
-# The month number signifies what month the role should be assignable,
-# use None for the month number if it should always be active.
+
+@dataclass(frozen=True)
+class AssignableRole:
+ """
+ A role that can be assigned to a user.
+
+ months_available is a tuple that signifies what months the role should be
+ self-assignable, using None for when it should always be available.
+ """
+
+ role_id: int
+ months_available: t.Optional[tuple[int]]
+ name: t.Optional[str] = None # This gets populated within Subscribe.init_cog()
+
+ def is_currently_available(self) -> bool:
+ """Check if the role is available for the current month."""
+ if self.months_available is None:
+ return True
+ return arrow.utcnow().month in self.months_available
+
+ def get_readable_available_months(self) -> str:
+ """Get a readable string of the months the role is available."""
+ if self.months_available is None:
+ return f"{self.name} is always available."
+
+ # Join the months together with comma separators, but use "and" for the final seperator.
+ month_names = [calendar.month_name[month] for month in self.months_available]
+ available_months_str = ", ".join(month_names[:-1]) + f" and {month_names[-1]}"
+ return f"{self.name} can only be assigned during {available_months_str}."
+
+
ASSIGNABLE_ROLES = (
- (constants.Roles.announcements, None),
- (constants.Roles.pyweek_announcements, None),
- (constants.Roles.lovefest, 2),
- (constants.Roles.advent_of_code, 12),
+ AssignableRole(constants.Roles.announcements, None),
+ AssignableRole(constants.Roles.pyweek_announcements, None),
+ AssignableRole(constants.Roles.lovefest, (1, 2)),
+ AssignableRole(constants.Roles.advent_of_code, (11, 12)),
)
+
ITEMS_PER_ROW = 3
DELETE_MESSAGE_AFTER = 300 # Seconds
@@ -47,14 +80,22 @@ class SingleRoleButton(discord.ui.Button):
ADD_STYLE = discord.ButtonStyle.success
REMOVE_STYLE = discord.ButtonStyle.secondary
- LABEL_FORMAT = "{action} role {role_name}"
+ UNAVAILABLE_STYLE = discord.ButtonStyle.red
+ LABEL_FORMAT = "{action} role {role_name}."
CUSTOM_ID_FORMAT = "subscribe-{role_id}"
- def __init__(self, role: discord.Role, assigned: bool, row: int):
+ def __init__(self, role: AssignableRole, assigned: bool, row: int):
+ if role.is_currently_available():
+ style = self.REMOVE_STYLE if assigned else self.ADD_STYLE
+ label = self.LABEL_FORMAT.format(action="Remove" if assigned else "Add", role_name=role.name)
+ else:
+ style = self.UNAVAILABLE_STYLE
+ label = role.name
+
super().__init__(
- style=self.REMOVE_STYLE if assigned else self.ADD_STYLE,
- label=self.LABEL_FORMAT.format(action="Remove" if assigned else "Add", role_name=role.name),
- custom_id=self.CUSTOM_ID_FORMAT.format(role_id=role.id),
+ style=style,
+ label=label,
+ custom_id=self.CUSTOM_ID_FORMAT.format(role_id=role.role_id),
row=row,
)
self.role = role
@@ -68,10 +109,14 @@ class SingleRoleButton(discord.ui.Button):
self.view.stop()
return
+ if not self.role.is_currently_available():
+ await interaction.response.send_message(self.role.get_readable_available_months(), ephemeral=True)
+ return
+
await members.handle_role_change(
interaction.user,
interaction.user.remove_roles if self.assigned else interaction.user.add_roles,
- self.role,
+ discord.Object(self.role.role_id),
)
self.assigned = not self.assigned
@@ -98,24 +143,27 @@ class Subscribe(commands.Cog):
def __init__(self, bot: Bot):
self.bot = bot
self.init_task = scheduling.create_task(self.init_cog(), event_loop=self.bot.loop)
- self.assignable_roles: list[discord.Role] = []
+ self.assignable_roles: list[AssignableRole] = []
self.guild: discord.Guild = None
async def init_cog(self) -> None:
"""Initialise the cog by resolving the role IDs in ASSIGNABLE_ROLES to role names."""
await self.bot.wait_until_guild_available()
- current_month = arrow.utcnow().month
self.guild = self.bot.get_guild(constants.Guild.id)
- for role_id, month_available in ASSIGNABLE_ROLES:
- if month_available is not None and month_available != current_month:
- continue
- role = self.guild.get_role(role_id)
- if role is None:
- log.warning("Could not resolve %d to a role in the guild, skipping.", role_id)
+ for role in ASSIGNABLE_ROLES:
+ discord_role = self.guild.get_role(role.role_id)
+ if discord_role is None:
+ log.warning("Could not resolve %d to a role in the guild, skipping.", role.role_id)
continue
- self.assignable_roles.append(role)
+ self.assignable_roles.append(
+ AssignableRole(
+ role_id=role.role_id,
+ months_available=role.months_available,
+ name=discord_role.name,
+ )
+ )
@commands.cooldown(1, 10, commands.BucketType.member)
@commands.command(name="subscribe")
@@ -125,9 +173,10 @@ class Subscribe(commands.Cog):
await self.init_task
button_view = RoleButtonView(ctx.author)
+ author_roles = [role.id for role in ctx.author.roles]
for index, role in enumerate(self.assignable_roles):
row = index // ITEMS_PER_ROW
- button_view.add_item(SingleRoleButton(role, role in ctx.author.roles, row))
+ button_view.add_item(SingleRoleButton(role, role.role_id in author_roles, row))
await ctx.send(
"Click the buttons below to add or remove your roles!",