From 2ae963db4fa40d187557515a8d1ac70de372b614 Mon Sep 17 00:00:00 2001 From: S1ro1 Date: Sat, 23 Nov 2024 22:42:41 +0100 Subject: [PATCH 1/9] Feat: initial db support --- docker-compose.yml | 18 +++++ requirements.txt | 3 +- src/discord-cluster-manager/bot.py | 7 ++ .../cogs/leaderboard_cog.py | 14 ++++ src/discord-cluster-manager/consts.py | 7 ++ src/discord-cluster-manager/leaderboard_db.py | 73 +++++++++++++++++++ 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 docker-compose.yml create mode 100644 src/discord-cluster-manager/cogs/leaderboard_cog.py create mode 100644 src/discord-cluster-manager/leaderboard_db.py diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..51b79b0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.8' + +services: + postgres: + image: postgres:latest + container_name: postgres_db + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=mydb + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + postgres_data: diff --git a/requirements.txt b/requirements.txt index 2af447f..cce924f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ discord.py audioop-lts # discord.py imports using * syntax python-dotenv requests -modal \ No newline at end of file +modal +psycopg2-binary diff --git a/src/discord-cluster-manager/bot.py b/src/discord-cluster-manager/bot.py index 56e2cf8..4044cfc 100644 --- a/src/discord-cluster-manager/bot.py +++ b/src/discord-cluster-manager/bot.py @@ -14,6 +14,7 @@ ) from cogs.modal_cog import ModalCog from cogs.github_cog import GitHubCog +from cogs.leaderboard_cog import LeaderboardCog logger = setup_logging() @@ -31,6 +32,11 @@ def __init__(self, debug_mode=False): ) self.tree.add_command(self.run_group) + self.leaderboard_group = app_commands.Group( + name="leaderboard", description="Leaderboard commands" + ) + self.tree.add_command(self.leaderboard_group) + async def setup_hook(self): logger.info(f"Syncing commands for staging guild {DISCORD_CLUSTER_STAGING_ID}") try: @@ -38,6 +44,7 @@ async def setup_hook(self): await self.add_cog(ModalCog(self)) await self.add_cog(GitHubCog(self)) await self.add_cog(BotManagerCog(self)) + await self.add_cog(LeaderboardCog(self)) guild_id = ( DISCORD_CLUSTER_STAGING_ID diff --git a/src/discord-cluster-manager/cogs/leaderboard_cog.py b/src/discord-cluster-manager/cogs/leaderboard_cog.py new file mode 100644 index 0000000..9b1b1ec --- /dev/null +++ b/src/discord-cluster-manager/cogs/leaderboard_cog.py @@ -0,0 +1,14 @@ +import discord + +from discord.ext import commands + + +class LeaderboardCog(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.get_leaderboard = bot.leaderboard_group.command(name="get")( + self.get_leaderboard + ) + + async def get_leaderboard(self, interaction: discord.Interaction): + await interaction.response.send_message("Leaderboard") diff --git a/src/discord-cluster-manager/consts.py b/src/discord-cluster-manager/consts.py index e7dc252..1cb0898 100644 --- a/src/discord-cluster-manager/consts.py +++ b/src/discord-cluster-manager/consts.py @@ -34,3 +34,10 @@ class SchedulerType(Enum): # GitHub-specific constants GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") GITHUB_REPO = os.getenv("GITHUB_REPO") + +# PostgreSQL-specific constants +POSTGRES_HOST = os.getenv("POSTGRES_HOST") +POSTGRES_DATABASE = os.getenv("POSTGRES_DATABASE") +POSTGRES_USER = os.getenv("POSTGRES_USER") +POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD") +POSTGRES_PORT = os.getenv("POSTGRES_PORT") diff --git a/src/discord-cluster-manager/leaderboard_db.py b/src/discord-cluster-manager/leaderboard_db.py new file mode 100644 index 0000000..35a088e --- /dev/null +++ b/src/discord-cluster-manager/leaderboard_db.py @@ -0,0 +1,73 @@ +import psycopg2 +from psycopg2 import Error +from typing import Optional + +class LeaderboardDB: + def __init__(self, host: str, database: str, user: str, password: str, port: str = "5432"): + """Initialize database connection parameters""" + self.connection_params = { + "host": host, + "database": database, + "user": user, + "password": password, + "port": port + } + self.connection: Optional[psycopg2.extensions.connection] = None + self.cursor: Optional[psycopg2.extensions.cursor] = None + + def connect(self) -> bool: + """Establish connection to the database""" + try: + self.connection = psycopg2.connect(**self.connection_params) + self.cursor = self.connection.cursor() + self._create_tables() + return True + except Error as e: + print(f"Error connecting to PostgreSQL: {e}") + return False + + def disconnect(self): + """Close database connection and cursor""" + if self.cursor: + self.cursor.close() + if self.connection: + self.connection.close() + + def _create_tables(self): + """Create necessary tables if they don't exist""" + create_table_query = """ + CREATE TABLE IF NOT EXISTS leaderboard ( + id BIGINT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP + deadline TIMESTAMP NOT NULL, + template_code TEXT NOT NULL + ); + """ + + create_submission_table_query = """ + CREATE TABLE IF NOT EXISTS submissions ( + id BIGINT PRIMARY KEY, + leaderboard_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + submission_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + code TEXT NOT NULL, + FOREIGN KEY (leaderboard_id) REFERENCES leaderboard(id) + ); + """ + + try: + self.cursor.execute(create_table_query) + self.cursor.execute(create_submission_table_query) + self.connection.commit() + except Error as e: + print(f"Error creating table: {e}") + + def __enter__(self): + """Context manager entry""" + self.connect() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Context manager exit""" + self.disconnect() From 0f9baf741107bbb93171d9d00317bffc9b549dcd Mon Sep 17 00:00:00 2001 From: S1ro1 Date: Sat, 23 Nov 2024 22:46:37 +0100 Subject: [PATCH 2/9] Feat: initial db support --- .../cogs/leaderboard_cog.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/discord-cluster-manager/cogs/leaderboard_cog.py b/src/discord-cluster-manager/cogs/leaderboard_cog.py index 9b1b1ec..f896d3d 100644 --- a/src/discord-cluster-manager/cogs/leaderboard_cog.py +++ b/src/discord-cluster-manager/cogs/leaderboard_cog.py @@ -1,14 +1,64 @@ import discord +from discord import ui +from datetime import datetime from discord.ext import commands +def generate_modal(): + class LeaderboardModal(ui.Modal, title="Create Leaderboard"): + name = ui.TextInput( + label="Leaderboard Name", + placeholder="Enter the leaderboard name", + required=True, + max_length=100 + ) + + date = ui.TextInput( + label="Date", + placeholder="YYYY-MM-DD HH:MM", + required=True + ) + + template_code = ui.TextInput( + label="Template Code", + style=discord.TextStyle.paragraph, + placeholder="Paste your template code here", + required=True + ) + + async def on_submit(self, interaction: discord.Interaction): + try: + # Parse the date string to datetime + date_value = datetime.strptime(str(self.date), "%Y-%m-%d %H:%M") + await interaction.response.send_message( + f"Leaderboard '{self.name}' created for {date_value}", + ephemeral=True + ) + except ValueError: + await interaction.response.send_message( + "Invalid date format. Please use YYYY-MM-DD HH:MM", + ephemeral=True + ) + + return LeaderboardModal() + + class LeaderboardCog(commands.Cog): def __init__(self, bot): self.bot = bot self.get_leaderboard = bot.leaderboard_group.command(name="get")( self.get_leaderboard ) + self.leaderboard_create = bot.leaderboard_group.command(name="create")( + self.leaderboard_create + ) async def get_leaderboard(self, interaction: discord.Interaction): await interaction.response.send_message("Leaderboard") + + + async def leaderboard_create(self, interaction: discord.Interaction): + modal = generate_modal() + await interaction.response.send_modal(modal) + From 50f2392070f1717b80b6dcbe066edf206fb43e1c Mon Sep 17 00:00:00 2001 From: S1ro1 Date: Sat, 23 Nov 2024 23:17:57 +0100 Subject: [PATCH 3/9] Feat: support adding and querying leaderboards --- scripts/flush_db.py | 53 ++++++++ src/discord-cluster-manager/bot.py | 11 ++ .../cogs/leaderboard_cog.py | 121 ++++++++++-------- src/discord-cluster-manager/leaderboard_db.py | 35 ++++- src/discord-cluster-manager/utils.py | 10 ++ 5 files changed, 175 insertions(+), 55 deletions(-) create mode 100644 scripts/flush_db.py diff --git a/scripts/flush_db.py b/scripts/flush_db.py new file mode 100644 index 0000000..8659d57 --- /dev/null +++ b/scripts/flush_db.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import psycopg2 +from psycopg2 import Error +from dotenv import load_dotenv +import os + +def flush_database(): + # Load environment variables + load_dotenv() + + # Get database connection parameters from environment + connection_params = { + "host": os.getenv("POSTGRES_HOST"), + "database": os.getenv("POSTGRES_DATABASE"), + "user": os.getenv("POSTGRES_USER"), + "password": os.getenv("POSTGRES_PASSWORD"), + "port": os.getenv("POSTGRES_PORT", "5432") + } + + # Verify all parameters are present + missing_params = [k for k, v in connection_params.items() if not v] + if missing_params: + print(f"❌ Missing environment variables: {', '.join(missing_params)}") + return + + try: + # Connect to database + print("📡 Connecting to database...") + connection = psycopg2.connect(**connection_params) + cursor = connection.cursor() + + # Drop existing tables + print("🗑️ Dropping existing tables...") + drop_tables_query = """ + DROP TABLE IF EXISTS submissions CASCADE; + DROP TABLE IF EXISTS leaderboard CASCADE; + """ + cursor.execute(drop_tables_query) + # Commit changes + connection.commit() + print("✅ Database flushed and recreated successfully!") + + except Error as e: + print(f"❌ Database error: {e}") + finally: + if 'connection' in locals(): + cursor.close() + connection.close() + print("🔌 Database connection closed") + +if __name__ == "__main__": + flush_database() diff --git a/src/discord-cluster-manager/bot.py b/src/discord-cluster-manager/bot.py index 4044cfc..817996f 100644 --- a/src/discord-cluster-manager/bot.py +++ b/src/discord-cluster-manager/bot.py @@ -11,10 +11,17 @@ DISCORD_DEBUG_TOKEN, DISCORD_CLUSTER_STAGING_ID, DISCORD_DEBUG_CLUSTER_STAGING_ID, + + POSTGRES_USER, + POSTGRES_PASSWORD, + POSTGRES_HOST, + POSTGRES_PORT, + POSTGRES_DATABASE, ) from cogs.modal_cog import ModalCog from cogs.github_cog import GitHubCog from cogs.leaderboard_cog import LeaderboardCog +from leaderboard_db import LeaderboardDB logger = setup_logging() @@ -37,6 +44,10 @@ def __init__(self, debug_mode=False): ) self.tree.add_command(self.leaderboard_group) + self.leaderboard_db = LeaderboardDB( + POSTGRES_HOST, POSTGRES_DATABASE, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_PORT + ) + async def setup_hook(self): logger.info(f"Syncing commands for staging guild {DISCORD_CLUSTER_STAGING_ID}") try: diff --git a/src/discord-cluster-manager/cogs/leaderboard_cog.py b/src/discord-cluster-manager/cogs/leaderboard_cog.py index f896d3d..1a2b9c9 100644 --- a/src/discord-cluster-manager/cogs/leaderboard_cog.py +++ b/src/discord-cluster-manager/cogs/leaderboard_cog.py @@ -1,64 +1,85 @@ import discord -from discord import ui from datetime import datetime from discord.ext import commands +from typing import TYPE_CHECKING -def generate_modal(): - class LeaderboardModal(ui.Modal, title="Create Leaderboard"): - name = ui.TextInput( - label="Leaderboard Name", - placeholder="Enter the leaderboard name", - required=True, - max_length=100 - ) - - date = ui.TextInput( - label="Date", - placeholder="YYYY-MM-DD HH:MM", - required=True - ) - - template_code = ui.TextInput( - label="Template Code", - style=discord.TextStyle.paragraph, - placeholder="Paste your template code here", - required=True - ) - - async def on_submit(self, interaction: discord.Interaction): - try: - # Parse the date string to datetime - date_value = datetime.strptime(str(self.date), "%Y-%m-%d %H:%M") - await interaction.response.send_message( - f"Leaderboard '{self.name}' created for {date_value}", - ephemeral=True - ) - except ValueError: - await interaction.response.send_message( - "Invalid date format. Please use YYYY-MM-DD HH:MM", - ephemeral=True - ) - - return LeaderboardModal() +if TYPE_CHECKING: + from bot import ClusterBot class LeaderboardCog(commands.Cog): def __init__(self, bot): - self.bot = bot - self.get_leaderboard = bot.leaderboard_group.command(name="get")( - self.get_leaderboard - ) - self.leaderboard_create = bot.leaderboard_group.command(name="create")( - self.leaderboard_create + self.bot: ClusterBot = bot + self.get_leaderboards = bot.leaderboard_group.command(name="get")( + self.get_leaderboards ) + self.leaderboard_create = bot.leaderboard_group.command( + name="create", description="Create a new leaderboard" + )(self.leaderboard_create) + + async def get_leaderboards(self, interaction: discord.Interaction): + """Display all leaderboards in a table format""" + await interaction.response.defer() - async def get_leaderboard(self, interaction: discord.Interaction): - await interaction.response.send_message("Leaderboard") - + with self.bot.leaderboard_db as db: + leaderboards = db.get_leaderboards() - async def leaderboard_create(self, interaction: discord.Interaction): - modal = generate_modal() - await interaction.response.send_modal(modal) + if not leaderboards: + await interaction.followup.send("No leaderboards found.", ephemeral=True) + return + + # Create embed + embed = discord.Embed(title="Active Leaderboards", color=discord.Color.blue()) + + # Add fields for each leaderboard + for lb in leaderboards: + deadline_str = lb["deadline"].strftime("%Y-%m-%d %H:%M") + embed.add_field( + name=lb["name"], value=f"Deadline: {deadline_str}", inline=False + ) + + await interaction.followup.send(embed=embed) + + @discord.app_commands.describe( + name="Name of the leaderboard", + date="Date in YYYY-MM-DD format (time HH:MM is optional)", + template_file="Template file to upload", + ) + async def leaderboard_create( + self, + interaction: discord.Interaction, + name: str, + date: str, + template_file: discord.Attachment, + ): + try: + # Try parsing with time first + try: + date_value = datetime.strptime(date, "%Y-%m-%d %H:%M") + except ValueError: + # If that fails, try parsing just the date (will set time to 00:00) + date_value = datetime.strptime(date, "%Y-%m-%d") + + # Read the template file + template_content = await template_file.read() + + with self.bot.leaderboard_db as db: + db.create_leaderboard( + { + "name": name, + "deadline": date_value, + "template_code": template_content.decode("utf-8"), + } + ) + await interaction.response.send_message( + f"Leaderboard '{name}'. Submission deadline: {date_value}", + ephemeral=True, + ) + except ValueError: + await interaction.response.send_message( + "Invalid date format. Please use YYYY-MM-DD or YYYY-MM-DD HH:MM", + ephemeral=True, + ) diff --git a/src/discord-cluster-manager/leaderboard_db.py b/src/discord-cluster-manager/leaderboard_db.py index 35a088e..34464da 100644 --- a/src/discord-cluster-manager/leaderboard_db.py +++ b/src/discord-cluster-manager/leaderboard_db.py @@ -1,16 +1,20 @@ import psycopg2 from psycopg2 import Error from typing import Optional +from utils import LeaderboardItem + class LeaderboardDB: - def __init__(self, host: str, database: str, user: str, password: str, port: str = "5432"): + def __init__( + self, host: str, database: str, user: str, password: str, port: str = "5432" + ): """Initialize database connection parameters""" self.connection_params = { "host": host, "database": database, "user": user, "password": password, - "port": port + "port": port, } self.connection: Optional[psycopg2.extensions.connection] = None self.cursor: Optional[psycopg2.extensions.cursor] = None @@ -37,9 +41,8 @@ def _create_tables(self): """Create necessary tables if they don't exist""" create_table_query = """ CREATE TABLE IF NOT EXISTS leaderboard ( - id BIGINT PRIMARY KEY, + id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, - last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP deadline TIMESTAMP NOT NULL, template_code TEXT NOT NULL ); @@ -47,7 +50,7 @@ def _create_tables(self): create_submission_table_query = """ CREATE TABLE IF NOT EXISTS submissions ( - id BIGINT PRIMARY KEY, + id SERIAL PRIMARY KEY, leaderboard_id BIGINT NOT NULL, user_id BIGINT NOT NULL, submission_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, @@ -71,3 +74,25 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): """Context manager exit""" self.disconnect() + + def create_leaderboard(self, leaderboard: LeaderboardItem): + self.cursor.execute( + """ + INSERT INTO leaderboard (name, deadline, template_code) + VALUES (%s, %s, %s) + """, + ( + leaderboard["name"], + leaderboard["deadline"], + leaderboard["template_code"], + ), + ) + self.connection.commit() + + def get_leaderboards(self) -> list[LeaderboardItem]: + self.cursor.execute("SELECT * FROM leaderboard") + + return [ + LeaderboardItem(id=lb[0], name=lb[1], deadline=lb[2], template_code=lb[3]) + for lb in self.cursor.fetchall() + ] diff --git a/src/discord-cluster-manager/utils.py b/src/discord-cluster-manager/utils.py index ed349f5..d3adb2e 100644 --- a/src/discord-cluster-manager/utils.py +++ b/src/discord-cluster-manager/utils.py @@ -1,5 +1,8 @@ import logging import subprocess +import datetime +from typing import TypedDict + def setup_logging(): """Configure and setup logging for the application""" @@ -20,6 +23,7 @@ def setup_logging(): return logger + def get_github_branch_name(): try: result = subprocess.run( @@ -31,3 +35,9 @@ def get_github_branch_name(): return result.stdout.strip().split("/", 1)[1] except subprocess.CalledProcessError: return "main" + + +class LeaderboardItem(TypedDict): + name: str + deadline: datetime.datetime + template_code: str From a9bd8726e11b58d17ca236333ce63ada6c188da7 Mon Sep 17 00:00:00 2001 From: S1ro1 Date: Sat, 23 Nov 2024 23:36:54 +0100 Subject: [PATCH 4/9] Feat: add querying submissions --- .../cogs/leaderboard_cog.py | 38 +++++++++++++++++++ src/discord-cluster-manager/leaderboard_db.py | 25 +++++++++++- src/discord-cluster-manager/utils.py | 7 ++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/discord-cluster-manager/cogs/leaderboard_cog.py b/src/discord-cluster-manager/cogs/leaderboard_cog.py index 1a2b9c9..5e176b2 100644 --- a/src/discord-cluster-manager/cogs/leaderboard_cog.py +++ b/src/discord-cluster-manager/cogs/leaderboard_cog.py @@ -19,6 +19,10 @@ def __init__(self, bot): name="create", description="Create a new leaderboard" )(self.leaderboard_create) + self.get_leaderboard_submissions = bot.leaderboard_group.command( + name="submissions", description="Get all submissions for a leaderboard" + )(self.get_leaderboard_submissions) + async def get_leaderboards(self, interaction: discord.Interaction): """Display all leaderboards in a table format""" await interaction.response.defer() @@ -83,3 +87,37 @@ async def leaderboard_create( "Invalid date format. Please use YYYY-MM-DD or YYYY-MM-DD HH:MM", ephemeral=True, ) + + @discord.app_commands.describe(leaderboard_name="Name of the leaderboard") + async def get_leaderboard_submissions( + self, interaction: discord.Interaction, leaderboard_name: str + ): + with self.bot.leaderboard_db as db: + leaderboard_id = db.get_leaderboard_id(leaderboard_name) + if not leaderboard_id: + await interaction.response.send_message( + "Leaderboard not found.", ephemeral=True + ) + return + + submissions = db.get_leaderboard_submissions(leaderboard_id) + + if not submissions: + await interaction.response.send_message( + "No submissions found.", ephemeral=True + ) + return + + # Create embed + embed = discord.Embed( + title="Leaderboard Submissions", color=discord.Color.blue() + ) + + for submission in submissions: + embed.add_field( + name=f"{submission['user_id']}: submission['submission_name']", + value=f"Submission time: {submission["submission_time"]}", + inline=False, + ) + + await interaction.response.send_message(embed=embed) diff --git a/src/discord-cluster-manager/leaderboard_db.py b/src/discord-cluster-manager/leaderboard_db.py index 34464da..4d00b9e 100644 --- a/src/discord-cluster-manager/leaderboard_db.py +++ b/src/discord-cluster-manager/leaderboard_db.py @@ -1,7 +1,7 @@ import psycopg2 from psycopg2 import Error from typing import Optional -from utils import LeaderboardItem +from utils import LeaderboardItem, SubmissionItem class LeaderboardDB: @@ -52,6 +52,7 @@ def _create_tables(self): CREATE TABLE IF NOT EXISTS submissions ( id SERIAL PRIMARY KEY, leaderboard_id BIGINT NOT NULL, + submission_name VARCHAR(255) NOT NULL, user_id BIGINT NOT NULL, submission_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, code TEXT NOT NULL, @@ -96,3 +97,25 @@ def get_leaderboards(self) -> list[LeaderboardItem]: LeaderboardItem(id=lb[0], name=lb[1], deadline=lb[2], template_code=lb[3]) for lb in self.cursor.fetchall() ] + + def get_leaderboard_submissions(self, leaderboard_id: int) -> list[SubmissionItem]: + self.cursor.execute( + "SELECT * FROM submissions WHERE leaderboard_id = %s", (leaderboard_id,) + ) + + return [ + SubmissionItem( + submission_name=submission[2], + submission_time=submission[3], + code=submission[4], + user_id=submission[5], + ) + for submission in self.cursor.fetchall() + ] + + def get_leaderboard_id(self, leaderboard_name: str) -> int | None: + self.cursor.execute("SELECT * FROM leaderboard", (leaderboard_name,)) + + res = self.cursor.fetchone() + + return res[0] if res else None diff --git a/src/discord-cluster-manager/utils.py b/src/discord-cluster-manager/utils.py index d3adb2e..88b4a75 100644 --- a/src/discord-cluster-manager/utils.py +++ b/src/discord-cluster-manager/utils.py @@ -41,3 +41,10 @@ class LeaderboardItem(TypedDict): name: str deadline: datetime.datetime template_code: str + + +class SubmissionItem(TypedDict): + submission_name: str + submission_time: datetime.datetime + code: str + user_id: int From ced74dcafc997ca4b86aff647fa2989309f2c78a Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Thu, 5 Dec 2024 13:06:52 -0500 Subject: [PATCH 5/9] Add leaderboard submit functionality (mainly subcommands) --- .../cogs/leaderboard_cog.py | 79 +++++++++++++++++-- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/src/discord-cluster-manager/cogs/leaderboard_cog.py b/src/discord-cluster-manager/cogs/leaderboard_cog.py index 5e176b2..ce17b20 100644 --- a/src/discord-cluster-manager/cogs/leaderboard_cog.py +++ b/src/discord-cluster-manager/cogs/leaderboard_cog.py @@ -9,6 +9,65 @@ from bot import ClusterBot +class LeaderboardSubmitCog(discord.app_commands.Group): + def __init__(self): + super().__init__(name="submit", description="Submit leaderboard data") + + # Parent command that defines global options + @discord.app_commands.command(name="submit", description="Submit leaderboard data") + @discord.app_commands.describe( + global_arg="A global argument that will propagate to subcommands" + ) + async def submit( + self, + interaction: discord.Interaction, + global_arg: str, # Global argument for the parent command + ): + pass + + ## MODAL SUBCOMMAND + @discord.app_commands.command( + name="modal", description="Submit leaderboard data for modal" + ) + @discord.app_commands.describe( + modal_x="Value for field X", + modal_y="Value for field Y", + modal_z="Value for field Z", + ) + async def modal( + self, + interaction: discord.Interaction, + global_arg: str, + modal_x: str, + modal_y: str, + modal_z: str, + ): + await interaction.response.send_message( + f"Submitted modal data: X={modal_x}, Y={modal_y}, Z={modal_z}" + ) + + ### GITHUB SUBCOMMAND + @discord.app_commands.command( + name="github", description="Submit leaderboard data for GitHub" + ) + @discord.app_commands.describe( + github_x="Value for field X", + github_yint="Value for field Y", + github_z="Value for field Z", + ) + async def github( + self, + interaction: discord.Interaction, + global_arg: str, + github_x: str, + github_yint: int, + github_z: str, + ): + await interaction.response.send_message( + f"Submitted GitHub data: X={github_x}, Y_int={github_yint}, Z={github_z}]" + ) + + class LeaderboardCog(commands.Cog): def __init__(self, bot): self.bot: ClusterBot = bot @@ -19,6 +78,12 @@ def __init__(self, bot): name="create", description="Create a new leaderboard" )(self.leaderboard_create) + # self.leaderboard_submit = bot.leaderboard_group.command( + # name="submit", description="Submit a file to the leaderboard" + # )(self.leaderboard_submit) + + bot.leaderboard_group.add_command(LeaderboardSubmitCog()) + self.get_leaderboard_submissions = bot.leaderboard_group.command( name="submissions", description="Get all submissions for a leaderboard" )(self.get_leaderboard_submissions) @@ -70,13 +135,11 @@ async def leaderboard_create( template_content = await template_file.read() with self.bot.leaderboard_db as db: - db.create_leaderboard( - { - "name": name, - "deadline": date_value, - "template_code": template_content.decode("utf-8"), - } - ) + db.create_leaderboard({ + "name": name, + "deadline": date_value, + "template_code": template_content.decode("utf-8"), + }) await interaction.response.send_message( f"Leaderboard '{name}'. Submission deadline: {date_value}", @@ -116,7 +179,7 @@ async def get_leaderboard_submissions( for submission in submissions: embed.add_field( name=f"{submission['user_id']}: submission['submission_name']", - value=f"Submission time: {submission["submission_time"]}", + value=f"Submission time: {submission['submission_time']}", inline=False, ) From 1e2b5ea3f00e92fbf8cd20884e710d0d81fa9f77 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Thu, 5 Dec 2024 22:23:38 -0500 Subject: [PATCH 6/9] add necessary slash commands --- .../cogs/leaderboard_cog.py | 100 ++++++++++-------- src/discord-cluster-manager/consts.py | 11 ++ 2 files changed, 68 insertions(+), 43 deletions(-) diff --git a/src/discord-cluster-manager/cogs/leaderboard_cog.py b/src/discord-cluster-manager/cogs/leaderboard_cog.py index ce17b20..c8b5b4f 100644 --- a/src/discord-cluster-manager/cogs/leaderboard_cog.py +++ b/src/discord-cluster-manager/cogs/leaderboard_cog.py @@ -1,70 +1,81 @@ import discord -from datetime import datetime - +from discord import app_commands from discord.ext import commands +from datetime import datetime from typing import TYPE_CHECKING +from consts import GitHubGPU, ModalGPU if TYPE_CHECKING: from bot import ClusterBot -class LeaderboardSubmitCog(discord.app_commands.Group): +class LeaderboardSubmitCog(app_commands.Group): def __init__(self): super().__init__(name="submit", description="Submit leaderboard data") # Parent command that defines global options - @discord.app_commands.command(name="submit", description="Submit leaderboard data") - @discord.app_commands.describe( - global_arg="A global argument that will propagate to subcommands" + @app_commands.describe( + leaderboard_name="Name of the competition / kernel to optimize", + script="The Python / CUDA script file to run", + dtype="dtype (e.g. FP32, BF16, FP4) that the input and output expects.", + shape="Data input shape as a tuple", ) async def submit( self, interaction: discord.Interaction, - global_arg: str, # Global argument for the parent command + leaderboard_name: str, + script: discord.Attachment, + dtype: app_commands.Choice[str] = None, + shape: app_commands.Choice[str] = None, ): pass - ## MODAL SUBCOMMAND - @discord.app_commands.command( - name="modal", description="Submit leaderboard data for modal" + @app_commands.command(name="modal", description="Submit leaderboard data for modal") + @app_commands.describe( + gpu_type="Choose the GPU type for Modal", ) - @discord.app_commands.describe( - modal_x="Value for field X", - modal_y="Value for field Y", - modal_z="Value for field Z", + @app_commands.choices( + gpu_type=[ + app_commands.Choice(name=gpu.value, value=gpu.value) for gpu in ModalGPU + ] ) async def modal( self, interaction: discord.Interaction, - global_arg: str, - modal_x: str, - modal_y: str, - modal_z: str, + leaderboard_name: str, + script: discord.Attachment, + gpu_type: app_commands.Choice[str], + dtype: app_commands.Choice[str] = "fp32", + shape: app_commands.Choice[str] = None, ): await interaction.response.send_message( - f"Submitted modal data: X={modal_x}, Y={modal_y}, Z={modal_z}" + f"Submitted modal data: GPU Type={gpu_type}" ) ### GITHUB SUBCOMMAND - @discord.app_commands.command( + @app_commands.command( name="github", description="Submit leaderboard data for GitHub" ) - @discord.app_commands.describe( - github_x="Value for field X", - github_yint="Value for field Y", - github_z="Value for field Z", + @app_commands.describe( + gpu_type="Choose the GPU type for Github Runners", + ) + @app_commands.choices( + gpu_type=[ + app_commands.Choice(name=gpu.value, value=gpu.value) for gpu in GitHubGPU + ] ) async def github( self, interaction: discord.Interaction, - global_arg: str, - github_x: str, - github_yint: int, - github_z: str, + leaderboard_name: str, + script: discord.Attachment, + gpu_type: app_commands.Choice[str], + dtype: app_commands.Choice[str] = "fp32", + shape: app_commands.Choice[str] = None, ): await interaction.response.send_message( - f"Submitted GitHub data: X={github_x}, Y_int={github_yint}, Z={github_z}]" + f"Submitted GitHub data: GPU Type={gpu_type}" ) @@ -112,37 +123,37 @@ async def get_leaderboards(self, interaction: discord.Interaction): await interaction.followup.send(embed=embed) @discord.app_commands.describe( - name="Name of the leaderboard", - date="Date in YYYY-MM-DD format (time HH:MM is optional)", - template_file="Template file to upload", + leaderboard_name="Name of the leaderboard", + deadline="Competition deadline in the form: 'Y-m-d'", + reference_code="Reference implementation of kernel. Also includes eval code.", ) async def leaderboard_create( self, interaction: discord.Interaction, - name: str, - date: str, - template_file: discord.Attachment, + leaderboard_name: str, + deadline: str, + reference_code: discord.Attachment, ): try: # Try parsing with time first try: - date_value = datetime.strptime(date, "%Y-%m-%d %H:%M") + date_value = datetime.strptime(deadline, "%Y-%m-%d %H:%M") except ValueError: # If that fails, try parsing just the date (will set time to 00:00) - date_value = datetime.strptime(date, "%Y-%m-%d") + date_value = datetime.strptime(deadline, "%Y-%m-%d") # Read the template file - template_content = await template_file.read() + template_content = await reference_code.read() with self.bot.leaderboard_db as db: db.create_leaderboard({ - "name": name, + "name": leaderboard_name, "deadline": date_value, - "template_code": template_content.decode("utf-8"), + "reference_code": template_content.decode("utf-8"), }) await interaction.response.send_message( - f"Leaderboard '{name}'. Submission deadline: {date_value}", + f"Leaderboard '{leaderboard_name}'. Submission deadline: {date_value}", ephemeral=True, ) except ValueError: @@ -153,7 +164,10 @@ async def leaderboard_create( @discord.app_commands.describe(leaderboard_name="Name of the leaderboard") async def get_leaderboard_submissions( - self, interaction: discord.Interaction, leaderboard_name: str + self, + interaction: discord.Interaction, + leaderboard_name: str, + dtype: app_commands.Choice[str] = "fp32", ): with self.bot.leaderboard_db as db: leaderboard_id = db.get_leaderboard_id(leaderboard_name) @@ -163,7 +177,7 @@ async def get_leaderboard_submissions( ) return - submissions = db.get_leaderboard_submissions(leaderboard_id) + submissions = db.get_leaderboard_submissions(leaderboard_id) # Add dtype if not submissions: await interaction.response.send_message( diff --git a/src/discord-cluster-manager/consts.py b/src/discord-cluster-manager/consts.py index 1cb0898..5d03c33 100644 --- a/src/discord-cluster-manager/consts.py +++ b/src/discord-cluster-manager/consts.py @@ -24,6 +24,17 @@ class SchedulerType(Enum): SLURM = "slurm" +class GitHubGPU(Enum): + T4 = "T4" + + +class ModalGPU(Enum): + T4 = "T4" + L4 = "L4" + A100 = "A100" + H100 = "H100" + + init_environment() # Discord-specific constants DISCORD_TOKEN = os.getenv("DISCORD_TOKEN") From 84e1027b2d8ab30677e95e6e666af4a2d9a6decf Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Fri, 6 Dec 2024 01:14:37 -0500 Subject: [PATCH 7/9] basic leaderboard functionality --- src/discord-cluster-manager/bot.py | 9 +- .../cogs/leaderboard_cog.py | 48 +++++-- src/discord-cluster-manager/leaderboard_db.py | 119 ++++++++++++++---- src/discord-cluster-manager/utils.py | 10 +- 4 files changed, 150 insertions(+), 36 deletions(-) diff --git a/src/discord-cluster-manager/bot.py b/src/discord-cluster-manager/bot.py index 817996f..bd1fb4e 100644 --- a/src/discord-cluster-manager/bot.py +++ b/src/discord-cluster-manager/bot.py @@ -11,7 +11,6 @@ DISCORD_DEBUG_TOKEN, DISCORD_CLUSTER_STAGING_ID, DISCORD_DEBUG_CLUSTER_STAGING_ID, - POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, @@ -45,7 +44,11 @@ def __init__(self, debug_mode=False): self.tree.add_command(self.leaderboard_group) self.leaderboard_db = LeaderboardDB( - POSTGRES_HOST, POSTGRES_DATABASE, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_PORT + POSTGRES_HOST, + POSTGRES_DATABASE, + POSTGRES_USER, + POSTGRES_PASSWORD, + POSTGRES_PORT, ) async def setup_hook(self): @@ -114,7 +117,7 @@ async def send_chunked_message( for i, chunk in enumerate(chunks): if code_block: await channel.send( - f"```\nOutput (part {i+1}/{len(chunks)}):\n{chunk}\n```" + f"```\nOutput (part {i + 1}/{len(chunks)}):\n{chunk}\n```" ) else: await channel.send(chunk) diff --git a/src/discord-cluster-manager/cogs/leaderboard_cog.py b/src/discord-cluster-manager/cogs/leaderboard_cog.py index c8b5b4f..b4f5a8d 100644 --- a/src/discord-cluster-manager/cogs/leaderboard_cog.py +++ b/src/discord-cluster-manager/cogs/leaderboard_cog.py @@ -6,12 +6,15 @@ from typing import TYPE_CHECKING from consts import GitHubGPU, ModalGPU +import random + if TYPE_CHECKING: from bot import ClusterBot class LeaderboardSubmitCog(app_commands.Group): - def __init__(self): + def __init__(self, bot): + self.bot: ClusterBot = bot super().__init__(name="submit", description="Submit leaderboard data") # Parent command that defines global options @@ -49,9 +52,32 @@ async def modal( dtype: app_commands.Choice[str] = "fp32", shape: app_commands.Choice[str] = None, ): - await interaction.response.send_message( - f"Submitted modal data: GPU Type={gpu_type}" - ) + try: + # Read the template file + submission_content = await script.read() + + # Compute eval or submission score, call runner here. + score = random.random() + + with self.bot.leaderboard_db as db: + db.create_submission({ + "submission_name": script.filename, + "submission_time": datetime.now(), + "leaderboard_name": leaderboard_name, + "code": submission_content, + "user_id": interaction.user.id, + "submission_score": score, + }) + + await interaction.response.send_message( + f"Leaderboard '{leaderboard_name}'. Submission title: {script.filename}. Submission user: {interaction.user.id}. Runtime: {score} ms", + ephemeral=True, + ) + except ValueError: + await interaction.response.send_message( + "Invalid date format. Please use YYYY-MM-DD or YYYY-MM-DD HH:MM", + ephemeral=True, + ) ### GITHUB SUBCOMMAND @app_commands.command( @@ -93,7 +119,7 @@ def __init__(self, bot): # name="submit", description="Submit a file to the leaderboard" # )(self.leaderboard_submit) - bot.leaderboard_group.add_command(LeaderboardSubmitCog()) + bot.leaderboard_group.add_command(LeaderboardSubmitCog(bot)) self.get_leaderboard_submissions = bot.leaderboard_group.command( name="submissions", description="Get all submissions for a leaderboard" @@ -146,6 +172,11 @@ async def leaderboard_create( template_content = await reference_code.read() with self.bot.leaderboard_db as db: + print( + leaderboard_name, + type(date_value), + type(template_content.decode("utf-8")), + ) db.create_leaderboard({ "name": leaderboard_name, "deadline": date_value, @@ -177,7 +208,8 @@ async def get_leaderboard_submissions( ) return - submissions = db.get_leaderboard_submissions(leaderboard_id) # Add dtype + # submissions = db.get_leaderboard_submissions(leaderboard_id) # Add dtype + submissions = db.get_leaderboard_submissions(leaderboard_name) # Add dtype if not submissions: await interaction.response.send_message( @@ -192,8 +224,8 @@ async def get_leaderboard_submissions( for submission in submissions: embed.add_field( - name=f"{submission['user_id']}: submission['submission_name']", - value=f"Submission time: {submission['submission_time']}", + name=f"{submission['user_id']}: {submission['submission_name']}", + value=f"Submission speed: {submission['submission_score']}", inline=False, ) diff --git a/src/discord-cluster-manager/leaderboard_db.py b/src/discord-cluster-manager/leaderboard_db.py index 4d00b9e..1493580 100644 --- a/src/discord-cluster-manager/leaderboard_db.py +++ b/src/discord-cluster-manager/leaderboard_db.py @@ -2,6 +2,13 @@ from psycopg2 import Error from typing import Optional from utils import LeaderboardItem, SubmissionItem +from consts import ( + POSTGRES_USER, + POSTGRES_PASSWORD, + POSTGRES_HOST, + POSTGRES_PORT, + POSTGRES_DATABASE, +) class LeaderboardDB: @@ -42,27 +49,38 @@ def _create_tables(self): create_table_query = """ CREATE TABLE IF NOT EXISTS leaderboard ( id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, + name TEXT UNIQUE NOT NULL, deadline TIMESTAMP NOT NULL, - template_code TEXT NOT NULL + reference_code TEXT NOT NULL ); """ create_submission_table_query = """ CREATE TABLE IF NOT EXISTS submissions ( - id SERIAL PRIMARY KEY, - leaderboard_id BIGINT NOT NULL, + submission_id SERIAL PRIMARY KEY, + leaderboard_id TEXT NOT NULL, submission_name VARCHAR(255) NOT NULL, - user_id BIGINT NOT NULL, - submission_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + user_id TEXT NOT NULL, code TEXT NOT NULL, - FOREIGN KEY (leaderboard_id) REFERENCES leaderboard(id) + submission_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + submission_score DOUBLE PRECISION NOT NULL, + FOREIGN KEY (leaderboard_id) REFERENCES leaderboard(name) + ); + """ + + create_run_information_table_query = """ + CREATE TABLE IF NOT EXISTS runinfo ( + submission_id INTEGER NOT NULL, + stdout TEXT, + ncu_output TEXT, + FOREIGN KEY (submission_id) REFERENCES submissions(submission_id) ); """ try: self.cursor.execute(create_table_query) self.cursor.execute(create_submission_table_query) + self.cursor.execute(create_run_information_table_query) self.connection.commit() except Error as e: print(f"Error creating table: {e}") @@ -77,38 +95,71 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.disconnect() def create_leaderboard(self, leaderboard: LeaderboardItem): - self.cursor.execute( - """ - INSERT INTO leaderboard (name, deadline, template_code) - VALUES (%s, %s, %s) - """, - ( - leaderboard["name"], - leaderboard["deadline"], - leaderboard["template_code"], - ), - ) - self.connection.commit() + try: + self.cursor.execute( + """ + INSERT INTO leaderboard (name, deadline, reference_code) + VALUES (%s, %s, %s) + """, + ( + leaderboard["name"], + leaderboard["deadline"], + leaderboard["reference_code"], + ), + ) + self.connection.commit() + except psycopg2.Error as e: + print(f"Error during leaderboard creation: {e}") + self.connection.rollback() # Ensure rollback if error occurs + + def create_submission(self, submission: SubmissionItem): + try: + self.cursor.execute( + """ + INSERT INTO submissions (submission_name, submission_time, leaderboard_id, code, user_id, submission_score) + VALUES (%s, %s, %s, %s, %s, %s) + """, + ( + submission["submission_name"], + submission["submission_time"], + submission["leaderboard_name"], + submission["code"], + submission["user_id"], + submission["submission_score"], + ), + ) + self.connection.commit() + except psycopg2.Error as e: + print(f"Error during leaderboard submission: {e}") + self.connection.rollback() # Ensure rollback if error occurs def get_leaderboards(self) -> list[LeaderboardItem]: self.cursor.execute("SELECT * FROM leaderboard") return [ - LeaderboardItem(id=lb[0], name=lb[1], deadline=lb[2], template_code=lb[3]) + LeaderboardItem(id=lb[0], name=lb[1], deadline=lb[2], reference_code=lb[3]) for lb in self.cursor.fetchall() ] - def get_leaderboard_submissions(self, leaderboard_id: int) -> list[SubmissionItem]: + def get_leaderboard_submissions( + self, leaderboard_name: str + ) -> list[SubmissionItem]: + """ + TODO: Change these all to be IDs instead + """ self.cursor.execute( - "SELECT * FROM submissions WHERE leaderboard_id = %s", (leaderboard_id,) + "SELECT * FROM submissions WHERE leaderboard_id = %s ORDER BY submission_score ASC", + (leaderboard_name,), ) return [ SubmissionItem( + leaderboard_name=submission[1], submission_name=submission[2], - submission_time=submission[3], + user_id=submission[3], code=submission[4], - user_id=submission[5], + submission_time=submission[5], + submission_score=submission[6], ) for submission in self.cursor.fetchall() ] @@ -119,3 +170,23 @@ def get_leaderboard_id(self, leaderboard_name: str) -> int | None: res = self.cursor.fetchone() return res[0] if res else None + + +if __name__ == "__main__": + print( + POSTGRES_HOST, + POSTGRES_DATABASE, + POSTGRES_USER, + POSTGRES_PASSWORD, + POSTGRES_PORT, + ) + + leaderboard_db = LeaderboardDB( + POSTGRES_HOST, + POSTGRES_DATABASE, + POSTGRES_USER, + POSTGRES_PASSWORD, + POSTGRES_PORT, + ) + leaderboard_db.connect() + leaderboard_db.disconnect() diff --git a/src/discord-cluster-manager/utils.py b/src/discord-cluster-manager/utils.py index 88b4a75..278ba98 100644 --- a/src/discord-cluster-manager/utils.py +++ b/src/discord-cluster-manager/utils.py @@ -40,11 +40,19 @@ def get_github_branch_name(): class LeaderboardItem(TypedDict): name: str deadline: datetime.datetime - template_code: str + reference_code: str class SubmissionItem(TypedDict): submission_name: str submission_time: datetime.datetime + submission_score: float + leaderboard_name: str code: str user_id: int + + +class ProfilingItem(TypedDict): + submission_name: str + ncu_output: str + stdout: str From 5f26055af80a79ad91f259d033554211abfbbcf4 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Fri, 6 Dec 2024 01:17:55 -0500 Subject: [PATCH 8/9] change runtimes to show --- src/discord-cluster-manager/cogs/leaderboard_cog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/discord-cluster-manager/cogs/leaderboard_cog.py b/src/discord-cluster-manager/cogs/leaderboard_cog.py index b4f5a8d..b6fea1c 100644 --- a/src/discord-cluster-manager/cogs/leaderboard_cog.py +++ b/src/discord-cluster-manager/cogs/leaderboard_cog.py @@ -121,9 +121,9 @@ def __init__(self, bot): bot.leaderboard_group.add_command(LeaderboardSubmitCog(bot)) - self.get_leaderboard_submissions = bot.leaderboard_group.command( + self.get_leaderboard_show = bot.leaderboard_group.command( name="submissions", description="Get all submissions for a leaderboard" - )(self.get_leaderboard_submissions) + )(self.get_leaderboard_show) async def get_leaderboards(self, interaction: discord.Interaction): """Display all leaderboards in a table format""" @@ -194,7 +194,7 @@ async def leaderboard_create( ) @discord.app_commands.describe(leaderboard_name="Name of the leaderboard") - async def get_leaderboard_submissions( + async def get_leaderboard_show( self, interaction: discord.Interaction, leaderboard_name: str, From 320ba3313941245e1b84683a9e761ccbecb6da41 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Fri, 6 Dec 2024 01:20:47 -0500 Subject: [PATCH 9/9] change runtimes to show --- .../cogs/leaderboard_cog.py | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/discord-cluster-manager/cogs/leaderboard_cog.py b/src/discord-cluster-manager/cogs/leaderboard_cog.py index b6fea1c..4e88789 100644 --- a/src/discord-cluster-manager/cogs/leaderboard_cog.py +++ b/src/discord-cluster-manager/cogs/leaderboard_cog.py @@ -24,6 +24,8 @@ def __init__(self, bot): dtype="dtype (e.g. FP32, BF16, FP4) that the input and output expects.", shape="Data input shape as a tuple", ) + # TODO: Modularize this so all the write functionality is in here. Haven't figured + # a good way to do this yet. async def submit( self, interaction: discord.Interaction, @@ -70,7 +72,7 @@ async def modal( }) await interaction.response.send_message( - f"Leaderboard '{leaderboard_name}'. Submission title: {script.filename}. Submission user: {interaction.user.id}. Runtime: {score} ms", + f"Ran on Modal. Leaderboard '{leaderboard_name}'. Submission title: {script.filename}. Submission user: {interaction.user.id}. Runtime: {score} ms", ephemeral=True, ) except ValueError: @@ -100,9 +102,32 @@ async def github( dtype: app_commands.Choice[str] = "fp32", shape: app_commands.Choice[str] = None, ): - await interaction.response.send_message( - f"Submitted GitHub data: GPU Type={gpu_type}" - ) + try: + # Read the template file + submission_content = await script.read() + + # Compute eval or submission score, call runner here. + score = random.random() + + with self.bot.leaderboard_db as db: + db.create_submission({ + "submission_name": script.filename, + "submission_time": datetime.now(), + "leaderboard_name": leaderboard_name, + "code": submission_content, + "user_id": interaction.user.id, + "submission_score": score, + }) + + await interaction.response.send_message( + f"Ran on GH. Leaderboard '{leaderboard_name}'. Submission title: {script.filename}. Submission user: {interaction.user.id}. Runtime: {score} ms", + ephemeral=True, + ) + except ValueError: + await interaction.response.send_message( + "Invalid date format. Please use YYYY-MM-DD or YYYY-MM-DD HH:MM", + ephemeral=True, + ) class LeaderboardCog(commands.Cog): @@ -121,9 +146,9 @@ def __init__(self, bot): bot.leaderboard_group.add_command(LeaderboardSubmitCog(bot)) - self.get_leaderboard_show = bot.leaderboard_group.command( - name="submissions", description="Get all submissions for a leaderboard" - )(self.get_leaderboard_show) + self.get_leaderboard_submissions = bot.leaderboard_group.command( + name="show", description="Get all submissions for a leaderboard" + )(self.get_leaderboard_submissions) async def get_leaderboards(self, interaction: discord.Interaction): """Display all leaderboards in a table format""" @@ -194,7 +219,7 @@ async def leaderboard_create( ) @discord.app_commands.describe(leaderboard_name="Name of the leaderboard") - async def get_leaderboard_show( + async def get_leaderboard_submissions( self, interaction: discord.Interaction, leaderboard_name: str,