# handlers.py - добавляем обработчики передачи прав import io from aiogram import Router, F from aiogram.filters import Command, StateFilter from aiogram.fsm.context import FSMContext from aiogram.fsm.state import StatesGroup, State from aiogram.types import Message, CallbackQuery, Document from database import ( add_user, set_user_allowed, is_user_allowed, get_target_email, set_target_email, get_all_users, delete_user, get_user_by_id, get_admin_id, set_admin_id, can_send_notification, update_notification_time, get_last_notification_time, reset_notification_timer, get_next_allowed_time # новые импорты ) from email_sender import send_batch_to_email from queue_manager import file_queue from keyboards import ( admin_decision_keyboard, admin_panel_keyboard, users_list_keyboard, confirm_delete_keyboard, confirm_transfer_keyboard, quick_decision_keyboard ) from config import ADMIN_ID as DEFAULT_ADMIN_ID import logging logger = logging.getLogger(__name__) router = Router() class ChangeEmailState(StatesGroup): waiting_for_email = State() async def is_admin(user_id: int) -> bool: """Проверка, является ли пользователь администратором""" current_admin_id = await get_admin_id() if current_admin_id: return user_id == current_admin_id return user_id == DEFAULT_ADMIN_ID @router.message(Command("start")) async def cmd_start(message: Message): user_id = message.from_user.id username = message.from_user.username or "без юзернейма" full_name = message.from_user.full_name await add_user(user_id, username, full_name) # Проверяем, есть ли в БД админ, если нет - устанавливаем current_admin = await get_admin_id() if not current_admin: await set_admin_id(DEFAULT_ADMIN_ID) current_admin = DEFAULT_ADMIN_ID if await is_admin(user_id): await message.answer( "👑 Вы администратор.\n" "Вот ваша панель управления:", reply_markup=admin_panel_keyboard() ) return if await is_user_allowed(user_id): await message.answer( "✅ Доступ подтверждён!\n" "Отправляйте мне любые файлы (фото, документы, архивы и т.д.).\n" "Они будут накапливаться и отправляться пачкой раз в минуту на почту." ) else: # Проверяем, можно ли отправлять уведомление админу if await can_send_notification(user_id): # Обновляем время уведомления await update_notification_time(user_id) # Отправляем уведомление админу admin_id = await get_admin_id() await message.bot.send_message( admin_id, f"🆕 Запрос доступа от пользователя:\n" f"ID: {user_id}\n" f"Имя: {message.from_user.full_name}\n" f"Username: @{message.from_user.username or 'нет'}", reply_markup=admin_decision_keyboard(user_id) ) await message.answer( "⏳ Ваш запрос отправлен администратору.\n" "Ожидайте подтверждения. Новый запрос можно будет отправить через час." ) else: # Получаем время следующего доступного запроса next_allowed = await get_next_allowed_time(user_id) if next_allowed: await message.answer( f"⏳ Вы уже отправляли запрос администратору.\n\n" f"🕐 Следующий запрос можно отправить после: **{next_allowed}** (по Екатеринбургу)\n\n" f"Если доступ нужен срочно, свяжитесь с администратором напрямую.", parse_mode="Markdown" ) else: await message.answer( "⏳ Ваш запрос уже был отправлен администратору.\n\n" "Пожалуйста, ожидайте. Новый запрос можно отправить через час.\n\n" "Если доступ нужен срочно, свяжитесь с администратором напрямую." ) @router.callback_query(F.data.startswith("approve_")) async def approve_user(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return user_id = int(callback.data.split("_")[1]) await set_user_allowed(user_id, True) # Сбрасываем таймер уведомлений для этого пользователя await reset_notification_timer(user_id) await callback.bot.send_message( user_id, "🎉 Ваш доступ подтверждён! Теперь вы можете отправлять любые файлы.\n" "Они будут отправляться пачкой раз в минуту." ) await callback.answer("Пользователь добавлен в список разрешённых") await callback.message.edit_reply_markup() @router.callback_query(F.data.startswith("deny_")) async def deny_user(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return user_id = int(callback.data.split("_")[1]) await set_user_allowed(user_id, False) # Сбрасываем таймер уведомлений для этого пользователя await reset_notification_timer(user_id) # Получаем текущее время Екатеринбурга для сообщения from timezone_utils import format_time, get_current_time current_time = format_time(get_current_time()) await callback.bot.send_message( user_id, f"❌ Ваш запрос доступа отклонён администратором.\n\n" f"Вы можете отправить новый запрос командой /start в любое время.\n" f"🕐 Текущее время: {current_time} (Екатеринбург)" ) await callback.answer("Доступ отклонён") await callback.message.edit_reply_markup() @router.callback_query(F.data == "change_email") async def change_email_request(callback: CallbackQuery, state: FSMContext): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return await callback.message.answer("📧 Введите новый email для получения файлов:") await state.set_state(ChangeEmailState.waiting_for_email) await callback.answer() @router.message(ChangeEmailState.waiting_for_email) async def set_new_email(message: Message, state: FSMContext): if not await is_admin(message.from_user.id): await message.answer("Нет прав") return new_email = message.text.strip() if "@" not in new_email or "." not in new_email: await message.answer("❌ Некорректный email. Попробуйте ещё раз:") return await set_target_email(new_email) await message.answer(f"✅ Почта для отправки изменена на: {new_email}") await state.clear() @router.callback_query(F.data == "list_users") async def list_users(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return users = await get_all_users() if not users: await callback.message.answer("📋 Список пользователей пуст") await callback.answer() return # Разделяем пользователей по статусу для наглядности pending = [u for u in users if u[3] == 0] allowed = [u for u in users if u[3] == 1] text = "👥 **Управление пользователями**\n\n" if pending: text += f"⏳ **Ожидают доступа ({len(pending)}):**\n" text += "Нажмите на пользователя для быстрых действий\n\n" else: text += "⏳ Нет пользователей в ожидании\n\n" if allowed: text += f"✅ **Подтверждённые ({len(allowed)}):**\n" text += "Нажмите на пользователя для удаления\n\n" await callback.message.answer( text, reply_markup=users_list_keyboard(users, 0, for_transfer=False), parse_mode="Markdown" ) await callback.answer() @router.callback_query(F.data.startswith("users_page_")) async def users_page(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return parts = callback.data.split("_") page = int(parts[2]) for_transfer = parts[3] == "True" if len(parts) > 3 else False users = await get_all_users() if not users: await callback.message.edit_text("📋 Список пользователей пуст") await callback.answer() return await callback.message.edit_reply_markup(reply_markup=users_list_keyboard(users, page, for_transfer)) await callback.answer() @router.callback_query(F.data.startswith("user_")) async def user_selected(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return user_id = int(callback.data.split("_")[1]) user = await get_user_by_id(user_id) if not user: await callback.answer("Пользователь не найден") await callback.message.edit_text("❌ Пользователь уже был удалён") return user_id, username, full_name, is_allowed = user text = f"🗑️ **Удаление пользователя**\n\n" text += f"ID: `{user_id}`\n" text += f"Имя: {full_name}\n" text += f"Username: @{username if username and username != 'None' else 'нет'}\n" text += f"Статус: {'✅ Доступ разрешён' if is_allowed else '⏳ Ожидает'}\n\n" text += "Вы уверены, что хотите удалить этого пользователя?" await callback.message.edit_text( text, reply_markup=confirm_delete_keyboard(user_id, username), parse_mode="Markdown" ) await callback.answer() @router.callback_query(F.data.startswith("confirm_delete_")) async def confirm_delete(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return user_id = int(callback.data.split("_")[2]) user = await get_user_by_id(user_id) if not user: await callback.message.edit_text("❌ Пользователь уже был удалён") await callback.answer() return user_id, username, full_name, is_allowed = user await delete_user(user_id) try: await callback.bot.send_message( user_id, "❌ Ваш доступ был отозван администратором. Вы больше не можете отправлять файлы.\n" "Для повторного доступа нужно снова запросить разрешение через /start" ) except Exception: pass await callback.message.edit_text( f"✅ Пользователь {full_name} (ID: {user_id}) удалён из базы данных.\n\n" f"Если нужно восстановить доступ, пользователь должен снова написать /start" ) await callback.answer("Пользователь удалён") @router.callback_query(F.data == "cancel_delete") async def cancel_delete(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return users = await get_all_users() await callback.message.edit_text( "👥 Управление пользователями\n\nНажмите на пользователя, чтобы удалить его.", reply_markup=users_list_keyboard(users, 0, for_transfer=False) ) await callback.answer() @router.callback_query(F.data == "close_users") async def close_users(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return await callback.message.delete() await callback.answer() @router.callback_query(F.data == "queue_status") async def queue_status(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return size = await file_queue.get_queue_size() await callback.message.answer(f"📊 В очереди {size} файлов") await callback.answer() # ========== НОВЫЕ ОБРАБОТЧИКИ ДЛЯ ПЕРЕДАЧИ ПРАВ АДМИНА ========== @router.callback_query(F.data == "transfer_admin") async def transfer_admin_request(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return users = await get_all_users() allowed_users = [u for u in users if u[3] == 1] # Только пользователи с доступом if not allowed_users: await callback.message.answer("❌ Нет пользователей с подтверждённым доступом для передачи прав") await callback.answer() return text = "👑 **Передача прав администратора**\n\n" text += "Выберите пользователя, которому хотите передать права:\n" text += "(только пользователи с подтверждённым доступом ✅)\n\n" text += "⚠️ **Внимание!** После передачи вы потеряете права администратора!" await callback.message.answer( text, reply_markup=users_list_keyboard(allowed_users, 0, for_transfer=True), parse_mode="Markdown" ) await callback.answer() @router.callback_query(F.data.startswith("select_new_admin_")) async def select_new_admin(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return new_admin_id = int(callback.data.split("_")[3]) user = await get_user_by_id(new_admin_id) if not user: await callback.answer("Пользователь не найден") return user_id, username, full_name, is_allowed = user if not is_allowed: await callback.answer("У этого пользователя нет доступа", show_alert=True) return text = f"⚠️ **Подтверждение передачи прав**\n\n" text += f"Вы собираетесь передать права администратора пользователю:\n" text += f"👤 {full_name}\n" text += f"📱 @{username if username and username != 'None' else 'нет'}\n" text += f"🆔 ID: `{user_id}`\n\n" text += f"После этого действия **ВЫ ПОТЕРЯЕТЕ ПРАВА АДМИНИСТРАТОРА**.\n\n" text += f"Вы уверены?" await callback.message.edit_text( text, reply_markup=confirm_transfer_keyboard(user_id, username, full_name), parse_mode="Markdown" ) await callback.answer() @router.callback_query(F.data.startswith("confirm_transfer_")) async def confirm_transfer(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return new_admin_id = int(callback.data.split("_")[2]) old_admin_id = callback.from_user.id user = await get_user_by_id(new_admin_id) if not user: await callback.message.edit_text("❌ Пользователь не найден") await callback.answer() return user_id, username, full_name, is_allowed = user if not is_allowed: await callback.message.edit_text("❌ У этого пользователя нет доступа") await callback.answer() return # Передаём права админа await set_admin_id(new_admin_id) # Уведомляем нового админа try: await callback.bot.send_message( new_admin_id, f"👑 **Поздравляем!**\n\n" f"Пользователь {callback.from_user.full_name} передал вам права администратора.\n\n" f"Теперь вы можете управлять ботом: изменять почту, одобрять пользователей и т.д.\n\n" f"Используйте /start для доступа к панели управления.", parse_mode="Markdown" ) except Exception: pass # Уведомляем старого админа await callback.message.edit_text( f"✅ **Права администратора переданы!**\n\n" f"Новый администратор: {full_name} (ID: {new_admin_id})\n\n" f"Вы больше не являетесь администратором. Для управления ботом обратитесь к новому админу." ) # Отправляем старому админу сообщение в личку (если не в чате с ботом) try: await callback.bot.send_message( old_admin_id, f"👑 Вы передали права администратора пользователю {full_name}.\n" f"Вы больше не являетесь администратором бота." ) except Exception: pass await callback.answer("Права переданы") @router.callback_query(F.data == "cancel_transfer") async def cancel_transfer(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return await callback.message.edit_text("❌ Передача прав отменена") await callback.answer() # Обработчики файлов @router.message(F.document) async def handle_document(message: Message): user_id = message.from_user.id if not await is_admin(user_id) and not await is_user_allowed(user_id): await message.answer("⛔ У вас нет доступа. Запросите его через /start") return document = message.document file = await message.bot.get_file(document.file_id) from io import BytesIO file_content = await message.bot.download_file(file.file_path) if isinstance(file_content, BytesIO): file_bytes = file_content.getvalue() elif isinstance(file_content, bytes): file_bytes = file_content else: file_bytes = file_content.read() filename = document.file_name user_info = f"{message.from_user.full_name}" await file_queue.add_file(file_bytes, filename, user_info, user_id, file_type='document') queue_size = await file_queue.get_queue_size() await message.answer( f"✅ Файл '{filename}' добавлен в очередь (всего: {queue_size})\n" f"📧 Отправка произойдёт в течение минуты" ) @router.message(F.photo) async def handle_photo(message: Message): user_id = message.from_user.id if not await is_admin(user_id) and not await is_user_allowed(user_id): await message.answer("⛔ У вас нет доступа. Запросите его через /start") return photo = message.photo[-1] file = await message.bot.get_file(photo.file_id) from io import BytesIO file_content = await message.bot.download_file(file.file_path) if isinstance(file_content, BytesIO): file_bytes = file_content.getvalue() elif isinstance(file_content, bytes): file_bytes = file_content else: file_bytes = file_content.read() filename = f"photo_{user_id}_{photo.file_unique_id}.jpg" user_info = f"{message.from_user.full_name}" await file_queue.add_file(file_bytes, filename, user_info, user_id, file_type='photo') queue_size = await file_queue.get_queue_size() await message.answer( f"✅ Фото добавлено в очередь (всего: {queue_size})\n" f"📧 Отправка произойдёт в течение минуты" ) @router.message(Command("send_now")) async def force_send(message: Message): """Принудительная отправка всех накопленных файлов""" if not await is_admin(message.from_user.id): await message.answer("⛔ Нет прав. Только администратор может использовать эту команду.") return # Получаем все файлы из очереди files = await file_queue.get_all_files() if not files: await message.answer("📭 Очередь пуста. Нечего отправлять.") return target_email = await get_target_email() if not target_email: await message.answer("⚠️ Целевая почта не настроена. Используйте кнопку 'Сменить почту'.") return # Отправляем уведомление о начале отправки status_msg = await message.answer(f"📤 Начинаю отправку {len(files)} файлов...") try: # Отправляем пачку файлов await send_batch_to_email(target_email, files) # Успешно await status_msg.edit_text( f"✅ Принудительно отправлено {len(files)} файлов на почту {target_email}\n" f"📊 Размер: {sum(len(f.file_bytes) for f in files) / (1024 * 1024):.2f} MB" ) # Логируем действие logger.info(f"Админ {message.from_user.id} принудительно отправил {len(files)} файлов") except Exception as e: # Ошибка при отправке await status_msg.edit_text( f"❌ Ошибка при отправке: {str(e)[:200]}\n\n" f"Файлы остались в очереди и будут отправлены при следующем автоматическом запуске." ) # Возвращаем файлы обратно в очередь for file in files: await file_queue.add_file( file.file_bytes, file.filename, file.user_info, file.user_id, file.file_type ) logger.error(f"Ошибка при принудительной отправке: {e}") @router.message(Command("queue")) async def check_queue(message: Message): """Показать содержимое очереди""" if not await is_admin(message.from_user.id): await message.answer("⛔ Нет прав") return size = await file_queue.get_queue_size() if size == 0: await message.answer("📭 Очередь пуста") return # Получаем первые 10 файлов для просмотра peek_files = await file_queue.peek_queue(10) text = f"📊 **Очередь отправки**\n\n" text += f"📎 Всего файлов: {size}\n\n" if peek_files: text += "**Первые файлы в очереди:**\n" for i, f in enumerate(peek_files, 1): text += f"{i}. {f['filename']}\n" text += f" └ от {f['user_info']} ({f['size_kb']:.1f} KB) в {f['timestamp']}\n" if size > 10: text += f"\n... и ещё {size - 10} файлов\n" text += f"\n⏱️ Автоотправка: каждую минуту\n" text += f"🚀 Принудительно: /send_now" await message.answer(text, parse_mode="Markdown") @router.message(Command("stats")) async def bot_stats(message: Message): """Показать статистику бота""" if not await is_admin(message.from_user.id): await message.answer("⛔ Нет прав") return users = await get_all_users() allowed_users = [u for u in users if u[3] == 1] pending_users = [u for u in users if u[3] == 0] target_email = await get_target_email() queue_size = await file_queue.get_queue_size() text = f"📊 **Статистика бота**\n\n" text += f"👥 Всего пользователей: {len(users)}\n" text += f"✅ С доступом: {len(allowed_users)}\n" text += f"⏳ Ожидают: {len(pending_users)}\n" text += f"📎 В очереди: {queue_size}\n" text += f"📧 Почта для отправки: {target_email or 'не настроена'}\n" await message.answer(text, parse_mode="Markdown") @router.callback_query(F.data.startswith("quick_approve_")) async def quick_approve(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return user_id = int(callback.data.split("_")[2]) user = await get_user_by_id(user_id) if not user: await callback.answer("Пользователь не найден") await callback.message.edit_text("❌ Пользователь уже был удалён") return await set_user_allowed(user_id, True) # Сбрасываем таймер уведомлений для этого пользователя await reset_notification_timer(user_id) await callback.bot.send_message( user_id, "🎉 Ваш доступ подтверждён! Теперь вы можете отправлять любые файлы.\n" "Они будут отправляться пачкой раз в минуту." ) # Обновляем сообщение users = await get_all_users() await callback.message.edit_text( "👥 Управление пользователями\n\n✅ Доступ подтверждён", reply_markup=users_list_keyboard(users, 0, for_transfer=False) ) await callback.answer("Доступ подтверждён") @router.callback_query(F.data.startswith("quick_deny_")) async def quick_deny(callback: CallbackQuery): if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return user_id = int(callback.data.split("_")[2]) user = await get_user_by_id(user_id) if not user: await callback.answer("Пользователь не найден") await callback.message.edit_text("❌ Пользователь уже был удалён") return await set_user_allowed(user_id, False) # Сбрасываем таймер уведомлений для этого пользователя await reset_notification_timer(user_id) # Получаем текущее время Екатеринбурга для сообщения from timezone_utils import format_time, get_current_time current_time = format_time(get_current_time()) await callback.bot.send_message( user_id, f"❌ Ваш запрос доступа отклонён администратором.\n\n" f"Вы можете отправить новый запрос командой /start в любое время.\n" f"🕐 Текущее время: {current_time} (Екатеринбург)" ) # Обновляем сообщение админу users = await get_all_users() await callback.message.edit_text( "👥 Управление пользователями\n\n❌ Доступ отклонён", reply_markup=users_list_keyboard(users, 0, for_transfer=False) ) await callback.answer("Доступ отклонён") @router.callback_query(F.data.startswith("pending_user_")) async def pending_user_info(callback: CallbackQuery): """Показывает информацию об ожидающем пользователе""" if not await is_admin(callback.from_user.id): await callback.answer("Нет прав", show_alert=True) return user_id = int(callback.data.split("_")[2]) user = await get_user_by_id(user_id) if not user: await callback.answer("Пользователь не найден") return user_id, username, full_name, is_allowed = user from database import get_last_notification_time last_notification = await get_last_notification_time(user_id) text = f"👤 **Информация о пользователе**\n\n" text += f"ID: `{user_id}`\n" text += f"Имя: {full_name}\n" text += f"Username: @{username if username and username != 'None' else 'нет'}\n" text += f"Статус: ⏳ Ожидает подтверждения\n" if last_notification: text += f"Последний запрос: {last_notification}\n\n" text += "Выберите действие:" await callback.message.edit_text( text, reply_markup=quick_decision_keyboard(user_id), parse_mode="Markdown" ) await callback.answer()