Compare commits

..
17 Commits
Author SHA1 Message Date
alwayscompiling 0acfa77492 Change delete_scores to a slash command. 2022-08-08 22:07:12 -04:00
alwayscompiling ffefd08761 Updated presense to account for slash commands. 2022-08-08 21:54:27 -04:00
alwayscompiling a61662429a Remove help command. Doesn't work with slash commands. 2022-08-08 21:51:25 -04:00
alwayscompiling 57244bcbfe Update post highscores command to slash command. 2022-08-08 21:51:06 -04:00
alwayscompiling 3fd9ff46a3 imports cleanup. 2022-08-08 21:36:45 -04:00
alwayscompiling d4ad727f2a Correcting error statement. 2022-08-06 22:58:43 -04:00
alwayscompiling 516ff7feeb Update minimum python version. Nextcord v2.0 requires python 3.8. 2022-08-06 22:26:55 -04:00
alwayscompiling 0eb332aefd Added verify and submission register subcommands. Removed bot argument from Register class. 2022-08-06 22:21:08 -04:00
alwayscompiling ff07787a9a Create register group and highscore slash subcommand. 2022-08-06 20:14:30 -04:00
alwayscompiling b5959ccc33 Change constructor of score. Create new function to add score value from a string. 2022-08-06 17:32:17 -04:00
alwayscompiling b80f160914 Update verification view to use Table instead of Boss. 2022-08-06 16:55:06 -04:00
alwayscompiling 3b866be3ab Corrected typo in submission_state setter. 2022-08-06 16:45:05 -04:00
alwayscompiling e55b22c821 Easier to read submission access. 2022-08-06 16:41:50 -04:00
alwayscompiling f06e6f1e1e Create function to know if a user has a submission. 2022-08-06 16:30:49 -04:00
alwayscompiling 5f3e7361b4 Convert old submission_objects to new class. 2022-08-06 16:30:31 -04:00
alwayscompiling 835611a11e Created submission classes. 2022-08-06 16:10:19 -04:00
alwayscompiling ee9a23ca79 Renamed data folder to classes. 2022-08-06 14:59:58 -04:00
17 changed files with 511 additions and 319 deletions
+7 -3
View File
@@ -2,7 +2,7 @@
import json import json
from data.score import Score, ScoreTime # pylint: disable=import-error from classes.score import Score, ScoreTime # pylint: disable=import-error
class Category: class Category:
@@ -46,9 +46,13 @@ class Category:
category.time_record = data["time_record"] category.time_record = data["time_record"]
for user, score in data["scores"].items(): for user, score in data["scores"].items():
if category.time_record: if category.time_record:
category.add_score(ScoreTime(user, score)) score_obj: ScoreTime = ScoreTime(user)
score_obj.value = float(score)
category.add_score(score_obj)
else: else:
category.add_score(Score(user, score)) score_obj: Score = Score(user)
score_obj.value = int(score)
category.add_score(score_obj)
return category return category
+1 -1
View File
@@ -2,7 +2,7 @@
import json import json
from data.table import Table # pylint: disable=import-error from classes.table import Table # pylint: disable=import-error
class Guild: class Guild:
@@ -3,9 +3,9 @@
import os import os
import json import json
from data.guild import Guild # pylint: disable=import-error from classes.guild import Guild # pylint: disable=import-error
from data.table import Table # pylint: disable=import-error from classes.table import Table # pylint: disable=import-error
from data.category import Category # pylint: disable=import-error from classes.category import Category # pylint: disable=import-error
HIGHSCORES_BACKUP_DIR = "deleted_backups" HIGHSCORES_BACKUP_DIR = "deleted_backups"
+25 -4
View File
@@ -8,7 +8,7 @@ class Score:
# no user setter, it should never change. # no user setter, it should never change.
def __init__(self, user: str, value: int) -> None: def __init__(self, user: str) -> None:
""" """
Default constructor for Score class. Default constructor for Score class.
@@ -17,7 +17,7 @@ class Score:
value (int): Score's value value (int): Score's value
""" """
self._user: str = user self._user: str = user
self._value: int = value self._value: int = -1
def __str__(self) -> str: def __str__(self) -> str:
""" """
@@ -67,6 +67,15 @@ class Score:
""" """
return str(self._value) return str(self._value)
def set_value_str(self, value: str) -> None:
"""
Setter for score value from string.
Args:
value (str): int as a string
"""
self._value = int(value)
def to_str(self) -> str: def to_str(self) -> str:
""" """
Returns Score object as a string. Returns Score object as a string.
@@ -80,7 +89,7 @@ class Score:
class ScoreTime(Score): class ScoreTime(Score):
"""Class for a time based score. Inherits from Score.""" """Class for a time based score. Inherits from Score."""
def __init__(self, user: str, score: float) -> None: # pylint: disable=super-init-not-called def __init__(self, user: str) -> None: # pylint: disable=super-init-not-called
""" """
Default constructor for ScoreTime class. Default constructor for ScoreTime class.
@@ -90,7 +99,7 @@ class ScoreTime(Score):
""" """
# store time score as seconds. # store time score as seconds.
self._user: str = user self._user: str = user
self._score: float = score self._score: float = -1.0
@property @property
def value(self) -> float: def value(self) -> float:
@@ -121,3 +130,15 @@ class ScoreTime(Score):
""" """
milliseconds = round((self.value - int(self.value)) * 10) milliseconds = round((self.value - int(self.value)) * 10)
return time.strftime(f"%-M:%S.{milliseconds}", time.gmtime(self.value)) return time.strftime(f"%-M:%S.{milliseconds}", time.gmtime(self.value))
def set_value_str(self, value: str) -> None:
"""
Setter for score value from a string.
Args:
value (str): string representation of time
"""
if value.find(":"):
self._score = int(value.split(":")[0]) * 60 + float(value.split(":")[1])
else:
self._score = float(value)
+256
View File
@@ -0,0 +1,256 @@
"""
Contains classes and enum for tracking data on submissions.
"""
from enum import Enum
from nextcord import Embed
class SubmissionState(Enum):
"""
Enum for which state a submission is in
"""
CATEGORY = 1
SCORE = 2
PROOF = 3
NAME = 4
SUBMIT = 5
VERIFY = 6
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, table: str, 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: str = ""
self._guild_id: int = guild_id
self._message_id: int = 0
self._score: str = ""
self._submission_state: SubmissionState = SubmissionState.CATEGORY
self._table: str = table
self._username: str = username
@property
def category(self) -> str:
"""
Getter for Submission's Category name.
Returns:
str: Category name.
"""
return self._category
@category.setter
def category(self, category: str) -> None:
"""
Setter for Submission's Category name.
Args:
category (str): Category name
"""
self._category = 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 table(self) -> str:
"""
Getter for table Submission is for.
Returns:
str: table name
"""
return self._table
@table.setter
def table(self, table: str) -> None:
"""
Getter for table Submission is for.
Args:
table (str): table name
"""
self._table = 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_embed(self) -> 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: Input your score!"
elif state is SubmissionState.PROOF:
description = "Step 3: Submit your proof!"
elif state is SubmissionState.NAME:
description = (
"Step 4 (Optional): 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)
embed.add_field(name="Table", value=self.table, inline=False)
if self.category:
embed.add_field(name="Category", value=self.category, inline=False)
if self.score:
embed.add_field(name="Score", value=self.score, inline=False)
return embed
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 -1
View File
@@ -2,7 +2,7 @@
import json import json
from data.category import Category # pylint: disable=import-error from classes.category import Category # pylint: disable=import-error
class Table: class Table:
+24 -15
View File
@@ -2,8 +2,8 @@
Bot command for deleting the guild's scores. Bot command for deleting the guild's scores.
""" """
from nextcord import Embed
from nextcord.ext import commands from nextcord.ext import commands
import nextcord
from highscores import highscores_data # pylint: disable=import-error from highscores import highscores_data # pylint: disable=import-error
@@ -14,29 +14,38 @@ class DeleteScores(commands.Cog, name="Delete Scores"):
Deletes this guild's scores. Deletes this guild's scores.
""" """
def __init__(self, bot: commands.Bot): def __init__(self):
self.bot = bot """
Default constructor for DeleteScores class. Does nothing.
"""
@commands.command(name="delete-scores") @nextcord.slash_command(
@commands.has_permissions(administrator=True) description="Deletes all scores for this guild. Used for testing.",
async def delete_scores(self, ctx: commands.Context): default_member_permissions=nextcord.Permissions(administrator=True),
"""A command which deletes all scores for this guild. )
Usage: async def delete_scores(self, interaction: nextcord.Interaction):
``` """
?delete_scores A command which deletes all scores in all tables for this guild.
``` Command limits to admins to prevent devs from usings on non-test servers.
""" """
# only allow AlwaysCompiling to use. # only allow AlwaysCompiling to use.
# If you are a developer and want to use command, can add name to list # If you are a developer and want to use command, can add name to list
if ctx.author.id == 206602713511886849: if interaction.user.id == 206602713511886849:
for table in highscores_data.get_guild(ctx.guild.id).tables: for table in highscores_data.get_guild(interaction.guild_id).tables:
for category in table.categories: for category in table.categories:
category.clear() category.clear()
highscores_data.save_guild_data(highscores_data.get_guild(ctx.guild.id)) highscores_data.save_guild_data(highscores_data.get_guild(interaction.guild_id))
await interaction.response.send_message(
f"Cleared score data for {interaction.guild.name}.", ephemeral=True
)
else:
await interaction.response.send_message(
"You shouldn't be using this message.", ephemeral=True
)
# This function will be called when this extension is loaded. # This function will be called when this extension is loaded.
# It is necessary to add these functions to the bot. # It is necessary to add these functions to the bot.
def setup(bot: commands.Bot): def setup(bot: commands.Bot):
"""Adds function to bot""" """Adds function to bot"""
bot.add_cog(DeleteScores(bot)) bot.add_cog(DeleteScores())
-26
View File
@@ -1,26 +0,0 @@
"""
Help command
"""
from nextcord.ext import commands
from .help_command import NewHelpCommand
class HelpCog(commands.Cog):
"""Help Command"""
def __init__(self, bot: commands.Bot):
self.bot = bot
self._original_help_command = bot.help_command
bot.help_command = NewHelpCommand()
bot.help_command.cog = self
def cog_unload(self):
self.bot.help_command = self._original_help_command
# setup functions for bot
def setup(bot: commands.Bot):
"""Adds function to bot"""
bot.add_cog(HelpCog(bot))
-84
View File
@@ -1,84 +0,0 @@
"""
Help command. Copied from discord-bot-template.
"""
import nextcord
from nextcord.embeds import EmptyEmbed
from nextcord.ext import commands
import config # pylint: disable=import-error
class NewHelpCommand(commands.MinimalHelpCommand):
"""Custom help command override using embeds"""
COLOUR = nextcord.Colour.blurple()
def get_ending_note(self):
"""Returns note to display at the bottom"""
invoked_with = self.invoked_with
return f"Use {config.BOT_PREFIX}{invoked_with} [command] for more info on a command."
def get_command_signature(self, command: commands.core.Command):
"""Retrieves the signature portion of the help page."""
return f"{command.qualified_name} {command.signature}"
async def send_bot_help(self, mapping: dict):
"""implements bot command help page"""
embed = nextcord.Embed(title="Bot Commands", colour=self.COLOUR)
avatar = self.context.bot.user.avatar
avatar_url = avatar.url if avatar else EmptyEmbed
embed.set_author(name=self.context.bot.user.name, icon_url=avatar_url)
description = self.context.bot.description
if description:
embed.description = description
for cog, commands in mapping.items(): # pylint: disable=redefined-outer-name
name = "No Category" if cog is None else cog.qualified_name
filtered = await self.filter_commands(commands, sort=True)
if filtered:
# \u2002 = middle dot
value = "\u2002".join(f"{config.BOT_PREFIX}{c.name}" for c in filtered)
if cog and cog.description:
value = f"{cog.description}\n{value}"
embed.add_field(name=name, value=value)
embed.set_footer(text=self.get_ending_note())
await self.get_destination().send(embed=embed)
async def send_cog_help(self, cog: commands.Cog):
"""implements cog help page"""
embed = nextcord.Embed(title=f"{cog.qualified_name} Commands", colour=self.COLOUR)
if cog.description:
embed.description = cog.description
filtered = await self.filter_commands(cog.get_commands(), sort=True)
for command in filtered:
embed.add_field(
name=self.get_command_signature(command),
value=command.short_doc or "...",
inline=False,
)
embed.set_footer(text=self.get_ending_note())
await self.get_destination().send(embed=embed)
async def send_group_help(self, group: commands.Group):
"""implements group help page and command help page"""
embed = nextcord.Embed(title=group.qualified_name, colour=self.COLOUR)
if group.help:
embed.description = group.help
if isinstance(group, commands.Group):
filtered = await self.filter_commands(group.commands, sort=True)
for command in filtered:
embed.add_field(
name=self.get_command_signature(command),
value=command.short_doc or "...",
inline=False,
)
embed.set_footer(text=self.get_ending_note())
await self.get_destination().send(embed=embed)
# Use the same function as group help for command help
send_command_help = send_group_help # type: ignore
+11 -14
View File
@@ -9,10 +9,7 @@ import re
from nextcord.ext import commands from nextcord.ext import commands
import nextcord import nextcord
from cogs.submission.submission_views import ( # pylint: disable=import-error from classes.submission import SubmissionState # pylint: disable=import-error
SubmissionState,
get_submission_embed,
)
from highscores import highscores_data, submission_objects # pylint: disable=import-error from highscores import highscores_data, submission_objects # pylint: disable=import-error
@@ -38,8 +35,9 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
if chance == 1: if chance == 1:
await message.channel.send("Happy Birthday Chris <@642330132958281750>") await message.channel.send("Happy Birthday Chris <@642330132958281750>")
if message.author.id in submission_objects: if submission_objects.has_user(message.author.id):
message_id = submission_objects[message.author.id]["message_id"] submission = submission_objects.get_user_submission(message.author.id)
message_id = submission.message_id
submission_channel_id = highscores_data.get_guild(guild_id).submission_channel submission_channel_id = highscores_data.get_guild(guild_id).submission_channel
submission_message: nextcord.Message = ( submission_message: nextcord.Message = (
await self.bot.get_guild(guild_id) await self.bot.get_guild(guild_id)
@@ -49,14 +47,14 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
# need check if correct user and correct channel # need check if correct user and correct channel
if message.channel.id == submission_channel_id: if message.channel.id == submission_channel_id:
state = submission_objects[message.author.id]["submission_state"] state = submission.submission_state
if state == SubmissionState.SCORE and message.content: if state == SubmissionState.SCORE and message.content:
# check with regex if score matches correctly # check with regex if score matches correctly
is_time_record = ( is_time_record = (
highscores_data.get_guild(guild_id) highscores_data.get_guild(guild_id)
.get_table(submission_objects[message.author.id]["boss_name"]) .get_table(submission.table)
.get_category(submission_objects[message.author.id]["category"]) .get_category(submission.category)
.time_record .time_record
) )
@@ -89,9 +87,8 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
elif re.search("^[0]?[0-9]:[0-9]{2}(\\.[0-9])?$", score): elif re.search("^[0]?[0-9]:[0-9]{2}(\\.[0-9])?$", score):
# if there is a minute 10ths place 0, remove # if there is a minute 10ths place 0, remove
score = score.lstrip("0") score = score.lstrip("0")
submission_objects[message.author.id]["score"] = score submission.score = score
embed = get_submission_embed(message.author.id) embed = submission.get_embed()
await submission_message.edit( await submission_message.edit(
embed=embed, embed=embed,
) )
@@ -100,10 +97,10 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
files=[await attch.to_file() for attch in message.attachments], files=[await attch.to_file() for attch in message.attachments],
) )
elif state == SubmissionState.NAME and message.content: elif state == SubmissionState.NAME and message.content:
submission_objects[message.author.id]["username"] = message.content[ submission.username = message.content[
: highscores_data.get_guild(guild_id).username_length : highscores_data.get_guild(guild_id).username_length
] ]
embed = get_submission_embed(message.author.id) embed = submission.get_embed()
await submission_message.edit( await submission_message.edit(
embed=embed, embed=embed,
) )
+30 -19
View File
@@ -13,35 +13,44 @@ from utilities.utils import send_highscore_message # pylint: disable=import-err
class PostHighscores(commands.Cog, name="Post Highscores"): class PostHighscores(commands.Cog, name="Post Highscores"):
"""Posts Highscores.""" """Posts Highscores."""
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot) -> None:
"""
Default constructor for PostHighscores class
Args:
bot (commands.Bot): Bot object. Required to register submission button view.
"""
self.bot = bot self.bot = bot
@commands.command(name="post-scores") @nextcord.slash_command(
@commands.has_permissions(administrator=True) description="Triggers post/update of highscores to registered highscore channel.",
async def post_highscores(self, ctx: commands.Context): default_member_permissions=nextcord.Permissions(administrator=True),
)
async def post_highscore_tables(self, interaction: nextcord.Interaction) -> None:
""" """
A command which posts the highscore in the designated channel. A command which posts the highscore in the registered channel.
Sends warning if no channel has been registered. Sends warning if no channel has been registered.
Usage:
```
?post-scores
```
""" """
guild = highscores_data.get_guild(ctx.guild.id) guild = highscores_data.get_guild(interaction.guild_id)
print(f"Printing out highscores information in channel {guild.highscore_channel}") print(f"Printing out highscores information in channel {guild.highscore_channel}")
channel = ctx.guild.get_channel(guild.highscore_channel) channel = interaction.guild.get_channel(guild.highscore_channel)
# Error checking for registered highscore channel
error_response = "Registered Highscores channel does not exist or was never registered. " error_response = "Registered Highscores channel does not exist or was never registered. "
error_response += 'Register with "?register" command.' error_response += 'Use "/register" to register channels.'
assert channel is not None, await ctx.send(error_response) assert channel is not None, await interaction.response(error_response, ephemeral=True)
if len(highscores_data.get_guild(ctx.guild.id).tables) < 1: # Error checking for tables existing
await channel.send( # TODO fill out response string once functionality created.
"No tables have been created for this guild. Please create some tables." error_response = "No tables have been created for this guild. Please create some tables."
) assert (
return len(highscores_data.get_guild(interaction.guild_id).tables) > 0
), await interaction.response(error_response, ephemeral=True)
for table in highscores_data.get_guild(ctx.guild.id).tables: # awaiting response because posting scores takes a while
await interaction.response.defer(ephemeral=True)
for table in highscores_data.get_guild(interaction.guild_id).tables:
message: nextcord.Message = await send_highscore_message(channel, guild, table) message: nextcord.Message = await send_highscore_message(channel, guild, table)
await message.edit(view=SubmissionCreateButton(self.bot)) await message.edit(view=SubmissionCreateButton(self.bot))
@@ -49,6 +58,8 @@ class PostHighscores(commands.Cog, name="Post Highscores"):
# messages have been sent. # messages have been sent.
highscores_data.save_guild_data(guild) highscores_data.save_guild_data(guild)
await interaction.followup.send(f"Posted all tables to <#{channel.id}>.", ephemeral=True)
# This function will be called when this extension is loaded. # This function will be called when this extension is loaded.
# It is necessary to add these functions to the bot. # It is necessary to add these functions to the bot.
+7 -4
View File
@@ -35,14 +35,17 @@ class BotPresence(commands.Cog, name="Bot Presence"):
async def change_presence(self): async def change_presence(self):
"""Randomly chooses a presence and updates bot's presence.""" """Randomly chooses a presence and updates bot's presence."""
statuses = [ statuses = [
f"?help | {len(self.bot.guilds)} servers", "Now with slash commands!",
f"Vuln Pls. | {len(self.bot.guilds)} servers", "Vuln Pls.",
f"All your base are belong to us. | {len(self.bot.guilds)} servers", "All your base are belong to us.",
f"Double plus good. | {len(self.bot.guilds)} servers", "Double plus good.",
"That's a lot of damage!",
] ]
status = random.choice(statuses) status = random.choice(statuses)
status += f" | {len(self.bot.guilds)} servers"
await self.bot.change_presence(activity=nextcord.Game(status)) await self.bot.change_presence(activity=nextcord.Game(status))
+94 -49
View File
@@ -2,63 +2,108 @@
Bot command for registering various channels to the bot. Bot command for registering various channels to the bot.
""" """
from nextcord import Embed
from nextcord.ext import commands from nextcord.ext import commands
import nextcord
from highscores import highscores_data # pylint: disable=import-error from highscores import highscores_data # pylint: disable=import-error
class RegisterVerificationChannel(commands.Cog, name="Register"): class Register(commands.Cog, name="Register"):
"""Bot command for registering various channels to the bot.""" """
Class holds various register commands.
"""
def __init__(self, bot: commands.Bot) -> None: def __init__(self) -> None:
self.bot = bot """
Default constructor for Register class. Does nothing.
@commands.command(name="register")
@commands.has_permissions(administrator=True)
async def register_highscores(self, ctx: commands.Context, registration_type: str) -> None:
"""A command which registers current channel as for various uses.
Usage:
```
?register verify (Use in channel you want to verify submissions in.)
?register highscore (Use in channel you want to show highscore messages in.)
?register submission (Use in channel you want submission messages to be send it.)
?register help (Shows help message.)
```
""" """
# respond to the message
channel_id = ctx.channel.id
guild = highscores_data.get_guild(ctx.guild.id)
if registration_type == "verify": @nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True))
await ctx.send(f"Registered <#{channel_id}> as channel to post verifications in.") async def register(self):
guild.verification_channel = channel_id """
elif registration_type == "highscore": Parent slash command, never called.
await ctx.send(f"Registered <#{channel_id}> as channel to post highscores in.") """
guild.highscore_channel = channel_id
elif registration_type == "submission":
await ctx.send(f"Registered <#{channel_id}> as channel to post submissions in.")
guild.submission_channel = channel_id
else:
embed = Embed(
title="Register command help", description="How to use the register command."
)
embed.add_field(
name="?register verify", value="Use in channel you want to verify submissions in."
)
embed.add_field(
name="?register highscore",
value="Use in channel you want to show highscore messages in.",
)
embed.add_field(
name="?register submission",
value="Use in channel you want submission messages to be send it.",
)
embed.add_field(name="?register help", value="Use to show this message.")
await ctx.reply(embed=embed)
# return, don't need to save data
return
@register.subcommand(description="Registers the channel for perminent highscores messages.")
async def highscore(
self,
interaction: nextcord.Interaction,
channel: nextcord.abc.GuildChannel = nextcord.SlashOption(
description="Channel to register",
required=False,
channel_types=[nextcord.ChannelType.text],
default=None,
),
) -> None:
"""
Command function to register the highscore printing channel.
Args:
interaction (nextcord.Interaction): Interaction that invoked the command
channel (nextcord.abc.GuildChannel, optional): Channel to register
Is a SlashOption. Limited to text channels, Defaults to None.
"""
if not channel:
channel = interaction.channel
channel_id = channel.id
guild = highscores_data.get_guild(interaction.guild_id)
await interaction.send(f"Registered <#{channel_id}>.", ephemeral=True)
guild.highscore_channel = channel_id
highscores_data.save_guild_data(guild)
@register.subcommand(description="Registers the channel for submission messages.")
async def submission(
self,
interaction: nextcord.Interaction,
channel: nextcord.abc.GuildChannel = nextcord.SlashOption(
description="Channel to register",
required=False,
channel_types=[nextcord.ChannelType.text],
default=None,
),
) -> None:
"""
Command function to register the submission message channel.
Args:
interaction (nextcord.Interaction): Interaction that invoked the command
channel (nextcord.abc.GuildChannel, optional): Channel to register
Is a SlashOption. Limited to text channels, Defaults to None.
"""
if not channel:
channel = interaction.channel
channel_id = channel.id
guild = highscores_data.get_guild(interaction.guild_id)
await interaction.send(f"Registered <#{channel_id}>.", ephemeral=True)
guild.submission_channel = channel_id
highscores_data.save_guild_data(guild)
@register.subcommand(description="Registers the channel for verification messages.")
async def verify(
self,
interaction: nextcord.Interaction,
channel: nextcord.abc.GuildChannel = nextcord.SlashOption(
description="Channel to register",
required=False,
channel_types=[nextcord.ChannelType.text],
default=None,
),
) -> None:
"""
Command function to register the verification message channel.
Args:
interaction (nextcord.Interaction): Interaction that invoked the command
channel (nextcord.abc.GuildChannel, optional): Channel to register
Is a SlashOption. Limited to text channels, Defaults to None.
"""
if not channel:
channel = interaction.channel
channel_id = channel.id
guild = highscores_data.get_guild(interaction.guild_id)
await interaction.send(f"Registered <#{channel_id}>.", ephemeral=True)
guild.verification_channel = channel_id
highscores_data.save_guild_data(guild) highscores_data.save_guild_data(guild)
@@ -66,4 +111,4 @@ class RegisterVerificationChannel(commands.Cog, name="Register"):
# It is necessary to add these functions to the bot. # It is necessary to add these functions to the bot.
def setup(bot: commands.Bot) -> None: def setup(bot: commands.Bot) -> None:
"""Adds function to bot""" """Adds function to bot"""
bot.add_cog(RegisterVerificationChannel(bot)) bot.add_cog(Register())
+34 -81
View File
@@ -1,12 +1,11 @@
"""Views used for submission process.""" """Views used for submission process."""
from typing import Optional from typing import Optional
from enum import Enum
import nextcord import nextcord
from nextcord import Embed
from nextcord.ext import commands from nextcord.ext import commands
from classes.submission import Submission, SubmissionState # pylint: disable=import-error
from highscores import ( # pylint: disable=import-error from highscores import ( # pylint: disable=import-error
highscores_data, highscores_data,
highscores_message_map, highscores_message_map,
@@ -15,45 +14,6 @@ from highscores import ( # pylint: disable=import-error
from .verification_view import VerificationView from .verification_view import VerificationView
class SubmissionState(Enum):
"""Enum for which state a submission is in"""
CATEGORY = 1
SCORE = 2
PROOF = 3
NAME = 4
SUBMIT = 5
VERIFY = 6
def get_submission_embed(userid: int) -> nextcord.Embed:
"""Creates an embed based off of the submission state and fields filled out."""
usersubmission: dict = submission_objects[userid]
state: SubmissionState = usersubmission["submission_state"]
if state is SubmissionState.CATEGORY:
description = "Step 1: Choose your category!"
elif state is SubmissionState.SCORE:
description = "Step 2: Input your score!"
elif state is SubmissionState.PROOF:
description = "Step 3: Submit your proof!"
elif state is SubmissionState.NAME:
description = "Step 4 (Optional): 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=usersubmission["username"], inline=False)
embed.add_field(name="Boss", value=usersubmission["boss_name"], inline=False)
if usersubmission["category"]:
embed.add_field(name="Category", value=usersubmission["category"], inline=False)
if usersubmission["score"]:
embed.add_field(name="Score", value=usersubmission["score"], inline=False)
return embed
def category_select_options(guild_id: int, boss_name: str) -> "list[nextcord.SelectOption]": def category_select_options(guild_id: int, boss_name: str) -> "list[nextcord.SelectOption]":
"""Returns list of categories for a given boss.""" """Returns list of categories for a given boss."""
options: "list[nextcord.SelectOption]" = [] options: "list[nextcord.SelectOption]" = []
@@ -81,8 +41,9 @@ class SubmissionDropdown(nextcord.ui.Select):
self._bot = bot self._bot = bot
async def callback(self, interaction: nextcord.Interaction): async def callback(self, interaction: nextcord.Interaction):
submission_objects[interaction.user.id]["category"] = self.values[0] submission = submission_objects.get_user_submission(interaction.user.id)
embed = get_submission_embed(interaction.user.id) submission.category = self.values[0]
embed = submission.get_embed()
await interaction.message.edit( await interaction.message.edit(
embed=embed, embed=embed,
) )
@@ -96,10 +57,11 @@ class BackButton(nextcord.ui.Button):
self._bot = bot self._bot = bot
async def callback(self, interaction: nextcord.Interaction): async def callback(self, interaction: nextcord.Interaction):
state: SubmissionState = submission_objects[interaction.user.id]["submission_state"] submission = submission_objects.get_user_submission(interaction.user.id)
state: SubmissionState = submission.submission_state
state = SubmissionState(state.value - 1) state = SubmissionState(state.value - 1)
submission_objects[interaction.user.id]["submission_state"] = state submission.submission_state = state
embed = get_submission_embed(interaction.user.id) embed = submission.get_embed()
await interaction.edit(embed=embed, view=SubmissionView(self._bot, interaction.user.id)) await interaction.edit(embed=embed, view=SubmissionView(self._bot, interaction.user.id))
@@ -110,7 +72,7 @@ class CancelButton(nextcord.ui.Button):
super().__init__(label="Cancel", style=nextcord.ButtonStyle.red, custom_id="cancel") super().__init__(label="Cancel", style=nextcord.ButtonStyle.red, custom_id="cancel")
async def callback(self, interaction: nextcord.Interaction): async def callback(self, interaction: nextcord.Interaction):
submission_objects.pop(interaction.user.id) submission_objects.remove_user_submission(interaction.user.id)
await interaction.message.delete() await interaction.message.delete()
@@ -123,22 +85,18 @@ class NextButton(nextcord.ui.Button):
async def callback(self, interaction: nextcord.Interaction): async def callback(self, interaction: nextcord.Interaction):
# check if fields have been filled out for each state # check if fields have been filled out for each state
state: SubmissionState = submission_objects[interaction.user.id]["submission_state"] submission = submission_objects.get_user_submission(interaction.user.id)
if ( state: SubmissionState = submission.submission_state
state == SubmissionState.CATEGORY if state == SubmissionState.CATEGORY and not submission.category:
and not submission_objects[interaction.user.id]["category"]
):
await interaction.send("Please choose a category.", ephemeral=True) await interaction.send("Please choose a category.", ephemeral=True)
elif ( elif state == SubmissionState.SCORE and not submission.score:
state == SubmissionState.SCORE and not submission_objects[interaction.user.id]["score"]
):
await interaction.send("Please send a score.", ephemeral=True) await interaction.send("Please send a score.", ephemeral=True)
elif state == SubmissionState.PROOF and not len(interaction.message.attachments) > 0: elif state == SubmissionState.PROOF and not len(interaction.message.attachments) > 0:
await interaction.send("Please attach picture for proof.", ephemeral=True) await interaction.send("Please attach picture for proof.", ephemeral=True)
else: else:
state = SubmissionState(state.value + 1) state = SubmissionState(state.value + 1)
submission_objects[interaction.user.id]["submission_state"] = state submission.submission_state = state
embed = get_submission_embed(interaction.user.id) embed = submission.get_embed()
await interaction.edit(embed=embed, view=SubmissionView(self._bot, interaction.user.id)) await interaction.edit(embed=embed, view=SubmissionView(self._bot, interaction.user.id))
@@ -154,14 +112,15 @@ class SubmitButton(nextcord.ui.Button):
interaction.guild_id interaction.guild_id
).verification_channel ).verification_channel
channel = interaction.guild.get_channel(verification_channel_id) 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. \ error_response = "Registered Verification channel does not exist or was never registered. \
Contact server admins." Contact server admins."
assert channel is not None, await channel.send(error_response) assert channel is not None, await channel.send(error_response)
submission_objects[interaction.user.id]["submission_state"] = SubmissionState.VERIFY submission.submission_state = SubmissionState.VERIFY
embed = get_submission_embed(interaction.user.id) embed = submission.get_embed()
await channel.send( await channel.send(
files=[await attch.to_file() for attch in interaction.message.attachments], files=[await attch.to_file() for attch in interaction.message.attachments],
@@ -170,7 +129,7 @@ class SubmitButton(nextcord.ui.Button):
content=f"Submission from {interaction.user.display_name}", content=f"Submission from {interaction.user.display_name}",
) )
await interaction.message.edit(content=f"<@{interaction.user.id}> Submitted!", view=None) await interaction.message.edit(content=f"<@{interaction.user.id}> Submitted!", view=None)
submission_objects.pop(interaction.user.id) submission_objects.remove_user_submission(interaction.user.id)
############ Submission Views ############ ############ Submission Views ############
@@ -191,31 +150,25 @@ class SubmissionCreateButton(nextcord.ui.View):
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
"""Button for score submission.""" """Button for score submission."""
# Don't allow a user to start a new submission if one already exists # Don't allow a user to start a new submission if one already exists
if interaction.user.id in submission_objects: if submission_objects.has_user(interaction.user.id):
await interaction.send("Please finish in progress submission first.", ephemeral=True) await interaction.send("Please finish in progress submission first.", ephemeral=True)
return return
boss_name = highscores_message_map[interaction.guild.id][interaction.message.id] table = highscores_message_map[interaction.guild.id][interaction.message.id]
channel = interaction.guild.get_channel( channel = interaction.guild.get_channel(
highscores_data.get_guild(interaction.guild_id).submission_channel highscores_data.get_guild(interaction.guild_id).submission_channel
) )
# a user should only have 1 submission active at once. # a user should only have 1 submission active at once.
# submitting with dummy message_id until message created # split username on pipe then limit length.
# split username on pipe. username: str = interaction.user.display_name.split("|")[0][
submission_objects[interaction.user.id] = { : highscores_data.get_guild(interaction.guild_id).username_length
"message_id": 0, ]
"guild_id": interaction.guild_id, submission: Submission = Submission(interaction.guild_id, table, username)
"submission_state": SubmissionState.CATEGORY,
"boss_name": boss_name,
"score": "",
"category": "",
"username": interaction.user.display_name.split("|")[0][
: highscores_data.get_guild(interaction.guild_id).username_length
],
}
embed = get_submission_embed(interaction.user.id) submission_objects.add_submission(interaction.user.id, submission)
embed = submission.get_embed()
submission_message = await channel.send( submission_message = await channel.send(
f"<@{interaction.user.id}>", f"<@{interaction.user.id}>",
@@ -227,7 +180,7 @@ class SubmissionCreateButton(nextcord.ui.View):
) )
# update the message_id once its created # update the message_id once its created
submission_objects[interaction.user.id]["message_id"] = submission_message.id submission.message_id = submission_message.id
class SubmissionView(nextcord.ui.View): class SubmissionView(nextcord.ui.View):
@@ -243,9 +196,9 @@ class SubmissionView(nextcord.ui.View):
super().__init__(timeout=timeout) super().__init__(timeout=timeout)
self._submission_user = submission_user self._submission_user = submission_user
self._bot = bot self._bot = bot
self._state = submission_objects[self._submission_user]["submission_state"] self._state = submission_objects.get_user_submission(self._submission_user).submission_state
self._boss_name = submission_objects[self._submission_user]["boss_name"] self._boss_name = submission_objects.get_user_submission(self._submission_user).table
self._guild_id = submission_objects[self._submission_user]["guild_id"] self._guild_id = submission_objects.get_user_submission(self._submission_user).guild_id
# add items depending on given state # add items depending on given state
if self._state is SubmissionState.CATEGORY: if self._state is SubmissionState.CATEGORY:
@@ -275,4 +228,4 @@ class SubmissionView(nextcord.ui.View):
async def on_timeout(self) -> None: async def on_timeout(self) -> None:
# remove user from submission objects after time out. Allows user to start new submission. # remove user from submission objects after time out. Allows user to start new submission.
submission_objects.pop(self._submission_user) submission_objects.remove_user_submission(self._submission_user)
+8 -6
View File
@@ -1,7 +1,7 @@
"""View for verification buttons""" """View for verification buttons"""
import nextcord import nextcord
from data.score import Score, ScoreTime # pylint: disable=import-error from classes.score import Score, ScoreTime # pylint: disable=import-error
from highscores import highscores_data # pylint: disable=import-error from highscores import highscores_data # pylint: disable=import-error
from utilities.utils import submit_score # pylint: disable=import-error from utilities.utils import submit_score # pylint: disable=import-error
@@ -32,8 +32,8 @@ class VerificationView(nextcord.ui.View):
content = interaction.message.embeds[0].to_dict() content = interaction.message.embeds[0].to_dict()
for field in content["fields"]: for field in content["fields"]:
if field["name"] == "Boss": if field["name"] == "Table":
boss_name = field["value"] table = field["value"]
elif field["name"] == "Category": elif field["name"] == "Category":
category = field["value"] category = field["value"]
elif field["name"] == "Score": elif field["name"] == "Score":
@@ -43,12 +43,14 @@ class VerificationView(nextcord.ui.View):
try: try:
submission_guild = highscores_data.get_guild(interaction.guild_id) submission_guild = highscores_data.get_guild(interaction.guild_id)
submission_table = submission_guild.get_table(boss_name) submission_table = submission_guild.get_table(table)
submission_category = submission_table.get_category(category) submission_category = submission_table.get_category(category)
if submission_category.time_record: if submission_category.time_record:
submission_score: ScoreTime = ScoreTime(user, float(score)) submission_score: ScoreTime = ScoreTime(user)
submission_score.set_value_str(score)
else: else:
submission_score: Score = Score(user, int(score)) submission_score: Score = Score(user)
submission_score.set_value_str(score)
await submit_score( await submit_score(
interaction, interaction,
+6 -5
View File
@@ -6,11 +6,12 @@ import logging
import os import os
import sys import sys
import nextcord
from nextcord.ext import commands from nextcord.ext import commands
import nextcord
import config import config
from data.highscores_data import HighscoresData from classes.highscores_data import HighscoresData
from classes.submission import SubmissionMap
logger = logging.getLogger("nextcord") logger = logging.getLogger("nextcord")
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@@ -27,14 +28,14 @@ for guild in highscores_data.guilds:
for table in guild.tables: for table in guild.tables:
highscores_message_map[guild.guild_id][table.message_id] = table.name highscores_message_map[guild.guild_id][table.message_id] = table.name
submission_objects: dict = {} submission_objects: SubmissionMap = SubmissionMap()
def main(): def main():
"""Main function for bot.""" """Main function for bot."""
min_python_version = (3, 7) min_python_version = (3, 8)
if sys.version_info < min_python_version: if sys.version_info < min_python_version:
print("ERROR: Python version must be >= 3.7") print("ERROR: Python version must be >= 3.8")
sys.exit() sys.exit()
# Allows privledged intents for monitoring members joining, roles editing, and role assignments # Allows privledged intents for monitoring members joining, roles editing, and role assignments
+4 -4
View File
@@ -4,10 +4,10 @@ Contains various utlity functions for highscores bot
import nextcord import nextcord
from highscores import highscores_data, highscores_message_map # pylint: disable=import-error from highscores import highscores_data, highscores_message_map # pylint: disable=import-error
from data.score import Score # pylint: disable=import-error from classes.score import Score # pylint: disable=import-error
from data.guild import Guild # pylint: disable=import-error from classes.guild import Guild # pylint: disable=import-error
from data.category import Category # pylint: disable=import-error from classes.category import Category # pylint: disable=import-error
from data.table import Table # pylint: disable=import-error from classes.table import Table # pylint: disable=import-error
def format_highscore_message(guild: Guild, table: Table) -> str: def format_highscore_message(guild: Guild, table: Table) -> str: