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

- Покупку, но надо подправить пару моментов
- И выдача конфигураций
- Проверил работу 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

261
bot.py
View File

@@ -1,243 +1,140 @@
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes,MessageHandler, filters, CommandHandler from telegram.ext import Application, CallbackQueryHandler, ContextTypes, MessageHandler, filters
from db import User, VPNServer, Transaction, Subscription from db import User, VPNServer, Transaction, Subscription, get_db_session, init_db, SessionLocal
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 sqlalchemy import desc from sqlalchemy import desc
from service import UserService
import json import json
from datetime import datetime from datetime import datetime
from logger_config import setup_logger from logger_config import setup_logger
# Чтение конфигурации и настройка логгера
with open('config.json', 'r') as file: with open('config.json', 'r') as file:
config = json.load(file) config = json.load(file)
logger = setup_logger() 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: async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [ buttons = [("Личный кабинет", "account"), ("О нас ;)", "about"), ("Поддержка", "support")]
[ await send_loading_message(update, context, 'Добро пожаловать в ...! Здесь вы можете приобрести VPN. И нечего более', create_keyboard(buttons))
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)
# Функция для обработки личного кабинета
async def personal_account(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def personal_account(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [ service = UserService(logger)
[ tgid = str(update.callback_query.from_user.id)
InlineKeyboardButton("Пополнить баланс", callback_data="pop_up"), user = service.get_user_by_telegram_id(tgid) or service.add_user(tgid)
InlineKeyboardButton("Приобрести подписку", callback_data='buy_tarif'), subscription = service.last_subscription(user)
],
[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
)
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: async def about(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [ buttons = [("Главное меню", "start")]
[ await send_loading_message(update, context, 'Игорь чё нить напишет, я продублирую', create_keyboard(buttons))
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)
# Функция для отображения поддержки
async def support(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def support(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [ buttons = [("Главное меню", "start"), ("Написать", "sup")]
[ await send_loading_message(update, context, 'Для связи с поддержкой выберите "Написать" и изложите проблему в одном сообщении.', create_keyboard(buttons))
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)
# Функция для пополнения баланса
async def pop_up(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def pop_up(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [ buttons = [("Главное меню", "start")]
[ await send_loading_message(update, context, 'Когда-нибудь эта функция заработает', create_keyboard(buttons))
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)
# Функция для покупки подписки
async def buy_subscription(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def buy_subscription(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [ service = UserService(logger)
[ tgid = str(update.callback_query.from_user.id)
InlineKeyboardButton("Главное меню", callback_data="start"), user = service.get_user_by_telegram_id(tgid)
]] subscription = service.last_subscription(user)
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: if subscription is None:
keyboard = [ buttons = [("Тариф 1 \"Бимжик\"", "Бимжик"), ("Тариф 2 \"Бизнес хомячёк\"", "Бизнес_хомячёк"), ("Тариф 3 \"Продвинутый Акулёнок\"", "Продвинутый_Акулёнок"), ("Главное меню", "start")]
[ text = 'Какую подписку вы хотите приобрести?\n1. "Бимжик" - 200 руб. на 1 месяц\n2. "Бизнес хомячёк" - 500 руб. на 3 месяца\n3. "Продвинутый Акулёнок" - 888 руб. на 6 месяцев'
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)
# проверяем, истекла ли подписка
else: else:
keyboard = [ buttons = [("Главное меню", "start")]
[ text = 'У вас уже приобретена подписка'
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)
await send_loading_message(update, context, text, create_keyboard(buttons))
# Функция для отображения FAQ
async def faq(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def faq(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [ buttons = [("Главное меню", "start")]
[ await send_loading_message(update, context, 'Когда-нибудь здесь появится полезная информация!', create_keyboard(buttons))
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)
# Функция для обработки ввода пользователя в поддержку
async def sup(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def sup(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if context.user_data.get('awaiting_input'): if context.user_data.get('awaiting_input'):
user_input = update.message.text user_input = update.message.text
await update.message.reply_text(f"Вы ввели: {user_input}") await update.message.reply_text(f"Вы ввели: {user_input}")
# После получения текста сбрасываем ожидание
context.user_data['awaiting_input'] = False context.user_data['awaiting_input'] = False
else: else:
await update.message.reply_text("Выберите команду или нажмите кнопку для продолжения.") await update.message.reply_text("Выберите команду или нажмите кнопку для продолжения.")
# Обработчик кнопок
async def button_handler(update: Update, context): async def button_handler(update: Update, context):
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
data = query.data
service = UserService(logger)
tgid = str(query.from_user.id)
session = next(get_db_session())
try: try:
if query.data == 'account': if data == 'account':
await personal_account(update,context) await personal_account(update, context)
elif query.data == 'start': elif data == 'start':
await start(update,context) await start(update, context)
elif query.data == 'about': elif data == 'about':
await about(update, context) await about(update, context)
elif query.data == 'support': elif data == 'support':
await support(update, context) await support(update, context)
elif query.data == 'sup': elif data == 'sup':
context.user_data['awaiting_input'] = True context.user_data['awaiting_input'] = True
elif query.data == 'pop_up': elif data == 'pop_up':
await pop_up(update, context) await pop_up(update, context)
elif query.data == 'buy_tarif': elif data == 'buy_tarif':
await buy_subscription(update, context) await buy_subscription(update, context)
elif query.data == 'faq': elif data == 'faq':
await faq(update, context) await faq(update, context)
elif query.data == 'payment_history': elif data == 'payment_history':
await active_sub(update, context) await active_sub(update, context)
elif data in ['Бимжик', 'Бизнес_хомячёк', 'Продвинутый_Акулёнок']:
if query.data in ['Бимжик', 'Бизнес_хомячёк', 'Продвинутый_Акулёнок']: plan = data.replace('_', ' ')
loading_message = await query.message.reply_text("Загрузка...") result = service.buy_sub(tgid, data)
plan = query.data.replace('_', ' ') if '_' in query.data else query.data text = {
check = buy_sub(session, query.from_user.id, plan, logger) "OK": "Ваша конфигурация готова!",
"100": "Недостаточно средств.",
"120": "Нет доступных серверов, подождите немного.",
if check != "OK": }.get(result, "Неизвестный тариф.")
await loading_message.edit_text("Неизвестный тариф.") await query.message.reply_text(text)
else:
await loading_message.edit_text("Тариф успешно установлен!")
except Exception as e: except Exception as e:
logger.error(f"Ошибка при обработке запроса пользователя {query.from_user.id}: {e}") logger.error(f"Ошибка при обработке запроса пользователя {tgid}: {e}")
await query.message.reply_text("Произошла ошибка. Пожалуйста, попробуйте снова.") 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: def main() -> None:
init_db() init_db()
db = SessionLocal()
application = Application.builder().token(config['token']).build() 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(CallbackQueryHandler(button_handler))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, sup)) application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, sup))
application.run_polling(allowed_updates=Update.ALL_TYPES) application.run_polling(allowed_updates=Update.ALL_TYPES)
db.close()
if __name__ == "__main__": if __name__ == "__main__":
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.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import desc from sqlalchemy import desc
@@ -19,13 +19,15 @@ class User(Base):
id = Column(String, primary_key=True, default=generate_uuid) id = Column(String, primary_key=True, default=generate_uuid)
telegram_id = Column(String, unique=True, nullable=False) 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) balance = Column(Numeric(10, 2), default=0.0)
created_at = Column(DateTime, default=datetime.now) created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
subscriptions = relationship("Subscription", back_populates="user") subscriptions = relationship("Subscription", back_populates="user")
transactions = relationship("Transaction", back_populates="user") transactions = relationship("Transaction", back_populates="user")
requests = relationship("Requests", back_populates="user")
admins = relationship("Administrators", back_populates="user")
#Подписки #Подписки
class Subscription(Base): class Subscription(Base):
@@ -57,20 +59,22 @@ class Transaction(Base):
class Requests(Base): class Requests(Base):
__tablename__ = 'requests' __tablename__ = 'requests'
id = Column(String,primary_key=True,default=generate_uuid) id = Column(String, primary_key=True, default=generate_uuid)
user_id = Column(String,ForeignKey('users.id')) user_id = Column(String, ForeignKey('users.id'))
username = Column(String) username = Column(String)
created_at = Column(DateTime,default=datetime.now) created_at = Column(DateTime, default=datetime.now)
content = Column(String) content = Column(String)
status = Column(String,default='open') status = Column(String, default='open')
user = relationship("User",back_populates="requests")
user = relationship("User", back_populates="requests")
class Administrators(Base): class Administrators(Base):
__tablename__ = 'admins' __tablename__ = 'admins'
id = Column(String,primary_key=True,default=generate_uuid) id = Column(String,primary_key=True,default=generate_uuid)
user_id = Column(String,ForeignKey('users.id')) user_id = Column(String,ForeignKey('users.id'))
admin = Column(bool,default=False) admin = Column(Boolean,default=False)
user = relationship("User",back_populates="admins") user = relationship("User",back_populates="admins")
# VPN-серверы # VPN-серверы
class VPNServer(Base): class VPNServer(Base):
@@ -80,9 +84,10 @@ class VPNServer(Base):
server_name = Column(String) server_name = Column(String)
ip_address = Column(String) ip_address = Column(String)
port = Column(Integer) port = Column(Integer)
login_data = Column(Text) login = Column(String)
inbound = Column(Text) password = Column(String)
config = Column(Text) config = Column(String)
secret = Column(String)
current_users = Column(Integer, default=0) current_users = Column(Integer, default=0)
max_users = Column(Integer, default=4) 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) 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(): 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_):
self.base_url = base_url self.base_url = base_url
@@ -47,7 +31,8 @@ class PanelInteraction:
def login(self): def login(self):
login_url = self.base_url + "/login" 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: if response.status_code == 200:
session_id = response.cookies.get("3x-ui") session_id = response.cookies.get("3x-ui")
return session_id return session_id
@@ -55,26 +40,31 @@ class PanelInteraction:
self.logger.error(f"Login failed: {response.status_code}") self.logger.error(f"Login failed: {response.status_code}")
return None return None
def getInboundInfo(self,inboundId): def getInboundInfo(self, inboundId):
url = f"{self.base_url}/panel/api/inbounds/get/{inboundId}" url = f"{self.base_url}/panel/api/inbounds/get/{inboundId}"
response = requests.get(url, headers=self.headers) try:
if response.status_code == 200: response = requests.get(url, headers=self.headers, verify=False)
return response.json() if response.status_code == 200:
else: return response.json()
self.logger.error(f"Failed to get inbound info: {response.status_code}") else:
self.logger.debug("Response:", response.text) self.logger.error(f"Failed to get inbound info: {response.status_code}")
return None self.logger.debug("Response:", response.text)
return None
finally:
self.logger.info("Finished attempting to get inbound info.")
def get_client_traffic(self, email): def get_client_traffic(self, email):
url = f"{self.base_url}/panel/api/inbounds/getClientTraffics/{email}" url = f"{self.base_url}/panel/api/inbounds/getClientTraffics/{email}"
response = requests.get(url, headers=self.headers) try:
if response.status_code == 200: response = requests.get(url, headers=self.headers, verify=False)
return response.json() if response.status_code == 200:
else: return response.json()
self.logger.error(f"Failed to get client traffic: {response.status_code}") else:
self.logger.debug("Response:", response.text) self.logger.error(f"Failed to get client traffic: {response.status_code}")
return None 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): def update_client_expiry(self, client_uuid, new_expiry_time, client_email):
url = f"{self.base_url}/panel/api/inbounds/updateClient" url = f"{self.base_url}/panel/api/inbounds/updateClient"
@@ -96,23 +86,27 @@ class PanelInteraction:
] ]
}) })
} }
response = requests.post(url, headers=self.headers, json=update_data) try:
if response.status_code == 200: response = requests.post(url, headers=self.headers, json=update_data, verify=False)
self.logger.debug("Client expiry time updated successfully.") if response.status_code == 200:
else: self.logger.debug("Client expiry time updated successfully.")
self.logger.error(f"Failed to update client: {response.status_code} {response.text}") 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" url = f"{self.base_url}/panel/api/inbounds/addClient"
client_info = { client_info = {
"clients": [ "clients": [
{ {
"id": generate_uuid(), "id": generate_uuid(),
"alterId": 0, "alterId": 0,
"email": generate_random_string(), "email": email,
"limitIp": 2, "limitIp": 2,
"totalGB": 0, "totalGB": 0,
"expiryTime": generate_date(months), "flow":"xtls-rprx-vision",
"expiryTime": expiry_date,
"enable": True, "enable": True,
"tgId": "", "tgId": "",
"subId": "" "subId": ""
@@ -123,11 +117,13 @@ class PanelInteraction:
"id": inbound_id, "id": inbound_id,
"settings": json.dumps(client_info) "settings": json.dumps(client_info)
} }
response = requests.post(url, headers=self.headers, json=payload) try:
if response.status_code == 200: response = requests.post(url, headers=self.headers, json=payload, verify=False)
self.logger.debug("Client added successfully!") if response.status_code == 200:
return response.json() return response.json()
else: else:
self.logger.error(f"Failed to add client: {response.status_code}") self.logger.error(f"Failed to add client: {response.status_code}")
self.logger.debug("Response:", response.text) self.logger.debug("Response:", response.text)
return None return None
finally:
self.logger.info("Finished attempting to add client.")

View File

@@ -1,23 +1,32 @@
from db import User from db import User, Subscription, Transaction, VPNServer
from db import Subscription import string
from db import Transaction import secrets
from db import VPNServer
from sqlalchemy import desc
from datetime import datetime,timedelta
from db import get_db_session
import json 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 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: class UserService:
def __init__(self,logger): def __init__(self, logger):
self.logger = 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()) session = next(get_db_session())
try: 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.add(new_user)
session.commit() session.commit()
except Exception as e: except Exception as e:
@@ -26,7 +35,7 @@ class UserService:
finally: finally:
session.close() 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()) session = next(get_db_session())
try: try:
return session.query(User).filter(User.telegram_id == telegram_id).first() return session.query(User).filter(User.telegram_id == telegram_id).first()
@@ -35,35 +44,48 @@ class UserService:
finally: finally:
session.close() 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()) session = next(get_db_session())
try: try:
transaction = Transaction(user_id = user_id,amount = amount) transaction = Transaction(user_id=user_id, amount=amount)
session.add(transaction) session.add(transaction)
session.commit() session.commit()
except Exception as e: except Exception as e:
self.logger.error(f"Ошибка добавления транзакции:{e}") self.logger.error(f"Ошибка добавления транзакции: {e}")
finally: finally:
session.close() 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()) session = next(get_db_session())
try: try:
user = session.query(User).filter(User.telegram_id == telegram_id).first() user = session.query(User).filter(User.telegram_id == telegram_id).first()
if user: if user:
user.balance = amount user.balance = amount
self.add_transaction(user.id,amount) self.add_transaction(user.id, amount)
session.commit() session.commit()
else: else:
self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.") self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.")
except Exception as e: 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: finally:
session.close() 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()) session = next(get_db_session())
try: try:
server = ( server = (
@@ -73,58 +95,159 @@ class UserService:
.first() .first()
) )
if server is None: if not server:
self.logger.error("Нет доступных VPN серверов.") self.logger.error("Нет доступных VPN серверов.")
return return "120"
# Рассчитываем дату окончания подписки
expiry_ = datetime.now() + relativedelta(months=expiry_duration)
self.logger.info(f"Создание подписки для пользователя {user.id} на сервере {server.id} с планом {plan} до {expiry_}")
expiry_ = datetime.now() + timedelta(days=expiry_duration)
new_subscription = Subscription(user_id=user.id, vpn_server_id=server.id, plan=plan, expiry_date=expiry_) new_subscription = Subscription(user_id=user.id, vpn_server_id=server.id, plan=plan, expiry_date=expiry_)
session.add(new_subscription) session.add(new_subscription)
session.commit() session.commit()
self.logger.info(f"Подписка успешно создана для пользователя {user.id}")
return "OK"
except Exception as e: except Exception as e:
self.logger.error(f"Ошибка в установке тарифа: {e}") self.logger.error(f"Ошибка в установке тарифа: {e}")
return "Ошибка"
finally: finally:
session.close() session.close()
def buy_sub(self, telegram_id: str, plan: str):
def create_uri(self,telegram_id,):
session = next(get_db_session()) session = next(get_db_session())
try: try:
user = session.query(User).filter(User.telegram_id == telegram_id).first() user = session.query(User).filter(User.telegram_id == telegram_id).first()
if user: if not user:
subscription = user.subscriptions self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.")
if not subscription: return "error"
return None
vpn_server = session.query(VPNServer).filter_by(id=subscription.vpn_server_id).first() current_plan = config['subscription_templates'].get(plan)
baseURL ="http://" + vpn_server.ip_address + ":" + vpn_server.port if not current_plan:
PI = PanelInteraction(baseURL,vpn_server.login_data,self.logger) self.logger.error(f"Тариф {plan} не найден в шаблонах.")
CIF3 = PI.get_client_traffic(user.username) # Client Info From 3x-ui return "error"
URI = self.generate_uri(vpn_config=vpn_server.config,CIF3=CIF3)
cost = current_plan['cost']
if user.balance >= cost:
user.balance -= cost
session.commit() 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: except Exception as e:
self.logger.error(f"Чё то ошибка в создании uri: {e}") self.logger.error(f"Ошибка при покупке тарифа для пользователя {telegram_id}: {e}")
session.rollback()
finally: finally:
session.close() 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): def generate_uri(self, vpn_config, CIF3):
try: try:
# Извлечение необходимых данных из конфигурации # Проверяем тип vpn_config и загружаем его, если это строка
config_data = json.loads(vpn_config) config_data = json.loads(vpn_config) if isinstance(vpn_config, str) else vpn_config
obj = config_data["obj"] obj = config_data["obj"]
port = obj["port"] 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: for client in clients:
if client["email"] == CIF3: if client["email"] == CIF3['obj']['email']:
uuid = client["id"] uuid = client["id"]
flow = client["flow"] flow = client["flow"]
# Извлечение параметров из streamSettings # Извлечение параметров из 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"] dest = stream_settings["realitySettings"]["dest"]
server_names = stream_settings["realitySettings"]["serverNames"] server_names = stream_settings["realitySettings"]["serverNames"]
public_key = stream_settings["realitySettings"]["settings"]["publicKey"] public_key = stream_settings["realitySettings"]["settings"]["publicKey"]
@@ -132,19 +255,15 @@ class UserService:
short_id = stream_settings["realitySettings"]["shortIds"][0] # Первый короткий ID short_id = stream_settings["realitySettings"]["shortIds"][0] # Первый короткий ID
# Сборка строки VLess # Сборка строки VLess
URI = ( return (
f"vless://{uuid}@{dest}:{port}?type=tcp&security=reality" f"vless://{uuid}@{dest}:{port}?type=tcp&security=reality"
f"&pbk={public_key}&fp={fingerprint}&sni={server_names[0]}" f"&pbk={public_key}&fp={fingerprint}&sni={server_names[0]}"
f"&sid={short_id}&spx=%2F&flow={flow}#user-{CIF3}" f"&sid={short_id}&spx=%2F&flow={flow}#user-{CIF3}"
) )
return URI self.logger.error(f"Клиент с email {CIF3} не найден.")
# Если клиент с указанным email не найден
self.logger.warning(f"Клиент с email {CIF3} не найден.")
return None return None
except Exception as e: except Exception as e:
self.logger.error(f"Ошибка в методе создания uri: {e}") self.logger.error(f"Ошибка в методе создания URI: {e}")
return None return None