- Объединены три проекта в один репозиторий - LichessWebServices - REST API для статистики - LichessClientTG_bot - Telegram бот с поддержкой множества пользователей - LichessWebView - Веб-интерфейс для просмотра пользователей и игроков - Добавлен общий docker-compose.yml для запуска всех сервисов - Добавлен скрипт start.sh для удобного запуска - Добавлен README с полным описанием проекта
155 lines
6.7 KiB
Python
155 lines
6.7 KiB
Python
from typing import Dict, Any, Optional
|
||
from datetime import datetime
|
||
|
||
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 f"0"
|
||
|
||
@staticmethod
|
||
def format_stats_response(data: Dict[str, Any], username: str, period: str) -> str:
|
||
"""Format statistics response according to the template"""
|
||
if not data or data.get('data') is None:
|
||
message = data.get('message', 'Нет данных') if data else 'Нет данных'
|
||
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)
|
||
|
||
# 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 = f"🧩 Задачи: {total_tasks} (✅ {solved} - ❌ {unsolved})\n\n"
|
||
|
||
# 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)
|
||
|
||
games_text += f"{emoji} {game_type.title()} — {games_count} игр • {rating_change_str}\n"
|
||
games_text += f"Рейтинг: {rating}\n"
|
||
games_text += f"✅ Победы: {wins}\n"
|
||
games_text += f"❌ Поражения: {losses}\n"
|
||
games_text += f"🤝 Ничьи: {draws}\n\n"
|
||
|
||
# Combine all parts
|
||
result = f"📊 Статистика {username} • {date_range}\n\n"
|
||
result += task_text
|
||
result += games_text.rstrip()
|
||
|
||
return result
|
||
|
||
@staticmethod
|
||
def _get_date_range(period: str) -> 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) -> str:
|
||
"""Format notification for periodic checks"""
|
||
from datetime import datetime
|
||
|
||
# Format period text
|
||
if period_minutes == 1:
|
||
period_text = "за 1 минуту"
|
||
elif period_minutes in [2, 3, 4]:
|
||
period_text = f"за {period_minutes} минуты"
|
||
else:
|
||
period_text = f"за {period_minutes} минут"
|
||
|
||
result = f"📊 Статистика {username} • {period_text}\n\n"
|
||
|
||
# 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 += f"🧩 Задачи: {total_puzzles} (✅ {solved} - ❌ {failed})\n\n"
|
||
|
||
# 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)
|
||
result += f"{emoji} {game_type.title()} — {games_count} игр • {rating_change_str}\n"
|
||
result += f"Рейтинг: {rating}\n"
|
||
result += f"✅ Победы: {wins}\n"
|
||
result += f"❌ Поражения: {losses}\n"
|
||
result += f"🤝 Ничьи: {draws}\n\n"
|
||
|
||
# If no activity
|
||
if not (games_data and games_data.get('data')) and not (puzzles_data and puzzles_data.get('data')):
|
||
result += "📭 Нет активности за этот период"
|
||
|
||
return result.rstrip()
|