Skip to content
12 changes: 12 additions & 0 deletions bot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,10 +683,22 @@ class VideoPermission(metaclass=YAMLGetter):
default_permission_duration: int


class ThreadArchiveTimes(Enum):
HOUR = 60
DAY = 1440
THREE_DAY = 4230
WEEK = 10080


# Debug mode
DEBUG_MODE: bool = _CONFIG_YAML["debug"] == "true"
FILE_LOGS: bool = _CONFIG_YAML["file_logs"].lower() == "true"

if DEBUG_MODE:
DEFAULT_THREAD_ARCHIVE_TIME = ThreadArchiveTimes.HOUR.value
else:
DEFAULT_THREAD_ARCHIVE_TIME = ThreadArchiveTimes.WEEK.value

# Paths
BOT_DIR = os.path.dirname(__file__)
PROJECT_ROOT = os.path.abspath(os.path.join(BOT_DIR, os.pardir))
Expand Down
14 changes: 7 additions & 7 deletions bot/exts/recruitment/talentpool/_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,12 +483,9 @@ async def mark_reviewed(self, ctx: Context, user_id: int) -> None:
@has_any_role(*MODERATION_ROLES)
async def get_review(self, ctx: Context, user_id: int) -> None:
"""Get the user's review as a markdown file."""
review = (await self.reviewer.make_review(user_id))[0]
if review:
file = discord.File(StringIO(review), f"{user_id}_review.md")
await ctx.send(file=file)
else:
await ctx.send(f"There doesn't appear to be an active nomination for {user_id}")
review, _, _ = await self.reviewer.make_review(user_id)
file = discord.File(StringIO(review), f"{user_id}_review.md")
await ctx.send(file=file)

@nomination_group.command(aliases=('review',))
@has_any_role(*MODERATION_ROLES)
Expand All @@ -501,7 +498,7 @@ async def post_review(self, ctx: Context, user_id: int) -> None:
await ctx.message.add_reaction(Emojis.check_mark)

@Cog.listener()
async def on_member_ban(self, guild: Guild, user: Union[MemberOrUser]) -> None:
async def on_member_ban(self, guild: Guild, user: MemberOrUser) -> None:
"""Remove `user` from the talent pool after they are banned."""
await self.end_nomination(user.id, "User was banned.")

Expand All @@ -516,6 +513,9 @@ async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None:
if payload.channel_id != Channels.nomination_voting:
return

if payload.user_id == self.bot.user.id:
return

message: PartialMessage = self.bot.get_channel(payload.channel_id).get_partial_message(payload.message_id)
emoji = str(payload.emoji)

Expand Down
53 changes: 34 additions & 19 deletions bot/exts/recruitment/talentpool/_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

import arrow
from dateutil.parser import isoparse
from discord import Embed, Emoji, Member, Message, NoMoreItems, PartialMessage, TextChannel
from discord import Embed, Emoji, Member, Message, NoMoreItems, NotFound, PartialMessage, TextChannel
from discord.ext.commands import Context

from bot.api import ResponseCodeError
from bot.bot import Bot
from bot.constants import Channels, Colours, Emojis, Guild
from bot.constants import Channels, Colours, DEFAULT_THREAD_ARCHIVE_TIME, Emojis, Guild, Roles
from bot.log import get_logger
from bot.utils.members import get_or_fetch_member
from bot.utils.messages import count_unique_users_reaction, pin_no_system_message
Expand All @@ -36,9 +36,8 @@
MAX_EMBED_SIZE = 4000

# Regex for finding the first message of a nomination, and extracting the nominee.
# Historic nominations will have 2 role mentions at the start, new ones won't, optionally match for this.
NOMINATION_MESSAGE_REGEX = re.compile(
r"(?:<@&\d+> <@&\d+>\n)*?<@!?(\d+?)> \(.+#\d{4}\) for Helper!\n\n\*\*Nominated by:\*\*",
r"<@!?(\d+)> \(.+#\d{4}\) for Helper!\n\n",
re.MULTILINE
)

Expand Down Expand Up @@ -78,14 +77,14 @@ def schedule_review(self, user_id: int) -> None:

async def post_review(self, user_id: int, update_database: bool) -> None:
Comment thread
ChrisLovering marked this conversation as resolved.
"""Format the review of a user and post it to the nomination voting channel."""
review, reviewed_emoji = await self.make_review(user_id)
if not review:
review, reviewed_emoji, nominee = await self.make_review(user_id)
if not nominee:
return

guild = self.bot.get_guild(Guild.id)
channel = guild.get_channel(Channels.nomination_voting)

