feat(rmenus): Support negative prices.

This commit is contained in:
2023-09-23 15:28:12 +03:00
parent 576d7cf02f
commit 5675f72853
4 changed files with 56 additions and 28 deletions

View File

@@ -49,16 +49,20 @@ class CoinSetting(IntegerSetting):
if num > cls._max: if num > cls._max:
t = ctx_translator.get().t t = ctx_translator.get().t
raise UserInputError(t(_p( raise UserInputError(
t(_p(
'settype:coin|parse|error:too_large', 'settype:coin|parse|error:too_large',
"Provided number of coins was too high!" "You cannot set this to more than {coin}**{max}**!"
))) from None )).format(coin=conf.emojis.coin, max=cls._max)
) from None
elif num < cls._min: elif num < cls._min:
t = ctx_translator.get().t t = ctx_translator.get().t
raise UserInputError(t(_p( raise UserInputError(
'settype:coin|parse|error:too_large', t(_p(
"Provided number of coins was too low!" 'settype:coin|parse|error:too_small',
))) from None "You cannot set this to less than {coin}**{min}**!"
)).format(coin=conf.emojis.coin, min=cls._min)
) from None
return num return num

View File

@@ -1192,7 +1192,7 @@ class RoleMenuCog(LionCog):
label: Optional[appcmds.Range[str, 1, 100]] = None, label: Optional[appcmds.Range[str, 1, 100]] = None,
emoji: Optional[appcmds.Range[str, 0, 100]] = None, emoji: Optional[appcmds.Range[str, 0, 100]] = None,
description: Optional[appcmds.Range[str, 0, 100]] = None, description: Optional[appcmds.Range[str, 0, 100]] = None,
price: Optional[appcmds.Range[int, 0, MAX_COINS]] = None, price: Optional[appcmds.Range[int, -MAX_COINS, MAX_COINS]] = None,
duration: Optional[Transform[int, DurationTransformer(60)]] = None, duration: Optional[Transform[int, DurationTransformer(60)]] = None,
): ):
# Type checking guards # Type checking guards
@@ -1448,7 +1448,7 @@ class RoleMenuCog(LionCog):
label: Optional[appcmds.Range[str, 1, 100]] = None, label: Optional[appcmds.Range[str, 1, 100]] = None,
emoji: Optional[appcmds.Range[str, 0, 100]] = None, emoji: Optional[appcmds.Range[str, 0, 100]] = None,
description: Optional[appcmds.Range[str, 0, 100]] = None, description: Optional[appcmds.Range[str, 0, 100]] = None,
price: Optional[appcmds.Range[int, 0, MAX_COINS]] = None, price: Optional[appcmds.Range[int, -MAX_COINS, MAX_COINS]] = None,
duration: Optional[Transform[int, DurationTransformer(60)]] = None, duration: Optional[Transform[int, DurationTransformer(60)]] = None,
): ):
# Type checking wards # Type checking wards

View File

@@ -165,7 +165,7 @@ class RoleMenu:
await menu.attach() await menu.attach()
return menu return menu
async def fetch_message(self, refresh=False): async def fetch_message(self, refresh=False) -> Optional[discord.Message]:
""" """
Fetch the message the menu is attached to. Fetch the message the menu is attached to.
""" """
@@ -529,11 +529,17 @@ class RoleMenu:
"Role removed" "Role removed"
)) ))
) )
if total_refund: if total_refund > 0:
embed.description = t(_p( embed.description = t(_p(
'rolemenu|deselect|success:refund|desc', 'rolemenu|deselect|success:refund|desc',
"You have removed **{role}**, and been refunded {coin} **{amount}**." "You have removed **{role}**, and been refunded {coin} **{amount}**."
)).format(role=role.name, coin=self.bot.config.emojis.coin, amount=total_refund) )).format(role=role.name, coin=self.bot.config.emojis.coin, amount=total_refund)
if total_refund < 0:
# TODO: Consider disallowing them from removing roles if their balance would go negative
embed.description = t(_p(
'rolemenu|deselect|success:negrefund|desc',
"You have removed **{role}**, and have lost {coin} **{amount}**."
)).format(role=role.name, coin=self.bot.config.emojis.coin, amount=-total_refund)
else: else:
embed.description = t(_p( embed.description = t(_p(
'rolemenu|deselect|success:norefund|desc', 'rolemenu|deselect|success:norefund|desc',
@@ -551,7 +557,7 @@ class RoleMenu:
raise UserInputError( raise UserInputError(
t(_p( t(_p(
'rolemenu|select|error:required_role', 'rolemenu|select|error:required_role',
"You need to have the **{role}** role to use this!" "You need to have the role **{role}** required to use this menu!"
)).format(role=name) )).format(role=name)
) )
@@ -647,7 +653,7 @@ class RoleMenu:
"Role equipped" "Role equipped"
)) ))
) )
if price > 0: if price:
embed.description = t(_p( embed.description = t(_p(
'rolemenu|select|success:purchase|desc', 'rolemenu|select|success:purchase|desc',
"You have purchased the role **{role}** for {coin}**{amount}**" "You have purchased the role **{role}** for {coin}**{amount}**"
@@ -665,6 +671,7 @@ class RoleMenu:
)).format( )).format(
timestamp=discord.utils.format_dt(expiry) timestamp=discord.utils.format_dt(expiry)
) )
# TODO Event logging
return embed return embed
async def interactive_selection(self, interaction: discord.Interaction, menuroleid: int): async def interactive_selection(self, interaction: discord.Interaction, menuroleid: int):

