fix by codex
This commit is contained in:
parent
3ffcf97c3f
commit
63e28c279c
3 changed files with 67 additions and 21 deletions
|
|
@ -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)."""
|
||||
|
|
@ -1275,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'])
|
||||
)
|
||||
|
|
@ -1440,8 +1451,12 @@ class LichessBot:
|
|||
task_key = f"{gamer['id']}_{user_id}"
|
||||
username = gamer['username']
|
||||
|
||||
# НЕ устанавливаем period_start_times при инициализации
|
||||
# Это позволит использовать логику первой проверки (else блок)
|
||||
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
|
||||
|
|
@ -1477,9 +1492,9 @@ class LichessBot:
|
|||
# Получаем сохраненное время последней проверки для расчета следующего периода
|
||||
# Используем флаг is_first_check для первой проверки вместо проверки наличия ключа
|
||||
last_check_time = self.period_start_times.get(task_key)
|
||||
if is_first_check:
|
||||
if is_first_check and checkpoint_ts is None:
|
||||
last_check_time = None # Принудительно делаем первую проверку
|
||||
is_first_check = False
|
||||
is_first_check = False
|
||||
|
||||
if last_check_time:
|
||||
# Уже была хотя бы одна проверка
|
||||
|
|
@ -1528,6 +1543,8 @@ class LichessBot:
|
|||
self.lichess_api.get_games_period,
|
||||
gamer['username'], since_timestamp, until_timestamp_approx
|
||||
)
|
||||
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}")
|
||||
|
|
@ -1537,10 +1554,8 @@ class LichessBot:
|
|||
if consecutive_errors >= max_consecutive_errors:
|
||||
logger.error(f"Too many consecutive errors for {gamer['username']}, stopping periodic check")
|
||||
break
|
||||
# Продолжаем с обновлением времени начала на планируемое время окончания периода
|
||||
# чтобы не создавать пропусков в следующих проверках
|
||||
self.period_start_times[task_key] = period_end_approx
|
||||
logger.warning(f"⚠️ Error occurred, updated period_start_time to {period_end_approx} (planned period end)")
|
||||
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'):
|
||||
|
|
@ -1656,6 +1671,7 @@ class LichessBot:
|
|||
#
|
||||
# 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()
|
||||
|
|
@ -1678,18 +1694,7 @@ class LichessBot:
|
|||
if task_key in self.period_start_times:
|
||||
del self.period_start_times[task_key]
|
||||
break
|
||||
|
||||
# Важно: обновляем period_start_times даже при ошибке, чтобы не зациклиться на одном периоде
|
||||
# Используем period_end_approx, если он был установлен, иначе используем текущее время
|
||||
if 'period_end_approx' in locals():
|
||||
self.period_start_times[task_key] = period_end_approx
|
||||
logger.warning(f"⚠️ Error occurred, updated period_start_time to {period_end_approx} to prevent loop")
|
||||
else:
|
||||
# Если period_end_approx не был установлен (например, ошибка в начале), используем текущее время
|
||||
now = datetime.now()
|
||||
self.period_start_times[task_key] = now
|
||||
logger.warning(f"⚠️ Error occurred early, updated period_start_time to {now} to prevent loop")
|
||||
|
||||
|
||||
# Ждем перед повторной попыткой при ошибке
|
||||
await asyncio.sleep(60) # 1 minute delay before retry
|
||||
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue