""" Обработчики авторизации: - bot_started — первый запуск / перезапуск - /start команда - Шаринг контакта """ import re from maxapi.dispatcher import Router from maxapi.filters import F from maxapi.filters.contact import ContactFilter from maxapi.types.attachments.attachment import ContactAttachmentPayload from maxapi.types.attachments.contact import Contact as ContactAttachment from maxapi.types.updates.bot_started import BotStarted from maxapi.types.updates.message_created import MessageCreated from maxapi.context import BaseContext from maxapi.filters.command import Command from bot.keyboards import contact_request_kb, main_menu_kb from bot.logging_setup import get_logger from bot.send_helper import bot_send from bot.states import AuthStates logger = get_logger("bot.handlers.start") from database.queries.bot_users import ( authorize_bot_user, block_bot_user, get_bot_user_by_max_id, upsert_bot_user, ) from database.queries.org_users import find_by_phone from database.queries.message_log import log_message router = Router(router_id="start") def normalize_phone(raw: str) -> str: """Убираем всё кроме цифр, заменяем ведущую 8 → 7.""" digits = re.sub(r"\D", "", raw) if digits.startswith("8") and len(digits) == 11: digits = "7" + digits[1:] return digits @router.bot_started() async def on_bot_started(event: BotStarted, context: BaseContext) -> None: user = event.user bot_user = await upsert_bot_user( max_user_id=user.user_id, max_chat_id=event.chat_id, username=getattr(user, "username", None), first_name=getattr(user, "name", None), ) if bot_user["is_blocked"]: logger.info("🚫 bot_started blocked | user_id=%s", user.user_id) await bot_send( event.bot, chat_id=event.chat_id, user_id=user.user_id, text=( "⛔ Ваш номер не найден в системе. Доступ заблокирован.\n" "Обратитесь к администратору для подключения." ), ) return if bot_user["is_authorized"]: logger.info("✅ bot_started returning user | user_id=%s", user.user_id) await context.set_state(None) await bot_send( event.bot, chat_id=event.chat_id, user_id=user.user_id, text="👋 С возвращением! Выберите действие:", attachments=[main_menu_kb()], ) return # Новый пользователь — запрашиваем контакт logger.info("👋 bot_started new user | user_id=%s", user.user_id) await context.set_state(AuthStates.waiting_contact) await bot_send( event.bot, chat_id=event.chat_id, user_id=user.user_id, text=( "👋 Добро пожаловать в систему технической поддержки!\n\n" "Для входа нажмите кнопку ниже и поделитесь своим номером телефона." ), attachments=[contact_request_kb()], ) @router.message_created(Command("start")) async def on_start_command(event: MessageCreated, context: BaseContext) -> None: chat_id, user_id = event.get_ids() if user_id is None or chat_id is None: return bot_user = await get_bot_user_by_max_id(user_id) if bot_user is None: # Первый раз — как bot_started await upsert_bot_user( max_user_id=user_id, max_chat_id=chat_id, username=None, first_name=None, ) await context.set_state(AuthStates.waiting_contact) await bot_send( event.bot, chat_id=chat_id, user_id=user_id, text=( "👋 Добро пожаловать!\n\n" "Поделитесь номером телефона для доступа к системе." ), attachments=[contact_request_kb()], ) return if bot_user["is_blocked"]: await bot_send( event.bot, chat_id=chat_id, user_id=user_id, text="⛔ Доступ заблокирован. Обратитесь к администратору.", ) return if not bot_user["is_authorized"]: await context.set_state(AuthStates.waiting_contact) await bot_send( event.bot, chat_id=chat_id, user_id=user_id, text="Поделитесь номером телефона для доступа:", attachments=[contact_request_kb()], ) return await context.set_state(None) await bot_send( event.bot, chat_id=chat_id, user_id=user_id, text="Главное меню:", attachments=[main_menu_kb()], ) @router.message_created(ContactFilter(), states=AuthStates.waiting_contact) async def on_contact_shared( event: MessageCreated, contact: ContactAttachment, context: BaseContext, ) -> None: chat_id, user_id = event.get_ids() if user_id is None or chat_id is None: return # Извлекаем телефон из VCF payload = contact.payload if not isinstance(payload, ContactAttachmentPayload): await bot_send( event.bot, chat_id=chat_id, user_id=user_id, text="Не удалось прочитать контакт. Попробуйте ещё раз.", attachments=[contact_request_kb()], ) return vcf = payload.vcf raw_phone = vcf.phone if not raw_phone: await bot_send( event.bot, chat_id=chat_id, user_id=user_id, text="В контакте не найден номер телефона. Попробуйте ещё раз.", attachments=[contact_request_kb()], ) return phone = normalize_phone(raw_phone) display_name = vcf.full_name # Логируем входящий контакт await log_message( direction="in", max_user_id=user_id, max_chat_id=chat_id, message_id=None, text=f"[Контакт: {raw_phone}]", ) # Ищем в белом списке org_user = await find_by_phone(phone) if org_user is None: logger.warning("🚫 phone not found, blocking | user_id=%s phone=%s", user_id, phone) await block_bot_user(max_user_id=user_id, phone=phone) await context.set_state(None) await bot_send( event.bot, chat_id=chat_id, user_id=user_id, text=( "⛔ Ваш номер не найден в системе.\n" "Доступ заблокирован. Обратитесь к администратору для подключения." ), ) return # Авторизуем logger.info("✅ authorized | user_id=%s phone=%s org_id=%s", user_id, phone, org_user["org_id"]) await authorize_bot_user( max_user_id=user_id, phone=phone, org_id=org_user["org_id"], first_name=display_name, ) await context.set_state(None) await bot_send( event.bot, chat_id=chat_id, user_id=user_id, text="✅ Вы успешно авторизованы! Выберите действие:", attachments=[main_menu_kb()], )