""" Commands for deleting groups/categories/scores in database and messages in discord.. """ from typing import Optional import nextcord from nextcord.ext import commands from sqlalchemy import distinct, select from sqlalchemy.orm import Session # pylint: disable=import-error from utilities.sql_data_classes import Category, CategoryGroup, Score, engine from utilities.utils import ( autofill_categories, autofill_groups, autofill_users_in_group_category, autofill_users_in_guild, send_highscore_message, ) from .views.delete_confirm import DeleteConfirmView, create_delete_id class DeleteCog(commands.Cog, name="Table Management"): """ Class holds commands for deleting. """ def __init__(self) -> None: """ Default constructor for DeleteCog class. """ @nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True)) async def delete(self, interaction: nextcord.Interaction): """ Parent slash command for deleting things. Never called. """ @delete.subcommand() async def all(self, interaction: nextcord.Interaction): """ Subcommand parent for deleting all of a thing. Never called. """ @all.subcommand(description="Clears highscores channel of all table messages. Does not change data.") async def channel_messages( self, interaction: nextcord.Interaction, channel: Optional[nextcord.abc.GuildChannel] = nextcord.SlashOption( description="Channel to clear of messages.", required=False, channel_types=[nextcord.ChannelType.text], default=None, ), ) -> None: """ Deletes all group messages from channel. Does not change data. Does not delete highscores channel id. Uses current channel if not supplied a channel. Args: interaction (nextcord.Interaction): Interaction that invoked the command channel (Optional[nextcord.abc.GuildChannel]): Channel to clear of messages. Is a SlashOption. Limited to text channels. Defaults to None. """ if not channel: channel = interaction.channel with Session(engine) as session: stmt = ( select(CategoryGroup) .where(CategoryGroup.GuildId == interaction.guild_id) .where(CategoryGroup.ChannelId == channel.id) ) # TODO test when nothing in channel groups = session.scalars(stmt) await interaction.response.defer(ephemeral=True) for group in groups: try: table_message: nextcord.Message = await channel.fetch_message(group.MessageId) await table_message.delete() except nextcord.NotFound: # silently ignore that messages are not found. They got deleted already. pass await interaction.send("Table messages deleted.", ephemeral=True) @all.subcommand( description="Removes all of a user's scores. Warning: data is destroyed, use with case.", ) async def users( self, interaction: nextcord.Interaction, user_name: str = nextcord.SlashOption( description="User to remove scores for. Note: This is not always a user's discord name.", required=True ), ): """ Searches all of guild's scores data for any score from the user. Removes. Updates table message is user was removed from that table. Args: interaction (nextcord.Interaction): Interaction that invoked the command. user_name (str): User to remove from highscores """ with Session(engine) as session: stmt = select(distinct(Score.CategoryId)).where(Score.UserName == user_name) category_ids: "list[int]" = list(session.scalars(stmt)) stmt = select(Score).where(Score.UserName == user_name) for score in session.scalars(stmt): session.delete(score) session.commit() group_ids = select(Category.GroupId).where(Category.CategoryId.in_(category_ids)) stmt = select(CategoryGroup).where(CategoryGroup.GroupId.in_(group_ids)) for group in session.scalars(stmt): await send_highscore_message(session, interaction.guild, group) await interaction.send(f'User "{user_name}" was removed from all tables.', ephemeral=True) @delete.subcommand(description="Delete a category from a group. Deletion is perminant.") async def category( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group to delete from.", required=True), category_id: str = nextcord.SlashOption(name="category", description="Category to delete.", required=True), ) -> None: """ Calls the delete confirmation view for the given group and category. Args: interaction (nextcord.Interaction): Interaction of command. group_id (str): Group to delete from. Is SlashOption. category_id (str): Category to delete. Is SlashOption. """ await interaction.response.send_modal( DeleteConfirmView(custom_id=create_delete_id(int(group_id), int(category_id))) ) @delete.subcommand(description="Delete a group. Deletion is perminant.") async def group( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group to delete.", required=True), ) -> None: """ Calls the delete confirmation view for the given group. Args: interaction (nextcord.Interaction): Interaction of command. group_id (str): Group to delete from. Is SlashOption. """ await interaction.response.send_modal(DeleteConfirmView(custom_id=create_delete_id(int(group_id)))) @delete.subcommand() async def user(self, interaction: nextcord.Interaction): """ Subcommand parent for deleting user information. Never called. """ @user.subcommand(description="Deletes a user's scores from a category.") async def from_category( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group to delete user from.", required=True), category_id: str = nextcord.SlashOption( name="category", description="Category to delete user from.", required=True ), user_name: str = nextcord.SlashOption(description="User to delete.", required=True), ): """ Removes a user from a category. Uses autofill functions to fill out group_id, category_id, and user_name. Args: interaction (nextcord.Interaction): Interaction that invoked the command. group_id (str): String representation of Group id to remove user from. Is SlashOption. category_id (str): String representation of Category id to remove user from. Is SlashOption. user_name (str): The user to remove. Is SlashOption. """ with Session(engine) as session: stmt = select(CategoryGroup).where(CategoryGroup.GroupId == int(group_id)) group = session.scalar(stmt) stmt = select(Category.CategoryName).where(Category.CategoryId == int(category_id)) category_name = session.scalar(stmt) stmt = select(Score).where(Score.CategoryId == int(category_id)).where(Score.UserName == user_name) # there should only be one because category id and username are a composite primary key in score session.delete(session.scalar(stmt)) session.commit() await send_highscore_message(session, interaction.guild, group) await interaction.response.send_message( f'User "{user_name}" removed from "{category_name}".', ephemeral=True ) @user.subcommand(description="Deletes a user's scores from entire group.") async def from_group( self, interaction: nextcord.Interaction, group_id: str = nextcord.SlashOption(name="group", description="Group to delete user from.", required=True), user_name: str = nextcord.SlashOption(description="User to delete.", required=True), ): """ Removes user from a goup. Uses autofill functions to fill out group_id and user_name. Args: interaction (nextcord.Interaction): Interaction that invoked the command. group_id (str): String representation of Group id to remove user from. Is SlashOption. user_name (str): The user to remove. Is SlashOption. """ with Session(engine) as session: stmt = select(CategoryGroup).where(CategoryGroup.GroupId == int(group_id)) group = session.scalar(stmt) category_ids = select(Category.CategoryId).where(Category.GroupId == int(group_id)) stmt = select(Score).where(Score.CategoryId.in_(category_ids)).where(Score.UserName == user_name) for score in session.scalars(stmt): session.delete(score) session.commit() await send_highscore_message(session, interaction.guild, group) await interaction.response.send_message( f'User "{user_name}" removed from all categories in {group.GroupName}.', ephemeral=True ) @from_category.on_autocomplete("group_id") @from_group.on_autocomplete("group_id") @category.on_autocomplete("group_id") @group.on_autocomplete("group_id") async def group_autocomplete(self, interaction: nextcord.Interaction, group_id: str): """ Autocomplete function for table names. Args: interaction (nextcord.Interaction): Interaction that invoked the command. group_id (str): String to be autocompleted with Group name. """ await interaction.response.send_autocomplete(autofill_groups(interaction.guild_id, group_id)) @from_category.on_autocomplete("category_id") @category.on_autocomplete("category_id") async def category_autocomplete(self, interaction: nextcord.Interaction, category_id: str, group_id: str): """ 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) ) @from_category.on_autocomplete("user_name") @from_group.on_autocomplete("user_name") async def user_autocomplete( self, interaction: nextcord.Interaction, user_name: str, group_id: str, category_id: Optional[str] = None ): """ Autocomplete functions for user names. Args: interaction (nextcord.Interaction): Interaction that invoked the command. user_name (str): String to be autocompleted with User name. group_id (str): String representation of Group id to find category from. category_id (Optional[str]): String representation of Category id to find User from, can be None. """ await interaction.response.send_autocomplete( autofill_users_in_group_category( interaction.guild_id, int(group_id), category_id if category_id else 0, user_name ) ) @users.on_autocomplete("user_name") async def all_users_autocomplete(self, interaction: nextcord.Interaction, username: str): """ Sends response from autofill_users_in_guild back to update_username for autocompletion of old_username. Args: interaction (nextcord.Interaction): Interaction with command. old_username (str): String to run autocompletion against. """ await interaction.response.send_autocomplete(autofill_users_in_guild(interaction.guild_id, username))