Compare commits

...
Author SHA1 Message Date
alwayscompiling 1ae6a4a223 Ensure formatting lines up times and right justifies int scores. 2022-07-26 22:09:35 -04:00
alwayscompiling e1a429c42a Trim leading 0s from time scores. 2022-07-26 21:48:31 -04:00
alwayscompiling 790e8ca50f Create delete-score command. 2022-07-26 21:35:56 -04:00
alwayscompiling 7ba43ea4f4 Creating message map entry if guild id isn't there already. 2022-07-26 21:22:12 -04:00
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
14 changed files with 503 additions and 180 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/
+49
View File
@@ -0,0 +1,49 @@
"""
Bot command for deleting the guild's scores.
"""
from nextcord import Embed
from nextcord.ext import commands
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 create_highscore_skeleton # pylint: disable=import-error
class DeleteScores(commands.Cog, name="Delete Scores"):
"""
Warning: This command only works for developers.
Deletes this guild's scores.
"""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="delete-scores")
@commands.has_permissions(administrator=True)
async def delete_scores(self, ctx: commands.Context):
"""A command which registers current channel as for various uses.
Usage:
```
?delete_scores
```
"""
# only allow AlwaysCompiling to use.
# If you are a developer and want to use command, can add name to list
if ctx.author.id == 206602713511886849:
guild_data: dict = highscores_data[str(ctx.guild.id)]
for boss, boss_data in guild_data["tables"].items():
for category, category_data in boss_data["categories"].items():
category_data = []
boss_data["categories"][category] = category_data
guild_data["tables"][boss] = boss_data
save_highscores_data(highscores_data)
# 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(DeleteScores(bot))
+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))
+61 -14
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,29 +21,71 @@ 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: guild_id = message.guild.id
message_id = submission_messages[proof_message.author.id] guild_data = highscores_data[str(guild_id)]
submission_message: nextcord.Message = await self.bot.get_channel( if message.author.id in submission_objects:
highscores_data["submission_channel_id"] message_id = submission_objects[message.author.id]["message_id"]
).fetch_message(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 # need check if correct user and correct channel
if proof_message.channel.id == highscores_data["submission_channel_id"]: 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.
# strip leading 0s and colon
# then add colon if its not there
score = score.lstrip("0:")
if score.find(":") == -1:
score = "0:" + score
submission_objects[message.author.id]["score"] = score
embed = get_submission_embed(message.author.id)
embed = submission_message.embeds[0]
if proof_message.content:
embed.add_field(name="Score", value=proof_message.content, inline=False)
await submission_message.edit( await submission_message.edit(
embed=embed, embed=embed,
) )
if len(proof_message.attachments) > 0: elif state == SubmissionState.PROOF and len(message.attachments) > 0:
await submission_message.edit( await submission_message.edit(
files=[await attch.to_file() for attch in proof_message.attachments], 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 proof_message.delete() await message.delete()
# This function will be called when this extension is loaded. # This function will be called when this extension is loaded.
+8 -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
@@ -29,20 +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, categories in highscores_config[ # pylint: disable=unused-variable for boss, categories in highscores_config[ # pylint: disable=unused-variable
"highscore_table" "tables"
].items(): ].items():
message: nextcord.Message = await send_highscore_message(channel, boss) message: nextcord.Message = await send_highscore_message(channel, boss, guild_data)
await message.edit(view=SubmissionButton(self.bot)) 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."
+5 -5
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
@@ -30,11 +29,12 @@ class Score(commands.Cog, name="Score"):
This command still works if you prefer to use it over the ui. 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}"
+2 -2
View File
@@ -4,7 +4,7 @@ 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 from .verification_view import VerificationView
@@ -17,7 +17,7 @@ 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()) self.bot.add_view(VerificationView())
+205 -67
View File
@@ -1,52 +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 submission_messages # 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,
)
# 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]" = []
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):
@@ -63,60 +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, 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_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) guild_id_str = str(interaction.guild.id)
await interaction.message.delete() 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
+10 -10
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,7 +35,7 @@ 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:
@@ -35,12 +44,3 @@ class VerificationView(nextcord.ui.View):
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()
+14 -6
View File
@@ -1,5 +1,5 @@
{ {
"highscore_table": { "tables": {
"The Ambassador": [ "The Ambassador": [
"Kill Count", "Kill Count",
"Kill Time", "Kill Time",
@@ -212,8 +212,8 @@
"Spooned Log" "Spooned Log"
], ],
"Taraket the Necromancer": [ "Taraket the Necromancer": [
"Kill Count", "Solo Kill Count",
"Kill Time", "Solo Kill Time",
"Group Kill Count", "Group Kill Count",
"Group Kill Time", "Group Kill Time",
"Spooned Log" "Spooned Log"
@@ -244,8 +244,8 @@
"Spooned Log" "Spooned Log"
], ],
"Verak Lith": [ "Verak Lith": [
"Kill Count", "Solo Kill Count",
"Kill Time", "Solo Kill Time",
"Group Kill Count", "Group Kill Count",
"Group Kill Time", "Group Kill Time",
"Spooned Log" "Spooned Log"
@@ -318,10 +318,18 @@
"ascending": true, "ascending": true,
"is_time_record": true "is_time_record": true
}, },
"Solo Kill Count": {
"ascending": false,
"is_time_record": false
},
"Solo Kill Time": {
"ascending": true,
"is_time_record": true
},
"Spooned Log": { "Spooned Log": {
"ascending": true, "ascending": true,
"is_time_record": false "is_time_record": false
} }
}, },
"highscore_size": 3 "highscore_size": 3
} }
+3 -4
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():
@@ -35,7 +35,6 @@ def main():
# Required in order to read messages (eg. prefix commands) # Required in order to read messages (eg. prefix commands)
intents.message_content = True # pylint: disable=assigning-non-slot intents.message_content = True # pylint: disable=assigning-non-slot
intents.messages = True # pylint: disable=assigning-non-slot
# To enable the guilds priveleged intent: # To enable the guilds priveleged intent:
# intents.guilds = True # intents.guilds = True
+43 -36
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,53 +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: dict = open_highscores_config()
for boss, categories in config["highscore_table"].items():
data[boss] = {"message_id": 0}
boss_categories = {}
for category in categories: # pylint: disable=unused-variable
rank_list = []
boss_categories[category] = rank_list
data[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))
+43 -26
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(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,25 +30,43 @@ 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(guild_data["username_length"], " ")
score: str = str(score_pair[1])
# pad score to correct length
# MM:SS.ms is 7 characters long
if highscores_config["categories"][key]["is_time_record"]:
# if there are single digit minutes, add a space for formatting.
if score.find(":") and len(score.split(":", maxsplit=1)[0]) < 2:
score = " " + score
else:
score = score.rjust(7, " ")
ret_string += f"{i+1}: {username} - {score}".ljust(26, " ")
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 + "```"
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 +74,14 @@ 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.
# check if guild is in message map already
if not str(channel.guild.id) in highscores_message_map:
highscores_message_map[str(channel.guild.id)] = {}
highscores_message_map[str(channel.guild.id)][message_id] = boss_name
return message return message
@@ -76,13 +98,7 @@ async def submit_score(
""" """
boss_category_config = highscores_config["categories"][category] boss_category_config = highscores_config["categories"][category]
guild_data = highscores_data[str(interaction.guild.id)]
# 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
@@ -100,7 +116,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]" = guild_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]
@@ -134,18 +150,19 @@ async def submit_score(
# 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, ephemeral=True) 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)