fix(config): Fix general settings UI.

This commit is contained in:
2023-10-11 07:28:53 +03:00
parent fe81945391
commit 66e7c2f2e4
9 changed files with 178 additions and 152 deletions

View File

@@ -80,7 +80,7 @@ class LionGuild(Timezoned):
return GuildMode.StudyGuild
@property
def timezone(self) -> pytz.timezone:
def timezone(self) -> str:
return self.config.timezone.value
@property
@@ -94,11 +94,3 @@ class LionGuild(Timezoned):
if self.data.name != guild.name:
await self.data.update(name=guild.name)
async def _event_log(self, ...):
...
def event_log(self, **kwargs):
asyncio.create_task(self._event_log(**kwargs), name='event-log')
def error_log(self, ...):
...

View File

@@ -1,3 +1,5 @@
from typing import Optional
import discord
from discord import app_commands as appcmds
from discord.ext import commands as cmds
@@ -22,7 +24,7 @@ class GuildConfigCog(LionCog):
async def cog_load(self):
self.bot.core.guild_config.register_model_setting(GeneralSettings.Timezone)
self.bot.core.guild_config.register_model_setting(GeneralSettings.Eventlog)
self.bot.core.guild_config.register_model_setting(GeneralSettings.EventLog)
configcog = self.bot.get_cog('ConfigCog')
if configcog is None:
@@ -36,43 +38,13 @@ class GuildConfigCog(LionCog):
@appcmds.guild_only
@appcmds.default_permissions(manage_guild=True)
async def dashboard_cmd(self, ctx: LionContext):
if not ctx.guild or not ctx.interaction:
return
ui = GuildDashboard(self.bot, ctx.guild, ctx.author.id, ctx.channel.id)
await ui.run(ctx.interaction)
await ui.wait()
@cmds.hybrid_group("configure", with_app_command=False)
async def configure_group(self, ctx: LionContext):
# Placeholder configure group command.
...
@configure_group.command(
name=_p('cmd:configure_general', "general"),
description=_p('cmd:configure_general|desc', "General configuration panel")
)
@appcmds.rename(
timezone=GeneralSettings.Timezone._display_name,
event_log=GeneralSettings.EventLog._display_name,
)
@appcmds.describe(
timezone=GeneralSettings.Timezone._desc,
event_log=GeneralSettings.EventLog._display_name,
)
@appcmds.guild_only()
@appcmds.default_permissions(manage_guild=True)
@low_management_ward
async def cmd_configure_general(self, ctx: LionContext,
timezone: Optional[str] = None,
event_log: Optional[discord.TextChannel] = None,
):
t = self.bot.translator.t
# Typechecker guards because they don't understand the check ward
if not ctx.guild:
return
if not ctx.interaction:
return
await ctx.interaction.response.defer(thinking=True)
# ----- Configuration -----
@LionCog.placeholder_group
@cmds.hybrid_group("configure", with_app_command=False)
@@ -90,7 +62,7 @@ class GuildConfigCog(LionCog):
)
@appcmds.describe(
timezone=GeneralSettings.Timezone._desc,
event_log=GeneralSettings.EventLog._display_name,
event_log=GeneralSettings.EventLog._desc,
)
@appcmds.guild_only()
@appcmds.default_permissions(manage_guild=True)
@@ -107,4 +79,34 @@ class GuildConfigCog(LionCog):
if not ctx.interaction:
return
await ctx.interaction.response.defer(thinking=True)
# TODO
modified = []
if timezone is not None:
setting = self.settings.Timezone
instance = await setting.from_string(ctx.guild.id, timezone)
modified.append(instance)
if event_log is not None:
setting = self.settings.EventLog
instance = await setting.from_value(ctx.guild.id, event_log)
modified.append(instance)
if modified:
ack_lines = []
for instance in modified:
await instance.write()
ack_lines.append(instance.update_message)
tick = self.bot.config.emojis.tick
embed = discord.Embed(
colour=discord.Colour.brand_green(),
description='\n'.join(f"{tick} {line}" for line in ack_lines)
)
await ctx.reply(embed=embed)
if ctx.channel.id not in GeneralSettingUI._listening or not modified:
ui = GeneralSettingUI(self.bot, ctx.guild.id, ctx.channel.id)
await ui.run(ctx.interaction)
await ui.wait()

