fix bug push from timer
This commit is contained in:
parent
278c5b9c40
commit
ef9aa3d3df
3 changed files with 402 additions and 18 deletions
|
|
@ -1243,16 +1243,31 @@ class LichessBot:
|
||||||
# Проверяем наличие реальной активности
|
# Проверяем наличие реальной активности
|
||||||
has_games = False
|
has_games = False
|
||||||
total_games = 0
|
total_games = 0
|
||||||
if games_data and games_data.get('data'):
|
if games_data:
|
||||||
total_games = games_data.get('data', {}).get('total', {}).get('games_played', 0)
|
# Логируем структуру ответа для отладки
|
||||||
has_games = total_games > 0
|
logger.debug(f"Games data structure for {gamer['username']}: {games_data}")
|
||||||
|
if games_data.get('data'):
|
||||||
|
total_games = games_data.get('data', {}).get('total', {}).get('games_played', 0)
|
||||||
|
has_games = total_games > 0
|
||||||
|
# Также проверяем games_count на верхнем уровне
|
||||||
|
elif games_data.get('games_count', 0) > 0:
|
||||||
|
total_games = games_data.get('games_count', 0)
|
||||||
|
has_games = True
|
||||||
|
else:
|
||||||
|
logger.warning(f"No games_data returned for {gamer['username']}")
|
||||||
|
|
||||||
has_puzzles = False
|
has_puzzles = False
|
||||||
if puzzles_data and puzzles_data.get('data'):
|
total_puzzles = 0
|
||||||
total_puzzles = puzzles_data.get('data', {}).get('total_attempts', 0)
|
if puzzles_data:
|
||||||
has_puzzles = total_puzzles > 0
|
if puzzles_data.get('data'):
|
||||||
|
total_puzzles = puzzles_data.get('data', {}).get('total_attempts', 0)
|
||||||
|
has_puzzles = total_puzzles > 0
|
||||||
|
# Также проверяем puzzles_in_period на верхнем уровне
|
||||||
|
elif puzzles_data.get('puzzles_in_period', 0) > 0:
|
||||||
|
total_puzzles = puzzles_data.get('puzzles_in_period', 0)
|
||||||
|
has_puzzles = True
|
||||||
|
|
||||||
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} (total={total_games}), has_puzzles={has_puzzles} (total={total_puzzles})")
|
||||||
|
|
||||||
# Отправляем уведомление только если есть реальная активность
|
# Отправляем уведомление только если есть реальная активность
|
||||||
if has_games or has_puzzles:
|
if has_games or has_puzzles:
|
||||||
|
|
@ -1271,29 +1286,23 @@ class LichessBot:
|
||||||
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
|
|
||||||
# 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
|
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.error(f"❌ Application not initialized, cannot send notification for {gamer['username']} to user {user_id}")
|
logger.error(f"❌ Application not initialized, cannot send notification for {gamer['username']} to user {user_id}")
|
||||||
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()}")
|
||||||
# Обновляем время начала даже при ошибке форматирования
|
else:
|
||||||
self.period_start_times[task_key] = now
|
logger.debug(f"⏭️ No activity found for {gamer['username']} in the last {period_minutes} minutes")
|
||||||
else:
|
|
||||||
logger.debug(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']}")
|
logger.info(f"Periodic check cancelled for {gamer['username']}")
|
||||||
|
|
|
||||||
145
analyze_notifications.py
Normal file
145
analyze_notifications.py
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Анализ уведомлений: проверяем сколько пользователей отслеживают каждого игрока
|
||||||
|
и сколько уведомлений должно быть отправлено
|
||||||
|
"""
|
||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Определяем путь к базе данных
|
||||||
|
db_path = os.getenv("DATABASE_PATH")
|
||||||
|
if not db_path:
|
||||||
|
possible_paths = [
|
||||||
|
"LichessClientTG_bot/data/lichess_bot.db",
|
||||||
|
"data/lichess_bot.db",
|
||||||
|
"/app/data/lichess_bot.db"
|
||||||
|
]
|
||||||
|
for path in possible_paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
db_path = path
|
||||||
|
break
|
||||||
|
|
||||||
|
if not db_path:
|
||||||
|
print("База данных не найдена!")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
print(f"Используется база данных: {db_path}\n")
|
||||||
|
|
||||||
|
# Список игроков, которые играли сегодня (из вашего вывода)
|
||||||
|
active_players = [
|
||||||
|
'AARON805', 'Aangelin', 'Agorchakov', 'Almansura', 'Anastaysha_champion',
|
||||||
|
'Artemij1', 'Benhayatinmagllbuyum', 'Berserkgirl', 'Danila_07', 'DianaVlad',
|
||||||
|
'ESP2209', 'Epineyf', 'Ferstep', 'Freakwithprime', 'Kerop',
|
||||||
|
'Kmknnaqw', 'Kraev_Arseniy', 'MX2017', 'MaEstRo_234', 'Maestro_234',
|
||||||
|
'Matovalov', 'Ovcharovnik', 'PAA17', 'PaholkovMaks', 'PerepechkoD',
|
||||||
|
'Petyabro', 'Recep-1903', 'Rudy23042014', 'Sayan_13', 'Sergey_Kleiner',
|
||||||
|
'Soup_Maktavish_t', 'Stolypin_2-0', 'Tahamorphyy', 'Taih', 'TasiaArgylova',
|
||||||
|
'Timofei700', 'TomasI_25', 'UrielSpb', 'VadimVits', 'Vadimvits',
|
||||||
|
'Valeria1309', 'Veselin_27', 'Vinni_Jr', 'Vyachesslav_Rusakov', 'Whateverw',
|
||||||
|
'Yaroslavsm', 'Yuliyana2013', 'besfighter', 'braveyounes', 'gobelen',
|
||||||
|
'kolobyxov_daniil52', 'kostik2811', 'li_alex2017', 'lmmaster', 'metiforce',
|
||||||
|
'motheroflearning', 'polypoker', 'qwertycher', 'speedrunchessgames',
|
||||||
|
'warriorchess70', 'wwwspl', 'yasumichi', 'yeti_tais', 'youngking0007'
|
||||||
|
]
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
print("="*80)
|
||||||
|
print("АНАЛИЗ УВЕДОМЛЕНИЙ")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
# 1. Сколько всего пар user-gamer с period_minutes > 0
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM user_gamers WHERE period_minutes > 0")
|
||||||
|
total_pairs = cursor.fetchone()[0]
|
||||||
|
print(f"\n1. Всего пар user-gamer с периодическими уведомлениями: {total_pairs}")
|
||||||
|
|
||||||
|
# 2. Сколько уникальных игроков отслеживается с периодами
|
||||||
|
cursor.execute("SELECT COUNT(DISTINCT gamer_id) FROM user_gamers WHERE period_minutes > 0")
|
||||||
|
unique_gamers = cursor.fetchone()[0]
|
||||||
|
print(f"2. Уникальных игроков с периодами: {unique_gamers}")
|
||||||
|
|
||||||
|
# 3. Для каждого из 64 активных игроков - сколько пользователей их отслеживают
|
||||||
|
print(f"\n3. Анализ активных игроков (играли сегодня):")
|
||||||
|
print("-"*80)
|
||||||
|
|
||||||
|
total_expected_notifications = 0
|
||||||
|
players_with_trackers = []
|
||||||
|
|
||||||
|
for player_username in active_players:
|
||||||
|
# Находим gamer_id по username
|
||||||
|
cursor.execute("SELECT id FROM gamers WHERE username = ?", (player_username,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
continue
|
||||||
|
|
||||||
|
gamer_id = result[0]
|
||||||
|
|
||||||
|
# Сколько пользователей отслеживают этого игрока с периодом > 0
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM user_gamers
|
||||||
|
WHERE gamer_id = ? AND period_minutes > 0
|
||||||
|
""", (gamer_id,))
|
||||||
|
|
||||||
|
tracker_count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
if tracker_count > 0:
|
||||||
|
total_expected_notifications += tracker_count
|
||||||
|
players_with_trackers.append((player_username, tracker_count))
|
||||||
|
|
||||||
|
# Показываем детали для первых 10
|
||||||
|
if len(players_with_trackers) <= 10:
|
||||||
|
print(f" • {player_username}: {tracker_count} пользователь(ей) отслеживает")
|
||||||
|
|
||||||
|
if len(players_with_trackers) > 10:
|
||||||
|
print(f" ... и еще {len(players_with_trackers) - 10} игроков с отслеживающими пользователями")
|
||||||
|
|
||||||
|
print(f"\n4. ИТОГО:")
|
||||||
|
print(f" - Активных игроков (играли сегодня): {len(active_players)}")
|
||||||
|
print(f" - Из них отслеживаются пользователями: {len(players_with_trackers)}")
|
||||||
|
print(f" - Ожидаемое количество уведомлений: {total_expected_notifications}")
|
||||||
|
print(f" - Фактически отправлено уведомлений: 27-28")
|
||||||
|
print(f" - ПРОПУЩЕНО уведомлений: {total_expected_notifications - 27}")
|
||||||
|
|
||||||
|
# 5. Проверяем распределение периодов
|
||||||
|
print(f"\n5. Распределение периодов уведомлений:")
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT period_minutes, COUNT(*)
|
||||||
|
FROM user_gamers
|
||||||
|
WHERE period_minutes > 0
|
||||||
|
GROUP BY period_minutes
|
||||||
|
ORDER BY period_minutes
|
||||||
|
""")
|
||||||
|
for period, count in cursor.fetchall():
|
||||||
|
period_hours = period / 60
|
||||||
|
print(f" - {period} минут ({period_hours:.1f} ч): {count} пар user-gamer")
|
||||||
|
|
||||||
|
# 6. Проверяем, есть ли игроки с несколькими отслеживающими
|
||||||
|
print(f"\n6. Игроки, отслеживаемые несколькими пользователями:")
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT g.username, COUNT(*) as tracker_count
|
||||||
|
FROM user_gamers ug
|
||||||
|
JOIN gamers g ON ug.gamer_id = g.id
|
||||||
|
WHERE ug.period_minutes > 0
|
||||||
|
GROUP BY g.id, g.username
|
||||||
|
HAVING tracker_count > 1
|
||||||
|
ORDER BY tracker_count DESC
|
||||||
|
LIMIT 10
|
||||||
|
""")
|
||||||
|
for username, count in cursor.fetchall():
|
||||||
|
print(f" - {username}: {count} пользователей")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("ВЫВОД:")
|
||||||
|
print("="*80)
|
||||||
|
print(f"Если ожидалось {total_expected_notifications} уведомлений, а отправлено только 27-28,")
|
||||||
|
print(f"то пропущено примерно {total_expected_notifications - 27} уведомлений.")
|
||||||
|
print("Возможные причины:")
|
||||||
|
print("1. Периодические задачи не запускаются для всех пар user-gamer")
|
||||||
|
print("2. Логика проверки активности работает неправильно")
|
||||||
|
print("3. Уведомления не отправляются всем пользователям, отслеживающим одного игрока")
|
||||||
|
|
||||||
230
check_today_activity.py
Normal file
230
check_today_activity.py
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Скрипт для проверки активности всех отслеживаемых игроков за сегодня.
|
||||||
|
Делает запросы к Lichess API с задержкой 200 мс между запросами.
|
||||||
|
"""
|
||||||
|
import sqlite3
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_tracked_players(db_path: str) -> List[str]:
|
||||||
|
"""Получить список всех уникальных отслеживаемых игроков из базы данных"""
|
||||||
|
try:
|
||||||
|
with sqlite3.connect(db_path) as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
# Получаем все уникальные username из таблицы gamers
|
||||||
|
cursor.execute("SELECT DISTINCT username FROM gamers ORDER BY username")
|
||||||
|
players = [row[0] for row in cursor.fetchall()]
|
||||||
|
return players
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при получении списка игроков из базы: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_today_timestamps() -> tuple[int, int]:
|
||||||
|
"""Получить начало и конец сегодняшнего дня в миллисекундах"""
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
# Начало сегодняшнего дня (00:00:00 UTC)
|
||||||
|
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
# Конец сегодняшнего дня (23:59:59.999 UTC)
|
||||||
|
today_end = now.replace(hour=23, minute=59, second=59, microsecond=999000)
|
||||||
|
|
||||||
|
# Конвертируем в миллисекунды (Unix timestamp * 1000)
|
||||||
|
since_ms = int(today_start.timestamp() * 1000)
|
||||||
|
until_ms = int(today_end.timestamp() * 1000)
|
||||||
|
|
||||||
|
return since_ms, until_ms
|
||||||
|
|
||||||
|
|
||||||
|
class RateLimiter:
|
||||||
|
"""Простой rate limiter для синхронных запросов"""
|
||||||
|
def __init__(self, min_delay: float = 1.0):
|
||||||
|
self.min_delay = min_delay
|
||||||
|
self.last_request_time = None
|
||||||
|
|
||||||
|
def wait_before_request(self):
|
||||||
|
"""Ждать перед запросом если нужно соблюсти минимальную задержку"""
|
||||||
|
if self.last_request_time is not None:
|
||||||
|
elapsed = time.time() - self.last_request_time
|
||||||
|
if elapsed < self.min_delay:
|
||||||
|
wait_time = self.min_delay - elapsed
|
||||||
|
logger.debug(f"Ждем {wait_time:.2f} сек перед следующим запросом")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
def wait_after_response(self):
|
||||||
|
"""Ждать после получения ответа чтобы соблюсти минимальную задержку между запросами"""
|
||||||
|
self.last_request_time = time.time()
|
||||||
|
# Всегда ждем 1 секунду после получения ответа перед следующим запросом
|
||||||
|
time.sleep(self.min_delay)
|
||||||
|
|
||||||
|
|
||||||
|
def get_games_of_period(rate_limiter: RateLimiter, username: str, since_ms: int, until_ms: int,
|
||||||
|
rated_only: bool = False) -> Optional[List[Dict[str, Any]]]:
|
||||||
|
"""Получить игры пользователя за период напрямую из Lichess API"""
|
||||||
|
# Ждем перед запросом если нужно (для первого запроса не ждем)
|
||||||
|
rate_limiter.wait_before_request()
|
||||||
|
|
||||||
|
url = f"https://lichess.org/api/games/user/{username}"
|
||||||
|
params = {
|
||||||
|
'since': since_ms,
|
||||||
|
'until': until_ms,
|
||||||
|
'max': 1000
|
||||||
|
}
|
||||||
|
if rated_only:
|
||||||
|
params['rated'] = 'true'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/x-ndjson'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Делаем запрос
|
||||||
|
response = requests.get(url, params=params, headers=headers, timeout=30)
|
||||||
|
|
||||||
|
# Получаем ответ и парсим
|
||||||
|
if response.status_code == 404:
|
||||||
|
logger.warning(f"Пользователь {username} не найден (404)")
|
||||||
|
rate_limiter.wait_after_response() # Ждем 1 сек после ответа
|
||||||
|
return None
|
||||||
|
elif response.status_code != 200:
|
||||||
|
logger.error(f"Ошибка API для {username}: {response.status_code} - {response.text[:200]}")
|
||||||
|
rate_limiter.wait_after_response() # Ждем 1 сек после ответа
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Парсим NDJSON
|
||||||
|
games = []
|
||||||
|
content = response.text.strip()
|
||||||
|
if content:
|
||||||
|
for line in content.split('\n'):
|
||||||
|
if line.strip():
|
||||||
|
try:
|
||||||
|
game = json.loads(line)
|
||||||
|
games.append(game)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.warning(f"Ошибка парсинга JSON для {username}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Ждем 1 секунду после получения ответа перед следующим запросом
|
||||||
|
rate_limiter.wait_after_response()
|
||||||
|
return games
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при запросе игр для {username}: {e}")
|
||||||
|
rate_limiter.wait_after_response() # Ждем 1 сек даже при ошибке
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_player_activity(rate_limiter: RateLimiter, username: str, since_ms: int, until_ms: int) -> bool:
|
||||||
|
"""Проверить, играл ли игрок сегодня"""
|
||||||
|
try:
|
||||||
|
games = get_games_of_period(rate_limiter, username, since_ms, until_ms, rated_only=False)
|
||||||
|
if games is None:
|
||||||
|
# Пользователь не найден или ошибка API
|
||||||
|
return False
|
||||||
|
return len(games) > 0
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при проверке игрока {username}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Основная функция скрипта"""
|
||||||
|
# Определяем путь к базе данных
|
||||||
|
db_path = os.getenv("DATABASE_PATH")
|
||||||
|
if not db_path:
|
||||||
|
# Пробуем найти базу в стандартных местах
|
||||||
|
possible_paths = [
|
||||||
|
"LichessClientTG_bot/data/lichess_bot.db",
|
||||||
|
"data/lichess_bot.db",
|
||||||
|
"/app/data/lichess_bot.db"
|
||||||
|
]
|
||||||
|
for path in possible_paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
db_path = path
|
||||||
|
break
|
||||||
|
|
||||||
|
if not db_path:
|
||||||
|
logger.error("Не удалось найти базу данных. Укажите DATABASE_PATH в переменных окружения.")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(f"Используется база данных: {db_path}")
|
||||||
|
|
||||||
|
# Получаем список всех игроков
|
||||||
|
players = get_all_tracked_players(db_path)
|
||||||
|
total_players = len(players)
|
||||||
|
|
||||||
|
if total_players == 0:
|
||||||
|
logger.warning("В базе данных не найдено отслеживаемых игроков")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(f"Найдено отслеживаемых игроков: {total_players}")
|
||||||
|
|
||||||
|
# Получаем временные метки для сегодняшнего дня
|
||||||
|
since_ms, until_ms = get_today_timestamps()
|
||||||
|
today_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||||
|
logger.info(f"Проверяем активность за {today_str} (с {since_ms} по {until_ms})")
|
||||||
|
|
||||||
|
# Создаем rate limiter (10 секунд)
|
||||||
|
rate_limiter = RateLimiter(min_delay=10.0)
|
||||||
|
|
||||||
|
# Проверяем каждого игрока
|
||||||
|
active_players = []
|
||||||
|
inactive_players = []
|
||||||
|
error_players = []
|
||||||
|
|
||||||
|
logger.info("Начинаем проверку игроков...")
|
||||||
|
|
||||||
|
for i, username in enumerate(players, 1):
|
||||||
|
logger.info(f"[{i}/{total_players}] Проверяем {username}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
is_active = check_player_activity(rate_limiter, username, since_ms, until_ms)
|
||||||
|
if is_active:
|
||||||
|
active_players.append(username)
|
||||||
|
logger.info(f" ✓ {username} играл сегодня")
|
||||||
|
else:
|
||||||
|
inactive_players.append(username)
|
||||||
|
logger.info(f" ✗ {username} не играл сегодня")
|
||||||
|
except Exception as e:
|
||||||
|
error_players.append((username, str(e)))
|
||||||
|
logger.error(f" ✗ Ошибка при проверке {username}: {e}")
|
||||||
|
|
||||||
|
# Выводим итоговую статистику
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("ИТОГОВАЯ СТАТИСТИКА")
|
||||||
|
print("="*60)
|
||||||
|
print(f"Всего отслеживаемых игроков: {total_players}")
|
||||||
|
print(f"Играли сегодня: {len(active_players)}")
|
||||||
|
print(f"Не играли сегодня: {len(inactive_players)}")
|
||||||
|
if error_players:
|
||||||
|
print(f"Ошибки при проверке: {len(error_players)}")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
if active_players:
|
||||||
|
print(f"\nИгроки, которые играли сегодня ({len(active_players)}):")
|
||||||
|
for username in active_players:
|
||||||
|
print(f" • {username}")
|
||||||
|
|
||||||
|
if error_players:
|
||||||
|
print(f"\nИгроки с ошибками ({len(error_players)}):")
|
||||||
|
for username, error in error_players:
|
||||||
|
print(f" • {username}: {error}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue