Доделал наконец

- Покупку, но надо подправить пару моментов
- И выдача конфигураций
- Проверил работу panel.py наконец, исправил пару приколов
This commit is contained in:
2024-10-26 19:38:27 +03:00
parent 7437525d0d
commit 824e007786
5 changed files with 318 additions and 375 deletions

269
bot.py
View File

@@ -1,243 +1,140 @@
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes,MessageHandler, filters, CommandHandler
from db import User, VPNServer, Transaction, Subscription
from db import get_db_session
from db import init_db, SessionLocal
from db_operations import last_subscription, create_user , get_sub_list,buy_sub
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Application, CallbackQueryHandler, ContextTypes, MessageHandler, filters
from db import User, VPNServer, Transaction, Subscription, get_db_session, init_db, SessionLocal
from sqlalchemy import desc
from service import UserService
import json
from datetime import datetime
from logger_config import setup_logger
# Чтение конфигурации и настройка логгера
with open('config.json', 'r') as file:
config = json.load(file)
logger = setup_logger()
# Общая функция для создания клавиатуры
def create_keyboard(buttons):
return InlineKeyboardMarkup([[InlineKeyboardButton(text, callback_data=data)] for text, data in buttons])
# Функция для отправки сообщений с загрузкой
async def send_loading_message(update, context, text, reply_markup=None):
loading_message = await context.bot.send_message(chat_id=update.effective_chat.id, text="Загрузка...")
await loading_message.edit_text(text, reply_markup=reply_markup)
# Функция для обработки главного меню
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Личный кабинет", callback_data="account"),
InlineKeyboardButton("О нac ;)", callback_data='about'),
],
[InlineKeyboardButton("Поддержка", callback_data='support')],
]
reply_markup = InlineKeyboardMarkup(keyboard)
loading_message = await context.bot.send_message(chat_id=update.callback_query.message.chat.id, text="Загрузка...")
await loading_message.edit_text(f'Добро пожаловать в ...! Здесь вы можете приобрести VPN. И нечего более',reply_markup=reply_markup)
buttons = [("Личный кабинет", "account"), ("О нас ;)", "about"), ("Поддержка", "support")]
await send_loading_message(update, context, 'Добро пожаловать в ...! Здесь вы можете приобрести VPN. И нечего более', create_keyboard(buttons))
# Функция для обработки личного кабинета
async def personal_account(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Пополнить баланс", callback_data="pop_up"),
InlineKeyboardButton("Приобрести подписку", callback_data='buy_tarif'),
],
[InlineKeyboardButton("❔FAQ❔", callback_data='faq')],
[InlineKeyboardButton("История платежей", callback_data='payment_history')]
]
reply_markup = InlineKeyboardMarkup(keyboard)
session = next(get_db_session())
if session.query(User).filter(User.telegram_id == str(update.callback_query.from_user.id)).first() == None:
create_user(str(update.callback_query.from_user.id),update.effective_user.username)
user = session.query(User).filter(User.telegram_id == str(update.callback_query.from_user.id)).first()
subscription = last_subscription(session=session,user=user)
loading_message = await context.bot.send_message(chat_id=update.callback_query.message.chat.id, text="Загрузка...")
if not subscription:
await loading_message.edit_text(
f'Профиль {user.username}, {user.telegram_id}\nВы не приобретали ещё у нас подписку, но это явно стоит сделать:)\nВаш счёт составляет: {user.balance}',reply_markup=reply_markup
)
# Проверяем, истекла ли подписка
elif subscription.expiry_date < datetime.now():
await loading_message.edit_text(
f'Ваш профиль {user.username}, {user.telegram_id}, Ваша подписка действует до - {subscription.expiry_date}',reply_markup=reply_markup
)
else:
await loading_message.edit_text(
f'Ваш профиль {user.username}, {user.telegram_id},\nВаша подписка истекла - {subscription.expiry_date}',reply_markup=reply_markup
)
service = UserService(logger)
tgid = str(update.callback_query.from_user.id)
user = service.get_user_by_telegram_id(tgid) or service.add_user(tgid)
subscription = service.last_subscription(user)
buttons = [("Пополнить баланс", "pop_up"), ("Приобрести подписку", "buy_tarif"), ("❔FAQ❔", "faq"), ("История платежей", "payment_history")]
text = (
f'Профиль {user.username}, {user.telegram_id}\n'
f'{"Вы не приобретали ещё у нас подписку, но это явно стоит сделать:)" if not subscription else f"Ваша подписка действует до - {subscription.expiry_date}" if subscription.expiry_date > datetime.now() else f"Ваша подписка истекла - {subscription.expiry_date}"}\n'
f'Ваш счёт составляет: {user.balance}'
)
await send_loading_message(update, context, text, create_keyboard(buttons))
# Функция для отображения информации "О нас"
async def about(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="start")
]]
reply_markup = InlineKeyboardMarkup(keyboard)
loading_message = await context.bot.send_message(chat_id=update.callback_query.message.chat.id, text="Загрузка...")
await loading_message.edit_text(f'Игорь чё нить напишет, я продублирую',reply_markup=reply_markup)
buttons = [("Главное меню", "start")]
await send_loading_message(update, context, 'Игорь чё нить напишет, я продублирую', create_keyboard(buttons))
# Функция для отображения поддержки
async def support(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="start"),
InlineKeyboardButton("Написать", callback_data="sup") # Нужно через каллбек доделать
]]
reply_markup = InlineKeyboardMarkup(keyboard)
loading_message = await context.bot.send_message(chat_id=update.callback_query.message.chat.id, text="Загрузка...")
await loading_message.edit_text(f'Что бы отправить сообщение поддержке выберите в меню кнопку "Написать", а далее изложите в одном сообщении свою ошибку.',reply_markup=reply_markup)
buttons = [("Главное меню", "start"), ("Написать", "sup")]
await send_loading_message(update, context, 'Для связи с поддержкой выберите "Написать" и изложите проблему в одном сообщении.', create_keyboard(buttons))
# Функция для пополнения баланса
async def pop_up(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="start"),
]]
reply_markup = InlineKeyboardMarkup(keyboard)
loading_message = await context.bot.send_message(chat_id=update.callback_query.message.chat.id, text="Загрузка...")
await loading_message.edit_text(f'Когда нибудь эта штука заработает',reply_markup=reply_markup)
buttons = [("Главное меню", "start")]
await send_loading_message(update, context, 'Когда-нибудь эта функция заработает', create_keyboard(buttons))
# Функция для покупки подписки
async def buy_subscription(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="start"),
]]
reply_markup = InlineKeyboardMarkup(keyboard)
session = next(get_db_session())
user = session.query(User).filter(User.telegram_id == str(update.callback_query.from_user.id)).first()
check = last_subscription(session=session,user=user)
if not check:
keyboard = [
[
InlineKeyboardButton("Тариф 1 \"Бимжик\"", callback_data="Бимжик"),
],
[
InlineKeyboardButton("Тариф 2 \"Бизнес хомячёк\"", callback_data="Бизнес_хомячёк"),
],
[
InlineKeyboardButton("Тариф 3 \"Продвинутый Акулёнок\"", callback_data="Продвинутый_Акулёнок"),
],
[
InlineKeyboardButton("Главное меню", callback_data="start"),
]]
reply_markup = InlineKeyboardMarkup(keyboard)
loading_message = await context.bot.send_message(chat_id=update.callback_query.message.chat.id, text="Загрузка...")
await loading_message.edit_text(f'Какую подписку вы хотели бы приобрести\nТариф 1 "Бимжик" - 200 рубликов - 1 месяцок\nТариф 2 "Бизнес хомячёк" - 500 рубликов - 3 месяцка\nТариф 3 "Продвинутый Акулёнок" - 888 рубликов - 6 месяцков\n',reply_markup=reply_markup)
# проверяем, истекла ли подписка
service = UserService(logger)
tgid = str(update.callback_query.from_user.id)
user = service.get_user_by_telegram_id(tgid)
subscription = service.last_subscription(user)
if subscription is None:
buttons = [("Тариф 1 \"Бимжик\"", "Бимжик"), ("Тариф 2 \"Бизнес хомячёк\"", "Бизнес_хомячёк"), ("Тариф 3 \"Продвинутый Акулёнок\"", "Продвинутый_Акулёнок"), ("Главное меню", "start")]
text = 'Какую подписку вы хотите приобрести?\n1. "Бимжик" - 200 руб. на 1 месяц\n2. "Бизнес хомячёк" - 500 руб. на 3 месяца\n3. "Продвинутый Акулёнок" - 888 руб. на 6 месяцев'
else:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="start"),
]]
reply_markup = InlineKeyboardMarkup(keyboard)
loading_message = await context.bot.send_message(chat_id=update.callback_query.message.chat.id, text="Загрузка...")
await loading_message.edit_text(f'У вас уже приобретена подписка',reply_markup=reply_markup)
buttons = [("Главное меню", "start")]
text = 'У вас уже приобретена подписка'
await send_loading_message(update, context, text, create_keyboard(buttons))
# Функция для отображения FAQ
async def faq(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="start"),
]]
reply_markup = InlineKeyboardMarkup(keyboard)
loading_message = await context.bot.send_message(chat_id=update.callback_query.message.chat.id, text="Загрузка...")
await loading_message.edit_text(f'Когда нибудь что нибудь здесь будет написано!!;)',reply_markup=reply_markup)
buttons = [("Главное меню", "start")]
await send_loading_message(update, context, 'Когда-нибудь здесь появится полезная информация!', create_keyboard(buttons))
# Функция для обработки ввода пользователя в поддержку
async def sup(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if context.user_data.get('awaiting_input'):
user_input = update.message.text
await update.message.reply_text(f"Вы ввели: {user_input}")
# После получения текста сбрасываем ожидание
context.user_data['awaiting_input'] = False
else:
await update.message.reply_text("Выберите команду или нажмите кнопку для продолжения.")
# Обработчик кнопок
async def button_handler(update: Update, context):
query = update.callback_query
await query.answer()
data = query.data
service = UserService(logger)
tgid = str(query.from_user.id)
session = next(get_db_session())
try:
if query.data == 'account':
await personal_account(update,context)
elif query.data == 'start':
await start(update,context)
elif query.data == 'about':
if data == 'account':
await personal_account(update, context)
elif data == 'start':
await start(update, context)
elif data == 'about':
await about(update, context)
elif query.data == 'support':
elif data == 'support':
await support(update, context)
elif query.data == 'sup':
context.user_data['awaiting_input'] = True
elif query.data == 'pop_up':
elif data == 'sup':
context.user_data['awaiting_input'] = True
elif data == 'pop_up':
await pop_up(update, context)
elif query.data == 'buy_tarif':
elif data == 'buy_tarif':
await buy_subscription(update, context)
elif query.data == 'faq':
elif data == 'faq':
await faq(update, context)
elif query.data == 'payment_history':
elif data == 'payment_history':
await active_sub(update, context)
if query.data in ['Бимжик', 'Бизнес_хомячёк', 'Продвинутый_Акулёнок']:
loading_message = await query.message.reply_text("Загрузка...")
plan = query.data.replace('_', ' ') if '_' in query.data else query.data
check = buy_sub(session, query.from_user.id, plan, logger)
if check != "OK":
await loading_message.edit_text("Неизвестный тариф.")
else:
await loading_message.edit_text("Тариф успешно установлен!")
elif data in ['Бимжик', 'Бизнес_хомячёк', 'Продвинутый_Акулёнок']:
plan = data.replace('_', ' ')
result = service.buy_sub(tgid, data)
text = {
"OK": "Ваша конфигурация готова!",
"100": "Недостаточно средств.",
"120": "Нет доступных серверов, подождите немного.",
}.get(result, "Неизвестный тариф.")
await query.message.reply_text(text)
except Exception as e:
logger.error(f"Ошибка при обработке запроса пользователя {query.from_user.id}: {e}")
logger.error(f"Ошибка при обработке запроса пользователя {tgid}: {e}")
await query.message.reply_text("Произошла ошибка. Пожалуйста, попробуйте снова.")
finally:
session.close()
async def active_sub(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="start"),
]]
reply_markup = InlineKeyboardMarkup(keyboard)
session = next(get_db_session())
list_sub = get_sub_list(session, 10, update.callback_query.from_user.id)
loading_message = await context.bot.send_message(chat_id=update.callback_query.message.chat.id, text="Загрузка...")
if list_sub:
message = "Ваши подписки:\n"
for cur_sub in list_sub:
if cur_sub.expiry_date > datetime.now():
message += f" Активная: {cur_sub.plan}, Дата покупки: {cur_sub.created_at}\n"
else:
message += f" Устаревшая: {cur_sub.plan}, Дата покупки: {cur_sub.created_at}\n"
else:
message = "Ты пидор, не приобрел у нас подписку?!"
await loading_message.edit_text(message,reply_markup=reply_markup)
# Запуск приложения
def main() -> None:
init_db()
db = SessionLocal()
application = Application.builder().token(config['token']).build()
application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("account", personal_account))
application.add_handler(CommandHandler("about", about))
application.add_handler(CommandHandler("support", support))
application.add_handler(CommandHandler("popup", pop_up))
application.add_handler(CommandHandler("buy_subscription", buy_subscription))
application.add_handler(CommandHandler("faq", faq))
application.add_handler(CommandHandler("active_sub", active_sub))
application.add_handler(CallbackQueryHandler(button_handler))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, sup))
application.run_polling(allowed_updates=Update.ALL_TYPES)
db.close()
if __name__ == "__main__":
main()

27
db.py
View File

@@ -1,4 +1,4 @@
from sqlalchemy import create_engine, Column, String, Integer, Numeric, DateTime, ForeignKey, Text
from sqlalchemy import create_engine, Column, String, Integer, Numeric, DateTime, ForeignKey, Text, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import desc
@@ -19,13 +19,15 @@ class User(Base):
id = Column(String, primary_key=True, default=generate_uuid)
telegram_id = Column(String, unique=True, nullable=False)
username = Column(String) # email 3x-ui
username = Column(String) # email 3x-ui
balance = Column(Numeric(10, 2), default=0.0)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
subscriptions = relationship("Subscription", back_populates="user")
transactions = relationship("Transaction", back_populates="user")
requests = relationship("Requests", back_populates="user")
admins = relationship("Administrators", back_populates="user")
#Подписки
class Subscription(Base):
@@ -57,20 +59,22 @@ class Transaction(Base):
class Requests(Base):
__tablename__ = 'requests'
id = Column(String,primary_key=True,default=generate_uuid)
user_id = Column(String,ForeignKey('users.id'))
id = Column(String, primary_key=True, default=generate_uuid)
user_id = Column(String, ForeignKey('users.id'))
username = Column(String)
created_at = Column(DateTime,default=datetime.now)
created_at = Column(DateTime, default=datetime.now)
content = Column(String)
status = Column(String,default='open')
user = relationship("User",back_populates="requests")
status = Column(String, default='open')
user = relationship("User", back_populates="requests")
class Administrators(Base):
__tablename__ = 'admins'
id = Column(String,primary_key=True,default=generate_uuid)
user_id = Column(String,ForeignKey('users.id'))
admin = Column(bool,default=False)
admin = Column(Boolean,default=False)
user = relationship("User",back_populates="admins")
# VPN-серверы
class VPNServer(Base):
@@ -80,9 +84,10 @@ class VPNServer(Base):
server_name = Column(String)
ip_address = Column(String)
port = Column(Integer)
login_data = Column(Text)
inbound = Column(Text)
config = Column(Text)
login = Column(String)
password = Column(String)
config = Column(String)
secret = Column(String)
current_users = Column(Integer, default=0)
max_users = Column(Integer, default=4)

View File

@@ -1,74 +0,0 @@
from service import UserService
#DataBase
from sqlalchemy import desc
from db import get_db_session
from db import User
from db import Subscription
from db import Transaction
from db import VPNServer
import json
with open('config.json', 'r') as file:
config = json.load(file)
def last_subscription(session,user):
last_subscription = (
session.query(Subscription)
.filter(Subscription.user_id == user.id)
.order_by(desc(Subscription.created_at))
.first()
)
return last_subscription
def get_sub_list(session,count,user_id):
subscriptions = (
session.query(Subscription)
.filter(Subscription.user_id == str(user_id))
.order_by(desc(Subscription.created_at))
.limit(count) # Ограничиваем результат 10 записями
.all() # Получаем все записи
)
return subscriptions
def create_user(telegram_id: str, username: str = None, balance: float = 0.0):
db = next(get_db_session())
try:
new_user = User(
telegram_id=telegram_id,
username=username,
balance=balance
)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user
except Exception as e:
db.rollback()
raise e
finally:
db.close()
def buy_sub(session, telegram_id: str, plan: str, logger):
try:
user = session.query(User).filter(User.telegram_id == str(telegram_id)).first()
if user is None:
logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.")
return "error"
current_plan = config['subscription_templates'].get(plan)
if current_plan is None:
logger.error(f"Тариф {plan} не найден в шаблонах.")
return "error"
if user.balance == current_plan['cost']:
service = UserService(logger)
service.tariff_setting(user, plan, current_plan['duration'])
return "OK"
else:
logger.error(f"Недостаточно средств на счету пользователя {telegram_id} для тарифа {plan}.")
return "error"
except Exception as e:
logger.error(f"Ошибка при покупке тарифа для пользователя {telegram_id}: {e}")

View File

@@ -9,27 +9,11 @@ from dateutil.relativedelta import relativedelta
with open('config.json', 'r') as file : config = json.load(file)
def generate_date(months):
now = datetime.now()
# Преобразуем months в число
try:
months = int(months) # или float(months), если месяцы могут быть дробными
except ValueError:
raise TypeError("months должно быть числом")
future_date = now + timedelta(days=30 * months)
return future_date.isoformat()
def generate_random_string(length=8):
characters = string.ascii_letters + string.digits
return ''.join(secrets.choice(characters) for _ in range(length))
def generate_uuid():
return str(uuid.uuid4())
class PanelInteraction:
def __init__(self, base_url, login_data, logger_):
self.base_url = base_url
@@ -47,7 +31,8 @@ class PanelInteraction:
def login(self):
login_url = self.base_url + "/login"
response = requests.post(login_url, data=self.login_data)
self.logger.info(f"Login URL : {login_url}")
response = requests.post(login_url, data=self.login_data, verify=False)
if response.status_code == 200:
session_id = response.cookies.get("3x-ui")
return session_id
@@ -55,27 +40,32 @@ class PanelInteraction:
self.logger.error(f"Login failed: {response.status_code}")
return None
def getInboundInfo(self,inboundId):
def getInboundInfo(self, inboundId):
url = f"{self.base_url}/panel/api/inbounds/get/{inboundId}"
response = requests.get(url, headers=self.headers)
if response.status_code == 200:
return response.json()
else:
self.logger.error(f"Failed to get inbound info: {response.status_code}")
self.logger.debug("Response:", response.text)
return None
try:
response = requests.get(url, headers=self.headers, verify=False)
if response.status_code == 200:
return response.json()
else:
self.logger.error(f"Failed to get inbound info: {response.status_code}")
self.logger.debug("Response:", response.text)
return None
finally:
self.logger.info("Finished attempting to get inbound info.")
def get_client_traffic(self, email):
url = f"{self.base_url}/panel/api/inbounds/getClientTraffics/{email}"
response = requests.get(url, headers=self.headers)
if response.status_code == 200:
return response.json()
else:
self.logger.error(f"Failed to get client traffic: {response.status_code}")
self.logger.debug("Response:", response.text)
return None
try:
response = requests.get(url, headers=self.headers, verify=False)
if response.status_code == 200:
return response.json()
else:
self.logger.error(f"Failed to get client traffic: {response.status_code}")
self.logger.debug("Response:", response.text)
return None
finally:
self.logger.info("Finished attempting to get client traffic.")
def update_client_expiry(self, client_uuid, new_expiry_time, client_email):
url = f"{self.base_url}/panel/api/inbounds/updateClient"
update_data = {
@@ -96,23 +86,27 @@ class PanelInteraction:
]
})
}
response = requests.post(url, headers=self.headers, json=update_data)
if response.status_code == 200:
self.logger.debug("Client expiry time updated successfully.")
else:
self.logger.error(f"Failed to update client: {response.status_code} {response.text}")
try:
response = requests.post(url, headers=self.headers, json=update_data, verify=False)
if response.status_code == 200:
self.logger.debug("Client expiry time updated successfully.")
else:
self.logger.error(f"Failed to update client: {response.status_code} {response.text}")
finally:
self.logger.info("Finished attempting to update client expiry.")
def add_client(self, inbound_id, months):
def add_client(self, inbound_id, expiry_date,email):
url = f"{self.base_url}/panel/api/inbounds/addClient"
client_info = {
"clients": [
{
"id": generate_uuid(),
"alterId": 0,
"email": generate_random_string(),
"email": email,
"limitIp": 2,
"totalGB": 0,
"expiryTime": generate_date(months),
"flow":"xtls-rprx-vision",
"expiryTime": expiry_date,
"enable": True,
"tgId": "",
"subId": ""
@@ -123,11 +117,13 @@ class PanelInteraction:
"id": inbound_id,
"settings": json.dumps(client_info)
}
response = requests.post(url, headers=self.headers, json=payload)
if response.status_code == 200:
self.logger.debug("Client added successfully!")
return response.json()
else:
self.logger.error(f"Failed to add client: {response.status_code}")
self.logger.debug("Response:", response.text)
return None
try:
response = requests.post(url, headers=self.headers, json=payload, verify=False)
if response.status_code == 200:
return response.json()
else:
self.logger.error(f"Failed to add client: {response.status_code}")
self.logger.debug("Response:", response.text)
return None
finally:
self.logger.info("Finished attempting to add client.")

View File

@@ -1,23 +1,32 @@
from db import User
from db import Subscription
from db import Transaction
from db import VPNServer
from db import User, Subscription, Transaction, VPNServer
import string
import secrets
import json
from sqlalchemy import desc
from datetime import datetime,timedelta
from dateutil.relativedelta import relativedelta
from datetime import datetime
from db import get_db_session
import json
from panel import PanelInteraction
with open('config.json', 'r') as file : config = json.load(file)
def generate_random_string(length=8):
characters = string.ascii_letters + string.digits
return ''.join(secrets.choice(characters) for _ in range(length))
# Загрузка конфигурации один раз
with open('config.json', 'r') as file:
config = json.load(file)
class UserService:
def __init__(self,logger):
def __init__(self, logger):
self.logger = logger
def add_user(self,telegram_id: int, username: str):
def add_user(self, telegram_id: int):
session = next(get_db_session())
try:
new_user = User(telegram_id=telegram_id, username=username)
new_user = User(telegram_id=telegram_id, username=generate_random_string())
session.add(new_user)
session.commit()
except Exception as e:
@@ -26,7 +35,7 @@ class UserService:
finally:
session.close()
def get_user_by_telegram_id(self,telegram_id: int):
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()
@@ -35,35 +44,48 @@ class UserService:
finally:
session.close()
def add_transaction(self,user_id: int,amount: float):
def add_transaction(self, user_id: int, amount: float):
session = next(get_db_session())
try:
transaction = Transaction(user_id = user_id,amount = amount)
transaction = Transaction(user_id=user_id, amount=amount)
session.add(transaction)
session.commit()
except Exception as e:
self.logger.error(f"Ошибка добавления транзакции:{e}")
self.logger.error(f"Ошибка добавления транзакции: {e}")
finally:
session.close()
def pop_up_balance(self,telegram_id: int,amount: float):
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)
self.add_transaction(user.id, amount)
session.commit()
else:
self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.")
except Exception as e:
self.logger.error(f"Ошибка при обновлении баланса:{e}")
self.logger.error(f"Сумма: {amount}, Пользователь: {telegram_id}")
session.rollback()
session.rollback()
self.logger.error(f"Ошибка при обновлении баланса: {e}")
finally:
session.close()
def tariff_setting(self, user, plan: str, expiry_duration):
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 = (
@@ -72,59 +94,160 @@ class UserService:
.order_by(VPNServer.current_users.asc())
.first()
)
if server is None:
self.logger.error("Нет доступных VPN серверов.")
return
expiry_ = datetime.now() + timedelta(days=expiry_duration)
if not server:
self.logger.error("Нет доступных VPN серверов.")
return "120"
# Рассчитываем дату окончания подписки
expiry_ = datetime.now() + relativedelta(months=expiry_duration)
self.logger.info(f"Создание подписки для пользователя {user.id} на сервере {server.id} с планом {plan} до {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"Подписка успешно создана для пользователя {user.id}")
return "OK"
except Exception as e:
self.logger.error(f"Ошибка в установке тарифа: {e}")
return "Ошибка"
finally:
session.close()
def create_uri(self,telegram_id,):
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 user:
subscription = user.subscriptions
if not subscription:
return None
vpn_server = session.query(VPNServer).filter_by(id=subscription.vpn_server_id).first()
baseURL ="http://" + vpn_server.ip_address + ":" + vpn_server.port
PI = PanelInteraction(baseURL,vpn_server.login_data,self.logger)
CIF3 = PI.get_client_traffic(user.username) # Client Info From 3x-ui
URI = self.generate_uri(vpn_config=vpn_server.config,CIF3=CIF3)
if not user:
self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.")
return "error"
current_plan = config['subscription_templates'].get(plan)
if not current_plan:
self.logger.error(f"Тариф {plan} не найден в шаблонах.")
return "error"
cost = current_plan['cost']
if user.balance >= cost:
user.balance -= cost
session.commit()
return URI
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"Недостаточно средств на счету пользователя {telegram_id} для тарифа {plan}.")
return 100
except Exception as e:
self.logger.error(f"Чё то ошибка в создании uri: {e}")
self.logger.error(f"Ошибка при покупке тарифа для пользователя {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,
}
# Преобразование server.config из строки в словарь
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)
# Проверяем тип vpn_config и загружаем его, если это строка
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"]
# Поиск клиента по email (CIF3)
# Обрабатываем настройки клиентов
clients = json.loads(obj["settings"])["clients"] if isinstance(obj["settings"], str) else obj["settings"]["clients"]
for client in clients:
if client["email"] == CIF3:
if client["email"] == CIF3['obj']['email']:
uuid = client["id"]
flow = client["flow"]
# Извлечение параметров из streamSettings
stream_settings = json.loads(obj["streamSettings"])
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"]
@@ -132,19 +255,15 @@ class UserService:
short_id = stream_settings["realitySettings"]["shortIds"][0] # Первый короткий ID
# Сборка строки VLess
URI = (
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}"
)
return URI
# Если клиент с указанным email не найден
self.logger.warning(f"Клиент с email {CIF3} не найден.")
self.logger.error(f"Клиент с email {CIF3} не найден.")
return None
except Exception as e:
self.logger.error(f"Ошибка в методе создания uri: {e}")
self.logger.error(f"Ошибка в методе создания URI: {e}")
return None