diff options
| author | Tolmachev Igor <me@igorek.dev> | 2026-04-20 20:56:35 +0300 |
|---|---|---|
| committer | Tolmachev Igor <me@igorek.dev> | 2026-04-20 20:56:35 +0300 |
| commit | 75e99ca0712a2c09230e5c6f8d093dc526cc717d (patch) | |
| tree | f3f00494364a82b866f093651cb9a08030135c4e /handlers/admin/users.py | |
| parent | f186fca0f1aa9bbe5eab7613f229df527b2ab774 (diff) | |
| download | vpn_manager_bot-75e99ca0712a2c09230e5c6f8d093dc526cc717d.tar.gz vpn_manager_bot-75e99ca0712a2c09230e5c6f8d093dc526cc717d.zip | |
Add users command
Diffstat (limited to 'handlers/admin/users.py')
| -rw-r--r-- | handlers/admin/users.py | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/handlers/admin/users.py b/handlers/admin/users.py new file mode 100644 index 0000000..88dfd89 --- /dev/null +++ b/handlers/admin/users.py | |||
| @@ -0,0 +1,557 @@ | |||
| 1 | from datetime import UTC, datetime | ||
| 2 | from math import ceil | ||
| 3 | |||
| 4 | from aiogram import Bot, F, Router | ||
| 5 | from aiogram.enums import ButtonStyle | ||
| 6 | from aiogram.filters import Command | ||
| 7 | from aiogram.fsm.context import FSMContext | ||
| 8 | from aiogram.fsm.state import State, StatesGroup | ||
| 9 | from aiogram.types import ( | ||
| 10 | CallbackQuery, | ||
| 11 | InlineKeyboardButton, | ||
| 12 | InlineKeyboardMarkup, | ||
| 13 | KeyboardButton, | ||
| 14 | KeyboardButtonRequestUsers, | ||
| 15 | Message, | ||
| 16 | ReplyKeyboardMarkup, | ||
| 17 | ReplyKeyboardRemove, | ||
| 18 | ) | ||
| 19 | from pydantic import BaseModel | ||
| 20 | from sqlalchemy import delete, select | ||
| 21 | from sqlalchemy.ext.asyncio import AsyncSession | ||
| 22 | from sqlalchemy.sql.functions import count | ||
| 23 | |||
| 24 | from libs.fsm import edit_data, get_data, set_data | ||
| 25 | from libs.user import load_user_cache | ||
| 26 | from models import Payment, User, UserRole | ||
| 27 | from models.callback_data import ( | ||
| 28 | UserAddClb, | ||
| 29 | UserDeleteClb, | ||
| 30 | UserDeleteConfirmClb, | ||
| 31 | UserItemClb, | ||
| 32 | UserPageClb, | ||
| 33 | UserRoleClb, | ||
| 34 | UserRoleSetClb, | ||
| 35 | UserVpnLinkClb, | ||
| 36 | ) | ||
| 37 | |||
| 38 | router = Router(name="users") | ||
| 39 | PAGE_SIZE = 5 | ||
| 40 | ROLE_ICON = { | ||
| 41 | UserRole.ADMIN: "👑", | ||
| 42 | UserRole.REGULAR: "👤", | ||
| 43 | } | ||
| 44 | ROLE_NAME = { | ||
| 45 | UserRole.ADMIN: "Администратор", | ||
| 46 | UserRole.REGULAR: "Пользователь", | ||
| 47 | } | ||
| 48 | |||
| 49 | LIST_TEXT = "Список пользователей:" | ||
| 50 | ADD_CANCEL_BUTTON = "Отменить добавление" | ||
| 51 | EDIT_CANCEL_BUTTON = "Отменить изменение" | ||
| 52 | |||
| 53 | |||
| 54 | class AddUserStates(StatesGroup): | ||
| 55 | user_id = State() | ||
| 56 | vpn_link = State() | ||
| 57 | |||
| 58 | |||
| 59 | class AddUserData(BaseModel): | ||
| 60 | user_id: int | None = None | ||
| 61 | |||
| 62 | |||
| 63 | class EditVpnLinkStates(StatesGroup): | ||
| 64 | vpn_link = State() | ||
| 65 | |||
| 66 | |||
| 67 | class EditVpnLinkData(BaseModel): | ||
| 68 | page: int | ||
| 69 | user_id: int | ||
| 70 | |||
| 71 | |||
| 72 | async def get_list_markup( | ||
| 73 | page: int, | ||
| 74 | bot: Bot, | ||
| 75 | session: AsyncSession, | ||
| 76 | ) -> InlineKeyboardMarkup: | ||
| 77 | total = await session.scalar(select(count()).select_from(User)) | ||
| 78 | assert total is not None | ||
| 79 | total_pages = max(1, ceil(total / PAGE_SIZE)) | ||
| 80 | page = max(0, min(page, total_pages - 1)) | ||
| 81 | |||
| 82 | users = await session.scalars( | ||
| 83 | select(User).offset(PAGE_SIZE * page).limit(PAGE_SIZE).order_by(User.id.desc()) | ||
| 84 | ) | ||
| 85 | |||
| 86 | user_buttons = [] | ||
| 87 | for u in users: | ||
| 88 | user_cache = await load_user_cache(bot, u.id) | ||
| 89 | user_buttons.append( | ||
| 90 | [ | ||
| 91 | InlineKeyboardButton( | ||
| 92 | text=f"{ROLE_ICON[u.role]} {user_cache.full_name}", | ||
| 93 | callback_data=UserItemClb(page=page, user_id=u.id).pack(), | ||
| 94 | ) | ||
| 95 | ] | ||
| 96 | ) | ||
| 97 | |||
| 98 | page_buttons = [] | ||
| 99 | if page > 0: | ||
| 100 | page_buttons.append( | ||
| 101 | InlineKeyboardButton( | ||
| 102 | text="◀️", | ||
| 103 | callback_data=UserPageClb(page=page - 1).pack(), | ||
| 104 | ) | ||
| 105 | ) | ||
| 106 | if page < total_pages - 1: | ||
| 107 | page_buttons.append( | ||
| 108 | InlineKeyboardButton( | ||
| 109 | text="▶️", | ||
| 110 | callback_data=UserPageClb(page=page + 1).pack(), | ||
| 111 | ) | ||
| 112 | ) | ||
| 113 | |||
| 114 | add_row = [ | ||
| 115 | InlineKeyboardButton( | ||
| 116 | text="➕ Добавить пользователя", | ||
| 117 | callback_data=UserAddClb().pack(), | ||
| 118 | ) | ||
| 119 | ] | ||
| 120 | |||
| 121 | return InlineKeyboardMarkup(inline_keyboard=[*user_buttons, page_buttons, add_row]) | ||
| 122 | |||
| 123 | |||
| 124 | def build_item_markup(user_id: int, page: int) -> InlineKeyboardMarkup: | ||
| 125 | return InlineKeyboardMarkup( | ||
| 126 | inline_keyboard=[ | ||
| 127 | [ | ||
| 128 | InlineKeyboardButton( | ||
| 129 | text="Поменять роль", | ||
| 130 | callback_data=UserRoleClb(page=page, user_id=user_id).pack(), | ||
| 131 | ), | ||
| 132 | ], | ||
| 133 | [ | ||
| 134 | InlineKeyboardButton( | ||
| 135 | text="Изменить VPN ссылку", | ||
| 136 | callback_data=UserVpnLinkClb(page=page, user_id=user_id).pack(), | ||
| 137 | ), | ||
| 138 | ], | ||
| 139 | [ | ||
| 140 | InlineKeyboardButton( | ||
| 141 | text="Удалить пользователя", | ||
| 142 | style=ButtonStyle.DANGER, | ||
| 143 | callback_data=UserDeleteClb(page=page, user_id=user_id).pack(), | ||
| 144 | ), | ||
| 145 | ], | ||
| 146 | [ | ||
| 147 | InlineKeyboardButton( | ||
| 148 | text="Назад к списку", | ||
| 149 | callback_data=UserPageClb(page=page).pack(), | ||
| 150 | ), | ||
| 151 | ], | ||
| 152 | ] | ||
| 153 | ) | ||
| 154 | |||
| 155 | |||
| 156 | def build_role_markup(user_id: int, page: int) -> InlineKeyboardMarkup: | ||
| 157 | return InlineKeyboardMarkup( | ||
| 158 | inline_keyboard=[ | ||
| 159 | [ | ||
| 160 | InlineKeyboardButton( | ||
| 161 | text=f"{ROLE_ICON[UserRole.REGULAR]} {ROLE_NAME[UserRole.REGULAR]}", | ||
| 162 | callback_data=UserRoleSetClb( | ||
| 163 | page=page, | ||
| 164 | user_id=user_id, | ||
| 165 | role=UserRole.REGULAR, | ||
| 166 | ).pack(), | ||
| 167 | ), | ||
| 168 | ], | ||
| 169 | [ | ||
| 170 | InlineKeyboardButton( | ||
| 171 | text=f"{ROLE_ICON[UserRole.ADMIN]} {ROLE_NAME[UserRole.ADMIN]}", | ||
| 172 | callback_data=UserRoleSetClb( | ||
| 173 | page=page, | ||
| 174 | user_id=user_id, | ||
| 175 | role=UserRole.ADMIN, | ||
| 176 | ).pack(), | ||
| 177 | ), | ||
| 178 | ], | ||
| 179 | [ | ||
| 180 | InlineKeyboardButton( | ||
| 181 | text="Назад", | ||
| 182 | callback_data=UserItemClb(page=page, user_id=user_id).pack(), | ||
| 183 | ), | ||
| 184 | ], | ||
| 185 | ] | ||
| 186 | ) | ||
| 187 | |||
| 188 | |||
| 189 | def build_delete_markup(user_id: int, page: int) -> InlineKeyboardMarkup: | ||
| 190 | return InlineKeyboardMarkup( | ||
| 191 | inline_keyboard=[ | ||
| 192 | [ | ||
| 193 | InlineKeyboardButton( | ||
| 194 | text="❌ Удалить", | ||
| 195 | style=ButtonStyle.DANGER, | ||
| 196 | callback_data=UserDeleteConfirmClb( | ||
| 197 | page=page, | ||
| 198 | user_id=user_id, | ||
| 199 | ).pack(), | ||
| 200 | ), | ||
| 201 | ], | ||
| 202 | [ | ||
| 203 | InlineKeyboardButton( | ||
| 204 | text="Назад", | ||
| 205 | callback_data=UserItemClb(page=page, user_id=user_id).pack(), | ||
| 206 | ), | ||
| 207 | ], | ||
| 208 | ] | ||
| 209 | ) | ||
| 210 | |||
| 211 | |||
| 212 | async def get_item_text(bot: Bot, target: User) -> str: | ||
| 213 | user_cache = await load_user_cache(bot, target.id) | ||
| 214 | return ( | ||
| 215 | f"Пользователь: {user_cache.mention}\n" | ||
| 216 | f"Роль: {ROLE_NAME[target.role]}\n" | ||
| 217 | f"VPN ссылка: {target.vpn_link}" | ||
| 218 | ) | ||
| 219 | |||
| 220 | |||
| 221 | @router.message(Command("users")) | ||
| 222 | async def command(msg: Message, bot: Bot, session: AsyncSession) -> None: | ||
| 223 | await msg.answer( | ||
| 224 | LIST_TEXT, | ||
| 225 | reply_markup=await get_list_markup(0, bot, session), | ||
| 226 | ) | ||
| 227 | |||
| 228 | |||
| 229 | @router.callback_query(UserPageClb.filter()) | ||
| 230 | async def page( | ||
| 231 | clb: CallbackQuery, | ||
| 232 | bot: Bot, | ||
| 233 | callback_data: UserPageClb, | ||
| 234 | session: AsyncSession, | ||
| 235 | ) -> None: | ||
| 236 | assert isinstance(clb.message, Message) | ||
| 237 | await clb.message.edit_text( | ||
| 238 | LIST_TEXT, | ||
| 239 | reply_markup=await get_list_markup(callback_data.page, bot, session), | ||
| 240 | ) | ||
| 241 | await clb.answer() | ||
| 242 | |||
| 243 | |||
| 244 | @router.callback_query(UserItemClb.filter()) | ||
| 245 | async def item( | ||
| 246 | clb: CallbackQuery, | ||
| 247 | bot: Bot, | ||
| 248 | callback_data: UserItemClb, | ||
| 249 | session: AsyncSession, | ||
| 250 | ) -> None: | ||
| 251 | assert isinstance(clb.message, Message) | ||
| 252 | target = await session.get(User, callback_data.user_id) | ||
| 253 | if target is None: | ||
| 254 | await clb.answer("Пользователь не найден.", show_alert=True) | ||
| 255 | return | ||
| 256 | |||
| 257 | await clb.message.edit_text( | ||
| 258 | await get_item_text(bot, target), | ||
| 259 | reply_markup=build_item_markup(target.id, callback_data.page), | ||
| 260 | ) | ||
| 261 | await clb.answer() | ||
| 262 | |||
| 263 | |||
| 264 | @router.callback_query(UserRoleClb.filter()) | ||
| 265 | async def role_menu( | ||
| 266 | clb: CallbackQuery, | ||
| 267 | callback_data: UserRoleClb, | ||
| 268 | session: AsyncSession, | ||
| 269 | user: User, | ||
| 270 | ) -> None: | ||
| 271 | assert isinstance(clb.message, Message) | ||
| 272 | if user.id == callback_data.user_id: | ||
| 273 | await clb.answer("Нельзя поменять свою роль.", show_alert=True) | ||
| 274 | return | ||
| 275 | |||
| 276 | target = await session.get(User, callback_data.user_id) | ||
| 277 | if target is None: | ||
| 278 | await clb.answer("Пользователь не найден.", show_alert=True) | ||
| 279 | return | ||
| 280 | |||
| 281 | await clb.message.edit_text( | ||
| 282 | f"Выберете новую роль для пользователя (текущая: {ROLE_NAME[target.role]}):", | ||
| 283 | reply_markup=build_role_markup(target.id, callback_data.page), | ||
| 284 | ) | ||
| 285 | await clb.answer() | ||
| 286 | |||
| 287 | |||
| 288 | @router.callback_query(UserRoleSetClb.filter()) | ||
| 289 | async def role_set( | ||
| 290 | clb: CallbackQuery, | ||
| 291 | bot: Bot, | ||
| 292 | callback_data: UserRoleSetClb, | ||
| 293 | session: AsyncSession, | ||
| 294 | user: User, | ||
| 295 | ) -> None: | ||
| 296 | assert isinstance(clb.message, Message) | ||
| 297 | if user.id == callback_data.user_id: | ||
| 298 | await clb.answer("Нельзя поменять свою роль.", show_alert=True) | ||
| 299 | return | ||
| 300 | |||
| 301 | target = await session.get(User, callback_data.user_id) | ||
| 302 | if target is None: | ||
| 303 | await clb.answer("Пользователь не найден.", show_alert=True) | ||
| 304 | return | ||
| 305 | |||
| 306 | if target.role != callback_data.role: | ||
| 307 | target.role = callback_data.role | ||
| 308 | await session.flush() | ||
| 309 | |||
| 310 | await clb.message.edit_text( | ||
| 311 | await get_item_text(bot, target), | ||
| 312 | reply_markup=build_item_markup(target.id, callback_data.page), | ||
| 313 | ) | ||
| 314 | await clb.answer(f"Роль: {ROLE_NAME[target.role]}.") | ||
| 315 | |||
| 316 | |||
| 317 | @router.callback_query(UserDeleteClb.filter()) | ||
| 318 | async def delete_menu( | ||
| 319 | clb: CallbackQuery, | ||
| 320 | bot: Bot, | ||
| 321 | callback_data: UserDeleteClb, | ||
| 322 | session: AsyncSession, | ||
| 323 | user: User, | ||
| 324 | ) -> None: | ||
| 325 | assert isinstance(clb.message, Message) | ||
| 326 | if user.id == callback_data.user_id: | ||
| 327 | await clb.answer("Нельзя удалить себя.", show_alert=True) | ||
| 328 | return | ||
| 329 | |||
| 330 | target = await session.get(User, callback_data.user_id) | ||
| 331 | if target is None: | ||
| 332 | await clb.answer("Пользователь не найден.", show_alert=True) | ||
| 333 | return | ||
| 334 | |||
| 335 | user_cache = await load_user_cache(bot, target.id) | ||
| 336 | await clb.message.edit_text( | ||
| 337 | f"Удалить пользователя {user_cache.mention}?", | ||
| 338 | reply_markup=build_delete_markup(target.id, callback_data.page), | ||
| 339 | ) | ||
| 340 | await clb.answer() | ||
| 341 | |||
| 342 | |||
| 343 | @router.callback_query(UserDeleteConfirmClb.filter()) | ||
| 344 | async def delete_confirm( | ||
| 345 | clb: CallbackQuery, | ||
| 346 | bot: Bot, | ||
| 347 | callback_data: UserDeleteConfirmClb, | ||
| 348 | session: AsyncSession, | ||
| 349 | user: User, | ||
| 350 | ) -> None: | ||
| 351 | assert isinstance(clb.message, Message) | ||
| 352 | if user.id == callback_data.user_id: | ||
| 353 | await clb.answer("Нельзя удалить себя.", show_alert=True) | ||
| 354 | return | ||
| 355 | |||
| 356 | target = await session.get(User, callback_data.user_id) | ||
| 357 | if target is None: | ||
| 358 | await clb.answer("Пользователь не найден.", show_alert=True) | ||
| 359 | return | ||
| 360 | |||
| 361 | await session.execute(delete(Payment).where(Payment.user_id == target.id)) | ||
| 362 | await session.delete(target) | ||
| 363 | await session.flush() | ||
| 364 | |||
| 365 | await clb.message.edit_text( | ||
| 366 | LIST_TEXT, | ||
| 367 | reply_markup=await get_list_markup(callback_data.page, bot, session), | ||
| 368 | ) | ||
| 369 | await clb.answer("Пользователь удалён.") | ||
| 370 | |||
| 371 | |||
| 372 | @router.callback_query(UserVpnLinkClb.filter()) | ||
| 373 | async def vpn_link_button( | ||
| 374 | clb: CallbackQuery, | ||
| 375 | bot: Bot, | ||
| 376 | state: FSMContext, | ||
| 377 | callback_data: UserVpnLinkClb, | ||
| 378 | session: AsyncSession, | ||
| 379 | ) -> None: | ||
| 380 | assert isinstance(clb.message, Message) | ||
| 381 | target = await session.get(User, callback_data.user_id) | ||
| 382 | if target is None: | ||
| 383 | await clb.answer("Пользователь не найден.", show_alert=True) | ||
| 384 | return | ||
| 385 | |||
| 386 | await clb.message.delete() | ||
| 387 | await bot.send_message( | ||
| 388 | clb.from_user.id, | ||
| 389 | "Укажите новую ссылку для доступа к VPN.", | ||
| 390 | reply_markup=ReplyKeyboardMarkup( | ||
| 391 | keyboard=[ | ||
| 392 | [KeyboardButton(text=EDIT_CANCEL_BUTTON, style=ButtonStyle.DANGER)] | ||
| 393 | ], | ||
| 394 | resize_keyboard=True, | ||
| 395 | ), | ||
| 396 | ) | ||
| 397 | await set_data( | ||
| 398 | state, | ||
| 399 | EditVpnLinkData(page=callback_data.page, user_id=callback_data.user_id), | ||
| 400 | ) | ||
| 401 | await state.set_state(EditVpnLinkStates.vpn_link) | ||
| 402 | await clb.answer() | ||
| 403 | |||
| 404 | |||
| 405 | async def send_item(bot: Bot, chat_id: int, target: User, page: int) -> None: | ||
| 406 | await bot.send_message( | ||
| 407 | chat_id, | ||
| 408 | await get_item_text(bot, target), | ||
| 409 | reply_markup=build_item_markup(target.id, page), | ||
| 410 | ) | ||
| 411 | |||
| 412 | |||
| 413 | @router.message(EditVpnLinkStates.vpn_link, F.text == EDIT_CANCEL_BUTTON) | ||
| 414 | async def vpn_link_cancel( | ||
| 415 | msg: Message, | ||
| 416 | bot: Bot, | ||
| 417 | state: FSMContext, | ||
| 418 | session: AsyncSession, | ||
| 419 | ) -> None: | ||
| 420 | data = await get_data(state, EditVpnLinkData) | ||
| 421 | await state.clear() | ||
| 422 | |||
| 423 | await msg.answer( | ||
| 424 | "Изменение VPN ссылки отменено.", | ||
| 425 | reply_markup=ReplyKeyboardRemove(), | ||
| 426 | ) | ||
| 427 | |||
| 428 | target = await session.get(User, data.user_id) | ||
| 429 | if target is None: | ||
| 430 | return | ||
| 431 | await send_item(bot, msg.chat.id, target, data.page) | ||
| 432 | |||
| 433 | |||
| 434 | @router.message(EditVpnLinkStates.vpn_link) | ||
| 435 | async def vpn_link_set( | ||
| 436 | msg: Message, | ||
| 437 | bot: Bot, | ||
| 438 | state: FSMContext, | ||
| 439 | session: AsyncSession, | ||
| 440 | ) -> None: | ||
| 441 | if msg.text is None: | ||
| 442 | await msg.answer("Вы должны указать ссылку отправив текстовое сообщение.") | ||
| 443 | return | ||
| 444 | |||
| 445 | data = await get_data(state, EditVpnLinkData) | ||
| 446 | target = await session.get(User, data.user_id) | ||
| 447 | if target is None: | ||
| 448 | await msg.answer( | ||
| 449 | "Пользователь не найден.", | ||
| 450 | reply_markup=ReplyKeyboardRemove(), | ||
| 451 | ) | ||
| 452 | await state.clear() | ||
| 453 | return | ||
| 454 | |||
| 455 | target.vpn_link = msg.text | ||
| 456 | await session.flush() | ||
| 457 | await state.clear() | ||
| 458 | |||
| 459 | await msg.answer("VPN ссылка обновлена.", reply_markup=ReplyKeyboardRemove()) | ||
| 460 | await send_item(bot, msg.chat.id, target, data.page) | ||
| 461 | |||
| 462 | |||
| 463 | @router.callback_query(UserAddClb.filter()) | ||
| 464 | async def add_button( | ||
| 465 | clb: CallbackQuery, | ||
| 466 | bot: Bot, | ||
| 467 | state: FSMContext, | ||
| 468 | ) -> None: | ||
| 469 | assert isinstance(clb.message, Message) | ||
| 470 | await clb.message.delete() | ||
| 471 | await bot.send_message( | ||
| 472 | clb.from_user.id, | ||
| 473 | "Выберете пользователя которого хотите добавить.", | ||
| 474 | reply_markup=ReplyKeyboardMarkup( | ||
| 475 | keyboard=[ | ||
| 476 | [ | ||
| 477 | KeyboardButton( | ||
| 478 | text="Выбрать пользователя", | ||
| 479 | style=ButtonStyle.PRIMARY, | ||
| 480 | request_users=KeyboardButtonRequestUsers(request_id=0), | ||
| 481 | ), | ||
| 482 | ], | ||
| 483 | [KeyboardButton(text=ADD_CANCEL_BUTTON, style=ButtonStyle.DANGER)], | ||
| 484 | ], | ||
| 485 | resize_keyboard=True, | ||
| 486 | ), | ||
| 487 | ) | ||
| 488 | await set_data(state, AddUserData(user_id=None)) | ||
| 489 | await state.set_state(AddUserStates.user_id) | ||
| 490 | await clb.answer() | ||
| 491 | |||
| 492 | |||
| 493 | @router.message(AddUserStates(), F.text == ADD_CANCEL_BUTTON) | ||
| 494 | async def add_cancel( | ||
| 495 | msg: Message, | ||
| 496 | bot: Bot, | ||
| 497 | state: FSMContext, | ||
| 498 | session: AsyncSession, | ||
| 499 | ) -> None: | ||
| 500 | await msg.answer( | ||
| 501 | "Добавление пользователя отменено.", | ||
| 502 | reply_markup=ReplyKeyboardRemove(), | ||
| 503 | ) | ||
| 504 | |||
| 505 | await msg.answer( | ||
| 506 | LIST_TEXT, | ||
| 507 | reply_markup=await get_list_markup(0, bot, session), | ||
| 508 | ) | ||
| 509 | await state.clear() | ||
| 510 | |||
| 511 | |||
| 512 | @router.message(AddUserStates.user_id) | ||
| 513 | async def add_set_user_id(msg: Message, state: FSMContext) -> None: | ||
| 514 | if msg.users_shared is None: | ||
| 515 | await msg.answer("Вы должны воспользоваться кнопкой ниже.") | ||
| 516 | return | ||
| 517 | |||
| 518 | async with edit_data(state, AddUserData) as data: | ||
| 519 | data.user_id = msg.users_shared.users[0].user_id | ||
| 520 | |||
| 521 | await msg.answer( | ||
| 522 | "Укажите ссылку для доступа к VPN.", | ||
| 523 | reply_markup=ReplyKeyboardMarkup( | ||
| 524 | keyboard=[ | ||
| 525 | [KeyboardButton(text=ADD_CANCEL_BUTTON, style=ButtonStyle.DANGER)] | ||
| 526 | ], | ||
| 527 | resize_keyboard=True, | ||
| 528 | ), | ||
| 529 | ) | ||
| 530 | await state.set_state(AddUserStates.vpn_link) | ||
| 531 | |||
| 532 | |||
| 533 | @router.message(AddUserStates.vpn_link) | ||
| 534 | async def add_set_vpn_link( | ||
| 535 | msg: Message, | ||
| 536 | bot: Bot, | ||
| 537 | state: FSMContext, | ||
| 538 | session: AsyncSession, | ||
| 539 | ) -> None: | ||
| 540 | if msg.text is None: | ||
| 541 | await msg.answer("Вы должны указать ссылку отправив текстовое сообщение.") | ||
| 542 | return | ||
| 543 | |||
| 544 | data = await get_data(state, AddUserData) | ||
| 545 | assert data.user_id is not None | ||
| 546 | |||
| 547 | session.add(User(id=data.user_id, vpn_link=msg.text, datetime=datetime.now(UTC))) | ||
| 548 | await session.flush() | ||
| 549 | |||
| 550 | await msg.answer("Пользователь добавлен.", reply_markup=ReplyKeyboardRemove()) | ||
| 551 | |||
| 552 | target = await session.get(User, data.user_id) | ||
| 553 | if target is None: | ||
| 554 | return | ||
| 555 | |||
| 556 | await send_item(bot, msg.chat.id, target, 0) | ||
| 557 | await state.clear() | ||
