aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTolmachev Igor <me@igorek.dev>2026-03-23 00:21:18 +0300
committerTolmachev Igor <me@igorek.dev>2026-03-23 00:21:18 +0300
commitffee163d8206f0fb1315015e4c60248b68d459bc (patch)
treee95f4eb2bb330f2223d00c80fa9e123263657a8e
parentbc7f486aa7b543a934f4cf23dc80a95f44afcb64 (diff)
downloadvpn_manager_bot-ffee163d8206f0fb1315015e4c60248b68d459bc.tar.gz
vpn_manager_bot-ffee163d8206f0fb1315015e4c60248b68d459bc.zip
Add start, help and vpn_link commands
-rw-r--r--handlers/__init__.py5
-rw-r--r--handlers/admin/__init__.py5
-rw-r--r--handlers/middleware.py56
-rw-r--r--handlers/user/__init__.py9
-rw-r--r--handlers/user/info.py77
-rw-r--r--handlers/user/vpn_link.py22
-rw-r--r--main.py12
-rw-r--r--pyproject.toml1
-rw-r--r--uv.lock6
9 files changed, 189 insertions, 4 deletions
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 @@
1# isort: off 1# isort: off
2from aiogram import Router 2from aiogram import Router
3from .middleware import InjectSessionMiddleware, UserAccessMiddleware
3from . import user 4from . import user
4from . import admin 5from . import admin
5# isort: on 6# isort: on
@@ -9,3 +10,7 @@ router.include_routers(
9 user.router, 10 user.router,
10 admin.router, 11 admin.router,
11) 12)
13
14for observer in (router.message, router.callback_query):
15 observer.outer_middleware(InjectSessionMiddleware())
16 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 @@
1from aiogram import Router 1from aiogram import F, Router
2from aiogram.filters import MagicData
2 3
3router = Router(name="admin") 4router = Router(name="admin")
5router.message.filter(MagicData(F.user.is_admin()))
6router.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 @@
1from typing import Any, Awaitable, Callable
2
3from aiogram import BaseMiddleware
4from aiogram.types import CallbackQuery, Message, TelegramObject
5from sqlalchemy.ext.asyncio import AsyncSession
6
7from database import sessions
8from models import User
9
10
11class InjectSessionMiddleware(BaseMiddleware):
12 async def __call__[T](
13 self,
14 handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
15 event: TelegramObject,
16 data: dict[str, Any],
17 ) -> Any:
18 async with sessions.begin() as session:
19 data["session"] = session
20 handler_result = await handler(event, data)
21 return handler_result
22
23
24class UserAccessMiddleware(BaseMiddleware):
25 async def __call__(
26 self,
27 handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
28 event: TelegramObject,
29 data: dict[str, Any],
30 ) -> Any:
31 if not isinstance(event, (Message, CallbackQuery)):
32 raise TypeError(
33 f"UserAccessMiddleware doesn't support event with type: {type(event).__name__}",
34 event,
35 )
36
37 if isinstance(event, Message):
38 user_id = event.chat.id
39 else:
40 user_id = event.from_user.id
41
42 session: AsyncSession = data["session"]
43 user = await session.get(User, user_id)
44 if user is None:
45 error_text = "Вы не добавлены в список пользователей VPN"
46
47 if isinstance(event, Message):
48 await event.answer(error_text)
49 else:
50 await event.answer(error_text)
51
52 return
53
54 data["user"] = user
55
56 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 @@
1from aiogram import Router 1from aiogram import Router
2 2
3# isort: off
4from . import info
5from . import vpn_link
6# isort: on
7
3router = Router(name="user") 8router = Router(name="user")
9router.include_routers(
10 info.router,
11 vpn_link.router,
12)
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 @@
1from aiogram import Bot, Router
2from aiogram.filters import Command
3from aiogram.types import BotCommand, BotCommandScopeChat, Message
4
5from models import User
6
7router = Router(name="info")
8
9COMMANDS = [
10 BotCommand(
11 command="start",
12 description="Показать стартовое сообщение",
13 ),
14 BotCommand(
15 command="help",
16 description="Показать список доступных команд",
17 ),
18 BotCommand(
19 command="vpn_link",
20 description="Показать ссылку доступа к VPN",
21 ),
22 BotCommand(
23 command="announcements",
24 description="Показать анонсы",
25 ),
26 BotCommand(
27 command="invoices",
28 description="Показать счета на оплату",
29 ),
30 BotCommand(
31 command="payments",
32 description="Показать платежи",
33 ),
34 BotCommand(
35 command="suggest_user",
36 description="Предложить нового пользователя",
37 ),
38]
39
40ADMIN_COMMANDS = COMMANDS + [
41 BotCommand(
42 command="new_announcement",
43 description="Создать новый анонс",
44 ),
45 BotCommand(
46 command="new_invoice",
47 description="Создать новый счёт на оплату",
48 ),
49 BotCommand(
50 command="add_user",
51 description="Создать нового пользователя",
52 ),
53 BotCommand(
54 command="suggest_list",
55 description="Показать предложения новых пользователей",
56 ),
57]
58
59
60@router.message(Command("start"))
61async def start_command(msg: Message, bot: Bot, user: User) -> None:
62 await msg.answer(
63 "Добро пожаловать в бота для пользователей VPN.\n"
64 "Посмотреть список доступных команд: /help"
65 )
66
67 await bot.set_my_commands(
68 ADMIN_COMMANDS if user.is_admin() else COMMANDS,
69 BotCommandScopeChat(chat_id=user.id),
70 )
71
72
73@router.message(Command("help"))
74async def help_command(msg: Message, user: User) -> None:
75 commands = ADMIN_COMMANDS if user.is_admin() else COMMANDS
76 commands_text = "\n".join(f"/{c.command} - {c.description}" for c in commands)
77 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 @@
1from aiogram import Router
2from aiogram.filters import Command
3from aiogram.types import Message
4
5from models import User
6
7router = Router(name="vpn_link")
8
9
10@router.message(Command("vpn_link"))
11async def vpn_link_command(msg: Message, user: User) -> None:
12 await msg.answer(
13 f"Ссылка для настройки VPN на ваших устройствах:\n{user.vpn_link}"
14 "\n\n"
15 "Пожалуйста не делитесь данной ссылкой с друзьями или знакомыми. "
16 "Данную ссылку разрешено предоставить только родным и близким "
17 "(Родители, Бабушка, Брат, Сестра и т.д.)"
18 "\n"
19 "Если ты вдруг хочешь предоставить VPN другу, то воспользуйся командой /suggest_user"
20 "\n\n"
21 "В противном случае я установлю лимит на количество устройств, а тебе это не нужно. "
22 )
diff --git a/main.py b/main.py
index 8e1566a..322e771 100644
--- a/main.py
+++ b/main.py
@@ -1,6 +1,8 @@
1import asyncio 1import asyncio
2import logging 2import logging
3 3
4from aiogram.enums import UpdateType
5
4from handlers import router 6from handlers import router
5from shared import bot, dp 7from shared import bot, dp
6 8
@@ -8,4 +10,12 @@ logging.basicConfig(level=logging.INFO)
8 10
9dp.include_router(router) 11dp.include_router(router)
10 12
11asyncio.run(dp.start_polling(bot)) 13asyncio.run(
14 dp.start_polling(
15 bot,
16 allowed_updates=[
17 UpdateType.MESSAGE,
18 UpdateType.CALLBACK_QUERY,
19 ],
20 )
21)
diff --git a/pyproject.toml b/pyproject.toml
index 4d18619..71a565e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,6 +8,7 @@ dependencies = [
8 "aiohttp-socks>=0.11.0", 8 "aiohttp-socks>=0.11.0",
9 "aiosqlite>=0.22.1", 9 "aiosqlite>=0.22.1",
10 "alembic>=1.18.4", 10 "alembic>=1.18.4",
11 "magic-filter>=1.0.12",
11 "pydantic>=2.12.5", 12 "pydantic>=2.12.5",
12 "pydantic-settings>=2.13.1", 13 "pydantic-settings>=2.13.1",
13 "sqlalchemy[asyncio]>=2.0.48", 14 "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 = [
512] 512]
513 513
514[[package]] 514[[package]]
515name = "vpn-bot" 515name = "vpn-manager-bot"
516version = "0.1.0" 516version = "0.0.1"
517source = { virtual = "." } 517source = { virtual = "." }
518dependencies = [ 518dependencies = [
519 { name = "aiogram" }, 519 { name = "aiogram" },
520 { name = "aiohttp-socks" }, 520 { name = "aiohttp-socks" },
521 { name = "aiosqlite" }, 521 { name = "aiosqlite" },
522 { name = "alembic" }, 522 { name = "alembic" },
523 { name = "magic-filter" },
523 { name = "pydantic" }, 524 { name = "pydantic" },
524 { name = "pydantic-settings" }, 525 { name = "pydantic-settings" },
525 { name = "sqlalchemy", extra = ["asyncio"] }, 526 { name = "sqlalchemy", extra = ["asyncio"] },
@@ -531,6 +532,7 @@ requires-dist = [
531 { name = "aiohttp-socks", specifier = ">=0.11.0" }, 532 { name = "aiohttp-socks", specifier = ">=0.11.0" },
532 { name = "aiosqlite", specifier = ">=0.22.1" }, 533 { name = "aiosqlite", specifier = ">=0.22.1" },
533 { name = "alembic", specifier = ">=1.18.4" }, 534 { name = "alembic", specifier = ">=1.18.4" },
535 { name = "magic-filter", specifier = ">=1.0.12" },
534 { name = "pydantic", specifier = ">=2.12.5" }, 536 { name = "pydantic", specifier = ">=2.12.5" },
535 { name = "pydantic-settings", specifier = ">=2.13.1" }, 537 { name = "pydantic-settings", specifier = ">=2.13.1" },
536 { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.48" }, 538 { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.48" },