massage 1

This commit is contained in:
0xbovs1
2024-12-21 12:38:57 +00:00
parent 8c6bcd76d0
commit f5d2051360
23 changed files with 623 additions and 0 deletions

View File

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
TBot/
logs/*

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
# Используем базовый Python-образ
FROM python:3.12-slim
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем файлы проекта
COPY . .
# Устанавливаем зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Указываем команду запуска бота
CMD ["python", "main.py"]

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright (c) 2024 disledg
Данная лицензия разрешает лицам, получившим копию данного программного обеспечения и сопутствующей документации (в дальнейшем именуемыми «Программное Обеспечение»), безвозмездно использовать Программное Обеспечение без ограничений, включая неограниченное право на использование, копирование, изменение, слияние, публикацию, распространение, сублицензирование и/или продажу копий Программного Обеспечения, а также лицам, которым предоставляется данное Программное Обеспечение, при соблюдении следующих условий:
Указанное выше уведомление об авторском праве и данные условия должны быть включены во все копии или значимые части данного Программного Обеспечения.
ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО КАКИМ-ЛИБО ИСКАМ, УЩЕРБАМ ИЛИ ИНЫМ ТРЕБОВАНИЯМ, В ТОМ ЧИСЛЕ, ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТА ИЛИ ИНОЙ СИТУАЦИИ, ВОЗНИКШИМ ИЗ-ЗА ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.

36
README.md Normal file
View File

@@ -0,0 +1,36 @@
# VPN Configuration Sales Bot
Бот для Telegram, предназначенный для продажи VPN конфигураций. Проект создан с целью автоматизации процесса продажи VPN и управления пользователями через удобный интерфейс Telegram, с использованием баз данных PostgreSQL и MongoDB.
## 📋 Описание
Этот проект представляет собой Telegram-бота, который позволяет пользователям приобретать VPN настройки, а администраторам управлять конфигурациями и отслеживать заказы. Бот поддерживает работу с двумя базами данных для обеспечения гибкого хранения и обработки данных.
## 🛠 Функционал
- Регистрация и авторизация пользователей
- Покупка VPN конфигураций
### В стадии разработки
- Автоматическая выдача VPN конфигураций
- Поддержка двух баз данных (PostgreSQL и MongoDB) для более гибкого и масштабируемого хранения данных
- Панель администратора для управления заказами и пользователями
- Саппорт система
## 🚀 Технологии
- **Python**
- **Telegram API**
- **PostgreSQL**
- **MongoDB**
- **SQLAlchemy** - для взаимодействия с PostgreSQL
- **PyMongo** - для работы с MongoDB
## 📝 Лицензия
Этот проект распространяется под лицензией MIT License. Подробности в файле [LICENSE](./LICENSE).

78
app/configdb1.py Normal file
View File

@@ -0,0 +1,78 @@
import os
from flask import Flask, g
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
import asyncio
# Инициализация Flask
app = Flask(__name__)
# Настройки 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]
@app.before_first_request
async def init_databases():
"""
Инициализация подключений к PostgreSQL и MongoDB перед первым запросом.
"""
try:
# Инициализация PostgreSQL
async with postgres_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
print("PostgreSQL connected.")
# Проверка подключения к MongoDB
await mongo_client.admin.command("ping")
print("MongoDB connected.")
except Exception as e:
print(f"Database initialization failed: {e}")
@app.teardown_appcontext
def close_connections(exception=None):
"""
Закрытие соединений с базами данных после окончания работы приложения.
"""
asyncio.run(postgres_engine.dispose())
mongo_client.close()
print("Database connections closed.")
@app.route("/postgres_session")
async def get_postgres_session():
"""
Пример использования сессии PostgreSQL в маршруте Flask.
"""
try:
async with AsyncSessionLocal() as session:
# Здесь можно выполнить запросы к базе данных PostgreSQL
result = await session.execute("SELECT 1")
return {"postgres_result": result.scalar()}
except Exception as e:
return {"error": str(e)}, 500
@app.route("/mongo_status")
async def get_mongo_status():
"""
Пример проверки MongoDB в маршруте Flask.
"""
try:
await mongo_client.admin.command("ping")
return {"mongo_status": "connected"}
except Exception as e:
return {"error": str(e)}, 500
if __name__ == "__main__":
app.run(debug=True)

137
app/model1.py Normal file
View File

@@ -0,0 +1,137 @@
import os
from flask import Flask, g
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from motor.motor_asyncio import AsyncIOMotorClient
from sqlalchemy import Column, String, Numeric, DateTime, Boolean, ForeignKey, Integer
from sqlalchemy.orm import declarative_base, relationship
from datetime import datetime
import uuid
import asyncio
# Инициализация Flask
app = Flask(__name__)
# Настройки 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]
# SQLAlchemy Base
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")
@app.before_first_request
async def init_databases():
"""
Инициализация подключений к PostgreSQL и MongoDB перед первым запросом.
"""
try:
# Инициализация PostgreSQL
async with postgres_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
print("PostgreSQL connected.")
# Проверка подключения к MongoDB
await mongo_client.admin.command("ping")
print("MongoDB connected.")
except Exception as e:
print(f"Database initialization failed: {e}")
@app.teardown_appcontext
def close_connections(exception=None):
"""
Закрытие соединений с базами данных после окончания работы приложения.
"""
asyncio.run(postgres_engine.dispose())
mongo_client.close()
print("Database connections closed.")
@app.route("/postgres_session")
async def get_postgres_session():
"""
Пример использования сессии PostgreSQL в маршруте Flask.
"""
try:
async with AsyncSessionLocal() as session:
# Здесь можно выполнить запросы к базе данных PostgreSQL
result = await session.execute("SELECT 1")
return {"postgres_result": result.scalar()}
except Exception as e:
return {"error": str(e)}, 500
@app.route("/mongo_status")
async def get_mongo_status():
"""
Пример проверки MongoDB в маршруте Flask.
"""
try:
await mongo_client.admin.command("ping")
return {"mongo_status": "connected"}
except Exception as e:
return {"error": str(e)}, 500
if __name__ == "__main__":
app.run(debug=True)

118
app/mongodb1.py Normal file
View File

@@ -0,0 +1,118 @@
import os
import logging
from flask import Flask, jsonify, request
from motor.motor_asyncio import AsyncIOMotorClient
from bson import ObjectId
from flask.logging import default_handler
import asyncio
app = Flask(__name__)
# Настройки логирования
logger = logging.getLogger("MongoDBRepository")
logger.setLevel(logging.DEBUG)
logger.addHandler(default_handler)
# Настройки MongoDB из переменных окружения
mongo_uri = os.getenv("MONGO_URL", "mongodb://localhost:27017")
database_name = os.getenv("DB_NAME", "mydatabase")
server_collection = os.getenv("SERVER_COLLECTION", "servers")
plan_collection = os.getenv("PLAN_COLLECTION", "plans")
# Подключение к базе данных
client = AsyncIOMotorClient(mongo_uri)
db = client[database_name]
servers = db[server_collection]
plans = db[plan_collection]
@app.route('/plans', methods=['POST'])
async def add_subscription_plan():
plan_data = request.json
result = await plans.insert_one(plan_data)
logger.debug(f"Тарифный план добавлен с ID: {result.inserted_id}")
return jsonify({"inserted_id": str(result.inserted_id)}), 201
@app.route('/plans/<plan_id>', methods=['GET'])
async def get_subscription_plan(plan_id):
plan = await plans.find_one({"_id": ObjectId(plan_id)})
if plan:
logger.debug(f"Найден тарифный план: {plan}")
plan["_id"] = str(plan["_id"])
return jsonify(plan)
else:
logger.error(f"Тарифный план {plan_id} не найден.")
return jsonify({"error": "Plan not found"}), 404
@app.route('/servers', methods=['POST'])
async def add_server():
server_data = request.json
result = await servers.insert_one(server_data)
logger.debug(f"VPN сервер добавлен с ID: {result.inserted_id}")
return jsonify({"inserted_id": str(result.inserted_id)}), 201
@app.route('/servers/<server_name>', methods=['GET'])
async def get_server(server_name):
server = await servers.find_one({"server.name": server_name})
if server:
logger.debug(f"Найден VPN сервер: {server}")
server["_id"] = str(server["_id"])
return jsonify(server)
else:
logger.debug(f"VPN сервер с именем {server_name} не найден.")
return jsonify({"error": "Server not found"}), 404
@app.route('/servers/least_clients', methods=['GET'])
async def get_server_with_least_clients():
pipeline = [
{"$addFields": {"current_clients": {"$size": {"$ifNull": ["$clients", []]}}}},
{"$sort": {"current_clients": 1}},
{"$limit": 1}
]
result = await servers.aggregate(pipeline).to_list(length=1)
if result:
server = result[0]
server["_id"] = str(server["_id"])
logger.debug(f"Найден сервер с наименьшим количеством клиентов: {server}")
return jsonify(server)
else:
logger.debug("Не найдено серверов.")
return jsonify({"error": "No servers found"}), 404
@app.route('/servers/<server_id>', methods=['PUT'])
async def update_server(server_id):
update_data = request.json
result = await servers.update_one({"_id": ObjectId(server_id)}, {"$set": update_data})
if result.matched_count > 0:
logger.debug(f"VPN сервер с ID {server_id} обновлен.")
return jsonify({"updated": True})
else:
logger.debug(f"VPN сервер с ID {server_id} не найден.")
return jsonify({"error": "Server not found"}), 404
@app.route('/servers/<server_id>', methods=['DELETE'])
async def delete_server(server_id):
result = await servers.delete_one({"_id": ObjectId(server_id)})
if result.deleted_count > 0:
logger.debug(f"VPN сервер с ID {server_id} удален.")
return jsonify({"deleted": True})
else:
logger.debug(f"VPN сервер с ID {server_id} не найден.")
return jsonify({"error": "Server not found"}), 404
@app.route('/servers', methods=['GET'])
async def list_servers():
server_list = await servers.find().to_list(length=1000)
for server in server_list:
server["_id"] = str(server["_id"])
logger.debug(f"Найдено {len(server_list)} VPN серверов.")
return jsonify(server_list)
@app.route('/shutdown', methods=['POST'])
async def close_connection():
client.close()
logger.debug("Подключение к MongoDB закрыто.")
return jsonify({"message": "Connection closed"}), 200
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(app.run(debug=True))

191
app/postgresql1.py Normal file
View File

@@ -0,0 +1,191 @@
from flask import Flask, request, jsonify
from databases.model import User, Subscription, Transaction
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
app = Flask(__name__)
# Настройка логирования
logger = logging.getLogger(__name__)
mongo_repo = MongoDBRepository()
# Генератор сессий передаётся извне (например, через dependency injection)
session_generator = None
def generate_string(length):
"""
Генерирует случайную строку заданной длины.
"""
characters = string.ascii_lowercase + string.digits
return ''.join(random.choices(characters, k=length))
@app.route('/create_user', methods=['POST'])
def create_user():
telegram_id = request.json.get('telegram_id')
if not telegram_id:
return jsonify({'error': 'Telegram ID is required'}), 400
async def process():
async for session in session_generator():
try:
username = 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 jsonify({'user': new_user.id, 'username': new_user.username}), 201
return jsonify({'user': user.id, 'username': user.username}), 200
except SQLAlchemyError as e:
logger.error(f"Ошибка при создании пользователя {telegram_id}: {e}")
await session.rollback()
return jsonify({'error': 'Internal server error'}), 500
return asyncio.run(process())
@app.route('/get_user/<int:telegram_id>', methods=['GET'])
def get_user_by_telegram_id(telegram_id):
async def process():
async for session in session_generator():
try:
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
user = result.scalars().first()
if user:
return jsonify({'id': user.id, 'username': user.username, 'balance': user.balance}), 200
return jsonify({'error': 'User not found'}), 404
except SQLAlchemyError as e:
logger.error(f"Ошибка при получении пользователя {telegram_id}: {e}")
return jsonify({'error': 'Internal server error'}), 500
return asyncio.run(process())
@app.route('/update_balance', methods=['POST'])
def update_balance():
data = request.json
telegram_id = data.get('telegram_id')
amount = data.get('amount')
if not telegram_id or amount is None:
return jsonify({'error': 'Telegram ID and amount are required'}), 400
async def process():
async for session in 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)
transaction = Transaction(user_id=user.id, amount=amount)
session.add(transaction)
await session.commit()
return jsonify({'balance': user.balance}), 200
return jsonify({'error': 'User not found'}), 404
except SQLAlchemyError as e:
logger.error(f"Ошибка при обновлении баланса: {e}")
await session.rollback()
return jsonify({'error': 'Internal server error'}), 500
return asyncio.run(process())
@app.route('/buy_subscription', methods=['POST'])
def buy_subscription():
data = request.json
telegram_id = data.get('telegram_id')
plan_id = data.get('plan_id')
if not telegram_id or not plan_id:
return jsonify({'error': 'Telegram ID and Plan ID are required'}), 400
async def process():
async for session in session_generator():
try:
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
user = result.scalars().first()
if not user:
return jsonify({'error': 'User not found'}), 404
plan = await mongo_repo.get_subscription_plan(plan_id)
if not plan:
return jsonify({'error': 'Plan not found'}), 404
cost = int(plan['price'])
if user.balance >= cost:
user.balance -= cost
expiry_date = datetime.utcnow() + relativedelta(months=plan['duration_months'])
server = await mongo_repo.get_server_with_least_clients()
new_subscription = Subscription(user_id=user.id, vpn_server_id=str(server['server']['name']),
plan=plan_id, expiry_date=expiry_date)
session.add(new_subscription)
await session.commit()
return jsonify({'message': 'Subscription purchased successfully'}), 200
return jsonify({'error': 'Insufficient funds'}), 400
except SQLAlchemyError as e:
logger.error(f"Ошибка при покупке подписки {plan_id} для пользователя {telegram_id}: {e}")
await session.rollback()
return jsonify({'error': 'Internal server error'}), 500
return asyncio.run(process())
@app.route('/add_to_server', methods=['POST'])
def add_to_server():
data = request.json
telegram_id = data.get('telegram_id')
if not telegram_id:
return jsonify({'error': 'Telegram ID is required'}), 400
async def process():
async for session in session_generator():
try:
result = await session.execute(select(Subscription).join(User).where(User.telegram_id == int(telegram_id)))
user_sub = result.scalars().first()
if not user_sub:
logger.error(f"Не удалось найти подписку для пользователя с Telegram ID {telegram_id}.")
return jsonify({'error': 'Subscription not found'}), 404
user_result = await session.execute(select(User).where(User.telegram_id == telegram_id))
user = user_result.scalars().first()
server = await mongo_repo.get_server(user_sub.vpn_server_id)
if not server:
logger.error(f"Не удалось найти сервер с ID {user_sub.vpn_server_id}.")
return jsonify({'error': 'Server not found'}), 404
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, logger)
expiry_date_iso = user_sub.expiry_date.isoformat()
response = await panel.add_client(user.id, expiry_date_iso, user.username)
if response == "OK":
logger.info(f"Клиент {telegram_id} успешно добавлен на сервер.")
return jsonify({'message': 'Client added successfully'}), 200
else:
logger.error(f"Ошибка при добавлении клиента {telegram_id} на сервер: {response}")
return jsonify({'error': 'Failed to add client to server'}), 500
except Exception as e:
logger.error(f"Ошибка при установке на сервер для пользователя {telegram_id}: {e}")
return jsonify({'error': 'Internal server error'}), 500
return asyncio.run(process())
if __name__ == '__main__':
app.run(debug=True)

0
app/routes/__init__.py Normal file
View File

View File

View File

0
app/services/__init__.py Normal file
View File

View File

View File

0
config.py Normal file
View File

0
docker-compose.yml Normal file
View File

0
instance/config.py Normal file
View File

40
requirements.txt Normal file
View File

@@ -0,0 +1,40 @@
aiofiles==24.1.0
aiogram==3.15.0
aiohappyeyeballs==2.4.3
aiohttp==3.10.11
aiosignal==1.3.1
annotated-types==0.7.0
anyio==4.6.0
asyncpg==0.30.0
attrs==24.2.0
certifi==2024.8.30
charset-normalizer==3.4.0
DateTime==5.5
dnspython==2.7.0
frozenlist==1.5.0
greenlet==3.1.1
h11==0.14.0
httpcore==1.0.6
httpx==0.27.2
idna==3.10
magic-filter==1.0.12
motor==3.6.0
multidict==6.1.0
propcache==0.2.0
psycopg2-binary==2.9.10
pydantic==2.9.2
pydantic_core==2.23.4
pymongo==4.9.2
python-dateutil==2.9.0.post0
python-telegram-bot==21.6
pytz==2024.2
requests==2.32.3
setuptools==75.1.0
six==1.16.0
sniffio==1.3.1
SQLAlchemy==2.0.35
telegram==0.0.1
typing_extensions==4.12.2
urllib3==2.2.3
yarl==1.17.2
zope.interface==7.1.0

0
run.py Normal file
View File

0
tests/__init__.py Normal file
View File

0
tests/test_auth.py Normal file
View File

0
tests/test_users.py Normal file
View File