Files
tg_loader/bot/utils/user_info_updater.py
2025-12-04 00:12:56 +03:00

162 lines
5.7 KiB
Python

"""
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)