Move DTO definitions from dto/ to schemas/ and update imports. Add buy handler with FSM state management for device selection, whitelist toggling, and duration choice. Include pricing logic, inline keyboards, and new configuration fields.
197 lines
6.8 KiB
Python
197 lines
6.8 KiB
Python
from typing import Optional
|
|
from aiogram.types import Message, ReactionTypeEmoji
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from remnawave import RemnawaveSDK
|
|
|
|
from config import settings
|
|
from db.models.tickets import TicketStatus
|
|
from db.models.users import User
|
|
from schemas.tickets import NewTicketDTO
|
|
from schemas.users import NewUserDTO, TicketCreator
|
|
from repositories import UserRepository
|
|
from repositories.tickets import TicketRepository
|
|
from services.rw import RWUserInfo, get_user_by_telegram_id
|
|
from texts import (
|
|
ADMIN_STANDARD_FALLBACK,
|
|
ADMIN_TICKET_WITH_RW,
|
|
SUPPORT_SIGNATURE,
|
|
TICKET_STATUS_EMOJIS,
|
|
ADMIN_TICKET_WITHOUT_RW,
|
|
)
|
|
from misc import utils
|
|
|
|
|
|
class UserServiceException(Exception): ...
|
|
|
|
|
|
class UserService:
|
|
def __init__(
|
|
self, user_repository: UserRepository, ticket_repository: TicketRepository
|
|
) -> None:
|
|
self.user_repository = user_repository
|
|
self.ticket_repository = ticket_repository
|
|
|
|
@staticmethod
|
|
def format_ticket_name(
|
|
creator: TicketCreator,
|
|
status: Optional[TicketStatus] = TicketStatus.OPEN,
|
|
ticket_id: Optional[int] = None,
|
|
):
|
|
return (
|
|
TICKET_STATUS_EMOJIS[status.value]
|
|
+ (f" T-{ticket_id:0>3}" if ticket_id else "")
|
|
+ " | "
|
|
+ (f"@{creator.username}" if creator.username else str(creator.id))
|
|
)
|
|
|
|
@staticmethod
|
|
async def format_admin_support_message(
|
|
ticket_number: int, user: User, rw_user: Optional[RWUserInfo]
|
|
):
|
|
username_display = utils.format_username(user.username)
|
|
|
|
if rw_user is None:
|
|
# Пользователь не найден в Remnawave
|
|
return ADMIN_TICKET_WITHOUT_RW.format(
|
|
ticket_number=ticket_number,
|
|
full_name=user.full_name,
|
|
username_display=username_display,
|
|
telegram_id=user.id,
|
|
)
|
|
|
|
status_icon, status_text = utils.format_status(rw_user.status)
|
|
expire_date = rw_user.expire_at.strftime("%d.%m.%Y")
|
|
days_left = utils.format_days_left(rw_user.expire_at)
|
|
used_traffic = utils.format_traffic(
|
|
rw_user.used_traffic_bytes, rw_user.traffic_limit_bytes
|
|
)
|
|
hwid_limit = utils.format_hwid_limit(rw_user.hwid_device_limit)
|
|
squads = utils.format_squads(rw_user.active_squads)
|
|
|
|
return ADMIN_TICKET_WITH_RW.format(
|
|
ticket_number=ticket_number,
|
|
full_name=user.full_name,
|
|
username_display=username_display,
|
|
telegram_id=user.id,
|
|
status_icon=status_icon,
|
|
status=status_text,
|
|
expire_date=expire_date,
|
|
days_left=days_left,
|
|
used_traffic=used_traffic,
|
|
hwid_limit=hwid_limit,
|
|
squads=squads,
|
|
remnawave_uuid=rw_user.uuid,
|
|
)
|
|
|
|
async def add_user(
|
|
self, session: AsyncSession, user_id: int, referal: Optional[int] = None
|
|
) -> User:
|
|
user = await self.user_repository.get_user_by_id(session, user_id)
|
|
if user:
|
|
return user
|
|
|
|
referal = int(referal) if str(referal).isdigit() else None
|
|
model = NewUserDTO(id=user_id, referal=referal)
|
|
return await self.user_repository.create_user(session, model)
|
|
|
|
async def support_forward_message(
|
|
self, session: AsyncSession, msg: Message, rw: RemnawaveSDK
|
|
):
|
|
user = await self.user_repository.get_user_by_id(session, msg.from_user.id)
|
|
if not user:
|
|
raise UserServiceException("User %d is not found.", msg.from_user.id)
|
|
|
|
ticket = await self.ticket_repository.get_available_ticket_by_user_id(
|
|
session, user.id
|
|
)
|
|
|
|
if ticket:
|
|
try:
|
|
await msg.copy_to(
|
|
chat_id=settings.admin_group_id, message_thread_id=ticket.thread_id
|
|
)
|
|
return
|
|
except Exception:
|
|
await self.ticket_repository.update_ticket_status(
|
|
session, ticket, TicketStatus.CLOSED
|
|
)
|
|
|
|
rw_user = await get_user_by_telegram_id(rw, user.id)
|
|
creator = TicketCreator(id=user.id, username=msg.from_user.username)
|
|
|
|
ticket_dto = NewTicketDTO(
|
|
creator_id=user.id,
|
|
)
|
|
ticket = await self.ticket_repository.create_ticket(
|
|
session, ticket_data=ticket_dto
|
|
)
|
|
|
|
thread = await msg.bot.create_forum_topic(
|
|
settings.admin_group_id,
|
|
self.format_ticket_name(
|
|
creator, ticket_id=ticket.id, status=TicketStatus.PENDING
|
|
),
|
|
)
|
|
ticket = await self.ticket_repository.update_ticket_thread_id(
|
|
session, ticket, thread.message_thread_id
|
|
)
|
|
|
|
await msg.bot.send_message(
|
|
settings.admin_group_id,
|
|
message_thread_id=thread.message_thread_id,
|
|
text=await self.format_admin_support_message(
|
|
ticket_number=ticket.id, user=msg.from_user, rw_user=rw_user
|
|
),
|
|
)
|
|
await msg.copy_to(
|
|
settings.admin_group_id, message_thread_id=thread.message_thread_id
|
|
)
|
|
|
|
async def support_answer(self, session: AsyncSession, msg: Message):
|
|
thread_id = msg.message_thread_id
|
|
ticket = await self.ticket_repository.get_ticket_by_thread_id(
|
|
session, thread_id=thread_id
|
|
)
|
|
|
|
if ticket.status == TicketStatus.PENDING:
|
|
await self.ticket_repository.update_ticket_status(
|
|
session, ticket, TicketStatus.OPEN
|
|
)
|
|
await msg.bot.edit_forum_topic(
|
|
settings.admin_group_id,
|
|
ticket.thread_id,
|
|
name=self.format_ticket_name(
|
|
TicketCreator(ticket.creator_id), ticket_id=ticket.id
|
|
),
|
|
)
|
|
|
|
try:
|
|
await msg.bot.send_message(ticket.creator_id, SUPPORT_SIGNATURE)
|
|
await msg.copy_to(ticket.creator_id)
|
|
await msg.react([ReactionTypeEmoji(emoji="⚡")])
|
|
except Exception:
|
|
await msg.reply(ADMIN_STANDARD_FALLBACK)
|
|
|
|
async def close_ticket(self, session: AsyncSession, msg: Message):
|
|
thread_id = msg.message_thread_id
|
|
ticket = await self.ticket_repository.get_ticket_by_thread_id(
|
|
session, thread_id=thread_id
|
|
)
|
|
|
|
await msg.bot.edit_forum_topic(
|
|
settings.admin_group_id,
|
|
ticket.thread_id,
|
|
name=self.format_ticket_name(
|
|
TicketCreator(ticket.creator_id),
|
|
ticket_id=ticket.id,
|
|
status=TicketStatus.CLOSED,
|
|
),
|
|
)
|
|
|
|
await self.ticket_repository.update_ticket_status(
|
|
session, ticket, TicketStatus.CLOSED
|
|
)
|
|
await msg.bot.close_forum_topic(
|
|
settings.admin_group_id, message_thread_id=thread_id
|
|
)
|