Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec84fa5687 | ||
|
|
5b078c4a56 | ||
|
|
226d7c77d7 | ||
|
|
4bb7a925e5 | ||
|
|
7ef8768634 |
+15
-154
@@ -2,6 +2,7 @@
|
||||
Contains classes and enum for tracking data on submissions.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
|
||||
from nextcord import Embed
|
||||
@@ -10,8 +11,10 @@ from sqlalchemy.orm import Session
|
||||
|
||||
# pylint: disable=import-error
|
||||
from utilities.sql_data_classes import Category, CategoryGroup, engine
|
||||
from utilities.utils import score_float_to_str
|
||||
|
||||
|
||||
# TODO use the fancy enums
|
||||
class SubmissionState(Enum):
|
||||
"""
|
||||
Enum for which state a submission is in
|
||||
@@ -25,158 +28,23 @@ class SubmissionState(Enum):
|
||||
VERIFY = 6
|
||||
|
||||
|
||||
# TODO remake into a data class
|
||||
@dataclass
|
||||
class Submission:
|
||||
"""
|
||||
Class for an individual submission. Contains submission information such as
|
||||
guild id, message id, table, category, username, score and submission state.
|
||||
"""
|
||||
|
||||
def __init__(self, guild_id: int, group_id: int, username: str) -> None:
|
||||
"""
|
||||
Default constructor for Submission. Just need to know guild_id, table, and username.
|
||||
Score and category are defaulted at empty, message id is 0, and submission state
|
||||
is SubmissionState.CATEGORY.
|
||||
"""
|
||||
self._category_id: int = 0
|
||||
self._guild_id: int = guild_id
|
||||
self._message_id: int = 0
|
||||
self._score: str = ""
|
||||
self._submission_state: SubmissionState = SubmissionState.CATEGORY
|
||||
self._group_id: int = group_id
|
||||
self._username: str = username
|
||||
category_id: int = field(init=False, default=0)
|
||||
group_id: int
|
||||
guild_id: int
|
||||
is_time_record: bool = field(init=False, default=False)
|
||||
message_id: int = field(init=False, default=0)
|
||||
score: float = field(init=False, default=0.0)
|
||||
submission_state: SubmissionState = field(init=False, default=SubmissionState.CATEGORY)
|
||||
username: str
|
||||
|
||||
@property
|
||||
def category_id(self) -> int:
|
||||
"""
|
||||
Getter for Submission's Category id.
|
||||
|
||||
Returns:
|
||||
int: Category id.
|
||||
"""
|
||||
return self._category_id
|
||||
|
||||
@category_id.setter
|
||||
def category_id(self, category: int) -> None:
|
||||
"""
|
||||
Setter for Submission's Category id.
|
||||
|
||||
Args:
|
||||
category (int): Category id
|
||||
"""
|
||||
self._category_id = category
|
||||
|
||||
@property
|
||||
def guild_id(self) -> int:
|
||||
"""
|
||||
Getter for guild id Submission is happening in. Will not change.
|
||||
|
||||
Returns:
|
||||
int: guild id
|
||||
"""
|
||||
return self._guild_id
|
||||
|
||||
@property
|
||||
def message_id(self) -> int:
|
||||
"""
|
||||
Getter for message id that Submission has a view on.
|
||||
|
||||
Returns:
|
||||
int: message id
|
||||
"""
|
||||
return self._message_id
|
||||
|
||||
@message_id.setter
|
||||
def message_id(self, m_id: int) -> None:
|
||||
"""
|
||||
Setter for message id that Submission has a view on.
|
||||
|
||||
Args:
|
||||
m_id (int): message id
|
||||
"""
|
||||
self._message_id = m_id
|
||||
|
||||
@property
|
||||
def score(self) -> str:
|
||||
"""
|
||||
Getter for score.
|
||||
|
||||
Returns:
|
||||
str: score
|
||||
"""
|
||||
return self._score
|
||||
|
||||
@score.setter
|
||||
def score(self, score: str) -> None:
|
||||
"""
|
||||
Setter for score.
|
||||
|
||||
Args:
|
||||
score (str): score
|
||||
"""
|
||||
self._score = score
|
||||
|
||||
@property
|
||||
def submission_state(self) -> SubmissionState:
|
||||
"""
|
||||
Getter for state Submission is in.
|
||||
|
||||
Returns:
|
||||
SubmissionState: State the Submission is in
|
||||
"""
|
||||
return self._submission_state
|
||||
|
||||
@submission_state.setter
|
||||
def submission_state(self, state: SubmissionState) -> None:
|
||||
"""
|
||||
Setter for state Submission is in.
|
||||
|
||||
Args:
|
||||
state (SubmissionState): State to change to
|
||||
"""
|
||||
self._submission_state = state
|
||||
|
||||
@property
|
||||
def group_id(self) -> str:
|
||||
"""
|
||||
Getter for group Submission is for.
|
||||
|
||||
Returns:
|
||||
str: group id
|
||||
"""
|
||||
return self._group_id
|
||||
|
||||
@group_id.setter
|
||||
def group_id(self, table: str) -> None:
|
||||
"""
|
||||
Getter for table Submission is for.
|
||||
|
||||
Args:
|
||||
table (str): group id
|
||||
"""
|
||||
self._group_id = table
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
"""
|
||||
Getter for Submission owner's username.
|
||||
|
||||
Returns:
|
||||
str: Submission owner's username
|
||||
"""
|
||||
return self._username
|
||||
|
||||
@username.setter
|
||||
def username(self, username: str) -> None:
|
||||
"""
|
||||
Setter for Submission owner's username.
|
||||
|
||||
Args:
|
||||
username (str): Submission owner's username
|
||||
"""
|
||||
self._username = username
|
||||
|
||||
def get_embeds(self) -> "list[Embed]":
|
||||
def get_embed(self) -> Embed:
|
||||
"""Creates an embed based off of the submission state and fields filled out."""
|
||||
state: SubmissionState = self.submission_state
|
||||
|
||||
@@ -210,16 +78,9 @@ class Submission:
|
||||
)
|
||||
embed.add_field(name="Category", value=session.scalar(stmt), inline=False)
|
||||
if self.score:
|
||||
embed.add_field(name="Score", value=self.score, inline=False)
|
||||
embed.add_field(name="Score", value=score_float_to_str(self.score, self.is_time_record), inline=False)
|
||||
|
||||
embeds = [embed]
|
||||
|
||||
if state is SubmissionState.VERIFY:
|
||||
embed = Embed(title="Bot Information", description="Please disregard.")
|
||||
embed.add_field(name="GroupId", value=self.group_id)
|
||||
embed.add_field(name="CategoryId", value=self.category_id)
|
||||
embeds.append(embed)
|
||||
return embeds
|
||||
return embed
|
||||
|
||||
|
||||
class SubmissionMap:
|
||||
|
||||
@@ -12,7 +12,8 @@ from sqlalchemy.orm import Session
|
||||
# pylint: disable=import-error
|
||||
from classes.submission import SubmissionState
|
||||
from highscores import submission_objects
|
||||
from utilities.sql_data_classes import Category, Guild, engine
|
||||
from utilities.sql_data_classes import Guild, engine
|
||||
from utilities.utils import score_str_to_float
|
||||
|
||||
|
||||
class SubmissionListener(commands.Cog, name="Message Listener"):
|
||||
@@ -41,60 +42,42 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
|
||||
await self.bot.get_guild(guild_id).get_channel(submission_channel_id).fetch_message(message_id)
|
||||
)
|
||||
|
||||
# need check if correct user and correct channel
|
||||
if message.channel.id == submission_channel_id:
|
||||
state = submission.submission_state
|
||||
if state == SubmissionState.SCORE and message.content:
|
||||
# need check if correct user and correct channel
|
||||
if message.channel.id == submission_channel_id:
|
||||
state = submission.submission_state
|
||||
if state == SubmissionState.SCORE and message.content:
|
||||
# check with regex if score matches correctly
|
||||
is_time_record = submission.is_time_record
|
||||
|
||||
stmt = select(Category.IsTimeRecord).where(Category.CategoryId == submission.category_id)
|
||||
|
||||
# check with regex if score matches correctly
|
||||
is_time_record = session.scalar(stmt)
|
||||
|
||||
time_regex = re.search("^([0-9]{1,2}:[0-9]|[0-9])?[0-9](\\.[0-9])?$", message.content)
|
||||
int_regex = re.search("^[0-9]+$", message.content)
|
||||
if time_regex is None and is_time_record:
|
||||
embeds = submission_message.embeds[0]
|
||||
embeds.add_field(
|
||||
name="Error",
|
||||
value="Time record must follow format MM:SS.ms, SS.ms, or SS.",
|
||||
)
|
||||
elif int_regex is None and not is_time_record:
|
||||
embeds = submission_message.embeds[0]
|
||||
embeds.add_field(name="Error", value="Number record must only be number.")
|
||||
else:
|
||||
score = message.content
|
||||
if is_time_record:
|
||||
# standardize time score format.
|
||||
if re.search("^[1-9]?[0-9]:[0-9]{2}(\\.[0-9])?$", score):
|
||||
# score is already fine.
|
||||
pass
|
||||
elif re.search("^[0-9]{2}(\\.[0-9])?$", score):
|
||||
# need to add leading colon and 0
|
||||
score = "0:" + score
|
||||
elif re.search("^[0-9](\\.[0-9])?$", score):
|
||||
# need to add 0 in seconds and leading colon and 0
|
||||
score = "0:0" + score
|
||||
elif re.search("^[0]?[0-9]:[0-9]{2}(\\.[0-9])?$", score):
|
||||
# if there is a minute 10ths place 0, remove
|
||||
score = score.lstrip("0")
|
||||
submission.score = score
|
||||
embeds = submission.get_embeds()
|
||||
await submission_message.edit(
|
||||
embeds=embeds,
|
||||
)
|
||||
elif state == SubmissionState.PROOF and len(message.attachments) > 0:
|
||||
await submission_message.edit(
|
||||
files=[await attch.to_file() for attch in message.attachments],
|
||||
)
|
||||
elif state == SubmissionState.NAME and message.content:
|
||||
submission.username = message.content
|
||||
embeds = submission.get_embeds()
|
||||
await submission_message.edit(
|
||||
embeds=embeds,
|
||||
time_regex = re.search("^([0-9]{1,2}:[0-9]|[0-9])?[0-9](\\.[0-9])?$", message.content)
|
||||
int_regex = re.search("^[0-9]+$", message.content)
|
||||
if time_regex is None and is_time_record:
|
||||
embed = submission_message.embeds[0]
|
||||
embed.add_field(
|
||||
name="Error",
|
||||
value="Time record must follow format MM:SS.ms, SS.ms, or SS.",
|
||||
)
|
||||
elif int_regex is None and not is_time_record:
|
||||
embed = submission_message.embeds[0]
|
||||
embed.add_field(name="Error", value="Number record must only be number.")
|
||||
else:
|
||||
submission.score = score_str_to_float(message.content, is_time_record)
|
||||
embed = submission.get_embed()
|
||||
await submission_message.edit(
|
||||
embed=embed,
|
||||
)
|
||||
elif state == SubmissionState.PROOF and len(message.attachments) > 0:
|
||||
await submission_message.edit(
|
||||
files=[await attch.to_file() for attch in message.attachments],
|
||||
)
|
||||
elif state == SubmissionState.NAME and message.content:
|
||||
submission.username = message.content
|
||||
embed = submission.get_embed()
|
||||
await submission_message.edit(
|
||||
embed=embed,
|
||||
)
|
||||
|
||||
await message.delete()
|
||||
await message.delete()
|
||||
|
||||
|
||||
# This function will be called when this extension is loaded.
|
||||
|
||||
@@ -10,7 +10,7 @@ from sqlalchemy.orm import Session, aliased
|
||||
# pylint: disable=import-error
|
||||
from classes.submission import Submission, SubmissionState
|
||||
from highscores import submission_objects
|
||||
from utilities.sql_data_classes import Category, CategoryGroup, Guild, engine
|
||||
from utilities.sql_data_classes import Category, CategoryGroup, Guild, Submissions, engine
|
||||
|
||||
from .verification_view import VerificationView
|
||||
|
||||
@@ -30,7 +30,7 @@ def category_select_options(group_id: int) -> "list[nextcord.SelectOption]":
|
||||
return options
|
||||
|
||||
|
||||
############ Items used in submission process ############
|
||||
# Items used in submission process ########################
|
||||
|
||||
|
||||
class SubmissionDropdown(nextcord.ui.Select):
|
||||
@@ -47,9 +47,14 @@ class SubmissionDropdown(nextcord.ui.Select):
|
||||
async def callback(self, interaction: nextcord.Interaction):
|
||||
submission = submission_objects.get_user_submission(interaction.user.id)
|
||||
submission.category_id = self.values[0]
|
||||
embeds = submission.get_embeds()
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(Category.IsTimeRecord).where(Category.CategoryId == submission.category_id)
|
||||
submission.is_time_record = session.scalar(stmt)
|
||||
|
||||
embed = submission.get_embed()
|
||||
await interaction.message.edit(
|
||||
embeds=embeds,
|
||||
embed=embed,
|
||||
)
|
||||
|
||||
|
||||
@@ -100,8 +105,8 @@ class NextButton(nextcord.ui.Button):
|
||||
else:
|
||||
state = SubmissionState(state.value + 1)
|
||||
submission.submission_state = state
|
||||
embeds = submission.get_embeds()
|
||||
await interaction.edit(embeds=embeds, view=SubmissionView(self._bot, interaction.user.id))
|
||||
embed = submission.get_embed()
|
||||
await interaction.edit(embed=embed, view=SubmissionView(self._bot, interaction.user.id))
|
||||
|
||||
|
||||
class SubmitButton(nextcord.ui.Button):
|
||||
@@ -116,27 +121,40 @@ class SubmitButton(nextcord.ui.Button):
|
||||
stmt = select(Guild.VerificationChannel).where(Guild.GuildId == interaction.guild_id)
|
||||
verification_channel_id = session.scalar(stmt)
|
||||
channel = interaction.guild.get_channel(verification_channel_id)
|
||||
submission = submission_objects.get_user_submission(interaction.user.id)
|
||||
active_submission = submission_objects.get_user_submission(interaction.user.id)
|
||||
|
||||
error_response = "Registered Verification channel does not exist or was never registered. \
|
||||
Contact server admins."
|
||||
assert channel is not None, await channel.send(error_response)
|
||||
|
||||
submission.submission_state = SubmissionState.VERIFY
|
||||
active_submission.submission_state = SubmissionState.VERIFY
|
||||
|
||||
embeds = submission.get_embeds()
|
||||
embed = active_submission.get_embed()
|
||||
|
||||
await channel.send(
|
||||
verification_message = await channel.send(
|
||||
files=[await attch.to_file() for attch in interaction.message.attachments],
|
||||
view=VerificationView(),
|
||||
embeds=embeds,
|
||||
embed=embed,
|
||||
content=f"Submission from <@{interaction.user.id}>.",
|
||||
)
|
||||
await interaction.message.edit(content=f"<@{interaction.user.id}> Submitted!", view=None)
|
||||
|
||||
new_submission = Submissions(
|
||||
CategoryId=active_submission.category_id,
|
||||
GroupId=active_submission.group_id,
|
||||
IsTimeRecord=active_submission.is_time_record,
|
||||
Score=active_submission.score,
|
||||
SubmissionMessageId=interaction.message.id,
|
||||
UserName=active_submission.username,
|
||||
VerificationMessageId=verification_message.id,
|
||||
)
|
||||
submission_objects.remove_user_submission(interaction.user.id)
|
||||
with Session(engine) as session:
|
||||
session.add(new_submission)
|
||||
session.commit()
|
||||
|
||||
|
||||
############ Submission Views ############
|
||||
# Submission Views ########################
|
||||
|
||||
|
||||
class SubmissionCreateButton(nextcord.ui.View):
|
||||
@@ -171,11 +189,11 @@ class SubmissionCreateButton(nextcord.ui.View):
|
||||
# a user should only have 1 submission active at once.
|
||||
# split username on pipe. Decided to store whole username...
|
||||
username: str = interaction.user.display_name.split("|")[0]
|
||||
submission: Submission = Submission(interaction.guild_id, group_id, username)
|
||||
submission: Submission = Submission(group_id, interaction.guild_id, username)
|
||||
|
||||
submission_objects.add_submission(interaction.user.id, submission)
|
||||
|
||||
embeds = submission.get_embeds()
|
||||
embed = submission.get_embed()
|
||||
|
||||
submission_message = await channel.send(
|
||||
f"<@{interaction.user.id}>",
|
||||
@@ -183,7 +201,7 @@ class SubmissionCreateButton(nextcord.ui.View):
|
||||
self._bot,
|
||||
interaction.user.id,
|
||||
),
|
||||
embeds=embeds,
|
||||
embed=embed,
|
||||
)
|
||||
|
||||
# update the message_id once its created
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"""View for verification buttons"""
|
||||
|
||||
import nextcord
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from utilities.sql_data_classes import Submissions, engine
|
||||
|
||||
# pylint: disable=import-error
|
||||
from utilities.utils import submit_score
|
||||
@@ -17,33 +21,34 @@ class VerificationView(nextcord.ui.View):
|
||||
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
|
||||
): # pylint: disable=unused-argument
|
||||
"""Button for denying score submission."""
|
||||
with Session(engine) as session:
|
||||
stmt = select(Submissions).where(Submissions.VerificationMessageId == interaction.message.id)
|
||||
submission = session.scalar(stmt)
|
||||
# let user know submission was denied
|
||||
|
||||
# TODO need to access bot here
|
||||
|
||||
# delete the submission
|
||||
session.delete(stmt)
|
||||
session.commit()
|
||||
await interaction.message.delete()
|
||||
|
||||
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve-button-submission")
|
||||
async def approve_button(
|
||||
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
|
||||
): # pylint: disable=unused-argument
|
||||
"""Button for approving score submission."""
|
||||
for embed in interaction.message.embeds:
|
||||
content = embed.to_dict()
|
||||
|
||||
for field in content["fields"]:
|
||||
if field["name"] == "GroupId":
|
||||
group_id = int(field["value"])
|
||||
elif field["name"] == "CategoryId":
|
||||
category_id = int(field["value"])
|
||||
elif field["name"] == "Score":
|
||||
score = field["value"]
|
||||
elif field["name"] == "User Name":
|
||||
user = field["value"]
|
||||
with Session(engine) as session:
|
||||
stmt = select(Submissions).where(Submissions.VerificationMessageId == interaction.message.id)
|
||||
submission = session.scalar(stmt)
|
||||
|
||||
try:
|
||||
|
||||
await submit_score(interaction, group_id, category_id, user, score)
|
||||
await submit_score(
|
||||
interaction, submission.GroupId, submission.CategoryId, submission.UserName, submission.Score
|
||||
)
|
||||
content = interaction.message.content
|
||||
content += f"\nApproved by <@{interaction.user.id}>."
|
||||
# remove the bot information embed. it should always be the first one.
|
||||
await interaction.message.edit(content=content, view=None, embed=interaction.message.embeds[0])
|
||||
await interaction.message.edit(content=content, view=None)
|
||||
# TODO send message to let user know submission was accepted. Need access to bot here.
|
||||
except UnboundLocalError:
|
||||
await interaction.send("Instruct user to submit with all fields.")
|
||||
await interaction.message.edit(view=None)
|
||||
|
||||
@@ -42,4 +42,15 @@ CREATE TABLE Scores(
|
||||
CategoryId int unsigned not null,
|
||||
FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId) ON DELETE CASCADE,
|
||||
PRIMARY KEY (CategoryId, UserName)
|
||||
);
|
||||
|
||||
CREATE TABLE Submissions(
|
||||
CategoryId int unsigned not null,
|
||||
GroupId int unsigned not null,
|
||||
IsTimeRecord boolean not null,
|
||||
Score float not null,
|
||||
SubmissionMessageId bigint unsigned not null,
|
||||
UserName varchar(30) not null,
|
||||
VerificationMessageId bigint unsigned not null,
|
||||
PRIMARY KEY (CategoryId, GroupId, UserName)
|
||||
);
|
||||
@@ -14,7 +14,8 @@ Base = automap_base()
|
||||
Base.prepare(autoload_with=engine)
|
||||
|
||||
# define the classes
|
||||
Guild = Base.classes.Guilds
|
||||
CategoryGroup = Base.classes.CategoryGroups
|
||||
Category = Base.classes.Categories
|
||||
CategoryGroup = Base.classes.CategoryGroups
|
||||
Guild = Base.classes.Guilds
|
||||
Score = Base.classes.Scores
|
||||
Submissions = Base.classes.Submissions
|
||||
|
||||
+39
-11
@@ -321,6 +321,44 @@ def get_time_str(time_in: float) -> str:
|
||||
return time.strftime(f"%-M:%S.{milliseconds}", time.gmtime(time_in))
|
||||
|
||||
|
||||
def score_float_to_str(score: float, is_time_record: bool) -> str:
|
||||
"""
|
||||
Converts a float representation of a score into a string. Easier to transport the score as one data type.
|
||||
Calls get_time_str if the score is a time record, otherwise just converts the string.
|
||||
|
||||
Args:
|
||||
score (float): float representation of the score.
|
||||
is_time_record (bool): true if the score is a time record.
|
||||
|
||||
Returns:
|
||||
str: string representation of the score.
|
||||
"""
|
||||
if is_time_record:
|
||||
return get_time_str(score)
|
||||
else:
|
||||
return str(score)
|
||||
|
||||
|
||||
def score_str_to_float(score: str, is_time_record: bool) -> float:
|
||||
"""
|
||||
Converts a string representation of score into a float. Easier to transport the score as one data type.
|
||||
Operates differently depending on if the score is a time record or an int.
|
||||
|
||||
Args:
|
||||
score (str): string representation of the score.
|
||||
is_time_record (bool): true if the score is a time record.
|
||||
|
||||
Returns:
|
||||
float: float representation of the score.
|
||||
"""
|
||||
if is_time_record:
|
||||
minutes = float(score.split(":", maxsplit=1)[0])
|
||||
seconds = float(score.split(":", maxsplit=1)[1])
|
||||
return (minutes * 60) + seconds
|
||||
else:
|
||||
return float(score)
|
||||
|
||||
|
||||
async def send_highscore_message(session: Session, guild: nextcord.Guild, group: CategoryGroup) -> nextcord.Message:
|
||||
"""
|
||||
Function takes care of checking if a message exists for a group.
|
||||
@@ -351,7 +389,7 @@ async def send_highscore_message(session: Session, guild: nextcord.Guild, group:
|
||||
|
||||
|
||||
async def submit_score(
|
||||
interaction: nextcord.Interaction, group_id: int, category_id: int, username: str, score: str
|
||||
interaction: nextcord.Interaction, group_id: int, category_id: int, username: str, score: float
|
||||
) -> None:
|
||||
"""
|
||||
Adds passed score to group. Then updates group message by calling send_highscore_message.
|
||||
@@ -366,16 +404,6 @@ async def submit_score(
|
||||
"""
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(Category.IsTimeRecord).where(Category.CategoryId == category_id)
|
||||
is_time_record = session.scalar(stmt)
|
||||
|
||||
if is_time_record:
|
||||
minutes = float(score.split(":", maxsplit=1)[0])
|
||||
seconds = float(score.split(":", maxsplit=1)[1])
|
||||
score = (minutes * 60) + seconds
|
||||
else:
|
||||
score = float(score)
|
||||
|
||||
stmt = select(Score).where(Score.UserName == username).where(Score.CategoryId == category_id)
|
||||
existing_score = session.scalar(stmt)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user