Перенос части логики на бекенд rest api и изменение логики работы бота так же из за бекенда

This commit is contained in:
Disledg
2024-12-24 16:43:55 +03:00
parent 4151e2a546
commit 379da6cbba
14 changed files with 245 additions and 699 deletions

View File

@@ -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.")

View File

@@ -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 закрыто.")

View File

@@ -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))

76
docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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"]

View File

@@ -1,12 +1,17 @@
from aiogram import types, Dispatcher from aiogram import types, Dispatcher
from aiogram.filters import Command from aiogram.filters import Command
from databases.postgresql import DatabaseManager import aiohttp
from databases.model import User, Subscription, Transaction, Administrators from instences.config import BASE_URL_FASTAPI
from databases.db_config import get_postgres_session
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 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): 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): async def profile_callback_handler(callback: types.CallbackQuery):
""" user_data = await call_api("POST", "/user/create", {"telegram_id": callback.from_user.id})
Обработчик callback_query с data="profile". if not user_data:
""" await callback.message.answer("Произошла ошибка, попробуйте позже.")
user = await db_manager.create_user(telegram_id=callback.from_user.id)
if user == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer() await callback.answer()
return return
if user: text = f"Ваш профиль:\nID: {user_data['username']}\nБаланс: {user_data['balance']}"
text = f"""Ваш профиль:\nID: {user.username}\nБаланс: {user.balance}""" await callback.message.edit_text(text, reply_markup=account_keyboard())
await callback.message.edit_text(
text,
reply_markup=account_keyboard()
)
else:
await callback.message.edit_text("Вы еще не зарегистрированы.")
await callback.answer() await callback.answer()
async def balance_callback_handler(callback: types.CallbackQuery): async def balance_callback_handler(callback: types.CallbackQuery):
""" user_data = await call_api("GET", f"/user/{callback.from_user.id}")
Обработчик callback_query с data="balance". if not user_data:
""" await callback.message.answer("Вы еще не зарегистрированы.")
user = await db_manager.create_user(telegram_id=callback.from_user.id)
if user == "ERROR":
await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer() await callback.answer()
return return
if user: await callback.message.edit_text(
await callback.message.edit_text( f"Ваш баланс: {user_data['balance']} ₽. Выберите сумму для пополнения 🐥",
f"Ваш баланс: {user.balance} ₽. Выберите сумму для пополнения 🐥", reply_markup=balance_keyboard()
reply_markup=balance_keyboard() )
)
else:
await callback.message.edit_text("Вы еще не зарегистрированы.")
await callback.answer() await callback.answer()
@@ -95,7 +81,7 @@ async def popup_callback_handler(callback: types.CallbackQuery):
""" """
Обработчик callback_query с data="popup". Обработчик 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": if user == "ERROR":
await callback.message.answer( await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией." "Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
@@ -113,68 +99,41 @@ async def popup_callback_handler(callback: types.CallbackQuery):
await callback.answer() await callback.answer()
async def tranhist_callback_handler(callback: types.CallbackQuery): async def tranhist_callback_handler(callback: types.CallbackQuery):
""" user_data = await call_api("GET", f"/user/{callback.from_user.id}")
Обработчик callback_query с data="tranhist". if not user_data:
""" await callback.message.edit_text("Вы еще не зарегистрированы.")
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(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
)
await callback.answer() await callback.answer()
return return
if not trans:
await callback.message.edit_text( transactions = await call_api("GET", f"/user/{user_data['id']}/transactions")
"У вас нет транзакций. Пожалуйста, пополните баланс.", if not transactions:
reply_markup=tranhist_keyboard() await callback.message.edit_text("У вас нет транзакций.", reply_markup=tranhist_keyboard())
)
await callback.answer() await callback.answer()
return return
result = "Ваши транзакции:\n" result = "Ваши транзакции:\n"
for count, tran in enumerate(trans, start=1): for count, tran in enumerate(transactions, start=1):
result += f"{count}. Сумма: {tran.amount}, Дата: {tran.created_at}\n" result += f"{count}. Сумма: {tran['amount']}, Дата: {tran['created_at']}\n"
await callback.message.edit_text( await callback.message.edit_text(result, reply_markup=tranhist_keyboard())
result,
reply_markup=tranhist_keyboard()
)
await callback.answer() await callback.answer()
async def subhist_callback_handler(callback: types.CallbackQuery): async def subhist_callback_handler(callback: types.CallbackQuery):
""" user_data = await call_api("GET", f"/user/{callback.from_user.id}")
Обработчик callback_query с data="subhist". if not user_data:
"""
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:
await callback.message.edit_text("Вы еще не зарегистрированы.") 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() await callback.answer()
async def buy_subscription_callback_handler(callback: types.CallbackQuery): 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(":") data = callback.data.split(":")
popup_info = data[1] 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": if result == "ERROR":
await callback.message.answer( 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()) await callback.message.edit_text(text=text, reply_markup=confirm_popup_keyboard())
async def confirm_callback_handler(callback: types.CallbackQuery): async def confirm_callback_handler(callback: types.CallbackQuery):
""" data = callback.data.split(":")[1]
Обработчик подтверждения покупки тарифа. tariff_info = data.split("_")
""" plan_id = f"{tariff_info[0]}_{tariff_info[1]}_{tariff_info[2]}"
tariff_info = callback.data.split(":")[1].split("_") result = await call_api("POST", "/subscription/buy", {"telegram_id": callback.from_user.id, "plan_id": plan_id})
tariff_name = tariff_info[0]
tariff_class = tariff_info[1]
tariff_amount = int(tariff_info[2])
sub = await db_manager.buy_sub(callback.from_user.id, f"{tariff_name}_{tariff_class}_{tariff_amount}") if result and result.get("message"):
if sub == "ERROR": await callback.message.edit_text(f"Подписка успешно оформлена!")
await callback.message.answer( else:
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией." await callback.message.edit_text("Произошла ошибка при оформлении подписки.")
) await callback.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)
def register_handlers(dp: Dispatcher): def register_handlers(dp: Dispatcher):
""" """

