From efed6e001fbee1a5f1f6438e9284eb3f424dcfa6 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Thu, 11 Aug 2022 21:55:06 -0400 Subject: [PATCH 01/34] Moved all commands into single commands cog. --- cogs/commands/__init__.py | 18 ++++++++++++++++++ .../__init__.py => commands/delete_scores.py} | 7 ------- .../post_highscores.py} | 7 ------- .../__init__.py => commands/register.py} | 7 ------- 4 files changed, 18 insertions(+), 21 deletions(-) create mode 100644 cogs/commands/__init__.py rename cogs/{delete_scores/__init__.py => commands/delete_scores.py} (88%) rename cogs/{post_highscores/__init__.py => commands/post_highscores.py} (92%) rename cogs/{register/__init__.py => commands/register.py} (94%) diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py new file mode 100644 index 0000000..e8e2eb0 --- /dev/null +++ b/cogs/commands/__init__.py @@ -0,0 +1,18 @@ +""" +Cog loader. Adds all command classes as cogs to the bot. +""" + +from nextcord.ext import commands + +# Command classes +from .delete_scores import DeleteScores +from .post_highscores import PostHighscores +from .register import Register + +# This function will be called when this extension is loaded. +# It is necessary to add these functions to the bot. +def setup(bot: commands.Bot): + """Adds all command classes to bot.""" + bot.add_cog(DeleteScores()) + bot.add_cog(PostHighscores(bot)) + bot.add_cog(Register()) diff --git a/cogs/delete_scores/__init__.py b/cogs/commands/delete_scores.py similarity index 88% rename from cogs/delete_scores/__init__.py rename to cogs/commands/delete_scores.py index 9c417b5..1c10dd0 100644 --- a/cogs/delete_scores/__init__.py +++ b/cogs/commands/delete_scores.py @@ -42,10 +42,3 @@ class DeleteScores(commands.Cog, name="Delete Scores"): await interaction.response.send_message( "You shouldn't be using this message.", ephemeral=True ) - - -# This function will be called when this extension is loaded. -# It is necessary to add these functions to the bot. -def setup(bot: commands.Bot): - """Adds function to bot""" - bot.add_cog(DeleteScores()) diff --git a/cogs/post_highscores/__init__.py b/cogs/commands/post_highscores.py similarity index 92% rename from cogs/post_highscores/__init__.py rename to cogs/commands/post_highscores.py index b6b39bd..6fa8dba 100644 --- a/cogs/post_highscores/__init__.py +++ b/cogs/commands/post_highscores.py @@ -59,10 +59,3 @@ class PostHighscores(commands.Cog, name="Post Highscores"): highscores_data.save_guild_data(guild) await interaction.followup.send(f"Posted all tables to <#{channel.id}>.", ephemeral=True) - - -# This function will be called when this extension is loaded. -# It is necessary to add these functions to the bot. -def setup(bot: commands.Bot): - """Adds function to bot""" - bot.add_cog(PostHighscores(bot)) diff --git a/cogs/register/__init__.py b/cogs/commands/register.py similarity index 94% rename from cogs/register/__init__.py rename to cogs/commands/register.py index 40ee7c8..88bcfad 100644 --- a/cogs/register/__init__.py +++ b/cogs/commands/register.py @@ -105,10 +105,3 @@ class Register(commands.Cog, name="Register"): await interaction.send(f"Registered <#{channel_id}>.", ephemeral=True) guild.verification_channel = channel_id highscores_data.save_guild_data(guild) - - -# This function will be called when this extension is loaded. -# It is necessary to add these functions to the bot. -def setup(bot: commands.Bot) -> None: - """Adds function to bot""" - bot.add_cog(Register()) From 349fdfe18d6e794729b85163565a55285c6f39e8 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Thu, 11 Aug 2022 22:16:57 -0400 Subject: [PATCH 02/34] Renamed python files to more describe their contents. Intend on having more than 1 class per file. --- cogs/commands/__init__.py | 6 +++--- .../commands/{post_highscores.py => message_management.py} | 7 +++++-- cogs/commands/{register.py => settings_management.py} | 5 ++--- cogs/commands/{delete_scores.py => table_management.py} | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) rename cogs/commands/{post_highscores.py => message_management.py} (92%) rename cogs/commands/{register.py => settings_management.py} (97%) rename cogs/commands/{delete_scores.py => table_management.py} (96%) diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py index e8e2eb0..897e62b 100644 --- a/cogs/commands/__init__.py +++ b/cogs/commands/__init__.py @@ -5,9 +5,9 @@ Cog loader. Adds all command classes as cogs to the bot. from nextcord.ext import commands # Command classes -from .delete_scores import DeleteScores -from .post_highscores import PostHighscores -from .register import Register +from .message_management import PostHighscores +from .settings_management import Register +from .table_management import DeleteScores # This function will be called when this extension is loaded. # It is necessary to add these functions to the bot. diff --git a/cogs/commands/post_highscores.py b/cogs/commands/message_management.py similarity index 92% rename from cogs/commands/post_highscores.py rename to cogs/commands/message_management.py index 6fa8dba..34cf001 100644 --- a/cogs/commands/post_highscores.py +++ b/cogs/commands/message_management.py @@ -1,5 +1,5 @@ """ -Bot command to post the highscores +Commands for managing/displaying the highscores messages. """ from nextcord.ext import commands @@ -11,7 +11,10 @@ from utilities.utils import send_highscore_message # pylint: disable=import-err class PostHighscores(commands.Cog, name="Post Highscores"): - """Posts Highscores.""" + """ + Posts Highscores messages. Either creates new messages for each table if one doesn't exist + or edits the existing message. + """ def __init__(self, bot: commands.Bot) -> None: """ diff --git a/cogs/commands/register.py b/cogs/commands/settings_management.py similarity index 97% rename from cogs/commands/register.py rename to cogs/commands/settings_management.py index 88bcfad..b649b28 100644 --- a/cogs/commands/register.py +++ b/cogs/commands/settings_management.py @@ -1,8 +1,7 @@ """ -Bot command for registering various channels to the bot. +Commands for managing the Guild's settings. """ - from nextcord.ext import commands import nextcord @@ -11,7 +10,7 @@ from highscores import highscores_data # pylint: disable=import-error class Register(commands.Cog, name="Register"): """ - Class holds various register commands. + Class holds the Register group command. Registers various channels. """ def __init__(self) -> None: diff --git a/cogs/commands/delete_scores.py b/cogs/commands/table_management.py similarity index 96% rename from cogs/commands/delete_scores.py rename to cogs/commands/table_management.py index 1c10dd0..c6f49bc 100644 --- a/cogs/commands/delete_scores.py +++ b/cogs/commands/table_management.py @@ -1,5 +1,5 @@ """ -Bot command for deleting the guild's scores. +Commands for managing the tables/categories/scores for a Guild. """ from nextcord.ext import commands From b2acafd6e7d02a0db936893a8d4efd5edc65118b Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Thu, 11 Aug 2022 22:44:22 -0400 Subject: [PATCH 03/34] Changed design to be one class per file. --- cogs/commands/__init__.py | 12 ++++++------ cogs/commands/message_management.py | 13 +++++++++---- cogs/commands/settings_management.py | 6 +++--- cogs/commands/table_management.py | 9 +++++---- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py index 897e62b..6985d26 100644 --- a/cogs/commands/__init__.py +++ b/cogs/commands/__init__.py @@ -5,14 +5,14 @@ Cog loader. Adds all command classes as cogs to the bot. from nextcord.ext import commands # Command classes -from .message_management import PostHighscores -from .settings_management import Register -from .table_management import DeleteScores +from .message_management import MessageManagement +from .settings_management import SettingsManagement +from .table_management import TableManagement # This function will be called when this extension is loaded. # It is necessary to add these functions to the bot. def setup(bot: commands.Bot): """Adds all command classes to bot.""" - bot.add_cog(DeleteScores()) - bot.add_cog(PostHighscores(bot)) - bot.add_cog(Register()) + bot.add_cog(TableManagement()) + bot.add_cog(MessageManagement(bot)) + bot.add_cog(SettingsManagement()) diff --git a/cogs/commands/message_management.py b/cogs/commands/message_management.py index 34cf001..d3172f7 100644 --- a/cogs/commands/message_management.py +++ b/cogs/commands/message_management.py @@ -10,15 +10,14 @@ from highscores import highscores_data # pylint: disable=import-error from utilities.utils import send_highscore_message # pylint: disable=import-error -class PostHighscores(commands.Cog, name="Post Highscores"): +class MessageManagement(commands.Cog, name="Message Management"): """ - Posts Highscores messages. Either creates new messages for each table if one doesn't exist - or edits the existing message. + Class holds commands for managing the messages that display highscores. """ def __init__(self, bot: commands.Bot) -> None: """ - Default constructor for PostHighscores class + Default constructor for MessageManagement class Args: bot (commands.Bot): Bot object. Required to register submission button view. @@ -33,6 +32,12 @@ class PostHighscores(commands.Cog, name="Post Highscores"): """ A command which posts the highscore in the registered channel. Sends warning if no channel has been registered. + + Either creates new messages for each table if one doesn't exist + or edits the existing message. + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command """ guild = highscores_data.get_guild(interaction.guild_id) print(f"Printing out highscores information in channel {guild.highscore_channel}") diff --git a/cogs/commands/settings_management.py b/cogs/commands/settings_management.py index b649b28..92efa06 100644 --- a/cogs/commands/settings_management.py +++ b/cogs/commands/settings_management.py @@ -8,14 +8,14 @@ import nextcord from highscores import highscores_data # pylint: disable=import-error -class Register(commands.Cog, name="Register"): +class SettingsManagement(commands.Cog, name="Settings Management"): """ - Class holds the Register group command. Registers various channels. + Class holds commands for managing Guild's settings. """ def __init__(self) -> None: """ - Default constructor for Register class. Does nothing. + Default constructor for SettingsManagement class. Does nothing. """ @nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True)) diff --git a/cogs/commands/table_management.py b/cogs/commands/table_management.py index c6f49bc..cf7e04e 100644 --- a/cogs/commands/table_management.py +++ b/cogs/commands/table_management.py @@ -8,15 +8,14 @@ import nextcord from highscores import highscores_data # pylint: disable=import-error -class DeleteScores(commands.Cog, name="Delete Scores"): +class TableManagement(commands.Cog, name="Table Management"): """ - Warning: This command only works for developers. - Deletes this guild's scores. + Class holds commands for managing the data storage of the highscores. """ def __init__(self): """ - Default constructor for DeleteScores class. Does nothing. + Default constructor for TableManagement class. Does nothing. """ @nextcord.slash_command( @@ -25,6 +24,8 @@ class DeleteScores(commands.Cog, name="Delete Scores"): ) async def delete_scores(self, interaction: nextcord.Interaction): """ + Warning: This command only works for developers. + A command which deletes all scores in all tables for this guild. Command limits to admins to prevent devs from usings on non-test servers. """ From caef6a3652b622d7caec1a6169339c650dbd72f7 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Thu, 11 Aug 2022 22:58:58 -0400 Subject: [PATCH 04/34] Created command to clear table messages. --- cogs/commands/message_management.py | 36 +++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/cogs/commands/message_management.py b/cogs/commands/message_management.py index d3172f7..c1dde49 100644 --- a/cogs/commands/message_management.py +++ b/cogs/commands/message_management.py @@ -24,6 +24,38 @@ class MessageManagement(commands.Cog, name="Message Management"): """ self.bot = bot + @nextcord.slash_command( + description="Clears highscores channel of all table messages. Does not change data.", + default_member_permissions=nextcord.Permissions(administrator=True), + ) + async def delete_table_messages(self, interaction: nextcord.Interaction) -> None: + """ + Deletes all table messages from highscores channel. Does not change data. + Does not delete highscores channel id. + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command + """ + guild = highscores_data.get_guild(interaction.guild_id) + + highscores_channel: nextcord.TextChannel = interaction.guild.get_channel( + guild.highscore_channel + ) + + await interaction.response.defer(ephemeral=True) + + for table in guild.tables: + try: + table_message: nextcord.Message = await highscores_channel.fetch_message( + table.message_id + ) + 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) + @nextcord.slash_command( description="Triggers post/update of highscores to registered highscore channel.", default_member_permissions=nextcord.Permissions(administrator=True), @@ -53,7 +85,7 @@ class MessageManagement(commands.Cog, name="Message Management"): error_response = "No tables have been created for this guild. Please create some tables." assert ( len(highscores_data.get_guild(interaction.guild_id).tables) > 0 - ), await interaction.response(error_response, ephemeral=True) + ), await interaction.send(error_response, ephemeral=True) # awaiting response because posting scores takes a while await interaction.response.defer(ephemeral=True) @@ -66,4 +98,4 @@ class MessageManagement(commands.Cog, name="Message Management"): # messages have been sent. highscores_data.save_guild_data(guild) - await interaction.followup.send(f"Posted all tables to <#{channel.id}>.", ephemeral=True) + await interaction.send(f"Posted all tables to <#{channel.id}>.", ephemeral=True) From c918b9492b3fab852b97deff110ebb2a4d9990dc Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Thu, 11 Aug 2022 23:02:44 -0400 Subject: [PATCH 05/34] Renamed to data_management. Better description. --- cogs/commands/__init__.py | 4 ++-- cogs/commands/{table_management.py => data_management.py} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename cogs/commands/{table_management.py => data_management.py} (92%) diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py index 6985d26..dbab5e5 100644 --- a/cogs/commands/__init__.py +++ b/cogs/commands/__init__.py @@ -7,12 +7,12 @@ from nextcord.ext import commands # Command classes from .message_management import MessageManagement from .settings_management import SettingsManagement -from .table_management import TableManagement +from .data_management import DataManagement # This function will be called when this extension is loaded. # It is necessary to add these functions to the bot. def setup(bot: commands.Bot): """Adds all command classes to bot.""" - bot.add_cog(TableManagement()) + bot.add_cog(DataManagement()) bot.add_cog(MessageManagement(bot)) bot.add_cog(SettingsManagement()) diff --git a/cogs/commands/table_management.py b/cogs/commands/data_management.py similarity index 92% rename from cogs/commands/table_management.py rename to cogs/commands/data_management.py index cf7e04e..dd42021 100644 --- a/cogs/commands/table_management.py +++ b/cogs/commands/data_management.py @@ -8,14 +8,14 @@ import nextcord from highscores import highscores_data # pylint: disable=import-error -class TableManagement(commands.Cog, name="Table Management"): +class DataManagement(commands.Cog, name="Table Management"): """ Class holds commands for managing the data storage of the highscores. """ def __init__(self): """ - Default constructor for TableManagement class. Does nothing. + Default constructor for DataManagement class. Does nothing. """ @nextcord.slash_command( From 432888e4688daaabb2f8d37880565c2dcde8513f Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Fri, 12 Aug 2022 22:28:14 -0400 Subject: [PATCH 06/34] Tag user on submission verify. --- cogs/submission/submission_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/submission/submission_views.py b/cogs/submission/submission_views.py index 35a4989..a4315ff 100644 --- a/cogs/submission/submission_views.py +++ b/cogs/submission/submission_views.py @@ -126,7 +126,7 @@ class SubmitButton(nextcord.ui.Button): files=[await attch.to_file() for attch in interaction.message.attachments], view=VerificationView(), embed=embed, - content=f"Submission from {interaction.user.display_name}", + content=f"Submission from <@{interaction.user.id}>", ) await interaction.message.edit(content=f"<@{interaction.user.id}> Submitted!", view=None) submission_objects.remove_user_submission(interaction.user.id) From ca58e9ac26e97154ff628709e2e9739d326462e1 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Fri, 12 Aug 2022 22:38:24 -0400 Subject: [PATCH 07/34] Create initial update_username command. --- cogs/commands/data_management.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cogs/commands/data_management.py b/cogs/commands/data_management.py index dd42021..9d28f37 100644 --- a/cogs/commands/data_management.py +++ b/cogs/commands/data_management.py @@ -43,3 +43,33 @@ class DataManagement(commands.Cog, name="Table Management"): await interaction.response.send_message( "You shouldn't be using this message.", ephemeral=True ) + + @nextcord.slash_command(description="Request a username update. Admins must approve.") + async def update_username( + self, + interaction: nextcord.Interaction, + old_username: str = nextcord.SlashOption( + description="Username currently stored in highscores.", required=True + ), + new_username: str = nextcord.SlashOption( + description="Username to update to.", required=True + ), + ): + """ + _summary_ + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command + old_username (str): Username to replace. + new_username (str): Username to replace old with. + """ + guild = highscores_data.get_guild(interaction.guild_id) + verify_channel = interaction.guild.get_channel(guild.verification_channel) + + embed: nextcord.Embed = nextcord.Embed(title="Username change request.") + embed.add_field(name="Old username", value=old_username) + embed.add_field(name="New username", value=new_username) + await verify_channel.send( + content=f"Username change request from <@{interaction.user.id}>", embed=embed + ) + await interaction.send("Request sent to admins for verification.", ephemeral=True) From ae9153b78e0b25c47c06c7f7ea9c6837da1a17c5 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Fri, 12 Aug 2022 22:49:54 -0400 Subject: [PATCH 08/34] Change submission views to be more informative. --- cogs/submission/submission_views.py | 2 +- cogs/submission/verification_view.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cogs/submission/submission_views.py b/cogs/submission/submission_views.py index a4315ff..2398393 100644 --- a/cogs/submission/submission_views.py +++ b/cogs/submission/submission_views.py @@ -126,7 +126,7 @@ class SubmitButton(nextcord.ui.Button): files=[await attch.to_file() for attch in interaction.message.attachments], view=VerificationView(), embed=embed, - content=f"Submission from <@{interaction.user.id}>", + content=f"Submission from <@{interaction.user.id}>.", ) await interaction.message.edit(content=f"<@{interaction.user.id}> Submitted!", view=None) submission_objects.remove_user_submission(interaction.user.id) diff --git a/cogs/submission/verification_view.py b/cogs/submission/verification_view.py index 3568706..c073490 100644 --- a/cogs/submission/verification_view.py +++ b/cogs/submission/verification_view.py @@ -59,7 +59,9 @@ class VerificationView(nextcord.ui.View): submission_category, submission_score, ) - await interaction.message.edit(f"Approved by <@{interaction.user.id}>.", view=None) + content = interaction.message.content + content += f"\nApproved by <@{interaction.user.id}>." + await interaction.message.edit(content=content, view=None) except UnboundLocalError: await interaction.send("Instruct user to submit with all fields.") await interaction.message.edit(view=None) From d4e19284f0d3fd4254a2d6635fadd32cbc5f550f Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Fri, 12 Aug 2022 22:55:19 -0400 Subject: [PATCH 09/34] Create view for verifyng username update request. --- cogs/commands/data_management.py | 6 +- cogs/commands/views/update_username_verify.py | 71 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 cogs/commands/views/update_username_verify.py diff --git a/cogs/commands/data_management.py b/cogs/commands/data_management.py index 9d28f37..8aec22a 100644 --- a/cogs/commands/data_management.py +++ b/cogs/commands/data_management.py @@ -67,9 +67,9 @@ class DataManagement(commands.Cog, name="Table Management"): verify_channel = interaction.guild.get_channel(guild.verification_channel) embed: nextcord.Embed = nextcord.Embed(title="Username change request.") - embed.add_field(name="Old username", value=old_username) - embed.add_field(name="New username", value=new_username) + embed.add_field(name="Old Username", value=old_username) + embed.add_field(name="New Username", value=new_username) await verify_channel.send( - content=f"Username change request from <@{interaction.user.id}>", embed=embed + content=f"Username change request from <@{interaction.user.id}>.", embed=embed ) await interaction.send("Request sent to admins for verification.", ephemeral=True) diff --git a/cogs/commands/views/update_username_verify.py b/cogs/commands/views/update_username_verify.py new file mode 100644 index 0000000..faa7f16 --- /dev/null +++ b/cogs/commands/views/update_username_verify.py @@ -0,0 +1,71 @@ +"""View for allowing a username update.""" + +import nextcord + +from highscores import highscores_data # pylint: disable=import-error +from utilities.utils import send_highscore_message # pylint: disable=import-error + + +class UpdateUsernameVerifyView(nextcord.ui.View): + """Defines view for allowing a username update.""" + + def __init__(self): + """ + Default constructor for UpdateUsernameVerifyView. Calls super constructor with no timeout. + """ + super().__init__(timeout=None) + + @nextcord.ui.button( + label="Deny", style=nextcord.ButtonStyle.red, custom_id="deny-username-update" + ) + async def deny_button( + self, button: nextcord.ui.Button, interaction: nextcord.Interaction + ): # pylint: disable=unused-argument + """ + Button for denying username update. Deletes message. + + Args: + button (nextcord.ui.Button): unused + interaction (nextcord.Interaction): Interaction with the button + """ + await interaction.message.delete() + + @nextcord.ui.button( + label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve-username-update" + ) + async def approve_button( + self, button: nextcord.ui.Button, interaction: nextcord.Interaction + ): # pylint: disable=unused-argument + """ + Button for approving username update. Tells guild data to update username, then updates + message to show it is complete. + + Args: + button (nextcord.ui.Button): unused + interaction (nextcord.Interaction): Interaction with the button + """ + embed_content = interaction.message.embeds[0].to_dict() + old_username: str = embed_content["Old Username"] + new_username: str = embed_content["New Username"] + + guild = highscores_data.get_guild(interaction.guild_id) + + content = interaction.message.content + content += f"\nApproved by <@{interaction.user.id}>." + await interaction.edit(content=content) + + tables = guild.update_username(old_username, new_username) + + # if no tables updated, send informing message and return. + if len(tables) == 0: + await interaction.send( + f'Username "{old_username}" not found. Have user try again with valid username.', + ephemeral=True, + ) + return + + # update all table messages + for table in tables: + await send_highscore_message( + interaction.guild.get_channel(guild.highscore_channel), guild, table + ) From 080356fe9c9531d9cee528de3dda05a983694723 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 19:48:29 -0400 Subject: [PATCH 10/34] Create Guild.update_username(). --- classes/guild.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/classes/guild.py b/classes/guild.py index 30c1177..da7ad63 100644 --- a/classes/guild.py +++ b/classes/guild.py @@ -209,3 +209,23 @@ class Guild: str: string of json representation of Guild """ return json.dumps(self.to_dict(), indent=4) + + def update_username(self, old_username: str, new_username: str) -> "list[Table]": + """ + Collects list of tables that had a username updated. Sends old and new username + to the table function to update username. + + Args: + old_username (str): Username to replace. Sent to table function + new_username (str): Username to replace with. Sent to table function + + Returns: + list[Table]: All the tables that had a username updated + """ + updated_tables: "list[Table]" = [] + + for table in self.tables: + if table.update_username(old_username, new_username): + updated_tables.append(table) + + return updated_tables From cb71cbe1d55054fa827bc706b8829ac54cc4e542 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 20:08:57 -0400 Subject: [PATCH 11/34] Create update_username functions for category and table. --- classes/category.py | 24 ++++++++++++++++++++++++ classes/guild.py | 4 ++-- classes/table.py | 21 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/classes/category.py b/classes/category.py index e442f8b..dd34dcd 100644 --- a/classes/category.py +++ b/classes/category.py @@ -188,3 +188,27 @@ class Category: str: string of json representation of Category """ return json.dumps(self.to_dict(), indent=4) + + def update_username(self, old_username: str, new_username: str) -> bool: + """ + Checks score map for old username. Pops old username to remove from map. + Updates score's username and calls self.add_score with new score to add back + to map and sort. + + Args: + old_username (str): Username to replace + new_username (str): Username to replace with + + Returns: + bool: True indicating a score was updated, False otherwise + """ + + ret_val: bool = False + + if old_username in self._scores_map: + ret_val = True + score: Score = self._scores_map.pop(old_username) + score.user = new_username + self.add_score(score) + + return ret_val diff --git a/classes/guild.py b/classes/guild.py index da7ad63..dc7ede4 100644 --- a/classes/guild.py +++ b/classes/guild.py @@ -216,8 +216,8 @@ class Guild: to the table function to update username. Args: - old_username (str): Username to replace. Sent to table function - new_username (str): Username to replace with. Sent to table function + old_username (str): Username to replace. Sent to Table function + new_username (str): Username to replace with. Sent to Table function Returns: list[Table]: All the tables that had a username updated diff --git a/classes/table.py b/classes/table.py index 0f2af7e..3d3cc22 100644 --- a/classes/table.py +++ b/classes/table.py @@ -162,3 +162,24 @@ class Table: str: string of json representation of Table """ return json.dumps(self.to_dict(), indent=4) + + def update_username(self, old_username: str, new_username: str) -> bool: + """ + Calls Category update_username function for each category. Returns true if a category + was updated to inform if the table message needs rewritten. + + Args: + old_username (str): Username to replace. Sent to Category function + new_username (str): Username to replace with. Sent to Category function + + Returns: + bool: True if category was updated and requires message update + """ + + ret_val: bool = False + + for category in self.categories: + if category.update_username(old_username, new_username): + ret_val &= True + + return ret_val From 6147263647f48db672fac154e29f018f660ab280 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 20:23:56 -0400 Subject: [PATCH 12/34] Add UpdateUsernameVerify view to MessageManagementCog. --- cogs/commands/__init__.py | 21 ++++++++++++------- cogs/commands/data_management.py | 19 +++++++++++++---- cogs/commands/message_management.py | 2 +- cogs/commands/settings_management.py | 2 +- cogs/commands/views/update_username_verify.py | 4 ++-- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py index dbab5e5..d383dad 100644 --- a/cogs/commands/__init__.py +++ b/cogs/commands/__init__.py @@ -1,18 +1,23 @@ """ -Cog loader. Adds all command classes as cogs to the bot. +Cog loader. Adds all command cogs to the bot. """ from nextcord.ext import commands # Command classes -from .message_management import MessageManagement -from .settings_management import SettingsManagement -from .data_management import DataManagement +from .message_management import MessageManagementCog +from .settings_management import SettingsManagementCog +from .data_management import DataManagementCog + +# Views for commands +from .views.update_username_verify import UpdateUsernameVerifyView # This function will be called when this extension is loaded. # It is necessary to add these functions to the bot. def setup(bot: commands.Bot): - """Adds all command classes to bot.""" - bot.add_cog(DataManagement()) - bot.add_cog(MessageManagement(bot)) - bot.add_cog(SettingsManagement()) + """ + Adds all command classes to bot. + """ + bot.add_cog(DataManagementCog(bot)) + bot.add_cog(MessageManagementCog(bot)) + bot.add_cog(SettingsManagementCog()) diff --git a/cogs/commands/data_management.py b/cogs/commands/data_management.py index 8aec22a..5d9be3b 100644 --- a/cogs/commands/data_management.py +++ b/cogs/commands/data_management.py @@ -6,17 +6,26 @@ from nextcord.ext import commands import nextcord from highscores import highscores_data # pylint: disable=import-error +from .views.update_username_verify import UpdateUsernameVerifyView -class DataManagement(commands.Cog, name="Table Management"): +class DataManagementCog(commands.Cog, name="Table Management"): """ Class holds commands for managing the data storage of the highscores. """ - def __init__(self): + def __init__(self, bot: commands.Bot): """ - Default constructor for DataManagement class. Does nothing. + Default constructor for DataManagement class. Sets internal bot variable. """ + self._bot: commands.Bot = bot + + @commands.Cog.listener() + async def on_ready(self): + """ + Called when cog is loaded. Adds views required for DataManagement commands. + """ + self._bot.add_view(UpdateUsernameVerifyView()) @nextcord.slash_command( description="Deletes all scores for this guild. Used for testing.", @@ -70,6 +79,8 @@ class DataManagement(commands.Cog, name="Table Management"): embed.add_field(name="Old Username", value=old_username) embed.add_field(name="New Username", value=new_username) await verify_channel.send( - content=f"Username change request from <@{interaction.user.id}>.", embed=embed + content=f"Username change request from <@{interaction.user.id}>.", + embed=embed, + view=UpdateUsernameVerifyView(), ) await interaction.send("Request sent to admins for verification.", ephemeral=True) diff --git a/cogs/commands/message_management.py b/cogs/commands/message_management.py index c1dde49..fa4f7db 100644 --- a/cogs/commands/message_management.py +++ b/cogs/commands/message_management.py @@ -10,7 +10,7 @@ from highscores import highscores_data # pylint: disable=import-error from utilities.utils import send_highscore_message # pylint: disable=import-error -class MessageManagement(commands.Cog, name="Message Management"): +class MessageManagementCog(commands.Cog, name="Message Management"): """ Class holds commands for managing the messages that display highscores. """ diff --git a/cogs/commands/settings_management.py b/cogs/commands/settings_management.py index 92efa06..6d61a80 100644 --- a/cogs/commands/settings_management.py +++ b/cogs/commands/settings_management.py @@ -8,7 +8,7 @@ import nextcord from highscores import highscores_data # pylint: disable=import-error -class SettingsManagement(commands.Cog, name="Settings Management"): +class SettingsManagementCog(commands.Cog, name="Settings Management"): """ Class holds commands for managing Guild's settings. """ diff --git a/cogs/commands/views/update_username_verify.py b/cogs/commands/views/update_username_verify.py index faa7f16..0c3a94c 100644 --- a/cogs/commands/views/update_username_verify.py +++ b/cogs/commands/views/update_username_verify.py @@ -45,8 +45,8 @@ class UpdateUsernameVerifyView(nextcord.ui.View): interaction (nextcord.Interaction): Interaction with the button """ embed_content = interaction.message.embeds[0].to_dict() - old_username: str = embed_content["Old Username"] - new_username: str = embed_content["New Username"] + old_username: str = embed_content[0].value() + new_username: str = embed_content[1].value() guild = highscores_data.get_guild(interaction.guild_id) From 2407e6191325fe84e2d8b9178bccc93cb6921225 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 20:30:03 -0400 Subject: [PATCH 13/34] Correcting embed access. --- cogs/commands/views/update_username_verify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cogs/commands/views/update_username_verify.py b/cogs/commands/views/update_username_verify.py index 0c3a94c..4d07442 100644 --- a/cogs/commands/views/update_username_verify.py +++ b/cogs/commands/views/update_username_verify.py @@ -45,8 +45,8 @@ class UpdateUsernameVerifyView(nextcord.ui.View): interaction (nextcord.Interaction): Interaction with the button """ embed_content = interaction.message.embeds[0].to_dict() - old_username: str = embed_content[0].value() - new_username: str = embed_content[1].value() + old_username: str = embed_content["fields"][0]["value"] + new_username: str = embed_content["fields"][1]["value"] guild = highscores_data.get_guild(interaction.guild_id) From 7fb87ae12fbd07b46881d368177875c40955fb14 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 20:30:21 -0400 Subject: [PATCH 14/34] Add username setter. --- classes/score.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/classes/score.py b/classes/score.py index 1f184f8..c6f4344 100644 --- a/classes/score.py +++ b/classes/score.py @@ -38,6 +38,16 @@ class Score: """ return self._user + @user.setter + def user(self, username: str) -> None: + """ + Setter for score's username. + + Args: + username (str): username to set to + """ + self._user = username + @property def value(self) -> int: """ From 790cff3406644e0b2e85ef9699c7cda245acb06d Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 20:33:14 -0400 Subject: [PATCH 15/34] Clear view on approval. --- cogs/commands/views/update_username_verify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/commands/views/update_username_verify.py b/cogs/commands/views/update_username_verify.py index 4d07442..db93818 100644 --- a/cogs/commands/views/update_username_verify.py +++ b/cogs/commands/views/update_username_verify.py @@ -52,7 +52,7 @@ class UpdateUsernameVerifyView(nextcord.ui.View): content = interaction.message.content content += f"\nApproved by <@{interaction.user.id}>." - await interaction.edit(content=content) + await interaction.edit(content=content, view=None) tables = guild.update_username(old_username, new_username) From 41ba4dc23c16760260ebaeae8874bade0d0e4e89 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 20:40:06 -0400 Subject: [PATCH 16/34] Save data after modified. --- cogs/commands/views/update_username_verify.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cogs/commands/views/update_username_verify.py b/cogs/commands/views/update_username_verify.py index db93818..4b0eb7f 100644 --- a/cogs/commands/views/update_username_verify.py +++ b/cogs/commands/views/update_username_verify.py @@ -69,3 +69,5 @@ class UpdateUsernameVerifyView(nextcord.ui.View): await send_highscore_message( interaction.guild.get_channel(guild.highscore_channel), guild, table ) + + highscores_data.save_guild_data(guild.guild_id) From 9dec15dbfada8c47ca34aa337c087794d31ca605 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 20:45:29 -0400 Subject: [PATCH 17/34] Correct table update_username. --- classes/table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/table.py b/classes/table.py index 3d3cc22..7b81571 100644 --- a/classes/table.py +++ b/classes/table.py @@ -180,6 +180,6 @@ class Table: for category in self.categories: if category.update_username(old_username, new_username): - ret_val &= True + ret_val = True return ret_val From 73e350c8458447cf87e07198476782066d2d81c7 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 20:46:59 -0400 Subject: [PATCH 18/34] Save takes guild object, not id. --- cogs/commands/views/update_username_verify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/commands/views/update_username_verify.py b/cogs/commands/views/update_username_verify.py index 4b0eb7f..edfe2ed 100644 --- a/cogs/commands/views/update_username_verify.py +++ b/cogs/commands/views/update_username_verify.py @@ -70,4 +70,4 @@ class UpdateUsernameVerifyView(nextcord.ui.View): interaction.guild.get_channel(guild.highscore_channel), guild, table ) - highscores_data.save_guild_data(guild.guild_id) + highscores_data.save_guild_data(guild) From 652533d071f3050ca542dcec0ea37a5ccb3bd02b Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 21:23:09 -0400 Subject: [PATCH 19/34] Should have handled username updating code in the command, didn't need to create new functions for the command. --- classes/category.py | 36 +++++-------------- classes/guild.py | 20 ----------- classes/table.py | 21 ----------- cogs/commands/data_management.py | 3 +- cogs/commands/views/update_username_verify.py | 28 +++++++-------- 5 files changed, 24 insertions(+), 84 deletions(-) diff --git a/classes/category.py b/classes/category.py index dd34dcd..a81b608 100644 --- a/classes/category.py +++ b/classes/category.py @@ -153,15 +153,19 @@ class Category: return self._scores_map[user] return None - def remove_score(self, score: str) -> None: + def remove_score(self, user: str) -> Score: """ Removes a user's score from dictionary storage. Args: - score (str): user whose score to remove + user (str): user whose score to remove + + Returns: + Score: User's Score or None """ - score = self._scores_map.pop(score) - del score + if user in self._scores_map: + return self._scores_map.pop(user) + return None def to_dict(self) -> dict: """ @@ -188,27 +192,3 @@ class Category: str: string of json representation of Category """ return json.dumps(self.to_dict(), indent=4) - - def update_username(self, old_username: str, new_username: str) -> bool: - """ - Checks score map for old username. Pops old username to remove from map. - Updates score's username and calls self.add_score with new score to add back - to map and sort. - - Args: - old_username (str): Username to replace - new_username (str): Username to replace with - - Returns: - bool: True indicating a score was updated, False otherwise - """ - - ret_val: bool = False - - if old_username in self._scores_map: - ret_val = True - score: Score = self._scores_map.pop(old_username) - score.user = new_username - self.add_score(score) - - return ret_val diff --git a/classes/guild.py b/classes/guild.py index dc7ede4..30c1177 100644 --- a/classes/guild.py +++ b/classes/guild.py @@ -209,23 +209,3 @@ class Guild: str: string of json representation of Guild """ return json.dumps(self.to_dict(), indent=4) - - def update_username(self, old_username: str, new_username: str) -> "list[Table]": - """ - Collects list of tables that had a username updated. Sends old and new username - to the table function to update username. - - Args: - old_username (str): Username to replace. Sent to Table function - new_username (str): Username to replace with. Sent to Table function - - Returns: - list[Table]: All the tables that had a username updated - """ - updated_tables: "list[Table]" = [] - - for table in self.tables: - if table.update_username(old_username, new_username): - updated_tables.append(table) - - return updated_tables diff --git a/classes/table.py b/classes/table.py index 7b81571..0f2af7e 100644 --- a/classes/table.py +++ b/classes/table.py @@ -162,24 +162,3 @@ class Table: str: string of json representation of Table """ return json.dumps(self.to_dict(), indent=4) - - def update_username(self, old_username: str, new_username: str) -> bool: - """ - Calls Category update_username function for each category. Returns true if a category - was updated to inform if the table message needs rewritten. - - Args: - old_username (str): Username to replace. Sent to Category function - new_username (str): Username to replace with. Sent to Category function - - Returns: - bool: True if category was updated and requires message update - """ - - ret_val: bool = False - - for category in self.categories: - if category.update_username(old_username, new_username): - ret_val = True - - return ret_val diff --git a/cogs/commands/data_management.py b/cogs/commands/data_management.py index 5d9be3b..8ab86d7 100644 --- a/cogs/commands/data_management.py +++ b/cogs/commands/data_management.py @@ -65,7 +65,8 @@ class DataManagementCog(commands.Cog, name="Table Management"): ), ): """ - _summary_ + Creates a request to send to admins for changing a username. Uses + UpdateUsernameVerifyView to do the work. Args: interaction (nextcord.Interaction): Interaction that invoked the command diff --git a/cogs/commands/views/update_username_verify.py b/cogs/commands/views/update_username_verify.py index edfe2ed..b6ffca9 100644 --- a/cogs/commands/views/update_username_verify.py +++ b/cogs/commands/views/update_username_verify.py @@ -54,20 +54,20 @@ class UpdateUsernameVerifyView(nextcord.ui.View): content += f"\nApproved by <@{interaction.user.id}>." await interaction.edit(content=content, view=None) - tables = guild.update_username(old_username, new_username) + # update username in all tables and categories + for table in guild.tables: + table_updated: bool = False + for category in table.categories: + score = category.remove_score(old_username) + if score: + # score exists + score.user = new_username + category.add_score(score) + table_updated = True - # if no tables updated, send informing message and return. - if len(tables) == 0: - await interaction.send( - f'Username "{old_username}" not found. Have user try again with valid username.', - ephemeral=True, - ) - return - - # update all table messages - for table in tables: - await send_highscore_message( - interaction.guild.get_channel(guild.highscore_channel), guild, table - ) + if table_updated: + await send_highscore_message( + interaction.guild.get_channel(guild.highscore_channel), guild, table + ) highscores_data.save_guild_data(guild) From d2f33959ed475414fb4ee3b31fa957ca84059217 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 13 Aug 2022 21:46:41 -0400 Subject: [PATCH 20/34] Create remove_user command. --- cogs/commands/data_management.py | 45 +++++++++++++++++++ cogs/commands/views/update_username_verify.py | 11 ++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/cogs/commands/data_management.py b/cogs/commands/data_management.py index 8ab86d7..d63222b 100644 --- a/cogs/commands/data_management.py +++ b/cogs/commands/data_management.py @@ -6,6 +6,8 @@ from nextcord.ext import commands import nextcord from highscores import highscores_data # pylint: disable=import-error +from utilities.utils import send_highscore_message # pylint: disable=import-error + from .views.update_username_verify import UpdateUsernameVerifyView @@ -53,6 +55,49 @@ class DataManagementCog(commands.Cog, name="Table Management"): "You shouldn't be using this message.", ephemeral=True ) + @nextcord.slash_command( + description="Removes all of a user's scores. Warning: data is destroyed, use with case.", + default_member_permissions=nextcord.Permissions(administrator=True), + ) + async def remove_user( + self, + interaction: nextcord.Interaction, + username: str = nextcord.SlashOption( + description="User to remove scores for. Note: This is not always a user's discord name." + ), + ): + """ + 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 + username (str): User to remove from highscores + """ + guild = highscores_data.get_guild(interaction.guild_id) + + user_was_found: bool = False + + # remove user from all tables and categories + for table in guild.tables: + table_updated: bool = False + for category in table.categories: + # user has a score in category + if category.remove_score(username): + user_was_found = True + table_updated = True + + if table_updated: + await send_highscore_message( + interaction.guild.get_channel(guild.highscore_channel), guild, table + ) + + if not user_was_found: + await interaction.send(f'User "{username}" was not found.', ephemeral=True) + else: + await interaction.send(f'User "{username}" was removed from all tables.') + highscores_data.save_guild_data(guild) + @nextcord.slash_command(description="Request a username update. Admins must approve.") async def update_username( self, diff --git a/cogs/commands/views/update_username_verify.py b/cogs/commands/views/update_username_verify.py index b6ffca9..4695865 100644 --- a/cogs/commands/views/update_username_verify.py +++ b/cogs/commands/views/update_username_verify.py @@ -54,6 +54,8 @@ class UpdateUsernameVerifyView(nextcord.ui.View): content += f"\nApproved by <@{interaction.user.id}>." await interaction.edit(content=content, view=None) + user_was_found: bool = False + # update username in all tables and categories for table in guild.tables: table_updated: bool = False @@ -64,10 +66,17 @@ class UpdateUsernameVerifyView(nextcord.ui.View): score.user = new_username category.add_score(score) table_updated = True + user_was_found = True if table_updated: await send_highscore_message( interaction.guild.get_channel(guild.highscore_channel), guild, table ) - highscores_data.save_guild_data(guild) + if not user_was_found: + await interaction.send( + f'User "{old_username}" was not found. Inform user to correct username.', + ephemeral=True, + ) + else: + highscores_data.save_guild_data(guild) From b4f5ddc822f1d9b73e18218e5118e0908614f042 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Sat, 12 Nov 2022 01:16:04 -0500 Subject: [PATCH 21/34] Update to nextcord 2.3.2. --- cogs/commands/settings_management.py | 8 +++++++- requirements.txt | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cogs/commands/settings_management.py b/cogs/commands/settings_management.py index 6d61a80..5ffe314 100644 --- a/cogs/commands/settings_management.py +++ b/cogs/commands/settings_management.py @@ -19,7 +19,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): """ @nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True)) - async def register(self): + async def register(self, interaction: nextcord.Interaction): """ Parent slash command, never called. """ @@ -104,3 +104,9 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): await interaction.send(f"Registered <#{channel_id}>.", ephemeral=True) guild.verification_channel = channel_id highscores_data.save_guild_data(guild) + + # @nextcord.slash_command() + # async def set(self): + # """ + # Parent slash command for setting settings. Never called. + # """ \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d4fcfe7..412a87a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -nextcord>=2.0.0rc1 -python-dotenv>=0.20.0 \ No newline at end of file +nextcord>=2.3.2 +python-dotenv>=0.20.0 From dd73e9b472a17699139058af18b7e4bb95f4f1e6 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Tue, 15 Nov 2022 15:48:17 -0500 Subject: [PATCH 22/34] Add commands to edit username length and category pad. --- classes/guild.py | 25 ++++++++++- cogs/commands/settings_management.py | 65 ++++++++++++++++++++++++---- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/classes/guild.py b/classes/guild.py index 30c1177..522fa06 100644 --- a/classes/guild.py +++ b/classes/guild.py @@ -16,6 +16,7 @@ class Guild: Args: guild_id (int): Discord Guild id of new Guild """ + self._category_pad_length: int = 25 self._guild_id: int = guild_id self._highscore_channel_id: int = 0 self._submission_channel_id: int = 0 @@ -45,6 +46,7 @@ class Guild: Guild: Populated Guild object """ guild = cls(guild_id) + guild.category_pad_length = data["category_pad_length"] guild.highscore_channel = data["highscore_channel_id"] guild.submission_channel = data["submission_channel_id"] guild.username_length = data["username_length"] @@ -56,10 +58,30 @@ class Guild: return guild + @property + def category_pad_length(self) -> int: + """ + Getter for Category Pad Length. + + Returns: + int: Length to pad each category entry. + """ + return self._category_pad_length + + @category_pad_length.setter + def category_pad_length(self, length: int) -> None: + """ + Setter for Category Pad Length. + + Args: + length (int): New length to set. + """ + self._category_pad_length = length + @property def guild_id(self) -> int: """ - Getter for Guild id + Getter for Guild id. Returns: int: Guild id @@ -190,6 +212,7 @@ class Guild: """ data: dict = {} + data["category_pad_length"] = self.category_pad_length data["highscore_channel_id"] = self.highscore_channel data["submission_channel_id"] = self.submission_channel data["tables"] = {} diff --git a/cogs/commands/settings_management.py b/cogs/commands/settings_management.py index 5ffe314..f18e602 100644 --- a/cogs/commands/settings_management.py +++ b/cogs/commands/settings_management.py @@ -2,8 +2,10 @@ Commands for managing the Guild's settings. """ -from nextcord.ext import commands +from typing import Optional + import nextcord +from nextcord.ext import commands from highscores import highscores_data # pylint: disable=import-error @@ -28,7 +30,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): async def highscore( self, interaction: nextcord.Interaction, - channel: nextcord.abc.GuildChannel = nextcord.SlashOption( + channel: Optional[nextcord.abc.GuildChannel] = nextcord.SlashOption( description="Channel to register", required=False, channel_types=[nextcord.ChannelType.text], @@ -55,7 +57,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): async def submission( self, interaction: nextcord.Interaction, - channel: nextcord.abc.GuildChannel = nextcord.SlashOption( + channel: Optional[nextcord.abc.GuildChannel] = nextcord.SlashOption( description="Channel to register", required=False, channel_types=[nextcord.ChannelType.text], @@ -82,7 +84,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): async def verify( self, interaction: nextcord.Interaction, - channel: nextcord.abc.GuildChannel = nextcord.SlashOption( + channel: Optional[nextcord.abc.GuildChannel] = nextcord.SlashOption( description="Channel to register", required=False, channel_types=[nextcord.ChannelType.text], @@ -105,8 +107,53 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): guild.verification_channel = channel_id highscores_data.save_guild_data(guild) - # @nextcord.slash_command() - # async def set(self): - # """ - # Parent slash command for setting settings. Never called. - # """ \ No newline at end of file + @nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True)) + async def set(self, interaction: nextcord.Interaction): + """ + Parent slash command for setting settings. Never called. + """ + + @set.subcommand( + description="Set character length for a category. Can cut off usernames if is too short. Affects all categories." + ) + async def category_pad_length( + self, + interaction: nextcord.Interaction, + length: Optional[int] = nextcord.SlashOption( + description="Number of characters.", required=True, default=25 + ), + ) -> None: + """ + Changes guild's category pad length. + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command + length (int): Number of characters. Is SlashOption. Defaults to 25. + """ + + guild = highscores_data.get_guild(interaction.guild_id) + guild.category_pad_length = length + highscores_data.save_guild_data(guild) + await interaction.response.send_message( + f"Category pad length set to {length}", ephemeral=True + ) + + @set.subcommand(description="Set username length. Limits all submissions to this length.") + async def username_length( + self, + interaction: nextcord.Interaction, + length: Optional[int] = nextcord.SlashOption( + description="Number of characters.", required=True, default=12 + ), + ) -> None: + """ + Changes guild's username length limit. + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command + length (int): Number of charactesr. Is SlashOption. Defaults to 12. + """ + guild = highscores_data.get_guild(interaction.guild_id) + guild.username_length = length + highscores_data.save_guild_data(guild) + await interaction.response.send_message(f"Username length set to {length}", ephemeral=True) From 09f7a4323c9242ce5e93e681ceaa86d4af2ebf00 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Tue, 15 Nov 2022 15:50:12 -0500 Subject: [PATCH 23/34] Correct category pad default. --- cogs/commands/settings_management.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cogs/commands/settings_management.py b/cogs/commands/settings_management.py index f18e602..8ccd8ad 100644 --- a/cogs/commands/settings_management.py +++ b/cogs/commands/settings_management.py @@ -120,7 +120,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): self, interaction: nextcord.Interaction, length: Optional[int] = nextcord.SlashOption( - description="Number of characters.", required=True, default=25 + description="Number of characters.", required=True, default=26 ), ) -> None: """ @@ -128,7 +128,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): Args: interaction (nextcord.Interaction): Interaction that invoked the command - length (int): Number of characters. Is SlashOption. Defaults to 25. + length (int): Number of characters. Is SlashOption. Defaults to 26. """ guild = highscores_data.get_guild(interaction.guild_id) From 2e324dc9c5a73223e54f7d4b2fad19d7c3c9ea23 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Tue, 15 Nov 2022 15:52:58 -0500 Subject: [PATCH 24/34] Use the new category pad length variable. --- cogs/commands/settings_management.py | 2 +- utilities/utils.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cogs/commands/settings_management.py b/cogs/commands/settings_management.py index 8ccd8ad..6293dcf 100644 --- a/cogs/commands/settings_management.py +++ b/cogs/commands/settings_management.py @@ -114,7 +114,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): """ @set.subcommand( - description="Set character length for a category. Can cut off usernames if is too short. Affects all categories." + description="Set character length for a category. Can cut off username/score. Affects all categories." ) async def category_pad_length( self, diff --git a/utilities/utils.py b/utilities/utils.py index fae5544..370a6cd 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -27,7 +27,7 @@ def format_highscore_message(guild: Guild, table: Table) -> str: # Gather all categories and add to string for category in table.categories: - ret_string += f"{category.name}".ljust(26, " ") + ret_string += f"{category.name}".ljust(guild.category_pad_length, " ") ret_string += "\n" # list rankings in a single line. Start with first place for each category and then continue @@ -49,9 +49,9 @@ def format_highscore_message(guild: Guild, table: Table) -> str: else: score = score.rjust(7, " ") - ret_string += f"{i+1}: {username} - {score}".ljust(26, " ") + ret_string += f"{i+1}: {username} - {score}".ljust(guild.category_pad_length, " ") else: - ret_string += f"{i+1}: submit your score".ljust(26, " ") + ret_string += f"{i+1}:".ljust(guild.category_pad_length, " ") ret_string += "\n" return ret_string + "```" From 792a225987c0553ef916004b0e7874228a691aeb Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Tue, 15 Nov 2022 16:17:22 -0500 Subject: [PATCH 25/34] Correct docstring --- cogs/commands/settings_management.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cogs/commands/settings_management.py b/cogs/commands/settings_management.py index 6293dcf..22f251c 100644 --- a/cogs/commands/settings_management.py +++ b/cogs/commands/settings_management.py @@ -69,7 +69,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): Args: interaction (nextcord.Interaction): Interaction that invoked the command - channel (nextcord.abc.GuildChannel, optional): Channel to register + channel Optional[nextcord.abc.GuildChannel], optional): Channel to register Is a SlashOption. Limited to text channels, Defaults to None. """ if not channel: @@ -96,7 +96,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): Args: interaction (nextcord.Interaction): Interaction that invoked the command - channel (nextcord.abc.GuildChannel, optional): Channel to register + channel (Optional[nextcord.abc.GuildChannel], optional): Channel to register Is a SlashOption. Limited to text channels, Defaults to None. """ if not channel: @@ -128,7 +128,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): Args: interaction (nextcord.Interaction): Interaction that invoked the command - length (int): Number of characters. Is SlashOption. Defaults to 26. + length (Optional[int]): Number of characters. Is SlashOption. Defaults to 26. """ guild = highscores_data.get_guild(interaction.guild_id) @@ -151,7 +151,7 @@ class SettingsManagementCog(commands.Cog, name="Settings Management"): Args: interaction (nextcord.Interaction): Interaction that invoked the command - length (int): Number of charactesr. Is SlashOption. Defaults to 12. + length (Optional[int]): Number of charactesr. Is SlashOption. Defaults to 12. """ guild = highscores_data.get_guild(interaction.guild_id) guild.username_length = length From 1858f0c37d841ea05bdf81606a83c9492758e339 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Wed, 16 Nov 2022 16:28:42 -0500 Subject: [PATCH 26/34] Add refresh table command. --- classes/guild.py | 11 ++++++ cogs/commands/message_management.py | 53 ++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/classes/guild.py b/classes/guild.py index 522fa06..5b3dfdf 100644 --- a/classes/guild.py +++ b/classes/guild.py @@ -202,6 +202,17 @@ class Guild: return self._table_map[table_name] return None + def get_tables(self) -> dict: + """ + Getter for dict of table names and tables. + + Returns: + dict: Dictionary consisting of the Table Names and the Table object itself. + """ + + # I don't know if this is const data. Don't want to allow data to be written as a result of this function. + return self._table_map + def to_dict(self) -> dict: """ Creates python dictionary object from class data. diff --git a/cogs/commands/message_management.py b/cogs/commands/message_management.py index fa4f7db..d032022 100644 --- a/cogs/commands/message_management.py +++ b/cogs/commands/message_management.py @@ -2,6 +2,8 @@ Commands for managing/displaying the highscores messages. """ +from typing import Optional + from nextcord.ext import commands import nextcord @@ -69,7 +71,7 @@ class MessageManagementCog(commands.Cog, name="Message Management"): or edits the existing message. Args: - interaction (nextcord.Interaction): Interaction that invoked the command + interaction (nextcord.Interaction): Interaction that invoked the command. """ guild = highscores_data.get_guild(interaction.guild_id) print(f"Printing out highscores information in channel {guild.highscore_channel}") @@ -99,3 +101,52 @@ class MessageManagementCog(commands.Cog, name="Message Management"): highscores_data.save_guild_data(guild) await interaction.send(f"Posted all tables to <#{channel.id}>.", ephemeral=True) + + @nextcord.slash_command( + description="Manually refresh a table. Use when changing settings or data manually changed.", + default_member_permissions=nextcord.Permissions(administrator=True), + ) + async def refresh_table( + self, + interaction: nextcord.Interaction, + table_name: Optional[str] = nextcord.SlashOption( + description="Name of table.", required=True + ), + ) -> None: + """ + Refreshes a table given by user. + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command. + table (Optional[str]): Table name to refresh. Not optional. Is SlashOption. + """ + + guild = highscores_data.get_guild(interaction.guild_id) + table = guild.get_table(table_name) + channel = interaction.guild.get_channel(guild.highscore_channel) + + message: nextcord.Message = await send_highscore_message(channel, guild, table) + await message.edit(view=SubmissionCreateButton(self.bot)) + + await interaction.response.send_message(f"Updated table {table_name}!", ephemeral=True) + + @refresh_table.on_autocomplete("table_name") + async def autofill_tables(self, interaction: nextcord.Interaction, table_name: str): + """ + Collects list of table names and compares to input table name for autocomplete assistance. + + Args: + interaction (nextcord.Interaction): Interaction with command. + table (str): String to run autocomplete compared to. + """ + if not table_name: + await interaction.response.send_autocomplete( + highscores_data.get_guild(interaction.guild_id).get_tables().keys() + ) + return + near_table = [ + name + for name in highscores_data.get_guild(interaction.guild_id).get_tables().keys() + if name.lower().startswith(table_name.lower()) + ] + await interaction.response.send_autocomplete(near_table) From 06b46ef27c28ab96bf6c08d1d8253940d31b425a Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Thu, 17 Nov 2022 14:50:48 -0500 Subject: [PATCH 27/34] Create general autofill table and category util functions. Update table class to have get categories function. --- classes/table.py | 10 ++++++ cogs/commands/message_management.py | 19 ++++------- utilities/utils.py | 53 +++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/classes/table.py b/classes/table.py index 0f2af7e..e12224e 100644 --- a/classes/table.py +++ b/classes/table.py @@ -123,6 +123,16 @@ class Table: if category.name not in self._categories_map: self._categories_map[category.name] = category + def get_categories(self) -> dict: + """ + Getter for dict of category names and categories. + + Returns: + dict: Dictionary consisting of Category names and Category objects. + """ + # I don't know if this is const data. Don't want to allow data to be written as a result of this function. + return self._categories_map + def get_category(self, category_name: str) -> Category: """ Getter for specific category. Returns None if not found. diff --git a/cogs/commands/message_management.py b/cogs/commands/message_management.py index d032022..8d7259b 100644 --- a/cogs/commands/message_management.py +++ b/cogs/commands/message_management.py @@ -9,7 +9,7 @@ import nextcord from cogs.submission.submission_views import SubmissionCreateButton # pylint: disable=import-error from highscores import highscores_data # pylint: disable=import-error -from utilities.utils import send_highscore_message # pylint: disable=import-error +from utilities.utils import send_highscore_message, autofill_tables # pylint: disable=import-error class MessageManagementCog(commands.Cog, name="Message Management"): @@ -131,7 +131,7 @@ class MessageManagementCog(commands.Cog, name="Message Management"): await interaction.response.send_message(f"Updated table {table_name}!", ephemeral=True) @refresh_table.on_autocomplete("table_name") - async def autofill_tables(self, interaction: nextcord.Interaction, table_name: str): + async def refresh_table_autofill(self, interaction: nextcord.Interaction, table_name: str): """ Collects list of table names and compares to input table name for autocomplete assistance. @@ -139,14 +139,7 @@ class MessageManagementCog(commands.Cog, name="Message Management"): interaction (nextcord.Interaction): Interaction with command. table (str): String to run autocomplete compared to. """ - if not table_name: - await interaction.response.send_autocomplete( - highscores_data.get_guild(interaction.guild_id).get_tables().keys() - ) - return - near_table = [ - name - for name in highscores_data.get_guild(interaction.guild_id).get_tables().keys() - if name.lower().startswith(table_name.lower()) - ] - await interaction.response.send_autocomplete(near_table) + + await interaction.response.send_autocomplete( + autofill_tables(interaction.guild_id, table_name) + ) diff --git a/utilities/utils.py b/utilities/utils.py index 370a6cd..ae94822 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -10,6 +10,59 @@ from classes.category import Category # pylint: disable=import-error from classes.table import Table # pylint: disable=import-error +def autofill_categories(guild_id: int, table_name: str, category_begin: str) -> "list[str]": + """ + Returns all category names that start with category_begin. + If category_begin is empty, returns list of all category names. + + Args: + guild_id (int): Guild id that holds the table and categories. + table_name (str): Table name to search in. + category_begin (str): The beginning of a category name to search with. Can be empty. + + Returns: + list[str]: List of all category names that start with category_begin. + """ + # if string empty, return all category names + if not category_begin: + return highscores_data.get_guild(guild_id).get_table(table_name).get_categories().keys() + + # else find all categories that start with category_begin + categories = [ + category + for category in highscores_data.get_guild(guild_id) + .get_table(table_name) + .get_categories() + .keys() + if category.lower().startswith(category_begin.lower()) + ] + return categories + + +def autofill_tables(guild_id: int, table_begin: str) -> "list[str]": + """ + Returns all table names that start with table_begin. If table_begin is empty, returns list of all table names. + + Args: + guild_id (int): Guild id that holds the tables. + table_begin (str): The beginning of a table name to search with. Can be empty. + + Returns: + list[str]: List of table names that start with table_begin. + """ + # if string empty, return all table names + if not table_begin: + return highscores_data.get_guild(guild_id).get_tables().keys() + + # else find all tables that start with table_begin + tables = [ + table + for table in highscores_data.get_guild(guild_id).get_tables().keys() + if table.lower().startswith(table_begin.lower()) + ] + return tables + + def format_highscore_message(guild: Guild, table: Table) -> str: """ Creates a formatted string for a boss's highscore discord message. From 8155fb0c8f694c7d6275eba8b4225b6452a85011 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Tue, 22 Nov 2022 16:01:55 -0500 Subject: [PATCH 28/34] Reorganized commands to facilitate new commands. --- cogs/commands/data_management.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cogs/commands/data_management.py b/cogs/commands/data_management.py index d63222b..e93876f 100644 --- a/cogs/commands/data_management.py +++ b/cogs/commands/data_management.py @@ -29,11 +29,16 @@ class DataManagementCog(commands.Cog, name="Table Management"): """ self._bot.add_view(UpdateUsernameVerifyView()) - @nextcord.slash_command( - description="Deletes all scores for this guild. Used for testing.", - default_member_permissions=nextcord.Permissions(administrator=True), + @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( + description="Deletes all scores for this guild. Used for testing. Do Not Use." ) - async def delete_scores(self, interaction: nextcord.Interaction): + async def scores(self, interaction: nextcord.Interaction): """ Warning: This command only works for developers. @@ -52,14 +57,13 @@ class DataManagementCog(commands.Cog, name="Table Management"): ) else: await interaction.response.send_message( - "You shouldn't be using this message.", ephemeral=True + "You shouldn't be using this command. Deletes everything.", ephemeral=True ) - @nextcord.slash_command( + @delete.subcommand( description="Removes all of a user's scores. Warning: data is destroyed, use with case.", - default_member_permissions=nextcord.Permissions(administrator=True), ) - async def remove_user( + async def user( self, interaction: nextcord.Interaction, username: str = nextcord.SlashOption( From a0c5527bf63f9adc76a73f0593d345a6ad9a94d5 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Tue, 22 Nov 2022 16:26:31 -0500 Subject: [PATCH 29/34] Create autofill functions for users in category and in table. Add function in category class to return a list of all users. --- classes/category.py | 13 +++++-- utilities/utils.py | 88 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/classes/category.py b/classes/category.py index a81b608..cb00c5e 100644 --- a/classes/category.py +++ b/classes/category.py @@ -128,9 +128,7 @@ class Category: else: self._scores_map[score.user] = score - self._scores_map = dict( - sorted(self._scores_map.items(), key=lambda x: x[1].value, reverse=not self.ascending) - ) + self._scores_map = dict(sorted(self._scores_map.items(), key=lambda x: x[1].value, reverse=not self.ascending)) def clear(self) -> None: """ @@ -139,6 +137,15 @@ class Category: """ self._scores_map.clear() + def get_users(self) -> "list[str]": + """ + Returns list of users in this category. + + Returns: + list[str]: List of all users in category. + """ + return self._scores_map.keys() + def get_user_score(self, user: str) -> Score: """ Gets a user's score. Returns None if user is not found. diff --git a/utilities/utils.py b/utilities/utils.py index ae94822..4418fb1 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -23,22 +23,46 @@ def autofill_categories(guild_id: int, table_name: str, category_begin: str) -> Returns: list[str]: List of all category names that start with category_begin. """ + + categories_list = highscores_data.get_guild(guild_id).get_table(table_name).get_categories().keys() + # if string empty, return all category names if not category_begin: - return highscores_data.get_guild(guild_id).get_table(table_name).get_categories().keys() + return categories_list # else find all categories that start with category_begin - categories = [ - category - for category in highscores_data.get_guild(guild_id) - .get_table(table_name) - .get_categories() - .keys() - if category.lower().startswith(category_begin.lower()) - ] + categories = [category for category in categories_list if category.lower().startswith(category_begin.lower())] return categories +def autofill_category_users(guild_id: int, table_name: str, category_name: str, user_begin: str) -> "list[str]": + """ + Returns list of users that are in the table and category that also start with the user_begin string. + + Args: + guild_id (int): Guild id corresponding to table and category. + table_name (str): Table to look in for category. + category_name (str): Category to look in for users. + user_begin (str): Beginning of user name to search with. Can be empty. + + Returns: + list[str]: List of users that start with user_begin. + """ + + users_list: "list[str]" = ( + highscores_data.get_guild(guild_id).get_table(table_name).get_category(category_name).get_users() + ) + + # if string empty, return all users + if not user_begin: + return users_list + + # else find all users that start with user_begin + users = [user for user in users_list if user.lower().startswith(user_begin.lower())] + + return users + + def autofill_tables(guild_id: int, table_begin: str) -> "list[str]": """ Returns all table names that start with table_begin. If table_begin is empty, returns list of all table names. @@ -50,19 +74,49 @@ def autofill_tables(guild_id: int, table_begin: str) -> "list[str]": Returns: list[str]: List of table names that start with table_begin. """ + + tables_list = highscores_data.get_guild(guild_id).get_tables().keys() + # if string empty, return all table names if not table_begin: - return highscores_data.get_guild(guild_id).get_tables().keys() + return tables_list # else find all tables that start with table_begin - tables = [ - table - for table in highscores_data.get_guild(guild_id).get_tables().keys() - if table.lower().startswith(table_begin.lower()) - ] + tables = [table for table in tables_list if table.lower().startswith(table_begin.lower())] return tables +def autofill_table_users(guild_id: int, table_name: str, user_begin: str) -> "list[str]": + """ + Gathers all users in a table's categories and performs autofill function on it. This is an expensive + function. + + Args: + guild_id (int): Guild id corresponding to table and category. + table_name (str): Table to look in for categories. + user_begin (str): Beginning of user name to search with. Can be empty. + + Returns: + list[str]: List of users that start with user_begin. + """ + + user_set: set + + for category in highscores_data.get_guild(guild_id).get_table(table_name).get_categories().values(): + user_set = user_set | set(category.get_users()) + + users_list = list(user_set) + + # if string empty, return all users + if not user_begin: + return users_list + + # else find all users that start with user_begin + users = [user for user in users_list if user.lower().startswith(user_begin.lower())] + + return users + + def format_highscore_message(guild: Guild, table: Table) -> str: """ Creates a formatted string for a boss's highscore discord message. @@ -110,9 +164,7 @@ def format_highscore_message(guild: Guild, table: Table) -> str: return ret_string + "```" -async def send_highscore_message( - channel: nextcord.TextChannel, guild: Guild, table: Table -) -> nextcord.Message: +async def send_highscore_message(channel: nextcord.TextChannel, guild: Guild, table: Table) -> nextcord.Message: """ Function takes care of checking if a message exists for a table. Creates and sends/edits the message. From 03c7a9303a622b4863026e657c0413f8f2842dd1 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Tue, 22 Nov 2022 17:02:18 -0500 Subject: [PATCH 30/34] Small rewrite to consolidate autofill_user function. Sort imports in message_management.py. --- cogs/commands/message_management.py | 26 +++++++++---------------- utilities/utils.py | 30 ++++++++++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/cogs/commands/message_management.py b/cogs/commands/message_management.py index 8d7259b..5a57f7e 100644 --- a/cogs/commands/message_management.py +++ b/cogs/commands/message_management.py @@ -4,12 +4,12 @@ Commands for managing/displaying the highscores messages. from typing import Optional -from nextcord.ext import commands import nextcord +from nextcord.ext import commands from cogs.submission.submission_views import SubmissionCreateButton # pylint: disable=import-error from highscores import highscores_data # pylint: disable=import-error -from utilities.utils import send_highscore_message, autofill_tables # pylint: disable=import-error +from utilities.utils import autofill_tables, send_highscore_message # pylint: disable=import-error class MessageManagementCog(commands.Cog, name="Message Management"): @@ -40,17 +40,13 @@ class MessageManagementCog(commands.Cog, name="Message Management"): """ guild = highscores_data.get_guild(interaction.guild_id) - highscores_channel: nextcord.TextChannel = interaction.guild.get_channel( - guild.highscore_channel - ) + highscores_channel: nextcord.TextChannel = interaction.guild.get_channel(guild.highscore_channel) await interaction.response.defer(ephemeral=True) for table in guild.tables: try: - table_message: nextcord.Message = await highscores_channel.fetch_message( - table.message_id - ) + table_message: nextcord.Message = await highscores_channel.fetch_message(table.message_id) await table_message.delete() except nextcord.NotFound: # silently ignore that messages are not found. They got deleted already. @@ -85,9 +81,9 @@ class MessageManagementCog(commands.Cog, name="Message Management"): # Error checking for tables existing # TODO fill out response string once functionality created. error_response = "No tables have been created for this guild. Please create some tables." - assert ( - len(highscores_data.get_guild(interaction.guild_id).tables) > 0 - ), await interaction.send(error_response, ephemeral=True) + assert len(highscores_data.get_guild(interaction.guild_id).tables) > 0, await interaction.send( + error_response, ephemeral=True + ) # awaiting response because posting scores takes a while await interaction.response.defer(ephemeral=True) @@ -109,9 +105,7 @@ class MessageManagementCog(commands.Cog, name="Message Management"): async def refresh_table( self, interaction: nextcord.Interaction, - table_name: Optional[str] = nextcord.SlashOption( - description="Name of table.", required=True - ), + table_name: Optional[str] = nextcord.SlashOption(description="Name of table.", required=True), ) -> None: """ Refreshes a table given by user. @@ -140,6 +134,4 @@ class MessageManagementCog(commands.Cog, name="Message Management"): table (str): String to run autocomplete compared to. """ - await interaction.response.send_autocomplete( - autofill_tables(interaction.guild_id, table_name) - ) + await interaction.response.send_autocomplete(autofill_tables(interaction.guild_id, table_name)) diff --git a/utilities/utils.py b/utilities/utils.py index 4418fb1..954d224 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -3,11 +3,12 @@ Contains various utlity functions for highscores bot """ import nextcord -from highscores import highscores_data, highscores_message_map # pylint: disable=import-error -from classes.score import Score # pylint: disable=import-error -from classes.guild import Guild # pylint: disable=import-error + from classes.category import Category # pylint: disable=import-error +from classes.guild import Guild # pylint: disable=import-error +from classes.score import Score # pylint: disable=import-error from classes.table import Table # pylint: disable=import-error +from highscores import highscores_data, highscores_message_map # pylint: disable=import-error def autofill_categories(guild_id: int, table_name: str, category_begin: str) -> "list[str]": @@ -86,26 +87,33 @@ def autofill_tables(guild_id: int, table_begin: str) -> "list[str]": return tables -def autofill_table_users(guild_id: int, table_name: str, user_begin: str) -> "list[str]": +def autofill_users(guild_id: int, table_name: str, category_name: str, user_begin: str) -> "list[str]": """ - Gathers all users in a table's categories and performs autofill function on it. This is an expensive - function. + Returns list of users that match user_begin. Either all users in table or just users in category if category is + None. Might be sexpensive due to unioning all users in table. Args: guild_id (int): Guild id corresponding to table and category. - table_name (str): Table to look in for categories. + table_name (str): Table to look in for category. + category_name (str): Category to look in for users. Can be None if not looking for category. user_begin (str): Beginning of user name to search with. Can be empty. Returns: list[str]: List of users that start with user_begin. """ - user_set: set + # If no category name, get all users in table. + if category_name is None: + user_set: set - for category in highscores_data.get_guild(guild_id).get_table(table_name).get_categories().values(): - user_set = user_set | set(category.get_users()) + for category in highscores_data.get_guild(guild_id).get_table(table_name).get_categories().values(): + user_set = user_set | set(category.get_users()) - users_list = list(user_set) + users_list = list(user_set) + else: + users_list: "list[str]" = ( + highscores_data.get_guild(guild_id).get_table(table_name).get_category(category_name).get_users() + ) # if string empty, return all users if not user_begin: From 8a2108fc14fdcd4204d9f6ae8cc087f7ee22f5c0 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Tue, 22 Nov 2022 17:12:26 -0500 Subject: [PATCH 31/34] Create shell commands and autofill functions. Fix set initializer in utils.autofill_users. --- cogs/commands/data_management.py | 119 ++++++++++++++++++++++++++----- utilities/utils.py | 2 +- 2 files changed, 101 insertions(+), 20 deletions(-) diff --git a/cogs/commands/data_management.py b/cogs/commands/data_management.py index e93876f..45830d2 100644 --- a/cogs/commands/data_management.py +++ b/cogs/commands/data_management.py @@ -2,11 +2,16 @@ Commands for managing the tables/categories/scores for a Guild. """ -from nextcord.ext import commands import nextcord +from nextcord.ext import commands from highscores import highscores_data # pylint: disable=import-error -from utilities.utils import send_highscore_message # pylint: disable=import-error +from utilities.utils import ( # pylint: disable=import-error + autofill_categories, + autofill_users, + autofill_tables, + send_highscore_message, +) from .views.update_username_verify import UpdateUsernameVerifyView @@ -35,9 +40,7 @@ class DataManagementCog(commands.Cog, name="Table Management"): Parent slash command for deleting things. Never called. """ - @delete.subcommand( - description="Deletes all scores for this guild. Used for testing. Do Not Use." - ) + @delete.subcommand(description="Deletes all scores for this guild. Used for testing. Do Not Use.") async def scores(self, interaction: nextcord.Interaction): """ Warning: This command only works for developers. @@ -52,18 +55,22 @@ class DataManagementCog(commands.Cog, name="Table Management"): for category in table.categories: category.clear() highscores_data.save_guild_data(highscores_data.get_guild(interaction.guild_id)) - await interaction.response.send_message( - f"Cleared score data for {interaction.guild.name}.", ephemeral=True - ) + await interaction.response.send_message(f"Cleared score data for {interaction.guild.name}.", ephemeral=True) else: await interaction.response.send_message( "You shouldn't be using this command. Deletes everything.", ephemeral=True ) - @delete.subcommand( + @delete.subcommand() + async def user(self, interaction: nextcord.Interaction): + """ + Subcommand parent for deleting user information. Never called. + """ + + @user.subcommand( description="Removes all of a user's scores. Warning: data is destroyed, use with case.", ) - async def user( + async def from_all( self, interaction: nextcord.Interaction, username: str = nextcord.SlashOption( @@ -92,9 +99,7 @@ class DataManagementCog(commands.Cog, name="Table Management"): table_updated = True if table_updated: - await send_highscore_message( - interaction.guild.get_channel(guild.highscore_channel), guild, table - ) + await send_highscore_message(interaction.guild.get_channel(guild.highscore_channel), guild, table) if not user_was_found: await interaction.send(f'User "{username}" was not found.', ephemeral=True) @@ -102,16 +107,92 @@ class DataManagementCog(commands.Cog, name="Table Management"): await interaction.send(f'User "{username}" was removed from all tables.') highscores_data.save_guild_data(guild) + @user.subcommand(description="Deletes a user's scores from a category.") + async def from_category( + self, + interaction: nextcord.Interaction, + table_name: str = nextcord.SlashOption(description="Table to delete user from.", required=True), + category_name: str = nextcord.SlashOption(description="Category to delete user from.", required=True), + user_name: str = nextcord.SlashOption(description="User to delete.", required=True), + ): + """ + _summary_ + + Args: + interaction (nextcord.Interaction): _description_ + table_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="", required=True). + category_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="", required=True). + user_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="", required=True). + """ + + @user.subcommand(description="Deletes a user's scores from entire table.") + async def from_table( + self, + interaction: nextcord.Interaction, + table_name: str = nextcord.SlashOption(description="Table to delete user from.", required=True), + user_name: str = nextcord.SlashOption(description="User to delete.", required=True), + ): + """ + _summary_ + + Args: + interaction (nextcord.Interaction): _description_ + table_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="Table to delete user from.", required=True). + user_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="User to delete.", required=True). + """ + + @from_category.on_autocomplete("table_name") + @from_table.on_autocomplete("table_name") + async def from_category_autocomplete_table(self, interaction: nextcord.Interaction, table_name: str): + """ + _summary_ + + Args: + interaction (nextcord.Interaction): _description_ + table_name (str): _description_ + """ + await interaction.response.send_autocomplete(autofill_tables(interaction.guild_id, table_name)) + + @from_category.on_autocomplete("category_name") + async def from_category_autocomplete_category( + self, interaction: nextcord.Interaction, category_name: str, table_name: str + ): + """ + _summary_ + + Args: + interaction (nextcord.Interaction): _description_ + category_name (str): _description_ + table_name (str): _description_ + """ + await interaction.response.send_autocomplete( + autofill_categories(interaction.guild_id, table_name, category_name) + ) + + @from_category.on_autocomplete("user_name") + @from_table.on_autocomplete("user_name") + async def from_category_autocomplete_user( + self, interaction: nextcord.Interaction, user_name: str, table_name: str, category_name: str = None + ): + """ + _summary_ + + Args: + interaction (nextcord.Interaction): _description_ + user_name (str): _description_ + table_name (str): _description_ + category_name (str): _description_ + """ + await interaction.response.send_autocomplete( + autofill_users(interaction.guild_id, table_name, category_name, user_name) + ) + @nextcord.slash_command(description="Request a username update. Admins must approve.") async def update_username( self, interaction: nextcord.Interaction, - old_username: str = nextcord.SlashOption( - description="Username currently stored in highscores.", required=True - ), - new_username: str = nextcord.SlashOption( - description="Username to update to.", required=True - ), + old_username: str = nextcord.SlashOption(description="Username currently stored in highscores.", required=True), + new_username: str = nextcord.SlashOption(description="Username to update to.", required=True), ): """ Creates a request to send to admins for changing a username. Uses diff --git a/utilities/utils.py b/utilities/utils.py index 954d224..9dde5d6 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -104,7 +104,7 @@ def autofill_users(guild_id: int, table_name: str, category_name: str, user_begi # If no category name, get all users in table. if category_name is None: - user_set: set + user_set: set = set() for category in highscores_data.get_guild(guild_id).get_table(table_name).get_categories().values(): user_set = user_set | set(category.get_users()) From 9ebbca2557d4d41ef367442cea4751f706fa87b0 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Thu, 24 Nov 2022 17:05:45 -0500 Subject: [PATCH 32/34] Fill out docstrings. --- classes/guild.py | 2 +- cogs/commands/data_management.py | 60 +++++++++++++++-------------- cogs/commands/message_management.py | 2 +- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/classes/guild.py b/classes/guild.py index 5b3dfdf..bcdd4fe 100644 --- a/classes/guild.py +++ b/classes/guild.py @@ -16,7 +16,7 @@ class Guild: Args: guild_id (int): Discord Guild id of new Guild """ - self._category_pad_length: int = 25 + self._category_pad_length: int = 26 self._guild_id: int = guild_id self._highscore_channel_id: int = 0 self._submission_channel_id: int = 0 diff --git a/cogs/commands/data_management.py b/cogs/commands/data_management.py index 45830d2..13b29bc 100644 --- a/cogs/commands/data_management.py +++ b/cogs/commands/data_management.py @@ -2,14 +2,16 @@ Commands for managing the tables/categories/scores for a Guild. """ +from typing import Optional + import nextcord from nextcord.ext import commands from highscores import highscores_data # pylint: disable=import-error from utilities.utils import ( # pylint: disable=import-error autofill_categories, - autofill_users, autofill_tables, + autofill_users, send_highscore_message, ) @@ -82,7 +84,7 @@ class DataManagementCog(commands.Cog, name="Table Management"): message is user was removed from that table. Args: - interaction (nextcord.Interaction): Interaction that invoked the command + interaction (nextcord.Interaction): Interaction that invoked the command. username (str): User to remove from highscores """ guild = highscores_data.get_guild(interaction.guild_id) @@ -116,14 +118,15 @@ class DataManagementCog(commands.Cog, name="Table Management"): user_name: str = nextcord.SlashOption(description="User to delete.", required=True), ): """ - _summary_ + Removes a user from a category. Uses autofill functions to fill out table_name, category_name, and user_name. Args: - interaction (nextcord.Interaction): _description_ - table_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="", required=True). - category_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="", required=True). - user_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="", required=True). + interaction (nextcord.Interaction): Interaction that invoked the command. + table_name (str): Table to remove user from. Is SlashOption. + category_name (str): Category to remove user from. Is SlashOption. + user_name (str): The user to remove. Is SlashOption. """ + # TODO ensure input params are valid @user.subcommand(description="Deletes a user's scores from entire table.") async def from_table( @@ -133,37 +136,36 @@ class DataManagementCog(commands.Cog, name="Table Management"): user_name: str = nextcord.SlashOption(description="User to delete.", required=True), ): """ - _summary_ + Removes user from a table. Uses autofill functions to fill out table_name and user_name. Args: - interaction (nextcord.Interaction): _description_ - table_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="Table to delete user from.", required=True). - user_name (str, optional): _description_. Defaults to nextcord.SlashOption(description="User to delete.", required=True). + interaction (nextcord.Interaction): Interaction that invoked the command. + table_name (str): Table to remove user from. Is SlashOption. + user_name (str): The user to remove. Is SlashOption. """ + # TODO ensure params are valid @from_category.on_autocomplete("table_name") @from_table.on_autocomplete("table_name") - async def from_category_autocomplete_table(self, interaction: nextcord.Interaction, table_name: str): + async def autocomplete_table(self, interaction: nextcord.Interaction, table_name: str): """ - _summary_ + Autocomplete function for table names. Args: - interaction (nextcord.Interaction): _description_ - table_name (str): _description_ + interaction (nextcord.Interaction): Interaction that invoked the command. + table_name (str): String to be autocompleted with Table name. """ await interaction.response.send_autocomplete(autofill_tables(interaction.guild_id, table_name)) @from_category.on_autocomplete("category_name") - async def from_category_autocomplete_category( - self, interaction: nextcord.Interaction, category_name: str, table_name: str - ): + async def autocomplete_category(self, interaction: nextcord.Interaction, category_name: str, table_name: str): """ - _summary_ + Autocomplete function for category names. Args: - interaction (nextcord.Interaction): _description_ - category_name (str): _description_ - table_name (str): _description_ + interaction (nextcord.Interaction): Interaction that invoked the command. + category_name (str): String to be autocompleted with Category name. + table_name (str): Table to find Category from. """ await interaction.response.send_autocomplete( autofill_categories(interaction.guild_id, table_name, category_name) @@ -171,17 +173,17 @@ class DataManagementCog(commands.Cog, name="Table Management"): @from_category.on_autocomplete("user_name") @from_table.on_autocomplete("user_name") - async def from_category_autocomplete_user( - self, interaction: nextcord.Interaction, user_name: str, table_name: str, category_name: str = None + async def autocomplete_user( + self, interaction: nextcord.Interaction, user_name: str, table_name: str, category_name: Optional[str] ): """ - _summary_ + Autocomplete functions for user names. Args: - interaction (nextcord.Interaction): _description_ - user_name (str): _description_ - table_name (str): _description_ - category_name (str): _description_ + interaction (nextcord.Interaction): Interaction that invoked the command. + user_name (str): String to be autocompleted with User name. + table_name (str): Table to find category from. + category_name (Optional[str]): Category to find User from, can be None. """ await interaction.response.send_autocomplete( autofill_users(interaction.guild_id, table_name, category_name, user_name) diff --git a/cogs/commands/message_management.py b/cogs/commands/message_management.py index 5a57f7e..27b609b 100644 --- a/cogs/commands/message_management.py +++ b/cogs/commands/message_management.py @@ -131,7 +131,7 @@ class MessageManagementCog(commands.Cog, name="Message Management"): Args: interaction (nextcord.Interaction): Interaction with command. - table (str): String to run autocomplete compared to. + table_name (str): String to run autocomplete compared to. """ await interaction.response.send_autocomplete(autofill_tables(interaction.guild_id, table_name)) From 1e336e290dae9de73af2dc1aa2d7d104ee0bc16a Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Thu, 24 Nov 2022 17:45:40 -0500 Subject: [PATCH 33/34] Catching AttributeError in autofill functions when invalid variables are sent. --- utilities/utils.py | 58 ++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/utilities/utils.py b/utilities/utils.py index 9dde5d6..5be80b8 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -50,18 +50,22 @@ def autofill_category_users(guild_id: int, table_name: str, category_name: str, list[str]: List of users that start with user_begin. """ - users_list: "list[str]" = ( - highscores_data.get_guild(guild_id).get_table(table_name).get_category(category_name).get_users() - ) + try: + users_list: "list[str]" = ( + highscores_data.get_guild(guild_id).get_table(table_name).get_category(category_name).get_users() + ) - # if string empty, return all users - if not user_begin: - return users_list + # if string empty, return all users + if not user_begin: + return users_list - # else find all users that start with user_begin - users = [user for user in users_list if user.lower().startswith(user_begin.lower())] + # else find all users that start with user_begin + users = [user for user in users_list if user.lower().startswith(user_begin.lower())] - return users + return users + + except AttributeError: + return [] def autofill_tables(guild_id: int, table_begin: str) -> "list[str]": @@ -102,27 +106,31 @@ def autofill_users(guild_id: int, table_name: str, category_name: str, user_begi list[str]: List of users that start with user_begin. """ - # If no category name, get all users in table. - if category_name is None: - user_set: set = set() + try: + # If no category name, get all users in table. + if category_name is None: + user_set: set = set() - for category in highscores_data.get_guild(guild_id).get_table(table_name).get_categories().values(): - user_set = user_set | set(category.get_users()) + for category in highscores_data.get_guild(guild_id).get_table(table_name).get_categories().values(): + user_set = user_set | set(category.get_users()) - users_list = list(user_set) - else: - users_list: "list[str]" = ( - highscores_data.get_guild(guild_id).get_table(table_name).get_category(category_name).get_users() - ) + users_list = list(user_set) + else: + users_list: "list[str]" = ( + highscores_data.get_guild(guild_id).get_table(table_name).get_category(category_name).get_users() + ) - # if string empty, return all users - if not user_begin: - return users_list + # if string empty, return all users + if not user_begin: + return users_list - # else find all users that start with user_begin - users = [user for user in users_list if user.lower().startswith(user_begin.lower())] + # else find all users that start with user_begin + users = [user for user in users_list if user.lower().startswith(user_begin.lower())] - return users + return users + + except AttributeError: + return [] def format_highscore_message(guild: Guild, table: Table) -> str: From f5342f1541c6cad790f1065cccf700d158595833 Mon Sep 17 00:00:00 2001 From: alwayscompiling Date: Thu, 24 Nov 2022 17:47:21 -0500 Subject: [PATCH 34/34] Fill functionality for delete user from_table and from_category. --- cogs/commands/data_management.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cogs/commands/data_management.py b/cogs/commands/data_management.py index 13b29bc..8bce454 100644 --- a/cogs/commands/data_management.py +++ b/cogs/commands/data_management.py @@ -126,7 +126,20 @@ class DataManagementCog(commands.Cog, name="Table Management"): category_name (str): Category to remove user from. Is SlashOption. user_name (str): The user to remove. Is SlashOption. """ - # TODO ensure input params are valid + + guild = highscores_data.get_guild(interaction.guild_id) + + try: + guild.get_table(table_name).get_category(category_name).remove_score(user_name) + await send_highscore_message( + interaction.guild.get_channel(guild.highscore_channel), guild, guild.get_table(table_name) + ) + await interaction.response.send_message( + f'User "{user_name}" removed from "{category_name}".', ephemeral=True + ) + + except nextcord.errors.ApplicationInvokeError: + await interaction.response.send_message("Invalid parameter(s).", ephemeral=True) @user.subcommand(description="Deletes a user's scores from entire table.") async def from_table( @@ -143,7 +156,18 @@ class DataManagementCog(commands.Cog, name="Table Management"): table_name (str): Table to remove user from. Is SlashOption. user_name (str): The user to remove. Is SlashOption. """ - # TODO ensure params are valid + guild = highscores_data.get_guild(interaction.guild_id) + + try: + for category in guild.get_table(table_name).categories: + category.remove_score(user_name) + await send_highscore_message( + interaction.guild.get_channel(guild.highscore_channel), guild, guild.get_table(table_name) + ) + await interaction.response.send_message(f'User "{user_name}" removed from all categories.', ephemeral=True) + + except nextcord.errors.ApplicationInvokeError: + await interaction.response.send_message("Invalid parameter(s).", ephemeral=True) @from_category.on_autocomplete("table_name") @from_table.on_autocomplete("table_name")