"""Set command hierarchy.""" import nextcord from nextcord.ext import commands from sqlalchemy import select from sqlalchemy.orm import Session from sqlmodel import col from database.db import engine from database.models import Category, CategoryGroup, Guild from utilities.logger import agon_logger from utilities.utils import MAX_LINE_LENGTH, autofill_categories, autofill_groups, send_highscore_message class SetCog(commands.Cog, name="Set command."): """Class holds commands for setting variables in guild's database.""" def __init__(self) -> None: """Default constructor for SetCog class. Does nothing.""" @nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True)) async def set(self, interaction: nextcord.Interaction) -> None: """Parent slash command for setting settings. Never called.""" @set.subcommand("Commands for editing a category.") async def category(self, interaction: nextcord.Interaction) -> None: """Parent slash subcommand for setting category settings. Never called.""" @category.subcommand(description="Update the number of entries to show for a category.", name="size") async def category_size( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group that Category is in.", required=True), category_id: str = nextcord.SlashOption( name="category", description="Category to update size of.", required=True ), size: int = nextcord.SlashOption(description="Number of entries to show for the category.", required=True), ) -> None: """ Command to update a category's size. Args: interaction (nextcord.Interaction): Interaction that invoked the command. group_id (str): String representation of Group id to register channel to. Is SlashOption. category_id (str): String representation of Category id to register channel to. Is SlashOption. size (int): Size to set. Is SlashOption. """ stmt = ( select(Category).where(col(Category.group_id) == int(group_id)).where(col(Category.id) == int(category_id)) ) with Session(engine) as session: category = session.scalar(stmt) if category is None: await interaction.response.send_message("Category or Group was not found.", ephemeral=True) return category.size = size # session.add(category) test if this is needed. SQLAlchemy can do the update without adding. session.commit() # Use the relationship to get group category is in. group = category.category_group if group is None: agon_logger.warning(f"{category.name}:{category.id} group relationship was None.") stmt = select(CategoryGroup).where(col(CategoryGroup.id) == group_id) group = session.scalar(stmt) if group is None: await interaction.response.send_message("Group was not found.", ephemeral=True) return await send_highscore_message(session, interaction.guild, group) await interaction.response.send_message(f"{category.name} size has been updated to {size}.", ephemeral=True) @category.subcommand(description="Update the is_ascending value for a category.") async def is_ascending( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group that Category is in.", required=True), category_id: str = nextcord.SlashOption( name="category", description="Category to update is_ascending of.", required=True ), ascending: bool = nextcord.SlashOption( # noqa: FBT001 description="Whether the scores are sorted ascending or not.", required=True ), ) -> None: """ Command to update a category's is_ascending boolean. Args: interaction (nextcord.Interaction): Interaction that invoked the command. group_id (str): String representation of Group id to register channel to. Is SlashOption. category_id (str): String representation of Category id to register channel to. Is SlashOption. ascending (bool): Boolean to set. Determines whether scores are sorted ascending or not. Is SlashOption. """ stmt = ( select(Category).where(col(Category.group_id) == int(group_id)).where(col(Category.id) == int(category_id)) ) with Session(engine) as session: category = session.scalar(stmt) if category is None: await interaction.response.send_message("Category or Group was not found.", ephemeral=True) return category.is_ascending = ascending # session.add(category) test if this is needed. SQLAlchemy can do the update without adding. session.commit() # Use the relationship to get group category is in. group = category.category_group if group is None: agon_logger.warning(f"{category.name}:{category.id} group relationship was None.") stmt = select(CategoryGroup).where(col(CategoryGroup.id) == group_id) group = session.scalar(stmt) if group is None: await interaction.response.send_message("Group was not found.", ephemeral=True) return await send_highscore_message(session, interaction.guild, group) await interaction.response.send_message( f"{category.name} is_ascending has been updated to {ascending}.", ephemeral=True ) @category.subcommand(description="Update the is_validated value for a category.") async def is_validated( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group that Category is in.", required=True), category_id: str = nextcord.SlashOption( name="category", description="Category to update is_ascending of.", required=True ), validated: bool = nextcord.SlashOption( # noqa: FBT001 description="Whether the scores need to be validated.", required=True ), ) -> None: """ Command to update a category's is_validated boolean. Args: interaction (nextcord.Interaction): Interaction that invoked the command. group_id (str): String representation of Group id to register channel to. Is SlashOption. category_id (str): String representation of Category id to register channel to. Is SlashOption. validated (bool): Boolean to set. Determines whether scores are validated or not. Is SlashOption. """ stmt = ( select(Category).where(col(Category.group_id) == int(group_id)).where(col(Category.id) == int(category_id)) ) with Session(engine) as session: category = session.scalar(stmt) if category is None: await interaction.response.send_message("Category or Group was not found.", ephemeral=True) return category.is_validated = validated # session.add(category) test if this is needed. SQLAlchemy can do the update without adding. session.commit() # Use the relationship to get group category is in. group = category.category_group if group is None: agon_logger.warning(f"{category.name}:{category.id} group relationship was None.") stmt = select(CategoryGroup).where(col(CategoryGroup.id) == group_id) group = session.scalar(stmt) if group is None: await interaction.response.send_message("Group was not found.", ephemeral=True) return await interaction.response.send_message( f"{category.name} is_validated has been updated to {validated}.", ephemeral=True ) @category.subcommand(name="name", description="Change the name of a category.") async def category_name( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group that Category is in.", required=True), category_id: str = nextcord.SlashOption( name="category", description="Category to update the name of.", required=True ), name: str = nextcord.SlashOption(description="Name to update to.", required=True, min_length=1, max_length=30), ) -> None: """ Command to update a category's name. Args: interaction (nextcord.Interaction): Interaction that invoked the command. group_id (str): String representation of Group id to register channel to. Is SlashOption. category_id (str): String representation of Category id to register channel to. Is SlashOption. name (str): The name to update the category's name to. Is SlashOption. """ stmt = ( select(Category).where(col(Category.group_id) == int(group_id)).where(col(Category.id) == int(category_id)) ) with Session(engine) as session: category = session.scalar(stmt) if category is None: await interaction.response.send_message("Category or Group was not found.", ephemeral=True) return old_name = category.name category.name = name # session.add(category) test if this is needed. SQLAlchemy can do the update without adding. session.commit() # Use the relationship to get group category is in. group = category.category_group if group is None: agon_logger.warning(f"{category.name}:{category.id} group relationship was None.") stmt = select(CategoryGroup).where(col(CategoryGroup.id) == group_id) group = session.scalar(stmt) if group is None: await interaction.response.send_message("Group was not found.", ephemeral=True) return await send_highscore_message(session, interaction.guild, group) await interaction.response.send_message(f"{old_name} has been updated to {category.name}.", ephemeral=True) @set.subcommand(description="Commands for editing a group.") async def group(self, interaction: nextcord.Interaction) -> None: """Parent slash subcommand for setting group settings. Never called.""" @group.subcommand(name="name", description="Change the name of a Group.") async def group_name( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group that Category is in.", required=True), name: str = nextcord.SlashOption(description="Name to update to.", required=True, min_length=1, max_length=50), ) -> None: """ Command to update a group's name. Args: interaction (nextcord.Interaction): Interaction that invoked the command. group_id (str): String representation of Group id to register channel to. Is SlashOption. name (str): The name to update the group's name to. Is SlashOption. """ stmt = select(CategoryGroup).where(col(CategoryGroup.id) == int(group_id)) with Session(engine) as session: group = session.scalar(stmt) if group is None: await interaction.response.send_message("Group was not found.", ephemeral=True) return old_name = group.name group.name = name # session.add(group) test if this is needed. SQLAlchemy can do the update without adding. session.commit() await send_highscore_message(session, interaction.guild, group) await interaction.response.send_message(f"{old_name} has been updated to {group.name}.", ephemeral=True) @group.subcommand(description="Registers the channel for a Group's messages.") async def output_channel( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group to assign to channel.", required=True), channel: nextcord.abc.GuildChannel | None = nextcord.SlashOption( description="Channel to register", required=False, channel_types=[nextcord.ChannelType.text], default=None, ), ) -> None: """ Command function to register a CategoryGroup's output channel. Args: interaction (nextcord.Interaction): Interaction that invoked the command. group_id (str): String representation of Group id to register channel to. Is SlashOption. channel (nextcord.abc.GuildChannel, optional): Channel to register. Is a SlashOption. Limited to text channels, Defaults to None. """ channel_id = interaction.channel.id if not channel else channel.id with Session(engine) as session: stmt = ( select(CategoryGroup) .where(col(CategoryGroup.guild_id) == interaction.guild_id) .where(col(CategoryGroup.id) == int(group_id)) ) group = session.scalar(stmt) group.ChannelId = channel_id session.commit() await interaction.send(f"Registered <#{channel_id}>.", ephemeral=True) @group.subcommand( description="Set character length for a category. Can cut off username/score. Affects all categories." ) async def pad_length( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group to assign pad length to.", required=True), length: int = nextcord.SlashOption( description="Number of characters alloted to a category.", required=True, default=26, min_value=7, max_value=MAX_LINE_LENGTH, ), ) -> None: """ Changes guild's category pad length. Args: interaction (nextcord.Interaction): Interaction that invoked the command group_id (str): String representation of Group id to set pad length in. Is SlashOption. length (int): Number of characters. Is SlashOption. Defaults to 26. """ with Session(engine) as session: stmt = ( select(CategoryGroup) .where(col(CategoryGroup.guild_id) == interaction.guild_id) .where(col(CategoryGroup.id) == int(group_id)) ) group = session.scalar(stmt) if group is None: await interaction.response.send_message("Group was not found.", ephemeral=True) return group.PadLength = length session.commit() await interaction.response.send_message( f"Category pad length set to {length} for {group.name}", ephemeral=True ) await send_highscore_message(session, interaction.guild, group) @group.subcommand( description="Registers the channel for submission messages. Currently this information is deprecated." ) async def submission_channel( self, interaction: nextcord.Interaction, channel: nextcord.abc.GuildChannel | None = nextcord.SlashOption( description="Channel to register", required=False, channel_types=[nextcord.ChannelType.text], default=None, ), ) -> None: """ Command function to register the submission message channel. Args: interaction (nextcord.Interaction): Interaction that invoked the command channel (nextcord.abc.GuildChannel): Channel to register Is a SlashOption. Limited to text channels, Defaults to None. """ channel_id = interaction.channel.id if not channel else channel.id with Session(engine) as session: stmt = select(Guild).where(col(Guild.id) == interaction.guild_id) guild = session.scalar(stmt) guild.submission_channel = channel_id session.commit() await interaction.send(f"Registered <#{channel_id}>.", ephemeral=True) @group.subcommand(description="Set username length for a Group of Categories.") async def username_length( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption( name="group", description="Group to change username length of.", required=True ), length: int = nextcord.SlashOption( description="Number of characters.", required=True, default=12, min_value=1, max_value=30 ), ) -> None: """ Changes guild's username length limit. Args: interaction (nextcord.Interaction): Interaction that invoked the command group_id (str): String representation of Group id to set username length in. Is SlashOption. length (int): Number of characters. Is SlashOption. Defaults to 12. """ with Session(engine) as session: stmt = ( select(CategoryGroup) .where(col(CategoryGroup.guild_id) == interaction.guild_id) .where(col(CategoryGroup.id) == int(group_id)) ) group = session.scalar(stmt) group.UsernameLength = length session.commit() await interaction.response.send_message(f"Username length set to {length}.", ephemeral=True) @set.subcommand(description="Registers the channel for verification messages.") async def verify_channel( self, interaction: nextcord.Interaction, channel: nextcord.abc.GuildChannel | None = nextcord.SlashOption( description="Channel to register", required=False, channel_types=[nextcord.ChannelType.text], default=None, ), ) -> None: """ Command function to register the verification message channel. Args: interaction (nextcord.Interaction): Interaction that invoked the command channel (Optional[nextcord.abc.GuildChannel]): Channel to register Is a SlashOption. Limited to text channels, Defaults to None. """ channel_id = interaction.channel.id if not channel else channel.id with Session(engine) as session: stmt = select(Guild).where(col(Guild.id) == interaction.guild_id) guild = session.scalar(stmt) guild.VerificationChannel = channel_id session.commit() await interaction.send(f"Registered <#{channel_id}>.", ephemeral=True) @category_name.on_autocomplete("category_id") @category_size.on_autocomplete("category_id") @is_ascending.on_autocomplete("category_id") @is_validated.on_autocomplete("category_id") async def category_autocomplete(self, interaction: nextcord.Interaction, category_id: str, group_id: str) -> None: """ Autocomplete function for category names. Args: interaction (nextcord.Interaction): Interaction that invoked the command. category_id (str): String to be autocompleted with Category name. group_id (str): Group to find Category from. """ await interaction.response.send_autocomplete( autofill_categories(interaction.guild_id, int(group_id), category_id) ) @category_name.on_autocomplete("group_id") @category_size.on_autocomplete("group_id") @group_name.on_autocomplete("group_id") @is_ascending.on_autocomplete("group_id") @is_validated.on_autocomplete("group_id") @output_channel.on_autocomplete("group_id") @pad_length.on_autocomplete("group_id") @username_length.on_autocomplete("group_id") async def group_autocomplete(self, interaction: nextcord.Interaction, group_id: str) -> None: """ Sends results from autofill_groups back to group_output_channel for autocompletion of group_id. Args: interaction (nextcord.Interaction): Interaction with command. group_id (str): String to run autocompletion against. """ await interaction.response.send_autocomplete(autofill_groups(interaction.guild_id, group_id))