diff options
| author | Tolmachev Igor <me@igorek.dev> | 2026-03-23 20:11:51 +0300 |
|---|---|---|
| committer | Tolmachev Igor <me@igorek.dev> | 2026-03-23 20:11:51 +0300 |
| commit | f2eb4e59327da4eabe875b077f8b311c6ac7251e (patch) | |
| tree | ea1e9bd0589bd93d49ee2ed552a0787487741a1d | |
| parent | d5994e732d7b1dfa469cf400132ba49c8f75315e (diff) | |
| download | vpn_manager_bot-f2eb4e59327da4eabe875b077f8b311c6ac7251e.tar.gz vpn_manager_bot-f2eb4e59327da4eabe875b077f8b311c6ac7251e.zip | |
Add add_user command
| -rw-r--r-- | handlers/admin/__init__.py | 2 | ||||
| -rw-r--r-- | handlers/admin/add_user.py | 99 | ||||
| -rw-r--r-- | handlers/admin/new_announcement.py | 10 | ||||
| -rw-r--r-- | handlers/admin/new_invoice.py | 6 |
4 files changed, 109 insertions, 8 deletions
diff --git a/handlers/admin/__init__.py b/handlers/admin/__init__.py index 98b127f..02f9e6e 100644 --- a/handlers/admin/__init__.py +++ b/handlers/admin/__init__.py | |||
| @@ -4,6 +4,7 @@ from aiogram.filters import MagicData | |||
| 4 | # isort: off | 4 | # isort: off |
| 5 | from . import new_announcement | 5 | from . import new_announcement |
| 6 | from . import new_invoice | 6 | from . import new_invoice |
| 7 | from . import add_user | ||
| 7 | # isort: on | 8 | # isort: on |
| 8 | 9 | ||
| 9 | router = Router(name="admin") | 10 | router = Router(name="admin") |
| @@ -13,4 +14,5 @@ router.callback_query.filter(MagicData(F.user.is_admin())) | |||
| 13 | router.include_routers( | 14 | router.include_routers( |
| 14 | new_announcement.router, | 15 | new_announcement.router, |
| 15 | new_invoice.router, | 16 | new_invoice.router, |
| 17 | add_user.router, | ||
| 16 | ) | 18 | ) |
diff --git a/handlers/admin/add_user.py b/handlers/admin/add_user.py new file mode 100644 index 0000000..b9b8604 --- /dev/null +++ b/handlers/admin/add_user.py | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | from datetime import UTC, datetime | ||
| 2 | |||
| 3 | from aiogram import F, Router | ||
| 4 | from aiogram.enums.button_style import ButtonStyle | ||
| 5 | from aiogram.filters import Command | ||
| 6 | from aiogram.fsm.context import FSMContext | ||
| 7 | from aiogram.fsm.state import State, StatesGroup | ||
| 8 | from aiogram.types import ( | ||
| 9 | KeyboardButton, | ||
| 10 | KeyboardButtonRequestUsers, | ||
| 11 | Message, | ||
| 12 | ReplyKeyboardMarkup, | ||
| 13 | ReplyKeyboardRemove, | ||
| 14 | ) | ||
| 15 | from pydantic import BaseModel | ||
| 16 | from sqlalchemy.ext.asyncio import AsyncSession | ||
| 17 | |||
| 18 | from libs.fsm import edit_data, get_data, set_data | ||
| 19 | from models import User | ||
| 20 | |||
| 21 | router = Router(name="add_user") | ||
| 22 | |||
| 23 | |||
| 24 | class AddUserStates(StatesGroup): | ||
| 25 | user_id = State() | ||
| 26 | vpn_link = State() | ||
| 27 | |||
| 28 | |||
| 29 | class AddUserData(BaseModel): | ||
| 30 | user_id: int | None = None | ||
| 31 | |||
| 32 | |||
| 33 | CANCEL_BUTTON = "Отменить добавление" | ||
| 34 | |||
| 35 | |||
| 36 | @router.message(Command("add_user")) | ||
| 37 | async def add_user_command(msg: Message, state: FSMContext) -> None: | ||
| 38 | await msg.answer( | ||
| 39 | "Выберете пользователя которого хотите добавить.", | ||
| 40 | reply_markup=ReplyKeyboardMarkup( | ||
| 41 | keyboard=[ | ||
| 42 | [ | ||
| 43 | KeyboardButton( | ||
| 44 | text="Выбрать пользователя", | ||
| 45 | style=ButtonStyle.PRIMARY, | ||
| 46 | request_users=KeyboardButtonRequestUsers(request_id=0), | ||
| 47 | ), | ||
| 48 | ], | ||
| 49 | [ | ||
| 50 | KeyboardButton(text=CANCEL_BUTTON, style=ButtonStyle.DANGER), | ||
| 51 | ], | ||
| 52 | ], | ||
| 53 | resize_keyboard=True, | ||
| 54 | ), | ||
| 55 | ) | ||
| 56 | await set_data(state, AddUserData(user_id=None)) | ||
| 57 | await state.set_state(AddUserStates.user_id) | ||
| 58 | |||
| 59 | |||
| 60 | @router.message(AddUserStates(), F.text == CANCEL_BUTTON) | ||
| 61 | async def add_user_cancel(msg: Message, state: FSMContext) -> None: | ||
| 62 | await msg.answer( | ||
| 63 | "Добавление пользователей отменено", | ||
| 64 | reply_markup=ReplyKeyboardRemove(), | ||
| 65 | ) | ||
| 66 | await state.clear() | ||
| 67 | |||
| 68 | |||
| 69 | @router.message(AddUserStates.user_id) | ||
| 70 | async def add_user_user_id(msg: Message, state: FSMContext) -> None: | ||
| 71 | if msg.users_shared is None: | ||
| 72 | await msg.answer("Вы должны воспользоваться кнопкой ниже.") | ||
| 73 | return | ||
| 74 | |||
| 75 | async with edit_data(state, AddUserData) as data: | ||
| 76 | data.user_id = msg.users_shared.users[0].user_id | ||
| 77 | |||
| 78 | await msg.answer("Укажите ссылку для доступа к VPN") | ||
| 79 | await state.set_state(AddUserStates.vpn_link) | ||
| 80 | |||
| 81 | |||
| 82 | @router.message(AddUserStates.vpn_link) | ||
| 83 | async def add_user_vpn_link( | ||
| 84 | msg: Message, | ||
| 85 | state: FSMContext, | ||
| 86 | session: AsyncSession, | ||
| 87 | ) -> None: | ||
| 88 | if msg.text is None: | ||
| 89 | await msg.answer("Вы должны указать ссылку отправив текстовое сообщение.") | ||
| 90 | return | ||
| 91 | |||
| 92 | data = await get_data(state, AddUserData) | ||
| 93 | assert data.user_id is not None | ||
| 94 | |||
| 95 | session.add(User(id=data.user_id, vpn_link=msg.text, datetime=datetime.now(UTC))) | ||
| 96 | await session.flush() | ||
| 97 | |||
| 98 | await msg.answer("Пользователь добавлен.") | ||
| 99 | await state.clear() | ||
diff --git a/handlers/admin/new_announcement.py b/handlers/admin/new_announcement.py index 0920c47..6aa099e 100644 --- a/handlers/admin/new_announcement.py +++ b/handlers/admin/new_announcement.py | |||
| @@ -7,7 +7,6 @@ from aiogram.filters import Command | |||
| 7 | from aiogram.fsm.context import FSMContext | 7 | from aiogram.fsm.context import FSMContext |
| 8 | from aiogram.fsm.state import State, StatesGroup | 8 | from aiogram.fsm.state import State, StatesGroup |
| 9 | from aiogram.types import ( | 9 | from aiogram.types import ( |
| 10 | ContentType, | ||
| 11 | KeyboardButton, | 10 | KeyboardButton, |
| 12 | Message, | 11 | Message, |
| 13 | ReplyKeyboardMarkup, | 12 | ReplyKeyboardMarkup, |
| @@ -67,6 +66,10 @@ async def announcement_send( | |||
| 67 | await msg.answer("Для публикации анонса укажите текст сообщения.") | 66 | await msg.answer("Для публикации анонса укажите текст сообщения.") |
| 68 | return | 67 | return |
| 69 | 68 | ||
| 69 | announcement = Announcement(message=data.rich_text, datetime=datetime.now(UTC)) | ||
| 70 | session.add(announcement) | ||
| 71 | await session.flush() | ||
| 72 | |||
| 70 | status_template = "Публикация анонса...\nОпубликовано: {}" | 73 | status_template = "Публикация анонса...\nОпубликовано: {}" |
| 71 | status_msg = await msg.answer(status_template.format(0)) | 74 | status_msg = await msg.answer(status_template.format(0)) |
| 72 | 75 | ||
| @@ -76,9 +79,6 @@ async def announcement_send( | |||
| 76 | except TelegramAPIError: | 79 | except TelegramAPIError: |
| 77 | pass | 80 | pass |
| 78 | 81 | ||
| 79 | announcement = Announcement(message=data, datetime=datetime.now(UTC)) | ||
| 80 | session.add(announcement) | ||
| 81 | |||
| 82 | await status_msg.delete() | 82 | await status_msg.delete() |
| 83 | await msg.answer( | 83 | await msg.answer( |
| 84 | "Анонс отправлен всем пользователям", | 84 | "Анонс отправлен всем пользователям", |
| @@ -95,7 +95,7 @@ async def announcement_cancel(msg: Message, state: FSMContext) -> None: | |||
| 95 | 95 | ||
| 96 | @router.message(NewAnnouncementStates.message) | 96 | @router.message(NewAnnouncementStates.message) |
| 97 | async def announcement_message(msg: Message, bot: Bot, state: FSMContext) -> None: | 97 | async def announcement_message(msg: Message, bot: Bot, state: FSMContext) -> None: |
| 98 | if msg.content_type != ContentType.TEXT or msg.text is None: | 98 | if msg.text is None: |
| 99 | await msg.answer( | 99 | await msg.answer( |
| 100 | "Неверный тип сообщения.\n" | 100 | "Неверный тип сообщения.\n" |
| 101 | "Бот поддерживает отправку только текстовых анонсов." | 101 | "Бот поддерживает отправку только текстовых анонсов." |
diff --git a/handlers/admin/new_invoice.py b/handlers/admin/new_invoice.py index 7e1a64d..0427d90 100644 --- a/handlers/admin/new_invoice.py +++ b/handlers/admin/new_invoice.py | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | from datetime import UTC, datetime | 1 | from datetime import UTC, datetime |
| 2 | 2 | ||
| 3 | from aiogram import Bot, F, Router | 3 | from aiogram import Bot, F, Router |
| 4 | from aiogram.enums import ButtonStyle, ContentType | 4 | from aiogram.enums import ButtonStyle |
| 5 | from aiogram.exceptions import TelegramAPIError | 5 | from aiogram.exceptions import TelegramAPIError |
| 6 | from aiogram.filters import Command | 6 | from aiogram.filters import Command |
| 7 | from aiogram.fsm.context import FSMContext | 7 | from aiogram.fsm.context import FSMContext |
| @@ -69,7 +69,7 @@ async def invoice_send( | |||
| 69 | status_template = "Рассылка счёта...\nОтправлено: {}" | 69 | status_template = "Рассылка счёта...\nОтправлено: {}" |
| 70 | status_msg = await msg.answer(status_template.format(0)) | 70 | status_msg = await msg.answer(status_template.format(0)) |
| 71 | 71 | ||
| 72 | invoice = Invoice(message=data, datetime=datetime.now(UTC)) | 72 | invoice = Invoice(message=data.rich_text, datetime=datetime.now(UTC)) |
| 73 | session.add(invoice) | 73 | session.add(invoice) |
| 74 | await session.flush() | 74 | await session.flush() |
| 75 | 75 | ||
| @@ -95,7 +95,7 @@ async def invoice_cancel(msg: Message, state: FSMContext) -> None: | |||
| 95 | 95 | ||
| 96 | @router.message(NewInvoiceStates.message) | 96 | @router.message(NewInvoiceStates.message) |
| 97 | async def invoice_message(msg: Message, bot: Bot, state: FSMContext) -> None: | 97 | async def invoice_message(msg: Message, bot: Bot, state: FSMContext) -> None: |
| 98 | if msg.content_type != ContentType.TEXT or msg.text is None: | 98 | if msg.text is None: |
| 99 | await msg.answer( | 99 | await msg.answer( |
| 100 | "Неверный тип сообщения.\n" | 100 | "Неверный тип сообщения.\n" |
| 101 | "Бот поддерживает отправку только текстовых счетов." | 101 | "Бот поддерживает отправку только текстовых счетов." |
