mirror of
https://github.com/koloideal/Quizzi.git
synced 2026-06-10 18:35:28 +03:00
Initial commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram_dialog import Dialog, DialogManager, Window, StartMode
|
||||
from aiogram_dialog.widgets.input import MessageInput
|
||||
from aiogram_dialog.widgets.kbd import Button, Row
|
||||
from aiogram_dialog.widgets.text import Const
|
||||
from dishka import FromDishka
|
||||
from dishka.integrations.aiogram_dialog import inject
|
||||
|
||||
from trudex.application.bot.admin_dialogs.states import AdminBroadcastSG, AdminMenuSG
|
||||
from trudex.infrastructure.database.dao.user import UserDAO
|
||||
from trudex.infrastructure.utils.broadcast import broadcast_message
|
||||
|
||||
|
||||
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(AdminBroadcastSG.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.done()
|
||||
|
||||
|
||||
async def on_back_to_main(_callback: CallbackQuery, _button: Button, manager: DialogManager):
|
||||
await manager.start(AdminMenuSG.main, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
broadcast_dialog = Dialog(
|
||||
Window(
|
||||
Const("<b>📢 Рассылка</b>\n\nОтправьте сообщение, которое хотите разослать всем пользователям:"),
|
||||
MessageInput(on_broadcast_input),
|
||||
Button(Const("◀️ Отмена"), id="back", on_click=on_back_to_main),
|
||||
state=AdminBroadcastSG.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=AdminBroadcastSG.broadcast_confirm,
|
||||
),
|
||||
)
|
||||
@@ -1,139 +1,21 @@
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram_dialog import Dialog, DialogManager, Window
|
||||
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.text import Const, Format
|
||||
from dishka import FromDishka
|
||||
from dishka.integrations.aiogram import CONTAINER_NAME
|
||||
from dishka.integrations.aiogram_dialog import inject
|
||||
from aiogram.types import CallbackQuery
|
||||
from aiogram_dialog import Dialog, DialogManager, Window, StartMode
|
||||
from aiogram_dialog.widgets.kbd import Button, Column
|
||||
from aiogram_dialog.widgets.text import Const
|
||||
|
||||
from trudex.application.bot.admin_dialogs.states import AdminMenuSG
|
||||
from trudex.infrastructure.database.dao.user import UserDAO
|
||||
from trudex.infrastructure.utils.broadcast import broadcast_message
|
||||
from trudex.application.bot.admin_dialogs.states import AdminMenuSG, AdminUsersSG, AdminTestsSG, AdminBroadcastSG
|
||||
|
||||
|
||||
@inject
|
||||
async def get_users_data(user_dao: FromDishka[UserDAO], **_kwargs):
|
||||
users = await user_dao.get_all()
|
||||
|
||||
return {
|
||||
"users": [
|
||||
(f"{u.first_name} (@{u.username or 'нет'})", u.id)
|
||||
for u in users
|
||||
],
|
||||
"count": len(users),
|
||||
}
|
||||
|
||||
|
||||
@inject
|
||||
async def get_user_detail_data(dialog_manager: DialogManager, user_dao: FromDishka[UserDAO], **_kwargs):
|
||||
user_id = dialog_manager.dialog_data.get("selected_user_id")
|
||||
if not user_id:
|
||||
return {"user_info": "Пользователь не выбран"}
|
||||
|
||||
user = await user_dao.get_by_id(user_id)
|
||||
if not user:
|
||||
return {"user_info": "Пользователь не найден"}
|
||||
|
||||
username_str = f"@{user.username}" if user.username else "—"
|
||||
last_name_str = user.last_name or "—"
|
||||
group_str = str(user.group) if user.group else "—"
|
||||
admin_status = "✅ Да" if user.is_admin else "❌ Нет"
|
||||
|
||||
user_info = (
|
||||
f"<b>👤 Информация о пользователе</b>\n\n"
|
||||
f"<b>ID:</b> <code>{user.id}</code>\n"
|
||||
f"<b>Имя:</b> {user.first_name}\n"
|
||||
f"<b>Фамилия:</b> {last_name_str}\n"
|
||||
f"<b>Username:</b> {username_str}\n"
|
||||
f"<b>Группа:</b> {group_str}\n"
|
||||
f"<b>Администратор:</b> {admin_status}"
|
||||
)
|
||||
|
||||
return {"user_info": user_info}
|
||||
|
||||
|
||||
async def on_user_selected(_callback: CallbackQuery, _widget: Select, manager: DialogManager, item_id: str):
|
||||
manager.dialog_data["selected_user_id"] = int(item_id)
|
||||
await manager.switch_to(AdminMenuSG.user_detail)
|
||||
|
||||
|
||||
async def on_input_mode(_callback: CallbackQuery, _button: Button, manager: DialogManager):
|
||||
await manager.switch_to(AdminMenuSG.users_input)
|
||||
|
||||
|
||||
async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager):
|
||||
container = manager.middleware_data[CONTAINER_NAME]
|
||||
user_dao = await container.get(UserDAO)
|
||||
|
||||
text = (message.text or "").strip()
|
||||
|
||||
user = None
|
||||
if text.startswith("@"):
|
||||
username = text[1:]
|
||||
all_users = await user_dao.get_all()
|
||||
user = next((u for u in all_users if u.username == username), None)
|
||||
elif text.isdigit():
|
||||
user = await user_dao.get_by_id(int(text))
|
||||
|
||||
if not user:
|
||||
await message.answer("❌ Пользователь не найден в базе данных.")
|
||||
return
|
||||
|
||||
manager.dialog_data["selected_user_id"] = user.id
|
||||
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:
|
||||
await _callback.answer("Управление тестами")
|
||||
async def on_tests_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.start(AdminTestsSG.tests_list, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
async def on_users_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.switch_to(AdminMenuSG.users_list)
|
||||
await manager.start(AdminUsersSG.users_list, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
async def on_broadcast_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.switch_to(AdminMenuSG.broadcast_input)
|
||||
await manager.start(AdminBroadcastSG.broadcast_input, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
admin_menu_dialog = Dialog(
|
||||
@@ -146,51 +28,4 @@ admin_menu_dialog = Dialog(
|
||||
),
|
||||
state=AdminMenuSG.main,
|
||||
),
|
||||
Window(
|
||||
Format("<b>👥 Пользователи</b>\n\nВсего: {count}"),
|
||||
ScrollingGroup(
|
||||
Select(
|
||||
Format("{item[0]}"),
|
||||
id="user_select",
|
||||
item_id_getter=lambda x: x[1],
|
||||
items="users",
|
||||
on_click=on_user_selected,
|
||||
),
|
||||
id="users_scroll",
|
||||
width=1,
|
||||
height=7,
|
||||
),
|
||||
Column(
|
||||
Button(Const("✏️ Ввести ID/Username"), id="input_mode", on_click=on_input_mode),
|
||||
Back(Const("◀️ Назад")),
|
||||
),
|
||||
state=AdminMenuSG.users_list,
|
||||
getter=get_users_data,
|
||||
),
|
||||
Window(
|
||||
Const("<b>Введите ID или @username пользователя:</b>"),
|
||||
MessageInput(on_user_input),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_to_list", state=AdminMenuSG.users_list),
|
||||
state=AdminMenuSG.users_input,
|
||||
),
|
||||
Window(
|
||||
Format("{user_info}"),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_to_list", state=AdminMenuSG.users_list),
|
||||
state=AdminMenuSG.user_detail,
|
||||
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,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -3,8 +3,18 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
class AdminMenuSG(StatesGroup):
|
||||
main = State()
|
||||
|
||||
|
||||
class AdminUsersSG(StatesGroup):
|
||||
users_list = State()
|
||||
users_input = State()
|
||||
user_detail = State()
|
||||
|
||||
|
||||
class AdminTestsSG(StatesGroup):
|
||||
tests_list = State()
|
||||
|
||||
|
||||
class AdminBroadcastSG(StatesGroup):
|
||||
broadcast_input = State()
|
||||
broadcast_confirm = State()
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
from aiogram.types import CallbackQuery
|
||||
from aiogram_dialog import Dialog, DialogManager, Window, StartMode
|
||||
from aiogram_dialog.widgets.kbd import Button, Column, ScrollingGroup, Select
|
||||
from aiogram_dialog.widgets.text import Const, Format
|
||||
from dishka import FromDishka
|
||||
from dishka.integrations.aiogram_dialog import inject
|
||||
|
||||
from trudex.application.bot.admin_dialogs.states import AdminTestsSG, AdminMenuSG
|
||||
from trudex.infrastructure.database.dao.test import TestDAO
|
||||
|
||||
|
||||
@inject
|
||||
async def get_tests_data(test_dao: FromDishka[TestDAO], **_kwargs):
|
||||
tests = await test_dao.get_all()
|
||||
|
||||
return {
|
||||
"tests": [
|
||||
(t.title, t.id)
|
||||
for t in tests
|
||||
],
|
||||
"count": len(tests),
|
||||
}
|
||||
|
||||
|
||||
async def on_test_selected(_callback: CallbackQuery, _widget: Select, manager: DialogManager, item_id: str):
|
||||
manager.dialog_data["selected_test_id"] = int(item_id)
|
||||
await _callback.answer("Тест выбран")
|
||||
|
||||
|
||||
async def on_add_test_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager):
|
||||
await _callback.answer("Добавление теста")
|
||||
|
||||
|
||||
async def on_back_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager):
|
||||
await manager.start(AdminMenuSG.main, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
tests_dialog = Dialog(
|
||||
Window(
|
||||
Format("<b>📝 Тесты</b>\n\nВсего: {count}"),
|
||||
ScrollingGroup(
|
||||
Select(
|
||||
Format("{item[0]}"),
|
||||
id="test_select",
|
||||
item_id_getter=lambda x: x[1],
|
||||
items="tests",
|
||||
on_click=on_test_selected,
|
||||
),
|
||||
id="tests_scroll",
|
||||
width=1,
|
||||
height=7,
|
||||
),
|
||||
Column(
|
||||
Button(Const("➕ Добавить тест"), id="add_test", on_click=on_add_test_clicked),
|
||||
Button(Const("◀️ Назад"), id="back", on_click=on_back_clicked),
|
||||
),
|
||||
state=AdminTestsSG.tests_list,
|
||||
getter=get_tests_data,
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,124 @@
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram_dialog import Dialog, DialogManager, Window, StartMode
|
||||
from aiogram_dialog.widgets.input import MessageInput
|
||||
from aiogram_dialog.widgets.kbd import Button, Column, ScrollingGroup, Select, SwitchTo
|
||||
from aiogram_dialog.widgets.text import Const, Format
|
||||
from dishka import FromDishka
|
||||
from dishka.integrations.aiogram import CONTAINER_NAME
|
||||
from dishka.integrations.aiogram_dialog import inject
|
||||
|
||||
from trudex.application.bot.admin_dialogs.states import AdminUsersSG, AdminMenuSG
|
||||
from trudex.infrastructure.database.dao.user import UserDAO
|
||||
|
||||
|
||||
@inject
|
||||
async def get_users_data(user_dao: FromDishka[UserDAO], **_kwargs):
|
||||
users = await user_dao.get_all()
|
||||
|
||||
return {
|
||||
"users": [
|
||||
(f"{u.first_name} (@{u.username or 'нет'})", u.id)
|
||||
for u in users
|
||||
],
|
||||
"count": len(users),
|
||||
}
|
||||
|
||||
|
||||
@inject
|
||||
async def get_user_detail_data(dialog_manager: DialogManager, user_dao: FromDishka[UserDAO], **_kwargs):
|
||||
user_id = dialog_manager.dialog_data.get("selected_user_id")
|
||||
if not user_id:
|
||||
return {"user_info": "Пользователь не выбран"}
|
||||
|
||||
user = await user_dao.get_by_id(user_id)
|
||||
if not user:
|
||||
return {"user_info": "Пользователь не найден"}
|
||||
|
||||
username_str = f"@{user.username}" if user.username else "—"
|
||||
last_name_str = user.last_name or "—"
|
||||
group_str = str(user.group) if user.group else "—"
|
||||
admin_status = "✅ Да" if user.is_admin else "❌ Нет"
|
||||
|
||||
user_info = (
|
||||
f"<b>👤 Информация о пользователе</b>\n\n"
|
||||
f"<b>ID:</b> <code>{user.id}</code>\n"
|
||||
f"<b>Имя:</b> {user.first_name}\n"
|
||||
f"<b>Фамилия:</b> {last_name_str}\n"
|
||||
f"<b>Username:</b> {username_str}\n"
|
||||
f"<b>Группа:</b> {group_str}\n"
|
||||
f"<b>Администратор:</b> {admin_status}"
|
||||
)
|
||||
|
||||
return {"user_info": user_info}
|
||||
|
||||
|
||||
async def on_user_selected(_callback: CallbackQuery, _widget: Select, manager: DialogManager, item_id: str):
|
||||
manager.dialog_data["selected_user_id"] = int(item_id)
|
||||
await manager.switch_to(AdminUsersSG.user_detail)
|
||||
|
||||
|
||||
async def on_input_mode(_callback: CallbackQuery, _button: Button, manager: DialogManager):
|
||||
await manager.switch_to(AdminUsersSG.users_input)
|
||||
|
||||
|
||||
async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager):
|
||||
container = manager.middleware_data[CONTAINER_NAME]
|
||||
user_dao = await container.get(UserDAO)
|
||||
|
||||
text = (message.text or "").strip()
|
||||
|
||||
user = None
|
||||
if text.startswith("@"):
|
||||
username = text[1:]
|
||||
all_users = await user_dao.get_all()
|
||||
user = next((u for u in all_users if u.username == username), None)
|
||||
elif text.isdigit():
|
||||
user = await user_dao.get_by_id(int(text))
|
||||
|
||||
if not user:
|
||||
await message.answer("❌ Пользователь не найден в базе данных.")
|
||||
return
|
||||
|
||||
manager.dialog_data["selected_user_id"] = user.id
|
||||
await manager.switch_to(AdminUsersSG.user_detail)
|
||||
|
||||
|
||||
async def on_back_to_main(_callback: CallbackQuery, _button: Button, manager: DialogManager):
|
||||
await manager.start(AdminMenuSG.main, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
users_dialog = Dialog(
|
||||
Window(
|
||||
Format("<b>👥 Пользователи</b>\n\nВсего: {count}"),
|
||||
ScrollingGroup(
|
||||
Select(
|
||||
Format("{item[0]}"),
|
||||
id="user_select",
|
||||
item_id_getter=lambda x: x[1],
|
||||
items="users",
|
||||
on_click=on_user_selected,
|
||||
),
|
||||
id="users_scroll",
|
||||
width=1,
|
||||
height=7,
|
||||
),
|
||||
Column(
|
||||
Button(Const("✏️ Ввести ID/Username"), id="input_mode", on_click=on_input_mode),
|
||||
Button(Const("◀️ Назад"), id="back", on_click=on_back_to_main),
|
||||
),
|
||||
state=AdminUsersSG.users_list,
|
||||
getter=get_users_data,
|
||||
),
|
||||
Window(
|
||||
Const("<b>Введите ID или @username пользователя:</b>"),
|
||||
MessageInput(on_user_input),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_to_list", state=AdminUsersSG.users_list),
|
||||
state=AdminUsersSG.users_input,
|
||||
),
|
||||
Window(
|
||||
Format("{user_info}"),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_to_list", state=AdminUsersSG.users_list),
|
||||
state=AdminUsersSG.user_detail,
|
||||
getter=get_user_detail_data,
|
||||
),
|
||||
)
|
||||
Reference in New Issue
Block a user