rewrite: Create guild timezone setting.
This commit is contained in:
@@ -11,6 +11,7 @@ from discord import app_commands as appcmds
|
||||
|
||||
from meta import LionBot, LionCog, LionContext
|
||||
from meta.errors import UserInputError
|
||||
from wards import low_management
|
||||
|
||||
from settings import ModelData
|
||||
from settings.setting_types import StringSetting, BoolSetting
|
||||
@@ -225,6 +226,7 @@ class BabelCog(LionCog):
|
||||
]
|
||||
)
|
||||
@appcmds.guild_only() # Can be removed when attached as a subcommand
|
||||
@cmds.check(low_management)
|
||||
async def cmd_configure_language(
|
||||
self, ctx: LionContext, language: Optional[str] = None, force_language: Optional[appcmds.Choice[int]] = None
|
||||
):
|
||||
|
||||
@@ -138,6 +138,7 @@ class CoreData(Registry, name="core"):
|
||||
first_joined_at TIMESTAMPTZ DEFAULT now(),
|
||||
left_at TIMESTAMPTZ,
|
||||
locale TEXT,
|
||||
timezone TEXT,
|
||||
force_locale BOOLEAN
|
||||
);
|
||||
|
||||
@@ -197,6 +198,8 @@ class CoreData(Registry, name="core"):
|
||||
first_joined_at = Timestamp()
|
||||
left_at = Timestamp()
|
||||
|
||||
timezone = String()
|
||||
|
||||
locale = String()
|
||||
force_locale = Bool()
|
||||
|
||||
|
||||
@@ -7,4 +7,7 @@ babel = LocalBabel('config')
|
||||
|
||||
async def setup(bot):
|
||||
from .cog import ConfigCog
|
||||
from .general import GeneralSettingsCog
|
||||
|
||||
await bot.add_cog(ConfigCog(bot))
|
||||
await bot.add_cog(GeneralSettingsCog(bot))
|
||||
|
||||
203
src/modules/config/general.py
Normal file
203
src/modules/config/general.py
Normal file
@@ -0,0 +1,203 @@
|
||||
"""
|
||||
Lion Module providing the "General" guild settings.
|
||||
|
||||
Also provides a placeholder place to put guild settings before migrating to their correct modules.
|
||||
"""
|
||||
from typing import Optional
|
||||
import datetime as dt
|
||||
|
||||
import pytz
|
||||
import discord
|
||||
from discord.ext import commands as cmds
|
||||
from discord import app_commands as appcmds
|
||||
|
||||
from meta import LionBot, LionCog, LionContext
|
||||
from meta.errors import UserInputError
|
||||
from wards import low_management
|
||||
from settings import ModelData
|
||||
from settings.setting_types import TimezoneSetting
|
||||
from settings.groups import SettingGroup
|
||||
|
||||
from core.data import CoreData
|
||||
from babel.translator import ctx_translator
|
||||
|
||||
from . import babel
|
||||
|
||||
_p = babel._p
|
||||
|
||||
|
||||
class GeneralSettings(SettingGroup):
|
||||
class Timezone(ModelData, TimezoneSetting):
|
||||
"""
|
||||
Guild timezone configuration.
|
||||
|
||||
Exposed via `/configure 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'
|
||||
|
||||
_display_name = _p('guildset:timezone', "timezone")
|
||||
_desc = _p(
|
||||
'guildset:timezone|desc',
|
||||
"Guild timezone for statistics display."
|
||||
)
|
||||
_long_desc = _p(
|
||||
'guildset:timezone|long_desc',
|
||||
"Guild-wide timezone. "
|
||||
"Used to determine start of the day for the leaderboards, "
|
||||
"and as the default statistics timezone for members who have not set one."
|
||||
)
|
||||
_default = 'UTC'
|
||||
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.timezone.name
|
||||
|
||||
@property
|
||||
def update_message(self):
|
||||
t = ctx_translator.get().t
|
||||
# TODO: update_message can state time in current timezone
|
||||
return t(_p(
|
||||
'guildset:timezone|response',
|
||||
"The guild timezone has been set to `{timezone}`."
|
||||
)).format(timezone=self.data)
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
# TODO
|
||||
return '</configure general:1038560947666694144>'
|
||||
|
||||
|
||||
class GeneralSettingsCog(LionCog):
|
||||
depends = {'CoreCog'}
|
||||
|
||||
def __init__(self, bot: LionBot):
|
||||
self.bot = bot
|
||||
self.settings = GeneralSettings()
|
||||
|
||||
async def cog_load(self):
|
||||
self.bot.core.guild_config.register_model_setting(GeneralSettings.Timezone)
|
||||
|
||||
configcog = self.bot.get_cog('ConfigCog')
|
||||
if configcog is None:
|
||||
# TODO: Critical logging error
|
||||
...
|
||||
self.crossload_group(self.configure_group, configcog.configure_group)
|
||||
|
||||
@LionCog.placeholder_group
|
||||
@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.guild_only()
|
||||
@cmds.check(low_management)
|
||||
async def cmd_configure_general(self, ctx: LionContext,
|
||||
timezone: Optional[str] = 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)
|
||||
|
||||
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(),
|
||||
title=t(_p(
|
||||
'cmd:configure_general|success',
|
||||
"Settings Updated!"
|
||||
)),
|
||||
description='\n'.join(
|
||||
f"{self.bot.config.emojis.tick} {line}" for line in results
|
||||
)
|
||||
)
|
||||
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')
|
||||
async def cmd_configure_general_acmpl_timezone(self, interaction: discord.Interaction, partial: str):
|
||||
"""
|
||||
Autocomplete timezone options.
|
||||
|
||||
Each option is formatted as timezone (current time).
|
||||
Partial text is matched directly by case-insensitive substring.
|
||||
"""
|
||||
t = self.bot.translator.t
|
||||
|
||||
timezones = pytz.all_timezones
|
||||
matching = [tz for tz in timezones if partial.strip().lower() in tz.lower()][:25]
|
||||
if not matching:
|
||||
choices = [
|
||||
appcmds.Choice(
|
||||
name=t(_p(
|
||||
'cmd:configure_general|acmpl:timezone|no_matching',
|
||||
"No timezones matching '{input}'!"
|
||||
)).format(input=partial),
|
||||
value=partial
|
||||
)
|
||||
]
|
||||
else:
|
||||
choices = []
|
||||
for tz in matching:
|
||||
timezone = pytz.timezone(tz)
|
||||
now = dt.datetime.now(timezone)
|
||||
nowstr = now.strftime("%H:%M")
|
||||
name = t(_p(
|
||||
'cmd:configure_general|acmpl:timezone|choice',
|
||||
"{tz} (Currently {now})"
|
||||
)).format(tz=tz, now=nowstr)
|
||||
choice = appcmds.Choice(
|
||||
name=name,
|
||||
value=tz
|
||||
)
|
||||
choices.append(choice)
|
||||
return choices
|
||||
@@ -702,7 +702,7 @@ class TasklistCog(LionCog):
|
||||
|
||||
@configure_group.command(
|
||||
name=_p('cmd:configure_tasklist', "tasklist"),
|
||||
description=_p('cmd:configure_tasklist|desc', "Configuration panel")
|
||||
description=_p('cmd:configure_tasklist|desc', "Tasklist configuration panel")
|
||||
)
|
||||
@appcmds.rename(
|
||||
reward=_p('cmd:configure_tasklist|param:reward', "reward"),
|
||||
@@ -712,7 +712,7 @@ class TasklistCog(LionCog):
|
||||
reward=TasklistSettings.task_reward._desc,
|
||||
reward_limit=TasklistSettings.task_reward_limit._desc
|
||||
)
|
||||
@appcmds.check(low_management)
|
||||
@cmds.check(low_management)
|
||||
async def configure_tasklist_cmd(self, ctx: LionContext,
|
||||
reward: Optional[int] = None,
|
||||
reward_limit: Optional[int] = None):
|
||||
|
||||
@@ -132,7 +132,7 @@ class TasklistConfigUI(LeoUI):
|
||||
# TODO: Cohesive edit
|
||||
_listening = {}
|
||||
|
||||
def __init__(self, bot: LionBot, settings: TasklistSettings, guildid: int, channelid: int,**kwargs):
|
||||
def __init__(self, bot: LionBot, settings: TasklistSettings, guildid: int, channelid: int, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bot = bot
|
||||
self.settings = settings
|
||||
|
||||
@@ -665,6 +665,10 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
||||
"""
|
||||
Typed Setting ABC representing timezone information.
|
||||
"""
|
||||
# TODO: Consider configuration UI for timezone by continent and country
|
||||
# Do any continents have more than 25 countries?
|
||||
# Maybe list e.g. Europe (Austria - Iceland) and Europe (Ireland - Ukraine) separately
|
||||
|
||||
# TODO Definitely need autocomplete here
|
||||
accepts = "A timezone name."
|
||||
_accepts = (
|
||||
@@ -703,6 +707,7 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
||||
"""
|
||||
Parse the user input into an integer.
|
||||
"""
|
||||
# TODO: Localise
|
||||
# TODO: Another selection case.
|
||||
if not string:
|
||||
return None
|
||||
@@ -714,6 +719,9 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
||||
timezone = timezones[0]
|
||||
elif timezones:
|
||||
raise UserInputError("Multiple matching timezones found!")
|
||||
# TODO: Add a selector-message here instead of dying instantly
|
||||
# Maybe only post a selector if there are less than 25 options!
|
||||
|
||||
# result = await ctx.selector(
|
||||
# "Multiple matching timezones found, please select one.",
|
||||
# timezones
|
||||
@@ -725,7 +733,7 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
||||
"Please provide a TZ name from "
|
||||
"[this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)".format(string)
|
||||
) from None
|
||||
return timezone
|
||||
return str(timezone)
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id: ParentID, data, **kwargs):
|
||||
|
||||
Reference in New Issue
Block a user