4
instences/config.py Normal file
View File

@@ -0,0 +1,4 @@
import os
# Настройки PostgreSQL из переменных окружения
BASE_URL_FASTAPI = os.getenv("BASE_URL")

View File

@@ -1,140 +1,123 @@
from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram import types, Dispatcher
from aiogram.types import InlineKeyboardButton 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(): # Личный кабинет клавиатура
""" def personal_menu_keyboard():
База keyboard = InlineKeyboardMarkup()
""" keyboard.add(InlineKeyboardButton("Баланс", callback_data="personal:balance"))
builder = InlineKeyboardBuilder() keyboard.add(InlineKeyboardButton("Приобрести подписку", callback_data="personal:subscribe"))
builder.row(InlineKeyboardButton(text="Профиль", callback_data="profile")) keyboard.add(InlineKeyboardButton("Руководство по использованию", callback_data="personal:guide"))
builder.row(InlineKeyboardButton(text="FAQ", callback_data="faq")) keyboard.add(InlineKeyboardButton("Назад", callback_data="back:main"))
builder.row(InlineKeyboardButton(text="О нас", url="https://www.youtube.com/watch?v=Zirn-CKck-c")) return keyboard
return builder.as_markup()
def account_keyboard(): # Подписка меню клавиатура
""" def subscribe_menu_keyboard():
Аккаунт keyboard = InlineKeyboardMarkup()
""" keyboard.add(InlineKeyboardButton("Lark", callback_data="subscribe:lark"))
builder = InlineKeyboardBuilder() keyboard.add(InlineKeyboardButton("Lark Pro", callback_data="subscribe:lark_pro"))
builder.row(InlineKeyboardButton(text="Баланс", callback_data="balance")) keyboard.add(InlineKeyboardButton("О тарифах", callback_data="subscribe:about_tariffs"))
builder.row(InlineKeyboardButton(text="Приобрести подписку", callback_data="buy_subscription")) keyboard.add(InlineKeyboardButton("Назад", callback_data="back:personal"))
builder.row(InlineKeyboardButton(text="Руководство по подключению", callback_data="guide")) return keyboard
builder.row(InlineKeyboardButton(text="Назад", callback_data="base"))
return builder.as_markup()
# Тарифы 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(): # Тарифы Lark Pro клавиатура
""" def lark_pro_tariffs_keyboard():
Приобрести подписку keyboard = InlineKeyboardMarkup()
""" keyboard.add(InlineKeyboardButton("Lark Pro 1 месяц", callback_data="lark_pro:1"))
builder = InlineKeyboardBuilder() keyboard.add(InlineKeyboardButton("Lark Pro 6 месяцев", callback_data="lark_pro:6"))
builder.row(InlineKeyboardButton(text="Тариф Lark", callback_data="subs")) keyboard.add(InlineKeyboardButton("Lark Pro 12 месяцев", callback_data="lark_pro:12"))
builder.row(InlineKeyboardButton(text="Тариф Lark Pro", callback_data="subs_pro")) keyboard.add(InlineKeyboardButton("Назад", callback_data="back:subscribe"))
builder.row(InlineKeyboardButton(text="О тарифах", url="https://t.me/proxylark/19")) return keyboard
builder.row(InlineKeyboardButton(text="Назад", callback_data="profile"))
return builder.as_markup()
def subhist_keyboard(): # Руководство меню клавиатура
""" def guide_menu_keyboard():
Подписки keyboard = InlineKeyboardMarkup()
""" keyboard.add(InlineKeyboardButton("iOS, Android", callback_data="guide:ios_android"))
builder = InlineKeyboardBuilder() keyboard.add(InlineKeyboardButton("Windows, Macintosh", callback_data="guide:windows_mac"))
builder.button(text="Назад", callback_data="profile") keyboard.add(InlineKeyboardButton("Назад", callback_data="back:personal"))
return builder.as_markup() return keyboard
def popup_keyboard(): async def start_command(message: types.Message):
""" """Обработчик команды /start."""
Пополнение await message.answer("Главное меню", reply_markup=main_menu_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()
def balance_keyboard(): async def callback_handler(callback: CallbackQuery):
""" """Обработчик callback."""
Баланс data = callback.data.split(":")
""" action = data[0]
builder = InlineKeyboardBuilder() sub_action = data[1] if len(data) > 1 else None
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()
def tarif_Lark_keyboard(): if action == "main":
""" if sub_action == "personal":
Тариф Lark await callback.message.edit_text("Личный кабинет", reply_markup=personal_menu_keyboard())
""" elif sub_action == "faq":
builder = InlineKeyboardBuilder() await callback.message.edit_text("FAQ", reply_markup=InlineKeyboardMarkup().add(
builder.row(InlineKeyboardButton(text="Тариф Lark 1 Месяц", callback_data="Lark:Standart:1")) InlineKeyboardButton("Назад", callback_data="back:main")))
builder.row(InlineKeyboardButton(text="Тариф Lark 6 Месяц", callback_data="Lark:Standart:6")) elif sub_action == "about":
builder.row(InlineKeyboardButton(text="Тариф Lark 12 Месяц", callback_data="Lark:Standart:12")) await callback.message.edit_text("Наш сайт: {URL}", reply_markup=InlineKeyboardMarkup().add(
builder.row(InlineKeyboardButton(text="Назад", callback_data="buy_subscription")) InlineKeyboardButton("Назад", callback_data="back:main")))
return builder.as_markup()
def tarif_Lark_pro_keyboard(): elif action == "personal":
""" if sub_action == "balance":
Тариф Lark Pro await callback.message.edit_text("Баланс:", reply_markup=InlineKeyboardMarkup().add(
""" InlineKeyboardButton("Пополнение", callback_data="balance:topup"),
builder = InlineKeyboardBuilder() InlineKeyboardButton("История транзакций", callback_data="balance:history"),
builder.row(InlineKeyboardButton(text="Тариф Lark Pro 1 Месяц", callback_data="Lark:Pro:1")) InlineKeyboardButton("Назад", callback_data="back:personal")
builder.row(InlineKeyboardButton(text="Тариф Lark Pro 6 Месяц", callback_data="Lark:Pro:6")) ))
builder.row(InlineKeyboardButton(text="Тариф Lark Pro 12 Месяц", callback_data="Lark:Pro:12")) elif sub_action == "subscribe":
builder.row(InlineKeyboardButton(text="Назад", callback_data="buy_subscription")) await callback.message.edit_text("Выберите подписку:", reply_markup=subscribe_menu_keyboard())
return builder.as_markup() elif sub_action == "guide":
await callback.message.edit_text("Руководство по использованию", reply_markup=guide_menu_keyboard())
def guide_keyboard(): elif action == "subscribe":
""" if sub_action == "lark":
Руководство по подключению await callback.message.edit_text("Тарифы Lark", reply_markup=lark_tariffs_keyboard())
""" elif sub_action == "lark_pro":
builder = InlineKeyboardBuilder() await callback.message.edit_text("Тарифы Lark Pro", reply_markup=lark_pro_tariffs_keyboard())
builder.row(InlineKeyboardButton(text="IOS,Android", callback_data="mob")) elif sub_action == "about_tariffs":
builder.row(InlineKeyboardButton(text="Windows,MacOS", callback_data="pc")) await callback.message.edit_text("О тарифах", reply_markup=InlineKeyboardMarkup().add(
builder.row(InlineKeyboardButton(text="Назад", callback_data="profile")) InlineKeyboardButton("Назад", callback_data="back:subscribe")
return builder.as_markup() ))
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(): 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")
# builder = InlineKeyboardBuilder() ))
# builder.row(InlineKeyboardButton(text="Назад", callback_data="buy_subscription"))
# return builder.as_markup()
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(): await callback.answer()
"""
FAQ
"""
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="Назад", callback_data="base"))
return builder.as_markup()
def tranhist_keyboard(): def register_handlers(dp: Dispatcher):
""" dp.message.register(start_command, Command("start"))
История транзакций dp.callback_query.register(callback_handler)
"""
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()

View File

@@ -1,7 +1,6 @@
import os import os
import asyncio import asyncio
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from databases.db_config import init_postgresql, init_mongodb, close_connections
from aiogram.types import BotCommand from aiogram.types import BotCommand
from Middleware.anti_spam_middleware import AntiSpamMiddleware from Middleware.anti_spam_middleware import AntiSpamMiddleware
import logging import logging
@@ -30,14 +29,9 @@ async def set_commands():
async def on_startup(): async def on_startup():
"""Действия при запуске бота.""" """Действия при запуске бота."""
# Инициализация баз данных
await init_mongodb()
await init_postgresql()
# Установка команд бота
await set_commands() await set_commands()
# Настройка логирования
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.info("Бот запущен!") logger.info("Бот запущен!")
@@ -45,10 +39,7 @@ async def on_startup():
async def on_shutdown(): async def on_shutdown():
"""Действия при остановке бота.""" """Действия при остановке бота."""
# Закрытие подключений к базам данных
await close_connections()
# Закрытие сессии бота
await bot.session.close() await bot.session.close()
print("Бот остановлен.") print("Бот остановлен.")