aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Joe Banks <[email protected]>2024-05-13 01:07:59 +0100
committerGravatar Joe Banks <[email protected]>2024-05-13 15:58:44 +0100
commit51e9e3e4dfed044f19e0f56d9924bf75c483f75f (patch)
tree1b3db10f61d744b8396376f32faa5469dd2f8c92
parentAdd new Talentpool API method for fetching a nomination reason (diff)
Add new context menu command for nominating users
This command either updates or creates a nomination for the author of the selected message, including a hyperlink to the selected message. Hopefully the benefits that this additional context brings as well as the reduced bar of entry for creating nominations will enhance the talentpool recruitment system.
-rw-r--r--bot/exts/recruitment/talentpool/_cog.py154
1 files changed, 153 insertions, 1 deletions
diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py
index 78084899f..ad232df8c 100644
--- a/bot/exts/recruitment/talentpool/_cog.py
+++ b/bot/exts/recruitment/talentpool/_cog.py
@@ -5,12 +5,13 @@ from io import StringIO
import discord
from async_rediscache import RedisCache
-from discord import Color, Embed, Member, PartialMessage, RawReactionActionEvent, User
+from discord import Color, Embed, Member, PartialMessage, RawReactionActionEvent, User, app_commands
from discord.ext import commands, tasks
from discord.ext.commands import BadArgument, Cog, Context, group, has_any_role
from pydis_core.site_api import ResponseCodeError
from pydis_core.utils.channel import get_or_fetch_channel
from pydis_core.utils.members import get_or_fetch_member
+from sentry_sdk import new_scope
from bot.bot import Bot
from bot.constants import Bot as BotConfig, Channels, Emojis, Guild, MODERATION_ROLES, Roles, STAFF_ROLES
@@ -30,6 +31,66 @@ DAYS_UNTIL_INACTIVE = 45
log = get_logger(__name__)
+class NominationContextModal(discord.ui.Modal, title="New Nomination"):
+ nomination = discord.ui.TextInput(
+ label="Additional nomination context:",
+ style=discord.TextStyle.long,
+ placeholder="Additional context for nomination...",
+ required=False,
+ # Take some length off for the URL that is going to be appended
+ max_length=REASON_MAX_CHARS - 110
+ )
+
+ def __init__(self, api: NominationAPI, message: discord.Message, noms_channel: discord.TextChannel):
+ self.message = message
+ self.api = api
+ self.noms_channel = noms_channel
+
+ super().__init__()
+
+ async def on_submit(self, interaction: discord.Interaction) -> None:
+ reason = ""
+
+ if self.nomination.value and self.nomination.value != "":
+ reason += f"{self.nomination.value}\n\n"
+
+ reason += f"Nominated from: {self.message.jump_url}"
+
+ try:
+ await self.api.post_nomination(self.message.author.id, interaction.user.id, reason)
+ except ResponseCodeError as e:
+ match (e.status, e.response_json):
+ case (400, {"user": _}):
+ await interaction.response.send_message(
+ f":x: {self.target.mention} can't be found in the database tables.",
+ ephemeral=True
+ )
+ log.warning(f"Could not find {self.target.author} in the site database tables, sync may be broken")
+ return
+
+ raise e
+
+ await interaction.response.send_message(
+ f":white_check_mark: The nomination for {self.message.author.mention}"
+ " has been added to the talent pool",
+ ephemeral=True
+ )
+
+ noms_channel_message = (
+ f":star: {interaction.user.mention} has nominated "
+ f"{self.message.author.mention} from {self.message.jump_url}"
+ )
+
+ if self.nomination.value and self.nomination.value != "":
+ noms_channel_message += "\n```\n"
+ noms_channel_message += self.nomination.value
+ noms_channel_message += "```"
+
+ await self.noms_channel.send(noms_channel_message)
+
+ async def on_error(self, interaction: discord.Interaction, error: Exception) -> None:
+ """Handle any exceptions in the processing of the modal."""
+ await TalentPool._nominate_context_error(interaction, error)
class TalentPool(Cog, name="Talentpool"):
"""Used to nominate potential helper candidates."""
@@ -45,6 +106,15 @@ class TalentPool(Cog, name="Talentpool"):
# This lock lets us avoid cancelling the reviewer loop while the review code is running.
self.autoreview_lock = asyncio.Lock()
+ # Setup the context menu command for message-based nomination
+ self.nominate_user_context_menu = app_commands.ContextMenu(
+ name="Nominate User",
+ callback=self._nominate_context_callback,
+ )
+ self.nominate_user_context_menu.default_permissions = discord.Permissions.none()
+ self.nominate_user_context_menu.error(self._nominate_context_error)
+ self.bot.tree.add_command(self.nominate_user_context_menu, guild=discord.Object(Guild.id))
+
async def cog_load(self) -> None:
"""Start autoreview loop if enabled."""
if await self.autoreview_enabled():
@@ -338,6 +408,82 @@ class TalentPool(Cog, name="Talentpool"):
await self._nominate_user(ctx, user, reason)
+ @app_commands.checks.has_any_role(*STAFF_ROLES)
+ async def _nominate_context_callback(self, interaction: discord.Interaction, message: discord.Message) -> None:
+ """Create or update a nomination for the author of the selected message."""
+ if message.author.bot:
+ await interaction.response.send_message(
+ ":x: I'm afraid I can't do that. Only humans can be nominated.",
+ ephemeral=True
+ )
+ return
+
+ if isinstance(message.author, Member) and any(role.id in STAFF_ROLES for role in message.author.roles):
+ await interaction.response.send_message(
+ ":x: Nominating staff members, eh? Here's a cookie :cookie:",
+ ephemeral=True
+ )
+ return
+
+ maybe_nom = await self.api.get_nomination_reason(message.author.id, interaction.user.id)
+
+ if maybe_nom:
+ nomination, reason = maybe_nom
+
+ reason += f"\n\n{message.jump_url}"
+
+ await self.api.edit_nomination_entry(
+ nomination.id,
+ actor_id=interaction.user.id,
+ reason=reason
+ )
+
+ await interaction.response.send_message(
+ ":white_check_mark: Existing nomination updated",
+ ephemeral=True
+ )
+ return
+
+ nominations_channel = self.bot.get_channel(Channels.nominations)
+
+ await interaction.response.send_modal(NominationContextModal(self.api, message, nominations_channel))
+
+ @staticmethod
+ async def _nominate_context_error(
+ interaction: discord.Interaction,
+ error: app_commands.AppCommandError
+ ) -> None:
+ """Handle any errors that occur with the nomination context command."""
+ if isinstance(error, app_commands.errors.MissingAnyRole):
+ await interaction.response.send_message(
+ ":x: You do not have permission to use this command", ephemeral=True
+ )
+ return
+
+ message = (
+ f":x: An unexpected error occured, Please let us know!\n\n"
+ f"```{error.__class__.__name__}: {error}```"
+ )
+
+ if not interaction.response.is_done():
+ await interaction.response.send_message(message, ephemeral=True)
+ else:
+ await interaction.followup.send(message, ephemeral=True)
+
+ with new_scope() as scope:
+ scope.user = {
+ "id": interaction.user.id,
+ "username": str(interaction.user)
+ }
+
+ scope.set_tag("command", interaction.command.name)
+ scope.set_extra("interaction_data", interaction.data)
+
+ log.error(
+ f"Error executing application command '{interaction.command.name}' invoked by {interaction.user}",
+ exc_info=error
+ )
+
async def _nominate_user(self, ctx: Context, user: MemberOrUser, reason: str) -> None:
"""Adds the given user to the talent pool."""
if user.bot:
@@ -660,3 +806,9 @@ class TalentPool(Cog, name="Talentpool"):
self.autoreview_loop.cancel()
self.prune_talentpool.cancel()
+
+ self.bot.tree.remove_command(
+ self.nominate_user_context_menu.name,
+ guild=discord.Object(Guild.id),
+ type=self.nominate_user_context_menu.type
+ )