diff --git a/.gitignore b/.gitignore index 7be293e..05b53ae 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,7 @@ env/ venv/ stats.json instagram_cookies.txt +*.db +*.db-journal +data/ diff --git a/bot.py b/bot.py index a880fd8..c97273b 100644 --- a/bot.py +++ b/bot.py @@ -3,8 +3,10 @@ import re import json import logging import asyncio +import sqlite3 from pathlib import Path from urllib.parse import urlparse +from datetime import datetime import yt_dlp from telegram import Update @@ -21,54 +23,123 @@ logger = logging.getLogger(__name__) TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN') TELEGRAM_BOT_USERNAME = os.getenv('TELEGRAM_BOT_USERNAME', 'vrubelVideoDownload_bot') +# Базовая директория проекта (абсолютный путь), чтобы не зависеть от рабочей директории процесса +BASE_DIR = Path(__file__).resolve().parent + # Директория для временных файлов -DOWNLOADS_DIR = Path('video') -DOWNLOADS_DIR.mkdir(exist_ok=True) +DOWNLOADS_DIR = BASE_DIR / 'video' +DOWNLOADS_DIR.mkdir(parents=True, exist_ok=True) -# Файл для хранения статистики -STATS_FILE = Path('stats.json') +# База данных (внутри папки data) +DATA_DIR = BASE_DIR / 'data' +DATA_DIR.mkdir(parents=True, exist_ok=True) +DB_FILE = DATA_DIR / 'bot.db' -def load_stats() -> dict: - """Загружает статистику из файла""" - if STATS_FILE.exists(): - try: - with open(STATS_FILE, 'r', encoding='utf-8') as f: - stats = json.load(f) - # Обеспечиваем обратную совместимость - if 'users' not in stats: - stats['users'] = [] - return stats - except Exception as e: - logger.error(f"Ошибка при загрузке статистики: {e}") - return {'total_downloads': 0, 'users': []} - return {'total_downloads': 0, 'users': []} - -def save_stats(stats: dict): - """Сохраняет статистику в файл""" +def init_database(): + """Инициализирует базу данных и создает таблицы если их нет""" try: - with open(STATS_FILE, 'w', encoding='utf-8') as f: - json.dump(stats, f, ensure_ascii=False, indent=2) + conn = sqlite3.connect(str(DB_FILE)) + cursor = conn.cursor() + + # Таблица пользователей + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + chat_id INTEGER PRIMARY KEY, + username TEXT, + first_name TEXT, + first_seen TEXT NOT NULL, + last_seen TEXT NOT NULL + ) + ''') + + # Таблица статистики + cursor.execute(''' + CREATE TABLE IF NOT EXISTS stats ( + id INTEGER PRIMARY KEY CHECK (id = 1), + total_downloads INTEGER DEFAULT 0 + ) + ''') + + # Инициализируем stats если его нет + cursor.execute('SELECT COUNT(*) FROM stats') + if cursor.fetchone()[0] == 0: + cursor.execute('INSERT INTO stats (id, total_downloads) VALUES (1, 0)') + + conn.commit() + conn.close() + logger.info("База данных инициализирована") except Exception as e: - logger.error(f"Ошибка при сохранении статистики: {e}") + logger.error(f"Ошибка при инициализации базы данных: {e}") + +def get_total_downloads() -> int: + """Возвращает общее количество скачанных видео""" + try: + conn = sqlite3.connect(str(DB_FILE)) + cursor = conn.cursor() + cursor.execute('SELECT total_downloads FROM stats WHERE id = 1') + result = cursor.fetchone() + conn.close() + return result[0] if result else 0 + except Exception as e: + logger.error(f"Ошибка при получении количества скачанных видео: {e}") + return 0 def increment_downloads(): """Увеличивает счетчик скачанных видео""" - stats = load_stats() - stats['total_downloads'] = stats.get('total_downloads', 0) + 1 - save_stats(stats) - logger.info(f"Общее количество скачанных видео: {stats['total_downloads']}") + try: + conn = sqlite3.connect(str(DB_FILE)) + cursor = conn.cursor() + cursor.execute('UPDATE stats SET total_downloads = total_downloads + 1 WHERE id = 1') + conn.commit() + new_total = get_total_downloads() + conn.close() + logger.info(f"Общее количество скачанных видео: {new_total}") + except Exception as e: + logger.error(f"Ошибка при увеличении счетчика скачанных видео: {e}") -def add_user(chat_id: int): - """Добавляет пользователя в список уникальных пользователей""" - stats = load_stats() - users = stats.get('users', []) - # Преобразуем в set для уникальности, затем обратно в list - users_set = set(users) - if chat_id not in users_set: - users_set.add(chat_id) - stats['users'] = list(users_set) - save_stats(stats) - logger.info(f"Добавлен новый пользователь. Всего пользователей: {len(users_set)}") +def get_total_users() -> int: + """Возвращает общее количество уникальных пользователей""" + try: + conn = sqlite3.connect(str(DB_FILE)) + cursor = conn.cursor() + cursor.execute('SELECT COUNT(*) FROM users') + result = cursor.fetchone() + conn.close() + return result[0] if result else 0 + except Exception as e: + logger.error(f"Ошибка при получении количества пользователей: {e}") + return 0 + +def add_user(chat_id: int, username: str = None, first_name: str = None): + """Добавляет пользователя в базу данных или обновляет информацию о нем""" + try: + now = datetime.now().isoformat() + conn = sqlite3.connect(str(DB_FILE)) + cursor = conn.cursor() + + # Проверяем, существует ли пользователь + cursor.execute('SELECT chat_id FROM users WHERE chat_id = ?', (chat_id,)) + exists = cursor.fetchone() + + if exists: + # Обновляем last_seen + cursor.execute( + 'UPDATE users SET last_seen = ?, username = ?, first_name = ? WHERE chat_id = ?', + (now, username, first_name, chat_id) + ) + else: + # Добавляем нового пользователя + cursor.execute( + 'INSERT INTO users (chat_id, username, first_name, first_seen, last_seen) VALUES (?, ?, ?, ?, ?)', + (chat_id, username, first_name, now, now) + ) + total_users = get_total_users() + logger.info(f"Добавлен новый пользователь (chat_id: {chat_id}). Всего пользователей: {total_users}") + + conn.commit() + conn.close() + except Exception as e: + logger.error(f"Ошибка при добавлении пользователя: {e}") def detect_video_source(url: str) -> str: @@ -350,9 +421,11 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): url = update.message.text.strip() chat_id = update.message.chat_id + username = update.message.from_user.username if update.message.from_user else None + first_name = update.message.from_user.first_name if update.message.from_user else None # Добавляем пользователя в статистику при первом взаимодействии - add_user(chat_id) + add_user(chat_id, username, first_name) # Проверяем, является ли сообщение URL if not (url.startswith('http://') or url.startswith('https://')): @@ -414,7 +487,9 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обрабатывает команду /start""" # Добавляем пользователя в статистику chat_id = update.message.chat_id - add_user(chat_id) + username = update.message.from_user.username if update.message.from_user else None + first_name = update.message.from_user.first_name if update.message.from_user else None + add_user(chat_id, username, first_name) await update.message.reply_text( "👋 Привет! Я бот для скачивания видео.\n\n" @@ -432,10 +507,8 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): async def stat_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обрабатывает команду /stat""" - stats = load_stats() - total_downloads = stats.get('total_downloads', 0) - users = stats.get('users', []) - total_users = len(users) + total_downloads = get_total_downloads() + total_users = get_total_users() await update.message.reply_text( f"📊 Статистика бота:\n\n" @@ -450,6 +523,9 @@ def main(): logger.error("TELEGRAM_BOT_TOKEN не установлен!") return + # Инициализируем базу данных + init_database() + # Создаем приложение application = Application.builder().token(TELEGRAM_BOT_TOKEN).build() diff --git a/docker-compose.yml b/docker-compose.yml index e37e02a..b1d15c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: volumes: - ./video:/app/video - ./instagram_cookies.txt:/app/instagram_cookies.txt - - ./stats.json:/app/stats.json + - ./data:/app/data:Z networks: - bot_network