Spaces:
Running
Running
| # stats_database.py - ПОЛНАЯ ВЕРСИЯ (как database.py) | |
| import sqlite3 | |
| import os | |
| import requests | |
| from datetime import datetime, timedelta | |
| from config import MSK_TZ | |
| # ============================================================ | |
| # НАСТРОЙКА БАКЕТА (как в database.py) | |
| # ============================================================ | |
| HF_TOKEN = os.environ.get("HF_TOKEN") | |
| if not HF_TOKEN: | |
| print("[STATS DB] ❌ НЕТ HF_TOKEN В ПЕРЕМЕННЫХ ОКРУЖЕНИЯ!") | |
| print("[STATS DB] Установи: export HF_TOKEN='твой_токен'") | |
| REPO_ID = "RomanJordansky/Messages-Jordan-manager" | |
| DB_PATH_IN_REPO = "bot_messages.db" | |
| HF_URL = f"https://huggingface.co/{REPO_ID}/resolve/main/{DB_PATH_IN_REPO}" | |
| DATA_DIR = "bot_data" | |
| os.makedirs(DATA_DIR, exist_ok=True) | |
| STATS_DB_PATH = os.path.join(DATA_DIR, "bot_messages.db") | |
| print(f"[STATS DB] База данных: {STATS_DB_PATH}") | |
| # ============================================================ | |
| # СОЗДАНИЕ РЕПОЗИТОРИЯ (ЕСЛИ ЕГО НЕТ) | |
| # ============================================================ | |
| def create_repo_if_not_exists(): | |
| """Создаёт репозиторий на Hugging Face, если его нет""" | |
| if not HF_TOKEN: | |
| print("[STATS DB] ❌ НЕТ HF_TOKEN!") | |
| return False | |
| try: | |
| from huggingface_hub import HfApi, create_repo, repo_exists | |
| if repo_exists(repo_id=REPO_ID, token=HF_TOKEN): | |
| print(f"[STATS DB] ✅ Репозиторий {REPO_ID} уже существует") | |
| return True | |
| create_repo( | |
| repo_id=REPO_ID, | |
| token=HF_TOKEN, | |
| repo_type="model", | |
| private=False, | |
| exist_ok=True | |
| ) | |
| print(f"[STATS DB] ✅ Репозиторий {REPO_ID} создан!") | |
| return True | |
| except Exception as e: | |
| print(f"[STATS DB] ❌ Ошибка создания репозитория: {e}") | |
| return False | |
| # ============================================================ | |
| # ЗАГРУЗКА/СОХРАНЕНИЕ БД (как в database.py) | |
| # ============================================================ | |
| def download_stats_db(): | |
| """Скачивает БД с Hugging Face""" | |
| print("[STATS DB] Попытка загрузить базу данных...") | |
| try: | |
| headers = {"User-Agent": "Mozilla/5.0", "Authorization": f"Bearer {HF_TOKEN}"} | |
| response = requests.get(HF_URL, headers=headers, timeout=30) | |
| if response.status_code == 200: | |
| with open(STATS_DB_PATH, 'wb') as f: | |
| f.write(response.content) | |
| print(f"[STATS DB] ✅ База данных загружена! Размер: {os.path.getsize(STATS_DB_PATH)} байт") | |
| return True | |
| else: | |
| print(f"[STATS DB] БД не найдена (код {response.status_code})") | |
| return False | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка загрузки: {e}") | |
| return False | |
| def upload_stats_db(): | |
| """Загружает БД на Hugging Face""" | |
| if not HF_TOKEN: | |
| print("[STATS DB] ❌ НЕТ HF_TOKEN!") | |
| return False | |
| try: | |
| from huggingface_hub import HfApi | |
| api = HfApi(token=HF_TOKEN) | |
| api.upload_file( | |
| path_or_fileobj=STATS_DB_PATH, | |
| path_in_repo=DB_PATH_IN_REPO, | |
| repo_id=REPO_ID, | |
| repo_type="model", | |
| ) | |
| print(f"[STATS DB] ✅ БД сохранена на HF! Размер: {os.path.getsize(STATS_DB_PATH)} байт") | |
| return True | |
| except Exception as e: | |
| print(f"[STATS DB] ❌ Ошибка сохранения: {e}") | |
| return False | |
| # ============================================================ | |
| # СОЗДАНИЕ РЕПОЗИТОРИЯ И ЗАГРУЗКА БД | |
| # ============================================================ | |
| # Создаём репозиторий, если его нет | |
| create_repo_if_not_exists() | |
| # Пробуем загрузить БД | |
| db_loaded = download_stats_db() | |
| if not db_loaded: | |
| print("[STATS DB] БД НЕ НАЙДЕНА! СОЗДАЮ НОВУЮ...") | |
| # ============================================================ | |
| # ПОДКЛЮЧЕНИЕ К БД СТАТИСТИКИ | |
| # ============================================================ | |
| stats_conn = sqlite3.connect(STATS_DB_PATH, check_same_thread=False) | |
| stats_cursor = stats_conn.cursor() | |
| def init_stats_database(): | |
| """Инициализирует таблицы в базе данных""" | |
| stats_cursor.execute(""" | |
| CREATE TABLE IF NOT EXISTS message_stats ( | |
| chat_id INTEGER, | |
| user_id INTEGER, | |
| date TEXT, | |
| messages_count INTEGER DEFAULT 0, | |
| last_activity TEXT, | |
| PRIMARY KEY (chat_id, user_id, date) | |
| ) | |
| """) | |
| stats_cursor.execute("CREATE INDEX IF NOT EXISTS idx_chat_date ON message_stats (chat_id, date)") | |
| stats_cursor.execute("CREATE INDEX IF NOT EXISTS idx_user ON message_stats (user_id)") | |
| stats_cursor.execute("CREATE INDEX IF NOT EXISTS idx_chat_user ON message_stats (chat_id, user_id)") | |
| stats_cursor.execute(""" | |
| CREATE TABLE IF NOT EXISTS total_stats ( | |
| chat_id INTEGER, | |
| user_id INTEGER, | |
| total_messages INTEGER DEFAULT 0, | |
| last_updated TEXT, | |
| PRIMARY KEY (chat_id, user_id) | |
| ) | |
| """) | |
| try: | |
| stats_cursor.execute("ALTER TABLE message_stats ADD COLUMN last_activity TEXT") | |
| except: | |
| pass | |
| stats_conn.commit() | |
| print("[STATS DB] Таблицы созданы/проверены") | |
| # После инициализации загружаем БД | |
| upload_stats_db() | |
| init_stats_database() | |
| # ============================================================ | |
| # ФУНКЦИИ ДЛЯ РАБОТЫ СО СТАТИСТИКОЙ | |
| # ============================================================ | |
| async def update_message_stats(chat_id: int, user_id: int): | |
| """Обновляет статистику сообщений для пользователя в беседе""" | |
| today = datetime.now(MSK_TZ).strftime("%Y-%m-%d") | |
| now = datetime.now(MSK_TZ).strftime("%Y-%m-%d %H:%M:%S") | |
| try: | |
| # 1. Обновляем дневную статистику | |
| stats_cursor.execute(""" | |
| INSERT INTO message_stats (chat_id, user_id, date, messages_count, last_activity) | |
| VALUES (?, ?, ?, 1, ?) | |
| ON CONFLICT(chat_id, user_id, date) DO UPDATE | |
| SET messages_count = messages_count + 1, | |
| last_activity = ? | |
| """, (chat_id, user_id, today, now, now)) | |
| # 2. Обновляем общую статистику | |
| stats_cursor.execute(""" | |
| INSERT INTO total_stats (chat_id, user_id, total_messages, last_updated) | |
| VALUES (?, ?, 1, ?) | |
| ON CONFLICT(chat_id, user_id) DO UPDATE | |
| SET total_messages = total_messages + 1, | |
| last_updated = ? | |
| """, (chat_id, user_id, now, now)) | |
| stats_conn.commit() | |
| upload_stats_db() | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка обновления статистики: {e}") | |
| async def get_total_messages(chat_id: int, user_id: int) -> int: | |
| """Получает общее количество сообщений пользователя в беседе""" | |
| try: | |
| stats_cursor.execute(""" | |
| SELECT total_messages | |
| FROM total_stats | |
| WHERE chat_id = ? AND user_id = ? | |
| """, (chat_id, user_id)) | |
| result = stats_cursor.fetchone() | |
| if result: | |
| return result[0] | |
| # Если нет в total_stats, считаем из message_stats | |
| stats_cursor.execute(""" | |
| SELECT SUM(messages_count) | |
| FROM message_stats | |
| WHERE chat_id = ? AND user_id = ? | |
| """, (chat_id, user_id)) | |
| result = stats_cursor.fetchone() | |
| total = result[0] if result and result[0] else 0 | |
| # Сохраняем в total_stats | |
| if total > 0: | |
| now = datetime.now(MSK_TZ).strftime("%Y-%m-%d %H:%M:%S") | |
| stats_cursor.execute(""" | |
| INSERT OR REPLACE INTO total_stats (chat_id, user_id, total_messages, last_updated) | |
| VALUES (?, ?, ?, ?) | |
| """, (chat_id, user_id, total, now)) | |
| stats_conn.commit() | |
| upload_stats_db() | |
| return total | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка get_total_messages: {e}") | |
| return 0 | |
| async def get_today_messages(chat_id: int, user_id: int) -> int: | |
| """Получает количество сообщений пользователя за сегодня""" | |
| try: | |
| today = datetime.now(MSK_TZ).strftime("%Y-%m-%d") | |
| stats_cursor.execute(""" | |
| SELECT messages_count | |
| FROM message_stats | |
| WHERE chat_id = ? AND user_id = ? AND date = ? | |
| """, (chat_id, user_id, today)) | |
| result = stats_cursor.fetchone() | |
| return result[0] if result else 0 | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка get_today_messages: {e}") | |
| return 0 | |
| async def get_last_activity(chat_id: int, user_id: int): | |
| """Получает время последней активности пользователя""" | |
| try: | |
| stats_cursor.execute(""" | |
| SELECT last_activity | |
| FROM total_stats | |
| WHERE chat_id = ? AND user_id = ? | |
| """, (chat_id, user_id)) | |
| result = stats_cursor.fetchone() | |
| if result and result[0]: | |
| return result[0] | |
| stats_cursor.execute(""" | |
| SELECT last_activity | |
| FROM message_stats | |
| WHERE chat_id = ? AND user_id = ? | |
| ORDER BY last_activity DESC | |
| LIMIT 1 | |
| """, (chat_id, user_id)) | |
| result = stats_cursor.fetchone() | |
| return result[0] if result and result[0] else None | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка get_last_activity: {e}") | |
| return None | |
| async def get_chat_top(chat_id: int, limit: int = 10) -> list: | |
| """Получает топ пользователей по общему количеству сообщений в беседе""" | |
| try: | |
| stats_cursor.execute(""" | |
| SELECT user_id, total_messages | |
| FROM total_stats | |
| WHERE chat_id = ? | |
| ORDER BY total_messages DESC | |
| LIMIT ? | |
| """, (chat_id, limit)) | |
| return stats_cursor.fetchall() | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка get_chat_top: {e}") | |
| return [] | |
| async def get_chat_top_today(chat_id: int, limit: int = 10) -> list: | |
| """Получает топ пользователей по сообщениям за сегодня""" | |
| try: | |
| today = datetime.now(MSK_TZ).strftime("%Y-%m-%d") | |
| stats_cursor.execute(""" | |
| SELECT user_id, messages_count | |
| FROM message_stats | |
| WHERE chat_id = ? AND date = ? | |
| ORDER BY messages_count DESC | |
| LIMIT ? | |
| """, (chat_id, today, limit)) | |
| return stats_cursor.fetchall() | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка get_chat_top_today: {e}") | |
| return [] | |
| async def clean_old_stats(days: int = 90) -> int: | |
| """Удаляет старые записи из message_stats (старше days дней)""" | |
| try: | |
| cutoff_date = (datetime.now(MSK_TZ) - timedelta(days=days)).strftime("%Y-%m-%d") | |
| stats_cursor.execute("DELETE FROM message_stats WHERE date < ?", (cutoff_date,)) | |
| deleted = stats_cursor.rowcount | |
| stats_conn.commit() | |
| upload_stats_db() | |
| return deleted | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка clean_old_stats: {e}") | |
| return 0 | |
| async def recalc_total_stats(chat_id: int = None): | |
| """Пересчитывает total_stats из message_stats""" | |
| try: | |
| if chat_id: | |
| stats_cursor.execute(""" | |
| SELECT user_id, SUM(messages_count) as total | |
| FROM message_stats | |
| WHERE chat_id = ? | |
| GROUP BY user_id | |
| """, (chat_id,)) | |
| else: | |
| stats_cursor.execute(""" | |
| SELECT user_id, chat_id, SUM(messages_count) as total | |
| FROM message_stats | |
| GROUP BY user_id, chat_id | |
| """) | |
| rows = stats_cursor.fetchall() | |
| now = datetime.now(MSK_TZ).strftime("%Y-%m-%d %H:%M:%S") | |
| for row in rows: | |
| if chat_id: | |
| user_id, total = row | |
| stats_cursor.execute(""" | |
| INSERT OR REPLACE INTO total_stats (chat_id, user_id, total_messages, last_updated) | |
| VALUES (?, ?, ?, ?) | |
| """, (chat_id, user_id, total, now)) | |
| else: | |
| user_id, chat_id, total = row | |
| stats_cursor.execute(""" | |
| INSERT OR REPLACE INTO total_stats (chat_id, user_id, total_messages, last_updated) | |
| VALUES (?, ?, ?, ?) | |
| """, (chat_id, user_id, total, now)) | |
| stats_conn.commit() | |
| upload_stats_db() | |
| return len(rows) | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка recalc_total_stats: {e}") | |
| return 0 | |
| def close_stats_connection(): | |
| """Закрывает соединение с БД статистики""" | |
| try: | |
| upload_stats_db() | |
| stats_conn.close() | |
| print("[STATS DB] Соединение закрыто, БД сохранена") | |
| except Exception as e: | |
| print(f"[STATS DB] Ошибка при закрытии: {e}") | |
| # ============================================================ | |
| # АВТОМАТИЧЕСКОЕ СОХРАНЕНИЕ ПРИ ЗАВЕРШЕНИИ | |
| # ============================================================ | |
| import atexit | |
| atexit.register(close_stats_connection) | |
| print("[STATS DB] База данных статистики инициализирована") |