vpnflow 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vpnflow/__init__.py +8 -0
- vpnflow/__main__.py +61 -0
- vpnflow/bot/__init__.py +1 -0
- vpnflow/bot/base.py +121 -0
- vpnflow/bot/callbacks.py +36 -0
- vpnflow/bot/commands.py +15 -0
- vpnflow/bot/filters.py +28 -0
- vpnflow/bot/keyboards/__init__.py +2 -0
- vpnflow/bot/keyboards/inline.py +162 -0
- vpnflow/bot/middleware.py +78 -0
- vpnflow/bot/routers/__init__.py +4 -0
- vpnflow/bot/routers/admin.py +218 -0
- vpnflow/bot/routers/base.py +481 -0
- vpnflow/bot/routers/errors.py +39 -0
- vpnflow/bot/states.py +36 -0
- vpnflow/cli.py +56 -0
- vpnflow/db/__init__.py +2 -0
- vpnflow/db/base.py +78 -0
- vpnflow/db/events.py +44 -0
- vpnflow/db/models.py +199 -0
- vpnflow/db/repositories.py +318 -0
- vpnflow/db/tools.py +105 -0
- vpnflow/enums.py +5 -0
- vpnflow/flows/__init__.py +1 -0
- vpnflow/flows/_dagster.py +37 -0
- vpnflow/flows/_schedule.py +39 -0
- vpnflow/flows/base.py +55 -0
- vpnflow/log/__init__.py +1 -0
- vpnflow/log/telegram/__init__.py +1 -0
- vpnflow/log/telegram/formatters.py +69 -0
- vpnflow/log/telegram/handlers.py +142 -0
- vpnflow/misc.py +29 -0
- vpnflow/services/__init__.py +2 -0
- vpnflow/services/_marzban.py +78 -0
- vpnflow/services/_redis.py +7 -0
- vpnflow/services/_telegram.py +122 -0
- vpnflow/services/business.py +548 -0
- vpnflow/services/cache.py +105 -0
- vpnflow/settings.py +172 -0
- vpnflow/web/__init__.py +1 -0
- vpnflow/web/app.py +61 -0
- vpnflow/web/schemas.py +206 -0
- vpnflow/web/views.py +67 -0
- vpnflow-1.0.0.dist-info/LICENSE +661 -0
- vpnflow-1.0.0.dist-info/METADATA +54 -0
- vpnflow-1.0.0.dist-info/RECORD +48 -0
- vpnflow-1.0.0.dist-info/WHEEL +4 -0
- vpnflow-1.0.0.dist-info/entry_points.txt +3 -0
vpnflow/__init__.py
ADDED
vpnflow/__main__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from asyncio import get_event_loop
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
|
|
5
|
+
import uvicorn
|
|
6
|
+
from aiogram.exceptions import TelegramRetryAfter
|
|
7
|
+
|
|
8
|
+
from vpnflow.bot.base import create_dispatcher
|
|
9
|
+
from vpnflow.cli import create_parser, show_art
|
|
10
|
+
from vpnflow.db.tools import init_db
|
|
11
|
+
from vpnflow.flows._schedule import run_scheduled_tasks
|
|
12
|
+
from vpnflow.misc import load_log_conf
|
|
13
|
+
from vpnflow.settings import settings
|
|
14
|
+
|
|
15
|
+
logger = getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
"""⭐"""
|
|
20
|
+
show_art()
|
|
21
|
+
parser, loop = create_parser(), get_event_loop()
|
|
22
|
+
|
|
23
|
+
args = parser.parse_args()
|
|
24
|
+
|
|
25
|
+
if args.log_conf_file:
|
|
26
|
+
load_log_conf(args.log_conf_file)
|
|
27
|
+
|
|
28
|
+
logger.debug(f"Run with args: {args}, settings: {settings}")
|
|
29
|
+
|
|
30
|
+
if args.run_scheduled_tasks:
|
|
31
|
+
run_scheduled_tasks()
|
|
32
|
+
|
|
33
|
+
if args.command == "db":
|
|
34
|
+
loop.run_until_complete(init_db(args))
|
|
35
|
+
|
|
36
|
+
if settings.telegram.webhook_use:
|
|
37
|
+
uvicorn.run(
|
|
38
|
+
"vpnflow.web.app:app",
|
|
39
|
+
host=settings.webserver.host, port=settings.webserver.port,
|
|
40
|
+
workers=settings.webserver.workers, reload=settings.webserver.reload
|
|
41
|
+
)
|
|
42
|
+
else:
|
|
43
|
+
bot_dispatcher = create_dispatcher()
|
|
44
|
+
try:
|
|
45
|
+
loop.run_until_complete(
|
|
46
|
+
bot_dispatcher.start_polling(
|
|
47
|
+
bot_dispatcher.bot,
|
|
48
|
+
allowed_updates=bot_dispatcher.resolve_used_update_types(),
|
|
49
|
+
polling_timeout=60, skip_updates=True
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
except KeyboardInterrupt:
|
|
53
|
+
loop.run_until_complete(bot_dispatcher.stop_polling())
|
|
54
|
+
except TelegramRetryAfter as exc:
|
|
55
|
+
logger.error(exc)
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
logger.exception(exc)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
main()
|
vpnflow/bot/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
vpnflow/bot/base.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from logging import getLogger
|
|
3
|
+
from typing import Optional, Tuple
|
|
4
|
+
|
|
5
|
+
from aiogram import Bot, Dispatcher
|
|
6
|
+
from aiogram.client.default import DefaultBotProperties
|
|
7
|
+
from aiogram.client.session.aiohttp import AiohttpSession
|
|
8
|
+
from aiogram.enums import ParseMode
|
|
9
|
+
from aiogram.fsm.storage.base import BaseStorage
|
|
10
|
+
from aiogram.fsm.storage.memory import MemoryStorage
|
|
11
|
+
from aiogram.fsm.storage.redis import RedisStorage
|
|
12
|
+
from aiogram.types.bot_command_scope_chat import BotCommandScopeChat
|
|
13
|
+
from aiogram.utils.callback_answer import CallbackAnswerMiddleware
|
|
14
|
+
|
|
15
|
+
from vpnflow.bot import commands, middleware, routers
|
|
16
|
+
from vpnflow.services import _redis
|
|
17
|
+
from vpnflow.services._telegram import broadcast
|
|
18
|
+
from vpnflow.services.cache import CacheRepository
|
|
19
|
+
from vpnflow.settings import Settings, TelegramSettings, settings
|
|
20
|
+
|
|
21
|
+
logger = getLogger(__name__)
|
|
22
|
+
telegram_settings = settings.telegram
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def on_startup_bot(
|
|
26
|
+
bot: Bot,
|
|
27
|
+
bot_dispatcher: Dispatcher,
|
|
28
|
+
settings_telegram: TelegramSettings = telegram_settings
|
|
29
|
+
):
|
|
30
|
+
"""⭐"""
|
|
31
|
+
logger.info("Startup bot")
|
|
32
|
+
bot_info = await bot.get_me()
|
|
33
|
+
logger.info(f"Name: {bot_info.full_name}. Username: @{bot_info.username}. ID: {bot_info.id}")
|
|
34
|
+
assert bot_info.username == settings.telegram.bot_name
|
|
35
|
+
await broadcast(bot, settings_telegram.messages["bot-on"], *settings_telegram.admins_id)
|
|
36
|
+
await bot.delete_my_commands()
|
|
37
|
+
await bot.set_my_commands(commands.user)
|
|
38
|
+
for admin_id in settings_telegram.admins_id:
|
|
39
|
+
await bot.set_my_commands(commands.admin, scope=BotCommandScopeChat(chat_id=admin_id))
|
|
40
|
+
if settings_telegram.webhook_use:
|
|
41
|
+
webhook_info = await bot.get_webhook_info()
|
|
42
|
+
logger.info(f"Webhook info: {webhook_info}")
|
|
43
|
+
if webhook_info.url != settings_telegram.webhook_url:
|
|
44
|
+
await bot.delete_webhook(drop_pending_updates=True)
|
|
45
|
+
await bot.set_webhook(
|
|
46
|
+
url=settings_telegram.webhook_url, drop_pending_updates=True,
|
|
47
|
+
allowed_updates=bot_dispatcher.resolve_used_update_types(),
|
|
48
|
+
max_connections=settings_telegram.webhook_max_connections,
|
|
49
|
+
secret_token=settings_telegram.webhook_secret_token.get_secret_value()
|
|
50
|
+
)
|
|
51
|
+
is_health = await CacheRepository.check_health()
|
|
52
|
+
assert is_health
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def on_shutdown_bot(
|
|
56
|
+
bot: Bot,
|
|
57
|
+
bot_dispatcher: Dispatcher,
|
|
58
|
+
settings_telegram: TelegramSettings = telegram_settings
|
|
59
|
+
):
|
|
60
|
+
"""⭐"""
|
|
61
|
+
logger.info("Shutdown bot")
|
|
62
|
+
if isinstance(bot_dispatcher.storage, RedisStorage):
|
|
63
|
+
await bot_dispatcher.storage.close()
|
|
64
|
+
await bot_dispatcher.fsm.storage.close()
|
|
65
|
+
await broadcast(bot, settings_telegram.messages["bot-off"], *settings_telegram.admins_id)
|
|
66
|
+
# if settings_telegram.webhook_use:
|
|
67
|
+
# await bot.delete_webhook(drop_pending_updates=True)
|
|
68
|
+
await bot.session.close()
|
|
69
|
+
await bot.close()
|
|
70
|
+
await CacheRepository.close()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def on_startup_bot_dispatcher(dispatcher) -> None:
|
|
74
|
+
"""⭐"""
|
|
75
|
+
logger.info("Startup bot dispatcher")
|
|
76
|
+
await on_startup_bot(dispatcher.bot, dispatcher)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def on_shutdown_bot_dispatcher(dispatcher) -> None:
|
|
80
|
+
"""⭐"""
|
|
81
|
+
logger.info("Shutdown bot dispatcher")
|
|
82
|
+
await on_shutdown_bot(dispatcher.bot, dispatcher)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def create_bot(
|
|
86
|
+
settings_telegram: TelegramSettings = telegram_settings,
|
|
87
|
+
session: Optional[AiohttpSession] = None
|
|
88
|
+
) -> Bot:
|
|
89
|
+
"""⭐"""
|
|
90
|
+
if session is None:
|
|
91
|
+
session: AiohttpSession = AiohttpSession()
|
|
92
|
+
return Bot(
|
|
93
|
+
token=settings_telegram.bot_token.get_secret_value(),
|
|
94
|
+
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
|
|
95
|
+
session=session
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def create_dispatcher(
|
|
100
|
+
bot: Bot = create_bot(),
|
|
101
|
+
middlewares: Tuple = middleware.ALL,
|
|
102
|
+
routers: Tuple = routers.ALL,
|
|
103
|
+
settings: Settings = settings,
|
|
104
|
+
on_startup = on_startup_bot_dispatcher,
|
|
105
|
+
on_shutdown = on_shutdown_bot_dispatcher
|
|
106
|
+
) -> Dispatcher:
|
|
107
|
+
"""⭐"""
|
|
108
|
+
redis_url = settings.telegram.redis_url.get_secret_value()
|
|
109
|
+
if redis_url:
|
|
110
|
+
storage: BaseStorage = RedisStorage(redis=_redis.create_client(redis_url))
|
|
111
|
+
else:
|
|
112
|
+
storage: BaseStorage = MemoryStorage()
|
|
113
|
+
dispatcher = Dispatcher(name="bot_dispatcher", storage=storage)
|
|
114
|
+
dispatcher.bot = bot
|
|
115
|
+
for _middleware in middlewares:
|
|
116
|
+
dispatcher.update.middleware.register(_middleware)
|
|
117
|
+
dispatcher.callback_query.middleware(CallbackAnswerMiddleware())
|
|
118
|
+
dispatcher.include_routers(*routers)
|
|
119
|
+
dispatcher.startup.register(on_startup)
|
|
120
|
+
dispatcher.shutdown.register(on_shutdown)
|
|
121
|
+
return dispatcher
|
vpnflow/bot/callbacks.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from typing import Any, Optional, Union
|
|
3
|
+
|
|
4
|
+
from aiogram.filters.callback_data import CallbackData
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PayPlanCallback(CallbackData, prefix="payplan"):
|
|
8
|
+
"""⭐"""
|
|
9
|
+
action: str
|
|
10
|
+
value: Union[int, str]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class YesNoCallback(CallbackData, prefix="yesno"):
|
|
14
|
+
"""⭐"""
|
|
15
|
+
value: bool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AdminCallback(CallbackData, prefix="admin"):
|
|
19
|
+
"""⭐"""
|
|
20
|
+
action: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SupportCallback(CallbackData, prefix="support"):
|
|
24
|
+
"""⭐"""
|
|
25
|
+
action: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UserCallback(CallbackData, prefix="user"):
|
|
29
|
+
"""⭐"""
|
|
30
|
+
action: str
|
|
31
|
+
value: Optional[Any] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NotifyCallback(CallbackData, prefix="notify"):
|
|
35
|
+
"""⭐"""
|
|
36
|
+
value: str
|
vpnflow/bot/commands.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from aiogram.types import BotCommand
|
|
3
|
+
|
|
4
|
+
from vpnflow.enums import BotCommands, BotCommandsAdmin
|
|
5
|
+
from vpnflow.settings import settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_menu_from_enum(enum, commands_text=settings.telegram.messages["commands"]):
|
|
9
|
+
"""⭐"""
|
|
10
|
+
for k, _ in enum.__members__.items():
|
|
11
|
+
yield BotCommand(command=f"/{k}", description=commands_text[k])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
user = list(build_menu_from_enum(BotCommands))
|
|
15
|
+
admin = user + list(build_menu_from_enum(BotCommandsAdmin))
|
vpnflow/bot/filters.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from aiogram.filters import BaseFilter
|
|
3
|
+
from aiogram.types import Message
|
|
4
|
+
|
|
5
|
+
from vpnflow.settings import settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AdminFilter(BaseFilter):
|
|
9
|
+
"""⭐"""
|
|
10
|
+
|
|
11
|
+
ADMINS = settings.telegram.admins_id
|
|
12
|
+
|
|
13
|
+
async def __call__(self, event: Message) -> bool:
|
|
14
|
+
user = event.from_user
|
|
15
|
+
if user:
|
|
16
|
+
return user.id in self.ADMINS
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BlacklistFilter(BaseFilter):
|
|
21
|
+
"""⭐"""
|
|
22
|
+
|
|
23
|
+
BLACKLIST = set(settings.telegram.blacklist)
|
|
24
|
+
|
|
25
|
+
async def __call__(self, event: Message) -> bool:
|
|
26
|
+
user = event.from_user
|
|
27
|
+
if user:
|
|
28
|
+
return False if user.id in self.BLACKLIST else True
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
|
|
4
|
+
from aiogram.types import WebAppInfo
|
|
5
|
+
from aiogram.utils.keyboard import InlineKeyboardBuilder, InlineKeyboardMarkup
|
|
6
|
+
|
|
7
|
+
from vpnflow.bot import callbacks as cb
|
|
8
|
+
from vpnflow.settings import settings
|
|
9
|
+
|
|
10
|
+
buttons_text = settings.telegram.messages["buttons"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_inline_keyboard(*buttons_data, rows_num=None, cols_num=None, attach=None, as_markup=True):
|
|
14
|
+
"""⭐"""
|
|
15
|
+
kb = InlineKeyboardBuilder()
|
|
16
|
+
for button_text, button_callback in buttons_data:
|
|
17
|
+
kb.button(text=button_text, callback_data=button_callback)
|
|
18
|
+
if attach:
|
|
19
|
+
kb.attach(attach)
|
|
20
|
+
if rows_num:
|
|
21
|
+
if cols_num:
|
|
22
|
+
kb.adjust(rows_num, cols_num)
|
|
23
|
+
else:
|
|
24
|
+
kb.adjust(rows_num)
|
|
25
|
+
if as_markup:
|
|
26
|
+
return kb.as_markup()
|
|
27
|
+
return kb
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@lru_cache(maxsize=32)
|
|
31
|
+
def build_referral(url: str) -> InlineKeyboardMarkup:
|
|
32
|
+
"""⭐"""
|
|
33
|
+
kb = InlineKeyboardBuilder()
|
|
34
|
+
kb.button(text=buttons_text["referral-share"], url=f"https://telegram.me/share/url?url={url}")
|
|
35
|
+
kb.button(text=buttons_text["start"], callback_data=cb.UserCallback(action="start"))
|
|
36
|
+
kb.adjust(1)
|
|
37
|
+
return kb.as_markup()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@lru_cache(maxsize=32)
|
|
41
|
+
def build_setup(url: str) -> InlineKeyboardMarkup:
|
|
42
|
+
"""⭐"""
|
|
43
|
+
kb = InlineKeyboardBuilder()
|
|
44
|
+
kb.button(text=buttons_text["setup-open"], web_app=WebAppInfo(url=url))
|
|
45
|
+
return kb.as_markup()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def build_pay_plans(pay_plans_agenerator) -> InlineKeyboardMarkup:
|
|
49
|
+
"""⭐"""
|
|
50
|
+
kb_data = []
|
|
51
|
+
async for pay_plan in pay_plans_agenerator:
|
|
52
|
+
kb_data.append(
|
|
53
|
+
(
|
|
54
|
+
pay_plan.button_text,
|
|
55
|
+
cb.PayPlanCallback(action="choice-pay-plan", value=pay_plan.id))
|
|
56
|
+
)
|
|
57
|
+
kb_data.append((buttons_text["back"], cb.UserCallback(action="start")))
|
|
58
|
+
return create_inline_keyboard(*kb_data, rows_num=1)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def build_pay_plan_prices(pay_plan) -> InlineKeyboardMarkup:
|
|
62
|
+
"""⭐"""
|
|
63
|
+
kb_data = []
|
|
64
|
+
for pay_plan_price in pay_plan.prices:
|
|
65
|
+
kb_data.append(
|
|
66
|
+
(
|
|
67
|
+
pay_plan_price.currency.symbol,
|
|
68
|
+
cb.PayPlanCallback(action="choice-pay-method", value=pay_plan_price.payment_method))
|
|
69
|
+
)
|
|
70
|
+
return create_inline_keyboard(*kb_data, rows_num=2)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@lru_cache(maxsize=32)
|
|
74
|
+
def build_setup_steps(download_url: str, add_url: str) -> InlineKeyboardMarkup:
|
|
75
|
+
"""⭐"""
|
|
76
|
+
kb = InlineKeyboardBuilder()
|
|
77
|
+
kb.button(text=buttons_text["setup-download"], url=download_url)
|
|
78
|
+
kb.button(text=buttons_text["setup-add"], url=add_url)
|
|
79
|
+
kb.button(text=buttons_text["start"], callback_data=cb.UserCallback(action="start"))
|
|
80
|
+
kb.adjust(1)
|
|
81
|
+
return kb.as_markup()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
support = InlineKeyboardBuilder()
|
|
85
|
+
support.button(text=buttons_text["help-faq"], web_app=WebAppInfo(url=settings.business.url_faq))
|
|
86
|
+
support.button(text=buttons_text["help-ask"], url=settings.business.url_support)
|
|
87
|
+
support.button(text=buttons_text["setup-help"], callback_data=cb.UserCallback(action="setup-help"))
|
|
88
|
+
support.button(text=buttons_text["back"], callback_data=cb.UserCallback(action="start"))
|
|
89
|
+
support.adjust(1)
|
|
90
|
+
support = support.as_markup()
|
|
91
|
+
|
|
92
|
+
support_back = InlineKeyboardBuilder()
|
|
93
|
+
support_back.button(text=buttons_text["back"], callback_data=cb.UserCallback(action="help"))
|
|
94
|
+
support_back.adjust(1)
|
|
95
|
+
support_back = support_back.as_markup()
|
|
96
|
+
|
|
97
|
+
yes_no_menu = create_inline_keyboard(
|
|
98
|
+
*(
|
|
99
|
+
(buttons_text["choice-yes"], cb.YesNoCallback(value=True)),
|
|
100
|
+
(buttons_text["choice-no"], cb.YesNoCallback(value=False))
|
|
101
|
+
), rows_num=2
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
user_promo = create_inline_keyboard(
|
|
105
|
+
*(
|
|
106
|
+
(buttons_text["promo-again"], cb.UserCallback(action="promo")),
|
|
107
|
+
(buttons_text["start"], cb.UserCallback(action="start"))
|
|
108
|
+
), rows_num=1
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
user_welcome = InlineKeyboardBuilder()
|
|
112
|
+
user_welcome.button(text=buttons_text["start-accept"], callback_data=cb.UserCallback(action="sign_up"))
|
|
113
|
+
user_welcome.adjust(1)
|
|
114
|
+
user_welcome = user_welcome.as_markup()
|
|
115
|
+
|
|
116
|
+
user_start = InlineKeyboardBuilder()
|
|
117
|
+
user_start.button(text=buttons_text["start"], callback_data=cb.UserCallback(action="start"))
|
|
118
|
+
user_start = user_start.as_markup()
|
|
119
|
+
|
|
120
|
+
user_start_new = InlineKeyboardBuilder()
|
|
121
|
+
user_start_new.button(text=buttons_text["start-setup"], callback_data=cb.UserCallback(action="setup"))
|
|
122
|
+
user_start_new = user_start_new.as_markup()
|
|
123
|
+
|
|
124
|
+
user_setup_platform = create_inline_keyboard(
|
|
125
|
+
*(
|
|
126
|
+
(
|
|
127
|
+
platform, cb.UserCallback(action="setup-platform", value=platform)
|
|
128
|
+
) for platform in settings.business.supported_platforms
|
|
129
|
+
), rows_num=4
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
user = create_inline_keyboard(
|
|
133
|
+
*(
|
|
134
|
+
(
|
|
135
|
+
buttons_text[k], cb.UserCallback(action=k)
|
|
136
|
+
) for k in ("pay", "setup", "promo", "invite", "help")
|
|
137
|
+
), rows_num=1
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
admin = create_inline_keyboard(
|
|
142
|
+
*(
|
|
143
|
+
(
|
|
144
|
+
buttons_text[f"admin-{k}"], cb.AdminCallback(action=k)
|
|
145
|
+
) for k in ("stats", "notify", "coupons", "services")
|
|
146
|
+
), rows_num=1
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
admin_notify = create_inline_keyboard(
|
|
150
|
+
*(
|
|
151
|
+
(buttons_text["admin-notify-all"], cb.NotifyCallback(value="all")),
|
|
152
|
+
(buttons_text["admin-notify-active"], cb.NotifyCallback(value="active")),
|
|
153
|
+
(buttons_text["admin-notify-expired"], cb.NotifyCallback(value="expired")),
|
|
154
|
+
(buttons_text["admin-notify-pay-expired"], cb.NotifyCallback(value="pay-expired")),
|
|
155
|
+
(buttons_text["admin-notify-pay-no-expired"], cb.NotifyCallback(value="pay-no-expired")),
|
|
156
|
+
(buttons_text["back"], cb.AdminCallback(action="panel"))
|
|
157
|
+
), rows_num=1
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
admin_start = InlineKeyboardBuilder()
|
|
161
|
+
admin_start.button(text=buttons_text["panel"], callback_data=cb.AdminCallback(action="panel"))
|
|
162
|
+
admin_start = admin_start.as_markup()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from logging import getLogger
|
|
3
|
+
from typing import Any, Awaitable, Callable, Dict
|
|
4
|
+
|
|
5
|
+
from aiogram import BaseMiddleware
|
|
6
|
+
from aiogram.types import CallbackQuery, Message, TelegramObject
|
|
7
|
+
|
|
8
|
+
from vpnflow.bot import keyboards as kbs
|
|
9
|
+
from vpnflow.bot.callbacks import UserCallback
|
|
10
|
+
from vpnflow.services import business
|
|
11
|
+
from vpnflow.services._telegram import edit_callback
|
|
12
|
+
from vpnflow.settings import settings
|
|
13
|
+
|
|
14
|
+
logger = getLogger(__name__)
|
|
15
|
+
messages = settings.telegram.messages
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CheckAcceptMiddleware(BaseMiddleware):
|
|
19
|
+
"""⭐"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
callbacks = ("start", "pay", "setup", "promo", "invite", "help", "setup-platform", "setup-help")
|
|
24
|
+
) -> None:
|
|
25
|
+
self.callbacks = callbacks
|
|
26
|
+
|
|
27
|
+
async def __call__(
|
|
28
|
+
self,
|
|
29
|
+
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
|
30
|
+
event: TelegramObject,
|
|
31
|
+
data: Dict[str, Any],
|
|
32
|
+
) -> Any:
|
|
33
|
+
if isinstance(event, Message) and "/start" not in str(event.text):
|
|
34
|
+
user = await business.get_user(telegram_id=event.from_user.id)
|
|
35
|
+
if user and user.marzban_username is None:
|
|
36
|
+
await event.answer(messages["start-welcome"], reply_markup=kbs.inline.user_welcome)
|
|
37
|
+
return
|
|
38
|
+
else:
|
|
39
|
+
data["user"] = user
|
|
40
|
+
if user is None:
|
|
41
|
+
logger.warning(f"User is None, {event.from_user.id}")
|
|
42
|
+
return
|
|
43
|
+
if isinstance(event, CallbackQuery):
|
|
44
|
+
callback_data = data.get("callback_data")
|
|
45
|
+
if (
|
|
46
|
+
callback_data and
|
|
47
|
+
isinstance(callback_data, UserCallback) and
|
|
48
|
+
callback_data.action in self.callbacks
|
|
49
|
+
):
|
|
50
|
+
user = await business.get_user(telegram_id=event.from_user.id)
|
|
51
|
+
if user and user.marzban_username is None:
|
|
52
|
+
await edit_callback(event, messages["start-welcome"], reply_markup=kbs.inline.user_welcome)
|
|
53
|
+
return
|
|
54
|
+
else:
|
|
55
|
+
data["user"] = user
|
|
56
|
+
if user is None:
|
|
57
|
+
logger.warning(f"User is None, {event.from_user.id}")
|
|
58
|
+
return
|
|
59
|
+
result = await handler(event, data)
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FilterNoUserMiddleware(BaseMiddleware):
|
|
64
|
+
"""⭐"""
|
|
65
|
+
|
|
66
|
+
async def __call__(
|
|
67
|
+
self,
|
|
68
|
+
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
|
69
|
+
event: TelegramObject,
|
|
70
|
+
data: Dict[str, Any],
|
|
71
|
+
) -> Any:
|
|
72
|
+
if isinstance(event, (CallbackQuery, Message)) and event.from_user is None:
|
|
73
|
+
return
|
|
74
|
+
result = await handler(event, data)
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
ALL = (FilterNoUserMiddleware(), )
|