Переделал нахуй всё
This commit is contained in:
14
Dockerfile
Normal file
14
Dockerfile
Normal 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"]
|
||||||
28
Middleware/anti_spam_middleware.py
Normal file
28
Middleware/anti_spam_middleware.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||||
|
from aiogram.types import TelegramObject
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class AntiSpamMiddleware(BaseMiddleware):
|
||||||
|
def __init__(self, rate_limit=1):
|
||||||
|
super().__init__()
|
||||||
|
self.rate_limit = rate_limit
|
||||||
|
self.users = {}
|
||||||
|
|
||||||
|
async def __call__(self, handler, event: TelegramObject, data: dict):
|
||||||
|
user_id = None
|
||||||
|
|
||||||
|
# Определяем user_id для события
|
||||||
|
if hasattr(event, "from_user") and event.from_user:
|
||||||
|
user_id = event.from_user.id
|
||||||
|
|
||||||
|
if user_id:
|
||||||
|
now = time.time()
|
||||||
|
last_time = self.users.get(user_id)
|
||||||
|
if last_time and now - last_time < self.rate_limit:
|
||||||
|
# Если сообщение отправлено слишком быстро, игнорируем
|
||||||
|
return
|
||||||
|
self.users[user_id] = now
|
||||||
|
|
||||||
|
# Если прошло достаточно времени, продолжаем обработку
|
||||||
|
return await handler(event, data)
|
||||||
139
bot.py
139
bot.py
@@ -1,139 +0,0 @@
|
|||||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
|
||||||
from telegram.ext import Application, CallbackQueryHandler, ContextTypes, MessageHandler, filters,CommandHandler
|
|
||||||
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:
|
|
||||||
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:
|
|
||||||
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:
|
|
||||||
buttons = [("Главное меню", "start")]
|
|
||||||
await send_loading_message(update, context, 'Игорь чё нить напишет, я продублирую', create_keyboard(buttons))
|
|
||||||
|
|
||||||
# Функция для отображения поддержки
|
|
||||||
async def support(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
buttons = [("Главное меню", "start"), ("Написать", "sup")]
|
|
||||||
await send_loading_message(update, context, 'Для связи с поддержкой выберите "Написать" и изложите проблему в одном сообщении.', create_keyboard(buttons))
|
|
||||||
|
|
||||||
# Функция для пополнения баланса
|
|
||||||
async def pop_up(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
buttons = [("Главное меню", "start")]
|
|
||||||
await send_loading_message(update, context, 'Когда-нибудь эта функция заработает', create_keyboard(buttons))
|
|
||||||
|
|
||||||
# Функция для покупки подписки
|
|
||||||
async def buy_subscription(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
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:
|
|
||||||
buttons = [("Главное меню", "start")]
|
|
||||||
text = 'У вас уже приобретена подписка'
|
|
||||||
|
|
||||||
await send_loading_message(update, context, text, create_keyboard(buttons))
|
|
||||||
|
|
||||||
# Функция для отображения FAQ
|
|
||||||
async def faq(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
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)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if data == 'account':
|
|
||||||
await personal_account(update, context)
|
|
||||||
elif data == 'start':
|
|
||||||
await start(update, context)
|
|
||||||
elif data == 'about':
|
|
||||||
await about(update, context)
|
|
||||||
elif data == 'support':
|
|
||||||
await support(update, context)
|
|
||||||
elif data == 'sup':
|
|
||||||
context.user_data['awaiting_input'] = True
|
|
||||||
elif data == 'pop_up':
|
|
||||||
await pop_up(update, context)
|
|
||||||
elif data == 'buy_tarif':
|
|
||||||
await buy_subscription(update, context)
|
|
||||||
elif data == 'faq':
|
|
||||||
await faq(update, context)
|
|
||||||
elif data == 'payment_history':
|
|
||||||
await active_sub(update, context)
|
|
||||||
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"Ошибка при обработке запроса пользователя {tgid}: {e}")
|
|
||||||
await query.message.reply_text("Произошла ошибка. Пожалуйста, попробуйте снова.")
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
init_db()
|
|
||||||
application = Application.builder().token(config['token']).build()
|
|
||||||
|
|
||||||
application.add_handler(CommandHandler("start", start))
|
|
||||||
|
|
||||||
application.add_handler(CallbackQueryHandler(button_handler))
|
|
||||||
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, sup))
|
|
||||||
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
74
databases/db_config.py
Normal file
74
databases/db_config.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
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
|
||||||
|
from utils.LogCon import setup_logger, load_config
|
||||||
|
|
||||||
|
# Загружаем конфигурацию
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
# Настройки PostgreSQL
|
||||||
|
postgres_user = config['postgreSQL']['username']
|
||||||
|
postgres_password = config['postgreSQL']['password_DB']
|
||||||
|
postgres_host = "postgres" # Хост для PostgreSQL в Docker
|
||||||
|
POSTGRES_DSN = f"postgresql+asyncpg://{postgres_user}:{postgres_password}@{postgres_host}:5432/bot_db"
|
||||||
|
|
||||||
|
# Создание движка для PostgreSQL
|
||||||
|
postgres_engine = create_async_engine(POSTGRES_DSN, echo=False)
|
||||||
|
AsyncSessionLocal = sessionmaker(bind=postgres_engine, class_=AsyncSession, expire_on_commit=False)
|
||||||
|
|
||||||
|
# Настройки MongoDB
|
||||||
|
mongodb_user = config['mongodb']['mongodb_username']
|
||||||
|
mongodb_password = config['mongodb']['mongodb_password']
|
||||||
|
mongodb_host = "mongodb" # Хост для MongoDB в Docker
|
||||||
|
mongodb_uri = f"mongodb://{mongodb_user}:{mongodb_password}@{mongodb_host}:27017"
|
||||||
|
database_name = config['mongodb']['database_name']
|
||||||
|
|
||||||
|
# Создание клиента MongoDB
|
||||||
|
mongo_client = AsyncIOMotorClient(mongodb_uri)
|
||||||
|
mongo_db = mongo_client[database_name]
|
||||||
|
|
||||||
|
# Инициализация PostgreSQL
|
||||||
|
async def init_postgresql():
|
||||||
|
"""
|
||||||
|
Инициализация подключения к PostgreSQL.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
async with postgres_engine.begin() as conn:
|
||||||
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
|
print("PostgreSQL connected.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to connect to PostgreSQL: {e}")
|
||||||
|
|
||||||
|
# Инициализация MongoDB
|
||||||
|
async def init_mongodb():
|
||||||
|
"""
|
||||||
|
Проверка подключения к MongoDB.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Проверяем подключение к MongoDB
|
||||||
|
await mongo_client.admin.command("ping")
|
||||||
|
print("MongoDB connected.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to connect to MongoDB: {e}")
|
||||||
|
|
||||||
|
# Получение сессии PostgreSQL
|
||||||
|
async def get_postgres_session():
|
||||||
|
"""
|
||||||
|
Асинхронный генератор сессий PostgreSQL.
|
||||||
|
"""
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
yield session
|
||||||
|
|
||||||
|
# Закрытие соединений
|
||||||
|
async def close_connections():
|
||||||
|
"""
|
||||||
|
Закрытие всех соединений с базами данных.
|
||||||
|
"""
|
||||||
|
# Закрытие PostgreSQL
|
||||||
|
await postgres_engine.dispose()
|
||||||
|
print("PostgreSQL connection closed.")
|
||||||
|
|
||||||
|
# Закрытие MongoDB
|
||||||
|
mongo_client.close()
|
||||||
|
print("MongoDB connection closed.")
|
||||||
60
databases/model.py
Normal file
60
databases/model.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from sqlalchemy import Column, String, Numeric, DateTime, Boolean, ForeignKey, Integer
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||||
|
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
|
||||||
|
from datetime import datetime
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
def generate_uuid():
|
||||||
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
"""Пользователи"""
|
||||||
|
class User(Base):
|
||||||
|
__tablename__ = 'users'
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True, default=generate_uuid)
|
||||||
|
telegram_id = Column(Integer, unique=True, nullable=False)
|
||||||
|
username = Column(String)
|
||||||
|
balance = Column(Numeric(10, 2), default=0.0)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
subscriptions = relationship("Subscription", back_populates="user")
|
||||||
|
transactions = relationship("Transaction", back_populates="user")
|
||||||
|
admins = relationship("Administrators", back_populates="user")
|
||||||
|
|
||||||
|
"""Подписки"""
|
||||||
|
class Subscription(Base):
|
||||||
|
__tablename__ = 'subscriptions'
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True, default=generate_uuid)
|
||||||
|
user_id = Column(String, ForeignKey('users.id'))
|
||||||
|
vpn_server_id = Column(String)
|
||||||
|
plan = Column(String)
|
||||||
|
expiry_date = Column(DateTime)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
user = relationship("User", back_populates="subscriptions")
|
||||||
|
|
||||||
|
"""Транзакции"""
|
||||||
|
class Transaction(Base):
|
||||||
|
__tablename__ = 'transactions'
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True, default=generate_uuid)
|
||||||
|
user_id = Column(String, ForeignKey('users.id'))
|
||||||
|
amount = Column(Numeric(10, 2))
|
||||||
|
transaction_type = Column(String)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
user = relationship("User", back_populates="transactions")
|
||||||
|
|
||||||
|
"""Администраторы"""
|
||||||
|
class Administrators(Base):
|
||||||
|
__tablename__ = 'admins'
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True, default=generate_uuid)
|
||||||
|
user_id = Column(String, ForeignKey('users.id'))
|
||||||
|
|
||||||
|
user = relationship("User", back_populates="admins")
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
import json
|
from utils.LogCon import setup_logger, load_config
|
||||||
|
|
||||||
class VPNServerRepository:
|
class MongoDBRepository:
|
||||||
def __init__(self, config_path="config.json"):
|
def __init__(self, config_path="config.json"):
|
||||||
with open(config_path, "r") as file:
|
self.config = load_config()
|
||||||
config = json.load(file)
|
|
||||||
self.client = MongoClient(config["mongodb_uri"])
|
self.client = MongoClient(config["mongodb_uri"])
|
||||||
self.db = self.client[config["database_name"]]
|
self.db = self.client[config["database_name"]]
|
||||||
self.collection = self.db["vpn_servers"]
|
self.collection = self.db["vpn_servers"]
|
||||||
105
databases/postgresql.py
Normal file
105
databases/postgresql.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
from databases.model import User, Subscription, Transaction, Administrators
|
||||||
|
from sqlalchemy.future import select
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy import desc
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseManager:
|
||||||
|
def __init__(self, session_generator):
|
||||||
|
"""
|
||||||
|
Инициализация с асинхронным генератором сессий (например, get_postgres_session).
|
||||||
|
"""
|
||||||
|
self.session_generator = session_generator
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def create_user(self, telegram_id):
|
||||||
|
"""
|
||||||
|
Создаёт нового пользователя, если его нет.
|
||||||
|
"""
|
||||||
|
async for session in self.session_generator():
|
||||||
|
try:
|
||||||
|
username = self.generate_string(6)
|
||||||
|
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
|
||||||
|
user = result.scalars().first()
|
||||||
|
if not user:
|
||||||
|
new_user = User(telegram_id=telegram_id, username=username)
|
||||||
|
session.add(new_user)
|
||||||
|
await session.commit()
|
||||||
|
return new_user
|
||||||
|
return user
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
self.logger.error(f"Ошибка при создании пользователя {telegram_id}: {e}")
|
||||||
|
await session.rollback()
|
||||||
|
return "ERROR"
|
||||||
|
|
||||||
|
|
||||||
|
async def get_user_by_telegram_id(self, telegram_id: int):
|
||||||
|
"""
|
||||||
|
Возвращает пользователя по Telegram ID.
|
||||||
|
"""
|
||||||
|
async for session in self.session_generator():
|
||||||
|
try:
|
||||||
|
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
|
||||||
|
return result.scalars().first()
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
self.logger.error(f"Ошибка при получении пользователя {telegram_id}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def add_transaction(self, user_id: int, amount: float):
|
||||||
|
"""
|
||||||
|
Добавляет транзакцию для пользователя.
|
||||||
|
"""
|
||||||
|
async for session in self.session_generator():
|
||||||
|
try:
|
||||||
|
transaction = Transaction(user_id=user_id, amount=amount)
|
||||||
|
session.add(transaction)
|
||||||
|
await session.commit()
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
self.logger.error(f"Ошибка добавления транзакции для пользователя {user_id}: {e}")
|
||||||
|
await session.rollback()
|
||||||
|
|
||||||
|
async def update_balance(self, telegram_id: int, amount: float):
|
||||||
|
"""
|
||||||
|
Обновляет баланс пользователя и добавляет транзакцию.
|
||||||
|
"""
|
||||||
|
async for session in self.session_generator():
|
||||||
|
try:
|
||||||
|
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
|
||||||
|
user = result.scalars().first()
|
||||||
|
if user:
|
||||||
|
user.balance = amount
|
||||||
|
await self.add_transaction(user.id, amount)
|
||||||
|
await session.commit()
|
||||||
|
else:
|
||||||
|
self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.")
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
self.logger.error(f"Ошибка при обновлении баланса: {e}")
|
||||||
|
await session.rollback()
|
||||||
|
|
||||||
|
async def last_subscription(self, user_id: int):
|
||||||
|
"""
|
||||||
|
Возвращает последнюю подписку пользователя.
|
||||||
|
"""
|
||||||
|
async for session in self.session_generator():
|
||||||
|
try:
|
||||||
|
result = await session.execute(
|
||||||
|
select(Subscription)
|
||||||
|
.where(Subscription.user_id == user_id)
|
||||||
|
.order_by(desc(Subscription.created_at))
|
||||||
|
)
|
||||||
|
print(result)
|
||||||
|
return result.scalars().all()
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
self.logger.error(f"Ошибка при получении последней подписки пользователя {user_id}: {e}")
|
||||||
|
return "ERROR"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_string(length):
|
||||||
|
"""
|
||||||
|
Генерирует случайную строку заданной длины.
|
||||||
|
"""
|
||||||
|
characters = string.ascii_lowercase + string.digits
|
||||||
|
return ''.join(random.choices(characters, k=length))
|
||||||
54
docker-compose.yml
Normal file
54
docker-compose.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bot_network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongo_data:
|
||||||
|
postgres_data:
|
||||||
|
logs_data:
|
||||||
|
|
||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
networks:
|
||||||
|
- bot_network
|
||||||
|
image: mongo:latest
|
||||||
|
container_name: mongodb
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: root
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: itOj4CE2miKR
|
||||||
|
volumes:
|
||||||
|
- mongo_data:/data/db
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
networks:
|
||||||
|
- bot_network
|
||||||
|
image: postgres:latest
|
||||||
|
container_name: postgres
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: AH3J9GSPBYOP
|
||||||
|
POSTGRES_PASSWORD: uPS9?y~mcu2
|
||||||
|
POSTGRES_DB: bot_db
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
bot:
|
||||||
|
networks:
|
||||||
|
- bot_network
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
container_name: telegram_bot
|
||||||
|
environment:
|
||||||
|
POSTGRES_URL: "postgresql://AH3J9GSPBYOP:uPS9?y~mcu2@postgres:5432/bot_db"
|
||||||
|
MONGO_URL: "mongodb://root:itOj4CE2miKR@mongodb:27017"
|
||||||
|
volumes:
|
||||||
|
- logs_data:/app/logs # Логи сохраняются в контейнере
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- mongodb
|
||||||
|
command: ["python", "main.py"] # Задаем явную команду запуска
|
||||||
178
handlers/handlers.py
Normal file
178
handlers/handlers.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
from aiogram import types, Dispatcher
|
||||||
|
from aiogram.filters import Command
|
||||||
|
from databases.postgresql import DatabaseManager
|
||||||
|
from databases.model import User, Subscription, Transaction, Administrators
|
||||||
|
from databases.db_config import get_postgres_session
|
||||||
|
from keyboard.keyboards import subhist_keyboard, popup_keyboard, main_keyboard, account_keyboard, buy_keyboard
|
||||||
|
|
||||||
|
# Инициализируем менеджер базы данных
|
||||||
|
db_manager = DatabaseManager(get_postgres_session)
|
||||||
|
|
||||||
|
async def popup_command(message: types.Message):
|
||||||
|
"""
|
||||||
|
Обработчик команды для отправки popup-сообщения.
|
||||||
|
"""
|
||||||
|
await message.answer("HAHAHHAHAHAHAHHAHA", reply_markup=popup_keyboard())
|
||||||
|
|
||||||
|
async def subhist_command(message: types.Message):
|
||||||
|
"""
|
||||||
|
Обработчик команды для отправки истории подписок.
|
||||||
|
"""
|
||||||
|
await message.answer("subhist", reply_markup=subhist_keyboard())
|
||||||
|
|
||||||
|
async def start_command(message: types.Message):
|
||||||
|
"""
|
||||||
|
Обработчик команды /start.
|
||||||
|
"""
|
||||||
|
await message.answer("Привет! Я ваш Telegram-бот.", reply_markup=main_keyboard())
|
||||||
|
|
||||||
|
async def profile_callback_handler(callback: types.CallbackQuery):
|
||||||
|
"""
|
||||||
|
Обработчик callback_query с data="profile".
|
||||||
|
"""
|
||||||
|
user = await db_manager.create_user(telegram_id=callback.from_user.id)
|
||||||
|
if user == "ERROR":
|
||||||
|
await callback.message.answer(
|
||||||
|
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
|
||||||
|
if user:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"Ваш профиль:\n"
|
||||||
|
f"👤 Username: {user.username}\n",
|
||||||
|
reply_markup=account_keyboard()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.edit_text("Вы еще не зарегистрированы.")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
async def balance_callback_handler(callback: types.CallbackQuery):
|
||||||
|
"""
|
||||||
|
Обработчик callback_query с data="balance".
|
||||||
|
"""
|
||||||
|
user = await db_manager.create_user(telegram_id=callback.from_user.id)
|
||||||
|
if user == "ERROR":
|
||||||
|
await callback.message.answer(
|
||||||
|
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
|
||||||
|
if user:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"Ваш баланс: {user.balance}",
|
||||||
|
reply_markup=buy_keyboard()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.edit_text("Вы еще не зарегистрированы.")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
async def popup_callback_handler(callback: types.CallbackQuery):
|
||||||
|
"""
|
||||||
|
Обработчик callback_query с data="profile".
|
||||||
|
"""
|
||||||
|
user = await db_manager.create_user(telegram_id=callback.from_user.id)
|
||||||
|
if user == "ERROR":
|
||||||
|
await callback.message.answer(
|
||||||
|
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
|
||||||
|
if user:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"Ты думал здесь что то будет?",
|
||||||
|
reply_markup=account_keyboard()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.edit_text("Вы еще не зарегистрированы.")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
async def subhist_callback_handler(callback: types.CallbackQuery):
|
||||||
|
"""
|
||||||
|
Обработчик callback_query с data="profile".
|
||||||
|
"""
|
||||||
|
subs = await db_manager.last_subscription(telegram_id=callback.from_user.id)
|
||||||
|
if subs == "ERROR":
|
||||||
|
await callback.message.answer(
|
||||||
|
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
if subs is None:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"Ты хули тут забыл, ты ж не покупаешь нихуя",
|
||||||
|
reply_markup=account_keyboard()
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
result = ""
|
||||||
|
count = 0
|
||||||
|
for sub in subs:
|
||||||
|
if count > 0:
|
||||||
|
result += f"Последняя подписка истекает: {sub.expiry_date}\n"
|
||||||
|
count += 1
|
||||||
|
result += f"{count}. Истекла {sub.expiry_date}"
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if subs:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
result,
|
||||||
|
reply_markup=account_keyboard()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.edit_text("Вы еще не зарегистрированы.")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def subhist_callback_handler(callback: types.CallbackQuery):
|
||||||
|
"""
|
||||||
|
Обработчик callback_query с data="subhist".
|
||||||
|
"""
|
||||||
|
user = await db_manager.get_user_by_telegram_id(telegram_id=callback.from_user.id)
|
||||||
|
subs = await db_manager.last_subscription(user.id)
|
||||||
|
if subs == "ERROR":
|
||||||
|
await callback.message.answer(
|
||||||
|
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
if not subs:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"Ты хули тут забыл, ты ж не покупаешь нихуя",
|
||||||
|
reply_markup=account_keyboard()
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
result = ""
|
||||||
|
count = 0
|
||||||
|
for sub in subs:
|
||||||
|
if count > 0:
|
||||||
|
result += f"Последняя подписка истекает: {sub.expiry_date}\n"
|
||||||
|
count += 1
|
||||||
|
result += f"{count}. Истекла {sub.expiry_date}"
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if sub:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
result,
|
||||||
|
reply_markup=account_keyboard()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.edit_text("Вы еще не зарегистрированы.")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
def register_handlers(dp: Dispatcher):
|
||||||
|
"""
|
||||||
|
Регистрация хэндлеров в диспетчере.
|
||||||
|
"""
|
||||||
|
dp.callback_query.register(popup_callback_handler, lambda c: c.data == "popup")
|
||||||
|
dp.callback_query.register(subhist_callback_handler, lambda c: c.data == "subs")
|
||||||
|
dp.callback_query.register(profile_callback_handler, lambda c: c.data == "profile")
|
||||||
|
dp.message.register(start_command, Command("start"))
|
||||||
|
dp.callback_query.register(balance_callback_handler, lambda c: c.data == "balance")
|
||||||
39
keyboard/keyboards.py
Normal file
39
keyboard/keyboards.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
from aiogram.types import InlineKeyboardButton
|
||||||
|
|
||||||
|
def main_keyboard():
|
||||||
|
# Создаём билдер для клавиатуры
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
# Добавляем кнопки
|
||||||
|
builder.button(text="Профиль", callback_data="profile")
|
||||||
|
builder.button(text="FAQ", callback_data="faq")
|
||||||
|
builder.button(text="О нас", callback_data="about")
|
||||||
|
|
||||||
|
# Строим клавиатуру и возвращаем её
|
||||||
|
return builder.as_markup()
|
||||||
|
|
||||||
|
def account_keyboard():
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="Баланс", callback_data="balance")
|
||||||
|
builder.button(text="Приобрести подписку", callback_data="buy_subscription")
|
||||||
|
builder.button(text="Руководство по подключению", callback_data="guide")
|
||||||
|
return builder.as_markup()
|
||||||
|
|
||||||
|
def buy_keyboard():
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="Подписки", callback_data="subs")
|
||||||
|
builder.button(text="О тарифах", callback_data="about_tarifs")
|
||||||
|
builder.button(text="Назад", callback_data="profile")
|
||||||
|
return builder.as_markup()
|
||||||
|
|
||||||
|
def subhist_keyboard():
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="Назад", callback_data="profile")
|
||||||
|
return builder.as_markup()
|
||||||
|
|
||||||
|
def popup_keyboard():
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="Хуй знает что здесь", callback_data="unknown")
|
||||||
|
builder.button(text="Назад", callback_data="profile")
|
||||||
|
return builder.as_markup()
|
||||||
48
main.py
Normal file
48
main.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import asyncio
|
||||||
|
from aiogram import Bot, Dispatcher
|
||||||
|
from databases.db_config import init_postgresql, init_mongodb, close_connections
|
||||||
|
from aiogram.types import BotCommand
|
||||||
|
from utils.LogCon import setup_logger, load_config
|
||||||
|
from Middleware.anti_spam_middleware import AntiSpamMiddleware
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
setup_logger()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
BOT_TOKEN = load_config()['token']
|
||||||
|
|
||||||
|
bot = Bot(token=BOT_TOKEN)
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
dp.message.middleware(AntiSpamMiddleware(rate_limit=1))
|
||||||
|
|
||||||
|
|
||||||
|
async def set_commands():
|
||||||
|
commands = [
|
||||||
|
BotCommand(command="/start", description="Запустить бота"),
|
||||||
|
]
|
||||||
|
await bot.set_my_commands(commands)
|
||||||
|
|
||||||
|
async def on_startup():
|
||||||
|
await init_mongodb()
|
||||||
|
await set_commands()
|
||||||
|
print("Бот запущен!")
|
||||||
|
|
||||||
|
async def on_shutdown():
|
||||||
|
await close_connections()
|
||||||
|
await bot.session.close()
|
||||||
|
print("Бот остановлен.")
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
from handlers.handlers import register_handlers
|
||||||
|
register_handlers(dp)
|
||||||
|
await init_postgresql() # Убедитесь, что таблицы создаются здесь
|
||||||
|
await on_startup()
|
||||||
|
try:
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
finally:
|
||||||
|
await on_shutdown()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
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
|
|
||||||
from datetime import datetime
|
|
||||||
import json
|
|
||||||
import uuid
|
|
||||||
with open('config.json', 'r') as file : config = json.load(file)
|
|
||||||
|
|
||||||
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")
|
|
||||||
requests = relationship("Requests", 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, 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")
|
|
||||||
|
|
||||||
#Транзакции
|
|
||||||
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")
|
|
||||||
|
|
||||||
class Requests(Base):
|
|
||||||
__tablename__ = 'requests'
|
|
||||||
|
|
||||||
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)
|
|
||||||
content = Column(String)
|
|
||||||
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(Boolean,default=False)
|
|
||||||
user = relationship("User",back_populates="admins")
|
|
||||||
|
|
||||||
# Настройка подключения к базе данных
|
|
||||||
DATABASE_URL = f"postgresql://{config['username']}:{config['password_DB']}@localhost/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()
|
|
||||||
|
|
||||||
40
requirements.txt
Normal file
40
requirements.txt
Normal 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
|
||||||
@@ -8,15 +8,15 @@ from datetime import datetime
|
|||||||
from db import get_db_session
|
from db import get_db_session
|
||||||
from panel import PanelInteraction
|
from panel import PanelInteraction
|
||||||
|
|
||||||
|
from utils.LogCon import setup_logger, load_config
|
||||||
|
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
def generate_random_string(length=8):
|
def generate_random_string(length=8):
|
||||||
characters = string.ascii_letters + string.digits
|
characters = string.ascii_letters + string.digits
|
||||||
return ''.join(secrets.choice(characters) for _ in range(length))
|
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):
|
||||||
@@ -95,21 +95,21 @@ class UserService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not server:
|
if not server:
|
||||||
self.logger.error("Нет доступных VPN серверов.")
|
self.logger.error("Error: 120")
|
||||||
return "120"
|
return "120"
|
||||||
|
|
||||||
# Рассчитываем дату окончания подписки
|
# Рассчитываем дату окончания подписки
|
||||||
expiry_ = datetime.now() + relativedelta(months=expiry_duration)
|
expiry_ = datetime.utcnow() + relativedelta(months=expiry_duration)
|
||||||
self.logger.info(f"Создание подписки для пользователя {user.id} на сервере {server.id} с планом {plan} до {expiry_}")
|
self.logger.info(f"Create subscribe to {user.id} on server {server.id} with plan {plan} until {expiry_}")
|
||||||
|
|
||||||
new_subscription = Subscription(user_id=user.id, vpn_server_id=server.id, plan=plan, expiry_date=expiry_)
|
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}")
|
self.logger.info(f"Subscribe successfully created for {user.id}")
|
||||||
return "OK"
|
return "OK"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка в установке тарифа: {e}")
|
self.logger.error(f"Error with created subscribe: {e}")
|
||||||
return "Ошибка"
|
return "Ошибка"
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
@@ -119,12 +119,12 @@ class UserService:
|
|||||||
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 not user:
|
if not user:
|
||||||
self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.")
|
self.logger.error(f"User with Telegram ID {telegram_id} not found.")
|
||||||
return "error"
|
return "error"
|
||||||
|
|
||||||
current_plan = config['subscription_templates'].get(plan)
|
current_plan = config['subscription_templates'].get(plan)
|
||||||
if not current_plan:
|
if not current_plan:
|
||||||
self.logger.error(f"Тариф {plan} не найден в шаблонах.")
|
self.logger.error(f"Tarif {plan} not found.")
|
||||||
return "error"
|
return "error"
|
||||||
|
|
||||||
cost = current_plan['cost']
|
cost = current_plan['cost']
|
||||||
@@ -141,11 +141,11 @@ class UserService:
|
|||||||
else:
|
else:
|
||||||
return "ERROR " + result
|
return "ERROR " + result
|
||||||
|
|
||||||
self.logger.error(f"Недостаточно средств на счету пользователя {telegram_id} для тарифа {plan}.")
|
self.logger.error(f"Nt enough money {telegram_id} for {plan}.")
|
||||||
return 100
|
return 100
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка при покупке тарифа для пользователя {telegram_id}: {e}")
|
self.logger.error(f"Error with buying sub {telegram_id}: {e}")
|
||||||
session.rollback()
|
session.rollback()
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import logging
|
import logging
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
def setup_logger():
|
def setup_logger():
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
handler = TimedRotatingFileHandler(
|
handler = TimedRotatingFileHandler(
|
||||||
"/logs/app.log",
|
"logs/app.log",
|
||||||
when="midnight",
|
when="midnight",
|
||||||
interval=1,
|
interval=1,
|
||||||
backupCount=7,
|
backupCount=7,
|
||||||
@@ -19,3 +23,14 @@ def setup_logger():
|
|||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(config_path='config/config.json'):
|
||||||
|
"""
|
||||||
|
Загрузка конфигурации из JSON файла.
|
||||||
|
"""
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
raise FileNotFoundError(f"Конфигурационный файл не найден: {config_path}")
|
||||||
|
|
||||||
|
with open(config_path, 'r') as file:
|
||||||
|
return json.load(file)
|
||||||
@@ -3,11 +3,11 @@ import uuid
|
|||||||
import string
|
import string
|
||||||
import secrets
|
import secrets
|
||||||
import json
|
import json
|
||||||
from logger_config import setup_logger
|
from utils.LogCon import setup_logger, load_config
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
with open('config.json', 'r') as file : config = json.load(file)
|
config = load_config()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -31,40 +31,48 @@ class PanelInteraction:
|
|||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
login_url = self.base_url + "/login"
|
login_url = self.base_url + "/login"
|
||||||
self.logger.info(f"Login URL : {login_url}")
|
self.logger.info(f"Attempting to login at: {login_url}")
|
||||||
response = requests.post(login_url, data=self.login_data, verify=False)
|
try:
|
||||||
if response.status_code == 200:
|
response = requests.post(login_url, data=self.login_data, verify=False, timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
session_id = response.cookies.get("3x-ui")
|
session_id = response.cookies.get("3x-ui")
|
||||||
|
if session_id:
|
||||||
return session_id
|
return session_id
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"Login failed: {response.status_code}")
|
self.logger.error(f"Login failed: {response.status_code}")
|
||||||
|
self.logger.debug(f"Response content: {response.text}")
|
||||||
|
return None
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.error(f"Login request failed: {e}")
|
||||||
return None
|
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}"
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, headers=self.headers, verify=False)
|
response = requests.get(url, headers=self.headers, verify=False, timeout=10)
|
||||||
if response.status_code == 200:
|
response.raise_for_status()
|
||||||
|
if response:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"Failed to get inbound info: {response.status_code}")
|
self.logger.error(f"Failed to get inbound info: {response.status_code}")
|
||||||
self.logger.debug("Response:", response.text)
|
self.logger.debug("Response:", response.text)
|
||||||
return None
|
return None
|
||||||
finally:
|
except requests.RequestException as e:
|
||||||
self.logger.info("Finished attempting to get inbound info.")
|
self.logger.error(f"Get inbound request failed: {e}")
|
||||||
|
|
||||||
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}"
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, headers=self.headers, verify=False)
|
response = requests.get(url, headers=self.headers, verify=False, timeout=10)
|
||||||
if response.status_code == 200:
|
response.raise_for_status()
|
||||||
|
if response:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"Failed to get client traffic: {response.status_code}")
|
self.logger.error(f"Failed to get client traffic: {response.status_code}")
|
||||||
self.logger.debug("Response:", response.text)
|
self.logger.debug("Response:", response.text)
|
||||||
return None
|
return None
|
||||||
finally:
|
except requests.RequestException as e:
|
||||||
self.logger.info("Finished attempting to get client traffic.")
|
self.loggin.error(f"Get client request failed: {e}")
|
||||||
|
|
||||||
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"
|
||||||
@@ -88,12 +96,13 @@ class PanelInteraction:
|
|||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=self.headers, json=update_data, verify=False)
|
response = requests.post(url, headers=self.headers, json=update_data, verify=False)
|
||||||
if response.status_code == 200:
|
response.raise_for_status()
|
||||||
|
if response:
|
||||||
self.logger.debug("Client expiry time updated successfully.")
|
self.logger.debug("Client expiry time updated successfully.")
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"Failed to update client: {response.status_code} {response.text}")
|
self.logger.error(f"Failed to update client: {response.status_code}, {response.text}")
|
||||||
finally:
|
except requests.RequestException as e:
|
||||||
self.logger.info("Finished attempting to update client expiry.")
|
self.logger.error(f"Update client request failed: {e}")
|
||||||
|
|
||||||
def add_client(self, inbound_id, expiry_date,email):
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user