""" Пользовательские команды Доступны всем жильцам """ import logging import json from aiogram import Router, F from aiogram.types import Message, CallbackQuery, ChatMemberUpdated, BotCommand from aiogram.filters import Command, CommandStart from aiogram.enums import ChatMemberStatus from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from database.db import AsyncSessionLocal from database.models import User, Service, Announcement from keyboards.inline import get_main_menu, get_phones_menu from utils.formatters import format_phone_list import config logger = logging.getLogger(__name__) router = Router() @router.message(CommandStart()) async def cmd_start(message: Message): """Команда /start (ТОЛЬКО В ЛИЧКЕ!)""" from utils.ui_styles import get_level_badge # Проверяем что это личка if message.chat.type != 'private': return user_id = message.from_user.id 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: # НОВЫЙ пользователь - добавляем в базу и приветствуем! user = User( user_id=user_id, username=message.from_user.username, first_name=message.from_user.first_name, last_name=message.from_user.last_name, ) session.add(user) await session.commit() logger.info(f'Новый пользователь {user_id} добавлен в базу') # Приветственное сообщение header = '🔴 ДОБРО ПОЖАЛОВАТЬ' welcome_text = f'''{header} 👋 Привет, {message.from_user.first_name}! {get_level_badge("unverified")} Я — домовой бот нашего дома. ⚠️ ВАЖНО: Для полного доступа пройдите верификацию! 📝 Как пройти верификацию: 1️⃣ Отправьте номер квартиры Примеры: 45, 12А, 100-1 2️⃣ (опционально) Телефон Пример: +79991234567 3️⃣ Дождитесь подтверждения Админ проверит и подтвердит. После верификации откроются все функции! 🎉''' await message.answer(welcome_text, parse_mode='HTML', reply_markup=get_unverified_menu()) return # Пользователь в базе - обновляем имя user.username = message.from_user.username user.first_name = message.from_user.first_name user.last_name = message.from_user.last_name # Проверяем статус is_admin = user.is_admin or (user_id == config.ADMIN_USER_ID) is_superadmin = (user_id == config.ADMIN_USER_ID) if user.verified: # Проверяем членство в инициативной группе from database.models import InitiativeGroup ig_stmt = select(InitiativeGroup).where( InitiativeGroup.user_id == user_id, InitiativeGroup.is_active == True ) ig_result = await session.execute(ig_stmt) ig_member = ig_result.scalar_one_or_none() is_ig = ig_member is not None # Определяем уровень if is_superadmin: level = 'superadmin' header = '🟣 УПРАВЛЕНИЕ СИСТЕМОЙ' elif is_admin: level = 'admin' header = '🟡 АДМИН-ПАНЕЛЬ' elif is_ig: level = 'ig' header = '🔵 ИНИЦИАТИВНАЯ ГРУППА' else: level = 'verified' header = '🟢 ДОСТУП РАЗРЕШЁН' await message.answer( f'{header}\n\n' f'👋 Здравствуйте, {message.from_user.first_name}!\n\n' f'{get_level_badge(level)}\n\n' f'✅ Вы верифицированы (кв. {user.apartment}).\n' f'Выберите действие:', reply_markup=get_main_menu(verified=True, is_ig=is_ig, is_admin=is_admin, is_superadmin=is_superadmin), parse_mode='HTML' ) else: # Неверифицированный — подробная инструкция verify_text = f'''🔴 ТРЕБУЕТСЯ ВЕРИФИКАЦИЯ 👋 Здравствуйте, {message.from_user.first_name}! {get_level_badge("unverified")} ⏳ Вы ещё не верифицированы 📝 Что делать? 1️⃣ Отправьте номер квартиры Примеры: 45 12А 100-1 2️⃣ (опционально) Телефон Пример: +79991234567 3️⃣ Дождитесь подтверждения Админ проверит и подтвердит. ✅ После верификации получите доступ ко всем функциям! 💡 Нужна помощь? Напишите администратору чата.''' await message.answer( verify_text, reply_markup=get_unverified_menu(), parse_mode='HTML' ) await session.commit() @router.message(Command('помощь')) @router.message(Command('help')) async def cmd_help(message: Message): """Справка по командам (для жильцов)""" from utils.ui_styles import get_colored_header, get_level_badge from database.models import InitiativeGroup user_id = message.from_user.id async with AsyncSessionLocal() as session: user = await session.get(User, user_id) # Проверяем есть ли в базе if not user: logger.info(f'Пользователь {user_id} не в базе - игнорируем /help') return # Определяем уровень if user.verified: ig_stmt = select(InitiativeGroup).where( InitiativeGroup.user_id == user_id, InitiativeGroup.is_active == True ) ig_result = await session.execute(ig_stmt) is_ig = ig_result.scalar_one_or_none() is not None is_admin = user.is_admin or (user_id == config.ADMIN_USER_ID) if is_admin: level = 'admin' elif is_ig: level = 'ig' else: level = 'verified' else: level = 'unverified' # Базовые команды для ВСЕХ text = f'''{get_colored_header('ПОМОЩЬ', level)} {get_level_badge(level)} 📱 Доступные команды: Для всех: 📞 /телефон — Экстренные службы 👷 /мастера — Сантехники, электрики 👮 /участковый — Контакты полиции 📜 /правила — Правила чата ❓ /помощь — Эта справка Верификация: Просто отправьте боту номер квартиры текстом Примеры: 45, 12А, 100-1 После верификации станут доступны: • 📅 График отключений • 💰 Платежи ЖКХ • 🏆 Достижения • 📋 Опросы • 🔍 Моя квартира ''' # Добавляем для админа if level == 'admin': text += ''' ⚙️ Администратору: /admin — Панель админа /stats — Статистика чата /spies — Подозрительные /export — Экспорт JSON /users — Список жильцов /verify — Верифицировать /broadcast — Рассылка ''' # Добавляем для ИГ if level == 'ig': text += ''' 🎯 Инициативной группе: /ig_stats — Статистика ИГ /ig_list — Список ИГ ''' await message.answer(text, parse_mode='HTML', reply_markup=get_main_menu( verified=(level != 'unverified'), is_ig=(level == 'ig'), is_admin=(level == 'admin'), is_superadmin=False )) @router.callback_query(F.data == 'help') async def cb_help(callback: CallbackQuery): """Справка (callback)""" await callback.answer() text = '''🏠 Домовой бот — помощь Этот бот помогает жильцам нашего дома быть в курсе событий и решать бытовые вопросы. 🆕 Вы впервые здесь? 1️⃣ Пройдите верификацию — это просто! • Отправьте боту номер вашей квартиры • Например: 45 или 12А • После проверки вы получите полный доступ 2️⃣ Что будет после верификации? • 📅 График отключений воды и света • 📢 Объявления и новости дома • 💰 Напоминания о платежах ЖКХ • 🏆 Рейтинг и достижения • 📋 Участие в опросах • 📅 События и собрания 📞 Телефоны служб Нажмите "Экстренные телефоны" чтобы увидеть: • 🚨 Аварийные службы (02, 03, 04, 112) • 🔧 ЖКХ, сантехники, электрики • 👮 Участковый полиция ❓ Нужна помощь? Напишите администратору чата или используйте команду /help 🔐 Конфиденциальность Бот видит только ваши сообщения в этом чате. Доступ к данным есть только у администраторов.''' await callback.message.answer(text, parse_mode='HTML') @router.callback_query(F.data == 'phones') async def cb_phones(callback: CallbackQuery): """Телефоны — главное меню""" await callback.answer() await callback.message.answer( '📞 Выберите категорию:', reply_markup=get_phones_menu() ) @router.callback_query(F.data == 'rules') async def cb_rules(callback: CallbackQuery): """Правила чата""" await callback.answer() text = '''📜 Правила нашего чата 1️⃣ Уважайте соседей • Не оскорбляйте и не унижайте других • Запрещены угрозы и дискриминация • Критикуйте идеи, а не людей 2️⃣ Не спамьте • Максимум 10 сообщений в минуту • Запрещена реклама без согласования • Не отправляйте цепочки и фейки 3️⃣ Мат — под запретом • 3 предупреждения = бан • Используйте цензурную лексику 4️⃣ Темы для обсуждений • Вопросы по дому и ЖКХ • Соседи и взаимопомощь • Объявления (продам, куплю, отдам) • События и собрания 5️⃣ Верификация обязательна • Отправьте номер квартиры боту • Без верификации доступ ограничен • Это защита от шпионов и ботов ⚠️ Нарушения • Предупреждение (устно) • Мут на 30 минут • Бан навсегда 💡 Хотите помочь чату? Вступайте в инициативную группу!''' await callback.message.answer(text, parse_mode='HTML') @router.message(Command('правила')) @router.message(Command('rules')) async def cmd_rules(message: Message): """Правила чата (команда)""" text = '''📜 Правила нашего чата 1️⃣ Уважайте соседей • Не оскорбляйте и не унижайте других • Запрещены угрозы и дискриминация • Критикуйте идеи, а не людей 2️⃣ Не спамьте • Максимум 10 сообщений в минуту • Запрещена реклама без согласования • Не отправляйте цепочки и фейки 3️⃣ Мат — под запретом • 3 предупреждения = бан • Используйте цензурную лексику 4️⃣ Темы для обсуждений • Вопросы по дому и ЖКХ • Соседи и взаимопомощь • Объявления (продам, куплю, отдам) • События и собрания 5️⃣ Верификация обязательна • Отправьте номер квартиры боту • Без верификации доступ ограничен • Это защита от шпионов и ботов ⚠️ Нарушения • Предупреждение (устно) • Мут на 30 минут • Бан навсегда 💡 Хотите помочь чату? Вступайте в инициативную группу!''' await message.answer(text, parse_mode='HTML') @router.callback_query(F.data == 'phones_emergency') async def cb_phones_emergency(callback: CallbackQuery): """Экстренные телефоны""" await callback.answer() async with AsyncSessionLocal() as session: stmt = select(Service).where(Service.category == 'emergency').order_by(Service.name) result = await session.execute(stmt) services = list(result.scalars().all()) text = '🚨 Экстренные службы\n\n' text += format_phone_list([ { 'name': s.name, 'phone': s.phone, 'description': s.description, 'category': s.category } for s in services ]) if not services: text += 'Список пуст. Администратор, добавьте телефоны в БД.' await callback.message.answer(text, parse_mode='HTML') return # Если есть картинка у первой службы — отправляем с фото first_service = services[0] if first_service.image_path: from aiogram.types import FSInputFile try: photo = FSInputFile(first_service.image_path) await callback.message.answer_photo(photo, text, parse_mode='HTML') return except Exception as e: logger.error(f"Ошибка отправки фото: {e}") # Если нет картинки — отправляем текстом await callback.message.answer(text, parse_mode='HTML') @router.callback_query(F.data == 'phones_utility') async def cb_phones_utility(callback: CallbackQuery): """ЖКХ службы""" await callback.answer() async with AsyncSessionLocal() as session: stmt = select(Service).where(Service.category == 'utility').order_by(Service.name) result = await session.execute(stmt) services = list(result.scalars().all()) text = '🔧 Службы ЖКХ\n\n' text += format_phone_list([ { 'name': s.name, 'phone': s.phone, 'description': s.description, 'category': s.category } for s in services ]) if not services: await callback.message.answer(text, parse_mode='HTML') return # Если есть картинка — отправляем с фото first_service = services[0] if first_service.image_path: from aiogram.types import FSInputFile try: photo = FSInputFile(first_service.image_path) await callback.message.answer_photo(photo, text, parse_mode='HTML') return except Exception as e: logger.error(f"Ошибка отправки фото: {e}") await callback.message.answer(text, parse_mode='HTML') @router.callback_query(F.data == 'phones_police') async def cb_phones_police(callback: CallbackQuery): """Полиция""" await callback.answer() async with AsyncSessionLocal() as session: stmt = select(Service).where(Service.category == 'police').order_by(Service.name) result = await session.execute(stmt) services = list(result.scalars().all()) text = '👮 Полиция\n\n' text += format_phone_list([ { 'name': s.name, 'phone': s.phone, 'description': s.description, 'category': s.category } for s in services ]) if not services: await callback.message.answer(text, parse_mode='HTML') return # Если есть картинка — отправляем с фото first_service = services[0] if first_service.image_path: from aiogram.types import FSInputFile try: photo = FSInputFile(first_service.image_path) await callback.message.answer_photo(photo, text, parse_mode='HTML') return except Exception as e: logger.error(f"Ошибка отправки фото: {e}") await callback.message.answer(text, parse_mode='HTML') @router.callback_query(F.data == 'masters') async def cb_masters(callback: CallbackQuery): """Мастера""" await callback.answer() async with AsyncSessionLocal() as session: stmt = select(Service).where(Service.category.in_(['masters', 'plumber', 'electrician'])).order_by(Service.name) result = await session.execute(stmt) services = list(result.scalars().all()) if not services: text = '👷 Проверенные мастера\n\n' text += '⏳ Список мастеров заполняется...\n\n' text += 'Скоро здесь появятся контакты проверенных сантехников, электриков и других мастеров.\n\n' text += '💡 Хотите предложить мастера?\n' text += 'Напишите администратору чата!' else: text = '👷 Проверенные мастера\n\n' text += format_phone_list([ { 'name': s.name, 'phone': s.phone, 'description': s.description, 'category': s.category, 'rating': s.rating } for s in services ]) await callback.message.answer(text, parse_mode='HTML') @router.callback_query(F.data == 'my_apartment') async def cb_my_apartment(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: text = '❌ Вы не зарегистрированы. Нажмите /start' elif not user.apartment: text = f'⏳ Вы не верифицированы.\n\nОтправьте номер квартиры текстом.' else: status = '✅' if user.verified else '⏳' text = ( f'{status} Ваша квартира: {user.apartment}\n\n' f'Статус: {"Верифицирован" if user.verified else "Не верифицирован"}\n' f'Сообщений: {user.message_count}' ) await callback.message.answer(text, parse_mode='HTML') @router.callback_query(F.data == 'schedule') async def cb_schedule(callback: CallbackQuery): """График отключений""" await callback.answer() # TODO: Загрузить реальный график text = '''📅 График отключений ⚠️ Информация обновляется Вода: • Плановое отключение: каждое второе воскресенье месяца, 9:00-17:00 Электричество: • Без плановых отключений Отопление: • Сезон: октябрь — апрель Для актуальной информации обратитесь в УК.''' await callback.message.answer(text, parse_mode='HTML') @router.callback_query(F.data == 'main_menu') async def cb_main_menu(callback: CallbackQuery): """Главное меню""" from utils.ui_styles import get_colored_header, get_level_badge await callback.answer() user_id = callback.from_user.id async with AsyncSessionLocal() as session: user = await session.get(User, user_id) if user and user.verified: # Проверяем ИГ и админа from database.models import InitiativeGroup ig_stmt = select(InitiativeGroup).where( InitiativeGroup.user_id == user_id, InitiativeGroup.is_active == True ) ig_result = await session.execute(ig_stmt) is_ig = ig_result.scalar_one_or_none() is not None is_admin = user.is_admin or (user_id == config.ADMIN_USER_ID) is_superadmin = (user_id == config.ADMIN_USER_ID) # Определяем уровень if is_superadmin: level = 'superadmin' header = get_colored_header('УПРАВЛЕНИЕ СИСТЕМОЙ', level) elif is_admin: level = 'admin' header = get_colored_header('АДМИН-ПАНЕЛЬ', level) elif is_ig: level = 'ig' header = get_colored_header('ИНИЦИАТИВНАЯ ГРУППА', level) else: level = 'verified' header = get_colored_header('ГЛАВНОЕ МЕНЮ', level) await callback.message.answer( f'{header}\n\n{get_level_badge(level)}', reply_markup=get_main_menu(verified=True, is_ig=is_ig, is_admin=is_admin, is_superadmin=is_superadmin), parse_mode='HTML' ) else: header = get_colored_header('МЕНЮ', 'unverified') await callback.message.answer( f'{header}\n\n{get_level_badge("unverified")}', reply_markup=get_unverified_menu(), parse_mode='HTML' ) # Текстовые команды (алиасы) @router.message(F.text.lower() == 'телефоны') @router.message(F.text.lower() == 'телефон') async def msg_phones_text(message: Message): """Текстовая команда телефоны""" await message.answer( '📞 Выберите категорию:', reply_markup=get_phones_menu() ) @router.message(F.text.lower() == 'мастера') async def msg_masters_text(message: Message): """Текстовая команда мастера""" await message.answer( '👷 Загружаю список мастеров...', reply_markup=get_main_menu(verified=True) ) # Перенаправляем на callback async with AsyncSessionLocal() as session: stmt = select(Service).where(Service.category.in_(['masters', 'plumber', 'electrician'])) result = await session.execute(stmt) services = list(result.scalars().all()) text = '👷 Проверенные мастера\n\n' text += format_phone_list([ {'name': s.name, 'phone': s.phone, 'description': s.description, 'rating': s.rating} for s in services ]) await message.answer(text, parse_mode='HTML') @router.message(F.text.lower() == 'помощь') 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] = {} # Проверка что сообщение в личке def check_private_chat(callback: CallbackQuery) -> bool: """Проверяет что callback из лички""" if callback.message.chat.type != 'private': callback.answer('💡 Это работает только в личных сообщениях!', show_alert=True) return False return True @router.callback_query(F.data == 'achievements') async def cb_achievements_menu(callback: CallbackQuery): """Кнопка Достижения""" if not check_private_chat(callback): return 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): """Кнопка События""" if not check_private_chat(callback): return 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): """Кнопка Платежи""" if not check_private_chat(callback): return 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): """Кнопка Опросы""" if not check_private_chat(callback): return 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): """Кнопка Поблагодарить - выбор пользователя""" if not check_private_chat(callback): return await callback.answer() async with AsyncSessionLocal() as session: # Показываем ВСЕХ верифицированных пользователей stmt = select(User).where(User.verified == True).order_by(desc(User.message_count)) 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 } total_pages = (len(users) + 19) // 20 await callback.message.answer( f'🙏 Поблагодарить пользователя\n\n' f'Всего верифицированных: {len(users)}\n' f'Страница 1 из {total_pages}\n\n' f'Выберите пользователя:\n' f'(+10 к рейтингу)\n\n' f'💡 Совет: Используйте /thank @username для быстрого поиска!', reply_markup=get_thank_user_keyboard(users, page=0, page_size=20), 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 'не указана' phone = user.phone or 'не указан' text = '🔍 Ваша квартира\n\n' text += f'🏠 Квартира: {apt}\n' text += f'📞 Телефон: {phone}\n\n' text += 'Хотите изменить?\n\n' text += 'Используйте команды:\n' text += '/setapt 45 — изменить квартиру\n' text += '/setphone +79991234567 — изменить телефон' await callback.message.answer(text, parse_mode='HTML') # ============================================================================= # ИЗМЕНИТЬ ПРОФИЛЬ (КОМАНДЫ) # ============================================================================= @router.message(Command('setapt')) @router.message(Command('set_apt')) async def cmd_set_apt(message: Message): """ Изменить номер квартиры Использование: /setapt 45 """ from utils.validators import validate_apartment user_id = message.from_user.id args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/setapt 45\n\n' 'Где 45 — ваш номер квартиры.', parse_mode='HTML' ) return apartment = args[1].strip().upper() if not validate_apartment(apartment): await message.answer( '❌ Неверный формат квартиры.\n\n' 'Примеры: 45, 12А, 100-1', parse_mode='HTML' ) return async with AsyncSessionLocal() as session: user = await session.get(User, user_id) if not user: await message.answer('❌ Вы не зарегистрированы') return user.apartment = apartment await session.commit() await message.answer( f'✅ Квартира обновлена!\n\n' f'Ваша квартира: {apartment}', parse_mode='HTML' ) @router.message(Command('setphone')) @router.message(Command('set_phone')) async def cmd_set_phone(message: Message): """ Изменить номер телефона Использование: /setphone +79991234567 """ user_id = message.from_user.id args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/setphone +79991234567', parse_mode='HTML' ) return phone = args[1].strip() # Простая валидация if not phone.startswith('+') or len(phone) < 10: await message.answer( '❌ Неверный формат телефона.\n\n' 'Пример: +79991234567', parse_mode='HTML' ) return async with AsyncSessionLocal() as session: user = await session.get(User, user_id) if not user: await message.answer('❌ Вы не зарегистрированы') return user.phone = phone await session.commit() await message.answer( f'✅ Телефон обновлён!\n\n' f'Ваш телефон: {phone}', parse_mode='HTML' ) # ============================================================================= # РЕДАКТИРОВАНИЕ ПРОФИЛЯ ЖИЛЬЦА (ДЛЯ АДМИНА) # ============================================================================= @router.message(Command('edit_user')) @router.message(Command('edituser')) async def cmd_edit_user(message: Message): """ Редактировать профиль жильца (для админа) Использование: /edit_user [квартира] [телефон] """ if not config.is_admin(message.from_user.id): await message.answer('❌ Только для администраторов') return args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n\n' '/edit_user <user_id> [квартира] [телефон]\n\n' 'Примеры:\n' '/edit_user 123456789 45 +79991234567\n' '/edit_user 123456789 apt:45 phone:+79991234567\n' '/edit_user 123456789 phone:+79991234567\n' '/edit_user 123456789 apt:45', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return # Парсим аргументы apartment = None phone = None for arg in args[2:]: if arg.startswith('apt:'): apartment = arg[4:].upper() elif arg.startswith('phone:'): phone = arg[6:] elif apartment is None: # Первый аргумент без префикса — квартира apartment = arg.upper() elif phone is None: # Второй аргумент без префикса — телефон phone = arg async with AsyncSessionLocal() as session: user = await session.get(User, user_id) if not user: await message.answer(f'❌ Пользователь {user_id} не найден') return # Обновляем данные updated = [] if apartment: user.apartment = apartment updated.append(f'квартира: {apartment}') if phone: user.phone = phone updated.append(f'телефон: {phone}') if not updated: await message.answer( '❌ Укажите квартиру или телефон\n\n' 'Пример: /edit_user 123456789 45 +79991234567', parse_mode='HTML' ) return await session.commit() name = user.full_name or user.username or f'user{user_id}' await message.answer( f'✅ Профиль обновлён!\n\n' f'👤 {name}\n' f'🔖 ID: {user_id}\n\n' f'Обновлено:\n' + '\n'.join(f'• {item}' for item in updated), parse_mode='HTML' ) # ============================================================================= # ПРИВЕТСТВИЕ НОВЫХ УЧАСТНИКОВ # ============================================================================= # ВРЕМЕННО ОТКЛЮЧЕНО - проблема с ChatMemberUpdatedFilter в aiogram 3.x # @router.chat_member(ChatMemberUpdatedFilter(member_added=MemberAdded(ChatMemberStatus.MEMBER))) # async def welcome_new_member(event: ChatMemberUpdated, bot: Bot): # """Приветствие нового участника группы""" # from aiogram.exceptions import TelegramForbiddenError # # user = event.from_user # chat = event.chat # # logger.info(f'Новый участник в чате {chat.title}: {user.first_name} (@{user.username})') # # # Отправляем привет в личку # try: # bot_username = (await bot.get_me()).username # # welcome_text = f'''🏠 Добро пожаловать в чат ЖК "Ленком 37"! # # 👋 Привет, {user.first_name}! # # Я — домовой бот нашего дома. # # 📋 Что здесь происходит: # • Общение соседей # • Объявления и новости # • Вопросы по ЖКХ # • Взаимопомощь # # ✅ Чтобы получить полный доступ: # 1. Напиши мне в личку: @{bot_username} # 2. Отправь номер квартиры (например: 45) # 3. Дождись подтверждения админа # # 📞 Полезные команды: # /телефон — Экстренные службы # /мастера — Сантехники, электрики # /участковый — Контакты полиции # /график — Отключения воды/света # /правила — Правила чата # # 💡 Нужна помощь? # Напишите администратору чата. # # 🎉 Рады видеть вас в нашем сообществе!''' # # await bot.send_message( # chat_id=user.id, # text=welcome_text, # parse_mode='HTML' # ) # logger.info(f'Приветствие отправлено {user.id}') # # except TelegramForbiddenError: # logger.warning(f'Не удалось отправить приветствие {user.id} (закрыт от ботов)') # except Exception as e: # logger.error(f'Ошибка при отправке приветствия: {e}')