From f11b014acfc5ff655d0abafedb334d905cd2bc74 Mon Sep 17 00:00:00 2001 From: hteppl Date: Sun, 12 Jan 2025 17:29:48 +0300 Subject: [PATCH] Implement admin sender handler --- handlers/admin/admin_sender.py | 105 +++++++++++++++++++++++++++++++++ keyboards/admin/sender_kb.py | 35 +++++++++++ 2 files changed, 140 insertions(+) create mode 100644 handlers/admin/admin_sender.py create mode 100644 keyboards/admin/sender_kb.py diff --git a/handlers/admin/admin_sender.py b/handlers/admin/admin_sender.py new file mode 100644 index 00000000..101aaf86 --- /dev/null +++ b/handlers/admin/admin_sender.py @@ -0,0 +1,105 @@ +from datetime import datetime +from typing import Any + +from aiogram import F, Router, types +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup +from aiogram.types import CallbackQuery + +from bot import bot +from filters.admin import IsAdminFilter +from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb +from keyboards.admin.sender_kb import build_sender_kb, AdminSenderCallback +from logger import logger + +router = Router() + + +class AdminSender(StatesGroup): + waiting_for_message = State() + + +@router.callback_query( + AdminPanelCallback.filter(F.action == "sender"), + IsAdminFilter(), +) +async def handle_sender( + callback_query: CallbackQuery +): + await callback_query.message.answer( + text="✍️ Выберите группу пользователей для рассылки:", + reply_markup=build_sender_kb(), + ) + + +@router.callback_query( + AdminSenderCallback, + IsAdminFilter(), +) +async def handle_sender_callback( + callback_query: CallbackQuery, + callback_data: AdminSenderCallback, + state: FSMContext +): + await callback_query.message.answer( + text="✍️ Введите текст сообщения для рассылки:", + reply_markup=build_admin_back_kb("sender"), + ) + await state.update_data(type=callback_data.type) + await state.set_state(AdminSender.waiting_for_message) + + +@router.message( + AdminSender.waiting_for_message, + IsAdminFilter(), +) +async def process_message( + message: types.Message, + state: FSMContext, + session: Any +): + text_message = message.text + + try: + state_data = await state.get_data() + send_to = state_data.get("type", "all") + + if send_to == "subscribed": + tg_ids = await session.fetch(""" + SELECT DISTINCT c.tg_id + FROM connections c + JOIN keys k ON c.tg_id = k.tg_id + WHERE k.expiry_time > $1 + """, int(datetime.utcnow().timestamp() * 1000)) + elif send_to == "unsubscribed": + tg_ids = await session.fetch(""" + SELECT c.tg_id + FROM connections c + LEFT JOIN keys k ON c.tg_id = k.tg_id + GROUP BY c.tg_id + HAVING COUNT(k.tg_id) = 0 OR MAX(k.expiry_time) <= $1 + """, int(datetime.utcnow().timestamp() * 1000)) + else: + tg_ids = await session.fetch("SELECT DISTINCT tg_id FROM connections") + + total_users = len(tg_ids) + success_count = 0 + + for record in tg_ids: + tg_id = record["tg_id"] + try: + await bot.send_message(chat_id=tg_id, text=text_message) + success_count += 1 + except Exception: + pass + + await message.answer( + f"📤 Рассылка завершена:\n" + f"👥 Всего пользователей: {total_users}\n" + f"✅ Доставлено: {success_count}\n" + f"❌ Не доставлено: {total_users - success_count}" + ) + except Exception as e: + logger.error(f"❗ Ошибка при подключении к базе данных: {e}") + + await state.clear() diff --git a/keyboards/admin/sender_kb.py b/keyboards/admin/sender_kb.py new file mode 100644 index 00000000..895e4fbb --- /dev/null +++ b/keyboards/admin/sender_kb.py @@ -0,0 +1,35 @@ +from aiogram.filters.callback_data import CallbackData +from aiogram.types import InlineKeyboardMarkup +from aiogram.utils.keyboard import InlineKeyboardBuilder + +from keyboards.admin.panel_kb import build_admin_back_btn + + +class AdminSenderCallback(CallbackData, prefix='admin_sender'): + type: str + + +def build_sender_kb() -> InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + builder.button( + text="📢 Отправить всем", + callback_data=AdminSenderCallback( + type="all" + ).pack() + ) + builder.button( + text="📢 Отправить с подпиской", + callback_data=AdminSenderCallback( + type="subscribed" + ).pack() + ) + builder.button( + text="📢 Отправить без подписки", + callback_data=AdminSenderCallback( + type="unsubscribed" + ).pack() + ) + builder.row( + build_admin_back_btn("admin") + ) + return builder.as_markup()