Create project

This commit is contained in:
Disledg
2024-09-30 19:21:48 +03:00
commit 47db6a5fb7
6 changed files with 497 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
*.json
pyvenv.cfg
bin/
include/
lib/
lib64/
lib64

107
bot.py Normal file
View File

@@ -0,0 +1,107 @@
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes
from db import User
from db import Subscription
from db import Transaction
from db import VPNServer
from sqlalchemy import desc
import json
from datetime import datetime
from db import get_db_session
from db import init_db, SessionLocal
from logger_config import setup_logger
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
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)
await update.message.reply_text(f'Добро пожаловать в ...! Здесь вы можете приобрести VPN. И нечего более',reply_markup=reply_markup)
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())
user = session.query(User).filter(User.telegram_id == update.chat_member.from_user.id).first()
check = last_subscription(session=session,user=user)
if not check:
await update.message.reply_text(f'Профиль {user.username}\nВы не приобретали ещё у нас подписку, но это явно стоит сделать:)',reply_markup=reply_markup)
# проверяем, истекла ли подписка
if check.expiry_date < datetime.now():
await update.message.reply_text(f'Ваш профиль {user.username},Ваша подписка действует до - {check.expiry_date}',reply_markup=reply_markup)
else:
await update.message.reply_text(f'Ваш профиль {user.username},\nВаша подписка истекла - {check.expiry_date}',reply_markup=reply_markup)
async def about(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="account")
]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f'Игорь чё нить напишет, я продублирую',reply_markup=reply_markup)
async def support(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="account"),
InlineKeyboardButton("Написать", callback_data="sup") # Нужно через каллбек доделать
]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f'Что бы отправить сообщение поддержке выберите в меню кнопку "Написать", а далее изложите в одном сообщении свою ошибку.',reply_markup=reply_markup)
async def pop_up(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
keyboard = [
[
InlineKeyboardButton("Главное меню", callback_data="account"),
]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f'Когда нибудь эта штука заработает',reply_markup=reply_markup)
#async def buy_subscription(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
def main() -> None:
logger = setup_logger()
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.run_polling(allowed_updates=Update.ALL_TYPES)
db.close()
if __name__ == "__main__":
main()

84
db.py Normal file
View File

@@ -0,0 +1,84 @@
from sqlalchemy import create_engine, Column, String, Integer, Numeric, DateTime, ForeignKey, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
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(String, unique=True, nullable=False)
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")
#Подписки
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, ForeignKey('vpn_servers.id'))
plan = Column(String)
expiry_date = Column(DateTime)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
user = relationship("User", back_populates="subscriptions")
vpn_server = relationship("VPNServer", 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.now)
user = relationship("User", back_populates="transactions")
# VPN-серверы
class VPNServer(Base):
__tablename__ = 'vpn_servers'
id = Column(String, primary_key=True, default=generate_uuid)
server_name = Column(String)
ip_address = Column(String)
port = Column(Integer)
login_data = Column(Text)
inbound = Column(Text)
config = Column(Text)
current_users = Column(Integer, default=0)
max_users = Column(Integer, default=4)
subscriptions = relationship("Subscription", back_populates="vpn_server")
# Настройка подключения к базе данных
DATABASE_URL = "postgresql://vpn_bot_user:yourpassword@localhost/vpn_bot_db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def init_db():
Base.metadata.create_all(bind=engine)
def get_db_session():
db = SessionLocal()
try:
yield db
finally:
db.close()

21
logger_config.py Normal file
View File

@@ -0,0 +1,21 @@
import logging
from logging.handlers import TimedRotatingFileHandler
def setup_logger():
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = TimedRotatingFileHandler(
"app.log",
when="midnight",
interval=1,
backupCount=7,
encoding='utf-8'
)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger

133
panel.py Normal file
View File

@@ -0,0 +1,133 @@
import requests
import uuid
import string
import secrets
import json
from logger_config import setup_logger
from datetime import datetime, timedelta
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
self.login_data = login_data
self.logger = logger_
self.session_id = 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("Login failed, session_id is None")
def login(self):
login_url = self.base_url + "/login"
response = requests.post(login_url, data=self.login_data)
if response.status_code == 200:
session_id = response.cookies.get("3x-ui")
return session_id
else:
self.logger.error(f"Login failed: {response.status_code}")
return None
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
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
def update_client_expiry(self, client_uuid, new_expiry_time, client_email):
url = f"{self.base_url}/panel/api/inbounds/updateClient"
update_data = {
"id": 1,
"settings": json.dumps({
"clients": [
{
"id": client_uuid,
"alterId": 0,
"email": client_email,
"limitIp": 2,
"totalGB": 0,
"expiryTime": new_expiry_time,
"enable": True,
"tgId": "",
"subId": ""
}
]
})
}
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}")
def add_client(self, inbound_id, months):
url = f"{self.base_url}/panel/api/inbounds/addClient"
client_info = {
"clients": [
{
"id": generate_uuid(),
"alterId": 0,
"email": generate_random_string(),
"limitIp": 2,
"totalGB": 0,
"expiryTime": generate_date(months),
"enable": True,
"tgId": "",
"subId": ""
}
]
}
payload = {
"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

145
service.py Normal file
View File

@@ -0,0 +1,145 @@
from db import User
from db import Subscription
from db import Transaction
from db import VPNServer
from datetime import datetime,timedelta
from db import get_db_session
import json
from panel import PanelInteraction
with open('config.json', 'r') as file : config = json.load(file)
class UserService:
def __init__(self,logger):
self.logger = logger
def add_user(self,telegram_id: int, username: str):
session = next(get_db_session())
try:
new_user = User(telegram_id=telegram_id, username=username)
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 pop_up_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:
self.logger.error(f"Ошибка при обновлении баланса:{e}")
self.logger.error(f"Сумма: {amount}, Пользователь: {telegram_id}")
session.rollback()
finally:
session.close()
def tariff_setting(self,telegram_id: int,plan: str):
session = next(get_db_session())
try:
user = session.query(User).filter(User.telegram_id == telegram_id).first()
if user:
server = (
session.query(VPNServer)
.filter(VPNServer.current_users < VPNServer.max_users)
.order_by(VPNServer.current_users.asc())
.first())
current_plan = config['subscription_templates'].get(plan)
expiry_ = datetime.now() + timedelta(days=current_plan['expiry_duration'])
new_subscription = Subscription(user_id = user.id,vpn_server_id = server.id,plan = plan,expiry_date = expiry_)
session.add(new_subscription)
session.commit()
except Exception as e:
self.logger.error(f"Чё то ошибка в установке тарифа: {e}")
finally:
session.close()
def create_uri(self,telegram_id,):
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)
session.commit()
return URI
except Exception as e:
self.logger.error(f"Чё то ошибка в создании uri: {e}")
finally:
session.close()
def generate_uri(self, vpn_config, CIF3):
try:
# Извлечение необходимых данных из конфигурации
config_data = json.loads(vpn_config)
obj = config_data["obj"]
port = obj["port"]
clients = json.loads(obj["settings"])["clients"]
# Поиск клиента по email (CIF3)
for client in clients:
if client["email"] == CIF3:
uuid = client["id"]
flow = client["flow"]
# Извлечение параметров из streamSettings
stream_settings = json.loads(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
# Сборка строки VLess
URI = (
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} не найден.")
return None
except Exception as e:
self.logger.error(f"Ошибка в методе создания uri: {e}")
return None