""" Utilities for updating user information from Telegram API """ import asyncio import logging from typing import Optional, Dict from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from shared.database.session import get_async_session_local from shared.database.models import User from bot.utils.telegram_user import get_user_info logger = logging.getLogger(__name__) # Constants for time intervals SECONDS_PER_HOUR = 3600 SECONDS_PER_MINUTE = 60 TELEGRAM_API_DELAY = 0.5 # Delay between Telegram API requests in seconds USER_INFO_UPDATE_INTERVAL_HOURS = 24 # Interval for updating user information ERROR_RETRY_DELAY_SECONDS = 60 # Delay before retry on error def _update_user_fields(user: User, user_info: Dict[str, Optional[str]]) -> bool: """ Update user fields from Telegram API information. Updates username, first_name, and last_name if they are missing and available in user_info. Args: user: User object from database user_info: Dictionary with user information from Telegram API Returns: True if any fields were updated, False otherwise """ updated = False if not user.username and user_info.get("username"): user.username = user_info.get("username") updated = True if not user.first_name and user_info.get("first_name"): user.first_name = user_info.get("first_name") updated = True if not user.last_name and user_info.get("last_name"): user.last_name = user_info.get("last_name") updated = True return updated async def update_user_info_from_telegram( user_id: int, db_session: Optional[AsyncSession] = None ) -> bool: """ Update user information from Telegram API. Fetches user information from Telegram API and updates the database with missing fields (username, first_name, last_name). Args: user_id: Telegram user ID db_session: Database session (if None, creates a new one) Returns: True if information was updated, False otherwise """ try: # Get user information from Telegram API user_info = await get_user_info(user_id) if not user_info: return False # Update information in database if db_session: # Use provided session user = await db_session.get(User, user_id) if user: if _update_user_fields(user, user_info): await db_session.commit() logger.info(f"User {user_id} information updated from Telegram API") return True else: # Create new session async with get_async_session_local()() as session: user = await session.get(User, user_id) if user: if _update_user_fields(user, user_info): await session.commit() logger.info(f"User {user_id} information updated from Telegram API") return True return False except Exception as e: logger.error(f"Error updating user {user_id} information: {e}", exc_info=True) return False async def update_users_without_info_periodically( interval_hours: int = USER_INFO_UPDATE_INTERVAL_HOURS ) -> None: """ Periodically update information for users without username or first_name. Runs in an infinite loop, updating user information at specified intervals. Can be cancelled with asyncio.CancelledError. Args: interval_hours: Interval between updates in hours (default: 24 hours) """ logger.info("Background task for updating user information started") while True: try: await asyncio.sleep(interval_hours * SECONDS_PER_HOUR) logger.info("Starting update of user information for users without username or first_name") async with get_async_session_local()() as session: # Get users without username or first_name result = await session.execute( select(User).where( (User.username == None) | (User.first_name == None) ) ) users = result.scalars().all() updated_count = 0 error_count = 0 for user in users: try: # Update user information if await update_user_info_from_telegram(user.user_id, db_session=session): updated_count += 1 # Delay between requests to avoid overloading Telegram API await asyncio.sleep(TELEGRAM_API_DELAY) except Exception as e: error_count += 1 logger.warning(f"Error updating user {user.user_id}: {e}") if updated_count > 0 or error_count > 0: logger.info( f"User information update completed: " f"updated {updated_count}, errors {error_count}, total checked {len(users)}" ) else: logger.debug("No users found for update") except asyncio.CancelledError: logger.info("User information update task stopped") break except Exception as e: logger.error(f"Error in user information update task: {e}", exc_info=True) # Continue working even on error await asyncio.sleep(ERROR_RETRY_DELAY_SECONDS)