[upd] nested settings
This commit is contained in:
@@ -23,15 +23,15 @@ async def on_input_setting(
|
|||||||
state: FSMContext,
|
state: FSMContext,
|
||||||
user_settings: FromDishka[UserSettings],
|
user_settings: FromDishka[UserSettings],
|
||||||
):
|
):
|
||||||
_, _, section, field = callback.data.split(":")
|
_, _, path, field = callback.data.split(":")
|
||||||
|
|
||||||
await state.update_data(section=section, field=field)
|
await state.update_data(path=path, field=field)
|
||||||
await state.set_state(SettingsStates.waiting_for_input)
|
await state.set_state(SettingsStates.waiting_for_input)
|
||||||
|
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=callback.from_user.id,
|
chat_id=callback.from_user.id,
|
||||||
text=SettingsMenuGenerator.get_input_prompt(section, field, user_settings),
|
text=SettingsMenuGenerator.get_input_prompt(path, field, user_settings),
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,22 +43,32 @@ async def on_input_value(
|
|||||||
user_settings: FromDishka[UserSettings],
|
user_settings: FromDishka[UserSettings],
|
||||||
):
|
):
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
section = data["section"]
|
path = data["path"]
|
||||||
field = data["field"]
|
field = data["field"]
|
||||||
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
section_obj = getattr(user_settings, section)
|
if path == "root":
|
||||||
|
setattr(user_settings, field, message.text)
|
||||||
|
|
||||||
setattr(section_obj, field, message.text)
|
await UserSettingsDocument.update_setting(
|
||||||
|
message.from_user.id, field, message.text
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
section = user_settings.get_setting(path)
|
||||||
|
setattr(section, field, message.text)
|
||||||
|
|
||||||
await UserSettingsDocument.update_field(
|
await UserSettingsDocument.update_setting(
|
||||||
message.from_user.id, section, field, message.text
|
message.from_user.id, f"{path}.{field}", message.text
|
||||||
)
|
)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
await message.answer("Setting not found")
|
||||||
|
return
|
||||||
|
|
||||||
await message.answer(
|
await message.answer(
|
||||||
"✅ Setting updated!",
|
"✅ Setting updated!",
|
||||||
reply_markup=SettingsMenuGenerator.get_section_menu(
|
reply_markup=SettingsMenuGenerator.get_section_menu(
|
||||||
section, user_settings, router
|
path, user_settings, router
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
from aiogram import Bot, F, Router, types
|
from aiogram import Bot, F, Router, types
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
from aiogram.types import CallbackQuery
|
from aiogram.types import CallbackQuery
|
||||||
|
from dishka import FromDishka
|
||||||
|
|
||||||
from bot.keyboards.settings import SettingsMenuGenerator
|
from bot.keyboards.settings import SettingsMenuGenerator
|
||||||
|
from bot.modules.settings import UserSettings
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "settings:main")
|
@router.callback_query(F.data == "settings:main")
|
||||||
async def on_main_settings(callback: CallbackQuery, bot: Bot):
|
async def on_main_settings(
|
||||||
|
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
||||||
|
):
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
await bot.edit_message_text(
|
await bot.edit_message_text(
|
||||||
"⚙️ Settings",
|
"⚙️ Settings",
|
||||||
chat_id=callback.from_user.id,
|
chat_id=callback.from_user.id,
|
||||||
message_id=callback.message.message_id,
|
message_id=callback.message.message_id,
|
||||||
reply_markup=SettingsMenuGenerator.get_main_menu(router),
|
reply_markup=SettingsMenuGenerator.get_main_menu(user_settings, router),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("settings"))
|
@router.message(Command("settings"))
|
||||||
async def on_settings_command(message: types.Message):
|
async def on_settings_command(
|
||||||
|
message: types.Message, user_settings: FromDishka[UserSettings]
|
||||||
|
):
|
||||||
await message.answer(
|
await message.answer(
|
||||||
"⚙️ Settings", reply_markup=SettingsMenuGenerator.get_main_menu()
|
"⚙️ Settings", reply_markup=SettingsMenuGenerator.get_main_menu(user_settings)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,14 +12,17 @@ router = Router()
|
|||||||
async def on_section_settings(
|
async def on_section_settings(
|
||||||
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
||||||
):
|
):
|
||||||
section = callback.data.split(":")[-1]
|
path = callback.data.split(":", 2)[-1]
|
||||||
|
|
||||||
|
section_title = path.split(".")[-1].capitalize()
|
||||||
|
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
await bot.edit_message_text(
|
await bot.edit_message_text(
|
||||||
f"⚙️ {section.capitalize()} settings",
|
f"⚙️ {section_title} settings",
|
||||||
chat_id=callback.from_user.id,
|
chat_id=callback.from_user.id,
|
||||||
message_id=callback.message.message_id,
|
message_id=callback.message.message_id,
|
||||||
reply_markup=SettingsMenuGenerator.get_section_menu(
|
reply_markup=SettingsMenuGenerator.get_section_menu(
|
||||||
section, user_settings, router
|
path, user_settings, router
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,11 +33,19 @@ async def on_section_settings(
|
|||||||
async def on_select_setting(
|
async def on_select_setting(
|
||||||
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
||||||
):
|
):
|
||||||
_, mode, section, field = callback.data.split(":")
|
_, mode, path, field = callback.data.split(":")
|
||||||
|
|
||||||
section_obj = getattr(user_settings, section)
|
if path == "root":
|
||||||
|
section = user_settings
|
||||||
|
info = section.get_info(field)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
section = user_settings.get_setting(path)
|
||||||
|
info = section.get_info(field)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
await callback.answer("Setting not found")
|
||||||
|
return
|
||||||
|
|
||||||
info = section_obj.get_info(field)
|
|
||||||
if not info:
|
if not info:
|
||||||
await callback.answer("Setting info not found")
|
await callback.answer("Setting info not found")
|
||||||
return
|
return
|
||||||
@@ -45,6 +56,6 @@ async def on_select_setting(
|
|||||||
chat_id=callback.from_user.id,
|
chat_id=callback.from_user.id,
|
||||||
message_id=callback.message.message_id,
|
message_id=callback.message.message_id,
|
||||||
reply_markup=getattr(SettingsMenuGenerator, f"get_{mode}_menu")(
|
reply_markup=getattr(SettingsMenuGenerator, f"get_{mode}_menu")(
|
||||||
section, field, user_settings, router
|
path, field, user_settings, router
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,23 +13,37 @@ router = Router()
|
|||||||
async def on_toggle_setting(
|
async def on_toggle_setting(
|
||||||
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
||||||
):
|
):
|
||||||
_, _, section, field = callback.data.split(":")
|
_, _, path, field = callback.data.split(":")
|
||||||
|
|
||||||
section_obj = getattr(user_settings, section)
|
if path == "root":
|
||||||
|
section = user_settings
|
||||||
|
current_value = getattr(section, field)
|
||||||
|
new_value = not current_value
|
||||||
|
setattr(section, field, new_value)
|
||||||
|
|
||||||
current_value = getattr(section_obj, field)
|
await UserSettingsDocument.update_setting(
|
||||||
setattr(section_obj, field, not current_value)
|
callback.from_user.id, field, new_value
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
section = user_settings.get_setting(path)
|
||||||
|
current_value = getattr(section, field)
|
||||||
|
new_value = not current_value
|
||||||
|
setattr(section, field, new_value)
|
||||||
|
|
||||||
await UserSettingsDocument.update_field(
|
await UserSettingsDocument.update_setting(
|
||||||
callback.from_user.id, section, field, not current_value
|
callback.from_user.id, f"{path}.{field}", new_value
|
||||||
)
|
)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
await callback.answer("Setting not found")
|
||||||
|
return
|
||||||
|
|
||||||
await callback.answer("Setting updated")
|
await callback.answer("Setting updated")
|
||||||
await bot.edit_message_reply_markup(
|
await bot.edit_message_reply_markup(
|
||||||
chat_id=callback.from_user.id,
|
chat_id=callback.from_user.id,
|
||||||
message_id=callback.message.message_id,
|
message_id=callback.message.message_id,
|
||||||
reply_markup=SettingsMenuGenerator.get_section_menu(
|
reply_markup=SettingsMenuGenerator.get_section_menu(
|
||||||
section, user_settings, router
|
path, user_settings, router
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,21 +52,31 @@ async def on_toggle_setting(
|
|||||||
async def on_set_value(
|
async def on_set_value(
|
||||||
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
||||||
):
|
):
|
||||||
_, _, section, field, value = callback.data.split(":")
|
_, _, path, field, value = callback.data.split(":")
|
||||||
|
|
||||||
section_obj = getattr(user_settings, section)
|
if path == "root":
|
||||||
|
section = user_settings
|
||||||
|
setattr(section, field, value)
|
||||||
|
|
||||||
setattr(section_obj, field, value)
|
await UserSettingsDocument.update_setting(callback.from_user.id, field, value)
|
||||||
await UserSettingsDocument.update_field(
|
else:
|
||||||
callback.from_user.id, section, field, value
|
try:
|
||||||
)
|
section = user_settings.get_setting(path)
|
||||||
|
setattr(section, field, value)
|
||||||
|
|
||||||
|
await UserSettingsDocument.update_setting(
|
||||||
|
callback.from_user.id, f"{path}.{field}", value
|
||||||
|
)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
await callback.answer("Setting not found")
|
||||||
|
return
|
||||||
|
|
||||||
await callback.answer("Setting updated")
|
await callback.answer("Setting updated")
|
||||||
await bot.edit_message_reply_markup(
|
await bot.edit_message_reply_markup(
|
||||||
chat_id=callback.from_user.id,
|
chat_id=callback.from_user.id,
|
||||||
message_id=callback.message.message_id,
|
message_id=callback.message.message_id,
|
||||||
reply_markup=SettingsMenuGenerator.get_select_menu(
|
reply_markup=SettingsMenuGenerator.get_select_menu(
|
||||||
section, field, user_settings, router
|
path, field, user_settings, router
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,11 +85,19 @@ async def on_set_value(
|
|||||||
async def on_move_item(
|
async def on_move_item(
|
||||||
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
callback: CallbackQuery, bot: Bot, user_settings: FromDishka[UserSettings]
|
||||||
):
|
):
|
||||||
_, _, section, field, index, direction = callback.data.split(":")
|
_, _, path, field, index, direction = callback.data.split(":")
|
||||||
index = int(index)
|
index = int(index)
|
||||||
|
|
||||||
section_obj = getattr(user_settings, section)
|
if path == "root":
|
||||||
current_list = getattr(section_obj, field)
|
section = user_settings
|
||||||
|
current_list = getattr(section, field)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
section = user_settings.get_setting(path)
|
||||||
|
current_list = getattr(section, field)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
await callback.answer("Setting not found")
|
||||||
|
return
|
||||||
|
|
||||||
if direction == "up" and index > 0:
|
if direction == "up" and index > 0:
|
||||||
current_list[index], current_list[index - 1] = (
|
current_list[index], current_list[index - 1] = (
|
||||||
@@ -78,17 +110,22 @@ async def on_move_item(
|
|||||||
current_list[index],
|
current_list[index],
|
||||||
)
|
)
|
||||||
|
|
||||||
setattr(section_obj, field, current_list)
|
setattr(section, field, current_list)
|
||||||
|
|
||||||
await UserSettingsDocument.update_field(
|
if path == "root":
|
||||||
callback.from_user.id, section, field, current_list
|
await UserSettingsDocument.update_setting(
|
||||||
)
|
callback.from_user.id, field, current_list
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await UserSettingsDocument.update_setting(
|
||||||
|
callback.from_user.id, f"{path}.{field}", current_list
|
||||||
|
)
|
||||||
|
|
||||||
await callback.answer("Order updated")
|
await callback.answer("Order updated")
|
||||||
await bot.edit_message_reply_markup(
|
await bot.edit_message_reply_markup(
|
||||||
chat_id=callback.from_user.id,
|
chat_id=callback.from_user.id,
|
||||||
message_id=callback.message.message_id,
|
message_id=callback.message.message_id,
|
||||||
reply_markup=SettingsMenuGenerator.get_list_menu(
|
reply_markup=SettingsMenuGenerator.get_list_menu(
|
||||||
section, field, user_settings, router
|
path, field, user_settings, router
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from aiogram import Router
|
|||||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
|
||||||
from bot.keyboards.utils import Paginator
|
from bot.keyboards.utils import Paginator
|
||||||
from bot.modules.settings import UserSettings
|
|
||||||
from bot.modules.settings.model import BaseSettings, SettingInfo
|
from bot.modules.settings.model import BaseSettings, SettingInfo
|
||||||
|
|
||||||
|
|
||||||
@@ -13,26 +12,73 @@ class SettingsMenuGenerator:
|
|||||||
ITEMS_PER_PAGE = 5
|
ITEMS_PER_PAGE = 5
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_main_menu(cls, router: Optional[Router] = None) -> InlineKeyboardMarkup:
|
def get_main_menu(
|
||||||
|
cls, settings: BaseSettings, router: Optional[Router] = None
|
||||||
|
) -> InlineKeyboardMarkup:
|
||||||
keyboard = []
|
keyboard = []
|
||||||
fields = get_type_hints(UserSettings)
|
fields = get_type_hints(settings.__class__)
|
||||||
|
|
||||||
for field_name, field_type in fields.items():
|
sorted_fields = sorted(fields.items(), key=lambda x: x[0])
|
||||||
if (
|
|
||||||
not isinstance(field_type, type)
|
for field_name, field_type in sorted_fields:
|
||||||
or not issubclass(field_type, BaseSettings)
|
if field_name.startswith("_"):
|
||||||
or field_name.startswith("_")
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
keyboard.append(
|
if hasattr(settings, field_name):
|
||||||
[
|
field_value = getattr(settings, field_name)
|
||||||
InlineKeyboardButton(
|
|
||||||
text=field_name.capitalize(),
|
if isinstance(field_value, BaseSettings):
|
||||||
callback_data=f"settings:section:{field_name}",
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=field_name.capitalize(),
|
||||||
|
callback_data=f"settings:section:{field_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
]
|
|
||||||
)
|
elif not field_name.endswith("_info"):
|
||||||
|
info = settings.get_info(field_name)
|
||||||
|
if info:
|
||||||
|
value = field_value
|
||||||
|
if isinstance(value, bool):
|
||||||
|
value_text = "✅ Enabled" if value else "❌ Disabled"
|
||||||
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{info.title}: {value_text}",
|
||||||
|
callback_data=f"settings:toggle:root:{field_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
elif field_type == str and info.options:
|
||||||
|
value_text = info.options.get(value, value)
|
||||||
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{info.title}: {value_text}",
|
||||||
|
callback_data=f"settings:select:root:{field_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
elif field_type == List[str]:
|
||||||
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{info.title}",
|
||||||
|
callback_data=f"settings:list:root:{field_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{info.title}: {value}",
|
||||||
|
callback_data=f"settings:input:root:{field_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
paginator = Paginator(
|
paginator = Paginator(
|
||||||
data=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
data=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||||
@@ -46,13 +92,17 @@ class SettingsMenuGenerator:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_section_menu(
|
def get_section_menu(
|
||||||
cls,
|
cls,
|
||||||
section_name: str,
|
path: str,
|
||||||
settings: UserSettings,
|
settings: BaseSettings,
|
||||||
router: Optional[Router] = None,
|
router: Optional[Router] = None,
|
||||||
page: int = 0,
|
page: int = 0,
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
section = getattr(settings, section_name)
|
if path == "root":
|
||||||
if not section:
|
return cls.get_main_menu(settings, router)
|
||||||
|
|
||||||
|
try:
|
||||||
|
section = settings.get_setting(path)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
return InlineKeyboardMarkup(
|
return InlineKeyboardMarkup(
|
||||||
inline_keyboard=[
|
inline_keyboard=[
|
||||||
[
|
[
|
||||||
@@ -63,9 +113,23 @@ class SettingsMenuGenerator:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
field_info = section.get_all_info()
|
if not isinstance(section, BaseSettings):
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=cls.BACK_BUTTON_TEXT, callback_data="settings:main"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
keyboard = []
|
||||||
|
|
||||||
fields = get_type_hints(section.__class__)
|
fields = get_type_hints(section.__class__)
|
||||||
|
|
||||||
|
field_info = section.get_all_info()
|
||||||
|
|
||||||
field_names = [
|
field_names = [
|
||||||
f
|
f
|
||||||
for f in fields.keys()
|
for f in fields.keys()
|
||||||
@@ -76,40 +140,53 @@ class SettingsMenuGenerator:
|
|||||||
key=lambda f: field_info.get(f, SettingInfo("", "", order=999)).order
|
key=lambda f: field_info.get(f, SettingInfo("", "", order=999)).order
|
||||||
)
|
)
|
||||||
|
|
||||||
keyboard = []
|
|
||||||
for field_name in field_names:
|
for field_name in field_names:
|
||||||
value = getattr(section, field_name)
|
value = getattr(section, field_name)
|
||||||
|
field_type = fields.get(field_name)
|
||||||
|
|
||||||
info = section.get_info(field_name)
|
info = section.get_info(field_name)
|
||||||
if info is None:
|
if info is None:
|
||||||
continue
|
title = field_name.replace("_", " ").capitalize()
|
||||||
|
info = SettingInfo(title=title, description=f"Configure {title}")
|
||||||
|
|
||||||
field_type = fields[field_name]
|
if isinstance(value, BaseSettings):
|
||||||
if field_type == bool:
|
nested_title = (
|
||||||
|
info.title if info and info.title else field_name.capitalize()
|
||||||
|
)
|
||||||
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{nested_title} ➡️",
|
||||||
|
callback_data=f"settings:section:{path}.{field_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
elif field_type == bool or isinstance(value, bool):
|
||||||
value_text = "✅ Enabled" if value else "❌ Disabled"
|
value_text = "✅ Enabled" if value else "❌ Disabled"
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=f"{info.title}: {value_text}",
|
text=f"{info.title}: {value_text}",
|
||||||
callback_data=f"settings:toggle:{section_name}:{field_name}",
|
callback_data=f"settings:toggle:{path}:{field_name}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
elif field_type == str and info.options:
|
elif (field_type == str or isinstance(value, str)) and info.options:
|
||||||
value_text = info.options.get(value, value)
|
value_text = info.options.get(value, value)
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=f"{info.title}: {value_text}",
|
text=f"{info.title}: {value_text}",
|
||||||
callback_data=f"settings:select:{section_name}:{field_name}",
|
callback_data=f"settings:select:{path}:{field_name}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
elif field_type == List[str]:
|
elif field_type == List[str] or isinstance(value, list):
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=f"{info.title}",
|
text=f"{info.title}",
|
||||||
callback_data=f"settings:list:{section_name}:{field_name}",
|
callback_data=f"settings:list:{path}:{field_name}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -118,131 +195,109 @@ class SettingsMenuGenerator:
|
|||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=f"{info.title}: {value}",
|
text=f"{info.title}: {value}",
|
||||||
callback_data=f"settings:input:{section_name}:{field_name}",
|
callback_data=f"settings:input:{path}:{field_name}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
back_target = "settings:main"
|
||||||
|
if "." in path:
|
||||||
|
parent_path = path.rsplit(".", 1)[0]
|
||||||
|
back_target = f"settings:section:{parent_path}"
|
||||||
|
|
||||||
after_data = [
|
after_data = [
|
||||||
[
|
[InlineKeyboardButton(text=cls.BACK_BUTTON_TEXT, callback_data=back_target)]
|
||||||
InlineKeyboardButton(
|
|
||||||
text=cls.BACK_BUTTON_TEXT, callback_data="settings:main"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
paginator = Paginator(
|
paginator = Paginator(
|
||||||
data=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
data=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||||
after_data=after_data,
|
after_data=after_data,
|
||||||
callback_startswith=f"settings_section_{section_name}_page_",
|
callback_startswith=f"settings_section_{path.replace('.', '_')}_page_",
|
||||||
size=cls.ITEMS_PER_PAGE,
|
size=cls.ITEMS_PER_PAGE,
|
||||||
dp=router,
|
dp=router,
|
||||||
)
|
)
|
||||||
|
|
||||||
return paginator(current_page=page)
|
return paginator(current_page=page)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_select_menu(
|
def get_select_menu(
|
||||||
cls,
|
cls,
|
||||||
section_name: str,
|
path: str,
|
||||||
field_name: str,
|
field: str,
|
||||||
settings: UserSettings,
|
settings: BaseSettings,
|
||||||
router: Optional[Router] = None,
|
router: Optional[Router] = None,
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
section = getattr(settings, section_name)
|
if path == "root":
|
||||||
|
section = settings
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
section = settings.get_setting(path)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
return cls._get_default_back_menu("settings:main")
|
||||||
|
|
||||||
if not section:
|
info = section.get_info(field)
|
||||||
return InlineKeyboardMarkup(
|
|
||||||
inline_keyboard=[
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text=cls.BACK_BUTTON_TEXT, callback_data="settings:main"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
info = section.get_info(field_name)
|
|
||||||
if not info or not info.options:
|
if not info or not info.options:
|
||||||
return InlineKeyboardMarkup(
|
return cls._get_default_back_menu(f"settings:section:{path}")
|
||||||
inline_keyboard=[
|
|
||||||
[
|
current_value = getattr(section, field)
|
||||||
InlineKeyboardButton(
|
|
||||||
text=cls.BACK_BUTTON_TEXT,
|
|
||||||
callback_data=f"settings:section:{section_name}",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
keyboard = []
|
keyboard = []
|
||||||
for option_key, option_text in info.options.items():
|
for option_key, option_text in info.options.items():
|
||||||
prefix = "✅ " if option_key == getattr(section, field_name) else ""
|
prefix = "✅ " if option_key == current_value else ""
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=f"{prefix}{option_text}",
|
text=f"{prefix}{option_text}",
|
||||||
callback_data=f"settings:set:{section_name}:{field_name}:{option_key}",
|
callback_data=f"settings:set:{path}:{field}:{option_key}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
after_data = [
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=cls.BACK_BUTTON_TEXT,
|
||||||
|
callback_data=f"settings:section:{path}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
callback_prefix = path.replace(".", "_") if path != "root" else "root"
|
||||||
paginator = Paginator(
|
paginator = Paginator(
|
||||||
data=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
data=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||||
after_data=[
|
after_data=after_data,
|
||||||
[
|
callback_startswith=f"settings_select_{callback_prefix}_{field}_page_",
|
||||||
InlineKeyboardButton(
|
|
||||||
text=cls.BACK_BUTTON_TEXT,
|
|
||||||
callback_data=f"settings:section:{section_name}",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
],
|
|
||||||
callback_startswith=f"settings_select_{section_name}_{field_name}_page_",
|
|
||||||
size=cls.ITEMS_PER_PAGE,
|
size=cls.ITEMS_PER_PAGE,
|
||||||
dp=router,
|
dp=router,
|
||||||
)
|
)
|
||||||
|
|
||||||
return paginator()
|
return paginator()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_list_menu(
|
def get_list_menu(
|
||||||
cls,
|
cls,
|
||||||
section_name: str,
|
path: str,
|
||||||
field_name: str,
|
field: str,
|
||||||
settings: UserSettings,
|
settings: BaseSettings,
|
||||||
router: Optional[Router] = None,
|
router: Optional[Router] = None,
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
section = getattr(settings, section_name)
|
if path == "root":
|
||||||
if not section:
|
section = settings
|
||||||
return InlineKeyboardMarkup(
|
else:
|
||||||
inline_keyboard=[
|
try:
|
||||||
[
|
section = settings.get_setting(path)
|
||||||
InlineKeyboardButton(
|
except (AttributeError, KeyError):
|
||||||
text=cls.BACK_BUTTON_TEXT, callback_data="settings:main"
|
return cls._get_default_back_menu("settings:main")
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
info = section.get_info(field_name)
|
info = section.get_info(field)
|
||||||
if not info:
|
if not info:
|
||||||
return InlineKeyboardMarkup(
|
return cls._get_default_back_menu(f"settings:section:{path}")
|
||||||
inline_keyboard=[
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text=cls.BACK_BUTTON_TEXT,
|
|
||||||
callback_data=f"settings:section:{section_name}",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
current_list = getattr(section, field_name)
|
current_list = getattr(section, field)
|
||||||
|
if not isinstance(current_list, list):
|
||||||
|
return cls._get_default_back_menu(f"settings:section:{path}")
|
||||||
|
|
||||||
keyboard = []
|
keyboard = []
|
||||||
for i, item in enumerate(current_list):
|
for i, item in enumerate(current_list):
|
||||||
item_text = info.options.get(item, item) if info.options else item
|
item_text = info.options.get(item, item) if info.options else item
|
||||||
|
|
||||||
row = [
|
row = [
|
||||||
InlineKeyboardButton(text=f"{i + 1}. {item_text}", callback_data="_")
|
InlineKeyboardButton(text=f"{i + 1}. {item_text}", callback_data="_")
|
||||||
]
|
]
|
||||||
@@ -252,14 +307,14 @@ class SettingsMenuGenerator:
|
|||||||
move_buttons.append(
|
move_buttons.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="⬆️",
|
text="⬆️",
|
||||||
callback_data=f"settings:move:{section_name}:{field_name}:{i}:up",
|
callback_data=f"settings:move:{path}:{field}:{i}:up",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if i < len(current_list) - 1:
|
if i < len(current_list) - 1:
|
||||||
move_buttons.append(
|
move_buttons.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="⬇️",
|
text="⬇️",
|
||||||
callback_data=f"settings:move:{section_name}:{field_name}:{i}:down",
|
callback_data=f"settings:move:{path}:{field}:{i}:down",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -269,31 +324,45 @@ class SettingsMenuGenerator:
|
|||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=cls.BACK_BUTTON_TEXT,
|
text=cls.BACK_BUTTON_TEXT,
|
||||||
callback_data=f"settings:section:{section_name}",
|
callback_data=f"settings:section:{path}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
callback_prefix = path.replace(".", "_") if path != "root" else "root"
|
||||||
paginator = Paginator(
|
paginator = Paginator(
|
||||||
data=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
data=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||||
after_data=after_data,
|
after_data=after_data,
|
||||||
callback_startswith=f"settings_list_{section_name}_{field_name}_page_",
|
callback_startswith=f"settings_list_{callback_prefix}_{field}_page_",
|
||||||
size=cls.ITEMS_PER_PAGE,
|
size=cls.ITEMS_PER_PAGE,
|
||||||
dp=router,
|
dp=router,
|
||||||
)
|
)
|
||||||
|
|
||||||
return paginator()
|
return paginator()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_input_prompt(
|
def get_input_prompt(cls, path: str, field: str, settings: BaseSettings) -> str:
|
||||||
cls, section_name: str, field_name: str, settings: UserSettings
|
if path == "root":
|
||||||
) -> str:
|
section = settings
|
||||||
section = getattr(settings, section_name)
|
else:
|
||||||
if not section:
|
try:
|
||||||
return "Enter your value:"
|
section = settings.get_setting(path)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
return "Enter your value:"
|
||||||
|
|
||||||
info = section.get_info(field_name)
|
info = section.get_info(field)
|
||||||
if not info:
|
if not info:
|
||||||
return "Enter your value:"
|
return "Enter your value:"
|
||||||
|
|
||||||
return f"<b>{info.title}</b>\n{info.description}\n\nPlease enter your value:"
|
return f"<b>{info.title}</b>\n{info.description}\n\nPlease enter your value:"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_default_back_menu(cls, back_target: str) -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=cls.BACK_BUTTON_TEXT, callback_data=back_target
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from typing import ClassVar, Dict, Optional, Unpack
|
from typing import Any, ClassVar, Dict, Generic, Optional, TypeVar, Unpack
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class SettingInfo:
|
class SettingInfo:
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -35,6 +37,103 @@ class BaseSettings(BaseModel):
|
|||||||
def get_all_info(self) -> Dict[str, SettingInfo]:
|
def get_all_info(self) -> Dict[str, SettingInfo]:
|
||||||
return self.__class__._settings_info
|
return self.__class__._settings_info
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_nested_value(obj: Any, path: str):
|
||||||
|
if not path or path == "root":
|
||||||
|
return obj
|
||||||
|
|
||||||
|
parts = path.split(".", 1)
|
||||||
|
current = parts[0]
|
||||||
|
|
||||||
|
if not hasattr(obj, current):
|
||||||
|
raise AttributeError(f"Object has no attribute '{current}'")
|
||||||
|
|
||||||
|
if len(parts) == 1:
|
||||||
|
return getattr(obj, current)
|
||||||
|
|
||||||
|
return BaseSettings._get_nested_value(getattr(obj, current), parts[1])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _set_nested_value(obj: Any, path: str, value: Any):
|
||||||
|
if not path or path == "root":
|
||||||
|
return
|
||||||
|
|
||||||
|
parts = path.split(".", 1)
|
||||||
|
current = parts[0]
|
||||||
|
|
||||||
|
if not hasattr(obj, current):
|
||||||
|
raise AttributeError(f"Object has no attribute '{current}'")
|
||||||
|
|
||||||
|
if len(parts) == 1:
|
||||||
|
setattr(obj, current, value)
|
||||||
|
return
|
||||||
|
|
||||||
|
BaseSettings._set_nested_value(getattr(obj, current), parts[1], value)
|
||||||
|
|
||||||
|
def get_setting(self, path: str) -> Any:
|
||||||
|
return self._get_nested_value(self, path)
|
||||||
|
|
||||||
|
def set_setting(self, path: str, value: Any) -> None:
|
||||||
|
self._set_nested_value(self, path, value)
|
||||||
|
|
||||||
|
def get_nested_info(self, path: str) -> Optional[SettingInfo]:
|
||||||
|
if not path or path == "root":
|
||||||
|
return None
|
||||||
|
|
||||||
|
parts = path.split(".")
|
||||||
|
if len(parts) == 1:
|
||||||
|
return self.get_info(parts[0])
|
||||||
|
|
||||||
|
section_name = parts[0]
|
||||||
|
if not hasattr(self, section_name):
|
||||||
|
return None
|
||||||
|
|
||||||
|
section = getattr(self, section_name)
|
||||||
|
if not isinstance(section, BaseSettings):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return section.get_nested_info(".".join(parts[1:]))
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
populate_by_name = True
|
populate_by_name = True
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsAccessor(Generic[T]):
|
||||||
|
def __init__(self, root_settings: BaseSettings, path: str = ""):
|
||||||
|
self._root = root_settings
|
||||||
|
self._path = path
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> Any:
|
||||||
|
full_path = f"{self._path}.{name}" if self._path else name
|
||||||
|
|
||||||
|
value = self._root.get_setting(full_path)
|
||||||
|
|
||||||
|
if isinstance(value, BaseSettings):
|
||||||
|
return SettingsAccessor(self._root, full_path)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __setattr__(self, name: str, value: Any) -> None:
|
||||||
|
if name.startswith("_"):
|
||||||
|
super().__setattr__(name, value)
|
||||||
|
return
|
||||||
|
|
||||||
|
full_path = f"{self._path}.{name}" if self._path else name
|
||||||
|
self._root.set_setting(full_path, value)
|
||||||
|
|
||||||
|
|
||||||
|
class AccessibleSettings(BaseSettings):
|
||||||
|
def __init__(self, **data):
|
||||||
|
super().__init__(**data)
|
||||||
|
self._accessor = SettingsAccessor(self)
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> Any:
|
||||||
|
try:
|
||||||
|
return super().__getattr__(name)
|
||||||
|
except AttributeError:
|
||||||
|
if name.startswith("_"):
|
||||||
|
raise
|
||||||
|
if hasattr(self._accessor, name):
|
||||||
|
return getattr(self._accessor, name)
|
||||||
|
raise
|
||||||
|
|||||||
@@ -2,7 +2,68 @@ from typing import ClassVar, List
|
|||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from bot.modules.settings.model import BaseSettings, SettingInfo
|
from .model import AccessibleSettings, BaseSettings, SettingInfo
|
||||||
|
|
||||||
|
|
||||||
|
class FormatSettings(BaseSettings):
|
||||||
|
bitrate: str = Field(default=320)
|
||||||
|
bitrate_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Bitrate",
|
||||||
|
description="Audio quality in kbps",
|
||||||
|
options={
|
||||||
|
"128": "Low (128kbps)",
|
||||||
|
"256": "Medium (256kbps)",
|
||||||
|
"320": "High (320kbps)",
|
||||||
|
},
|
||||||
|
order=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
codec: str = Field(default="mp3")
|
||||||
|
codec_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Audio Codec",
|
||||||
|
description="Format to encode audio files",
|
||||||
|
options={"mp3": "MP3", "ogg": "OGG Vorbis", "m4a": "AAC"},
|
||||||
|
order=20,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicSettings(BaseSettings):
|
||||||
|
format: FormatSettings = Field(default_factory=FormatSettings)
|
||||||
|
format_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Format Settings",
|
||||||
|
description="Audio format configuration",
|
||||||
|
order=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
normalize_volume: bool = Field(default=True)
|
||||||
|
normalize_volume_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Normalize Volume",
|
||||||
|
description="Automatically adjust volume to a standard level",
|
||||||
|
order=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
add_metadata: bool = Field(default=True)
|
||||||
|
add_metadata_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Add Metadata",
|
||||||
|
description="Include artist, album and track information in files",
|
||||||
|
order=40,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationSettings(BaseSettings):
|
||||||
|
downloads_complete: bool = Field(default=True)
|
||||||
|
downloads_complete_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Download Notifications",
|
||||||
|
description="Notify when downloads are complete",
|
||||||
|
order=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
new_features: bool = Field(default=True)
|
||||||
|
new_features_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Feature Announcements",
|
||||||
|
description="Receive notifications about new bot features",
|
||||||
|
order=20,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SearchSettings(BaseSettings):
|
class SearchSettings(BaseSettings):
|
||||||
@@ -21,24 +82,6 @@ class SearchSettings(BaseSettings):
|
|||||||
order=20,
|
order=20,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DownloadSettings(BaseSettings):
|
|
||||||
recode_youtube: bool = Field(default=True)
|
|
||||||
recode_youtube_info: ClassVar[SettingInfo] = SettingInfo(
|
|
||||||
title="Recode YouTube",
|
|
||||||
description="Recode when downloading from YouTube to more compatible format (may take some time)",
|
|
||||||
order=10,
|
|
||||||
)
|
|
||||||
|
|
||||||
exact_spotify_match: bool = Field(default=True)
|
|
||||||
exact_spotify_match_info: ClassVar[SettingInfo] = SettingInfo(
|
|
||||||
title="Exact Spotify matches",
|
|
||||||
description="When searching on YouTube from Spotify, show only exact matches",
|
|
||||||
order=20,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderSettings(BaseSettings):
|
|
||||||
order: List[str] = Field(default=["youtube", "deezer", "soundcloud", "spotify"])
|
order: List[str] = Field(default=["youtube", "deezer", "soundcloud", "spotify"])
|
||||||
order_info: ClassVar[SettingInfo] = SettingInfo(
|
order_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
title="Provider order",
|
title="Provider order",
|
||||||
@@ -49,22 +92,36 @@ class ProviderSettings(BaseSettings):
|
|||||||
"soundcloud": "SoundCloud",
|
"soundcloud": "SoundCloud",
|
||||||
"spotify": "Spotify",
|
"spotify": "Spotify",
|
||||||
},
|
},
|
||||||
order=10,
|
order=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AppearanceSettings(BaseSettings):
|
class UserSettings(AccessibleSettings):
|
||||||
theme: str = Field(default="default")
|
|
||||||
theme_info: ClassVar[SettingInfo] = SettingInfo(
|
|
||||||
title="Theme",
|
|
||||||
description="Visual appearance of the bot",
|
|
||||||
options={"default": "Default", "compact": "Compact", "emoji": "Emoji Rich"},
|
|
||||||
order=10,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UserSettings(BaseSettings):
|
|
||||||
search: SearchSettings = Field(default_factory=SearchSettings)
|
search: SearchSettings = Field(default_factory=SearchSettings)
|
||||||
download: DownloadSettings = Field(default_factory=DownloadSettings)
|
search_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
providers: ProviderSettings = Field(default_factory=ProviderSettings)
|
title="Search Settings",
|
||||||
appearance: AppearanceSettings = Field(default_factory=AppearanceSettings)
|
description="Configure search behavior",
|
||||||
|
order=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
music: MusicSettings = Field(default_factory=MusicSettings)
|
||||||
|
music_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Music Settings",
|
||||||
|
description="Configure music playback and downloads",
|
||||||
|
order=20,
|
||||||
|
)
|
||||||
|
|
||||||
|
notifications: NotificationSettings = Field(default_factory=NotificationSettings)
|
||||||
|
notifications_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Notifications",
|
||||||
|
description="Configure notification preferences",
|
||||||
|
order=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
language: str = Field(default="en")
|
||||||
|
language_info: ClassVar[SettingInfo] = SettingInfo(
|
||||||
|
title="Language",
|
||||||
|
description="Bot interface language",
|
||||||
|
options={"en": "English", "es": "Spanish", "fr": "French", "de": "German"},
|
||||||
|
order=40,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
from beanie import Document
|
from beanie import Document
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
@@ -27,27 +27,24 @@ class UserSettingsDocument(Document):
|
|||||||
await self.save()
|
await self.save()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def update_section(cls, user_id: int, section: str, value: Any) -> None:
|
async def update_setting(cls, user_id: int, path: str, value: Any) -> None:
|
||||||
doc = await cls.find_one(cls.user_id == user_id)
|
doc = await cls.find_one(cls.user_id == user_id)
|
||||||
if not doc:
|
if not doc:
|
||||||
doc = cls(user_id=user_id)
|
doc = cls(user_id=user_id)
|
||||||
setattr(doc.settings, section, value)
|
|
||||||
await doc.insert()
|
await doc.insert()
|
||||||
else:
|
|
||||||
setattr(doc.settings, section, value)
|
doc.settings.set_setting(path, value)
|
||||||
await doc.save()
|
await doc.save()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def update_field(
|
async def get_setting(cls, user_id: int, path: str) -> Any:
|
||||||
cls, user_id: int, section: str, field: str, value: Any
|
|
||||||
) -> None:
|
|
||||||
doc = await cls.find_one(cls.user_id == user_id)
|
doc = await cls.find_one(cls.user_id == user_id)
|
||||||
if not doc:
|
if not doc:
|
||||||
doc = cls(user_id=user_id)
|
return UserSettings().get_setting(path)
|
||||||
section_obj = getattr(doc.settings, section)
|
|
||||||
setattr(section_obj, field, value)
|
return doc.settings.get_setting(path)
|
||||||
await doc.insert()
|
|
||||||
else:
|
@classmethod
|
||||||
section_obj = getattr(doc.settings, section)
|
async def get_setting_info(cls, path: str) -> Optional[Any]:
|
||||||
setattr(section_obj, field, value)
|
settings = UserSettings()
|
||||||
await doc.save()
|
return settings.get_nested_info(path)
|
||||||
|
|||||||
Reference in New Issue
Block a user