diff --git a/LichessClientTG_bot/bot.py b/LichessClientTG_bot/bot.py index f72832f..c3fd447 100644 --- a/LichessClientTG_bot/bot.py +++ b/LichessClientTG_bot/bot.py @@ -8,14 +8,16 @@ from pathlib import Path from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import ( - Application, CommandHandler, CallbackQueryHandler, - MessageHandler, filters, ContextTypes, ConversationHandler + Application, CommandHandler, CallbackQueryHandler, + MessageHandler, filters, ContextTypes, ConversationHandler, + PicklePersistence ) from config import ( - TELEGRAM_BOT_TOKEN, PERIOD_OPTIONS, POLL_INTERVAL, + TELEGRAM_BOT_TOKEN, PERIOD_OPTIONS, POLL_INTERVAL, POLL_TIMEOUT, DROP_PENDING_UPDATES, ALLOWED_UPDATES, - LICHESS_STATS_API_BASE_URL, ADMINPANEL_TELEGRAM_BOT_TOKEN + LICHESS_STATS_API_BASE_URL, ADMINPANEL_TELEGRAM_BOT_TOKEN, + DATABASE_PATH ) from version import BOT_VERSION from database import Database @@ -1587,18 +1589,32 @@ class LichessBot: # Рассчитываем, когда должен начаться следующий период 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}") + # Используем сохраненное время как начало периода + 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 + # за раз, при большом отставании (недели простоя, много отслеживаемых + # игроков через один общий RequestQueue) чекпоинт никогда не догонит + # текущее время — отставание только растёт. Вместо этого закрываем + # весь пропущенный промежуток одним запросом и сразу прыгаем к "сейчас". + since_time = last_check_time + period_end_approx = now + if period_end_approx - next_period_start > timedelta(minutes=period_minutes): + logger.warning( + f"⏰ {username} is behind schedule by " + f"{(now - next_period_start).total_seconds():.0f}s; " + f"collapsing backlog into one request from {since_time} to {period_end_approx}" + ) + 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}") @@ -1729,8 +1745,13 @@ class LichessBot: if has_games or has_puzzles: logger.info(f"📊 Activity detected for {gamer['username']}, preparing notification...") user_lang = self.db.get_user_language(user_id) + # Label the notification with the actual queried window, not the + # configured period: after a backlog collapse (see above) they can + # differ by days/weeks, and showing "for 15 minutes" on activity + # that's actually from weeks ago is misleading. + actual_span_minutes = max(1, round((period_end_approx - since_time).total_seconds() / 60)) notification = StatsFormatter.format_period_notification( - gamer['username'], games_data, puzzles_data, period_minutes, lang=user_lang + gamer['username'], games_data, puzzles_data, actual_span_minutes, lang=user_lang ) if not self.application: @@ -1840,9 +1861,15 @@ def main(): init_admin_bot() bot = LichessBot() - + + # Persist user_data (e.g. awaiting_addgamer_username) to disk so an in-flight + # /addgamer conversation survives a bot restart instead of silently dropping + # the next message the user sends. + persistence_path = os.path.join(os.path.dirname(DATABASE_PATH), "bot_persistence.pickle") + persistence = PicklePersistence(filepath=persistence_path) + # Create application with Long Polling configuration - application = Application.builder().token(TELEGRAM_BOT_TOKEN).build() + application = Application.builder().token(TELEGRAM_BOT_TOKEN).persistence(persistence).build() # Setup handlers bot.setup_handlers(application) diff --git a/LichessClientTG_bot/formatters.py b/LichessClientTG_bot/formatters.py index 3bf63ca..14b852b 100644 --- a/LichessClientTG_bot/formatters.py +++ b/LichessClientTG_bot/formatters.py @@ -282,14 +282,24 @@ class StatsFormatter: 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) + + # Format period text. period_minutes here is the actual queried span, which + # can be much larger than the configured check interval after a backlog + # collapse (see periodic_check in bot.py) — fall back to hours/days so a + # weeks-old catch-up doesn't get mislabeled as "for 15 minutes". + if period_minutes < 60: + 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) + elif period_minutes < 1440: + hours = round(period_minutes / 60) + period_text = t('period_hours_text', lang, hours=hours) else: - period_text = t('period_minutes_text', lang, period=period_minutes) + days = round(period_minutes / 1440) + period_text = t('period_days_text', lang, days=days) result = t('period_notification_title', lang, username=username, period_text=period_text) diff --git a/LichessClientTG_bot/i18n.py b/LichessClientTG_bot/i18n.py index ef501fc..c0cc1da 100644 --- a/LichessClientTG_bot/i18n.py +++ b/LichessClientTG_bot/i18n.py @@ -113,6 +113,8 @@ TRANSLATIONS = { 'period_1_minute': "for 1 minute", 'period_2_3_4_minutes': "for {period} minutes", 'period_minutes_text': "for {period} minutes", + 'period_hours_text': "for {hours}h", + 'period_days_text': "for {days}d", 'period_notification_title': "📊 Statistics {username} • {period_text}\n\n", 'period_puzzles_section': "🧩 Puzzles: {total} (✅ {solved} - ❌ {failed})\n\n", 'no_activity': "📭 No activity for this period", @@ -254,6 +256,8 @@ TRANSLATIONS = { 'period_1_minute': "за 1 минуту", 'period_2_3_4_minutes': "за {period} минуты", 'period_minutes_text': "за {period} минут", + 'period_hours_text': "за {hours}ч", + 'period_days_text': "за {days}д", 'period_notification_title': "📊 Статистика {username} • {period_text}\n\n", 'period_puzzles_section': "🧩 Пазлы: {total} (✅ {solved} - ❌ {failed})\n\n", 'no_activity': "📭 Нет активности за этот период",