10 Commits

22 changed files with 887 additions and 1277 deletions

8
.gitignore vendored
View File

@@ -1,6 +1,6 @@
config.json
TBot/ TBot/
logs/ logs/*
*.code-workspace
__pycache__/ __pycache__/
.gitignore handlers.py
docker-compose.yml *.pyc

View File

@@ -4,6 +4,18 @@ FROM python:3.12-slim
# Устанавливаем рабочую директорию # Устанавливаем рабочую директорию
WORKDIR /app WORKDIR /app
# Устанавливаем необходимые пакеты и локаль
RUN apt-get update && apt-get install -y --no-install-recommends \
locales && \
echo "ru_RU.UTF-8 UTF-8" > /etc/locale.gen && \
locale-gen && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Устанавливаем переменные окружения для локали
ENV LANG ru_RU.UTF-8
ENV LANGUAGE ru_RU:ru
ENV LC_ALL ru_RU.UTF-8
# Копируем файлы проекта # Копируем файлы проекта
COPY . . COPY . .

View File

@@ -0,0 +1,14 @@
{
"folders": [
{
"path": "."
},
{
"path": "../Bot"
},
{
"path": "../bot/Lark_VPN_Bot"
}
],
"settings": {}
}

View File

@@ -1,65 +0,0 @@
import os
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from motor.motor_asyncio import AsyncIOMotorClient
from databases.model import Base
# Настройки PostgreSQL из переменных окружения
POSTGRES_DSN = os.getenv("POSTGRES_URL")
# Создание движка для PostgreSQL
postgres_engine = create_async_engine(POSTGRES_DSN, echo=False)
AsyncSessionLocal = sessionmaker(bind=postgres_engine, class_=AsyncSession, expire_on_commit=False)
# Настройки MongoDB из переменных окружения
MONGO_URI = os.getenv("MONGO_URL")
DATABASE_NAME = os.getenv("DB_NAME")
# Создание клиента MongoDB
mongo_client = AsyncIOMotorClient(MONGO_URI)
mongo_db = mongo_client[DATABASE_NAME]
# Инициализация PostgreSQL
async def init_postgresql():
"""
Инициализация подключения к PostgreSQL.
"""
try:
async with postgres_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
print("PostgreSQL connected.")
except Exception as e:
print(f"Failed to connect to PostgreSQL: {e}")
# Инициализация MongoDB
async def init_mongodb():
"""
Проверка подключения к MongoDB.
"""
try:
# Проверяем подключение к MongoDB
await mongo_client.admin.command("ping")
print("MongoDB connected.")
except Exception as e:
print(f"Failed to connect to MongoDB: {e}")
# Получение сессии PostgreSQL
async def get_postgres_session():
"""
Асинхронный генератор сессий PostgreSQL.
"""
async with AsyncSessionLocal() as session:
yield session
# Закрытие соединений
async def close_connections():
"""
Закрытие всех соединений с базами данных.
"""
# Закрытие PostgreSQL
await postgres_engine.dispose()
print("PostgreSQL connection closed.")
# Закрытие MongoDB
mongo_client.close()
print("MongoDB connection closed.")

View File

@@ -1,60 +0,0 @@
from sqlalchemy import Column, String, Numeric, DateTime, Boolean, ForeignKey, Integer
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
from datetime import datetime
import uuid
Base = declarative_base()
def generate_uuid():
return str(uuid.uuid4())
"""Пользователи"""
class User(Base):
__tablename__ = 'users'
id = Column(String, primary_key=True, default=generate_uuid)
telegram_id = Column(Integer, unique=True, nullable=False)
username = Column(String)
balance = Column(Numeric(10, 2), default=0.0)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
subscriptions = relationship("Subscription", back_populates="user")
transactions = relationship("Transaction", back_populates="user")
admins = relationship("Administrators", back_populates="user")
"""Подписки"""
class Subscription(Base):
__tablename__ = 'subscriptions'
id = Column(String, primary_key=True, default=generate_uuid)
user_id = Column(String, ForeignKey('users.id'))
vpn_server_id = Column(String)
plan = Column(String)
expiry_date = Column(DateTime)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user = relationship("User", back_populates="subscriptions")
"""Транзакции"""
class Transaction(Base):
__tablename__ = 'transactions'
id = Column(String, primary_key=True, default=generate_uuid)
user_id = Column(String, ForeignKey('users.id'))
amount = Column(Numeric(10, 2))
transaction_type = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)
user = relationship("User", back_populates="transactions")
"""Администраторы"""
class Administrators(Base):
__tablename__ = 'admins'
id = Column(String, primary_key=True, default=generate_uuid)
user_id = Column(String, ForeignKey('users.id'))
user = relationship("User", back_populates="admins")

View File

@@ -1,103 +0,0 @@
import os
from motor.motor_asyncio import AsyncIOMotorClient
import logging
class MongoDBRepository:
def __init__(self):
# Настройки MongoDB из переменных окружения
mongo_uri = os.getenv("MONGO_URL")
database_name = os.getenv("DB_NAME")
server_collection = os.getenv("SERVER_COLLECTION", "servers")
plan_collection = os.getenv("PLAN_COLLECTION", "plans")
# Подключение к базе данных и коллекциям
self.client = AsyncIOMotorClient(mongo_uri)
self.db = self.client[database_name]
self.collection = self.db[server_collection] # Коллекция серверов
self.plans_collection = self.db[plan_collection] # Коллекция тарифных планов
self.logger = logging.getLogger(__name__)
async def add_subscription_plan(self, plan_data):
"""Добавляет новый тарифный план в коллекцию."""
result = await self.plans_collection.insert_one(plan_data)
self.logger.debug(f"Тарифный план добавлен с ID: {result.inserted_id}")
return result.inserted_id
async def get_subscription_plan(self, plan_id):
"""Получает тарифный план по его имени."""
plan = await self.plans_collection.find_one({"_id": plan_id})
if plan:
self.logger.debug(f"Найден тарифный план: {plan}")
else:
self.logger.error(f"Тарифный план {plan_id} не найден.")
return plan
async def add_server(self, server_data):
"""Добавляет новый VPN сервер в коллекцию."""
result = await self.collection.insert_one(server_data)
self.logger.debug(f"VPN сервер добавлен с ID: {result.inserted_id}")
return result.inserted_id
async def get_server(self, server_name: str):
"""Получает сервер VPN по его ID."""
server = await self.collection.find_one({"server.name": server_name})
if server:
self.logger.debug(f"Найден VPN сервер: {server}")
else:
self.logger.debug(f"VPN сервер с ID {server_name} не найден.")
return server
async def get_server_with_least_clients(self):
"""Возвращает сервер с наименьшим количеством подключенных клиентов."""
pipeline = [
{
"$addFields": {
"current_clients": {"$size": {"$ifNull": ["$clients", []]}}
}
},
{
"$sort": {"current_clients": 1}
},
{
"$limit": 1
}
]
result = await self.collection.aggregate(pipeline).to_list(length=1)
if result:
server = result[0]
self.logger.debug(f"Найден сервер с наименьшим количеством клиентов: {server}")
return server
else:
self.logger.debug("Не найдено серверов.")
return None
async def update_server(self, server_id, update_data):
"""Обновляет данные VPN сервера."""
result = await self.collection.update_one({"_id": server_id}, {"$set": update_data})
if result.matched_count > 0:
self.logger.debug(f"VPN сервер с ID {server_id} обновлен.")
else:
self.logger.debug(f"VPN сервер с ID {server_id} не найден.")
return result.matched_count > 0
async def delete_server(self, server_id):
"""Удаляет VPN сервер по его ID."""
result = await self.collection.delete_one({"_id": server_id})
if result.deleted_count > 0:
self.logger.debug(f"VPN сервер с ID {server_id} удален.")
else:
self.logger.debug(f"VPN сервер с ID {server_id} не найден.")
return result.deleted_count > 0
async def list_servers(self):
"""Возвращает список всех VPN серверов."""
servers = await self.collection.find().to_list(length=1000) # Получить до 1000 серверов (можно настроить)
self.logger.debug(f"Найдено {len(servers)} VPN серверов.")
return servers
async def close_connection(self):
"""Закрывает подключение к базе данных MongoDB."""
self.client.close()
self.logger.debug("Подключение к MongoDB закрыто.")

View File

@@ -1,216 +0,0 @@
from databases.model import User, Subscription, Transaction, Administrators
from sqlalchemy.future import select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import desc
from dateutil.relativedelta import relativedelta
from datetime import datetime
from utils.panel import PanelInteraction
from databases.mongodb import MongoDBRepository
import random
import string
import logging
import asyncio
class DatabaseManager:
def __init__(self, session_generator):
"""
Инициализация с асинхронным генератором сессий (например, get_postgres_session).
"""
self.session_generator = session_generator
self.logger = logging.getLogger(__name__)
self.mongo_repo = MongoDBRepository()
async def create_user(self, telegram_id: int):
"""
Создаёт нового пользователя, если его нет.
"""
async for session in self.session_generator():
try:
username = self.generate_string(6)
result = await session.execute(select(User).where(User.telegram_id == int(telegram_id)))
user = result.scalars().first()
if not user:
new_user = User(telegram_id=int(telegram_id), username=username)
session.add(new_user)
await session.commit()
return new_user
return user
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при создании пользователя {telegram_id}: {e}")
await session.rollback()
return "ERROR"
async def get_user_by_telegram_id(self, telegram_id: int):
"""
Возвращает пользователя по Telegram ID.
"""
async for session in self.session_generator():
try:
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
return result.scalars().first()
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении пользователя {telegram_id}: {e}")
return None
async def add_transaction(self, user_id: int, amount: float):
"""
Добавляет транзакцию для пользователя.
"""
async for session in self.session_generator():
try:
transaction = Transaction(user_id=user_id, amount=amount)
session.add(transaction)
await session.commit()
except SQLAlchemyError as e:
self.logger.error(f"Ошибка добавления транзакции для пользователя {user_id}: {e}")
await session.rollback()
async def update_balance(self, telegram_id: int, amount: float):
"""
Обновляет баланс пользователя и добавляет транзакцию.
"""
async for session in self.session_generator():
try:
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
user = result.scalars().first()
if user:
user.balance += int(amount)
await self.add_transaction(user.id, amount)
await session.commit()
else:
self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.")
return "ERROR"
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при обновлении баланса: {e}")
await session.rollback()
return "ERROR"
async def last_subscription(self, user_id: int):
"""
Возвращает список подписок пользователя.
"""
async for session in self.session_generator():
try:
result = await session.execute(
select(Subscription)
.where(Subscription.user_id == user_id)
.order_by(desc(Subscription.created_at))
)
return result.scalars().all()
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении последней подписки пользователя {user_id}: {e}")
return "ERROR"
async def last_transaction(self, user_id: int):
"""
Возвращает список транзакций пользователя.
"""
async for session in self.session_generator():
try:
result = await session.execute(
select(Transaction)
.where(Transaction.user_id == user_id)
.order_by(desc(Transaction.created_at))
)
transactions = result.scalars().all()
return transactions
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении транзакций пользователя {user_id}: {e}")
return "ERROR"
async def buy_sub(self, telegram_id: str, plan_id: str):
async for session in self.session_generator():
try:
result = await self.create_user(telegram_id)
if not result:
self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.")
return "ERROR"
# Получение тарифного плана из MongoDB
plan = await self.mongo_repo.get_subscription_plan(plan_id)
if not plan:
self.logger.error(f"Тарифный план {plan_id} не найден.")
return "ERROR"
# Проверка достаточности средств для покупки подписки
cost = int(plan["price"])
if result.balance >= cost:
result.balance -= cost
await session.commit()
# Создание подписки для пользователя
expiry_date = datetime.utcnow() + relativedelta(months=plan["duration_months"])
server = await self.mongo_repo.get_server_with_least_clients()
self.logger.info(f"{server}")
new_subscription = Subscription(user_id=result.id, vpn_server_id=str(server['server']["name"]), plan=plan_id, expiry_date=expiry_date)
session.add(new_subscription)
await session.commit()
self.logger.info(f"Подписка успешно оформлена для пользователя {telegram_id} на план {plan_id}.")
return "OK"
else:
self.logger.error(f"Недостаточно средств у пользователя {telegram_id} для покупки плана {plan_id}.")
return "INSUFFICIENT_FUNDS"
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при покупке подписки {plan_id} для пользователя {telegram_id}: {e}")
await session.rollback()
return "ERROR"
async def add_to_server(self, telegram_id: int):
"""
Метод для добавления пользователя на сервер.
"""
async for session in self.session_generator():
try:
# Получаем подписку пользователя по telegram_id
result = await session.execute(select(Subscription).join(User).where(User.telegram_id == int(telegram_id)))
user_sub = result.scalars().first()
if not user_sub:
self.logger.error(f"Не удалось найти подписку для пользователя с Telegram ID {telegram_id}.")
return "ERROR"
# Получаем информацию о пользователе
user_result = await session.execute(select(User).where(User.telegram_id == telegram_id))
user = user_result.scalars().first()
# Получаем сервер с MongoDB
server = await self.mongo_repo.get_server(user_sub.vpn_server_id)
if not server:
self.logger.error(f"Не удалось найти сервер с ID {user_sub.vpn_server_id}.")
return "ERROR"
# Доступ к данным сервера для добавления клиента
server_info = server['server']
url_base = f"https://{server_info['ip']}:{server_info['port']}/{server_info['secretKey']}"
login_data = {
'username': server_info['login'],
'password': server_info['password'],
}
# Инициализируем взаимодействие с панелью управления
panel = PanelInteraction(url_base, login_data, self.logger)
expiry_date_iso = user_sub.expiry_date.isoformat()
response = await panel.add_client(user.id, expiry_date_iso, user.username)
# Логируем результат
if response == "OK":
self.logger.info(f"Клиент {telegram_id} успешно добавлен на сервер.")
return "OK"
else:
self.logger.error(f"Ошибка при добавлении клиента {telegram_id} на сервер: {response}")
return "ERROR"
except Exception as e:
self.logger.error(f"Ошибка при установке на сервер для пользователя {telegram_id}: {e}")
return "ERROR"
@staticmethod
def generate_string(length):
"""
Генерирует случайную строку заданной длины.
"""
characters = string.ascii_lowercase + string.digits
return ''.join(random.choices(characters, k=length))

View File

@@ -1,56 +0,0 @@
networks:
bot_network:
driver: bridge
volumes:
mongo_data:
postgres_data:
logs_data:
services:
mongodb:
networks:
- bot_network
image: mongo:latest
container_name: mongodb
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: USERNAME
MONGO_INITDB_ROOT_PASSWORD: PASSWORD
volumes:
- mongo_data:/data/db
postgres:
networks:
- bot_network
image: postgres:latest
container_name: postgres
ports:
- "5432:5432"
environment:
POSTGRES_USER: USER
POSTGRES_PASSWORD: PASSWORD
POSTGRES_DB: DB_NAME
volumes:
- postgres_data:/var/lib/postgresql/data
bot:
networks:
- bot_network
build:
context: .
container_name: telegram_bot
environment:
TOKEN: "TOKEN"
POSTGRES_URL: "postgresql://USER:PASSWORD@postgres:5432/DB_NAME"
MONGO_URL: "mongodb://USERNAME:PASSWORD@mongodb:27017"
DB_NAME: "MONGO_DB_NAME"
SERVER_COLLECTION: "COLLECTION IN MONGO DB "
PLAN_COLLECTION: "COLLECTION IN MONGO DB"
volumes:
- logs_data:/app/logs
depends_on:
- postgres
- mongodb
command: ["python", "main.py"]

View File

@@ -1,3 +1,4 @@
networks: networks:
bot_network: bot_network:
driver: bridge driver: bridge
@@ -8,6 +9,7 @@ volumes:
logs_data: logs_data:
services: services:
# MongoDB
mongodb: mongodb:
networks: networks:
- bot_network - bot_network
@@ -21,6 +23,7 @@ services:
volumes: volumes:
- mongo_data:/data/db - mongo_data:/data/db
# PostgreSQL
postgres: postgres:
networks: networks:
- bot_network - bot_network
@@ -35,22 +38,39 @@ services:
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
bot: # Бэкенд (FastAPI)
backend:
networks: networks:
- bot_network - bot_network
build: build:
context: . context: ./Flask-Backend-All
container_name: telegram_bot dockerfile: Dockerfile
container_name: backend
environment: environment:
TOKEN: "8104061818:AAGJ6H1PTFmfJm-_mZqGv7EnHxGl4dZndnU"
POSTGRES_URL: "postgresql+asyncpg://AH3J9GSPBYOP:uPS9?y~mcu2@postgres:5432/bot_db" POSTGRES_URL: "postgresql+asyncpg://AH3J9GSPBYOP:uPS9?y~mcu2@postgres:5432/bot_db"
MONGO_URL: "mongodb://root:itOj4CE2miKR@mongodb:27017" MONGO_URL: "mongodb://root:itOj4CE2miKR@mongodb:27017"
DB_NAME: "MongoDBSub&Ser" DB_NAME: "MongoDBSub&Ser"
SERVER_COLLECTION: "servers" SERVER_COLLECTION: "servers"
PLAN_COLLECTION: "plans" PLAN_COLLECTION: "plans"
volumes: BASE_URL: "http://backend:8000"
- logs_data:/app/logs ports:
- "8000:8000"
depends_on: depends_on:
- postgres - postgres
- mongodb - mongodb
command: ["python", "main.py"]
# Telegram Bot
bot:
networks:
- bot_network
build:
context: ./Lark_VPN_Bot
dockerfile: Dockerfile
container_name: telegram_bot
environment:
BASE_URL: "http://backend:8000"
TOKEN: "8104061818:AAGJ6H1PTFmfJm-_mZqGv7EnHxGl4dZndnU"
volumes:
- logs_data:/app/logs
depends_on:
- backend

7
handlers/__init__.py Normal file
View File

@@ -0,0 +1,7 @@
from .start import router as start_router
from .profile import router as profile_router
from .subscriptions import router as subscriptions_router
from .support import router as support_router
# Экспортируем все маршрутизаторы
routers = [start_router,profile_router,subscriptions_router,support_router]

View File

@@ -1,327 +0,0 @@
from aiogram import types, Dispatcher
from aiogram.filters import Command
from databases.postgresql import DatabaseManager
from databases.model import User, Subscription, Transaction, Administrators
from databases.db_config import get_postgres_session
from keyboard.keyboards import subhist_keyboard,confirm_popup_keyboard,tarif_confirm_keyboard, popup_keyboard, main_keyboard,faq_keyboard, account_keyboard, buy_keyboard,balance_keyboard,guide_keyboard,tarif_Lark_keyboard,tarif_Lark_pro_keyboard,tranhist_keyboard
# Инициализируем менеджер базы данных
db_manager = DatabaseManager(get_postgres_session)
async def popup_command(message: types.Message):
"""
Обработчик команды для отправки popup-сообщения.
"""
await message.answer("HAHAHHAHAHAHAHHAHA", reply_markup=popup_keyboard())
async def subhist_command(message: types.Message):
"""
Обработчик команды для отправки истории подписок.
"""
await message.answer("subhist", reply_markup=subhist_keyboard())
async def start_command(message: types.Message):
"""
Обработчик команды /start.
"""
await message.answer(f"""Приветствуем в рядах птенец {message.from_user.username}🐣
\nСкорее пополни баланс, приобрети подписку и получай доступ к TikTok, YouTube, Discord, Instagram на всех устройствах без ограничений ❕❕❕
\nОзнакомься с руководством по пользованию и выбери подходящий тариф 🦅
\nСледи за акциями, спец-предложениями, а также розыгрышами по ссылке ниже
\n👇👇👇
\nhttps://t.me/+0z5xqn3F1m02OTJi
\nС любовью ваши пернатые разработчики 🤍🤍🤍""", reply_markup=main_keyboard())
async def start_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="base".
"""
await callback.message.edit_text(
f"""Приветствуем в рядах птенец {callback.from_user.username}🐣
\nСкорее пополни баланс, приобрети подписку и получай доступ к TikTok, YouTube, Discord, Instagram на всех устройствах без ограничений ❕❕❕
\nОзнакомься с руководством по пользованию и выбери подходящий тариф 🦅
\nСледи за акциями, спец-предложениями, а также розыгрышами по ссылке ниже
\n👇👇👇
\nhttps://t.me/+0z5xqn3F1m02OTJi
\nС любовью ваши пернатые разработчики 🤍🤍🤍""",
reply_markup=main_keyboard()
)
async def profile_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="profile".
"""
user = await db_manager.create_user(telegram_id=callback.from_user.id)
if user == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer()
return
if user:
text = f"""Ваш профиль:\nID: {user.username}\nБаланс: {user.balance}"""
await callback.message.edit_text(
text,
reply_markup=account_keyboard()
)
else:
await callback.message.edit_text("Вы еще не зарегистрированы.")
await callback.answer()
async def balance_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="balance".
"""
user = await db_manager.create_user(telegram_id=callback.from_user.id)
if user == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer()
return
if user:
await callback.message.edit_text(
f"Ваш баланс: {user.balance} ₽. Выберите сумму для пополнения 🐥",
reply_markup=balance_keyboard()
)
else:
await callback.message.edit_text("Вы еще не зарегистрированы.")
await callback.answer()
async def popup_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="popup".
"""
user = await db_manager.create_user(telegram_id=callback.from_user.id)
if user == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer()
return
if user:
await callback.message.edit_text(
f"Работает в режиме теста!!!",
reply_markup=popup_keyboard()
)
else:
await callback.message.edit_text("Вы еще не зарегистрированы.")
await callback.answer()
async def tranhist_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="tranhist".
"""
user = await db_manager.create_user(callback.from_user.id)
trans = await db_manager.last_transaction(user.id)
if trans == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer()
return
if not trans:
await callback.message.edit_text(
"У вас нет транзакций. Пожалуйста, пополните баланс.",
reply_markup=tranhist_keyboard()
)
await callback.answer()
return
result = "Ваши транзакции:\n"
for count, tran in enumerate(trans, start=1):
result += f"{count}. Сумма: {tran.amount}, Дата: {tran.created_at}\n"
await callback.message.edit_text(
result,
reply_markup=tranhist_keyboard()
)
await callback.answer()
async def subhist_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="subhist".
"""
user = await db_manager.create_user(callback.from_user.id)
subs = await db_manager.last_subscription(user.id)
if subs == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer()
return
if subs is None:
await callback.message.edit_text(
f"Ты хули тут забыл, ты ж не покупаешь нихуя",
reply_markup=account_keyboard()
)
await callback.answer()
return
result = ""
count = 0
for sub in subs:
if count > 0:
result += f"Последняя подписка истекает: {sub.expiry_date}\n"
count += 1
result += f"{count}. Истекла {sub.expiry_date}"
count += 1
if subs:
await callback.message.edit_text(
result,
reply_markup=account_keyboard()
)
else:
await callback.message.edit_text("Вы еще не зарегистрированы.")
await callback.answer()
async def buy_subscription_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="buy_subscription".
"""
await callback.message.edit_text(
f"Ознакомься с условиями в вкладке \"О тарифах\" и выбери подходящий 🦅",
reply_markup=buy_keyboard()
)
async def guide_callback_handler(callback:types.CallbackQuery):
"""
Обработчик callback_query с data="guide".
"""
await callback.message.edit_text(
f"Руководство по использованию продкута что мы высрали;)",
reply_markup=guide_keyboard()
)
async def subs_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="subs".
"""
await callback.message.edit_text(
f"Подписки птенчик",
reply_markup=tarif_Lark_keyboard()
)
async def subs_pro_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="subs_pro".
"""
await callback.message.edit_text(
f"Подписки птенчик ПРО",
reply_markup=tarif_Lark_pro_keyboard()
)
# async def about_tarifs_callback_handler(callback: types.CallbackQuery):
# """
# Обработчик callback_query с data="about_tarifs".
# """
# await callback.message.edit_text(
# f"Бла бла бла, хуйня на хуйне",
# reply_markup=about_tarifs_keyboard()
# )
async def faq_callback_handler(callback:types.CallbackQuery):
"""
Обработчик callback_query с data="faq".
"""
await callback.message.edit_text(
f"FAQ YOU",
reply_markup=faq_keyboard()
)
async def lark_tariff_callback_handler(callback: types.CallbackQuery):
"""
Обработчик для выбора тарифа Lark.
"""
data = callback.data.split(":")
tariff_name = data[0]
tariff_class = data[1]
tariff_time = int(data[2])
# Определение окончания для месяцев
if tariff_time == 1:
months = f"{tariff_time} месяц"
elif 2 <= tariff_time <= 4:
months = f"{tariff_time} месяца"
else:
months = f"{tariff_time} месяцев"
text = f"Тариф {tariff_name} на {months}. Продолжите покупку..."
# Рендеринг клавиатуры
keyboard = tarif_confirm_keyboard(tariff_name, tariff_time, tariff_class)
await callback.message.edit_text(text=text, reply_markup=keyboard)
async def popup_confirm_callback_handler(callback: types.CallbackQuery):
"""
Обработчик подтверждения пополнения баланса.
"""
data = callback.data.split(":")
popup_info = data[1]
result = await db_manager.update_balance(callback.from_user.id,popup_info)
if result == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer()
return
text = f"Вы пополнили свой баланс на {popup_info}. P.S. Мы завтра закрываемся"
await callback.message.edit_text(text=text, reply_markup=confirm_popup_keyboard())
async def confirm_callback_handler(callback: types.CallbackQuery):
"""
Обработчик подтверждения покупки тарифа.
"""
tariff_info = callback.data.split(":")[1].split("_")
tariff_name = tariff_info[0]
tariff_class = tariff_info[1]
tariff_amount = int(tariff_info[2])
sub = await db_manager.buy_sub(callback.from_user.id, f"{tariff_name}_{tariff_class}_{tariff_amount}")
if sub == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer()
return
elif sub == "INSUFFICIENT_FUNDS":
await callback.message.answer(
"Произошла ошибка, не достаточно средств на балансе."
)
await callback.answer()
return
add_to_server = await db_manager.add_to_server(callback.from_user.id)
if add_to_server == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer()
return
# Текст подтверждения на основе тарифа
months_text = f"{tariff_amount} месяцев" if tariff_amount > 1 else f"{tariff_amount} месяц"
text = f"Вы успешно оформили тариф {tariff_name} на {months_text}. Спасибо за покупку!"
await callback.message.edit_text(text=text)
def register_handlers(dp: Dispatcher):
"""
Регистрация хэндлеров в диспетчере.
"""
dp.callback_query.register(popup_callback_handler, lambda c: c.data == "popup")
dp.callback_query.register(start_callback_handler, lambda c: c.data == "base")
dp.callback_query.register(subs_callback_handler, lambda c: c.data == "subs")
dp.callback_query.register(subs_pro_callback_handler, lambda c: c.data == "subs_pro")
dp.callback_query.register(faq_callback_handler, lambda c: c.data == "faq")
dp.callback_query.register(profile_callback_handler, lambda c: c.data == "profile")
dp.callback_query.register(tranhist_callback_handler, lambda c: c.data == "tranhist")
dp.callback_query.register(buy_subscription_callback_handler, lambda c: c.data == "buy_subscription")
dp.message.register(start_command, Command("start"))
dp.callback_query.register(balance_callback_handler, lambda c: c.data == "balance")
dp.callback_query.register(guide_callback_handler, lambda c: c.data == "guide")
# dp.callback_query.register(about_tarifs_callback_handler, lambda c: c.data == "about_tarifs")
dp.callback_query.register(lark_tariff_callback_handler, lambda c: c.data.startswith("Lark:"))
dp.callback_query.register(confirm_callback_handler, lambda c: c.data.startswith("confirm:"))
dp.callback_query.register(popup_confirm_callback_handler, lambda c: c.data.startswith("popup:"))

170
handlers/profile.py Normal file
View File

@@ -0,0 +1,170 @@
from aiogram import Router, types
from aiogram.types import CallbackQuery
import logging
from datetime import datetime
from aiogram.enums.parse_mode import ParseMode
import locale
from instences.config import BASE_URL_FASTAPI
import aiohttp
from keyboard.keyboards import account_keyboard, popup_keyboard, tranhist_keyboard, confirm_popup_keyboard, guide_keyboard, balance_keyboard
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
router = Router()
logger = logging.getLogger(__name__)
async def call_api(method, endpoint, data=None):
"""
Выполняет HTTP-запрос к FastAPI.
"""
url = f"{BASE_URL_FASTAPI}{endpoint}"
logger.info(f"Инициализация запроса: {method} {url} с данными {data}")
try:
async with aiohttp.ClientSession() as session:
async with session.request(method, url, json=data) as response:
logger.info(f"Получен ответ от {url}: статус {response.status}")
if response.status in {200, 201}:
result = await response.json()
logger.debug(f"Ответ JSON: {result}")
return result
if response.status == 404:
logger.debug(f"Код {response.status}, возвращаю ничего")
return None
logger.error(f"Ошибка в запросе: статус {response.status}, причина {response.reason}")
return "ERROR"
except Exception as e:
logger.exception(f"Исключение при выполнении запроса к {url}: {e}")
return "ERROR"
@router.callback_query(lambda callback: callback.data == "profile")
async def profile_callback_handler(callback: CallbackQuery):
"""
Обработчик callback_query для профиля.
"""
try:
user_data = await call_api("GET", f"/user/{callback.from_user.id}")
if not user_data:
await callback.message.answer("Произошла ошибка, попробуйте позже или свяжитесь с администрацией.")
await callback.answer()
return
sub_data = await call_api("GET", f"/subscription/{user_data['id']}/last")
if sub_data == "ERROR" or not isinstance(sub_data, dict):
sub_data = None
balance_text = f"Баланс: {user_data['balance']}"
if not sub_data:
text = f"Профиль {callback.from_user.username}\n{balance_text}\nПополните баланс и приобретите подписку, чтобы получить активный статус (🐣,🦅)"
else:
expiry_date = sub_data.get("expiry_date")
formatted_date = datetime.fromisoformat(expiry_date).strftime("%d %B %Y г.") if expiry_date else None
is_expired = datetime.fromisoformat(expiry_date) < datetime.now() if expiry_date else True
status_icon = "✖️" if is_expired else "☑️"
profile_status = "🦅" if "Pro" in sub_data.get("plan", "") else "🐣"
sub_text = (
f"Ваша подписка действует до {formatted_date} {status_icon}"
if not is_expired else f"Статус подписки: {status_icon}"
)
text = f"Профиль {callback.from_user.username} {profile_status}:\n{sub_text}\n{balance_text}"
await callback.message.edit_text(text, reply_markup=account_keyboard())
except Exception as e:
logger.exception(f"Ошибка в обработчике профиля: {e}")
await callback.message.answer("Произошла ошибка. Попробуйте позже.")
finally:
await callback.answer()
@router.callback_query(lambda callback: callback.data == "balance")
async def balance_callback_handler(callback: CallbackQuery):
"""
Обработчик callback_query для баланса.
"""
user_data = await call_api("GET", f"/user/{callback.from_user.id}")
if not user_data:
await callback.message.answer("Произошла ошибка, попробуйте позже или свяжитесь с администрацией.")
await callback.answer()
return
await callback.message.edit_text(
f"Ваш баланс: {user_data['balance']} ₽. Выберите сумму для пополнения 🐥",
reply_markup=balance_keyboard()
)
await callback.answer()
@router.callback_query(lambda callback: callback.data == "popup")
async def popup_callback_handler(callback: CallbackQuery):
"""
Обработчик callback_query для пополнения.
"""
user = await call_api("GET", f"/user/{callback.from_user.id}")
if not user:
await callback.message.answer("Произошла ошибка, попробуйте позже или свяжитесь с администрацией.")
await callback.answer()
return
await callback.message.edit_text("Работает в режиме теста!!!", reply_markup=popup_keyboard())
await callback.answer()
@router.callback_query(lambda callback: callback.data == "tranhist")
async def tranhist_callback_handler(callback: CallbackQuery):
"""
Обработчик callback_query для истории транзакций.
"""
user_data = await call_api("GET", f"/user/{callback.from_user.id}")
if not user_data:
await callback.message.edit_text("Вы еще не зарегистрированы.")
await callback.answer()
return
try:
transactions = await call_api("GET", f"/user/{user_data['id']}/transactions")
if not transactions:
await callback.message.edit_text("У вас нет транзакций.", reply_markup=tranhist_keyboard())
await callback.answer()
return
result = "Ваши транзакции:```\n"
for count, tran in enumerate(transactions, start=1):
dt = datetime.fromisoformat(tran['created_at']).strftime("%d.%m.%Y %H:%M:%S")
result += f"{count}. Сумма: {tran['amount']}, Дата: {dt}\n"
if len(result) > 4000:
result += "...\nСлишком много транзакций для отображения."
break
result += "```"
await callback.message.edit_text(result,parse_mode=ParseMode.MARKDOWN_V2, reply_markup=tranhist_keyboard())
except Exception as e:
logger.error(f"Ошибка обработки транзакций: {e}")
await callback.message.edit_text("Произошла ошибка. Попробуйте позже.")
finally:
await callback.answer()
@router.callback_query(lambda callback: callback.data.startswith("popup:"))
async def popup_confirm_callback_handler(callback: CallbackQuery):
"""
Обработчик подтверждения пополнения баланса.
"""
data = callback.data.split(":")
popup_info = data[1]
result = await call_api("POST", f"/user/{callback.from_user.id}/balance/{float(popup_info)}")
if result == "ERROR":
await callback.message.answer("Произошла ошибка, попробуйте позже или свяжитесь с администрацией.")
await callback.answer()
return
text = f"Вы пополнили свой баланс на {popup_info} ₽. Спасибо!"
await callback.message.edit_text(text=text, reply_markup=confirm_popup_keyboard())
await callback.answer()
@router.callback_query(lambda callback: callback.data == "guide")
async def guide_callback_handler(callback: CallbackQuery):
"""
Обработчик callback_query для руководства.
"""
await callback.message.edit_text(
"Руководство по использованию продукта:",
reply_markup=guide_keyboard()
)
await callback.answer()

95
handlers/start.py Normal file
View File

@@ -0,0 +1,95 @@
from aiogram import Router, types
from aiogram.filters import Command
from aiogram.types import Message, CallbackQuery
import logging
from instences.config import BASE_URL_FASTAPI
import aiohttp
from keyboard.keyboards import main_keyboard
router = Router()
logger = logging.getLogger(__name__)
async def call_api(method, endpoint, data=None):
"""
Выполняет HTTP-запрос к FastAPI.
:param method: HTTP метод (GET, POST, и т.д.)
:param endpoint: конечная точка API
:param data: тело запроса (если необходимо)
:return: JSON-ответ или "ERROR" при неуспехе
"""
url = f"{BASE_URL_FASTAPI}{endpoint}"
logger.info(f"Инициализация запроса: {method} {url} с данными {data}")
try:
async with aiohttp.ClientSession() as session:
async with session.request(method, url, json=data) as response:
logger.info(f"Получен ответ от {url}: статус {response.status}")
if response.status in {200, 201}:
result = await response.json()
logger.debug(f"Ответ JSON: {result}")
return result
if response.status == 404:
logger.debug(f"Код {response.status}, возвращаю ничего")
return None
logger.error(f"Ошибка в запросе: статус {response.status}, причина {response.reason}")
return "ERROR"
except Exception as e:
logger.exception(f"Исключение при выполнении запроса к {url}: {e}")
return "ERROR"
@router.message(Command("start"))
async def start_command(message: Message):
"""
Обработчик команды /start.
"""
logger.info(f"Получена команда /start от пользователя: {message.from_user.id} ({message.from_user.username})")
try:
user_data = await call_api("GET", f"/user/{message.from_user.id}")
if not user_data:
logger.debug("Пользователь не найден в базе, создаем новую запись.")
await call_api("POST", "/user/create", {"telegram_id": message.from_user.id})
logger.debug("Отправка приветственного сообщения пользователю.")
await message.answer(
f"""Приветствуем в рядах птенец {message.from_user.username}🐣
\nСкорее пополни баланс, приобрети подписку и получай доступ к TikTok, YouTube, Discord, Instagram на всех устройствах без ограничений ❕❕❕
\nОзнакомься с руководством по пользованию и выбери подходящий тариф 🦅
\nСледи за акциями, спец-предложениями, а также розыгрышами по ссылке ниже
\n👇👇👇
\nhttps://t.me/+0z5xqn3F1m02OTJi
\nС любовью ваши пернатые разработчики 🤍🤍🤍""",
reply_markup=main_keyboard()
)
logger.info("Приветственное сообщение отправлено.")
except Exception as e:
logger.exception(f"Ошибка при обработке команды /start для пользователя {message.from_user.id}: {e}")
await message.answer("Произошла ошибка. Попробуйте позже.")
@router.callback_query(lambda callback: callback.data == "base")
async def start_callback_handler(callback: CallbackQuery):
"""
Обработчик callback_query с data="base".
"""
try:
user_data = await call_api("GET", f"/user/{callback.from_user.id}")
if not user_data:
await call_api("POST", "/user/create", {"telegram_id": callback.from_user.id})
await callback.message.edit_text(
f"""Приветствуем в рядах птенец {callback.from_user.username}🐣
\nСкорее пополни баланс, приобрети подписку и получай доступ к TikTok, YouTube, Discord, Instagram на всех устройствах без ограничений ❕❕❕
\nОзнакомься с руководством по пользованию и выбери подходящий тариф 🦅
\nСледи за акциями, спец-предложениями, а также розыгрышами по ссылке ниже
\n👇👇👇
\nhttps://t.me/+0z5xqn3F1m02OTJi
\nС любовью ваши пернатые разработчики 🤍🤍🤍""",
reply_markup=main_keyboard()
)
except Exception as e:
logger.exception(f"Ошибка при обработке callback с data='base': {e}")
await callback.message.answer("Произошла ошибка. Попробуйте позже.")
finally:
await callback.answer()

168
handlers/subscriptions.py Normal file
View File

@@ -0,0 +1,168 @@
from aiogram import Router, types
import logging
from instences.config import BASE_URL_FASTAPI
import aiohttp
from aiogram.enums.parse_mode import ParseMode
from aiogram.filters import Command
from keyboard.keyboards import tarif_Lark_pro_keyboard, tarif_Lark_keyboard, tarif_confirm_keyboard,buy_keyboard
router = Router()
logger = logging.getLogger(__name__)
async def call_api(method, endpoint, data=None):
"""
Выполняет HTTP-запрос к FastAPI.
"""
url = f"{BASE_URL_FASTAPI}{endpoint}"
logger.info(f"Инициализация запроса: {method} {url} с данными {data}")
try:
async with aiohttp.ClientSession() as session:
async with session.request(method, url, json=data) as response:
logger.info(f"Получен ответ от {url}: статус {response.status}")
if response.status in {200, 201}:
result = await response.json()
logger.debug(f"Ответ JSON: {result}")
return result
if response.status in {404,400}:
result = await response.json()
logger.debug(f"Код {response.status}, возвращаю {result}")
return result
logger.error(f"Ошибка в запросе: статус {response.status}, причина {response.reason}")
return "ERROR"
except Exception as e:
logger.exception(f"Исключение при выполнении запроса к {url}: {e}")
return "ERROR"
def escape_markdown_v2(text: str) -> str:
"""
Экранирует специальные символы для Markdown_V2.
"""
special_chars = r"_*[]()~`>#+-=|{}.!"
for char in special_chars:
text = text.replace(char, f"\\{char}")
return text
@router.message(Command("subscriptions"))
async def supp(message: types.Message):
"""
Меню системы подписок
"""
text = ""
uri = None # Инициализация переменной
try:
# Вызов API для получения URI
result = await call_api("GET", f"/uri?telegram_id={message.from_user.id}")
uri = result.get('detail', "Error") # Получаем URI из ответа или "Error", если ключ отсутствует
# Проверка результата
if uri == "Error":
text = escape_markdown_v2("Произошла ошибка при получении URI")
elif uri == "SUB_ERROR":
text = escape_markdown_v2("Вы ещё не приобрели подписки!!")
elif "vless" in uri:
escaped_uri = escape_markdown_v2(uri) # Экранирование URI
text = f"Ваша подписка: ```{escaped_uri}```"
else:
text = escape_markdown_v2("Произошла ошибка при обработке URI")
except Exception as e:
# Логирование ошибок
logger.error(f"Ошибка при вызове API для подписки: {e}")
text = escape_markdown_v2("Произошла неожиданная ошибка при получении подписки.")
# Ответ пользователю
await message.answer(
text,
parse_mode=ParseMode.MARKDOWN_V2
)
@router.callback_query(lambda callback: callback.data == "buy_subscription")
async def buy_subscription_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="buy_subscription".
"""
await callback.message.edit_text(
f"Ознакомься с условиями в вкладке \"О тарифах\" и выбери подходящий 🦅",
reply_markup=buy_keyboard()
)
@router.callback_query(lambda callback: callback.data == "subs")
async def subs_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="subs".
"""
await callback.message.edit_text(
"Подписки птенчик",
reply_markup=tarif_Lark_keyboard()
)
@router.callback_query(lambda callback: callback.data == "subs_pro")
async def subs_pro_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="subs_pro".
"""
await callback.message.edit_text(
"Подписки птенчик ПРО",
reply_markup=tarif_Lark_pro_keyboard()
)
@router.callback_query(lambda callback: callback.data.startswith("Lark:"))
async def lark_tariff_callback_handler(callback: types.CallbackQuery):
"""
Обработчик для выбора тарифа Lark.
"""
data = callback.data.split(":")
tariff_name = data[0]
tariff_class = data[1]
tariff_time = int(data[2])
# Определение окончания для месяцев
if tariff_time == 1:
months = f"{tariff_time} месяц"
elif 2 <= tariff_time <= 4:
months = f"{tariff_time} месяца"
else:
months = f"{tariff_time} месяцев"
text = f"Тариф {tariff_name} на {months}. Продолжите покупку..."
# Рендеринг клавиатуры
keyboard = tarif_confirm_keyboard(tariff_name, tariff_time, tariff_class)
await callback.message.edit_text(text=text, reply_markup=keyboard)
@router.callback_query(lambda callback: callback.data.startswith("confirm:"))
async def confirm_callback_handler(callback: types.CallbackQuery):
"""
Обработчик подтверждения подписки.
"""
try:
data = callback.data.split(":")[1]
tariff_info = data.split("_")
plan_id = f"{tariff_info[0]}_{tariff_info[1]}_{tariff_info[2]}"
result = await call_api("POST", "/subscription/buy", {"telegram_id": callback.from_user.id, "plan_id": plan_id})
detail = result.get("detail", {})
if detail == "ERROR":
await callback.message.edit_text("Произошла ошибка при оформлении подписки.")
elif detail == "INSUFFICIENT_FUNDS":
await callback.message.edit_text("Денег на вашем балансе не достаточно.")
elif detail == "TARIFF_NOT_FOUND":
await callback.message.edit_text("Ваш тариф не найден.")
elif detail == "ACTIVE_SUBSCRIPTION_EXISTS":
await callback.message.edit_text("Вы уже имеете активную подписку.")
else:
uri = result.get("message", {})
escaped_text = escape_markdown_v2(f"Подписка успешно оформлена!")
answer_text = f"Ваш конфиг для подключения: ```{uri}```"
await callback.message.edit_text(escaped_text)
await callback.message.answer(answer_text, parse_mode=ParseMode.MARKDOWN_V2)
except Exception as e:
logger.exception(f"Ошибка при обработке подтверждения подписки: {e}")
await callback.message.edit_text("Произошла ошибка при оформлении подписки.")
finally:
await callback.answer()

160
handlers/support.py Normal file
View File

@@ -0,0 +1,160 @@
from aiogram import Router, types
from aiogram.filters import Command
import logging
from datetime import datetime
from instences.config import BASE_URL_FASTAPI
import aiohttp
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from keyboard.keyboards import faq_keyboard, sup_keyboard, ticket_list_keyboard, ticket_keyboard
logger = logging.getLogger(__name__)
router = Router()
class TicketState(StatesGroup):
subject = State()
message = State()
async def call_api(method, endpoint, data=None):
"""
Выполняет HTTP-запрос к FastAPI.
"""
url = f"{BASE_URL_FASTAPI}{endpoint}"
logger.info(f"Инициализация запроса: {method} {url} с данными {data}")
try:
async with aiohttp.ClientSession() as session:
async with session.request(method, url, json=data) as response:
logger.info(f"Получен ответ от {url}: статус {response.status}")
if response.status in {200, 201}:
result = await response.json()
logger.debug(f"Ответ JSON: {result}")
return result
if response.status == 404:
logger.debug(f"Код {response.status}, возвращаю ничего")
return None
logger.error(f"Ошибка в запросе: статус {response.status}, причина {response.reason}")
return "ERROR"
except Exception as e:
logger.exception(f"Исключение при выполнении запроса к {url}: {e}")
return "ERROR"
@router.callback_query(lambda callback: callback.data == "faq")
async def faq_callback_handler(callback: types.CallbackQuery):
"""
Обработчик callback_query с data="faq".
"""
await callback.message.edit_text(
"FAQ YOU",
reply_markup=faq_keyboard()
)
@router.message(Command("support"))
async def supp(message: types.Message):
"""
Меню сапп системы
"""
await message.answer(
"Добро пожаловать в саппорт систему!",
reply_markup=sup_keyboard()
)
@router.callback_query(lambda callback: callback.data == "main_sup")
async def supp_callback(callback: types.CallbackQuery):
"""
Меню сапп системы (callback версия)
"""
await callback.message.answer(
"Добро пожаловать в саппорт систему!",
reply_markup=sup_keyboard()
)
@router.callback_query(lambda callback: callback.data == "my_tickets")
async def list_tickets_callback(callback: types.CallbackQuery):
user_id = callback.from_user.id
user_data = await call_api("GET", f"/user/{user_id}")
if not user_data:
user_data = await call_api("POST", f"/user/create", {"telegram_id": f"{user_id}"})
tickets = await call_api("GET", f"/support/tickets?user_id={user_data['id']}")
if tickets == "ERROR" or not tickets:
await callback.message.edit_text("У вас нет тикетов.", reply_markup=sup_keyboard())
return
await callback.message.edit_text(
"Ваши тикеты:",
reply_markup=ticket_list_keyboard(tickets)
)
@router.callback_query(lambda callback: callback.data == "make_ticket")
async def start_ticket_creation(callback: types.CallbackQuery, state: FSMContext):
"""
Начинает процесс создания тикета.
"""
await callback.message.answer(
"Введите тему тикета (или нажмите 'Отмена', чтобы выйти):",
reply_markup=ticket_keyboard()
)
await state.set_state(TicketState.subject)
@router.message()
async def handle_ticket_input(message: types.Message, state: FSMContext):
"""
Обрабатывает ввод данных для тикета.
"""
current_state = await state.get_state()
if current_state == TicketState.subject:
await state.update_data(subject=message.text)
await message.answer("Введите описание проблемы:", reply_markup=ticket_keyboard())
await state.set_state(TicketState.message)
elif current_state == TicketState.message:
user_data = await state.get_data()
subject = user_data.get("subject")
message_text = message.text
await create_ticket(message, subject, message_text, state)
await state.clear()
async def create_ticket(message: types.Message, subject: str, message_text: str, state: FSMContext):
"""
Отправляет запрос на создание тикета через FastAPI.
"""
user_id = message.from_user.id
try:
logger.info(f"Создание тикета для пользователя {user_id}: Тема - {subject}, Сообщение - {message_text}")
user_data = await call_api("GET", f"/user/{user_id}")
if not user_data:
await message.answer("Вы еще не зарегистрированы.")
return
ticket_data = await call_api(
"POST",
f"/support/tickets?user_id={user_data['id']}",
data={"subject": subject, "message": message_text}
)
if ticket_data != "ERROR":
await message.answer(
f"✅ Тикет успешно создан!\n"
f"📌 Тема: {ticket_data['subject']}\n"
f"📊 Статус: {ticket_data['status']}\n"
f"📅 Дата создания: {ticket_data['created_at']}"
)
else:
await message.answer("❌ Ошибка создания тикета. Попробуйте позже.")
except Exception as e:
logger.exception(f"Ошибка при создании тикета для пользователя {user_id}: {e}")
await message.answer("❌ Произошла ошибка при создании тикета.")
@router.callback_query(lambda callback: callback.data == "cancel_ticket")
async def cancel_ticket_creation(callback: types.CallbackQuery, state: FSMContext):
"""
Отмена создания тикета.
"""
await state.clear()
await callback.message.answer("Создание тикета отменено.", reply_markup=types.ReplyKeyboardRemove())

9
instences/config.py Normal file
View File

@@ -0,0 +1,9 @@
import os
import logging
logger = logging.getLogger(__name__)
BASE_URL_FASTAPI = os.getenv("BASE_URL")

View File

@@ -1,5 +1,5 @@
from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
from aiogram.types import InlineKeyboardButton from aiogram.types import InlineKeyboardButton, KeyboardButton
def main_keyboard(): def main_keyboard():
@@ -8,7 +8,7 @@ def main_keyboard():
""" """
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="Профиль", callback_data="profile")) builder.row(InlineKeyboardButton(text="Профиль", callback_data="profile"))
builder.row(InlineKeyboardButton(text="FAQ", callback_data="faq")) builder.row(InlineKeyboardButton(text="FAQ", callback_data="faq"))
builder.row(InlineKeyboardButton(text="О нас", url="https://www.youtube.com/watch?v=Zirn-CKck-c")) builder.row(InlineKeyboardButton(text="О нас", url="https://www.youtube.com/watch?v=Zirn-CKck-c"))
return builder.as_markup() return builder.as_markup()
@@ -17,12 +17,31 @@ def account_keyboard():
Аккаунт Аккаунт
""" """
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="Баланс", callback_data="balance")) builder.row(InlineKeyboardButton(text="Пополнение баланса", callback_data="popup"))
builder.row(InlineKeyboardButton(text="Приобрести подписку", callback_data="buy_subscription")) builder.row(InlineKeyboardButton(text="Приобрести подписку", callback_data="buy_subscription"))
builder.row(InlineKeyboardButton(text="Руководство по подключению", callback_data="guide")) builder.row(InlineKeyboardButton(text="Руководство по подключению", callback_data="guide"))
builder.row(InlineKeyboardButton(text="📑История транзакций", callback_data="tranhist"))
builder.row(InlineKeyboardButton(text="Назад", callback_data="base")) builder.row(InlineKeyboardButton(text="Назад", callback_data="base"))
return builder.as_markup() return builder.as_markup()
def ticket_list_keyboard(tickets):
builder = InlineKeyboardBuilder()
for ticket in tickets:
builder.row(InlineKeyboardButton(text=f"Тикет: {ticket['subject']}",callback_data=f"ticket_{ticket['id']}"))
builder.row(InlineKeyboardButton(text="Назад", callback_data="main_sup"))
return builder.as_markup()
def sup_keyboard():
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="Создать запрос", callback_data="make_ticket"))
builder.row(InlineKeyboardButton(text="Мои запросы", callback_data="my_tickets"))
return builder.as_markup()
def ticket_keyboard():
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="Отмена", callback_data="cancel"))
return builder.as_markup()
def buy_keyboard(): def buy_keyboard():
""" """
@@ -51,7 +70,7 @@ def popup_keyboard():
builder.row(InlineKeyboardButton(text="200₽", callback_data="popup:200"),InlineKeyboardButton(text="500₽", callback_data="popup:500")) builder.row(InlineKeyboardButton(text="200₽", callback_data="popup:200"),InlineKeyboardButton(text="500₽", callback_data="popup:500"))
builder.row(InlineKeyboardButton(text="1000₽", callback_data="popup:1000"),InlineKeyboardButton(text="2000₽", callback_data="popup:2000")) builder.row(InlineKeyboardButton(text="1000₽", callback_data="popup:1000"),InlineKeyboardButton(text="2000₽", callback_data="popup:2000"))
builder.row(InlineKeyboardButton(text="3000₽", callback_data="popup:3000"),InlineKeyboardButton(text="5000₽", callback_data="popup:5000")) builder.row(InlineKeyboardButton(text="3000₽", callback_data="popup:3000"),InlineKeyboardButton(text="5000₽", callback_data="popup:5000"))
builder.row(InlineKeyboardButton(text="Назад", callback_data="balance")) builder.row(InlineKeyboardButton(text="Назад", callback_data="profile"))
return builder.as_markup() return builder.as_markup()
def balance_keyboard(): def balance_keyboard():
@@ -119,7 +138,7 @@ def tranhist_keyboard():
История транзакций История транзакций
""" """
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="Назад",callback_data="balance")) builder.row(InlineKeyboardButton(text="Назад",callback_data="profile"))
return builder.as_markup() return builder.as_markup()
def tarif_confirm_keyboard(name,amount,classif): def tarif_confirm_keyboard(name,amount,classif):
@@ -136,5 +155,5 @@ def confirm_popup_keyboard():
Подтверждение пополнения Подтверждение пополнения
""" """
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="Теперь иди нахуй", callback_data="balance")) builder.row(InlineKeyboardButton(text="Теперь иди нахуй", callback_data="profile"))
return builder.as_markup() return builder.as_markup()

18
main.py
View File

@@ -1,8 +1,8 @@
import os import os
import asyncio import asyncio
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from databases.db_config import init_postgresql, init_mongodb, close_connections
from aiogram.types import BotCommand from aiogram.types import BotCommand
from handlers import routers
from Middleware.anti_spam_middleware import AntiSpamMiddleware from Middleware.anti_spam_middleware import AntiSpamMiddleware
import logging import logging
@@ -23,21 +23,18 @@ dp.message.middleware(AntiSpamMiddleware(rate_limit=1))
async def set_commands(): async def set_commands():
"""Устанавливает команды для бота.""" """Устанавливает команды для бота."""
commands = [ commands = [
BotCommand(command="/start", description="Запустить бота"), BotCommand(command="/start", description="🥚Главное меню"),
BotCommand(command="/subscriptions", description="🦴Мои подписки"),
BotCommand(command="/support", description="❕Поддержка❕"),
] ]
await bot.set_my_commands(commands) await bot.set_my_commands(commands)
async def on_startup(): async def on_startup():
"""Действия при запуске бота.""" """Действия при запуске бота."""
# Инициализация баз данных
await init_mongodb()
await init_postgresql()
# Установка команд бота
await set_commands() await set_commands()
# Настройка логирования
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.info("Бот запущен!") logger.info("Бот запущен!")
@@ -45,18 +42,15 @@ async def on_startup():
async def on_shutdown(): async def on_shutdown():
"""Действия при остановке бота.""" """Действия при остановке бота."""
# Закрытие подключений к базам данных
await close_connections()
# Закрытие сессии бота
await bot.session.close() await bot.session.close()
print("Бот остановлен.") print("Бот остановлен.")
async def main(): async def main():
"""Основной цикл работы бота.""" """Основной цикл работы бота."""
from handlers.handlers import register_handlers for router in routers:
register_handlers(dp) # Регистрация хендлеров dp.include_router(router) # Регистрация хендлеров
await on_startup() await on_startup()

View File

@@ -1,263 +0,0 @@
from db import User, Subscription, Transaction, VPNServer
import string
import secrets
import json
from sqlalchemy import desc
from dateutil.relativedelta import relativedelta
from datetime import datetime
from db import get_db_session
from panel import PanelInteraction
from utils.LogCon import setup_logger, load_config
config = load_config()
def generate_random_string(length=8):
characters = string.ascii_letters + string.digits
return ''.join(secrets.choice(characters) for _ in range(length))
class UserService:
def __init__(self, logger):
self.logger = logger
def add_user(self, telegram_id: int):
session = next(get_db_session())
try:
new_user = User(telegram_id=telegram_id, username=generate_random_string())
session.add(new_user)
session.commit()
except Exception as e:
session.rollback()
self.logger.error(f"Ошибка при добавлении пользователя: {e}")
finally:
session.close()
def get_user_by_telegram_id(self, telegram_id: int):
session = next(get_db_session())
try:
return session.query(User).filter(User.telegram_id == telegram_id).first()
except Exception as e:
self.logger.error(f"Ошибка при получении пользователя: {e}")
finally:
session.close()
def add_transaction(self, user_id: int, amount: float):
session = next(get_db_session())
try:
transaction = Transaction(user_id=user_id, amount=amount)
session.add(transaction)
session.commit()
except Exception as e:
self.logger.error(f"Ошибка добавления транзакции: {e}")
finally:
session.close()
def update_balance(self, telegram_id: int, amount: float):
session = next(get_db_session())
try:
user = session.query(User).filter(User.telegram_id == telegram_id).first()
if user:
user.balance = amount
self.add_transaction(user.id, amount)
session.commit()
else:
self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.")
except Exception as e:
session.rollback()
self.logger.error(f"Ошибка при обновлении баланса: {e}")
finally:
session.close()
def last_subscription(self, user):
session = next(get_db_session())
try:
return (
session.query(Subscription)
.filter(Subscription.user_id == user.id)
.order_by(desc(Subscription.created_at))
.first()
)
except Exception as e:
self.logger.error(f"Ошибка при получении последней подписки: {e}")
finally:
session.close()
def tariff_setting(self, user, plan: str, expiry_duration: int):
session = next(get_db_session())
try:
server = (
session.query(VPNServer)
.filter(VPNServer.current_users < VPNServer.max_users)
.order_by(VPNServer.current_users.asc())
.first()
)
if not server:
self.logger.error("Error: 120")
return "120"
# Рассчитываем дату окончания подписки
expiry_ = datetime.utcnow() + relativedelta(months=expiry_duration)
self.logger.info(f"Create subscribe to {user.id} on server {server.id} with plan {plan} until {expiry_}")
new_subscription = Subscription(user_id=user.id, vpn_server_id=server.id, plan=plan, expiry_date=expiry_)
session.add(new_subscription)
session.commit()
self.logger.info(f"Subscribe successfully created for {user.id}")
return "OK"
except Exception as e:
self.logger.error(f"Error with created subscribe: {e}")
return "Ошибка"
finally:
session.close()
def buy_sub(self, telegram_id: str, plan: str):
session = next(get_db_session())
try:
user = session.query(User).filter(User.telegram_id == telegram_id).first()
if not user:
self.logger.error(f"User with Telegram ID {telegram_id} not found.")
return "error"
current_plan = config['subscription_templates'].get(plan)
if not current_plan:
self.logger.error(f"Tarif {plan} not found.")
return "error"
cost = current_plan['cost']
if user.balance >= cost:
user.balance -= cost
session.commit()
result = self.tariff_setting(user, plan, current_plan['duration'])
if result == "OK":
add_server_result = self.add_to_server(telegram_id)
if add_server_result == "OK":
return "OK"
else:
return "ERROR " + add_server_result
else:
return "ERROR " + result
self.logger.error(f"Nt enough money {telegram_id} for {plan}.")
return 100
except Exception as e:
self.logger.error(f"Error with buying sub {telegram_id}: {e}")
session.rollback()
finally:
session.close()
def get_sub_list(self, count: int, user_id: int):
session = next(get_db_session())
try:
return (
session.query(Subscription)
.filter(Subscription.user_id == user_id)
.order_by(desc(Subscription.created_at))
.limit(count)
.all()
)
except Exception as e:
self.logger.error(f"Ошибка при получении списка подписок для пользователя {user_id}: {e}")
def add_to_server(self, telegram_id: str):
session = next(get_db_session())
try:
user_sub = (
session.query(Subscription)
.join(User)
.filter(User.telegram_id == telegram_id)
.first()
)
user = session.query(User).filter(User.telegram_id == telegram_id).first()
server = session.query(VPNServer).filter(VPNServer.id == user_sub.vpn_server_id).first()
url_base = f"https://{server.ip_address}:{server.port}/{server.secret}"
login_data = {
'username': server.login,
'password': server.password,
}
try:
server_config_dict = json.loads(server.config)
except json.JSONDecodeError as e:
self.logger.error(f"Ошибка разбора JSON: {e}")
return "180"
client_id = server_config_dict['obj']['id']
panel = PanelInteraction(url_base, login_data, self.logger)
panel.add_client(client_id, user_sub.expiry_date.isoformat(), user.username)
return "OK"
except Exception as e:
self.logger.error(f"Ошибка при установке на сервер для пользователя {telegram_id}: {e}")
return "ERROR"
def create_uri(self, telegram_id: str):
session = next(get_db_session())
try:
user = session.query(User).filter(User.telegram_id == telegram_id).first()
if not user:
self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.")
return "error"
sub = self.last_subscription(user)
if not sub:
self.logger.error("Подписка не найдена.")
return "error"
vpn_server = session.query(VPNServer).filter_by(id=sub.vpn_server_id).first()
base_url = f"https://{vpn_server.ip_address}:{vpn_server.port}/{vpn_server.secret}"
login_data = {
'username': vpn_server.login,
'password': vpn_server.password
}
server_config_dict = json.loads(vpn_server.config)
client_id = server_config_dict['obj']['id']
PI = PanelInteraction(base_url, login_data, self.logger)
CIF3 = PI.get_client_traffic(user.username) # Client Info From 3x-ui
VPNCIF3 = PI.getInboundInfo(client_id)
return self.generate_uri(vpn_config=VPNCIF3, CIF3=CIF3)
except Exception as e:
self.logger.error(f"Ошибка в создании URI: {e}")
return "error"
finally:
session.close()
def generate_uri(self, vpn_config, CIF3):
try:
config_data = json.loads(vpn_config) if isinstance(vpn_config, str) else vpn_config
obj = config_data["obj"]
port = obj["port"]
clients = json.loads(obj["settings"])["clients"] if isinstance(obj["settings"], str) else obj["settings"]["clients"]
for client in clients:
if client["email"] == CIF3['obj']['email']:
uuid = client["id"]
flow = client["flow"]
stream_settings = json.loads(obj["streamSettings"]) if isinstance(obj["streamSettings"], str) else obj["streamSettings"]
dest = stream_settings["realitySettings"]["dest"]
server_names = stream_settings["realitySettings"]["serverNames"]
public_key = stream_settings["realitySettings"]["settings"]["publicKey"]
fingerprint = stream_settings["realitySettings"]["settings"]["fingerprint"]
short_id = stream_settings["realitySettings"]["shortIds"][0] # Первый короткий ID
return (
f"vless://{uuid}@{dest}:{port}?type=tcp&security=reality"
f"&pbk={public_key}&fp={fingerprint}&sni={server_names[0]}"
f"&sid={short_id}&spx=%2F&flow={flow}#user-{CIF3}"
)
self.logger.error(f"Клиент с email {CIF3} не найден.")
return None
except Exception as e:
self.logger.error(f"Ошибка в методе создания URI: {e}")
return None

View File

@@ -1,70 +0,0 @@
import argparse
from datetime import datetime
import json
import base64
from pymongo import MongoClient
def connect_to_mongo(uri, db_name):
"""Подключение к MongoDB."""
client = MongoClient(uri)
db = client[db_name]
return db
def load_json(json_path):
"""Загружает JSON-данные из файла."""
with open(json_path, "r", encoding="utf-8") as f:
return json.load(f)
def encode_file(file_path):
"""Читает файл и кодирует его в Base64."""
with open(file_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def insert_certificate(data, cert_path, cert_location):
"""Добавляет сертификат в указанное место внутри структуры JSON."""
# Читаем и кодируем сертификат
certificate_data = encode_file(cert_path)
# Разбиваем путь на вложенные ключи
keys = cert_location.split(".")
target = data
for key in keys[:-1]:
if key not in target:
target[key] = {} # Создаем вложенные ключи, если их нет
target = target[key]
target[keys[-1]] = {
"data": certificate_data,
"uploaded_at": datetime.utcnow()
}
def insert_data(db, collection_name, data):
"""Вставляет данные в указанную коллекцию MongoDB."""
collection = db[collection_name]
collection.insert_one(data)
print(f"Данные успешно вставлены в коллекцию '{collection_name}'.")
def main():
parser = argparse.ArgumentParser(description="Insert JSON data into MongoDB with certificate")
parser.add_argument("--mongo-uri",default="mongodb://root:itOj4CE2miKR@mongodb:27017" ,required=True, help="MongoDB URI")
parser.add_argument("--db-name",default="MongoDBSub&Ser" ,required=True, help="MongoDB database name")
parser.add_argument("--collection",default="servers", required=True, help="Collection name")
parser.add_argument("--json-path", required=True, help="Path to the JSON file with data")
parser.add_argument("--cert-path", required=True, help="Path to the certificate file (.crt)")
parser.add_argument("--cert-location", required=True, help="Path inside JSON structure to store certificate (e.g., 'server.certificate')")
args = parser.parse_args()
# Подключение к MongoDB
db = connect_to_mongo(args.mongo_uri, args.db_name)
# Загрузка данных из JSON-файла
data = load_json(args.json_path)
# Вставка сертификата в структуру данных
insert_certificate(data, args.cert_path, args.cert_location)
# Вставка данных в MongoDB
insert_data(db, args.collection, data)
if __name__ == "__main__":
main()

View File

@@ -22,4 +22,6 @@ def setup_logger():
logger.addHandler(handler) logger.addHandler(handler)
return logger return logger
app_logger = setup_logger()

View File

@@ -1,107 +1,203 @@
import requests import aiohttp
import uuid import uuid
import string import json
import secrets import base64
import json import ssl
from datetime import datetime, timedelta from datetime import datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
def generate_uuid(): def generate_uuid():
return str(uuid.uuid4()) return str(uuid.uuid4())
class PanelInteraction: class PanelInteraction:
def __init__(self, base_url, login_data, logger_): def __init__(self, base_url, login_data, logger, certificate=None, is_encoded=True):
"""
Initialize the PanelInteraction class.
:param base_url: Base URL for the panel.
:param login_data: Login data (username/password or token).
:param logger: Logger for debugging.
:param certificate: Certificate content (Base64-encoded or raw string).
:param is_encoded: Indicates whether the certificate is Base64-encoded.
"""
self.base_url = base_url self.base_url = base_url
self.login_data = login_data self.login_data = login_data
self.logger = logger_ self.logger = logger
self.session_id = self.login() self.cert_content = self._decode_certificate(certificate, is_encoded)
if self.session_id: self.session_id = None # Session ID will be initialized lazily
self.headers = { self.headers = None
'Accept': 'application/json',
'Cookie': f'3x-ui={self.session_id}',
'Content-Type': 'application/json'
}
else:
raise ValueError("Login failed, session_id is None")
def login(self): def _decode_certificate(self, certificate, is_encoded):
login_url = self.base_url + "/login" """
Decode the provided certificate content.
:param certificate: Certificate content (Base64-encoded or raw string).
:param is_encoded: Indicates whether the certificate is Base64-encoded.
:return: Decoded certificate content as bytes.
"""
if not certificate:
self.logger.error("No certificate provided.")
raise ValueError("Certificate is required.")
try:
# Создаем SSLContext
ssl_context = ssl.create_default_context()
# Декодируем, если нужно
if is_encoded:
certificate = base64.b64decode(certificate).decode()
# Загружаем сертификат в SSLContext
ssl_context.load_verify_locations(cadata=certificate)
return ssl_context
except Exception as e:
self.logger.error(f"Error while decoding certificate: {e}")
raise ValueError("Invalid certificate format or content.") from e
async def _ensure_logged_in(self):
"""
Ensure the session ID is available for authenticated requests.
"""
if not self.session_id:
self.session_id = await self.login()
if self.session_id:
self.headers = {
'Accept': 'application/json',
'Cookie': f'3x-ui={self.session_id}',
'Content-Type': 'application/json'
}
else:
raise ValueError("Unable to log in and retrieve session ID.")
async def login(self):
"""
Perform login to the panel.
:return: Session ID or None.
"""
login_url = f"{self.base_url}/login"
self.logger.info(f"Attempting to login at: {login_url}") self.logger.info(f"Attempting to login at: {login_url}")
try:
response = requests.post(login_url, data=self.login_data, verify=False, timeout=10)
response.raise_for_status()
session_id = response.cookies.get("3x-ui")
if session_id:
return session_id
else:
self.logger.error(f"Login failed: {response.status_code}")
self.logger.debug(f"Response content: {response.text}")
return None
except requests.RequestException as e:
self.logger.error(f"Login request failed: {e}")
return None
def getInboundInfo(self, inboundId): async with aiohttp.ClientSession() as session:
url = f"{self.base_url}/panel/api/inbounds/get/{inboundId}" try:
try: async with session.post(
response = requests.get(url, headers=self.headers, verify=False, timeout=10) login_url, data=self.login_data, ssl=self.cert_content, timeout=10
response.raise_for_status() ) as response:
if response: if response.status == 200:
return response.json() session_id = response.cookies.get("3x-ui")
else: if session_id:
self.logger.error(f"Failed to get inbound info: {response.status_code}") return session_id.value
self.logger.debug("Response:", response.text) else:
self.logger.error("Login failed: No session ID received.")
return None
else:
self.logger.error(f"Login failed: {response.status}")
return None
except aiohttp.ClientError as e:
self.logger.error(f"Login request failed: {e}")
return None return None
except requests.RequestException as e:
self.logger.error(f"Get inbound request failed: {e}") async def get_inbound_info(self, inbound_id):
"""
def get_client_traffic(self, email): Fetch inbound information by ID.
:param inbound_id: ID of the inbound.
:return: JSON response or None.
"""
await self._ensure_logged_in()
url = f"{self.base_url}/panel/api/inbounds/get/{inbound_id}"
async with aiohttp.ClientSession() as session:
try:
async with session.get(
url, headers=self.headers, ssl=self.cert_content, timeout=10
) as response:
if response.status == 200:
return await response.json()
else:
self.logger.error(f"Failed to get inbound info: {response.status}")
return None
except aiohttp.ClientError as e:
self.logger.error(f"Get inbound info request failed: {e}")
return None
async def get_client_traffic(self, email):
"""
Fetch traffic information for a specific client.
:param email: Client's email.
:return: JSON response or None.
"""
await self._ensure_logged_in()
url = f"{self.base_url}/panel/api/inbounds/getClientTraffics/{email}" url = f"{self.base_url}/panel/api/inbounds/getClientTraffics/{email}"
try: async with aiohttp.ClientSession() as session:
response = requests.get(url, headers=self.headers, verify=False, timeout=10) try:
response.raise_for_status() async with session.get(
if response: url, headers=self.headers, ssl=self.cert_content, timeout=10
return response.json() ) as response:
else: if response.status == 200:
self.logger.error(f"Failed to get client traffic: {response.status_code}") return await response.json()
self.logger.debug("Response:", response.text) else:
self.logger.error(f"Failed to get client traffic: {response.status}")
return None
except aiohttp.ClientError as e:
self.logger.error(f"Get client traffic request failed: {e}")
return None return None
except requests.RequestException as e:
self.loggin.error(f"Get client request failed: {e}") async def update_client_expiry(self, client_uuid, new_expiry_time, client_email):
"""
def update_client_expiry(self, client_uuid, new_expiry_time, client_email): Update the expiry date of a specific client.
:param client_uuid: UUID of the client.
:param new_expiry_time: New expiry date in ISO format.
:param client_email: Client's email.
:return: None.
"""
await self._ensure_logged_in()
url = f"{self.base_url}/panel/api/inbounds/updateClient" url = f"{self.base_url}/panel/api/inbounds/updateClient"
update_data = { update_data = {
"id": 1, "id": 1,
"settings": json.dumps({ "settings": {
"clients": [ "clients": [
{ {
"id": client_uuid, "id": client_uuid,
"alterId": 0, "alterId": 0,
"email": client_email, "email": client_email,
"limitIp": 2, "limitIp": 2,
"totalGB": 0, "totalGB": 0,
"expiryTime": new_expiry_time, "expiryTime": new_expiry_time,
"enable": True, "enable": True,
"tgId": "", "tgId": "",
"subId": "" "subId": ""
} }
] ]
}) }
} }
try:
response = requests.post(url, headers=self.headers, json=update_data, verify=False)
response.raise_for_status()
if response:
self.logger.debug("Client expiry time updated successfully.")
else:
self.logger.error(f"Failed to update client: {response.status_code}, {response.text}")
except requests.RequestException as e:
self.logger.error(f"Update client request failed: {e}")
def add_client(self, inbound_id, expiry_date,email): async with aiohttp.ClientSession() as session:
try:
async with session.post(
url, headers=self.headers, json=update_data, ssl=self.cert_content
) as response:
if response.status == 200:
self.logger.info("Client expiry updated successfully.")
else:
self.logger.error(f"Failed to update client expiry: {response.status}")
except aiohttp.ClientError as e:
self.logger.error(f"Update client expiry request failed: {e}")
async def add_client(self, inbound_id, expiry_date, email):
"""
Add a new client to an inbound.
:param inbound_id: ID of the inbound.
:param expiry_date: Expiry date in ISO format.
:param email: Client's email.
:return: JSON response or None.
"""
await self._ensure_logged_in()
url = f"{self.base_url}/panel/api/inbounds/addClient" url = f"{self.base_url}/panel/api/inbounds/addClient"
client_info = { client_info = {
"clients": [ "clients": [
@@ -111,7 +207,7 @@ class PanelInteraction:
"email": email, "email": email,
"limitIp": 2, "limitIp": 2,
"totalGB": 0, "totalGB": 0,
"flow":"xtls-rprx-vision", "flow": "xtls-rprx-vision",
"expiryTime": expiry_date, "expiryTime": expiry_date,
"enable": True, "enable": True,
"tgId": "", "tgId": "",
@@ -121,15 +217,19 @@ class PanelInteraction:
} }
payload = { payload = {
"id": inbound_id, "id": inbound_id,
"settings": json.dumps(client_info) "settings": client_info
} }
try:
response = requests.post(url, headers=self.headers, json=payload, verify=False) async with aiohttp.ClientSession() as session:
if response.status_code == 200: try:
return response.json() async with session.post(
else: url, headers=self.headers, json=payload, ssl=self.cert_content
self.logger.error(f"Failed to add client: {response.status_code}") ) as response:
self.logger.debug("Response:", response.text) if response.status == 200:
return await response.status
else:
self.logger.error(f"Failed to add client: {response.status}")
return None
except aiohttp.ClientError as e:
self.logger.error(f"Add client request failed: {e}")
return None return None
finally:
self.logger.info("Finished attempting to add client.")