Управление фабрикой состояний
This commit is contained in:
650
bot/utils/state_utils.py
Normal file
650
bot/utils/state_utils.py
Normal file
@@ -0,0 +1,650 @@
|
|||||||
|
"""
|
||||||
|
Утилиты для работы с FSM состояниями и обновлениями
|
||||||
|
"""
|
||||||
|
from typing import Optional, Any, Set, Union
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from aiogram.fsm.state import State
|
||||||
|
from aiogram.types import CallbackQuery, Message, ReplyKeyboardRemove
|
||||||
|
from aiogram.exceptions import TelegramBadRequest
|
||||||
|
|
||||||
|
from middleware.loggers import logger
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'clear_state',
|
||||||
|
'answer_callback',
|
||||||
|
'safe_answer_callback',
|
||||||
|
'safe_delete_message',
|
||||||
|
'safe_edit_message',
|
||||||
|
'clear_state_keep_data',
|
||||||
|
'get_state_data',
|
||||||
|
'set_state_data',
|
||||||
|
'update_state_data',
|
||||||
|
'is_state_active',
|
||||||
|
'inline_clear',
|
||||||
|
'status_clear',
|
||||||
|
'delete_messages',
|
||||||
|
'set_state_with_data',
|
||||||
|
'get_or_create_data',
|
||||||
|
'increment_state_value',
|
||||||
|
'append_to_state_list',
|
||||||
|
'remove_from_state_list',
|
||||||
|
'toggle_state_flag',
|
||||||
|
'debug_state'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ================= РАБОТА С FSM СОСТОЯНИЯМИ =================
|
||||||
|
|
||||||
|
async def clear_state(
|
||||||
|
state: FSMContext,
|
||||||
|
log: bool = True
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Очищает FSM состояние.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
log: Логировать очистку
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> await clear_state(state)
|
||||||
|
"""
|
||||||
|
current_state = await state.get_state()
|
||||||
|
|
||||||
|
if log and current_state:
|
||||||
|
logger.debug(
|
||||||
|
f"Очистка FSM состояния: {current_state}",
|
||||||
|
log_type='FSM'
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
async def clear_state_keep_data(
|
||||||
|
state: FSMContext,
|
||||||
|
keep_keys: Optional[Set[str]] = None
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Очищает FSM состояние, но сохраняет определенные данные.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
keep_keys: Множество ключей для сохранения
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> # Очищаем состояние, но сохраняем user_id и language
|
||||||
|
>> await clear_state_keep_data(state, keep_keys={'user_id', 'language'})
|
||||||
|
"""
|
||||||
|
if keep_keys:
|
||||||
|
# Получаем текущие данные
|
||||||
|
current_data = await state.get_data()
|
||||||
|
|
||||||
|
# Сохраняем только нужные ключи
|
||||||
|
saved_data = {
|
||||||
|
key: value for key, value in current_data.items()
|
||||||
|
if key in keep_keys
|
||||||
|
}
|
||||||
|
|
||||||
|
# Очищаем состояние
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
# Восстанавливаем сохраненные данные
|
||||||
|
if saved_data:
|
||||||
|
await state.update_data(**saved_data)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"FSM очищен, сохранены ключи: {', '.join(keep_keys)}",
|
||||||
|
log_type='FSM'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_state_data(
|
||||||
|
state: FSMContext,
|
||||||
|
key: Optional[str] = None,
|
||||||
|
default: Any = None
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Получает данные из FSM состояния.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
key: Ключ для получения (если None, возвращает все данные)
|
||||||
|
default: Значение по умолчанию
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: Данные из состояния
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> # Получить все данные
|
||||||
|
>> data = await get_state_data(state)
|
||||||
|
|
||||||
|
>> # Получить конкретный ключ
|
||||||
|
>> user_id = await get_state_data(state, 'user_id')
|
||||||
|
|
||||||
|
>> # С значением по умолчанию
|
||||||
|
>> lang = await get_state_data(state, 'language', default='ru')
|
||||||
|
"""
|
||||||
|
data = await state.get_data()
|
||||||
|
|
||||||
|
if key is None:
|
||||||
|
return data
|
||||||
|
|
||||||
|
return data.get(key, default)
|
||||||
|
|
||||||
|
|
||||||
|
async def set_state_data(
|
||||||
|
state: FSMContext,
|
||||||
|
key: str,
|
||||||
|
value: Any
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Устанавливает данные в FSM состояние.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
key: Ключ
|
||||||
|
value: Значение
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> await set_state_data(state, 'user_id', 123456789)
|
||||||
|
"""
|
||||||
|
await state.update_data(**{key: value})
|
||||||
|
|
||||||
|
|
||||||
|
async def update_state_data(
|
||||||
|
state: FSMContext,
|
||||||
|
**kwargs
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Обновляет несколько полей в FSM состоянии.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
**kwargs: Пары ключ-значение для обновления
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> await update_state_data(
|
||||||
|
... state,
|
||||||
|
... user_id=123456789,
|
||||||
|
... language='ru',
|
||||||
|
... step=1
|
||||||
|
... )
|
||||||
|
"""
|
||||||
|
await state.update_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
async def is_state_active(state: FSMContext) -> bool:
|
||||||
|
"""
|
||||||
|
Проверяет, активно ли какое-либо состояние.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если есть активное состояние
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> if await is_state_active(state):
|
||||||
|
... await message.answer("У вас есть незавершенное действие")
|
||||||
|
"""
|
||||||
|
current_state = await state.get_state()
|
||||||
|
return current_state is not None
|
||||||
|
|
||||||
|
|
||||||
|
# ================= РАБОТА С CALLBACK QUERIES =================
|
||||||
|
|
||||||
|
async def answer_callback(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
text: Optional[str] = None,
|
||||||
|
show_alert: bool = False,
|
||||||
|
cache_time: int = 0
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Отвечает на callback query.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: Callback query
|
||||||
|
text: Текст уведомления
|
||||||
|
show_alert: Показать как alert
|
||||||
|
cache_time: Время кэширования
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если успешно
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> await answer_callback(callback, "✅ Готово!")
|
||||||
|
>> await answer_callback(callback, "⚠️ Ошибка", show_alert=True)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await callback.answer(text=text, show_alert=show_alert, cache_time=cache_time)
|
||||||
|
return True
|
||||||
|
except TelegramBadRequest as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Не удалось ответить на callback: {e}",
|
||||||
|
log_type='CALLBACK'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def safe_answer_callback(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
text: Optional[str] = None,
|
||||||
|
show_alert: bool = False
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Безопасно отвечает на callback query (подавляет ошибки).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: Callback query
|
||||||
|
text: Текст уведомления
|
||||||
|
show_alert: Показать как alert
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> await safe_answer_callback(callback, "✅ Готово!")
|
||||||
|
"""
|
||||||
|
with suppress(TelegramBadRequest):
|
||||||
|
await callback.answer(text=text, show_alert=show_alert)
|
||||||
|
|
||||||
|
|
||||||
|
# ================= РАБОТА С СООБЩЕНИЯМИ =================
|
||||||
|
|
||||||
|
async def safe_delete_message(
|
||||||
|
message: Message,
|
||||||
|
log: bool = False
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Безопасно удаляет сообщение.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Сообщение для удаления
|
||||||
|
log: Логировать попытку удаления
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если успешно удалено
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> await safe_delete_message(message)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await message.delete()
|
||||||
|
|
||||||
|
if log:
|
||||||
|
logger.debug(
|
||||||
|
f"Сообщение удалено: {message.message_id}",
|
||||||
|
log_type='MESSAGE'
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except TelegramBadRequest as e:
|
||||||
|
if log:
|
||||||
|
logger.warning(
|
||||||
|
f"Не удалось удалить сообщение: {e}",
|
||||||
|
log_type='MESSAGE'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def safe_edit_message(
|
||||||
|
message: Message,
|
||||||
|
text: str,
|
||||||
|
**kwargs
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Безопасно редактирует сообщение.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Сообщение для редактирования
|
||||||
|
text: Новый текст
|
||||||
|
**kwargs: Дополнительные параметры (reply_markup, parse_mode, и т.д.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если успешно отредактировано
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> await safe_edit_message(
|
||||||
|
... message,
|
||||||
|
... "Новый текст",
|
||||||
|
... parse_mode="HTML"
|
||||||
|
... )
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await message.edit_text(text, **kwargs)
|
||||||
|
return True
|
||||||
|
except TelegramBadRequest as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Не удалось отредактировать сообщение: {e}",
|
||||||
|
log_type='MESSAGE'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_messages(
|
||||||
|
chat_id: int,
|
||||||
|
message_ids: list[int],
|
||||||
|
bot
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Удаляет несколько сообщений.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: ID чата
|
||||||
|
message_ids: Список ID сообщений
|
||||||
|
bot: Экземпляр бота
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Количество успешно удаленных сообщений
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> deleted = await delete_messages(
|
||||||
|
... chat_id=message.chat.id,
|
||||||
|
... message_ids=[123, 124, 125],
|
||||||
|
... bot=bot
|
||||||
|
... )
|
||||||
|
>> print(f"Удалено {deleted} сообщений")
|
||||||
|
"""
|
||||||
|
deleted_count = 0
|
||||||
|
|
||||||
|
for message_id in message_ids:
|
||||||
|
try:
|
||||||
|
await bot.delete_message(chat_id=chat_id, message_id=message_id)
|
||||||
|
deleted_count += 1
|
||||||
|
except TelegramBadRequest:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return deleted_count
|
||||||
|
|
||||||
|
|
||||||
|
# ================= КОМБИНИРОВАННЫЕ ФУНКЦИИ =================
|
||||||
|
|
||||||
|
async def inline_clear(update: Union[Message, CallbackQuery]) -> None:
|
||||||
|
"""
|
||||||
|
Очищает все инлайн взаимодействия (отвечает на callback).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
update: Объект обновления (Message или CallbackQuery)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> await inline_clear(callback)
|
||||||
|
"""
|
||||||
|
if isinstance(update, CallbackQuery):
|
||||||
|
await safe_answer_callback(update)
|
||||||
|
|
||||||
|
|
||||||
|
async def status_clear(
|
||||||
|
update: Union[Message, CallbackQuery],
|
||||||
|
state: FSMContext,
|
||||||
|
keep_data: Optional[Set[str]] = None,
|
||||||
|
remove_keyboard: bool = False
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Полная очистка: состояние FSM + ответ на callback + удаление клавиатуры.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
update: Объект обновления
|
||||||
|
state: Контекст FSM
|
||||||
|
keep_data: Данные для сохранения
|
||||||
|
remove_keyboard: Удалить клавиатуру (только для Message)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> # Полная очистка
|
||||||
|
>> await status_clear(message, state)
|
||||||
|
|
||||||
|
>> # С сохранением данных
|
||||||
|
>> await status_clear(
|
||||||
|
... callback,
|
||||||
|
... state,
|
||||||
|
... keep_data={'user_id', 'language'}
|
||||||
|
... )
|
||||||
|
|
||||||
|
>> # С удалением клавиатуры
|
||||||
|
>> await status_clear(message, state, remove_keyboard=True)
|
||||||
|
"""
|
||||||
|
# Очищаем состояние
|
||||||
|
if keep_data:
|
||||||
|
await clear_state_keep_data(state, keep_keys=keep_data)
|
||||||
|
else:
|
||||||
|
await clear_state(state, log=True)
|
||||||
|
|
||||||
|
# Отвечаем на callback
|
||||||
|
await inline_clear(update)
|
||||||
|
|
||||||
|
# Удаляем клавиатуру если нужно
|
||||||
|
if remove_keyboard and isinstance(update, Message):
|
||||||
|
with suppress(TelegramBadRequest):
|
||||||
|
await update.answer(
|
||||||
|
"Отменено",
|
||||||
|
reply_markup=ReplyKeyboardRemove()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ================= УТИЛИТЫ ДЛЯ РАБОТЫ С СОСТОЯНИЯМИ =================
|
||||||
|
|
||||||
|
async def set_state_with_data(
|
||||||
|
state: FSMContext,
|
||||||
|
new_state: State,
|
||||||
|
**data
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Устанавливает новое состояние и данные одновременно.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
new_state: Новое состояние
|
||||||
|
**data: Данные для сохранения
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> await set_state_with_data(
|
||||||
|
... state,
|
||||||
|
... FormStates.waiting_name,
|
||||||
|
... user_id=123456789,
|
||||||
|
... step=1
|
||||||
|
... )
|
||||||
|
"""
|
||||||
|
await state.set_state(new_state)
|
||||||
|
if data:
|
||||||
|
await state.update_data(**data)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Установлено состояние: {new_state.state}",
|
||||||
|
log_type='FSM'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_or_create_data(
|
||||||
|
state: FSMContext,
|
||||||
|
key: str,
|
||||||
|
factory: Any
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Получает данные из состояния или создает их если их нет.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
key: Ключ данных
|
||||||
|
factory: Значение по умолчанию или функция для создания
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: Данные из состояния или созданные
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> # С простым значением
|
||||||
|
>> items = await get_or_create_data(state, 'items', [])
|
||||||
|
|
||||||
|
>> # С функцией
|
||||||
|
>> data = await get_or_create_data(state, 'data', lambda: {'count': 0})
|
||||||
|
"""
|
||||||
|
data = await state.get_data()
|
||||||
|
|
||||||
|
if key not in data:
|
||||||
|
# Создаем значение
|
||||||
|
if callable(factory):
|
||||||
|
value = factory()
|
||||||
|
else:
|
||||||
|
value = factory
|
||||||
|
|
||||||
|
await state.update_data(**{key: value})
|
||||||
|
return value
|
||||||
|
|
||||||
|
return data[key]
|
||||||
|
|
||||||
|
|
||||||
|
async def increment_state_value(
|
||||||
|
state: FSMContext,
|
||||||
|
key: str,
|
||||||
|
amount: int = 1
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Инкрементирует числовое значение в состоянии.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
key: Ключ значения
|
||||||
|
amount: Величина инкремента
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Новое значение
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> # Увеличиваем счетчик
|
||||||
|
>> new_count = await increment_state_value(state, 'attempts')
|
||||||
|
>> if new_count >= 3:
|
||||||
|
... await message.answer("Слишком много попыток!")
|
||||||
|
"""
|
||||||
|
data = await state.get_data()
|
||||||
|
current = data.get(key, 0)
|
||||||
|
new_value = current + amount
|
||||||
|
|
||||||
|
await state.update_data(**{key: new_value})
|
||||||
|
return new_value
|
||||||
|
|
||||||
|
|
||||||
|
async def append_to_state_list(
|
||||||
|
state: FSMContext,
|
||||||
|
key: str,
|
||||||
|
value: Any
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
Добавляет значение в список в состоянии.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
key: Ключ списка
|
||||||
|
value: Значение для добавления
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: Обновленный список
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> # Добавляем товар в корзину
|
||||||
|
>> cart = await append_to_state_list(state, 'cart', product_id)
|
||||||
|
>> await message.answer(f"В корзине {len(cart)} товаров")
|
||||||
|
"""
|
||||||
|
data = await state.get_data()
|
||||||
|
current_list = data.get(key, [])
|
||||||
|
|
||||||
|
if not isinstance(current_list, list):
|
||||||
|
current_list = []
|
||||||
|
|
||||||
|
current_list.append(value)
|
||||||
|
await state.update_data(**{key: current_list})
|
||||||
|
|
||||||
|
return current_list
|
||||||
|
|
||||||
|
|
||||||
|
async def remove_from_state_list(
|
||||||
|
state: FSMContext,
|
||||||
|
key: str,
|
||||||
|
value: Any
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
Удаляет значение из списка в состоянии.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
key: Ключ списка
|
||||||
|
value: Значение для удаления
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: Обновленный список
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> # Удаляем товар из корзины
|
||||||
|
>> cart = await remove_from_state_list(state, 'cart', product_id)
|
||||||
|
"""
|
||||||
|
data = await state.get_data()
|
||||||
|
current_list = data.get(key, [])
|
||||||
|
|
||||||
|
if isinstance(current_list, list) and value in current_list:
|
||||||
|
current_list.remove(value)
|
||||||
|
await state.update_data(**{key: current_list})
|
||||||
|
|
||||||
|
return current_list
|
||||||
|
|
||||||
|
|
||||||
|
async def toggle_state_flag(
|
||||||
|
state: FSMContext,
|
||||||
|
key: str
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Переключает boolean флаг в состоянии.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
key: Ключ флага
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: Новое значение флага
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> # Переключаем режим
|
||||||
|
>> is_active = await toggle_state_flag(state, 'notifications')
|
||||||
|
>> await message.answer(
|
||||||
|
... f"Уведомления: {'включены' if is_active else 'выключены'}"
|
||||||
|
... )
|
||||||
|
"""
|
||||||
|
data = await state.get_data()
|
||||||
|
current = data.get(key, False)
|
||||||
|
new_value = not current
|
||||||
|
|
||||||
|
await state.update_data(**{key: new_value})
|
||||||
|
return new_value
|
||||||
|
|
||||||
|
|
||||||
|
# ================= ОТЛАДКА =================
|
||||||
|
|
||||||
|
async def debug_state(state: FSMContext) -> str:
|
||||||
|
"""
|
||||||
|
Возвращает отладочную информацию о состоянии.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Контекст FSM
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Форматированная информация о состоянии
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>> debug_info = await debug_state(state)
|
||||||
|
>> print(debug_info)
|
||||||
|
"""
|
||||||
|
current_state = await state.get_state()
|
||||||
|
data = await state.get_data()
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"🔍 <b>Debug FSM:</b>\n",
|
||||||
|
f"📊 Состояние: <code>{current_state or 'None'}</code>\n",
|
||||||
|
f"📦 Данных: {len(data)}\n"
|
||||||
|
]
|
||||||
|
|
||||||
|
if data:
|
||||||
|
lines.append("\n<b>Данные:</b>")
|
||||||
|
for key, value in data.items():
|
||||||
|
value_str = str(value)
|
||||||
|
if len(value_str) > 50:
|
||||||
|
value_str = value_str[:50] + "..."
|
||||||
|
lines.append(f"• {key}: <code>{value_str}</code>")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
Reference in New Issue
Block a user