from typing import Dict, Any, Optional from datetime import datetime from i18n import t class StatsFormatter: @staticmethod def _format_rating_change(rating_change: int) -> str: """Format rating change with colored circles""" if rating_change > 0: return f"๐ŸŸข +{rating_change}" elif rating_change < 0: return f"๐Ÿ”ด {rating_change}" else: return "โšช 0" @staticmethod def format_stats_response(data: Dict[str, Any], username: str, period: str, lang: str = 'en') -> str: """Format statistics response according to the template""" if not data or data.get('data') is None: message = data.get('message', t('no_data', lang)) if data else t('no_data', lang) return f"๐Ÿ“ญ {message}" # Extract data from API response api_data = data.get('data', {}) tasks = api_data.get('tasks', {}) games = api_data.get('games', {}) # Format date range date_range = StatsFormatter._get_date_range(period, lang) # Format tasks section task_text = "" if tasks and tasks.get('total', 0) > 0: total_tasks = tasks.get('total', 0) solved = tasks.get('solved', 0) unsolved = tasks.get('unsolved', 0) task_text = t('puzzles_section', lang, total=total_tasks, solved=solved, unsolved=unsolved) # Format games section games_text = "" if games: for game_type, game_data in games.items(): if not game_data or game_data.get('games_played', 0) == 0: continue # Get game type emoji emoji = StatsFormatter._get_game_type_emoji(game_type) games_count = game_data.get('games_played', 0) rating_change = game_data.get('rating_change', 0) rating = game_data.get('final_rating', 0) wins = game_data.get('wins', 0) losses = game_data.get('losses', 0) draws = game_data.get('draws', 0) # Format rating change rating_change_str = StatsFormatter._format_rating_change(rating_change) # Get game type name (capitalize first letter) game_type_name = game_type.title() games_text += t('games_section', lang, emoji=emoji, game_type=game_type_name, games_count=games_count, rating_change=rating_change_str, rating=rating, wins=wins, losses=losses, draws=draws ) # Combine all parts result = t('stats_title', lang, username=username, date_range=date_range) result += task_text result += games_text.rstrip() return result @staticmethod def _get_date_range(period: str, lang: str = 'en') -> str: """Get date range string for the period""" from datetime import datetime, timedelta today = datetime.now() if period == "today": return today.strftime("%d.%m.%Y") elif period == "yesterday": yesterday = today - timedelta(days=1) return yesterday.strftime("%d.%m.%Y") elif period == "week": week_ago = today - timedelta(days=7) return f"{week_ago.strftime('%d.%m.%Y')}โ€“{today.strftime('%d.%m.%Y')}" else: return today.strftime("%d.%m.%Y") @staticmethod def _get_game_type_emoji(game_type: str) -> str: """Get emoji for game type""" emoji_map = { 'bullet': 'โšก๏ธ', 'blitz': '๐Ÿ”ฅ', 'rapid': '๐Ÿ‡', 'classical': 'โ™Ÿ๏ธ', 'correspondence': '๐Ÿ“ฎ' } return emoji_map.get(game_type.lower(), '๐ŸŽฏ') @staticmethod def format_period_notification(username: str, games_data: Optional[Dict], puzzles_data: Optional[Dict], period_minutes: int, lang: str = 'en') -> str: """Format notification for periodic checks""" from datetime import datetime # Format period text if period_minutes == 1: period_text = t('period_1_minute', lang) elif period_minutes in [2, 3, 4]: period_text = t('period_2_3_4_minutes', lang, period=period_minutes) else: period_text = t('period_minutes_text', lang, period=period_minutes) result = t('period_notification_title', lang, username=username, period_text=period_text) # Format puzzles first (if available and there's actual activity) if puzzles_data and puzzles_data.get('data'): puzzles_info = puzzles_data['data'] total_puzzles = puzzles_info.get('total_attempts', 0) solved = puzzles_info.get('solved', 0) failed = puzzles_info.get('failed', 0) # Only show tasks section if there's actual activity (not all zeros) if total_puzzles > 0 or solved > 0 or failed > 0: result += t('period_puzzles_section', lang, total=total_puzzles, solved=solved, failed=failed) # Format games if games_data and games_data.get('data'): games_info = games_data['data'] total_games = games_info.get('total', {}).get('games_played', 0) # Show details for each game type if there were games if total_games > 0: for game_type, game_data in games_info.items(): if game_type != 'total' and game_data and game_data.get('games_played', 0) > 0: emoji = StatsFormatter._get_game_type_emoji(game_type) games_count = game_data.get('games_played', 0) rating_change = game_data.get('rating_change', 0) rating = game_data.get('rating', 0) wins = game_data.get('wins', 0) losses = game_data.get('losses', 0) draws = game_data.get('draws', 0) rating_change_str = StatsFormatter._format_rating_change(rating_change) game_type_name = game_type.title() result += t('period_games_section', lang, emoji=emoji, game_type=game_type_name, games_count=games_count, rating_change=rating_change_str, rating=rating, wins=wins, losses=losses, draws=draws ) # If no activity if not (games_data and games_data.get('data')) and not (puzzles_data and puzzles_data.get('data')): result += t('no_activity', lang) return result.rstrip() @staticmethod def format_last_year_or_1000(data: Dict[str, Any], username: str, lang: str = 'en') -> str: """ Format response for last year or last 1000 games. Expects GamesOfPeriodResponse-like payload. """ if not data: return "๐Ÿ“ญ No data" games_count = data.get('games_count', 0) period_start = data.get('period_start') period_end = data.get('period_end') stats = (data.get('data') or {}) # Title and subheader if games_count >= 1000: header = f"๐Ÿ“ˆ {username}: last 1000 rated games" earliest_ts = data.get('earliest_game_ts') if isinstance(earliest_ts, int): earliest = datetime.fromtimestamp(earliest_ts).strftime("%d.%m.%Y") header += f"\nStart of these 1000 games: {earliest}" else: header = f"๐Ÿ“ˆ {username}: last year (rated), games: {games_count}" # Use earliest actual game date instead of naive 'year ago' earliest_ts = data.get('earliest_game_ts', period_start) if isinstance(earliest_ts, int) and isinstance(period_end, int): start_str = datetime.fromtimestamp(earliest_ts).strftime("%d.%m.%Y") end_str = datetime.fromtimestamp(period_end).strftime("%d.%m.%Y") header += f"\nPeriod: {start_str}โ€“{end_str}" # Body per mode lines = [] for mode in ["bullet", "blitz", "rapid", "classical", "correspondence"]: mode_stats = stats.get(mode) if not mode_stats: continue games_played = mode_stats.get('games_played', 0) if games_played == 0: continue emoji = StatsFormatter._get_game_type_emoji(mode) wins = mode_stats.get('wins', 0) losses = mode_stats.get('losses', 0) draws = mode_stats.get('draws', 0) rating_change = mode_stats.get('rating_change', 0) rating_change_str = StatsFormatter._format_rating_change(rating_change) rating = mode_stats.get('rating') rating_str = rating if rating is not None else "โ€”" lines.append( f"{emoji} {mode.title()}: {games_played} ฮ” {rating_change_str} R {rating_str} โœ… {wins} โŒ {losses} ๐Ÿค {draws}" ) # Total # Do not print 'ะ˜ั‚ะพะณะพ' line per new requirement body = "\n".join(lines) return f"{header}\n{body}".rstrip()