rewrite: Create guild timezone setting.
This commit is contained in:
@@ -493,6 +493,10 @@ CREATE INDEX user_monthly_goals_users ON user_monthly_goals (userid);
|
|||||||
|
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
|
-- Timezone data {{{
|
||||||
|
ALTER TABLE guild_config ADD COLUMN timezone TEXT;
|
||||||
|
-- }}}
|
||||||
|
|
||||||
INSERT INTO VersionHistory (version, author) VALUES (13, 'v12-v13 migration');
|
INSERT INTO VersionHistory (version, author) VALUES (13, 'v12-v13 migration');
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from discord import app_commands as appcmds
|
|||||||
|
|
||||||
from meta import LionBot, LionCog, LionContext
|
from meta import LionBot, LionCog, LionContext
|
||||||
from meta.errors import UserInputError
|
from meta.errors import UserInputError
|
||||||
|
from wards import low_management
|
||||||
|
|
||||||
from settings import ModelData
|
from settings import ModelData
|
||||||
from settings.setting_types import StringSetting, BoolSetting
|
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
|
@appcmds.guild_only() # Can be removed when attached as a subcommand
|
||||||
|
@cmds.check(low_management)
|
||||||
async def cmd_configure_language(
|
async def cmd_configure_language(
|
||||||
self, ctx: LionContext, language: Optional[str] = None, force_language: Optional[appcmds.Choice[int]] = None
|
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(),
|
first_joined_at TIMESTAMPTZ DEFAULT now(),
|
||||||
left_at TIMESTAMPTZ,
|
left_at TIMESTAMPTZ,
|
||||||
locale TEXT,
|
locale TEXT,
|
||||||
|
timezone TEXT,
|
||||||
force_locale BOOLEAN
|
force_locale BOOLEAN
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -197,6 +198,8 @@ class CoreData(Registry, name="core"):
|
|||||||
first_joined_at = Timestamp()
|
first_joined_at = Timestamp()
|
||||||
left_at = Timestamp()
|
left_at = Timestamp()
|
||||||
|
|
||||||
|
timezone = String()
|
||||||
|
|
||||||
locale = String()
|
locale = String()
|
||||||
force_locale = Bool()
|
force_locale = Bool()
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,7 @@ babel = LocalBabel('config')
|
|||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
from .cog import ConfigCog
|
from .cog import ConfigCog
|
||||||
|
from .general import GeneralSettingsCog
|
||||||
|
|
||||||
await bot.add_cog(ConfigCog(bot))
|
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(
|
@configure_group.command(
|
||||||
name=_p('cmd:configure_tasklist', "tasklist"),
|
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(
|
@appcmds.rename(
|
||||||
reward=_p('cmd:configure_tasklist|param:reward', "reward"),
|
reward=_p('cmd:configure_tasklist|param:reward', "reward"),
|
||||||
@@ -712,7 +712,7 @@ class TasklistCog(LionCog):
|
|||||||
reward=TasklistSettings.task_reward._desc,
|
reward=TasklistSettings.task_reward._desc,
|
||||||
reward_limit=TasklistSettings.task_reward_limit._desc
|
reward_limit=TasklistSettings.task_reward_limit._desc
|
||||||
)
|
)
|
||||||
@appcmds.check(low_management)
|
@cmds.check(low_management)
|
||||||
async def configure_tasklist_cmd(self, ctx: LionContext,
|
async def configure_tasklist_cmd(self, ctx: LionContext,
|
||||||
reward: Optional[int] = None,
|
reward: Optional[int] = None,
|
||||||
reward_limit: Optional[int] = None):
|
reward_limit: Optional[int] = None):
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class TasklistConfigUI(LeoUI):
|
|||||||
# TODO: Cohesive edit
|
# TODO: Cohesive edit
|
||||||
_listening = {}
|
_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)
|
super().__init__(**kwargs)
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
|
|||||||
@@ -665,6 +665,10 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
|||||||
"""
|
"""
|
||||||
Typed Setting ABC representing timezone information.
|
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
|
# TODO Definitely need autocomplete here
|
||||||
accepts = "A timezone name."
|
accepts = "A timezone name."
|
||||||
_accepts = (
|
_accepts = (
|
||||||
@@ -703,6 +707,7 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
|||||||
"""
|
"""
|
||||||
Parse the user input into an integer.
|
Parse the user input into an integer.
|
||||||
"""
|
"""
|
||||||
|
# TODO: Localise
|
||||||
# TODO: Another selection case.
|
# TODO: Another selection case.
|
||||||
if not string:
|
if not string:
|
||||||
return None
|
return None
|
||||||
@@ -714,6 +719,9 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
|||||||
timezone = timezones[0]
|
timezone = timezones[0]
|
||||||
elif timezones:
|
elif timezones:
|
||||||
raise UserInputError("Multiple matching timezones found!")
|
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(
|
# result = await ctx.selector(
|
||||||
# "Multiple matching timezones found, please select one.",
|
# "Multiple matching timezones found, please select one.",
|
||||||
# timezones
|
# timezones
|
||||||
@@ -725,7 +733,7 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
|||||||
"Please provide a TZ name from "
|
"Please provide a TZ name from "
|
||||||
"[this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)".format(string)
|
"[this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)".format(string)
|
||||||
) from None
|
) from None
|
||||||
return timezone
|
return str(timezone)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _format_data(cls, parent_id: ParentID, data, **kwargs):
|
def _format_data(cls, parent_id: ParentID, data, **kwargs):
|
||||||
|
|||||||
Reference in New Issue
Block a user