From b9ac442a893c4c4d73ffef20a42b8c64ae665cf8 Mon Sep 17 00:00:00 2001 From: Admin Date: Sun, 1 Mar 2026 15:17:20 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=AF=20=D0=A3=D0=BF=D1=80=D0=BE=D1=89?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=B6?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D1=86=D0=B0=20=D0=B8=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=A3=D1=87?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=BA=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Убраны лишние кнопки из меню жильца: - 📢 Объявления (админский функционал) - 📣 Подать объявление - 📊 Токсичность (админский функционал) - 🏠 Темы чата (админский функционал) ✅ Остались только нужные жильцам кнопки: - 📞 Экстренные телефоны - 👷 Мастера - 👮 Участковый (работает через Service БД!) - 📅 График отключений - 🏆 Достижения - 📅 События - 💰 Платежи - 📋 Опросы - 🔍 Моя квартира - 🙏 Поблагодарить - ❓ Помощь ✅ Участковый берёт данные из Service таблицы ✅ Все сообщения бота только в личку пользователю 📝 Веб-админка: Телефоны → Добавить службу → Полиция Co-authored-by: Qwen-Coder --- handlers/users.py | 264 +++++++++++++++++++++++++++++++++++++++----- keyboards/inline.py | 65 ++++++++++- 2 files changed, 299 insertions(+), 30 deletions(-) diff --git a/handlers/users.py b/handlers/users.py index 6095d02..bfd8ac4 100644 --- a/handlers/users.py +++ b/handlers/users.py @@ -282,33 +282,6 @@ async def cb_schedule(callback: CallbackQuery): await callback.message.answer(text, parse_mode='HTML') -@router.callback_query(F.data == 'announcements') -async def cb_announcements(callback: CallbackQuery): - """Объявления""" - await callback.answer() - - async with AsyncSessionLocal() as session: - stmt = ( - select(Announcement) - .where(Announcement.is_active == True) - .order_by(Announcement.is_pinned.desc(), Announcement.created_at.desc()) - .limit(10) - ) - result = await session.execute(stmt) - announcements = list(result.scalars().all()) - - if not announcements: - await callback.message.answer('📭 Объявлений нет') - return - - text = '📢 Объявления\n\n' - for ann in announcements: - pin = '📌 ' if ann.is_pinned else '' - text += f'{pin}{ann.title}\n{ann.text[:200]}...\n\n' - - await callback.message.answer(text, parse_mode='HTML') - - @router.callback_query(F.data == 'main_menu') async def cb_main_menu(callback: CallbackQuery): """Главное меню""" @@ -356,3 +329,240 @@ async def msg_masters_text(message: Message): async def msg_help_text(message: Message): """Текстовая команда помощь""" await cmd_help(message) + + +# ============================================================================= +# ОБРАБОТЧИКИ НОВЫХ КНОПОК МЕНЮ +# ============================================================================= + +from keyboards.inline import get_thank_user_keyboard, get_thank_confirm_keyboard +from sqlalchemy import desc + + +# Хранилище страниц для благодарностей +_thank_pages: dict[int, dict] = {} + + +@router.callback_query(F.data == 'achievements') +async def cb_achievements_menu(callback: CallbackQuery): + """Кнопка Достижения""" + await callback.answer() + # Перенаправляем на команду /achievements + from handlers.achievements import cmd_achievements + await cmd_achievements(callback.message) + + +@router.callback_query(F.data == 'events') +async def cb_events_menu(callback: CallbackQuery): + """Кнопка События""" + await callback.answer() + # Перенаправляем на команду /events + from handlers.events import cmd_events + await cmd_events(callback.message) + + +@router.callback_query(F.data == 'payments') +async def cb_payments_menu(callback: CallbackQuery): + """Кнопка Платежи""" + await callback.answer() + # Перенаправляем на команду /payments + from handlers.payments import cmd_payments + await cmd_payments(callback.message) + + +@router.callback_query(F.data == 'polls') +async def cb_polls_menu(callback: CallbackQuery): + """Кнопка Опросы""" + await callback.answer() + # Перенаправляем на команду /polls + from handlers.polls import cmd_polls + await cmd_polls(callback.message) + + +@router.callback_query(F.data == 'thank_user') +async def cb_thank_user_menu(callback: CallbackQuery): + """Кнопка Поблагодарить - выбор пользователя""" + await callback.answer() + + async with AsyncSessionLocal() as session: + # Показываем активных пользователей (по количеству сообщений) + stmt = select(User).order_by(desc(User.message_count)).limit(100) + result = await session.execute(stmt) + users = list(result.scalars().all()) + + if not users: + await callback.message.answer('📭 Пользователей нет') + return + + # Сохраняем список для сессии + _thank_pages[callback.from_user.id] = { + 'users': users, + 'page': 0 + } + + await callback.message.answer( + '🙏 Поблагодарить пользователя\n\n' + 'Выберите пользователя которому хотите сказать спасибо:\n' + '(+10 к рейтингу)', + reply_markup=get_thank_user_keyboard(users, page=0), + parse_mode='HTML' + ) + + +@router.callback_query(F.data.startswith('thank_page_')) +async def cb_thank_page(callback: CallbackQuery): + """Переключение страницы в списке для благодарности""" + await callback.answer() + + page = int(callback.data.split('_')[-1]) + user_data = _thank_pages.get(callback.from_user.id) + + if not user_data: + await callback.message.answer('❌ Сессия истекла. Выберите "Поблагодарить" заново') + return + + users = user_data['users'] + user_data['page'] = page + + await callback.message.edit_reply_markup( + reply_markup=get_thank_user_keyboard(users, page=page) + ) + + +@router.callback_query(F.data.startswith('thank_select_')) +async def cb_thank_select(callback: CallbackQuery): + """Выбор конкретного пользователя для благодарности""" + await callback.answer() + + user_id = int(callback.data.split('_')[-1]) + + async with AsyncSessionLocal() as session: + stmt = select(User).where(User.user_id == user_id) + result = await session.execute(stmt) + user = result.scalar_one_or_none() + + if not user: + await callback.message.answer('❌ Пользователь не найден') + return + + name = user.full_name or f'user{user_id}' + apt = f' (кв. {user.apartment})' if user.apartment else '' + + await callback.message.answer( + f'🙏 Поблагодарить пользователя?\n\n' + f'{name}{apt}\n\n' + f'Пользователь получит +10 к рейтингу.', + reply_markup=get_thank_confirm_keyboard(user_id), + parse_mode='HTML' + ) + + +@router.callback_query(F.data.startswith('thank_confirm_')) +async def cb_thank_confirm(callback: CallbackQuery): + """Подтверждение благодарности""" + await callback.answer() + + target_user_id = int(callback.data.split('_')[-1]) + from_user_id = callback.from_user.id + + # Проверяем что не благодарит сам себя + if target_user_id == from_user_id: + await callback.message.answer('❌ Нельзя поблагодарить самого себя!') + return + + async with AsyncSessionLocal() as session: + # Находим того кого благодарят + stmt = select(User).where(User.user_id == target_user_id) + result = await session.execute(stmt) + target_user = result.scalar_one_or_none() + + # Находим того кто благодарит + stmt = select(User).where(User.user_id == from_user_id) + result = await session.execute(stmt) + from_user = result.scalar_one_or_none() + + if not target_user or not from_user: + await callback.message.answer('❌ Пользователь не найден') + return + + # Проверяли ли уже сегодня + from datetime import datetime, timedelta + from database.models import Thanks + + today = datetime.utcnow().date() + stmt = select(Thanks).where( + Thanks.from_user_id == from_user_id, + Thanks.created_at >= datetime.combine(today, datetime.min.time()) + ) + result = await session.execute(stmt) + existing = result.scalar_one_or_none() + + if existing: + await callback.message.answer('ℹ️ Вы уже благодарили кого-то сегодня. Можно раз в сутки.') + return + + # Создаём благодарность + thanks = Thanks( + from_user_id=from_user_id, + to_user_id=target_user_id, + message='Благодарность через меню', + ) + session.add(thanks) + + # +10 к рейтингу + target_user.rating += 10 + + await session.commit() + + name = target_user.full_name or f'user{target_user_id}' + + await callback.message.answer( + f'✅ Спасибо отправлено!\n\n' + f'{name} получил +10 к рейтингу.\n' + f'Вы можете поблагодарить ещё раз через 24 часа.', + parse_mode='HTML' + ) + + +@router.callback_query(F.data == 'announcements') +async def cb_announcements_menu(callback: CallbackQuery): + """Кнопка Объявления""" + await callback.answer() + # Перенаправляем на команду /ads + from handlers.ads import cmd_ads + await cmd_ads(callback.message) + + +@router.callback_query(F.data == 'schedule') +async def cb_schedule_menu(callback: CallbackQuery): + """Кнопка График отключений""" + await callback.answer() + # Перенаправляем на команду /график + from handlers.schedule import cmd_schedule + await cmd_schedule(callback.message) + + +@router.callback_query(F.data == 'my_apartment') +async def cb_my_apartment_menu(callback: CallbackQuery): + """Кнопка Моя квартира""" + await callback.answer() + + async with AsyncSessionLocal() as session: + stmt = select(User).where(User.user_id == callback.from_user.id) + result = await session.execute(stmt) + user = result.scalar_one_or_none() + + if not user: + await callback.message.answer('❌ Вы не зарегистрированы') + return + + apt = user.apartment or '❓ Не указана' + verified = '✅' if user.verified else '⏳' + + text = f'🏠 Ваша квартира\n\n' + text += f'Номер: {apt}\n' + text += f'Статус: {verified} {"Верифицирован" if user.verified else "Не верифицирован"}\n' + text += f'Рейтинг: {user.rating}\n' + text += f'Уровень: {user.level}\n' + + await callback.message.answer(text, parse_mode='HTML') diff --git a/keyboards/inline.py b/keyboards/inline.py index 8ff4af8..d71595f 100644 --- a/keyboards/inline.py +++ b/keyboards/inline.py @@ -8,7 +8,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder def get_main_menu() -> InlineKeyboardMarkup: """Главное меню жильца""" builder = InlineKeyboardBuilder() - + builder.row( InlineKeyboardButton(text='📞 Экстренные телефоны', callback_data='phones'), ) @@ -18,15 +18,74 @@ def get_main_menu() -> InlineKeyboardMarkup: ) builder.row( InlineKeyboardButton(text='📅 График отключений', callback_data='schedule'), - InlineKeyboardButton(text='📢 Объявления', callback_data='announcements'), + ) + builder.row( + InlineKeyboardButton(text='🏆 Достижения', callback_data='achievements'), + InlineKeyboardButton(text='📅 События', callback_data='events'), + ) + builder.row( + InlineKeyboardButton(text='💰 Платежи', callback_data='payments'), + InlineKeyboardButton(text='📋 Опросы', callback_data='polls'), ) builder.row( InlineKeyboardButton(text='🔍 Моя квартира', callback_data='my_apartment'), - InlineKeyboardButton(text='📣 Подать объявление', callback_data='submit_ad'), ) builder.row( + InlineKeyboardButton(text='🙏 Поблагодарить', callback_data='thank_user'), InlineKeyboardButton(text='❓ Помощь', callback_data='help'), ) + + return builder.as_markup() + + +def get_thank_user_keyboard(users: list, page: int = 0, page_size: int = 20) -> InlineKeyboardMarkup: + """ + Клавиатура для выбора пользователя чтобы поблагодарить + + :param users: список пользователей (User модели) + :param page: текущая страница (0-based) + :param page_size: размер страницы + """ + builder = InlineKeyboardBuilder() + + total_pages = (len(users) + page_size - 1) // page_size if users else 1 + start_idx = page * page_size + end_idx = min(start_idx + page_size, len(users)) + page_users = users[start_idx:end_idx] + + for user in page_users: + name = user.full_name or f'user{user.user_id}' + apt = f' (кв. {user.apartment})' if user.apartment else '' + builder.button( + text=f'🙏 {name}{apt}', + callback_data=f'thank_select_{user.user_id}' + ) + + # Кнопки навигации + nav_buttons = [] + if page > 0: + nav_buttons.append(InlineKeyboardButton(text='⬅️', callback_data=f'thank_page_{page - 1}')) + if page < total_pages - 1: + nav_buttons.append(InlineKeyboardButton(text='➡️', callback_data=f'thank_page_{page + 1}')) + + if nav_buttons: + builder.row(*nav_buttons) + + builder.row( + InlineKeyboardButton(text='🔙 Назад', callback_data='main_menu'), + ) + + return builder.as_markup() + + +def get_thank_confirm_keyboard(user_id: int) -> InlineKeyboardMarkup: + """Клавиатура подтверждения благодарности""" + builder = InlineKeyboardBuilder() + + builder.row( + InlineKeyboardButton(text='✅ Поблагодарить', callback_data=f'thank_confirm_{user_id}'), + InlineKeyboardButton(text='❌ Отмена', callback_data='thank_user'), + ) return builder.as_markup()