Compare commits

..
14 changed files with 295 additions and 409 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
-3
View File
@@ -125,9 +125,6 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
# IDE
.vscode/
# logging # logging
nextcord.log nextcord.log
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"kevinrose.vsc-python-indent",
"charliermarsh.ruff",
"donjayamanne.python-extension-pack"
]
}
+16 -27
View File
@@ -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
View File
@@ -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))
+218
View File
@@ -0,0 +1,218 @@
"""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 utilities.utils import 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()
async def submit(
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"),
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 not interaction.message.attachments > 0:
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
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 score is None:
return
# Create submission verification request or submit score if verification not needed.
if not category.IsValidated:
await submit_score(interaction, group_id, category_id, username, score)
await interaction.response.send_message("Score has been submitted!", ephemeral=True)
return
stmt = select(Guild.VerificationChannel).where(Guild.GuildId == interaction.guild_id)
verification_channel_id = session.scalar(stmt)
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)
# Send the verification message to admins
verification_message = await channel.send(
files=[await attach.to_file() for attach in interaction.message.attachments],
view=VerificationView(),
embeds=self.create_embed(interaction, group_id, category_id, 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=score,
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 -1
View File
@@ -12,8 +12,8 @@ from sqlalchemy.orm import Session
# pylint: disable=import-error # pylint: disable=import-error
from classes.submission import SubmissionState from classes.submission import SubmissionState
from main import submission_objects
from database_setup.sql_data_classes import Category, Guild, engine from database_setup.sql_data_classes import Category, Guild, engine
from main import submission_objects
class SubmissionListener(commands.Cog, name="Message Listener"): class SubmissionListener(commands.Cog, name="Message Listener"):
+1 -1
View File
@@ -7,8 +7,8 @@ from sqlalchemy.orm import Session, aliased
# pylint: disable=import-error # pylint: disable=import-error
from classes.submission import Submission, SubmissionState from classes.submission import Submission, SubmissionState
from main import submission_objects
from database_setup.sql_data_classes import Category, CategoryGroup, Guild, engine from database_setup.sql_data_classes import Category, CategoryGroup, Guild, engine
from main import submission_objects
from .verification_view import VerificationView from .verification_view import VerificationView
+5 -5
View File
@@ -1,4 +1,4 @@
"""View for verification buttons""" """View for verification buttons."""
import nextcord import nextcord
@@ -7,18 +7,18 @@ 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: for embed in interaction.message.embeds:
content = embed.to_dict() content = embed.to_dict()
+3 -2
View File
@@ -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
-335
View File
@@ -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
}
+9
View File
@@ -42,4 +42,13 @@ CREATE TABLE Scores(
CategoryId int unsigned not null, CategoryId int unsigned not null,
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 int unsigned not null,
MessageId int unsigned not null,
Score varchar(30) not null, -- Score is stored as a string until converted during submission
UserName varchar(30) not null
); );
+11 -1
View File
@@ -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"
}
}
} }