Compare commits

...
Author SHA1 Message Date
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 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
alwayscompiling ae1d820297 Refactored register command to be one command that takes in parameter. 2022-07-09 14:21:45 -04:00
alwayscompiling a602347d09 Removing TODOs. 2022-07-09 13:57:48 -04:00
alwayscompiling 03ed723f5c Add input verification in verification_view.py. 2022-07-09 13:53:23 -04:00
alwayscompiling 18db48a55e Sending to verification view. 2022-07-09 11:43:49 -04:00
alwayscompiling 7854be890f Entire submission process works somehow. 2022-07-09 11:24:29 -04:00
alwayscompiling 6a7207d710 Slight organization changes. Decided to use new message for score input. 2022-07-07 22:57:38 -04:00
alwayscompiling f9800677cc Got response of dropdown. 2022-07-06 22:16:22 -04:00
alwayscompiling 3d16fe81e5 First dropdown works. 2022-07-06 21:37:46 -04:00
alwayscompiling c4261fc7f4 Changed where submission views live. 2022-07-06 21:05:04 -04:00
alwayscompiling d0428afb3f Add example config, ignore working config. 2022-07-06 20:39:59 -04:00
alwayscompiling a92f790a5f Adding new boss. 2022-07-04 10:15:01 -04:00
alwayscompiling 22d368f0a8 Adding submission button for all highscore messages. 2022-07-04 10:13:16 -04:00
alwayscompiling 84cf3781ee Adding map to track message id to boss. 2022-07-03 23:01:24 -04:00
alwayscompiling 8a20c1f50a Solved message deletion. 2022-07-03 22:34:16 -04:00
alwayscompiling a8aad570d3 Add logging message for admins, trying to move message with buttons. 2022-07-03 17:29:39 -04:00
alwayscompiling 02ba90134e Removing some local variables. 2022-07-03 17:13:23 -04:00
alwayscompiling 0834634cde Verification button working. 2022-07-03 17:11:06 -04:00
alwayscompiling 445c77b012 Renamed utils folder because import shenanigans with module with same name. 2022-07-03 16:51:54 -04:00
alwayscompiling b3af09f1c6 Changed utils to data_storage where I didn't need to. 2022-07-03 16:39:43 -04:00
alwayscompiling dc0fd694d3 Fix circular dependencies after refractor. 2022-07-03 16:38:22 -04:00
alwayscompiling 6d41baef98 Adding missed utils refactor. 2022-07-03 16:32:51 -04:00
alwayscompiling f29e459b2e Refractor utils to one file. 2022-07-03 16:12:15 -04:00
alwayscompiling 35aa1f34f4 Merge branch 'score-verification' of github.com:alwayscompiling/pvm-highscores into score-verification 2022-07-03 16:09:26 -04:00
alwayscompiling c046fa2c45 Using asserts for error catching. 2022-07-03 13:33:13 -04:00
alwayscompiling ffeb8119f7 Verification message is sent. 2022-07-03 11:46:07 -04:00
alwayscompiling a94ccdc425 Adding verification channel register command. 2022-07-03 11:21:06 -04:00
alwayscompiling 04faff3bdb Adding verification channel register command. 2022-07-03 11:20:08 -04:00
alwayscompiling cfde28ad4b Removing errors in help_command.py. 2022-07-03 10:47:06 -04:00
alwayscompiling f23cd71647 Editing information that would be posted from help command. 2022-07-03 10:35:08 -04:00
alwayscompiling 75fdef6388 Adding premade help command. 2022-07-03 10:29:18 -04:00
alwayscompiling b1b22fb595 Changing message to be a code block for monospace. Easier formatting. 2022-07-03 10:13:58 -04:00
alwayscompiling 9e19e10b88 Fixed sorting on either int or time record. 2022-07-03 09:54:14 -04:00
alwayscompiling c45606a7a4 Add message edit to score command. 2022-06-29 22:53:32 -04:00
alwayscompiling 02e4d075dd Only sending boss name as a string, no dict objects. 2022-06-29 22:52:50 -04:00
alwayscompiling 061411f18d Converting other functions to use new data storage format. 2022-06-29 22:26:41 -04:00
alwayscompiling f204be8d52 Add score submission command. Add highscore management function. Change how config file and data file are defined. 2022-06-29 19:52:19 -04:00
alwayscompiling ed62836b7f cleaning up highscores imports a little. 2022-06-28 22:33:52 -04:00
alwayscompiling 2b5c49a896 Updated data structure to have categories be lists of key value pairs. This will make sorting easier. 2022-06-28 22:27:31 -04:00
alwayscompiling b134a8107d solved issue with editting message. 2022-06-28 21:05:41 -04:00
alwayscompiling 71611502cf Filling out skeleton dict for highscores_data rather than let it be empty and filled at data creation. 2022-06-28 08:14:36 -04:00
alwayscompiling d30382dd91 Create function to send message. Function handles editing message if exists. 2022-06-27 21:48:28 -04:00
alwayscompiling 68c15c48a5 Adding left justification for highscore message string. 2022-06-27 20:15:47 -04:00
alwayscompiling 46dcb81a9a Changing file name and function name for better scope management. 2022-06-27 17:48:50 -04:00
alwayscompiling 96c6bd7935 correcting type of hardmode 2022-06-26 18:05:52 -04:00
18 changed files with 803 additions and 216 deletions
+3 -1
View File
@@ -131,4 +131,6 @@ dmypy.json
nextcord.log
# data file for bot
highscores_data.json
highscores_data.json
highscores_message_map.json
highscores_config.json
+26
View File
@@ -0,0 +1,26 @@
"""
Help command
"""
from nextcord.ext import commands
from .help_command import NewHelpCommand
class HelpCog(commands.Cog):
"""Help Command"""
def __init__(self, bot: commands.Bot):
self.bot = bot
self._original_help_command = bot.help_command
bot.help_command = NewHelpCommand()
bot.help_command.cog = self
def cog_unload(self):
self.bot.help_command = self._original_help_command
# setup functions for bot
def setup(bot: commands.Bot):
"""Adds function to bot"""
bot.add_cog(HelpCog(bot))
+84
View File
@@ -0,0 +1,84 @@
"""
Help command. Copied from discord-bot-template.
"""
import nextcord
from nextcord.embeds import EmptyEmbed
from nextcord.ext import commands
import config # pylint: disable=import-error
class NewHelpCommand(commands.MinimalHelpCommand):
"""Custom help command override using embeds"""
COLOUR = nextcord.Colour.blurple()
def get_ending_note(self):
"""Returns note to display at the bottom"""
invoked_with = self.invoked_with
return f"Use {config.BOT_PREFIX}{invoked_with} [command] for more info on a command."
def get_command_signature(self, command: commands.core.Command):
"""Retrieves the signature portion of the help page."""
return f"{command.qualified_name} {command.signature}"
async def send_bot_help(self, mapping: dict):
"""implements bot command help page"""
embed = nextcord.Embed(title="Bot Commands", colour=self.COLOUR)
avatar = self.context.bot.user.avatar
avatar_url = avatar.url if avatar else EmptyEmbed
embed.set_author(name=self.context.bot.user.name, icon_url=avatar_url)
description = self.context.bot.description
if description:
embed.description = description
for cog, commands in mapping.items(): # pylint: disable=redefined-outer-name
name = "No Category" if cog is None else cog.qualified_name
filtered = await self.filter_commands(commands, sort=True)
if filtered:
# \u2002 = middle dot
value = "\u2002".join(f"{config.BOT_PREFIX}{c.name}" for c in filtered)
if cog and cog.description:
value = f"{cog.description}\n{value}"
embed.add_field(name=name, value=value)
embed.set_footer(text=self.get_ending_note())
await self.get_destination().send(embed=embed)
async def send_cog_help(self, cog: commands.Cog):
"""implements cog help page"""
embed = nextcord.Embed(title=f"{cog.qualified_name} Commands", colour=self.COLOUR)
if cog.description:
embed.description = cog.description
filtered = await self.filter_commands(cog.get_commands(), sort=True)
for command in filtered:
embed.add_field(
name=self.get_command_signature(command),
value=command.short_doc or "...",
inline=False,
)
embed.set_footer(text=self.get_ending_note())
await self.get_destination().send(embed=embed)
async def send_group_help(self, group: commands.Group):
"""implements group help page and command help page"""
embed = nextcord.Embed(title=group.qualified_name, colour=self.COLOUR)
if group.help:
embed.description = group.help
if isinstance(group, commands.Group):
filtered = await self.filter_commands(group.commands, sort=True)
for command in filtered:
embed.add_field(
name=self.get_command_signature(command),
value=command.short_doc or "...",
inline=False,
)
embed.set_footer(text=self.get_ending_note())
await self.get_destination().send(embed=embed)
# Use the same function as group help for command help
send_command_help = send_group_help # type: ignore
+43
View File
@@ -0,0 +1,43 @@
"""
Defines cog for on message listener.
"""
from nextcord.ext import commands
import nextcord
from highscores import submission_messages # pylint: disable=import-error
from highscores import highscores_data # 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, proof_message: nextcord.Message):
"""Listener for message creation. Adds score and attachments to submission."""
if proof_message.author.id in submission_messages:
message_id = submission_messages[proof_message.author.id]
submission_message: nextcord.Message = await self.bot.get_channel(
highscores_data["submission_channel_id"]
).fetch_message(message_id)
# need check if correct user and correct channel
if proof_message.channel.id == highscores_data["submission_channel_id"]:
embed = submission_message.embeds[0]
embed.add_field(name="Score", value=proof_message.content, inline=False)
await submission_message.edit(
files=[await attch.to_file() for attch in proof_message.attachments],
embed=embed,
)
await proof_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))
+23 -26
View File
@@ -3,52 +3,49 @@ Bot command to post the highscores
"""
from nextcord.ext import commands
import highscores # pylint: disable=import-error
from utils import data_storage # pylint: disable=import-error
from utils import message_formatter # pylint: disable=import-error
import nextcord
from cogs.submission.submission_views import SubmissionButton # pylint: disable=import-error
from highscores import highscores_config # 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.utils import send_highscore_message # pylint: disable=import-error
class PostHighscores(commands.Cog, name="Post Highscores"):
"""Actions of bot command"""
"""Posts Highscores."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="post-scores")
@commands.has_permissions(administrator=True)
async def post_highscores(self, ctx: commands.Context):
"""A command which posts the highscore in the designated channel. Sends warning if no channel has been registered.
"""
A command which posts the highscore in the designated channel.
Sends warning if no channel has been registered.
Usage:
```
?post-scores
```
"""
# temporarily sending config data...
channel_id = highscores.highscores_data["channel_id"]
if (channel_id != -1):
print(
f"Printing out highscores information in channel {channel_id}")
channel = self.bot.get_channel(channel_id)
for boss in highscores.highscores_config["bosses"]:
highscore_string = message_formatter.format_boss_highscore(
boss)
message = await channel.send(highscore_string)
message_id = message.id
highscore_channel_id = highscores_data["highscore_channel_id"]
print(f"Printing out highscores information in channel {highscore_channel_id}")
channel = self.bot.get_channel(highscore_channel_id)
# check if there is a dict for the boss in the highscores data
if not boss["boss"] in highscores.highscores_data:
highscores.highscores_data[boss["boss"]] = {}
error_response = 'Registered Highscores channel does not exist or was never registered. \n\
Register with "?register" command.'
assert channel is not None, await ctx.send(error_response)
highscores.highscores_data[boss["boss"]
]["message_id"] = message_id
else:
response = "Highscores channel is not registered. Register with \"?register\" command."
await ctx.send(response)
for boss in highscores_config["bosses"]:
message: nextcord.Message = await send_highscore_message(channel, boss["boss"])
await message.edit(view=SubmissionButton(self.bot))
data_storage.save_highscores_data(highscores.highscores_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 functon to bot"""
"""Adds function to bot"""
bot.add_cog(PostHighscores(bot))
+67
View File
@@ -0,0 +1,67 @@
"""
Bot command for registering various channels to the bot.
"""
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
class RegisterVerificationChannel(commands.Cog, name="Register"):
"""Bot command for registering various channels to the bot."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="register")
@commands.has_permissions(administrator=True)
async def register_highscores(self, ctx: commands.Context, registration_type: str):
"""A command which registers current channel as for various uses.
Usage:
```
?register verify (Use in channel you want to verify submissions in.)
?register highscore (Use in channel you want to show highscore messages in.)
?register submission (Use in channel you want submission messages to be send it.)
?register help (Shows help message.)
```
"""
# respond to the message
channel_id = ctx.channel.id
if registration_type == "verify":
await ctx.send(f"Registered <#{channel_id}> as channel to post verifications in.")
highscores_data["verification_channel_id"] = channel_id
elif registration_type == "highscore":
await ctx.send(f"Registered <#{channel_id}> as channel to post highscores in.")
highscores_data["highscore_channel_id"] = channel_id
elif registration_type == "submission":
await ctx.send(f"Registered <#{channel_id}> as channel to post submissions in.")
highscores_data["submission_channel_id"] = channel_id
else:
embed = Embed(
title="Register command help", description="How to use the register command."
)
embed.add_field(
name="?register verify", value="Use in channel you want to verify submissions in."
)
embed.add_field(
name="?register highscore",
value="Use in channel you want to show highscore messages in.",
)
embed.add_field(
name="?register submission",
value="Use in channel you want submission messages to be send it.",
)
embed.add_field(name="?register help", value="Use to show this message.")
await ctx.reply(embed=embed)
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(RegisterVerificationChannel(bot))
@@ -1,35 +0,0 @@
"""
Bot command to register the highscores channel
"""
from nextcord.ext import commands
import highscores # pylint: disable=import-error
from utils import data_storage # pylint: disable=import-error
class RegisterHighscoreChannel(commands.Cog, name="Register HighScore Channel"):
"""Actions of bot command"""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="register")
async def register_highscores(self, ctx: commands.Context):
"""A command which registers the channel command is sent in as channel to post highscores in
Usage:
```
?register (in channel to post highscores in)
```
"""
print(f"Registering {ctx.channel.id}.")
# respond to the message
await ctx.send(f"Registered <#{ctx.channel.id}> as channel to post highscores in.")
highscores.highscores_data["channel_id"] = ctx.channel.id
data_storage.save_highscores_data(highscores.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 functon to bot"""
bot.add_cog(RegisterHighscoreChannel(bot))
+53
View File
@@ -0,0 +1,53 @@
"""
Bot command to submit a score
"""
from nextcord.ext import commands
from highscores import highscores_data # pylint: disable=import-error
from utilities.utils import submit_score # pylint: disable=import-error
from .verification_view import VerificationView
class Score(commands.Cog, name="Score"):
"""Submits a score."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.Cog.listener()
async def on_ready(self):
"""Called when the cog is loaded"""
self.bot.add_view(VerificationView())
@commands.command(name="score")
async def score(self, ctx: commands.Context, boss_name: str, category: str, score: str):
"""A command which calls a function to submit the score to highscores.
Gathers required information from command and passes on.
Usage:
```
?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"]
channel = self.bot.get_channel(verification_channel_id)
error_response = "Registered Verification channel does not exist or was never registered. \
Contact server admins."
assert channel is not None, await ctx.send(error_response)
verification_string = f"{ctx.author.display_name}:{boss_name}:{category}:{score}"
await channel.send(
content=verification_string,
files=[await attch.to_file() for attch in ctx.message.attachments],
view=VerificationView(),
)
# 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(Score(bot))
+36
View File
@@ -0,0 +1,36 @@
"""View for verification buttons"""
import nextcord
from utilities.utils import submit_score # pylint: disable=import-error
class VerificationView(nextcord.ui.View):
"""Defines views for verifying score submissions"""
def __init__(self):
super().__init__(timeout=None)
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve")
async def approve_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
"""Button for approving score submission."""
content = interaction.message.content.split(":")
user = content[0]
boss_name = content[1]
category = content[2]
score = content[3]
await submit_score(interaction, user, boss_name, category, score)
await interaction.message.delete()
await interaction.channel.send(
f"Approved: {boss_name} {category} {user} {score}",
files=[await attch.to_file() for attch in interaction.message.attachments],
)
@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()
+26
View File
@@ -0,0 +1,26 @@
"""
Cog for creating submission process.
"""
from nextcord.ext import commands
from .submission_views import SubmissionButton
class SubmissionCog(commands.Cog, name="Submission Cog"):
"""Constructs submission method."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.Cog.listener()
async def on_ready(self):
"""Called when cog is loaded"""
self.bot.add_view(SubmissionButton(self.bot))
# 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(SubmissionCog(bot))
+122
View File
@@ -0,0 +1,122 @@
"""Views used for submission process."""
from typing import Optional
import nextcord
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 submission_messages # pylint: disable=import-error
from .verification_view import VerificationView
class SubmissionButton(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(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.
submission_messages[interaction.user.id] = submission_message.id
async def _category_select_options(self, boss_name: str) -> "list[nextcord.SelectOption]":
options: list[nextcord.SelectOption] = [] # pylint: disable=unsubscriptable-object
boss_dict = highscores_data[boss_name]
for category in boss_dict["categories"].items():
options.append(nextcord.SelectOption(label=category[0]))
return options
class SubmissionDropdown(nextcord.ui.Select):
"""Defines dropdown menu for submission"""
def __init__(
self,
bot: commands.Bot,
options: "list[nextcord.SelectOption]",
):
super().__init__(
placeholder="Choose submission category.", min_values=1, max_values=1, options=options
)
self._bot = bot
async def callback(self, interaction: nextcord.Interaction):
embed = interaction.message.embeds[0]
embed.add_field(name="Category", value=self.values[0], inline=False)
await interaction.message.edit(
embed=embed,
)
class SubmissionDropdownView(nextcord.ui.View):
"""Defines submission dropdown process view"""
def __init__(
self,
bot: commands.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:
return self._prev_interaction.user == interaction.user
@nextcord.ui.button(
label="Submit Score", style=nextcord.ButtonStyle.green, custom_id="submit-score"
)
async def submit_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
"""Defines button to submit a user's score."""
verification_channel_id = highscores_data["verification_channel_id"]
channel = self.bot.get_channel(verification_channel_id)
error_response = "Registered Verification channel does not exist or was never registered. \
Contact server admins."
assert channel is not None, await channel.send(error_response)
await channel.send(
files=[await attch.to_file() for attch in interaction.message.attachments],
view=VerificationView(),
embed=interaction.message.embeds[0],
)
await interaction.message.edit(content=f"<@{interaction.user.id}> Submitted!", view=None)
submission_messages.pop(interaction.user.id)
@nextcord.ui.button(
label="Cancel", style=nextcord.ButtonStyle.red, custom_id="cancel-submission"
)
async def cancel_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
"""Deletes message to cancel interaction."""
submission_messages.pop(interaction.user.id)
await interaction.message.delete()
+42
View File
@@ -0,0 +1,42 @@
"""View for verification buttons"""
import nextcord
from utilities.utils import submit_score # pylint: disable=import-error
class VerificationView(nextcord.ui.View):
"""Defines views for verifying score submissions"""
def __init__(self):
super().__init__(timeout=None)
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve")
async def approve_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
"""Button for approving score submission."""
content = interaction.message.embeds[0].to_dict()
for field in content["fields"]:
if field["name"] == "Boss":
boss_name = field["value"]
elif field["name"] == "Category":
category = field["value"]
elif field["name"] == "Score":
score = field["value"]
elif field["name"] == "User":
user = field["value"]
try:
await submit_score(interaction, user, boss_name, category, score)
await interaction.message.edit("Approved.", view=None)
except UnboundLocalError:
await interaction.send("Instruct user to submit with all fields.")
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()
@@ -2,201 +2,205 @@
"bosses": [
{
"boss": "The Ambassador",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Araxxi",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Arch-Glacor",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Astellarn",
"hardmode": "true"
"hardmode": true
},
{
"boss": "The Barrows Brothers",
"hardmode": "false"
"hardmode": false
},
{
"boss": "The Barrows: Rise of the Six",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Beastmaster Durzag",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Black Stone Dragon",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Chaos Elemental",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Commander Zilyana",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Corporeal Beast",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Crassian Leviathan",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Croesus",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Dagannoth Kings",
"hardmode": "false"
"hardmode": false
},
{
"boss": "General Graardor",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Giant Mole",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Gregorovic",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Har-Aken",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Helwyr",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Kalphite King",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Kalphite Queen",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Kerapac",
"hardmode": "true"
"hardmode": true
},
{
"boss": "King Black Dragon",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Kree'arra",
"hardmode": "true"
"hardmode": true
},
{
"boss": "K'ril Tsutsaroth",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Legiones",
"hardmode": "false"
"hardmode": false
},
{
"boss": "The Magister",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Masuta the Ascended",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Nex",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Nex - Angel of Death",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Queen Black Dragon",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Raksha",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Rex Matriarchs",
"hardmode": "false"
"hardmode": false
},
{
"boss": "The Sanctum Guardian",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Seiryu the Azure Serpent",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Solak",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Taraket the Necromancer",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Telos",
"hardmode": "false"
"hardmode": false
},
{
"boss": "The Twin Furies",
"hardmode": "true"
"hardmode": true
},
{
"boss": "TzKal-Zuk",
"hardmode": "true"
"hardmode": true
},
{
"boss": "TzTok-Jad",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Verak Lith",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Vindicta & Gorvek",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Vorago",
"hardmode": "true"
"hardmode": true
},
{
"boss": "Yakamaru",
"hardmode": "false"
"hardmode": false
},
{
"boss": "Zamorak, Lord of Chaos",
"hardmode": true
}
],
"categories": [
{
"category": "Kill Count",
"categories": {
"Kill Count": {
"hardmode": true,
"ascending": false
"ascending": false,
"is_time_record": false
},
{
"category": "Kill Time",
"Kill Time": {
"hardmode": true,
"ascending": true
"ascending": true,
"is_time_record": true
},
{
"category": "Spooned Log",
"Spooned Log": {
"hardmode": false,
"ascending": true
"ascending": true,
"is_time_record": false
}
],
},
"highscore_size": 3
}
+13 -10
View File
@@ -2,7 +2,6 @@
main file for pvm-highscores bot
"""
import json
import logging
import os
@@ -10,19 +9,22 @@ import nextcord
from nextcord.ext import commands
import config
from utils import data_storage
from utilities.data_storage import open_highscores_config
from utilities.data_storage import open_highscores_data
from utilities.data_storage import open_message_map
logger = logging.getLogger('nextcord')
logger = logging.getLogger("nextcord")
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(
filename='nextcord.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter(
'%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w")
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
logger.addHandler(handler)
highscores_config = data_storage.open_highscores_config()
highscores_data = data_storage.open_highscores_data()
highscores_config = open_highscores_config()
highscores_data = open_highscores_data()
highscores_message_map = open_message_map()
submission_messages: dict = {}
def main():
@@ -32,7 +34,8 @@ def main():
intents = nextcord.Intents.default()
# Required in order to read messages (eg. prefix commands)
intents.message_content = True
intents.message_content = True # pylint: disable=assigning-non-slot
intents.messages = True # pylint: disable=assigning-non-slot
# To enable the guilds priveleged intent:
# intents.guilds = True
+75
View File
@@ -0,0 +1,75 @@
"""Functions for retreiving and storing highscores data and configurations."""
import os
import json
HIGHSCORES_CONFIG_FILE = "highscores_config.json"
HIGHSCORES_DATA_FILE = "highscores_data.json"
HIGHSCORES_MESSAGE_MAP_FILE = "highscores_message_map.json"
def open_highscores_config():
"""
Opens the highscores config file and returns.
@return: dict of highscores config.
"""
# Load bot config
with open(HIGHSCORES_CONFIG_FILE, encoding="utf-8", mode="r") as file:
return json.load(file)
def open_highscores_data():
"""
Opens a highscores_data.json file if one exists, otherwise creates a skeleton of the data.
@return: dict of highscores data.
"""
if os.path.exists(HIGHSCORES_DATA_FILE):
with open(HIGHSCORES_DATA_FILE, encoding="utf-8", mode="r") as file:
data = json.load(file)
else:
# create the json object
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
def open_message_map():
"""
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.
@param data: dict of highscores data.
"""
with open(HIGHSCORES_DATA_FILE, encoding="utf-8", mode="w") as file:
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))
+130
View File
@@ -0,0 +1,130 @@
"""
Contains various utlity functions for highscores bot
"""
import nextcord
from highscores import highscores_config # pylint: disable=import-error
from highscores import highscores_data # 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_message_map # pylint: disable=import-error
def format_highscore_message(boss_name: str):
"""
returns a formatted string for a boss's highscore
@param boss: the dictionary object for the boss
@return the formatted string
"""
ret_string = f"**{boss_name}**\n```"
boss_data = highscores_data[boss_name]
# Gather all categories and add to string
for key, value in boss_data["categories"].items():
ret_string += f"{key}".ljust(30, " ")
ret_string += "\n"
highscore_size = highscores_config["highscore_size"]
# list rankings in a single line. Start with first place for each category and then continue
for i in range(highscore_size):
for key, value in boss_data["categories"].items():
if len(value) > i:
score = value[i]
ret_string += f"{i+1}: {score[0]} - {score[1]}".ljust(30, " ")
else:
ret_string += f"{i+1}: submit your score".ljust(30, " ")
ret_string += "\n"
return ret_string + "```"
async def send_highscore_message(channel, boss_name: str) -> nextcord.Message:
"""
Function takes care of checking if a message exists for a boss, then
creating and sending/editing the message
@param channel: the channel to send the message in/search for message id
@param boss: the boss to send the message about
"""
highscore_string = format_highscore_message(boss_name)
message_id = highscores_data[boss_name]["message_id"]
try:
message: nextcord.Message = await channel.fetch_message(message_id)
await message.edit(content=highscore_string)
except nextcord.NotFound:
# message doesn't exist.
message: nextcord.Message = await channel.send(highscore_string)
message_id = message.id
highscores_data[boss_name]["message_id"] = message_id
# save message -> boss_name in message_map
highscores_message_map[str(message_id)] = boss_name
save_message_map(highscores_message_map)
return message
async def submit_score(
interaction: nextcord.Interaction, user: str, boss_name: str, category: str, score: str
):
"""
Adds a score to the high scores and sorts. Removes a score if larger than size limit.
@param boss: the boss to add score to
@param category: the category to add score to
@param user: user who submitted score
@param score: score to add
"""
# TODO update user submission to not have dupe users
# hardmode category, must have different category variable for config access.
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.
user = user[:12]
# create score tuple for either time or int
# define sort index
if highscores_config["categories"][config_category]["is_time_record"]:
if score.find(":") == -1:
score_seconds = float(score)
else:
minutes = score.split(":")[0]
seconds = score.split(":")[1]
score_seconds = int(minutes) * 60 + float(seconds)
score_tuple = (user, score, score_seconds)
sort_index = 2
else:
score_tuple = (user, int(score))
sort_index = 1
scores = highscores_data[boss_name]["categories"][category]
scores.append(score_tuple)
# sort scores.
scores.sort(
key=lambda x: x[sort_index],
reverse=not highscores_config["categories"][config_category]["ascending"],
)
# enforce highscore size
while len(scores) > highscores_config["highscore_size"]:
scores.pop()
highscores_data[boss_name]["categories"][category] = scores
# edit message
highscore_channel_id = highscores_data["highscore_channel_id"]
print(f"Editting message {highscore_channel_id}")
channel = interaction.guild.get_channel(highscore_channel_id)
error_response = 'Registered Highscores channel does not exist or was never registered. \
Register with "?register" command.'
assert channel is not None, await interaction.send(error_response)
await send_highscore_message(channel, boss_name)
# save data
save_highscores_data(highscores_data)
-43
View File
@@ -1,43 +0,0 @@
"""
Contains functions for saving and loading highscore data and configs.
"""
import json
import os
HIGHSCORES_CONFIG_FILE = "highscores_config.json"
HIGHSCORES_DATA_FILE = "highscores_data.json"
def open_highscores_data():
"""
Opens a highscores_data.json file if one exists, otherwise creates a skeleton of the data.
@return: dict of highscores data.
"""
if (os.path.exists(HIGHSCORES_DATA_FILE)):
with open(HIGHSCORES_DATA_FILE, encoding='utf-8', mode="r") as file:
data = json.load(file)
else:
# create the json object
data = {}
return data
def open_highscores_config():
"""
Opens the highscores config file and returns.
@return: dict of highscores config.
"""
# Load bot config
with open(HIGHSCORES_CONFIG_FILE, encoding='utf-8', mode='r') as file:
return json.load(file)
def save_highscores_data(data):
"""
Saves the highscores data dict into a json file.
@param: dict of highscores data.
"""
with open(HIGHSCORES_DATA_FILE, encoding='utf-8', mode='w') as file:
file.write(json.dumps(data, indent=4))
-45
View File
@@ -1,45 +0,0 @@
"""
Contains various function to format the highscore messages
"""
import highscores # pylint: disable=import-error
def format_boss_highscore(boss):
"""
returns a formattedd string for a boss's highscore
@param the dictionary object for the boss
@return the formatted string
"""
boss_name = boss["boss"]
ret_string = f"**{boss_name}**\n"
# Gather all categories and add to string
for category in highscores.highscores_config["categories"]:
category_name = category["category"]
ret_string += f"{category_name}\t\t"
ret_string += "\n"
highscore_size = highscores.highscores_config["highscore_size"]
# list rankings
for i in range(highscore_size):
for category in highscores.highscores_config["categories"]:
ret_string += f"{i+1}: (name here)\t\t"
ret_string += "\n"
# if boss is hardmode, repeat above for hardmode applicable categories
if boss["hardmode"]:
for category in highscores.highscores_config["categories"]:
if category["hardmode"]:
category_name = category["category"]
ret_string += f"{category_name}\t\t"
ret_string += "\n"
for i in range(highscore_size):
for category in highscores.highscores_config["categories"]:
if category["hardmode"]:
ret_string += f"{i+1}: (name here)\t\t"
ret_string += "\n"
return ret_string