log.trace(f"Posting the review of {user_id}")
log.trace(f"Posting the review of {nominee} ({nominee.id})")
messages = await self._bulk_send(channel, review)

await pin_no_system_message(messages[0])
Expand All @@ -95,12 +94,18 @@ async def post_review(self, user_id: int, update_database: bool) -> None:
for reaction in (reviewed_emoji, "\N{THUMBS UP SIGN}", "\N{THUMBS DOWN SIGN}"):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a general comment, but the reviewed_emoji will almost always return a valid ducky. But if it doesn't find a ducky for whatever reason, it will return ":eyes:" and that will fail when it tries to add it as a reaction.

Doesn't need to be fixed in this PR since it's out of scope, but that was Not Fun debugging.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been fixed in 4bde791

await last_message.add_reaction(reaction)

thread = await last_message.create_thread(
name=f"Nomination - {nominee}",
auto_archive_duration=DEFAULT_THREAD_ARCHIVE_TIME
)
await thread.send(fr"<@&{Roles.mod_team}> <@&{Roles.admins}>")

if update_database:
nomination = self._pool.cache.get(user_id)
await self.bot.api_client.patch(f"bot/nominations/{nomination['id']}", json={"reviewed": True})

async def make_review(self, user_id: int) -> typing.Tuple[str, Optional[Emoji]]:
"""Format a generic review of a user and return it with the reviewed emoji."""
async def make_review(self, user_id: int) -> typing.Tuple[str, Optional[Emoji], Optional[Member]]:
"""Format a generic review of a user and return it with the reviewed emoji and the user themselves."""
log.trace(f"Formatting the review of {user_id}")

# Since `cache` is a defaultdict, we should take care
Expand All @@ -110,25 +115,25 @@ async def make_review(self, user_id: int) -> typing.Tuple[str, Optional[Emoji]]:
nomination = self._pool.cache.get(user_id)
if not nomination:
log.trace(f"There doesn't appear to be an active nomination for {user_id}")
return "", None
return f"There doesn't appear to be an active nomination for {user_id}", None, None

guild = self.bot.get_guild(Guild.id)
member = await get_or_fetch_member(guild, user_id)
nominee = await get_or_fetch_member(guild, user_id)

if not member:
if not nominee:
return (
f"I tried to review the user with ID `{user_id}`, but they don't appear to be on the server :pensive:"
), None
), None, None

opening = f"{member.mention} ({member}) for Helper!"
opening = f"{nominee.mention} ({nominee}) for Helper!"

current_nominations = "\n\n".join(
f"**<@{entry['actor']}>:** {entry['reason'] or '*no reason given*'}"
for entry in nomination['entries'][::-1]
)
current_nominations = f"**Nominated by:**\n{current_nominations}"

review_body = await self._construct_review_body(member)
review_body = await self._construct_review_body(nominee)

reviewed_emoji = self._random_ducky(guild)
vote_request = (
Expand All @@ -138,7 +143,7 @@ async def make_review(self, user_id: int) -> typing.Tuple[str, Optional[Emoji]]:
)

review = "\n\n".join((opening, current_nominations, review_body, vote_request))
return review, reviewed_emoji
return review, reviewed_emoji, nominee

async def archive_vote(self, message: PartialMessage, passed: bool) -> None:
"""Archive this vote to #nomination-archive."""
Expand Down Expand Up @@ -210,8 +215,18 @@ async def archive_vote(self, message: PartialMessage, passed: bool) -> None:
colour=colour
))

# Thread channel IDs are the same as the message ID of the parent message.
nomination_thread = message.guild.get_thread(message.id)
if not nomination_thread:
log.warning(f"Could not find a thread linked to {message.channel.id}-{message.id}")
return

for message_ in messages:
await message_.delete()
with contextlib.suppress(NotFound):
await message_.delete()

with contextlib.suppress(NotFound):
await nomination_thread.edit(archived=True)

async def _construct_review_body(self, member: Member) -> str:
"""Formats the body of the nomination, with details of activity, infractions, and previous nominations."""
Expand Down Expand Up @@ -360,10 +375,10 @@ async def _previous_nominations_review(self, member: Member) -> Optional[str]:

@staticmethod
def _random_ducky(guild: Guild) -> Union[Emoji, str]:
"""Picks a random ducky emoji. If no duckies found returns :eyes:."""
"""Picks a random ducky emoji. If no duckies found returns 👀."""
duckies = [emoji for emoji in guild.emojis if emoji.name.startswith("ducky")]
if not duckies:
return ":eyes:"
return "\N{EYES}"
return random.choice(duckies)

@staticmethod
Expand Down