fix silent addgamer drop on restart and periodic-check backlog buildup
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 13s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 13s
Persist user_data (PicklePersistence) so an in-flight /addgamer username prompt survives a bot restart instead of being silently swallowed by handle_username when the in-memory awaiting flag is gone. Collapse periodic-check backlog into a single request spanning the whole missed gap instead of replaying it one period_minutes window at a time — with enough tracked gamers sharing one RequestQueue, per-window replay could never catch up and the checkpoint fell further behind indefinitely. Notification period label now reflects the actual queried span (minutes/ hours/days) instead of the configured interval, so a weeks-old catch-up no longer gets mislabeled as "for 15 minutes".
This commit is contained in:
parent
62cdd750f8
commit
b0173950b8
3 changed files with 62 additions and 21 deletions
|
|
@ -9,13 +9,15 @@ from pathlib import Path
|
|||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import (
|
||||
Application, CommandHandler, CallbackQueryHandler,
|
||||
MessageHandler, filters, ContextTypes, ConversationHandler
|
||||
MessageHandler, filters, ContextTypes, ConversationHandler,
|
||||
PicklePersistence
|
||||
)
|
||||
|
||||
from config import (
|
||||
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
|
||||
|
|
@ -1593,12 +1595,26 @@ class LichessBot:
|
|||
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:
|
||||
|
|
@ -1841,8 +1862,14 @@ def main():
|
|||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -283,13 +283,23 @@ class StatsFormatter:
|
|||
"""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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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': "📭 Нет активности за этот период",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue