Files
agon/agon/cogs/commands/delete.py
T

289 lines
12 KiB
Python

"""Commands for deleting groups/categories/scores in database and messages in discord."""
import nextcord
from nextcord.ext import commands
from sqlalchemy import distinct, select
from sqlalchemy.orm import Session
from sqlmodel import col
from agon.database.db import engine
from agon.database.models import Category, CategoryGroup, Score
from agon.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="Delete Cog"):
"""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) -> None:
"""Parent slash command for deleting things. Never called."""
@delete.subcommand()
async def all(self, interaction: nextcord.Interaction) -> None:
"""Subcommand parent for deleting all of a thing. Never called."""
@all.subcommand(description="Clears highscores channel of all highscore messages. Does not change data.")
async def channel_messages(
self,
interaction: nextcord.Interaction,
channel: nextcord.TextChannel | None = nextcord.SlashOption(
description="Channel to clear of messages.",
required=False,
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:
if isinstance(interaction.channel, nextcord.TextChannel):
channel = interaction.channel
else:
# This should never happen.
await interaction.response.send_message("Channel is not a text channel somehow.", ephemeral=True)
return
with Session(engine) as session:
stmt = (
select(CategoryGroup)
.where(col(CategoryGroup.guild_id) == interaction.guild_id)
.where(col(CategoryGroup.channel_id) == channel.id)
)
# TODO test when nothing in channel
groups = session.scalars(stmt)
await interaction.response.defer(ephemeral=True)
for group in groups:
if group.message_id is not None:
try:
message: nextcord.Message = await channel.fetch_message(group.message_id)
await message.delete()
except nextcord.NotFound:
# silently ignore that messages are not found. They got deleted already.
pass
await interaction.send("Highscore messages deleted.", ephemeral=True)
@all.subcommand(
description="Removes all of a user's scores. Warning: data is destroyed, use with care.",
)
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
),
) -> None:
"""
Searches all of guild's scores data for any score from the user and removes.
Updates highscore message if user was removed from that Category Group.
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(col(Score.category_id))).where(col(Score.username) == user_name)
category_ids: list[int] = list(session.scalars(stmt))
stmt = select(Score).where(col(Score.username) == user_name)
for score in session.scalars(stmt):
session.delete(score)
session.commit()
group_ids = select(col(Category.group_id)).where(col(Category.id).in_(category_ids))
stmt = select(CategoryGroup).where(col(CategoryGroup.id).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 Category Groups.', ephemeral=True)
@delete.subcommand(description="Delete a category from a group. Deletion is permanent.")
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 permanent.")
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) -> None:
"""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),
) -> None:
"""
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(col(CategoryGroup.id) == int(group_id))
group = session.scalar(stmt)
stmt = select(col(Category.name)).where(col(Category.id) == int(category_id))
category_name = session.scalar(stmt)
stmt = (
select(Score).where(col(Score.category_id) == int(category_id)).where(col(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),
) -> None:
"""
Removes user from a group. 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(col(CategoryGroup.id) == int(group_id))
group = session.scalar(stmt)
category_ids = select(col(Category.id)).where(col(Category.group_id) == int(group_id))
stmt = select(Score).where(col(Score.category_id).in_(category_ids)).where(col(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.name}.', 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) -> None:
"""
Autocomplete function for group 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) -> 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)
)
@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: str | None = None
) -> 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), int(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) -> None:
"""
Sends response from autofill_users_in_guild back to update_username for autocompletion of old_username.
Args:
interaction (nextcord.Interaction): Interaction with command.
username (str): String to run autocompletion against.
"""
await interaction.response.send_autocomplete(autofill_users_in_guild(interaction.guild_id, username))