From ab39ceee71a9396d570bcd6dc09213671ac06abb Mon Sep 17 00:00:00 2001 From: Conatum Date: Mon, 16 Oct 2023 21:43:54 +0300 Subject: [PATCH] feat(config): Split mod and admin config. --- src/babel/cog.py | 4 +--- src/babel/settings.py | 11 ++++++---- src/babel/settingui.py | 3 ++- src/core/config.py | 29 ++++++++++++++++++++++--- src/modules/config/cog.py | 6 ++--- src/modules/config/settings.py | 9 +++++--- src/modules/config/settingui.py | 3 ++- src/modules/economy/cog.py | 3 +-- src/modules/economy/settings.py | 8 +++++-- src/modules/economy/settingui.py | 2 +- src/modules/member_admin/cog.py | 2 +- src/modules/member_admin/settings.py | 7 ++++++ src/modules/member_admin/settingui.py | 11 +++++++--- src/modules/meta/help_sections.py | 4 ++-- src/modules/moderation/cog.py | 2 +- src/modules/moderation/settings.py | 23 +++++++++++++++----- src/modules/moderation/settingui.py | 6 ++++- src/modules/pomodoro/cog.py | 3 +-- src/modules/pomodoro/settings.py | 4 +++- src/modules/pomodoro/settingui.py | 3 ++- src/modules/ranks/cog.py | 3 +-- src/modules/ranks/settings.py | 10 ++++++--- src/modules/ranks/ui/config.py | 4 +++- src/modules/ranks/ui/overview.py | 2 +- src/modules/rooms/cog.py | 9 ++++---- src/modules/rooms/settings.py | 13 +++++++---- src/modules/rooms/settingui.py | 4 +++- src/modules/schedule/cog.py | 6 ++--- src/modules/schedule/settings.py | 26 +++++++++++++++------- src/modules/schedule/ui/settingui.py | 12 ++++++---- src/modules/statistics/cog.py | 7 +++--- src/modules/statistics/settings.py | 11 ++++++++-- src/modules/tasklist/cog.py | 3 +-- src/modules/tasklist/settings.py | 10 ++++++--- src/modules/video_channels/cog.py | 5 ++--- src/modules/video_channels/settings.py | 6 +++++ src/modules/video_channels/settingui.py | 5 ++++- src/settings/ui.py | 14 ++++++++++++ src/tracking/text/cog.py | 5 ++--- src/tracking/text/settings.py | 8 +++++-- src/tracking/text/ui.py | 3 ++- src/tracking/voice/cog.py | 3 +-- src/tracking/voice/settings.py | 16 +++++++++----- src/utils/ui/config.py | 1 + 44 files changed, 227 insertions(+), 102 deletions(-) diff --git a/src/babel/cog.py b/src/babel/cog.py index 440db01c..bc649033 100644 --- a/src/babel/cog.py +++ b/src/babel/cog.py @@ -41,7 +41,7 @@ class BabelCog(LionCog): self.bot.core.user_config.register_model_setting(LocaleSettings.UserLocale) configcog = self.bot.get_cog('ConfigCog') - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.config_group) userconfigcog = self.bot.get_cog('UserConfigCog') self.crossload_group(self.userconfig_group, userconfigcog.userconfig_group) @@ -114,8 +114,6 @@ class BabelCog(LionCog): language=LocaleSettings.GuildLocale._display_name, force_language=LocaleSettings.ForceLocale._display_name ) - @appcmds.guild_only() # Can be removed when attached as a subcommand - @appcmds.default_permissions(manage_guild=True) @low_management_ward async def cmd_configure_language(self, ctx: LionContext, language: Optional[str] = None, diff --git a/src/babel/settings.py b/src/babel/settings.py index 24c45914..5d77f1bb 100644 --- a/src/babel/settings.py +++ b/src/babel/settings.py @@ -7,6 +7,7 @@ from settings.groups import SettingGroup from meta.errors import UserInputError from meta.context import ctx_bot from core.data import CoreData +from wards import low_management_iward from .translator import ctx_translator from . import babel @@ -104,9 +105,10 @@ class LocaleSettings(SettingGroup): """ Guild configuration for whether to force usage of the guild locale. - Exposed via `/configure language` command and standard configuration interface. + Exposed via `/config language` command and standard configuration interface. """ setting_id = 'force_locale' + _write_ward = low_management_iward _display_name = _p('guildset:force_locale', 'force_language') _desc = _p('guildset:force_locale|desc', @@ -144,15 +146,16 @@ class LocaleSettings(SettingGroup): def set_str(self): bot = ctx_bot.get() if bot: - return bot.core.mention_cmd('configure language') + return bot.core.mention_cmd('config language') class GuildLocale(ModelData, LocaleSetting): """ Guild-configured locale. - Exposed via `/configure language` command, and standard configuration interface. + Exposed via `/config language` command, and standard configuration interface. """ setting_id = 'guild_locale' + _write_ward = low_management_iward _display_name = _p('guildset:locale', 'language') _desc = _p('guildset:locale|desc', "Your preferred language for interacting with me.") @@ -180,4 +183,4 @@ class LocaleSettings(SettingGroup): def set_str(self): bot = ctx_bot.get() if bot: - return bot.core.mention_cmd('configure language') + return bot.core.mention_cmd('config language') diff --git a/src/babel/settingui.py b/src/babel/settingui.py index 0449d1f6..2be92865 100644 --- a/src/babel/settingui.py +++ b/src/babel/settingui.py @@ -29,6 +29,7 @@ class LocaleSettingUI(ConfigUI): async def force_button(self, press: discord.Interaction, pressed: Button): await press.response.defer() setting = next(inst for inst in self.instances if inst.setting_id == LocaleSettings.ForceLocale.setting_id) + await setting.interaction_check(self.guildid, press) setting.value = not setting.value await setting.write() @@ -80,7 +81,7 @@ class LocaleSettingUI(ConfigUI): class LocaleDashboard(DashboardSection): section_name = _p( 'dash:locale|title', - "Server Language Configuration ({commands[configure language]})" + "Server Language Configuration ({commands[config language]})" ) _option_name = _p( "dash:locale|dropdown|placeholder", diff --git a/src/core/config.py b/src/core/config.py index 0468c715..7ea07736 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -25,12 +25,35 @@ class ConfigCog(LionCog): ... @cmds.hybrid_group( - name=_p('group:configure', "configure"), - description=_p('group:configure|desc', "View and adjust my configuration options."), + name=_p('group:config', "config"), + description=_p('group:config|desc', "View and adjust moderation-level configuration."), ) @appcmds.guild_only @appcmds.default_permissions(manage_guild=True) - async def configure_group(self, ctx: LionContext): + async def config_group(self, ctx: LionContext): + """ + Bare command group, has no function. + """ + return + + @cmds.hybrid_group( + name=_p('group:admin', "admin"), + description=_p('group:admin|desc', "Administrative commands."), + ) + @appcmds.guild_only + @appcmds.default_permissions(administrator=True) + async def admin_group(self, ctx: LionContext): + """ + Bare command group, has no function. + """ + return + + @admin_group.group( + name=_p('group:admin_config', "config"), + description=_p('group:admin_config|desc', "View and adjust admin-level configuration."), + ) + @appcmds.guild_only + async def admin_config_group(self, ctx: LionContext): """ Bare command group, has no function. """ diff --git a/src/modules/config/cog.py b/src/modules/config/cog.py index 307b1df0..c146f768 100644 --- a/src/modules/config/cog.py +++ b/src/modules/config/cog.py @@ -29,14 +29,14 @@ class GuildConfigCog(LionCog): configcog = self.bot.get_cog('ConfigCog') if configcog is None: raise ValueError("Cannot load GuildConfigCog without ConfigCog") - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.config_group) @cmds.hybrid_command( name="dashboard", description="At-a-glance view of the server's configuration." ) @appcmds.guild_only - @appcmds.default_permissions(manage_guild=True) + @low_management_ward async def dashboard_cmd(self, ctx: LionContext): if not ctx.guild or not ctx.interaction: return @@ -64,8 +64,6 @@ class GuildConfigCog(LionCog): timezone=GeneralSettings.Timezone._desc, event_log=GeneralSettings.EventLog._desc, ) - @appcmds.guild_only() - @appcmds.default_permissions(manage_guild=True) @low_management_ward async def cmd_configure_general(self, ctx: LionContext, timezone: Optional[str] = None, diff --git a/src/modules/config/settings.py b/src/modules/config/settings.py index 87c5f0d4..6403980a 100644 --- a/src/modules/config/settings.py +++ b/src/modules/config/settings.py @@ -9,6 +9,7 @@ from meta.context import ctx_bot from meta.errors import UserInputError from core.data import CoreData from babel.translator import ctx_translator +from wards import low_management_iward from . import babel @@ -20,13 +21,14 @@ class GeneralSettings(SettingGroup): """ Guild timezone configuration. - Exposed via `/configure general timezone:`, and the standard interface. + Exposed via `/config general timezone:`, and the standard interface. The `timezone` setting acts as the default timezone for all members, and the timezone used to display guild-wide statistics. """ setting_id = 'timezone' _event = 'guildset_timezone' - _set_cmd = 'configure general' + _set_cmd = 'config general' + _write_ward = low_management_iward _display_name = _p('guildset:timezone', "timezone") _desc = _p( @@ -58,7 +60,8 @@ class GeneralSettings(SettingGroup): """ setting_id = 'eventlog' _event = 'guildset_eventlog' - _set_cmd = 'configure general' + _set_cmd = 'config general' + _write_ward = low_management_iward _display_name = _p('guildset:eventlog', "event_log") _desc = _p( diff --git a/src/modules/config/settingui.py b/src/modules/config/settingui.py index 3359fa9d..55cf9081 100644 --- a/src/modules/config/settingui.py +++ b/src/modules/config/settingui.py @@ -41,6 +41,7 @@ class GeneralSettingUI(ConfigUI): await selection.response.defer(thinking=True, ephemeral=True) setting = self.get_instance(GeneralSettings.EventLog) + await setting.interaction_check(setting.parent_id, selection) value = selected.values[0].resolve() if selected.values else None setting = await setting.from_value(self.guildid, value) @@ -95,7 +96,7 @@ class GeneralSettingUI(ConfigUI): class GeneralDashboard(DashboardSection): section_name = _p( "dash:general|title", - "General Configuration ({commands[configure general]})" + "General Configuration ({commands[admin config general]})" ) _option_name = _p( "dash:general|option|name", diff --git a/src/modules/economy/cog.py b/src/modules/economy/cog.py index db383d4f..16cde9d2 100644 --- a/src/modules/economy/cog.py +++ b/src/modules/economy/cog.py @@ -64,7 +64,7 @@ class Economy(LionCog): "Attempting to load the EconomyCog before ConfigCog! Failed to crossload configuration group." ) else: - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.config_group) # ----- Economy Bonus registration ----- def register_economy_bonus(self, bonus_coro, name=None): @@ -903,7 +903,6 @@ class Economy(LionCog): appcmds.Choice(name=EconomySettings.AllowTransfers._outputs[False], value=0), ] ) - @appcmds.default_permissions(manage_guild=True) @moderator_ward async def configure_economy(self, ctx: LionContext, allow_transfers: Optional[appcmds.Choice[int]] = None, diff --git a/src/modules/economy/settings.py b/src/modules/economy/settings.py index 076710d4..1270323b 100644 --- a/src/modules/economy/settings.py +++ b/src/modules/economy/settings.py @@ -17,6 +17,7 @@ from meta.logger import log_wrap from core.data import CoreData from core.setting_types import CoinSetting from babel.translator import ctx_translator +from wards import low_management_iward from . import babel, logger from .data import EconomyData @@ -32,6 +33,7 @@ class EconomySettings(SettingGroup): """ class CoinsPerXP(ModelData, CoinSetting): setting_id = 'coins_per_xp' + _write_ward = low_management_iward _display_name = _p('guildset:coins_per_xp', "coins_per_100xp") _desc = _p( @@ -63,10 +65,11 @@ class EconomySettings(SettingGroup): @property def set_str(self): bot = ctx_bot.get() - return bot.core.mention_cmd('configure economy') if bot else None + return bot.core.mention_cmd('config economy') if bot else None class AllowTransfers(ModelData, BoolSetting): setting_id = 'allow_transfers' + _write_ward = low_management_iward _display_name = _p('guildset:allow_transfers', "allow_transfers") _desc = _p( @@ -91,7 +94,7 @@ class EconomySettings(SettingGroup): @property def set_str(self): bot = ctx_bot.get() - return bot.core.mention_cmd('configure economy') if bot else None + return bot.core.mention_cmd('config economy') if bot else None @property def update_message(self): @@ -115,6 +118,7 @@ class EconomySettings(SettingGroup): class StartingFunds(ModelData, CoinSetting): setting_id = 'starting_funds' + _write_ward = low_management_iward _display_name = _p('guildset:starting_funds', "starting_funds") _desc = _p( diff --git a/src/modules/economy/settingui.py b/src/modules/economy/settingui.py index f357d6e5..64b2091b 100644 --- a/src/modules/economy/settingui.py +++ b/src/modules/economy/settingui.py @@ -64,7 +64,7 @@ class EconomyConfigUI(ConfigUI): class EconomyDashboard(DashboardSection): section_name = _p( 'dash:economy|title', - "Economy Configuration ({commands[configure economy]})" + "Economy Configuration ({commands[config economy]})" ) _option_name = _p( "dash:economy|dropdown|placeholder", diff --git a/src/modules/member_admin/cog.py b/src/modules/member_admin/cog.py index 250db0b0..b8da79d2 100644 --- a/src/modules/member_admin/cog.py +++ b/src/modules/member_admin/cog.py @@ -46,7 +46,7 @@ class MemberAdminCog(LionCog): "Configuration command cannot be crossloaded." ) else: - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.config_group) # ----- Cog API ----- async def absent_remove_role(self, guildid, userid, roleid): diff --git a/src/modules/member_admin/settings.py b/src/modules/member_admin/settings.py index 057a202c..bbdb200b 100644 --- a/src/modules/member_admin/settings.py +++ b/src/modules/member_admin/settings.py @@ -9,6 +9,7 @@ from settings import ListData, ModelData from settings.groups import SettingGroup from settings.setting_types import BoolSetting, ChannelSetting, RoleListSetting from utils.lib import recurse_map, replace_multiple, tabulate +from wards import low_management_iward, high_management_iward from . import babel from .data import MemberAdminData @@ -36,6 +37,7 @@ _greeting_subkey_desc = { class MemberAdminSettings(SettingGroup): class GreetingChannel(ModelData, ChannelSetting): setting_id = 'greeting_channel' + _write_ward = low_management_iward _display_name = _p('guildset:greeting_channel', "welcome_channel") _desc = _p( @@ -87,6 +89,7 @@ class MemberAdminSettings(SettingGroup): class GreetingMessage(ModelData, MessageSetting): setting_id = 'greeting_message' + _write_ward = low_management_iward _display_name = _p( 'guildset:greeting_message', "welcome_message" @@ -209,6 +212,7 @@ class MemberAdminSettings(SettingGroup): class ReturningMessage(ModelData, MessageSetting): setting_id = 'returning_message' + _write_ward = low_management_iward _display_name = _p( 'guildset:returning_message', "returning_message" @@ -335,6 +339,7 @@ class MemberAdminSettings(SettingGroup): class Autoroles(ListData, RoleListSetting): setting_id = 'autoroles' + _write_ward = high_management_iward _display_name = _p( 'guildset:autoroles', "autoroles" @@ -357,6 +362,7 @@ class MemberAdminSettings(SettingGroup): class BotAutoroles(ListData, RoleListSetting): setting_id = 'bot_autoroles' + _write_ward = high_management_iward _display_name = _p( 'guildset:bot_autoroles', "bot_autoroles" @@ -379,6 +385,7 @@ class MemberAdminSettings(SettingGroup): class RolePersistence(ModelData, BoolSetting): setting_id = 'role_persistence' _event = 'guildset_role_persistence' + _write_ward = low_management_iward _display_name = _p('guildset:role_persistence', "role_persistence") _desc = _p( diff --git a/src/modules/member_admin/settingui.py b/src/modules/member_admin/settingui.py index 91e0e93e..52efce20 100644 --- a/src/modules/member_admin/settingui.py +++ b/src/modules/member_admin/settingui.py @@ -45,6 +45,7 @@ class MemberAdminUI(ConfigUI): """ await selection.response.defer(thinking=True, ephemeral=True) setting = self.get_instance(Settings.GreetingChannel) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() await selection.delete_original_response() @@ -73,6 +74,7 @@ class MemberAdminUI(ConfigUI): await equippable_role(self.bot, role, selection.user) setting = self.get_instance(Settings.Autoroles) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values await setting.write() # Instance hooks will update the menu @@ -102,6 +104,7 @@ class MemberAdminUI(ConfigUI): await equippable_role(self.bot, role, selection.user) setting = self.get_instance(Settings.BotAutoroles) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values await setting.write() # Instance hooks will update the menu @@ -131,6 +134,7 @@ class MemberAdminUI(ConfigUI): await press.response.defer(thinking=True, ephemeral=True) t = self.bot.translator.t setting = self.get_instance(Settings.GreetingMessage) + await setting.interaction_check(setting.parent_id, press) value = setting.value if value is None: @@ -173,6 +177,7 @@ class MemberAdminUI(ConfigUI): await press.response.defer(thinking=True, ephemeral=True) t = self.bot.translator.t setting = self.get_instance(Settings.ReturningMessage) + await setting.interaction_check(setting.parent_id, press) greeting = self.get_instance(Settings.GreetingMessage) value = setting.value @@ -254,7 +259,7 @@ class MemberAdminUI(ConfigUI): class MemberAdminDashboard(DashboardSection): section_name = _p( "dash:member_admin|title", - "Greetings and Initial Roles ({commands[configure welcome]})" + "Greetings and Initial Roles ({commands[admin config welcome]})" ) _option_name = _p( "dash:member_admin|dropdown|placeholder", @@ -278,7 +283,7 @@ class MemberAdminDashboard(DashboardSection): page.add_field( name=t(_p( 'dash:member_admin|section:greeting_messages|name', - "Greeting Messages ({commands[configure welcome]})" + "Greeting Messages ({commands[admin config welcome]})" )).format(commands=self.bot.core.mention_cache), value=table, inline=False @@ -289,7 +294,7 @@ class MemberAdminDashboard(DashboardSection): page.add_field( name=t(_p( 'dash:member_admin|section:initial_roles|name', - "Initial Roles ({commands[configure welcome]})" + "Initial Roles ({commands[admin config welcome]})" )).format(commands=self.bot.core.mention_cache), value=table, inline=False diff --git a/src/modules/meta/help_sections.py b/src/modules/meta/help_sections.py index b5c19672..0d9f917c 100644 --- a/src/modules/meta/help_sections.py +++ b/src/modules/meta/help_sections.py @@ -74,8 +74,8 @@ admin_extra = _p( Use {cmd_dashboard} to see an overview of the server configuration, \ and quickly jump to the feature configuration panels to modify settings. - Configuration panels are also accessible directly through the `/configure` commands \ - and most features may be configured through these commands. + Most settings may also be directly set through the `/config` and `/admin config` commands, \ + depending on whether the settings require moderator (manage server) or admin level permissions, respectively. Other relevant commands for guild configuration below: `/editshop`: Add/Edit/Remove colour roles from the {coin} shop. diff --git a/src/modules/moderation/cog.py b/src/modules/moderation/cog.py index 4ed52d11..c66b50c5 100644 --- a/src/modules/moderation/cog.py +++ b/src/modules/moderation/cog.py @@ -51,7 +51,7 @@ class ModerationCog(LionCog): "Moderation configuration will not crossload." ) else: - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.admin_config_group) if self.bot.is_ready(): await self.initialise() diff --git a/src/modules/moderation/settings.py b/src/modules/moderation/settings.py index c0dc2efb..a73416aa 100644 --- a/src/modules/moderation/settings.py +++ b/src/modules/moderation/settings.py @@ -6,6 +6,7 @@ from settings.setting_types import ( from core.data import CoreData from babel.translator import ctx_translator +from wards import low_management_iward, high_management_iward from . import babel @@ -16,6 +17,7 @@ class ModerationSettings(SettingGroup): class TicketLog(ModelData, ChannelSetting): setting_id = "ticket_log" _event = 'guildset_ticket_log' + _write_ward = low_management_iward _display_name = _p('guildset:ticket_log', "ticket_log") _desc = _p( @@ -66,6 +68,7 @@ class ModerationSettings(SettingGroup): class AlertChannel(ModelData, ChannelSetting): setting_id = "alert_channel" _event = 'guildset_alert_channel' + _write_ward = low_management_iward _display_name = _p('guildset:alert_channel', "alert_channel") _desc = _p( @@ -119,18 +122,23 @@ class ModerationSettings(SettingGroup): class ModRole(ModelData, RoleSetting): setting_id = "mod_role" _event = 'guildset_mod_role' + _write_ward = high_management_iward _display_name = _p('guildset:mod_role', "mod_role") _desc = _p( 'guildset:mod_role|desc', - "Guild role permitted to view configuration and perform moderation tasks." + "Server role permitted to perform moderation and minor bot configuration." ) _long_desc = _p( 'guildset:mod_role|long_desc', - "Members with the set role will be able to access my configuration panels, " - "and perform some moderation tasks, such as setting up pomodoro timers. " - "Moderators cannot reconfigure most bot configuration, " - "or perform operations they do not already have permission for in Discord." + "Members with the moderator role are considered moderators," + " and are permitted to use moderator commands," + " such as viewing and pardoning moderation tickets," + " creating moderation notes," + " and performing minor reconfiguration through the `/config` command.\n" + "Moderators are never permitted to perform actions (such as giving roles)" + " that they do not already have the Discord permissions for.\n" + "Members with the 'Manage Guild' permission are always considered moderators." ) _accepts = _p( 'guildset:mod_role|accepts', @@ -149,11 +157,13 @@ class ModerationSettings(SettingGroup): resp = t(_p( 'guildset:mod_role|set_response:set', "Members with {role} will be considered moderators." + " You may need to grant them access to view moderation commands" + " via the server integration settings." )).format(role=value.mention) else: resp = t(_p( 'guildset:mod_role|set_response:unset', - "No members will be given moderation privileges." + "Only members with the 'Manage Guild' permission will be considered moderators." )) return resp @@ -171,6 +181,7 @@ class ModerationSettings(SettingGroup): class AdminRole(ModelData, RoleSetting): setting_id = "admin_role" _event = 'guildset_admin_role' + _write_ward = high_management_iward _display_name = _p('guildset:admin_role', "admin_role") _desc = _p( diff --git a/src/modules/moderation/settingui.py b/src/modules/moderation/settingui.py index c0f90c5e..7d47e79f 100644 --- a/src/modules/moderation/settingui.py +++ b/src/modules/moderation/settingui.py @@ -42,6 +42,7 @@ class ModerationSettingUI(ConfigUI): await selection.response.defer(thinking=True, ephemeral=True) setting = self.get_instance(ModerationSettings.TicketLog) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() await selection.delete_original_response() @@ -67,6 +68,7 @@ class ModerationSettingUI(ConfigUI): await selection.response.defer(thinking=True, ephemeral=True) setting = self.get_instance(ModerationSettings.AlertChannel) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() await selection.delete_original_response() @@ -92,6 +94,7 @@ class ModerationSettingUI(ConfigUI): await selection.response.defer(thinking=True, ephemeral=True) setting = self.get_instance(ModerationSettings.ModRole) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() await selection.delete_original_response() @@ -117,6 +120,7 @@ class ModerationSettingUI(ConfigUI): await selection.response.defer(thinking=True, ephemeral=True) setting = self.get_instance(ModerationSettings.AdminRole) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() await selection.delete_original_response() @@ -175,7 +179,7 @@ class ModerationSettingUI(ConfigUI): class ModerationDashboard(DashboardSection): section_name = _p( "dash:moderation|title", - "Moderation Settings ({commands[configure moderation]})" + "Moderation Settings ({commands[admin config moderation]})" ) _option_name = _p( "dash:moderation|dropdown|placeholder", diff --git a/src/modules/pomodoro/cog.py b/src/modules/pomodoro/cog.py index 8a2d263c..230dd998 100644 --- a/src/modules/pomodoro/cog.py +++ b/src/modules/pomodoro/cog.py @@ -90,7 +90,7 @@ class TimerCog(LionCog): self.bot.core.guild_config.register_model_setting(self.settings.PomodoroChannel) configcog = self.bot.get_cog('ConfigCog') - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.config_group) if self.bot.is_ready(): await self.initialise() @@ -977,7 +977,6 @@ class TimerCog(LionCog): @appcmds.describe( pomodoro_channel=TimerSettings.PomodoroChannel._desc ) - @appcmds.default_permissions(manage_guild=True) @low_management_ward async def configure_pomodoro_command(self, ctx: LionContext, pomodoro_channel: Optional[discord.VoiceChannel | discord.TextChannel] = None): diff --git a/src/modules/pomodoro/settings.py b/src/modules/pomodoro/settings.py index 9c6fc6bf..4200d2a1 100644 --- a/src/modules/pomodoro/settings.py +++ b/src/modules/pomodoro/settings.py @@ -4,6 +4,7 @@ from settings.setting_types import ChannelSetting from core.data import CoreData from babel.translator import ctx_translator +from wards import low_management_iward from . import babel @@ -14,7 +15,8 @@ class TimerSettings(SettingGroup): class PomodoroChannel(ModelData, ChannelSetting): setting_id = 'pomodoro_channel' _event = 'guildset_pomodoro_channel' - _set_cmd = 'configure pomodoro' + _set_cmd = 'config pomodoro' + _write_ward = low_management_iward _display_name = _p('guildset:pomodoro_channel', "pomodoro_channel") _desc = _p( diff --git a/src/modules/pomodoro/settingui.py b/src/modules/pomodoro/settingui.py index fbdeedf1..bf2ee198 100644 --- a/src/modules/pomodoro/settingui.py +++ b/src/modules/pomodoro/settingui.py @@ -30,6 +30,7 @@ class TimerConfigUI(ConfigUI): async def channel_menu(self, selection: discord.Interaction, selected: ChannelSelect): await selection.response.defer() setting = self.instances[0] + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() @@ -78,7 +79,7 @@ class TimerConfigUI(ConfigUI): class TimerDashboard(DashboardSection): section_name = _p( 'dash:pomodoro|title', - "Pomodoro Configuration ({commands[configure pomodoro]})" + "Pomodoro Configuration ({commands[admin config pomodoro]})" ) _option_name = _p( "dash:stats|dropdown|placeholder", diff --git a/src/modules/ranks/cog.py b/src/modules/ranks/cog.py index 7d1da652..8f138dc3 100644 --- a/src/modules/ranks/cog.py +++ b/src/modules/ranks/cog.py @@ -140,7 +140,7 @@ class RankCog(LionCog): self.bot.core.guild_config.register_model_setting(self.settings.DMRanks) configcog = self.bot.get_cog('ConfigCog') - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.admin_config_group) def ranklock(self, guildid): lock = self._rank_locks.get(guildid, None) @@ -926,7 +926,6 @@ class RankCog(LionCog): dm_ranks=RankSettings.DMRanks._desc, rank_channel=RankSettings.RankChannel._desc, ) - @appcmds.default_permissions(administrator=True) @high_management_ward async def configure_ranks_cmd(self, ctx: LionContext, rank_type: Optional[Transformed[RankTypeChoice, AppCommandOptionType.string]] = None, diff --git a/src/modules/ranks/settings.py b/src/modules/ranks/settings.py index b98960b3..ff72c4d3 100644 --- a/src/modules/ranks/settings.py +++ b/src/modules/ranks/settings.py @@ -4,6 +4,7 @@ from settings.setting_types import BoolSetting, ChannelSetting, EnumSetting from core.data import RankType, CoreData from babel.translator import ctx_translator +from wards import high_management_iward from . import babel @@ -40,7 +41,8 @@ class RankSettings(SettingGroup): setting_id = 'rank_type' _event = 'guildset_rank_type' - _set_cmd = 'configure ranks' + _set_cmd = 'admin config ranks' + _write_ward = high_management_iward _display_name = _p('guildset:rank_type', "rank_type") _desc = _p( @@ -98,7 +100,8 @@ class RankSettings(SettingGroup): If DMRanks is set, this will only be used when the target user has disabled DM notifications. """ setting_id = 'rank_channel' - _set_cmd = 'configure ranks' + _set_cmd = 'admin config ranks' + _write_ward = high_management_iward _display_name = _p('guildset:rank_channel', "rank_channel") _desc = _p( @@ -148,7 +151,8 @@ class RankSettings(SettingGroup): Whether to DM rank notifications. """ setting_id = 'dm_ranks' - _set_cmd = 'configure ranks' + _set_cmd = 'admin config ranks' + _write_ward = high_management_iward _display_name = _p('guildset:dm_ranks', "dm_ranks") _desc = _p( diff --git a/src/modules/ranks/ui/config.py b/src/modules/ranks/ui/config.py index 1dbf1aa3..d84ffd52 100644 --- a/src/modules/ranks/ui/config.py +++ b/src/modules/ranks/ui/config.py @@ -69,6 +69,7 @@ class RankConfigUI(ConfigUI): async def type_menu(self, selection: discord.Interaction, selected: Select): await selection.response.defer(thinking=True) setting = self.instances[0] + await setting.interaction_check(setting.parent_id, selection) value = selected.values[0] data = RankType((value,)) setting.data = data @@ -117,6 +118,7 @@ class RankConfigUI(ConfigUI): async def channel_menu(self, selection: discord.Interaction, selected: ChannelSelect): await selection.response.defer() setting = self.instances[2] + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() @@ -168,7 +170,7 @@ class RankConfigUI(ConfigUI): class RankDashboard(DashboardSection): section_name = _p( 'dash:rank|title', - "Rank Configuration ({commands[configure ranks]})", + "Rank Configuration ({commands[admin config ranks]})", ) _option_name = _p( "dash:rank|dropdown|placeholder", diff --git a/src/modules/ranks/ui/overview.py b/src/modules/ranks/ui/overview.py index 9f87e78a..f9cfbcd6 100644 --- a/src/modules/ranks/ui/overview.py +++ b/src/modules/ranks/ui/overview.py @@ -430,7 +430,7 @@ class RankOverviewUI(MessageUI): "Ranks are determined by *all-time* statistics.\n" "To reward ranks from a later time (e.g. to have monthly/quarterly/yearly ranks) " "set the `season_start` with {stats_cmd}" - )).format(stats_cmd=self.bot.core.mention_cmd('configure statistics')) + )).format(stats_cmd=self.bot.core.mention_cmd('admin config statistics')) if self.rank_type is RankType.VOICE: addendum = t(_p( 'ui:rank_overview|embed|field:note|value|voice_addendum', diff --git a/src/modules/rooms/cog.py b/src/modules/rooms/cog.py index 3490848a..8b7e52da 100644 --- a/src/modules/rooms/cog.py +++ b/src/modules/rooms/cog.py @@ -16,7 +16,7 @@ from utils.ui import Confirm from constants import MAX_COINS from core.data import CoreData -from wards import low_management_ward +from wards import high_management_ward from . import babel, logger from .data import RoomData @@ -47,7 +47,7 @@ class RoomCog(LionCog): self.bot.core.guild_config.register_model_setting(setting) configcog = self.bot.get_cog('ConfigCog') - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.admin_config_group) if self.bot.is_ready(): await self.initialise() @@ -414,7 +414,7 @@ class RoomCog(LionCog): t(_p( 'cmd:room_rent|error:not_setup', "The private room system has not been set up! " - "A private room category needs to be set first with `/configure rooms`." + "A private room category needs to be set first with `/admin config rooms`." )) ), ephemeral=True ) @@ -987,8 +987,7 @@ class RoomCog(LionCog): @appcmds.describe( **{setting.setting_id: setting._desc for setting in RoomSettings.model_settings} ) - @appcmds.default_permissions(manage_guild=True) - @low_management_ward + @high_management_ward async def configure_rooms_cmd(self, ctx: LionContext, rooms_category: Optional[discord.CategoryChannel] = None, rooms_price: Optional[Range[int, 0, MAX_COINS]] = None, diff --git a/src/modules/rooms/settings.py b/src/modules/rooms/settings.py index e8e38144..0d8d9cce 100644 --- a/src/modules/rooms/settings.py +++ b/src/modules/rooms/settings.py @@ -5,6 +5,7 @@ from settings.setting_types import ChannelSetting, IntegerSetting, BoolSetting from meta import conf from core.data import CoreData from babel.translator import ctx_translator +from wards import low_management_iward, high_management_iward from . import babel @@ -15,7 +16,8 @@ class RoomSettings(SettingGroup): class Category(ModelData, ChannelSetting): setting_id = 'rooms_category' _event = 'guildset_rooms_category' - _set_cmd = 'configure rooms' + _set_cmd = 'admin config rooms' + _write_ward = high_management_iward _display_name = _p( 'guildset:room_category', "rooms_category" @@ -70,7 +72,8 @@ class RoomSettings(SettingGroup): class Rent(ModelData, IntegerSetting): setting_id = 'rooms_price' _event = 'guildset_rooms_price' - _set_cmd = 'configure rooms' + _set_cmd = 'admin config rooms' + _write_ward = low_management_iward _display_name = _p( 'guildset:rooms_price', "room_rent" @@ -107,7 +110,8 @@ class RoomSettings(SettingGroup): class MemberLimit(ModelData, IntegerSetting): setting_id = 'rooms_slots' _event = 'guildset_rooms_slots' - _set_cmd = 'configure rooms' + _set_cmd = 'admin config rooms' + _write_ward = low_management_iward _display_name = _p('guildset:rooms_slots', "room_member_cap") _desc = _p( @@ -141,7 +145,8 @@ class RoomSettings(SettingGroup): class Visible(ModelData, BoolSetting): setting_id = 'rooms_visible' _event = 'guildset_rooms_visible' - _set_cmd = 'configure rooms' + _set_cmd = 'admin config rooms' + _write_ward = high_management_iward _display_name = _p('guildset:rooms_visible', "room_visibility") _desc = _p( diff --git a/src/modules/rooms/settingui.py b/src/modules/rooms/settingui.py index becfb51e..e51d2c7c 100644 --- a/src/modules/rooms/settingui.py +++ b/src/modules/rooms/settingui.py @@ -29,6 +29,7 @@ class RoomSettingUI(ConfigUI): async def category_menu(self, selection: discord.Interaction, selected: ChannelSelect): await selection.response.defer() setting = self.instances[0] + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() @@ -42,6 +43,7 @@ class RoomSettingUI(ConfigUI): async def visible_button(self, press: discord.Interaction, pressed: Button): await press.response.defer() setting = next(inst for inst in self.instances if inst.setting_id == RoomSettings.Visible.setting_id) + await setting.interaction_check(setting.parent_id, press) setting.value = not setting.value await setting.write() @@ -95,7 +97,7 @@ class RoomSettingUI(ConfigUI): class RoomDashboard(DashboardSection): section_name = _p( 'dash:rooms|title', - "Private Room Configuration ({commands[configure rooms]})" + "Private Room Configuration ({commands[admin config rooms]})" ) _option_name = _p( "dash:economy|dropdown|placeholder", diff --git a/src/modules/schedule/cog.py b/src/modules/schedule/cog.py index 1208f562..a974bb1a 100644 --- a/src/modules/schedule/cog.py +++ b/src/modules/schedule/cog.py @@ -17,7 +17,7 @@ from meta.monitor import ComponentMonitor, ComponentStatus, StatusLevel from utils.lib import utc_now, error_embed from utils.ui import Confirm from utils.data import MULTIVALUE_IN, MEMBERS -from wards import low_management_ward +from wards import high_management_ward from core.data import CoreData from data import NULL, ORDER from modules.economy.data import TransactionType @@ -118,7 +118,7 @@ class ScheduleCog(LionCog): await self.settings.SessionChannels.setup(self.bot) configcog = self.bot.get_cog('ConfigCog') - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.admin_config_group) if self.bot.is_ready(): await self.initialise() @@ -1090,7 +1090,7 @@ class ScheduleCog(LionCog): @appcmds.describe( **{param: option._desc for param, option in config_params.items()} ) - @low_management_ward + @high_management_ward async def configure_schedule_command(self, ctx: LionContext, session_lobby: Optional[discord.TextChannel | discord.VoiceChannel] = None, session_room: Optional[discord.VoiceChannel] = None, diff --git a/src/modules/schedule/settings.py b/src/modules/schedule/settings.py index 921ec8a0..ac5f5e95 100644 --- a/src/modules/schedule/settings.py +++ b/src/modules/schedule/settings.py @@ -11,6 +11,7 @@ from meta import conf from meta.errors import UserInputError from meta.sharding import THIS_SHARD from meta.logger import log_wrap +from wards import low_management_iward, high_management_iward from babel.translator import ctx_translator @@ -63,7 +64,8 @@ class ScheduleSettings(SettingGroup): class SessionLobby(ModelData, ChannelSetting): setting_id = 'session_lobby' _event = 'guildset_session_lobby' - _set_cmd = 'configure schedule' + _set_cmd = 'admin config schedule' + _write_ward = high_management_iward _display_name = _p('guildset:session_lobby', "session_lobby") _desc = _p( @@ -119,7 +121,8 @@ class ScheduleSettings(SettingGroup): @ScheduleConfig.register_model_setting class SessionRoom(ModelData, ChannelSetting): setting_id = 'session_room' - _set_cmd = 'configure schedule' + _set_cmd = 'admin config schedule' + _write_ward = high_management_iward _display_name = _p('guildset:session_room', "session_room") _desc = _p( @@ -163,6 +166,7 @@ class ScheduleSettings(SettingGroup): class SessionChannels(ListData, ChannelListSetting): setting_id = 'session_channels' + _write_ward = high_management_iward _display_name = _p('guildset:session_channels', "session_channels") _desc = _p( @@ -238,7 +242,8 @@ class ScheduleSettings(SettingGroup): @ScheduleConfig.register_model_setting class ScheduleCost(ModelData, CoinSetting): setting_id = 'schedule_cost' - _set_cmd = 'configure schedule' + _set_cmd = 'admin config schedule' + _write_ward = low_management_iward _display_name = _p('guildset:schedule_cost', "schedule_cost") _desc = _p( @@ -283,7 +288,8 @@ class ScheduleSettings(SettingGroup): @ScheduleConfig.register_model_setting class AttendanceReward(ModelData, CoinSetting): setting_id = 'attendance_reward' - _set_cmd = 'configure schedule' + _set_cmd = 'admin config schedule' + _write_ward = low_management_iward _display_name = _p('guildset:attendance_reward', "attendance_reward") _desc = _p( @@ -327,7 +333,8 @@ class ScheduleSettings(SettingGroup): @ScheduleConfig.register_model_setting class AttendanceBonus(ModelData, CoinSetting): setting_id = 'attendance_bonus' - _set_cmd = 'configure schedule' + _set_cmd = 'admin config schedule' + _write_ward = low_management_iward _display_name = _p('guildset:attendance_bonus', "group_attendance_bonus") _desc = _p( @@ -370,7 +377,8 @@ class ScheduleSettings(SettingGroup): @ScheduleConfig.register_model_setting class MinAttendance(ModelData, IntegerSetting): setting_id = 'min_attendance' - _set_cmd = 'configure schedule' + _set_cmd = 'admin config schedule' + _write_ward = low_management_iward _display_name = _p('guildset:min_attendance', "min_attendance") _desc = _p( @@ -437,8 +445,9 @@ class ScheduleSettings(SettingGroup): @ScheduleConfig.register_model_setting class BlacklistRole(ModelData, RoleSetting): setting_id = 'schedule_blacklist_role' - _set_cmd = 'configure schedule' + _set_cmd = 'admin config schedule' _event = 'guildset_schedule_blacklist_role' + _write_ward = high_management_iward _display_name = _p('guildset:schedule_blacklist_role', "schedule_blacklist_role") _desc = _p( @@ -495,7 +504,8 @@ class ScheduleSettings(SettingGroup): @ScheduleConfig.register_model_setting class BlacklistAfter(ModelData, IntegerSetting): setting_id = 'schedule_blacklist_after' - _set_cmd = 'configure schedule' + _set_cmd = 'admin config schedule' + _write_ward = low_management_iward _display_name = _p('guildset:schedule_blacklist_after', "schedule_blacklist_after") _desc = _p( diff --git a/src/modules/schedule/ui/settingui.py b/src/modules/schedule/ui/settingui.py index 4ed4d4bb..4d1a185d 100644 --- a/src/modules/schedule/ui/settingui.py +++ b/src/modules/schedule/ui/settingui.py @@ -78,6 +78,7 @@ class ScheduleSettingUI(ConfigUI): # TODO: Setting value checks await selection.response.defer() setting = self.get_instance(ScheduleSettings.SessionLobby) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() @@ -95,6 +96,7 @@ class ScheduleSettingUI(ConfigUI): async def room_menu(self, selection: discord.Interaction, selected: ChannelSelect): await selection.response.defer() setting = self.get_instance(ScheduleSettings.SessionRoom) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None await setting.write() @@ -113,6 +115,7 @@ class ScheduleSettingUI(ConfigUI): # TODO: Consider XORing input await selection.response.defer() setting = self.get_instance(ScheduleSettings.SessionChannels) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values await setting.write() @@ -158,6 +161,7 @@ class ScheduleSettingUI(ConfigUI): async def blacklist_role_menu(self, selection: discord.Interaction, selected: RoleSelect): await selection.response.defer() setting = self.get_instance(ScheduleSettings.BlacklistRole) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None # TODO: Warning for insufficient permissions? await setting.write() @@ -227,7 +231,7 @@ class ScheduleSettingUI(ConfigUI): class ScheduleDashboard(DashboardSection): section_name = _p( 'dash:schedule|title', - "Scheduled Session Configuration ({commands[configure schedule]})" + "Scheduled Session Configuration ({commands[admin config schedule]})" ) _option_name = _p( "dash:schedule|dropdown|placeholder", @@ -248,7 +252,7 @@ class ScheduleDashboard(DashboardSection): page.add_field( name=t(_p( 'dash:schedule|section:schedule_channels|name', - "Scheduled Session Channels ({commands[configure schedule]})", + "Scheduled Session Channels ({commands[admin config schedule]})", )).format(commands=self.bot.core.mention_cache), value=table, inline=False @@ -258,7 +262,7 @@ class ScheduleDashboard(DashboardSection): page.add_field( name=t(_p( 'dash:schedule|section:schedule_rewards|name', - "Scheduled Session Rewards ({commands[configure schedule]})", + "Scheduled Session Rewards ({commands[admin config schedule]})", )).format(commands=self.bot.core.mention_cache), value=table, inline=False @@ -268,7 +272,7 @@ class ScheduleDashboard(DashboardSection): page.add_field( name=t(_p( 'dash:schedule|section:schedule_blacklist|name', - "Scheduled Session Blacklist ({commands[configure schedule]})", + "Scheduled Session Blacklist ({commands[admin config schedule]})", )).format(commands=self.bot.core.mention_cache), value=table, inline=False diff --git a/src/modules/statistics/cog.py b/src/modules/statistics/cog.py index fa3d923c..07c6d469 100644 --- a/src/modules/statistics/cog.py +++ b/src/modules/statistics/cog.py @@ -12,7 +12,7 @@ from core.lion_guild import VoiceMode from utils.lib import error_embed from utils.ui import LeoUI, AButton, utc_now from gui.base import CardMode -from wards import low_management_ward +from wards import high_management_ward from . import babel from .data import StatsData @@ -41,7 +41,7 @@ class StatsCog(LionCog): self.bot.core.guild_config.register_setting(self.settings.UnrankedRoles) configcog = self.bot.get_cog('ConfigCog') - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.admin_config_group) @cmds.hybrid_command( name=_p('cmd:me', "me"), @@ -204,8 +204,7 @@ class StatsCog(LionCog): "Time from which to start counting activity for rank badges and season leaderboards. (YYYY-MM-DD)" ) ) - @appcmds.default_permissions(manage_guild=True) - @low_management_ward + @high_management_ward async def configure_statistics_cmd(self, ctx: LionContext, season_start: Optional[str] = None): t = self.bot.translator.t diff --git a/src/modules/statistics/settings.py b/src/modules/statistics/settings.py index 375c577f..37a4ac83 100644 --- a/src/modules/statistics/settings.py +++ b/src/modules/statistics/settings.py @@ -21,6 +21,7 @@ from utils.lib import MessageArgs from core.data import CoreData from core.lion_guild import VoiceMode from babel.translator import ctx_translator +from wards import low_management_iward, high_management_iward from . import babel from .data import StatsData, StatisticType @@ -83,7 +84,8 @@ class StatisticsSettings(SettingGroup): Time is assumed to be in set guild timezone (although supports +00 syntax) """ setting_id = 'season_start' - _set_cmd = 'configure statistics' + _set_cmd = 'admin config statistics' + _write_ward = high_management_iward _display_name = _p('guildset:season_start', "season_start") _desc = _p( @@ -155,6 +157,7 @@ class StatisticsSettings(SettingGroup): List of roles not displayed on the leaderboard """ setting_id = 'unranked_roles' + _write_ward = high_management_iward _display_name = _p('guildset:unranked_roles', "unranked_roles") _desc = _p( @@ -211,6 +214,7 @@ class StatisticsSettings(SettingGroup): Default is determined by current guild mode """ setting_id = 'visible_stats' + _write_ward = high_management_iward _setting = StatTypeSetting @@ -263,6 +267,7 @@ class StatisticsSettings(SettingGroup): Which of the three stats to display by default """ setting_id = 'default_stat' + _write_ward = high_management_iward _display_name = _p('guildset:default_stat', "default_stat") _desc = _p( @@ -294,6 +299,7 @@ class StatisticsConfigUI(ConfigUI): """ await selection.response.defer(thinking=True) setting = self.instances[1] + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values await setting.write() # Don't need to refresh due to instance hooks @@ -314,6 +320,7 @@ class StatisticsConfigUI(ConfigUI): """ await selection.response.defer(thinking=True) setting = self.instances[2] + await setting.interaction_check(setting.parent_id, selection) data = [StatisticType((value,)) for value in selected.values] setting.data = data await setting.write() @@ -405,7 +412,7 @@ class StatisticsConfigUI(ConfigUI): class StatisticsDashboard(DashboardSection): section_name = _p( 'dash:stats|title', - "Activity Statistics Configuration ({commands[configure statistics]})" + "Activity Statistics Configuration ({commands[admin config statistics]})" ) _option_name = _p( "dash:stats|dropdown|placeholder", diff --git a/src/modules/tasklist/cog.py b/src/modules/tasklist/cog.py index fc63ca90..7aa8bf5f 100644 --- a/src/modules/tasklist/cog.py +++ b/src/modules/tasklist/cog.py @@ -139,7 +139,7 @@ class TasklistCog(LionCog): self.bot.add_view(TasklistCaller(self.bot)) configcog = self.bot.get_cog('ConfigCog') - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.config_group) @LionCog.listener('on_tasks_completed') @log_wrap(action="reward tasks completed") @@ -984,7 +984,6 @@ class TasklistCog(LionCog): reward=TasklistSettings.task_reward._desc, reward_limit=TasklistSettings.task_reward_limit._desc ) - @appcmds.default_permissions(manage_guild=True) @low_management_ward async def configure_tasklist_cmd(self, ctx: LionContext, reward: Optional[int] = None, diff --git a/src/modules/tasklist/settings.py b/src/modules/tasklist/settings.py index 703416e0..aee300c7 100644 --- a/src/modules/tasklist/settings.py +++ b/src/modules/tasklist/settings.py @@ -13,6 +13,7 @@ from utils.lib import tabulate from utils.ui import LeoUI, FastModal, error_handler_for, ModalRetryUI, DashboardSection from core.data import CoreData from babel.translator import ctx_translator +from wards import low_management_iward, high_management_iward from . import babel from .data import TasklistData @@ -28,7 +29,8 @@ class TasklistSettings(SettingGroup): Exposed via `/configure tasklist`, and the standard configuration interface. """ setting_id = 'task_reward' - _set_cmd = 'configure tasklist' + _set_cmd = 'config tasklist' + _write_ward = low_management_iward _display_name = _p('guildset:task_reward', "task_reward") _desc = _p( @@ -68,7 +70,8 @@ class TasklistSettings(SettingGroup): class task_reward_limit(ModelData, IntegerSetting): setting_id = 'task_reward_limit' - _set_cmd = 'configure tasklist' + _set_cmd = 'config tasklist' + _write_ward = low_management_iward _display_name = _p('guildset:task_reward_limit', "task_reward_limit") _desc = _p( @@ -109,6 +112,7 @@ class TasklistSettings(SettingGroup): class tasklist_channels(ListData, ChannelListSetting): setting_id = 'tasklist_channels' + _write_ward = low_management_iward _display_name = _p('guildset:tasklist_channels', "tasklist_channels") _desc = _p( @@ -317,7 +321,7 @@ class TasklistConfigUI(LeoUI): class TasklistDashboard(DashboardSection): - section_name = _p('dash:tasklist|name', "Tasklist Configuration ({commands[configure tasklist]})") + section_name = _p('dash:tasklist|name', "Tasklist Configuration ({commands[config tasklist]})") _option_name = _p( "dash:tasklist|dropdown|placeholder", "Tasklist Options Panel" diff --git a/src/modules/video_channels/cog.py b/src/modules/video_channels/cog.py index 7e6338ac..ec84bce8 100644 --- a/src/modules/video_channels/cog.py +++ b/src/modules/video_channels/cog.py @@ -57,7 +57,7 @@ class VideoCog(LionCog): "Could not load ConfigCog. VideoCog configuration will not crossload." ) else: - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.admin_config_group) if self.bot.is_ready(): await self.initialise() @@ -522,7 +522,7 @@ class VideoCog(LionCog): video_blacklist_durations=VideoSettings.VideoBlacklistDurations._desc, video_grace_period=VideoSettings.VideoGracePeriod._desc, ) - @low_management_ward + @high_management_ward async def configure_video(self, ctx: LionContext, video_blacklist: Optional[discord.Role] = None, video_blacklist_durations: Optional[str] = None, @@ -572,4 +572,3 @@ class VideoCog(LionCog): ui = VideoSettingUI(self.bot, ctx.guild.id, ctx.channel.id) await ui.run(ctx.interaction) await ui.wait() - diff --git a/src/modules/video_channels/settings.py b/src/modules/video_channels/settings.py index 82fe58c4..2d7c4e35 100644 --- a/src/modules/video_channels/settings.py +++ b/src/modules/video_channels/settings.py @@ -14,6 +14,7 @@ from meta.sharding import THIS_SHARD from meta.logger import log_wrap from core.data import CoreData from babel.translator import ctx_translator +from wards import low_management_iward, high_management_iward from . import babel, logger from .data import VideoData @@ -25,6 +26,7 @@ class VideoSettings(SettingGroup): class VideoChannels(ListData, ChannelListSetting): setting_id = "video_channels" _event = 'guildset_video_channels' + _write_ward = high_management_iward _display_name = _p('guildset:video_channels', "video_channels") _desc = _p( @@ -101,6 +103,7 @@ class VideoSettings(SettingGroup): class VideoBlacklist(ModelData, RoleSetting): setting_id = "video_blacklist" _event = 'guildset_video_blacklist' + _write_ward = high_management_iward _display_name = _p('guildset:video_blacklist', "video_blacklist") _desc = _p( @@ -158,6 +161,7 @@ class VideoSettings(SettingGroup): class VideoBlacklistDurations(ListData, ListSetting, InteractiveSetting): setting_id = 'video_durations' _setting = DurationSetting + _write_ward = high_management_iward _display_name = _p('guildset:video_durations', "video_blacklist_durations") _desc = _p( @@ -217,6 +221,7 @@ class VideoSettings(SettingGroup): class VideoGracePeriod(ModelData, DurationSetting): setting_id = "video_grace_period" _event = 'guildset_video_grace_period' + _write_ward = high_management_iward _display_name = _p('guildset:video_grace_period', "video_grace_period") _desc = _p( @@ -252,6 +257,7 @@ class VideoSettings(SettingGroup): class VideoExempt(ListData, RoleListSetting): setting_id = "video_exempt" _event = 'guildset_video_exempt' + _write_ward = high_management_iward _display_name = _p('guildset:video_exempt', "video_exempt") _desc = _p( diff --git a/src/modules/video_channels/settingui.py b/src/modules/video_channels/settingui.py index 399fe72f..608977e8 100644 --- a/src/modules/video_channels/settingui.py +++ b/src/modules/video_channels/settingui.py @@ -45,6 +45,7 @@ class VideoSettingUI(ConfigUI): await selection.response.defer(thinking=True, ephemeral=True) setting = self.get_instance(VideoSettings.VideoChannels) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values await setting.write() await selection.delete_original_response() @@ -70,6 +71,7 @@ class VideoSettingUI(ConfigUI): await selection.response.defer(thinking=True, ephemeral=True) setting = self.get_instance(VideoSettings.VideoExempt) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values await setting.write() await selection.delete_original_response() @@ -95,6 +97,7 @@ class VideoSettingUI(ConfigUI): await selection.response.defer(thinking=True, ephemeral=True) setting = self.get_instance(VideoSettings.VideoBlacklist) + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values[0] if selected.values else None if setting.value: await equippable_role(self.bot, setting.value, selection.user) @@ -153,7 +156,7 @@ class VideoSettingUI(ConfigUI): class VideoDashboard(DashboardSection): section_name = _p( "dash:video|title", - "Video Channel Settings ({commands[configure video_channels]})" + "Video Channel Settings ({commands[admin config video_channels]})" ) _option_name = _p( "dash:video|option|name", diff --git a/src/settings/ui.py b/src/settings/ui.py index 5fff6e32..b2a263ea 100644 --- a/src/settings/ui.py +++ b/src/settings/ui.py @@ -7,6 +7,7 @@ from discord import ui from discord.ui.button import ButtonStyle, Button, button from discord.ui.modal import Modal from discord.ui.text_input import TextInput +from meta.errors import UserInputError from utils.lib import tabulate, recover_context from utils.ui import FastModal @@ -192,6 +193,9 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]): # Event handlers should be of the form Callable[ParentID, SettingData] _event: Optional[str] = None + # Interaction ward that should be validated via interaction_check + _write_ward: Optional[Callable[[discord.Interaction], Coroutine[Any, Any, bool]]] = None + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -488,6 +492,16 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]): """ pass + @classmethod + async def interaction_check(cls, parent_id, interaction: discord.Interaction, **kwargs): + if cls._write_ward is not None and not await cls._write_ward(interaction): + # TODO: Combine the check system so we can do customised errors here + t = ctx_translator.get().t + raise UserInputError(t(_p( + 'setting|interaction_check|error', + "You do not have sufficient permissions to do this!" + ))) + """ command callback for set command? diff --git a/src/tracking/text/cog.py b/src/tracking/text/cog.py index 1d48a2ee..8da98811 100644 --- a/src/tracking/text/cog.py +++ b/src/tracking/text/cog.py @@ -16,7 +16,7 @@ from meta.app import appname from meta.monitor import ComponentMonitor, ComponentStatus, StatusLevel from utils.lib import utc_now, error_embed -from wards import low_management_ward, sys_admin_ward +from wards import low_management_ward, sys_admin_ward, low_management_iward from . import babel, logger from .data import TextTrackerData @@ -116,7 +116,7 @@ class TextTrackerCog(LionCog): "Attempting to load the TextTrackerCog before ConfigCog! Failed to crossload configuration group." ) else: - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.config_group) if self.bot.is_ready(): await self.initialise() @@ -318,7 +318,6 @@ class TextTrackerCog(LionCog): xp_per_period=TextTrackerSettings.XPPerPeriod._desc, word_xp=TextTrackerSettings.WordXP._desc, ) - @appcmds.default_permissions(manage_guild=True) @low_management_ward async def configure_text_tracking_cmd(self, ctx: LionContext, xp_per_period: Optional[appcmds.Range[int, 0, 2**15]] = None, diff --git a/src/tracking/text/settings.py b/src/tracking/text/settings.py index f25b2773..3f7ee3a9 100644 --- a/src/tracking/text/settings.py +++ b/src/tracking/text/settings.py @@ -11,6 +11,7 @@ from meta.sharding import THIS_SHARD from meta.logger import log_wrap from core.data import CoreData from babel.translator import ctx_translator +from wards import low_management_iward from . import babel, logger from .data import TextTrackerData @@ -28,7 +29,8 @@ class TextTrackerSettings(SettingGroup): """ class XPPerPeriod(ModelData, IntegerSetting): setting_id = 'xp_per_period' - _set_cmd = 'configure message_exp' + _set_cmd = 'config message_exp' + _write_ward = low_management_iward _display_name = _p('guildset:xp_per_period', "xp_per_5min") _desc = _p( @@ -60,7 +62,8 @@ class TextTrackerSettings(SettingGroup): class WordXP(ModelData, IntegerSetting): setting_id = 'word_xp' - _set_cmd = 'configure message_exp' + _set_cmd = 'config message_exp' + _write_ward = low_management_iward _display_name = _p('guildset:word_xp', "xp_per_100words") _desc = _p( @@ -91,6 +94,7 @@ class TextTrackerSettings(SettingGroup): class UntrackedTextChannels(ListData, ChannelListSetting): setting_id = 'untracked_text_channels' + _write_ward = low_management_iward _display_name = _p('guildset:untracked_text_channels', "untracked_text_channels") _desc = _p( diff --git a/src/tracking/text/ui.py b/src/tracking/text/ui.py index fa6bfa52..8a00a27e 100644 --- a/src/tracking/text/ui.py +++ b/src/tracking/text/ui.py @@ -35,6 +35,7 @@ class TextTrackerConfigUI(ConfigUI): async def untracked_channels_menu(self, selection: discord.Interaction, selected): await selection.response.defer() setting = self.instances[2] + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values await setting.write() @@ -86,7 +87,7 @@ class TextTrackerConfigUI(ConfigUI): class TextTrackerDashboard(DashboardSection): section_name = _p( 'dash:text_tracking|title', - "Message XP configuration ({commands[configure message_exp]})", + "Message XP configuration ({commands[config message_exp]})", ) _option_name = _p( "dash:text_tracking|dropdown|placeholder", diff --git a/src/tracking/voice/cog.py b/src/tracking/voice/cog.py index 2b1645d8..919056af 100644 --- a/src/tracking/voice/cog.py +++ b/src/tracking/voice/cog.py @@ -133,7 +133,7 @@ class VoiceTrackerCog(LionCog): "Attempting to load VoiceTrackerCog before ConfigCog! Cannot crossload configuration group." ) else: - self.crossload_group(self.configure_group, configcog.configure_group) + self.crossload_group(self.configure_group, configcog.config_group) if self.bot.is_ready(): await self.initialise() @@ -867,7 +867,6 @@ class VoiceTrackerCog(LionCog): hourly_live_bonus=VoiceTrackerSettings.HourlyLiveBonus._desc, daily_voice_cap=VoiceTrackerSettings.DailyVoiceCap._desc, ) - @appcmds.default_permissions(manage_guild=True) @low_management_ward async def configure_voice_tracking_cmd(self, ctx: LionContext, hourly_reward: Optional[int] = None, # TODO: Change these to Ranges diff --git a/src/tracking/voice/settings.py b/src/tracking/voice/settings.py index 4d74a3da..bfa90c67 100644 --- a/src/tracking/voice/settings.py +++ b/src/tracking/voice/settings.py @@ -14,6 +14,7 @@ from meta.sharding import THIS_SHARD from meta.logger import log_wrap from utils.lib import MessageArgs from utils.ui import LeoUI, ConfigUI, DashboardSection +from wards import low_management_iward from core.data import CoreData from core.lion_guild import VoiceMode @@ -35,7 +36,8 @@ class VoiceTrackerSettings(SettingGroup): class UntrackedChannels(ListData, ChannelListSetting): setting_id = 'untracked_channels' _event = 'guildset_untracked_channels' - _set_cmd = 'configure voice_rewards' + _set_cmd = 'config voice_rewards' + _write_ward = low_management_iward _display_name = _p('guildset:untracked_channels', "untracked_channels") _desc = _p( @@ -112,7 +114,8 @@ class VoiceTrackerSettings(SettingGroup): class HourlyReward(ModelData, IntegerSetting): setting_id = 'hourly_reward' _event = 'on_guildset_hourly_reward' - _set_cmd = 'configure voice_rewards' + _set_cmd = 'config voice_rewards' + _write_ward = low_management_iward _display_name = _p('guildset:hourly_reward', "hourly_reward") _desc = _p( @@ -192,7 +195,8 @@ class VoiceTrackerSettings(SettingGroup): """ setting_id = 'hourly_live_bonus' _event = 'on_guildset_hourly_live_bonus' - _set_cmd = 'configure voice_rewards' + _set_cmd = 'config voice_rewards' + _write_ward = low_management_iward _display_name = _p('guildset:hourly_live_bonus', "hourly_live_bonus") _desc = _p( @@ -243,7 +247,8 @@ class VoiceTrackerSettings(SettingGroup): class DailyVoiceCap(ModelData, DurationSetting): setting_id = 'daily_voice_cap' _event = 'on_guildset_daily_voice_cap' - _set_cmd = 'configure voice_rewards' + _set_cmd = 'config voice_rewards' + _write_ward = low_management_iward _display_name = _p('guildset:daily_voice_cap', "daily_voice_cap") _desc = _p( @@ -465,6 +470,7 @@ class VoiceTrackerConfigUI(ConfigUI): async def untracked_channels_menu(self, selection: discord.Interaction, selected): await selection.response.defer() setting = self.instances[3] + await setting.interaction_check(setting.parent_id, selection) setting.value = selected.values await setting.write() @@ -528,7 +534,7 @@ class VoiceTrackerConfigUI(ConfigUI): class VoiceTrackerDashboard(DashboardSection): section_name = _p( 'dash:voice_tracker|title', - "Voice Tracker Configuration ({commands[configure voice_rewards]})" + "Voice Tracker Configuration ({commands[config voice_rewards]})" ) _option_name = _p( "dash:voice_tracking|dropdown|placeholder", diff --git a/src/utils/ui/config.py b/src/utils/ui/config.py index e1567e1f..93482ca8 100644 --- a/src/utils/ui/config.py +++ b/src/utils/ui/config.py @@ -126,6 +126,7 @@ class ConfigUI(LeoUI): new_data = None else: # If this raises a UserInputError, it will be caught and the modal retried + await setting.interaction_check(setting.parent_id, interaction) new_data = await setting._parse_string(setting.parent_id, input_value) setting.data = new_data modified.append(setting)