# -*- coding: utf-8 -*- """ Utility functions and decorators for the bot. """ import os import functools import logging from telegram import Update from telegram.ext import ContextTypes logger = logging.getLogger(__name__) def create_progress_bar(current: int, maximum: int, length: int = 10, filled_char: str = "█", empty_char: str = "░") -> str: """ Create a visual progress bar. Args: current: Current value maximum: Maximum value length: Length of the bar in characters (default 10) filled_char: Character for filled portion (default █) empty_char: Character for empty portion (default ░) Returns: String representation of progress bar Examples: >>> create_progress_bar(75, 100) "███████░░░" >>> create_progress_bar(0, 100) "░░░░░░░░░░" >>> create_progress_bar(100, 100) "██████████" """ if maximum <= 0: return empty_char * length percentage = min(1.0, max(0.0, current / maximum)) filled_length = int(length * percentage) empty_length = length - filled_length return filled_char * filled_length + empty_char * empty_length def format_stat_bar(label: str, emoji: str, current: int, maximum: int, bar_length: int = 10, label_width: int = 7) -> str: """ Format a stat (HP, Stamina, etc.) with visual progress bar. Uses right-aligned label format to avoid alignment issues with Telegram's proportional font. Args: label: Stat label (e.g., "HP", "Stamina", "Your HP") emoji: Emoji to display (e.g., "❤️", "⚡", "🐕") current: Current value maximum: Maximum value bar_length: Length of the progress bar label_width: Not used, kept for backwards compatibility Returns: Formatted string with bar on left, label on right Examples: >>> format_stat_bar("HP", "❤️", 75, 100) "███████░░░ 75% (75/100) ❤️ HP" >>> format_stat_bar("Stamina", "⚡", 50, 100) "█████░░░░░ 50% (50/100) ⚡ Stamina" """ bar = create_progress_bar(current, maximum, bar_length) percentage = int((current / maximum * 100)) if maximum > 0 else 0 # Right-aligned format: bar first, then stats, then emoji + label # This way bars are always left-aligned regardless of label length if emoji: return f"{bar} {percentage}% ({current}/{maximum}) {emoji} {label}" else: # If no emoji provided, just use label return f"{bar} {percentage}% ({current}/{maximum}) {label}" def get_admin_ids(): """Get the list of admin user IDs from environment variable.""" admin_ids_str = os.getenv("ADMIN_IDS", "") if not admin_ids_str: logger.warning("ADMIN_IDS not set in .env file. No admins configured.") return set() try: # Parse comma-separated list of IDs admin_ids = set(int(id.strip()) for id in admin_ids_str.split(",") if id.strip()) return admin_ids except ValueError as e: logger.error(f"Error parsing ADMIN_IDS: {e}") return set() def admin_only(func): """ Decorator that restricts command to admin users only. Usage: @admin_only async def my_admin_command(update: Update, context: ContextTypes.DEFAULT_TYPE): ... """ @functools.wraps(func) async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs): user_id = update.effective_user.id admin_ids = get_admin_ids() if user_id not in admin_ids: await update.message.reply_html( "🚫 Access Denied\n\n" "This command is restricted to administrators only." ) logger.warning(f"User {user_id} attempted to use admin command: {func.__name__}") return # User is admin, execute the command return await func(update, context, *args, **kwargs) return wrapper def is_admin(user_id: int) -> bool: """Check if a user ID is an admin.""" admin_ids = get_admin_ids() return user_id in admin_ids