View File

@@ -48,88 +48,88 @@ class GeneralSettingsCog(LionCog):
# Placeholder configure group command.
...
@configure_group.command(
name=_p('cmd:configure_general', "general"),
description=_p('cmd:configure_general|desc', "General configuration panel")
)
@appcmds.rename(
timezone=GeneralSettings.Timezone._display_name,
event_log=GeneralSettings.EventLog._display_name,
)
@appcmds.describe(
timezone=GeneralSettings.Timezone._desc,
event_log=GeneralSettings.EventLog._display_name,
)
@appcmds.guild_only()
@appcmds.default_permissions(manage_guild=True)
@low_management_ward
async def cmd_configure_general(self, ctx: LionContext,
timezone: Optional[str] = None,
event_log: Optional[discord.TextChannel] = None,
):
t = self.bot.translator.t
@configure_group.command(
name=_p('cmd:configure_general', "general"),
description=_p('cmd:configure_general|desc', "General configuration panel")
)
@appcmds.rename(
timezone=GeneralSettings.Timezone._display_name,
event_log=GeneralSettings.EventLog._display_name,
)
@appcmds.describe(
timezone=GeneralSettings.Timezone._desc,
event_log=GeneralSettings.EventLog._display_name,
)
@appcmds.guild_only()
@appcmds.default_permissions(manage_guild=True)
@low_management_ward
async def cmd_configure_general(self, ctx: LionContext,
timezone: Optional[str] = None,
event_log: Optional[discord.TextChannel] = None,
):
t = self.bot.translator.t
# Typechecker guards because they don't understand the check ward
if not ctx.guild:
return
if not ctx.interaction:
return
await ctx.interaction.response.defer(thinking=True)
# Typechecker guards because they don't understand the check ward
if not ctx.guild:
return
if not ctx.interaction:
return
await ctx.interaction.response.defer(thinking=True)
updated = [] # Possibly empty list of setting instances which were updated, with new data stored
error_embed = None
updated = [] # Possibly empty list of setting instances which were updated, with new data stored
error_embed = None
if timezone is not None:
try:
timezone_setting = await self.settings.Timezone.from_string(ctx.guild.id, timezone)
updated.append(timezone_setting)
except UserInputError as err:
error_embed = discord.Embed(
colour=discord.Colour.brand_red(),
title=t(_p(
'cmd:configure_general|parse_failure:timezone',
"Could not set the timezone!"
)),
description=err.msg
)
if error_embed is not None:
# User requested configuration updated, but we couldn't parse input
await ctx.reply(embed=error_embed)
elif updated:
# Save requested configuration updates
results = [] # List of "success" update responses for each updated setting
for to_update in updated:
# TODO: Again need a better way of batch writing
# Especially since most of these are on one model...
await to_update.write()
results.append(to_update.update_message)
# Post aggregated success message
success_embed = discord.Embed(
colour=discord.Colour.brand_green(),
if timezone is not None:
try:
timezone_setting = await self.settings.Timezone.from_string(ctx.guild.id, timezone)
updated.append(timezone_setting)
except UserInputError as err:
error_embed = discord.Embed(
colour=discord.Colour.brand_red(),
title=t(_p(
'cmd:configure_general|success',
"Settings Updated!"
'cmd:configure_general|parse_failure:timezone',
"Could not set the timezone!"
)),
description='\n'.join(
f"{self.bot.config.emojis.tick} {line}" for line in results
description=err.msg
)
)
await ctx.reply(embed=success_embed)
# TODO: Trigger configuration panel update if listening UI.
else:
# Show general configuration panel UI
# TODO Interactive UI
embed = discord.Embed(
colour=discord.Colour.orange(),
if error_embed is not None:
# User requested configuration updated, but we couldn't parse input
await ctx.reply(embed=error_embed)
elif updated:
# Save requested configuration updates
results = [] # List of "success" update responses for each updated setting
for to_update in updated:
# TODO: Again need a better way of batch writing
# Especially since most of these are on one model...
await to_update.write()
results.append(to_update.update_message)
# Post aggregated success message
success_embed = discord.Embed(
colour=discord.Colour.brand_green(),
title=t(_p(
'cmd:configure_general|panel|title',
"General Configuration Panel"
))
'cmd:configure_general|success',
"Settings Updated!"
)),
description='\n'.join(
f"{self.bot.config.emojis.tick} {line}" for line in results
)
embed.add_field(
**ctx.lguild.config.timezone.embed_field
)
await ctx.reply(embed=embed)
)
await ctx.reply(embed=success_embed)
# TODO: Trigger configuration panel update if listening UI.
else:
# Show general configuration panel UI
# TODO Interactive UI
embed = discord.Embed(
colour=discord.Colour.orange(),
title=t(_p(
'cmd:configure_general|panel|title',
"General Configuration Panel"
))
)
embed.add_field(
**ctx.lguild.config.timezone.embed_field
)
await ctx.reply(embed=embed)
cmd_configure_general.autocomplete('timezone')(TimezoneSetting.parse_acmpl)

