From ffee163d8206f0fb1315015e4c60248b68d459bc Mon Sep 17 00:00:00 2001 From: Tolmachev Igor Date: Mon, 23 Mar 2026 00:21:18 +0300 Subject: Add start, help and vpn_link commands --- handlers/__init__.py | 5 +++ handlers/admin/__init__.py | 5 ++- handlers/middleware.py | 56 +++++++++++++++++++++++++++++++++ handlers/user/__init__.py | 9 ++++++ handlers/user/info.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++ handlers/user/vpn_link.py | 22 +++++++++++++ main.py | 12 +++++++- pyproject.toml | 1 + uv.lock | 6 ++-- 9 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 handlers/middleware.py create mode 100644 handlers/user/info.py create mode 100644 handlers/user/vpn_link.py diff --git a/handlers/__init__.py b/handlers/__init__.py index 92143c9..19704d6 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -1,5 +1,6 @@ # isort: off from aiogram import Router +from .middleware import InjectSessionMiddleware, UserAccessMiddleware from . import user from . import admin # isort: on @@ -9,3 +10,7 @@ router.include_routers( user.router, admin.router, ) + +for observer in (router.message, router.callback_query): + observer.outer_middleware(InjectSessionMiddleware()) + observer.outer_middleware(UserAccessMiddleware()) diff --git a/handlers/admin/__init__.py b/handlers/admin/__init__.py index 7197879..d0a5587 100644 --- a/handlers/admin/__init__.py +++ b/handlers/admin/__init__.py @@ -1,3 +1,6 @@ -from aiogram import Router +from aiogram import F, Router +from aiogram.filters import MagicData router = Router(name="admin") +router.message.filter(MagicData(F.user.is_admin())) +router.callback_query.filter(MagicData(F.user.is_admin())) diff --git a/handlers/middleware.py b/handlers/middleware.py new file mode 100644 index 0000000..87a117a --- /dev/null +++ b/handlers/middleware.py @@ -0,0 +1,56 @@ +from typing import Any, Awaitable, Callable + +from aiogram import BaseMiddleware +from aiogram.types import CallbackQuery, Message, TelegramObject +from sqlalchemy.ext.asyncio import AsyncSession + +from database import sessions +from models import User + + +class InjectSessionMiddleware(BaseMiddleware): + async def __call__[T]( + self, + handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: dict[str, Any], + ) -> Any: + async with sessions.begin() as session: + data["session"] = session + handler_result = await handler(event, data) + return handler_result + + +class UserAccessMiddleware(BaseMiddleware): + async def __call__( + self, + handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: dict[str, Any], + ) -> Any: + if not isinstance(event, (Message, CallbackQuery)): + raise TypeError( + f"UserAccessMiddleware doesn't support event with type: {type(event).__name__}", + event, + ) + + if isinstance(event, Message): + user_id = event.chat.id + else: + user_id = event.from_user.id + + session: AsyncSession = data["session"] + user = await session.get(User, user_id) + if user is None: + error_text = "Вы не добавлены в список пользователей VPN" + + if isinstance(event, Message): + await event.answer(error_text) + else: + await event.answer(error_text) + + return + + data["user"] = user + + return await handler(event, data) diff --git a/handlers/user/__init__.py b/handlers/user/__init__.py index 575982f..ac4c0a3 100644 --- a/handlers/user/__init__.py +++ b/handlers/user/__init__.py @@ -1,3 +1,12 @@ from aiogram import Router +# isort: off +from . import info +from . import vpn_link +# isort: on + router = Router(name="user") +router.include_routers( + info.router, + vpn_link.router, +) diff --git a/handlers/user/info.py b/handlers/user/info.py new file mode 100644 index 0000000..9776e7e --- /dev/null +++ b/handlers/user/info.py @@ -0,0 +1,77 @@ +from aiogram import Bot, Router +from aiogram.filters import Command +from aiogram.types import BotCommand, BotCommandScopeChat, Message + +from models import User + +router = Router(name="info") + +COMMANDS = [ + BotCommand( + command="start", + description="Показать стартовое сообщение", + ), + BotCommand( + command="help", + description="Показать список доступных команд", + ), + BotCommand( + command="vpn_link", + description="Показать ссылку доступа к VPN", + ), + BotCommand( + command="announcements", + description="Показать анонсы", + ), + BotCommand( + command="invoices", + description="Показать счета на оплату", + ), + BotCommand( + command="payments", + description="Показать платежи", + ), + BotCommand( + command="suggest_user", + description="Предложить нового пользователя", + ), +] + +ADMIN_COMMANDS = COMMANDS + [ + BotCommand( + command="new_announcement", + description="Создать новый анонс", + ), + BotCommand( + command="new_invoice", + description="Создать новый счёт на оплату", + ), + BotCommand( + command="add_user", + description="Создать нового пользователя", + ), + BotCommand( + command="suggest_list", + description="Показать предложения новых пользователей", + ), +] + + +@router.message(Command("start")) +async def start_command(msg: Message, bot: Bot, user: User) -> None: + await msg.answer( + "Добро пожаловать в бота для пользователей VPN.\n" + "Посмотреть список доступных команд: /help" + ) + + await bot.set_my_commands( + ADMIN_COMMANDS if user.is_admin() else COMMANDS, + BotCommandScopeChat(chat_id=user.id), + ) + + +@router.message(Command("help")) +async def help_command(msg: Message, user: User) -> None: + commands = ADMIN_COMMANDS if user.is_admin() else COMMANDS + commands_text = "\n".join(f"/{c.command} - {c.description}" for c in commands) + await msg.answer(f"Список доступных команд:\n{commands_text}") diff --git a/handlers/user/vpn_link.py b/handlers/user/vpn_link.py new file mode 100644 index 0000000..88d2963 --- /dev/null +++ b/handlers/user/vpn_link.py @@ -0,0 +1,22 @@ +from aiogram import Router +from aiogram.filters import Command +from aiogram.types import Message + +from models import User + +router = Router(name="vpn_link") + + +@router.message(Command("vpn_link")) +async def vpn_link_command(msg: Message, user: User) -> None: + await msg.answer( + f"Ссылка для настройки VPN на ваших устройствах:\n{user.vpn_link}" + "\n\n" + "Пожалуйста не делитесь данной ссылкой с друзьями или знакомыми. " + "Данную ссылку разрешено предоставить только родным и близким " + "(Родители, Бабушка, Брат, Сестра и т.д.)" + "\n" + "Если ты вдруг хочешь предоставить VPN другу, то воспользуйся командой /suggest_user" + "\n\n" + "В противном случае я установлю лимит на количество устройств, а тебе это не нужно. " + ) diff --git a/main.py b/main.py index 8e1566a..322e771 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,8 @@ import asyncio import logging +from aiogram.enums import UpdateType + from handlers import router from shared import bot, dp @@ -8,4 +10,12 @@ logging.basicConfig(level=logging.INFO) dp.include_router(router) -asyncio.run(dp.start_polling(bot)) +asyncio.run( + dp.start_polling( + bot, + allowed_updates=[ + UpdateType.MESSAGE, + UpdateType.CALLBACK_QUERY, + ], + ) +) diff --git a/pyproject.toml b/pyproject.toml index 4d18619..71a565e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [ "aiohttp-socks>=0.11.0", "aiosqlite>=0.22.1", "alembic>=1.18.4", + "magic-filter>=1.0.12", "pydantic>=2.12.5", "pydantic-settings>=2.13.1", "sqlalchemy[asyncio]>=2.0.48", diff --git a/uv.lock b/uv.lock index f644b07..a9b8849 100644 --- a/uv.lock +++ b/uv.lock @@ -512,14 +512,15 @@ wheels = [ ] [[package]] -name = "vpn-bot" -version = "0.1.0" +name = "vpn-manager-bot" +version = "0.0.1" source = { virtual = "." } dependencies = [ { name = "aiogram" }, { name = "aiohttp-socks" }, { name = "aiosqlite" }, { name = "alembic" }, + { name = "magic-filter" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "sqlalchemy", extra = ["asyncio"] }, @@ -531,6 +532,7 @@ requires-dist = [ { name = "aiohttp-socks", specifier = ">=0.11.0" }, { name = "aiosqlite", specifier = ">=0.22.1" }, { name = "alembic", specifier = ">=1.18.4" }, + { name = "magic-filter", specifier = ">=1.0.12" }, { name = "pydantic", specifier = ">=2.12.5" }, { name = "pydantic-settings", specifier = ">=2.13.1" }, { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.48" }, -- cgit v1.3