mirror of
https://github.com/koloideal/Quizzi.git
synced 2026-06-10 10:25:28 +03:00
commit
This commit is contained in:
@@ -1,13 +1,15 @@
|
|||||||
from aiogram.types import CallbackQuery, Message
|
from aiogram.types import CallbackQuery, Message
|
||||||
from aiogram_dialog import Dialog, DialogManager, Window
|
from aiogram_dialog import Dialog, DialogManager, Window
|
||||||
from aiogram_dialog.widgets.input import MessageInput
|
from aiogram_dialog.widgets.input import MessageInput
|
||||||
from aiogram_dialog.widgets.kbd import Back, Button, Column, ScrollingGroup, Select, SwitchTo
|
from aiogram_dialog.widgets.kbd import Back, Button, Cancel, Column, Row, ScrollingGroup, Select, SwitchTo
|
||||||
from aiogram_dialog.widgets.text import Const, Format
|
from aiogram_dialog.widgets.text import Const, Format
|
||||||
from dishka import FromDishka
|
from dishka import FromDishka
|
||||||
|
from dishka.integrations.aiogram import CONTAINER_NAME
|
||||||
from dishka.integrations.aiogram_dialog import inject
|
from dishka.integrations.aiogram_dialog import inject
|
||||||
|
|
||||||
from trudex.application.bot.admin_dialogs.states import AdminMenuSG
|
from trudex.application.bot.admin_dialogs.states import AdminMenuSG
|
||||||
from trudex.infrastructure.database.dao.user import UserDAO
|
from trudex.infrastructure.database.dao.user import UserDAO
|
||||||
|
from trudex.infrastructure.utils.broadcast import broadcast_message
|
||||||
|
|
||||||
|
|
||||||
@inject
|
@inject
|
||||||
@@ -61,7 +63,6 @@ async def on_input_mode(_callback: CallbackQuery, _button: Button, manager: Dial
|
|||||||
|
|
||||||
|
|
||||||
async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager):
|
async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager):
|
||||||
from dishka.integrations.aiogram import CONTAINER_NAME
|
|
||||||
container = manager.middleware_data[CONTAINER_NAME]
|
container = manager.middleware_data[CONTAINER_NAME]
|
||||||
user_dao = await container.get(UserDAO)
|
user_dao = await container.get(UserDAO)
|
||||||
|
|
||||||
@@ -83,6 +84,46 @@ async def on_user_input(message: Message, _widget: MessageInput, manager: Dialog
|
|||||||
await manager.switch_to(AdminMenuSG.user_detail)
|
await manager.switch_to(AdminMenuSG.user_detail)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_broadcast_input(message: Message, _widget: MessageInput, manager: DialogManager):
|
||||||
|
manager.dialog_data["broadcast_message_id"] = message.message_id
|
||||||
|
manager.dialog_data["broadcast_chat_id"] = message.chat.id
|
||||||
|
await manager.switch_to(AdminMenuSG.broadcast_confirm)
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def on_broadcast_confirm(_callback: CallbackQuery, _button: Button, manager: DialogManager, user_dao: FromDishka[UserDAO]):
|
||||||
|
message_id = manager.dialog_data.get("broadcast_message_id")
|
||||||
|
chat_id = manager.dialog_data.get("broadcast_chat_id")
|
||||||
|
|
||||||
|
if not message_id or not chat_id or not _callback.message:
|
||||||
|
await _callback.answer("Ошибка: сообщение не найдено")
|
||||||
|
return
|
||||||
|
|
||||||
|
await _callback.message.answer("⏳ Рассылка началась...")
|
||||||
|
|
||||||
|
bot = _callback.bot
|
||||||
|
if not bot:
|
||||||
|
await _callback.answer("Ошибка: бот не найден")
|
||||||
|
return
|
||||||
|
|
||||||
|
stats = await broadcast_message(bot, message_id, chat_id, user_dao)
|
||||||
|
|
||||||
|
stats_text = (
|
||||||
|
f"✅ <b>Рассылка завершена</b>\n\n"
|
||||||
|
f"Всего пользователей: {stats.total}\n"
|
||||||
|
f"Успешно отправлено: {stats.success}\n"
|
||||||
|
f"Не удалось отправить: {stats.failed}"
|
||||||
|
)
|
||||||
|
|
||||||
|
await _callback.message.answer(stats_text)
|
||||||
|
await manager.done()
|
||||||
|
|
||||||
|
|
||||||
|
async def on_broadcast_cancel(_callback: CallbackQuery, _button: Button, manager: DialogManager):
|
||||||
|
await _callback.answer("Рассылка отменена")
|
||||||
|
await manager.switch_to(AdminMenuSG.main)
|
||||||
|
|
||||||
|
|
||||||
async def on_tests_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
async def on_tests_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
||||||
await _callback.answer("Управление тестами")
|
await _callback.answer("Управление тестами")
|
||||||
|
|
||||||
@@ -91,8 +132,8 @@ async def on_users_clicked(_callback: CallbackQuery, _button: Button, manager: D
|
|||||||
await manager.switch_to(AdminMenuSG.users_list)
|
await manager.switch_to(AdminMenuSG.users_list)
|
||||||
|
|
||||||
|
|
||||||
async def on_broadcast_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
async def on_broadcast_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||||
await _callback.answer("Рассылка")
|
await manager.switch_to(AdminMenuSG.broadcast_input)
|
||||||
|
|
||||||
|
|
||||||
admin_menu_dialog = Dialog(
|
admin_menu_dialog = Dialog(
|
||||||
@@ -138,4 +179,18 @@ admin_menu_dialog = Dialog(
|
|||||||
state=AdminMenuSG.user_detail,
|
state=AdminMenuSG.user_detail,
|
||||||
getter=get_user_detail_data,
|
getter=get_user_detail_data,
|
||||||
),
|
),
|
||||||
|
Window(
|
||||||
|
Const("<b>📢 Рассылка</b>\n\nОтправьте сообщение, которое хотите разослать всем пользователям:"),
|
||||||
|
MessageInput(on_broadcast_input),
|
||||||
|
SwitchTo(Const("◀️ Отмена"), id="cancel_broadcast", state=AdminMenuSG.main),
|
||||||
|
state=AdminMenuSG.broadcast_input,
|
||||||
|
),
|
||||||
|
Window(
|
||||||
|
Const("<b>⚠️ Подтверждение рассылки</b>\n\nВы уверены, что хотите отправить это сообщение всем пользователям?"),
|
||||||
|
Row(
|
||||||
|
Button(Const("✅ Да"), id="broadcast_confirm", on_click=on_broadcast_confirm),
|
||||||
|
Button(Const("❌ Нет"), id="broadcast_cancel", on_click=on_broadcast_cancel),
|
||||||
|
),
|
||||||
|
state=AdminMenuSG.broadcast_confirm,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ class AdminMenuSG(StatesGroup):
|
|||||||
users_list = State()
|
users_list = State()
|
||||||
users_input = State()
|
users_input = State()
|
||||||
user_detail = State()
|
user_detail = State()
|
||||||
|
broadcast_input = State()
|
||||||
|
broadcast_confirm = State()
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ from aiogram_dialog.widgets.input import MessageInput
|
|||||||
from aiogram_dialog.widgets.kbd import Back, Button, Cancel, Column, Row, ScrollingGroup, Select, SwitchTo
|
from aiogram_dialog.widgets.kbd import Back, Button, Cancel, Column, Row, ScrollingGroup, Select, SwitchTo
|
||||||
from aiogram_dialog.widgets.text import Const, Format
|
from aiogram_dialog.widgets.text import Const, Format
|
||||||
from dishka import FromDishka
|
from dishka import FromDishka
|
||||||
|
from dishka.integrations.aiogram import CONTAINER_NAME
|
||||||
from dishka.integrations.aiogram_dialog import inject
|
from dishka.integrations.aiogram_dialog import inject
|
||||||
|
|
||||||
from trudex.application.bot.creator_dialogs.states import CreatorMenuSG
|
from trudex.application.bot.creator_dialogs.states import CreatorMenuSG
|
||||||
from trudex.infrastructure.database.dao.user import UserDAO
|
from trudex.infrastructure.database.dao.user import UserDAO
|
||||||
|
from trudex.infrastructure.utils.broadcast import broadcast_message
|
||||||
|
|
||||||
|
|
||||||
@inject
|
@inject
|
||||||
@@ -81,7 +83,6 @@ async def on_input_mode(_callback: CallbackQuery, _button: Button, manager: Dial
|
|||||||
|
|
||||||
|
|
||||||
async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager):
|
async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager):
|
||||||
from dishka.integrations.aiogram import CONTAINER_NAME
|
|
||||||
container = manager.middleware_data[CONTAINER_NAME]
|
container = manager.middleware_data[CONTAINER_NAME]
|
||||||
user_dao = await container.get(UserDAO)
|
user_dao = await container.get(UserDAO)
|
||||||
|
|
||||||
@@ -108,7 +109,6 @@ async def on_make_admin_clicked(_callback: CallbackQuery, _button: Button, manag
|
|||||||
|
|
||||||
|
|
||||||
async def on_confirm_yes(_callback: CallbackQuery, _button: Button, manager: DialogManager):
|
async def on_confirm_yes(_callback: CallbackQuery, _button: Button, manager: DialogManager):
|
||||||
from dishka.integrations.aiogram import CONTAINER_NAME
|
|
||||||
container = manager.middleware_data[CONTAINER_NAME]
|
container = manager.middleware_data[CONTAINER_NAME]
|
||||||
user_dao = await container.get(UserDAO)
|
user_dao = await container.get(UserDAO)
|
||||||
|
|
||||||
@@ -127,6 +127,46 @@ async def on_confirm_no(_callback: CallbackQuery, _button: Button, manager: Dial
|
|||||||
await manager.switch_to(CreatorMenuSG.user_detail)
|
await manager.switch_to(CreatorMenuSG.user_detail)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_broadcast_input(message: Message, _widget: MessageInput, manager: DialogManager):
|
||||||
|
manager.dialog_data["broadcast_message_id"] = message.message_id
|
||||||
|
manager.dialog_data["broadcast_chat_id"] = message.chat.id
|
||||||
|
await manager.switch_to(CreatorMenuSG.broadcast_confirm)
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def on_broadcast_confirm(_callback: CallbackQuery, _button: Button, manager: DialogManager, user_dao: FromDishka[UserDAO]):
|
||||||
|
message_id = manager.dialog_data.get("broadcast_message_id")
|
||||||
|
chat_id = manager.dialog_data.get("broadcast_chat_id")
|
||||||
|
|
||||||
|
if not message_id or not chat_id or not _callback.message:
|
||||||
|
await _callback.answer("Ошибка: сообщение не найдено")
|
||||||
|
return
|
||||||
|
|
||||||
|
await _callback.message.answer("⏳ Рассылка началась...")
|
||||||
|
|
||||||
|
bot = _callback.bot
|
||||||
|
if not bot:
|
||||||
|
await _callback.answer("Ошибка: бот не найден")
|
||||||
|
return
|
||||||
|
|
||||||
|
stats = await broadcast_message(bot, message_id, chat_id, user_dao)
|
||||||
|
|
||||||
|
stats_text = (
|
||||||
|
f"✅ <b>Рассылка завершена</b>\n\n"
|
||||||
|
f"Всего пользователей: {stats.total}\n"
|
||||||
|
f"Успешно отправлено: {stats.success}\n"
|
||||||
|
f"Не удалось отправить: {stats.failed}"
|
||||||
|
)
|
||||||
|
|
||||||
|
await _callback.message.answer(stats_text)
|
||||||
|
await manager.done()
|
||||||
|
|
||||||
|
|
||||||
|
async def on_broadcast_cancel(_callback: CallbackQuery, _button: Button, manager: DialogManager):
|
||||||
|
await _callback.answer("Рассылка отменена")
|
||||||
|
await manager.switch_to(CreatorMenuSG.main)
|
||||||
|
|
||||||
|
|
||||||
async def on_tests_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
async def on_tests_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
||||||
await _callback.answer("Тесты")
|
await _callback.answer("Тесты")
|
||||||
|
|
||||||
@@ -135,8 +175,8 @@ async def on_users_clicked(_callback: CallbackQuery, _button: Button, manager: D
|
|||||||
await manager.switch_to(CreatorMenuSG.users_list)
|
await manager.switch_to(CreatorMenuSG.users_list)
|
||||||
|
|
||||||
|
|
||||||
async def on_broadcast_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
async def on_broadcast_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||||
await _callback.answer("Рассылка")
|
await manager.switch_to(CreatorMenuSG.broadcast_input)
|
||||||
|
|
||||||
|
|
||||||
creator_menu_dialog = Dialog(
|
creator_menu_dialog = Dialog(
|
||||||
@@ -195,4 +235,18 @@ creator_menu_dialog = Dialog(
|
|||||||
state=CreatorMenuSG.make_admin_confirm,
|
state=CreatorMenuSG.make_admin_confirm,
|
||||||
getter=get_confirm_data,
|
getter=get_confirm_data,
|
||||||
),
|
),
|
||||||
|
Window(
|
||||||
|
Const("<b>📢 Рассылка</b>\n\nОтправьте сообщение, которое хотите разослать всем пользователям:"),
|
||||||
|
MessageInput(on_broadcast_input),
|
||||||
|
SwitchTo(Const("◀️ Отмена"), id="cancel_broadcast", state=CreatorMenuSG.main),
|
||||||
|
state=CreatorMenuSG.broadcast_input,
|
||||||
|
),
|
||||||
|
Window(
|
||||||
|
Const("<b>⚠️ Подтверждение рассылки</b>\n\nВы уверены, что хотите отправить это сообщение всем пользователям?"),
|
||||||
|
Row(
|
||||||
|
Button(Const("✅ Да"), id="broadcast_confirm", on_click=on_broadcast_confirm),
|
||||||
|
Button(Const("❌ Нет"), id="broadcast_cancel", on_click=on_broadcast_cancel),
|
||||||
|
),
|
||||||
|
state=CreatorMenuSG.broadcast_confirm,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,3 +7,5 @@ class CreatorMenuSG(StatesGroup):
|
|||||||
users_input = State()
|
users_input = State()
|
||||||
user_detail = State()
|
user_detail = State()
|
||||||
make_admin_confirm = State()
|
make_admin_confirm = State()
|
||||||
|
broadcast_input = State()
|
||||||
|
broadcast_confirm = State()
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import asyncio
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from aiogram import Bot
|
||||||
|
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError
|
||||||
|
|
||||||
|
from trudex.infrastructure.database.dao.user import UserDAO
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BroadcastStats:
|
||||||
|
success: int
|
||||||
|
failed: int
|
||||||
|
total: int
|
||||||
|
|
||||||
|
|
||||||
|
async def broadcast_message(bot: Bot, message_id: int, chat_id: int, user_dao: UserDAO) -> BroadcastStats:
|
||||||
|
users = await user_dao.get_all()
|
||||||
|
success = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
try:
|
||||||
|
await bot.copy_message(chat_id=user.id, from_chat_id=chat_id, message_id=message_id)
|
||||||
|
success += 1
|
||||||
|
except TelegramForbiddenError:
|
||||||
|
failed += 1
|
||||||
|
except TelegramBadRequest:
|
||||||
|
failed += 1
|
||||||
|
except Exception:
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
return BroadcastStats(success=success, failed=failed, total=len(users))
|
||||||
Reference in New Issue
Block a user