Compare commits
2 Commits
fc45816eba
...
1be4770afd
| Author | SHA1 | Date | |
|---|---|---|---|
| 1be4770afd | |||
| 930fabc028 |
@@ -13,6 +13,11 @@ class settings(BaseSettings):
|
|||||||
admin_group_id: int = Field(alias="ADMIN_GROUP_ID")
|
admin_group_id: int = Field(alias="ADMIN_GROUP_ID")
|
||||||
remnawave_url: str = Field(alias="REMNAWAVE_URL")
|
remnawave_url: str = Field(alias="REMNAWAVE_URL")
|
||||||
remnawave_token: str = Field(alias="REMNAWAVE_TOKEN")
|
remnawave_token: str = Field(alias="REMNAWAVE_TOKEN")
|
||||||
|
min_devices: int = Field(3, alias="MIN_DEVICES")
|
||||||
|
max_devices: int = Field(12, alias="MAX_DEVICES")
|
||||||
|
per_device_cost: int = Field(50, alias="PER_DEVICE_COST")
|
||||||
|
whitelist_cost: int = Field(100, alias="WHITELIST_COST")
|
||||||
|
whitelist_device_threshold: int = Field(6, alias="WHITELIST_DEVICE_THRESHOLD")
|
||||||
|
|
||||||
|
|
||||||
settings = settings() # type: ignore
|
settings = settings() # type: ignore
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from .menus import router as menus_router
|
from .menus import router as menus_router
|
||||||
from .support import router as support_router
|
from .support import router as support_router
|
||||||
|
from .buy import router as buy_router
|
||||||
|
|
||||||
routers = [menus_router, support_router]
|
routers = [menus_router, support_router, buy_router]
|
||||||
|
|||||||
151
handlers/buy.py
151
handlers/buy.py
@@ -1,10 +1,153 @@
|
|||||||
from aiogram import Router
|
from typing import Optional
|
||||||
|
from aiogram import Router, F
|
||||||
from aiogram.types import CallbackQuery
|
from aiogram.types import CallbackQuery
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
from dto.di import DependenciesDTO
|
from keyboards.client import devices_selector, duration_selector
|
||||||
|
from misc.utils import calculate_price, convert_int_to_duration
|
||||||
|
from schemas.subscriptions import SubscriptionPlan
|
||||||
|
from states.buy import SubscriptionStorage
|
||||||
|
from texts import (
|
||||||
|
CALLBACK_FALLBACK,
|
||||||
|
CONTEXT_REINITIATED,
|
||||||
|
NOTHING_PLACEHOLDER,
|
||||||
|
SUBSCRIPTION_DEVICE_SELECTOR,
|
||||||
|
SUBSCRIPTION_DURATION_SELECTOR,
|
||||||
|
)
|
||||||
|
from config import settings
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
# @router.callback_query()
|
|
||||||
|
@router.callback_query(F.data == "buy")
|
||||||
|
async def buy_init(cb: CallbackQuery, state: FSMContext):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
ctx = SubscriptionPlan()
|
||||||
|
await cb.message.edit_text(
|
||||||
|
SUBSCRIPTION_DEVICE_SELECTOR.format(price=calculate_price(ctx)),
|
||||||
|
reply_markup=devices_selector(settings.min_devices),
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.set_state(SubscriptionStorage.devices)
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("devices:"))
|
||||||
|
async def device_count_select(cb: CallbackQuery, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
ctx: Optional[SubscriptionPlan] = data.get("ctx", SubscriptionPlan())
|
||||||
|
|
||||||
|
cb_data = cb.data.split(":")
|
||||||
|
devices = int(cb_data[1]) if cb_data[1].isdigit() else settings.min_devices
|
||||||
|
ctx.devices = devices
|
||||||
|
ctx.whitelists = ctx.whitelists or devices >= settings.whitelist_device_threshold
|
||||||
|
|
||||||
|
await cb.message.edit_text(
|
||||||
|
SUBSCRIPTION_DEVICE_SELECTOR.format(price=calculate_price(ctx)),
|
||||||
|
reply_markup=devices_selector(current=ctx.devices, whitelists=ctx.whitelists),
|
||||||
|
)
|
||||||
|
await state.set_state(SubscriptionStorage.devices)
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(SubscriptionStorage.devices, F.data == "whitelist:toggle")
|
||||||
|
async def toggle_whitelists(cb: CallbackQuery, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
ctx: Optional[SubscriptionPlan] = data.get("ctx")
|
||||||
|
if not ctx:
|
||||||
|
await state.set_state(SubscriptionPlan())
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
await cb.answer(CALLBACK_FALLBACK)
|
||||||
|
return
|
||||||
|
ctx.whitelists = (
|
||||||
|
not ctx.whitelists or ctx.devices >= settings.whitelist_device_threshold
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await cb.message.edit_reply_markup(
|
||||||
|
SUBSCRIPTION_DEVICE_SELECTOR.format(price=calculate_price(ctx)),
|
||||||
|
reply_markup=devices_selector(
|
||||||
|
current=ctx.devices, whitelists=ctx.whitelists
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
if "message is not modified:" in str(exc):
|
||||||
|
await cb.answer(NOTHING_PLACEHOLDER)
|
||||||
|
else:
|
||||||
|
await cb.answer(CALLBACK_FALLBACK)
|
||||||
|
await state.set_state(SubscriptionStorage.devices)
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(SubscriptionStorage.devices, F.data == "confirm")
|
||||||
|
async def select_duration_init(cb: CallbackQuery, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
ctx: Optional[SubscriptionPlan] = data.get("ctx")
|
||||||
|
if not ctx:
|
||||||
|
await state.set_state(SubscriptionPlan())
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
await cb.answer(CALLBACK_FALLBACK)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await cb.message.edit_text(
|
||||||
|
SUBSCRIPTION_DURATION_SELECTOR.format(price=calculate_price(ctx)),
|
||||||
|
reply_markup=duration_selector(
|
||||||
|
current=ctx.duration, back_cb=f"devices:{ctx.devices}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
if "message is not modified:" in str(exc):
|
||||||
|
await cb.answer(NOTHING_PLACEHOLDER)
|
||||||
|
else:
|
||||||
|
await cb.answer(CALLBACK_FALLBACK)
|
||||||
|
raise
|
||||||
|
|
||||||
|
await state.set_state(SubscriptionStorage.duration)
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("duration:"))
|
||||||
|
async def select_duration(cb: CallbackQuery, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
ctx: Optional[SubscriptionPlan] = data.get("ctx")
|
||||||
|
if not ctx:
|
||||||
|
await state.set_state()
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
await cb.answer(CONTEXT_REINITIATED)
|
||||||
|
ctx = SubscriptionPlan()
|
||||||
|
|
||||||
|
cb_data = cb.data.split(":")
|
||||||
|
duration = int(cb_data[1]) if cb_data[1].isdigit() else 1
|
||||||
|
ctx.duration = convert_int_to_duration(duration)
|
||||||
|
try:
|
||||||
|
await cb.message.edit_text(
|
||||||
|
SUBSCRIPTION_DURATION_SELECTOR.format(price=calculate_price(ctx)),
|
||||||
|
reply_markup=duration_selector(
|
||||||
|
current=ctx.duration, back_cb=f"devices:{ctx.devices}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
if "message is not modified:" in str(exc):
|
||||||
|
await cb.answer(NOTHING_PLACEHOLDER)
|
||||||
|
else:
|
||||||
|
await cb.answer(CALLBACK_FALLBACK)
|
||||||
|
raise
|
||||||
|
|
||||||
|
await state.set_state(SubscriptionStorage.duration)
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(SubscriptionStorage.duration, F.data == "confirm")
|
||||||
|
async def proceed_to_checkout(cb: CallbackQuery, state: FSMContext):
|
||||||
|
await cb.answer("yay!!!! fuck off.")
|
||||||
|
...
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from aiogram.types import CallbackQuery, Message
|
|||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from dto.di import DependenciesDTO
|
from schemas.di import DependenciesDTO
|
||||||
from keyboards.client import main_menu
|
from keyboards.client import main_menu
|
||||||
from texts import build_main_menu_text
|
from texts import build_main_menu_text
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from aiogram.types import CallbackQuery, Message
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from config import settings
|
from config import settings
|
||||||
from dto.di import DependenciesDTO
|
from schemas.di import DependenciesDTO
|
||||||
from services.user_service import UserServiceException
|
from services.user_service import UserServiceException
|
||||||
from states.support import SupportStorage
|
from states.support import SupportStorage
|
||||||
from texts import GENERAL_PROCESSING, STANDARD_FALLBACK, SUPPORT_INIT, SUPPORT_MSG_SENT
|
from texts import GENERAL_PROCESSING, STANDARD_FALLBACK, SUPPORT_INIT, SUPPORT_MSG_SENT
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
|
from config import settings
|
||||||
|
from misc.utils import convert_duration_to_int
|
||||||
|
from schemas.subscriptions import SubscriptionDuration
|
||||||
|
|
||||||
main_menu: InlineKeyboardMarkup = InlineKeyboardBuilder(
|
main_menu: InlineKeyboardMarkup = InlineKeyboardBuilder(
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
@@ -14,6 +18,71 @@ main_menu: InlineKeyboardMarkup = InlineKeyboardBuilder(
|
|||||||
]
|
]
|
||||||
).as_markup()
|
).as_markup()
|
||||||
|
|
||||||
|
_return_to_menu_btn: InlineKeyboardButton = InlineKeyboardButton(
|
||||||
|
text="⬅️ Назад", callback_data="menu:main"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _return_btn(
|
||||||
|
data: str = "menu:main", text: str = "⬅️ Назад"
|
||||||
|
) -> InlineKeyboardButton:
|
||||||
|
return InlineKeyboardButton(text=text, callback_data=data)
|
||||||
|
|
||||||
|
|
||||||
return_to_menu: InlineKeyboardMarkup = InlineKeyboardBuilder(
|
return_to_menu: InlineKeyboardMarkup = InlineKeyboardBuilder(
|
||||||
[[InlineKeyboardButton(text="⬅️ Назад", callback_data="menu:main")]]
|
[[_return_to_menu_btn]]
|
||||||
).as_markup()
|
).as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def devices_selector(current: int = settings.min_devices, whitelists: bool = False):
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
buttons = [
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{'✅ ' if i == current else ''}{i}", callback_data=f"devices:{i}"
|
||||||
|
)
|
||||||
|
for i in range(settings.min_devices, settings.max_devices + 1)
|
||||||
|
]
|
||||||
|
builder.add(*buttons).adjust(6)
|
||||||
|
|
||||||
|
wl_icon = "✅ " if whitelists else ""
|
||||||
|
builder.row(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{wl_icon}Обход Белых Списков (+100₽)",
|
||||||
|
callback_data="whitelist:toggle",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.row(InlineKeyboardButton(text="🟢", callback_data="confirm"))
|
||||||
|
builder.row(_return_to_menu_btn)
|
||||||
|
|
||||||
|
return builder.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def duration_selector(
|
||||||
|
current: SubscriptionDuration = SubscriptionDuration.MONTH,
|
||||||
|
back_cb: str = "menu:main",
|
||||||
|
) -> InlineKeyboardMarkup:
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
duration_int = convert_duration_to_int(current)
|
||||||
|
|
||||||
|
durations = [
|
||||||
|
(1, "1 Месяц"),
|
||||||
|
(3, "3 Месяца"),
|
||||||
|
(6, "6 Месяцев"),
|
||||||
|
(12, "1 Год"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for val, label in durations:
|
||||||
|
is_selected = val == duration_int
|
||||||
|
|
||||||
|
builder.button(
|
||||||
|
text=f"{'✅ ' if is_selected else ''}{label}",
|
||||||
|
callback_data=f"duration:{val}",
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.row(
|
||||||
|
InlineKeyboardButton(text="🛒 Перейти к оплате", callback_data="confirm")
|
||||||
|
)
|
||||||
|
builder.row(_return_btn(back_cb))
|
||||||
|
|
||||||
|
return builder.as_markup()
|
||||||
|
|||||||
2
main.py
2
main.py
@@ -6,7 +6,7 @@ from aiogram import Bot, Dispatcher
|
|||||||
from aiogram.client.default import DefaultBotProperties
|
from aiogram.client.default import DefaultBotProperties
|
||||||
from aiogram.client.session.aiohttp import AiohttpSession
|
from aiogram.client.session.aiohttp import AiohttpSession
|
||||||
|
|
||||||
from dto.di import DependenciesDTO
|
from schemas.di import DependenciesDTO
|
||||||
from handlers import routers
|
from handlers import routers
|
||||||
from middlewares import DIMiddleware
|
from middlewares import DIMiddleware
|
||||||
from config import settings
|
from config import settings
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from aiogram import BaseMiddleware
|
|||||||
from aiogram.types import TelegramObject
|
from aiogram.types import TelegramObject
|
||||||
from sqlalchemy.ext.asyncio import async_sessionmaker
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
|
||||||
from dto.di import DependenciesDTO
|
from schemas.di import DependenciesDTO
|
||||||
|
|
||||||
|
|
||||||
class DIMiddleware(BaseMiddleware):
|
class DIMiddleware(BaseMiddleware):
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from random import choice
|
from random import choice
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
from config import settings
|
||||||
|
from schemas.subscriptions import SubscriptionDuration, SubscriptionPlan
|
||||||
import texts
|
import texts
|
||||||
|
|
||||||
QUOTES: list[str] = [
|
QUOTES: list[str] = [
|
||||||
@@ -125,3 +127,45 @@ def format_username(username: Optional[str]) -> str:
|
|||||||
if username:
|
if username:
|
||||||
return "@" + username
|
return "@" + username
|
||||||
return texts.NO_USERNAME
|
return texts.NO_USERNAME
|
||||||
|
|
||||||
|
|
||||||
|
def convert_duration_to_int(duration: SubscriptionDuration) -> int:
|
||||||
|
mapping: dict[str, int] = {
|
||||||
|
SubscriptionDuration.MONTH.value: 1,
|
||||||
|
SubscriptionDuration.THREE_MONTHS.value: 3,
|
||||||
|
SubscriptionDuration.SIX_MONTHS.value: 6,
|
||||||
|
SubscriptionDuration.YEAR.value: 12,
|
||||||
|
}
|
||||||
|
return mapping.get(duration.value, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_int_to_duration(value: int) -> SubscriptionDuration:
|
||||||
|
mapping: dict[int, SubscriptionDuration] = {
|
||||||
|
1: SubscriptionDuration.MONTH,
|
||||||
|
3: SubscriptionDuration.THREE_MONTHS,
|
||||||
|
6: SubscriptionDuration.SIX_MONTHS,
|
||||||
|
12: SubscriptionDuration.YEAR,
|
||||||
|
}
|
||||||
|
return mapping.get(value, SubscriptionDuration.MONTH)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_duration_to_timedelta(duration: SubscriptionDuration) -> timedelta:
|
||||||
|
MONTH_DELTA = timedelta(days=30)
|
||||||
|
mapping: dict[str, timedelta] = {
|
||||||
|
SubscriptionDuration.MONTH.value: MONTH_DELTA,
|
||||||
|
SubscriptionDuration.THREE_MONTHS.value: MONTH_DELTA * 3,
|
||||||
|
SubscriptionDuration.SIX_MONTHS.value: MONTH_DELTA * 6,
|
||||||
|
SubscriptionDuration.YEAR.value: MONTH_DELTA * 12,
|
||||||
|
}
|
||||||
|
return mapping.get(duration.value, MONTH_DELTA)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_price(subscription: SubscriptionPlan):
|
||||||
|
return (
|
||||||
|
(subscription.devices * settings.per_device_cost)
|
||||||
|
+ (
|
||||||
|
int(subscription.whitelists) * settings.whitelist_cost
|
||||||
|
if subscription.devices < settings.whitelist_device_threshold
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
) * convert_duration_to_int(subscription.duration)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from sqlalchemy import select
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from db.models.tickets import Ticket, TicketStatus
|
from db.models.tickets import Ticket, TicketStatus
|
||||||
from dto.tickets import NewTicketDTO
|
from schemas.tickets import NewTicketDTO
|
||||||
|
|
||||||
|
|
||||||
class TicketRepository:
|
class TicketRepository:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from sqlalchemy import select
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from db.models import User
|
from db.models import User
|
||||||
from dto.users import NewUserDTO
|
from schemas.users import NewUserDTO
|
||||||
|
|
||||||
|
|
||||||
class UserRepository:
|
class UserRepository:
|
||||||
|
|||||||
18
schemas/subscriptions.py
Normal file
18
schemas/subscriptions.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from config import settings
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionDuration(Enum):
|
||||||
|
MONTH = "month"
|
||||||
|
THREE_MONTHS = "three_months"
|
||||||
|
SIX_MONTHS = "six_months"
|
||||||
|
YEAR = "year"
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionPlan(BaseModel):
|
||||||
|
devices: int = settings.min_devices
|
||||||
|
duration: SubscriptionDuration = SubscriptionDuration.MONTH
|
||||||
|
whitelists: bool = False
|
||||||
@@ -6,8 +6,8 @@ from remnawave import RemnawaveSDK
|
|||||||
from config import settings
|
from config import settings
|
||||||
from db.models.tickets import TicketStatus
|
from db.models.tickets import TicketStatus
|
||||||
from db.models.users import User
|
from db.models.users import User
|
||||||
from dto.tickets import NewTicketDTO
|
from schemas.tickets import NewTicketDTO
|
||||||
from dto.users import NewUserDTO, TicketCreator
|
from schemas.users import NewUserDTO, TicketCreator
|
||||||
from repositories import UserRepository
|
from repositories import UserRepository
|
||||||
from repositories.tickets import TicketRepository
|
from repositories.tickets import TicketRepository
|
||||||
from services.rw import RWUserInfo, get_user_by_telegram_id
|
from services.rw import RWUserInfo, get_user_by_telegram_id
|
||||||
@@ -107,7 +107,6 @@ class UserService:
|
|||||||
|
|
||||||
if ticket:
|
if ticket:
|
||||||
try:
|
try:
|
||||||
print(ticket.thread_id)
|
|
||||||
await msg.copy_to(
|
await msg.copy_to(
|
||||||
chat_id=settings.admin_group_id, message_thread_id=ticket.thread_id
|
chat_id=settings.admin_group_id, message_thread_id=ticket.thread_id
|
||||||
)
|
)
|
||||||
|
|||||||
6
states/buy.py
Normal file
6
states/buy.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from aiogram.fsm.state import StatesGroup, State
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionStorage(StatesGroup):
|
||||||
|
devices = State()
|
||||||
|
duration = State()
|
||||||
14
texts.py
14
texts.py
@@ -38,8 +38,19 @@ STANDARD_FALLBACK = (
|
|||||||
"<b>❌ Что-то пошло не так</b>, прожмите /start и повторите попытку позже."
|
"<b>❌ Что-то пошло не так</b>, прожмите /start и повторите попытку позже."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CALLBACK_FALLBACK = "❌ Что-то пошло не так"
|
||||||
|
CONTEXT_REINITIATED = "🔴 Контекст данного взаимодействия сброшен. Данные могут отличаться от указанных вами."
|
||||||
|
|
||||||
GENERAL_PROCESSING = "<i>⏳ Выполняю...</i>"
|
GENERAL_PROCESSING = "<i>⏳ Выполняю...</i>"
|
||||||
|
|
||||||
|
SUBSCRIPTION_DEVICE_SELECTOR = (
|
||||||
|
"выбери колво устройств епта. тут ещё цена есть смотри опа: {price}"
|
||||||
|
)
|
||||||
|
WHITELISTS_ALREADY_ON = "уже включены!"
|
||||||
|
SUBSCRIPTION_DURATION_SELECTOR = (
|
||||||
|
"тут короче сколько время. а ещё цена смотри ОПА {price}"
|
||||||
|
)
|
||||||
|
|
||||||
SUPPORT_INIT = f"<b>{MALENIA_SUPPORT} <i>Что-то не работает? Жалоба? Предложение?</i></b>\n— всё сюда, желательно со скриншотами. Чем детальнее описание, тем быстрее ответ.\n\n<i>❌ Для отмены — /cancel</i>"
|
SUPPORT_INIT = f"<b>{MALENIA_SUPPORT} <i>Что-то не работает? Жалоба? Предложение?</i></b>\n— всё сюда, желательно со скриншотами. Чем детальнее описание, тем быстрее ответ.\n\n<i>❌ Для отмены — /cancel</i>"
|
||||||
SUPPORT_MSG_SENT = "⏳ Сообщение отправлено. Для выхода — /cancel, вы можете вернуться сюда из главного меню в любое время."
|
SUPPORT_MSG_SENT = "⏳ Сообщение отправлено. Для выхода — /cancel, вы можете вернуться сюда из главного меню в любое время."
|
||||||
SUPPORT_SIGNATURE = "<b>✍️ Сообщение от технической поддержки:</b>"
|
SUPPORT_SIGNATURE = "<b>✍️ Сообщение от технической поддержки:</b>"
|
||||||
@@ -122,3 +133,6 @@ TRAFFIC_WITH_LIMIT = "{used} / {limit}"
|
|||||||
|
|
||||||
# Шаблон для отображения трафика без лимита: {used}
|
# Шаблон для отображения трафика без лимита: {used}
|
||||||
TRAFFIC_NO_LIMIT = "{used}"
|
TRAFFIC_NO_LIMIT = "{used}"
|
||||||
|
|
||||||
|
|
||||||
|
NOTHING_PLACEHOLDER = "🍃"
|
||||||
|
|||||||
Reference in New Issue
Block a user