delete activ status

This commit is contained in:
vrubelroman 2025-11-16 23:36:57 +03:00
parent c39bb33282
commit 0e66a05b90
6 changed files with 218 additions and 108 deletions

View file

@ -387,7 +387,6 @@ class LichessBot:
for i, gamer in enumerate(gamers):
try:
logger.info(f"Processing gamer {i+1}/{len(gamers)}: {gamer['username']} (ID: {gamer['id']})")
status = "🟢" if gamer['is_active'] else ""
username = gamer['username']
# Get user ratings from Lichess API
@ -414,7 +413,6 @@ class LichessBot:
gamers_data.append({
'id': gamer['id'],
'status': status,
'username': username,
'bullet': bullet_rating,
'blitz': blitz_rating,
@ -432,7 +430,6 @@ class LichessBot:
period_text = f" · {period_minutes}{period_suffix}" if period_minutes > 0 else ""
gamers_data.append({
'id': gamer['id'],
'status': "🟢" if gamer['is_active'] else "",
'username': gamer['username'],
'bullet': 'N/A',
'blitz': 'N/A',
@ -445,7 +442,7 @@ class LichessBot:
text_lines = []
for gamer in gamers_data:
text_lines.append(
f"{gamer['status']} <b>{gamer['username']}</b> "
f"<b>{gamer['username']}</b> "
f"{gamer['bullet']} 🔥 {gamer['blitz']} 🐇 {gamer['rapid']}{gamer['period']}"
)
@ -454,24 +451,11 @@ class LichessBot:
logger.info(f"getgamers: message length: {len(gamers_text)} characters")
# Create simple keyboard with just usernames
keyboard = []
for gamer in gamers_data:
keyboard.append([InlineKeyboardButton(
text=f"{gamer['status']} {gamer['username']}",
callback_data=f"select_{gamer['id']}"
)])
logger.debug(f"Added button for {gamer['username']} (ID: {gamer['id']})")
logger.info(f"getgamers: created {len(keyboard)} buttons in keyboard")
reply_markup = InlineKeyboardMarkup(keyboard)
# Edit the loading message with the results
# Edit the loading message with the results (no keyboard)
try:
await loading_msg.edit_text(
gamers_text,
parse_mode='HTML',
reply_markup=reply_markup
parse_mode='HTML'
)
except Exception as e:
logger.error(f"Error editing message: {e}")
@ -482,8 +466,7 @@ class LichessBot:
pass
await update.message.reply_text(
gamers_text,
parse_mode='HTML',
reply_markup=reply_markup
parse_mode='HTML'
)
async def select_gamer(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
@ -835,19 +818,90 @@ class LichessBot:
self.counters.increment('last_year_1000')
async def setperiod(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Set period command"""
"""Set period command - first select gamer, then select period"""
user_id = update.effective_user.id
# Get active gamer for this user
active_gamer = self.db.get_user_active_gamer(user_id)
# Get all gamers for this user
gamers = self.db.get_user_gamers(user_id)
lang = self.get_user_language_from_update(update)
if not active_gamer:
await update.message.reply_text(
t('no_active_gamer', lang)
)
if not gamers:
await update.message.reply_text(t('no_gamers', lang))
self.counters.increment('setperiod')
return
# Create keyboard with gamers and their periods
keyboard = []
for gamer in gamers:
username = gamer['username']
period_minutes = gamer.get('period_minutes', 0)
# Format period text
if period_minutes == 0:
period_text = ""
elif period_minutes < 60:
period_text = f"{period_minutes}m"
elif period_minutes == 60:
period_text = "1h"
else:
hours = period_minutes // 60
period_text = f"{hours}h"
keyboard.append([InlineKeyboardButton(
text=f"{username} · {period_text}",
callback_data=f"select_gamer_period_{gamer['id']}"
)])
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"⏱️ Select player to set notification period:",
reply_markup=reply_markup
)
self.counters.increment('setperiod')
async def select_gamer_for_period(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle gamer selection for period setting"""
query = update.callback_query
await query.answer()
logger.info(f"select_gamer_for_period called with callback_data: {query.data}")
user_id = query.from_user.id
# Parse callback_data: select_gamer_period_{gamer_id}
try:
gamer_id = int(query.data.split('_')[-1])
logger.info(f"Parsed gamer_id: {gamer_id}")
except (ValueError, IndexError) as e:
logger.error(f"Error parsing gamer_id from callback_data '{query.data}': {e}")
await query.edit_message_text("❌ Error: Invalid player selection")
return
# Get gamer info
gamers = self.db.get_user_gamers(user_id)
selected_gamer = None
for gamer in gamers:
if gamer['id'] == gamer_id:
selected_gamer = gamer
break
if not selected_gamer:
await query.edit_message_text("❌ Player not found")
return
# Для callback query получаем язык из БД
if update.effective_user:
self.db.add_or_get_telegram_user(
user_id=update.effective_user.id,
username=update.effective_user.username,
first_name=update.effective_user.first_name,
last_name=update.effective_user.last_name,
language_code=update.effective_user.language_code
)
lang = self.db.get_user_language(user_id)
# Show period options for selected gamer
keyboard = []
for period in PERIOD_OPTIONS:
if period == 0:
@ -861,14 +915,17 @@ class LichessBot:
else:
hours = period // 60
button_text = f"{hours} hours"
keyboard.append([InlineKeyboardButton(button_text, callback_data=f"period_{period}")])
keyboard.append([InlineKeyboardButton(
button_text,
callback_data=f"period_{gamer_id}_{period}"
)])
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
t('select_period', lang, username=active_gamer['username']),
reply_markup=reply_markup
await query.edit_message_text(
t('select_period', lang, username=selected_gamer['username']),
reply_markup=reply_markup,
parse_mode='HTML'
)
self.counters.increment('setperiod')
async def select_period(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle period selection"""
@ -876,10 +933,22 @@ class LichessBot:
await query.answer()
user_id = query.from_user.id
period = int(query.data.split('_')[1])
# Parse callback data: period_{gamer_id}_{period}
parts = query.data.split('_')
gamer_id = int(parts[1])
period = int(parts[2])
# Get active gamer for this user
active_gamer = self.db.get_user_active_gamer(user_id)
# Get gamer info
gamers = self.db.get_user_gamers(user_id)
selected_gamer = None
for gamer in gamers:
if gamer['id'] == gamer_id:
selected_gamer = gamer
break
if not selected_gamer:
await query.edit_message_text("❌ Player not found")
return
# Для callback query получаем язык из БД
if update.effective_user:
@ -891,29 +960,29 @@ class LichessBot:
language_code=update.effective_user.language_code
)
lang = self.db.get_user_language(user_id)
if active_gamer:
# Set period for this user-gamer pair
self.db.set_user_gamer_period(user_id, active_gamer['id'], period)
if period == 0:
await query.edit_message_text(
t('notifications_disabled', lang, username=active_gamer['username'])
)
# Set period for this user-gamer pair
self.db.set_user_gamer_period(user_id, gamer_id, period)
if period == 0:
await query.edit_message_text(
t('notifications_disabled', lang, username=selected_gamer['username'])
)
else:
# Format period text for confirmation message
if period < 60:
period_text = f"{period} minutes"
elif period == 60:
period_text = "1 hour"
else:
# Format period text for confirmation message
if period < 60:
period_text = f"{period} minutes"
elif period == 60:
period_text = "1 hour"
else:
hours = period // 60
period_text = f"{hours} hours"
await query.edit_message_text(
f"✅ Period {period_text} set for {active_gamer['username']}\n📱 Notifications will be sent to personal messages"
)
# Start periodic task for this gamer (send to user's personal messages)
await self.start_periodic_task(active_gamer, user_id, period)
hours = period // 60
period_text = f"{hours} hours"
await query.edit_message_text(
f"✅ Period {period_text} set for {selected_gamer['username']}\n📱 Notifications will be sent to personal messages"
)
# Start periodic task for this gamer (send to user's personal messages)
await self.start_periodic_task(selected_gamer, user_id, period)
async def check_language(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Check and display current language settings"""
@ -996,8 +1065,35 @@ class LichessBot:
self.period_start_times[task_key] = start_time
logger.info(f"Started periodic monitoring for {gamer['username']} with {period_minutes} minute intervals")
consecutive_errors = 0
max_consecutive_errors = 5
while True:
try:
# Проверяем, что период все еще установлен в БД
current_gamers = self.db.get_user_gamers(user_id)
gamer_still_exists = False
current_period = 0
for g in current_gamers:
if g['id'] == gamer['id']:
gamer_still_exists = True
current_period = g.get('period_minutes', 0)
break
# Если игрок удален или период отключен, прекращаем мониторинг
if not gamer_still_exists or current_period == 0:
logger.info(f"Periodic monitoring stopped for {gamer['username']}: gamer removed or period disabled")
if task_key in self.periodic_tasks:
del self.periodic_tasks[task_key]
if task_key in self.period_start_times:
del self.period_start_times[task_key]
break
# Обновляем период на случай, если он был изменен
if current_period != period_minutes:
logger.info(f"Period changed for {gamer['username']} from {period_minutes} to {current_period} minutes")
period_minutes = current_period
# Ждем заданное количество минут
await asyncio.sleep(period_minutes * 60)
@ -1012,34 +1108,44 @@ class LichessBot:
logger.info(f"Checking period for {gamer['username']}: {period_start} to {now}")
logger.info(f"Unix timestamps: since={since_timestamp}, until={until_timestamp}")
# Делаем запросы к API
games_url = f"{LICHESS_STATS_API_BASE_URL}/games/{gamer['username']}/period?since={since_timestamp}&until={until_timestamp}"
logger.info(f"🎮 GAMES API REQUEST: {games_url}")
games_data = await self.lichess_api.get_games_period(
gamer['username'], since_timestamp, until_timestamp
)
logger.info(f"Games API response: {games_data}")
# Делаем запросы к API с обработкой ошибок
games_data = None
puzzles_data = None
if gamer['token']:
puzzles_url = f"{LICHESS_STATS_API_BASE_URL}/puzzle/period?since={since_timestamp}&until={until_timestamp}&max=150"
logger.info(f"🧩 PUZZLES API REQUEST: {puzzles_url}")
puzzles_data = await self.lichess_api.get_puzzles_period(
gamer['token'], since_timestamp, until_timestamp, max_puzzles=150
try:
games_data = await self.lichess_api.get_games_period(
gamer['username'], since_timestamp, until_timestamp
)
logger.info(f"Puzzles API response: {puzzles_data}")
else:
logger.info(f"No token for {gamer['username']}, skipping puzzles API call")
logger.info(f"Games API response received for {gamer['username']}")
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
continue
if gamer.get('token'):
try:
puzzles_data = await self.lichess_api.get_puzzles_period(
gamer['token'], since_timestamp, until_timestamp, max_puzzles=150
)
logger.info(f"Puzzles API response received for {gamer['username']}")
except Exception as e:
logger.warning(f"Error getting puzzles data for {gamer['username']}: {e}")
# Продолжаем без данных по пазлам
# Сбрасываем счетчик ошибок при успешном запросе
consecutive_errors = 0
# Проверяем наличие реальной активности
has_games = False
total_games = 0
total_losses = 0
if games_data and games_data.get('data'):
total_games = games_data.get('data', {}).get('total', {}).get('games_played', 0)
total_losses = games_data.get('data', {}).get('total', {}).get('losses', 0)
has_games = total_games > 0
has_puzzles = False
@ -1047,15 +1153,7 @@ class LichessBot:
total_puzzles = puzzles_data.get('data', {}).get('total_attempts', 0)
has_puzzles = total_puzzles > 0
# Детальное логирование для отладки
logger.info(f"Activity check for {gamer['username']}: has_games={has_games}, has_puzzles={has_puzzles}")
if games_data and games_data.get('data'):
total_games = games_data.get('data', {}).get('total', {}).get('games_played', 0)
total_losses = games_data.get('data', {}).get('total', {}).get('losses', 0)
logger.info(f"Games data: total_games={total_games}, total_losses={total_losses}")
if puzzles_data and puzzles_data.get('data'):
total_puzzles = puzzles_data.get('data', {}).get('total_attempts', 0)
logger.info(f"Puzzles data: total_attempts={total_puzzles}")
# Отправляем уведомление только если есть реальная активность
if has_games or has_puzzles:
@ -1072,30 +1170,50 @@ class LichessBot:
chat_id=user_id,
text=notification
)
logger.info(f"Sent periodic notification for {gamer['username']} to user {user_id}")
logger.info(f"Sent periodic notification for {gamer['username']} to user {user_id}")
# Обновляем время начала только после успешной отправки уведомления
self.period_start_times[task_key] = now
# Increment periodic notification counter
self.counters.increment('periodic_notification')
except Exception as e:
logger.error(f"Failed to send notification to user {user_id}: {e}")
# Не обновляем время начала при ошибке отправки
logger.error(f"❌ Failed to send notification to user {user_id}: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
# Обновляем время начала даже при ошибке отправки, чтобы не зацикливаться
self.period_start_times[task_key] = now
else:
logger.error(f"Application not initialized, cannot send notification for {gamer['username']}")
self.period_start_times[task_key] = now
except Exception as e:
logger.error(f"Error formatting notification for {gamer['username']}: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
# Не обновляем время начала при ошибке форматирования
# Обновляем время начала даже при ошибке форматирования
self.period_start_times[task_key] = now
else:
logger.info(f"No activity found for {gamer['username']} in the last {period_minutes} minutes")
# Обновляем время начала даже если нет активности, чтобы не зацикливаться
self.period_start_times[task_key] = now
except asyncio.CancelledError:
logger.info(f"Periodic check cancelled for {gamer['username']}")
break
except Exception as e:
logger.error(f"Error in periodic check: {e}")
consecutive_errors += 1
logger.error(f"Error in periodic check for {gamer['username']}: {e}")
import traceback
logger.error(f"Full traceback: {traceback.format_exc()}")
if consecutive_errors >= max_consecutive_errors:
logger.error(f"Too many consecutive errors for {gamer['username']}, stopping periodic check")
if task_key in self.periodic_tasks:
del self.periodic_tasks[task_key]
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
def setup_handlers(self, application: Application):
"""Setup all handlers"""
@ -1127,7 +1245,8 @@ class LichessBot:
application.add_handler(CommandHandler("resetlang", self.reset_language))
application.add_handler(CommandHandler("test_admin_notify", self.test_admin_notify))
# Callback handlers
# Callback handlers (order matters - more specific patterns first)
application.add_handler(CallbackQueryHandler(self.select_gamer_for_period, pattern="^select_gamer_period_"))
application.add_handler(CallbackQueryHandler(self.select_gamer, pattern="^select_"))
application.add_handler(CallbackQueryHandler(self.handle_delete_gamer, pattern="^delete_"))
application.add_handler(CallbackQueryHandler(self.select_period, pattern="^period_"))