From 1605c8ad50df15272be099d6489355e0c2abdb26 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 12 Feb 2023 13:33:05 -0500 Subject: [PATCH 1/6] Add new view file. --- cogs/commands/view.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 cogs/commands/view.py diff --git a/cogs/commands/view.py b/cogs/commands/view.py new file mode 100644 index 0000000..949fe2b --- /dev/null +++ b/cogs/commands/view.py @@ -0,0 +1,3 @@ +""" +_summary_ +""" \ No newline at end of file From 44c2720f8033c9738a9e8eceb6e1a469dbecae52 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Fri, 10 Mar 2023 12:17:58 -0500 Subject: [PATCH 2/6] Add new view file. --- cogs/commands/view.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 cogs/commands/view.py diff --git a/cogs/commands/view.py b/cogs/commands/view.py new file mode 100644 index 0000000..949fe2b --- /dev/null +++ b/cogs/commands/view.py @@ -0,0 +1,3 @@ +""" +_summary_ +""" \ No newline at end of file From ab721c16258d58b71716936d2a6f6caa7103d2eb Mon Sep 17 00:00:00 2001 From: imcompiling Date: Fri, 10 Mar 2023 16:27:40 -0500 Subject: [PATCH 3/6] create format_score_string function. --- utilities/utils.py | 51 +++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/utilities/utils.py b/utilities/utils.py index be41bbb..074bf28 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -231,20 +231,12 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str: # add each score to list for ind in range(max_cat_size): # need to subtract to deal with category name string if ind in processed_group.index: - user_length = group.UsernameLength - username = processed_group["UserName"][ind][:user_length].ljust(user_length, " ") + username = processed_group["UserName"][ind] + score = processed_group["Score"][ind] - # get score, pad to correct length - if category.IsTimeRecord: - # MM:SS.ms is 7 characters long - # if there are single digit minutes, add a space for formatting. - score = get_time_str(processed_group["Score"][ind]) - if score.find(":") and len(score.split(":", maxsplit=1)[0]) < 2: - score = " " + score - else: - score = str(int(processed_group["Score"][ind])).rjust(7, " ") - - str_list[ind + 1] += f"{ind + 1}: {username} - {score}".ljust(pad_length, " ") + str_list[ind + 1] += format_score_string( + username, score, group.UsernameLength, group.IsTimeRecord, ind + 1, pad_length + ) else: # if not enough entries, pad out if ind + 1 <= category.Size: @@ -262,7 +254,6 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str: # need to use index to allow for modifying the list for ind, line in enumerate(str_list): if len(line) > MAX_LINE_LENGTH: - split_loc = int(MAX_LINE_LENGTH / pad_length) * pad_length new_line = line[split_loc:] str_list[ind] = line[:split_loc] @@ -283,6 +274,38 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str: return ret_string + "```" +def format_score_string( + username: str, score: float, username_length: int, is_time_record: bool, rank: int, pad_length: int +) -> str: + """ + Builds the string to show a user's score. + + Args: + username (str): username. + score (int): score in int form. + username_length (float): limit to how long username is allowed to be. + is_time_record (bool): if the score represents an number or a time record. + rank (int): the user's rank. + pad_length (int): limit to how long the string can be. + + Returns: + str: string to show user's score. + """ + username = username[:username_length].ljust(username_length, " ") + + # get score, pad to correct length + if is_time_record: + # MM:SS.ms is 7 characters long + # if there are single digit minutes, add a space for formatting. + score = get_time_str(score) + if score.find(":") and len(score.split(":", maxsplit=1)[0]) < 2: + score = " " + score + else: + score = str(int(score)).rjust(7, " ") + + return f"{rank}: {username} - {score}".ljust(pad_length, " ") + + def get_time_str(time_in: float) -> str: """ Formats a float value into the desired string format for a timestamp. From 2c9e9e246aa773efa0241196ef106159b151e0d1 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Fri, 10 Mar 2023 17:37:59 -0500 Subject: [PATCH 4/6] Create view leaderboard command. --- cogs/commands/__init__.py | 2 + cogs/commands/view.py | 108 +++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py index 05bb85d..21a0f0c 100644 --- a/cogs/commands/__init__.py +++ b/cogs/commands/__init__.py @@ -10,6 +10,7 @@ from .help import HelpCog from .refresh import RefreshCog from .set import SetCog from .update import UpdateCog +from .view import ViewCog # This function will be called when this extension is loaded. @@ -24,3 +25,4 @@ def setup(bot: commands.Bot): bot.add_cog(RefreshCog(bot)) bot.add_cog(SetCog()) bot.add_cog(UpdateCog(bot)) + bot.add_cog(ViewCog()) diff --git a/cogs/commands/view.py b/cogs/commands/view.py index 949fe2b..1be4000 100644 --- a/cogs/commands/view.py +++ b/cogs/commands/view.py @@ -1,3 +1,107 @@ """ -_summary_ -""" \ No newline at end of file +View command heirarchy. +""" + +import nextcord +import pandas as pd +from nextcord.ext import commands +from sqlalchemy import select +from sqlalchemy.orm import Session + +# pylint: disable=import-error +from utilities.sql_data_classes import Category, CategoryGroup, Score, engine +from utilities.utils import autofill_categories, autofill_groups, format_score_string + + +class ViewCog(commands.Cog, name="View Command"): + """ + Class holds commands for viewing data. + """ + + def __init__(self) -> None: + """ + Default constructor for ViewCog class. Does nothing. + """ + + @nextcord.slash_command() + async def view(self, interaction: nextcord.Interaction): + """ + Parent slash command for view. Never called. + """ + + @view.subcommand(description="Lists all scores for a given category.") + async def leaderboard( + self, + interaction: nextcord.Interaction, + group_id: str = nextcord.SlashOption( + name="group", description="Group to show leaderboard from.", required=True + ), + category_id: str = nextcord.SlashOption( + name="category", description="Category to show leaderboard.", required=True + ), + ): + """ + Gathers all users that have a submission in the group/category combo and returns a message with them + in ranked order. + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command. + group_id (str): String representation of Group id to search from. Is SlashOption. + category_id (str): String representation of Category id to show leaderboard. Is SlashOption. + """ + str_list: "list[str]" = [] + + # get group, category, and all scores sorted correctly. + with Session(engine) as session: + stmt = select(CategoryGroup).where(CategoryGroup.GroupId == int(group_id)) + group = session.scalar(stmt) + + stmt = select(Category).where(Category.CategoryId == int(category_id)) + category = session.scalar(stmt) + + d_f = pd.read_sql_query( + sql=session.query(Score).where(Score.CategoryId == category_id).statement, con=engine + ) + + scores = d_f.sort_values("Score", ascending=category.IsAscending).reset_index() + + for i, row in scores.iterrows(): + str_list.append( + format_score_string( + row["UserName"], row["Score"], group.UsernameLength, category.IsTimeRecord, i + 1, group.PadLength + ) + ) + + message_str: str = f"**{group.GroupName} {category.CategoryName} Leaderboard!**\n" + if len(str_list) == 0: + message_str += "No Submissions!" + else: + for line in str_list: + message_str += f"{line}\n" + + await interaction.response.send_message(content=message_str) + + @leaderboard.on_autocomplete("group_id") + async def group_autocomplete(self, interaction: nextcord.Interaction, group_id: str): + """ + Autocomplete function for table names. + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command. + group_id (str): String to be autocompleted with Group name. + """ + await interaction.response.send_autocomplete(autofill_groups(interaction.guild_id, group_id)) + + @leaderboard.on_autocomplete("category_id") + async def category_autocomplete(self, interaction: nextcord.Interaction, category_id: str, group_id: str): + """ + Autocomplete function for category names. + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command. + category_id (str): String to be autocompleted with Category name. + group_id (str): Group to find Category from. + """ + await interaction.response.send_autocomplete( + autofill_categories(interaction.guild_id, int(group_id), category_id) + ) From 9c762fb346e35705e4cf28952243296886b555d3 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Fri, 10 Mar 2023 17:56:49 -0500 Subject: [PATCH 5/6] Add on delete cascade to sql definition. --- highscores_database.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/highscores_database.sql b/highscores_database.sql index 74dfc59..ae0a0f6 100644 --- a/highscores_database.sql +++ b/highscores_database.sql @@ -17,7 +17,7 @@ CREATE TABLE CategoryGroups( PadLength smallint not null DEFAULT 26, UsernameLength smallint DEFAULT 12, CONSTRAINT UniqueGuildCategoryGroup UNIQUE (GuildId, GroupName), - FOREIGN KEY (GuildId) REFERENCES Guilds(GuildId), + FOREIGN KEY (GuildId) REFERENCES Guilds(GuildId) ON DELETE CASCADE, PRIMARY KEY (GroupId) ); @@ -30,7 +30,7 @@ CREATE TABLE Categories( IsTimeRecord boolean not null DEFAULT false, IsValidated boolean not null DEFAULT true, CONSTRAINT UniqueCategoryInGroup UNIQUE (GroupId, CategoryName), - FOREIGN KEY (GroupId) REFERENCES CategoryGroups(GroupId), + FOREIGN KEY (GroupId) REFERENCES CategoryGroups(GroupId) ON DELETE CASCADE, PRIMARY Key (CategoryId) ); @@ -38,6 +38,6 @@ CREATE TABLE Scores( UserName varchar(30) not null, Score float not null, -- Maybe should have time and int scores?? timestamp type? CategoryId int unsigned not null, - FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId), + FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId) ON DELETE CASCADE, PRIMARY KEY (CategoryId, UserName) ); \ No newline at end of file From 37b4d2d62e9e014cfae61fcc98779ecbe780bb73 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 11 Mar 2023 15:12:36 -0500 Subject: [PATCH 6/6] correct usage of format_score_string. Make format of view message better. --- cogs/commands/view.py | 11 ++++++++--- utilities/utils.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cogs/commands/view.py b/cogs/commands/view.py index 1be4000..148abe9 100644 --- a/cogs/commands/view.py +++ b/cogs/commands/view.py @@ -10,7 +10,7 @@ from sqlalchemy.orm import Session # pylint: disable=import-error from utilities.sql_data_classes import Category, CategoryGroup, Score, engine -from utilities.utils import autofill_categories, autofill_groups, format_score_string +from utilities.utils import MAX_LINE_LENGTH, autofill_categories, autofill_groups, format_score_string class ViewCog(commands.Cog, name="View Command"): @@ -65,20 +65,25 @@ class ViewCog(commands.Cog, name="View Command"): scores = d_f.sort_values("Score", ascending=category.IsAscending).reset_index() + # get the longest string + longest_str: int = scores.UserName.map(len).max() + for i, row in scores.iterrows(): str_list.append( format_score_string( - row["UserName"], row["Score"], group.UsernameLength, category.IsTimeRecord, i + 1, group.PadLength + row["UserName"], row["Score"], longest_str, category.IsTimeRecord, i + 1, MAX_LINE_LENGTH ) ) - message_str: str = f"**{group.GroupName} {category.CategoryName} Leaderboard!**\n" + message_str: str = f"**{group.GroupName} {category.CategoryName} Leaderboard!**\n```" if len(str_list) == 0: message_str += "No Submissions!" else: for line in str_list: message_str += f"{line}\n" + message_str += "```" + await interaction.response.send_message(content=message_str) @leaderboard.on_autocomplete("group_id") diff --git a/utilities/utils.py b/utilities/utils.py index 074bf28..0ade38c 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -235,7 +235,7 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str: score = processed_group["Score"][ind] str_list[ind + 1] += format_score_string( - username, score, group.UsernameLength, group.IsTimeRecord, ind + 1, pad_length + username, score, group.UsernameLength, category.IsTimeRecord, ind + 1, pad_length ) else: # if not enough entries, pad out