diff --git a/bot.py b/bot.py index 69b5fcce..e0488422 100644 --- a/bot.py +++ b/bot.py @@ -2,7 +2,8 @@ from aiogram import Bot, Dispatcher, Router from aiogram.fsm.storage.memory import MemoryStorage from config import API_TOKEN, CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, STARS_ENABLE, YOOKASSA_ENABLE -from middlewares.logging import UserActivityMiddleware +from middlewares.database import DatabaseMiddleware +from middlewares.logging import LoggingMiddleware bot = Bot(token=API_TOKEN) storage = MemoryStorage() @@ -33,5 +34,9 @@ if STARS_ENABLE: dp.include_router(stars_pay.router) dp.include_router(notifications.router) -dp.message.middleware(UserActivityMiddleware()) -dp.callback_query.middleware(UserActivityMiddleware()) +dp.message.middleware(LoggingMiddleware()) +dp.callback_query.middleware(LoggingMiddleware()) + + +dp.message.middleware(DatabaseMiddleware()) +dp.callback_query.middleware(DatabaseMiddleware()) diff --git a/client.py b/client.py index dfa928dc..f31967ab 100644 --- a/client.py +++ b/client.py @@ -25,7 +25,7 @@ async def add_client( id=client_id, email=email.lower(), limit_ip=limit_ip, - total_gb = TOTAL_GB, + total_gb=TOTAL_GB, expiry_time=expiry_time, enable=enable, tg_id=tg_id, diff --git a/handlers/filters/admin.py b/filters/admin.py similarity index 100% rename from handlers/filters/admin.py rename to filters/admin.py diff --git a/handlers/admin/admin_commands.py b/handlers/admin/admin_commands.py index fd42ad71..af783e57 100644 --- a/handlers/admin/admin_commands.py +++ b/handlers/admin/admin_commands.py @@ -8,7 +8,7 @@ from loguru import logger from bot import bot from config import DATABASE_URL from database import add_balance_to_client, check_connection_exists -from handlers.filters.admin import IsAdminFilter +from filters.admin import IsAdminFilter from handlers.texts import TRIAL router = Router() diff --git a/handlers/admin/admin_panel.py b/handlers/admin/admin_panel.py index d7f9a63c..08e5c907 100644 --- a/handlers/admin/admin_panel.py +++ b/handlers/admin/admin_panel.py @@ -12,8 +12,8 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder from backup import backup_database from bot import bot from config import DATABASE_URL +from filters.admin import IsAdminFilter from handlers.admin.admin_commands import send_message_to_all_clients -from handlers.filters.admin import IsAdminFilter router = Router() diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index 07673ee9..3d10cab5 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -12,8 +12,8 @@ from loguru import logger from bot import bot from config import DATABASE_URL, SERVERS from database import get_client_id_by_email, restore_trial, update_key_expiry +from filters.admin import IsAdminFilter from handlers.admin.admin_panel import back_to_admin_menu -from handlers.filters.admin import IsAdminFilter from handlers.keys.key_utils import delete_key_from_server, renew_server_key from handlers.utils import sanitize_key_name @@ -300,22 +300,18 @@ async def handle_key_name_input(message: types.Message, state: FSMContext): key_buttons.row( InlineKeyboardButton( text="⏳ Изменить время истечения", - callback_data=f"change_expiry|{email}" + callback_data=f"change_expiry|{email}", ) ) key_buttons.row( InlineKeyboardButton( - text="❌ Удалить ключ", - callback_data=f"delete_key_admin|{email}" + text="❌ Удалить ключ", callback_data=f"delete_key_admin|{email}" ) ) key_buttons.row( - InlineKeyboardButton( - text="🔙 Назад", - callback_data="back_to_user_editor" - ) - ) + InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_user_editor") + ) await message.reply( "\n".join(response_messages), diff --git a/handlers/payments/cryprobot_pay.py b/handlers/payments/cryprobot_pay.py index 2e5dd39a..bfad4bf3 100644 --- a/handlers/payments/cryprobot_pay.py +++ b/handlers/payments/cryprobot_pay.py @@ -77,6 +77,12 @@ async def process_callback_pay_cryptobot( callback_data=PAYMENT_OPTIONS[i]["callback_data"], ) ) + builder.row( + InlineKeyboardButton( + text="💰 Ввести свою сумму", callback_data="enter_custom_amount" + ) + ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile")) key_count = await get_key_count(tg_id) diff --git a/handlers/payments/freekassa_pay.py b/handlers/payments/freekassa_pay.py index a916e587..dc4007e1 100644 --- a/handlers/payments/freekassa_pay.py +++ b/handlers/payments/freekassa_pay.py @@ -119,6 +119,12 @@ async def process_callback_pay_freekassa( callback_data=PAYMENT_OPTIONS[i]["callback_data"], ) ) + builder.row( + InlineKeyboardButton( + text="💰 Ввести свою сумму", callback_data="enter_custom_amount" + ) + ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile")) await bot.delete_message( chat_id=tg_id, message_id=callback_query.message.message_id diff --git a/handlers/payments/stars_pay.py b/handlers/payments/stars_pay.py index 695ed355..83002603 100644 --- a/handlers/payments/stars_pay.py +++ b/handlers/payments/stars_pay.py @@ -72,6 +72,12 @@ async def process_callback_pay_stars( callback_data=PAYMENT_OPTIONS[i]["callback_data"], ) ) + builder.row( + InlineKeyboardButton( + text="💰 Ввести свою сумму", callback_data="enter_custom_amount" + ) + ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile")) key_count = await get_key_count(tg_id) diff --git a/handlers/payments/yookassa_pay.py b/handlers/payments/yookassa_pay.py index 894e9090..87d59a43 100644 --- a/handlers/payments/yookassa_pay.py +++ b/handlers/payments/yookassa_pay.py @@ -63,24 +63,32 @@ async def process_callback_pay_yookassa( builder = InlineKeyboardBuilder() - for i in range(0, 4, 2): - builder.row( - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i]["text"], - callback_data=PAYMENT_OPTIONS[i]["callback_data"], - ), - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i + 1]["text"], - callback_data=PAYMENT_OPTIONS[i + 1]["callback_data"], - ), - ) - - for i in range(4, len(PAYMENT_OPTIONS)): - builder.row( - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i]["text"], - callback_data=PAYMENT_OPTIONS[i]["callback_data"], + for i in range(0, len(PAYMENT_OPTIONS), 2): + if i + 1 < len(PAYMENT_OPTIONS): + builder.row( + InlineKeyboardButton( + text=PAYMENT_OPTIONS[i]["text"], + callback_data=PAYMENT_OPTIONS[i]["callback_data"], + ), + InlineKeyboardButton( + text=PAYMENT_OPTIONS[i + 1]["text"], + callback_data=PAYMENT_OPTIONS[i + 1]["callback_data"], + ), ) + else: + builder.row( + InlineKeyboardButton( + text=PAYMENT_OPTIONS[i]["text"], + callback_data=PAYMENT_OPTIONS[i]["callback_data"], + ) + ) + builder.row( + InlineKeyboardButton( + text="💰 Ввести свою сумму", callback_data="enter_custom_amount" + ) + ) + builder.row( + InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile") ) key_count = await get_key_count(tg_id) diff --git a/handlers/start.py b/handlers/start.py index b2c0b553..37ffe227 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -1,6 +1,5 @@ import os -import asyncpg from aiogram import F, Router from aiogram.filters import Command from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, Message @@ -8,7 +7,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder from loguru import logger from bot import bot -from config import APP_URL, CHANNEL_URL, DATABASE_URL, SUPPORT_CHAT_URL, DOWNLOAD_IOS, DOWNLOAD_ANDROID, CONNECT_ANDROID, CONNECT_IOS +from config import CHANNEL_URL, CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, SUPPORT_CHAT_URL from database import add_connection, add_referral, check_connection_exists, get_trial from handlers.keys.trial_key import create_trial_key from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn @@ -69,7 +68,7 @@ async def start_command(message: Message): @router.callback_query(F.data == "connect_vpn") -async def handle_connect_vpn(callback_query: CallbackQuery): +async def handle_connect_vpn(callback_query: CallbackQuery, session): await callback_query.message.delete() user_id = callback_query.from_user.id @@ -78,9 +77,8 @@ async def handle_connect_vpn(callback_query: CallbackQuery): if "error" in trial_key_info: await callback_query.message.answer(trial_key_info["error"]) else: - conn = await asyncpg.connect(DATABASE_URL) try: - result = await conn.execute( + result = await session.execute( """ UPDATE connections SET trial = 1 WHERE tg_id = $1 """, @@ -91,9 +89,6 @@ async def handle_connect_vpn(callback_query: CallbackQuery): except Exception as e: logger.error(f"Ошибка при обновлении trial: {e}") - finally: - await conn.close() - key_message = ( f"🔑 Ваш персональный ключ доступа:\n" f"
{trial_key_info['key']}
\n\n" diff --git a/middlewares/database.py b/middlewares/database.py index c2d05102..98482283 100644 --- a/middlewares/database.py +++ b/middlewares/database.py @@ -7,7 +7,7 @@ from aiogram.types import TelegramObject from config import DATABASE_URL -class AdminMiddleware(BaseMiddleware): +class DatabaseMiddleware(BaseMiddleware): async def __call__( self, handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], diff --git a/middlewares/logging.py b/middlewares/logging.py index cc51f324..32c07acc 100644 --- a/middlewares/logging.py +++ b/middlewares/logging.py @@ -5,7 +5,7 @@ from aiogram.types import CallbackQuery, Message, TelegramObject from loguru import logger -class UserActivityMiddleware(BaseMiddleware): +class LoggingMiddleware(BaseMiddleware): async def __call__( self, handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], diff --git a/models.py b/models.py new file mode 100644 index 00000000..f3ea708d --- /dev/null +++ b/models.py @@ -0,0 +1,54 @@ +from sqlalchemy import Column, BigInteger, Float, Text, Boolean, ForeignKey +from sqlalchemy.orm import declarative_base, relationship + +Base = declarative_base() + +class Connection(Base): + """ + Модель для таблицы connections + """ + __tablename__ = 'connections' + + tg_id = Column(BigInteger, primary_key=True, nullable=False) + balance = Column(Float, nullable=False, default=0.0) + trial = Column(BigInteger, nullable=False, default=0) + + # Связь с ключами и рефералами + keys = relationship('Key', back_populates='connection') + referrals_received = relationship('Referral', foreign_keys='Referral.referred_tg_id', back_populates='referred') + referrals_sent = relationship('Referral', foreign_keys='Referral.referrer_tg_id', back_populates='referrer') + + +class Key(Base): + """ + Модель для таблицы keys + """ + __tablename__ = 'keys' + + tg_id = Column(BigInteger, ForeignKey('connections.tg_id'), primary_key=True, nullable=False) + client_id = Column(Text, primary_key=True, nullable=False) + email = Column(Text, nullable=False) + created_at = Column(BigInteger, nullable=False) + expiry_time = Column(BigInteger, nullable=False) + key = Column(Text, nullable=False) + server_id = Column(Text, nullable=False, default='server1') + notified = Column(Boolean, nullable=False, default=False) + notified_24h = Column(Boolean, nullable=False, default=False) + + # Связь с подключением + connection = relationship('Connection', back_populates='keys') + + +class Referral(Base): + """ + Модель для таблицы referrals + """ + __tablename__ = 'referrals' + + referred_tg_id = Column(BigInteger, ForeignKey('connections.tg_id'), primary_key=True, nullable=False) + referrer_tg_id = Column(BigInteger, ForeignKey('connections.tg_id'), nullable=False) + reward_issued = Column(Boolean, default=False) + + # Связи с подключениями + referred = relationship('Connection', foreign_keys=[referred_tg_id], back_populates='referrals_received') + referrer = relationship('Connection', foreign_keys=[referrer_tg_id], back_populates='referrals_sent') \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5aad9d5b..262fc7fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,4 +27,5 @@ yarl==1.15.5 yookassa==3.3.0 loguru aiocryptopay -py3xui \ No newline at end of file +py3xui +sqlalchemy \ No newline at end of file