"""Contained commands and views for record commands.""" import nextcord from discord import TextInputStyle from nextcord.ext import commands from DakinySettings import settings from utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, record_event_points class RecordCog(commands.Cog, name="Record Commands"): """Class holds record commands.""" def __init__(self, bot: commands.Bot) -> None: """Default constructor for RecordCog class. Does nothing.""" super().__init__() self.bot: commands.Bot = bot @nextcord.slash_command(guild_ids=GUILDS) async def record(self, interaction: nextcord.Interaction) -> None: """Parent slash command for record. Never called.""" @record.subcommand(description="Record an event and its participants.") async def event( self, interaction: nextcord.Interaction, name: str = nextcord.SlashOption(required=True, description="Name of the event that was hosted."), host: nextcord.Member = nextcord.SlashOption( required=False, description="An alternative host if you are recording event for someone else.", ), ) -> None: """ Create and send recording event. This command creates the base event recording embed and sends a message with it and the EventView for gathering more information on the event. Args: interaction (nextcord.Interaction): interaction with this command. name (str): Name of the event. host (nextcord.Member, optional): Alternative host to user using command. """ # check role if interaction.guild.get_role(settings.role_ids.event_host) not in interaction.user.roles: await interaction.response.send_message( f"You must have the <@&{settings.role_ids.event_host}> role to record an event.", ephemeral=True, ) return if host is None: host = interaction.user embed: nextcord.Embed = nextcord.Embed(title=name, description="") embed.add_field(name="Participants", value="") embed.add_field(name="Non-Discord Participants", value="") embed.add_field(name="Hosted by", value=f"<@{host.id}>", inline=False) await interaction.response.send_message( "Fill out event details and select all members who participated!", view=EventView(name, host.id), ephemeral=True, embed=embed, ) @record.subcommand(description="Record a miscellaneous point for a clan member.") async def point( self, interaction: nextcord.Interaction, activity: str = nextcord.SlashOption( description="The type of activity to receive points for.", required=True, choices=[ "Clan chat activity", "Teaching or sharing in-game tips", "Recruiting efforts", "Successful recruitment", ], ), justification: str = nextcord.SlashOption( required=True, description="Reason to give the recipient a point.", max_length=int(MAX_DISCORD_MESSAGE_LENGTH / 2), ), recipient: nextcord.Member = nextcord.SlashOption( required=False, description="Discord user to give point to.", ), non_discord_recipient: str = nextcord.SlashOption( required=False, description="Non Discord user to give point to.", max_length=32 ), ) -> None: """ Gives a point to the provided recipient. Sends recording message to event participation channel. Only one recipient should be given point/provided to the command. Fails if not exactly one recipient is given. Args: interaction (nextcord.Interaction): interaction with this command. activity (str): Choice list of activities to give point for. justification (str): Reason to give point. recipient (nextcord.Member): Discord member to give point to. non_discord_recipient (str): Non Discord member to give point to. """ # role check if interaction.guild.get_role(settings.role_ids.clan_member) not in interaction.user.roles: await interaction.response.send_message( f"You must have the <@&{settings.role_ids.clan_member}> role to record points.", ephemeral=True, ) return # pylint or whatever seems to think that there are lines of unreachable code. That is false. if recipient and non_discord_recipient: await interaction.response.send_message("Please only provide 1 recipient.", ephemeral=True) return elif recipient is not None: # noqa: RET505 record_event_points( [str(recipient.id)], settings.event_settings.participate_points, is_member=True, ) recipient_str = f"<@{recipient.id}>" elif non_discord_recipient is not None: record_event_points( [non_discord_recipient], settings.event_settings.participate_points, is_member=False, ) recipient_str = f'"{non_discord_recipient}"' else: await interaction.response.send_message("You must provide a recipient.", ephemeral=True) return event_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.event_post) msg = f"<@{interaction.user.id}> has given {recipient_str} points for the {activity} activity. Justification: {justification}" # noqa: E501 if not msg.endswith("."): msg += "." await event_channel.send(msg) await interaction.response.send_message("Point has been added.", ephemeral=True) class EventView(nextcord.ui.View): """ Class handles gathering information on an event. Has buttons to edit the information, cancel, and submit. Also contains a UserSelect object to pick discord members. """ def __init__(self, name: str, host_id: int): super().__init__() self.name: str = name self.host_id: int = host_id self.event_participants = ParticipantSelect() self.add_item(self.event_participants) @nextcord.ui.button(label="Cancel", style=nextcord.ButtonStyle.red, row=1) async def cancel(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """Cancels event submission. Removes embed and view.""" await interaction.response.edit_message("Canceled", ephemeral=True, embed=None, view=None) self.stop() @nextcord.ui.button(label="Edit", style=nextcord.ButtonStyle.blurple, row=1) async def edit(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """Calls modal to update the text input information of the event.""" await interaction.response.send_modal(ParticipantModal(interaction.message.embeds[0])) @nextcord.ui.button(label="Confirm", style=nextcord.ButtonStyle.green, row=1) async def confirm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ Submits the event embed to the event channel. Removes view and embed from recording message. Sends point information to database. """ discord_users = self.event_participants.values.members # noqa: PD011 not a pandas object if discord_users: discord_users_list: list[str] = [str(user.id) for user in discord_users if user.id is not self.host_id] record_event_points( discord_users_list, settings.event_settings.participate_points, is_member=True, ) non_discord_users = interaction.message.embeds[0].fields[1].value if non_discord_users: non_discord_users_list: list[str] = non_discord_users.split("\n") record_event_points( non_discord_users_list, settings.event_settings.participate_points, is_member=False, ) record_event_points([self.host_id], settings.event_settings.host_points, is_member=True) event_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.event_post) await event_channel.send(embed=interaction.message.embeds[0]) await interaction.response.edit_message(content="Event has been submitted.", view=None, embed=None) self.stop() class ParticipantModal(nextcord.ui.Modal): """ This modal contains all the text edits for an event. You can change the name, description, and add the usernames of participants who are not in the discord. """ # consider turning this embed into a dict def __init__(self, embed: nextcord.Embed): super().__init__( "Edit the event.", timeout=5 * 60, ) self.embed: nextcord.Embed = embed self.name: nextcord.ui.TextInput = nextcord.ui.TextInput( required=True, label="Event Name", default_value=self.embed.title, ) self.description: nextcord.ui.TextInput = nextcord.ui.TextInput( required=False, label="Event Description", default_value=self.embed.description, style=TextInputStyle.paragraph, placeholder="Add a description if desired.", max_length=1000, ) self.participants: nextcord.ui.TextInput = nextcord.ui.TextInput( required=False, label="Non-Discord Usernames", style=TextInputStyle.paragraph, placeholder="Please separate usernames with a new line.", default_value=self.embed.fields[1].value, ) self.add_item(self.name) self.add_item(self.description) self.add_item(self.participants) async def callback(self, interaction: nextcord.Interaction) -> None: """ Edits the interaction message to contain the Non-Discord participants. Args: interaction (nextcord.Interaction): Interaction of the callback. """ self.embed.title = self.name.value self.embed.description = self.description.value self.embed.set_field_at(index=1, name="Non-Discord Participants", value=self.participants.value) await interaction.response.edit_message(embed=self.embed) class ParticipantSelect(nextcord.ui.UserSelect): """A selection drop down for discord members. Updates the embed with all users selected.""" def __init__(self) -> None: super().__init__( min_values=1, max_values=25, placeholder="Select all participants", row=0, ) async def callback(self, interaction: nextcord.Interaction) -> None: """ Edits the interaction message to contain the Discord participants. Args: interaction (nextcord.Interaction): Interaction of the callback. """ embed: nextcord.Embed = interaction.message.embeds[0] participants: str = "\n".join([f"<@{participant.id}>" for participant in self.values.members]) embed.set_field_at(0, name="Participants", value=participants) await interaction.response.edit_message(embed=embed)