From 379da6cbba33f190f0edf1da0ea10559bf2a4679 Mon Sep 17 00:00:00 2001 From: Disledg Date: Tue, 24 Dec 2024 16:43:55 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=D1=82=D0=B8=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=B0=20=D0=B1=D0=B5=D0=BA=D0=B5=D0=BD=D0=B4=20?= =?UTF-8?q?rest=20api=20=D0=B8=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D1=82=D0=B0=D0=BA=20=D0=B6=D0=B5=20=D0=B8=D0=B7=20=D0=B7?= =?UTF-8?q?=D0=B0=20=D0=B1=D0=B5=D0=BA=D0=B5=D0=BD=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/Dockerfile => Dockerfile | 0 databases/db_config.py | 65 ----- databases/mongodb.py | 103 -------- databases/postgresql.py | 211 ---------------- docker-compose.yml | 76 ++++++ docker/docker-compose.db.yml | 37 --- docker/docker-compose.yml | 27 -- handlers/handlers.py | 181 +++++--------- .../__pycache__/db_config.cpython-312.pyc | Bin .../__pycache__/postgresql.cpython-312.pyc | Bin instences/config.py | 4 + {databases => instences}/model.py | 0 keyboard/keyboards.py | 231 ++++++++---------- main.py | 9 - 14 files changed, 245 insertions(+), 699 deletions(-) rename docker/Dockerfile => Dockerfile (100%) delete mode 100644 databases/db_config.py delete mode 100644 databases/mongodb.py delete mode 100644 databases/postgresql.py create mode 100644 docker-compose.yml delete mode 100644 docker/docker-compose.db.yml delete mode 100644 docker/docker-compose.yml rename {databases => instences}/__pycache__/db_config.cpython-312.pyc (100%) rename {databases => instences}/__pycache__/postgresql.cpython-312.pyc (100%) create mode 100644 instences/config.py rename {databases => instences}/model.py (100%) diff --git a/docker/Dockerfile b/Dockerfile similarity index 100% rename from docker/Dockerfile rename to Dockerfile diff --git a/databases/db_config.py b/databases/db_config.py deleted file mode 100644 index baa3d55..0000000 --- a/databases/db_config.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -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 - -# Настройки 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] - -# Инициализация 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.") diff --git a/databases/mongodb.py b/databases/mongodb.py deleted file mode 100644 index 7b06bb8..0000000 --- a/databases/mongodb.py +++ /dev/null @@ -1,103 +0,0 @@ -import os -from motor.motor_asyncio import AsyncIOMotorClient -import logging - - -class MongoDBRepository: - def __init__(self): - # Настройки MongoDB из переменных окружения - mongo_uri = os.getenv("MONGO_URL") - database_name = os.getenv("DB_NAME") - server_collection = os.getenv("SERVER_COLLECTION", "servers") - plan_collection = os.getenv("PLAN_COLLECTION", "plans") - - # Подключение к базе данных и коллекциям - self.client = AsyncIOMotorClient(mongo_uri) - self.db = self.client[database_name] - self.collection = self.db[server_collection] # Коллекция серверов - self.plans_collection = self.db[plan_collection] # Коллекция тарифных планов - self.logger = logging.getLogger(__name__) - - async def add_subscription_plan(self, plan_data): - """Добавляет новый тарифный план в коллекцию.""" - result = await self.plans_collection.insert_one(plan_data) - self.logger.debug(f"Тарифный план добавлен с ID: {result.inserted_id}") - return result.inserted_id - - async def get_subscription_plan(self, plan_name): - """Получает тарифный план по его имени.""" - plan = await self.plans_collection.find_one({"name": plan_name}) - if plan: - self.logger.debug(f"Найден тарифный план: {plan}") - else: - self.logger.error(f"Тарифный план {plan_name} не найден.") - return plan - - async def add_server(self, server_data): - """Добавляет новый VPN сервер в коллекцию.""" - result = await self.collection.insert_one(server_data) - self.logger.debug(f"VPN сервер добавлен с ID: {result.inserted_id}") - return result.inserted_id - - async def get_server(self, server_name: str): - """Получает сервер VPN по его ID.""" - server = await self.collection.find_one({"server.name": server_name}) - if server: - self.logger.debug(f"Найден VPN сервер: {server}") - else: - self.logger.debug(f"VPN сервер с ID {server_name} не найден.") - return server - - async def get_server_with_least_clients(self): - """Возвращает сервер с наименьшим количеством подключенных клиентов.""" - pipeline = [ - { - "$addFields": { - "current_clients": {"$size": {"$ifNull": ["$clients", []]}} - } - }, - { - "$sort": {"current_clients": 1} - }, - { - "$limit": 1 - } - ] - - result = await self.collection.aggregate(pipeline).to_list(length=1) - if result: - server = result[0] - self.logger.debug(f"Найден сервер с наименьшим количеством клиентов: {server}") - return server - else: - self.logger.debug("Не найдено серверов.") - return None - - async def update_server(self, server_id, update_data): - """Обновляет данные VPN сервера.""" - result = await self.collection.update_one({"_id": server_id}, {"$set": update_data}) - if result.matched_count > 0: - self.logger.debug(f"VPN сервер с ID {server_id} обновлен.") - else: - self.logger.debug(f"VPN сервер с ID {server_id} не найден.") - return result.matched_count > 0 - - async def delete_server(self, server_id): - """Удаляет VPN сервер по его ID.""" - result = await self.collection.delete_one({"_id": server_id}) - if result.deleted_count > 0: - self.logger.debug(f"VPN сервер с ID {server_id} удален.") - else: - self.logger.debug(f"VPN сервер с ID {server_id} не найден.") - return result.deleted_count > 0 - - async def list_servers(self): - """Возвращает список всех VPN серверов.""" - servers = await self.collection.find().to_list(length=1000) # Получить до 1000 серверов (можно настроить) - self.logger.debug(f"Найдено {len(servers)} VPN серверов.") - return servers - - async def close_connection(self): - """Закрывает подключение к базе данных MongoDB.""" - self.client.close() - self.logger.debug("Подключение к MongoDB закрыто.") diff --git a/databases/postgresql.py b/databases/postgresql.py deleted file mode 100644 index bde8e2e..0000000 --- a/databases/postgresql.py +++ /dev/null @@ -1,211 +0,0 @@ -from databases.model import User, Subscription, Transaction, Administrators -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 - -class DatabaseManager: - def __init__(self, session_generator): - """ - Инициализация с асинхронным генератором сессий (например, get_postgres_session). - """ - self.session_generator = session_generator - self.logger = logging.getLogger(__name__) - self.mongo_repo = MongoDBRepository() - - async def create_user(self, telegram_id: int): - """ - Создаёт нового пользователя, если его нет. - """ - async for session in self.session_generator(): - try: - username = self.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 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 += int(amount) - await self.add_transaction(user.id, amount) - await session.commit() - else: - self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.") - return "ERROR" - except SQLAlchemyError as e: - self.logger.error(f"Ошибка при обновлении баланса: {e}") - await session.rollback() - return "ERROR" - - 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)) - ) - return result.scalars().all() - except SQLAlchemyError as e: - self.logger.error(f"Ошибка при получении последней подписки пользователя {user_id}: {e}") - return "ERROR" - - async def last_transaction(self, user_id: int): - """ - Возвращает список транзакций пользователя. - """ - async for session in self.session_generator(): - try: - result = await session.execute( - select(Transaction) - .where(Transaction.user_id == user_id) - .order_by(desc(Transaction.created_at)) - ) - transactions = result.scalars().all() - return transactions - except SQLAlchemyError as e: - self.logger.error(f"Ошибка при получении транзакций пользователя {user_id}: {e}") - return "ERROR" - - async def buy_sub(self, telegram_id: str, plan_id: str): - async for session in self.session_generator(): - try: - result = await self.create_user(telegram_id) - if not result: - self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.") - return "ERROR" - - # Получение тарифного плана из MongoDB - plan = await self.mongo_repo.get_subscription_plan(plan_id) - if not plan: - self.logger.error(f"Тарифный план {plan_id} не найден.") - return "ERROR" - - # Проверка достаточности средств - cost = int(plan["price"]) - if result.balance < cost: - self.logger.error(f"Недостаточно средств у пользователя {telegram_id} для покупки плана {plan_id}.") - return "INSUFFICIENT_FUNDS" - - # Списываем средства - result.balance -= cost - - # Создаем подписку - expiry_date = datetime.utcnow() + relativedelta(months=plan["duration_months"]) - server = await self.mongo_repo.get_server_with_least_clients() - self.logger.info(f"Выбран сервер для подписки: {server}") - new_subscription = Subscription( - user_id=result.id, - vpn_server_id=str(server['server']["name"]), - plan=plan_id, - expiry_date=expiry_date - ) - session.add(new_subscription) - - # Попытка добавить пользователя на сервер - # Получаем информацию о пользователе - user = result # так как result уже содержит пользователя - if not user: - self.logger.error(f"Не удалось найти пользователя для добавления на сервер.") - await session.rollback() - return "ERROR" - - # Получаем сервер из MongoDB - server_data = await self.mongo_repo.get_server(new_subscription.vpn_server_id) - if not server_data: - self.logger.error(f"Не удалось найти сервер с ID {new_subscription.vpn_server_id}.") - await session.rollback() - return "ERROR" - - server_info = server_data['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, self.logger,server_info['certificate']['data']) - expiry_date_iso = new_subscription.expiry_date.isoformat() - - # Добавляем на сервер - response = await panel.add_client(user.id, expiry_date_iso, user.username) - - if response != "OK": - self.logger.error(f"Ошибка при добавлении клиента {telegram_id} на сервер: {response}") - # Если не получилось добавить на сервер, откатываем транзакцию - await session.rollback() - return "ERROR" - - # Если мы здесь - значит и подписка, и добавление на сервер успешны - await session.commit() - self.logger.info(f"Подписка успешно оформлена для пользователя {telegram_id} на план {plan_id} и клиент добавлен на сервер.") - return "OK" - - except SQLAlchemyError as e: - self.logger.error(f"Ошибка при покупке подписки {plan_id} для пользователя {telegram_id}: {e}") - await session.rollback() - return "ERROR" - except Exception as e: - self.logger.error(f"Непредвиденная ошибка: {e}") - await session.rollback() - return "ERROR" - - - @staticmethod - def generate_string(length): - """ - Генерирует случайную строку заданной длины. - """ - characters = string.ascii_lowercase + string.digits - return ''.join(random.choices(characters, k=length)) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5da2fcf --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,76 @@ + +networks: + bot_network: + driver: bridge + +volumes: + mongo_data: + postgres_data: + logs_data: + +services: + # MongoDB + 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 + + # PostgreSQL + 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 + + # Бэкенд (FastAPI) + backend: + networks: + - bot_network + build: + context: ./Flask-Backend-All + dockerfile: Dockerfile + container_name: backend + environment: + POSTGRES_URL: "postgresql+asyncpg://AH3J9GSPBYOP:uPS9?y~mcu2@postgres:5432/bot_db" + MONGO_URL: "mongodb://root:itOj4CE2miKR@mongodb:27017" + DB_NAME: "MongoDBSub&Ser" + SERVER_COLLECTION: "servers" + PLAN_COLLECTION: "plans" + BASE_URL: "http://backend:8000" + ports: + - "8000:8000" + depends_on: + - postgres + - mongodb + + # Telegram Bot + bot: + networks: + - bot_network + build: + context: ./Lark_VPN_Bot + dockerfile: Dockerfile + container_name: telegram_bot + environment: + BASE_URL: "http://backend:8000" + TOKEN: "8104061818:AAGJ6H1PTFmfJm-_mZqGv7EnHxGl4dZndnU" + volumes: + - logs_data:/app/logs + depends_on: + - backend diff --git a/docker/docker-compose.db.yml b/docker/docker-compose.db.yml deleted file mode 100644 index 3c5079b..0000000 --- a/docker/docker-compose.db.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: '3.8' - -networks: - bot_network: - driver: bridge - -volumes: - mongo_data: - postgres_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 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index eb0e09b..0000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,27 +0,0 @@ -version: '3.8' - -networks: - bot_network: - external: true - -volumes: - logs_data: - -services: - bot: - networks: - - bot_network - build: - context: .. - dockerfile: docker/Dockerfile - container_name: telegram_bot - environment: - TOKEN: "8104061818:AAGJ6H1PTFmfJm-_mZqGv7EnHxGl4dZndnU" - POSTGRES_URL: "postgresql+asyncpg://AH3J9GSPBYOP:uPS9?y~mcu2@postgres:5432/bot_db" - MONGO_URL: "mongodb://root:itOj4CE2miKR@mongodb:27017" - DB_NAME: "MongoDBSub&Ser" - SERVER_COLLECTION: "servers" - PLAN_COLLECTION: "plans" - volumes: - - logs_data:/app/logs - command: ["python", "main.py"] diff --git a/handlers/handlers.py b/handlers/handlers.py index bd965ed..8c887db 100644 --- a/handlers/handlers.py +++ b/handlers/handlers.py @@ -1,12 +1,17 @@ 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 +import aiohttp +from instences.config import BASE_URL_FASTAPI from keyboard.keyboards import subhist_keyboard,confirm_popup_keyboard,tarif_confirm_keyboard, popup_keyboard, main_keyboard,faq_keyboard, account_keyboard, buy_keyboard,balance_keyboard,guide_keyboard,tarif_Lark_keyboard,tarif_Lark_pro_keyboard,tranhist_keyboard # Инициализируем менеджер базы данных -db_manager = DatabaseManager(get_postgres_session) +async def call_api(method, endpoint, data=None): + async with aiohttp.ClientSession() as session: + url = f"{BASE_URL_FASTAPI}{endpoint}" + async with session.request(method, url, json=data) as response: + if response.status in {200, 201}: + return await response.json() + return "ERROR" async def popup_command(message: types.Message): """ @@ -48,46 +53,27 @@ async def start_callback_handler(callback: types.CallbackQuery): ) 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( - "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." - ) + user_data = await call_api("POST", "/user/create", {"telegram_id": callback.from_user.id}) + if not user_data: + await callback.message.answer("Произошла ошибка, попробуйте позже.") await callback.answer() return - if user: - text = f"""Ваш профиль:\nID: {user.username}\nБаланс: {user.balance}""" - await callback.message.edit_text( - text, - reply_markup=account_keyboard() - ) - else: - await callback.message.edit_text("Вы еще не зарегистрированы.") + text = f"Ваш профиль:\nID: {user_data['username']}\nБаланс: {user_data['balance']} ₽" + await callback.message.edit_text(text, reply_markup=account_keyboard()) 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( - "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." - ) + user_data = await call_api("GET", f"/user/{callback.from_user.id}") + if not user_data: + await callback.message.answer("Вы еще не зарегистрированы.") await callback.answer() return - if user: - await callback.message.edit_text( - f"Ваш баланс: {user.balance} ₽. Выберите сумму для пополнения 🐥", - reply_markup=balance_keyboard() - ) - else: - await callback.message.edit_text("Вы еще не зарегистрированы.") + await callback.message.edit_text( + f"Ваш баланс: {user_data['balance']} ₽. Выберите сумму для пополнения 🐥", + reply_markup=balance_keyboard() + ) await callback.answer() @@ -95,7 +81,7 @@ async def popup_callback_handler(callback: types.CallbackQuery): """ Обработчик callback_query с data="popup". """ - user = await db_manager.create_user(telegram_id=callback.from_user.id) + user = await call_api("POST", "/user/create", {"telegram_id": callback.from_user.id}) if user == "ERROR": await callback.message.answer( "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." @@ -113,68 +99,41 @@ async def popup_callback_handler(callback: types.CallbackQuery): await callback.answer() async def tranhist_callback_handler(callback: types.CallbackQuery): - """ - Обработчик callback_query с data="tranhist". - """ - user = await db_manager.create_user(callback.from_user.id) - trans = await db_manager.last_transaction(user.id) - if trans == "ERROR": - await callback.message.answer( - "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." - ) + user_data = await call_api("GET", f"/user/{callback.from_user.id}") + if not user_data: + await callback.message.edit_text("Вы еще не зарегистрированы.") await callback.answer() return - if not trans: - await callback.message.edit_text( - "У вас нет транзакций. Пожалуйста, пополните баланс.", - reply_markup=tranhist_keyboard() - ) + + transactions = await call_api("GET", f"/user/{user_data['id']}/transactions") + if not transactions: + await callback.message.edit_text("У вас нет транзакций.", reply_markup=tranhist_keyboard()) await callback.answer() return + result = "Ваши транзакции:\n" - for count, tran in enumerate(trans, start=1): - result += f"{count}. Сумма: {tran.amount}, Дата: {tran.created_at}\n" - await callback.message.edit_text( - result, - reply_markup=tranhist_keyboard() - ) + for count, tran in enumerate(transactions, start=1): + result += f"{count}. Сумма: {tran['amount']}, Дата: {tran['created_at']}\n" + await callback.message.edit_text(result, reply_markup=tranhist_keyboard()) await callback.answer() async def subhist_callback_handler(callback: types.CallbackQuery): - """ - Обработчик callback_query с data="subhist". - """ - user = await db_manager.create_user(callback.from_user.id) - subs = await db_manager.last_subscription(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: + user_data = await call_api("GET", f"/user/{callback.from_user.id}") + if not user_data: await callback.message.edit_text("Вы еще не зарегистрированы.") + await callback.answer() + return + + subscriptions = await call_api("GET", f"/subscription/{user_data['id']}/last") + if not subscriptions: + await callback.message.edit_text("У вас нет активных подписок.", reply_markup=account_keyboard()) + await callback.answer() + return + + result = "Ваши подписки:\n" + for count, sub in enumerate(subscriptions, start=1): + result += f"{count}. Тариф: {sub['plan']}, Истекает: {sub['expiry_date']}\n" + await callback.message.edit_text(result, reply_markup=account_keyboard()) await callback.answer() async def buy_subscription_callback_handler(callback: types.CallbackQuery): @@ -260,7 +219,7 @@ async def popup_confirm_callback_handler(callback: types.CallbackQuery): """ data = callback.data.split(":") popup_info = data[1] - result = await db_manager.update_balance(callback.from_user.id,popup_info) + result = await call_api("POST", f"/user/{callback.from_user.id}/balance", {"telegram_id": callback.from_user.id, "amount": popup_info}) if result == "ERROR": await callback.message.answer( "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." @@ -271,40 +230,16 @@ async def popup_confirm_callback_handler(callback: types.CallbackQuery): await callback.message.edit_text(text=text, reply_markup=confirm_popup_keyboard()) async def confirm_callback_handler(callback: types.CallbackQuery): - """ - Обработчик подтверждения покупки тарифа. - """ - tariff_info = callback.data.split(":")[1].split("_") - tariff_name = tariff_info[0] - tariff_class = tariff_info[1] - tariff_amount = int(tariff_info[2]) + data = callback.data.split(":")[1] + tariff_info = data.split("_") + plan_id = f"{tariff_info[0]}_{tariff_info[1]}_{tariff_info[2]}" + result = await call_api("POST", "/subscription/buy", {"telegram_id": callback.from_user.id, "plan_id": plan_id}) - sub = await db_manager.buy_sub(callback.from_user.id, f"{tariff_name}_{tariff_class}_{tariff_amount}") - if sub == "ERROR": - await callback.message.answer( - "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." - ) - await callback.answer() - return - elif sub == "INSUFFICIENT_FUNDS": - await callback.message.answer( - "Произошла ошибка, не достаточно средств на балансе." - ) - await callback.answer() - return - add_to_server = await db_manager.add_to_server(callback.from_user.id) - if add_to_server == "ERROR": - await callback.message.answer( - "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." - ) - await callback.answer() - return - - # Текст подтверждения на основе тарифа - months_text = f"{tariff_amount} месяцев" if tariff_amount > 1 else f"{tariff_amount} месяц" - text = f"Вы успешно оформили тариф {tariff_name} на {months_text}. Спасибо за покупку!" - - await callback.message.edit_text(text=text) + if result and result.get("message"): + await callback.message.edit_text(f"Подписка успешно оформлена!") + else: + await callback.message.edit_text("Произошла ошибка при оформлении подписки.") + await callback.answer() def register_handlers(dp: Dispatcher): """ diff --git a/databases/__pycache__/db_config.cpython-312.pyc b/instences/__pycache__/db_config.cpython-312.pyc similarity index 100% rename from databases/__pycache__/db_config.cpython-312.pyc rename to instences/__pycache__/db_config.cpython-312.pyc diff --git a/databases/__pycache__/postgresql.cpython-312.pyc b/instences/__pycache__/postgresql.cpython-312.pyc similarity index 100% rename from databases/__pycache__/postgresql.cpython-312.pyc rename to instences/__pycache__/postgresql.cpython-312.pyc diff --git a/instences/config.py b/instences/config.py new file mode 100644 index 0000000..806b6ee --- /dev/null +++ b/instences/config.py @@ -0,0 +1,4 @@ +import os + +# Настройки PostgreSQL из переменных окружения +BASE_URL_FASTAPI = os.getenv("BASE_URL") diff --git a/databases/model.py b/instences/model.py similarity index 100% rename from databases/model.py rename to instences/model.py diff --git a/keyboard/keyboards.py b/keyboard/keyboards.py index a0fc285..b294012 100644 --- a/keyboard/keyboards.py +++ b/keyboard/keyboards.py @@ -1,140 +1,123 @@ -from aiogram.utils.keyboard import InlineKeyboardBuilder -from aiogram.types import InlineKeyboardButton +from aiogram import types, Dispatcher +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery +from aiogram.filters import Command +# Главное меню клавиатура +def main_menu_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton("Личный кабинет", callback_data="main:personal")) + keyboard.add(InlineKeyboardButton("FAQ", callback_data="main:faq")) + keyboard.add(InlineKeyboardButton("О нас", callback_data="main:about")) + return keyboard -def main_keyboard(): - """ - База - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Профиль", callback_data="profile")) - builder.row(InlineKeyboardButton(text="FAQ", callback_data="faq")) - builder.row(InlineKeyboardButton(text="О нас", url="https://www.youtube.com/watch?v=Zirn-CKck-c")) - return builder.as_markup() +# Личный кабинет клавиатура +def personal_menu_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton("Баланс", callback_data="personal:balance")) + keyboard.add(InlineKeyboardButton("Приобрести подписку", callback_data="personal:subscribe")) + keyboard.add(InlineKeyboardButton("Руководство по использованию", callback_data="personal:guide")) + keyboard.add(InlineKeyboardButton("Назад", callback_data="back:main")) + return keyboard -def account_keyboard(): - """ - Аккаунт - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Баланс", callback_data="balance")) - builder.row(InlineKeyboardButton(text="Приобрести подписку", callback_data="buy_subscription")) - builder.row(InlineKeyboardButton(text="Руководство по подключению", callback_data="guide")) - builder.row(InlineKeyboardButton(text="Назад", callback_data="base")) - return builder.as_markup() +# Подписка меню клавиатура +def subscribe_menu_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton("Lark", callback_data="subscribe:lark")) + keyboard.add(InlineKeyboardButton("Lark Pro", callback_data="subscribe:lark_pro")) + keyboard.add(InlineKeyboardButton("О тарифах", callback_data="subscribe:about_tariffs")) + keyboard.add(InlineKeyboardButton("Назад", callback_data="back:personal")) + return keyboard +# Тарифы Lark клавиатура +def lark_tariffs_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton("Lark 1 месяц", callback_data="lark:1")) + keyboard.add(InlineKeyboardButton("Lark 6 месяцев", callback_data="lark:6")) + keyboard.add(InlineKeyboardButton("Lark 12 месяцев", callback_data="lark:12")) + keyboard.add(InlineKeyboardButton("Назад", callback_data="back:subscribe")) + return keyboard -def buy_keyboard(): - """ - Приобрести подписку - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Тариф Lark", callback_data="subs")) - builder.row(InlineKeyboardButton(text="Тариф Lark Pro", callback_data="subs_pro")) - builder.row(InlineKeyboardButton(text="О тарифах", url="https://t.me/proxylark/19")) - builder.row(InlineKeyboardButton(text="Назад", callback_data="profile")) - return builder.as_markup() +# Тарифы Lark Pro клавиатура +def lark_pro_tariffs_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton("Lark Pro 1 месяц", callback_data="lark_pro:1")) + keyboard.add(InlineKeyboardButton("Lark Pro 6 месяцев", callback_data="lark_pro:6")) + keyboard.add(InlineKeyboardButton("Lark Pro 12 месяцев", callback_data="lark_pro:12")) + keyboard.add(InlineKeyboardButton("Назад", callback_data="back:subscribe")) + return keyboard -def subhist_keyboard(): - """ - Подписки - """ - builder = InlineKeyboardBuilder() - builder.button(text="Назад", callback_data="profile") - return builder.as_markup() +# Руководство меню клавиатура +def guide_menu_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton("iOS, Android", callback_data="guide:ios_android")) + keyboard.add(InlineKeyboardButton("Windows, Macintosh", callback_data="guide:windows_mac")) + keyboard.add(InlineKeyboardButton("Назад", callback_data="back:personal")) + return keyboard -def popup_keyboard(): - """ - Пополнение - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="200₽", callback_data="popup:200"),InlineKeyboardButton(text="500₽", callback_data="popup:500")) - builder.row(InlineKeyboardButton(text="1000₽", callback_data="popup:1000"),InlineKeyboardButton(text="2000₽", callback_data="popup:2000")) - builder.row(InlineKeyboardButton(text="3000₽", callback_data="popup:3000"),InlineKeyboardButton(text="5000₽", callback_data="popup:5000")) - builder.row(InlineKeyboardButton(text="Назад", callback_data="balance")) - return builder.as_markup() +async def start_command(message: types.Message): + """Обработчик команды /start.""" + await message.answer("Главное меню", reply_markup=main_menu_keyboard()) -def balance_keyboard(): - """ - Баланс - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Пополнение", callback_data="popup")) - builder.row(InlineKeyboardButton(text="История транзакций", callback_data="tranhist")) - builder.row(InlineKeyboardButton(text="Назад", callback_data="profile")) - return builder.as_markup() +async def callback_handler(callback: CallbackQuery): + """Обработчик callback.""" + data = callback.data.split(":") + action = data[0] + sub_action = data[1] if len(data) > 1 else None -def tarif_Lark_keyboard(): - """ - Тариф Lark - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Тариф Lark 1 Месяц", callback_data="Lark:Standart:1")) - builder.row(InlineKeyboardButton(text="Тариф Lark 6 Месяц", callback_data="Lark:Standart:6")) - builder.row(InlineKeyboardButton(text="Тариф Lark 12 Месяц", callback_data="Lark:Standart:12")) - builder.row(InlineKeyboardButton(text="Назад", callback_data="buy_subscription")) - return builder.as_markup() + if action == "main": + if sub_action == "personal": + await callback.message.edit_text("Личный кабинет", reply_markup=personal_menu_keyboard()) + elif sub_action == "faq": + await callback.message.edit_text("FAQ", reply_markup=InlineKeyboardMarkup().add( + InlineKeyboardButton("Назад", callback_data="back:main"))) + elif sub_action == "about": + await callback.message.edit_text("Наш сайт: {URL}", reply_markup=InlineKeyboardMarkup().add( + InlineKeyboardButton("Назад", callback_data="back:main"))) -def tarif_Lark_pro_keyboard(): - """ - Тариф Lark Pro - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Тариф Lark Pro 1 Месяц", callback_data="Lark:Pro:1")) - builder.row(InlineKeyboardButton(text="Тариф Lark Pro 6 Месяц", callback_data="Lark:Pro:6")) - builder.row(InlineKeyboardButton(text="Тариф Lark Pro 12 Месяц", callback_data="Lark:Pro:12")) - builder.row(InlineKeyboardButton(text="Назад", callback_data="buy_subscription")) - return builder.as_markup() + elif action == "personal": + if sub_action == "balance": + await callback.message.edit_text("Баланс:", reply_markup=InlineKeyboardMarkup().add( + InlineKeyboardButton("Пополнение", callback_data="balance:topup"), + InlineKeyboardButton("История транзакций", callback_data="balance:history"), + InlineKeyboardButton("Назад", callback_data="back:personal") + )) + elif sub_action == "subscribe": + await callback.message.edit_text("Выберите подписку:", reply_markup=subscribe_menu_keyboard()) + elif sub_action == "guide": + await callback.message.edit_text("Руководство по использованию", reply_markup=guide_menu_keyboard()) -def guide_keyboard(): - """ - Руководство по подключению - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="IOS,Android", callback_data="mob")) - builder.row(InlineKeyboardButton(text="Windows,MacOS", callback_data="pc")) - builder.row(InlineKeyboardButton(text="Назад", callback_data="profile")) - return builder.as_markup() + elif action == "subscribe": + if sub_action == "lark": + await callback.message.edit_text("Тарифы Lark", reply_markup=lark_tariffs_keyboard()) + elif sub_action == "lark_pro": + await callback.message.edit_text("Тарифы Lark Pro", reply_markup=lark_pro_tariffs_keyboard()) + elif sub_action == "about_tariffs": + await callback.message.edit_text("О тарифах", reply_markup=InlineKeyboardMarkup().add( + InlineKeyboardButton("Назад", callback_data="back:subscribe") + )) + elif action == "lark": + await callback.message.edit_text(f"Вы выбрали тариф Lark {sub_action} месяцев.", reply_markup=InlineKeyboardMarkup().add( + InlineKeyboardButton("Подтвердить", callback_data=f"confirm:lark:{sub_action}"), + InlineKeyboardButton("Отменить", callback_data="back:subscribe") + )) -# def about_tarifs_keyboard(): -# """ -# О тарифах -# """ -# builder = InlineKeyboardBuilder() -# builder.row(InlineKeyboardButton(text="Назад", callback_data="buy_subscription")) -# return builder.as_markup() + elif action == "lark_pro": + await callback.message.edit_text(f"Вы выбрали тариф Lark Pro {sub_action} месяцев.", reply_markup=InlineKeyboardMarkup().add( + InlineKeyboardButton("Подтвердить", callback_data=f"confirm:lark_pro:{sub_action}"), + InlineKeyboardButton("Отменить", callback_data="back:subscribe") + )) + elif action == "back": + if sub_action == "main": + await callback.message.edit_text("Главное меню", reply_markup=main_menu_keyboard()) + elif sub_action == "personal": + await callback.message.edit_text("Личный кабинет", reply_markup=personal_menu_keyboard()) + elif sub_action == "subscribe": + await callback.message.edit_text("Выберите подписку:", reply_markup=subscribe_menu_keyboard()) -def faq_keyboard(): - """ - FAQ - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Назад", callback_data="base")) - return builder.as_markup() + await callback.answer() -def tranhist_keyboard(): - """ - История транзакций - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Назад",callback_data="balance")) - return builder.as_markup() - -def tarif_confirm_keyboard(name,amount,classif): - """ - Подтверждение покупки тарифа - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Подтвердить", callback_data=f"confirm:{name}_{classif}_{amount}")) - builder.row(InlineKeyboardButton(text="Отменить",callback_data="buy_subscription")) - return builder.as_markup() - -def confirm_popup_keyboard(): - """ - Подтверждение пополнения - """ - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="Теперь иди нахуй", callback_data="balance")) - return builder.as_markup() +def register_handlers(dp: Dispatcher): + dp.message.register(start_command, Command("start")) + dp.callback_query.register(callback_handler) diff --git a/main.py b/main.py index 5d4b9a3..1b0c331 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,6 @@ import os import asyncio from aiogram import Bot, Dispatcher -from databases.db_config import init_postgresql, init_mongodb, close_connections from aiogram.types import BotCommand from Middleware.anti_spam_middleware import AntiSpamMiddleware import logging @@ -30,14 +29,9 @@ async def set_commands(): async def on_startup(): """Действия при запуске бота.""" - # Инициализация баз данных - await init_mongodb() - await init_postgresql() - # Установка команд бота await set_commands() - # Настройка логирования logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) logger.info("Бот запущен!") @@ -45,10 +39,7 @@ async def on_startup(): async def on_shutdown(): """Действия при остановке бота.""" - # Закрытие подключений к базам данных - await close_connections() - # Закрытие сессии бота await bot.session.close() print("Бот остановлен.")