delete activ status
This commit is contained in:
parent
c39bb33282
commit
0e66a05b90
6 changed files with 218 additions and 108 deletions
|
|
@ -387,7 +387,6 @@ class LichessBot:
|
||||||
for i, gamer in enumerate(gamers):
|
for i, gamer in enumerate(gamers):
|
||||||
try:
|
try:
|
||||||
logger.info(f"Processing gamer {i+1}/{len(gamers)}: {gamer['username']} (ID: {gamer['id']})")
|
logger.info(f"Processing gamer {i+1}/{len(gamers)}: {gamer['username']} (ID: {gamer['id']})")
|
||||||
status = "🟢" if gamer['is_active'] else "⚪"
|
|
||||||
username = gamer['username']
|
username = gamer['username']
|
||||||
|
|
||||||
# Get user ratings from Lichess API
|
# Get user ratings from Lichess API
|
||||||
|
|
@ -414,7 +413,6 @@ class LichessBot:
|
||||||
|
|
||||||
gamers_data.append({
|
gamers_data.append({
|
||||||
'id': gamer['id'],
|
'id': gamer['id'],
|
||||||
'status': status,
|
|
||||||
'username': username,
|
'username': username,
|
||||||
'bullet': bullet_rating,
|
'bullet': bullet_rating,
|
||||||
'blitz': blitz_rating,
|
'blitz': blitz_rating,
|
||||||
|
|
@ -432,7 +430,6 @@ class LichessBot:
|
||||||
period_text = f" · {period_minutes}{period_suffix}" if period_minutes > 0 else ""
|
period_text = f" · {period_minutes}{period_suffix}" if period_minutes > 0 else ""
|
||||||
gamers_data.append({
|
gamers_data.append({
|
||||||
'id': gamer['id'],
|
'id': gamer['id'],
|
||||||
'status': "🟢" if gamer['is_active'] else "⚪",
|
|
||||||
'username': gamer['username'],
|
'username': gamer['username'],
|
||||||
'bullet': 'N/A',
|
'bullet': 'N/A',
|
||||||
'blitz': 'N/A',
|
'blitz': 'N/A',
|
||||||
|
|
@ -445,7 +442,7 @@ class LichessBot:
|
||||||
text_lines = []
|
text_lines = []
|
||||||
for gamer in gamers_data:
|
for gamer in gamers_data:
|
||||||
text_lines.append(
|
text_lines.append(
|
||||||
f"{gamer['status']} <b>{gamer['username']}</b> "
|
f"<b>{gamer['username']}</b> "
|
||||||
f"⚡ {gamer['bullet']} 🔥 {gamer['blitz']} 🐇 {gamer['rapid']}{gamer['period']}"
|
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")
|
logger.info(f"getgamers: message length: {len(gamers_text)} characters")
|
||||||
|
|
||||||
# Create simple keyboard with just usernames
|
# Edit the loading message with the results (no keyboard)
|
||||||
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
|
|
||||||
try:
|
try:
|
||||||
await loading_msg.edit_text(
|
await loading_msg.edit_text(
|
||||||
gamers_text,
|
gamers_text,
|
||||||
parse_mode='HTML',
|
parse_mode='HTML'
|
||||||
reply_markup=reply_markup
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error editing message: {e}")
|
logger.error(f"Error editing message: {e}")
|
||||||
|
|
@ -482,8 +466,7 @@ class LichessBot:
|
||||||
pass
|
pass
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
gamers_text,
|
gamers_text,
|
||||||
parse_mode='HTML',
|
parse_mode='HTML'
|
||||||
reply_markup=reply_markup
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def select_gamer(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def select_gamer(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
|
@ -835,19 +818,90 @@ class LichessBot:
|
||||||
self.counters.increment('last_year_1000')
|
self.counters.increment('last_year_1000')
|
||||||
|
|
||||||
async def setperiod(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
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
|
user_id = update.effective_user.id
|
||||||
|
|
||||||
# Get active gamer for this user
|
# Get all gamers for this user
|
||||||
active_gamer = self.db.get_user_active_gamer(user_id)
|
gamers = self.db.get_user_gamers(user_id)
|
||||||
|
|
||||||
lang = self.get_user_language_from_update(update)
|
lang = self.get_user_language_from_update(update)
|
||||||
if not active_gamer:
|
if not gamers:
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(t('no_gamers', lang))
|
||||||
t('no_active_gamer', lang)
|
self.counters.increment('setperiod')
|
||||||
)
|
|
||||||
return
|
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 = []
|
keyboard = []
|
||||||
for period in PERIOD_OPTIONS:
|
for period in PERIOD_OPTIONS:
|
||||||
if period == 0:
|
if period == 0:
|
||||||
|
|
@ -861,14 +915,17 @@ class LichessBot:
|
||||||
else:
|
else:
|
||||||
hours = period // 60
|
hours = period // 60
|
||||||
button_text = f"⏰ {hours} hours"
|
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)
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
await update.message.reply_text(
|
await query.edit_message_text(
|
||||||
t('select_period', lang, username=active_gamer['username']),
|
t('select_period', lang, username=selected_gamer['username']),
|
||||||
reply_markup=reply_markup
|
reply_markup=reply_markup,
|
||||||
|
parse_mode='HTML'
|
||||||
)
|
)
|
||||||
self.counters.increment('setperiod')
|
|
||||||
|
|
||||||
async def select_period(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def select_period(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Handle period selection"""
|
"""Handle period selection"""
|
||||||
|
|
@ -876,10 +933,22 @@ class LichessBot:
|
||||||
await query.answer()
|
await query.answer()
|
||||||
|
|
||||||
user_id = query.from_user.id
|
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
|
# Get gamer info
|
||||||
active_gamer = self.db.get_user_active_gamer(user_id)
|
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 получаем язык из БД
|
# Для callback query получаем язык из БД
|
||||||
if update.effective_user:
|
if update.effective_user:
|
||||||
|
|
@ -891,13 +960,13 @@ class LichessBot:
|
||||||
language_code=update.effective_user.language_code
|
language_code=update.effective_user.language_code
|
||||||
)
|
)
|
||||||
lang = self.db.get_user_language(user_id)
|
lang = self.db.get_user_language(user_id)
|
||||||
if active_gamer:
|
|
||||||
# Set period for this user-gamer pair
|
# Set period for this user-gamer pair
|
||||||
self.db.set_user_gamer_period(user_id, active_gamer['id'], period)
|
self.db.set_user_gamer_period(user_id, gamer_id, period)
|
||||||
|
|
||||||
if period == 0:
|
if period == 0:
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
t('notifications_disabled', lang, username=active_gamer['username'])
|
t('notifications_disabled', lang, username=selected_gamer['username'])
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Format period text for confirmation message
|
# Format period text for confirmation message
|
||||||
|
|
@ -909,11 +978,11 @@ class LichessBot:
|
||||||
hours = period // 60
|
hours = period // 60
|
||||||
period_text = f"{hours} hours"
|
period_text = f"{hours} hours"
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
f"✅ Period {period_text} set for {active_gamer['username']}\n📱 Notifications will be sent to personal messages"
|
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)
|
# Start periodic task for this gamer (send to user's personal messages)
|
||||||
await self.start_periodic_task(active_gamer, user_id, period)
|
await self.start_periodic_task(selected_gamer, user_id, period)
|
||||||
|
|
||||||
async def check_language(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def check_language(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Check and display current language settings"""
|
"""Check and display current language settings"""
|
||||||
|
|
@ -996,8 +1065,35 @@ class LichessBot:
|
||||||
self.period_start_times[task_key] = start_time
|
self.period_start_times[task_key] = start_time
|
||||||
logger.info(f"Started periodic monitoring for {gamer['username']} with {period_minutes} minute intervals")
|
logger.info(f"Started periodic monitoring for {gamer['username']} with {period_minutes} minute intervals")
|
||||||
|
|
||||||
|
consecutive_errors = 0
|
||||||
|
max_consecutive_errors = 5
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
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)
|
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"Checking period for {gamer['username']}: {period_start} to {now}")
|
||||||
logger.info(f"Unix timestamps: since={since_timestamp}, until={until_timestamp}")
|
logger.info(f"Unix timestamps: since={since_timestamp}, until={until_timestamp}")
|
||||||
|
|
||||||
# Делаем запросы к API
|
# Делаем запросы к API с обработкой ошибок
|
||||||
games_url = f"{LICHESS_STATS_API_BASE_URL}/games/{gamer['username']}/period?since={since_timestamp}&until={until_timestamp}"
|
games_data = None
|
||||||
logger.info(f"🎮 GAMES API REQUEST: {games_url}")
|
puzzles_data = None
|
||||||
|
|
||||||
|
try:
|
||||||
games_data = await self.lichess_api.get_games_period(
|
games_data = await self.lichess_api.get_games_period(
|
||||||
gamer['username'], since_timestamp, until_timestamp
|
gamer['username'], since_timestamp, until_timestamp
|
||||||
)
|
)
|
||||||
logger.info(f"Games API response: {games_data}")
|
logger.info(f"Games API response received for {gamer['username']}")
|
||||||
|
except Exception as e:
|
||||||
puzzles_data = None
|
logger.error(f"Error getting games data for {gamer['username']}: {e}")
|
||||||
if gamer['token']:
|
consecutive_errors += 1
|
||||||
puzzles_url = f"{LICHESS_STATS_API_BASE_URL}/puzzle/period?since={since_timestamp}&until={until_timestamp}&max=150"
|
if consecutive_errors >= max_consecutive_errors:
|
||||||
logger.info(f"🧩 PUZZLES API REQUEST: {puzzles_url}")
|
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(
|
puzzles_data = await self.lichess_api.get_puzzles_period(
|
||||||
gamer['token'], since_timestamp, until_timestamp, max_puzzles=150
|
gamer['token'], since_timestamp, until_timestamp, max_puzzles=150
|
||||||
)
|
)
|
||||||
logger.info(f"Puzzles API response: {puzzles_data}")
|
logger.info(f"Puzzles API response received for {gamer['username']}")
|
||||||
else:
|
except Exception as e:
|
||||||
logger.info(f"No token for {gamer['username']}, skipping puzzles API call")
|
logger.warning(f"Error getting puzzles data for {gamer['username']}: {e}")
|
||||||
|
# Продолжаем без данных по пазлам
|
||||||
|
|
||||||
|
# Сбрасываем счетчик ошибок при успешном запросе
|
||||||
|
consecutive_errors = 0
|
||||||
|
|
||||||
# Проверяем наличие реальной активности
|
# Проверяем наличие реальной активности
|
||||||
has_games = False
|
has_games = False
|
||||||
total_games = 0
|
total_games = 0
|
||||||
total_losses = 0
|
|
||||||
if games_data and games_data.get('data'):
|
if games_data and games_data.get('data'):
|
||||||
total_games = games_data.get('data', {}).get('total', {}).get('games_played', 0)
|
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_games = total_games > 0
|
||||||
|
|
||||||
has_puzzles = False
|
has_puzzles = False
|
||||||
|
|
@ -1047,15 +1153,7 @@ class LichessBot:
|
||||||
total_puzzles = puzzles_data.get('data', {}).get('total_attempts', 0)
|
total_puzzles = puzzles_data.get('data', {}).get('total_attempts', 0)
|
||||||
has_puzzles = total_puzzles > 0
|
has_puzzles = total_puzzles > 0
|
||||||
|
|
||||||
# Детальное логирование для отладки
|
|
||||||
logger.info(f"Activity check for {gamer['username']}: has_games={has_games}, has_puzzles={has_puzzles}")
|
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:
|
if has_games or has_puzzles:
|
||||||
|
|
@ -1072,31 +1170,51 @@ class LichessBot:
|
||||||
chat_id=user_id,
|
chat_id=user_id,
|
||||||
text=notification
|
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
|
self.period_start_times[task_key] = now
|
||||||
# Increment periodic notification counter
|
# Increment periodic notification counter
|
||||||
self.counters.increment('periodic_notification')
|
self.counters.increment('periodic_notification')
|
||||||
except Exception as e:
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Error formatting notification for {gamer['username']}: {e}")
|
logger.error(f"Error formatting notification for {gamer['username']}: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
# Не обновляем время начала при ошибке форматирования
|
# Обновляем время начала даже при ошибке форматирования
|
||||||
|
self.period_start_times[task_key] = now
|
||||||
else:
|
else:
|
||||||
logger.info(f"No activity found for {gamer['username']} in the last {period_minutes} minutes")
|
logger.info(f"No activity found for {gamer['username']} in the last {period_minutes} minutes")
|
||||||
# Обновляем время начала даже если нет активности, чтобы не зацикливаться
|
# Обновляем время начала даже если нет активности, чтобы не зацикливаться
|
||||||
self.period_start_times[task_key] = now
|
self.period_start_times[task_key] = now
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
|
logger.info(f"Periodic check cancelled for {gamer['username']}")
|
||||||
break
|
break
|
||||||
except Exception as e:
|
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
|
import traceback
|
||||||
logger.error(f"Full traceback: {traceback.format_exc()}")
|
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):
|
def setup_handlers(self, application: Application):
|
||||||
"""Setup all handlers"""
|
"""Setup all handlers"""
|
||||||
self.application = application # Store application reference
|
self.application = application # Store application reference
|
||||||
|
|
@ -1127,7 +1245,8 @@ class LichessBot:
|
||||||
application.add_handler(CommandHandler("resetlang", self.reset_language))
|
application.add_handler(CommandHandler("resetlang", self.reset_language))
|
||||||
application.add_handler(CommandHandler("test_admin_notify", self.test_admin_notify))
|
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.select_gamer, pattern="^select_"))
|
||||||
application.add_handler(CallbackQueryHandler(self.handle_delete_gamer, pattern="^delete_"))
|
application.add_handler(CallbackQueryHandler(self.handle_delete_gamer, pattern="^delete_"))
|
||||||
application.add_handler(CallbackQueryHandler(self.select_period, pattern="^period_"))
|
application.add_handler(CallbackQueryHandler(self.select_period, pattern="^period_"))
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ DATABASE_PATH = _resolve_database_path()
|
||||||
PERIOD_OPTIONS = [0, 15, 30, 60, 120, 180, 360, 720, 1440] # minutes (0=disable, then: 15min, 30min, 1h, 2h, 3h, 6h, 12h, 24h)
|
PERIOD_OPTIONS = [0, 15, 30, 60, 120, 180, 360, 720, 1440] # minutes (0=disable, then: 15min, 30min, 1h, 2h, 3h, 6h, 12h, 24h)
|
||||||
|
|
||||||
# Bot Version
|
# Bot Version
|
||||||
BOT_VERSION = "1.1.1"
|
BOT_VERSION = "1.2.0"
|
||||||
|
|
||||||
# Telegram Bot Long Polling Configuration
|
# Telegram Bot Long Polling Configuration
|
||||||
POLL_INTERVAL = 1.0 # seconds
|
POLL_INTERVAL = 1.0 # seconds
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ TRANSLATIONS = {
|
||||||
'games_section': "{emoji} {game_type} — {games_count} games • {rating_change}\nRating: {rating}\n✅ Wins: {wins}\n❌ Losses: {losses}\n🤝 Draws: {draws}\n\n",
|
'games_section': "{emoji} {game_type} — {games_count} games • {rating_change}\nRating: {rating}\n✅ Wins: {wins}\n❌ Losses: {losses}\n🤝 Draws: {draws}\n\n",
|
||||||
|
|
||||||
# Set period
|
# Set period
|
||||||
|
'select_gamer_for_period': "⏱️ <b>Select player to set notification period:</b>\n\n",
|
||||||
'select_period': "⏱️ Select period for player {username}:\n📱 Notifications will be sent to personal messages",
|
'select_period': "⏱️ Select period for player {username}:\n📱 Notifications will be sent to personal messages",
|
||||||
'notifications_disabled': "✅ Notifications disabled for {username}",
|
'notifications_disabled': "✅ Notifications disabled for {username}",
|
||||||
'period_set': "✅ Period {period} minutes set for {username}\n📱 Notifications will be sent to personal messages",
|
'period_set': "✅ Period {period} minutes set for {username}\n📱 Notifications will be sent to personal messages",
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,6 @@ def get_users():
|
||||||
tu.last_name,
|
tu.last_name,
|
||||||
tu.created_at,
|
tu.created_at,
|
||||||
COUNT(ug.id) as gamer_count,
|
COUNT(ug.id) as gamer_count,
|
||||||
SUM(CASE WHEN ug.is_active = 1 THEN 1 ELSE 0 END) as active_gamers,
|
|
||||||
SUM(CASE WHEN ug.period_minutes > 0 THEN 1 ELSE 0 END) as monitored_gamers
|
SUM(CASE WHEN ug.period_minutes > 0 THEN 1 ELSE 0 END) as monitored_gamers
|
||||||
FROM telegram_users tu
|
FROM telegram_users tu
|
||||||
LEFT JOIN user_gamers ug ON tu.user_id = ug.user_id
|
LEFT JOIN user_gamers ug ON tu.user_id = ug.user_id
|
||||||
|
|
@ -137,8 +136,7 @@ def get_users():
|
||||||
'last_name': row[3],
|
'last_name': row[3],
|
||||||
'created_at': row[4],
|
'created_at': row[4],
|
||||||
'gamer_count': row[5],
|
'gamer_count': row[5],
|
||||||
'active_gamers': row[6],
|
'monitored_gamers': row[6]
|
||||||
'monitored_gamers': row[7]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Получаем общее количество игроков (уникальных)
|
# Получаем общее количество игроков (уникальных)
|
||||||
|
|
@ -187,7 +185,6 @@ def get_user_gamers(user_id):
|
||||||
g.id,
|
g.id,
|
||||||
g.username,
|
g.username,
|
||||||
ug.token,
|
ug.token,
|
||||||
ug.is_active,
|
|
||||||
ug.period_minutes,
|
ug.period_minutes,
|
||||||
ug.created_at
|
ug.created_at
|
||||||
FROM user_gamers ug
|
FROM user_gamers ug
|
||||||
|
|
@ -204,9 +201,8 @@ def get_user_gamers(user_id):
|
||||||
'id': row[0],
|
'id': row[0],
|
||||||
'username': row[1],
|
'username': row[1],
|
||||||
'has_token': bool(row[2]), # Token from user_gamers, not gamers
|
'has_token': bool(row[2]), # Token from user_gamers, not gamers
|
||||||
'is_active': bool(row[3]),
|
'period_minutes': row[3],
|
||||||
'period_minutes': row[4],
|
'created_at': row[4]
|
||||||
'created_at': row[5]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
|
|
|
||||||
|
|
@ -387,8 +387,7 @@
|
||||||
<div class="user-info">@${escapeHtml(user.username)} • ID: ${user.user_id}</div>
|
<div class="user-info">@${escapeHtml(user.username)} • ID: ${user.user_id}</div>
|
||||||
<div class="user-info">Добавлен: ${formatDate(user.created_at)}</div>
|
<div class="user-info">Добавлен: ${formatDate(user.created_at)}</div>
|
||||||
<div class="user-stats">
|
<div class="user-stats">
|
||||||
<span>📊 ${user.gamer_count} игроков</span>
|
<span>📊 ${user.gamer_count} игроков</span>
|
||||||
<span>✅ ${user.active_gamers} активен</span>
|
|
||||||
<span>⏰ ${user.monitored_gamers} с уведомл.</span>
|
<span>⏰ ${user.monitored_gamers} с уведомл.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -447,7 +446,6 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Игрок</th>
|
<th>Игрок</th>
|
||||||
<th>Статус</th>
|
|
||||||
<th>Токен</th>
|
<th>Токен</th>
|
||||||
<th>Период уведомлений</th>
|
<th>Период уведомлений</th>
|
||||||
<th>Добавлен</th>
|
<th>Добавлен</th>
|
||||||
|
|
@ -457,11 +455,6 @@
|
||||||
${gamers.map(gamer => `
|
${gamers.map(gamer => `
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><a href="https://lichess.org/@/${gamer.username}" target="_blank" class="gamer-link">${escapeHtml(gamer.username)}</a></strong> (ID: ${gamer.id})</td>
|
<td><strong><a href="https://lichess.org/@/${gamer.username}" target="_blank" class="gamer-link">${escapeHtml(gamer.username)}</a></strong> (ID: ${gamer.id})</td>
|
||||||
<td>
|
|
||||||
<span class="badge ${gamer.is_active ? 'badge-success' : 'badge-secondary'}">
|
|
||||||
${gamer.is_active ? '✅ АКТИВЕН' : '⚪'}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<span class="badge badge-token">
|
<span class="badge badge-token">
|
||||||
${gamer.has_token ? '🔑 Есть' : '❌ Нет'}
|
${gamer.has_token ? '🔑 Есть' : '❌ Нет'}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ services:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./LichessClientTG_bot/data:/app/data:ro
|
- ./LichessClientTG_bot/data:/app/data:ro
|
||||||
|
- ./LichessWebView:/app
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
- lichess-bot
|
- lichess-bot
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue