rewrite: New ranks module.
This commit is contained in:
4
src/modules/ranks/ui/__init__.py
Normal file
4
src/modules/ranks/ui/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .editor import RankEditor
|
||||
from .preview import RankPreviewUI
|
||||
from .overview import RankOverviewUI
|
||||
from .config import RankConfigUI
|
||||
161
src/modules/ranks/ui/config.py
Normal file
161
src/modules/ranks/ui/config.py
Normal file
@@ -0,0 +1,161 @@
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
from discord.ui.select import select, ChannelSelect, Select, SelectOption
|
||||
from discord.ui.button import button, Button, ButtonStyle
|
||||
|
||||
from meta import LionBot
|
||||
from wards import i_high_management
|
||||
from core.data import RankType
|
||||
|
||||
from utils.ui import ConfigUI, DashboardSection
|
||||
from utils.lib import MessageArgs
|
||||
|
||||
from ..settings import RankSettings
|
||||
from .. import babel, logger
|
||||
from .overview import RankOverviewUI
|
||||
|
||||
_p = babel._p
|
||||
|
||||
|
||||
class RankConfigUI(ConfigUI):
|
||||
setting_classes = (
|
||||
RankSettings.RankStatType,
|
||||
RankSettings.DMRanks,
|
||||
RankSettings.RankChannel,
|
||||
)
|
||||
|
||||
def __init__(self, bot: LionBot,
|
||||
guildid: int, channelid: int, **kwargs):
|
||||
self.settings = bot.get_cog('RankCog').settings
|
||||
super().__init__(bot, guildid, channelid, **kwargs)
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
return await i_high_management(interaction)
|
||||
|
||||
# ----- UI Components -----
|
||||
|
||||
# Button to summon Overview UI
|
||||
@button(label="OVERVIEW_PLACEHOLDER", style=ButtonStyle.blurple)
|
||||
async def overview_button(self, press: discord.Interaction, pressed: Button):
|
||||
"""
|
||||
Display the Overview UI
|
||||
"""
|
||||
overviewui = RankOverviewUI(self.bot, press.guild, press.user.id)
|
||||
self._slaves.append(overviewui)
|
||||
await overviewui.run(press)
|
||||
|
||||
async def overview_button_refresh(self):
|
||||
self.overview_button.label = self.bot.translator.t(_p(
|
||||
'ui:rank_config|button:overview|label',
|
||||
"Edit Ranks"
|
||||
))
|
||||
|
||||
# Channel select menu
|
||||
@select(placeholder="TYPE_SELECT_PLACEHOLDER", min_values=1, max_values=1)
|
||||
async def type_menu(self, selection: discord.Interaction, selected: Select):
|
||||
await selection.response.defer(thinking=True)
|
||||
setting = self.instances[0]
|
||||
value = selected.values[0]
|
||||
data = RankType((value,))
|
||||
setting.data = data
|
||||
await setting.write()
|
||||
await selection.delete_original_response()
|
||||
|
||||
async def type_menu_refresh(self):
|
||||
t = self.bot.translator.t
|
||||
self.type_menu.placeholder = t(_p(
|
||||
'ui:rank_config|menu:types|placeholder',
|
||||
"Select Statistic Type"
|
||||
))
|
||||
|
||||
current = self.instances[0].data
|
||||
options = [
|
||||
SelectOption(
|
||||
label=t(_p(
|
||||
'ui:rank_config|menu:types|option:voice',
|
||||
"Voice Activity"
|
||||
)),
|
||||
value=RankType.VOICE.value[0],
|
||||
default=(current is RankType.VOICE)
|
||||
),
|
||||
SelectOption(
|
||||
label=t(_p(
|
||||
'ui:rank_config|menu:types|option:xp',
|
||||
"XP Earned"
|
||||
)),
|
||||
value=RankType.XP.value[0],
|
||||
default=(current is RankType.XP)
|
||||
),
|
||||
SelectOption(
|
||||
label=t(_p(
|
||||
'ui:rank_config|menu:types|option:messages',
|
||||
"Messages Sent"
|
||||
)),
|
||||
value=RankType.MESSAGE.value[0],
|
||||
default=(current is RankType.MESSAGE)
|
||||
),
|
||||
]
|
||||
self.type_menu.options = options
|
||||
|
||||
@select(cls=ChannelSelect, channel_types=[discord.ChannelType.text, discord.ChannelType.news],
|
||||
placeholder="CHANNEL_SELECT_PLACEHOLDER",
|
||||
min_values=0, max_values=1)
|
||||
async def channel_menu(self, selection: discord.Interaction, selected: ChannelSelect):
|
||||
await selection.response.defer()
|
||||
setting = self.instances[2]
|
||||
setting.value = selected.values[0] if selected.values else None
|
||||
await setting.write()
|
||||
|
||||
async def channel_menu_refresh(self):
|
||||
self.channel_menu.placeholder = self.bot.translator.t(_p(
|
||||
'ui:rank_config|menu:channels|placeholder',
|
||||
"Select Rank Notification Channel"
|
||||
))
|
||||
|
||||
# ----- UI Flow -----
|
||||
async def make_message(self) -> MessageArgs:
|
||||
t = self.bot.translator.t
|
||||
title = t(_p(
|
||||
'ui:rank_config|embed|title',
|
||||
"Ranks Configuration Panel"
|
||||
))
|
||||
embed = discord.Embed(
|
||||
colour=discord.Colour.orange(),
|
||||
title=title
|
||||
)
|
||||
for setting in self.instances:
|
||||
embed.add_field(**setting.embed_field, inline=False)
|
||||
|
||||
args = MessageArgs(embed=embed)
|
||||
return args
|
||||
|
||||
async def reload(self):
|
||||
lguild = await self.bot.core.lions.fetch_guild(self.guildid)
|
||||
self.instances = tuple(
|
||||
lguild.config.get(setting.setting_id) for setting in self.setting_classes
|
||||
)
|
||||
|
||||
async def refresh_components(self):
|
||||
await asyncio.gather(
|
||||
self.overview_button_refresh(),
|
||||
self.channel_menu_refresh(),
|
||||
self.type_menu_refresh(),
|
||||
self.edit_button_refresh(),
|
||||
self.close_button_refresh(),
|
||||
self.reset_button_refresh(),
|
||||
)
|
||||
self._layout = [
|
||||
(self.type_menu,),
|
||||
(self.channel_menu,),
|
||||
(self.overview_button, self.edit_button, self.reset_button, self.close_button)
|
||||
]
|
||||
|
||||
|
||||
class RankDashboard(DashboardSection):
|
||||
section_name = _p(
|
||||
'dash:rank|title',
|
||||
"Rank Configuration",
|
||||
)
|
||||
configui = RankConfigUI
|
||||
setting_classes = RankConfigUI.setting_classes
|
||||
369
src/modules/ranks/ui/editor.py
Normal file
369
src/modules/ranks/ui/editor.py
Normal file
@@ -0,0 +1,369 @@
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from discord.ui.text_input import TextInput, TextStyle
|
||||
|
||||
from meta import LionBot
|
||||
from meta.errors import UserInputError
|
||||
from core.data import RankType
|
||||
|
||||
from utils.ui import FastModal, error_handler_for, ModalRetryUI
|
||||
from utils.lib import parse_duration, replace_multiple
|
||||
|
||||
from .. import babel, logger
|
||||
from ..data import AnyRankData
|
||||
from ..utils import format_stat_range, rank_model_from_type, rank_message_keys
|
||||
|
||||
_p = babel._p
|
||||
|
||||
|
||||
class RankEditor(FastModal):
|
||||
"""
|
||||
Create or edit a single Rank.
|
||||
"""
|
||||
role_name: TextInput = TextInput(
|
||||
label='ROLE_NAME_PLACHOLDER',
|
||||
max_length=128,
|
||||
required=True
|
||||
)
|
||||
|
||||
def role_name_setup(self):
|
||||
self.role_name.label = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|input:role_name|label',
|
||||
"Role Name"
|
||||
))
|
||||
self.role_name.placeholder = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|input:role_name|placeholder',
|
||||
"Name of the awarded guild role"
|
||||
))
|
||||
|
||||
def role_name_parse(self) -> str:
|
||||
return self.role_name.value
|
||||
|
||||
role_colour: TextInput = TextInput(
|
||||
label='ROLE_COLOUR_PLACEHOLDER',
|
||||
min_length=7,
|
||||
max_length=16,
|
||||
required=False
|
||||
)
|
||||
|
||||
def role_colour_setup(self):
|
||||
self.role_colour.label = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|input:role_volour|label',
|
||||
"Role Colour"
|
||||
))
|
||||
self.role_colour.placeholder = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|input:role_colour|placeholder',
|
||||
"Colour of the awarded guild role, e.g. #AB1321"
|
||||
))
|
||||
|
||||
def role_colour_parse(self) -> discord.Colour:
|
||||
t = self.bot.translator.t
|
||||
if self.role_colour.value:
|
||||
try:
|
||||
colour = discord.Colour.from_str(self.role_colour.value)
|
||||
except ValueError:
|
||||
raise UserInputError(
|
||||
_msg=t(_p(
|
||||
'ui:rank_editor|input:role_colour|error:parse',
|
||||
"`role_colour`: Could not parse colour! Please use `#<hex>` format e.g. `#AB1325`."
|
||||
))
|
||||
)
|
||||
else:
|
||||
# TODO: Could use a standardised spectrum
|
||||
# And use the required value to select a colour
|
||||
colour = discord.Colour.random()
|
||||
return colour
|
||||
|
||||
requires: TextInput = TextInput(
|
||||
label='REQUIRES_PLACEHOLDER',
|
||||
max_length=9,
|
||||
required=True,
|
||||
)
|
||||
|
||||
def requires_setup(self):
|
||||
if self.rank_type is RankType.VOICE:
|
||||
self.requires.label = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|type:voice|input:requires|label',
|
||||
"Required Voice Hours"
|
||||
))
|
||||
self.requires.placholder = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|type:voice|input:requires|placeholder',
|
||||
"Number of voice hours before awarding this rank"
|
||||
))
|
||||
elif self.rank_type is RankType.XP:
|
||||
self.requires.label = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|type:xp|input:requires|label',
|
||||
"Required XP"
|
||||
))
|
||||
self.requires.placholder = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|type:xp|input:requires|placeholder',
|
||||
"Amount of XP needed before obtaining this rank"
|
||||
))
|
||||
elif self.rank_type is RankType.MESSAGE:
|
||||
self.requires.label = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|type:message|input:requires|label',
|
||||
"Required Message Count"
|
||||
))
|
||||
self.requires.placholder = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|type:message|input:requires|placeholder',
|
||||
"Number of messages needed before awarding rank"
|
||||
))
|
||||
|
||||
def requires_parse(self) -> int:
|
||||
t = self.bot.translator.t
|
||||
value = self.requires.value
|
||||
# TODO: Bound checking and errors for each type
|
||||
if self.rank_type is RankType.VOICE:
|
||||
if value.isdigit():
|
||||
data = int(value) * 3600
|
||||
else:
|
||||
data = parse_duration(self.requires.value)
|
||||
if not data:
|
||||
raise UserInputError(
|
||||
_msg=t(_p(
|
||||
'ui:rank_editor|type:voice|input:requires|error:parse',
|
||||
"`requires`: Could not parse provided minimum time! Please write a number of hours."
|
||||
))
|
||||
)
|
||||
elif self.rank_type is RankType.MESSAGE:
|
||||
value = value.lower().strip(' messages')
|
||||
if value.isdigit():
|
||||
data = int(value)
|
||||
else:
|
||||
raise UserInputError(
|
||||
_msg=t(_p(
|
||||
'ui:rank_editor|type:message|input:requires|error:parse',
|
||||
"`requires`: Could not parse provided minimum message count! Please enter an integer."
|
||||
))
|
||||
)
|
||||
elif self.rank_type is RankType.XP:
|
||||
value = value.lower().strip(' xps')
|
||||
if value.isdigit():
|
||||
data = int(value)
|
||||
else:
|
||||
raise UserInputError(
|
||||
_msg=t(_p(
|
||||
'ui:rank_editor|type:xp|input:requires|error:parse',
|
||||
"`requires`: Could not parse provided minimum XP! Please enter an integer."
|
||||
))
|
||||
)
|
||||
return data
|
||||
|
||||
reward: TextInput = TextInput(
|
||||
label='REWARD_PLACEHOLDER',
|
||||
max_length=9,
|
||||
required=False
|
||||
)
|
||||
|
||||
def reward_setup(self):
|
||||
self.reward.label = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|input:reward|label',
|
||||
"LionCoins awarded upon achieving this rank"
|
||||
))
|
||||
self.reward.placeholder = self.bot.translator.t(_p(
|
||||
'ui:rank_editor|input:reward|placeholder',
|
||||
"LionCoins awarded upon achieving this rank"
|
||||
))
|
||||
|
||||
def reward_parse(self) -> int:
|
||||
t = self.bot.translator.t
|
||||
value = self.reward.value
|
||||
if not value:
|
||||
# Empty value
|
||||
data = 0
|
||||
elif value.isdigit():
|
||||
data = int(value)
|
||||
else:
|
||||
raise UserInputError(
|
||||
_msg=t(_p(
|
||||
'ui:rank_editor|input:reward|error:parse',
|
||||
'`reward`: Please enter an integer number of LionCoins.'
|
||||
))
|
||||
)
|
||||
return data
|
||||
|
||||
message: TextInput = TextInput(
|
||||
label='MESSAGE_PLACEHOLDER',
|
||||
style=TextStyle.long,
|
||||
max_length=1024,
|
||||
required=True
|
||||
)
|
||||
|
||||
def message_setup(self):
|
||||
t = self.bot.translator.t
|
||||
self.message.label = t(_p(
|
||||
'ui:rank_editor|input:message|label',
|
||||
"Rank Message"
|
||||
))
|
||||
self.message.placeholder = t(_p(
|
||||
'ui:rank_editor|input:message|placeholder',
|
||||
(
|
||||
"Congratulatory message sent to the user upon achieving this rank."
|
||||
)
|
||||
))
|
||||
if self.rank_type is RankType.VOICE:
|
||||
# TRANSLATOR NOTE: Don't change the keys here, they will be automatically replaced by the localised key
|
||||
msg_default = t(_p(
|
||||
'ui:rank_editor|input:message|default|type:voice',
|
||||
(
|
||||
"Congratulations {user_mention}!\n"
|
||||
"For working hard for **{requires}**, you have achieved the rank of "
|
||||
"**{role_name}** in **{guild_name}**! Keep up the good work."
|
||||
)
|
||||
))
|
||||
elif self.rank_type is RankType.XP:
|
||||
# TRANSLATOR NOTE: Don't change the keys here, they will be automatically replaced by the localised key
|
||||
msg_default = t(_p(
|
||||
'ui:rank_editor|input:message|default|type:xp',
|
||||
(
|
||||
"Congratulations {user_mention}!\n"
|
||||
"For earning **{requires}**, you have achieved the guild rank of "
|
||||
"**{role_name}** in **{guild_name}**!"
|
||||
)
|
||||
))
|
||||
elif self.rank_type is RankType.MESSAGE:
|
||||
# TRANSLATOR NOTE: Don't change the keys here, they will be automatically replaced by the localised key
|
||||
msg_default = t(_p(
|
||||
'ui:rank_editor|input:message|default|type:msg',
|
||||
(
|
||||
"Congratulations {user_mention}!\n"
|
||||
"For sending **{requires}**, you have achieved the guild rank of "
|
||||
"**{role_name}** in **{guild_name}**!"
|
||||
)
|
||||
))
|
||||
# Replace the progam keys in the default message with the correct localised keys.
|
||||
replace_map = {pkey: t(lkey) for pkey, lkey in rank_message_keys}
|
||||
self.message.default = replace_multiple(msg_default, replace_map)
|
||||
|
||||
def message_parse(self) -> str:
|
||||
# Replace the localised keys with programmatic keys
|
||||
t = self.bot.translator.t
|
||||
replace_map = {t(lkey): pkey for pkey, lkey in rank_message_keys}
|
||||
return replace_multiple(self.message.value, replace_map)
|
||||
|
||||
def __init__(self, bot: LionBot, rank_type: RankType, **kwargs):
|
||||
self.bot = bot
|
||||
self.rank_type = rank_type
|
||||
|
||||
self.message_setup()
|
||||
self.reward_setup()
|
||||
self.requires_setup()
|
||||
self.role_name_setup()
|
||||
self.role_colour_setup()
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@classmethod
|
||||
async def edit_rank(cls, interaction: discord.Interaction,
|
||||
rank_type: RankType,
|
||||
rank: AnyRankData, role: discord.Role,
|
||||
callback=None):
|
||||
bot = interaction.client
|
||||
self = cls(
|
||||
bot,
|
||||
rank_type,
|
||||
title=bot.translator.t(_p('ui:rank_editor|mode:edit|title', "Rank Editor"))
|
||||
)
|
||||
self.role_name.default = role.name
|
||||
self.role_colour.default = str(role.colour)
|
||||
self.requires.default = format_stat_range(rank_type, rank.required, None, short=True)
|
||||
self.reward.default = rank.reward
|
||||
if rank.message:
|
||||
t = bot.translator.t
|
||||
replace_map = {pkey: t(lkey) for pkey, lkey in rank_message_keys}
|
||||
self.message.default = replace_multiple(rank.message, replace_map)
|
||||
|
||||
@self.submit_callback(timeout=15*60)
|
||||
async def _edit_rank_callback(interaction):
|
||||
# Parse each field in turn
|
||||
# A parse error will raise UserInputError and trigger ModalRetry
|
||||
role_name = self.role_name_parse()
|
||||
role_colour = self.role_colour_parse()
|
||||
requires = self.requires_parse()
|
||||
reward = self.reward_parse()
|
||||
message = self.message_parse()
|
||||
|
||||
# Once successful, use rank.update() to edit the rank if modified,
|
||||
if requires != rank.required or reward != rank.reward or message != rank.message:
|
||||
# In the corner-case where the rank has been externally deleted, this will be a no-op
|
||||
await rank.update(
|
||||
required=requires,
|
||||
reward=reward,
|
||||
message=message
|
||||
)
|
||||
self.bot.get_cog('RankCog').flush_guild_ranks(interaction.guild.id)
|
||||
# and edit the role with role.edit() if modified.
|
||||
if role_name != role.name or role_colour != role.colour:
|
||||
await role.edit(name=role_name, colour=role_colour)
|
||||
|
||||
# Respond with an update ack..
|
||||
# (Might not be required? Or maybe use ephemeral ack?)
|
||||
# Finally, run the provided parent callback if provided
|
||||
if callback is not None:
|
||||
await callback(rank, interaction)
|
||||
|
||||
# Editor ready, now send
|
||||
await interaction.response.send_modal(self)
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
async def create_rank(cls, interaction: discord.Interaction,
|
||||
rank_type: RankType,
|
||||
guild: discord.Guild, role: Optional[discord.Role] = None,
|
||||
callback=None):
|
||||
bot = interaction.client
|
||||
self = cls(
|
||||
bot,
|
||||
rank_type,
|
||||
title=bot.translator.t(_p(
|
||||
'ui:rank_editor|mode:create|title',
|
||||
"Rank Creator"
|
||||
))
|
||||
)
|
||||
if role is not None:
|
||||
self.role_name.default = role.name
|
||||
self.role_colour.default = str(role.colour)
|
||||
|
||||
@self.submit_callback(timeout=15*60)
|
||||
async def _create_rank_callback(interaction):
|
||||
# Parse each field in turn
|
||||
# A parse error will raise UserInputError and trigger ModalRetry
|
||||
role_name = self.role_name_parse()
|
||||
role_colour = self.role_colour_parse()
|
||||
requires = self.requires_parse()
|
||||
reward = self.reward_parse()
|
||||
message = self.message_parse()
|
||||
|
||||
# Create or edit the role
|
||||
if role is not None:
|
||||
rank_role = role
|
||||
# Edit role if properties were updated
|
||||
if (role_name != role.name or role_colour != role.colour):
|
||||
await role.edit(name=role_name, colour=role_colour)
|
||||
else:
|
||||
# Create the role
|
||||
rank_role = await guild.create_role(name=role_name, colour=role_colour)
|
||||
# TODO: Move role to correct position, based on rank list
|
||||
|
||||
# Create the Rank
|
||||
model = rank_model_from_type(rank_type)
|
||||
rank = await model.create(
|
||||
roleid=rank_role.id,
|
||||
guildid=guild.id,
|
||||
required=requires,
|
||||
reward=reward,
|
||||
message=message
|
||||
)
|
||||
self.bot.get_cog('RankCog').flush_guild_ranks(guild.id)
|
||||
|
||||
if callback is not None:
|
||||
await callback(rank, interaction)
|
||||
|
||||
# Editor ready, now send
|
||||
await interaction.response.send_modal(self)
|
||||
return self
|
||||
|
||||
@error_handler_for(UserInputError)
|
||||
async def rerequest(self, interaction: discord.Interaction, error: UserInputError):
|
||||
await ModalRetryUI(self, error.msg).respond_to(interaction)
|
||||
378
src/modules/ranks/ui/overview.py
Normal file
378
src/modules/ranks/ui/overview.py
Normal file
@@ -0,0 +1,378 @@
|
||||
from typing import Optional
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
from discord.ui.select import select, Select, SelectOption, RoleSelect
|
||||
from discord.ui.button import button, Button, ButtonStyle
|
||||
|
||||
from meta import conf, LionBot
|
||||
from core.data import RankType
|
||||
from data import ORDER
|
||||
|
||||
from utils.ui import MessageUI
|
||||
from utils.lib import MessageArgs
|
||||
from babel.translator import ctx_translator
|
||||
|
||||
from .. import babel, logger
|
||||
from ..data import AnyRankData
|
||||
from ..utils import rank_model_from_type, format_stat_range, stat_data_to_value
|
||||
from .editor import RankEditor
|
||||
from .preview import RankPreviewUI
|
||||
|
||||
_p = babel._p
|
||||
|
||||
|
||||
class RankOverviewUI(MessageUI):
|
||||
def __init__(self, bot: LionBot, guild: discord.Guild, callerid: int, **kwargs):
|
||||
super().__init__(callerid=callerid, **kwargs)
|
||||
self.bot = bot
|
||||
self.guild = guild
|
||||
self.guildid = guild.id
|
||||
|
||||
self.lguild = None
|
||||
# List of ranks rows in ASC order
|
||||
self.ranks: list[AnyRankData] = []
|
||||
self.rank_type: RankType = None
|
||||
|
||||
self.rank_preview: Optional[RankPreviewUI] = None
|
||||
|
||||
@property
|
||||
def rank_model(self):
|
||||
"""
|
||||
Return the correct Rank model for the current rank type.
|
||||
"""
|
||||
if self.rank_type is None:
|
||||
return None
|
||||
else:
|
||||
return rank_model_from_type(self.rank_type)
|
||||
|
||||
# ----- API -----
|
||||
async def run(self, *args, **kwargs):
|
||||
await super().run(*args, **kwargs)
|
||||
|
||||
# ----- UI Components -----
|
||||
@button(emoji=conf.emojis.cancel, style=ButtonStyle.red)
|
||||
async def quit_button(self, press: discord.Interaction, pressed: Button):
|
||||
"""
|
||||
Quit the UI.
|
||||
"""
|
||||
await press.response.defer()
|
||||
await self.quit()
|
||||
|
||||
async def quit_button_refresh(self):
|
||||
pass
|
||||
|
||||
@button(label="AUTO_BUTTON_PLACEHOLDER", style=ButtonStyle.blurple)
|
||||
async def auto_button(self, press: discord.Interaction, pressed: Button):
|
||||
"""
|
||||
Automatically generate a set of activity ranks for the guild.
|
||||
|
||||
Ranks are determined by rank type.
|
||||
"""
|
||||
await press.response.send_message("Not Implemented Yet")
|
||||
|
||||
async def auto_button_refresh(self):
|
||||
self.auto_button.label = self.bot.translator.t(_p(
|
||||
'ui:rank_overview|button:auto|label',
|
||||
"Auto Create"
|
||||
))
|
||||
|
||||
@button(label="REFRESH_BUTTON_PLACEHOLDER", style=ButtonStyle.blurple)
|
||||
async def refresh_button(self, press: discord.Interaction, pressed: Button):
|
||||
"""
|
||||
Refresh the current ranks,
|
||||
ensuring that all members have the correct rank.
|
||||
"""
|
||||
await press.response.send_message("Not Implemented Yet")
|
||||
|
||||
async def refresh_button_refresh(self):
|
||||
self.refresh_button.label = self.bot.translator.t(_p(
|
||||
'ui:rank_overview|button:refresh|label',
|
||||
"Refresh Member Ranks"
|
||||
))
|
||||
|
||||
@button(label="CLEAR_BUTTON_PLACEHOLDER", style=ButtonStyle.blurple)
|
||||
async def clear_button(self, press: discord.Interaction, pressed: Button):
|
||||
"""
|
||||
Clear the rank list.
|
||||
"""
|
||||
await self.rank_model.table.delete_where(guildid=self.guildid)
|
||||
self.bot.get_cog('RankCog').flush_guild_ranks(self.guild.id)
|
||||
self.ranks = []
|
||||
await self.redraw()
|
||||
|
||||
async def clear_button_refresh(self):
|
||||
self.clear_button.label = self.bot.translator.t(_p(
|
||||
'ui:rank_overview|button:clear|label',
|
||||
"Clear Ranks"
|
||||
))
|
||||
|
||||
@button(label="CREATE_BUTTON_PLACEHOLDER", style=ButtonStyle.blurple)
|
||||
async def create_button(self, press: discord.Interaction, pressed: Button):
|
||||
"""
|
||||
Create a new rank, and role to go with it.
|
||||
|
||||
Errors if the client does not have permission to create roles.
|
||||
"""
|
||||
async def _create_callback(rank, submit: discord.Interaction):
|
||||
await submit.response.send_message(
|
||||
embed=discord.Embed(
|
||||
colour=discord.Colour.brand_green(),
|
||||
description="Rank Created!"
|
||||
),
|
||||
ephemeral=True
|
||||
)
|
||||
await self.refresh()
|
||||
|
||||
await RankEditor.create_rank(
|
||||
press,
|
||||
self.rank_type,
|
||||
self.guild,
|
||||
callback=_create_callback
|
||||
)
|
||||
|
||||
async def create_button_refresh(self):
|
||||
self.create_button.label = self.bot.translator.t(_p(
|
||||
'ui:rank_overview|button:create|label',
|
||||
"Create Rank"
|
||||
))
|
||||
|
||||
@select(cls=RoleSelect, placeholder="ROLE_SELECT_PLACEHOLDER", min_values=1, max_values=1)
|
||||
async def role_menu(self, selection: discord.Interaction, selected):
|
||||
"""
|
||||
Create a new rank based on the selected role,
|
||||
or edit an existing rank,
|
||||
or throw an error if the role is @everyone or not manageable by the client.
|
||||
"""
|
||||
role: discord.Role = selected.values[0]
|
||||
if role.is_assignable():
|
||||
existing = next((rank for rank in self.ranks if rank.roleid == role.id), None)
|
||||
if existing:
|
||||
# Display and edit the given role
|
||||
await RankEditor.edit_rank(
|
||||
selection,
|
||||
self.rank_type,
|
||||
existing,
|
||||
role,
|
||||
callback=self._editor_callback
|
||||
)
|
||||
else:
|
||||
# Create new rank based on role
|
||||
await RankEditor.create_rank(
|
||||
selection,
|
||||
self.rank_type,
|
||||
self.guild,
|
||||
role=role,
|
||||
callback=self._editor_callback
|
||||
)
|
||||
else:
|
||||
# Ack with a complaint depending on the type of error
|
||||
t = self.bot.translator.t
|
||||
|
||||
if role.is_default():
|
||||
error = t(_p(
|
||||
'ui:rank_overview|menu:roles|error:not_assignable|suberror:is_default',
|
||||
"The @everyone role cannot be removed, and cannot be a rank!"
|
||||
))
|
||||
elif role.managed:
|
||||
error = t(_p(
|
||||
'ui:rank_overview|menu:roles|error:not_assignable|suberror:is_managed',
|
||||
"The role is managed by another application or integration, and cannot be a rank!"
|
||||
))
|
||||
elif not self.guild.me.guild_permissions.manage_roles:
|
||||
error = t(_p(
|
||||
'ui:rank_overview|menu:roles|error:not_assignable|suberror:no_permissions',
|
||||
"I do not have the `MANAGE_ROLES` permission in this server, so I cannot manage ranks!"
|
||||
))
|
||||
elif (role >= self.guild.me.top_role):
|
||||
error = t(_p(
|
||||
'ui:rank_overview|menu:roles|error:not_assignable|suberror:above_me',
|
||||
"This role is above my top role in the role hierarchy, so I cannot add or remove it!"
|
||||
))
|
||||
else:
|
||||
# Catch all for other potential issues
|
||||
error = t(_p(
|
||||
'ui:rank_overview|menu:roles|error:not_assignable|suberror:other',
|
||||
"I am not able to manage the selected role, so it cannot be a rank!"
|
||||
))
|
||||
|
||||
embed = discord.Embed(
|
||||
title=t(_p(
|
||||
'ui:rank_overview|menu:roles|error:not_assignable|title',
|
||||
"Could not create rank!"
|
||||
)),
|
||||
description=error,
|
||||
colour=discord.Colour.brand_red()
|
||||
)
|
||||
await selection.response.send_message(embed=embed, ephemeral=True)
|
||||
|
||||
async def _editor_callback(self, rank: AnyRankData, submit: discord.Interaction):
|
||||
asyncio.create_task(self.refresh())
|
||||
await self._open_preview(rank, submit)
|
||||
|
||||
async def _open_preview(self, rank: AnyRankData, interaction: discord.Interaction):
|
||||
previewui = RankPreviewUI(
|
||||
self.bot, self.guild, self.rank_type, rank, callerid=self._callerid, parent=self
|
||||
)
|
||||
if self.rank_preview is not None:
|
||||
asyncio.create_task(self.rank_preview.quit())
|
||||
self.rank_preview = previewui
|
||||
self._slaves = [previewui]
|
||||
await previewui.run(interaction)
|
||||
|
||||
async def role_menu_refresh(self):
|
||||
self.role_menu.placeholder = self.bot.translator.t(_p(
|
||||
'ui:rank_overview|menu:roles|placeholder',
|
||||
"Create from role"
|
||||
))
|
||||
|
||||
@select(cls=Select, placeholder="RANK_PLACEHOLDER", min_values=1, max_values=1)
|
||||
async def rank_menu(self, selection: discord.Interaction, selected):
|
||||
"""
|
||||
Select a rank to open the preview UI for that rank.
|
||||
|
||||
Replaces the previously opened preview ui, if open.
|
||||
"""
|
||||
rankid = int(selected.values[0])
|
||||
rank = await self.rank_model.fetch(rankid)
|
||||
await self._open_preview(rank, selection)
|
||||
|
||||
async def rank_menu_refresh(self):
|
||||
self.rank_menu.placeholder = self.bot.translator.t(_p(
|
||||
'ui:rank_overview|menu:ranks|placeholder',
|
||||
"View or edit rank"
|
||||
))
|
||||
|
||||
options = []
|
||||
for rank in self.ranks:
|
||||
role = self.guild.get_role(rank.roleid)
|
||||
name = role.name if role else "Unknown Role"
|
||||
option = SelectOption(
|
||||
value=str(rank.rankid),
|
||||
label=name,
|
||||
description=format_stat_range(self.rank_type, rank.required, short=False),
|
||||
)
|
||||
options.append(option)
|
||||
self.rank_menu.options = options
|
||||
|
||||
# ----- UI Flow -----
|
||||
def _format_range(self, start: int, end: Optional[int] = None):
|
||||
"""
|
||||
Appropriately format the given required amount for the current rank type.
|
||||
"""
|
||||
if self.rank_type is RankType.VOICE:
|
||||
startval = stat_data_to_value(self.rank_type, start)
|
||||
if end:
|
||||
endval = stat_data_to_value(self.rank_type, end)
|
||||
string = f"{startval} - {endval} h"
|
||||
else:
|
||||
string = f"{startval} h"
|
||||
elif self.rank_type is RankType.XP:
|
||||
if end:
|
||||
string = f"{start} - {end} XP"
|
||||
else:
|
||||
string = f"{start} XP"
|
||||
elif self.rank_type is RankType.MESSAGE:
|
||||
if end:
|
||||
string = f"{start} - {end} msgs"
|
||||
else:
|
||||
string = f"{start} msgs"
|
||||
return string
|
||||
|
||||
async def make_message(self) -> MessageArgs:
|
||||
t = self.bot.translator.t
|
||||
|
||||
if self.ranks:
|
||||
# Format the ranks into a neat list
|
||||
# TODO: Error symbols for non-existent or permitted roles
|
||||
required = [rank.required for rank in self.ranks]
|
||||
ranges = list(zip(required, required[1:]))
|
||||
pad = 1 if len(ranges) < 10 else 2
|
||||
|
||||
lines = []
|
||||
for i, rank in enumerate(self.ranks):
|
||||
if i == len(self.ranks) - 1:
|
||||
reqstr = format_stat_range(self.rank_type, rank.required)
|
||||
rangestr = f"≥ {reqstr}"
|
||||
else:
|
||||
start, end = ranges[i]
|
||||
rangestr = format_stat_range(self.rank_type, start, end)
|
||||
|
||||
line = "`[{pos:<{pad}}]` | <@&{roleid}> **({rangestr})**".format(
|
||||
pad=pad,
|
||||
pos=i+1,
|
||||
roleid=rank.roleid,
|
||||
rangestr=rangestr
|
||||
)
|
||||
lines.append(line)
|
||||
desc = '\n'.join(reversed(lines))
|
||||
else:
|
||||
# No ranks, give hints about adding ranks
|
||||
desc = t(_p(
|
||||
'ui:rank_overview|embed:noranks|desc',
|
||||
"No activity ranks have been set up!\n"
|
||||
"Press 'AUTO' to automatically create a "
|
||||
"standard heirachy of voice | text | xp ranks, "
|
||||
"or select a role or press Create below!"
|
||||
))
|
||||
if self.rank_type is RankType.VOICE:
|
||||
title = t(_p(
|
||||
'ui:rank_overview|embed|title|type:voice',
|
||||
"Voice Ranks in {guild_name}"
|
||||
))
|
||||
elif self.rank_type is RankType.XP:
|
||||
title = t(_p(
|
||||
'ui:rank_overview|embed|title|type:xp',
|
||||
"XP ranks in {guild_name}"
|
||||
))
|
||||
elif self.rank_type is RankType.MESSAGE:
|
||||
title = t(_p(
|
||||
'ui:rank_overview|embed|title|type:message',
|
||||
"Message ranks in {guild_name}"
|
||||
))
|
||||
title = title.format(guild_name=self.guild.name)
|
||||
embed = discord.Embed(
|
||||
colour=discord.Colour.orange(),
|
||||
title=title,
|
||||
description=desc
|
||||
)
|
||||
return MessageArgs(embed=embed)
|
||||
|
||||
async def refresh_layout(self):
|
||||
if self.ranks:
|
||||
# If the guild has at least one rank setup
|
||||
await asyncio.gather(
|
||||
self.rank_menu_refresh(),
|
||||
self.role_menu_refresh(),
|
||||
self.refresh_button_refresh(),
|
||||
self.create_button_refresh(),
|
||||
self.clear_button_refresh(),
|
||||
self.quit_button_refresh(),
|
||||
)
|
||||
self.set_layout(
|
||||
(self.rank_menu,),
|
||||
(self.role_menu,),
|
||||
(self.refresh_button, self.create_button, self.clear_button, self.quit_button)
|
||||
)
|
||||
else:
|
||||
# If the guild has no ranks set up
|
||||
await asyncio.gather(
|
||||
self.role_menu_refresh(),
|
||||
self.auto_button_refresh(),
|
||||
self.create_button_refresh(),
|
||||
self.quit_button_refresh(),
|
||||
)
|
||||
self.set_layout(
|
||||
(self.role_menu,),
|
||||
(self.auto_button, self.create_button, self.quit_button)
|
||||
)
|
||||
|
||||
async def reload(self):
|
||||
"""
|
||||
Refresh the rank list and type from data.
|
||||
"""
|
||||
self.lguild = await self.bot.core.lions.fetch_guild(self.guildid)
|
||||
self.rank_type = self.lguild.config.get('rank_type').value
|
||||
self.ranks = await self.rank_model.fetch_where(
|
||||
guildid=self.guildid
|
||||
).order_by('required', ORDER.ASC)
|
||||
328
src/modules/ranks/ui/preview.py
Normal file
328
src/modules/ranks/ui/preview.py
Normal file
@@ -0,0 +1,328 @@
|
||||
from typing import Optional
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
from discord.ui.select import select, RoleSelect
|
||||
from discord.ui.button import button, Button, ButtonStyle
|
||||
|
||||
from meta import conf, LionBot
|
||||
from core.data import RankType
|
||||
|
||||
from utils.ui import MessageUI, AButton, AsComponents
|
||||
from utils.lib import MessageArgs, replace_multiple
|
||||
from babel.translator import ctx_translator
|
||||
|
||||
from .. import babel, logger
|
||||
from ..data import AnyRankData
|
||||
from ..utils import format_stat_range, rank_message_keys
|
||||
from .editor import RankEditor
|
||||
|
||||
_p = babel._p
|
||||
|
||||
|
||||
class RankPreviewUI(MessageUI):
|
||||
"""
|
||||
Preview and edit a single guild rank.
|
||||
|
||||
This UI primarily serves as a platform for deleting the rank and changing the underlying role.
|
||||
"""
|
||||
def __init__(self, bot: LionBot,
|
||||
guild: discord.Guild,
|
||||
rank_type: RankType, rank: AnyRankData,
|
||||
parent: Optional[MessageUI] = None,
|
||||
**kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.bot = bot
|
||||
self.guild = guild
|
||||
self.guildid = guild.id
|
||||
|
||||
self.rank_type = rank_type
|
||||
self.rank = rank
|
||||
|
||||
self.parent = parent
|
||||
|
||||
# ----- UI API -----
|
||||
|
||||
# ----- UI Components -----
|
||||
@button(emoji=conf.emojis.cancel, style=ButtonStyle.red)
|
||||
async def quit_button(self, press: discord.Interaction, pressed: Button):
|
||||
"""
|
||||
Quit the UI.
|
||||
"""
|
||||
await press.response.defer()
|
||||
await self.quit()
|
||||
|
||||
async def quit_button_refresh(self):
|
||||
pass
|
||||
|
||||
@button(label="EDIT_PLACEHOLDER", style=ButtonStyle.blurple)
|
||||
async def edit_button(self, press: discord.Interaction, pressed: Button):
|
||||
"""
|
||||
Open the rank editor for the underlying rank.
|
||||
|
||||
Silent callback, just reload the UI.
|
||||
"""
|
||||
t = self.bot.translator.t
|
||||
role = self.guild.get_role(self.rank.roleid)
|
||||
|
||||
error = None
|
||||
if role is None:
|
||||
# Role no longer exists, prompt to select a new role
|
||||
error = t(_p(
|
||||
'ui:rank_preview|button:edit|error:role_deleted',
|
||||
"The role underlying this rank no longer exists! "
|
||||
"Please select a new role from the role menu."
|
||||
))
|
||||
elif not role.is_assignable():
|
||||
# Role exists but is invalid, prompt to select a new role
|
||||
error = t(_p(
|
||||
'ui:rank_preview|button:edit|error:role_not_assignable',
|
||||
"I do not have permission to edit the underlying role! "
|
||||
"Please select a new role from the role menu, "
|
||||
"or ensure my top role is above the selected role."
|
||||
))
|
||||
|
||||
if error is not None:
|
||||
embed = discord.Embed(
|
||||
title=t(_p(
|
||||
'ui:rank_preview|button:edit|error|title',
|
||||
"Failed to edit rank!"
|
||||
)),
|
||||
description=error,
|
||||
colour=discord.Colour.brand_red()
|
||||
)
|
||||
await press.response.send_message(embed=embed)
|
||||
else:
|
||||
await RankEditor.edit_rank(
|
||||
press,
|
||||
self.rank_type,
|
||||
self.rank,
|
||||
role,
|
||||
callback=self._editor_callback
|
||||
)
|
||||
|
||||
async def edit_button_refresh(self):
|
||||
self.edit_button.label = self.bot.translator.t(_p(
|
||||
'ui:rank_preview|button:edit|label',
|
||||
"Edit"
|
||||
))
|
||||
|
||||
async def _editor_callback(self, rank: AnyRankData, submit: discord.Interaction):
|
||||
await submit.response.defer(thinking=False)
|
||||
if self.parent is not None:
|
||||
asyncio.create_task(self.parent.refresh())
|
||||
await self.refresh()
|
||||
|
||||
@button(label="DELETE_PLACEHOLDER", style=ButtonStyle.red)
|
||||
async def delete_button(self, press: discord.Interaction, pressed: Button):
|
||||
"""
|
||||
Delete the current rank, post a deletion message, and quit the UI.
|
||||
|
||||
Also refreshes the parent, if set.
|
||||
"""
|
||||
t = self.bot.translator.t
|
||||
await press.response.defer(thinking=True, ephemeral=True)
|
||||
|
||||
roleid = self.rank.roleid
|
||||
role = self.guild.get_role(roleid)
|
||||
if not (role and self.guild.me.guild_permissions.manage_roles and self.guild.me.top_role > role):
|
||||
role = None
|
||||
|
||||
await self.rank.delete()
|
||||
|
||||
mention = role.mention if role else str(self.rank.roleid)
|
||||
|
||||
if role:
|
||||
desc = t(_p(
|
||||
'ui:rank_preview|button:delete|response:success|description|with_role',
|
||||
"You have deleted the rank {mention}. Press the button below to also delete the role."
|
||||
)).format(mention=mention)
|
||||
else:
|
||||
desc = t(_p(
|
||||
'ui:rank_preview|button:delete|response:success|description|no_role',
|
||||
"You have deleted the rank {mention}."
|
||||
)).format(mention=mention)
|
||||
|
||||
embed = discord.Embed(
|
||||
title=t(_p(
|
||||
'ui:rank_preview|button:delete|response:success|title',
|
||||
"Rank Deleted"
|
||||
)),
|
||||
description=desc,
|
||||
colour=discord.Colour.red()
|
||||
)
|
||||
|
||||
if role:
|
||||
# Add a micro UI to the response to delete the underlying role
|
||||
delete_role_label = t(_p(
|
||||
'ui:rank_preview|button:delete|response:success|button:delete_role|label',
|
||||
"Delete Role"
|
||||
))
|
||||
|
||||
@AButton(label=delete_role_label, style=ButtonStyle.red)
|
||||
async def delete_role(_press: discord.Interaction, pressed: Button):
|
||||
# Don't need an interaction check here because the message is ephemeral
|
||||
rolename = role.name
|
||||
try:
|
||||
await role.delete()
|
||||
errored = False
|
||||
except discord.HTTPException:
|
||||
errored = True
|
||||
|
||||
if errored:
|
||||
embed.description = t(_p(
|
||||
'ui:rank_preview|button:delete|response:success|button:delete_role|response:errored|desc',
|
||||
"You have deleted the rank **{name}**! "
|
||||
"Could not delete the role due to an unknown error."
|
||||
)).format(name=rolename)
|
||||
else:
|
||||
embed.description = t(_p(
|
||||
'ui:rank_preview|button:delete|response:success|button:delete_role|response:success|desc',
|
||||
"You have deleted the rank **{name}** along with the underlying role."
|
||||
)).format(name=rolename)
|
||||
|
||||
await press.edit_original_response(embed=embed, view=None)
|
||||
|
||||
await press.edit_original_response(embed=embed, view=AsComponents(delete_role))
|
||||
else:
|
||||
# Just send the deletion embed
|
||||
await press.edit_original_response(embed=embed)
|
||||
|
||||
if self.parent is not None and not self.parent.is_finished():
|
||||
asyncio.create_task(self.parent.refresh())
|
||||
await self.quit()
|
||||
|
||||
async def delete_button_refresh(self):
|
||||
self.delete_button.label = self.bot.translator.t(_p(
|
||||
'ui:rank_preview|button:delete|label',
|
||||
"Delete Rank"
|
||||
))
|
||||
|
||||
@select(cls=RoleSelect, placeholder="NEW_ROLE_MENU", min_values=1, max_values=1)
|
||||
async def role_menu(self, selection: discord.Interaction, selected):
|
||||
"""
|
||||
Select a new role for this rank.
|
||||
|
||||
Certain checks are enforced.
|
||||
Note this can potentially create two ranks with the same role.
|
||||
This will not cause any systemic issues aside from confusion.
|
||||
"""
|
||||
t = self.bot.translator.t
|
||||
role: discord.Role = selected.values[0]
|
||||
await selection.response.defer(thinking=True, ephemeral=True)
|
||||
|
||||
if role.is_assignable():
|
||||
# Update the rank role
|
||||
await self.rank.update(roleid=role.id)
|
||||
if self.parent is not None and not self.parent.is_finished():
|
||||
asyncio.create_task(self.parent.refresh())
|
||||
await self.refresh(thinking=selection)
|
||||
else:
|
||||
if role.is_default():
|
||||
error = t(_p(
|
||||
'ui:rank_preview|menu:roles|error:not_assignable|suberror:is_default',
|
||||
"The @everyone role cannot be removed, and cannot be a rank!"
|
||||
))
|
||||
elif role.managed:
|
||||
error = t(_p(
|
||||
'ui:rank_preview|menu:roles|error:not_assignable|suberror:is_managed',
|
||||
"The role is managed by another application or integration, and cannot be a rank!"
|
||||
))
|
||||
elif not self.guild.me.guild_permissions.manage_roles:
|
||||
error = t(_p(
|
||||
'ui:rank_preview|menu:roles|error:not_assignable|suberror:no_permissions',
|
||||
"I do not have the `MANAGE_ROLES` permission in this server, so I cannot manage ranks!"
|
||||
))
|
||||
elif (role >= self.guild.me.top_role):
|
||||
error = t(_p(
|
||||
'ui:rank_preview|menu:roles|error:not_assignable|suberror:above_me',
|
||||
"This role is above my top role in the role hierarchy, so I cannot add or remove it!"
|
||||
))
|
||||
else:
|
||||
# Catch all for other potential issues
|
||||
error = t(_p(
|
||||
'ui:rank_preview|menu:roles|error:not_assignable|suberror:other',
|
||||
"I am not able to manage the selected role, so it cannot be a rank!"
|
||||
))
|
||||
|
||||
embed = discord.Embed(
|
||||
title=t(_p(
|
||||
'ui:rank_preview|menu:roles|error:not_assignable|title',
|
||||
"Could not update rank!"
|
||||
)),
|
||||
description=error,
|
||||
colour=discord.Colour.brand_red()
|
||||
)
|
||||
await selection.edit_original_response(embed=embed)
|
||||
|
||||
async def role_menu_refresh(self):
|
||||
self.role_menu.placeholder = self.bot.translator.t(_p(
|
||||
'ui:rank_preview|menu:roles|placeholder',
|
||||
"Update Rank Role"
|
||||
))
|
||||
|
||||
# ----- UI Flow -----
|
||||
async def make_message(self) -> MessageArgs:
|
||||
# TODO: Localise
|
||||
t = self.bot.translator.t
|
||||
rank = self.rank
|
||||
|
||||
embed = discord.Embed(
|
||||
title=t(_p(
|
||||
'ui:rank_preview|embed|title',
|
||||
"Rank Information"
|
||||
)),
|
||||
colour=discord.Colour.orange()
|
||||
)
|
||||
embed.add_field(
|
||||
name=t(_p(
|
||||
'ui:rank_preview|embed|field:role|name',
|
||||
"Role"
|
||||
)),
|
||||
value=f"<@&{rank.roleid}>"
|
||||
)
|
||||
embed.add_field(
|
||||
name=t(_p(
|
||||
'ui:rank_preview|embed|field:required|name',
|
||||
"Required"
|
||||
)),
|
||||
value=format_stat_range(self.rank_type, rank.required, short=False)
|
||||
)
|
||||
embed.add_field(
|
||||
name=t(_p(
|
||||
'ui:rank_preview|embed|field:reward|name',
|
||||
"Reward"
|
||||
)),
|
||||
value=f"{conf.emojis.coin}**{rank.reward}**"
|
||||
)
|
||||
replace_map = {pkey: t(lkey) for pkey, lkey in rank_message_keys}
|
||||
message = replace_multiple(rank.message, replace_map)
|
||||
embed.add_field(
|
||||
name=t(_p(
|
||||
'ui:rank_preview|embed|field:message',
|
||||
"Congratulatory Message"
|
||||
)),
|
||||
value=f"```{message}```"
|
||||
)
|
||||
return MessageArgs(embed=embed)
|
||||
|
||||
async def refresh_layout(self):
|
||||
await asyncio.gather(
|
||||
self.role_menu_refresh(),
|
||||
self.edit_button_refresh(),
|
||||
self.delete_button_refresh(),
|
||||
self.quit_button_refresh()
|
||||
)
|
||||
self.set_layout(
|
||||
(self.role_menu,),
|
||||
(self.edit_button, self.delete_button, self.quit_button,)
|
||||
)
|
||||
|
||||
async def reload(self):
|
||||
"""
|
||||
Refresh the stored rank data.
|
||||
|
||||
Generally not required since RankData uses a Registry pattern.
|
||||
"""
|
||||
...
|
||||
Reference in New Issue
Block a user