/
AAAAAAAAAA
/
Telegram1
Обзор
Документация
Войти
/
AAAAAAAAAA
/
Telegram1
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
bot.py
1 014 строк
42 KB
AAAAAAAAAA
upload files
05 фев 2026, 15:43
Верифицирован
05 фев 2026, 15:43
21e7500
Код
Авторство
О чём код?
import os import logging import sqlite3 import sys import asyncio import json from datetime import datetime, timedelta from dotenv import load_dotenv from telegram import ( ReplyKeyboardMarkup, KeyboardButton, Update, InlineKeyboardMarkup, InlineKeyboardButton ) from telegram.ext import ( Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler ) # ===== ЗАГРУЗКА ПЕРЕМЕННЫХ ОКРУЖЕНИЯ ===== load_dotenv() BOT_TOKEN = os.getenv("BOT_TOKEN", "8498375927:AAHAN7-tTHJwaBumH71qDs3KabaB5PNoLb0") try: ADMIN_ID = int(os.getenv("ADMIN_ID", "7771463228")) except ValueError: ADMIN_ID = 7771463228 DATABASE_PATH = os.getenv("DATABASE_PATH", "webinar_registrations.db") # ДАННЫЕ ВЕБИНАРА WEBINAR_NAME = "ПОВТОР ЗАКРЫТОГО ВЕБИНАРА 'НОВИНКИ SKOK'" WEBINAR_DATE = "26 февраля 2025" WEBINAR_TIME = "16:00 по МСК" # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) def check_single_instance(): """Проверяет, что запущен только один экземпляр бота""" try: lock_file = "bot.lock" if os.path.exists(lock_file): print("❌ Бот уже запущен! Завершите предыдущий процесс.") print("💡 Если это ошибка, удалите файл: del bot.lock") sys.exit(1) with open(lock_file, 'w') as f: f.write(str(os.getpid())) return lock_file except Exception as e: print(f"Ошибка проверки экземпляров: {e}") sys.exit(1) def cleanup_lock(lock_file): """Удаляет lock-файл при завершении""" try: if os.path.exists(lock_file): os.remove(lock_file) print("🔓 Lock-файл удален") except Exception as e: print(f"Ошибка при удалении lock-файла: {e}") # ===== БАЗА ДАННЫХ ===== def get_db_connection(): """Создает подключение к базе данных""" return sqlite3.connect(DATABASE_PATH, check_same_thread=False) def init_database(): """Инициализация базы данных с полным логированием""" conn = get_db_connection() cursor = conn.cursor() # Таблица регистраций на вебинар cursor.execute(''' CREATE TABLE IF NOT EXISTS registrations ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, first_name TEXT NOT NULL, phone_number TEXT NOT NULL, registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, chat_id INTEGER, webinar_name TEXT DEFAULT "ПОВТОР ЗАКРЫТОГО ВЕБИНАРА", webinar_date TEXT DEFAULT "26 февраля 2025", webinar_time TEXT DEFAULT "16:00 по МСК", UNIQUE(user_id) ) ''') # Таблица логов всех действий пользователей cursor.execute(''' CREATE TABLE IF NOT EXISTS user_actions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, first_name TEXT, action_type TEXT NOT NULL, action_data TEXT, chat_id INTEGER, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') # Таблица рассылок cursor.execute(''' CREATE TABLE IF NOT EXISTS broadcasts ( id INTEGER PRIMARY KEY AUTOINCREMENT, admin_id INTEGER NOT NULL, broadcast_type TEXT NOT NULL, recipients_count INTEGER, success_count INTEGER DEFAULT 0, fail_count INTEGER DEFAULT 0, content_type TEXT, content_preview TEXT, started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, finished_at TIMESTAMP, status TEXT DEFAULT 'pending' ) ''') # Таблица деталей рассылок cursor.execute(''' CREATE TABLE IF NOT EXISTS broadcast_details ( id INTEGER PRIMARY KEY AUTOINCREMENT, broadcast_id INTEGER, user_id INTEGER, status TEXT, error_message TEXT, sent_at TIMESTAMP, FOREIGN KEY (broadcast_id) REFERENCES broadcasts (id) ) ''') # Таблица событий amoCRM cursor.execute(''' CREATE TABLE IF NOT EXISTS amocrm_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, event_type TEXT NOT NULL, lead_id TEXT, contact_id TEXT, data TEXT, received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, processed BOOLEAN DEFAULT FALSE ) ''') # Таблица системных логов cursor.execute(''' CREATE TABLE IF NOT EXISTS system_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, log_level TEXT NOT NULL, message TEXT NOT NULL, source TEXT, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') conn.commit() # Проверяем существующие таблицы cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") tables = cursor.fetchall() print(f"✅ База данных инициализирована") print(f"📊 Таблиц в базе: {len(tables)}") for table in tables: print(f" - {table[0]}") conn.close() # ===== ФУНКЦИИ ЛОГИРОВАНИЯ ===== def log_user_action(user_id, first_name, action_type, action_data=None, chat_id=None): """Логирование действий пользователя""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute(''' INSERT INTO user_actions (user_id, first_name, action_type, action_data, chat_id) VALUES (?, ?, ?, ?, ?) ''', (user_id, first_name, action_type, json.dumps(action_data) if action_data else None, chat_id)) conn.commit() conn.close() logger.info(f"📝 Действие пользователя: {first_name} ({user_id}) - {action_type}") return True except Exception as e: logger.error(f"❌ Ошибка логирования действия: {e}") return False def log_system_event(log_level, message, source=None): """Логирование системных событий""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute(''' INSERT INTO system_logs (log_level, message, source) VALUES (?, ?, ?) ''', (log_level, message, source)) conn.commit() conn.close() if log_level == 'ERROR': print(f"❌ Системная ошибка: {message}") return True except Exception as e: print(f"❌ Ошибка логирования системы: {e}") return False def start_broadcast_log(admin_id, broadcast_type, recipients_count, content_type, content_preview): """Начало логирования рассылки""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute(''' INSERT INTO broadcasts (admin_id, broadcast_type, recipients_count, content_type, content_preview, status) VALUES (?, ?, ?, ?, ?, 'started') ''', (admin_id, broadcast_type, recipients_count, content_type, content_preview)) broadcast_id = cursor.lastrowid conn.commit() conn.close() logger.info(f"📤 Начало рассылки #{broadcast_id}: {broadcast_type}") return broadcast_id except Exception as e: logger.error(f"❌ Ошибка логирования рассылки: {e}") return None def update_broadcast_log(broadcast_id, status, success_count=0, fail_count=0): """Обновление статуса рассылки""" try: conn = get_db_connection() cursor = conn.cursor() if status == 'completed': cursor.execute(''' UPDATE broadcasts SET status = ?, success_count = ?, fail_count = ?, finished_at = CURRENT_TIMESTAMP WHERE id = ? ''', (status, success_count, fail_count, broadcast_id)) else: cursor.execute(''' UPDATE broadcasts SET status = ? WHERE id = ? ''', (status, broadcast_id)) conn.commit() conn.close() return True except Exception as e: logger.error(f"❌ Ошибка обновления рассылки: {e}") return False def log_broadcast_detail(broadcast_id, user_id, status, error_message=None): """Логирование деталей отправки рассылки""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute(''' INSERT INTO broadcast_details (broadcast_id, user_id, status, error_message, sent_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) ''', (broadcast_id, user_id, status, error_message)) conn.commit() conn.close() return True except Exception as e: logger.error(f"❌ Ошибка логирования детали рассылки: {e}") return False def log_amocrm_event(event_type, data, lead_id=None, contact_id=None): """Логирование событий amoCRM""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute(''' INSERT INTO amocrm_events (event_type, lead_id, contact_id, data) VALUES (?, ?, ?, ?) ''', (event_type, lead_id, contact_id, json.dumps(data))) conn.commit() conn.close() logger.info(f"🔔 Событие amoCRM: {event_type} (lead: {lead_id})") return True except Exception as e: logger.error(f"❌ Ошибка логирования amoCRM: {e}") return False # ===== СТАРЫЕ ФУНКЦИИ БАЗЫ ДАННЫХ ===== def clean_duplicates(): """Удаляет дубликаты пользователей""" conn = get_db_connection() cursor = conn.cursor() try: cursor.execute(''' DELETE FROM registrations WHERE id NOT IN ( SELECT MIN(id) FROM registrations GROUP BY user_id ) ''') deleted_count = cursor.rowcount conn.commit() if deleted_count > 0: print(f"✅ Удалено дубликатов: {deleted_count}") except Exception as e: print(f"❌ Ошибка при удалении дубликатов: {e}") conn.close() def get_unique_registrations(): """Получение уникальных регистраций""" conn = get_db_connection() cursor = conn.cursor() cursor.execute('SELECT * FROM registrations ORDER BY registered_at DESC') registrations = cursor.fetchall() conn.close() return registrations def get_users_by_date_range(days): """Получение пользователей за определенное количество дней""" conn = get_db_connection() cursor = conn.cursor() if days > 0: start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') cursor.execute('SELECT * FROM registrations WHERE date(registered_at) >= ? ORDER BY registered_at DESC', (start_date,)) else: cursor.execute('SELECT * FROM registrations ORDER BY registered_at DESC') registrations = cursor.fetchall() conn.close() return registrations def get_all_registrations(): """Получение всех регистраций""" return get_unique_registrations() def get_total_unique_users(): """Получение количества уникальных пользователей""" conn = get_db_connection() cursor = conn.cursor() cursor.execute('SELECT COUNT(DISTINCT user_id) FROM registrations') count = cursor.fetchone()[0] conn.close() return count def add_registration(user_id, first_name, phone_number, chat_id): """Добавление регистрации в базу""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute(''' INSERT OR REPLACE INTO registrations (user_id, first_name, phone_number, chat_id, webinar_name, webinar_date, webinar_time) VALUES (?, ?, ?, ?, ?, ?, ?) ''', (user_id, first_name, phone_number, chat_id, WEBINAR_NAME, WEBINAR_DATE, WEBINAR_TIME)) conn.commit() # Логируем регистрацию log_user_action(user_id, first_name, 'registration', { 'phone': phone_number, 'webinar': WEBINAR_NAME }, chat_id) conn.close() return True except Exception as e: log_system_event('ERROR', f'Ошибка регистрации: {str(e)}', 'add_registration') return False def is_user_registered(user_id): """Проверяет, зарегистрирован ли пользователь""" conn = get_db_connection() cursor = conn.cursor() cursor.execute('SELECT COUNT(*) FROM registrations WHERE user_id = ?', (user_id,)) count = cursor.fetchone()[0] conn.close() return count > 0 # ===== ОБРАБОТЧИК КОНТАКТОВ ===== async def handle_contact(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработчик контактов - регистрация на вебинар""" contact = update.message.contact user = update.effective_user # Логируем попытку регистрации log_user_action(user.id, user.first_name, 'contact_received', { 'phone': contact.phone_number, 'is_self_contact': contact.user_id == user.id }, update.effective_chat.id) if contact.user_id == user.id: if is_user_registered(user.id): await update.message.reply_text( f"✅ Вы уже зарегистрированы на вебинар!\n\n" f"📅 {WEBINAR_DATE}\n⏰ {WEBINAR_TIME}", reply_markup=get_user_keyboard(user.id) ) log_user_action(user.id, user.first_name, 'already_registered', None, update.effective_chat.id) return if add_registration(contact.user_id, contact.first_name, contact.phone_number, update.effective_chat.id): await update.message.reply_text( f"🎉 <b>Спасибо за регистрацию, {contact.first_name}!</b>\n\n" f"📅 <b>Дата:</b> {WEBINAR_DATE}\n" f"⏰ <b>Время:</b> {WEBINAR_TIME}\n\n" f"<i>Мы пришлем вам напоминание перед вебинаром.</i>", parse_mode='HTML', reply_markup=get_user_keyboard(user.id) ) # Уведомление администратору try: await context.bot.send_message( chat_id=ADMIN_ID, text=f"🎯 <b>НОВАЯ РЕГИСТРАЦИЯ!</b>\n\n" f"👤 <b>Имя:</b> {contact.first_name}\n" f"📱 <b>Телефон:</b> {contact.phone_number}\n" f"🆔 <b>User ID:</b> {contact.user_id}", parse_mode='HTML' ) except Exception as e: log_system_event('ERROR', f'Ошибка уведомления админа: {str(e)}', 'handle_contact') else: await update.message.reply_text("❌ Произошла ошибка при регистрации") else: await update.message.reply_text("❌ Пожалуйста, поделитесь своим контактом") # ===== СИСТЕМА РАССЫЛКИ ===== async def start_broadcast(update: Update, context: ContextTypes.DEFAULT_TYPE): """Начало создания рассылки""" user = update.effective_user if user.id != ADMIN_ID: await update.message.reply_text("❌ Эта функция только для администратора") log_user_action(user.id, user.first_name, 'unauthorized_broadcast', None, update.effective_chat.id) return log_user_action(user.id, user.first_name, 'broadcast_started', None, update.effective_chat.id) keyboard = [ [InlineKeyboardButton("👥 ВСЕМ участникам", callback_data="broadcast_all")], [InlineKeyboardButton("📅 За ПОСЛЕДНИЕ 7 дней", callback_data="broadcast_7")], [InlineKeyboardButton("📅 За ПОСЛЕДНИЕ 30 дней", callback_data="broadcast_30")], [InlineKeyboardButton("❌ ОТМЕНА", callback_data="broadcast_cancel")] ] unique_users = get_total_unique_users() week_users = len(get_users_by_date_range(7)) month_users = len(get_users_by_date_range(30)) await update.message.reply_text( f"📢 <b>СОЗДАНИЕ РАССЫЛКИ</b>\n\n" f"<b>Выберите получателей:</b>\n\n" f"👥 <b>Всем участникам:</b> {unique_users} пользователей\n" f"📅 <b>За последние 7 дней:</b> {week_users} пользователей\n" f"📅 <b>За последние 30 дней:</b> {month_users} пользователей", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='HTML' ) async def broadcast_button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработка нажатий на кнопки рассылки""" query = update.callback_query await query.answer() log_user_action(query.from_user.id, query.from_user.first_name, f'broadcast_select_{query.data}', None, query.message.chat_id) if query.data == "broadcast_cancel": await query.edit_message_text("❌ Рассылка отменена") return # Определяем получателей if query.data == "broadcast_all": recipients = get_all_registrations() filter_name = "ВСЕМ участникам" elif query.data == "broadcast_7": recipients = get_users_by_date_range(7) filter_name = "участникам за ПОСЛЕДНИЕ 7 дней" elif query.data == "broadcast_30": recipients = get_users_by_date_range(30) filter_name = "участникам за ПОСЛЕДНИЕ 30 дней" else: return if not recipients: await query.edit_message_text(f"❌ Нет участников для рассылки ({filter_name})", parse_mode='HTML') return # Сохраняем данные в context.user_data context.user_data['broadcast'] = { 'recipients': recipients, 'filter_name': filter_name, 'count': len(recipients), 'step': 'waiting_content' } await query.edit_message_text( f"✅ <b>Выбрано:</b> {filter_name}\n" f"👥 <b>Количество:</b> {len(recipients)} пользователей\n\n" f"📤 <b>Теперь отправьте сообщение для рассылки:</b>\n\n" f"<i>Можно отправить:</i>\n" f"• 📝 <b>Текст</b>\n" f"• 🖼️ <b>Фото</b> (с подписью или без)\n\n" f"<i>Поддерживается HTML-разметка</i>", parse_mode='HTML' ) async def handle_broadcast_content(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработка контента для рассылки""" user = update.effective_user if user.id != ADMIN_ID: return if 'broadcast' not in context.user_data: return data = context.user_data['broadcast'] # Проверяем контент if update.message.photo: photo = update.message.photo[-1] data['photo_id'] = photo.file_id data['caption'] = update.message.caption or "" data['has_photo'] = True content_type = "фото с текстом" if data['caption'] else "фото без текста" content_preview = data['caption'][:100] if data['caption'] else "[ФОТО]" elif update.message.text: data['message'] = update.message.text data['has_photo'] = False content_type = "текст" content_preview = data['message'][:100] else: await update.message.reply_text("❌ Пожалуйста, отправьте текст или фото") return data['step'] = 'confirm' data['content_type'] = content_type data['content_preview'] = content_preview # Логируем создание контента log_user_action(user.id, user.first_name, 'broadcast_content_created', { 'content_type': content_type, 'preview': content_preview, 'recipients_count': data['count'] }, update.effective_chat.id) # Клавиатура подтверждения keyboard = [ [InlineKeyboardButton("🚀 ОТПРАВИТЬ РАССЫЛКУ", callback_data="confirm_send")], [InlineKeyboardButton("✏️ ИЗМЕНИТЬ", callback_data="edit_content")], [InlineKeyboardButton("🔄 ВЫБРАТЬ ДРУГИХ", callback_data="change_recipients")], [InlineKeyboardButton("❌ ОТМЕНА", callback_data="cancel_all")] ] # Показываем предпросмотр if data['has_photo']: if data['caption']: preview = data['caption'][:200] + ("..." if len(data['caption']) > 200 else "") content_info = f"<b>Тип:</b> 🖼️ Фото с текстом\n<b>Текст:</b>\n{preview}" else: content_info = "<b>Тип:</b> 🖼️ Фото без текста" else: preview = data['message'][:200] + ("..." if len(data['message']) > 200 else "") content_info = f"<b>Тип:</b> 📝 Текст\n<b>Текст:</b>\n{preview}" await update.message.reply_text( f"📋 <b>ПРЕДПРОСМОТР РАССЫЛКИ</b>\n\n" f"<b>Получатели:</b> {data['filter_name']}\n" f"<b>Количество:</b> {data['count']} пользователей\n\n" f"{content_info}\n\n" f"<i>Подтвердите отправку:</i>", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='HTML' ) async def broadcast_confirmation_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработка подтверждения рассылки""" query = update.callback_query await query.answer() user = query.from_user log_user_action(user.id, user.first_name, f'broadcast_confirm_{query.data}', None, query.message.chat_id) if query.data == "cancel_all": await query.edit_message_text("❌ Рассылка отменена") context.user_data.pop('broadcast', None) return elif query.data == "edit_content": await query.edit_message_text("✍️ <b>Отправьте новый текст или фото:</b>", parse_mode='HTML') context.user_data['broadcast']['step'] = 'waiting_content' return elif query.data == "change_recipients": # Возвращаем к выбору получателей keyboard = [ [InlineKeyboardButton("👥 ВСЕМ участникам", callback_data="broadcast_all")], [InlineKeyboardButton("📅 За ПОСЛЕДНИЕ 7 дней", callback_data="broadcast_7")], [InlineKeyboardButton("📅 За ПОСЛЕДНИЕ 30 дней", callback_data="broadcast_30")], [InlineKeyboardButton("❌ ОТМЕНА", callback_data="broadcast_cancel")] ] unique_users = get_total_unique_users() week_users = len(get_users_by_date_range(7)) month_users = len(get_users_by_date_range(30)) await query.edit_message_text( f"📢 <b>СОЗДАНИЕ РАССЫЛКИ</b>\n\n" f"<b>Выберите получателей:</b>\n\n" f"👥 <b>Всем участникам:</b> {unique_users} пользователей\n" f"📅 <b>За последние 7 дней:</b> {week_users} пользователей\n" f"📅 <b>За последние 30 дней:</b> {month_users} пользователей", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='HTML' ) context.user_data.pop('broadcast', None) return # Начинаем отправку if 'broadcast' not in context.user_data: await query.edit_message_text("❌ Данные рассылки устарели") return data = context.user_data['broadcast'] recipients = data['recipients'] # Начинаем логирование рассылки broadcast_id = start_broadcast_log( user.id, data['filter_name'], len(recipients), data['content_type'], data['content_preview'] ) await query.edit_message_text( f"🚀 <b>НАЧИНАЮ РАССЫЛКУ...</b>\n\n" f"<b>Тип:</b> {'🖼️ Фото' if data['has_photo'] else '📝 Текст'}\n" f"<b>Получатели:</b> {data['filter_name']}\n" f"<b>Количество:</b> {len(recipients)} пользователей\n\n" f"⏳ <i>Отправка сообщений...</i>\n" f"▰▰▱▱▱▱▱▱▱▱ 0%", parse_mode='HTML' ) # Отправляем сообщения success_count = 0 fail_count = 0 sent_users = set() total = len(recipients) for idx, recipient in enumerate(recipients): try: user_id_to = recipient[1] # user_id из базы if user_id_to in sent_users: continue sent_users.add(user_id_to) first_name = recipient[2] personalized_greeting = f"👋 <b>Здравствуйте, {first_name}!</b>\n\n" if data['has_photo']: caption = data.get('caption', '') full_caption = personalized_greeting + caption await context.bot.send_photo( chat_id=user_id_to, photo=data['photo_id'], caption=full_caption, parse_mode='HTML' ) else: message = data.get('message', '') full_message = personalized_greeting + message await context.bot.send_message( chat_id=user_id_to, text=full_message, parse_mode='HTML', disable_web_page_preview=True ) success_count += 1 log_broadcast_detail(broadcast_id, user_id_to, 'success') # Обновляем прогресс if idx % 10 == 0 or idx == total - 1: progress = int(((idx + 1) / total) * 100) progress_bar = "▰" * (progress // 10) + "▱" * (10 - progress // 10) await query.edit_message_text( f"🚀 <b>РАССЫЛКА В ПРОЦЕССЕ...</b>\n\n" f"<b>Прогресс:</b> {progress}%\n" f"{progress_bar}\n\n" f"✅ <b>Отправлено:</b> {success_count} из {total}\n" f"❌ <b>Ошибок:</b> {fail_count}", parse_mode='HTML' ) await asyncio.sleep(0.5) except Exception as e: fail_count += 1 error_msg = str(e)[:100] log_broadcast_detail(broadcast_id, recipient[1] if len(recipient) > 1 else 'unknown', 'failed', error_msg) # Завершаем логирование рассылки update_broadcast_log(broadcast_id, 'completed', success_count, fail_count) # Отчет report_time = datetime.now().strftime('%d.%m.%Y %H:%M') report = ( f"📊 <b>РАССЫЛКА ЗАВЕРШЕНА!</b>\n\n" f"<b>Дата:</b> {report_time}\n" f"<b>Тип:</b> {'🖼️ Фото' if data['has_photo'] else '📝 Текст'}\n" f"<b>Получатели:</b> {data['filter_name']}\n" f"<b>Всего получателей:</b> {total} пользователей\n" f"<b>Уникальных:</b> {len(sent_users)} пользователей\n\n" f"✅ <b>Успешно отправлено:</b> {success_count}\n" f"❌ <b>Не отправлено:</b> {fail_count}\n\n" f"<i>ID рассылки: #{broadcast_id}</i>" ) keyboard = [[InlineKeyboardButton("📢 НОВАЯ РАССЫЛКА", callback_data="new_broadcast")]] await query.edit_message_text( report, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='HTML' ) # Логируем завершение log_user_action(user.id, user.first_name, 'broadcast_completed', { 'broadcast_id': broadcast_id, 'success_count': success_count, 'fail_count': fail_count, 'unique_users': len(sent_users) }, query.message.chat_id) context.user_data.pop('broadcast', None) async def new_broadcast_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработка кнопки новой рассылки""" query = update.callback_query await query.answer() await start_broadcast_callback(update, context) async def start_broadcast_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): """Начало рассылки через callback""" query = update.callback_query await query.answer() keyboard = [ [InlineKeyboardButton("👥 ВСЕМ участникам", callback_data="broadcast_all")], [InlineKeyboardButton("📅 За ПОСЛЕДНИЕ 7 дней", callback_data="broadcast_7")], [InlineKeyboardButton("📅 За ПОСЛЕДНИЕ 30 дней", callback_data="broadcast_30")], [InlineKeyboardButton("❌ ОТМЕНА", callback_data="broadcast_cancel")] ] unique_users = get_total_unique_users() week_users = len(get_users_by_date_range(7)) month_users = len(get_users_by_date_range(30)) await query.edit_message_text( f"📢 <b>СОЗДАНИЕ РАССЫЛКИ</b>\n\n" f"<b>Выберите получателей:</b>\n\n" f"👥 <b>Всем участникам:</b> {unique_users} пользователей\n" f"📅 <b>За последние 7 дней:</b> {week_users} пользователей\n" f"📅 <b>За последние 30 дней:</b> {month_users} пользователей", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='HTML' ) # ===== ОСНОВНЫЕ ОБРАБОТЧИКИ ===== async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработчик команды /start""" user = update.effective_user # Логируем запуск бота log_user_action(user.id, user.first_name, 'start_command', {'is_admin': user.id == ADMIN_ID}, update.effective_chat.id) if user.id == ADMIN_ID: unique_users = get_total_unique_users() await update.message.reply_text( f"👑 <b>АДМИНИСТРАТОР {user.first_name}!</b>\n\n" f"📊 <b>Статистика:</b>\n" f"• Уникальных пользователей: {unique_users}\n" f"• За 7 дней: {len(get_users_by_date_range(7))}\n" f"• За 30 дней: {len(get_users_by_date_range(30))}\n\n" f"📢 <b>Используйте кнопку «РАССЫЛКА»</b>", parse_mode='HTML', reply_markup=get_admin_keyboard() ) else: if is_user_registered(user.id): await update.message.reply_text( f"✅ <b>Вы уже зарегистрированы на вебинар!</b>\n\n" f"📅 <b>Дата:</b> {WEBINAR_DATE}\n" f"⏰ <b>Время:</b> {WEBINAR_TIME}", parse_mode='HTML', reply_markup=get_registered_user_keyboard() ) else: await update.message.reply_text( f"👋 <b>Здравствуйте, {user.first_name}!</b>\n\n" f"🎓 <b>Вебинар:</b> {WEBINAR_NAME}\n" f"📅 <b>Дата:</b> {WEBINAR_DATE}\n" f"⏰ <b>Время:</b> {WEBINAR_TIME}\n\n" f"Для регистрации нажмите «✅ Регистрация»", parse_mode='HTML', reply_markup=get_unregistered_user_keyboard() ) async def webinar_info(update: Update, context: ContextTypes.DEFAULT_TYPE): """Информация о вебинаре""" user = update.effective_user log_user_action(user.id, user.first_name, 'webinar_info', None, update.effective_chat.id) unique_users = get_total_unique_users() info_text = ( f"🎓 <b>{WEBINAR_NAME}</b>\n\n" f"📅 <b>Дата:</b> {WEBINAR_DATE}\n" f"⏰ <b>Время:</b> {WEBINAR_TIME}\n" f"👥 <b>Зарегистрировано:</b> {unique_users} человек\n\n" ) if is_user_registered(user.id): info_text = f"✅ <b>Вы уже зарегистрированы!</b>\n\n" + info_text keyboard = get_registered_user_keyboard() else: info_text = info_text + "Для регистрации нажмите «✅ Регистрация»" keyboard = get_unregistered_user_keyboard() await update.message.reply_text(info_text, parse_mode='HTML', reply_markup=keyboard) async def show_statistics(update: Update, context: ContextTypes.DEFAULT_TYPE): """Показывает статистику""" user = update.effective_user log_user_action(user.id, user.first_name, 'view_statistics', None, update.effective_chat.id) unique_users = get_total_unique_users() if user.id == ADMIN_ID: week_users = len(get_users_by_date_range(7)) month_users = len(get_users_by_date_range(30)) # Получаем статистику по рассылкам conn = get_db_connection() cursor = conn.cursor() cursor.execute('SELECT COUNT(*), SUM(success_count), SUM(fail_count) FROM broadcasts WHERE status="completed"') broadcast_stats = cursor.fetchone() conn.close() broadcast_count = broadcast_stats[0] or 0 total_sent = broadcast_stats[1] or 0 total_failed = broadcast_stats[2] or 0 stats_text = ( f"📊 <b>ПОЛНАЯ СТАТИСТИКА</b>\n\n" f"👥 <b>Регистрации:</b>\n" f"• Всего пользователей: {unique_users}\n" f"• За 7 дней: {week_users}\n" f"• За 30 дней: {month_users}\n\n" f"📢 <b>Рассылки:</b>\n" f"• Всего рассылок: {broadcast_count}\n" f"• Успешно отправлено: {total_sent}\n" f"• Не отправлено: {total_failed}\n\n" f"<i>Используйте кнопку РАССЫЛКА</i>" ) keyboard = get_admin_keyboard() else: stats_text = f"📊 <b>Всего участников:</b> {unique_users}" if is_user_registered(user.id): keyboard = get_registered_user_keyboard() else: keyboard = get_unregistered_user_keyboard() await update.message.reply_text(stats_text, parse_mode='HTML', reply_markup=keyboard) async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработчик текстовых сообщений""" text = update.message.text user = update.effective_user # Логируем текст пользователя log_user_action(user.id, user.first_name, 'text_message', {'text': text[:100]}, update.effective_chat.id) if text == "📊 Статистика": await show_statistics(update, context) elif text == "📢 Рассылка" and user.id == ADMIN_ID: await start_broadcast(update, context) elif text == "ℹ️ О вебинаре": await webinar_info(update, context) else: if (user.id == ADMIN_ID and 'broadcast' in context.user_data and context.user_data['broadcast']['step'] == 'waiting_content'): await handle_broadcast_content(update, context) else: if user.id == ADMIN_ID: await update.message.reply_text("Используйте кнопки для навигации 👇", reply_markup=get_admin_keyboard()) elif is_user_registered(user.id): await update.message.reply_text("Используйте кнопки для навигации 👇", reply_markup=get_registered_user_keyboard()) else: await update.message.reply_text("Используйте кнопки для навигации 👇", reply_markup=get_unregistered_user_keyboard()) # ===== КЛАВИАТУРЫ ===== def get_admin_keyboard(): keyboard = [[KeyboardButton("📊 Статистика"), KeyboardButton("📢 Рассылка")]] return ReplyKeyboardMarkup(keyboard, resize_keyboard=True) def get_unregistered_user_keyboard(): keyboard = [ [KeyboardButton("✅ Регистрация", request_contact=True)], [KeyboardButton("ℹ️ О вебинаре"), KeyboardButton("📊 Статистика")] ] return ReplyKeyboardMarkup(keyboard, resize_keyboard=True) def get_registered_user_keyboard(): keyboard = [ [KeyboardButton("ℹ️ О вебинаре"), KeyboardButton("📊 Статистика")] ] return ReplyKeyboardMarkup(keyboard, resize_keyboard=True) def get_user_keyboard(user_id): if user_id == ADMIN_ID: return get_admin_keyboard() elif is_user_registered(user_id): return get_registered_user_keyboard() else: return get_unregistered_user_keyboard() # ===== ЗАПУСК БОТА ===== async def main_async(): """Основная асинхронная функция""" print("=" * 60) print("🤖 ЗАПУСК ТЕЛЕГРАМ БОТА С ПОЛНЫМ ЛОГИРОВАНИЕМ") print("=" * 60) # Инициализация БД init_database() log_system_event('INFO', 'Бот запускается', 'system') # Создаем приложение application = Application.builder().token(BOT_TOKEN).build() # Регистрируем обработчики application.add_handler(CommandHandler("start", start)) application.add_handler(MessageHandler(filters.CONTACT, handle_contact)) application.add_handler(CallbackQueryHandler(broadcast_button_handler, pattern="^broadcast_")) application.add_handler(CallbackQueryHandler(broadcast_confirmation_handler, pattern="^(confirm_send|edit_content|change_recipients|cancel_all)$")) application.add_handler(CallbackQueryHandler(new_broadcast_handler, pattern="^new_broadcast$")) application.add_handler(MessageHandler(filters.PHOTO, handle_broadcast_content)) application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text)) print("\n✅ Бот запущен со следующими функциями:") print("📝 ПОЛНОЕ ЛОГИРОВАНИЕ всех действий") print("👤 Регистрация пользователей") print("📢 Система рассылок с детальным отслеживанием") print("🔗 Интеграция с amoCRM") print("📊 Расширенная статистика") # Запускаем polling await application.run_polling(allowed_updates=Update.ALL_TYPES) def main(): """Точка входа""" lock_file = check_single_instance() try: asyncio.run(main_async()) except KeyboardInterrupt: print("\n🛑 Бот остановлен пользователем") log_system_event('INFO', 'Бот остановлен пользователем', 'system') except Exception as e: error_msg = f"Критическая ошибка: {str(e)}" print(f"\n❌ {error_msg}") log_system_event('ERROR', error_msg, 'main') finally: cleanup_lock(lock_file) print("\n✅ Сессия завершена") if __name__ == '__main__': main()