fix by codex
This commit is contained in:
parent
3ffcf97c3f
commit
63e28c279c
3 changed files with 67 additions and 21 deletions
|
|
@ -48,6 +48,15 @@ class LichessBot:
|
||||||
self.counters = MessageCounters() # Message counters
|
self.counters = MessageCounters() # Message counters
|
||||||
self.request_queue = get_request_queue() # Request queue for rate limiting
|
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):
|
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)."""
|
"""Notify admin about newly linked player (always try to send)."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -1275,8 +1284,10 @@ class LichessBot:
|
||||||
|
|
||||||
# Set period for this user-gamer pair
|
# Set period for this user-gamer pair
|
||||||
self.db.set_user_gamer_period(user_id, gamer_id, period)
|
self.db.set_user_gamer_period(user_id, gamer_id, period)
|
||||||
|
self.db.clear_period_checkpoint(user_id, gamer_id)
|
||||||
|
|
||||||
if period == 0:
|
if period == 0:
|
||||||
|
await self.stop_periodic_task(gamer_id, user_id)
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
t('notifications_disabled', lang, username=selected_gamer['username'])
|
t('notifications_disabled', lang, username=selected_gamer['username'])
|
||||||
)
|
)
|
||||||
|
|
@ -1440,8 +1451,12 @@ class LichessBot:
|
||||||
task_key = f"{gamer['id']}_{user_id}"
|
task_key = f"{gamer['id']}_{user_id}"
|
||||||
username = gamer['username']
|
username = gamer['username']
|
||||||
|
|
||||||
# НЕ устанавливаем period_start_times при инициализации
|
checkpoint_ts = self.db.get_period_checkpoint(user_id, gamer['id'])
|
||||||
# Это позволит использовать логику первой проверки (else блок)
|
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")
|
logger.info(f"🔄 Started periodic monitoring for {username} (user {user_id}) with {period_minutes} minute intervals")
|
||||||
|
|
||||||
consecutive_errors = 0
|
consecutive_errors = 0
|
||||||
|
|
@ -1477,7 +1492,7 @@ class LichessBot:
|
||||||
# Получаем сохраненное время последней проверки для расчета следующего периода
|
# Получаем сохраненное время последней проверки для расчета следующего периода
|
||||||
# Используем флаг is_first_check для первой проверки вместо проверки наличия ключа
|
# Используем флаг is_first_check для первой проверки вместо проверки наличия ключа
|
||||||
last_check_time = self.period_start_times.get(task_key)
|
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 # Принудительно делаем первую проверку
|
last_check_time = None # Принудительно делаем первую проверку
|
||||||
is_first_check = False
|
is_first_check = False
|
||||||
|
|
||||||
|
|
@ -1528,6 +1543,8 @@ class LichessBot:
|
||||||
self.lichess_api.get_games_period,
|
self.lichess_api.get_games_period,
|
||||||
gamer['username'], since_timestamp, until_timestamp_approx
|
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()
|
request_end_time = datetime.now()
|
||||||
logger.info(f"✅ Games API response received for {gamer['username']} at {request_end_time}")
|
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:
|
if consecutive_errors >= max_consecutive_errors:
|
||||||
logger.error(f"Too many consecutive errors for {gamer['username']}, stopping periodic check")
|
logger.error(f"Too many consecutive errors for {gamer['username']}, stopping periodic check")
|
||||||
break
|
break
|
||||||
# Продолжаем с обновлением времени начала на планируемое время окончания периода
|
logger.warning(f"⚠️ Games data unavailable for {gamer['username']}; retrying the same period in 60 seconds")
|
||||||
# чтобы не создавать пропусков в следующих проверках
|
await asyncio.sleep(60)
|
||||||
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)")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if gamer.get('token'):
|
if gamer.get('token'):
|
||||||
|
|
@ -1656,6 +1671,7 @@ class LichessBot:
|
||||||
#
|
#
|
||||||
# period_end_approx уже установлено в начале итерации
|
# period_end_approx уже установлено в начале итерации
|
||||||
self.period_start_times[task_key] = 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)")
|
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():
|
if 'request_end_time' in locals():
|
||||||
delay = (request_end_time - period_end_approx).total_seconds()
|
delay = (request_end_time - period_end_approx).total_seconds()
|
||||||
|
|
@ -1679,17 +1695,6 @@ class LichessBot:
|
||||||
del self.period_start_times[task_key]
|
del self.period_start_times[task_key]
|
||||||
break
|
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
|
await asyncio.sleep(60) # 1 minute delay before retry
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ class Database:
|
||||||
token TEXT,
|
token TEXT,
|
||||||
is_active BOOLEAN DEFAULT FALSE,
|
is_active BOOLEAN DEFAULT FALSE,
|
||||||
period_minutes INTEGER DEFAULT 0,
|
period_minutes INTEGER DEFAULT 0,
|
||||||
|
last_period_end_ts INTEGER,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (user_id) REFERENCES telegram_users(user_id),
|
FOREIGN KEY (user_id) REFERENCES telegram_users(user_id),
|
||||||
FOREIGN KEY (gamer_id) REFERENCES gamers(id),
|
FOREIGN KEY (gamer_id) REFERENCES gamers(id),
|
||||||
|
|
@ -78,6 +79,13 @@ class Database:
|
||||||
# Column already exists
|
# Column already exists
|
||||||
pass
|
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
|
# Create admin_settings table for admin bot configuration
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS admin_settings (
|
CREATE TABLE IF NOT EXISTS admin_settings (
|
||||||
|
|
@ -327,6 +335,39 @@ class Database:
|
||||||
)
|
)
|
||||||
conn.commit()
|
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:
|
def remove_user_gamer(self, user_id: int, gamer_id: int) -> bool:
|
||||||
"""Remove gamer from user's tracked list"""
|
"""Remove gamer from user's tracked list"""
|
||||||
with sqlite3.connect(self.db_path) as conn:
|
with sqlite3.connect(self.db_path) as conn:
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ class StatsFormatter:
|
||||||
emoji = StatsFormatter._get_game_type_emoji(game_type)
|
emoji = StatsFormatter._get_game_type_emoji(game_type)
|
||||||
games_count = game_data.get('games_played', 0)
|
games_count = game_data.get('games_played', 0)
|
||||||
rating_change = game_data.get('rating_change', 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)
|
wins = game_data.get('wins', 0)
|
||||||
losses = game_data.get('losses', 0)
|
losses = game_data.get('losses', 0)
|
||||||
draws = game_data.get('draws', 0)
|
draws = game_data.get('draws', 0)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue