EmailBot/handlers.py

756 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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()