Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9be9279239 | ||
|
|
ffc5c20fec | ||
|
|
65453eb7d6 | ||
|
|
2ad1c808c2 | ||
|
|
3898e5e494 | ||
|
|
b16132d61b | ||
|
|
d3aaf642bd | ||
|
|
1902c486fd | ||
|
|
7e90e7c819 | ||
|
|
279d3f4885 | ||
|
|
2c0f7f91c1 | ||
|
|
d699f8b3e7 | ||
|
|
56ced3f8a2 | ||
|
|
ed4f8681c8 | ||
|
|
d52008c536 | ||
|
|
8d0d0acc3b | ||
|
|
9a1c3db7f9 | ||
|
|
96d78213ff | ||
|
|
cdb54658ee | ||
|
|
4ce37ee3ea | ||
|
|
c62b87c7f7 | ||
|
|
ba04858328 | ||
|
|
a12fca6220 | ||
|
|
136702654e | ||
|
|
23adad403a | ||
|
|
3a72d9f0e7 | ||
|
|
da0acb2827 | ||
|
|
0baffe96f3 |
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Docker
|
- name: Install Docker
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Docker
|
- name: Install Docker
|
||||||
|
|||||||
@@ -125,9 +125,6 @@ dmypy.json
|
|||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# IDE
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
nextcord.log
|
nextcord.log
|
||||||
|
|
||||||
|
|||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"kevinrose.vsc-python-indent",
|
||||||
|
"charliermarsh.ruff",
|
||||||
|
"donjayamanne.python-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: Module",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "main",
|
||||||
|
"justMyCode": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"highscore",
|
||||||
|
"highscores"
|
||||||
|
],
|
||||||
|
"python.analysis.diagnosticSeverityOverrides": {
|
||||||
|
"reportOptionalMemberAccess": false,
|
||||||
|
"reportArgumentType": "information",
|
||||||
|
"reportAttributeAccessIssue": "warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
"""
|
|
||||||
Contains classes and enum for tracking data on submissions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from nextcord import Embed
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
# pylint: disable=import-error
|
|
||||||
from database_setup.sql_data_classes import Category, CategoryGroup, engine
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionState(Enum):
|
|
||||||
"""
|
|
||||||
Enum for which state a submission is in
|
|
||||||
"""
|
|
||||||
|
|
||||||
CATEGORY = 1
|
|
||||||
SCORE = 2
|
|
||||||
PROOF = 3
|
|
||||||
NAME = 4
|
|
||||||
SUBMIT = 5
|
|
||||||
VERIFY = 6
|
|
||||||
|
|
||||||
|
|
||||||
# TODO remake into a data class
|
|
||||||
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
|
|
||||||
|
|
||||||
@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]":
|
|
||||||
"""Creates an embed based off of the submission state and fields filled out."""
|
|
||||||
state: SubmissionState = self.submission_state
|
|
||||||
|
|
||||||
if state is SubmissionState.CATEGORY:
|
|
||||||
description = "Step 1: Choose your category!"
|
|
||||||
elif state is SubmissionState.SCORE:
|
|
||||||
description = "Step 2: Send a message to input your score!"
|
|
||||||
elif state is SubmissionState.PROOF:
|
|
||||||
description = "Step 3: Send a message with a picture attached to submit your proof!"
|
|
||||||
elif state is SubmissionState.NAME:
|
|
||||||
description = (
|
|
||||||
"Step 4 (Optional): Send a message to change name on submission! (proceed to next if skipping)"
|
|
||||||
)
|
|
||||||
elif state is SubmissionState.SUBMIT:
|
|
||||||
description = "Step 5: Submit if everything looks correct!"
|
|
||||||
elif state is SubmissionState.VERIFY:
|
|
||||||
description = "Review fields and accept."
|
|
||||||
|
|
||||||
embed = Embed(title="Highscores Submission", description=description)
|
|
||||||
embed.add_field(name="User Name", value=self.username, inline=False)
|
|
||||||
stmt = (
|
|
||||||
select(CategoryGroup.GroupName)
|
|
||||||
.where(CategoryGroup.GuildId == self.guild_id)
|
|
||||||
.where(CategoryGroup.GroupId == self.group_id)
|
|
||||||
)
|
|
||||||
with Session(engine) as session:
|
|
||||||
embed.add_field(name="Group", value=session.scalar(stmt), inline=False)
|
|
||||||
if self.category_id:
|
|
||||||
stmt = (
|
|
||||||
select(Category.CategoryName)
|
|
||||||
.where(Category.GroupId == self.group_id)
|
|
||||||
.where(Category.CategoryId == self.category_id)
|
|
||||||
)
|
|
||||||
embed.add_field(name="Category", value=session.scalar(stmt), inline=False)
|
|
||||||
if self.score:
|
|
||||||
embed.add_field(name="Score", value=self.score, 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
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionMap:
|
|
||||||
"""
|
|
||||||
Tracks all active submission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
"""
|
|
||||||
Default constructor for SubmissionMap. Created empty map.
|
|
||||||
"""
|
|
||||||
self._submission_map: dict = {}
|
|
||||||
|
|
||||||
def add_submission(self, user_id: int, submission: Submission) -> None:
|
|
||||||
"""
|
|
||||||
Adds new submission to submission map. User id is used as a key, a user should only
|
|
||||||
have one submission at a time.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user_id (int): User id of submitter. Unique, used as key.
|
|
||||||
submission (Submission): Submission object
|
|
||||||
"""
|
|
||||||
self._submission_map[user_id] = submission
|
|
||||||
|
|
||||||
def get_user_submission(self, user_id: int) -> Submission:
|
|
||||||
"""
|
|
||||||
Getter for a user's Submission.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user_id (int): user id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Submission: user's submission
|
|
||||||
"""
|
|
||||||
return self._submission_map[user_id]
|
|
||||||
|
|
||||||
def has_user(self, user_id: int) -> bool:
|
|
||||||
"""
|
|
||||||
Tells if user has an active submission in submission map.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user_id (int): user id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: bool indicating if user id is in submission map
|
|
||||||
"""
|
|
||||||
return user_id in self._submission_map
|
|
||||||
|
|
||||||
def remove_user_submission(self, user_id: int) -> None:
|
|
||||||
"""
|
|
||||||
Removes a user's Submission. Use when Submission is complete.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user_id (int): user id to identify user's Submission
|
|
||||||
"""
|
|
||||||
self._submission_map.pop(user_id)
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Cog loader. Adds all command cogs to the bot."""
|
||||||
Cog loader. Adds all command cogs to the bot.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
|
|
||||||
@@ -9,20 +7,20 @@ from .delete import DeleteCog
|
|||||||
from .help import HelpCog
|
from .help import HelpCog
|
||||||
from .refresh import RefreshCog
|
from .refresh import RefreshCog
|
||||||
from .set import SetCog
|
from .set import SetCog
|
||||||
|
from .submission import SubmissionCog
|
||||||
from .update import UpdateCog
|
from .update import UpdateCog
|
||||||
from .view import ViewCog
|
from .view import ViewCog
|
||||||
|
|
||||||
|
|
||||||
# This function will be called when this extension is loaded.
|
# This function will be called when this extension is loaded.
|
||||||
# It is necessary to add these functions to the bot.
|
# It is necessary to add these functions to the bot.
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot) -> None:
|
||||||
"""
|
"""Adds all command classes to bot."""
|
||||||
Adds all command classes to bot.
|
|
||||||
"""
|
|
||||||
bot.add_cog(CreateCog(bot))
|
bot.add_cog(CreateCog(bot))
|
||||||
bot.add_cog(DeleteCog())
|
bot.add_cog(DeleteCog())
|
||||||
bot.add_cog(HelpCog())
|
bot.add_cog(HelpCog())
|
||||||
bot.add_cog(RefreshCog(bot))
|
bot.add_cog(RefreshCog(bot))
|
||||||
bot.add_cog(SetCog())
|
bot.add_cog(SetCog())
|
||||||
|
bot.add_cog(SubmissionCog())
|
||||||
bot.add_cog(UpdateCog(bot))
|
bot.add_cog(UpdateCog(bot))
|
||||||
bot.add_cog(ViewCog())
|
bot.add_cog(ViewCog())
|
||||||
|
|||||||
+16
-27
@@ -1,48 +1,37 @@
|
|||||||
"""
|
"""Commands for creating items in bot."""
|
||||||
Commands for creating items in bot.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
from sqlalchemy import exc, select
|
from sqlalchemy import exc, select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
# pylint: disable=import-error
|
|
||||||
from cogs.submission.submission_views import SubmissionCreateButton
|
from cogs.submission.submission_views import SubmissionCreateButton
|
||||||
from database_setup.sql_data_classes import Category, CategoryGroup, engine
|
from database_setup.sql_data_classes import Category, CategoryGroup, engine
|
||||||
from utilities.utils import MAX_LINE_LENGTH, autofill_groups, send_highscore_message
|
from utilities.utils import MAX_LINE_LENGTH, autofill_groups, send_highscore_message
|
||||||
|
|
||||||
|
|
||||||
class CreateCog(commands.Cog, name="Create Cog"):
|
class CreateCog(commands.Cog, name="Create Cog"):
|
||||||
"""
|
"""Class holds commands for creating things."""
|
||||||
Class holds commands for creating things.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot) -> None:
|
def __init__(self, bot: commands.Bot) -> None:
|
||||||
"""
|
"""Default constructor for CreateCog class."""
|
||||||
Default constructor for CreateCog class.
|
|
||||||
"""
|
|
||||||
self._bot = bot
|
self._bot = bot
|
||||||
|
|
||||||
@nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True))
|
@nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True))
|
||||||
async def create(self, interaction: nextcord.Interaction):
|
async def create(self, interaction: nextcord.Interaction) -> None:
|
||||||
"""
|
"""Parent slash command for creating things. Never called."""
|
||||||
Parent slash command for creating things. Never called.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# need all these arguments because discord command.
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
@create.subcommand(description="Create a new Category. Must not exist already in Group.")
|
@create.subcommand(description="Create a new Category. Must not exist already in Group.")
|
||||||
async def category(
|
async def category( # noqa: PLR0913 need all these arguments because discord command.
|
||||||
self,
|
self,
|
||||||
interaction: nextcord.Interaction,
|
interaction: nextcord.Interaction,
|
||||||
group_id: str = nextcord.SlashOption(name="group", description="Group to add Category to.", required=True),
|
group_id: str = nextcord.SlashOption(name="group", description="Group to add Category to.", required=True),
|
||||||
category_name: str = nextcord.SlashOption(description="Name of new Category.", required=True),
|
category_name: str = nextcord.SlashOption(description="Name of new Category.", required=True),
|
||||||
is_ascending: bool = nextcord.SlashOption(description="If lower value is a better score.", required=True),
|
is_ascending: bool = nextcord.SlashOption(description="If lower value is a better score.", required=True), # noqa: FBT001
|
||||||
is_time_record: bool = nextcord.SlashOption(
|
is_time_record: bool = nextcord.SlashOption( # noqa: FBT001
|
||||||
description="If the score should be shown in time format. Cannot be changed.", required=True
|
description="If the score should be shown in time format. Cannot be changed.", required=True
|
||||||
),
|
),
|
||||||
is_validated: bool = nextcord.SlashOption(
|
is_validated: bool = nextcord.SlashOption( # noqa: FBT001
|
||||||
description="If scores need to be validated before posted.", required=True
|
description="If scores need to be validated before posted.", required=True
|
||||||
),
|
),
|
||||||
size: int = nextcord.SlashOption(
|
size: int = nextcord.SlashOption(
|
||||||
@@ -50,8 +39,9 @@ class CreateCog(commands.Cog, name="Create Cog"):
|
|||||||
),
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a new category to be placed into group_id group. Fails if the category name already exists within the
|
Creates a new category to be placed into group_id group.
|
||||||
group. Then calls send_highscore_message().
|
|
||||||
|
Fails if the category name already exists within the group. Then calls send_highscore_message().
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (nextcord.Interaction): Interaction for the command.
|
interaction (nextcord.Interaction): Interaction for the command.
|
||||||
@@ -89,8 +79,6 @@ class CreateCog(commands.Cog, name="Create Cog"):
|
|||||||
f'"{category_name}" already exists in "{group.GroupName}". Please rename.', ephemeral=True
|
f'"{category_name}" already exists in "{group.GroupName}". Please rename.', ephemeral=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# need all these arguments because discord command.
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
@create.subcommand(description="Create a new Category Group. Must not exist already for Guild.")
|
@create.subcommand(description="Create a new Category Group. Must not exist already for Guild.")
|
||||||
async def group(
|
async def group(
|
||||||
self,
|
self,
|
||||||
@@ -118,8 +106,9 @@ class CreateCog(commands.Cog, name="Create Cog"):
|
|||||||
),
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a new category group to be placed into the database with the guild's id. Fails if the category group
|
Creates a new category group to be placed into the database with the guild's id.
|
||||||
name already exists within the guild. Then calls send_highscore_message().
|
|
||||||
|
Fails if the category group name already exists within the guild. Then calls send_highscore_message().
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (nextcord.Interaction): Interaction for the command.
|
interaction (nextcord.Interaction): Interaction for the command.
|
||||||
@@ -156,7 +145,7 @@ class CreateCog(commands.Cog, name="Create Cog"):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@category.on_autocomplete("group_id")
|
@category.on_autocomplete("group_id")
|
||||||
async def group_autocomplete(self, interaction: nextcord.Interaction, group_id: str):
|
async def group_autocomplete(self, interaction: nextcord.Interaction, group_id: str) -> None:
|
||||||
"""
|
"""
|
||||||
Collects list of table names and compares to input table name for autocomplete assistance.
|
Collects list of table names and compares to input table name for autocomplete assistance.
|
||||||
|
|
||||||
|
|||||||
+22
-32
@@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Commands for deleting groups/categories/scores in database and messages in discord."""
|
||||||
Commands for deleting groups/categories/scores in database and messages in discord..
|
|
||||||
"""
|
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
@@ -21,26 +19,18 @@ from .views.delete_confirm import DeleteConfirmView, create_delete_id
|
|||||||
|
|
||||||
|
|
||||||
class DeleteCog(commands.Cog, name="Table Management"):
|
class DeleteCog(commands.Cog, name="Table Management"):
|
||||||
"""
|
"""Class holds commands for deleting."""
|
||||||
Class holds commands for deleting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""
|
"""Default constructor for DeleteCog class."""
|
||||||
Default constructor for DeleteCog class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True))
|
@nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True))
|
||||||
async def delete(self, interaction: nextcord.Interaction):
|
async def delete(self, interaction: nextcord.Interaction) -> None:
|
||||||
"""
|
"""Parent slash command for deleting things. Never called."""
|
||||||
Parent slash command for deleting things. Never called.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@delete.subcommand()
|
@delete.subcommand()
|
||||||
async def all(self, interaction: nextcord.Interaction):
|
async def all(self, interaction: nextcord.Interaction) -> None:
|
||||||
"""
|
"""Subcommand parent for deleting all of a thing. Never called."""
|
||||||
Subcommand parent for deleting all of a thing. Never called.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@all.subcommand(description="Clears highscores channel of all table messages. Does not change data.")
|
@all.subcommand(description="Clears highscores channel of all table messages. Does not change data.")
|
||||||
async def channel_messages(
|
async def channel_messages(
|
||||||
@@ -55,6 +45,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Deletes all group messages from channel. Does not change data.
|
Deletes all group messages from channel. Does not change data.
|
||||||
|
|
||||||
Does not delete highscores channel id. Uses current channel if not supplied a channel.
|
Does not delete highscores channel id. Uses current channel if not supplied a channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -95,10 +86,11 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
|||||||
user_name: str = nextcord.SlashOption(
|
user_name: str = nextcord.SlashOption(
|
||||||
description="User to remove scores for. Note: This is not always a user's discord name.", required=True
|
description="User to remove scores for. Note: This is not always a user's discord name.", required=True
|
||||||
),
|
),
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Searches all of guild's scores data for any score from the user. Removes. Updates table
|
Searches all of guild's scores data for any score from the user and removes.
|
||||||
message is user was removed from that table.
|
|
||||||
|
Updates table message is user was removed from that table.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (nextcord.Interaction): Interaction that invoked the command.
|
interaction (nextcord.Interaction): Interaction that invoked the command.
|
||||||
@@ -157,10 +149,8 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
|||||||
await interaction.response.send_modal(DeleteConfirmView(custom_id=create_delete_id(int(group_id))))
|
await interaction.response.send_modal(DeleteConfirmView(custom_id=create_delete_id(int(group_id))))
|
||||||
|
|
||||||
@delete.subcommand()
|
@delete.subcommand()
|
||||||
async def user(self, interaction: nextcord.Interaction):
|
async def user(self, interaction: nextcord.Interaction) -> None:
|
||||||
"""
|
"""Subcommand parent for deleting user information. Never called."""
|
||||||
Subcommand parent for deleting user information. Never called.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@user.subcommand(description="Deletes a user's scores from a category.")
|
@user.subcommand(description="Deletes a user's scores from a category.")
|
||||||
async def from_category(
|
async def from_category(
|
||||||
@@ -171,7 +161,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
|||||||
name="category", description="Category to delete user from.", required=True
|
name="category", description="Category to delete user from.", required=True
|
||||||
),
|
),
|
||||||
user_name: str = nextcord.SlashOption(description="User to delete.", required=True),
|
user_name: str = nextcord.SlashOption(description="User to delete.", required=True),
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Removes a user from a category. Uses autofill functions to fill out group_id, category_id, and user_name.
|
Removes a user from a category. Uses autofill functions to fill out group_id, category_id, and user_name.
|
||||||
|
|
||||||
@@ -205,9 +195,9 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
|||||||
interaction: nextcord.Interaction,
|
interaction: nextcord.Interaction,
|
||||||
group_id: str = nextcord.SlashOption(name="group", description="Group to delete user from.", required=True),
|
group_id: str = nextcord.SlashOption(name="group", description="Group to delete user from.", required=True),
|
||||||
user_name: str = nextcord.SlashOption(description="User to delete.", required=True),
|
user_name: str = nextcord.SlashOption(description="User to delete.", required=True),
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Removes user from a goup. Uses autofill functions to fill out group_id and user_name.
|
Removes user from a group. Uses autofill functions to fill out group_id and user_name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (nextcord.Interaction): Interaction that invoked the command.
|
interaction (nextcord.Interaction): Interaction that invoked the command.
|
||||||
@@ -233,7 +223,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
|||||||
@from_group.on_autocomplete("group_id")
|
@from_group.on_autocomplete("group_id")
|
||||||
@category.on_autocomplete("group_id")
|
@category.on_autocomplete("group_id")
|
||||||
@group.on_autocomplete("group_id")
|
@group.on_autocomplete("group_id")
|
||||||
async def group_autocomplete(self, interaction: nextcord.Interaction, group_id: str):
|
async def group_autocomplete(self, interaction: nextcord.Interaction, group_id: str) -> None:
|
||||||
"""
|
"""
|
||||||
Autocomplete function for table names.
|
Autocomplete function for table names.
|
||||||
|
|
||||||
@@ -245,7 +235,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
|||||||
|
|
||||||
@from_category.on_autocomplete("category_id")
|
@from_category.on_autocomplete("category_id")
|
||||||
@category.on_autocomplete("category_id")
|
@category.on_autocomplete("category_id")
|
||||||
async def category_autocomplete(self, interaction: nextcord.Interaction, category_id: str, group_id: str):
|
async def category_autocomplete(self, interaction: nextcord.Interaction, category_id: str, group_id: str) -> None:
|
||||||
"""
|
"""
|
||||||
Autocomplete function for category names.
|
Autocomplete function for category names.
|
||||||
|
|
||||||
@@ -262,7 +252,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
|||||||
@from_group.on_autocomplete("user_name")
|
@from_group.on_autocomplete("user_name")
|
||||||
async def user_autocomplete(
|
async def user_autocomplete(
|
||||||
self, interaction: nextcord.Interaction, user_name: str, group_id: str, category_id: str | None = None
|
self, interaction: nextcord.Interaction, user_name: str, group_id: str, category_id: str | None = None
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Autocomplete functions for user names.
|
Autocomplete functions for user names.
|
||||||
|
|
||||||
@@ -279,12 +269,12 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@users.on_autocomplete("user_name")
|
@users.on_autocomplete("user_name")
|
||||||
async def all_users_autocomplete(self, interaction: nextcord.Interaction, username: str):
|
async def all_users_autocomplete(self, interaction: nextcord.Interaction, username: str) -> None:
|
||||||
"""
|
"""
|
||||||
Sends response from autofill_users_in_guild back to update_username for autocompletion of old_username.
|
Sends response from autofill_users_in_guild back to update_username for autocompletion of old_username.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (nextcord.Interaction): Interaction with command.
|
interaction (nextcord.Interaction): Interaction with command.
|
||||||
old_username (str): String to run autocompletion against.
|
username (str): String to run autocompletion against.
|
||||||
"""
|
"""
|
||||||
await interaction.response.send_autocomplete(autofill_users_in_guild(interaction.guild_id, username))
|
await interaction.response.send_autocomplete(autofill_users_in_guild(interaction.guild_id, username))
|
||||||
|
|||||||
+12
-22
@@ -1,29 +1,21 @@
|
|||||||
"""
|
"""Commands for helping a user."""
|
||||||
Commands for helping a user.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
class HelpCog(commands.Cog, name="Help commands."):
|
class HelpCog(commands.Cog, name="Help commands."):
|
||||||
"""
|
"""Class holds commands for getting help."""
|
||||||
Class holds commands for getting help.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""
|
"""Default constructor for HelpCog class. Does nothing."""
|
||||||
Default constructor for HelpCog class. Does nothing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@nextcord.slash_command()
|
@nextcord.slash_command()
|
||||||
async def help(self, interaction: nextcord.Interaction):
|
async def help(self, interaction: nextcord.Interaction):
|
||||||
"""
|
"""Parent slash command for help. Never called."""
|
||||||
Parent slash command for help. Never called.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@help.subcommand(description="Describes how to get started with Agon.")
|
@help.subcommand(description="Describes how to get started with Agon.")
|
||||||
async def setup(self, interaction: nextcord.Interaction):
|
async def setup(self, interaction: nextcord.Interaction) -> None:
|
||||||
"""
|
"""
|
||||||
Sends a message describing how to get started with Agon.
|
Sends a message describing how to get started with Agon.
|
||||||
|
|
||||||
@@ -31,15 +23,13 @@ class HelpCog(commands.Cog, name="Help commands."):
|
|||||||
interaction (nextcord.Interaction): Interaction that invoked the command.
|
interaction (nextcord.Interaction): Interaction that invoked the command.
|
||||||
"""
|
"""
|
||||||
# I hate this but don't want indents in the string.
|
# I hate this but don't want indents in the string.
|
||||||
# pylint: disable=line-too-long
|
|
||||||
help_str: str = """Welcome to Agon! Three simple steps are needed to get started.
|
|
||||||
1.) Set a channel to recieve submission verifications with "/set verify_channel."
|
|
||||||
This channel is used to allow moderators to check if a submission is real/valid.
|
|
||||||
|
|
||||||
2.) Set a channel for submissions to be posted in with "/set submission_channel."
|
help_str: str = "Welcome to Agon! Three simple steps are needed to get started."
|
||||||
This channel will be where all user submission messages are sent.
|
help_str += '1.) Set a channel to receive submission verifications with "/set verify_channel."\n'
|
||||||
|
help_str += "This channel is used to allow moderators to check if a submission is real/valid.\n\n"
|
||||||
3.) Set up your highscore tables! Use "/create group" to first create a group message in the channel you would like and then use "/create category" to create a category in that group message!"""
|
help_str += '2.) Set up your highscore tables! Use "/create group" to first create a group message in the'
|
||||||
# pylint: enable=line-too-long
|
help_str += 'channel you would like and then use "/create category" to create a category in that group message!'
|
||||||
|
help_str += "\n\n"
|
||||||
|
help_str += '3.) Users can then submit scores with the "/submit" command.'
|
||||||
|
|
||||||
await interaction.response.send_message(content=help_str)
|
await interaction.response.send_message(content=help_str)
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ class SetCog(commands.Cog, name="Set command."):
|
|||||||
f"Category pad length set to {length} for {group.GroupName}", ephemeral=True
|
f"Category pad length set to {length} for {group.GroupName}", ephemeral=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@set.subcommand(description="Registers the channel for submission messages.")
|
@set.subcommand(
|
||||||
|
description="Registers the channel for submission messages. Currently this information is deprecated."
|
||||||
|
)
|
||||||
async def submission_channel(
|
async def submission_channel(
|
||||||
self,
|
self,
|
||||||
interaction: nextcord.Interaction,
|
interaction: nextcord.Interaction,
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
"""Contains command for submitting a score."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import nextcord
|
||||||
|
from nextcord.ext import commands
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from cogs.submission.verification_view import VerificationView
|
||||||
|
from database_setup.sql_data_classes import Category, CategoryGroup, Guild, Submission, engine
|
||||||
|
from main import logger
|
||||||
|
from utilities.utils import GUILDS, autofill_categories, autofill_groups, submit_score
|
||||||
|
|
||||||
|
|
||||||
|
class SubmissionCog(commands.Cog):
|
||||||
|
"""Class holds command for submitting a score."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
return
|
||||||
|
|
||||||
|
@nextcord.slash_command(guild_ids=GUILDS)
|
||||||
|
async def submit( # noqa: PLR0913
|
||||||
|
self,
|
||||||
|
interaction: nextcord.Interaction,
|
||||||
|
group_id: str = nextcord.SlashOption(name="group", required=True, description="Group to submit score to."),
|
||||||
|
category_id: str = nextcord.SlashOption(
|
||||||
|
name="category", required=True, description="Category to submit score to."
|
||||||
|
),
|
||||||
|
score: str = nextcord.SlashOption(required=True, description="Score to submit"),
|
||||||
|
proof: nextcord.Attachment = nextcord.SlashOption(
|
||||||
|
required=False, description="Proof for submission. Only needed if score requires verification."
|
||||||
|
),
|
||||||
|
username: str = nextcord.SlashOption(
|
||||||
|
required=False, description="Modified username if submitting under not your discord name."
|
||||||
|
),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Submit a highscore.
|
||||||
|
|
||||||
|
Sets the username if one isn't passed in. Checks if a submission needs verified. Ensures that the score is
|
||||||
|
following the correct format. Then either creates the verification request or submits score if verification
|
||||||
|
isn't required.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interaction (nextcord.Interaction): Interaction of the command.
|
||||||
|
group_id (str): Is SlashOption. Group that the score category belongs in.
|
||||||
|
category_id (str): Is SlashOption. Category to submit the score in.
|
||||||
|
score (str): Is SlashOption. The score.
|
||||||
|
username (str, optional): Is SlashOption. Username to associate with the score.
|
||||||
|
"""
|
||||||
|
# fill out username if not passed in
|
||||||
|
if username is None:
|
||||||
|
username = interaction.user.display_name
|
||||||
|
|
||||||
|
with Session(engine) as session:
|
||||||
|
stmt = (
|
||||||
|
select(Category).where(Category.GroupId == int(group_id)).where(Category.CategoryId == int(category_id))
|
||||||
|
)
|
||||||
|
|
||||||
|
category: Category = session.scalar(stmt)
|
||||||
|
|
||||||
|
# check if verification is required. if no picture, deny
|
||||||
|
if category.IsValidated and proof is None:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"This category required verification. Please attach a picture and re-submit.", ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# check with regex if score is in correct format
|
||||||
|
verified_score = await self.verify_score(interaction, score, is_time_record=category.IsTimeRecord)
|
||||||
|
|
||||||
|
# check if verify_score was successful. It replies with corrections required itself.
|
||||||
|
if verified_score is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create submission verification request or submit score if verification not needed.
|
||||||
|
if not category.IsValidated:
|
||||||
|
await submit_score(interaction, int(group_id), int(category_id), username, verified_score)
|
||||||
|
await interaction.response.send_message("Score has been submitted!", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
stmt = select(Guild).where(Guild.GuildId == interaction.guild_id)
|
||||||
|
verification_channel_id: int = session.scalar(stmt).VerificationChannel
|
||||||
|
|
||||||
|
channel = interaction.guild.get_channel(verification_channel_id)
|
||||||
|
|
||||||
|
if channel is None:
|
||||||
|
error_response = (
|
||||||
|
"Registered Verification channel does not exist or was never registered. Contact server admins."
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(error_response, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not isinstance(channel, nextcord.TextChannel):
|
||||||
|
await interaction.response.send_message("An error happened with submission.")
|
||||||
|
logger.error(
|
||||||
|
"Channel id %s in guild %s is does not correspond to a TextChannel.", channel.id, channel.guild.id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Send the verification message to admins
|
||||||
|
verification_message = await channel.send(
|
||||||
|
file=await proof.to_file(),
|
||||||
|
view=VerificationView(),
|
||||||
|
embeds=[self.create_embed(interaction, group_id, category_id, verified_score, username, session)],
|
||||||
|
content=f"Submission from <@{interaction.user.id}>.",
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(content="Score submitted!", ephemeral=True)
|
||||||
|
|
||||||
|
# Save the submission data for later use.
|
||||||
|
submission: Submission = Submission(
|
||||||
|
CategoryId=category_id,
|
||||||
|
GroupId=group_id,
|
||||||
|
GuildId=interaction.guild_id,
|
||||||
|
MessageId=verification_message.id,
|
||||||
|
Score=verified_score,
|
||||||
|
UserId=interaction.user.id,
|
||||||
|
UserName=username,
|
||||||
|
)
|
||||||
|
|
||||||
|
session.add(submission)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
def create_embed( # noqa: PLR0913
|
||||||
|
self,
|
||||||
|
interaction: nextcord.Interaction,
|
||||||
|
group_id: str,
|
||||||
|
category_id: str,
|
||||||
|
score: str,
|
||||||
|
username: str,
|
||||||
|
session: Session,
|
||||||
|
) -> nextcord.Embed:
|
||||||
|
"""
|
||||||
|
Creates an embed containing submission data for human verification.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interaction (nextcord.Interaction): Discord interaction of the command.
|
||||||
|
group_id (str): Used to get group name.
|
||||||
|
category_id (str): Used to get category name.
|
||||||
|
score (str): Score to set.
|
||||||
|
username (str): Username to set.
|
||||||
|
session (Session): Open connection to database.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
nextcord.Embed: Embed containing human readable submission data.
|
||||||
|
"""
|
||||||
|
embed = nextcord.Embed(title="Highscores Submission", description="Review fields and accept.")
|
||||||
|
embed.add_field(name="User Name", value=username, inline=False)
|
||||||
|
|
||||||
|
# Get group name from database
|
||||||
|
stmt = (
|
||||||
|
select(CategoryGroup.GroupName)
|
||||||
|
.where(CategoryGroup.GuildId == interaction.guild_id)
|
||||||
|
.where(CategoryGroup.GroupId == group_id)
|
||||||
|
)
|
||||||
|
embed.add_field(name="Group", value=session.scalar(stmt), inline=False)
|
||||||
|
|
||||||
|
# Get category name from database
|
||||||
|
stmt = (
|
||||||
|
select(Category.CategoryName).where(Category.GroupId == group_id).where(Category.CategoryId == category_id)
|
||||||
|
)
|
||||||
|
embed.add_field(name="Category", value=session.scalar(stmt), inline=False)
|
||||||
|
embed.add_field(name="Score", value=score, inline=False)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
async def verify_score(self, interaction: nextcord.Interaction, score: str, *, is_time_record: bool) -> str | None:
|
||||||
|
"""
|
||||||
|
Uses regex to verify that scores are in the correct format.
|
||||||
|
|
||||||
|
Handles responding to the interaction if the format is incorrect. Also standardizes the time record.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interaction (nextcord.Interaction): Discord interaction of the command.
|
||||||
|
score (str): String representation of the score being verified.
|
||||||
|
is_time_record (bool): Whether the score needs to be verified/formatted as a time record.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str | None: String of corrected score. None if formatted incorrectly.
|
||||||
|
"""
|
||||||
|
time_regex = re.search("^([0-9]{1,2}:[0-9]|[0-9])?[0-9](\\.[0-9])?$", score)
|
||||||
|
int_regex = re.search("^[0-9]+$", score)
|
||||||
|
if time_regex is None and is_time_record:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"Time record must follow format MM:SS.ms, SS.ms, or SS.", ephemeral=True
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
if int_regex is None and not is_time_record:
|
||||||
|
await interaction.response.send_message("Number record must only be number.", ephemeral=True)
|
||||||
|
return None
|
||||||
|
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")
|
||||||
|
|
||||||
|
return score
|
||||||
|
|
||||||
|
@submit.on_autocomplete("group_id")
|
||||||
|
async def group_autocomplete(self, interaction: nextcord.Interaction, group_id: str) -> None:
|
||||||
|
"""
|
||||||
|
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))
|
||||||
|
|
||||||
|
@submit.on_autocomplete("category_id")
|
||||||
|
async def category_autocomplete(self, interaction: nextcord.Interaction, category_id: str, group_id: str) -> None:
|
||||||
|
"""
|
||||||
|
Autocomplete function for category names.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interaction (nextcord.Interaction): Interaction that invoked the command.
|
||||||
|
category_id (str): String to be autocompleted with Category name.
|
||||||
|
group_id (str): Group to find Category from.
|
||||||
|
"""
|
||||||
|
await interaction.response.send_autocomplete(
|
||||||
|
autofill_categories(interaction.guild_id, int(group_id), category_id)
|
||||||
|
)
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
"""
|
|
||||||
Defines cog for on message listener.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import nextcord
|
|
||||||
from nextcord.ext import commands
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
# pylint: disable=import-error
|
|
||||||
from classes.submission import SubmissionState
|
|
||||||
from main import submission_objects
|
|
||||||
from database_setup.sql_data_classes import Category, Guild, engine
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionListener(commands.Cog, name="Message Listener"):
|
|
||||||
"""Sets up message listener."""
|
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_message(self, message: nextcord.Message):
|
|
||||||
"""Listener for message creation. Adds score and attachments to submission."""
|
|
||||||
# don't want to process any messages from bot.
|
|
||||||
if message.author.id == self.bot.user.id:
|
|
||||||
return
|
|
||||||
|
|
||||||
await birthday_action(message)
|
|
||||||
|
|
||||||
guild_id = message.guild.id
|
|
||||||
|
|
||||||
if submission_objects.has_user(message.author.id):
|
|
||||||
submission = submission_objects.get_user_submission(message.author.id)
|
|
||||||
message_id = submission.message_id
|
|
||||||
|
|
||||||
with Session(engine) as session:
|
|
||||||
stmt = select(Guild.SubmissionChannel).where(Guild.GuildId == guild_id)
|
|
||||||
submission_channel_id = session.scalar(stmt)
|
|
||||||
submission_message: nextcord.Message = (
|
|
||||||
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:
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
await message.delete()
|
|
||||||
|
|
||||||
|
|
||||||
# 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(SubmissionListener(bot))
|
|
||||||
|
|
||||||
|
|
||||||
async def birthday_action(message: nextcord.Message) -> None:
|
|
||||||
"""
|
|
||||||
Does the birthday thing, currently for chris only.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
message (nextcord.Message): The message to check for birthday thing.
|
|
||||||
"""
|
|
||||||
today = datetime.today()
|
|
||||||
birthday = datetime(today.year, 7, 31)
|
|
||||||
chris = 642330132958281750
|
|
||||||
if today.date() == birthday.date():
|
|
||||||
if message.author.id == chris or chris in message.raw_mentions:
|
|
||||||
await message.add_reaction("🎂")
|
|
||||||
@@ -2,139 +2,6 @@
|
|||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.orm import Session, aliased
|
|
||||||
|
|
||||||
# pylint: disable=import-error
|
|
||||||
from classes.submission import Submission, SubmissionState
|
|
||||||
from main import submission_objects
|
|
||||||
from database_setup.sql_data_classes import Category, CategoryGroup, Guild, engine
|
|
||||||
|
|
||||||
from .verification_view import VerificationView
|
|
||||||
|
|
||||||
|
|
||||||
def category_select_options(group_id: int) -> "list[nextcord.SelectOption]":
|
|
||||||
"""Returns list of categories for a given boss."""
|
|
||||||
options: list[nextcord.SelectOption] = []
|
|
||||||
|
|
||||||
with Session(engine) as session:
|
|
||||||
subq = select(CategoryGroup).where(CategoryGroup.GroupId == group_id).subquery()
|
|
||||||
cat_group_alias = aliased(CategoryGroup, subq)
|
|
||||||
stmt = select(Category).where(Category.GroupId == cat_group_alias.GroupId)
|
|
||||||
categories_list = session.scalars(stmt)
|
|
||||||
for category in categories_list:
|
|
||||||
options.append(nextcord.SelectOption(label=category.CategoryName, value=str(category.CategoryId)))
|
|
||||||
|
|
||||||
return options
|
|
||||||
|
|
||||||
|
|
||||||
############ Items used in submission process ############
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionDropdown(nextcord.ui.Select):
|
|
||||||
"""Defines dropdown menu for submission"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
bot: commands.Bot,
|
|
||||||
options: "list[nextcord.SelectOption]",
|
|
||||||
):
|
|
||||||
super().__init__(placeholder="Choose submission category.", min_values=1, max_values=1, options=options)
|
|
||||||
self._bot = bot
|
|
||||||
|
|
||||||
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()
|
|
||||||
await interaction.message.edit(
|
|
||||||
embeds=embeds,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BackButton(nextcord.ui.Button):
|
|
||||||
"""Defines view for back button."""
|
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
|
||||||
super().__init__(label="Back", style=nextcord.ButtonStyle.primary, custom_id="back")
|
|
||||||
self._bot = bot
|
|
||||||
|
|
||||||
async def callback(self, interaction: nextcord.Interaction):
|
|
||||||
submission = submission_objects.get_user_submission(interaction.user.id)
|
|
||||||
state: SubmissionState = submission.submission_state
|
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
class CancelButton(nextcord.ui.Button):
|
|
||||||
"""Deletes message to cancel interaction.."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__(label="Cancel", style=nextcord.ButtonStyle.red, custom_id="cancel")
|
|
||||||
|
|
||||||
async def callback(self, interaction: nextcord.Interaction):
|
|
||||||
submission_objects.remove_user_submission(interaction.user.id)
|
|
||||||
await interaction.message.delete()
|
|
||||||
|
|
||||||
|
|
||||||
class NextButton(nextcord.ui.Button):
|
|
||||||
"""Defines view for next button."""
|
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
|
||||||
super().__init__(label="Next", style=nextcord.ButtonStyle.primary, custom_id="next")
|
|
||||||
self._bot = bot
|
|
||||||
|
|
||||||
async def callback(self, interaction: nextcord.Interaction):
|
|
||||||
# check if fields have been filled out for each state
|
|
||||||
submission = submission_objects.get_user_submission(interaction.user.id)
|
|
||||||
state: SubmissionState = submission.submission_state
|
|
||||||
if state == SubmissionState.CATEGORY and not submission.category_id:
|
|
||||||
await interaction.send("Please choose a category.", ephemeral=True)
|
|
||||||
elif state == SubmissionState.SCORE and not submission.score:
|
|
||||||
await interaction.send("Please send a score.", ephemeral=True)
|
|
||||||
elif state == SubmissionState.PROOF and not len(interaction.message.attachments) > 0:
|
|
||||||
await interaction.send("Please attach picture for proof.", ephemeral=True)
|
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
class SubmitButton(nextcord.ui.Button):
|
|
||||||
"""Defines button to submit a user's score."""
|
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
|
||||||
super().__init__(label="Submit", style=nextcord.ButtonStyle.green, custom_id="submit")
|
|
||||||
self._bot = bot
|
|
||||||
|
|
||||||
async def callback(self, interaction: nextcord.Interaction):
|
|
||||||
with Session(engine) as session:
|
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
embeds = submission.get_embeds()
|
|
||||||
|
|
||||||
await channel.send(
|
|
||||||
files=[await attch.to_file() for attch in interaction.message.attachments],
|
|
||||||
view=VerificationView(),
|
|
||||||
embeds=embeds,
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
############ Submission Views ############
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionCreateButton(nextcord.ui.View):
|
class SubmissionCreateButton(nextcord.ui.View):
|
||||||
@@ -145,87 +12,12 @@ class SubmissionCreateButton(nextcord.ui.View):
|
|||||||
self._bot = bot
|
self._bot = bot
|
||||||
|
|
||||||
@nextcord.ui.button(label="Submit", style=nextcord.ButtonStyle.primary, custom_id="submitcreate")
|
@nextcord.ui.button(label="Submit", style=nextcord.ButtonStyle.primary, custom_id="submitcreate")
|
||||||
async def submitcreate_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): # pylint: disable=unused-argument
|
async def submitcreate_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
||||||
"""Button for score submission."""
|
"""
|
||||||
# Don't allow a user to start a new submission if one already exists
|
Button for score submission.
|
||||||
if submission_objects.has_user(interaction.user.id):
|
|
||||||
await interaction.send("Please finish in progress submission first.", ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
with Session(engine) as session:
|
Is deprecated, sends response telling user to use the new command instead.
|
||||||
stmt = (
|
"""
|
||||||
select(CategoryGroup.GroupId)
|
await interaction.response.send_message(
|
||||||
.where(CategoryGroup.GuildId == interaction.guild_id)
|
"This feature has been deprecated. Please use the '/submit' slash command instead.", ephemeral=True
|
||||||
.where(CategoryGroup.MessageId == interaction.message.id)
|
|
||||||
)
|
)
|
||||||
group_id = session.scalar(stmt)
|
|
||||||
|
|
||||||
stmt = select(Guild.SubmissionChannel).where(Guild.GuildId == interaction.guild_id)
|
|
||||||
channel = interaction.guild.get_channel(session.scalar(stmt))
|
|
||||||
|
|
||||||
# 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_objects.add_submission(interaction.user.id, submission)
|
|
||||||
|
|
||||||
embeds = submission.get_embeds()
|
|
||||||
|
|
||||||
submission_message = await channel.send(
|
|
||||||
f"<@{interaction.user.id}>",
|
|
||||||
view=SubmissionView(
|
|
||||||
self._bot,
|
|
||||||
interaction.user.id,
|
|
||||||
),
|
|
||||||
embeds=embeds,
|
|
||||||
)
|
|
||||||
|
|
||||||
# update the message_id once its created
|
|
||||||
submission.message_id = submission_message.id
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionView(nextcord.ui.View):
|
|
||||||
"""Defines view submitting. Adjusts to all states."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
bot: commands.Bot,
|
|
||||||
submission_user: int,
|
|
||||||
*,
|
|
||||||
timeout: float | None = 300,
|
|
||||||
):
|
|
||||||
super().__init__(timeout=timeout)
|
|
||||||
self._submission_user = submission_user
|
|
||||||
self._bot = bot
|
|
||||||
self._state = submission_objects.get_user_submission(self._submission_user).submission_state
|
|
||||||
self._group_id = submission_objects.get_user_submission(self._submission_user).group_id
|
|
||||||
self._guild_id = submission_objects.get_user_submission(self._submission_user).guild_id
|
|
||||||
|
|
||||||
# add items depending on given state
|
|
||||||
if self._state is SubmissionState.CATEGORY:
|
|
||||||
self.add_item(SubmissionDropdown(bot, category_select_options(self._group_id)))
|
|
||||||
self.add_item(CancelButton())
|
|
||||||
self.add_item(NextButton(self._bot))
|
|
||||||
elif (
|
|
||||||
self._state is SubmissionState.SCORE
|
|
||||||
or self._state is SubmissionState.PROOF
|
|
||||||
or self._state is SubmissionState.NAME
|
|
||||||
):
|
|
||||||
self.add_item(BackButton(self._bot))
|
|
||||||
self.add_item(NextButton(self._bot))
|
|
||||||
elif self._state is SubmissionState.SUBMIT:
|
|
||||||
self.add_item(BackButton(self._bot))
|
|
||||||
self.add_item(SubmitButton(self._bot))
|
|
||||||
|
|
||||||
async def interaction_check(self, interaction: nextcord.Interaction) -> bool:
|
|
||||||
check = self._submission_user == interaction.user.id
|
|
||||||
|
|
||||||
if not check:
|
|
||||||
await interaction.send("This is not your submission.", ephemeral=True)
|
|
||||||
|
|
||||||
return check
|
|
||||||
|
|
||||||
# async def on_timeout(self) -> None:
|
|
||||||
# # remove user from submission objects after time out. Allows user to start new submission.
|
|
||||||
# submission_objects.remove_user_submission(self._submission_user)
|
|
||||||
|
|||||||
@@ -1,44 +1,47 @@
|
|||||||
"""View for verification buttons"""
|
"""View for verification buttons."""
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from database_setup.sql_data_classes import Submission, engine
|
||||||
|
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
from utilities.utils import submit_score
|
from utilities.utils import submit_score
|
||||||
|
|
||||||
|
|
||||||
class VerificationView(nextcord.ui.View):
|
class VerificationView(nextcord.ui.View):
|
||||||
"""Defines views for verifying score submissions"""
|
"""Defines views for verifying score submissions."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__(timeout=None)
|
super().__init__(timeout=None)
|
||||||
|
|
||||||
@nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red, custom_id="deny-button-submission")
|
@nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red, custom_id="deny-button-submission")
|
||||||
async def deny_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): # pylint: disable=unused-argument
|
async def deny_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
||||||
"""Button for denying score submission."""
|
"""Button for denying score submission."""
|
||||||
await interaction.message.delete()
|
await interaction.message.delete()
|
||||||
|
|
||||||
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve-button-submission")
|
@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
|
async def approve_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
||||||
"""Button for approving score submission."""
|
"""Button for approving score submission."""
|
||||||
for embed in interaction.message.embeds:
|
with Session(engine) as session:
|
||||||
content = embed.to_dict()
|
stmt = (
|
||||||
|
select(Submission)
|
||||||
|
.where(Submission.GuildId == interaction.guild_id)
|
||||||
|
.where(Submission.MessageId == interaction.message.id)
|
||||||
|
)
|
||||||
|
|
||||||
for field in content["fields"]:
|
submission = session.scalar(stmt)
|
||||||
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"]
|
|
||||||
|
|
||||||
try:
|
if submission is None:
|
||||||
await submit_score(interaction, group_id, category_id, user, score)
|
await interaction.response.send_message("Could not find submission data in database.", ephemeral=True)
|
||||||
|
await interaction.message.delete()
|
||||||
|
return
|
||||||
|
|
||||||
|
await submit_score(
|
||||||
|
interaction, submission.GroupId, submission.CategoryId, submission.UserName, submission.Score
|
||||||
|
)
|
||||||
content = interaction.message.content
|
content = interaction.message.content
|
||||||
content += f"\nApproved by <@{interaction.user.id}>."
|
content += f"\nApproved by <@{interaction.user.id}>."
|
||||||
# remove the bot information embed. it should always be the first one.
|
# 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)
|
||||||
except UnboundLocalError:
|
|
||||||
await interaction.send("Instruct user to submit with all fields.")
|
|
||||||
await interaction.message.edit(view=None)
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ Base = automap_base()
|
|||||||
Base.prepare(autoload_with=engine)
|
Base.prepare(autoload_with=engine)
|
||||||
|
|
||||||
# define the classes
|
# define the classes
|
||||||
Guild = Base.classes.Guilds
|
|
||||||
CategoryGroup = Base.classes.CategoryGroups
|
|
||||||
Category = Base.classes.Categories
|
Category = Base.classes.Categories
|
||||||
|
CategoryGroup = Base.classes.CategoryGroups
|
||||||
|
Guild = Base.classes.Guilds
|
||||||
Score = Base.classes.Scores
|
Score = Base.classes.Scores
|
||||||
|
Submission = Base.classes.Submissions
|
||||||
|
|||||||
@@ -1,335 +0,0 @@
|
|||||||
{
|
|
||||||
"tables": {
|
|
||||||
"The Ambassador": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Group Kill Count",
|
|
||||||
"Group Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Araxxi": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Arch-Glacor": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Enrage",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Astellarn": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Group Kill Count",
|
|
||||||
"Group Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"The Barrows Brothers": [
|
|
||||||
"Kill Count",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"The Barrows: Rise of the Six": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Beastmaster Durzag": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Black Stone Dragon": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Group Kill Count",
|
|
||||||
"Group Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Chaos Elemental": [
|
|
||||||
"Kill Count",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Commander Zilyana": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Corporeal Beast": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Crassian Leviathan": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Group Kill Count",
|
|
||||||
"Group Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Croesus": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Dagannoth Kings": [
|
|
||||||
"Kill Count",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"General Graardor": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Giant Mole": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Gregorovic": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Har-Aken": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Helwyr": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Kalphite King": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Kalphite Queen": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Kerapac": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"King Black Dragon": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Kree'arra": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"K'ril Tsutsaroth": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Legiones": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"The Magister": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Masuta the Ascended": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Group Kill Count",
|
|
||||||
"Group Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Nex": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Nex - Angel of Death": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Queen Black Dragon": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Raksha": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Duo Kill Count",
|
|
||||||
"Duo Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Rex Matriarchs": [
|
|
||||||
"Kill Count",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"The Sanctum Guardian": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Group Kill Count",
|
|
||||||
"Group Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Seiryu the Azure Serpent": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Group Kill Count",
|
|
||||||
"Group Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Solak": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Taraket the Necromancer": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Group Kill Count",
|
|
||||||
"Group Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Telos": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Enrage",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"The Twin Furies": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"TzKal-Zuk": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"TzTok-Jad": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Verak Lith": [
|
|
||||||
"Solo Kill Count",
|
|
||||||
"Solo Kill Time",
|
|
||||||
"Group Kill Count",
|
|
||||||
"Group Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Vindicta & Gorvek": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Vorago": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Hardmode Kill Count",
|
|
||||||
"Hardmode Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Yakamaru": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Zamorak, Lord of Chaos (solo)": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Enrage",
|
|
||||||
"Spooned Log"
|
|
||||||
],
|
|
||||||
"Zamorak, Lord of Chaos (group)": [
|
|
||||||
"Kill Count",
|
|
||||||
"Kill Time",
|
|
||||||
"Enrage"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"categories": {
|
|
||||||
"Duo Kill Count": {
|
|
||||||
"ascending": false,
|
|
||||||
"is_time_record": false
|
|
||||||
},
|
|
||||||
"Duo Kill Time": {
|
|
||||||
"ascending": true,
|
|
||||||
"is_time_record": true
|
|
||||||
},
|
|
||||||
"Enrage": {
|
|
||||||
"ascending": false,
|
|
||||||
"is_time_record": false
|
|
||||||
},
|
|
||||||
"Group Kill Count": {
|
|
||||||
"ascending": false,
|
|
||||||
"is_time_record": false
|
|
||||||
},
|
|
||||||
"Group Kill Time": {
|
|
||||||
"ascending": true,
|
|
||||||
"is_time_record": true
|
|
||||||
},
|
|
||||||
"Hardmode Kill Count": {
|
|
||||||
"ascending": false,
|
|
||||||
"is_time_record": false
|
|
||||||
},
|
|
||||||
"Hardmode Kill Time": {
|
|
||||||
"ascending": true,
|
|
||||||
"is_time_record": true
|
|
||||||
},
|
|
||||||
"Kill Count": {
|
|
||||||
"ascending": false,
|
|
||||||
"is_time_record": false
|
|
||||||
},
|
|
||||||
"Kill Time": {
|
|
||||||
"ascending": true,
|
|
||||||
"is_time_record": true
|
|
||||||
},
|
|
||||||
"Solo Kill Count": {
|
|
||||||
"ascending": false,
|
|
||||||
"is_time_record": false
|
|
||||||
},
|
|
||||||
"Solo Kill Time": {
|
|
||||||
"ascending": true,
|
|
||||||
"is_time_record": true
|
|
||||||
},
|
|
||||||
"Spooned Log": {
|
|
||||||
"ascending": true,
|
|
||||||
"is_time_record": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"table_size": 3
|
|
||||||
}
|
|
||||||
+12
-1
@@ -4,7 +4,7 @@ use highscores;
|
|||||||
CREATE TABLE Guilds(
|
CREATE TABLE Guilds(
|
||||||
GuildId bigint unsigned not null unique,
|
GuildId bigint unsigned not null unique,
|
||||||
GuildName varchar(95) not null,
|
GuildName varchar(95) not null,
|
||||||
SubmissionChannel bigint unsigned,
|
SubmissionChannel bigint unsigned, -- deprecated, is not used anymore.
|
||||||
VerificationChannel bigint unsigned,
|
VerificationChannel bigint unsigned,
|
||||||
Active boolean DEFAULT true,
|
Active boolean DEFAULT true,
|
||||||
PRIMARY KEY (GuildId)
|
PRIMARY KEY (GuildId)
|
||||||
@@ -43,3 +43,14 @@ CREATE TABLE Scores(
|
|||||||
FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId) ON DELETE CASCADE,
|
FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId) ON DELETE CASCADE,
|
||||||
PRIMARY KEY (CategoryId, UserName)
|
PRIMARY KEY (CategoryId, UserName)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE Submissions(
|
||||||
|
CategoryId int unsigned not null,
|
||||||
|
GroupId int unsigned not null,
|
||||||
|
GuildId bigint unsigned not null,
|
||||||
|
MessageId bigint unsigned not null,
|
||||||
|
Score varchar(30) not null, -- Score is stored as a string until converted during submission
|
||||||
|
UserId bigint unsigned not null,
|
||||||
|
UserName varchar(30) not null,
|
||||||
|
PRIMARY KEY (CategoryId, UserName) -- one submission a category per user.
|
||||||
|
);
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Main file for Agon bot."""
|
||||||
main file for highscores bot
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -9,25 +7,26 @@ import sys
|
|||||||
import nextcord
|
import nextcord
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
|
|
||||||
from classes.submission import SubmissionMap
|
|
||||||
|
|
||||||
# pylint: disable=import-error
|
|
||||||
from utilities import config
|
from utilities import config
|
||||||
|
|
||||||
logger = logging.getLogger("nextcord")
|
nextcord_logger = logging.getLogger("nextcord")
|
||||||
logger.setLevel(logging.DEBUG)
|
nextcord_logger.setLevel(logging.DEBUG)
|
||||||
handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w")
|
nextcord_handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w")
|
||||||
|
nextcord_handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
||||||
|
nextcord_logger.addHandler(nextcord_handler)
|
||||||
|
|
||||||
|
logger = logging.getLogger("default")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
submission_objects: SubmissionMap = SubmissionMap()
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
def main():
|
|
||||||
"""Main function for bot."""
|
"""Main function for bot."""
|
||||||
min_python_version = (3, 8)
|
min_python_version = (3, 12)
|
||||||
if sys.version_info < min_python_version:
|
if sys.version_info < min_python_version:
|
||||||
print("ERROR: Python version must be >= 3.8")
|
logger.error("ERROR: Python version must be >= 3.12")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
# Allows privileged intents for monitoring members joining, roles editing, and role assignments
|
# Allows privileged intents for monitoring members joining, roles editing, and role assignments
|
||||||
@@ -35,13 +34,7 @@ def main():
|
|||||||
intents = nextcord.Intents.default()
|
intents = nextcord.Intents.default()
|
||||||
|
|
||||||
# Required in order to read messages (eg. prefix commands)
|
# Required in order to read messages (eg. prefix commands)
|
||||||
intents.message_content = True # pylint: disable=assigning-non-slot
|
intents.message_content = True
|
||||||
|
|
||||||
# To enable the guilds priveleged intent:
|
|
||||||
# intents.guilds = True
|
|
||||||
|
|
||||||
# To enable the members priveleged intent:
|
|
||||||
# intents.members = True
|
|
||||||
|
|
||||||
bot = commands.Bot(
|
bot = commands.Bot(
|
||||||
commands.when_mentioned_or(config.BOT_PREFIX),
|
commands.when_mentioned_or(config.BOT_PREFIX),
|
||||||
@@ -53,10 +46,12 @@ def main():
|
|||||||
bot.load_extension(f"cogs.{folder}")
|
bot.load_extension(f"cogs.{folder}")
|
||||||
|
|
||||||
@bot.listen()
|
@bot.listen()
|
||||||
async def on_ready():
|
async def on_ready() -> None:
|
||||||
"""When Discord is connected"""
|
"""When Discord is connected."""
|
||||||
assert bot.user is not None
|
if bot.user is None:
|
||||||
print(f"{bot.user.name} has connected to Discord!")
|
logger.error("Discord connection failed.")
|
||||||
|
return
|
||||||
|
logger.info("%s has connected to Discord!", bot.user.name)
|
||||||
|
|
||||||
# Run Discord bot
|
# Run Discord bot
|
||||||
bot.run(config.DISCORD_TOKEN)
|
bot.run(config.DISCORD_TOKEN)
|
||||||
|
|||||||
+11
-1
@@ -3,6 +3,10 @@
|
|||||||
"lockFileMaintenance": {
|
"lockFileMaintenance": {
|
||||||
"automerge": true
|
"automerge": true
|
||||||
},
|
},
|
||||||
|
"major": {
|
||||||
|
"automerge": false,
|
||||||
|
"automergeType": "pr"
|
||||||
|
},
|
||||||
"minor": {
|
"minor": {
|
||||||
"automerge": true,
|
"automerge": true,
|
||||||
"automergeType": "branch"
|
"automergeType": "branch"
|
||||||
@@ -15,5 +19,11 @@
|
|||||||
"automerge": true,
|
"automerge": true,
|
||||||
"automergeType": "branch"
|
"automergeType": "branch"
|
||||||
},
|
},
|
||||||
"ignoreTests": true
|
"ignoreTests": true,
|
||||||
|
"dockerfile": {
|
||||||
|
"minor": {
|
||||||
|
"automerge": false,
|
||||||
|
"automergeType": "pr"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,8 @@ from database_setup.sql_data_classes import Category, CategoryGroup, Guild, Scor
|
|||||||
|
|
||||||
MAX_LINE_LENGTH = 142
|
MAX_LINE_LENGTH = 142
|
||||||
|
|
||||||
|
GUILDS = [1000247361639030804]
|
||||||
|
|
||||||
|
|
||||||
def autofill_categories(guild_id: int, group_id: int, category_begin: str) -> "dict[str, str]":
|
def autofill_categories(guild_id: int, group_id: int, category_begin: str) -> "dict[str, str]":
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user