rewrite: Setting input strings and localisation.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
src/modules/test/*
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
@@ -137,4 +139,4 @@ dmypy.json
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
config/**
|
||||
config/**
|
||||
|
||||
@@ -136,7 +136,7 @@ class BabelCog(LionCog):
|
||||
if language:
|
||||
lang_data = await lang_setting._parse_string(ctx.guild.id, language)
|
||||
if force_language is not None:
|
||||
force_data = bool(force_language)
|
||||
force_data = bool(force_language.value)
|
||||
|
||||
if force_language is not None and not (lang_data if language is not None else lang_setting.value):
|
||||
# Setting force without having a language!
|
||||
@@ -204,12 +204,8 @@ class BabelCog(LionCog):
|
||||
new_data = await setting._parse_string(ctx.author.id, language)
|
||||
await setting.interactive_set(new_data, ctx.interaction, ephemeral=True)
|
||||
else:
|
||||
embed = setting.embed
|
||||
if setting.value:
|
||||
desc = t(_p(
|
||||
'cmd:userconfig_language|response:set',
|
||||
"Your preferred language is currently set to {language}"
|
||||
)).format(language=setting.formatted)
|
||||
|
||||
@AButton(
|
||||
label=t(_p('cmd:userconfig_language|button:reset|label', "Reset")),
|
||||
style=ButtonStyle.red
|
||||
@@ -220,15 +216,7 @@ class BabelCog(LionCog):
|
||||
|
||||
view = AsComponents(reset_button)
|
||||
else:
|
||||
desc = t(_p(
|
||||
'cmd:userconfig_language|response:unset',
|
||||
"You have not set a preferred language!"
|
||||
))
|
||||
view = None
|
||||
embed = discord.Embed(
|
||||
colour=discord.Colour.orange(),
|
||||
description=desc
|
||||
)
|
||||
await ctx.reply(embed=embed, ephemeral=True, view=view)
|
||||
|
||||
@userconfig_language_cmd.autocomplete('language')
|
||||
|
||||
@@ -4,6 +4,7 @@ from settings.setting_types import StringSetting, BoolSetting
|
||||
from settings.groups import SettingGroup
|
||||
|
||||
from meta.errors import UserInputError
|
||||
from meta.context import ctx_bot
|
||||
from core.data import CoreData
|
||||
|
||||
from .translator import ctx_translator
|
||||
@@ -17,11 +18,30 @@ class LocaleSetting(StringSetting):
|
||||
"""
|
||||
Base class describing a LocaleSetting.
|
||||
"""
|
||||
_accepts = _p(
|
||||
'settype:locale|accepts',
|
||||
"Enter a supported language (e.g. 'en-GB')."
|
||||
)
|
||||
|
||||
def _desc_table(self) -> list[str]:
|
||||
translator = ctx_translator.get()
|
||||
t = translator.t
|
||||
|
||||
lines = super()._desc_table()
|
||||
lines.append((
|
||||
t(_p(
|
||||
'settype:locale|summary_table|field:supported|key',
|
||||
"Supported"
|
||||
)),
|
||||
', '.join(f"`{locale}`" for locale in translator.supported_locales)
|
||||
))
|
||||
return lines
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id, data, **kwargs):
|
||||
t = ctx_translator.get().t
|
||||
if data is None:
|
||||
formatted = t(_p('set_type:locale|formatted:unset', "Unset"))
|
||||
formatted = t(_p('settype:locale|formatted:unset', "Unset"))
|
||||
else:
|
||||
name = locale_names.get(data, None)
|
||||
if name:
|
||||
@@ -37,7 +57,7 @@ class LocaleSetting(StringSetting):
|
||||
lang = string[:20]
|
||||
raise UserInputError(
|
||||
translator.t(
|
||||
_p('set_type:locale|error', "Sorry, we do not support the language `{lang}` at this time!")
|
||||
_p('settype:locale|error', "Sorry, we do not support the language `{lang}` at this time!")
|
||||
).format(lang=lang)
|
||||
)
|
||||
return string
|
||||
@@ -54,6 +74,11 @@ class LocaleSettings(SettingGroup):
|
||||
|
||||
_display_name = _p('userset:locale', 'language')
|
||||
_desc = _p('userset:locale|desc', "Your preferred language for interacting with me.")
|
||||
_long_desc = _p(
|
||||
'userset:locale|long_desc',
|
||||
"The language you would prefer me to respond to commands and interactions in. "
|
||||
"Servers may be configured to override this with their own language."
|
||||
)
|
||||
|
||||
_model = CoreData.User
|
||||
_column = CoreData.User.locale.name
|
||||
@@ -68,6 +93,12 @@ class LocaleSettings(SettingGroup):
|
||||
lang=self.formatted
|
||||
)
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
bot = ctx_bot.get()
|
||||
if bot:
|
||||
return bot.core.mention_cmd('my language')
|
||||
|
||||
class ForceLocale(ModelData, BoolSetting):
|
||||
"""
|
||||
Guild configuration for whether to force usage of the guild locale.
|
||||
@@ -108,10 +139,11 @@ class LocaleSettings(SettingGroup):
|
||||
"I will now allow the members to set their own language here."
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id, data, **kwargs):
|
||||
t = ctx_translator.get().t
|
||||
return t(cls._outputs[data])
|
||||
@property
|
||||
def set_str(self):
|
||||
bot = ctx_bot.get()
|
||||
if bot:
|
||||
return bot.core.mention_cmd('configure language')
|
||||
|
||||
class GuildLocale(ModelData, LocaleSetting):
|
||||
"""
|
||||
@@ -142,3 +174,9 @@ class LocaleSettings(SettingGroup):
|
||||
return t(_p('guildset:locale|response', "You have set the guild language to {lang}.")).format(
|
||||
lang=self.formatted
|
||||
)
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
bot = ctx_bot.get()
|
||||
if bot:
|
||||
return bot.core.mention_cmd('configure language')
|
||||
|
||||
@@ -80,7 +80,7 @@ class LocaleSettingUI(ConfigUI):
|
||||
class LocaleDashboard(DashboardSection):
|
||||
section_name = _p(
|
||||
'dash:locale|title',
|
||||
"Server Language Configuration"
|
||||
"Server Language Configuration ({commands[configure language]})"
|
||||
)
|
||||
configui = LocaleSettingUI
|
||||
setting_classes = LocaleSettingUI.setting_classes
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Optional
|
||||
from collections import defaultdict
|
||||
|
||||
import discord
|
||||
import discord.app_commands as appcmd
|
||||
@@ -17,6 +18,15 @@ from .lion_member import MemberConfig
|
||||
from .lion_user import UserConfig
|
||||
|
||||
|
||||
class keydefaultdict(defaultdict):
|
||||
def __missing__(self, key):
|
||||
if self.default_factory is None:
|
||||
raise KeyError(key)
|
||||
else:
|
||||
ret = self[key] = self.default_factory(key)
|
||||
return ret
|
||||
|
||||
|
||||
class CoreCog(LionCog):
|
||||
def __init__(self, bot: LionBot):
|
||||
self.bot = bot
|
||||
@@ -43,7 +53,7 @@ class CoreCog(LionCog):
|
||||
|
||||
self.app_cmd_cache: list[discord.app_commands.AppCommand] = []
|
||||
self.cmd_name_cache: dict[str, discord.app_commands.AppCommand] = {}
|
||||
self.mention_cache: dict[str, str] = {}
|
||||
self.mention_cache: dict[str, str] = keydefaultdict(self.mention_cmd)
|
||||
|
||||
async def cog_load(self):
|
||||
# Fetch (and possibly create) core data rows.
|
||||
@@ -74,7 +84,7 @@ class CoreCog(LionCog):
|
||||
self.mention_cache = self._mention_cache_from(self.app_cmd_cache)
|
||||
|
||||
def _mention_cache_from(self, cmds: list[appcmd.AppCommand | appcmd.AppCommandGroup]):
|
||||
cache = {}
|
||||
cache = keydefaultdict(self.mention_cmd)
|
||||
for cmd in cmds:
|
||||
cache[cmd.qualified_name if isinstance(cmd, appcmd.AppCommandGroup) else cmd.name] = cmd.mention
|
||||
subcommands = [option for option in cmd.options if isinstance(option, appcmd.AppCommandGroup)]
|
||||
|
||||
@@ -141,7 +141,10 @@ class GuildDashboard(BasePager):
|
||||
for i, page in enumerate(self.pages):
|
||||
for j, section in enumerate(page):
|
||||
option = SelectOption(
|
||||
label=t(section.section_name),
|
||||
label=t(section.section_name).format(
|
||||
bot=self.bot,
|
||||
commands=self.bot.core.mention_cache
|
||||
),
|
||||
value=str(i * 10 + j)
|
||||
)
|
||||
options.append(option)
|
||||
|
||||
@@ -11,7 +11,7 @@ import discord
|
||||
from discord.ext import commands as cmds
|
||||
from discord import app_commands as appcmds
|
||||
|
||||
from meta import LionBot, LionCog, LionContext
|
||||
from meta import LionBot, LionCog, LionContext, ctx_bot
|
||||
from meta.errors import UserInputError
|
||||
from wards import low_management
|
||||
from settings import ModelData
|
||||
@@ -57,7 +57,6 @@ class GeneralSettings(SettingGroup):
|
||||
@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}`."
|
||||
@@ -65,8 +64,8 @@ class GeneralSettings(SettingGroup):
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
# TODO
|
||||
return '</configure general:1038560947666694144>'
|
||||
bot = ctx_bot.get()
|
||||
return bot.core.mention_cmd('configure general') if bot else None
|
||||
|
||||
|
||||
class GeneralSettingsCog(LionCog):
|
||||
|
||||
@@ -787,9 +787,23 @@ class Economy(LionCog):
|
||||
"Configure LionCoin Economy"
|
||||
)
|
||||
)
|
||||
@appcmds.rename(
|
||||
allow_transfers=EconomySettings.AllowTransfers._display_name,
|
||||
coins_per_xp=EconomySettings.CoinsPerXP._display_name
|
||||
)
|
||||
@appcmds.describe(
|
||||
allow_transfers=EconomySettings.AllowTransfers._desc,
|
||||
coins_per_xp=EconomySettings.CoinsPerXP._desc
|
||||
)
|
||||
@appcmds.choices(
|
||||
allow_transfers=[
|
||||
appcmds.Choice(name=EconomySettings.AllowTransfers._outputs[True], value=1),
|
||||
appcmds.Choice(name=EconomySettings.AllowTransfers._outputs[False], value=0),
|
||||
]
|
||||
)
|
||||
@cmds.check(low_management)
|
||||
async def configure_economy(self, ctx: LionContext,
|
||||
allow_transfers: Optional[bool] = None,
|
||||
allow_transfers: Optional[appcmds.Choice[int]] = None,
|
||||
coins_per_xp: Optional[appcmds.Range[int, 0, 2**15]] = None):
|
||||
t = self.bot.translator.t
|
||||
if not ctx.interaction:
|
||||
@@ -802,7 +816,7 @@ class Economy(LionCog):
|
||||
|
||||
modified = []
|
||||
if allow_transfers is not None:
|
||||
setting_allow_transfers.data = allow_transfers
|
||||
setting_allow_transfers.data = bool(allow_transfers.value)
|
||||
await setting_allow_transfers.write()
|
||||
modified.append(setting_allow_transfers)
|
||||
if coins_per_xp is not None:
|
||||
|
||||
@@ -10,6 +10,7 @@ from settings.groups import SettingGroup
|
||||
from settings.data import ModelData, ListData
|
||||
from settings.setting_types import ChannelListSetting, IntegerSetting, BoolSetting
|
||||
|
||||
from meta.context import ctx_bot
|
||||
from meta.config import conf
|
||||
from meta.sharding import THIS_SHARD
|
||||
from meta.logger import log_wrap
|
||||
@@ -40,6 +41,10 @@ class EconomySettings(SettingGroup):
|
||||
'guildset:coins_per_xp|long_desc',
|
||||
"Members will be rewarded with this many LionCoins for every 100 XP they earn."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:coins_per_xp|long_desc',
|
||||
"The number of coins to reward per 100 XP."
|
||||
)
|
||||
# This default needs to dynamically depend on the guild mode!
|
||||
_default = 50
|
||||
|
||||
@@ -54,6 +59,11 @@ class EconomySettings(SettingGroup):
|
||||
"For every **100** XP they earn, members will now be given {coin}**{amount}**."
|
||||
)).format(amount=self.value, coin=conf.emojis.coin)
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
bot = ctx_bot.get()
|
||||
return bot.core.mention_cmd('configure economy') if bot else None
|
||||
|
||||
class AllowTransfers(ModelData, BoolSetting):
|
||||
setting_id = 'allow_transfers'
|
||||
|
||||
@@ -64,9 +74,40 @@ class EconomySettings(SettingGroup):
|
||||
)
|
||||
_long_desc = _p(
|
||||
'guildset:allow_transfers|long_desc',
|
||||
"If disabled, members will not be able to use `/sendcoins` to transfer LionCoinds."
|
||||
"If disabled, members will not be able to transfer LionCoins to each other."
|
||||
)
|
||||
_default = True
|
||||
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.allow_transfers.name
|
||||
|
||||
_outputs = {
|
||||
True: _p('guildset:allow_transfers|outputs:true', "Enabled (Coin transfers allowed.)"),
|
||||
False: _p('guildset:allow_transfers|outputs:false', "Disabled (Coin transfers not allowed.)"),
|
||||
}
|
||||
_outputs[None] = _outputs[_default]
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
bot = ctx_bot.get()
|
||||
return bot.core.mention_cmd('configure economy') if bot else None
|
||||
|
||||
@property
|
||||
def update_message(self):
|
||||
t = ctx_translator.get().t
|
||||
bot = ctx_bot.get()
|
||||
if self.value:
|
||||
formatted = t(_p(
|
||||
'guildset:allow_transfers|set_response|set:true',
|
||||
"Members will now be able to use {send_cmd} to transfer {coin}"
|
||||
))
|
||||
else:
|
||||
formatted = t(_p(
|
||||
'guildset:allow_transfers|set_response|set:false',
|
||||
"Members will not be able to use {send_cmd} to transfer {coin}"
|
||||
))
|
||||
formatted = formatted.format(
|
||||
send_cmd=bot.core.mention_cmd('send'),
|
||||
coin=conf.emojis.coin
|
||||
)
|
||||
return formatted
|
||||
|
||||
@@ -65,7 +65,7 @@ class EconomyConfigUI(ConfigUI):
|
||||
class EconomyDashboard(DashboardSection):
|
||||
section_name = _p(
|
||||
'dash:economy|title',
|
||||
"Economy Configuration"
|
||||
"Economy Configuration ({commands[configure economy]})"
|
||||
)
|
||||
configui = EconomyConfigUI
|
||||
setting_classes = EconomyConfigUI.setting_classes
|
||||
|
||||
@@ -304,8 +304,8 @@ class TimerCog(LionCog):
|
||||
|
||||
# ----- Timer Commands -----
|
||||
@cmds.hybrid_group(
|
||||
name=_p('cmd:pomodoro', "pomodoro"),
|
||||
desc=_p('cmd:pomodoro|desc', "Base group for all pomodoro timer commands.")
|
||||
name=_p('cmd:pomodoro', "timers"),
|
||||
description=_p('cmd:pomodoro|desc', "Base group for all pomodoro timer commands.")
|
||||
)
|
||||
@cmds.guild_only()
|
||||
async def pomodoro_group(self, ctx: LionContext):
|
||||
@@ -787,7 +787,7 @@ class TimerCog(LionCog):
|
||||
await timer.update_status_card()
|
||||
|
||||
# Show the config UI
|
||||
ui = TimerOptionsUI(self.bot, timer, timer_role)
|
||||
ui = TimerOptionsUI(self.bot, timer, timer_role, callerid=ctx.author.id)
|
||||
await ui.run(ctx.interaction)
|
||||
await ui.wait()
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class TimerSettings(SettingGroup):
|
||||
class PomodoroChannel(ModelData, ChannelSetting):
|
||||
setting_id = 'pomodoro_channel'
|
||||
_event = 'guildset_pomodoro_channel'
|
||||
_set_cmd = 'configure pomodoro'
|
||||
|
||||
_display_name = _p('guildset:pomodoro_channel', "pomodoro_channel")
|
||||
_desc = _p(
|
||||
@@ -27,6 +28,15 @@ class TimerSettings(SettingGroup):
|
||||
"If this setting is not set, pomodoro notifications will default to the "
|
||||
"timer voice channel itself."
|
||||
)
|
||||
_notset_str = _p(
|
||||
'guildset:pomodoro_channel|formatted|notset',
|
||||
"Not Set (Will use timer voice channel.)"
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:pomodoro_channel|accepts',
|
||||
"Timer notification channel name or id."
|
||||
)
|
||||
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.pomodoro_channel.name
|
||||
|
||||
@@ -45,3 +55,12 @@ class TimerSettings(SettingGroup):
|
||||
"Pomodoro timer notifications will now default to their voice channel."
|
||||
))
|
||||
return resp
|
||||
|
||||
@property
|
||||
def set_str(self) -> str:
|
||||
cmdstr = super().set_str
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:pomdoro_channel|set_using',
|
||||
"{cmd} or channel selector below."
|
||||
)).format(cmd=cmdstr)
|
||||
|
||||
@@ -78,7 +78,7 @@ class TimerConfigUI(ConfigUI):
|
||||
class TimerDashboard(DashboardSection):
|
||||
section_name = _p(
|
||||
'dash:pomodoro|title',
|
||||
"Pomodoro Configuration"
|
||||
"Pomodoro Configuration ({commands[configure pomodoro]})"
|
||||
)
|
||||
configui = TimerConfigUI
|
||||
setting_classes = TimerConfigUI.setting_classes
|
||||
|
||||
@@ -23,22 +23,24 @@ class RankSettings(SettingGroup):
|
||||
_enum = RankType
|
||||
_default = RankType.VOICE
|
||||
_outputs = {
|
||||
RankType.VOICE: '`Voice`',
|
||||
RankType.XP: '`Exp`',
|
||||
RankType.MESSAGE: '`Messages`'
|
||||
RankType.VOICE: _p('guildset:rank_type|output:voice', '`Voice`'),
|
||||
RankType.XP: _p('guildset:rank_type|output:xp', '`Exp`'),
|
||||
RankType.MESSAGE: _p('guildset:rank_type|output:message', '`Messages`'),
|
||||
}
|
||||
_inputs = {
|
||||
'voice': RankType.VOICE,
|
||||
'study': RankType.VOICE,
|
||||
'text': RankType.MESSAGE,
|
||||
'message': RankType.MESSAGE,
|
||||
'messages': RankType.MESSAGE,
|
||||
'xp': RankType.XP,
|
||||
'exp': RankType.XP
|
||||
_input_formatted = {
|
||||
RankType.VOICE: _p('guildset:rank_type|input_format:voice', 'Voice'),
|
||||
RankType.XP: _p('guildset:rank_type|input_format:xp', 'Exp'),
|
||||
RankType.MESSAGE: _p('guildset:rank_type|input_format:message', 'Messages'),
|
||||
}
|
||||
_input_patterns = {
|
||||
RankType.VOICE: _p('guildset:rank_type|input_pattern:voice', 'voice|study'),
|
||||
RankType.MESSAGE: _p('guildset:rank_type|input_pattern:voice', 'text|message|messages'),
|
||||
RankType.XP: _p('guildset:rank_type|input_pattern:xp', 'xp|exp|experience'),
|
||||
}
|
||||
|
||||
setting_id = 'rank_type'
|
||||
_event = 'guildset_rank_type'
|
||||
_set_cmd = 'configure ranks'
|
||||
|
||||
_display_name = _p('guildset:rank_type', "rank_type")
|
||||
_desc = _p(
|
||||
@@ -52,6 +54,10 @@ class RankSettings(SettingGroup):
|
||||
"`Exp` is a measure of message activity, and "
|
||||
"`Message` is a simple count of messages sent."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:rank_type|accepts',
|
||||
"Voice/Exp/Messages"
|
||||
)
|
||||
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.rank_type.name
|
||||
@@ -76,6 +82,15 @@ class RankSettings(SettingGroup):
|
||||
))
|
||||
return resp
|
||||
|
||||
@property
|
||||
def set_str(self) -> str:
|
||||
cmdstr = super().set_str
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:rank_channel|set_using',
|
||||
"{cmd} or option menu below."
|
||||
)).format(cmd=cmdstr)
|
||||
|
||||
class RankChannel(ModelData, ChannelSetting):
|
||||
"""
|
||||
Channel to send Rank notifications.
|
||||
@@ -83,6 +98,7 @@ 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'
|
||||
|
||||
_display_name = _p('guildset:rank_channel', "rank_channel")
|
||||
_desc = _p(
|
||||
@@ -95,14 +111,44 @@ class RankSettings(SettingGroup):
|
||||
"If `dm_ranks` is enabled, this channel will only be used when the user has opted not to receive "
|
||||
"DM notifications, or is otherwise unreachable."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:rank_channel|accepts',
|
||||
"Rank notification channel name or id."
|
||||
)
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.rank_channel.name
|
||||
|
||||
@property
|
||||
def update_message(self) -> str:
|
||||
t = ctx_translator.get().t
|
||||
value = self.value
|
||||
if value is not None:
|
||||
resp = t(_p(
|
||||
'guildset:rank_channel|set_response|set',
|
||||
"Rank update messages will be sent to {channel}."
|
||||
)).format(channel=value.mention)
|
||||
else:
|
||||
resp = t(_p(
|
||||
'guildset:rank_channel|set_response|unset',
|
||||
"Rank update messages will be ignored or sent via DM (if `dm_ranks` is enabled)."
|
||||
))
|
||||
return resp
|
||||
|
||||
@property
|
||||
def set_str(self) -> str:
|
||||
cmdstr = super().set_str
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:rank_channel|set_using',
|
||||
"{cmd} or channel selector below."
|
||||
)).format(cmd=cmdstr)
|
||||
|
||||
class DMRanks(ModelData, BoolSetting):
|
||||
"""
|
||||
Whether to DM rank notifications.
|
||||
"""
|
||||
setting_id = 'dm_ranks'
|
||||
_set_cmd = 'configure ranks'
|
||||
|
||||
_display_name = _p('guildset:dm_ranks', "dm_ranks")
|
||||
_desc = _p(
|
||||
@@ -114,6 +160,21 @@ class RankSettings(SettingGroup):
|
||||
"If enabled, congratulatory messages for rank advancement will be direct messaged to the user, "
|
||||
"instead of being sent to the configured `rank_channel`."
|
||||
)
|
||||
_default = True
|
||||
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.dm_ranks.name
|
||||
|
||||
@property
|
||||
def update_message(self):
|
||||
t = ctx_translator.get().t
|
||||
if self.data:
|
||||
return t(_p(
|
||||
'guildset:dm_ranks|response:true',
|
||||
"I will direct message members upon rank advancement."
|
||||
))
|
||||
else:
|
||||
return t(_p(
|
||||
'guildset:dm_ranks|response:false',
|
||||
"I will never direct message members upon rank advancement."
|
||||
))
|
||||
|
||||
@@ -155,7 +155,7 @@ class RankConfigUI(ConfigUI):
|
||||
class RankDashboard(DashboardSection):
|
||||
section_name = _p(
|
||||
'dash:rank|title',
|
||||
"Rank Configuration",
|
||||
"Rank Configuration ({commands[configure ranks]})",
|
||||
)
|
||||
configui = RankConfigUI
|
||||
setting_classes = RankConfigUI.setting_classes
|
||||
|
||||
@@ -15,6 +15,7 @@ class RoomSettings(SettingGroup):
|
||||
class Category(ModelData, ChannelSetting):
|
||||
setting_id = 'rooms_category'
|
||||
_event = 'guildset_rooms_category'
|
||||
_set_cmd = 'configure rooms'
|
||||
|
||||
_display_name = _p(
|
||||
'guildset:room_category', "rooms_category"
|
||||
@@ -31,6 +32,10 @@ class RoomSettings(SettingGroup):
|
||||
"I must have permission to create new channels in this category, "
|
||||
"as well as to manage permissions."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:room_category|accepts',
|
||||
"Private room category name or id."
|
||||
)
|
||||
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.renting_category.name
|
||||
@@ -53,9 +58,19 @@ class RoomSettings(SettingGroup):
|
||||
)).format(channel=self.value.mention)
|
||||
return resp
|
||||
|
||||
@property
|
||||
def set_str(self) -> str:
|
||||
cmdstr = super().set_str
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:room_category|set_using',
|
||||
"{cmd} or category selector below."
|
||||
)).format(cmd=cmdstr)
|
||||
|
||||
class Rent(ModelData, IntegerSetting):
|
||||
setting_id = 'rooms_price'
|
||||
_event = 'guildset_rooms_price'
|
||||
_set_cmd = 'configure rooms'
|
||||
|
||||
_display_name = _p(
|
||||
'guildset:rooms_price', "room_rent"
|
||||
@@ -68,6 +83,10 @@ class RoomSettings(SettingGroup):
|
||||
'guildset:rooms_rent|long_desc',
|
||||
"Members will be charged this many LionCoins for each day they rent a private room."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:rooms_rent|accepts',
|
||||
"Number of LionCoins charged per day for a private room."
|
||||
)
|
||||
_default = 1000
|
||||
|
||||
_model = CoreData.Guild
|
||||
@@ -88,6 +107,7 @@ class RoomSettings(SettingGroup):
|
||||
class MemberLimit(ModelData, IntegerSetting):
|
||||
setting_id = 'rooms_slots'
|
||||
_event = 'guildset_rooms_slots'
|
||||
_set_cmd = 'configure rooms'
|
||||
|
||||
_display_name = _p('guildset:rooms_slots', "room_member_cap")
|
||||
_desc = _p(
|
||||
@@ -100,6 +120,10 @@ class RoomSettings(SettingGroup):
|
||||
"or through the `/room invite` command. "
|
||||
"This setting limits the maximum number of members a private room may hold."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:rooms_slots|accepts',
|
||||
"Maximum number of members allowed per private room."
|
||||
)
|
||||
_default = 25
|
||||
|
||||
_model = CoreData.Guild
|
||||
@@ -117,6 +141,7 @@ class RoomSettings(SettingGroup):
|
||||
class Visible(ModelData, BoolSetting):
|
||||
setting_id = 'rooms_visible'
|
||||
_event = 'guildset_rooms_visible'
|
||||
_set_cmd = 'configure rooms'
|
||||
|
||||
_display_name = _p('guildset:rooms_visible', "room_visibility")
|
||||
_desc = _p(
|
||||
@@ -129,6 +154,21 @@ class RoomSettings(SettingGroup):
|
||||
"enabled for the `@everyone` role."
|
||||
)
|
||||
_default = False
|
||||
_accepts = _p('guildset:rooms_visible|accepts', "Visible/Invisible")
|
||||
_outputs = {
|
||||
True: _p('guildset:rooms_visible|output:true', "Visible"),
|
||||
False: _p('guildset:rooms_visible|output:false', "Invisible"),
|
||||
}
|
||||
_outputs[None] = _outputs[_default]
|
||||
|
||||
_truthy = _p(
|
||||
'guildset:rooms_visible|parse:truthy_values',
|
||||
"visible|enabled|yes|true|on|enable|1"
|
||||
)
|
||||
_falsey = _p(
|
||||
'guildset:rooms_visible|parse:falsey_values',
|
||||
'invisible|disabled|no|false|off|disable|0'
|
||||
)
|
||||
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.renting_visible.name
|
||||
@@ -148,6 +188,15 @@ class RoomSettings(SettingGroup):
|
||||
))
|
||||
return resp
|
||||
|
||||
@property
|
||||
def set_str(self) -> str:
|
||||
cmdstr = super().set_str
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:rooms_visible|set_using',
|
||||
"{cmd} or toggle below."
|
||||
)).format(cmd=cmdstr)
|
||||
|
||||
model_settings = (
|
||||
Category,
|
||||
Rent,
|
||||
|
||||
@@ -95,7 +95,7 @@ class RoomSettingUI(ConfigUI):
|
||||
class RoomDashboard(DashboardSection):
|
||||
section_name = _p(
|
||||
'dash:rooms|title',
|
||||
"Private Room Configuration"
|
||||
"Private Room Configuration ({commands[configure rooms]})"
|
||||
)
|
||||
configui = RoomSettingUI
|
||||
setting_classes = RoomSettingUI.setting_classes
|
||||
|
||||
@@ -14,7 +14,8 @@ from settings.groups import SettingGroup
|
||||
|
||||
from meta import conf, LionBot
|
||||
from meta.context import ctx_bot
|
||||
from utils.lib import tabulate
|
||||
from meta.errors import UserInputError
|
||||
from utils.lib import tabulate, utc_now
|
||||
from utils.ui import ConfigUI, FastModal, error_handler_for, ModalRetryUI
|
||||
from utils.lib import MessageArgs
|
||||
from core.data import CoreData
|
||||
@@ -33,16 +34,24 @@ class StatTypeSetting(EnumSetting):
|
||||
"""
|
||||
_enum = StatisticType
|
||||
_outputs = {
|
||||
StatisticType.VOICE: '`Voice`',
|
||||
StatisticType.TEXT: '`Text`',
|
||||
StatisticType.ANKI: '`Anki`'
|
||||
StatisticType.VOICE: _p('settype:stat|output:voice', "`Voice`"),
|
||||
StatisticType.TEXT: _p('settype:stat|output:text', "`Text`"),
|
||||
StatisticType.ANKI: _p('settype:stat|output:anki', "`Anki`"),
|
||||
}
|
||||
_inputs = {
|
||||
'voice': StatisticType.VOICE,
|
||||
'study': StatisticType.VOICE,
|
||||
'text': StatisticType.TEXT,
|
||||
'anki': StatisticType.ANKI
|
||||
_input_formatted = {
|
||||
StatisticType.VOICE: _p('settype:stat|input_format:voice', "Voice"),
|
||||
StatisticType.TEXT: _p('settype:stat|input_format:text', "Text"),
|
||||
StatisticType.ANKI: _p('settype:stat|input_format:anki', "Anki"),
|
||||
}
|
||||
_input_patterns = {
|
||||
StatisticType.VOICE: _p('settype:stat|input_pattern:voice', "voice|study"),
|
||||
StatisticType.TEXT: _p('settype:stat|input_pattern:text', "text|messages"),
|
||||
StatisticType.ANKI: _p('settype:stat|input_pattern:anki', "anki"),
|
||||
}
|
||||
_accepts = _p(
|
||||
'settype:state|accepts',
|
||||
'Voice/Text/Anki'
|
||||
)
|
||||
|
||||
|
||||
class StatisticsSettings(SettingGroup):
|
||||
@@ -74,6 +83,7 @@ class StatisticsSettings(SettingGroup):
|
||||
Time is assumed to be in set guild timezone (although supports +00 syntax)
|
||||
"""
|
||||
setting_id = 'season_start'
|
||||
_set_cmd = 'configure statistics'
|
||||
|
||||
_display_name = _p('guildset:season_start', "season_start")
|
||||
_desc = _p(
|
||||
@@ -86,11 +96,17 @@ class StatisticsSettings(SettingGroup):
|
||||
"and the leaderboard will display activity since this time by default. "
|
||||
"Unset to disable seasons and use all-time statistics instead."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:season_start|accepts',
|
||||
"The season start time in the form YYYY-MM-DD HH:MM"
|
||||
)
|
||||
_notset_str = _p(
|
||||
'guildset:season_start|notset',
|
||||
"Not Set (Using all-time statistics)"
|
||||
)
|
||||
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.season_start.name
|
||||
# TODO: Offer to update badge ranks when this changes?
|
||||
# TODO: Don't allow future times?
|
||||
|
||||
@classmethod
|
||||
async def _timezone_from_id(cls, guildid, **kwargs):
|
||||
@@ -98,6 +114,38 @@ class StatisticsSettings(SettingGroup):
|
||||
lguild = await bot.core.lions.fetch_guild(guildid)
|
||||
return lguild.timezone
|
||||
|
||||
@classmethod
|
||||
async def _parse_string(cls, parent_id, string, **kwargs):
|
||||
parsed = await super()._parse_string(parent_id, string, **kwargs)
|
||||
if parsed is not None and parsed > utc_now():
|
||||
t = ctx_translator.get().t
|
||||
raise UserInputError(t(_p(
|
||||
'guildset:season_start|parse|error:future_time',
|
||||
"Provided season start time {timestamp} is in the future!"
|
||||
)).format(timestamp=f"<t:{int(parsed.timestamp())}>"))
|
||||
|
||||
@property
|
||||
def update_message(self) -> str:
|
||||
t = ctx_translator.get().t
|
||||
bot = ctx_bot.get()
|
||||
value = self.value
|
||||
if value is not None:
|
||||
resp = t(_p(
|
||||
'guildset:season_start|set_response|set',
|
||||
"The leaderboard season and activity ranks will now count from {timestamp}. "
|
||||
"Member ranks will update when they are next active. Use {rank_cmd} to refresh immediately."
|
||||
)).format(
|
||||
timestamp=self.formatted,
|
||||
rank_cmd=bot.core.mention_cmd('ranks')
|
||||
)
|
||||
else:
|
||||
resp = t(_p(
|
||||
'guildset:season_start|set_response|unset',
|
||||
"The leaderboard and activity ranks will now count all-time statistics. "
|
||||
"Member ranks will update when they are next active. Use {rank_cmd} to refresh immediately."
|
||||
)).format(rank_cmd=bot.core.mention_cmd('ranks'))
|
||||
return resp
|
||||
|
||||
class UnrankedRoles(ListData, RoleListSetting):
|
||||
"""
|
||||
List of roles not displayed on the leaderboard
|
||||
@@ -113,6 +161,10 @@ class StatisticsSettings(SettingGroup):
|
||||
'guildset:unranked_roles|long_desc',
|
||||
"When set, members with *any* of these roles will not appear on the /leaderboard ranking list."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:unranked_roles|accepts',
|
||||
"Comma separated list of unranked role names or ids."
|
||||
)
|
||||
_default = None
|
||||
|
||||
_table_interface = StatsData.unranked_roles
|
||||
@@ -124,7 +176,29 @@ class StatisticsSettings(SettingGroup):
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
return "Role selector below"
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:unranked_roles|set_using',
|
||||
"Role selector below."
|
||||
))
|
||||
|
||||
@property
|
||||
def update_message(self) -> str:
|
||||
t = ctx_translator.get().t
|
||||
value = self.value
|
||||
if value is not None:
|
||||
resp = t(_p(
|
||||
'guildset:unranked_roles|set_response|set',
|
||||
"Members of the following roles will not appear on the leaderboard: {roles}"
|
||||
)).format(
|
||||
roles=self.formatted
|
||||
)
|
||||
else:
|
||||
resp = t(_p(
|
||||
'guildset:unranked_roles|set_response|unset',
|
||||
"You have cleared the unranked role list."
|
||||
))
|
||||
return resp
|
||||
|
||||
class VisibleStats(ListData, ListSetting, InteractiveSetting):
|
||||
"""
|
||||
@@ -145,6 +219,10 @@ class StatisticsSettings(SettingGroup):
|
||||
'guildset:visible_stats|desc',
|
||||
"Choose which statistics types to display in the leaderboard and statistics commands."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:visible_stats|accepts',
|
||||
"Voice, Text, Anki"
|
||||
)
|
||||
# TODO: Format VOICE as STUDY when possible?
|
||||
|
||||
_default = [
|
||||
@@ -159,6 +237,23 @@ class StatisticsSettings(SettingGroup):
|
||||
|
||||
_cache = {}
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:visible_stats|set_using',
|
||||
"Option menu below."
|
||||
))
|
||||
|
||||
@property
|
||||
def update_message(self) -> str:
|
||||
t = ctx_translator.get().t
|
||||
resp = t(_p(
|
||||
'guildset:visible_stats|set_response',
|
||||
"Members will be able to view the following statistics types: {types}"
|
||||
)).format(types=self.formatted)
|
||||
return resp
|
||||
|
||||
class DefaultStat(ModelData, StatTypeSetting):
|
||||
"""
|
||||
Which of the three stats to display by default
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
from .exec_cog import Exec
|
||||
from .blacklists import Blacklists
|
||||
from .guild_log import GuildLog
|
||||
from .presence import PresenceCtrl
|
||||
|
||||
from .dash import LeoSettings
|
||||
from babel.translator import LocalBabel
|
||||
babel = LocalBabel('sysadmin')
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
from .exec_cog import Exec
|
||||
from .blacklists import Blacklists
|
||||
from .guild_log import GuildLog
|
||||
from .presence import PresenceCtrl
|
||||
|
||||
from .dash import LeoSettings
|
||||
await bot.add_cog(LeoSettings(bot))
|
||||
|
||||
await bot.add_cog(Blacklists(bot))
|
||||
|
||||
@@ -23,6 +23,10 @@ from settings.groups import SettingGroup
|
||||
|
||||
from wards import sys_admin
|
||||
|
||||
from . import babel
|
||||
|
||||
_p = babel._p
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -104,49 +108,93 @@ class PresenceSettings(SettingGroup):
|
||||
"""
|
||||
Control the bot status and activity.
|
||||
"""
|
||||
_title = "Presence Settings ({bot.core.cmd_name_cache[presence].mention})"
|
||||
_title = "Presence Settings ({bot.core.mention_cache[presence]})"
|
||||
|
||||
class PresenceStatus(ModelData, EnumSetting[str, AppStatus]):
|
||||
setting_id = 'presence_status'
|
||||
|
||||
display_name = 'online_status'
|
||||
desc = "Bot status indicator"
|
||||
long_desc = "Whether the bot account displays as online, idle, dnd, or offline."
|
||||
accepts = "One of 'online', 'idle', 'dnd', or 'offline'."
|
||||
_display_name = _p('botset:presence_status', 'online_status')
|
||||
_desc = _p('botset:presence_status|desc', "Bot status indicator")
|
||||
_long_desc = _p(
|
||||
'botset:presence_status|long_desc',
|
||||
"Whether the bot account displays as online, idle, dnd, or offline."
|
||||
)
|
||||
_accepts = _p(
|
||||
'botset:presence_status|accepts',
|
||||
"Online/Idle/Dnd/Offline"
|
||||
)
|
||||
|
||||
_model = PresenceData.AppPresence
|
||||
_column = PresenceData.AppPresence.online_status.name
|
||||
_create_row = True
|
||||
|
||||
_enum = AppStatus
|
||||
_outputs = {item: item.value[1] for item in _enum}
|
||||
_inputs = {item.name: item for item in _enum}
|
||||
_outputs = {
|
||||
AppStatus.online: _p('botset:presence_status|output:online', "**Online**"),
|
||||
AppStatus.idle: _p('botset:presence_status|output:idle', "**Idle**"),
|
||||
AppStatus.dnd: _p('botset:presence_status|output:dnd', "**Do Not Disturb**"),
|
||||
AppStatus.offline: _p('botset:presence_status|output:offline', "**Offline**"),
|
||||
}
|
||||
_input_formatted = {
|
||||
AppStatus.online: _p('botset:presence_status|input_format:online', "Online"),
|
||||
AppStatus.idle: _p('botset:presence_status|input_format:idle', "Idle"),
|
||||
AppStatus.dnd: _p('botset:presence_status|input_format:dnd', "DND"),
|
||||
AppStatus.offline: _p('botset:presence_status|input_format:offline', "Offline"),
|
||||
}
|
||||
_input_patterns = {
|
||||
AppStatus.online: _p('botset:presence_status|input_pattern:online', "on|online"),
|
||||
AppStatus.idle: _p('botset:presence_status|input_pattern:idle', "idle"),
|
||||
AppStatus.dnd: _p('botset:presence_status|input_pattern:dnd', "do not disturb|dnd"),
|
||||
AppStatus.offline: _p('botset:presence_status|input_pattern:offline', "off|offline|invisible"),
|
||||
}
|
||||
_default = AppStatus.online
|
||||
|
||||
class PresenceType(ModelData, EnumSetting[str, AppActivityType]):
|
||||
setting_id = 'presence_type'
|
||||
|
||||
display_name = 'activity_type'
|
||||
desc = "Type of presence activity"
|
||||
long_desc = "Whether the bot activity is shown as 'Listening', 'Playing', or 'Watching'."
|
||||
accepts = "One of 'listening', 'playing', 'watching', or 'streaming'."
|
||||
_display_name = _p('botset:presence_type', 'activity_type')
|
||||
_desc = _p('botset:presence_type|desc', "Type of presence activity")
|
||||
_long_desc = _p(
|
||||
'botset:presence_type|long_desc',
|
||||
"Whether the bot activity is shown as 'Listening', 'Playing', or 'Watching'."
|
||||
)
|
||||
_accepts = _p(
|
||||
'botset:presence_type|accepts',
|
||||
"Listening/Playing/Watching/Streaming"
|
||||
)
|
||||
|
||||
_model = PresenceData.AppPresence
|
||||
_column = PresenceData.AppPresence.activity_type.name
|
||||
_create_row = True
|
||||
|
||||
_enum = AppActivityType
|
||||
_outputs = {item: item.value[1] for item in _enum}
|
||||
_inputs = {item.name: item for item in _enum}
|
||||
_outputs = {
|
||||
AppActivityType.watching: _p('botset:presence_type|output:watching', "**Watching**"),
|
||||
AppActivityType.listening: _p('botset:presence_type|output:listening', "**Listening**"),
|
||||
AppActivityType.playing: _p('botset:presence_type|output:playing', "**Playing**"),
|
||||
AppActivityType.streaming: _p('botset:presence_type|output:streaming', "**Streaming**"),
|
||||
}
|
||||
_input_formats = {
|
||||
AppActivityType.watching: _p('botset:presence_type|input_format:watching', "Watching"),
|
||||
AppActivityType.listening: _p('botset:presence_type|input_format:listening', "Listening"),
|
||||
AppActivityType.playing: _p('botset:presence_type|input_format:playing', "Playing"),
|
||||
AppActivityType.streaming: _p('botset:presence_type|input_format:streaming', "Streaming"),
|
||||
}
|
||||
_input_patterns = {
|
||||
AppActivityType.watching: _p('botset:presence_type|input_pattern:watching', "watching"),
|
||||
AppActivityType.listening: _p('botset:presence_type|input_pattern:listening', "listening"),
|
||||
AppActivityType.playing: _p('botset:presence_type|input_pattern:playing', "playing"),
|
||||
AppActivityType.streaming: _p('botset:presence_type|input_pattern:streaming', "streaming"),
|
||||
}
|
||||
_default = AppActivityType.watching
|
||||
|
||||
class PresenceName(ModelData, StringSetting[str]):
|
||||
setting_id = 'presence_name'
|
||||
|
||||
display_name = 'activity_name'
|
||||
desc = "Name of the presence activity"
|
||||
long_desc = "Presence activity name."
|
||||
accepts = "Any string."
|
||||
_display_name = _p('botset:presence_name', 'activity_name')
|
||||
_desc = _p("botset:presence_name|desc", "Name of the presence activity")
|
||||
_long_desc = _p("botset:presence_name|long_desc", "Presence activity name.")
|
||||
_accepts = _p('botset:presence_name|accepts', "The name of the activity to show.")
|
||||
|
||||
_model = PresenceData.AppPresence
|
||||
_column = PresenceData.AppPresence.activity_name.name
|
||||
|
||||
@@ -8,7 +8,7 @@ from settings import ListData, ModelData
|
||||
from settings.setting_types import StringSetting, BoolSetting, ChannelListSetting, IntegerSetting
|
||||
from settings.groups import SettingGroup
|
||||
|
||||
from meta import conf, LionBot
|
||||
from meta import conf, LionBot, ctx_bot
|
||||
from utils.lib import tabulate
|
||||
from utils.ui import LeoUI, FastModal, error_handler_for, ModalRetryUI, DashboardSection
|
||||
from core.data import CoreData
|
||||
@@ -28,6 +28,7 @@ class TasklistSettings(SettingGroup):
|
||||
Exposed via `/configure tasklist`, and the standard configuration interface.
|
||||
"""
|
||||
setting_id = 'task_reward'
|
||||
_set_cmd = 'configure tasklist'
|
||||
|
||||
_display_name = _p('guildset:task_reward', "task_reward")
|
||||
_desc = _p(
|
||||
@@ -38,6 +39,10 @@ class TasklistSettings(SettingGroup):
|
||||
'guildset:task_reward|long_desc',
|
||||
"The number of coins members will be rewarded each time they complete a task on their tasklist."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:task_reward|accepts',
|
||||
"The number of LionCoins to reward per task."
|
||||
)
|
||||
_default = 50
|
||||
|
||||
_model = CoreData.Guild
|
||||
@@ -51,20 +56,19 @@ class TasklistSettings(SettingGroup):
|
||||
"Members will now be rewarded {coin}**{amount}** for each completed task."
|
||||
)).format(coin=conf.emojis.coin, amount=self.data)
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
return '</configure tasklist:1038560947666694144>'
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id, data, **kwargs):
|
||||
if data is not None:
|
||||
return "{coin}**{amount}** per task.".format(
|
||||
coin=conf.emojis.coin,
|
||||
amount=data
|
||||
)
|
||||
t = ctx_translator.get().t
|
||||
formatted = t(_p(
|
||||
'guildset:task_reward|formatted',
|
||||
"{coin}**{amount}** per task."
|
||||
)).format(coin=conf.emojis.coin, amount=data)
|
||||
return formatted
|
||||
|
||||
class task_reward_limit(ModelData, IntegerSetting):
|
||||
setting_id = 'task_reward_limit'
|
||||
_set_cmd = 'configure tasklist'
|
||||
|
||||
_display_name = _p('guildset:task_reward_limit', "task_reward_limit")
|
||||
_desc = _p(
|
||||
@@ -76,6 +80,10 @@ class TasklistSettings(SettingGroup):
|
||||
"Maximum number of times in each 24h period that members will be rewarded "
|
||||
"for completing a task."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:task_reward_limit|accepts',
|
||||
"The maximum number of tasks to reward LC for per 24h."
|
||||
)
|
||||
_default = 10
|
||||
|
||||
_model = CoreData.Guild
|
||||
@@ -89,16 +97,15 @@ class TasklistSettings(SettingGroup):
|
||||
"Members will now be rewarded for task completion at most **{amount}** times per 24h."
|
||||
)).format(amount=self.data)
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
return '</configure tasklist:1038560947666694144>'
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id, data, **kwargs):
|
||||
if data is not None:
|
||||
return "`{number}` per 24 hours.".format(
|
||||
number=data
|
||||
)
|
||||
t = ctx_translator.get().t
|
||||
formatted = t(_p(
|
||||
'guildset:task_reward_limit|formatted',
|
||||
"`{number}` per 24 hours."
|
||||
)).format(number=data)
|
||||
return formatted
|
||||
|
||||
class tasklist_channels(ListData, ChannelListSetting):
|
||||
setting_id = 'tasklist_channels'
|
||||
@@ -113,6 +120,10 @@ class TasklistSettings(SettingGroup):
|
||||
"If set, members will only be able to open their tasklist in these channels.\n"
|
||||
"If a category is selected, this will allow all channels under that category."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:tasklist_channels|accepts',
|
||||
"Comma separated list of tasklist channel names or ids."
|
||||
)
|
||||
_default = None
|
||||
|
||||
_table_interface = TasklistData.channels
|
||||
@@ -122,14 +133,32 @@ class TasklistSettings(SettingGroup):
|
||||
|
||||
_cache = {}
|
||||
|
||||
@property
|
||||
def update_message(self):
|
||||
t = ctx_translator.get().t
|
||||
if self.data:
|
||||
resp = t(_p(
|
||||
'guildset:tasklist_channels|set_response|set',
|
||||
"Members may now open their tasklist in the following channels: {channels}"
|
||||
)).format(channels=self.formatted)
|
||||
else:
|
||||
resp = t(_p(
|
||||
'guildset:tasklist_channels|set_response|unset',
|
||||
"Members may now open their tasklist in any channel."
|
||||
))
|
||||
return resp
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
return "Channel selector below."
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:tasklist_channels|set_using',
|
||||
"Channel selector below."
|
||||
))
|
||||
|
||||
|
||||
class TasklistConfigUI(LeoUI):
|
||||
# TODO: Back option to global guild config
|
||||
# TODO: Cohesive edit
|
||||
# TODO: Migrate to ConfigUI
|
||||
_listening = {}
|
||||
setting_classes = (
|
||||
TasklistSettings.task_reward,
|
||||
@@ -286,6 +315,6 @@ class TasklistConfigUI(LeoUI):
|
||||
|
||||
|
||||
class TasklistDashboard(DashboardSection):
|
||||
section_name = _p('dash:tasklist|name', "Tasklist Configuration")
|
||||
section_name = _p('dash:tasklist|name', "Tasklist Configuration ({commands[configure tasklist]})")
|
||||
configui = TasklistConfigUI
|
||||
setting_classes = configui.setting_classes
|
||||
|
||||
@@ -33,6 +33,7 @@ class UserConfigSettings(SettingGroup):
|
||||
and several other components such as reminder times.
|
||||
"""
|
||||
setting_id = 'timezone'
|
||||
_set_cmd = 'my timezone'
|
||||
|
||||
_display_name = _p('userset:timezone', "timezone")
|
||||
_desc = _p(
|
||||
|
||||
@@ -14,7 +14,7 @@ from dateutil.parser import parse, ParserError
|
||||
from meta.context import ctx_bot
|
||||
from meta.errors import UserInputError
|
||||
from utils.lib import strfdur, parse_duration
|
||||
from babel import ctx_translator
|
||||
from babel.translator import ctx_translator, LazyStr
|
||||
|
||||
from .base import ParentID
|
||||
from .ui import InteractiveSetting, SettingWidget
|
||||
@@ -45,7 +45,7 @@ class StringSetting(InteractiveSetting[ParentID, str, str]):
|
||||
Default: True
|
||||
"""
|
||||
|
||||
_accepts = _p('settype:string|accepts', "Any text")
|
||||
_accepts = _p('settype:string|accepts', "Any Text")
|
||||
|
||||
_maxlen: int = 4000
|
||||
_quote: bool = True
|
||||
@@ -123,7 +123,7 @@ class ChannelSetting(Generic[ParentID, CT], InteractiveSetting[ParentID, int, CT
|
||||
List of guild channel types to accept.
|
||||
Default: []
|
||||
"""
|
||||
_accepts = _p('settype:channel|accepts', "Enter a channel name or id")
|
||||
_accepts = _p('settype:channel|accepts', "A channel name or id")
|
||||
|
||||
_selector_placeholder = "Select a Channel"
|
||||
channel_types: list[discord.ChannelType] = []
|
||||
@@ -151,8 +151,26 @@ class ChannelSetting(Generic[ParentID, CT], InteractiveSetting[ParentID, int, CT
|
||||
|
||||
@classmethod
|
||||
async def _parse_string(cls, parent_id, string: str, **kwargs):
|
||||
# TODO: Waiting on seeker utils.
|
||||
...
|
||||
if not string or string.lower() == 'none':
|
||||
return None
|
||||
|
||||
t = ctx_translator.get().t
|
||||
bot = ctx_bot.get()
|
||||
channel = None
|
||||
guild = bot.get_guild(parent_id)
|
||||
|
||||
if string.isdigit():
|
||||
maybe_id = int(string)
|
||||
channel = guild.get_channel(maybe_id)
|
||||
else:
|
||||
channel = next((channel for channel in guild.channels if channel.name.lower() == string.lower()), None)
|
||||
|
||||
if channel is None:
|
||||
raise UserInputError(t(_p(
|
||||
'settype:channel|parse|error:not_found',
|
||||
"Channel `{string}` could not be found in this guild!".format(string=string)
|
||||
)))
|
||||
return channel.id
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id, data, **kwargs):
|
||||
@@ -161,25 +179,11 @@ class ChannelSetting(Generic[ParentID, CT], InteractiveSetting[ParentID, int, CT
|
||||
"""
|
||||
if data:
|
||||
return "<#{}>".format(data)
|
||||
else:
|
||||
return "Not Set"
|
||||
|
||||
@property
|
||||
def input_formatted(self) -> str:
|
||||
"""
|
||||
Returns the channel name if possible, otherwise the id.
|
||||
"""
|
||||
if self._data is not None:
|
||||
channel = self.value
|
||||
if channel is not None:
|
||||
if isinstance(channel, discord.Object):
|
||||
return str(channel.id)
|
||||
else:
|
||||
return f"#{channel.name}"
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
return ""
|
||||
data = self._data
|
||||
return str(data) if data else ''
|
||||
|
||||
class Widget(SettingWidget['ChannelSetting']):
|
||||
def update_children(self):
|
||||
@@ -236,7 +240,7 @@ class MessageablelSetting(ChannelSetting):
|
||||
bot = ctx_bot.get()
|
||||
channel = bot.get_channel(data)
|
||||
if channel is None:
|
||||
channel = ctx.bot.get_partial_messageable(data, guild_id=parent_id)
|
||||
channel = bot.get_partial_messageable(data, guild_id=parent_id)
|
||||
return channel
|
||||
|
||||
|
||||
@@ -250,7 +254,7 @@ class RoleSetting(InteractiveSetting[ParentID, int, Union[discord.Role, discord.
|
||||
Placeholder to use in the Widget selector.
|
||||
Default: "Select a Role"
|
||||
"""
|
||||
_accepts = _p('settype:role|accepts', "Enter a role name or id")
|
||||
_accepts = _p('settype:role|accepts', "A role name or id")
|
||||
|
||||
_selector_placeholder = "Select a Role"
|
||||
|
||||
@@ -291,8 +295,26 @@ class RoleSetting(InteractiveSetting[ParentID, int, Union[discord.Role, discord.
|
||||
|
||||
@classmethod
|
||||
async def _parse_string(cls, parent_id, string: str, **kwargs):
|
||||
# TODO: Waiting on seeker utils.
|
||||
...
|
||||
if not string or string.lower() == 'none':
|
||||
return None
|
||||
|
||||
t = ctx_translator.get().t
|
||||
bot = ctx_bot.get()
|
||||
role = None
|
||||
guild = bot.get_guild(parent_id)
|
||||
|
||||
if string.isdigit():
|
||||
maybe_id = int(string)
|
||||
role = guild.get_role(maybe_id)
|
||||
else:
|
||||
role = next((role for role in guild.roles if role.name.lower() == string.lower()), None)
|
||||
|
||||
if role is None:
|
||||
raise UserInputError(t(_p(
|
||||
'settype:role|parse|error:not_found',
|
||||
"Role `{string}` could not be found in this guild!".format(string=string)
|
||||
)))
|
||||
return role.id
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id, data, **kwargs):
|
||||
@@ -306,20 +328,8 @@ class RoleSetting(InteractiveSetting[ParentID, int, Union[discord.Role, discord.
|
||||
|
||||
@property
|
||||
def input_formatted(self) -> str:
|
||||
"""
|
||||
Returns the role name if possible, otherwise the id.
|
||||
"""
|
||||
if self._data is not None:
|
||||
role = self.value
|
||||
if role is not None:
|
||||
if isinstance(role, discord.Object):
|
||||
return str(role.id)
|
||||
else:
|
||||
return f"@{role.name}"
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
return ""
|
||||
data = self._data
|
||||
return str(data) if data else ''
|
||||
|
||||
class Widget(SettingWidget['RoleSetting']):
|
||||
def update_children(self):
|
||||
@@ -367,33 +377,54 @@ class BoolSetting(InteractiveSetting[ParentID, bool, bool]):
|
||||
Default: {True: "On", False: "Off", None: "Not Set"}
|
||||
"""
|
||||
|
||||
_accepts = _p('settype:bool|accepts', "True/False")
|
||||
_accepts = _p('settype:bool|accepts', "Enabled/Disabled")
|
||||
|
||||
# Values that are accepted as truthy and falsey by the parser
|
||||
_truthy = {"yes", "true", "on", "enable", "enabled"}
|
||||
_falsey = {"no", "false", "off", "disable", "disabled"}
|
||||
_truthy = _p(
|
||||
'settype:bool|parse:truthy_values',
|
||||
"enabled|yes|true|on|enable|1"
|
||||
)
|
||||
_falsey = _p(
|
||||
'settype:bool|parse:falsey_values',
|
||||
'disabled|no|false|off|disable|0'
|
||||
)
|
||||
|
||||
# The user-friendly output strings to use for each value
|
||||
_outputs = {True: "On", False: "Off", None: "Not Set"}
|
||||
_outputs = {
|
||||
True: _p('settype:bool|output:true', "On"),
|
||||
False: _p('settype:bool|output:false', "Off"),
|
||||
None: _p('settype:bool|output:none', "Not Set"),
|
||||
}
|
||||
|
||||
# Button labels
|
||||
_true_button_args: dict[str, Any] = {}
|
||||
_false_button_args: dict[str, Any] = {}
|
||||
_reset_button_args: dict[str, Any] = {}
|
||||
|
||||
@classmethod
|
||||
def truthy_values(cls) -> set:
|
||||
t = ctx_translator.get().t
|
||||
return t(cls._truthy).lower().split('|')
|
||||
|
||||
@classmethod
|
||||
def falsey_values(cls) -> set:
|
||||
t = ctx_translator.get().t
|
||||
return t(cls._falsey).lower().split('|')
|
||||
|
||||
@property
|
||||
def input_formatted(self) -> str:
|
||||
"""
|
||||
Return the current data string.
|
||||
"""
|
||||
if self._data is not None:
|
||||
output = self._outputs[self._data]
|
||||
set = (self._falsey, self._truthy)[self._data]
|
||||
t = ctx_translator.get().t
|
||||
output = t(self._outputs[self._data])
|
||||
input_set = self.truthy_values() if self._data else self.falsey_values()
|
||||
|
||||
if output.lower() in set:
|
||||
if output.lower() in input_set:
|
||||
return output
|
||||
else:
|
||||
return next(iter(set))
|
||||
return next(iter(input_set))
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -419,9 +450,9 @@ class BoolSetting(InteractiveSetting[ParentID, bool, bool]):
|
||||
_userstr = string.lower()
|
||||
if not _userstr or _userstr == "none":
|
||||
return None
|
||||
if _userstr in cls._truthy:
|
||||
if _userstr in cls.truthy_values():
|
||||
return True
|
||||
elif _userstr in cls._falsey:
|
||||
elif _userstr in cls.falsey_values():
|
||||
return False
|
||||
else:
|
||||
raise UserInputError("Could not parse `{}` as a boolean.".format(string))
|
||||
@@ -431,7 +462,8 @@ class BoolSetting(InteractiveSetting[ParentID, bool, bool]):
|
||||
"""
|
||||
Use provided _outputs dictionary to format data.
|
||||
"""
|
||||
return cls._outputs[data]
|
||||
t = ctx_translator.get().t
|
||||
return t(cls._outputs[data])
|
||||
|
||||
class Widget(SettingWidget['BoolSetting']):
|
||||
def update_children(self):
|
||||
@@ -676,8 +708,7 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
||||
# TODO Definitely need autocomplete here
|
||||
_accepts = _p(
|
||||
'settype:timezone|accepts',
|
||||
"A timezone name from [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) "
|
||||
"(e.g. `Europe/London`)."
|
||||
"A timezone name from the 'tz database' (e.g. 'Europe/London')"
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -739,6 +770,23 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
|
||||
) from None
|
||||
return str(timezone)
|
||||
|
||||
def _desc_table(self) -> list[str]:
|
||||
translator = ctx_translator.get()
|
||||
t = translator.t
|
||||
|
||||
lines = super()._desc_table()
|
||||
lines.append((
|
||||
t(_p(
|
||||
'settype:timezone|summary_table|field:supported|key',
|
||||
"Supported"
|
||||
)),
|
||||
t(_p(
|
||||
'settype:timezone|summary_table|field:supported|value',
|
||||
"Any timezone from the [tz database]({link})."
|
||||
)).format(link="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones")
|
||||
))
|
||||
return lines
|
||||
|
||||
@classmethod
|
||||
async def parse_acmpl(cls, interaction: discord.Interaction, partial: str):
|
||||
bot = interaction.client
|
||||
@@ -794,7 +842,7 @@ class TimestampSetting(InteractiveSetting[ParentID, str, dt.datetime]):
|
||||
"""
|
||||
_accepts = _p(
|
||||
'settype:timestamp|accepts',
|
||||
"A timestamp in the form yyyy-mm-dd HH:MM"
|
||||
"A timestamp in the form YYYY-MM-DD HH:MM"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -812,22 +860,24 @@ class TimestampSetting(InteractiveSetting[ParentID, str, dt.datetime]):
|
||||
ts = None
|
||||
else:
|
||||
local_tz = await cls._timezone_from_id(parent_id, **kwargs)
|
||||
default = dt.datetime.now(tz=local_tz).replace(
|
||||
now = dt.datetime.now(tz=local_tz)
|
||||
default = now.replace(
|
||||
hour=0, minute=0,
|
||||
second=0, microsecond=0
|
||||
)
|
||||
try:
|
||||
ts = parse(string, fuzzy=True, default=default)
|
||||
except ParserError:
|
||||
# TOLOCALISE:
|
||||
raise UserInputError("Invalid date string passed")
|
||||
t = ctx_translator.get().t
|
||||
raise UserInputError(t(_p(
|
||||
'settype:timestamp|parse|error:invalid',
|
||||
"Could not parse `{provided}` as a timestamp. Please use `YYYY-MM-DD HH:MM` format."
|
||||
)))
|
||||
return ts
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id: ParentID, data, **kwargs):
|
||||
if data is None:
|
||||
return "Not Set"
|
||||
else:
|
||||
if data is not None:
|
||||
return "<t:{}>".format(int(data.timestamp()))
|
||||
|
||||
@classmethod
|
||||
@@ -839,6 +889,41 @@ class TimestampSetting(InteractiveSetting[ParentID, str, dt.datetime]):
|
||||
"""
|
||||
return pytz.UTC
|
||||
|
||||
@property
|
||||
def input_formatted(self) -> str:
|
||||
if self._data:
|
||||
formatted = self._data.strftime('%Y-%M-%d %H:%M')
|
||||
else:
|
||||
formatted = ''
|
||||
return formatted
|
||||
|
||||
|
||||
class RawSetting(InteractiveSetting[ParentID, Any, Any]):
|
||||
"""
|
||||
Basic implementation of an interactive setting with identical value and data type.
|
||||
"""
|
||||
_accepts = _p('settype:raw|accepts', "Anything")
|
||||
|
||||
@property
|
||||
def input_formatted(self) -> str:
|
||||
return str(self._data) if self._data is not None else ''
|
||||
|
||||
@classmethod
|
||||
def _data_from_value(cls, parent_id, value, **kwargs):
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _data_to_value(cls, parent_id, data, **kwargs):
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
async def _parse_string(cls, parent_id: ParentID, string: str, **kwargs):
|
||||
return string
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id: ParentID, data, **kwargs):
|
||||
return str(data) if data is not None else None
|
||||
|
||||
|
||||
ET = TypeVar('ET', bound='Enum')
|
||||
|
||||
@@ -866,8 +951,9 @@ class EnumSetting(InteractiveSetting[ParentID, ET, ET]):
|
||||
"""
|
||||
|
||||
_enum: Type[ET]
|
||||
_outputs: dict[ET, str]
|
||||
_inputs: dict[str, ET]
|
||||
_outputs: dict[ET, LazyStr]
|
||||
_input_patterns: dict[ET: LazyStr]
|
||||
_input_formatted: dict[ET: LazyStr]
|
||||
|
||||
_accepts = _p('settype:enum|accepts', "A valid option.")
|
||||
|
||||
@@ -877,8 +963,9 @@ class EnumSetting(InteractiveSetting[ParentID, ET, ET]):
|
||||
Return the output string for the current data.
|
||||
This assumes the output strings are accepted as inputs!
|
||||
"""
|
||||
t = ctx_translator.get().t
|
||||
if self._data is not None:
|
||||
return self._outputs[self._data]
|
||||
return t(self._input_formatted[self._data])
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -901,23 +988,39 @@ class EnumSetting(InteractiveSetting[ParentID, ET, ET]):
|
||||
"""
|
||||
Parse the user input into an enum item.
|
||||
"""
|
||||
# TODO: Another selection case.
|
||||
if not string:
|
||||
return None
|
||||
|
||||
string = string.lower()
|
||||
if string not in cls._inputs:
|
||||
raise UserInputError("Invalid choice!")
|
||||
return cls._inputs[string]
|
||||
t = ctx_translator.get().t
|
||||
|
||||
found = None
|
||||
for enumitem, pattern in cls._input_patterns.items():
|
||||
item_keys = set(t(pattern).lower().split('|'))
|
||||
if string in item_keys:
|
||||
found = enumitem
|
||||
break
|
||||
|
||||
if not found:
|
||||
raise UserInputError(
|
||||
t(_p(
|
||||
'settype:enum|parse|error:not_found',
|
||||
"`{provided}` is not a valid option!"
|
||||
)).format(provided=string)
|
||||
)
|
||||
|
||||
return found
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, parent_id: ParentID, data, **kwargs):
|
||||
"""
|
||||
Format the enum using the provided output map.
|
||||
"""
|
||||
t = ctx_translator.get().t
|
||||
if data is not None:
|
||||
if data not in cls._outputs:
|
||||
raise ValueError(f"Enum item {data} unmapped.")
|
||||
return cls._outputs[data]
|
||||
return t(cls._outputs[data])
|
||||
|
||||
|
||||
class DurationSetting(InteractiveSetting[ParentID, int, int]):
|
||||
@@ -1110,9 +1213,7 @@ class ListSetting:
|
||||
"""
|
||||
Format the list by adding `,` between each formatted item
|
||||
"""
|
||||
if not data:
|
||||
return 'Not Set'
|
||||
else:
|
||||
if data:
|
||||
formatted_items = []
|
||||
for item in data:
|
||||
formatted_item = cls._setting._format_data(id, item)
|
||||
@@ -1142,8 +1243,7 @@ class ChannelListSetting(ListSetting, InteractiveSetting):
|
||||
"""
|
||||
_accepts = _p(
|
||||
'settype:channel_list|accepts',
|
||||
"Comma separated list of channel mentions/ids/names. Use `None` to unset. "
|
||||
"Write `--add` or `--remove` to add or remove channels."
|
||||
"Comma separated list of channel ids."
|
||||
)
|
||||
_setting = ChannelSetting
|
||||
|
||||
@@ -1154,8 +1254,7 @@ class RoleListSetting(ListSetting, InteractiveSetting):
|
||||
"""
|
||||
_accepts = _p(
|
||||
'settype:role_list|accepts',
|
||||
"Comma separated list of role mentions/ids/names. Use `None` to unset. "
|
||||
"Write `--add` or `--remove` to add or remove roles."
|
||||
'Comma separated list of role ids.'
|
||||
)
|
||||
_setting = RoleSetting
|
||||
|
||||
@@ -1171,8 +1270,7 @@ class StringListSetting(InteractiveSetting, ListSetting):
|
||||
"""
|
||||
_accepts = _p(
|
||||
'settype:stringlist|accepts',
|
||||
"Comma separated list of strings. Use `None` to unset. "
|
||||
"Write `--add` or `--remove` to add or remove strings."
|
||||
'Comma separated strings.'
|
||||
)
|
||||
_setting = StringSetting
|
||||
|
||||
@@ -1183,9 +1281,7 @@ class GuildIDListSetting(InteractiveSetting, ListSetting):
|
||||
"""
|
||||
_accepts = _p(
|
||||
'settype:guildidlist|accepts',
|
||||
"Comma separated list of guild ids. Use `None` to unset. "
|
||||
"Write `--add` or `--remove` to add or remove ids. "
|
||||
"The provided ids are not verified in any way."
|
||||
'Comma separated list of guild ids.'
|
||||
)
|
||||
|
||||
_setting = GuildIDSetting
|
||||
|
||||
@@ -15,6 +15,9 @@ from meta.context import ctx_bot
|
||||
from babel.translator import ctx_translator, LazyStr
|
||||
|
||||
from .base import BaseSetting, ParentID, SettingData, SettingValue
|
||||
from . import babel
|
||||
|
||||
_p = babel._p
|
||||
|
||||
|
||||
ST = TypeVar('ST', bound='InteractiveSetting')
|
||||
@@ -172,6 +175,8 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]):
|
||||
_desc: LazyStr # User readable brief description of the setting
|
||||
_long_desc: LazyStr # User readable long description of the setting
|
||||
_accepts: LazyStr # User readable description of the acceptable values
|
||||
_set_cmd: str = None
|
||||
_notset_str: LazyStr = _p('setting|formatted|notset', "Not Set")
|
||||
_virtual: bool = False # Whether the setting should be hidden from tables and dashboards
|
||||
_required: bool = False
|
||||
|
||||
@@ -305,29 +310,61 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]):
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
return None
|
||||
if self._set_cmd is not None:
|
||||
bot = ctx_bot.get()
|
||||
if bot:
|
||||
return bot.core.mention_cmd(self._set_cmd)
|
||||
else:
|
||||
return f"`/{self._set_cmd}`"
|
||||
|
||||
@property
|
||||
def notset_str(self):
|
||||
t = ctx_translator.get().t
|
||||
return t(self._notset_str)
|
||||
|
||||
@property
|
||||
def embed(self):
|
||||
"""
|
||||
Returns a full embed describing this setting.
|
||||
"""
|
||||
t = ctx_translator.get().t
|
||||
embed = discord.Embed(
|
||||
title="Configuration options for `{}`".format(self.display_name),
|
||||
title=t(_p(
|
||||
'setting|summary_embed|title',
|
||||
"Configuration options for `{name}`"
|
||||
)).format(name=self.display_name),
|
||||
)
|
||||
embed.description = "{}\n{}".format(self.long_desc.format(self=self), self.desc_table)
|
||||
return embed
|
||||
|
||||
@property
|
||||
def desc_table(self):
|
||||
def _desc_table(self) -> list[str]:
|
||||
t = ctx_translator.get().t
|
||||
lines = []
|
||||
lines.append(('Currently', self.formatted or "Not Set"))
|
||||
if (default := self.default) is not None:
|
||||
lines.append(('By Default', self._format_data(self.parent_id, default) or "No Default"))
|
||||
if (set_str := self.set_str) is not None:
|
||||
lines.append(('Set Using', set_str))
|
||||
|
||||
return '\n'.join(tabulate(*lines))
|
||||
# Currently line
|
||||
lines.append((
|
||||
t(_p('setting|summary_table|field:currently|key', "Currently")),
|
||||
self.formatted or self.notset_str
|
||||
))
|
||||
|
||||
# Default line
|
||||
if (default := self.default) is not None:
|
||||
lines.append((
|
||||
t(_p('setting|summary_table|field:default|key', "By Default")),
|
||||
self._format_data(self.parent_id, default) or 'None'
|
||||
))
|
||||
|
||||
# Set using line
|
||||
if (set_str := self.set_str) is not None:
|
||||
lines.append((
|
||||
t(_p('setting|summary_table|field:set|key', "Set Using")),
|
||||
set_str
|
||||
))
|
||||
return lines
|
||||
|
||||
@property
|
||||
def desc_table(self) -> str:
|
||||
return '\n'.join(tabulate(*self._desc_table()))
|
||||
|
||||
@property
|
||||
def input_field(self) -> TextInput:
|
||||
@@ -366,7 +403,7 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]):
|
||||
Default user-readable form of the setting.
|
||||
Should be a short single line.
|
||||
"""
|
||||
return self._format_data(self.parent_id, self.data, **self.kwargs)
|
||||
return self._format_data(self.parent_id, self.data, **self.kwargs) or self.notset_str
|
||||
|
||||
@property
|
||||
def input_formatted(self) -> str:
|
||||
|
||||
@@ -28,6 +28,7 @@ class TextTrackerSettings(SettingGroup):
|
||||
"""
|
||||
class XPPerPeriod(ModelData, IntegerSetting):
|
||||
setting_id = 'xp_per_period'
|
||||
_set_cmd = 'configure message_exp'
|
||||
|
||||
_display_name = _p('guildset:xp_per_period', "xp_per_5min")
|
||||
_desc = _p(
|
||||
@@ -39,6 +40,10 @@ class TextTrackerSettings(SettingGroup):
|
||||
"Amount of message XP to give members for each 5 minute period in which they are active (send a message). "
|
||||
"Note that this XP is only given *once* per period."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:xp_per_period|accepts',
|
||||
"Number of message XP to reward per 5 minute active period."
|
||||
)
|
||||
_default = 101 # TODO: Make a dynamic default based on the global setting?
|
||||
|
||||
_model = CoreData.Guild
|
||||
@@ -55,6 +60,7 @@ class TextTrackerSettings(SettingGroup):
|
||||
|
||||
class WordXP(ModelData, IntegerSetting):
|
||||
setting_id = 'word_xp'
|
||||
_set_cmd = 'configure message_exp'
|
||||
|
||||
_display_name = _p('guildset:word_xp', "xp_per_100words")
|
||||
_desc = _p(
|
||||
@@ -66,6 +72,10 @@ class TextTrackerSettings(SettingGroup):
|
||||
"Amount of message XP to be given (additionally to the XP per period) for each hundred words. "
|
||||
"Useful for rewarding communication."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:word_xp|accepts',
|
||||
"Number of XP to reward per hundred words sent."
|
||||
)
|
||||
_default = 50
|
||||
|
||||
_model = CoreData.Guild
|
||||
@@ -92,6 +102,14 @@ class TextTrackerSettings(SettingGroup):
|
||||
"Messages sent in these channels will not count towards a member's message XP. "
|
||||
"If a category is selected, then all channels under the category will also be untracked."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:untracked_text_channels|accepts',
|
||||
"Comma separated list of untracked text channel names or ids."
|
||||
)
|
||||
_notset_str = _p(
|
||||
'guildset:untracked_text_channels|notset',
|
||||
"Not Set (all text channels will be tracked.)"
|
||||
)
|
||||
|
||||
_default = None
|
||||
_table_interface = TextTrackerData.untracked_channels
|
||||
@@ -101,6 +119,29 @@ class TextTrackerSettings(SettingGroup):
|
||||
|
||||
_cache = {}
|
||||
|
||||
@property
|
||||
def update_message(self):
|
||||
t = ctx_translator.get().t
|
||||
if self.data:
|
||||
resp = t(_p(
|
||||
'guildset:untracked_text_channels|set_response|set',
|
||||
"Messages in or under the following channels will be ignored: {channels}"
|
||||
)).format(channels=self.formatted)
|
||||
else:
|
||||
resp = t(_p(
|
||||
'guildset:untracked_text_channels|set_response|notset',
|
||||
"Message XP will now be tracked in every channel."
|
||||
))
|
||||
return resp
|
||||
|
||||
@property
|
||||
def set_str(self) -> str:
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:untracked_text_channels|set_using',
|
||||
"Channel selector below"
|
||||
))
|
||||
|
||||
@classmethod
|
||||
@log_wrap(action='Cache Untracked Text Channels')
|
||||
async def setup(cls, bot):
|
||||
@@ -127,6 +168,7 @@ class TextTrackerGlobalSettings(SettingGroup):
|
||||
"""
|
||||
class XPPerPeriod(ModelData, IntegerSetting):
|
||||
setting_id = 'xp_per_period'
|
||||
_set_cmd = 'leo configure experience_rates'
|
||||
|
||||
_display_name = _p('botset:xp_per_period', "xp_per_5min")
|
||||
_desc = _p(
|
||||
@@ -139,6 +181,10 @@ class TextTrackerGlobalSettings(SettingGroup):
|
||||
"for each 5 minute period in which they are active (send a message). "
|
||||
"Note that this XP is only given *once* per period."
|
||||
)
|
||||
_accepts = _p(
|
||||
'botset:xp_per_period|accepts',
|
||||
"Number of message XP to reward per 5 minute active period."
|
||||
)
|
||||
_default = 101
|
||||
|
||||
_model = TextTrackerData.BotConfigText
|
||||
@@ -155,6 +201,7 @@ class TextTrackerGlobalSettings(SettingGroup):
|
||||
|
||||
class WordXP(ModelData, IntegerSetting):
|
||||
setting_id = 'word_xp'
|
||||
_set_cmd = 'leo configure experience_rates'
|
||||
|
||||
_display_name = _p('botset:word_xp', "xp_per_100words")
|
||||
_desc = _p(
|
||||
@@ -166,6 +213,10 @@ class TextTrackerGlobalSettings(SettingGroup):
|
||||
"Amount of global message XP to be given (additionally to the XP per period) for each hundred words. "
|
||||
"Useful for rewarding communication."
|
||||
)
|
||||
_accepts = _p(
|
||||
'botset:word_xp|accepts',
|
||||
"Number of XP to reward per hundred words sent."
|
||||
)
|
||||
_default = 50
|
||||
|
||||
_model = TextTrackerData.BotConfigText
|
||||
|
||||
@@ -86,7 +86,7 @@ class TextTrackerConfigUI(ConfigUI):
|
||||
class TextTrackerDashboard(DashboardSection):
|
||||
section_name = _p(
|
||||
'dash:text_tracking|title',
|
||||
"Message XP configuration",
|
||||
"Message XP configuration ({commands[configure message_exp]})",
|
||||
)
|
||||
configui = TextTrackerConfigUI
|
||||
setting_classes = configui.setting_classes
|
||||
|
||||
@@ -33,9 +33,9 @@ _p = babel._p
|
||||
|
||||
class VoiceTrackerSettings(SettingGroup):
|
||||
class UntrackedChannels(ListData, ChannelListSetting):
|
||||
# TODO: Factor out into combined tracking settings?
|
||||
setting_id = 'untracked_channels'
|
||||
_event = 'guild_setting_update_untracked_channels'
|
||||
_set_cmd = 'configure voice_rewards'
|
||||
|
||||
_display_name = _p('guildset:untracked_channels', "untracked_channels")
|
||||
_desc = _p(
|
||||
@@ -47,6 +47,14 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
"Activity in these channels will not count towards a member's statistics. "
|
||||
"If a category is selected, all channels under the category will be untracked."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:untracked_channels|accepts',
|
||||
"Comma separated list of untracked channel name/ids."
|
||||
)
|
||||
_notset_str = _p(
|
||||
'guildset:untracked_channels|notset',
|
||||
"Not Set (all voice channels will be tracked.)"
|
||||
)
|
||||
|
||||
_default = None
|
||||
|
||||
@@ -68,12 +76,19 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
@property
|
||||
def update_message(self):
|
||||
t = ctx_translator.get().t
|
||||
return t(_p(
|
||||
'guildset:untracked_channels|response',
|
||||
"Activity in the following channels will now be ignored: {channels}"
|
||||
)).format(
|
||||
channels=self.formatted
|
||||
)
|
||||
if self.data:
|
||||
resp = t(_p(
|
||||
'guildset:untracked_channels|set_response|set',
|
||||
"Activity in the following channels will now be ignored: {channels}"
|
||||
)).format(
|
||||
channels=self.formatted
|
||||
)
|
||||
else:
|
||||
resp = t(_p(
|
||||
'guildset:untracked_channels|set_response|unset',
|
||||
"All voice channels will now be tracked."
|
||||
))
|
||||
return resp
|
||||
|
||||
@classmethod
|
||||
@log_wrap(action='Cache Untracked Channels')
|
||||
@@ -97,6 +112,7 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
class HourlyReward(ModelData, IntegerSetting):
|
||||
setting_id = 'hourly_reward'
|
||||
_event = 'guild_setting_update_hourly_reward'
|
||||
_set_cmd = 'configure voice_rewards'
|
||||
|
||||
_display_name = _p('guildset:hourly_reward', "hourly_reward")
|
||||
_desc = _p(
|
||||
@@ -107,6 +123,10 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
'guildset:hourly_reward|mode:voice|long_desc',
|
||||
"Number of LionCoins to each member per hour that they stay in a tracked voice channel."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:hourly_reward|accepts',
|
||||
"Number of coins to reward per hour in voice."
|
||||
)
|
||||
|
||||
_default = 50
|
||||
_min = 0
|
||||
@@ -127,29 +147,10 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
amount=data
|
||||
)
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
# TODO: Dynamic retrieval of command id
|
||||
return '</configure voice_tracking:1038560947666694144>'
|
||||
|
||||
class HourlyReward_Voice(HourlyReward):
|
||||
"""
|
||||
Voice-mode specialised version of HourlyReward
|
||||
"""
|
||||
_desc = _p(
|
||||
'guildset:hourly_reward|mode:voice|desc',
|
||||
"LionCoins given per hour in a voice channel."
|
||||
)
|
||||
_long_desc = _p(
|
||||
'guildset:hourly_reward|mode:voice|long_desc',
|
||||
"Number of LionCoins rewarded to each member per hour that they stay in a tracked voice channel."
|
||||
)
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
# TODO: Dynamic retrieval of command id
|
||||
return '</configure voice_tracking:1038560947666694144>'
|
||||
|
||||
@property
|
||||
def update_message(self):
|
||||
t = ctx_translator.get().t
|
||||
@@ -191,6 +192,7 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
"""
|
||||
setting_id = 'hourly_live_bonus'
|
||||
_event = 'guild_setting_update_hourly_live_bonus'
|
||||
_set_cmd = 'configure voice_rewards'
|
||||
|
||||
_display_name = _p('guildset:hourly_live_bonus', "hourly_live_bonus")
|
||||
_desc = _p(
|
||||
@@ -203,6 +205,10 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
"When a member streams or video-chats in a channel they will be given this bonus *additionally* "
|
||||
"to the `hourly_reward`."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:hourly_live_bonus|accepts',
|
||||
"Number of bonus coins to reward per hour when live."
|
||||
)
|
||||
|
||||
_default = 150
|
||||
_min = 0
|
||||
@@ -223,11 +229,6 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
amount=data
|
||||
)
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
# TODO: Dynamic retrieval of command id
|
||||
return '</configure voice_tracking:1038560947666694144>'
|
||||
|
||||
@property
|
||||
def update_message(self):
|
||||
t = ctx_translator.get().t
|
||||
@@ -242,6 +243,7 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
class DailyVoiceCap(ModelData, DurationSetting):
|
||||
setting_id = 'daily_voice_cap'
|
||||
_event = 'guild_setting_update_daily_voice_cap'
|
||||
_set_cmd = 'configure voice_rewards'
|
||||
|
||||
_display_name = _p('guildset:daily_voice_cap', "daily_voice_cap")
|
||||
_desc = _p(
|
||||
@@ -254,6 +256,10 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
"Tracking will resume at the start of the next day. "
|
||||
"The start of the day is determined by the configured guild timezone."
|
||||
)
|
||||
_accepts = _p(
|
||||
'guildset:daily_voice_cap|accepts',
|
||||
"The maximum number of voice hours to track per day."
|
||||
)
|
||||
|
||||
_default = 16 * 60 * 60
|
||||
_default_multiplier = 60 * 60
|
||||
@@ -263,11 +269,6 @@ class VoiceTrackerSettings(SettingGroup):
|
||||
_model = CoreData.Guild
|
||||
_column = CoreData.Guild.daily_study_cap.name
|
||||
|
||||
@property
|
||||
def set_str(self):
|
||||
# TODO: Dynamic retrieval of command id
|
||||
return '</configure voice_tracking:1038560947666694144>'
|
||||
|
||||
@property
|
||||
def update_message(self):
|
||||
t = ctx_translator.get().t
|
||||
@@ -524,7 +525,7 @@ class VoiceTrackerConfigUI(ConfigUI):
|
||||
class VoiceTrackerDashboard(DashboardSection):
|
||||
section_name = _p(
|
||||
'dash:voice_tracker|title',
|
||||
"Voice Tracker Configuration"
|
||||
"Voice Tracker Configuration ({commands[configure voice_rewards]})"
|
||||
)
|
||||
configui = VoiceTrackerConfigUI
|
||||
setting_classes = configui.setting_classes
|
||||
|
||||
@@ -138,7 +138,7 @@ class MessageArgs:
|
||||
def tabulate(
|
||||
*fields: tuple[str, str],
|
||||
row_format: str = "`{invis}{key:<{pad}}{colon}`\t{value}",
|
||||
sub_format: str = "`{invis:<{pad}}{invis}`\t{value}",
|
||||
sub_format: str = "`{invis:<{pad}}{colon}`\t{value}",
|
||||
colon: str = ':',
|
||||
invis: str = "",
|
||||
**args
|
||||
@@ -189,6 +189,7 @@ def tabulate(
|
||||
sub_line = sub_format.format(
|
||||
invis=invis,
|
||||
pad=max_len + len(colon),
|
||||
colon=colon,
|
||||
value=line,
|
||||
**args
|
||||
)
|
||||
|
||||
@@ -83,6 +83,8 @@ class ConfigUI(LeoUI):
|
||||
t = ctx_translator.get().t
|
||||
instances = self.instances
|
||||
items = [setting.input_field for setting in instances]
|
||||
# Filter out settings which don't have input fields
|
||||
items = [item for item in items if item]
|
||||
strings = [item.value for item in items]
|
||||
modal = ConfigEditor(*items, title=t(self.edit_modal_title))
|
||||
|
||||
@@ -126,7 +128,7 @@ class ConfigUI(LeoUI):
|
||||
t = ctx_translator.get().t
|
||||
self.edit_button.label = t(_p(
|
||||
'ui:configui|button:edit|label',
|
||||
"Bulk Edit"
|
||||
"Edit"
|
||||
))
|
||||
|
||||
@button(emoji=conf.emojis.cancel, style=ButtonStyle.red)
|
||||
@@ -287,7 +289,7 @@ class DashboardSection:
|
||||
# TODO: Header/description field
|
||||
table = self.make_table()
|
||||
page.add_field(
|
||||
name=t(self.section_name),
|
||||
name=t(self.section_name).format(bot=self.bot, commands=self.bot.core.mention_cache),
|
||||
value=table,
|
||||
inline=False
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user