edit_text/lots of improvements
This commit is contained in:
@@ -3,8 +3,8 @@ import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
from aiogram.types import BufferedInputFile
|
||||
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER, PG_HOST, PG_PORT
|
||||
|
||||
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER, PG_HOST, PG_PORT
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ from aiogram.filters import ExceptionTypeFilter
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
from aiogram.types import BufferedInputFile, ErrorEvent
|
||||
from aiogram.utils.markdown import hbold
|
||||
from config import ADMIN_ID, API_TOKEN
|
||||
|
||||
from config import ADMIN_ID, API_TOKEN
|
||||
from filters.private import IsPrivateFilter
|
||||
from logger import logger
|
||||
from middlewares import register_middleware
|
||||
@@ -18,7 +18,7 @@ bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTM
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot=bot, storage=storage)
|
||||
|
||||
version = "4.0.0-Alpha(13-dev)"
|
||||
version = "4.0.0-beta(16)"
|
||||
|
||||
register_middleware(dp)
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import py3xui
|
||||
from config import LIMIT_IP, SUPERNODE
|
||||
|
||||
from config import LIMIT_IP, SUPERNODE
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -4,8 +4,8 @@ from typing import Any
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
from config import CASHBACK, CHECK_REFERRAL_REWARD_ISSUED, DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
|
||||
|
||||
from config import CASHBACK, CHECK_REFERRAL_REWARD_ISSUED, DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from aiogram.filters import BaseFilter
|
||||
from aiogram.types import Message
|
||||
|
||||
from config import ADMIN_ID
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from config import USERNAME_BOT
|
||||
|
||||
from config import USERNAME_BOT
|
||||
from database import create_coupon, delete_coupon, get_all_coupons
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.coupons_kb import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb
|
||||
|
||||
@@ -6,10 +6,10 @@ from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from backup import create_backup_and_send_to_admins
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||
from database import check_unique_server_name, delete_server, get_servers
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import create_key_on_cluster
|
||||
|
||||
@@ -7,8 +7,8 @@ from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from config import TOTAL_GB
|
||||
|
||||
from config import TOTAL_GB
|
||||
from database import (
|
||||
delete_key,
|
||||
delete_user_data,
|
||||
|
||||
+1
-1
@@ -6,8 +6,8 @@ from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import CAPTCHA_EMOJIS
|
||||
|
||||
from config import CAPTCHA_EMOJIS
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
+1
-1
@@ -3,8 +3,8 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, LabeledPrice, Message, PreCheckoutQuery
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import RUB_TO_XTR
|
||||
|
||||
from config import RUB_TO_XTR
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import aiofiles
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.types import (
|
||||
CallbackQuery,
|
||||
InlineKeyboardButton,
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import CONNECT_MACOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
|
||||
|
||||
from config import CONNECT_MACOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
|
||||
from database import get_key_details
|
||||
from handlers.texts import (
|
||||
CONNECT_TV_TEXT,
|
||||
@@ -15,70 +18,63 @@ from handlers.texts import (
|
||||
KEY_MESSAGE,
|
||||
SUBSCRIPTION_DETAILS_TEXT,
|
||||
)
|
||||
from logger import logger
|
||||
from handlers.utils import edit_or_send_message
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "instructions")
|
||||
@router.message(F.text == "/instructions")
|
||||
async def send_instructions(
|
||||
callback_query_or_message: CallbackQuery | Message,
|
||||
):
|
||||
async def send_instructions(callback_query_or_message: CallbackQuery | Message):
|
||||
instructions_message = INSTRUCTIONS
|
||||
image_path = os.path.join("img", "instructions.jpg")
|
||||
|
||||
if not os.path.isfile(image_path):
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
await callback_query_or_message.message.answer("Файл изображения не найден.")
|
||||
else:
|
||||
await callback_query_or_message.answer("Файл изображения не найден.")
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
send_photo = callback_query_or_message.message.answer_photo
|
||||
target_message = callback_query_or_message.message
|
||||
else:
|
||||
send_photo = callback_query_or_message.answer_photo
|
||||
target_message = callback_query_or_message
|
||||
|
||||
async with aiofiles.open(image_path, "rb") as image_from_buffer:
|
||||
image_data = await image_from_buffer.read()
|
||||
await send_photo(
|
||||
BufferedInputFile(image_data, filename="instructions.jpg"),
|
||||
caption=instructions_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=instructions_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=image_path,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("connect_pc|"))
|
||||
async def process_connect_pc(callback_query: CallbackQuery, session: Any):
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
record = await get_key_details(key_name, session)
|
||||
|
||||
if not record:
|
||||
await callback_query.message.answer("❌ <b>Ключ не найден. Проверьте имя ключа.</b> 🔍")
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="❌ <b>Ключ не найден. Проверьте имя ключа.</b> 🔍",
|
||||
reply_markup=types.InlineKeyboardMarkup(),
|
||||
media_path=None,
|
||||
)
|
||||
return
|
||||
|
||||
key = record["key"]
|
||||
key_message = KEY_MESSAGE.format(key)
|
||||
instruction_message = f"{key_message}{INSTRUCTION_PC}"
|
||||
key_message_text = KEY_MESSAGE.format(key)
|
||||
instruction_message = f"{key_message_text}{INSTRUCTION_PC}"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}"))
|
||||
builder.row(InlineKeyboardButton(text="💻 Подключить MacOS", url=f"{CONNECT_MACOS}{key}"))
|
||||
builder.row(InlineKeyboardButton(text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}"))
|
||||
builder.row(InlineKeyboardButton(text="🆘 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
instruction_message,
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=instruction_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
disable_web_page_preview=True,
|
||||
media_path=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -88,11 +84,14 @@ async def process_connect_tv(callback_query: CallbackQuery):
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="▶ Продолжить", callback_data=f"continue_tv|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=CONNECT_TV_TEXT,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
@@ -102,18 +101,15 @@ async def process_continue_tv(callback_query: CallbackQuery, session: Any):
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
logger.info(f"tg_id: {tg_id}, key_name: {key_name}")
|
||||
|
||||
record = await get_key_details(key_name, session)
|
||||
|
||||
logger.info(f"Query result: {record}")
|
||||
|
||||
subscription_link = record["key"]
|
||||
|
||||
message_text = SUBSCRIPTION_DETAILS_TEXT.format(subscription_link=subscription_link)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="📖 Полная инструкция", url="https://vpn4tv.com/quick-guide.html"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(text=message_text, reply_markup=builder.as_markup())
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message, text=message_text, reply_markup=builder.as_markup(), media_path=None
|
||||
)
|
||||
|
||||
@@ -8,11 +8,16 @@ from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from bot import bot
|
||||
from client import delete_client
|
||||
from config import (
|
||||
ADMIN_PASSWORD,
|
||||
ADMIN_USERNAME,
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
CONNECT_PHONE_BUTTON,
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
NOTIFY_EXTRA_DAYS,
|
||||
@@ -24,10 +29,6 @@ from config import (
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from bot import bot
|
||||
from client import delete_client
|
||||
from database import (
|
||||
add_connection,
|
||||
check_connection_exists,
|
||||
@@ -52,7 +53,7 @@ from handlers.keys.key_utils import create_client_on_server, create_key_on_clust
|
||||
from handlers.payments.robokassa_pay import handle_custom_amount_input
|
||||
from handlers.payments.yookassa_pay import process_custom_amount_input
|
||||
from handlers.texts import DISCOUNTS, key_message_success
|
||||
from handlers.utils import generate_random_email, get_least_loaded_cluster
|
||||
from handlers.utils import edit_or_send_message, generate_random_email, get_least_loaded_cluster
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
@@ -84,13 +85,10 @@ async def handle_key_creation(
|
||||
|
||||
if not TRIAL_TIME_DISABLE:
|
||||
trial_status = await get_trial(tg_id, session)
|
||||
|
||||
if trial_status in [0, -1]:
|
||||
extra_days = NOTIFY_EXTRA_DAYS if trial_status == -1 else 0
|
||||
expiry_time = current_time + timedelta(days=TRIAL_TIME + extra_days)
|
||||
|
||||
logger.info(f"Доступен {TRIAL_TIME + extra_days}-дневный пробный период пользователю {tg_id}.")
|
||||
|
||||
updated = await update_trial(tg_id, 1, session)
|
||||
if updated:
|
||||
await create_key(tg_id, expiry_time, state, session, message_or_query)
|
||||
@@ -114,9 +112,16 @@ async def handle_key_creation(
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await message_or_query.message.answer(
|
||||
"💳 Выберите тарифный план для создания нового ключа:",
|
||||
if isinstance(message_or_query, CallbackQuery):
|
||||
target_message = message_or_query.message
|
||||
else:
|
||||
target_message = message_or_query
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text="💳 Выберите тарифный план для создания нового ключа:",
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
|
||||
await state.update_data(tg_id=tg_id)
|
||||
@@ -154,11 +159,22 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
await callback_query.message.answer(
|
||||
f"💳 Недостаточно средств. Для продолжения необходимо пополнить баланс на {required_amount}₽.",
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=f"💳 Недостаточно средств. Для продолжения необходимо пополнить баланс на {required_amount}₽.",
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
return
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⏳ Подождите...", callback_data="creating_key"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="⏳ Подождите, создаем вам подключение...",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
expiry_time = datetime.now(moscow_tz) + timedelta(days=duration_days)
|
||||
await create_key(tg_id, expiry_time, None, session, callback_query, None, plan_id)
|
||||
await update_balance(tg_id, -plan_price, session)
|
||||
@@ -175,6 +191,7 @@ async def create_key(
|
||||
):
|
||||
"""Создаёт ключ с заданным сроком действия."""
|
||||
|
||||
target_message = message_or_query.message if isinstance(message_or_query, CallbackQuery) else message_or_query
|
||||
if not await check_connection_exists(tg_id):
|
||||
await add_connection(tg_id, balance=0.0, trial=0, session=session)
|
||||
logger.info(f"[Connection] Подключение создано для пользователя {tg_id}")
|
||||
@@ -202,25 +219,19 @@ async def create_key(
|
||||
builder.row(InlineKeyboardButton(text=country, callback_data=callback_data))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="profile"))
|
||||
|
||||
if isinstance(message_or_query, Message):
|
||||
await message_or_query.answer(
|
||||
"🌍 Пожалуйста, выберите страну для вашего ключа:",
|
||||
if target_message:
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text="🌍 Пожалуйста, выберите страну для вашего ключа:",
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
elif isinstance(message_or_query, CallbackQuery):
|
||||
await message_or_query.message.answer(
|
||||
"🌍 Пожалуйста, выберите страну для вашего ключа:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
elif tg_id is not None:
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text="🌍 Пожалуйста, выберите страну для вашего ключа:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
logger.error("[Country Selection] Невозможно определить идентификатор чата. Сообщение не отправлено.")
|
||||
|
||||
return
|
||||
|
||||
while True:
|
||||
@@ -259,42 +270,48 @@ async def create_key(
|
||||
except Exception as e:
|
||||
logger.error(f"[Error] Ошибка при создании ключа для пользователя {tg_id}: {e}")
|
||||
error_message = "❌ Произошла ошибка при создании подписки. Пожалуйста, попробуйте снова."
|
||||
if isinstance(message_or_query, Message):
|
||||
await message_or_query.answer(error_message)
|
||||
elif isinstance(message_or_query, CallbackQuery):
|
||||
await message_or_query.message.answer(error_message)
|
||||
if target_message:
|
||||
await edit_or_send_message(
|
||||
target_message=target_message, text=error_message, reply_markup=None, media_path=None
|
||||
)
|
||||
else:
|
||||
await bot.send_message(chat_id=tg_id, text=error_message)
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{public_link}"),
|
||||
InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{public_link}"),
|
||||
)
|
||||
if CONNECT_PHONE_BUTTON:
|
||||
builder.row(InlineKeyboardButton(text="📱 Подключить телефон", callback_data=f"connect_phone|{key_name}"))
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{key_name}"),
|
||||
InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key_name}"),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{email}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
expiry_time = expiry_time.replace(tzinfo=None).astimezone(moscow_tz)
|
||||
|
||||
remaining_time = expiry_time - datetime.now(moscow_tz)
|
||||
expiry_time_local = expiry_time.replace(tzinfo=None).astimezone(moscow_tz)
|
||||
remaining_time = expiry_time_local - datetime.now(moscow_tz)
|
||||
days = remaining_time.days
|
||||
key_message_text = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅")
|
||||
|
||||
if isinstance(message_or_query, Message):
|
||||
await message_or_query.answer(key_message_text, reply_markup=builder.as_markup())
|
||||
elif isinstance(message_or_query, CallbackQuery):
|
||||
await message_or_query.message.answer(key_message_text, reply_markup=builder.as_markup())
|
||||
if target_message:
|
||||
await edit_or_send_message(
|
||||
target_message=target_message, text=key_message_text, reply_markup=builder.as_markup(), media_path=None
|
||||
)
|
||||
else:
|
||||
await bot.send_message(chat_id=tg_id, text=key_message_text, reply_markup=builder.as_markup())
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text=key_message_text,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
if state:
|
||||
await state.clear()
|
||||
@@ -327,8 +344,11 @@ async def change_location_callback(callback_query: CallbackQuery, session: Any):
|
||||
builder.row(InlineKeyboardButton(text=country, callback_data=callback_data))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{old_key_name}"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"🌍 Пожалуйста, выберите новую локацию для вашей подписки:", reply_markup=builder.as_markup()
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="🌍 Пожалуйста, выберите новую локацию для вашей подписки:",
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при смене локации для пользователя {callback_query.from_user.id}: {e}")
|
||||
@@ -434,14 +454,17 @@ async def finalize_key_creation(
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{public_link}"),
|
||||
InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{public_link}"),
|
||||
)
|
||||
if CONNECT_PHONE_BUTTON:
|
||||
builder.row(InlineKeyboardButton(text="📱 Подключить телефон", callback_data=f"connect_phone|{key_name}"))
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{key_name}"),
|
||||
InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key_name}"),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{email}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"),
|
||||
@@ -452,7 +475,9 @@ async def finalize_key_creation(
|
||||
days = remaining_time.days
|
||||
key_message_text = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅")
|
||||
|
||||
await callback_query.message.answer(key_message_text, reply_markup=builder.as_markup())
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message, text=key_message_text, reply_markup=builder.as_markup(), media_path=None
|
||||
)
|
||||
|
||||
if state:
|
||||
await state.clear()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, USE_COUNTRY_SELECTION
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import ClientConfig, add_client, delete_client, extend_client_key, get_client_traffic, toggle_client
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, USE_COUNTRY_SELECTION
|
||||
from database import get_servers, store_key
|
||||
from handlers.utils import get_least_loaded_cluster
|
||||
from logger import logger
|
||||
|
||||
+53
-71
@@ -4,12 +4,13 @@ import os
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
import aiofiles
|
||||
import asyncpg
|
||||
import pytz
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
@@ -25,8 +26,6 @@ from config import (
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
|
||||
from bot import bot
|
||||
from database import (
|
||||
check_server_name_by_cluster,
|
||||
create_temporary_data,
|
||||
@@ -62,7 +61,7 @@ from handlers.texts import (
|
||||
SUCCESS_RENEWAL_MSG,
|
||||
key_message,
|
||||
)
|
||||
from handlers.utils import handle_error
|
||||
from handlers.utils import edit_or_send_message, handle_error
|
||||
from logger import logger
|
||||
|
||||
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
|
||||
@@ -74,24 +73,21 @@ router = Router()
|
||||
@router.message(F.text == "/subs")
|
||||
async def process_callback_or_message_view_keys(callback_query_or_message: Message | CallbackQuery, session: Any):
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
chat_id = callback_query_or_message.message.chat.id
|
||||
send_message = callback_query_or_message.message.answer
|
||||
send_photo = callback_query_or_message.message.answer_photo
|
||||
target_message = callback_query_or_message.message
|
||||
else:
|
||||
chat_id = callback_query_or_message.chat.id
|
||||
send_message = callback_query_or_message.answer
|
||||
send_photo = callback_query_or_message.answer_photo
|
||||
target_message = callback_query_or_message
|
||||
|
||||
try:
|
||||
records = await get_keys(chat_id, session)
|
||||
|
||||
records = await get_keys(target_message.chat.id, session)
|
||||
inline_keyboard, response_message = build_keys_response(records)
|
||||
|
||||
image_path = os.path.join("img", "pic_keys.jpg")
|
||||
await send_with_optional_image(send_message, send_photo, image_path, response_message, inline_keyboard)
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=target_message, text=response_message, reply_markup=inline_keyboard, media_path=image_path
|
||||
)
|
||||
except Exception as e:
|
||||
error_message = f"Ошибка при получении ключей: {e}"
|
||||
await send_message(text=error_message)
|
||||
await target_message.answer(text=error_message)
|
||||
|
||||
|
||||
def build_keys_response(records):
|
||||
@@ -134,32 +130,12 @@ def build_keys_response(records):
|
||||
return inline_keyboard, response_message
|
||||
|
||||
|
||||
async def send_with_optional_image(send_message, send_photo, image_path, text, keyboard):
|
||||
"""
|
||||
Отправляет сообщение с изображением, если файл существует. В противном случае отправляет только текст.
|
||||
"""
|
||||
if os.path.isfile(image_path):
|
||||
async with aiofiles.open(image_path, "rb") as image_file:
|
||||
image_data = await image_file.read()
|
||||
await send_photo(
|
||||
photo=BufferedInputFile(image_data, filename=os.path.basename(image_path)),
|
||||
caption=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await send_message(
|
||||
text=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("view_key|"))
|
||||
async def process_callback_view_key(callback_query: CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
try:
|
||||
record = await get_key_details(key_name, session)
|
||||
|
||||
if record:
|
||||
key = record["key"]
|
||||
expiry_time = record["expiry_time"]
|
||||
@@ -176,11 +152,9 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
|
||||
days = total_seconds // 86400
|
||||
hours = (total_seconds % 86400) // 3600
|
||||
minutes = (total_seconds % 3600) // 60
|
||||
|
||||
days_left_message = f"Осталось: <b>{days}</b> дней, <b>{hours}</b> часов, <b>{minutes}</b> минут"
|
||||
|
||||
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
|
||||
|
||||
response_message = key_message(
|
||||
key, formatted_expiry_date, days_left_message, server_name, country if USE_COUNTRY_SELECTION else None
|
||||
)
|
||||
@@ -231,24 +205,20 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
keyboard = builder.as_markup()
|
||||
|
||||
image_path = os.path.join("img", "pic_view.jpg")
|
||||
|
||||
if not os.path.isfile(image_path):
|
||||
await callback_query.message.answer("Файл изображения не найден.")
|
||||
return
|
||||
|
||||
async with aiofiles.open(image_path, "rb") as image_file:
|
||||
image_data = await image_file.read()
|
||||
await callback_query.message.answer_photo(
|
||||
photo=BufferedInputFile(image_data, filename="pic_view.jpg"),
|
||||
caption=response_message,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
text="<b>Информация о подписке не найдена.</b>",
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=response_message,
|
||||
reply_markup=keyboard,
|
||||
media_path=image_path,
|
||||
)
|
||||
else:
|
||||
await callback_query.message.answer(text="<b>Информация о подписке не найдена.</b>")
|
||||
except Exception as e:
|
||||
await handle_error(
|
||||
tg_id,
|
||||
@@ -298,7 +268,9 @@ async def process_callback_connect_phone(callback_query: CallbackQuery):
|
||||
builder.row(InlineKeyboardButton(text="📖 Ручная установка", callback_data="instructions"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{email}"))
|
||||
|
||||
await callback_query.message.answer(description, reply_markup=builder.as_markup())
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message, text=description, reply_markup=builder.as_markup(), media_path=None
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("update_subscription|"))
|
||||
@@ -330,13 +302,17 @@ async def process_callback_delete_key(callback_query: CallbackQuery):
|
||||
]
|
||||
)
|
||||
|
||||
await callback_query.message.answer(
|
||||
text="<b>Вы уверены, что хотите удалить ключ?</b>",
|
||||
reply_markup=confirmation_keyboard,
|
||||
)
|
||||
if callback_query.message.caption:
|
||||
await callback_query.message.edit_caption(
|
||||
caption="<b>Вы уверены, что хотите удалить ключ?</b>", reply_markup=confirmation_keyboard
|
||||
)
|
||||
else:
|
||||
await callback_query.message.edit_text(
|
||||
text="<b>Вы уверены, что хотите удалить ключ?</b>", reply_markup=confirmation_keyboard
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
logger.error(f"Ошибка при обработке запроса на удаление ключа {client_id}: {e}")
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("renew_key|"))
|
||||
@@ -345,7 +321,6 @@ async def process_callback_renew_key(callback_query: CallbackQuery, session: Any
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
try:
|
||||
record = await get_key_details(key_name, session)
|
||||
|
||||
if record:
|
||||
client_id = record["client_id"]
|
||||
expiry_time = record["expiry_time"]
|
||||
@@ -375,9 +350,11 @@ async def process_callback_renew_key(callback_query: CallbackQuery, session: Any
|
||||
expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
|
||||
await callback_query.message.answer(
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=response_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
else:
|
||||
await callback_query.message.answer("<b>Ключ не найден.</b>")
|
||||
@@ -390,7 +367,6 @@ async def process_callback_confirm_delete(callback_query: CallbackQuery, session
|
||||
email = callback_query.data.split("|")[1]
|
||||
try:
|
||||
record = await get_key_details(email, session)
|
||||
|
||||
if record:
|
||||
client_id = record["client_id"]
|
||||
response_message = "Ключ успешно удален."
|
||||
@@ -398,9 +374,9 @@ async def process_callback_confirm_delete(callback_query: CallbackQuery, session
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await delete_key(client_id, session)
|
||||
await callback_query.message.answer(
|
||||
response_message,
|
||||
reply_markup=keyboard,
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message, text=response_message, reply_markup=keyboard, media_path=None
|
||||
)
|
||||
|
||||
servers = await get_servers(session)
|
||||
@@ -410,24 +386,19 @@ async def process_callback_confirm_delete(callback_query: CallbackQuery, session
|
||||
tasks = []
|
||||
for cluster_id, _cluster in servers.items():
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении ключа {client_id}: {e}")
|
||||
|
||||
asyncio.create_task(delete_key_from_servers())
|
||||
|
||||
await delete_key(client_id, session)
|
||||
|
||||
else:
|
||||
response_message = "Ключ не найден или уже удален."
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await callback_query.message.answer(
|
||||
response_message,
|
||||
reply_markup=keyboard,
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message, text=response_message, reply_markup=keyboard, media_path=None
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
@@ -492,9 +463,11 @@ async def process_callback_renew_plan(callback_query: CallbackQuery, session: An
|
||||
builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"💳 Недостаточно средств. Пополните баланс на {required_amount}₽.",
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=f"💳 Недостаточно средств. Пополните баланс на {required_amount}₽.",
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -522,7 +495,16 @@ async def complete_key_renewal(tg_id, client_id, email, new_expiry_time, total_g
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
if callback_query:
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
try:
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=response_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка редактирования сообщения в complete_key_renewal: {e}")
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
else:
|
||||
await bot.send_message(tg_id, response_message, reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ from datetime import datetime
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
from aiohttp import web
|
||||
from config import DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, SUPERNODE, TRANSITION_DATE_STR, USE_COUNTRY_SELECTION
|
||||
|
||||
from config import DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, SUPERNODE, TRANSITION_DATE_STR, USE_COUNTRY_SELECTION
|
||||
from database import get_key_details, get_servers
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
|
||||
import asyncpg
|
||||
import pytz
|
||||
from aiogram import Bot, Router
|
||||
|
||||
from config import (
|
||||
DATABASE_URL,
|
||||
NOTIFICATION_TIME,
|
||||
@@ -16,7 +17,6 @@ from config import (
|
||||
TOTAL_GB,
|
||||
TRIAL_TIME_DISABLE,
|
||||
)
|
||||
|
||||
from database import (
|
||||
add_notification,
|
||||
check_notification_time,
|
||||
|
||||
@@ -6,8 +6,8 @@ import pytz
|
||||
from aiogram import Bot, Router, types
|
||||
from aiogram.exceptions import TelegramForbiddenError
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import NOTIFY_EXTRA_DAYS, NOTIFY_INACTIVE, NOTIFY_INACTIVE_TRAFFIC, SUPPORT_CHAT_URL, TRIAL_TIME
|
||||
|
||||
from config import NOTIFY_EXTRA_DAYS, NOTIFY_INACTIVE, NOTIFY_INACTIVE_TRAFFIC, SUPPORT_CHAT_URL, TRIAL_TIME
|
||||
from database import (
|
||||
add_notification,
|
||||
check_notification_time,
|
||||
|
||||
+13
-4
@@ -1,6 +1,7 @@
|
||||
from aiogram import F, Router
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import (
|
||||
CRYPTO_BOT_ENABLE,
|
||||
DONATIONS_ENABLE,
|
||||
@@ -10,6 +11,8 @@ from config import (
|
||||
YOOMONEY_ENABLE,
|
||||
)
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -56,15 +59,21 @@ async def handle_pay(callback_query: CallbackQuery):
|
||||
builder.row(InlineKeyboardButton(text="🎟️ Активировать купон", callback_data="activate_coupon"))
|
||||
if DONATIONS_ENABLE:
|
||||
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
payment_text = (
|
||||
"💸 <b>Выберите удобный способ пополнения баланса:</b>\n"
|
||||
"<blockquote>"
|
||||
"• Быстро и безопасно\n"
|
||||
"• Поддержка разных платежных систем\n"
|
||||
"• Моментальное зачисление средств 🚀\n"
|
||||
"</blockquote>",
|
||||
reply_markup=builder.as_markup(),
|
||||
"</blockquote>"
|
||||
)
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=payment_text,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
+4118
-1844
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -30,6 +30,8 @@ from handlers.payments.utils import send_payment_success_notification
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from logger import logger
|
||||
|
||||
from handlers.utils import edit_or_send_message
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -102,9 +104,11 @@ async def process_callback_pay_robokassa(
|
||||
await add_connection(tg_id, balance=0.0, trial=0, session=session)
|
||||
logger.info(f"Created new connection for user {tg_id} with balance 0.0.")
|
||||
|
||||
await callback_query.message.answer(
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="Выберите сумму пополнения:",
|
||||
reply_markup=builder.as_markup(),
|
||||
force_text=True
|
||||
)
|
||||
await state.set_state(ReplenishBalanceState.choosing_amount_robokassa)
|
||||
logger.info(f"Displayed amount selection for user {tg_id}.")
|
||||
@@ -119,7 +123,12 @@ async def process_amount_selection(
|
||||
data = callback_query.data.split("|")
|
||||
if len(data) != 3 or data[1] != "amount":
|
||||
logger.error("Ошибка: callback_data не соответствует формату.")
|
||||
await callback_query.message.answer("Ошибка: данные повреждены.")
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="Ошибка: данные повреждены.",
|
||||
reply_markup=types.InlineKeyboardMarkup(),
|
||||
force_text=True
|
||||
)
|
||||
return
|
||||
|
||||
amount_str = data[2]
|
||||
@@ -129,7 +138,12 @@ async def process_amount_selection(
|
||||
raise ValueError("Сумма должна быть положительным числом.")
|
||||
except ValueError as e:
|
||||
logger.error(f"Некорректное значение суммы: {amount_str}. Ошибка: {e}")
|
||||
await callback_query.message.answer("Некорректная сумма.")
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="Некорректная сумма.",
|
||||
reply_markup=types.InlineKeyboardMarkup(),
|
||||
force_text=True
|
||||
)
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
@@ -148,9 +162,11 @@ async def process_amount_selection(
|
||||
]
|
||||
)
|
||||
|
||||
await callback_query.message.answer(
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=f"Вы выбрали пополнение на {amount} рублей. Для оплаты перейдите по ссылке ниже:",
|
||||
reply_markup=confirm_keyboard,
|
||||
force_text=True
|
||||
)
|
||||
logger.info(f"Payment link sent to user {callback_query.message.chat.id}.")
|
||||
|
||||
@@ -228,9 +244,11 @@ async def process_custom_amount_selection(
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_robokassa"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"Пожалуйста, введите сумму пополнения.",
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="Пожалуйста, введите сумму пополнения.",
|
||||
reply_markup=builder.as_markup(),
|
||||
force_text=True
|
||||
)
|
||||
|
||||
await state.set_state(
|
||||
@@ -242,67 +260,70 @@ async def process_custom_amount_selection(
|
||||
async def handle_custom_amount_input(message: types.Message | types.CallbackQuery, state: FSMContext = None, session: Any = None):
|
||||
if isinstance(message, types.CallbackQuery):
|
||||
tg_id = message.message.chat.id
|
||||
target_message = message.message
|
||||
else:
|
||||
tg_id = message.chat.id
|
||||
target_message = message
|
||||
|
||||
logger.info(f"User {tg_id} initiated payment through ROBOKASSA")
|
||||
inv_id = 0
|
||||
|
||||
try:
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
user_data = await get_temporary_data(conn, tg_id)
|
||||
await conn.close()
|
||||
|
||||
if not user_data:
|
||||
await message.answer("Данные для оплаты не найдены. Попробуйте снова.")
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text="Данные для оплаты не найдены. Попробуйте снова.",
|
||||
reply_markup=types.InlineKeyboardMarkup()
|
||||
)
|
||||
return
|
||||
|
||||
state_type = user_data["state"]
|
||||
amount = user_data["data"].get("required_amount", 0)
|
||||
|
||||
if amount <= 0:
|
||||
await message.answer("Недостаточная сумма для пополнения.")
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text="Недостаточная сумма для пополнения.",
|
||||
reply_markup=types.InlineKeyboardMarkup()
|
||||
)
|
||||
return
|
||||
|
||||
payment_url = generate_payment_link(amount, inv_id, "Пополнение баланса", tg_id)
|
||||
|
||||
logger.info(f"Generated payment link for user {tg_id}: {payment_url}")
|
||||
|
||||
confirm_keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[InlineKeyboardButton(text="💳 Оплатить", url=payment_url)],
|
||||
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay_robokassa")],
|
||||
]
|
||||
)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💳 Оплатить", url=payment_url))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay_robokassa"))
|
||||
|
||||
if state_type == "waiting_for_payment":
|
||||
message_text = f"Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:"
|
||||
elif state_type == "waiting_for_renewal_payment":
|
||||
message_text = f"Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:"
|
||||
else:
|
||||
await message.answer("Некорректное состояние данных. Попробуйте снова.")
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text="Некорректное состояние данных. Попробуйте снова.",
|
||||
reply_markup=types.InlineKeyboardMarkup()
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(message, types.CallbackQuery):
|
||||
await message.message.answer(
|
||||
text=message_text,
|
||||
reply_markup=confirm_keyboard,
|
||||
)
|
||||
else:
|
||||
await message.answer(
|
||||
text=message_text,
|
||||
reply_markup=confirm_keyboard,
|
||||
)
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=message_text,
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
|
||||
if isinstance(state, FSMContext):
|
||||
await state.clear()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании платежа для пользователя {tg_id}: {e}")
|
||||
error_message = "Произошла ошибка при создании платежа. Попробуйте позже."
|
||||
|
||||
if isinstance(message, types.CallbackQuery):
|
||||
await message.message.answer(error_message)
|
||||
else:
|
||||
await message.answer(error_message)
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text="Произошла ошибка при создании платежа. Попробуйте позже.",
|
||||
reply_markup=types.InlineKeyboardMarkup()
|
||||
)
|
||||
|
||||
@@ -2442,7 +2442,6 @@ static const char __pyx_k_USE_NEW_PAYMENT_FLOW[] = "USE_NEW_PAYMENT_FLOW";
|
||||
static const char __pyx_k_clear_temporary_data[] = "clear_temporary_data";
|
||||
static const char __pyx_k_complete_key_renewal[] = "complete_key_renewal";
|
||||
static const char __pyx_k_InlineKeyboardBuilder[] = "InlineKeyboardBuilder";
|
||||
static const char __pyx_k_USE_COUNTRY_SELECTION[] = "USE_COUNTRY_SELECTION";
|
||||
static const char __pyx_k_aiogram_utils_keyboard[] = "aiogram.utils.keyboard";
|
||||
static const char __pyx_k_PAYMENT_SUCCESS_MESSAGE[] = "PAYMENT_SUCCESS_MESSAGE";
|
||||
static const char __pyx_k_handlers_payments_utils[] = "handlers.payments.utils";
|
||||
@@ -2495,7 +2494,6 @@ typedef struct {
|
||||
PyObject *__pyx_n_s_PROFILE;
|
||||
PyObject *__pyx_kp_u_RENEW;
|
||||
PyObject *__pyx_n_s_RENEW_KEY;
|
||||
PyObject *__pyx_n_s_USE_COUNTRY_SELECTION;
|
||||
PyObject *__pyx_n_s_USE_NEW_PAYMENT_FLOW;
|
||||
PyObject *__pyx_kp_u__10;
|
||||
PyObject *__pyx_kp_u__11;
|
||||
@@ -2683,7 +2681,6 @@ static int __pyx_m_clear(PyObject *m) {
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_PROFILE);
|
||||
Py_CLEAR(clear_module_state->__pyx_kp_u_RENEW);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_RENEW_KEY);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_USE_COUNTRY_SELECTION);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_USE_NEW_PAYMENT_FLOW);
|
||||
Py_CLEAR(clear_module_state->__pyx_kp_u__10);
|
||||
Py_CLEAR(clear_module_state->__pyx_kp_u__11);
|
||||
@@ -2849,7 +2846,6 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_PROFILE);
|
||||
Py_VISIT(traverse_module_state->__pyx_kp_u_RENEW);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_RENEW_KEY);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_USE_COUNTRY_SELECTION);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_USE_NEW_PAYMENT_FLOW);
|
||||
Py_VISIT(traverse_module_state->__pyx_kp_u__10);
|
||||
Py_VISIT(traverse_module_state->__pyx_kp_u__11);
|
||||
@@ -3025,7 +3021,6 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
|
||||
#define __pyx_n_s_PROFILE __pyx_mstate_global->__pyx_n_s_PROFILE
|
||||
#define __pyx_kp_u_RENEW __pyx_mstate_global->__pyx_kp_u_RENEW
|
||||
#define __pyx_n_s_RENEW_KEY __pyx_mstate_global->__pyx_n_s_RENEW_KEY
|
||||
#define __pyx_n_s_USE_COUNTRY_SELECTION __pyx_mstate_global->__pyx_n_s_USE_COUNTRY_SELECTION
|
||||
#define __pyx_n_s_USE_NEW_PAYMENT_FLOW __pyx_mstate_global->__pyx_n_s_USE_NEW_PAYMENT_FLOW
|
||||
#define __pyx_kp_u__10 __pyx_mstate_global->__pyx_kp_u__10
|
||||
#define __pyx_kp_u__11 __pyx_mstate_global->__pyx_kp_u__11
|
||||
@@ -6859,7 +6854,6 @@ static int __Pyx_CreateStringTabAndInitStrings(void) {
|
||||
{&__pyx_n_s_PROFILE, __pyx_k_PROFILE, sizeof(__pyx_k_PROFILE), 0, 0, 1, 1},
|
||||
{&__pyx_kp_u_RENEW, __pyx_k_RENEW, sizeof(__pyx_k_RENEW), 0, 1, 0, 0},
|
||||
{&__pyx_n_s_RENEW_KEY, __pyx_k_RENEW_KEY, sizeof(__pyx_k_RENEW_KEY), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_USE_COUNTRY_SELECTION, __pyx_k_USE_COUNTRY_SELECTION, sizeof(__pyx_k_USE_COUNTRY_SELECTION), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_USE_NEW_PAYMENT_FLOW, __pyx_k_USE_NEW_PAYMENT_FLOW, sizeof(__pyx_k_USE_NEW_PAYMENT_FLOW), 0, 0, 1, 1},
|
||||
{&__pyx_kp_u__10, __pyx_k__10, sizeof(__pyx_k__10), 0, 1, 0, 0},
|
||||
{&__pyx_kp_u__11, __pyx_k__11, sizeof(__pyx_k__11), 0, 1, 0, 0},
|
||||
@@ -7472,7 +7466,7 @@ if (!__Pyx_RefNanny) {
|
||||
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
|
||||
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
|
||||
|
||||
__pyx_t_2 = PyList_New(3); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 8, __pyx_L1_error)
|
||||
__pyx_t_2 = PyList_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 8, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_2);
|
||||
__Pyx_INCREF(__pyx_n_s_DATABASE_URL);
|
||||
__Pyx_GIVEREF(__pyx_n_s_DATABASE_URL);
|
||||
@@ -7480,9 +7474,6 @@ if (!__Pyx_RefNanny) {
|
||||
__Pyx_INCREF(__pyx_n_s_USE_NEW_PAYMENT_FLOW);
|
||||
__Pyx_GIVEREF(__pyx_n_s_USE_NEW_PAYMENT_FLOW);
|
||||
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 1, __pyx_n_s_USE_NEW_PAYMENT_FLOW)) __PYX_ERR(0, 8, __pyx_L1_error);
|
||||
__Pyx_INCREF(__pyx_n_s_USE_COUNTRY_SELECTION);
|
||||
__Pyx_GIVEREF(__pyx_n_s_USE_COUNTRY_SELECTION);
|
||||
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 2, __pyx_n_s_USE_COUNTRY_SELECTION)) __PYX_ERR(0, 8, __pyx_L1_error);
|
||||
__pyx_t_3 = __Pyx_Import(__pyx_n_s_config, __pyx_t_2, 0); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 8, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_3);
|
||||
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
|
||||
@@ -7494,10 +7485,6 @@ if (!__Pyx_RefNanny) {
|
||||
__Pyx_GOTREF(__pyx_t_2);
|
||||
if (PyDict_SetItem(__pyx_d, __pyx_n_s_USE_NEW_PAYMENT_FLOW, __pyx_t_2) < 0) __PYX_ERR(0, 8, __pyx_L1_error)
|
||||
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
|
||||
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_USE_COUNTRY_SELECTION); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 8, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_2);
|
||||
if (PyDict_SetItem(__pyx_d, __pyx_n_s_USE_COUNTRY_SELECTION, __pyx_t_2) < 0) __PYX_ERR(0, 8, __pyx_L1_error)
|
||||
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
|
||||
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
|
||||
|
||||
__pyx_t_3 = PyList_New(4); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 9, __pyx_L1_error)
|
||||
|
||||
Binary file not shown.
+1350
-1327
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+43
-77
@@ -1,13 +1,11 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import aiofiles
|
||||
import asyncpg
|
||||
from aiogram import F, Router
|
||||
from aiogram.enums import ParseMode
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import (
|
||||
BufferedInputFile,
|
||||
CallbackQuery,
|
||||
InlineKeyboardButton,
|
||||
InlineQuery,
|
||||
@@ -16,6 +14,7 @@ from aiogram.types import (
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import (
|
||||
DATABASE_URL,
|
||||
INSTRUCTIONS_BUTTON,
|
||||
@@ -25,7 +24,6 @@ from config import (
|
||||
TRIAL_TIME,
|
||||
USERNAME_BOT,
|
||||
)
|
||||
|
||||
from database import get_balance, get_key_count, get_last_payments, get_referral_stats, get_trial
|
||||
from handlers.buttons.profile import (
|
||||
ADD_SUB,
|
||||
@@ -39,6 +37,9 @@ from handlers.buttons.profile import (
|
||||
PAYMENT,
|
||||
)
|
||||
from handlers.texts import get_referral_link, invite_message_send, profile_message_send
|
||||
from logger import logger
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -53,11 +54,11 @@ async def process_callback_view_profile(
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
chat_id = callback_query_or_message.message.chat.id
|
||||
username = callback_query_or_message.from_user.full_name
|
||||
is_callback = True
|
||||
elif isinstance(callback_query_or_message, Message):
|
||||
target_message = callback_query_or_message.message
|
||||
else:
|
||||
chat_id = callback_query_or_message.chat.id
|
||||
username = callback_query_or_message.from_user.full_name
|
||||
is_callback = False
|
||||
target_message = callback_query_or_message
|
||||
|
||||
image_path = os.path.join("img", "profile.jpg")
|
||||
key_count = await get_key_count(chat_id)
|
||||
@@ -70,7 +71,6 @@ async def process_callback_view_profile(
|
||||
trial_status = await get_trial(chat_id, conn)
|
||||
|
||||
profile_message = profile_message_send(username, chat_id, int(balance), key_count)
|
||||
|
||||
if key_count == 0:
|
||||
profile_message += (
|
||||
"\n<blockquote>🔧 <i>Нажмите кнопку ➕ Подписка, чтобы настроить VPN-подключение</i></blockquote>"
|
||||
@@ -79,56 +79,28 @@ async def process_callback_view_profile(
|
||||
profile_message += f"\n<blockquote> <i>{NEWS_MESSAGE}</i></blockquote>"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if trial_status == 0 or key_count == 0:
|
||||
builder.row(InlineKeyboardButton(text=ADD_SUB, callback_data="create_key"))
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text=MY_SUBS, callback_data="view_keys"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=BALANCE,
|
||||
callback_data="balance",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=BALANCE, callback_data="balance"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=INVITE, callback_data="invite"),
|
||||
InlineKeyboardButton(text=GIFTS, callback_data="gifts"),
|
||||
)
|
||||
if INSTRUCTIONS_BUTTON:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=INSTRUCTIONS, callback_data="instructions"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=INSTRUCTIONS, callback_data="instructions"))
|
||||
if admin:
|
||||
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="start"))
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
async with aiofiles.open(image_path, "rb") as image_file:
|
||||
image_data = await image_file.read()
|
||||
if is_callback:
|
||||
await callback_query_or_message.message.answer_photo(
|
||||
photo=BufferedInputFile(image_data, filename="profile.jpg"),
|
||||
caption=profile_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await callback_query_or_message.answer_photo(
|
||||
photo=BufferedInputFile(image_data, filename="profile.jpg"),
|
||||
caption=profile_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
if is_callback:
|
||||
await callback_query_or_message.message.answer(
|
||||
text=profile_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await callback_query_or_message.answer(
|
||||
text=profile_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=profile_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=image_path,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
@@ -146,9 +118,13 @@ async def balance_handler(callback_query: CallbackQuery, session: Any):
|
||||
builder.row(InlineKeyboardButton(text=BALANCE_HISTORY, callback_data="balance_history"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"<b>Управление вашим балансом 💰</b>\n\nВаш баланс: {balance}",
|
||||
text = f"<b>Управление вашим балансом 💰</b>\n\nВаш баланс: {balance}"
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=text,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -176,7 +152,13 @@ async def balance_history_handler(callback_query: CallbackQuery, session: Any):
|
||||
else:
|
||||
history_text = "❌ У вас пока нет операций с балансом."
|
||||
|
||||
await callback_query.message.answer(history_text, reply_markup=builder.as_markup())
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=history_text,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
|
||||
@router.message(F.text == "/tariffs")
|
||||
@@ -186,7 +168,6 @@ async def view_tariffs_handler(callback_query: CallbackQuery):
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
image_path = os.path.join("img", "tariffs.jpg")
|
||||
|
||||
tariffs_message = "<b>🚀 Доступные тарифы VPN:</b>\n\n" + "\n".join(
|
||||
[
|
||||
f"{months} {'месяц' if months == '1' else 'месяца' if int(months) in [2, 3, 4] else 'месяцев'}: "
|
||||
@@ -196,19 +177,13 @@ async def view_tariffs_handler(callback_query: CallbackQuery):
|
||||
]
|
||||
)
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
async with aiofiles.open(image_path, "rb") as image_file:
|
||||
image_data = await image_file.read()
|
||||
await callback_query.message.answer_photo(
|
||||
photo=BufferedInputFile(image_data, filename="tariffs.jpg"),
|
||||
caption=tariffs_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
text=tariffs_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=tariffs_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=image_path,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "invite")
|
||||
@@ -216,30 +191,21 @@ async def invite_handler(callback_query: CallbackQuery):
|
||||
chat_id = callback_query.message.chat.id
|
||||
referral_link = get_referral_link(chat_id)
|
||||
referral_stats = await get_referral_stats(chat_id)
|
||||
invite_text = f"\nПриглашаю тебя пользоваться действительно быстрым VPN вместе:\n\n{referral_link}"
|
||||
|
||||
invite_message = invite_message_send(referral_link, referral_stats)
|
||||
image_path = os.path.join("img", "pic_invite.jpg")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
# builder.button(text="📢 Поделиться", switch_inline_query=invite_text)
|
||||
builder.button(text="👥 Пригласить друга", switch_inline_query="invite ")
|
||||
builder.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
builder.adjust(1)
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
async with aiofiles.open(image_path, "rb") as image_file:
|
||||
image_data = await image_file.read()
|
||||
await callback_query.message.answer_photo(
|
||||
photo=BufferedInputFile(image_data, filename="pic_invite.jpg"),
|
||||
caption=invite_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
text=invite_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=invite_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=image_path,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
|
||||
@router.inline_query(F.query.in_(["referral", "ref", "invite"]))
|
||||
|
||||
+35
-33
@@ -1,17 +1,17 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import aiofiles
|
||||
from aiogram import F, Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import (
|
||||
BufferedInputFile,
|
||||
CallbackQuery,
|
||||
InlineKeyboardButton,
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CAPTCHA_ENABLE,
|
||||
CHANNEL_EXISTS,
|
||||
@@ -21,8 +21,6 @@ from config import (
|
||||
DONATIONS_ENABLE,
|
||||
SUPPORT_CHAT_URL,
|
||||
)
|
||||
|
||||
from bot import bot
|
||||
from database import (
|
||||
add_connection,
|
||||
add_referral,
|
||||
@@ -36,6 +34,8 @@ from handlers.keys.key_management import create_key
|
||||
from handlers.texts import WELCOME_TEXT, get_about_vpn
|
||||
from logger import logger
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -59,7 +59,11 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
|
||||
if CAPTCHA_ENABLE and captcha:
|
||||
captcha_data = await generate_captcha(message, state)
|
||||
await message.answer(text=captcha_data["text"], reply_markup=captcha_data["markup"])
|
||||
await edit_or_send_message(
|
||||
target_message=message,
|
||||
text=captcha_data["text"],
|
||||
reply_markup=captcha_data["markup"],
|
||||
)
|
||||
return
|
||||
|
||||
if CHANNEL_EXISTS and CHANNEL_REQUIRED:
|
||||
@@ -70,8 +74,9 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
await state.update_data(original_text=original_text)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription"))
|
||||
await message.answer(
|
||||
f"Для использования бота, пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}",
|
||||
await edit_or_send_message(
|
||||
target_message=message,
|
||||
text=f"Для использования бота, пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
return
|
||||
@@ -84,8 +89,10 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
await state.update_data(start_text=message.text)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription"))
|
||||
await message.answer(
|
||||
f"Пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}", reply_markup=builder.as_markup()
|
||||
await edit_or_send_message(
|
||||
target_message=message,
|
||||
text=f"Пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -287,7 +294,7 @@ async def check_subscription_callback(callback_query: CallbackQuery, state: FSMC
|
||||
await callback_query.answer("Вы еще не подписаны на канал!", show_alert=True)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription"))
|
||||
await callback_query.message.answer(
|
||||
await callback_query.message.edit_text(
|
||||
f"Для использования бота, пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
@@ -305,7 +312,8 @@ async def check_subscription_callback(callback_query: CallbackQuery, state: FSMC
|
||||
|
||||
|
||||
async def show_start_menu(message: Message, admin: bool, session: Any):
|
||||
"""Функция для отображения стандартного меню"""
|
||||
"""Функция для отображения стандартного меню через редактирование сообщения.
|
||||
Если редактирование не удалось, отправляем новое сообщение."""
|
||||
logger.info(f"Показываю главное меню для пользователя {message.chat.id}")
|
||||
|
||||
image_path = os.path.join("img", "pic.jpg")
|
||||
@@ -334,35 +342,29 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
|
||||
|
||||
builder.row(InlineKeyboardButton(text="🌐 О VPN", callback_data="about_vpn"))
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
async with aiofiles.open(image_path, "rb") as image_from_buffer:
|
||||
image_data = await image_from_buffer.read()
|
||||
await message.answer_photo(
|
||||
photo=BufferedInputFile(image_data, filename="pic.jpg"),
|
||||
caption=WELCOME_TEXT,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await message.answer(
|
||||
text=WELCOME_TEXT,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await edit_or_send_message(
|
||||
target_message=message,
|
||||
text=WELCOME_TEXT,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=image_path,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "about_vpn")
|
||||
async def handle_about_vpn(callback_query: CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if DONATIONS_ENABLE:
|
||||
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL))
|
||||
if CHANNEL_EXISTS:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📢 Официальный канал", url=CHANNEL_URL),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="start"))
|
||||
text = get_about_vpn("3.2.3-minor")
|
||||
|
||||
await callback_query.message.answer(get_about_vpn("3.2.3-minor"), reply_markup=builder.as_markup())
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=text,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
force_text=False,
|
||||
)
|
||||
|
||||
+65
-1
@@ -1,13 +1,16 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
import string
|
||||
|
||||
import aiofiles
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
from config import DATABASE_URL
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardMarkup, InputMediaPhoto, Message
|
||||
|
||||
from bot import bot
|
||||
from config import DATABASE_URL
|
||||
from database import get_all_keys, get_servers
|
||||
from logger import logger
|
||||
|
||||
@@ -142,3 +145,64 @@ def format_time_until_deletion(seconds: int) -> str:
|
||||
parts.append(f"{minutes} минут")
|
||||
|
||||
return " и ".join(parts) if parts else "менее минуты"
|
||||
|
||||
|
||||
async def edit_or_send_message(
|
||||
target_message: Message,
|
||||
text: str,
|
||||
reply_markup: InlineKeyboardMarkup,
|
||||
media_path: str = None,
|
||||
disable_web_page_preview: bool = False,
|
||||
force_text: bool = False,
|
||||
):
|
||||
"""
|
||||
Универсальная функция для редактирования исходного сообщения target_message.
|
||||
|
||||
- Если media_path указан и существует, считается, что сообщение содержит фото, и используется редактирование медиа
|
||||
(замена фото и подписи) через edit_media. Если редактирование не удаётся, отправляется новое сообщение с фото.
|
||||
|
||||
- Если media_path не указан:
|
||||
- Если force_text=False и target_message уже имеет caption, пытаемся отредактировать подпись (edit_caption).
|
||||
- Иначе (или если редактирование caption не удалось) — редактируем текст (edit_text).
|
||||
|
||||
В случае неудачи fallback – отправка нового сообщения.
|
||||
"""
|
||||
if media_path and os.path.isfile(media_path):
|
||||
async with aiofiles.open(media_path, "rb") as f:
|
||||
image_data = await f.read()
|
||||
media = InputMediaPhoto(
|
||||
media=BufferedInputFile(image_data, filename=os.path.basename(media_path)),
|
||||
caption=text,
|
||||
)
|
||||
try:
|
||||
await target_message.edit_media(media=media, reply_markup=reply_markup)
|
||||
return
|
||||
except Exception as e:
|
||||
await target_message.answer_photo(
|
||||
photo=BufferedInputFile(image_data, filename=os.path.basename(media_path)),
|
||||
caption=text,
|
||||
reply_markup=reply_markup,
|
||||
disable_web_page_preview=disable_web_page_preview,
|
||||
)
|
||||
return
|
||||
else:
|
||||
if not force_text and target_message.caption is not None:
|
||||
try:
|
||||
await target_message.edit_caption(caption=text, reply_markup=reply_markup)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error("Ошибка редактирования подписи: %s", e)
|
||||
try:
|
||||
await target_message.edit_text(
|
||||
text=text,
|
||||
reply_markup=reply_markup,
|
||||
disable_web_page_preview=disable_web_page_preview,
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error("Ошибка редактирования текста: %s", e)
|
||||
await target_message.answer(
|
||||
text=text,
|
||||
reply_markup=reply_markup,
|
||||
disable_web_page_preview=disable_web_page_preview,
|
||||
)
|
||||
|
||||
@@ -3,8 +3,8 @@ from datetime import datetime, timezone
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import RENEWAL_PRICES
|
||||
|
||||
from config import RENEWAL_PRICES
|
||||
from keyboards.admin.panel_kb import build_admin_back_btn
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import Any
|
||||
from aiogram import Dispatcher
|
||||
|
||||
from .admin import AdminMiddleware
|
||||
from .delete import DeleteMessageMiddleware
|
||||
from .loggings import LoggingMiddleware
|
||||
from .session import SessionMiddleware
|
||||
from .throttling import ThrottlingMiddleware
|
||||
@@ -14,7 +13,6 @@ def register_middleware(dispatcher: Dispatcher) -> None:
|
||||
middlewares = [
|
||||
AdminMiddleware(),
|
||||
SessionMiddleware(),
|
||||
DeleteMessageMiddleware(),
|
||||
LoggingMiddleware(),
|
||||
ThrottlingMiddleware(),
|
||||
UserMiddleware(),
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import ADMIN_ID
|
||||
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import CallbackQuery, Message, TelegramObject
|
||||
|
||||
from keyboards.admin.coupons_kb import AdminCouponDeleteCallback
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback
|
||||
from keyboards.admin.sender_kb import AdminSenderCallback
|
||||
from keyboards.admin.servers_kb import AdminServerEditorCallback
|
||||
from keyboards.admin.users_kb import AdminUserEditorCallback, AdminUserKeyEditorCallback
|
||||
from logger import logger
|
||||
|
||||
pass_callbacks = [
|
||||
AdminPanelCallback,
|
||||
AdminCouponDeleteCallback,
|
||||
AdminSenderCallback,
|
||||
AdminServerEditorCallback,
|
||||
AdminUserEditorCallback,
|
||||
AdminUserKeyEditorCallback,
|
||||
]
|
||||
|
||||
|
||||
class DeleteMessageMiddleware(BaseMiddleware):
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: dict[str, Any],
|
||||
) -> Any:
|
||||
if isinstance(event, Message):
|
||||
if not event.text or not event.text.startswith("/start"):
|
||||
try:
|
||||
await event.bot.delete_message(event.chat.id, event.message_id - 1)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
await event.delete()
|
||||
|
||||
if isinstance(event, CallbackQuery):
|
||||
await event.answer()
|
||||
|
||||
if not await self._check_callbacks(event):
|
||||
await event.message.delete()
|
||||
|
||||
return await handler(event, data)
|
||||
|
||||
@staticmethod
|
||||
async def _check_callbacks(event: CallbackQuery) -> bool:
|
||||
for callback in pass_callbacks:
|
||||
if await callback.filter()(event):
|
||||
return True
|
||||
return False
|
||||
@@ -4,6 +4,7 @@ from typing import Any
|
||||
import asyncpg
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import DATABASE_URL
|
||||
|
||||
|
||||
|
||||
+11
-46
@@ -2,56 +2,16 @@ import asyncio
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import asyncpg
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import ADMIN_ID, DATABASE_URL, PING_TIME
|
||||
from ping3 import ping
|
||||
|
||||
from bot import bot
|
||||
from database import check_unique_server_name, create_server, get_servers
|
||||
from config import ADMIN_ID, DATABASE_URL, PING_TIME
|
||||
from database import get_servers
|
||||
from keyboards.admin.servers_kb import AdminServerEditorCallback
|
||||
from logger import logger
|
||||
|
||||
try:
|
||||
from config import CLUSTERS
|
||||
except ImportError:
|
||||
CLUSTERS = None
|
||||
logger.info("Переменная CLUSTERS не найдена в конфигурации. Добавьте сервера через админ-панель в боте!")
|
||||
|
||||
|
||||
async def sync_servers_with_db():
|
||||
"""
|
||||
Синхронизирует сервера из конфигурации CLUSTERS с базой данных.
|
||||
"""
|
||||
if CLUSTERS is None:
|
||||
logger.info("Конфигурация CLUSTERS не найдена. Синхронизация не будет выполнена.")
|
||||
return
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
|
||||
for cluster_name, servers in CLUSTERS.items():
|
||||
for _server_key, server_info in servers.items():
|
||||
exists = await check_unique_server_name(server_info["name"], conn, cluster_name)
|
||||
|
||||
if not exists:
|
||||
await create_server(
|
||||
cluster_name=cluster_name,
|
||||
server_name=server_info["name"],
|
||||
api_url=server_info["API_URL"],
|
||||
subscription_url=server_info["SUBSCRIPTION"],
|
||||
inbound_id=server_info["INBOUND_ID"],
|
||||
session=conn,
|
||||
)
|
||||
logger.info("✅ Синхронизация серверов завершена.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при синхронизации серверов: {e}")
|
||||
finally:
|
||||
if "conn" in locals():
|
||||
await conn.close()
|
||||
|
||||
|
||||
last_ping_times = {}
|
||||
last_notification_times = {}
|
||||
PING_SEMAPHORE = asyncio.Semaphore(3)
|
||||
@@ -89,7 +49,12 @@ async def notify_admin(server_name: str):
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Управление сервером", callback_data=f"manage_server|{server_name}"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="Управление сервером",
|
||||
callback_data=AdminServerEditorCallback(action="servers_manage", data=server_name).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
for admin_id in ADMIN_ID:
|
||||
await bot.send_message(
|
||||
@@ -116,7 +81,7 @@ async def check_servers():
|
||||
tasks = []
|
||||
server_info_list = []
|
||||
|
||||
for cluster_name, cluster_servers in servers.items():
|
||||
for _, cluster_servers in servers.items():
|
||||
for server in cluster_servers:
|
||||
original_api_url = server["api_url"]
|
||||
server_name = server["server_name"]
|
||||
@@ -141,7 +106,7 @@ async def check_servers():
|
||||
last_ping_times[server_name] = current_time
|
||||
|
||||
online_servers = [name for name, _ in server_info_list if name not in offline_servers]
|
||||
logger.info(f"Проверка серверов завершена. Онлайн: {len(online_servers)}, Оффлайн: {len(offline_servers)}")
|
||||
logger.info(f"Проверка серверов завершена. Доступно: {len(online_servers)}, Недоступно: {len(offline_servers)}")
|
||||
if offline_servers:
|
||||
logger.warning(f"🚨 Не отвечает {len(offline_servers)} серверов: {', '.join(offline_servers)}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user