View File

@@ -1,7 +1,12 @@
from typing import Optional
import discord
from settings import ModelData
from settings.setting_types import TimezoneSetting, ChannelSetting
from settings.groups import SettingGroup
from meta.context import ctx_bot
from meta.errors import UserInputError
from core.data import CoreData
from babel.translator import ctx_translator
@@ -20,7 +25,8 @@ class GeneralSettings(SettingGroup):
and the timezone used to display guild-wide statistics.
"""
setting_id = 'timezone'
_event = 'guild_setting_update_timezone'
_event = 'guildset_timezone'
_set_cmd = 'configure general'
_display_name = _p('guildset:timezone', "timezone")
_desc = _p(
@@ -46,29 +52,24 @@ class GeneralSettings(SettingGroup):
"The guild timezone has been set to `{timezone}`."
)).format(timezone=self.data)
@property
def set_str(self):
bot = ctx_bot.get()
return bot.core.mention_cmd('configure general') if bot else None
class EventLog(ModelData, ChannelSetting):
"""
Guild event log channel.
"""
setting_id = 'eventlog'
_event = 'guildset_eventlog'
_set_cmd = 'configure general'
_display_name = _p('guildset:eventlog', "event_log")
_desc = _p(
'guildset:eventlog|desc',
"Channel to which to log server events, such as voice sessions and equipped roles."
"My audit log channel where I send server actions and events (e.g. rankgs and expiring roles)."
)
# TODO: Reword
_long_desc = _p(
'guildset:eventlog|long_desc',
"An audit log for my own systems, "
"I will send most significant actions and events that occur through my interface "
"to this channel. For example, this includes:\n"
"If configured, I will log most significant actions taken "
"or events which occur through my interface, into this channel. "
"Logged events include, for example:\n"
"- Member voice activity\n"
"- Roles equipped and expiring from rolemenus\n"
"- Privated rooms rented and expiring\n"
@@ -76,4 +77,34 @@ class GeneralSettings(SettingGroup):
"I must have the 'Manage Webhooks' permission in this channel."
)
# TODO: Updatestr
_model = CoreData.Guild
_column = CoreData.Guild.event_log_channel.name
@classmethod
async def _check_value(cls, parent_id: int, value: Optional[discord.abc.GuildChannel], **kwargs):
if value is not None:
t = ctx_translator.get().t
if not value.permissions_for(value.guild.me).manage_webhooks:
raise UserInputError(
t(_p(
'guildset:eventlog|check_value|error:perms|perm:manage_webhooks',
"Cannot set {channel} as an event log! I lack the 'Manage Webhooks' permission there."
)).format(channel=value)
)
@property
def update_message(self):
t = ctx_translator.get().t
channel = self.value
if channel is not None:
response = t(_p(
'guildset:eventlog|response|set',
"Events will now be logged to {channel}"
)).format(channel=channel.mention)
else:
response = t(_p(
'guildset:eventlog|response|unset',
"Guild events will no longer be logged."
))
return response

View File

@@ -4,6 +4,7 @@ import discord
from discord.ui.select import select, ChannelSelect
from meta import LionBot
from meta.errors import UserInputError
from utils.ui import ConfigUI, DashboardSection
from utils.lib import MessageArgs
@@ -18,11 +19,11 @@ _p = babel._p
class GeneralSettingUI(ConfigUI):
setting_classes = (
GeneralSettings.Timezone,
GeneralSettings.Eventlog,
GeneralSettings.EventLog,
)
def __init__(self, bot: LionBot, guildid: int, channelid: int, **kwargs):
self.settings = bot.get_cog('GeneralSettingsCog').settings
self.settings = bot.get_cog('GuildConfigCog').settings
super().__init__(bot, guildid, channelid, **kwargs)
# ----- UI Components -----
@@ -39,13 +40,10 @@ class GeneralSettingUI(ConfigUI):
"""
await selection.response.defer(thinking=True, ephemeral=True)
setting = self.get_instance(GeneralSettings.Eventlog)
setting = self.get_instance(GeneralSettings.EventLog)
value = selected.values[0] if selected.values else None
if issue := (await setting.check_value(value)):
raise UserInputError(issue)
setting.value = value
value = selected.values[0].resolve() if selected.values else None
setting = await setting.from_value(self.guildid, value)
await setting.write()
await selection.delete_original_response()
@@ -103,5 +101,5 @@ class GeneralDashboard(DashboardSection):
"dash:general|option|name",
"General Configuration Panel"
)
configui = GeneralSettingsUI
configui = GeneralSettingUI
setting_classes = configui.setting_classes

