From 6e6111df1891d9cd62975ea17e92957882d594d8 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Wed, 12 Nov 2025 00:01:19 +0200 Subject: [PATCH] Refactor Telegram bot login flow: clean UI with single editable message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Consolidate login flow to use single message that edits in place - Auto-delete all user messages (email, code, password, /start command) - Remove all emojis from bot messages for cleaner interface - Fix "Retrimite Cod" bug - buttons now persist after resending code - Replace "Oracle" with "ROA" in all user-facing messages - Add clear instructions for each step (email input, code input, password) - Implement message tracking with context.user_data['login_message_id'] - Clean chat history - only menu message remains visible Files modified: - app/bot/email_handlers.py: Complete refactor of email login flow - app/bot/handlers.py: Update /start command to delete user message and edit existing message 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../telegram-bot/app/bot/email_handlers.py | 381 +++++++++++------- reports-app/telegram-bot/app/bot/handlers.py | 88 ++-- 2 files changed, 301 insertions(+), 168 deletions(-) diff --git a/reports-app/telegram-bot/app/bot/email_handlers.py b/reports-app/telegram-bot/app/bot/email_handlers.py index 36ea177..98e2210 100644 --- a/reports-app/telegram-bot/app/bot/email_handlers.py +++ b/reports-app/telegram-bot/app/bot/email_handlers.py @@ -12,6 +12,7 @@ from telegram.ext import ( ) import logging from datetime import datetime +import asyncio from app.auth.email_auth import ( is_valid_email_format, @@ -44,6 +45,67 @@ AWAITING_EMAIL, AWAITING_CODE, AWAITING_PASSWORD = range(3) MAX_CODE_ATTEMPTS = 3 +# ============================================================================ +# HELPER FUNCTIONS +# ============================================================================ + +async def edit_login_message( + context: ContextTypes.DEFAULT_TYPE, + chat_id: int, + text: str, + reply_markup=None, + parse_mode="Markdown" +): + """ + Helper function to edit the login message stored in context. + If message_id is not stored, creates a new message instead. + """ + message_id = context.user_data.get('login_message_id') + + if message_id: + try: + await context.bot.edit_message_text( + chat_id=chat_id, + message_id=message_id, + text=text, + reply_markup=reply_markup, + parse_mode=parse_mode + ) + except Exception as e: + logger.warning(f"Could not edit message {message_id}: {e}") + # Fallback: send new message and update ID + msg = await context.bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=reply_markup, + parse_mode=parse_mode + ) + context.user_data['login_message_id'] = msg.message_id + else: + # No message ID stored - create new message + msg = await context.bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=reply_markup, + parse_mode=parse_mode + ) + context.user_data['login_message_id'] = msg.message_id + + +async def delete_login_message(context: ContextTypes.DEFAULT_TYPE, chat_id: int): + """Delete the login message and clear the message_id from context""" + message_id = context.user_data.get('login_message_id') + + if message_id: + try: + await context.bot.delete_message(chat_id=chat_id, message_id=message_id) + except Exception as e: + logger.warning(f"Could not delete message {message_id}: {e}") + + # Clear from context + context.user_data.pop('login_message_id', None) + + # ============================================================================ # ENTRY POINTS: /login command and action:login button # ============================================================================ @@ -82,20 +144,16 @@ async def login_command(update: Update, context: ContextTypes.DEFAULT_TYPE): ] reply_markup = InlineKeyboardMarkup(keyboard) - await update.message.reply_text( - "**Alege metoda de autentificare:**\n\n" - "**Email + Parolă (2FA)**\n" - " • Primești cod pe email\n" - " • Introduci codul\n" - " • Introduci parola Oracle\n\n" - "**Web App**\n" - " • Login în aplicația web\n" - " • Generează cod de linking\n" - " • Trimite codul cu /start", + # CREATE message and SAVE message_id + msg = await update.message.reply_text( + "Alege metoda de autentificare:", reply_markup=reply_markup, parse_mode="Markdown" ) + # Save message ID for future edits + context.user_data['login_message_id'] = msg.message_id + return AWAITING_EMAIL @@ -138,20 +196,16 @@ async def action_login_callback(update: Update, context: ContextTypes.DEFAULT_TY ] reply_markup = InlineKeyboardMarkup(keyboard) + # EDIT existing menu message and SAVE message_id await query.edit_message_text( - "**Alege metoda de autentificare:**\n\n" - "**Email + Parolă (2FA)**\n" - " • Primești cod pe email\n" - " • Introduci codul\n" - " • Introduci parola Oracle\n\n" - "**Web App**\n" - " • Login în aplicația web\n" - " • Generează cod de linking\n" - " • Trimite codul cu /start", + "Alege metoda de autentificare:", reply_markup=reply_markup, parse_mode="Markdown" ) + # Save message ID for future edits + context.user_data['login_message_id'] = query.message.message_id + return AWAITING_EMAIL @@ -168,12 +222,12 @@ async def email_login_callback(update: Update, context: ContextTypes.DEFAULT_TYP await query.answer() + # IMPORTANT: Salvează message_id înainte de a edita + context.user_data['login_message_id'] = query.message.message_id + + # EDIT same message - remove buttons, ask for email await query.edit_message_text( - "**Autentificare prin Email + Parolă**\n\n" - "Te rugăm să introduci adresa ta de **email Oracle**:\n\n" - "Exemplu: nume.prenume@companie.ro\n\n" - "Vei primi un cod de 6 cifre pe email.\n\n" - "Scrie /cancel pentru a anula.", + text="Introdu adresa de email ROA:", parse_mode="Markdown" ) @@ -197,6 +251,9 @@ async def web_login_info_callback(update: Update, context: ContextTypes.DEFAULT_ parse_mode="Markdown" ) + # IMPORTANT: Salvează message_id pentru ca /start să poată edita același mesaj + context.user_data['web_login_message_id'] = query.message.message_id + return ConversationHandler.END @@ -209,13 +266,22 @@ async def receive_email(update: Update, context: ContextTypes.DEFAULT_TYPE): email = update.message.text.strip().lower() user_id = update.effective_user.id + # ȘTERG mesajul utilizatorului imediat (chat curat) + try: + await update.message.delete() + except Exception as e: + logger.warning(f"Could not delete email message: {e}") + # Validare format email if not is_valid_email_format(email): - await update.message.reply_text( - "**Email invalid**\n\n" - "Te rugăm să introduci o adresă de email validă.\n\n" - "Format: nume@domeniu.ro", - parse_mode="Markdown" + # Show error in main message + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Email invalid\n\nIntrodu o adresă validă (nume@domeniu.ro)", + reply_markup=InlineKeyboardMarkup([ + [InlineKeyboardButton("Anulează", callback_data="cancel")] + ]) ) return AWAITING_EMAIL @@ -226,8 +292,13 @@ async def receive_email(update: Update, context: ContextTypes.DEFAULT_TYPE): await delete_user_email_codes(user_id) logger.info(f"Deleted existing pending code for user {user_id}") - # Loading message - loading_msg = await update.message.reply_text("Verificare email...") + # EDIT login message to show loading + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Verificare email...", + reply_markup=None + ) try: # Verifică email în Oracle @@ -250,8 +321,10 @@ async def receive_email(update: Update, context: ContextTypes.DEFAULT_TYPE): ) if not code_saved: - await loading_msg.edit_text( - "Eroare la salvarea codului. Te rugăm să încerci din nou cu /login" + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Eroare la salvarea codului.\n\nIncearcă din nou cu /login" ) return ConversationHandler.END @@ -263,16 +336,15 @@ async def receive_email(update: Update, context: ContextTypes.DEFAULT_TYPE): logger.error(f"Failed to send email to {email}") # Don't reveal this to user - they'll timeout naturally + # Wait 1 second for better UX (looks like verification happened) + await asyncio.sleep(1) + # ALWAYS show this message (prevent enumeration) - await loading_msg.edit_text( - "**Cod trimis**\n\n" - f"Am trimis un cod de 6 cifre pe **{email}**\n\n" - "Verifică:\n" - " • Inbox-ul\n" - " • Folderul Spam/Junk\n\n" - "Codul expiră în **5 minute**\n\n" - "Introdu codul aici sau apasă butonul de mai jos.", - parse_mode="Markdown", + # EDIT same message with success + buttons + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text=f"Cod trimis pe {email}\n\nIntrodu codul primit pe email:", reply_markup=InlineKeyboardMarkup([ [InlineKeyboardButton("Retrimite Cod", callback_data=f"resend:{email}")], [InlineKeyboardButton("Anulează", callback_data="cancel")] @@ -287,8 +359,10 @@ async def receive_email(update: Update, context: ContextTypes.DEFAULT_TYPE): except Exception as e: logger.error(f"Error in receive_email: {e}", exc_info=True) - await loading_msg.edit_text( - "Eroare internă. Te rugăm să încerci din nou mai târziu." + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Eroare internă.\n\nIncearcă din nou mai târziu." ) return ConversationHandler.END @@ -302,13 +376,22 @@ async def receive_code(update: Update, context: ContextTypes.DEFAULT_TYPE): code = update.message.text.strip() user_id = update.effective_user.id + # ȘTERG mesajul utilizatorului imediat (chat curat) + try: + await update.message.delete() + except Exception as e: + logger.warning(f"Could not delete code message: {e}") + # Validare format cod (6 digits) if not (code.isdigit() and len(code) == 6): - await update.message.reply_text( - "**Cod invalid**\n\n" - "Te rugăm să introduci cele **6 cifre** din email.\n\n" - "Format: 123456", - parse_mode="Markdown" + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Cod invalid\n\nIntrodu cele 6 cifre din email.", + reply_markup=InlineKeyboardMarkup([ + [InlineKeyboardButton("Retrimite Cod", callback_data=f"resend:{context.user_data.get('pending_email', '')}")], + [InlineKeyboardButton("Anulează", callback_data="cancel")] + ]) ) return AWAITING_CODE @@ -317,11 +400,11 @@ async def receive_code(update: Update, context: ContextTypes.DEFAULT_TYPE): code_data = await get_email_auth_code(code) if not code_data: - await update.message.reply_text( - "**Cod invalid sau expirat**\n\n" - "Te rugăm să:\n" - "• Verifici codul din email\n" - "• Sau reîncepi cu /login" + # EDIT login message to show error + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Cod invalid sau expirat\n\nIncearcă din nou cu /login" ) return ConversationHandler.END @@ -329,19 +412,19 @@ async def receive_code(update: Update, context: ContextTypes.DEFAULT_TYPE): # 1. Check if already used if code_data['used']: - await update.message.reply_text( - "**Cod deja folosit**\n\n" - "Fiecare cod poate fi folosit o singură dată.\n\n" - "Te rugăm să reîncepi cu /login" + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Cod deja folosit\n\nIncearcă din nou cu /login" ) return ConversationHandler.END # 2. Check if expired if datetime.now() > code_data['expires_at']: - await update.message.reply_text( - "**Cod expirat**\n\n" - "Codul era valabil 5 minute.\n\n" - "Te rugăm să reîncepi cu /login" + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Cod expirat\n\nIncearcă din nou cu /login" ) return ConversationHandler.END @@ -351,16 +434,19 @@ async def receive_code(update: Update, context: ContextTypes.DEFAULT_TYPE): f"User {user_id} tried to use code belonging to " f"user {code_data['telegram_user_id']}" ) - await update.message.reply_text( - "**Cod invalid**" + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Cod invalid" ) return ConversationHandler.END # 4. Check failed attempts (max 3) if code_data['failed_attempts'] >= MAX_CODE_ATTEMPTS: - await update.message.reply_text( - "**Prea multe încercări greșite**\n\n" - "Te rugăm să reîncepi cu /login pentru un cod nou." + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Prea multe încercări greșite\n\nIncearcă din nou cu /login" ) return ConversationHandler.END @@ -375,23 +461,22 @@ async def receive_code(update: Update, context: ContextTypes.DEFAULT_TYPE): code_data['email'] ) - await update.message.reply_text( - "**Cod validat cu succes**\n\n" - "Acum introdu **parola ta Oracle**:\n\n" - "**Important:**\n" - " • Parola va fi ștearsă automat\n" - " • Nu va fi vizibilă în chat\n" - " • Verificată direct în Oracle\n\n" - "Scrie /cancel pentru a anula.", - parse_mode="Markdown" + # EDIT same message - ask for password + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Cod validat!\n\nIntroduci parola ROA:", + reply_markup=None # No buttons for security ) return AWAITING_PASSWORD except Exception as e: logger.error(f"Error validating code: {e}", exc_info=True) - await update.message.reply_text( - "Eroare la validarea codului. Te rugăm să încerci din nou." + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Eroare la validarea codului.\n\nIncearcă din nou." ) return ConversationHandler.END @@ -404,7 +489,11 @@ async def resend_code_callback(update: Update, context: ContextTypes.DEFAULT_TYP # Extract email from callback data callback_data = query.data # Format: "resend:email@example.com" if not callback_data.startswith("resend:"): - await query.edit_message_text("Eroare. Te rugăm să reîncepi cu /login") + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Eroare\n\nIncearcă din nou cu /login" + ) return ConversationHandler.END email = callback_data.split(":", 1)[1] @@ -412,9 +501,10 @@ async def resend_code_callback(update: Update, context: ContextTypes.DEFAULT_TYP # Check rate limiting for resend (max 2 per 10 minutes) if not await check_rate_limit(f"resend_{user_id}", max_attempts=2, window_minutes=10): - await query.edit_message_text( - "Prea multe solicitări de retrimitere.\n\n" - "Te rugăm să aștepți 10 minute." + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Prea multe solicitări\n\nAșteaptă 10 minute." ) return ConversationHandler.END @@ -424,8 +514,10 @@ async def resend_code_callback(update: Update, context: ContextTypes.DEFAULT_TYP if not username: username = await verify_email_in_oracle(email) if not username: - await query.edit_message_text( - "Eroare. Te rugăm să reîncepi cu /login" + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Eroare\n\nIncearcă din nou cu /login" ) return ConversationHandler.END @@ -447,11 +539,15 @@ async def resend_code_callback(update: Update, context: ContextTypes.DEFAULT_TYP email_service = get_email_service() await email_service.send_auth_code(email, code, username) - await query.edit_message_text( - f"**Cod retrimis pe {email}**\n\n" - "Verifică inbox-ul (și spam).\n\n" - "Introdu codul aici.", - parse_mode="Markdown" + # FIX BUG: EDIT message and KEEP buttons! + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text=f"Cod retrimis pe {email}\n\nIntrodu codul primit pe email:", + reply_markup=InlineKeyboardMarkup([ + [InlineKeyboardButton("Retrimite Cod", callback_data=f"resend:{email}")], + [InlineKeyboardButton("Anulează", callback_data="cancel")] + ]) ) return AWAITING_CODE @@ -479,14 +575,19 @@ async def receive_password(update: Update, context: ContextTypes.DEFAULT_TYPE): session_token = context.user_data.get('session_token') if not all([username, email, session_token]): - await update.effective_chat.send_message( - "Sesiune expirată. Te rugăm să reîncepi cu /login" + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Sesiune expirată\n\nIncearcă din nou cu /login" ) return ConversationHandler.END - # Loading message - loading_msg = await update.effective_chat.send_message( - "Verificare credențiale în Oracle..." + # EDIT login message to show loading + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Verificare...", + reply_markup=None ) try: @@ -501,10 +602,10 @@ async def receive_password(update: Update, context: ContextTypes.DEFAULT_TYPE): ) if not response.get('success'): - await loading_msg.edit_text( - "**Credențiale invalide**\n\n" - "Parolă incorectă sau cont inactiv.\n\n" - "Te rugăm să reîncepi cu /login" + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Credențiale invalide\n\nParolă incorectă sau cont inactiv.\n\nIncearcă din nou cu /login" ) return ConversationHandler.END @@ -533,17 +634,10 @@ async def receive_password(update: Update, context: ContextTypes.DEFAULT_TYPE): clear_rate_limit(f"login_{user_id}") clear_rate_limit(f"resend_{user_id}") - # Delete loading message - try: - await loading_msg.delete() - except Exception: - pass - - # Show main menu with buttons (user is now authenticated) + # Get session and active company BEFORE editing message from app.agent.session import get_session_manager from app.bot.menus import create_main_menu, pad_message_for_wide_buttons - # Get session and active company session_manager = get_session_manager() session = await session_manager.get_or_create_session(user_id) company = session.get_active_company() @@ -559,28 +653,22 @@ async def receive_password(update: Update, context: ContextTypes.DEFAULT_TYPE): cache_enabled=True # Default enabled ) - # Success message with company info + # Menu message with company info companies_count = len(response.get('companies', [])) if company_name: - welcome_message = pad_message_for_wide_buttons( - f"**Autentificat cu succes**\n\n" - f"Bun venit, **{response['username']}**\n\n" - f"{company_name}" - ) + menu_text = f"{company_name}" else: - welcome_message = pad_message_for_wide_buttons( - f"**Autentificat cu succes**\n\n" - f"Bun venit, **{response['username']}**\n\n" - f"Companii disponibile: **{companies_count}**\n\n" - f"Selectează o companie pentru a continua" - ) + menu_text = f"Companii disponibile: {companies_count}\n\nSelectează o companie pentru a continua" - # Send menu with buttons - await update.effective_chat.send_message( - welcome_message, - reply_markup=keyboard, - parse_mode="Markdown" + menu_message = pad_message_for_wide_buttons(menu_text) + + # EDIT login message to show menu (no deletion, direct edit) + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text=menu_message, + reply_markup=keyboard ) # Clear sensitive data from context @@ -592,9 +680,10 @@ async def receive_password(update: Update, context: ContextTypes.DEFAULT_TYPE): except Exception as e: logger.error(f"Error during password verification: {e}", exc_info=True) - await loading_msg.edit_text( - "Eroare la autentificare.\n\n" - "Te rugăm să încerci din nou cu /login" + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Eroare la autentificare.\n\nIncearcă din nou cu /login" ) return ConversationHandler.END @@ -605,32 +694,44 @@ async def receive_password(update: Update, context: ContextTypes.DEFAULT_TYPE): async def cancel_login(update: Update, context: ContextTypes.DEFAULT_TYPE): """Cancel conversation""" - context.user_data.clear() - if update.message: - await update.message.reply_text( - "Autentificare anulată.\n\n" - "Folosește /login pentru a încerca din nou." + # EDIT login message to show cancellation (don't delete) + if update.callback_query: + # Called from button + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Login anulat", + reply_markup=None ) - elif update.callback_query: - await update.callback_query.edit_message_text( - "Autentificare anulată." + elif update.message: + # Called from /cancel command + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Login anulat", + reply_markup=None ) + # Clear context + context.user_data.clear() + return ConversationHandler.END async def conversation_timeout(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handler for conversation timeout""" - context.user_data.clear() - await update.effective_chat.send_message( - "**Sesiune expirată**\n\n" - "Conversația de autentificare a expirat după 5 minute de inactivitate.\n\n" - "Te rugăm să reîncepi cu /login", - parse_mode="Markdown" + # EDIT login message to show timeout + await edit_login_message( + context=context, + chat_id=update.effective_chat.id, + text="Sesiune expirată\n\nConversația a expirat după 5 minute.\n\nIncearcă din nou cu /login" ) + # Clear context + context.user_data.clear() + return ConversationHandler.END diff --git a/reports-app/telegram-bot/app/bot/handlers.py b/reports-app/telegram-bot/app/bot/handlers.py index 280f72b..b300470 100644 --- a/reports-app/telegram-bot/app/bot/handlers.py +++ b/reports-app/telegram-bot/app/bot/handlers.py @@ -54,24 +54,65 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): auth_code = args[0].upper() logger.info(f"Attempting to link user {telegram_user_id} with code {auth_code}") - # Show "linking..." message - linking_msg = await update.message.reply_text( - "Linking contul...\n" - "Te rog asteapta..." - ) + # ȘTERGE mesajul utilizatorului imediat (chat curat) + try: + await update.message.delete() + except Exception as e: + logger.warning(f"Could not delete /start message: {e}") + + # Check dacă user-ul a apăsat pe "Login din Web App" înainte + web_login_msg_id = context.user_data.get('web_login_message_id') + + if web_login_msg_id: + # EDITEAZĂ mesajul existent cu "Login din Web App" + try: + await context.bot.edit_message_text( + chat_id=update.effective_chat.id, + message_id=web_login_msg_id, + text="Conectare cont...", + parse_mode=ParseMode.MARKDOWN + ) + linking_msg = await context.bot.get_updates() # Dummy, ne interesează doar message_id + # Simulăm un obiect Message pentru a putea folosi .edit_text() mai jos + class FakeMessage: + def __init__(self, chat_id, message_id, bot): + self.chat_id = chat_id + self.message_id = message_id + self._bot = bot + async def edit_text(self, text, reply_markup=None, parse_mode=None): + await self._bot.edit_message_text( + chat_id=self.chat_id, + message_id=self.message_id, + text=text, + reply_markup=reply_markup, + parse_mode=parse_mode + ) + linking_msg = FakeMessage(update.effective_chat.id, web_login_msg_id, context.bot) + # Clear message_id din context + context.user_data.pop('web_login_message_id', None) + except Exception as e: + logger.warning(f"Could not edit web_login message: {e}") + # Fallback: creează mesaj nou + linking_msg = await update.effective_chat.send_message( + "Conectare cont...", + parse_mode=ParseMode.MARKDOWN + ) + else: + # Nu există mesaj anterior - creează mesaj nou + linking_msg = await update.effective_chat.send_message( + "Conectare cont...", + parse_mode=ParseMode.MARKDOWN + ) # Attempt linking result = await link_telegram_account(telegram_user, auth_code) - # Delete "linking..." message - await linking_msg.delete() - if result: # Success! username = result['username'] jwt_token = result['jwt_token'] - # Show main menu with buttons for newly linked user + # Get session and company info session_manager = get_session_manager() session = await session_manager.get_or_create_session(telegram_user_id) company = session.get_active_company() @@ -92,24 +133,16 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): from app.bot.menus import create_main_menu, pad_message_for_wide_buttons keyboard = create_main_menu(company_name, company_cui, is_authenticated=True, cache_enabled=cache_enabled) - # Single welcome message with menu + # EDIT message to show menu with company if company_name: - welcome_text = ( - f"**Cont conectat cu succes**\n\n" - f"Bun venit, **{username}**!\n\n" - f"{company_name}" - ) + menu_text = f"{company_name}" else: - welcome_text = ( - f"**Cont conectat cu succes**\n\n" - f"Bun venit, **{username}**!\n\n" - f"Selectează o companie pentru a continua" - ) + menu_text = "Selectează o companie pentru a continua" - welcome_message = pad_message_for_wide_buttons(welcome_text) + menu_message = pad_message_for_wide_buttons(menu_text) - await update.message.reply_text( - welcome_message, + await linking_msg.edit_text( + menu_message, reply_markup=keyboard, parse_mode=ParseMode.MARKDOWN ) @@ -117,12 +150,11 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): logger.info(f"User {telegram_user_id} successfully linked to {username}") else: - # Failed linking - await update.message.reply_text( - "**Cod invalid sau expirat**\n\n" + # Failed linking - EDIT message to show error + await linking_msg.edit_text( + "Cod invalid sau expirat\n\n" "Generează un cod nou din aplicația web și trimite:\n" - "`/start CODUL_TAU`\n\n" - "Codul expiră în 15 minute.", + "/start CODUL_TAU", parse_mode=ParseMode.MARKDOWN )