View File

@@ -1,14 +1,17 @@
from typing import Optional
import discord import discord
from settings import ModelData from settings import ModelData
from settings.groups import SettingGroup, ModelConfig, SettingDotDict from settings.groups import SettingGroup, ModelConfig, SettingDotDict
from settings.setting_types import ( from settings.setting_types import (
RoleSetting, BoolSetting, StringSetting, DurationSetting, EmojiSetting RoleSetting, StringSetting, DurationSetting, EmojiSetting
) )
from core.setting_types import CoinSetting from core.setting_types import CoinSetting
from utils.ui import AButton, AsComponents from utils.ui import AButton, AsComponents
from meta.errors import UserInputError from meta.errors import UserInputError
from meta import conf
from babel.translator import ctx_translator from babel.translator import ctx_translator
from constants import MAX_COINS
from .data import RoleMenuData from .data import RoleMenuData
from . import babel from . import babel
@@ -74,6 +77,9 @@ class RoleMenuRoleOptions(SettingGroup):
"This menu item will now give the role {role}." "This menu item will now give the role {role}."
)).format(role=self.formatted) )).format(role=self.formatted)
return resp return resp
else:
raise ValueError("Attempt to call update_message without a value.")
@RoleMenuRoleConfig.register_model_setting @RoleMenuRoleConfig.register_model_setting
class Label(ModelData, StringSetting): class Label(ModelData, StringSetting):
@@ -138,7 +144,9 @@ class RoleMenuRoleOptions(SettingGroup):
return button return button
@classmethod @classmethod
async def _parse_string(cls, parent_id, string: str, interaction: discord.Interaction = None, **kwargs): async def _parse_string(cls, parent_id, string: str,
interaction: Optional[discord.Interaction] = None,
**kwargs):
emojistr = await super()._parse_string(parent_id, string, interaction=interaction, **kwargs) emojistr = await super()._parse_string(parent_id, string, interaction=interaction, **kwargs)
if emojistr and interaction is not None: if emojistr and interaction is not None:
# Use the interaction to test # Use the interaction to test
@@ -151,7 +159,7 @@ class RoleMenuRoleOptions(SettingGroup):
view=view, view=view,
) )
except discord.HTTPException: except discord.HTTPException:
t = interaction.client.translator.t t = ctx_translator.get().t
raise UserInputError(t(_p( raise UserInputError(t(_p(
'roleset:emoji|error:test_emoji', 'roleset:emoji|error:test_emoji',
"The selected emoji `{emoji}` is invalid or has been deleted." "The selected emoji `{emoji}` is invalid or has been deleted."
@@ -218,34 +226,43 @@ class RoleMenuRoleOptions(SettingGroup):
_display_name = _p('roleset:price', "price") _display_name = _p('roleset:price', "price")
_desc = _p( _desc = _p(
'roleset:price|desc', 'roleset:price|desc',
"Price of the role, in LionCoins." "Price of the role, in LionCoins. May be negative."
) )
_long_desc = _p( _long_desc = _p(
'roleset:price|long_desc', 'roleset:price|long_desc',
"How much the role costs when selected, in LionCoins." "How many LionCoins should be deducted from a member's account "
"when they equip this role through this menu.\n"
"The price may be negative, in which case the member will instead be rewarded "
"coins when they equip the role."
) )
_accepts = _p( _accepts = _p(
'roleset:price|accepts', 'roleset:price|accepts',
"Amount of coins that the role costs." "Amount of coins that the role costs when equipped."
) )
_default = 0 _default = 0
_min = - MAX_COINS
_model = RoleMenuData.RoleMenuRole _model = RoleMenuData.RoleMenuRole
_column = RoleMenuData.RoleMenuRole.price.name _column = RoleMenuData.RoleMenuRole.price.name
@property @property
def update_message(self) -> str: def update_message(self) -> str:
t = ctx_translator.get().t t = ctx_translator.get().t
value = self.value value = self.value or 0
if value: if value > 0:
resp = t(_p( resp = t(_p(
'roleset:price|set_response:set', 'roleset:price|set_response:positive',
"This role will now cost {price} to equip." "Equipping this role will now cost {coin}**{price}**."
)).format(price=self.formatted) )).format(price=value, coin=conf.emojis.coin)
elif value == 0:
resp = t(_p(
'roleset:price|set_response:zero',
"Equipping this role is now free."
))
else: else:
resp = t(_p( resp = t(_p(
'roleset:price|set_response:unset', 'roleset:price|set_response:negative',
"This role will now be free to equip from this role menu." "Equipping this role will now reward {coin}**{price}**."
)) )).format(price=-value, coin=conf.emojis.coin)
return resp return resp
@RoleMenuRoleConfig.register_model_setting @RoleMenuRoleConfig.register_model_setting