Compare commits

...
Author SHA1 Message Date
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 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
10 changed files with 644 additions and 344 deletions
+61 -12
View File
@@ -2,11 +2,16 @@
Defines cog for on message listener. Defines cog for on message listener.
""" """
import re
from nextcord.ext import commands from nextcord.ext import commands
import nextcord import nextcord
from highscores import submission_messages # pylint: disable=import-error 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 highscores_data # pylint: disable=import-error
from highscores import submission_objects # pylint: disable=import-error
class SubmissionListener(commands.Cog, name="Message Listener"): class SubmissionListener(commands.Cog, name="Message Listener"):
@@ -16,24 +21,68 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
self.bot = bot self.bot = bot
@commands.Cog.listener() @commands.Cog.listener()
async def on_message(self, proof_message: nextcord.Message): async def on_message(self, message: nextcord.Message):
"""Listener for message creation. Adds score and attachments to submission.""" """Listener for message creation. Adds score and attachments to submission."""
if proof_message.author.id in submission_messages: if message.author.id in submission_objects:
message_id = submission_messages[proof_message.author.id] message_id = submission_objects[message.author.id]["message_id"]
submission_message: nextcord.Message = await self.bot.get_channel( submission_message: nextcord.Message = await self.bot.get_channel(
highscores_data["submission_channel_id"] highscores_data["submission_channel_id"]
).fetch_message(message_id) ).fetch_message(message_id)
# need check if correct user and correct channel # need check if correct user and correct channel
if proof_message.channel.id == highscores_data["submission_channel_id"]: if message.channel.id == highscores_data["submission_channel_id"]:
state = submission_objects[message.author.id]["submission_state"]
if state == SubmissionState.SCORE and message.content:
embed = submission_message.embeds[0] # check with regex if score matches correctly
embed.add_field(name="Score", value=proof_message.content, inline=False) is_time_record = highscores_config["categories"][
await submission_message.edit( submission_objects[message.author.id]["category"]
files=[await attch.to_file() for attch in proof_message.attachments], ]["is_time_record"]
embed=embed,
) time_regex = re.search(
await proof_message.delete() "([0-9]{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[
: highscores_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. # This function will be called when this extension is loaded.
+7 -4
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
@@ -37,10 +37,13 @@ class PostHighscores(commands.Cog, name="Post Highscores"):
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"]) "highscore_table"
await message.edit(view=SubmissionButton(self.bot)) ].items():
message: nextcord.Message = await send_highscore_message(channel, boss)
await message.edit(view=SubmissionCreateButton(self.bot))
# Saving in case new message was sent.
save_highscores_data(highscores_data) save_highscores_data(highscores_data)
-1
View File
@@ -5,7 +5,6 @@ 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
+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.
+198 -66
View File
@@ -1,52 +1,68 @@
"""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_data # pylint: disable=import-error from highscores import highscores_data # pylint: disable=import-error
from highscores import submission_messages # pylint: disable=import-error from highscores import submission_objects # pylint: disable=import-error
from highscores import highscores_message_map # 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,
)
# a user should only have 1 submission active at once. def get_submission_embed(userid: int) -> nextcord.Embed:
submission_messages[interaction.user.id] = submission_message.id """Creates an embed based off of the submission state and fields filled out."""
usersubmission: dict = submission_objects[userid]
state: SubmissionState = usersubmission["submission_state"]
async def _category_select_options(self, boss_name: str) -> "list[nextcord.SelectOption]": if state is SubmissionState.CATEGORY:
options: list[nextcord.SelectOption] = [] # pylint: disable=unsubscriptable-object description = "Step 1: Choose your category!"
boss_dict = highscores_data[boss_name] elif state is SubmissionState.SCORE:
for category in boss_dict["categories"].items(): description = "Step 2: Input your score!"
options.append(nextcord.SelectOption(label=category[0])) 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."
return options 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(boss_name: str) -> "list[nextcord.SelectOption]":
"""Returns list of categories for a given boss."""
options: "list[nextcord.SelectOption]" = []
boss_dict = highscores_data["tables"][boss_name]
for category in boss_dict["categories"].items():
options.append(nextcord.SelectOption(label=category[0]))
return options
############ Items used in submission process ############
class SubmissionDropdown(nextcord.ui.Select): class SubmissionDropdown(nextcord.ui.Select):
@@ -63,60 +79,176 @@ 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, 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."""
async def callback(self, interaction: nextcord.Interaction):
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["verification_channel_id"] verification_channel_id = highscores_data["verification_channel_id"]
channel = self.bot.get_channel(verification_channel_id) channel = self._bot.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_messages.pop(interaction.user.id) 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."""
submission_messages.pop(interaction.user.id) boss_name = highscores_message_map[interaction.message.id]
await interaction.message.delete() channel = self._bot.get_channel(highscores_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][
: highscores_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
+11 -11
View File
@@ -10,6 +10,15 @@ class VerificationView(nextcord.ui.View):
def __init__(self): def __init__(self):
super().__init__(timeout=None) super().__init__(timeout=None)
@nextcord.ui.button(
label="Deny", style=nextcord.ButtonStyle.red, custom_id="deny-button-submission"
)
async def deny_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
"""Button for denying score submission."""
await interaction.message.delete()
@nextcord.ui.button( @nextcord.ui.button(
label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve-button-submission" label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve-button-submission"
) )
@@ -26,21 +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-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()
+319 -190
View File
@@ -1,206 +1,335 @@
{ {
"bosses": [ "highscore_table": {
{ "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
} }
+3 -3
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,9 +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_messages: dict = {} submission_objects: dict = {}
def main(): def main():
+17 -28
View File
@@ -5,7 +5,6 @@ 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 open_highscores_config(): def open_highscores_config():
@@ -28,32 +27,31 @@ def open_highscores_data():
data = json.load(file) data = json.load(file)
else: else:
# create the json object # create the json object
data = {"highscore_channel_id": -1, "verification_channel_id": -1} data = {"highscore_channel_id": -1, "verification_channel_id": -1, "username_length": 12}
config = open_highscores_config() config: dict = open_highscores_config()
for boss in config["bosses"]: for boss, categories in config["highscore_table"].items():
data[boss["boss"]] = {"message_id": 0} data["tables"][boss] = {"message_id": 0}
boss_categories = {} boss_categories = {}
for key, value in config["categories"].items(): for category in categories: # pylint: disable=unused-variable
rank_list = [] rank_list = []
boss_categories[key] = rank_list boss_categories[category] = rank_list
if boss["hardmode"] and value["hardmode"]: data["tables"][boss]["categories"] = boss_categories
rank_list = []
boss_categories["Hardmode " + key] = rank_list
data[boss["boss"]]["categories"] = boss_categories
return data return data
def open_message_map(): def create_message_map(data: dict):
""" """
Opens a highscore_message_map.json file if one exists. Otherwise returns empty 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. @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 = {}
message_map = json.load(file)
else: for boss, info in data["tables"].items():
# create the json object message_map[info["message_id"]] = boss
message_map = {}
return message_map return message_map
@@ -64,12 +62,3 @@ def save_highscores_data(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))
+24 -27
View File
@@ -7,7 +7,6 @@ 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):
@@ -17,11 +16,11 @@ def format_highscore_message(boss_name: str):
@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 = highscores_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(25, " ") 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,10 +29,21 @@ 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(25, " ")
# pad username to max length.
username: str = score_pair[0].ljust(highscores_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(25, " ") ret_string += f"{i+1}: submit your score".ljust(26, " ")
ret_string += "\n" ret_string += "\n"
return ret_string + "```" return ret_string + "```"
@@ -48,7 +58,7 @@ async def send_highscore_message(channel, boss_name: str) -> nextcord.Message:
""" """
highscore_string = format_highscore_message(boss_name) highscore_string = format_highscore_message(boss_name)
message_id = highscores_data[boss_name]["message_id"] message_id = highscores_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 +66,9 @@ 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 highscores_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 highscores_message_map[str(message_id)] = boss_name
save_message_map(highscores_message_map)
return message return message
@@ -74,24 +83,12 @@ async def submit_score(
@param user: user who submitted score @param user: user who submitted score
@param score: score to add @param score: score to add
""" """
# TODO update user submission to not have dupe users
# hardmode category, must have different category variable for config access. boss_category_config = highscores_config["categories"][category]
if category.find("Hardmode ") != -1:
config_category = category.replace("Hardmode ", "")
else:
config_category = category
# capping to 12 characters because runescape names are 12 characters long.
# Splitting on pipe. My discord has multiple names on account split by pipe.
user = user.split("|")[0][:12]
# strip leading zeros and colon from score
score = score.lstrip("0:")
# 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"][config_category]["is_time_record"]: if boss_category_config["is_time_record"]:
if score.find(":") == -1: if score.find(":") == -1:
score_seconds = float(score) score_seconds = float(score)
else: else:
@@ -105,7 +102,7 @@ async def submit_score(
score_tuple = (user, int(score)) score_tuple = (user, int(score))
sort_index = 1 sort_index = 1
scores: "list[tuple]" = highscores_data[boss_name]["categories"][category] scores: "list[tuple]" = highscores_data["tables"][boss_name]["categories"][category]
# if user is in an existing tuple, delete it. # if user is in an existing tuple, delete it.
users_scores: "list[tuple]" = [score_tuple] users_scores: "list[tuple]" = [score_tuple]
@@ -117,7 +114,7 @@ async def submit_score(
# sort users_scores # sort users_scores
users_scores.sort( users_scores.sort(
key=lambda x: x[sort_index], key=lambda x: x[sort_index],
reverse=not highscores_config["categories"][config_category]["ascending"], reverse=not boss_category_config["ascending"],
) )
for index, tup in enumerate(users_scores): for index, tup in enumerate(users_scores):
@@ -133,13 +130,13 @@ async def submit_score(
# 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"][config_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 highscores_data["tables"][boss_name]["categories"][category] = scores
# edit message # edit message
highscore_channel_id = highscores_data["highscore_channel_id"] highscore_channel_id = highscores_data["highscore_channel_id"]