View File

@@ -57,7 +57,7 @@ class TimerOptions(SettingGroup):
_allow_object = False
@classmethod
async def _check_value(cls, parent_id: int, value: Optional[discord.abc.GuildChannel], **kwargs):
async def _check_value(cls, parent_id: int, value, **kwargs):
if value is not None:
# TODO: Check we either have or can create a webhook
# TODO: Check we can send messages, embeds, and files

View File

@@ -145,13 +145,11 @@ class TimerOptionsUI(MessageUI):
value = selected.values[0] if selected.values else None
setting = self.timer.config.get('notification_channel')
if issue := await setting._check_value(self.timer.data.channelid, value):
await selection.edit_original_response(embed=error_embed(issue))
else:
setting.value = value
await setting.write()
await self.timer.send_status()
await self.refresh(thinking=selection)
await setting._check_value(self.timer.data.channelid, value)
setting.value = value
await setting.write()
await self.timer.send_status()
await self.refresh(thinking=selection)
async def refresh_notification_menu(self):
self.notification_menu.placeholder = self.bot.translator.t(_p(

View File

@@ -453,6 +453,12 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]):
data = await cls._parse_string(parent_id, userstr, **kwargs)
return cls(parent_id, data, **kwargs)
@classmethod
async def from_value(cls, parent_id, value, **kwargs):
await cls._check_value(parent_id, value, **kwargs)
data = cls._data_from_value(parent_id, value, **kwargs)
return cls(parent_id, data, **kwargs)
@classmethod
async def _parse_string(cls, parent_id, string: str, **kwargs) -> Optional[SettingData]:
"""
@@ -471,15 +477,14 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]):
raise NotImplementedError
@classmethod
async def _check_value(cls, parent_id, value, **kwargs) -> Optional[str]:
async def _check_value(cls, parent_id, value, **kwargs):
"""
Check the provided value is valid.
Many setting update methods now provide Discord objects instead of raw data or user strings.
This method may be used for value-checking such a value.
Returns `None` if there are no issues, otherwise an error message.
Subclasses should override this to implement a value checker.
Raises UserInputError if the value fails validation.
"""
pass