add statistics
This commit is contained in:
parent
23de80f94d
commit
ceb62b408a
6 changed files with 318 additions and 3 deletions
|
|
@ -11,6 +11,7 @@ from telegram.ext import Application, CommandHandler, ContextTypes
|
|||
|
||||
from config import ADMINPANEL_TELEGRAM_BOT_TOKEN, DATABASE_PATH
|
||||
from database import Database
|
||||
from message_counters import MessageCounters
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
|
|
@ -24,6 +25,7 @@ class AdminBot:
|
|||
def __init__(self):
|
||||
self.db = Database()
|
||||
self.application = None
|
||||
self.counters = MessageCounters()
|
||||
|
||||
async def send_notification(self, message: str):
|
||||
"""Send notification to admin chat"""
|
||||
|
|
@ -115,10 +117,30 @@ class AdminBot:
|
|||
|
||||
conn.close()
|
||||
|
||||
# Get message counters statistics
|
||||
stats = self.counters.get_stats_summary()
|
||||
|
||||
# Filter out commands that should not be displayed
|
||||
excluded_commands = {'lang', 'resetlang', 'start'}
|
||||
filtered_stats = {
|
||||
cmd: data for cmd, data in stats['by_command'].items()
|
||||
if cmd not in excluded_commands
|
||||
}
|
||||
|
||||
# Format message counters
|
||||
counters_text = "\n".join([
|
||||
f" • {cmd}: {data['total']} (сегодня: {data['today']})"
|
||||
for cmd, data in sorted(filtered_stats.items())
|
||||
])
|
||||
|
||||
message = (
|
||||
f"📊 <b>Статистика базы данных</b>\n\n"
|
||||
f"👥 Пользователей Telegram: {users_count}\n"
|
||||
f"🎮 Отслеживаемых игроков: {gamers_count}"
|
||||
f"🎮 Отслеживаемых игроков: {gamers_count}\n\n"
|
||||
f"📨 <b>Счетчики сообщений</b>\n\n"
|
||||
f"Всего отправлено: <b>{stats['total_all_time']}</b>\n"
|
||||
f"Сегодня отправлено: <b>{stats['total_today']}</b>\n\n"
|
||||
f"<b>По командам:</b>\n{counters_text}"
|
||||
)
|
||||
await update.message.reply_text(message, parse_mode='HTML')
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from lichess_api import LichessAPI
|
|||
from formatters import StatsFormatter
|
||||
from i18n import t
|
||||
from admin_bot import get_admin_bot, init_admin_bot
|
||||
from message_counters import MessageCounters
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
|
|
@ -38,6 +39,7 @@ class LichessBot:
|
|||
self.periodic_tasks = {} # Store periodic tasks
|
||||
self.period_start_times = {} # Store start times for each gamer
|
||||
self.application = None # Will be set when application is created
|
||||
self.counters = MessageCounters() # Message counters
|
||||
|
||||
def get_user_language_from_update(self, update: Update) -> str:
|
||||
"""Always return English language"""
|
||||
|
|
@ -65,9 +67,38 @@ class LichessBot:
|
|||
# Start periodic task with user_id and gamer
|
||||
await self.start_periodic_task(gamer, user_id, gamer['period_minutes'])
|
||||
logger.info(f"Started periodic task for {gamer['username']} (user {user_id}) with period {gamer['period_minutes']} minutes")
|
||||
|
||||
# Start daily counter reset task
|
||||
asyncio.create_task(self.daily_counter_reset_task())
|
||||
logger.info("Started daily counter reset task")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error starting existing periodic tasks: {e}")
|
||||
|
||||
async def daily_counter_reset_task(self):
|
||||
"""Background task to reset daily counters at midnight"""
|
||||
while True:
|
||||
try:
|
||||
# Calculate seconds until next midnight
|
||||
now = datetime.now()
|
||||
next_midnight = (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
seconds_until_midnight = (next_midnight - now).total_seconds()
|
||||
|
||||
logger.info(f"Daily counter reset task: waiting {seconds_until_midnight} seconds until next midnight")
|
||||
await asyncio.sleep(seconds_until_midnight)
|
||||
|
||||
# Reset daily counters
|
||||
self.counters._reset_daily_counters_if_needed()
|
||||
logger.info("Daily counters reset at midnight")
|
||||
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Error in daily counter reset task: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
# Wait 1 hour before retrying
|
||||
await asyncio.sleep(3600)
|
||||
|
||||
async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Start command handler"""
|
||||
|
|
@ -96,6 +127,7 @@ class LichessBot:
|
|||
lang = self.get_user_language_from_update(update)
|
||||
start_msg = t('start_message', lang)
|
||||
await update.message.reply_text(start_msg)
|
||||
self.counters.increment('start')
|
||||
|
||||
async def start_and_addgamer(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Start command that shows welcome message and starts addgamer conversation"""
|
||||
|
|
@ -121,6 +153,7 @@ class LichessBot:
|
|||
try:
|
||||
await update.message.reply_text(t('addgamer_prompt', lang))
|
||||
logger.info(f"Addgamer prompt sent to user {update.effective_user.id}")
|
||||
self.counters.increment('addgamer')
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending addgamer prompt: {e}")
|
||||
import traceback
|
||||
|
|
@ -131,6 +164,7 @@ class LichessBot:
|
|||
"""Start addtoken command - token required"""
|
||||
lang = self.get_user_language_from_update(update)
|
||||
await update.message.reply_text(t('addtoken_prompt', lang))
|
||||
self.counters.increment('addtoken')
|
||||
return WAITING_FOR_TOKEN
|
||||
|
||||
async def handle_token(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
|
|
@ -283,8 +317,11 @@ class LichessBot:
|
|||
lang = self.get_user_language_from_update(update)
|
||||
if not gamers:
|
||||
await update.message.reply_text(t('no_gamers', lang))
|
||||
self.counters.increment('getgamers')
|
||||
return
|
||||
|
||||
self.counters.increment('getgamers')
|
||||
|
||||
# Show loading message
|
||||
loading_msg = await update.message.reply_text(t('loading_ratings', lang))
|
||||
|
||||
|
|
@ -431,8 +468,11 @@ class LichessBot:
|
|||
lang = self.get_user_language_from_update(update)
|
||||
if not gamers:
|
||||
await update.message.reply_text(t('no_gamers_to_delete', lang))
|
||||
self.counters.increment('delgamer')
|
||||
return
|
||||
|
||||
self.counters.increment('delgamer')
|
||||
|
||||
# Show loading message
|
||||
loading_msg = await update.message.reply_text(t('loading_gamers', lang))
|
||||
|
||||
|
|
@ -563,6 +603,14 @@ class LichessBot:
|
|||
# Format and send response
|
||||
formatted_response = StatsFormatter.format_stats_response(data, username, period, lang)
|
||||
await update.message.reply_text(formatted_response)
|
||||
|
||||
# Increment counter for the period command
|
||||
if period == "today":
|
||||
self.counters.increment('today')
|
||||
elif period == "yesterday":
|
||||
self.counters.increment('yesterday')
|
||||
elif period == "week":
|
||||
self.counters.increment('week')
|
||||
|
||||
async def today(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Today command"""
|
||||
|
|
@ -603,6 +651,7 @@ class LichessBot:
|
|||
t('select_period', lang, username=active_gamer['username']),
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
self.counters.increment('setperiod')
|
||||
|
||||
async def select_period(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle period selection"""
|
||||
|
|
@ -668,6 +717,7 @@ class LichessBot:
|
|||
f"Bot uses English language only."
|
||||
)
|
||||
await update.message.reply_text(message)
|
||||
self.counters.increment('lang')
|
||||
else:
|
||||
await update.message.reply_text("❌ Failed to get user information")
|
||||
|
||||
|
|
@ -689,6 +739,7 @@ class LichessBot:
|
|||
await update.message.reply_text(
|
||||
"✅ Language reset! Bot uses English language only."
|
||||
)
|
||||
self.counters.increment('resetlang')
|
||||
else:
|
||||
await update.message.reply_text("❌ Failed to get user information")
|
||||
|
||||
|
|
@ -799,6 +850,8 @@ class LichessBot:
|
|||
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}")
|
||||
# Не обновляем время начала при ошибке отправки
|
||||
|
|
|
|||
|
|
@ -76,6 +76,26 @@ class Database:
|
|||
)
|
||||
''')
|
||||
|
||||
# Create message_counters table for tracking sent messages
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS message_counters (
|
||||
command TEXT PRIMARY KEY,
|
||||
total_count INTEGER DEFAULT 0,
|
||||
today_count INTEGER DEFAULT 0,
|
||||
last_reset_date DATE DEFAULT CURRENT_DATE
|
||||
)
|
||||
''')
|
||||
|
||||
# Initialize counters for all commands
|
||||
commands = ['start', 'addgamer', 'addtoken', 'getgamers', 'delgamer',
|
||||
'today', 'yesterday', 'week', 'setperiod', 'lang', 'resetlang',
|
||||
'periodic_notification']
|
||||
for cmd in commands:
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO message_counters (command, total_count, today_count, last_reset_date)
|
||||
VALUES (?, 0, 0, CURRENT_DATE)
|
||||
''', (cmd,))
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Migrate tokens from gamers to user_gamers if needed
|
||||
|
|
|
|||
136
LichessClientTG_bot/message_counters.py
Normal file
136
LichessClientTG_bot/message_counters.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
"""
|
||||
Module for tracking message counters in Telegram bot
|
||||
"""
|
||||
import sqlite3
|
||||
import logging
|
||||
from datetime import datetime, date
|
||||
from typing import Dict, Any
|
||||
from config import DATABASE_PATH
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MessageCounters:
|
||||
"""Class for managing message counters"""
|
||||
|
||||
def __init__(self, db_path: str = DATABASE_PATH):
|
||||
self.db_path = db_path
|
||||
self._ensure_counters_exist()
|
||||
self._reset_daily_counters_if_needed()
|
||||
|
||||
def _ensure_counters_exist(self):
|
||||
"""Ensure all command counters exist in database"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
commands = ['start', 'addgamer', 'addtoken', 'getgamers', 'delgamer',
|
||||
'today', 'yesterday', 'week', 'setperiod', 'lang', 'resetlang',
|
||||
'periodic_notification']
|
||||
for cmd in commands:
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO message_counters (command, total_count, today_count, last_reset_date)
|
||||
VALUES (?, 0, 0, CURRENT_DATE)
|
||||
''', (cmd,))
|
||||
conn.commit()
|
||||
|
||||
def _reset_daily_counters_if_needed(self):
|
||||
"""Reset daily counters if it's a new day"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
today = date.today()
|
||||
|
||||
# Check all counters and reset if needed
|
||||
cursor.execute('''
|
||||
SELECT command, last_reset_date FROM message_counters
|
||||
''')
|
||||
|
||||
for row in cursor.fetchall():
|
||||
cmd, last_reset = row[0], row[1]
|
||||
if last_reset:
|
||||
try:
|
||||
last_reset_date = datetime.strptime(last_reset, '%Y-%m-%d').date()
|
||||
if last_reset_date < today:
|
||||
cursor.execute('''
|
||||
UPDATE message_counters
|
||||
SET today_count = 0, last_reset_date = ?
|
||||
WHERE command = ?
|
||||
''', (today.isoformat(), cmd))
|
||||
except (ValueError, TypeError):
|
||||
# Invalid date format, reset it
|
||||
cursor.execute('''
|
||||
UPDATE message_counters
|
||||
SET today_count = 0, last_reset_date = ?
|
||||
WHERE command = ?
|
||||
''', (today.isoformat(), cmd))
|
||||
else:
|
||||
# No reset date, set it to today
|
||||
cursor.execute('''
|
||||
UPDATE message_counters
|
||||
SET last_reset_date = ?
|
||||
WHERE command = ?
|
||||
''', (today.isoformat(), cmd))
|
||||
|
||||
conn.commit()
|
||||
|
||||
def increment(self, command: str):
|
||||
"""Increment counter for a command"""
|
||||
self._reset_daily_counters_if_needed()
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Ensure counter exists
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO message_counters (command, total_count, today_count, last_reset_date)
|
||||
VALUES (?, 0, 0, CURRENT_DATE)
|
||||
''', (command,))
|
||||
|
||||
# Increment both counters
|
||||
cursor.execute('''
|
||||
UPDATE message_counters
|
||||
SET total_count = total_count + 1,
|
||||
today_count = today_count + 1
|
||||
WHERE command = ?
|
||||
''', (command,))
|
||||
|
||||
conn.commit()
|
||||
|
||||
def get_all_stats(self) -> Dict[str, Any]:
|
||||
"""Get all statistics"""
|
||||
self._reset_daily_counters_if_needed()
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT command, total_count, today_count
|
||||
FROM message_counters
|
||||
ORDER BY command
|
||||
''')
|
||||
|
||||
stats = {
|
||||
'by_command': {},
|
||||
'total_all_time': 0,
|
||||
'total_today': 0
|
||||
}
|
||||
|
||||
for row in cursor.fetchall():
|
||||
cmd, total, today = row[0], row[1], row[2]
|
||||
stats['by_command'][cmd] = {
|
||||
'total': total,
|
||||
'today': today
|
||||
}
|
||||
stats['total_all_time'] += total
|
||||
stats['total_today'] += today
|
||||
|
||||
return stats
|
||||
|
||||
def get_stats_summary(self) -> Dict[str, Any]:
|
||||
"""Get summary statistics for display"""
|
||||
stats = self.get_all_stats()
|
||||
|
||||
return {
|
||||
'total_all_time': stats['total_all_time'],
|
||||
'total_today': stats['total_today'],
|
||||
'by_command': stats['by_command']
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
from flask import Flask, jsonify, render_template
|
||||
from flask_cors import CORS
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, date
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
|
@ -9,6 +11,14 @@ CORS(app)
|
|||
# Путь к базе данных бота
|
||||
DB_PATH = "/app/data/lichess_bot.db"
|
||||
|
||||
# Add parent directory to path to import message_counters
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'LichessClientTG_bot'))
|
||||
try:
|
||||
from message_counters import MessageCounters
|
||||
except ImportError:
|
||||
# Fallback if import fails
|
||||
MessageCounters = None
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Главная страница"""
|
||||
|
|
@ -60,11 +70,32 @@ def get_users():
|
|||
''')
|
||||
total_gamers = cursor.fetchone()[0]
|
||||
|
||||
# Get message counters statistics
|
||||
message_stats = {}
|
||||
if MessageCounters:
|
||||
try:
|
||||
counters = MessageCounters(db_path=DB_PATH)
|
||||
message_stats = counters.get_stats_summary()
|
||||
except Exception as e:
|
||||
print(f"Error getting message counters: {e}")
|
||||
message_stats = {
|
||||
'total_all_time': 0,
|
||||
'total_today': 0,
|
||||
'by_command': {}
|
||||
}
|
||||
else:
|
||||
message_stats = {
|
||||
'total_all_time': 0,
|
||||
'total_today': 0,
|
||||
'by_command': {}
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'users': users,
|
||||
'total_users': len(users),
|
||||
'total_gamers': total_gamers
|
||||
'total_gamers': total_gamers,
|
||||
'message_stats': message_stats
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -250,6 +250,20 @@
|
|||
Кол-во игроков: <strong id="total-gamers">0</strong>
|
||||
</div>
|
||||
|
||||
<div class="stats" style="margin-top: 15px; padding: 15px; background: #f5f5f5; border-radius: 8px;">
|
||||
<h3 style="margin-top: 0; margin-bottom: 10px; font-size: 16px;">📨 Счетчики сообщений</h3>
|
||||
<div style="margin-bottom: 8px;">
|
||||
Всего отправлено: <strong id="total-messages-all">0</strong>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px;">
|
||||
Сегодня отправлено: <strong id="total-messages-today">0</strong>
|
||||
</div>
|
||||
<div id="message-stats-by-command" style="margin-top: 10px; font-size: 12px; color: #666;">
|
||||
<div style="margin-bottom: 5px;"><strong>По командам:</strong></div>
|
||||
<div id="command-stats-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-box">
|
||||
<input type="text" id="search-input" placeholder="Поиск по имени или никнейму...">
|
||||
</div>
|
||||
|
|
@ -292,6 +306,45 @@
|
|||
users = data.users;
|
||||
document.getElementById('total-users').textContent = data.total_users;
|
||||
document.getElementById('total-gamers').textContent = data.total_gamers;
|
||||
|
||||
// Update message counters
|
||||
if (data.message_stats) {
|
||||
document.getElementById('total-messages-all').textContent = data.message_stats.total_all_time || 0;
|
||||
document.getElementById('total-messages-today').textContent = data.message_stats.total_today || 0;
|
||||
|
||||
// Render command stats
|
||||
const commandStatsList = document.getElementById('command-stats-list');
|
||||
if (data.message_stats.by_command && Object.keys(data.message_stats.by_command).length > 0) {
|
||||
const commandNames = {
|
||||
'addgamer': 'Add Gamer',
|
||||
'addtoken': 'Add Token',
|
||||
'getgamers': 'Get Gamers',
|
||||
'delgamer': 'Del Gamer',
|
||||
'today': 'Today',
|
||||
'yesterday': 'Yesterday',
|
||||
'week': 'Week',
|
||||
'setperiod': 'Set Period',
|
||||
'periodic_notification': 'Periodic Notifications'
|
||||
};
|
||||
|
||||
// Filter out excluded commands
|
||||
const excludedCommands = ['start', 'lang', 'resetlang'];
|
||||
const filteredCommands = Object.entries(data.message_stats.by_command)
|
||||
.filter(([cmd]) => !excludedCommands.includes(cmd));
|
||||
|
||||
commandStatsList.innerHTML = filteredCommands
|
||||
.sort((a, b) => b[1].total - a[1].total)
|
||||
.map(([cmd, stats]) => {
|
||||
const cmdName = commandNames[cmd] || cmd;
|
||||
return `<div style="margin-bottom: 3px;">
|
||||
${cmdName}: <strong>${stats.total}</strong> (сегодня: <strong>${stats.today}</strong>)
|
||||
</div>`;
|
||||
}).join('');
|
||||
} else {
|
||||
commandStatsList.innerHTML = '<div style="color: #999;">Нет данных</div>';
|
||||
}
|
||||
}
|
||||
|
||||
filteredUsers = users;
|
||||
renderUsers();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue