rewrite: Setting input strings and localisation.

This commit is contained in:
2023-06-06 12:57:29 +03:00
parent 809cada228
commit e1a23695ee
29 changed files with 823 additions and 236 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
src/modules/test/*
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]

View File

@@ -136,7 +136,7 @@ class BabelCog(LionCog):
if language: if language:
lang_data = await lang_setting._parse_string(ctx.guild.id, language) lang_data = await lang_setting._parse_string(ctx.guild.id, language)
if force_language is not None: 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): 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! # Setting force without having a language!
@@ -204,12 +204,8 @@ class BabelCog(LionCog):
new_data = await setting._parse_string(ctx.author.id, language) new_data = await setting._parse_string(ctx.author.id, language)
await setting.interactive_set(new_data, ctx.interaction, ephemeral=True) await setting.interactive_set(new_data, ctx.interaction, ephemeral=True)
else: else:
embed = setting.embed
if setting.value: if setting.value:
desc = t(_p(
'cmd:userconfig_language|response:set',
"Your preferred language is currently set to {language}"
)).format(language=setting.formatted)
@AButton( @AButton(
label=t(_p('cmd:userconfig_language|button:reset|label', "Reset")), label=t(_p('cmd:userconfig_language|button:reset|label', "Reset")),
style=ButtonStyle.red style=ButtonStyle.red
@@ -220,15 +216,7 @@ class BabelCog(LionCog):
view = AsComponents(reset_button) view = AsComponents(reset_button)
else: else:
desc = t(_p(
'cmd:userconfig_language|response:unset',
"You have not set a preferred language!"
))
view = None view = None
embed = discord.Embed(
colour=discord.Colour.orange(),
description=desc
)
await ctx.reply(embed=embed, ephemeral=True, view=view) await ctx.reply(embed=embed, ephemeral=True, view=view)
@userconfig_language_cmd.autocomplete('language') @userconfig_language_cmd.autocomplete('language')

View File

@@ -4,6 +4,7 @@ from settings.setting_types import StringSetting, BoolSetting
from settings.groups import SettingGroup from settings.groups import SettingGroup
from meta.errors import UserInputError from meta.errors import UserInputError
from meta.context import ctx_bot
from core.data import CoreData from core.data import CoreData
from .translator import ctx_translator from .translator import ctx_translator
@@ -17,11 +18,30 @@ class LocaleSetting(StringSetting):
""" """
Base class describing a LocaleSetting. 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 @classmethod
def _format_data(cls, parent_id, data, **kwargs): def _format_data(cls, parent_id, data, **kwargs):
t = ctx_translator.get().t t = ctx_translator.get().t
if data is None: if data is None:
formatted = t(_p('set_type:locale|formatted:unset', "Unset")) formatted = t(_p('settype:locale|formatted:unset', "Unset"))
else: else:
name = locale_names.get(data, None) name = locale_names.get(data, None)
if name: if name:
@@ -37,7 +57,7 @@ class LocaleSetting(StringSetting):
lang = string[:20] lang = string[:20]
raise UserInputError( raise UserInputError(
translator.t( 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) ).format(lang=lang)
) )
return string return string
@@ -54,6 +74,11 @@ class LocaleSettings(SettingGroup):
_display_name = _p('userset:locale', 'language') _display_name = _p('userset:locale', 'language')
_desc = _p('userset:locale|desc', "Your preferred language for interacting with me.") _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 _model = CoreData.User
_column = CoreData.User.locale.name _column = CoreData.User.locale.name
@@ -68,6 +93,12 @@ class LocaleSettings(SettingGroup):
lang=self.formatted 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): class ForceLocale(ModelData, BoolSetting):
""" """
Guild configuration for whether to force usage of the guild locale. 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." "I will now allow the members to set their own language here."
)) ))
@classmethod @property
def _format_data(cls, parent_id, data, **kwargs): def set_str(self):
t = ctx_translator.get().t bot = ctx_bot.get()
return t(cls._outputs[data]) if bot:
return bot.core.mention_cmd('configure language')
class GuildLocale(ModelData, LocaleSetting): 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( return t(_p('guildset:locale|response', "You have set the guild language to {lang}.")).format(
lang=self.formatted lang=self.formatted
) )
@property
def set_str(self):
bot = ctx_bot.get()
if bot:
return bot.core.mention_cmd('configure language')

View File

@@ -80,7 +80,7 @@ class LocaleSettingUI(ConfigUI):
class LocaleDashboard(DashboardSection): class LocaleDashboard(DashboardSection):
section_name = _p( section_name = _p(
'dash:locale|title', 'dash:locale|title',
"Server Language Configuration" "Server Language Configuration ({commands[configure language]})"
) )
configui = LocaleSettingUI configui = LocaleSettingUI
setting_classes = LocaleSettingUI.setting_classes setting_classes = LocaleSettingUI.setting_classes

View File

@@ -1,4 +1,5 @@
from typing import Optional from typing import Optional
from collections import defaultdict
import discord import discord
import discord.app_commands as appcmd import discord.app_commands as appcmd
@@ -17,6 +18,15 @@ from .lion_member import MemberConfig
from .lion_user import UserConfig 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): class CoreCog(LionCog):
def __init__(self, bot: LionBot): def __init__(self, bot: LionBot):
self.bot = bot self.bot = bot
@@ -43,7 +53,7 @@ class CoreCog(LionCog):
self.app_cmd_cache: list[discord.app_commands.AppCommand] = [] self.app_cmd_cache: list[discord.app_commands.AppCommand] = []
self.cmd_name_cache: dict[str, 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): async def cog_load(self):
# Fetch (and possibly create) core data rows. # 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) self.mention_cache = self._mention_cache_from(self.app_cmd_cache)
def _mention_cache_from(self, cmds: list[appcmd.AppCommand | appcmd.AppCommandGroup]): def _mention_cache_from(self, cmds: list[appcmd.AppCommand | appcmd.AppCommandGroup]):
cache = {} cache = keydefaultdict(self.mention_cmd)
for cmd in cmds: for cmd in cmds:
cache[cmd.qualified_name if isinstance(cmd, appcmd.AppCommandGroup) else cmd.name] = cmd.mention 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)] subcommands = [option for option in cmd.options if isinstance(option, appcmd.AppCommandGroup)]

View File

@@ -141,7 +141,10 @@ class GuildDashboard(BasePager):
for i, page in enumerate(self.pages): for i, page in enumerate(self.pages):
for j, section in enumerate(page): for j, section in enumerate(page):
option = SelectOption( 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) value=str(i * 10 + j)
) )
options.append(option) options.append(option)

View File

@@ -11,7 +11,7 @@ import discord
from discord.ext import commands as cmds from discord.ext import commands as cmds
from discord import app_commands as appcmds 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 meta.errors import UserInputError
from wards import low_management from wards import low_management
from settings import ModelData from settings import ModelData
@@ -57,7 +57,6 @@ class GeneralSettings(SettingGroup):
@property @property
def update_message(self): def update_message(self):
t = ctx_translator.get().t t = ctx_translator.get().t
# TODO: update_message can state time in current timezone
return t(_p( return t(_p(
'guildset:timezone|response', 'guildset:timezone|response',
"The guild timezone has been set to `{timezone}`." "The guild timezone has been set to `{timezone}`."
@@ -65,8 +64,8 @@ class GeneralSettings(SettingGroup):
@property @property
def set_str(self): def set_str(self):
# TODO bot = ctx_bot.get()
return '</configure general:1038560947666694144>' return bot.core.mention_cmd('configure general') if bot else None
class GeneralSettingsCog(LionCog): class GeneralSettingsCog(LionCog):

View File

@@ -787,9 +787,23 @@ class Economy(LionCog):
"Configure LionCoin Economy" "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) @cmds.check(low_management)
async def configure_economy(self, ctx: LionContext, 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): coins_per_xp: Optional[appcmds.Range[int, 0, 2**15]] = None):
t = self.bot.translator.t t = self.bot.translator.t
if not ctx.interaction: if not ctx.interaction:
@@ -802,7 +816,7 @@ class Economy(LionCog):
modified = [] modified = []
if allow_transfers is not None: 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() await setting_allow_transfers.write()
modified.append(setting_allow_transfers) modified.append(setting_allow_transfers)
if coins_per_xp is not None: if coins_per_xp is not None:

View File

@@ -10,6 +10,7 @@ from settings.groups import SettingGroup
from settings.data import ModelData, ListData from settings.data import ModelData, ListData
from settings.setting_types import ChannelListSetting, IntegerSetting, BoolSetting from settings.setting_types import ChannelListSetting, IntegerSetting, BoolSetting
from meta.context import ctx_bot
from meta.config import conf from meta.config import conf
from meta.sharding import THIS_SHARD from meta.sharding import THIS_SHARD
from meta.logger import log_wrap from meta.logger import log_wrap
@@ -40,6 +41,10 @@ class EconomySettings(SettingGroup):
'guildset:coins_per_xp|long_desc', 'guildset:coins_per_xp|long_desc',
"Members will be rewarded with this many LionCoins for every 100 XP they earn." "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! # This default needs to dynamically depend on the guild mode!
_default = 50 _default = 50
@@ -54,6 +59,11 @@ class EconomySettings(SettingGroup):
"For every **100** XP they earn, members will now be given {coin}**{amount}**." "For every **100** XP they earn, members will now be given {coin}**{amount}**."
)).format(amount=self.value, coin=conf.emojis.coin) )).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): class AllowTransfers(ModelData, BoolSetting):
setting_id = 'allow_transfers' setting_id = 'allow_transfers'
@@ -64,9 +74,40 @@ class EconomySettings(SettingGroup):
) )
_long_desc = _p( _long_desc = _p(
'guildset:allow_transfers|long_desc', '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 _default = True
_model = CoreData.Guild _model = CoreData.Guild
_column = CoreData.Guild.allow_transfers.name _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

View File

@@ -65,7 +65,7 @@ class EconomyConfigUI(ConfigUI):
class EconomyDashboard(DashboardSection): class EconomyDashboard(DashboardSection):
section_name = _p( section_name = _p(
'dash:economy|title', 'dash:economy|title',
"Economy Configuration" "Economy Configuration ({commands[configure economy]})"
) )
configui = EconomyConfigUI configui = EconomyConfigUI
setting_classes = EconomyConfigUI.setting_classes setting_classes = EconomyConfigUI.setting_classes

View File

@@ -304,8 +304,8 @@ class TimerCog(LionCog):
# ----- Timer Commands ----- # ----- Timer Commands -----
@cmds.hybrid_group( @cmds.hybrid_group(
name=_p('cmd:pomodoro', "pomodoro"), name=_p('cmd:pomodoro', "timers"),
desc=_p('cmd:pomodoro|desc', "Base group for all pomodoro timer commands.") description=_p('cmd:pomodoro|desc', "Base group for all pomodoro timer commands.")
) )
@cmds.guild_only() @cmds.guild_only()
async def pomodoro_group(self, ctx: LionContext): async def pomodoro_group(self, ctx: LionContext):
@@ -787,7 +787,7 @@ class TimerCog(LionCog):
await timer.update_status_card() await timer.update_status_card()
# Show the config UI # 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.run(ctx.interaction)
await ui.wait() await ui.wait()

View File

@@ -14,6 +14,7 @@ class TimerSettings(SettingGroup):
class PomodoroChannel(ModelData, ChannelSetting): class PomodoroChannel(ModelData, ChannelSetting):
setting_id = 'pomodoro_channel' setting_id = 'pomodoro_channel'
_event = 'guildset_pomodoro_channel' _event = 'guildset_pomodoro_channel'
_set_cmd = 'configure pomodoro'
_display_name = _p('guildset:pomodoro_channel', "pomodoro_channel") _display_name = _p('guildset:pomodoro_channel', "pomodoro_channel")
_desc = _p( _desc = _p(
@@ -27,6 +28,15 @@ class TimerSettings(SettingGroup):
"If this setting is not set, pomodoro notifications will default to the " "If this setting is not set, pomodoro notifications will default to the "
"timer voice channel itself." "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 _model = CoreData.Guild
_column = CoreData.Guild.pomodoro_channel.name _column = CoreData.Guild.pomodoro_channel.name
@@ -45,3 +55,12 @@ class TimerSettings(SettingGroup):
"Pomodoro timer notifications will now default to their voice channel." "Pomodoro timer notifications will now default to their voice channel."
)) ))
return resp 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)

View File

@@ -78,7 +78,7 @@ class TimerConfigUI(ConfigUI):
class TimerDashboard(DashboardSection): class TimerDashboard(DashboardSection):
section_name = _p( section_name = _p(
'dash:pomodoro|title', 'dash:pomodoro|title',
"Pomodoro Configuration" "Pomodoro Configuration ({commands[configure pomodoro]})"
) )
configui = TimerConfigUI configui = TimerConfigUI
setting_classes = TimerConfigUI.setting_classes setting_classes = TimerConfigUI.setting_classes

View File

@@ -23,22 +23,24 @@ class RankSettings(SettingGroup):
_enum = RankType _enum = RankType
_default = RankType.VOICE _default = RankType.VOICE
_outputs = { _outputs = {
RankType.VOICE: '`Voice`', RankType.VOICE: _p('guildset:rank_type|output:voice', '`Voice`'),
RankType.XP: '`Exp`', RankType.XP: _p('guildset:rank_type|output:xp', '`Exp`'),
RankType.MESSAGE: '`Messages`' RankType.MESSAGE: _p('guildset:rank_type|output:message', '`Messages`'),
} }
_inputs = { _input_formatted = {
'voice': RankType.VOICE, RankType.VOICE: _p('guildset:rank_type|input_format:voice', 'Voice'),
'study': RankType.VOICE, RankType.XP: _p('guildset:rank_type|input_format:xp', 'Exp'),
'text': RankType.MESSAGE, RankType.MESSAGE: _p('guildset:rank_type|input_format:message', 'Messages'),
'message': RankType.MESSAGE, }
'messages': RankType.MESSAGE, _input_patterns = {
'xp': RankType.XP, RankType.VOICE: _p('guildset:rank_type|input_pattern:voice', 'voice|study'),
'exp': RankType.XP 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' setting_id = 'rank_type'
_event = 'guildset_rank_type' _event = 'guildset_rank_type'
_set_cmd = 'configure ranks'
_display_name = _p('guildset:rank_type', "rank_type") _display_name = _p('guildset:rank_type', "rank_type")
_desc = _p( _desc = _p(
@@ -52,6 +54,10 @@ class RankSettings(SettingGroup):
"`Exp` is a measure of message activity, and " "`Exp` is a measure of message activity, and "
"`Message` is a simple count of messages sent." "`Message` is a simple count of messages sent."
) )
_accepts = _p(
'guildset:rank_type|accepts',
"Voice/Exp/Messages"
)
_model = CoreData.Guild _model = CoreData.Guild
_column = CoreData.Guild.rank_type.name _column = CoreData.Guild.rank_type.name
@@ -76,6 +82,15 @@ class RankSettings(SettingGroup):
)) ))
return resp 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): class RankChannel(ModelData, ChannelSetting):
""" """
Channel to send Rank notifications. 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. If DMRanks is set, this will only be used when the target user has disabled DM notifications.
""" """
setting_id = 'rank_channel' setting_id = 'rank_channel'
_set_cmd = 'configure ranks'
_display_name = _p('guildset:rank_channel', "rank_channel") _display_name = _p('guildset:rank_channel', "rank_channel")
_desc = _p( _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 " "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." "DM notifications, or is otherwise unreachable."
) )
_accepts = _p(
'guildset:rank_channel|accepts',
"Rank notification channel name or id."
)
_model = CoreData.Guild _model = CoreData.Guild
_column = CoreData.Guild.rank_channel.name _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): class DMRanks(ModelData, BoolSetting):
""" """
Whether to DM rank notifications. Whether to DM rank notifications.
""" """
setting_id = 'dm_ranks' setting_id = 'dm_ranks'
_set_cmd = 'configure ranks'
_display_name = _p('guildset:dm_ranks', "dm_ranks") _display_name = _p('guildset:dm_ranks', "dm_ranks")
_desc = _p( _desc = _p(
@@ -114,6 +160,21 @@ class RankSettings(SettingGroup):
"If enabled, congratulatory messages for rank advancement will be direct messaged to the user, " "If enabled, congratulatory messages for rank advancement will be direct messaged to the user, "
"instead of being sent to the configured `rank_channel`." "instead of being sent to the configured `rank_channel`."
) )
_default = True
_model = CoreData.Guild _model = CoreData.Guild
_column = CoreData.Guild.dm_ranks.name _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."
))

View File

@@ -155,7 +155,7 @@ class RankConfigUI(ConfigUI):
class RankDashboard(DashboardSection): class RankDashboard(DashboardSection):
section_name = _p( section_name = _p(
'dash:rank|title', 'dash:rank|title',
"Rank Configuration", "Rank Configuration ({commands[configure ranks]})",
) )
configui = RankConfigUI configui = RankConfigUI
setting_classes = RankConfigUI.setting_classes setting_classes = RankConfigUI.setting_classes

View File

@@ -15,6 +15,7 @@ class RoomSettings(SettingGroup):
class Category(ModelData, ChannelSetting): class Category(ModelData, ChannelSetting):
setting_id = 'rooms_category' setting_id = 'rooms_category'
_event = 'guildset_rooms_category' _event = 'guildset_rooms_category'
_set_cmd = 'configure rooms'
_display_name = _p( _display_name = _p(
'guildset:room_category', "rooms_category" 'guildset:room_category', "rooms_category"
@@ -31,6 +32,10 @@ class RoomSettings(SettingGroup):
"I must have permission to create new channels in this category, " "I must have permission to create new channels in this category, "
"as well as to manage permissions." "as well as to manage permissions."
) )
_accepts = _p(
'guildset:room_category|accepts',
"Private room category name or id."
)
_model = CoreData.Guild _model = CoreData.Guild
_column = CoreData.Guild.renting_category.name _column = CoreData.Guild.renting_category.name
@@ -53,9 +58,19 @@ class RoomSettings(SettingGroup):
)).format(channel=self.value.mention) )).format(channel=self.value.mention)
return resp 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): class Rent(ModelData, IntegerSetting):
setting_id = 'rooms_price' setting_id = 'rooms_price'
_event = 'guildset_rooms_price' _event = 'guildset_rooms_price'
_set_cmd = 'configure rooms'
_display_name = _p( _display_name = _p(
'guildset:rooms_price', "room_rent" 'guildset:rooms_price', "room_rent"
@@ -68,6 +83,10 @@ class RoomSettings(SettingGroup):
'guildset:rooms_rent|long_desc', 'guildset:rooms_rent|long_desc',
"Members will be charged this many LionCoins for each day they rent a private room." "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 _default = 1000
_model = CoreData.Guild _model = CoreData.Guild
@@ -88,6 +107,7 @@ class RoomSettings(SettingGroup):
class MemberLimit(ModelData, IntegerSetting): class MemberLimit(ModelData, IntegerSetting):
setting_id = 'rooms_slots' setting_id = 'rooms_slots'
_event = 'guildset_rooms_slots' _event = 'guildset_rooms_slots'
_set_cmd = 'configure rooms'
_display_name = _p('guildset:rooms_slots', "room_member_cap") _display_name = _p('guildset:rooms_slots', "room_member_cap")
_desc = _p( _desc = _p(
@@ -100,6 +120,10 @@ class RoomSettings(SettingGroup):
"or through the `/room invite` command. " "or through the `/room invite` command. "
"This setting limits the maximum number of members a private room may hold." "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 _default = 25
_model = CoreData.Guild _model = CoreData.Guild
@@ -117,6 +141,7 @@ class RoomSettings(SettingGroup):
class Visible(ModelData, BoolSetting): class Visible(ModelData, BoolSetting):
setting_id = 'rooms_visible' setting_id = 'rooms_visible'
_event = 'guildset_rooms_visible' _event = 'guildset_rooms_visible'
_set_cmd = 'configure rooms'
_display_name = _p('guildset:rooms_visible', "room_visibility") _display_name = _p('guildset:rooms_visible', "room_visibility")
_desc = _p( _desc = _p(
@@ -129,6 +154,21 @@ class RoomSettings(SettingGroup):
"enabled for the `@everyone` role." "enabled for the `@everyone` role."
) )
_default = False _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 _model = CoreData.Guild
_column = CoreData.Guild.renting_visible.name _column = CoreData.Guild.renting_visible.name
@@ -148,6 +188,15 @@ class RoomSettings(SettingGroup):
)) ))
return resp 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 = ( model_settings = (
Category, Category,
Rent, Rent,

View File

@@ -95,7 +95,7 @@ class RoomSettingUI(ConfigUI):
class RoomDashboard(DashboardSection): class RoomDashboard(DashboardSection):
section_name = _p( section_name = _p(
'dash:rooms|title', 'dash:rooms|title',
"Private Room Configuration" "Private Room Configuration ({commands[configure rooms]})"
) )
configui = RoomSettingUI configui = RoomSettingUI
setting_classes = RoomSettingUI.setting_classes setting_classes = RoomSettingUI.setting_classes

View File

@@ -14,7 +14,8 @@ from settings.groups import SettingGroup
from meta import conf, LionBot from meta import conf, LionBot
from meta.context import ctx_bot 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.ui import ConfigUI, FastModal, error_handler_for, ModalRetryUI
from utils.lib import MessageArgs from utils.lib import MessageArgs
from core.data import CoreData from core.data import CoreData
@@ -33,16 +34,24 @@ class StatTypeSetting(EnumSetting):
""" """
_enum = StatisticType _enum = StatisticType
_outputs = { _outputs = {
StatisticType.VOICE: '`Voice`', StatisticType.VOICE: _p('settype:stat|output:voice', "`Voice`"),
StatisticType.TEXT: '`Text`', StatisticType.TEXT: _p('settype:stat|output:text', "`Text`"),
StatisticType.ANKI: '`Anki`' StatisticType.ANKI: _p('settype:stat|output:anki', "`Anki`"),
} }
_inputs = { _input_formatted = {
'voice': StatisticType.VOICE, StatisticType.VOICE: _p('settype:stat|input_format:voice', "Voice"),
'study': StatisticType.VOICE, StatisticType.TEXT: _p('settype:stat|input_format:text', "Text"),
'text': StatisticType.TEXT, StatisticType.ANKI: _p('settype:stat|input_format:anki', "Anki"),
'anki': StatisticType.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): class StatisticsSettings(SettingGroup):
@@ -74,6 +83,7 @@ class StatisticsSettings(SettingGroup):
Time is assumed to be in set guild timezone (although supports +00 syntax) Time is assumed to be in set guild timezone (although supports +00 syntax)
""" """
setting_id = 'season_start' setting_id = 'season_start'
_set_cmd = 'configure statistics'
_display_name = _p('guildset:season_start', "season_start") _display_name = _p('guildset:season_start', "season_start")
_desc = _p( _desc = _p(
@@ -86,11 +96,17 @@ class StatisticsSettings(SettingGroup):
"and the leaderboard will display activity since this time by default. " "and the leaderboard will display activity since this time by default. "
"Unset to disable seasons and use all-time statistics instead." "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 _model = CoreData.Guild
_column = CoreData.Guild.season_start.name _column = CoreData.Guild.season_start.name
# TODO: Offer to update badge ranks when this changes?
# TODO: Don't allow future times?
@classmethod @classmethod
async def _timezone_from_id(cls, guildid, **kwargs): async def _timezone_from_id(cls, guildid, **kwargs):
@@ -98,6 +114,38 @@ class StatisticsSettings(SettingGroup):
lguild = await bot.core.lions.fetch_guild(guildid) lguild = await bot.core.lions.fetch_guild(guildid)
return lguild.timezone 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): class UnrankedRoles(ListData, RoleListSetting):
""" """
List of roles not displayed on the leaderboard List of roles not displayed on the leaderboard
@@ -113,6 +161,10 @@ class StatisticsSettings(SettingGroup):
'guildset:unranked_roles|long_desc', 'guildset:unranked_roles|long_desc',
"When set, members with *any* of these roles will not appear on the /leaderboard ranking list." "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 _default = None
_table_interface = StatsData.unranked_roles _table_interface = StatsData.unranked_roles
@@ -124,7 +176,29 @@ class StatisticsSettings(SettingGroup):
@property @property
def set_str(self): 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): class VisibleStats(ListData, ListSetting, InteractiveSetting):
""" """
@@ -145,6 +219,10 @@ class StatisticsSettings(SettingGroup):
'guildset:visible_stats|desc', 'guildset:visible_stats|desc',
"Choose which statistics types to display in the leaderboard and statistics commands." "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? # TODO: Format VOICE as STUDY when possible?
_default = [ _default = [
@@ -159,6 +237,23 @@ class StatisticsSettings(SettingGroup):
_cache = {} _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): class DefaultStat(ModelData, StatTypeSetting):
""" """
Which of the three stats to display by default Which of the three stats to display by default

View File

@@ -1,12 +1,14 @@
from .exec_cog import Exec from babel.translator import LocalBabel
from .blacklists import Blacklists babel = LocalBabel('sysadmin')
from .guild_log import GuildLog
from .presence import PresenceCtrl
from .dash import LeoSettings
async def setup(bot): 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(LeoSettings(bot))
await bot.add_cog(Blacklists(bot)) await bot.add_cog(Blacklists(bot))

View File

@@ -23,6 +23,10 @@ from settings.groups import SettingGroup
from wards import sys_admin from wards import sys_admin
from . import babel
_p = babel._p
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -104,49 +108,93 @@ class PresenceSettings(SettingGroup):
""" """
Control the bot status and activity. 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]): class PresenceStatus(ModelData, EnumSetting[str, AppStatus]):
setting_id = 'presence_status' setting_id = 'presence_status'
display_name = 'online_status' _display_name = _p('botset:presence_status', 'online_status')
desc = "Bot status indicator" _desc = _p('botset:presence_status|desc', "Bot status indicator")
long_desc = "Whether the bot account displays as online, idle, dnd, or offline." _long_desc = _p(
accepts = "One of 'online', 'idle', 'dnd', or 'offline'." '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 _model = PresenceData.AppPresence
_column = PresenceData.AppPresence.online_status.name _column = PresenceData.AppPresence.online_status.name
_create_row = True _create_row = True
_enum = AppStatus _enum = AppStatus
_outputs = {item: item.value[1] for item in _enum} _outputs = {
_inputs = {item.name: item for item in _enum} 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 _default = AppStatus.online
class PresenceType(ModelData, EnumSetting[str, AppActivityType]): class PresenceType(ModelData, EnumSetting[str, AppActivityType]):
setting_id = 'presence_type' setting_id = 'presence_type'
display_name = 'activity_type' _display_name = _p('botset:presence_type', 'activity_type')
desc = "Type of presence activity" _desc = _p('botset:presence_type|desc', "Type of presence activity")
long_desc = "Whether the bot activity is shown as 'Listening', 'Playing', or 'Watching'." _long_desc = _p(
accepts = "One of 'listening', 'playing', 'watching', or 'streaming'." '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 _model = PresenceData.AppPresence
_column = PresenceData.AppPresence.activity_type.name _column = PresenceData.AppPresence.activity_type.name
_create_row = True _create_row = True
_enum = AppActivityType _enum = AppActivityType
_outputs = {item: item.value[1] for item in _enum} _outputs = {
_inputs = {item.name: item for item in _enum} 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 _default = AppActivityType.watching
class PresenceName(ModelData, StringSetting[str]): class PresenceName(ModelData, StringSetting[str]):
setting_id = 'presence_name' setting_id = 'presence_name'
display_name = 'activity_name' _display_name = _p('botset:presence_name', 'activity_name')
desc = "Name of the presence activity" _desc = _p("botset:presence_name|desc", "Name of the presence activity")
long_desc = "Presence activity name." _long_desc = _p("botset:presence_name|long_desc", "Presence activity name.")
accepts = "Any string." _accepts = _p('botset:presence_name|accepts', "The name of the activity to show.")
_model = PresenceData.AppPresence _model = PresenceData.AppPresence
_column = PresenceData.AppPresence.activity_name.name _column = PresenceData.AppPresence.activity_name.name

View File

@@ -8,7 +8,7 @@ from settings import ListData, ModelData
from settings.setting_types import StringSetting, BoolSetting, ChannelListSetting, IntegerSetting from settings.setting_types import StringSetting, BoolSetting, ChannelListSetting, IntegerSetting
from settings.groups import SettingGroup from settings.groups import SettingGroup
from meta import conf, LionBot from meta import conf, LionBot, ctx_bot
from utils.lib import tabulate from utils.lib import tabulate
from utils.ui import LeoUI, FastModal, error_handler_for, ModalRetryUI, DashboardSection from utils.ui import LeoUI, FastModal, error_handler_for, ModalRetryUI, DashboardSection
from core.data import CoreData from core.data import CoreData
@@ -28,6 +28,7 @@ class TasklistSettings(SettingGroup):
Exposed via `/configure tasklist`, and the standard configuration interface. Exposed via `/configure tasklist`, and the standard configuration interface.
""" """
setting_id = 'task_reward' setting_id = 'task_reward'
_set_cmd = 'configure tasklist'
_display_name = _p('guildset:task_reward', "task_reward") _display_name = _p('guildset:task_reward', "task_reward")
_desc = _p( _desc = _p(
@@ -38,6 +39,10 @@ class TasklistSettings(SettingGroup):
'guildset:task_reward|long_desc', 'guildset:task_reward|long_desc',
"The number of coins members will be rewarded each time they complete a task on their tasklist." "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 _default = 50
_model = CoreData.Guild _model = CoreData.Guild
@@ -51,20 +56,19 @@ class TasklistSettings(SettingGroup):
"Members will now be rewarded {coin}**{amount}** for each completed task." "Members will now be rewarded {coin}**{amount}** for each completed task."
)).format(coin=conf.emojis.coin, amount=self.data) )).format(coin=conf.emojis.coin, amount=self.data)
@property
def set_str(self):
return '</configure tasklist:1038560947666694144>'
@classmethod @classmethod
def _format_data(cls, parent_id, data, **kwargs): def _format_data(cls, parent_id, data, **kwargs):
if data is not None: if data is not None:
return "{coin}**{amount}** per task.".format( t = ctx_translator.get().t
coin=conf.emojis.coin, formatted = t(_p(
amount=data 'guildset:task_reward|formatted',
) "{coin}**{amount}** per task."
)).format(coin=conf.emojis.coin, amount=data)
return formatted
class task_reward_limit(ModelData, IntegerSetting): class task_reward_limit(ModelData, IntegerSetting):
setting_id = 'task_reward_limit' setting_id = 'task_reward_limit'
_set_cmd = 'configure tasklist'
_display_name = _p('guildset:task_reward_limit', "task_reward_limit") _display_name = _p('guildset:task_reward_limit', "task_reward_limit")
_desc = _p( _desc = _p(
@@ -76,6 +80,10 @@ class TasklistSettings(SettingGroup):
"Maximum number of times in each 24h period that members will be rewarded " "Maximum number of times in each 24h period that members will be rewarded "
"for completing a task." "for completing a task."
) )
_accepts = _p(
'guildset:task_reward_limit|accepts',
"The maximum number of tasks to reward LC for per 24h."
)
_default = 10 _default = 10
_model = CoreData.Guild _model = CoreData.Guild
@@ -89,16 +97,15 @@ class TasklistSettings(SettingGroup):
"Members will now be rewarded for task completion at most **{amount}** times per 24h." "Members will now be rewarded for task completion at most **{amount}** times per 24h."
)).format(amount=self.data) )).format(amount=self.data)
@property
def set_str(self):
return '</configure tasklist:1038560947666694144>'
@classmethod @classmethod
def _format_data(cls, parent_id, data, **kwargs): def _format_data(cls, parent_id, data, **kwargs):
if data is not None: if data is not None:
return "`{number}` per 24 hours.".format( t = ctx_translator.get().t
number=data formatted = t(_p(
) 'guildset:task_reward_limit|formatted',
"`{number}` per 24 hours."
)).format(number=data)
return formatted
class tasklist_channels(ListData, ChannelListSetting): class tasklist_channels(ListData, ChannelListSetting):
setting_id = 'tasklist_channels' 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 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." "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 _default = None
_table_interface = TasklistData.channels _table_interface = TasklistData.channels
@@ -122,14 +133,32 @@ class TasklistSettings(SettingGroup):
_cache = {} _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 @property
def set_str(self): 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): class TasklistConfigUI(LeoUI):
# TODO: Back option to global guild config # TODO: Migrate to ConfigUI
# TODO: Cohesive edit
_listening = {} _listening = {}
setting_classes = ( setting_classes = (
TasklistSettings.task_reward, TasklistSettings.task_reward,
@@ -286,6 +315,6 @@ class TasklistConfigUI(LeoUI):
class TasklistDashboard(DashboardSection): class TasklistDashboard(DashboardSection):
section_name = _p('dash:tasklist|name', "Tasklist Configuration") section_name = _p('dash:tasklist|name', "Tasklist Configuration ({commands[configure tasklist]})")
configui = TasklistConfigUI configui = TasklistConfigUI
setting_classes = configui.setting_classes setting_classes = configui.setting_classes

View File

@@ -33,6 +33,7 @@ class UserConfigSettings(SettingGroup):
and several other components such as reminder times. and several other components such as reminder times.
""" """
setting_id = 'timezone' setting_id = 'timezone'
_set_cmd = 'my timezone'
_display_name = _p('userset:timezone', "timezone") _display_name = _p('userset:timezone', "timezone")
_desc = _p( _desc = _p(

View File

@@ -14,7 +14,7 @@ from dateutil.parser import parse, ParserError
from meta.context import ctx_bot from meta.context import ctx_bot
from meta.errors import UserInputError from meta.errors import UserInputError
from utils.lib import strfdur, parse_duration from utils.lib import strfdur, parse_duration
from babel import ctx_translator from babel.translator import ctx_translator, LazyStr
from .base import ParentID from .base import ParentID
from .ui import InteractiveSetting, SettingWidget from .ui import InteractiveSetting, SettingWidget
@@ -45,7 +45,7 @@ class StringSetting(InteractiveSetting[ParentID, str, str]):
Default: True Default: True
""" """
_accepts = _p('settype:string|accepts', "Any text") _accepts = _p('settype:string|accepts', "Any Text")
_maxlen: int = 4000 _maxlen: int = 4000
_quote: bool = True _quote: bool = True
@@ -123,7 +123,7 @@ class ChannelSetting(Generic[ParentID, CT], InteractiveSetting[ParentID, int, CT
List of guild channel types to accept. List of guild channel types to accept.
Default: [] 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" _selector_placeholder = "Select a Channel"
channel_types: list[discord.ChannelType] = [] channel_types: list[discord.ChannelType] = []
@@ -151,8 +151,26 @@ class ChannelSetting(Generic[ParentID, CT], InteractiveSetting[ParentID, int, CT
@classmethod @classmethod
async def _parse_string(cls, parent_id, string: str, **kwargs): 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 @classmethod
def _format_data(cls, parent_id, data, **kwargs): def _format_data(cls, parent_id, data, **kwargs):
@@ -161,25 +179,11 @@ class ChannelSetting(Generic[ParentID, CT], InteractiveSetting[ParentID, int, CT
""" """
if data: if data:
return "<#{}>".format(data) return "<#{}>".format(data)
else:
return "Not Set"
@property @property
def input_formatted(self) -> str: def input_formatted(self) -> str:
""" data = self._data
Returns the channel name if possible, otherwise the id. return str(data) if data else ''
"""
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 ""
class Widget(SettingWidget['ChannelSetting']): class Widget(SettingWidget['ChannelSetting']):
def update_children(self): def update_children(self):
@@ -236,7 +240,7 @@ class MessageablelSetting(ChannelSetting):
bot = ctx_bot.get() bot = ctx_bot.get()
channel = bot.get_channel(data) channel = bot.get_channel(data)
if channel is None: 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 return channel
@@ -250,7 +254,7 @@ class RoleSetting(InteractiveSetting[ParentID, int, Union[discord.Role, discord.
Placeholder to use in the Widget selector. Placeholder to use in the Widget selector.
Default: "Select a Role" 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" _selector_placeholder = "Select a Role"
@@ -291,8 +295,26 @@ class RoleSetting(InteractiveSetting[ParentID, int, Union[discord.Role, discord.
@classmethod @classmethod
async def _parse_string(cls, parent_id, string: str, **kwargs): 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 @classmethod
def _format_data(cls, parent_id, data, **kwargs): def _format_data(cls, parent_id, data, **kwargs):
@@ -306,20 +328,8 @@ class RoleSetting(InteractiveSetting[ParentID, int, Union[discord.Role, discord.
@property @property
def input_formatted(self) -> str: def input_formatted(self) -> str:
""" data = self._data
Returns the role name if possible, otherwise the id. return str(data) if data else ''
"""
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 ""
class Widget(SettingWidget['RoleSetting']): class Widget(SettingWidget['RoleSetting']):
def update_children(self): def update_children(self):
@@ -367,33 +377,54 @@ class BoolSetting(InteractiveSetting[ParentID, bool, bool]):
Default: {True: "On", False: "Off", None: "Not Set"} 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 # Values that are accepted as truthy and falsey by the parser
_truthy = {"yes", "true", "on", "enable", "enabled"} _truthy = _p(
_falsey = {"no", "false", "off", "disable", "disabled"} '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 # 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 # Button labels
_true_button_args: dict[str, Any] = {} _true_button_args: dict[str, Any] = {}
_false_button_args: dict[str, Any] = {} _false_button_args: dict[str, Any] = {}
_reset_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 @property
def input_formatted(self) -> str: def input_formatted(self) -> str:
""" """
Return the current data string. Return the current data string.
""" """
if self._data is not None: if self._data is not None:
output = self._outputs[self._data] t = ctx_translator.get().t
set = (self._falsey, self._truthy)[self._data] 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 return output
else: else:
return next(iter(set)) return next(iter(input_set))
else: else:
return "" return ""
@@ -419,9 +450,9 @@ class BoolSetting(InteractiveSetting[ParentID, bool, bool]):
_userstr = string.lower() _userstr = string.lower()
if not _userstr or _userstr == "none": if not _userstr or _userstr == "none":
return None return None
if _userstr in cls._truthy: if _userstr in cls.truthy_values():
return True return True
elif _userstr in cls._falsey: elif _userstr in cls.falsey_values():
return False return False
else: else:
raise UserInputError("Could not parse `{}` as a boolean.".format(string)) 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. 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']): class Widget(SettingWidget['BoolSetting']):
def update_children(self): def update_children(self):
@@ -676,8 +708,7 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
# TODO Definitely need autocomplete here # TODO Definitely need autocomplete here
_accepts = _p( _accepts = _p(
'settype:timezone|accepts', 'settype:timezone|accepts',
"A timezone name from [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) " "A timezone name from the 'tz database' (e.g. 'Europe/London')"
"(e.g. `Europe/London`)."
) )
@property @property
@@ -739,6 +770,23 @@ class TimezoneSetting(InteractiveSetting[ParentID, str, TZT]):
) from None ) from None
return str(timezone) 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 @classmethod
async def parse_acmpl(cls, interaction: discord.Interaction, partial: str): async def parse_acmpl(cls, interaction: discord.Interaction, partial: str):
bot = interaction.client bot = interaction.client
@@ -794,7 +842,7 @@ class TimestampSetting(InteractiveSetting[ParentID, str, dt.datetime]):
""" """
_accepts = _p( _accepts = _p(
'settype:timestamp|accepts', 'settype:timestamp|accepts',
"A timestamp in the form yyyy-mm-dd HH:MM" "A timestamp in the form YYYY-MM-DD HH:MM"
) )
@classmethod @classmethod
@@ -812,22 +860,24 @@ class TimestampSetting(InteractiveSetting[ParentID, str, dt.datetime]):
ts = None ts = None
else: else:
local_tz = await cls._timezone_from_id(parent_id, **kwargs) 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, hour=0, minute=0,
second=0, microsecond=0 second=0, microsecond=0
) )
try: try:
ts = parse(string, fuzzy=True, default=default) ts = parse(string, fuzzy=True, default=default)
except ParserError: except ParserError:
# TOLOCALISE: t = ctx_translator.get().t
raise UserInputError("Invalid date string passed") 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 return ts
@classmethod @classmethod
def _format_data(cls, parent_id: ParentID, data, **kwargs): def _format_data(cls, parent_id: ParentID, data, **kwargs):
if data is None: if data is not None:
return "Not Set"
else:
return "<t:{}>".format(int(data.timestamp())) return "<t:{}>".format(int(data.timestamp()))
@classmethod @classmethod
@@ -839,6 +889,41 @@ class TimestampSetting(InteractiveSetting[ParentID, str, dt.datetime]):
""" """
return pytz.UTC 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') ET = TypeVar('ET', bound='Enum')
@@ -866,8 +951,9 @@ class EnumSetting(InteractiveSetting[ParentID, ET, ET]):
""" """
_enum: Type[ET] _enum: Type[ET]
_outputs: dict[ET, str] _outputs: dict[ET, LazyStr]
_inputs: dict[str, ET] _input_patterns: dict[ET: LazyStr]
_input_formatted: dict[ET: LazyStr]
_accepts = _p('settype:enum|accepts', "A valid option.") _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. Return the output string for the current data.
This assumes the output strings are accepted as inputs! This assumes the output strings are accepted as inputs!
""" """
t = ctx_translator.get().t
if self._data is not None: if self._data is not None:
return self._outputs[self._data] return t(self._input_formatted[self._data])
else: else:
return "" return ""
@@ -901,23 +988,39 @@ class EnumSetting(InteractiveSetting[ParentID, ET, ET]):
""" """
Parse the user input into an enum item. Parse the user input into an enum item.
""" """
# TODO: Another selection case.
if not string: if not string:
return None return None
string = string.lower() string = string.lower()
if string not in cls._inputs: t = ctx_translator.get().t
raise UserInputError("Invalid choice!")
return cls._inputs[string] 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 @classmethod
def _format_data(cls, parent_id: ParentID, data, **kwargs): def _format_data(cls, parent_id: ParentID, data, **kwargs):
""" """
Format the enum using the provided output map. Format the enum using the provided output map.
""" """
t = ctx_translator.get().t
if data is not None: if data is not None:
if data not in cls._outputs: if data not in cls._outputs:
raise ValueError(f"Enum item {data} unmapped.") raise ValueError(f"Enum item {data} unmapped.")
return cls._outputs[data] return t(cls._outputs[data])
class DurationSetting(InteractiveSetting[ParentID, int, int]): class DurationSetting(InteractiveSetting[ParentID, int, int]):
@@ -1110,9 +1213,7 @@ class ListSetting:
""" """
Format the list by adding `,` between each formatted item Format the list by adding `,` between each formatted item
""" """
if not data: if data:
return 'Not Set'
else:
formatted_items = [] formatted_items = []
for item in data: for item in data:
formatted_item = cls._setting._format_data(id, item) formatted_item = cls._setting._format_data(id, item)
@@ -1142,8 +1243,7 @@ class ChannelListSetting(ListSetting, InteractiveSetting):
""" """
_accepts = _p( _accepts = _p(
'settype:channel_list|accepts', 'settype:channel_list|accepts',
"Comma separated list of channel mentions/ids/names. Use `None` to unset. " "Comma separated list of channel ids."
"Write `--add` or `--remove` to add or remove channels."
) )
_setting = ChannelSetting _setting = ChannelSetting
@@ -1154,8 +1254,7 @@ class RoleListSetting(ListSetting, InteractiveSetting):
""" """
_accepts = _p( _accepts = _p(
'settype:role_list|accepts', 'settype:role_list|accepts',
"Comma separated list of role mentions/ids/names. Use `None` to unset. " 'Comma separated list of role ids.'
"Write `--add` or `--remove` to add or remove roles."
) )
_setting = RoleSetting _setting = RoleSetting
@@ -1171,8 +1270,7 @@ class StringListSetting(InteractiveSetting, ListSetting):
""" """
_accepts = _p( _accepts = _p(
'settype:stringlist|accepts', 'settype:stringlist|accepts',
"Comma separated list of strings. Use `None` to unset. " 'Comma separated strings.'
"Write `--add` or `--remove` to add or remove strings."
) )
_setting = StringSetting _setting = StringSetting
@@ -1183,9 +1281,7 @@ class GuildIDListSetting(InteractiveSetting, ListSetting):
""" """
_accepts = _p( _accepts = _p(
'settype:guildidlist|accepts', 'settype:guildidlist|accepts',
"Comma separated list of guild ids. Use `None` to unset. " 'Comma separated list of guild ids.'
"Write `--add` or `--remove` to add or remove ids. "
"The provided ids are not verified in any way."
) )
_setting = GuildIDSetting _setting = GuildIDSetting

View File

@@ -15,6 +15,9 @@ from meta.context import ctx_bot
from babel.translator import ctx_translator, LazyStr from babel.translator import ctx_translator, LazyStr
from .base import BaseSetting, ParentID, SettingData, SettingValue from .base import BaseSetting, ParentID, SettingData, SettingValue
from . import babel
_p = babel._p
ST = TypeVar('ST', bound='InteractiveSetting') ST = TypeVar('ST', bound='InteractiveSetting')
@@ -172,6 +175,8 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]):
_desc: LazyStr # User readable brief description of the setting _desc: LazyStr # User readable brief description of the setting
_long_desc: LazyStr # User readable long description of the setting _long_desc: LazyStr # User readable long description of the setting
_accepts: LazyStr # User readable description of the acceptable values _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 _virtual: bool = False # Whether the setting should be hidden from tables and dashboards
_required: bool = False _required: bool = False
@@ -305,29 +310,61 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]):
@property @property
def set_str(self): 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 @property
def embed(self): def embed(self):
""" """
Returns a full embed describing this setting. Returns a full embed describing this setting.
""" """
t = ctx_translator.get().t
embed = discord.Embed( 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) embed.description = "{}\n{}".format(self.long_desc.format(self=self), self.desc_table)
return embed return embed
@property def _desc_table(self) -> list[str]:
def desc_table(self): t = ctx_translator.get().t
lines = [] 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 @property
def input_field(self) -> TextInput: def input_field(self) -> TextInput:
@@ -366,7 +403,7 @@ class InteractiveSetting(BaseSetting[ParentID, SettingData, SettingValue]):
Default user-readable form of the setting. Default user-readable form of the setting.
Should be a short single line. 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 @property
def input_formatted(self) -> str: def input_formatted(self) -> str:

View File

@@ -28,6 +28,7 @@ class TextTrackerSettings(SettingGroup):
""" """
class XPPerPeriod(ModelData, IntegerSetting): class XPPerPeriod(ModelData, IntegerSetting):
setting_id = 'xp_per_period' setting_id = 'xp_per_period'
_set_cmd = 'configure message_exp'
_display_name = _p('guildset:xp_per_period', "xp_per_5min") _display_name = _p('guildset:xp_per_period', "xp_per_5min")
_desc = _p( _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). " "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." "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? _default = 101 # TODO: Make a dynamic default based on the global setting?
_model = CoreData.Guild _model = CoreData.Guild
@@ -55,6 +60,7 @@ class TextTrackerSettings(SettingGroup):
class WordXP(ModelData, IntegerSetting): class WordXP(ModelData, IntegerSetting):
setting_id = 'word_xp' setting_id = 'word_xp'
_set_cmd = 'configure message_exp'
_display_name = _p('guildset:word_xp', "xp_per_100words") _display_name = _p('guildset:word_xp', "xp_per_100words")
_desc = _p( _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. " "Amount of message XP to be given (additionally to the XP per period) for each hundred words. "
"Useful for rewarding communication." "Useful for rewarding communication."
) )
_accepts = _p(
'guildset:word_xp|accepts',
"Number of XP to reward per hundred words sent."
)
_default = 50 _default = 50
_model = CoreData.Guild _model = CoreData.Guild
@@ -92,6 +102,14 @@ class TextTrackerSettings(SettingGroup):
"Messages sent in these channels will not count towards a member's message XP. " "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." "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 _default = None
_table_interface = TextTrackerData.untracked_channels _table_interface = TextTrackerData.untracked_channels
@@ -101,6 +119,29 @@ class TextTrackerSettings(SettingGroup):
_cache = {} _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 @classmethod
@log_wrap(action='Cache Untracked Text Channels') @log_wrap(action='Cache Untracked Text Channels')
async def setup(cls, bot): async def setup(cls, bot):
@@ -127,6 +168,7 @@ class TextTrackerGlobalSettings(SettingGroup):
""" """
class XPPerPeriod(ModelData, IntegerSetting): class XPPerPeriod(ModelData, IntegerSetting):
setting_id = 'xp_per_period' setting_id = 'xp_per_period'
_set_cmd = 'leo configure experience_rates'
_display_name = _p('botset:xp_per_period', "xp_per_5min") _display_name = _p('botset:xp_per_period', "xp_per_5min")
_desc = _p( _desc = _p(
@@ -139,6 +181,10 @@ class TextTrackerGlobalSettings(SettingGroup):
"for each 5 minute period in which they are active (send a message). " "for each 5 minute period in which they are active (send a message). "
"Note that this XP is only given *once* per period." "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 _default = 101
_model = TextTrackerData.BotConfigText _model = TextTrackerData.BotConfigText
@@ -155,6 +201,7 @@ class TextTrackerGlobalSettings(SettingGroup):
class WordXP(ModelData, IntegerSetting): class WordXP(ModelData, IntegerSetting):
setting_id = 'word_xp' setting_id = 'word_xp'
_set_cmd = 'leo configure experience_rates'
_display_name = _p('botset:word_xp', "xp_per_100words") _display_name = _p('botset:word_xp', "xp_per_100words")
_desc = _p( _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. " "Amount of global message XP to be given (additionally to the XP per period) for each hundred words. "
"Useful for rewarding communication." "Useful for rewarding communication."
) )
_accepts = _p(
'botset:word_xp|accepts',
"Number of XP to reward per hundred words sent."
)
_default = 50 _default = 50
_model = TextTrackerData.BotConfigText _model = TextTrackerData.BotConfigText

View File

@@ -86,7 +86,7 @@ class TextTrackerConfigUI(ConfigUI):
class TextTrackerDashboard(DashboardSection): class TextTrackerDashboard(DashboardSection):
section_name = _p( section_name = _p(
'dash:text_tracking|title', 'dash:text_tracking|title',
"Message XP configuration", "Message XP configuration ({commands[configure message_exp]})",
) )
configui = TextTrackerConfigUI configui = TextTrackerConfigUI
setting_classes = configui.setting_classes setting_classes = configui.setting_classes

View File

@@ -33,9 +33,9 @@ _p = babel._p
class VoiceTrackerSettings(SettingGroup): class VoiceTrackerSettings(SettingGroup):
class UntrackedChannels(ListData, ChannelListSetting): class UntrackedChannels(ListData, ChannelListSetting):
# TODO: Factor out into combined tracking settings?
setting_id = 'untracked_channels' setting_id = 'untracked_channels'
_event = 'guild_setting_update_untracked_channels' _event = 'guild_setting_update_untracked_channels'
_set_cmd = 'configure voice_rewards'
_display_name = _p('guildset:untracked_channels', "untracked_channels") _display_name = _p('guildset:untracked_channels', "untracked_channels")
_desc = _p( _desc = _p(
@@ -47,6 +47,14 @@ class VoiceTrackerSettings(SettingGroup):
"Activity in these channels will not count towards a member's statistics. " "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." "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 _default = None
@@ -68,12 +76,19 @@ class VoiceTrackerSettings(SettingGroup):
@property @property
def update_message(self): def update_message(self):
t = ctx_translator.get().t t = ctx_translator.get().t
return t(_p( if self.data:
'guildset:untracked_channels|response', resp = t(_p(
"Activity in the following channels will now be ignored: {channels}" 'guildset:untracked_channels|set_response|set',
)).format( "Activity in the following channels will now be ignored: {channels}"
channels=self.formatted )).format(
) channels=self.formatted
)
else:
resp = t(_p(
'guildset:untracked_channels|set_response|unset',
"All voice channels will now be tracked."
))
return resp
@classmethod @classmethod
@log_wrap(action='Cache Untracked Channels') @log_wrap(action='Cache Untracked Channels')
@@ -97,6 +112,7 @@ class VoiceTrackerSettings(SettingGroup):
class HourlyReward(ModelData, IntegerSetting): class HourlyReward(ModelData, IntegerSetting):
setting_id = 'hourly_reward' setting_id = 'hourly_reward'
_event = 'guild_setting_update_hourly_reward' _event = 'guild_setting_update_hourly_reward'
_set_cmd = 'configure voice_rewards'
_display_name = _p('guildset:hourly_reward', "hourly_reward") _display_name = _p('guildset:hourly_reward', "hourly_reward")
_desc = _p( _desc = _p(
@@ -107,6 +123,10 @@ class VoiceTrackerSettings(SettingGroup):
'guildset:hourly_reward|mode:voice|long_desc', 'guildset:hourly_reward|mode:voice|long_desc',
"Number of LionCoins to each member per hour that they stay in a tracked voice channel." "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 _default = 50
_min = 0 _min = 0
@@ -127,29 +147,10 @@ class VoiceTrackerSettings(SettingGroup):
amount=data amount=data
) )
@property
def set_str(self):
# TODO: Dynamic retrieval of command id
return '</configure voice_tracking:1038560947666694144>'
class HourlyReward_Voice(HourlyReward): class HourlyReward_Voice(HourlyReward):
""" """
Voice-mode specialised version of 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 @property
def update_message(self): def update_message(self):
t = ctx_translator.get().t t = ctx_translator.get().t
@@ -191,6 +192,7 @@ class VoiceTrackerSettings(SettingGroup):
""" """
setting_id = 'hourly_live_bonus' setting_id = 'hourly_live_bonus'
_event = 'guild_setting_update_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") _display_name = _p('guildset:hourly_live_bonus', "hourly_live_bonus")
_desc = _p( _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* " "When a member streams or video-chats in a channel they will be given this bonus *additionally* "
"to the `hourly_reward`." "to the `hourly_reward`."
) )
_accepts = _p(
'guildset:hourly_live_bonus|accepts',
"Number of bonus coins to reward per hour when live."
)
_default = 150 _default = 150
_min = 0 _min = 0
@@ -223,11 +229,6 @@ class VoiceTrackerSettings(SettingGroup):
amount=data amount=data
) )
@property
def set_str(self):
# TODO: Dynamic retrieval of command id
return '</configure voice_tracking:1038560947666694144>'
@property @property
def update_message(self): def update_message(self):
t = ctx_translator.get().t t = ctx_translator.get().t
@@ -242,6 +243,7 @@ class VoiceTrackerSettings(SettingGroup):
class DailyVoiceCap(ModelData, DurationSetting): class DailyVoiceCap(ModelData, DurationSetting):
setting_id = 'daily_voice_cap' setting_id = 'daily_voice_cap'
_event = 'guild_setting_update_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") _display_name = _p('guildset:daily_voice_cap', "daily_voice_cap")
_desc = _p( _desc = _p(
@@ -254,6 +256,10 @@ class VoiceTrackerSettings(SettingGroup):
"Tracking will resume at the start of the next day. " "Tracking will resume at the start of the next day. "
"The start of the day is determined by the configured guild timezone." "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 = 16 * 60 * 60
_default_multiplier = 60 * 60 _default_multiplier = 60 * 60
@@ -263,11 +269,6 @@ class VoiceTrackerSettings(SettingGroup):
_model = CoreData.Guild _model = CoreData.Guild
_column = CoreData.Guild.daily_study_cap.name _column = CoreData.Guild.daily_study_cap.name
@property
def set_str(self):
# TODO: Dynamic retrieval of command id
return '</configure voice_tracking:1038560947666694144>'
@property @property
def update_message(self): def update_message(self):
t = ctx_translator.get().t t = ctx_translator.get().t
@@ -524,7 +525,7 @@ class VoiceTrackerConfigUI(ConfigUI):
class VoiceTrackerDashboard(DashboardSection): class VoiceTrackerDashboard(DashboardSection):
section_name = _p( section_name = _p(
'dash:voice_tracker|title', 'dash:voice_tracker|title',
"Voice Tracker Configuration" "Voice Tracker Configuration ({commands[configure voice_rewards]})"
) )
configui = VoiceTrackerConfigUI configui = VoiceTrackerConfigUI
setting_classes = configui.setting_classes setting_classes = configui.setting_classes

View File

@@ -138,7 +138,7 @@ class MessageArgs:
def tabulate( def tabulate(
*fields: tuple[str, str], *fields: tuple[str, str],
row_format: str = "`{invis}{key:<{pad}}{colon}`\t{value}", 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 = ':', colon: str = ':',
invis: str = "", invis: str = "",
**args **args
@@ -189,6 +189,7 @@ def tabulate(
sub_line = sub_format.format( sub_line = sub_format.format(
invis=invis, invis=invis,
pad=max_len + len(colon), pad=max_len + len(colon),
colon=colon,
value=line, value=line,
**args **args
) )

View File

@@ -83,6 +83,8 @@ class ConfigUI(LeoUI):
t = ctx_translator.get().t t = ctx_translator.get().t
instances = self.instances instances = self.instances
items = [setting.input_field for setting in 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] strings = [item.value for item in items]
modal = ConfigEditor(*items, title=t(self.edit_modal_title)) modal = ConfigEditor(*items, title=t(self.edit_modal_title))
@@ -126,7 +128,7 @@ class ConfigUI(LeoUI):
t = ctx_translator.get().t t = ctx_translator.get().t
self.edit_button.label = t(_p( self.edit_button.label = t(_p(
'ui:configui|button:edit|label', 'ui:configui|button:edit|label',
"Bulk Edit" "Edit"
)) ))
@button(emoji=conf.emojis.cancel, style=ButtonStyle.red) @button(emoji=conf.emojis.cancel, style=ButtonStyle.red)
@@ -287,7 +289,7 @@ class DashboardSection:
# TODO: Header/description field # TODO: Header/description field
table = self.make_table() table = self.make_table()
page.add_field( 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, value=table,
inline=False inline=False
) )