BOT_JORDANS / stats_database.py
RomanJordansky's picture
Update stats_database.py
5aabb76 verified
Raw
History Blame Contribute Delete
14.4 kB
# 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] База данных статистики инициализирована")