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
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
|
||||
@@ -125,9 +125,6 @@ dmypy.json
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
|
||||
# logging
|
||||
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
|
||||
|
||||
@@ -9,20 +7,20 @@ from .delete import DeleteCog
|
||||
from .help import HelpCog
|
||||
from .refresh import RefreshCog
|
||||
from .set import SetCog
|
||||
from .submission import SubmissionCog
|
||||
from .update import UpdateCog
|
||||
from .view import ViewCog
|
||||
|
||||
|
||||
# This function will be called when this extension is loaded.
|
||||
# It is necessary to add these functions to the bot.
|
||||
def setup(bot: commands.Bot):
|
||||
"""
|
||||
Adds all command classes to bot.
|
||||
"""
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
"""Adds all command classes to bot."""
|
||||
bot.add_cog(CreateCog(bot))
|
||||
bot.add_cog(DeleteCog())
|
||||
bot.add_cog(HelpCog())
|
||||
bot.add_cog(RefreshCog(bot))
|
||||
bot.add_cog(SetCog())
|
||||
bot.add_cog(SubmissionCog())
|
||||
bot.add_cog(UpdateCog(bot))
|
||||
bot.add_cog(ViewCog())
|
||||
|
||||
+16
-27
@@ -1,48 +1,37 @@
|
||||
"""
|
||||
Commands for creating items in bot.
|
||||
"""
|
||||
"""Commands for creating items in bot."""
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
from sqlalchemy import exc, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
# pylint: disable=import-error
|
||||
from cogs.submission.submission_views import SubmissionCreateButton
|
||||
from database_setup.sql_data_classes import Category, CategoryGroup, engine
|
||||
from utilities.utils import MAX_LINE_LENGTH, autofill_groups, send_highscore_message
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
Default constructor for CreateCog class.
|
||||
"""
|
||||
"""Default constructor for CreateCog class."""
|
||||
self._bot = bot
|
||||
|
||||
@nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True))
|
||||
async def create(self, interaction: nextcord.Interaction):
|
||||
"""
|
||||
Parent slash command for creating things. Never called.
|
||||
"""
|
||||
async def create(self, interaction: nextcord.Interaction) -> None:
|
||||
"""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.")
|
||||
async def category(
|
||||
async def category( # noqa: PLR0913 need all these arguments because discord command.
|
||||
self,
|
||||
interaction: nextcord.Interaction,
|
||||
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),
|
||||
is_ascending: bool = nextcord.SlashOption(description="If lower value is a better score.", required=True),
|
||||
is_time_record: bool = nextcord.SlashOption(
|
||||
is_ascending: bool = nextcord.SlashOption(description="If lower value is a better score.", required=True), # noqa: FBT001
|
||||
is_time_record: bool = nextcord.SlashOption( # noqa: FBT001
|
||||
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
|
||||
),
|
||||
size: int = nextcord.SlashOption(
|
||||
@@ -50,8 +39,9 @@ class CreateCog(commands.Cog, name="Create Cog"):
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Creates a new category to be placed into group_id group. Fails if the category name already exists within the
|
||||
group. Then calls send_highscore_message().
|
||||
Creates a new category to be placed into group_id group.
|
||||
|
||||
Fails if the category name already exists within the group. Then calls send_highscore_message().
|
||||
|
||||
Args:
|
||||
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
|
||||
)
|
||||
|
||||
# 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.")
|
||||
async def group(
|
||||
self,
|
||||
@@ -118,8 +106,9 @@ class CreateCog(commands.Cog, name="Create Cog"):
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Creates a new category group to be placed into the database with the guild's id. Fails if the category group
|
||||
name already exists within the guild. Then calls send_highscore_message().
|
||||
Creates a new category group to be placed into the database with the guild's id.
|
||||
|
||||
Fails if the category group name already exists within the guild. Then calls send_highscore_message().
|
||||
|
||||
Args:
|
||||
interaction (nextcord.Interaction): Interaction for the command.
|
||||
@@ -156,7 +145,7 @@ class CreateCog(commands.Cog, name="Create Cog"):
|
||||
)
|
||||
|
||||
@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.
|
||||
|
||||
|
||||
+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
|
||||
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 holds commands for deleting.
|
||||
"""
|
||||
"""Class holds commands for deleting."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Default constructor for DeleteCog class.
|
||||
"""
|
||||
"""Default constructor for DeleteCog class."""
|
||||
|
||||
@nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True))
|
||||
async def delete(self, interaction: nextcord.Interaction):
|
||||
"""
|
||||
Parent slash command for deleting things. Never called.
|
||||
"""
|
||||
async def delete(self, interaction: nextcord.Interaction) -> None:
|
||||
"""Parent slash command for deleting things. Never called."""
|
||||
|
||||
@delete.subcommand()
|
||||
async def all(self, interaction: nextcord.Interaction):
|
||||
"""
|
||||
Subcommand parent for deleting all of a thing. Never called.
|
||||
"""
|
||||
async def all(self, interaction: nextcord.Interaction) -> None:
|
||||
"""Subcommand parent for deleting all of a thing. Never called."""
|
||||
|
||||
@all.subcommand(description="Clears highscores channel of all table messages. Does not change data.")
|
||||
async def channel_messages(
|
||||
@@ -55,6 +45,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
||||
) -> None:
|
||||
"""
|
||||
Deletes all group messages from channel. Does not change data.
|
||||
|
||||
Does not delete highscores channel id. Uses current channel if not supplied a channel.
|
||||
|
||||
Args:
|
||||
@@ -95,10 +86,11 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
||||
user_name: str = nextcord.SlashOption(
|
||||
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
|
||||
message is user was removed from that table.
|
||||
Searches all of guild's scores data for any score from the user and removes.
|
||||
|
||||
Updates table message is user was removed from that table.
|
||||
|
||||
Args:
|
||||
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))))
|
||||
|
||||
@delete.subcommand()
|
||||
async def user(self, interaction: nextcord.Interaction):
|
||||
"""
|
||||
Subcommand parent for deleting user information. Never called.
|
||||
"""
|
||||
async def user(self, interaction: nextcord.Interaction) -> None:
|
||||
"""Subcommand parent for deleting user information. Never called."""
|
||||
|
||||
@user.subcommand(description="Deletes a user's scores from a 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
|
||||
),
|
||||
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.
|
||||
|
||||
@@ -205,9 +195,9 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
||||
interaction: nextcord.Interaction,
|
||||
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),
|
||||
):
|
||||
) -> 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:
|
||||
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")
|
||||
@category.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.
|
||||
|
||||
@@ -245,7 +235,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
||||
|
||||
@from_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.
|
||||
|
||||
@@ -262,7 +252,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
||||
@from_group.on_autocomplete("user_name")
|
||||
async def user_autocomplete(
|
||||
self, interaction: nextcord.Interaction, user_name: str, group_id: str, category_id: str | None = None
|
||||
):
|
||||
) -> None:
|
||||
"""
|
||||
Autocomplete functions for user names.
|
||||
|
||||
@@ -279,12 +269,12 @@ class DeleteCog(commands.Cog, name="Table Management"):
|
||||
)
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
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))
|
||||
|
||||
+12
-22
@@ -1,29 +1,21 @@
|
||||
"""
|
||||
Commands for helping a user.
|
||||
"""
|
||||
"""Commands for helping a user."""
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
|
||||
class HelpCog(commands.Cog, name="Help commands."):
|
||||
"""
|
||||
Class holds commands for getting help.
|
||||
"""
|
||||
"""Class holds commands for getting help."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Default constructor for HelpCog class. Does nothing.
|
||||
"""
|
||||
"""Default constructor for HelpCog class. Does nothing."""
|
||||
|
||||
@nextcord.slash_command()
|
||||
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.")
|
||||
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.
|
||||
|
||||
@@ -31,15 +23,13 @@ class HelpCog(commands.Cog, name="Help commands."):
|
||||
interaction (nextcord.Interaction): Interaction that invoked the command.
|
||||
"""
|
||||
# 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."
|
||||
This channel will be where all user submission messages are sent.
|
||||
|
||||
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!"""
|
||||
# pylint: enable=line-too-long
|
||||
help_str: str = "Welcome to Agon! Three simple steps are needed to get started."
|
||||
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"
|
||||
help_str += '2.) Set up your highscore tables! Use "/create group" to first create a group message in the'
|
||||
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)
|
||||
|
||||
@@ -103,7 +103,9 @@ class SetCog(commands.Cog, name="Set command."):
|
||||
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(
|
||||
self,
|
||||
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
|
||||
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):
|
||||
@@ -145,87 +12,12 @@ class SubmissionCreateButton(nextcord.ui.View):
|
||||
self._bot = bot
|
||||
|
||||
@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
|
||||
"""Button for score submission."""
|
||||
# Don't allow a user to start a new submission if one already exists
|
||||
if submission_objects.has_user(interaction.user.id):
|
||||
await interaction.send("Please finish in progress submission first.", ephemeral=True)
|
||||
return
|
||||
async def submitcreate_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
||||
"""
|
||||
Button for score submission.
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(CategoryGroup.GroupId)
|
||||
.where(CategoryGroup.GuildId == interaction.guild_id)
|
||||
.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,
|
||||
Is deprecated, sends response telling user to use the new command instead.
|
||||
"""
|
||||
await interaction.response.send_message(
|
||||
"This feature has been deprecated. Please use the '/submit' slash command instead.", ephemeral=True
|
||||
)
|
||||
|
||||
# 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
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from database_setup.sql_data_classes import Submission, engine
|
||||
|
||||
# pylint: disable=import-error
|
||||
from utilities.utils import submit_score
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@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."""
|
||||
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
|
||||
async def approve_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
||||
"""Button for approving score submission."""
|
||||
for embed in interaction.message.embeds:
|
||||
content = embed.to_dict()
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(Submission)
|
||||
.where(Submission.GuildId == interaction.guild_id)
|
||||
.where(Submission.MessageId == interaction.message.id)
|
||||
)
|
||||
|
||||
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"]
|
||||
submission = session.scalar(stmt)
|
||||
|
||||
try:
|
||||
await submit_score(interaction, group_id, category_id, user, score)
|
||||
if submission is None:
|
||||
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 += 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])
|
||||
except UnboundLocalError:
|
||||
await interaction.send("Instruct user to submit with all fields.")
|
||||
await interaction.message.edit(view=None)
|
||||
await interaction.message.edit(content=content, view=None)
|
||||
|
||||
@@ -11,7 +11,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
|
||||
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(
|
||||
GuildId bigint unsigned not null unique,
|
||||
GuildName varchar(95) not null,
|
||||
SubmissionChannel bigint unsigned,
|
||||
SubmissionChannel bigint unsigned, -- deprecated, is not used anymore.
|
||||
VerificationChannel bigint unsigned,
|
||||
Active boolean DEFAULT true,
|
||||
PRIMARY KEY (GuildId)
|
||||
@@ -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,
|
||||
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 highscores bot
|
||||
"""
|
||||
"""Main file for Agon bot."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
@@ -9,25 +7,26 @@ import sys
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
from classes.submission import SubmissionMap
|
||||
|
||||
# pylint: disable=import-error
|
||||
from utilities import config
|
||||
|
||||
logger = logging.getLogger("nextcord")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w")
|
||||
nextcord_logger = logging.getLogger("nextcord")
|
||||
nextcord_logger.setLevel(logging.DEBUG)
|
||||
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"))
|
||||
logger.addHandler(handler)
|
||||
|
||||
submission_objects: SubmissionMap = SubmissionMap()
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
"""Main function for bot."""
|
||||
min_python_version = (3, 8)
|
||||
min_python_version = (3, 12)
|
||||
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()
|
||||
|
||||
# Allows privileged intents for monitoring members joining, roles editing, and role assignments
|
||||
@@ -35,13 +34,7 @@ def main():
|
||||
intents = nextcord.Intents.default()
|
||||
|
||||
# Required in order to read messages (eg. prefix commands)
|
||||
intents.message_content = True # pylint: disable=assigning-non-slot
|
||||
|
||||
# To enable the guilds priveleged intent:
|
||||
# intents.guilds = True
|
||||
|
||||
# To enable the members priveleged intent:
|
||||
# intents.members = True
|
||||
intents.message_content = True
|
||||
|
||||
bot = commands.Bot(
|
||||
commands.when_mentioned_or(config.BOT_PREFIX),
|
||||
@@ -53,10 +46,12 @@ def main():
|
||||
bot.load_extension(f"cogs.{folder}")
|
||||
|
||||
@bot.listen()
|
||||
async def on_ready():
|
||||
"""When Discord is connected"""
|
||||
assert bot.user is not None
|
||||
print(f"{bot.user.name} has connected to Discord!")
|
||||
async def on_ready() -> None:
|
||||
"""When Discord is connected."""
|
||||
if bot.user is None:
|
||||
logger.error("Discord connection failed.")
|
||||
return
|
||||
logger.info("%s has connected to Discord!", bot.user.name)
|
||||
|
||||
# Run Discord bot
|
||||
bot.run(config.DISCORD_TOKEN)
|
||||
|
||||
+11
-1
@@ -3,6 +3,10 @@
|
||||
"lockFileMaintenance": {
|
||||
"automerge": true
|
||||
},
|
||||
"major": {
|
||||
"automerge": false,
|
||||
"automergeType": "pr"
|
||||
},
|
||||
"minor": {
|
||||
"automerge": true,
|
||||
"automergeType": "branch"
|
||||
@@ -15,5 +19,11 @@
|
||||
"automerge": true,
|
||||
"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
|
||||
|
||||
GUILDS = [1000247361639030804]
|
||||
|
||||
|
||||
def autofill_categories(guild_id: int, group_id: int, category_begin: str) -> "dict[str, str]":
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user