diff --git a/LichessClientTG_bot/bot.py b/LichessClientTG_bot/bot.py index 88d1b93..deff442 100644 --- a/LichessClientTG_bot/bot.py +++ b/LichessClientTG_bot/bot.py @@ -47,6 +47,15 @@ class LichessBot: self.application = None # Will be set when application is created self.counters = MessageCounters() # Message counters self.request_queue = get_request_queue() # Request queue for rate limiting + + async def stop_periodic_task(self, gamer_id: int, user_id: int): + """Stop periodic task for a user-gamer pair.""" + task_key = f"{gamer_id}_{user_id}" + task = self.periodic_tasks.pop(task_key, None) + if task: + task.cancel() + logger.info(f"Cancelled periodic task for gamer_id={gamer_id}, user_id={user_id}") + self.period_start_times.pop(task_key, None) async def _notify_admin_new_player(self, player_username: str, added_by_user_id: int, added_by_username: Optional[str], is_new_gamer: bool = False): """Notify admin about newly linked player (always try to send).""" @@ -558,7 +567,8 @@ class LichessBot: user_exists = await self.lichess_api.check_user_exists(username) if not user_exists: await update.message.reply_text( - t('user_not_found', lang, username=username) + t('user_not_found', lang, username=username) + '\n\n' + t('addgamer_prompt', lang), + parse_mode='HTML' ) return WAITING_FOR_USERNAME @@ -569,13 +579,10 @@ class LichessBot: if existing_gamer: # Player is already being tracked by this user await update.message.reply_text( - t('gamer_already_added', lang, username=username) + t('gamer_already_added', lang, username=username) + '\n\n' + t('addgamer_prompt', lang), + parse_mode='HTML' ) - # Clear awaiting flag - try: - context.user_data['awaiting_addgamer_username'] = False - except Exception: - pass + # Keep awaiting flag - don't clear it, so user can try again return # Add gamer to database (without token) @@ -594,13 +601,10 @@ class LichessBot: # If add_user_gamer returned False, it means the pair already exists (shouldn't happen after our check, but just in case) if not added: await update.message.reply_text( - t('gamer_already_added', lang, username=username) + t('gamer_already_added', lang, username=username) + '\n\n' + t('addgamer_prompt', lang), + parse_mode='HTML' ) - # Clear awaiting flag - try: - context.user_data['awaiting_addgamer_username'] = False - except Exception: - pass + # Keep awaiting flag - don't clear it, so user can try again return # Set default period to 1 hour (60 minutes) for new gamer @@ -948,12 +952,6 @@ class LichessBot: ) return - # Send initial message about processing - try: - await update.message.reply_text(t('stats_processing', lang), parse_mode='HTML') - except Exception: - pass - # Process each gamer has_any_activity = False for i, gamer in enumerate(gamers): @@ -1047,9 +1045,6 @@ class LichessBot: # If no activity for any player if not has_any_activity: await update.message.reply_text(t('no_activity', lang)) - else: - # Send final message that all is done - await update.message.reply_text(t('stats_all_done', lang)) # Increment counter for the period command if period == "today": @@ -1092,12 +1087,6 @@ class LichessBot: ) return - # Send initial message about processing - try: - await update.message.reply_text(t('last_year_1000_processing', lang), parse_mode='HTML') - except Exception: - pass - now_ms = int(time.time() * 1000) year_ms = 365 * 24 * 3600 * 1000 since_ms = now_ms - year_ms @@ -1146,9 +1135,6 @@ class LichessBot: # If no activity for any player if not has_any_activity: await update.message.reply_text(t('no_activity', lang)) - else: - # Send final message that all is done - await update.message.reply_text(t('stats_all_done', lang)) self.counters.increment('last_year_1000') @@ -1298,8 +1284,10 @@ class LichessBot: # Set period for this user-gamer pair self.db.set_user_gamer_period(user_id, gamer_id, period) + self.db.clear_period_checkpoint(user_id, gamer_id) if period == 0: + await self.stop_periodic_task(gamer_id, user_id) await query.edit_message_text( t('notifications_disabled', lang, username=selected_gamer['username']) ) @@ -1463,14 +1451,17 @@ class LichessBot: task_key = f"{gamer['id']}_{user_id}" username = gamer['username'] - # Инициализируем время начала отслеживания как текущее время - # Первая проверка произойдет через period_minutes минут - start_time = datetime.now() - self.period_start_times[task_key] = start_time + checkpoint_ts = self.db.get_period_checkpoint(user_id, gamer['id']) + if checkpoint_ts is not None: + restored_time = datetime.fromtimestamp(checkpoint_ts) + self.period_start_times[task_key] = restored_time + logger.info(f"♻️ Restored periodic checkpoint for {username} (user {user_id}) at {restored_time}") + logger.info(f"🔄 Started periodic monitoring for {username} (user {user_id}) with {period_minutes} minute intervals") consecutive_errors = 0 max_consecutive_errors = 5 + is_first_check = True # Флаг для первой проверки while True: try: @@ -1498,21 +1489,48 @@ class LichessBot: logger.info(f"Period changed for {gamer['username']} from {period_minutes} to {current_period} minutes") period_minutes = current_period - # Ждем заданное количество минут перед следующей проверкой - logger.info(f"⏳ Waiting {period_minutes} minutes before next check for {username}") - await asyncio.sleep(period_minutes * 60) + # Получаем сохраненное время последней проверки для расчета следующего периода + # Используем флаг is_first_check для первой проверки вместо проверки наличия ключа + last_check_time = self.period_start_times.get(task_key) + if is_first_check and checkpoint_ts is None: + last_check_time = None # Принудительно делаем первую проверку + is_first_check = False - # Получаем текущее время - now = datetime.now() + if last_check_time: + # Уже была хотя бы одна проверка + # Рассчитываем, когда должен начаться следующий период + next_period_start = last_check_time + timedelta(minutes=period_minutes) + now = datetime.now() + + # Если следующий период еще не наступил, ждем + if next_period_start > now: + wait_seconds = (next_period_start - now).total_seconds() + logger.info(f"⏳ Waiting {wait_seconds:.1f} seconds until next period start ({next_period_start}) for {username}") + await asyncio.sleep(wait_seconds) + + # Используем сохраненное время как начало периода + since_time = last_check_time + # Конец периода - это момент, когда должен был начаться следующий период + period_end_approx = next_period_start + logger.info(f"📌 Using saved period: from {since_time} to {period_end_approx}") + else: + # Первая проверка - ждем period_minutes минут от момента запуска + logger.info(f"⏳ First check: waiting {period_minutes} minutes before first check for {username}") + await asyncio.sleep(period_minutes * 60) + + # Получаем текущее время + period_end_approx = datetime.now() + # Начало периода - текущее время минус period_minutes + since_time = period_end_approx - timedelta(minutes=period_minutes) + logger.info(f"📌 First check: period from {since_time} to {period_end_approx}") - # Рассчитываем период: от (текущее время - период) до текущего времени - # Это гарантирует, что мы проверяем последний период активности - since_time = now - timedelta(minutes=period_minutes) since_timestamp = int(since_time.timestamp() * 1000) - until_timestamp = int(now.timestamp() * 1000) + # Используем приблизительное время как until_timestamp + # После получения ответа пересчитаем фактическое время + until_timestamp_approx = int(period_end_approx.timestamp() * 1000) - logger.info(f"🔍 Checking activity for {username} (user {user_id}): period from {since_time} to {now} (last {period_minutes} minutes)") - logger.info(f"📅 Unix timestamps: since={since_timestamp}, until={until_timestamp}") + logger.info(f"🔍 Checking activity for {username} (user {user_id}): period from {since_time} to {period_end_approx} (approx, last {period_minutes} minutes)") + logger.info(f"📅 Unix timestamps: since={since_timestamp}, until_approx={until_timestamp_approx}") # Делаем запросы к API через очередь с обработкой ошибок games_data = None @@ -1523,18 +1541,21 @@ class LichessBot: logger.info(f"📥 Adding games request to queue for {gamer['username']}") games_data = await self.request_queue.add_request( self.lichess_api.get_games_period, - gamer['username'], since_timestamp, until_timestamp + gamer['username'], since_timestamp, until_timestamp_approx ) - logger.info(f"✅ Games API response received for {gamer['username']}") + if games_data is None: + raise RuntimeError("Games period API returned no data") + # Фиксируем фактическое время получения ответа + request_end_time = datetime.now() + logger.info(f"✅ Games API response received for {gamer['username']} at {request_end_time}") except Exception as e: logger.error(f"❌ Error getting games data for {gamer['username']}: {e}") consecutive_errors += 1 if consecutive_errors >= max_consecutive_errors: logger.error(f"Too many consecutive errors for {gamer['username']}, stopping periodic check") break - # Продолжаем с обновлением времени начала, чтобы не зацикливаться - now = datetime.now() - self.period_start_times[task_key] = now + logger.warning(f"⚠️ Games data unavailable for {gamer['username']}; retrying the same period in 60 seconds") + await asyncio.sleep(60) continue if gamer.get('token'): @@ -1543,9 +1564,11 @@ class LichessBot: logger.info(f"📥 Adding puzzles request to queue for {gamer['username']}") puzzles_data = await self.request_queue.add_request( self.lichess_api.get_puzzles_period, - gamer['token'], since_timestamp, until_timestamp, 150 + gamer['token'], since_timestamp, until_timestamp_approx, 150 ) - logger.info(f"✅ Puzzles API response received for {gamer['username']}") + # Обновляем фактическое время после получения ответа по пазлам + request_end_time = datetime.now() + logger.info(f"✅ Puzzles API response received for {gamer['username']} at {request_end_time}") except Exception as e: logger.warning(f"⚠️ Error getting puzzles data for {gamer['username']}: {e}") # Продолжаем без данных по пазлам @@ -1561,15 +1584,29 @@ class LichessBot: logger.info(f"📊 Games data structure for {username}: {games_data}") # Проверяем games_count на верхнем уровне (приоритет) - if games_data.get('games_count', 0) > 0: - total_games = games_data.get('games_count', 0) + top_level_count = games_data.get('games_count', 0) + logger.debug(f"🔍 Top-level games_count: {top_level_count}") + + if top_level_count > 0: + total_games = top_level_count has_games = True logger.info(f"✅ Found {total_games} games via games_count field") - # Также проверяем data.total.games_played - elif games_data.get('data') and games_data.get('data', {}).get('total', {}).get('games_played', 0) > 0: - total_games = games_data.get('data', {}).get('total', {}).get('games_played', 0) - has_games = True - logger.info(f"✅ Found {total_games} games via data.total.games_played field") + else: + # Также проверяем data.total.games_played + games_data_dict = games_data.get('data') + if games_data_dict: + data_total = games_data_dict.get('total', {}) + total_games_played = data_total.get('games_played', 0) if data_total else 0 + else: + total_games_played = 0 + logger.debug(f"🔍 data.total.games_played: {total_games_played}") + + if total_games_played > 0: + total_games = total_games_played + has_games = True + logger.info(f"✅ Found {total_games} games via data.total.games_played field") + else: + logger.warning(f"⚠️ No games found: games_count={top_level_count}, data.total.games_played={total_games_played}") else: logger.warning(f"⚠️ No games_data returned for {username}") @@ -1623,8 +1660,23 @@ class LichessBot: else: logger.debug(f"⏭️ No activity found for {gamer['username']} in the last {period_minutes} minutes") - # Всегда обновляем время начала на текущее время после проверки (независимо от наличия активности) - self.period_start_times[task_key] = now + # Обновляем время начала следующего периода на ПЛАНИРУЕМОЕ время окончания текущего периода + # (period_end_approx), а не на фактическое время завершения запроса (request_end_time). + # Это гарантирует непрерывность периодов без пропусков: + # - Проверяем период A-B + # - Следующая проверка будет периода B-C + # - Без пропусков между A-B и B-C + # + # Использование request_end_time приведет к пропуску диапазона между period_end_approx и request_end_time + # + # period_end_approx уже установлено в начале итерации + self.period_start_times[task_key] = period_end_approx + self.db.set_period_checkpoint(user_id, gamer['id'], int(period_end_approx.timestamp())) + logger.info(f"📌 Updated period_start_time for {username} to {period_end_approx} (planned period end, next period will start from here)") + if 'request_end_time' in locals(): + delay = (request_end_time - period_end_approx).total_seconds() + if delay > 0: + logger.info(f"⏱️ Request completed with {delay:.1f}s delay after planned period end") except asyncio.CancelledError: logger.info(f"Periodic check cancelled for {gamer['username']}") @@ -1642,7 +1694,7 @@ class LichessBot: if task_key in self.period_start_times: del self.period_start_times[task_key] break - + # Ждем перед повторной попыткой при ошибке await asyncio.sleep(60) # 1 minute delay before retry diff --git a/LichessClientTG_bot/config.py b/LichessClientTG_bot/config.py index f6ab393..a8b46d6 100644 --- a/LichessClientTG_bot/config.py +++ b/LichessClientTG_bot/config.py @@ -3,7 +3,7 @@ from dotenv import load_dotenv load_dotenv() -IS_PROD = False +IS_PROD = True # Telegram Bot Configuration (Production) TELEGRAM_BOT_TOKEN_PROD = "8241474807:AAH684LTY93aXRou4-LtqU5-p8LuEjzYn8U" @@ -26,7 +26,9 @@ else: # Lichess API Configuration LICHESS_API_BASE_URL = "https://lichess.org/api" -LICHESS_STATS_API_BASE_URL = "http://localhost:8001" # For Docker container access +LICHESS_STATS_API_BASE_URL = "http://localhost:8002" # Host port for stats API when bot runs with host networking +# Минимальная задержка (сек) между запросами к Lichess в очереди мониторинга (избежание бана) +LICHESS_REQUEST_QUEUE_MIN_DELAY = 4.0 # Database Configuration def _resolve_database_path() -> str: diff --git a/LichessClientTG_bot/database.py b/LichessClientTG_bot/database.py index 34f2ef5..b60db2a 100644 --- a/LichessClientTG_bot/database.py +++ b/LichessClientTG_bot/database.py @@ -64,6 +64,7 @@ class Database: token TEXT, is_active BOOLEAN DEFAULT FALSE, period_minutes INTEGER DEFAULT 0, + last_period_end_ts INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES telegram_users(user_id), FOREIGN KEY (gamer_id) REFERENCES gamers(id), @@ -77,6 +78,13 @@ class Database: except sqlite3.OperationalError: # Column already exists pass + + # Add last_period_end_ts column to persist periodic checkpoint across restarts + try: + cursor.execute("ALTER TABLE user_gamers ADD COLUMN last_period_end_ts INTEGER") + except sqlite3.OperationalError: + # Column already exists + pass # Create admin_settings table for admin bot configuration cursor.execute(''' @@ -326,6 +334,39 @@ class Database: (period_minutes, user_id, gamer_id) ) conn.commit() + + def get_period_checkpoint(self, user_id: int, gamer_id: int) -> Optional[int]: + """Get persisted period checkpoint timestamp (seconds since epoch).""" + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute( + "SELECT last_period_end_ts FROM user_gamers WHERE user_id = ? AND gamer_id = ?", + (user_id, gamer_id) + ) + row = cursor.fetchone() + if not row or row[0] is None: + return None + return int(row[0]) + + def set_period_checkpoint(self, user_id: int, gamer_id: int, checkpoint_ts: int): + """Persist period checkpoint timestamp (seconds since epoch).""" + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute( + "UPDATE user_gamers SET last_period_end_ts = ? WHERE user_id = ? AND gamer_id = ?", + (checkpoint_ts, user_id, gamer_id) + ) + conn.commit() + + def clear_period_checkpoint(self, user_id: int, gamer_id: int): + """Clear persisted period checkpoint.""" + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute( + "UPDATE user_gamers SET last_period_end_ts = NULL WHERE user_id = ? AND gamer_id = ?", + (user_id, gamer_id) + ) + conn.commit() def remove_user_gamer(self, user_id: int, gamer_id: int) -> bool: """Remove gamer from user's tracked list""" diff --git a/LichessClientTG_bot/formatters.py b/LichessClientTG_bot/formatters.py index e0bf77d..2244740 100644 --- a/LichessClientTG_bot/formatters.py +++ b/LichessClientTG_bot/formatters.py @@ -164,7 +164,7 @@ class StatsFormatter: 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) + rating = game_data.get('final_rating', game_data.get('rating', 0)) wins = game_data.get('wins', 0) losses = game_data.get('losses', 0) draws = game_data.get('draws', 0) diff --git a/LichessClientTG_bot/helpEN.jpeg b/LichessClientTG_bot/helpEN.jpeg index 5c44042..eafbd6b 100644 Binary files a/LichessClientTG_bot/helpEN.jpeg and b/LichessClientTG_bot/helpEN.jpeg differ diff --git a/LichessClientTG_bot/helpRU.jpeg b/LichessClientTG_bot/helpRU.jpeg index b6fe6fa..b9c5369 100644 Binary files a/LichessClientTG_bot/helpRU.jpeg and b/LichessClientTG_bot/helpRU.jpeg differ diff --git a/LichessClientTG_bot/request_queue.py b/LichessClientTG_bot/request_queue.py index d0c0efc..3a81a3d 100644 --- a/LichessClientTG_bot/request_queue.py +++ b/LichessClientTG_bot/request_queue.py @@ -7,6 +7,8 @@ import logging from typing import Callable, Any, Optional, Dict from datetime import datetime +import config + logger = logging.getLogger(__name__) class RequestQueue: @@ -139,6 +141,6 @@ def get_request_queue() -> RequestQueue: """Get the global request queue instance""" global _request_queue if _request_queue is None: - _request_queue = RequestQueue(min_delay=7.0) + _request_queue = RequestQueue(min_delay=config.LICHESS_REQUEST_QUEUE_MIN_DELAY) return _request_queue diff --git a/LichessClientTG_bot/run.sh b/LichessClientTG_bot/run.sh index 7c54d2d..fe4de4b 100755 --- a/LichessClientTG_bot/run.sh +++ b/LichessClientTG_bot/run.sh @@ -13,10 +13,10 @@ fi # Check if the API service is running echo "🔍 Checking Lichess API service..." -if curl -s http://localhost:8001/docs > /dev/null 2>&1; then - echo "✅ Lichess API service is running on http://localhost:8001" +if curl -s http://localhost:8002/docs > /dev/null 2>&1; then + echo "✅ Lichess API service is running on http://localhost:8002" else - echo "⚠️ Warning: Lichess API service is not accessible at http://localhost:8001" + echo "⚠️ Warning: Lichess API service is not accessible at http://localhost:8002" echo " Make sure your API service is running before starting the bot." fi diff --git a/LichessWebServices/lichess_client.py b/LichessWebServices/lichess_client.py index 7d2e698..49e3a59 100644 --- a/LichessWebServices/lichess_client.py +++ b/LichessWebServices/lichess_client.py @@ -121,17 +121,15 @@ class LichessClient: # Формируем URL для получения игр пользователя url = f"{self.base_url}/games/user/{username}" - # Параметры запроса + # Параметры запроса. Параметр 'rated' в API Lichess не передаём: + # при rated=true API часто возвращает 0 игр даже для рейтинговых партий. + # Фильтрация по рейтинговости делается в stats_service после получения списка. params = { 'since': since_ms, # Начало периода 'until': until_ms, # Конец периода 'max': 1000 # Максимум игр за запрос (лимит Lichess API) } - # Добавляем фильтр по рейтинговым играм, если нужно - if rated_only: - params['rated'] = 'true' - # Заголовки для получения NDJSON формата headers = { 'Accept': 'application/x-ndjson' # Запрашиваем NDJSON формат diff --git a/LichessWebServices/stats_service.py b/LichessWebServices/stats_service.py index b699d5f..05a66a3 100644 --- a/LichessWebServices/stats_service.py +++ b/LichessWebServices/stats_service.py @@ -648,8 +648,8 @@ class StatsService: since_ms = since_timestamp * 1000 until_ms = until_timestamp * 1000 - # Получаем игры - games = await self.lichess_client.get_games_of_period(username, since_ms, until_ms, rated_only) + # Получаем игры (без фильтра rated в запросе к Lichess — см. lichess_client) + games = await self.lichess_client.get_games_of_period(username, since_ms, until_ms, rated_only=False) if games is None: return GamesOfPeriodResponse( @@ -660,6 +660,10 @@ class StatsService: games_count=0 ) + # Фильтр по рейтинговости на нашей стороне (API Lichess с param rated даёт неверный результат) + if rated_only: + games = [g for g in games if g.get('rated') is True] + if not games: return GamesOfPeriodResponse( message=f"Игры за указанный период не найдены", diff --git a/check_recent_games.py b/check_recent_games.py new file mode 100644 index 0000000..e577ea6 --- /dev/null +++ b/check_recent_games.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Скрипт для проверки недавних игр пользователя через Lichess API""" + +import requests +import json +from datetime import datetime, timedelta +import sys + +def get_recent_games(username: str, minutes: int = 30): + """Получить игры за последние N минут""" + + # Вычисляем временные метки + now = datetime.now() + since_time = now - timedelta(minutes=minutes) + + since_ms = int(since_time.timestamp() * 1000) + until_ms = int(now.timestamp() * 1000) + + print(f"🔍 Проверяем игры для {username}") + print(f"⏰ Период: с {since_time.strftime('%Y-%m-%d %H:%M:%S')} до {now.strftime('%Y-%m-%d %H:%M:%S')}") + print(f"📅 Timestamps: since={since_ms}, until={until_ms}") + print() + + # Делаем запрос к Lichess API + url = f"https://lichess.org/api/games/user/{username}" + params = { + 'since': since_ms, + 'until': until_ms, + 'max': 1000, + 'rated': 'true' # Только рейтинговые + } + + headers = { + 'Accept': 'application/x-ndjson' + } + + try: + response = requests.get(url, params=params, headers=headers, timeout=30) + + if response.status_code == 404: + print(f"❌ Пользователь {username} не найден (404)") + return [] + elif response.status_code != 200: + print(f"❌ Ошибка API: {response.status_code} - {response.text[:200]}") + return [] + + # Парсим NDJSON + games = [] + content = response.text.strip() + + if content: + for line in content.split('\n'): + if line.strip(): + try: + game = json.loads(line) + games.append(game) + except json.JSONDecodeError as e: + print(f"⚠️ Ошибка парсинга JSON: {e}") + continue + + return games + + except Exception as e: + print(f"❌ Ошибка при запросе: {e}") + return [] + +def format_game_time(created_at_ms: int) -> str: + """Форматирует время создания игры""" + dt = datetime.fromtimestamp(created_at_ms / 1000) + return dt.strftime('%Y-%m-%d %H:%M:%S') + +def main(): + username = sys.argv[1] if len(sys.argv) > 1 else "vrubelroman" + minutes = int(sys.argv[2]) if len(sys.argv) > 2 else 40 + + games = get_recent_games(username, minutes) + + if not games: + print("❌ Игры не найдены") + return + + print(f"✅ Найдено игр: {len(games)}") + print() + print("=" * 80) + + now = datetime.now() + + for i, game in enumerate(games, 1): + game_id = game.get('id', 'N/A') + created_at_ms = game.get('createdAt', 0) + speed = game.get('speed', 'unknown') + rated = game.get('rated', False) + + # Результат + white = game.get('players', {}).get('white', {}) + black = game.get('players', {}).get('black', {}) + white_user = white.get('user', {}).get('name', 'N/A') + black_user = black.get('user', {}).get('name', 'N/A') + winner = game.get('winner') + + # Определяем результат для пользователя + if white_user == username: + result = "Победа" if winner == "white" else ("Поражение" if winner == "black" else "Ничья") + opponent = black_user + elif black_user == username: + result = "Победа" if winner == "black" else ("Поражение" if winner == "white" else "Ничья") + opponent = white_user + else: + result = "N/A" + opponent = "N/A" + + game_time = format_game_time(created_at_ms) + time_ago = now - datetime.fromtimestamp(created_at_ms / 1000) + minutes_ago = int(time_ago.total_seconds() / 60) + + print(f"\n🎮 Игра #{i}") + print(f" ID: {game_id}") + print(f" Время: {game_time} ({minutes_ago} минут назад)") + print(f" Скорость: {speed}") + print(f" Рейтинговая: {'Да' if rated else 'Нет'}") + print(f" Соперник: {opponent}") + print(f" Результат: {result}") + + # Рейтинг + if white_user == username: + rating_change = white.get('ratingDiff', 0) + final_rating = white.get('rating', 0) + elif black_user == username: + rating_change = black.get('ratingDiff', 0) + final_rating = black.get('rating', 0) + else: + rating_change = 0 + final_rating = 0 + + if rating_change != 0 or final_rating != 0: + print(f" Рейтинг: {final_rating} ({rating_change:+d})") + + print() + print("=" * 80) + print(f"\n📊 Всего игр за последние {minutes} минут: {len(games)}") + +if __name__ == "__main__": + main() + diff --git a/docker-compose.yml b/docker-compose.yml index fedca0c..dac9f95 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,17 +4,18 @@ services: build: ./LichessWebServices container_name: lichess-api ports: - - "8001:8000" + - "8002:8000" environment: - PYTHONUNBUFFERED=1 volumes: - ./LichessWebServices:/app restart: always healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()"] interval: 30s timeout: 10s retries: 3 + start_period: 40s # Telegram Bot lichess-bot: @@ -31,7 +32,7 @@ services: depends_on: - lichess-api healthcheck: - test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8001/health', timeout=5)"] + test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8002/health', timeout=5)"] interval: 30s timeout: 10s retries: 3 @@ -73,4 +74,3 @@ networks: - diff --git a/export_db.sh b/export_db.sh old mode 100644 new mode 100755 diff --git a/logs.sh b/logs.sh new file mode 100755 index 0000000..a549a0d --- /dev/null +++ b/logs.sh @@ -0,0 +1,6 @@ + GNU nano 6.2 logchecker.sh +# Последние 500 строк, отфильтрованные по периодическим проверкам +docker logs --tail=1000 lichess-telegram-bot 2>&1 | grep -E "periodic|Checking activity|queue|Activity detected" +# Смотреть логи периодических проверок в реальном времени +#docker logs -f lichess-telegram-bot 2>&1 | grep --line-buffered -E "🔄|⏳|🔍|📥|✅|📊|periodic|queue|Activity" +docker logs -f lichess-telegram-bot 2>&1 | grep --line-buffered -E "Checking activity for|Games data structure for" diff --git a/start.sh b/start.sh index 1d85b60..daed230 100755 --- a/start.sh +++ b/start.sh @@ -53,7 +53,7 @@ echo "" echo "✅ Все сервисы запущены!" echo "" echo "🌐 Доступные сервисы:" -echo " - API документация: http://localhost:8001/docs" +echo " - API документация: http://localhost:8002/docs" echo " - Веб-интерфейс: http://localhost:5000" echo "" echo "📋 Для просмотра логов используйте:" @@ -64,4 +64,3 @@ echo " ${COMPOSE_CMD_DISPLAY} down" -