Compare commits

..
Author SHA1 Message Date
alwayscompiling 39c858af64 Correct regex to ensure start and end of string. 2022-07-23 15:23:16 -04:00
alwayscompiling 093861b2e6 Improve readability with guild id variable. 2022-07-23 14:26:07 -04:00
alwayscompiling 4ef487a8c4 Saving deleted data from left servers. 2022-07-23 14:12:32 -04:00
alwayscompiling 849ebb966a Guild id is a string in message map. 2022-07-23 14:02:47 -04:00
alwayscompiling f1b0e34194 message_map doesn't need guild id as a string. 2022-07-23 00:06:56 -04:00
alwayscompiling 7cdc7d2cef Need empty dict before adding to it. 2022-07-23 00:00:18 -04:00
alwayscompiling db4672dcda Correct spacing. 2022-07-22 23:59:46 -04:00
alwayscompiling fc2b601687 Somehow removed await on fetch message. 2022-07-22 23:44:33 -04:00
alwayscompiling 493d44fa19 Pass guild_data when needed. 2022-07-22 23:41:00 -04:00
alwayscompiling 5d3a1568a2 Guild id is a string in highscores data json. 2022-07-22 23:39:51 -04:00
alwayscompiling be29194a57 Get guild data everywhere highscores_data was used. 2022-07-22 23:33:11 -04:00
alwayscompiling d067da2300 Changed config table name. 2022-07-22 23:02:24 -04:00
alwayscompiling 6d0c10e1cc Create empty dict for each guild during message_map creation. 2022-07-22 22:41:35 -04:00
alwayscompiling 609bfa1277 Update message map and send highscore message for multiple guilds. 2022-07-22 22:36:47 -04:00
alwayscompiling 158e53e799 Add on_guild_remove listener. 2022-07-22 22:09:09 -04:00
alwayscompiling 3cc67a4e9e Corrected skeleton provider. Add cog for guild listener. 2022-07-22 22:05:21 -04:00
alwayscompiling df80eb2952 Move data skeleton creation out of opening highscores data. 2022-07-22 21:52:13 -04:00
alwayscompiling b78eb7b9bf Changed order of functions. 2022-07-22 21:41:45 -04:00
alwayscompiling 65ce143cfc Merge branch '18-remove-boss-message-map' into develop 2022-07-22 21:37:29 -04:00
alwayscompiling 8dbb2a6895 Missed adding tables one place. 2022-07-22 21:34:23 -04:00
alwayscompiling a24fe52182 Message id is no longer a string in the message map. 2022-07-22 21:32:18 -04:00
alwayscompiling d0131b1044 Updated all table data accesses to have table as part of access. 2022-07-22 21:30:46 -04:00
alwayscompiling 307ef86493 Removed old usage of message map from file. 2022-07-22 21:22:17 -04:00
alwayscompiling a1a9fae9fa Generating message map on bot start. 2022-07-22 21:18:01 -04:00
alwayscompiling 56984b3df0 Remove unnecessary intent activation. 2022-07-22 20:59:25 -04:00
alwayscompiling 7a261d56fc Move formatting to when score is retrieved. 2022-07-21 10:31:07 -04:00
alwayscompiling 4ee2280565 Correcting example runescape config. 2022-07-21 10:04:52 -04:00
alwayscompiling a24900a957 Merge branch '15-format-messages-to-look-cleaner' into develop 2022-07-20 22:56:22 -04:00
alwayscompiling ef17d3dc3e Made all scores line up properly. 2022-07-20 22:55:40 -04:00
alwayscompiling d711763c80 Standardize time score format. 2022-07-20 22:35:33 -04:00
alwayscompiling 8318bf741a Remove old TODO. 2022-07-20 22:32:59 -04:00
alwayscompiling 17c1915810 Change location of approve button. Should be on right. 2022-07-20 22:31:39 -04:00
alwayscompiling 674ea94588 Pad username out to max length. 2022-07-20 22:24:50 -04:00
alwayscompiling 090184c1e9 Tracking username length limit in data file. 2022-07-20 22:17:20 -04:00
alwayscompiling 31fa0b50a3 Added regex for time and int submissions. 2022-07-20 22:06:07 -04:00
alwayscompiling 374632bbc6 Removing TODOs. 2022-07-20 20:58:17 -04:00
alwayscompiling 8a36c9dac9 Users cannot move to next step without completing current step. 2022-07-20 20:50:29 -04:00
alwayscompiling 1900e21f72 Updating message listener to work with states. 2022-07-20 20:37:29 -04:00
alwayscompiling 2c85483db5 Buttons now change views, embed has proper steps. Added todos for what is next. 2022-07-19 22:29:06 -04:00
alwayscompiling 63db9fb7d4 Create buttons and enum for submission state navigation. 2022-07-19 21:05:37 -04:00
alwayscompiling 5155744ec5 Made config file more efficient. 2022-07-13 22:02:40 -04:00
alwayscompiling fedc8be778 Adding VerificationView to bot on cog load. 2022-07-13 20:01:21 -04:00
alwayscompiling 125939e5b3 Updating runescape_bosses.json example config to follow new config style. 2022-07-12 21:26:12 -04:00
alwayscompiling 3b410b5f90 Redid configs to allow for unique categories on each table. 2022-07-12 21:00:51 -04:00
alwayscompiling d7bac2048f Changed listener to allow for score or attachment by themselves. 2022-07-12 19:59:21 -04:00
alwayscompiling dd4bee73c6 Approved message now mentions person who approved. 2022-07-12 18:30:25 -04:00
alwayscompiling d21683fb59 Only add best score if its new, only delete worse scores if they exist. 2022-07-12 16:07:20 -04:00
alwayscompiling 44b3d7a80b Merge branch 'develop' into 4-user-score-update 2022-07-12 15:59:42 -04:00
alwayscompiling af5017c95d Changed id of verification views. Solved problem for long lasting buttons. 2022-07-12 15:59:17 -04:00
alwayscompiling 3087c6bc39 Worked out dup check method, just need to debug. 2022-07-12 15:58:39 -04:00
alwayscompiling cf0ef96edc Merge branch 'develop' into 4-user-score-update 2022-07-12 15:22:07 -04:00
alwayscompiling 9ba37c22bb Splitting username on pipe. 2022-07-12 15:18:59 -04:00
alwayscompiling 8eeda14119 Strip leading 0 and : from scores. 2022-07-12 15:15:37 -04:00
alwayscompiling 115e3dd3d0 Shortening the pad size for messages a little. 2022-07-10 21:59:11 -04:00
alwayscompiling 9ef2acf34c Capping user names to 12 characters because runescape names. Will need to revisit user name limit later. 2022-07-10 21:51:19 -04:00
alwayscompiling 82f47ac136 Temporary attempt. Makes assumption that a user will submit a better score. 2022-07-10 21:47:01 -04:00
alwayscompiling 4a15c63e8c Corrected some comments. 2022-07-10 21:11:51 -04:00
alwayscompiling 4c6279b444 Made post command admin only. 2022-07-10 21:08:30 -04:00
alwayscompiling 39f4a108ca Fixing hardmode category searching. 2022-07-10 20:33:39 -04:00
alwayscompiling 83f0bf6498 Slight time score bug fixes. 2022-07-10 20:27:56 -04:00
alwayscompiling 3f5ebdf175 Changed listener check to be more efficient. 2022-07-10 20:16:52 -04:00
alwayscompiling f98c80d200 Updated comments in message listener. 2022-07-10 20:09:27 -04:00
alwayscompiling b61008518c Moved message listener to a cog. Listener is set up on bot run. Listener listens to all messages. Pulls score and image from a message sent in submission channel if a user has an active submission. 2022-07-10 20:05:20 -04:00
alwayscompiling 198d9008b9 Fixed missing parameter. 2022-07-09 19:46:50 -04:00
14 changed files with 841 additions and 363 deletions
+2 -1
View File
@@ -133,4 +133,5 @@ nextcord.log
# data file for bot # data file for bot
highscores_data.json highscores_data.json
highscores_message_map.json highscores_message_map.json
highscores_config.json highscores_config.json
deleted_backups/
+54
View File
@@ -0,0 +1,54 @@
"""
Defines cog for guild listeners. Contains functions for various guild events.
"""
import os
import json
from nextcord.ext import commands
import nextcord
from highscores import highscores_data # pylint: disable=import-error
from utilities.data_storage import create_highscore_skeleton # pylint: disable=import-error
from utilities.data_storage import save_highscores_data # pylint: disable=import-error
class GuildListener(commands.Cog, name="Guild Listener"):
"""Sets up message listener."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.Cog.listener()
async def on_guild_join(self, guild: nextcord.Guild):
"""Listener for Guild Join. Creates skeleton for new guild joined and saves it."""
# turning guild id into string because json doesn't support ints as object keys.
highscores_data[str(guild.id)] = create_highscore_skeleton()
save_highscores_data(highscores_data)
welcome_message: str = "Hello, I keep track of Highscores for you!"
welcome_message += " Currently only supporting RuneScape boss highscores,"
welcome_message += " but will be more dynamic support in future."
welcome_message += ' For assistance on getting started, use "?register help".'
await guild.system_channel.send(content=welcome_message)
@commands.Cog.listener()
async def on_guild_remove(self, guild: nextcord.Guild):
"""Listener for Guild Remove. Removes data from highscores_data."""
print(f"Removing data for guild {guild.name} (id {guild.id}).")
old_data = highscores_data.pop(str(guild.id))
save_highscores_data(highscores_data)
if not os.path.exists("deleted_backups"):
os.mkdir("deleted_backups")
with open(f"deleted_backups/{guild.name}_backup.json", encoding="utf-8", mode="w") as file:
file.write(json.dumps(old_data, indent=4))
# 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(GuildListener(bot))
+96
View File
@@ -0,0 +1,96 @@
"""
Defines cog for on message listener.
"""
import re
from nextcord.ext import commands
import nextcord
from cogs.submission.submission_views import SubmissionState # pylint: disable=import-error
from cogs.submission.submission_views import get_submission_embed # pylint: disable=import-error
from highscores import highscores_config # pylint: disable=import-error
from highscores import highscores_data # pylint: disable=import-error
from highscores import submission_objects # pylint: disable=import-error
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."""
guild_id = message.guild.id
guild_data = highscores_data[str(guild_id)]
if message.author.id in submission_objects:
message_id = submission_objects[message.author.id]["message_id"]
submission_message: nextcord.Message = (
await self.bot.get_guild(guild_id)
.get_channel(guild_data["submission_channel_id"])
.fetch_message(message_id)
)
# need check if correct user and correct channel
if message.channel.id == guild_data["submission_channel_id"]:
state = submission_objects[message.author.id]["submission_state"]
if state == SubmissionState.SCORE and message.content:
# check with regex if score matches correctly
is_time_record = highscores_config["categories"][
submission_objects[message.author.id]["category"]
]["is_time_record"]
time_regex = re.search(
"^([0-9]{1,2}:)?[0-9]{2}(\.[0-9])?$", # pylint: disable=anomalous-backslash-in-string
message.content,
)
int_regex = re.search("^[0-9]+$", message.content)
if time_regex is None and is_time_record:
embed = submission_message.embeds[0]
embed.add_field(
name="Error",
value="Time record must follow format MM:SS.ms or SS.ms or SS.",
)
elif int_regex is None and not is_time_record:
embed = submission_message.embeds[0]
embed.add_field(name="Error", value="Number record must only be number.")
else:
score = message.content
if is_time_record:
# standardize time score format.
# add colon if its not there
if score.find(":") == -1:
score = "00:" + score
# add a 0 if there isn't 2 digits before a colon
elif len(score.split(":", maxsplit=1)[0]) < 2:
score = "0" + score
submission_objects[message.author.id]["score"] = score
embed = get_submission_embed(message.author.id)
await submission_message.edit(
embed=embed,
)
elif state == SubmissionState.PROOF and len(message.attachments) > 0:
await submission_message.edit(
files=[await attch.to_file() for attch in message.attachments],
)
elif state == SubmissionState.NAME and message.content:
submission_objects[message.author.id]["username"] = message.content[
: guild_data["username_length"]
]
embed = get_submission_embed(message.author.id)
await submission_message.edit(
embed=embed,
)
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))
+11 -6
View File
@@ -5,7 +5,7 @@ Bot command to post the highscores
from nextcord.ext import commands from nextcord.ext import commands
import nextcord import nextcord
from cogs.submission.submission_views import SubmissionButton # pylint: disable=import-error from cogs.submission.submission_views import SubmissionCreateButton # pylint: disable=import-error
from highscores import highscores_config # pylint: disable=import-error from highscores import highscores_config # pylint: disable=import-error
from highscores import highscores_data # pylint: disable=import-error from highscores import highscores_data # pylint: disable=import-error
from utilities.data_storage import save_highscores_data # pylint: disable=import-error from utilities.data_storage import save_highscores_data # pylint: disable=import-error
@@ -19,6 +19,7 @@ class PostHighscores(commands.Cog, name="Post Highscores"):
self.bot = bot self.bot = bot
@commands.command(name="post-scores") @commands.command(name="post-scores")
@commands.has_permissions(administrator=True)
async def post_highscores(self, ctx: commands.Context): async def post_highscores(self, ctx: commands.Context):
""" """
A command which posts the highscore in the designated channel. A command which posts the highscore in the designated channel.
@@ -28,18 +29,22 @@ class PostHighscores(commands.Cog, name="Post Highscores"):
?post-scores ?post-scores
``` ```
""" """
highscore_channel_id = highscores_data["highscore_channel_id"] guild_data = highscores_data[str(ctx.guild.id)]
highscore_channel_id = guild_data["highscore_channel_id"]
print(f"Printing out highscores information in channel {highscore_channel_id}") print(f"Printing out highscores information in channel {highscore_channel_id}")
channel = self.bot.get_channel(highscore_channel_id) channel = ctx.guild.get_channel(highscore_channel_id)
error_response = 'Registered Highscores channel does not exist or was never registered. \n\ error_response = 'Registered Highscores channel does not exist or was never registered. \n\
Register with "?register" command.' Register with "?register" command.'
assert channel is not None, await ctx.send(error_response) assert channel is not None, await ctx.send(error_response)
for boss in highscores_config["bosses"]: for boss, categories in highscores_config[ # pylint: disable=unused-variable
message: nextcord.Message = await send_highscore_message(channel, boss["boss"]) "tables"
await message.edit(view=SubmissionButton()) ].items():
message: nextcord.Message = await send_highscore_message(channel, boss, guild_data)
await message.edit(view=SubmissionCreateButton(self.bot))
# Saving in case new message was sent.
save_highscores_data(highscores_data) save_highscores_data(highscores_data)
+4 -3
View File
@@ -29,16 +29,17 @@ class RegisterVerificationChannel(commands.Cog, name="Register"):
""" """
# respond to the message # respond to the message
channel_id = ctx.channel.id channel_id = ctx.channel.id
guild_data = highscores_data[str(ctx.guild.id)]
if registration_type == "verify": if registration_type == "verify":
await ctx.send(f"Registered <#{channel_id}> as channel to post verifications in.") await ctx.send(f"Registered <#{channel_id}> as channel to post verifications in.")
highscores_data["verification_channel_id"] = channel_id guild_data["verification_channel_id"] = channel_id
elif registration_type == "highscore": elif registration_type == "highscore":
await ctx.send(f"Registered <#{channel_id}> as channel to post highscores in.") await ctx.send(f"Registered <#{channel_id}> as channel to post highscores in.")
highscores_data["highscore_channel_id"] = channel_id guild_data["highscore_channel_id"] = channel_id
elif registration_type == "submission": elif registration_type == "submission":
await ctx.send(f"Registered <#{channel_id}> as channel to post submissions in.") await ctx.send(f"Registered <#{channel_id}> as channel to post submissions in.")
highscores_data["submission_channel_id"] = channel_id guild_data["submission_channel_id"] = channel_id
else: else:
embed = Embed( embed = Embed(
title="Register command help", description="How to use the register command." title="Register command help", description="How to use the register command."
+7 -5
View File
@@ -3,8 +3,8 @@ Bot command to submit a score
""" """
from nextcord.ext import commands from nextcord.ext import commands
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 .verification_view import VerificationView from .verification_view import VerificationView
@@ -26,13 +26,15 @@ class Score(commands.Cog, name="Score"):
Usage: Usage:
``` ```
?score "Boss Name" "Category" "score" ?score "Boss Name" "Category" "score"
This command still works if you prefer to use it over the ui.
``` ```
""" """
verification_channel_id = highscores_data["verification_channel_id"] guild_data = highscores_data[str(ctx.guild.id)]
channel = self.bot.get_channel(verification_channel_id) verification_channel_id = guild_data["verification_channel_id"]
channel = ctx.guild.get_channel(verification_channel_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." error_response += "Contact server admins."
assert channel is not None, await ctx.send(error_response) assert channel is not None, await ctx.send(error_response)
verification_string = f"{ctx.author.display_name}:{boss_name}:{category}:{score}" verification_string = f"{ctx.author.display_name}:{boss_name}:{category}:{score}"
+6 -2
View File
@@ -10,7 +10,9 @@ class VerificationView(nextcord.ui.View):
def __init__(self): def __init__(self):
super().__init__(timeout=None) super().__init__(timeout=None)
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve") @nextcord.ui.button(
label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve-score-command"
)
async def approve_button( async def approve_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
@@ -28,7 +30,9 @@ class VerificationView(nextcord.ui.View):
files=[await attch.to_file() for attch in interaction.message.attachments], files=[await attch.to_file() for attch in interaction.message.attachments],
) )
@nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red, custom_id="deny") @nextcord.ui.button(
label="Deny", style=nextcord.ButtonStyle.red, custom_id="deny-score-command"
)
async def deny_button( async def deny_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
+4 -2
View File
@@ -4,7 +4,8 @@ Cog for creating submission process.
from nextcord.ext import commands from nextcord.ext import commands
from .submission_views import SubmissionButton from .submission_views import SubmissionCreateButton
from .verification_view import VerificationView
class SubmissionCog(commands.Cog, name="Submission Cog"): class SubmissionCog(commands.Cog, name="Submission Cog"):
@@ -16,7 +17,8 @@ class SubmissionCog(commands.Cog, name="Submission Cog"):
@commands.Cog.listener() @commands.Cog.listener()
async def on_ready(self): async def on_ready(self):
"""Called when cog is loaded""" """Called when cog is loaded"""
self.bot.add_view(SubmissionButton(self.bot)) self.bot.add_view(SubmissionCreateButton(self.bot))
self.bot.add_view(VerificationView())
# This function will be called when this extension is loaded. # This function will be called when this extension is loaded.
+205 -75
View File
@@ -1,62 +1,69 @@
"""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 utilities.data_storage import open_message_map # pylint: disable=import-error from highscores import highscores_config # pylint: disable=import-error
from highscores import highscores_data # pylint: disable=import-error from highscores import highscores_data # pylint: disable=import-error
from highscores import highscores_message_map # pylint: disable=import-error
from highscores import submission_objects # pylint: disable=import-error
from .verification_view import VerificationView from .verification_view import VerificationView
class SubmissionButton(nextcord.ui.View): class SubmissionState(Enum):
"""Defines view for score submission button.""" """Enum for which state a submission is in"""
def __init__(self, bot: commands.Bot): CATEGORY = 1
super().__init__(timeout=None) SCORE = 2
self._bot = bot PROOF = 3
NAME = 4
SUBMIT = 5
VERIFY = 6
@nextcord.ui.button(label="Submit", style=nextcord.ButtonStyle.primary, custom_id="submit")
async def submit_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
"""Button for score submission."""
highscores_message_map = open_message_map()
boss_name = highscores_message_map[str(interaction.message.id)]
embed = nextcord.Embed(title="Highscores Submission")
embed.add_field(name="Boss", value=boss_name, inline=False)
embed.add_field(name="User", value=interaction.user.display_name, inline=False)
channel = self._bot.get_channel(highscores_data["submission_channel_id"])
submission_message = await channel.send(
f"<@{interaction.user.id}>",
view=SubmissionDropdownView(
self._bot, interaction, await self._category_select_options(boss_name)
),
embed=embed,
)
def wait_check(check_message: nextcord.Message): def get_submission_embed(userid: int) -> nextcord.Embed:
return ( """Creates an embed based off of the submission state and fields filled out."""
interaction.user.id == check_message.author.id usersubmission: dict = submission_objects[userid]
and interaction.channel == check_message.channel state: SubmissionState = usersubmission["submission_state"]
)
proof_message = await self._bot.wait_for("message", check=wait_check) if state is SubmissionState.CATEGORY:
embed = submission_message.embeds[0] description = "Step 1: Choose your category!"
embed.add_field(name="Score", value=proof_message.content, inline=False) elif state is SubmissionState.SCORE:
await submission_message.edit( description = "Step 2: Input your score!"
files=[await attch.to_file() for attch in proof_message.attachments], embed=embed elif state is SubmissionState.PROOF:
) description = "Step 3: Submit your proof!"
await proof_message.delete() 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."
async def _category_select_options(self, boss_name: str) -> "list[nextcord.SelectOption]": embed = Embed(title="Highscores Submission", description=description)
options: list[nextcord.SelectOption] = [] # pylint: disable=unsubscriptable-object embed.add_field(name="User Name", value=usersubmission["username"], inline=False)
boss_dict = highscores_data[boss_name] embed.add_field(name="Boss", value=usersubmission["boss_name"], inline=False)
for category in boss_dict["categories"].items(): if usersubmission["category"]:
options.append(nextcord.SelectOption(label=category[0])) 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
return options
def category_select_options(boss_name: str) -> "list[nextcord.SelectOption]":
"""Returns list of categories for a given boss."""
options: "list[nextcord.SelectOption]" = []
category_list = highscores_config["tables"][boss_name]
for category in category_list:
options.append(nextcord.SelectOption(label=category))
return options
############ Items used in submission process ############
class SubmissionDropdown(nextcord.ui.Select): class SubmissionDropdown(nextcord.ui.Select):
@@ -73,58 +80,181 @@ 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):
embed = interaction.message.embeds[0] submission_objects[interaction.user.id]["category"] = self.values[0]
embed.add_field(name="Category", value=self.values[0], inline=False) embed = get_submission_embed(interaction.user.id)
await interaction.message.edit( await interaction.message.edit(
embed=embed, # TODO solve ordering issue. currently have to choose category after score submission. embed=embed,
) )
class SubmissionDropdownView(nextcord.ui.View): class BackButton(nextcord.ui.Button):
"""Defines submission dropdown process view""" """Defines view for back button."""
def __init__( def __init__(self, bot: commands.Bot):
self, super().__init__(label="Back", style=nextcord.ButtonStyle.primary, custom_id="back")
bot: commands.Bot, self._bot = bot
prev_interaction: nextcord.Interaction,
options: "list[nextcord.SelectOption]",
*,
timeout: Optional[float] = 180,
):
super().__init__(timeout=timeout)
self.add_item(SubmissionDropdown(bot, options))
self._prev_interaction = prev_interaction
self.bot = bot
async def interaction_check(self, interaction: nextcord.Interaction) -> bool: async def callback(self, interaction: nextcord.Interaction):
return self._prev_interaction.user == interaction.user state: SubmissionState = submission_objects[interaction.user.id]["submission_state"]
state = SubmissionState(state.value - 1)
submission_objects[interaction.user.id]["submission_state"] = state
embed = get_submission_embed(interaction.user.id)
await interaction.edit(embed=embed, view=SubmissionView(self._bot, interaction.user.id))
@nextcord.ui.button(
label="Submit Score", style=nextcord.ButtonStyle.green, custom_id="submit-score" class CancelButton(nextcord.ui.Button):
) """Deletes message to cancel interaction.."""
async def submit_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction def __init__(self):
): # pylint: disable=unused-argument super().__init__(label="Cancel", style=nextcord.ButtonStyle.red, custom_id="cancel")
"""Defines button to submit a user's score."""
verification_channel_id = highscores_data["verification_channel_id"] async def callback(self, interaction: nextcord.Interaction):
channel = self.bot.get_channel(verification_channel_id) submission_objects.pop(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
state: SubmissionState = submission_objects[interaction.user.id]["submission_state"]
if (
state == SubmissionState.CATEGORY
and not submission_objects[interaction.user.id]["category"]
):
await interaction.send("Please choose a category.", ephemeral=True)
elif (
state == SubmissionState.SCORE and not submission_objects[interaction.user.id]["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_objects[interaction.user.id]["submission_state"] = state
embed = get_submission_embed(interaction.user.id)
await interaction.edit(embed=embed, 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):
verification_channel_id = highscores_data[str(interaction.guild.id)][
"verification_channel_id"
]
channel = interaction.guild.get_channel(verification_channel_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
embed = get_submission_embed(interaction.user.id)
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],
view=VerificationView(), view=VerificationView(),
embed=interaction.message.embeds[0], embed=embed,
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 Views ############
class SubmissionCreateButton(nextcord.ui.View):
"""Defines view for score submission button."""
def __init__(self, bot: commands.Bot):
super().__init__(timeout=None)
self._bot = bot
@nextcord.ui.button( @nextcord.ui.button(
label="Cancel", style=nextcord.ButtonStyle.red, custom_id="cancel-submission" label="Submit", style=nextcord.ButtonStyle.primary, custom_id="submitcreate"
) )
async def cancel_button( async def submitcreate_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
"""Deletes message to cancel interaction.""" """Button for score submission."""
await interaction.message.delete() guild_id_str = str(interaction.guild.id)
guild_data = highscores_data[guild_id_str]
boss_name = highscores_message_map[guild_id_str][interaction.message.id]
channel = interaction.guild.get_channel(guild_data["submission_channel_id"])
# a user should only have 1 submission active at once.
# submitting with dummy message_id until message created
# split username on pipe.
submission_objects[interaction.user.id] = {
"message_id": 0,
"submission_state": SubmissionState.CATEGORY,
"boss_name": boss_name,
"score": "",
"category": "",
"username": interaction.user.display_name.split("|")[0][
: guild_data["username_length"]
],
}
embed = get_submission_embed(interaction.user.id)
submission_message = await channel.send(
f"<@{interaction.user.id}>",
view=SubmissionView(
self._bot,
interaction.user.id,
),
embed=embed,
)
# update the message_id once its created
submission_objects[interaction.user.id]["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: Optional[float] = 180,
):
super().__init__(timeout=timeout)
self._submission_user = submission_user
self._bot = bot
self._state = submission_objects[self._submission_user]["submission_state"]
self._boss_name = submission_objects[self._submission_user]["boss_name"]
# add items depending on given state
if self._state is SubmissionState.CATEGORY:
self.add_item(SubmissionDropdown(bot, category_select_options(self._boss_name)))
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:
return self._submission_user == interaction.user.id
+14 -10
View File
@@ -10,7 +10,18 @@ class VerificationView(nextcord.ui.View):
def __init__(self): def __init__(self):
super().__init__(timeout=None) super().__init__(timeout=None)
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve") @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
"""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( async def approve_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
@@ -24,19 +35,12 @@ class VerificationView(nextcord.ui.View):
category = field["value"] category = field["value"]
elif field["name"] == "Score": elif field["name"] == "Score":
score = field["value"] score = field["value"]
elif field["name"] == "User": elif field["name"] == "User Name":
user = field["value"] user = field["value"]
try: try:
await submit_score(interaction, user, boss_name, category, score) await submit_score(interaction, user, boss_name, category, score)
await interaction.message.edit("Approved.", view=None) await interaction.message.edit(f"Approved by <@{interaction.user.id}>.", view=None)
except UnboundLocalError: except UnboundLocalError:
await interaction.send("Instruct user to submit with all fields.") await interaction.send("Instruct user to submit with all fields.")
await interaction.message.edit(view=None) await interaction.message.edit(view=None)
@nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red, custom_id="deny")
async def deny_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
"""Button for denying score submission."""
await interaction.message.delete()
+319 -190
View File
@@ -1,206 +1,335 @@
{ {
"bosses": [ "tables": {
{ "The Ambassador": [
"boss": "The Ambassador", "Kill Count",
"hardmode": true "Kill Time",
}, "Group Kill Count",
{ "Group Kill Time",
"boss": "Araxxi", "Spooned Log"
"hardmode": false ],
}, "Araxxi": [
{ "Kill Count",
"boss": "Arch-Glacor", "Kill Time",
"hardmode": true "Spooned Log"
}, ],
{ "Arch-Glacor": [
"boss": "Astellarn", "Kill Count",
"hardmode": true "Kill Time",
}, "Hardmode Kill Count",
{ "Hardmode Kill Time",
"boss": "The Barrows Brothers", "Enrage",
"hardmode": false "Spooned Log"
}, ],
{ "Astellarn": [
"boss": "The Barrows: Rise of the Six", "Kill Count",
"hardmode": false "Kill Time",
}, "Group Kill Count",
{ "Group Kill Time",
"boss": "Beastmaster Durzag", "Spooned Log"
"hardmode": false ],
}, "The Barrows Brothers": [
{ "Kill Count",
"boss": "Black Stone Dragon", "Spooned Log"
"hardmode": true ],
}, "The Barrows: Rise of the Six": [
{ "Kill Count",
"boss": "Chaos Elemental", "Kill Time",
"hardmode": false "Spooned Log"
}, ],
{ "Beastmaster Durzag": [
"boss": "Commander Zilyana", "Kill Count",
"hardmode": true "Kill Time",
}, "Spooned Log"
{ ],
"boss": "Corporeal Beast", "Black Stone Dragon": [
"hardmode": false "Kill Count",
}, "Kill Time",
{ "Group Kill Count",
"boss": "Crassian Leviathan", "Group Kill Time",
"hardmode": true "Spooned Log"
}, ],
{ "Chaos Elemental": [
"boss": "Croesus", "Kill Count",
"hardmode": false "Spooned Log"
}, ],
{ "Commander Zilyana": [
"boss": "Dagannoth Kings", "Kill Count",
"hardmode": false "Kill Time",
}, "Hardmode Kill Count",
{ "Hardmode Kill Time",
"boss": "General Graardor", "Spooned Log"
"hardmode": true ],
}, "Corporeal Beast": [
{ "Kill Count",
"boss": "Giant Mole", "Kill Time",
"hardmode": true "Spooned Log"
}, ],
{ "Crassian Leviathan": [
"boss": "Gregorovic", "Kill Count",
"hardmode": true "Kill Time",
}, "Group Kill Count",
{ "Group Kill Time",
"boss": "Har-Aken", "Spooned Log"
"hardmode": false ],
}, "Croesus": [
{ "Kill Count",
"boss": "Helwyr", "Kill Time",
"hardmode": true "Spooned Log"
}, ],
{ "Dagannoth Kings": [
"boss": "Kalphite King", "Kill Count",
"hardmode": false "Spooned Log"
}, ],
{ "General Graardor": [
"boss": "Kalphite Queen", "Kill Count",
"hardmode": false "Kill Time",
}, "Hardmode Kill Count",
{ "Hardmode Kill Time",
"boss": "Kerapac", "Spooned Log"
"hardmode": true ],
}, "Giant Mole": [
{ "Kill Count",
"boss": "King Black Dragon", "Kill Time",
"hardmode": false "Hardmode Kill Count",
}, "Hardmode Kill Time",
{ "Spooned Log"
"boss": "Kree'arra", ],
"hardmode": true "Gregorovic": [
}, "Kill Count",
{ "Kill Time",
"boss": "K'ril Tsutsaroth", "Hardmode Kill Count",
"hardmode": true "Hardmode Kill Time",
}, "Spooned Log"
{ ],
"boss": "Legiones", "Har-Aken": [
"hardmode": false "Kill Count",
}, "Kill Time",
{ "Spooned Log"
"boss": "The Magister", ],
"hardmode": false "Helwyr": [
}, "Kill Count",
{ "Kill Time",
"boss": "Masuta the Ascended", "Hardmode Kill Count",
"hardmode": true "Hardmode Kill Time",
}, "Spooned Log"
{ ],
"boss": "Nex", "Kalphite King": [
"hardmode": false "Kill Count",
}, "Kill Time",
{ "Spooned Log"
"boss": "Nex - Angel of Death", ],
"hardmode": false "Kalphite Queen": [
}, "Kill Count",
{ "Kill Time",
"boss": "Queen Black Dragon", "Spooned Log"
"hardmode": false ],
}, "Kerapac": [
{ "Kill Count",
"boss": "Raksha", "Kill Time",
"hardmode": true "Hardmode Kill Count",
}, "Hardmode Kill Time",
{ "Spooned Log"
"boss": "Rex Matriarchs", ],
"hardmode": false "King Black Dragon": [
}, "Kill Count",
{ "Kill Time",
"boss": "The Sanctum Guardian", "Spooned Log"
"hardmode": true ],
}, "Kree'arra": [
{ "Kill Count",
"boss": "Seiryu the Azure Serpent", "Kill Time",
"hardmode": true "Hardmode Kill Count",
}, "Hardmode Kill Time",
{ "Spooned Log"
"boss": "Solak", ],
"hardmode": false "K'ril Tsutsaroth": [
}, "Kill Count",
{ "Kill Time",
"boss": "Taraket the Necromancer", "Hardmode Kill Count",
"hardmode": true "Hardmode Kill Time",
}, "Spooned Log"
{ ],
"boss": "Telos", "Legiones": [
"hardmode": false "Kill Count",
}, "Kill Time",
{ "Spooned Log"
"boss": "The Twin Furies", ],
"hardmode": true "The Magister": [
}, "Kill Count",
{ "Kill Time",
"boss": "TzKal-Zuk", "Spooned Log"
"hardmode": true ],
}, "Masuta the Ascended": [
{ "Kill Count",
"boss": "TzTok-Jad", "Kill Time",
"hardmode": false "Group Kill Count",
}, "Group Kill Time",
{ "Spooned Log"
"boss": "Verak Lith", ],
"hardmode": true "Nex": [
}, "Kill Count",
{ "Kill Time",
"boss": "Vindicta & Gorvek", "Spooned Log"
"hardmode": true ],
}, "Nex - Angel of Death": [
{ "Kill Count",
"boss": "Vorago", "Kill Time",
"hardmode": true "Spooned Log"
}, ],
{ "Queen Black Dragon": [
"boss": "Yakamaru", "Kill Count",
"hardmode": false "Kill Time",
}, "Spooned Log"
{ ],
"boss": "Zamorak, Lord of Chaos", "Raksha": [
"hardmode": true "Solo Kill Count",
} "Solo Kill Time",
], "Duo Kill Count",
"Duo Kill Time",
"Spooned Log"
],
"Rex Matriarchs": [
"Kill Count",
"Spooned Log"
],
"The Sanctum Guardian": [
"Kill Count",
"Kill Time",
"Group Kill Count",
"Group Kill Time",
"Spooned Log"
],
"Seiryu the Azure Serpent": [
"Kill Count",
"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": { "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": { "Kill Count": {
"hardmode": true,
"ascending": false, "ascending": false,
"is_time_record": false "is_time_record": false
}, },
"Kill Time": { "Kill Time": {
"hardmode": true, "ascending": true,
"is_time_record": true
},
"Solo Kill Count": {
"ascending": false,
"is_time_record": false
},
"Solo Kill Time": {
"ascending": true, "ascending": true,
"is_time_record": true "is_time_record": true
}, },
"Spooned Log": { "Spooned Log": {
"hardmode": false,
"ascending": true, "ascending": true,
"is_time_record": false "is_time_record": false
} }
}, },
"highscore_size": 3 "highscore_size": 3
} }
+4 -2
View File
@@ -11,7 +11,7 @@ from nextcord.ext import commands
import config import config
from utilities.data_storage import open_highscores_config from utilities.data_storage import open_highscores_config
from utilities.data_storage import open_highscores_data from utilities.data_storage import open_highscores_data
from utilities.data_storage import open_message_map from utilities.data_storage import create_message_map
logger = logging.getLogger("nextcord") logger = logging.getLogger("nextcord")
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@@ -22,7 +22,9 @@ logger.addHandler(handler)
highscores_config = open_highscores_config() highscores_config = open_highscores_config()
highscores_data = open_highscores_data() highscores_data = open_highscores_data()
highscores_message_map = open_message_map() highscores_message_map = create_message_map(highscores_data)
submission_objects: dict = {}
def main(): def main():
+43 -39
View File
@@ -5,7 +5,46 @@ import json
HIGHSCORES_CONFIG_FILE = "highscores_config.json" HIGHSCORES_CONFIG_FILE = "highscores_config.json"
HIGHSCORES_DATA_FILE = "highscores_data.json" HIGHSCORES_DATA_FILE = "highscores_data.json"
HIGHSCORES_MESSAGE_MAP_FILE = "highscores_message_map.json"
def create_highscore_skeleton() -> dict:
"""
Creates a skeleton entry for new highscores data
@return: dict skeleton entry
"""
# create the dict
skeleton = {"highscore_channel_id": -1, "verification_channel_id": -1, "username_length": 12}
config: dict = open_highscores_config()
skeleton["tables"] = {}
for boss, categories in config["tables"].items():
skeleton["tables"][boss] = {"message_id": 0}
boss_categories = {}
for category in categories:
rank_list = []
boss_categories[category] = rank_list
skeleton["tables"][boss]["categories"] = boss_categories
return skeleton
def create_message_map(data: dict):
"""
Creates a message map from input highscores data.
@param data: highscores_data dict to create map from.
@return: dict message_map of message to boss.
"""
message_map = {}
# guild id will be a string. Json object keys are strings.
for guild, guild_scores in data.items():
message_map[guild] = {}
for boss, info in guild_scores["tables"].items():
message_map[guild][info["message_id"]] = boss
return message_map
def open_highscores_config(): def open_highscores_config():
@@ -20,56 +59,21 @@ def open_highscores_config():
def open_highscores_data(): def open_highscores_data():
""" """
Opens a highscores_data.json file if one exists, otherwise creates a skeleton of the data. Opens a highscores_data.json file if one exists, otherwise returns empty dict.
@return: dict of highscores data. @return: dict of highscores data.
""" """
if os.path.exists(HIGHSCORES_DATA_FILE): if os.path.exists(HIGHSCORES_DATA_FILE):
with open(HIGHSCORES_DATA_FILE, encoding="utf-8", mode="r") as file: with open(HIGHSCORES_DATA_FILE, encoding="utf-8", mode="r") as file:
data = json.load(file) data = json.load(file)
else: else:
# create the json object data = {}
data = {"highscore_channel_id": -1, "verification_channel_id": -1}
config = open_highscores_config()
for boss in config["bosses"]:
data[boss["boss"]] = {"message_id": 0}
boss_categories = {}
for key, value in config["categories"].items():
rank_list = []
boss_categories[key] = rank_list
if boss["hardmode"] and value["hardmode"]:
rank_list = []
boss_categories["Hardmode " + key] = rank_list
data[boss["boss"]]["categories"] = boss_categories
return data return data
def open_message_map(): def save_highscores_data(data: dict):
"""
Opens a highscore_message_map.json file if one exists. Otherwise returns empty dict.
@return: dict message_map of message to boss.
"""
if os.path.exists(HIGHSCORES_MESSAGE_MAP_FILE):
with open(HIGHSCORES_MESSAGE_MAP_FILE, encoding="utf-8", mode="r") as file:
message_map = json.load(file)
else:
# create the json object
message_map = {}
return message_map
def save_highscores_data(data):
""" """
Saves the highscores data dict into a json file. Saves the highscores data dict into a json file.
@param data: dict of highscores data. @param data: dict of highscores data.
""" """
with open(HIGHSCORES_DATA_FILE, encoding="utf-8", mode="w") as file: with open(HIGHSCORES_DATA_FILE, encoding="utf-8", mode="w") as file:
file.write(json.dumps(data, indent=4)) file.write(json.dumps(data, indent=4))
def save_message_map(message_map):
"""
Saves the highscores message mao data dict into a json file.
@param map: dict map of message to boss.
"""
with open(HIGHSCORES_MESSAGE_MAP_FILE, encoding="utf-8", mode="w") as file:
file.write(json.dumps(message_map, indent=4))
+72 -28
View File
@@ -7,21 +7,21 @@ from highscores import highscores_config # pylint: disable=import-error
from highscores import highscores_data # pylint: disable=import-error from highscores import highscores_data # pylint: disable=import-error
from highscores import highscores_message_map # pylint: disable=import-error from highscores import highscores_message_map # pylint: disable=import-error
from utilities.data_storage import save_highscores_data # pylint: disable=import-error from utilities.data_storage import save_highscores_data # pylint: disable=import-error
from utilities.data_storage import save_message_map # pylint: disable=import-error
def format_highscore_message(boss_name: str): def format_highscore_message(boss_name: str, guild_data: dict):
""" """
returns a formatted string for a boss's highscore returns a formatted string for a boss's highscore
@param boss: the dictionary object for the boss @param boss: the dictionary object for the boss
@param guild_data: the guild's highscore data
@return the formatted string @return the formatted string
""" """
ret_string = f"**{boss_name}**\n```" ret_string = f"**{boss_name}**\n```"
boss_data = highscores_data[boss_name] boss_data = guild_data["tables"][boss_name]
# Gather all categories and add to string # Gather all categories and add to string
for key, value in boss_data["categories"].items(): for key, value in boss_data["categories"].items():
ret_string += f"{key}".ljust(30, " ") ret_string += f"{key}".ljust(26, " ")
ret_string += "\n" ret_string += "\n"
highscore_size = highscores_config["highscore_size"] highscore_size = highscores_config["highscore_size"]
@@ -30,25 +30,40 @@ def format_highscore_message(boss_name: str):
for i in range(highscore_size): for i in range(highscore_size):
for key, value in boss_data["categories"].items(): for key, value in boss_data["categories"].items():
if len(value) > i: if len(value) > i:
score = value[i] score_pair = value[i]
ret_string += f"{i+1}: {score[0]} - {score[1]}".ljust(30, " ")
# pad username to max length.
username: str = score_pair[0].ljust(guild_data["username_length"], " ")
score: str = str(score_pair[1])
if highscores_config["categories"][key]["is_time_record"]:
# pad score to correct length
# MM:SS.ms is 7 characters long
score = score.ljust(7, " ")
line_str = f"{i+1}: {username} - "
ret_string += line_str + score.rjust(26 - len(line_str) - 1) + " "
else: else:
ret_string += f"{i+1}: submit your score".ljust(30, " ") ret_string += f"{i+1}: submit your score".ljust(26, " ")
ret_string += "\n" ret_string += "\n"
return ret_string + "```" return ret_string + "```"
async def send_highscore_message(channel, boss_name: str) -> nextcord.Message: async def send_highscore_message(
channel: nextcord.TextChannel, boss_name: str, guild_data: dict
) -> nextcord.Message:
""" """
Function takes care of checking if a message exists for a boss, then Function takes care of checking if a message exists for a boss, then
creating and sending/editing the message creating and sending/editing the message
@param channel: the channel to send the message in/search for message id @param channel: the channel to send the message in/search for message id
@param boss: the boss to send the message about @param boss: the boss to send the message about
@param guild_data: the guild's highscore data
@return: The nextcord message
""" """
highscore_string = format_highscore_message(boss_name) highscore_string = format_highscore_message(boss_name, guild_data)
message_id = highscores_data[boss_name]["message_id"] message_id = guild_data["tables"][boss_name]["message_id"]
try: try:
message: nextcord.Message = await channel.fetch_message(message_id) message: nextcord.Message = await channel.fetch_message(message_id)
await message.edit(content=highscore_string) await message.edit(content=highscore_string)
@@ -56,10 +71,11 @@ async def send_highscore_message(channel, boss_name: str) -> nextcord.Message:
# message doesn't exist. # message doesn't exist.
message: nextcord.Message = await channel.send(highscore_string) message: nextcord.Message = await channel.send(highscore_string)
message_id = message.id message_id = message.id
highscores_data[boss_name]["message_id"] = message_id guild_data["tables"][boss_name]["message_id"] = message_id
# save message -> boss_name in message_map # save message -> boss_name in message_map
highscores_message_map[str(message_id)] = boss_name # get guild id from channel
save_message_map(highscores_message_map) # channel should come from the guild that is requesting this highscores message anyways.
highscores_message_map[str(channel.guild.id)][message_id] = boss_name
return message return message
@@ -72,16 +88,21 @@ async def submit_score(
@param boss: the boss to add score to @param boss: the boss to add score to
@param category: the category to add score to @param category: the category to add score to
@param user: user who submitted score @param user: user who submitted score
@param score: score (int) to add @param score: score to add
""" """
# TODO update user submission to not have dupe users
boss_category_config = highscores_config["categories"][category]
guild_data = highscores_data[str(interaction.guild.id)]
# create score tuple for either time or int # create score tuple for either time or int
# define sort index # define sort index
if highscores_config["categories"][category]["is_time_record"]: if boss_category_config["is_time_record"]:
minutes = score.split(":")[0] if score.find(":") == -1:
seconds = score.split(":")[1] score_seconds = float(score)
score_seconds = int(minutes) * 60 + int(seconds) else:
minutes = score.split(":")[0]
seconds = score.split(":")[1]
score_seconds = int(minutes) * 60 + float(seconds)
score_tuple = (user, score, score_seconds) score_tuple = (user, score, score_seconds)
sort_index = 2 sort_index = 2
@@ -89,30 +110,53 @@ async def submit_score(
score_tuple = (user, int(score)) score_tuple = (user, int(score))
sort_index = 1 sort_index = 1
scores = highscores_data[boss_name]["categories"][category] scores: "list[tuple]" = guild_data["tables"][boss_name]["categories"][category]
scores.append(score_tuple)
# if user is in an existing tuple, delete it.
users_scores: "list[tuple]" = [score_tuple]
for tup in scores:
if user == tup[0]:
# Add tup to users_scores, remove from scores
users_scores.append(tup)
# sort users_scores
users_scores.sort(
key=lambda x: x[sort_index],
reverse=not boss_category_config["ascending"],
)
for index, tup in enumerate(users_scores):
if index == 0:
# first score is the best score.
if tup not in scores:
scores.append(tup)
else:
# discard all other scores from scores list.
if tup in scores:
scores.remove(tup)
# sort scores. # sort scores.
scores.sort( scores.sort(
key=lambda x: x[sort_index], key=lambda x: x[sort_index],
reverse=not highscores_config["categories"][category]["ascending"], reverse=not boss_category_config["ascending"],
) )
# enforce highscore size # enforce highscore size
while len(scores) > highscores_config["highscore_size"]: while len(scores) > highscores_config["highscore_size"]:
scores.pop() scores.pop()
highscores_data[boss_name]["categories"][category] = scores guild_data["tables"][boss_name]["categories"][category] = scores
# edit message # edit message
highscore_channel_id = highscores_data["highscore_channel_id"] highscore_channel_id = guild_data["highscore_channel_id"]
print(f"Editting message {highscore_channel_id}") print(f"Editting message {highscore_channel_id}")
channel = interaction.guild.get_channel(highscore_channel_id) channel = interaction.guild.get_channel(highscore_channel_id)
error_response = 'Registered Highscores channel does not exist or was never registered. \ error_response = "Registered Highscores channel does not exist or was never registered."
Register with "?register" command.' error_response += 'Register with "?register" command.'
assert channel is not None, await interaction.send(error_response) assert channel is not None, await interaction.send(error_response, ephemeral=True)
await send_highscore_message(channel, boss_name) await send_highscore_message(channel, boss_name, guild_data)
# save data # save data
highscores_data[str(interaction.guild.id)] = guild_data
save_highscores_data(highscores_data) save_highscores_data(highscores_data)