From 633b0f0d052ef962b26a623a8f6890e682f66d94 Mon Sep 17 00:00:00 2001 From: Admin Date: Fri, 26 Jun 2026 21:02:42 +0400 Subject: [PATCH] Fix: resolve buttons crash in phones menu when callback message contains photo --- handlers/users.py | 83 ++++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/handlers/users.py b/handlers/users.py index cbb6212..97dd9fe 100644 --- a/handlers/users.py +++ b/handlers/users.py @@ -18,6 +18,33 @@ import config logger = logging.getLogger(__name__) router = Router() + +async def safe_edit_or_reply(event, text: str, reply_markup=None, parse_mode='HTML'): + """ + Безопасное редактирование сообщения. + Если сообщение содержит медиа (фото и т.д.), мы удаляем его и отправляем новое. + """ + if isinstance(event, CallbackQuery): + await event.answer() + message = event.message + if (hasattr(message, 'photo') and message.photo) or \ + (hasattr(message, 'video') and message.video) or \ + (hasattr(message, 'document') and message.document) or \ + (hasattr(message, 'content_type') and message.content_type != 'text'): + try: + await message.delete() + except Exception as e: + logger.warning(f"Не удалось удалить сообщение с медиа: {e}") + await message.answer(text, reply_markup=reply_markup, parse_mode=parse_mode) + else: + try: + await message.edit_text(text, reply_markup=reply_markup, parse_mode=parse_mode) + except Exception as e: + logger.warning(f"Ошибка edit_text: {e}. Отправляем новое сообщение.") + await message.answer(text, reply_markup=reply_markup, parse_mode=parse_mode) + elif isinstance(event, Message): + await event.answer(text, reply_markup=reply_markup, parse_mode=parse_mode) + @router.message(Command('cancel'), StateFilter('*')) @router.message(F.text.lower().in_(['отмена', 'отменить']), StateFilter('*')) async def global_cancel(message: Message, state: FSMContext): @@ -74,7 +101,8 @@ async def cb_main_menu(callback: CallbackQuery): ig_stmt = select(InitiativeGroup).where(InitiativeGroup.user_id == user_id, InitiativeGroup.is_active == True) is_ig = (await session.execute(ig_stmt)).scalar_one_or_none() is not None - await callback.message.edit_text( + await safe_edit_or_reply( + callback, "Главное меню:", reply_markup=get_main_menu(verified=user.verified, is_ig=is_ig, is_admin=(user.is_admin or user_id == config.ADMIN_USER_ID), is_superadmin=(user_id == config.ADMIN_USER_ID)), parse_mode='HTML' @@ -85,11 +113,7 @@ async def cb_main_menu(callback: CallbackQuery): async def cb_phones(event): text = '📞 ЭКСТРЕННЫЕ ТЕЛЕФОНЫ\n\nВыберите категорию ниже:' kb = get_phones_menu() - if isinstance(event, CallbackQuery): - await event.answer() - await event.message.edit_text(text, reply_markup=kb, parse_mode='HTML') - else: - await event.answer(text, reply_markup=kb, parse_mode='HTML') + await safe_edit_or_reply(event, text, reply_markup=kb, parse_mode='HTML') @router.message(F.text & ~F.text.startswith('/')) async def chat_with_ai(message: Message): @@ -135,9 +159,19 @@ async def cb_phone_category(callback: CallbackQuery): async with AsyncSessionLocal() as session: stmt = select(Service).where(Service.category == cat_code).order_by(Service.name) services = list((await session.execute(stmt)).scalars().all()) + + user_stmt = select(User).where(User.user_id == callback.from_user.id) + user = (await session.execute(user_stmt)).scalar_one_or_none() + ig_stmt = select(InitiativeGroup).where(InitiativeGroup.user_id == callback.from_user.id, InitiativeGroup.is_active == True) + is_ig = (await session.execute(ig_stmt)).scalar_one_or_none() is not None + + is_admin = user.is_admin or callback.from_user.id == config.ADMIN_USER_ID if user else False + is_superadmin = callback.from_user.id == config.ADMIN_USER_ID + verified = user.verified if user else False + menu_kb = get_main_menu(verified=verified, is_ig=is_ig, is_admin=is_admin, is_superadmin=is_superadmin) if not services: - return await callback.message.edit_text("👷 Данные для этой категории еще не заполнены.", reply_markup=get_main_menu()) + return await safe_edit_or_reply(callback, "👷 Данные для этой категории еще не заполнены.", reply_markup=menu_kb) text = f"📞 СПИСОК ТЕЛЕФОНОВ: {cat_code.upper()}\n\n" for s in services: @@ -150,11 +184,9 @@ async def cb_phone_category(callback: CallbackQuery): if photo_path and os.path.exists(photo_path): await callback.message.delete() - await callback.message.answer_photo(FSInputFile(photo_path), caption=text, reply_markup=get_main_menu(), parse_mode='HTML') + await callback.message.answer_photo(FSInputFile(photo_path), caption=text, reply_markup=menu_kb, parse_mode='HTML') else: - await callback.message.edit_text(text, reply_markup=get_main_menu(), parse_mode='HTML') - - await callback.answer() + await safe_edit_or_reply(callback, text, reply_markup=menu_kb, parse_mode='HTML') @router.message(Command('rules')) @router.callback_query(F.data == 'rules') @@ -163,11 +195,7 @@ async def cb_rules(event): kb = InlineKeyboardBuilder() kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="main_menu")) - if isinstance(event, Message): - await event.answer(text, reply_markup=kb.as_markup(), parse_mode='HTML') - else: - await event.answer() - await event.message.edit_text(text, reply_markup=kb.as_markup(), parse_mode='HTML') + await safe_edit_or_reply(event, text, reply_markup=kb.as_markup(), parse_mode='HTML') @router.message(Command('about')) @router.callback_query(F.data == 'about_house') @@ -180,11 +208,7 @@ async def cb_about(event): kb = InlineKeyboardBuilder() kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="main_menu")) - if isinstance(event, Message): - await event.answer(text, reply_markup=kb.as_markup(), parse_mode='HTML') - else: - await event.answer() - await event.message.edit_text(text, reply_markup=kb.as_markup(), parse_mode='HTML') + await safe_edit_or_reply(event, text, reply_markup=kb.as_markup(), parse_mode='HTML') @router.message(Command('profile')) @router.callback_query(F.data == 'my_profile') @@ -207,11 +231,7 @@ async def cb_profile(event): kb = get_profile_keyboard(user.meter_reminders_enabled if user else True) - if isinstance(event, Message): - await event.answer(text, reply_markup=kb, parse_mode='HTML') - else: - await event.answer() - await event.message.edit_text(text, reply_markup=kb, parse_mode='HTML') + await safe_edit_or_reply(event, text, reply_markup=kb, parse_mode='HTML') @router.callback_query(F.data == "toggle_meter_reminders") async def cb_toggle_meter_reminders(callback: CallbackQuery): @@ -244,12 +264,11 @@ async def cb_toggle_meter_reminders(callback: CallbackQuery): f"Рейтинг: {user.rating} 🏆") kb = get_profile_keyboard(new_val) - await callback.message.edit_text(text, reply_markup=kb, parse_mode='HTML') + await safe_edit_or_reply(callback, text, reply_markup=kb, parse_mode='HTML') @router.callback_query(F.data == "edit_location") async def edit_location_start(callback: CallbackQuery): - await callback.message.edit_text("Введите ваш подъезд и этаж через пробел\nНапример: '3 5' (3 подъезд, 5 этаж)", parse_mode='HTML') - await callback.answer() + await safe_edit_or_reply(callback, "Введите ваш подъезд и этаж через пробел\nНапример: '3 5' (3 подъезд, 5 этаж)") @router.message(F.text.regexp(r"^\d+\s+\d+$")) async def process_location(message: Message): @@ -282,8 +301,4 @@ async def cb_help(event): kb = InlineKeyboardBuilder() kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="main_menu")) - if isinstance(event, Message): - await event.answer(text, reply_markup=kb.as_markup(), parse_mode='HTML') - else: - await event.answer() - await event.message.edit_text(text, reply_markup=kb.as_markup(), parse_mode='HTML') + await safe_edit_or_reply(event, text, reply_markup=kb.as_markup(), parse_mode='HTML')