diff --git a/src/core/setting_types.py b/src/core/setting_types.py index 7bc1710d..96c31364 100644 --- a/src/core/setting_types.py +++ b/src/core/setting_types.py @@ -49,16 +49,20 @@ class CoinSetting(IntegerSetting): if num > cls._max: t = ctx_translator.get().t - raise UserInputError(t(_p( - 'settype:coin|parse|error:too_large', - "Provided number of coins was too high!" - ))) from None + raise UserInputError( + t(_p( + 'settype:coin|parse|error:too_large', + "You cannot set this to more than {coin}**{max}**!" + )).format(coin=conf.emojis.coin, max=cls._max) + ) from None elif num < cls._min: t = ctx_translator.get().t - raise UserInputError(t(_p( - 'settype:coin|parse|error:too_large', - "Provided number of coins was too low!" - ))) from None + raise UserInputError( + t(_p( + 'settype:coin|parse|error:too_small', + "You cannot set this to less than {coin}**{min}**!" + )).format(coin=conf.emojis.coin, min=cls._min) + ) from None return num diff --git a/src/modules/rolemenus/cog.py b/src/modules/rolemenus/cog.py index 3724cb76..3618b4fd 100644 --- a/src/modules/rolemenus/cog.py +++ b/src/modules/rolemenus/cog.py @@ -1192,7 +1192,7 @@ class RoleMenuCog(LionCog): label: Optional[appcmds.Range[str, 1, 100]] = None, emoji: 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, ): # Type checking guards @@ -1448,7 +1448,7 @@ class RoleMenuCog(LionCog): label: Optional[appcmds.Range[str, 1, 100]] = None, emoji: 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, ): # Type checking wards diff --git a/src/modules/rolemenus/rolemenu.py b/src/modules/rolemenus/rolemenu.py index da25d30c..fcaafdcf 100644 --- a/src/modules/rolemenus/rolemenu.py +++ b/src/modules/rolemenus/rolemenu.py @@ -165,7 +165,7 @@ class RoleMenu: await menu.attach() 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. """ @@ -529,11 +529,17 @@ class RoleMenu: "Role removed" )) ) - if total_refund: + if total_refund > 0: embed.description = t(_p( 'rolemenu|deselect|success:refund|desc', "You have removed **{role}**, and been refunded {coin} **{amount}**." )).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: embed.description = t(_p( 'rolemenu|deselect|success:norefund|desc', @@ -551,7 +557,7 @@ class RoleMenu: raise UserInputError( t(_p( '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) ) @@ -647,7 +653,7 @@ class RoleMenu: "Role equipped" )) ) - if price > 0: + if price: embed.description = t(_p( 'rolemenu|select|success:purchase|desc', "You have purchased the role **{role}** for {coin}**{amount}**" @@ -665,6 +671,7 @@ class RoleMenu: )).format( timestamp=discord.utils.format_dt(expiry) ) + # TODO Event logging return embed async def interactive_selection(self, interaction: discord.Interaction, menuroleid: int): diff --git a/src/modules/rolemenus/roleoptions.py b/src/modules/rolemenus/roleoptions.py index e3ab699c..bdcc8d89 100644 --- a/src/modules/rolemenus/roleoptions.py +++ b/src/modules/rolemenus/roleoptions.py @@ -1,14 +1,17 @@ +from typing import Optional import discord from settings import ModelData from settings.groups import SettingGroup, ModelConfig, SettingDotDict from settings.setting_types import ( - RoleSetting, BoolSetting, StringSetting, DurationSetting, EmojiSetting + RoleSetting, StringSetting, DurationSetting, EmojiSetting ) from core.setting_types import CoinSetting from utils.ui import AButton, AsComponents from meta.errors import UserInputError +from meta import conf from babel.translator import ctx_translator +from constants import MAX_COINS from .data import RoleMenuData from . import babel @@ -74,6 +77,9 @@ class RoleMenuRoleOptions(SettingGroup): "This menu item will now give the role {role}." )).format(role=self.formatted) return resp + else: + raise ValueError("Attempt to call update_message without a value.") + @RoleMenuRoleConfig.register_model_setting class Label(ModelData, StringSetting): @@ -138,7 +144,9 @@ class RoleMenuRoleOptions(SettingGroup): return button @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) if emojistr and interaction is not None: # Use the interaction to test @@ -151,7 +159,7 @@ class RoleMenuRoleOptions(SettingGroup): view=view, ) except discord.HTTPException: - t = interaction.client.translator.t + t = ctx_translator.get().t raise UserInputError(t(_p( 'roleset:emoji|error:test_emoji', "The selected emoji `{emoji}` is invalid or has been deleted." @@ -218,34 +226,43 @@ class RoleMenuRoleOptions(SettingGroup): _display_name = _p('roleset:price', "price") _desc = _p( 'roleset:price|desc', - "Price of the role, in LionCoins." + "Price of the role, in LionCoins. May be negative." ) _long_desc = _p( '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( 'roleset:price|accepts', - "Amount of coins that the role costs." + "Amount of coins that the role costs when equipped." ) _default = 0 + _min = - MAX_COINS _model = RoleMenuData.RoleMenuRole _column = RoleMenuData.RoleMenuRole.price.name @property def update_message(self) -> str: t = ctx_translator.get().t - value = self.value - if value: + value = self.value or 0 + if value > 0: resp = t(_p( - 'roleset:price|set_response:set', - "This role will now cost {price} to equip." - )).format(price=self.formatted) + 'roleset:price|set_response:positive', + "Equipping this role will now cost {coin}**{price}**." + )).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: resp = t(_p( - 'roleset:price|set_response:unset', - "This role will now be free to equip from this role menu." - )) + 'roleset:price|set_response:negative', + "Equipping this role will now reward {coin}**{price}**." + )).format(price=-value, coin=conf.emojis.coin) return resp @RoleMenuRoleConfig.register_model_setting