Compare commits
4 Commits
edfe1e6874
...
245741b182
| Author | SHA1 | Date | |
|---|---|---|---|
| 245741b182 | |||
| 7ab0e71108 | |||
| 3adcc90b74 | |||
| 93f05e46b5 |
@@ -4,21 +4,40 @@ from aiogram.fsm.context import FSMContext
|
|||||||
from aiogram.types import CallbackQuery, Message
|
from aiogram.types import CallbackQuery, Message
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from bot.keyboards.client import close_kb, construct_devices_list, main_menu, subscription_controls
|
from bot.keyboards.client import (
|
||||||
|
close_kb,
|
||||||
|
construct_devices_list,
|
||||||
|
main_menu,
|
||||||
|
prompt_hwid_deletion_kb,
|
||||||
|
subscription_controls,
|
||||||
|
)
|
||||||
from bot.keyboards.common import return_to_menu
|
from bot.keyboards.common import return_to_menu
|
||||||
from bot.texts import (
|
from bot.texts import (
|
||||||
CALLBACK_FALLBACK,
|
CALLBACK_FALLBACK,
|
||||||
FAQ_TEXT,
|
FAQ_TEXT,
|
||||||
GENERAL_PROCESSING,
|
GENERAL_PROCESSING,
|
||||||
|
HWID_LIST,
|
||||||
NO_CONNECTIONS,
|
NO_CONNECTIONS,
|
||||||
NO_SUBSCRIPTION,
|
NO_SUBSCRIPTION,
|
||||||
|
PROMPT_HWID_DELETION,
|
||||||
|
STANDARD_FALLBACK,
|
||||||
|
SUCCESSFUL_HWID_DELETION,
|
||||||
)
|
)
|
||||||
from db.models.users import User
|
from db.models.users import User
|
||||||
from misc import utils
|
from misc import utils
|
||||||
from misc.utils import build_main_menu_text, format_subscription_link
|
from misc.utils import (
|
||||||
|
build_main_menu_text,
|
||||||
|
convert_duration_to_int,
|
||||||
|
format_subscription_link,
|
||||||
|
)
|
||||||
from schemas.di import DependenciesDTO
|
from schemas.di import DependenciesDTO
|
||||||
from schemas.subscriptions import InternalSquads, SubscriptionPlan
|
from schemas.subscriptions import (
|
||||||
from services.rw import get_hwid_list, get_user_by_telegram_id
|
InternalSquads,
|
||||||
|
SubscriptionDuration,
|
||||||
|
SubscriptionPlan,
|
||||||
|
SubscriptionStatus,
|
||||||
|
)
|
||||||
|
from services.rw import delete_hwid, get_hwid_list, get_user_by_telegram_id
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@@ -130,25 +149,40 @@ async def sub_info(
|
|||||||
await cb.answer(CALLBACK_FALLBACK)
|
await cb.answer(CALLBACK_FALLBACK)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
dto = SubscriptionPlan(
|
||||||
|
devices=max(rw_user.hwid_device_limit, 3),
|
||||||
|
whitelists=bool(rw_user.active_squads.count(InternalSquads.WHITELISTS)),
|
||||||
|
)
|
||||||
if rw_user and not sub:
|
if rw_user and not sub:
|
||||||
try:
|
try:
|
||||||
dto = SubscriptionPlan(
|
|
||||||
devices=rw_user.hwid_device_limit,
|
|
||||||
whitelists=bool(rw_user.active_squads.count(InternalSquads.WHITELISTS)),
|
|
||||||
discount=0,
|
|
||||||
)
|
|
||||||
sub = await deps.subscriptions_repository.create_subscription(
|
sub = await deps.subscriptions_repository.create_subscription(
|
||||||
session,
|
session,
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
end_date=rw_user.expire_at,
|
end_date=rw_user.expire_at,
|
||||||
devices=rw_user.hwid_device_limit,
|
devices=dto.devices,
|
||||||
duration_days=30,
|
duration_days=convert_duration_to_int(SubscriptionDuration.MONTH) * 30,
|
||||||
whitelists=dto.whitelists,
|
whitelists=dto.whitelists,
|
||||||
plan_price_paid=utils.calculate_price(dto),
|
plan_price_paid=utils.calculate_price(dto),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
await cb.answer(CALLBACK_FALLBACK)
|
await cb.answer(CALLBACK_FALLBACK)
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
await deps.subscriptions_repository.update_sub(
|
||||||
|
session,
|
||||||
|
cb.from_user.id,
|
||||||
|
status=(
|
||||||
|
SubscriptionStatus.ACTIVE
|
||||||
|
if rw_user.status == "ACTIVE"
|
||||||
|
else SubscriptionStatus.EXPIRED
|
||||||
|
),
|
||||||
|
end_date=rw_user.expire_at,
|
||||||
|
devices=dto.devices,
|
||||||
|
whitelists=dto.whitelists,
|
||||||
|
duration_days=convert_duration_to_int(SubscriptionDuration.MONTH) * 30,
|
||||||
|
plan_price_paid=utils.calculate_price(dto),
|
||||||
|
autorenew=sub.autorenew
|
||||||
|
)
|
||||||
|
|
||||||
await cb.message.edit_text(
|
await cb.message.edit_text(
|
||||||
deps.user_service.format_subscription_message(
|
deps.user_service.format_subscription_message(
|
||||||
@@ -159,6 +193,7 @@ async def sub_info(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "hwid_control")
|
@router.callback_query(F.data == "hwid_control")
|
||||||
async def hwid_control(cb: CallbackQuery, state: FSMContext, deps: DependenciesDTO):
|
async def hwid_control(cb: CallbackQuery, state: FSMContext, deps: DependenciesDTO):
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -175,8 +210,40 @@ async def hwid_control(cb: CallbackQuery, state: FSMContext, deps: DependenciesD
|
|||||||
await cb.message.edit_text(NO_CONNECTIONS, reply_markup=return_to_menu)
|
await cb.message.edit_text(NO_CONNECTIONS, reply_markup=return_to_menu)
|
||||||
return
|
return
|
||||||
|
|
||||||
await cb.message.edit_text(text="pepe", reply_markup=construct_devices_list(devices))
|
hwid_limit = max(rw_user.hwid_device_limit, 3)
|
||||||
|
|
||||||
|
await cb.message.edit_text(
|
||||||
|
text=HWID_LIST.format(count=len(devices), limit=hwid_limit),
|
||||||
|
reply_markup=construct_devices_list(devices),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("hwid:"))
|
||||||
|
async def prompt_delete_hwid(cb: CallbackQuery, state: FSMContext):
|
||||||
|
await state.clear()
|
||||||
|
hwid = cb.data.split(":")[1]
|
||||||
|
|
||||||
|
await cb.message.edit_text(PROMPT_HWID_DELETION, reply_markup=prompt_hwid_deletion_kb(hwid))
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("delete_hwid:"))
|
||||||
|
async def delete_hwid_bot(cb: CallbackQuery, state: FSMContext, deps: DependenciesDTO):
|
||||||
|
await state.clear()
|
||||||
|
hwid = cb.data.split(":")[1]
|
||||||
|
|
||||||
|
await cb.message.edit_text(GENERAL_PROCESSING, reply_markup=close_kb)
|
||||||
|
rw_user = await get_user_by_telegram_id(deps.rw_sdk, cb.from_user.id)
|
||||||
|
|
||||||
|
if not rw_user:
|
||||||
|
await cb.message.edit_text(STANDARD_FALLBACK, reply_markup=return_to_menu)
|
||||||
|
return
|
||||||
|
|
||||||
|
status = await delete_hwid(deps.rw_sdk, rw_user.uuid, hwid)
|
||||||
|
|
||||||
|
if status:
|
||||||
|
await cb.message.edit_text(SUCCESSFUL_HWID_DELETION, reply_markup=return_to_menu)
|
||||||
|
else:
|
||||||
|
await cb.message.edit_text(STANDARD_FALLBACK, reply_markup=return_to_menu)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "toggle:autorenewal")
|
@router.callback_query(F.data == "toggle:autorenewal")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
|||||||
from remnawave.models.hwid import HwidDeviceDto
|
from remnawave.models.hwid import HwidDeviceDto
|
||||||
|
|
||||||
from config import settings
|
from config import settings
|
||||||
from misc.utils import convert_duration_to_int
|
from misc.utils import convert_duration_to_int, format_hwid_device
|
||||||
from schemas.subscriptions import SubscriptionDuration
|
from schemas.subscriptions import SubscriptionDuration
|
||||||
|
|
||||||
from .common import _return_to_menu_btn
|
from .common import _return_to_menu_btn
|
||||||
@@ -64,7 +64,9 @@ def subscription_controls(
|
|||||||
)
|
)
|
||||||
builder.row(InlineKeyboardButton(text=autorenewal_text, callback_data="toggle:autorenewal"))
|
builder.row(InlineKeyboardButton(text=autorenewal_text, callback_data="toggle:autorenewal"))
|
||||||
|
|
||||||
builder.row(InlineKeyboardButton(text="Управление устройствами", callback_data="hwid_control"))
|
builder.row(
|
||||||
|
InlineKeyboardButton(text="💻 Управление устройствами", callback_data="hwid_control")
|
||||||
|
)
|
||||||
builder.row(_return_to_menu_btn)
|
builder.row(_return_to_menu_btn)
|
||||||
return builder.as_markup()
|
return builder.as_markup()
|
||||||
|
|
||||||
@@ -146,10 +148,24 @@ def pay_url(url: str) -> InlineKeyboardMarkup:
|
|||||||
]
|
]
|
||||||
).as_markup()
|
).as_markup()
|
||||||
|
|
||||||
|
|
||||||
def construct_devices_list(devices: list[HwidDeviceDto]) -> InlineKeyboardMarkup:
|
def construct_devices_list(devices: list[HwidDeviceDto]) -> InlineKeyboardMarkup:
|
||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
|
|
||||||
for device in devices:
|
for device in devices:
|
||||||
builder.row(InlineKeyboardButton(text=f"{device.platform} | {device.device_model} | {device.user_agent.split('/')[0]}", callback_data=f"hwid:{device.hwid}"))
|
builder.row(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=format_hwid_device(device), callback_data=f"hwid:{device.hwid}"
|
||||||
|
)
|
||||||
|
)
|
||||||
builder.row(_return_btn(data="sub_info"))
|
builder.row(_return_btn(data="sub_info"))
|
||||||
return builder.as_markup()
|
return builder.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_hwid_deletion_kb(hwid: str) -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardBuilder(
|
||||||
|
[
|
||||||
|
[InlineKeyboardButton(text="🗑️ Удалить", callback_data=f"delete_hwid:{hwid}")],
|
||||||
|
[_return_btn("hwid_control", text="❌ Отмена")],
|
||||||
|
]
|
||||||
|
).as_markup()
|
||||||
|
|||||||
14
bot/texts.py
14
bot/texts.py
@@ -74,6 +74,13 @@ NO_SQUADS = "отсутствуют"
|
|||||||
# — HWID лимит
|
# — HWID лимит
|
||||||
UNLIMITED_HWID = "без лимитов"
|
UNLIMITED_HWID = "без лимитов"
|
||||||
|
|
||||||
|
# — OS эмодзи
|
||||||
|
WINDOWS = "💻"
|
||||||
|
LINUX = "🐧"
|
||||||
|
ANDROID = "💚"
|
||||||
|
APPLE = "🍎"
|
||||||
|
UNKNOWN_OS = "👤"
|
||||||
|
|
||||||
# — Трафик
|
# — Трафик
|
||||||
NO_TRAFFIC_LIMIT = "не ограничен"
|
NO_TRAFFIC_LIMIT = "не ограничен"
|
||||||
TRAFFIC_WITH_LIMIT = "{used} / {limit}"
|
TRAFFIC_WITH_LIMIT = "{used} / {limit}"
|
||||||
@@ -155,7 +162,10 @@ SUBSCRIPTION_INFORMATION = (
|
|||||||
|
|
||||||
NO_SUBSCRIPTION = f"{MALENIA_WARN} <b>Подписка не найдена.</b>"
|
NO_SUBSCRIPTION = f"{MALENIA_WARN} <b>Подписка не найдена.</b>"
|
||||||
|
|
||||||
NO_CONNECTIONS = f"{MALENIA_SHIELD} <b>Вы ещё не подключались по своей ссылке.</b>"
|
NO_CONNECTIONS = f"{MALENIA_SHIELD} <b>У вас нет подключённых устройств.</b>"
|
||||||
|
HWID_LIST = f"{MALENIA_SHIELD} <b>Ваши устройства ({{count}} / {{limit}}):</b>"
|
||||||
|
PROMPT_HWID_DELETION = f"{MALENIA_WARN} <b>Вы уверены что хотите удалить это устройство?</b>"
|
||||||
|
SUCCESSFUL_HWID_DELETION = f"{MALENIA_HEART} <b>Устройство успешно удалено.</b>"
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# РЕФЕРАЛЬНАЯ СИСТЕМА
|
# РЕФЕРАЛЬНАЯ СИСТЕМА
|
||||||
@@ -229,7 +239,7 @@ AUTORENEWAL_INSUFFICIENT_FUNDS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
AUTORENEWAL_SUCCESS = (
|
AUTORENEWAL_SUCCESS = (
|
||||||
f"<b>{MALENIA_CARD} Подписка продлена автоматически. Списано <code>{{price}}₽</code>.</b>"
|
f"<b>{MALENIA_CARD} Подписка продлена автоматически. Списано <code>{{price}}₽</code>.</b>\n"
|
||||||
"Новая дата окончания: <code>{end_date}</code>."
|
"Новая дата окончания: <code>{end_date}</code>."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import urllib.parse
|
|||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from random import choice
|
from random import choice
|
||||||
|
|
||||||
|
from remnawave.models.hwid import HwidDeviceDto
|
||||||
|
|
||||||
from bot import texts
|
from bot import texts
|
||||||
from config import settings
|
from config import settings
|
||||||
from schemas.subscriptions import InternalSquads, SubscriptionDuration, SubscriptionPlan
|
from schemas.subscriptions import InternalSquads, SubscriptionDuration, SubscriptionPlan
|
||||||
@@ -271,3 +273,25 @@ def is_same_plan(rw_user: RWUserInfo, new_plan: SubscriptionPlan) -> bool:
|
|||||||
active_squad_uuids = {squad["uuid"] for squad in rw_user.active_squads}
|
active_squad_uuids = {squad["uuid"] for squad in rw_user.active_squads}
|
||||||
current_has_whitelist = InternalSquads.WHITELISTS in active_squad_uuids
|
current_has_whitelist = InternalSquads.WHITELISTS in active_squad_uuids
|
||||||
return current_has_whitelist == new_plan.whitelists
|
return current_has_whitelist == new_plan.whitelists
|
||||||
|
|
||||||
|
|
||||||
|
def _format_os_emoji(os: str) -> str:
|
||||||
|
os = os.lower()
|
||||||
|
if os == "windows":
|
||||||
|
return texts.WINDOWS
|
||||||
|
if os == "linux":
|
||||||
|
return texts.LINUX
|
||||||
|
if os == "ios":
|
||||||
|
return texts.APPLE
|
||||||
|
if os == "android":
|
||||||
|
return texts.ANDROID
|
||||||
|
return texts.UNKNOWN_OS
|
||||||
|
|
||||||
|
|
||||||
|
def format_hwid_device(device: HwidDeviceDto) -> str:
|
||||||
|
os = device.platform
|
||||||
|
model = device.device_model
|
||||||
|
client = device.user_agent.split("/")[0]
|
||||||
|
emoji = _format_os_emoji(os)
|
||||||
|
|
||||||
|
return f"{emoji} {os} | {model} | {client}"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ class SubscriptionRepository:
|
|||||||
select(Subscription)
|
select(Subscription)
|
||||||
.where(
|
.where(
|
||||||
Subscription.autorenew == True, # noqa: E712
|
Subscription.autorenew == True, # noqa: E712
|
||||||
Subscription.status == SubscriptionStatus.ACTIVE,
|
|
||||||
Subscription.end_date <= threshold,
|
Subscription.end_date <= threshold,
|
||||||
)
|
)
|
||||||
.with_for_update()
|
.with_for_update()
|
||||||
@@ -69,7 +68,7 @@ class SubscriptionRepository:
|
|||||||
whitelists: bool,
|
whitelists: bool,
|
||||||
duration_days: int,
|
duration_days: int,
|
||||||
plan_price_paid: int,
|
plan_price_paid: int,
|
||||||
autorenew: bool = False,
|
autorenew: bool,
|
||||||
status: SubscriptionStatus = SubscriptionStatus.ACTIVE,
|
status: SubscriptionStatus = SubscriptionStatus.ACTIVE,
|
||||||
) -> Subscription | None:
|
) -> Subscription | None:
|
||||||
stmt = (
|
stmt = (
|
||||||
|
|||||||
@@ -99,13 +99,13 @@ async def notify_subscription_renewal(
|
|||||||
bot,
|
bot,
|
||||||
settings.admin_log_subscriptions_topic_id,
|
settings.admin_log_subscriptions_topic_id,
|
||||||
(
|
(
|
||||||
"<b>Продление подписки</b>",
|
"<b>Продление подписки</b>\n\n"
|
||||||
f"Пользователь: <code>{user_id}</code>",
|
f"Пользователь: <code>{user_id}</code>\n"
|
||||||
f"Стоимость: <b>{amount}₽</b>",
|
f"Стоимость: <b>{amount}₽</b>\n"
|
||||||
f"Баланс: {balance_before}₽ -> {balance_after}₽",
|
f"Баланс: {balance_before}₽ -> {balance_after}₽\n"
|
||||||
f"Устройства: {devices}",
|
f"Устройства: {devices}\n"
|
||||||
f"Срок: {duration_days} дн.",
|
f"Срок: {duration_days} дн.\n"
|
||||||
f"Whitelist: {'да' if whitelists else 'нет'}",
|
f"Whitelist: {'да' if whitelists else 'нет'}\n"
|
||||||
f"Новая дата окончания: {escape(end_date)}",
|
f"Новая дата окончания: {escape(end_date)}"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ class RenewalService:
|
|||||||
)
|
)
|
||||||
return utils.calculate_price(plan)
|
return utils.calculate_price(plan)
|
||||||
|
|
||||||
async def _renew_subscription_in_rw(self, subscription: Subscription) -> None:
|
async def _renew_subscription_in_rw(
|
||||||
|
self, subscription: Subscription, expire_at: datetime.datetime
|
||||||
|
) -> None:
|
||||||
rw_user = await rw_service.get_user_by_telegram_id(self.rw_sdk, subscription.user_id)
|
rw_user = await rw_service.get_user_by_telegram_id(self.rw_sdk, subscription.user_id)
|
||||||
if not rw_user:
|
if not rw_user:
|
||||||
raise RenewalServiceError(f"RW user not found for user_id={subscription.user_id}")
|
raise RenewalServiceError(f"RW user not found for user_id={subscription.user_id}")
|
||||||
@@ -58,7 +60,7 @@ class RenewalService:
|
|||||||
if subscription.whitelists:
|
if subscription.whitelists:
|
||||||
squads.append(InternalSquads.WHITELISTS)
|
squads.append(InternalSquads.WHITELISTS)
|
||||||
|
|
||||||
await rw_service.add_days(self.rw_sdk, rw_user.uuid, subscription.duration_days)
|
await rw_service.update_expire_at(self.rw_sdk, rw_user.uuid, expire_at)
|
||||||
await rw_service.update_squads(self.rw_sdk, rw_user.uuid, squads)
|
await rw_service.update_squads(self.rw_sdk, rw_user.uuid, squads)
|
||||||
await rw_service.set_hwid_limit(self.rw_sdk, rw_user.uuid, subscription.devices)
|
await rw_service.set_hwid_limit(self.rw_sdk, rw_user.uuid, subscription.devices)
|
||||||
|
|
||||||
@@ -89,7 +91,9 @@ class RenewalService:
|
|||||||
await self.subscription_repository.update_subscription_autorenew(
|
await self.subscription_repository.update_subscription_autorenew(
|
||||||
session, user_id, False
|
session, user_id, False
|
||||||
)
|
)
|
||||||
await self._notify_user(user_id, AUTORENEWAL_INSUFFICIENT_FUNDS)
|
await self._notify_user(
|
||||||
|
user_id, AUTORENEWAL_INSUFFICIENT_FUNDS.format(price=price, balance=user.balance)
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
renewed_user = await self.user_repository.decrease_user_balance(
|
renewed_user = await self.user_repository.decrease_user_balance(
|
||||||
@@ -104,8 +108,14 @@ class RenewalService:
|
|||||||
logger.error("Failed to decrease balance for user %d", user_id)
|
logger.error("Failed to decrease balance for user %d", user_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
now_utc = datetime.datetime.now(datetime.UTC)
|
||||||
|
if subscription.end_date > now_utc:
|
||||||
|
new_expire = subscription.end_date + datetime.timedelta(days=subscription.duration_days)
|
||||||
|
else:
|
||||||
|
new_expire = now_utc + datetime.timedelta(days=subscription.duration_days)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._renew_subscription_in_rw(subscription)
|
await self._renew_subscription_in_rw(subscription, new_expire)
|
||||||
except RenewalServiceError:
|
except RenewalServiceError:
|
||||||
logger.exception("Failed to renew subscription in RW for user %d", user_id)
|
logger.exception("Failed to renew subscription in RW for user %d", user_id)
|
||||||
await self.user_repository.increase_user_balance(
|
await self.user_repository.increase_user_balance(
|
||||||
@@ -117,11 +127,10 @@ class RenewalService:
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
new_end_date = subscription.end_date + datetime.timedelta(days=subscription.duration_days)
|
|
||||||
await self.subscription_repository.update_sub(
|
await self.subscription_repository.update_sub(
|
||||||
session,
|
session,
|
||||||
user_id,
|
user_id,
|
||||||
end_date=new_end_date,
|
end_date=new_expire,
|
||||||
autorenew=True,
|
autorenew=True,
|
||||||
status=SubscriptionStatus.ACTIVE,
|
status=SubscriptionStatus.ACTIVE,
|
||||||
devices=subscription.devices,
|
devices=subscription.devices,
|
||||||
@@ -134,7 +143,7 @@ class RenewalService:
|
|||||||
user_id,
|
user_id,
|
||||||
AUTORENEWAL_SUCCESS.format(
|
AUTORENEWAL_SUCCESS.format(
|
||||||
price=price,
|
price=price,
|
||||||
end_date=new_end_date.strftime("%d.%m.%Y"),
|
end_date=new_expire.strftime("%d.%m.%Y"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
await notify_subscription_renewal(
|
await notify_subscription_renewal(
|
||||||
@@ -146,7 +155,7 @@ class RenewalService:
|
|||||||
devices=subscription.devices,
|
devices=subscription.devices,
|
||||||
duration_days=subscription.duration_days,
|
duration_days=subscription.duration_days,
|
||||||
whitelists=subscription.whitelists,
|
whitelists=subscription.whitelists,
|
||||||
end_date=new_end_date.strftime("%d.%m.%Y"),
|
end_date=new_expire.strftime("%d.%m.%Y"),
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("Successfully renewed subscription for user %d", user_id)
|
logger.info("Successfully renewed subscription for user %d", user_id)
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from typing import List
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from remnawave import RemnawaveSDK
|
from remnawave import RemnawaveSDK
|
||||||
from remnawave.models import CreateUserRequestDto, UpdateUserRequestDto
|
from remnawave.models import (
|
||||||
|
CreateUserRequestDto,
|
||||||
|
DeleteUserHwidDeviceResponseDto,
|
||||||
|
HWIDDeleteRequest,
|
||||||
|
UpdateUserRequestDto,
|
||||||
|
)
|
||||||
from remnawave.models.hwid import HwidDeviceDto
|
from remnawave.models.hwid import HwidDeviceDto
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -352,14 +356,31 @@ async def create_user(
|
|||||||
logger.warning("Failed to create user username=%s: %s", username, exc)
|
logger.warning("Failed to create user username=%s: %s", username, exc)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_hwid_list(
|
|
||||||
sdk: RemnawaveSDK,
|
async def get_hwid_list(sdk: RemnawaveSDK, user_uuid: str) -> list[HwidDeviceDto] | None:
|
||||||
user_uuid: str
|
|
||||||
) -> List[HwidDeviceDto] | None:
|
|
||||||
try:
|
try:
|
||||||
resp = await sdk.hwid.get_hwid_user(user_uuid)
|
resp = await sdk.hwid.get_hwid_user(user_uuid)
|
||||||
return resp.devices
|
return resp.devices
|
||||||
except Exception as exc:
|
except Exception:
|
||||||
logger.exception("failed to fetch hwid list for user=%s", user_uuid)
|
logger.exception("failed to fetch hwid list for user=%s", user_uuid)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_hwid(sdk: RemnawaveSDK, user_uuid: str, hwid: str) -> bool:
|
||||||
|
body = HWIDDeleteRequest(user_uuid=user_uuid, hwid=hwid)
|
||||||
|
try:
|
||||||
|
resp = await sdk.hwid.delete_hwid_to_user(body)
|
||||||
|
return isinstance(resp, DeleteUserHwidDeviceResponseDto)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("failed to delete hwid=%s for user=%s", hwid, user_uuid)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def update_expire_at(sdk: RemnawaveSDK, user_uuid: str, expire_at: datetime):
|
||||||
|
dto = UpdateUserRequestDto(uuid=user_uuid, expire_at=expire_at) # type: ignore
|
||||||
|
try:
|
||||||
|
await sdk.users.update_user(body=dto)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
logger.exception("failed to update expire_at=%s for user=%s", str(expire_at), user_uuid)
|
||||||
|
return False
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from aiogram.types import InlineKeyboardMarkup, Message, User as TelegramUser
|
from aiogram.types import Message, User as TelegramUser
|
||||||
from remnawave import RemnawaveSDK
|
from remnawave import RemnawaveSDK
|
||||||
from remnawave.models.hwid import HwidDeviceDto
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from bot.texts import NO_SUBSCRIPTION, NOTHING_PLACEHOLDER, SUBSCRIPTION_INFORMATION
|
from bot.texts import NO_SUBSCRIPTION, NOTHING_PLACEHOLDER, SUBSCRIPTION_INFORMATION
|
||||||
from config import settings
|
from config import settings
|
||||||
|
from db.models import Subscription
|
||||||
from db.models.users import User
|
from db.models.users import User
|
||||||
from misc import utils
|
from misc import utils
|
||||||
from repositories import UserRepository
|
from repositories import UserRepository
|
||||||
@@ -123,7 +123,7 @@ class UserService:
|
|||||||
session,
|
session,
|
||||||
user.id,
|
user.id,
|
||||||
end_date=rw_user.expire_at + duration,
|
end_date=rw_user.expire_at + duration,
|
||||||
autorenew=False,
|
autorenew=db_subscription.autorenew,
|
||||||
status=SubscriptionStatus.ACTIVE,
|
status=SubscriptionStatus.ACTIVE,
|
||||||
devices=int(subscription.devices),
|
devices=int(subscription.devices),
|
||||||
whitelists=subscription.whitelists,
|
whitelists=subscription.whitelists,
|
||||||
@@ -180,7 +180,7 @@ class UserService:
|
|||||||
session,
|
session,
|
||||||
user.id,
|
user.id,
|
||||||
end_date=expire,
|
end_date=expire,
|
||||||
autorenew=False,
|
autorenew=db_subscription.autorenew,
|
||||||
status=SubscriptionStatus.ACTIVE,
|
status=SubscriptionStatus.ACTIVE,
|
||||||
devices=int(subscription.devices),
|
devices=int(subscription.devices),
|
||||||
whitelists=subscription.whitelists,
|
whitelists=subscription.whitelists,
|
||||||
@@ -240,3 +240,14 @@ class UserService:
|
|||||||
logger.exception("caught an exception while launching promo, skipping user...")
|
logger.exception("caught an exception while launching promo, skipping user...")
|
||||||
|
|
||||||
return sent
|
return sent
|
||||||
|
|
||||||
|
def validate_db_subscription(self, subscription: Subscription, rw_user: rw.RWUserInfo):
|
||||||
|
return all(
|
||||||
|
[
|
||||||
|
(rw_user.status != "ACTIVE" and subscription.status == SubscriptionStatus.EXPIRED)
|
||||||
|
or (
|
||||||
|
rw_user.status == "ACTIVE" and subscription.status == SubscriptionStatus.ACTIVE
|
||||||
|
),
|
||||||
|
rw_user.expire_at == subscription.end_date,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user