FlowPayRenew/fixNotify/pay_history/custom/statistic/minorFixes
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
|
||||
|
||||
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ from aiogram.client.default import DefaultBotProperties
|
||||
from aiogram.enums import ParseMode
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
from aiogram.types import ErrorEvent
|
||||
from config import API_TOKEN
|
||||
|
||||
from config import API_TOKEN
|
||||
from logger import logger
|
||||
from middlewares.admin import AdminMiddleware
|
||||
from middlewares.database import DatabaseMiddleware
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import py3xui
|
||||
from config import LIMIT_IP
|
||||
|
||||
from config import LIMIT_IP
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -3,8 +3,8 @@ from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
from config import DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
|
||||
|
||||
from config import 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
|
||||
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import DATABASE_URL
|
||||
|
||||
from backup import backup_database
|
||||
from bot import bot
|
||||
from config import DATABASE_URL
|
||||
from database import delete_user_data
|
||||
from filters.admin import IsAdminFilter
|
||||
from logger import logger
|
||||
@@ -37,7 +37,7 @@ async def handle_admin_callback_query(callback_query: CallbackQuery, state: FSMC
|
||||
async def handle_admin_message(message: types.Message, state: FSMContext):
|
||||
await state.clear()
|
||||
|
||||
BOT_VERSION = "4.0.0-preAlpha(9)"
|
||||
BOT_VERSION = "4.0.0-preAlpha(14)"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
|
||||
@@ -6,10 +6,10 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
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, get_servers_from_db
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import create_key_on_cluster
|
||||
@@ -378,22 +378,19 @@ async def sync_cluster_handler(callback_query: types.CallbackQuery):
|
||||
)
|
||||
return
|
||||
|
||||
tasks = []
|
||||
|
||||
for key in keys_to_sync:
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
create_key_on_cluster(
|
||||
cluster_name,
|
||||
key["tg_id"],
|
||||
key["client_id"],
|
||||
key["email"],
|
||||
key["expiry_time"],
|
||||
)
|
||||
try:
|
||||
await create_key_on_cluster(
|
||||
cluster_name,
|
||||
key["tg_id"],
|
||||
key["client_id"],
|
||||
key["email"],
|
||||
key["expiry_time"],
|
||||
)
|
||||
)
|
||||
await asyncio.sleep(0.2)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при добавлении ключа {key['client_id']} в кластер {cluster_name}: {e}")
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
await callback_query.message.answer(
|
||||
f"✅ Ключи успешно синхронизированы для кластера {cluster_name}.",
|
||||
reply_markup=InlineKeyboardBuilder()
|
||||
|
||||
@@ -7,8 +7,8 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import TOTAL_GB
|
||||
|
||||
from config import TOTAL_GB
|
||||
from database import (
|
||||
delete_user_data,
|
||||
get_client_id_by_email,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
DOWNLOAD_IOS_BUTTON = "🍏 Скачать для iOS"
|
||||
DOWNLOAD_ANDROID_BUTTON = "🤖 Скачать для Android"
|
||||
IMPORT_IOS = "🍏 Подключить на iOS"
|
||||
IMPORT_ANDROID = "🤖 Подключить на Android"
|
||||
DOWNLOAD_IOS_BUTTON = "🍏 Скачать iOS"
|
||||
DOWNLOAD_ANDROID_BUTTON = "🤖 Скачать Android"
|
||||
IMPORT_IOS = "🍏 Подключить"
|
||||
IMPORT_ANDROID = "🤖 Подключить"
|
||||
PC_BUTTON = "💻 Компьютеры"
|
||||
TV_BUTTON = "📺 Андроид TV"
|
||||
|
||||
+1
-1
@@ -3,8 +3,8 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import RUB_TO_XTR
|
||||
|
||||
from config import RUB_TO_XTR
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import asyncpg
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import CONNECT_MACOS, CONNECT_WINDOWS, DATABASE_URL, SUPPORT_CHAT_URL
|
||||
|
||||
from config import CONNECT_MACOS, CONNECT_WINDOWS, DATABASE_URL, SUPPORT_CHAT_URL
|
||||
from handlers.texts import (
|
||||
CONNECT_TV_TEXT,
|
||||
INSTRUCTION_PC,
|
||||
|
||||
@@ -8,6 +8,8 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
@@ -19,8 +21,6 @@ from config import (
|
||||
TRIAL_TIME,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
|
||||
from bot import bot
|
||||
from database import (
|
||||
get_balance,
|
||||
get_trial,
|
||||
@@ -37,6 +37,7 @@ from handlers.buttons.add_subscribe import (
|
||||
TV_BUTTON,
|
||||
)
|
||||
from handlers.keys.key_utils import create_key_on_cluster
|
||||
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
|
||||
@@ -145,8 +146,10 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
|
||||
if balance < plan_price:
|
||||
required_amount = plan_price - balance
|
||||
|
||||
if USE_NEW_PAYMENT_FLOW:
|
||||
if USE_NEW_PAYMENT_FLOW == "YOOKASSA":
|
||||
await process_custom_amount_input(callback_query, session)
|
||||
elif USE_NEW_PAYMENT_FLOW == "ROBOKASSA":
|
||||
await handle_custom_amount_input(callback_query, session)
|
||||
else:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import asyncio
|
||||
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, TOTAL_GB
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import add_client, delete_client, extend_client_key
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, TOTAL_GB
|
||||
from database import get_servers_from_db
|
||||
from logger import logger
|
||||
|
||||
|
||||
+105
-72
@@ -7,21 +7,25 @@ from typing import Any
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
ENABLE_DELETE_KEY_BUTTON,
|
||||
ENABLE_UPDATE_SUBSCRIPTION_BUTTON,
|
||||
PUBLIC_LINK,
|
||||
RENEWAL_PLANS,
|
||||
TOTAL_GB,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
|
||||
from database import (
|
||||
delete_key,
|
||||
get_balance,
|
||||
get_servers_from_db,
|
||||
save_temporary_data,
|
||||
store_key,
|
||||
update_balance,
|
||||
update_key_expiry,
|
||||
@@ -40,9 +44,10 @@ from handlers.keys.key_utils import (
|
||||
renew_key_in_cluster,
|
||||
update_key_on_cluster,
|
||||
)
|
||||
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,
|
||||
INSUFFICIENT_FUNDS_MSG,
|
||||
KEY_NOT_FOUND_MSG,
|
||||
PLAN_SELECTION_MSG,
|
||||
SUCCESS_RENEWAL_MSG,
|
||||
@@ -113,7 +118,7 @@ def build_keys_response(records):
|
||||
inline_keyboard = builder.as_markup()
|
||||
response_message = (
|
||||
"<b>🔑 Список ваших подписок</b>\n\n"
|
||||
"<i>👇 Выберите подписку для управления или добавьте новую (например, для подключения нового устройства):</i>"
|
||||
"<i>👇 Выберите подписку для управления или добавьте новую для подключения дополнительного устройства:</i>"
|
||||
)
|
||||
return inline_keyboard, response_message
|
||||
|
||||
@@ -214,14 +219,22 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
|
||||
),
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="⏳ Продлить", callback_data=f"renew_key|{key_name}"
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить", callback_data=f"delete_key|{key_name}"
|
||||
),
|
||||
)
|
||||
# ✅ Добавлена проверка флага ENABLE_DELETE_KEY_BUTTON
|
||||
if ENABLE_DELETE_KEY_BUTTON:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="⏳ Продлить", callback_data=f"renew_key|{key_name}"
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить", callback_data=f"delete_key|{key_name}"
|
||||
),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="⏳ Продлить", callback_data=f"renew_key|{key_name}"
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
|
||||
@@ -480,14 +493,9 @@ async def process_callback_confirm_delete(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("renew_plan|"))
|
||||
async def process_callback_renew_plan(
|
||||
callback_query: types.CallbackQuery, session: Any
|
||||
):
|
||||
async def process_callback_renew_plan(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
plan, client_id = (
|
||||
callback_query.data.split("|")[1],
|
||||
callback_query.data.split("|")[2],
|
||||
)
|
||||
plan, client_id = callback_query.data.split("|")[1], callback_query.data.split("|")[2]
|
||||
days_to_extend = 30 * int(plan)
|
||||
|
||||
gb_multiplier = {"1": 1, "3": 3, "6": 6, "12": 12}
|
||||
@@ -505,70 +513,95 @@ async def process_callback_renew_plan(
|
||||
current_time = datetime.utcnow().timestamp() * 1000
|
||||
|
||||
if expiry_time <= current_time:
|
||||
new_expiry_time = int(
|
||||
current_time + timedelta(days=days_to_extend).total_seconds() * 1000
|
||||
)
|
||||
new_expiry_time = int(current_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
||||
else:
|
||||
new_expiry_time = int(
|
||||
expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000
|
||||
)
|
||||
new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
||||
|
||||
cost = RENEWAL_PLANS[plan]["price"]
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
if balance < cost:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="Пополнить баланс", callback_data="pay")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="👤 Личный кабинет", callback_data="profile"
|
||||
)
|
||||
required_amount = cost - balance
|
||||
|
||||
logger.info(f"[RENEW] Пользователю {tg_id} не хватает {required_amount}₽. Запуск доплаты через {USE_NEW_PAYMENT_FLOW}")
|
||||
|
||||
await save_temporary_data(
|
||||
session,
|
||||
tg_id,
|
||||
"waiting_for_renewal_payment",
|
||||
{
|
||||
"plan": plan,
|
||||
"client_id": client_id,
|
||||
"cost": cost,
|
||||
"required_amount": required_amount,
|
||||
"new_expiry_time": new_expiry_time,
|
||||
"total_gb": total_gb,
|
||||
"email": email,
|
||||
},
|
||||
)
|
||||
|
||||
await callback_query.message.answer(
|
||||
INSUFFICIENT_FUNDS_MSG,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
if USE_NEW_PAYMENT_FLOW == "YOOKASSA":
|
||||
logger.info(f"[RENEW] Запуск оплаты через Юкассу для пользователя {tg_id}")
|
||||
await process_custom_amount_input(callback_query, session)
|
||||
elif USE_NEW_PAYMENT_FLOW == "ROBOKASSA":
|
||||
logger.info(f"[RENEW] Запуск оплаты через Робокассу для пользователя {tg_id}")
|
||||
await handle_custom_amount_input(callback_query, session)
|
||||
else:
|
||||
logger.info(f"[RENEW] Отправка сообщения о доплате пользователю {tg_id}")
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"💳 Недостаточно средств. Пополните баланс на {required_amount}₽.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
return
|
||||
|
||||
response_message = SUCCESS_RENEWAL_MSG.format(
|
||||
months=RENEWAL_PLANS[plan]["months"]
|
||||
)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
|
||||
)
|
||||
|
||||
await callback_query.message.answer(
|
||||
response_message, reply_markup=builder.as_markup()
|
||||
)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
async def renew_key_on_servers():
|
||||
tasks = []
|
||||
for cluster_id in servers:
|
||||
task = asyncio.create_task(
|
||||
renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
client_id,
|
||||
new_expiry_time,
|
||||
total_gb,
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await update_balance(tg_id, -cost)
|
||||
await update_key_expiry(client_id, new_expiry_time)
|
||||
|
||||
await renew_key_on_servers()
|
||||
logger.info(f"[RENEW] Средств достаточно. Продление ключа для пользователя {tg_id}")
|
||||
await complete_key_renewal(tg_id, client_id, email, new_expiry_time, total_gb, cost, callback_query, plan)
|
||||
|
||||
else:
|
||||
await callback_query.message.answer(KEY_NOT_FOUND_MSG)
|
||||
logger.error(f"[RENEW] Ключ с client_id={client_id} не найден.")
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
logger.error(f"[RENEW] Ошибка при продлении ключа для пользователя {tg_id}: {e}")
|
||||
|
||||
|
||||
async def complete_key_renewal(tg_id, client_id, email, new_expiry_time, total_gb, cost, callback_query, plan):
|
||||
response_message = SUCCESS_RENEWAL_MSG.format(months=plan)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
if callback_query:
|
||||
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())
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
logger.info(f"[RENEW] Запуск продления ключа для пользователя {tg_id} на {plan} мес. на всех серверах.")
|
||||
|
||||
async def renew_key_on_servers():
|
||||
tasks = []
|
||||
for cluster_id in servers:
|
||||
task = asyncio.create_task(
|
||||
renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
client_id,
|
||||
new_expiry_time,
|
||||
total_gb,
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await update_balance(tg_id, -cost)
|
||||
await update_key_expiry(client_id, new_expiry_time)
|
||||
logger.info(f"[RENEW] Ключ {client_id} успешно продлён на {plan} мес. для пользователя {tg_id}.")
|
||||
|
||||
await renew_key_on_servers()
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import asyncio
|
||||
import base64
|
||||
from datetime import datetime
|
||||
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
from aiohttp import web
|
||||
from config import DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, TRANSITION_DATE_STR
|
||||
|
||||
from config import DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, TRANSITION_DATE_STR
|
||||
from database import get_servers_from_db
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
import pytz
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import add_client
|
||||
from config import (
|
||||
ADMIN_PASSWORD,
|
||||
ADMIN_USERNAME,
|
||||
@@ -12,9 +15,6 @@ from config import (
|
||||
TOTAL_GB,
|
||||
TRIAL_TIME,
|
||||
)
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import add_client
|
||||
from database import get_servers_from_db, store_key, use_trial
|
||||
from handlers.texts import INSTRUCTIONS
|
||||
from handlers.utils import generate_random_email, get_least_loaded_cluster
|
||||
|
||||
+141
-270
@@ -5,17 +5,19 @@ import asyncpg
|
||||
from aiogram import Bot, Router, types
|
||||
from aiogram.exceptions import TelegramForbiddenError
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from config import (
|
||||
ADMIN_PASSWORD,
|
||||
ADMIN_USERNAME,
|
||||
AUTO_DELETE_EXPIRED_KEYS,
|
||||
AUTO_RENEW_KEYS,
|
||||
DATABASE_URL,
|
||||
DEV_MODE,
|
||||
RENEWAL_PLANS,
|
||||
TOTAL_GB,
|
||||
TRIAL_TIME,
|
||||
)
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from database import (
|
||||
add_blocked_user,
|
||||
add_notification,
|
||||
@@ -124,119 +126,60 @@ async def notify_10h_keys(
|
||||
|
||||
logger.info(f"Найдено {len(records)} ключей для уведомления за 10 часов.")
|
||||
|
||||
async def process_record(record):
|
||||
tg_id = record["tg_id"]
|
||||
email = record["email"]
|
||||
expiry_time = record["expiry_time"]
|
||||
for record in records:
|
||||
await process_10h_record(record, bot, conn)
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
if time_left.total_seconds() <= 0:
|
||||
days_left_message = "Ключ истек"
|
||||
elif time_left.days > 0:
|
||||
days_left_message = f"{time_left.days}"
|
||||
else:
|
||||
hours_left = time_left.seconds // 3600
|
||||
days_left_message = f"{hours_left}"
|
||||
|
||||
message = KEY_EXPIRY_10H.format(
|
||||
email=email,
|
||||
expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
days_left_message=days_left_message,
|
||||
price=RENEWAL_PLANS["1"]["price"],
|
||||
)
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
if balance >= RENEWAL_PLANS["1"]["price"]:
|
||||
try:
|
||||
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
|
||||
new_expiry_time = int(
|
||||
(datetime.utcnow() + timedelta(days=30)).timestamp() * 1000
|
||||
)
|
||||
await update_key_expiry(record["client_id"], new_expiry_time)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
for cluster_id in servers:
|
||||
await renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
record["client_id"],
|
||||
new_expiry_time,
|
||||
TOTAL_GB,
|
||||
)
|
||||
logger.info(
|
||||
f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}."
|
||||
)
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE keys
|
||||
SET notified = FALSE, notified_24h = FALSE
|
||||
WHERE client_id = $1
|
||||
""",
|
||||
record["client_id"],
|
||||
)
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет", callback_data="profile"
|
||||
)
|
||||
]
|
||||
]
|
||||
)
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(
|
||||
f"Уведомление об успешном продлении отправлено клиенту {tg_id}."
|
||||
)
|
||||
except TelegramForbiddenError:
|
||||
logger.warning(
|
||||
f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users."
|
||||
)
|
||||
await add_blocked_user(tg_id, conn)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при продлении подписки для клиента {tg_id}: {e}")
|
||||
else:
|
||||
try:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.button(
|
||||
text="🔄 Продлить VPN", callback_data=f"renew_key|{email}"
|
||||
)
|
||||
keyboard.button(text="💳 Пополнить баланс", callback_data="pay")
|
||||
keyboard.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
keyboard.adjust(1)
|
||||
|
||||
await bot.send_message(
|
||||
tg_id, message, reply_markup=keyboard.as_markup()
|
||||
)
|
||||
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
|
||||
|
||||
await conn.execute(
|
||||
"UPDATE keys SET notified = TRUE WHERE client_id = $1",
|
||||
record["client_id"],
|
||||
)
|
||||
logger.info(
|
||||
f"Обновлено поле notified для клиента {record['client_id']}."
|
||||
)
|
||||
except TelegramForbiddenError:
|
||||
logger.warning(
|
||||
f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users."
|
||||
)
|
||||
await add_blocked_user(tg_id, conn)
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"Ошибка при отправке уведомления пользователю {tg_id}: {e}"
|
||||
)
|
||||
|
||||
await asyncio.gather(*(process_record(record) for record in records))
|
||||
logger.info("Обработка всех уведомлений за 10 часов завершена.")
|
||||
|
||||
|
||||
async def process_10h_record(record, bot, conn):
|
||||
tg_id = record["tg_id"]
|
||||
email = record["email"]
|
||||
expiry_time = record["expiry_time"]
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
days_left_message = (
|
||||
"Ключ истек" if time_left.total_seconds() <= 0 else f"{time_left.days}" if time_left.days > 0 else f"{time_left.seconds // 3600}"
|
||||
)
|
||||
|
||||
message = KEY_EXPIRY_10H.format(
|
||||
email=email,
|
||||
expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
days_left_message=days_left_message,
|
||||
price=RENEWAL_PLANS["1"]["price"],
|
||||
)
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
if AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
|
||||
try:
|
||||
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
|
||||
new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000)
|
||||
await update_key_expiry(record["client_id"], new_expiry_time)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
for cluster_id in servers:
|
||||
await renew_key_in_cluster(cluster_id, email, record["client_id"], new_expiry_time, TOTAL_GB)
|
||||
logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.")
|
||||
|
||||
await conn.execute("UPDATE keys SET notified = TRUE WHERE client_id = $1", record["client_id"])
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]]
|
||||
)
|
||||
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при продлении подписки для клиента {tg_id}: {e}")
|
||||
else:
|
||||
await send_renewal_notification(bot, tg_id, email, message, conn, record["client_id"], "notified")
|
||||
|
||||
|
||||
async def notify_24h_keys(
|
||||
bot: Bot,
|
||||
conn: asyncpg.Connection,
|
||||
@@ -256,127 +199,76 @@ async def notify_24h_keys(
|
||||
|
||||
logger.info(f"Найдено {len(records_24h)} ключей для уведомления за 24 часа.")
|
||||
|
||||
async def process_record(record):
|
||||
tg_id = record["tg_id"]
|
||||
email = record["email"]
|
||||
expiry_time = record["expiry_time"]
|
||||
for record in records_24h:
|
||||
await process_24h_record(record, bot, conn)
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
if time_left.total_seconds() <= 0:
|
||||
days_left_message = "Ключ истек"
|
||||
elif time_left.days > 0:
|
||||
days_left_message = f"{time_left.days}"
|
||||
else:
|
||||
hours_left = time_left.seconds // 3600
|
||||
days_left_message = f"{hours_left}"
|
||||
|
||||
message_24h = KEY_EXPIRY_24H.format(
|
||||
email=email,
|
||||
days_left_message=days_left_message,
|
||||
expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
if balance >= RENEWAL_PLANS["1"]["price"]:
|
||||
try:
|
||||
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
|
||||
new_expiry_time = int(
|
||||
(datetime.utcnow() + timedelta(days=30)).timestamp() * 1000
|
||||
)
|
||||
await update_key_expiry(record["client_id"], new_expiry_time)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
for cluster_id in servers:
|
||||
await renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
record["client_id"],
|
||||
new_expiry_time,
|
||||
TOTAL_GB,
|
||||
)
|
||||
logger.info(
|
||||
f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}."
|
||||
)
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE keys
|
||||
SET notified_24h = FALSE, notified = FALSE
|
||||
WHERE client_id = $1
|
||||
""",
|
||||
record["client_id"],
|
||||
)
|
||||
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет", callback_data="profile"
|
||||
)
|
||||
)
|
||||
await bot.send_message(
|
||||
tg_id,
|
||||
text=KEY_RENEWED,
|
||||
reply_markup=keyboard.as_markup(),
|
||||
)
|
||||
logger.info(
|
||||
f"Уведомление об успешном продлении отправлено клиенту {tg_id}."
|
||||
)
|
||||
except TelegramForbiddenError:
|
||||
logger.warning(
|
||||
f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users."
|
||||
)
|
||||
await add_blocked_user(tg_id, conn)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при продлении подписки для клиента {tg_id}: {e}")
|
||||
else:
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="🔄 Продлить VPN",
|
||||
callback_data=f"renew_key|{email}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="💳 Пополнить баланс",
|
||||
callback_data="pay",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет",
|
||||
callback_data="profile",
|
||||
)
|
||||
)
|
||||
keyboard = builder.as_markup()
|
||||
await bot.send_message(tg_id, message_24h, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление за 24 часа отправлено пользователю {tg_id}.")
|
||||
except TelegramForbiddenError:
|
||||
logger.warning(
|
||||
f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users."
|
||||
)
|
||||
await add_blocked_user(tg_id, conn)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при отправке уведомления за 24 часа пользователю {tg_id}: {e}"
|
||||
)
|
||||
|
||||
await conn.execute(
|
||||
"UPDATE keys SET notified_24h = TRUE WHERE client_id = $1",
|
||||
record["client_id"],
|
||||
)
|
||||
logger.info(f"Обновлено поле notified_24h для клиента {record['client_id']}.")
|
||||
|
||||
await asyncio.gather(*(process_record(record) for record in records_24h))
|
||||
logger.info("Обработка всех уведомлений за 24 часа завершена.")
|
||||
|
||||
|
||||
|
||||
async def process_24h_record(record, bot, conn):
|
||||
tg_id = record["tg_id"]
|
||||
email = record["email"]
|
||||
expiry_time = record["expiry_time"]
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
days_left_message = (
|
||||
"Ключ истек" if time_left.total_seconds() <= 0 else f"{time_left.days}" if time_left.days > 0 else f"{time_left.seconds // 3600}"
|
||||
)
|
||||
|
||||
message_24h = KEY_EXPIRY_24H.format(
|
||||
email=email,
|
||||
days_left_message=days_left_message,
|
||||
expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
if AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
|
||||
try:
|
||||
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
|
||||
new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000)
|
||||
await update_key_expiry(record["client_id"], new_expiry_time)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
for cluster_id in servers:
|
||||
await renew_key_in_cluster(cluster_id, email, record["client_id"], new_expiry_time, TOTAL_GB)
|
||||
logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.")
|
||||
|
||||
await conn.execute("UPDATE keys SET notified_24h = TRUE WHERE client_id = $1", record["client_id"])
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]]
|
||||
)
|
||||
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при продлении подписки для клиента {tg_id}: {e}")
|
||||
else:
|
||||
await send_renewal_notification(bot, tg_id, email, message_24h, conn, record["client_id"], "notified_24h")
|
||||
|
||||
|
||||
async def send_renewal_notification(bot, tg_id, email, message, conn, client_id, flag):
|
||||
try:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.row(types.InlineKeyboardButton(text="🔄 Продлить VPN", callback_data=f"renew_key|{email}"))
|
||||
keyboard.row(types.InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
|
||||
keyboard.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await bot.send_message(tg_id, message, reply_markup=keyboard.as_markup())
|
||||
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
|
||||
|
||||
await conn.execute(f"UPDATE keys SET {flag} = TRUE WHERE client_id = $1", client_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")
|
||||
|
||||
|
||||
async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
logger.info("Проверка пользователей, не активировавших пробный период...")
|
||||
|
||||
@@ -461,9 +353,7 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
current_time,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Найдено {len(expiring_keys)} подписок, срок действия которых скоро истекает."
|
||||
)
|
||||
logger.info(f"Найдено {len(expiring_keys)} подписок, срок действия которых скоро истекает.")
|
||||
|
||||
for record in expiring_keys:
|
||||
try:
|
||||
@@ -499,22 +389,16 @@ async def process_key(record, bot, conn):
|
||||
)
|
||||
|
||||
try:
|
||||
if balance >= RENEWAL_PLANS["1"]["price"]:
|
||||
if AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
|
||||
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
|
||||
new_expiry_time = int(
|
||||
(datetime.utcnow() + timedelta(days=30)).timestamp() * 1000
|
||||
)
|
||||
new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000)
|
||||
await update_key_expiry(client_id, new_expiry_time)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
for cluster_id in servers:
|
||||
await renew_key_in_cluster(
|
||||
cluster_id, email, client_id, new_expiry_time, TOTAL_GB
|
||||
)
|
||||
logger.info(
|
||||
f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}."
|
||||
)
|
||||
await renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, TOTAL_GB)
|
||||
logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.")
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -524,40 +408,27 @@ async def process_key(record, bot, conn):
|
||||
""",
|
||||
client_id,
|
||||
)
|
||||
logger.info(
|
||||
f"Флаги notified и notified_24 сброшены для клиента с ID {client_id}."
|
||||
)
|
||||
try:
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(
|
||||
f"Уведомление об успешном продлении отправлено клиенту {tg_id}."
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления клиенту {tg_id}: {e}")
|
||||
logger.info(f"Флаги notified сброшены для клиента {client_id}.")
|
||||
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
|
||||
|
||||
else:
|
||||
message_expired = "Ваша подписка истекла и была удалена. Получите новую через личный кабинет"
|
||||
message_expired = "Ваша подписка истекла. Пополните баланс для продления."
|
||||
await bot.send_message(tg_id, text=message_expired, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об истечении подписки отправлено пользователю {tg_id}.")
|
||||
|
||||
try:
|
||||
await bot.send_message(
|
||||
tg_id, text=message_expired, reply_markup=keyboard
|
||||
)
|
||||
logger.info(
|
||||
f"Уведомление об истечении подписки и удалении ключа отправлено пользователю {tg_id}."
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при отправке уведомления об истечении подписки пользователю {tg_id}: {e}"
|
||||
)
|
||||
if AUTO_DELETE_EXPIRED_KEYS:
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
for cluster_id in servers:
|
||||
await delete_key_from_cluster(cluster_id, email, client_id)
|
||||
logger.info(f"Клиент {client_id} удален из кластера {cluster_id}.")
|
||||
|
||||
for cluster_id in servers:
|
||||
await delete_key_from_cluster(cluster_id, email, client_id)
|
||||
logger.info(f"Клиент {client_id} удален из кластера {cluster_id}.")
|
||||
|
||||
await delete_key(client_id)
|
||||
logger.info(f"Ключ для клиента с ID {client_id} удален из базы данных.")
|
||||
await delete_key(client_id)
|
||||
logger.info(f"Ключ {client_id} удалён из базы данных.")
|
||||
else:
|
||||
logger.info(f"Ключ {client_id} НЕ был удалён (AUTO_DELETE_EXPIRED_KEYS=False).")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from aiogram import Router
|
||||
|
||||
from config import (
|
||||
CRYPTO_BOT_ENABLE,
|
||||
ROBOKASSA_ENABLE,
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import hashlib
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiohttp import web
|
||||
from robokassa import HashAlgorithm, Robokassa
|
||||
|
||||
from config import (
|
||||
DATABASE_URL,
|
||||
ROBOKASSA_ENABLE,
|
||||
ROBOKASSA_LOGIN,
|
||||
ROBOKASSA_PASSWORD1,
|
||||
ROBOKASSA_PASSWORD2,
|
||||
ROBOKASSA_TEST_MODE,
|
||||
)
|
||||
from robokassa import HashAlgorithm, Robokassa
|
||||
|
||||
from database import (
|
||||
add_connection,
|
||||
add_payment,
|
||||
check_connection_exists,
|
||||
get_key_count,
|
||||
get_temporary_data,
|
||||
update_balance,
|
||||
)
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
@@ -242,17 +245,31 @@ async def process_custom_amount_selection(
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
async def handle_custom_amount_input(message: types.Message, state: FSMContext):
|
||||
tg_id = message.chat.id
|
||||
logger.info(f"User {tg_id} entered custom amount: {message.text}")
|
||||
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
|
||||
else:
|
||||
tg_id = message.chat.id
|
||||
|
||||
logger.info(f"User {tg_id} initiated payment through ROBOKASSA")
|
||||
inv_id = 0
|
||||
|
||||
try:
|
||||
amount = int(message.text)
|
||||
if amount <= 0:
|
||||
raise ValueError("Сумма должна быть положительным числом.")
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
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("Данные для оплаты не найдены. Попробуйте снова.")
|
||||
return
|
||||
|
||||
state_type = user_data["state"]
|
||||
amount = user_data["data"].get("required_amount", 0)
|
||||
|
||||
if amount <= 0:
|
||||
await message.answer("Недостаточная сумма для пополнения.")
|
||||
return
|
||||
|
||||
payment_url = generate_payment_link(amount, inv_id, "Пополнение баланса", tg_id)
|
||||
|
||||
@@ -260,18 +277,38 @@ async def handle_custom_amount_input(message: types.Message, state: FSMContext):
|
||||
|
||||
confirm_keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Оплатить", url=payment_url)],
|
||||
[InlineKeyboardButton(text="💳 Оплатить", url=payment_url)],
|
||||
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay_robokassa")],
|
||||
]
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
text=f"Вы выбрали пополнение на {amount} рублей. Для оплаты перейдите по ссылке ниже:",
|
||||
reply_markup=confirm_keyboard,
|
||||
)
|
||||
await state.clear()
|
||||
except ValueError as e:
|
||||
logger.error(f"Некорректная сумма от пользователя {tg_id}: {e}")
|
||||
await message.answer(
|
||||
text="Введите корректную сумму в рублях (целое положительное число)."
|
||||
)
|
||||
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("Некорректное состояние данных. Попробуйте снова.")
|
||||
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,
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
+2074
-1185
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1040
-772
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+20
-10
@@ -1,13 +1,13 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import DATABASE_URL, NEWS_MESSAGE, RENEWAL_PLANS
|
||||
from typing import Any
|
||||
|
||||
from config import DATABASE_URL, NEWS_MESSAGE, RENEWAL_PLANS
|
||||
from database import get_balance, get_key_count, get_referral_stats, get_trial
|
||||
from handlers.buttons.profile import (
|
||||
ADD_SUB,
|
||||
@@ -56,7 +56,7 @@ async def process_callback_view_profile(
|
||||
)
|
||||
|
||||
if key_count == 0:
|
||||
profile_message += "\n<pre>🔧 <i>Нажмите кнопку ➕ Устройство, чтобы настроить VPN-подключение</i></pre>"
|
||||
profile_message += "\n<pre>🔧 <i>Нажмите кнопку ➕ Подписка, чтобы настроить VPN-подключение</i></pre>"
|
||||
else:
|
||||
profile_message += f"\n<pre> <i>{NEWS_MESSAGE}</i></pre>"
|
||||
|
||||
@@ -138,16 +138,25 @@ async def balance_history_handler(callback_query: types.CallbackQuery, session:
|
||||
FROM payments
|
||||
WHERE tg_id = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 3
|
||||
"""
|
||||
records = await session.fetch(query, callback_query.from_user.id)
|
||||
|
||||
history_text = "📊 История операций с балансом:\n\n"
|
||||
for record in records:
|
||||
amount = record['amount']
|
||||
payment_system = record['payment_system']
|
||||
status = record['status']
|
||||
date = record['created_at'].strftime('%Y-%m-%d %H:%M:%S')
|
||||
history_text += f"<b>Сумма:</b> {amount}\n<b>Способ оплаты:</b> {payment_system}\n<b>Статус:</b> {status}\n<b>Дата:</b> {date}\n\n"
|
||||
if records:
|
||||
history_text = "📊 <b>Последние 3 операции с балансом:</b>\n\n"
|
||||
for record in records:
|
||||
amount = record['amount']
|
||||
payment_system = record['payment_system']
|
||||
status = record['status']
|
||||
date = record['created_at'].strftime('%Y-%m-%d %H:%M:%S')
|
||||
history_text += (
|
||||
f"<b>Сумма:</b> {amount}₽\n"
|
||||
f"<b>Способ оплаты:</b> {payment_system}\n"
|
||||
f"<b>Статус:</b> {status}\n"
|
||||
f"<b>Дата:</b> {date}\n\n"
|
||||
)
|
||||
else:
|
||||
history_text = "❌ У вас пока нет операций с балансом."
|
||||
|
||||
await callback_query.message.answer(
|
||||
history_text,
|
||||
@@ -155,6 +164,7 @@ async def balance_history_handler(callback_query: types.CallbackQuery, session:
|
||||
)
|
||||
|
||||
|
||||
|
||||
@router.message(F.text == "/tariffs")
|
||||
@router.callback_query(F.data == "view_tariffs")
|
||||
async def view_tariffs_handler(callback_query: types.CallbackQuery):
|
||||
|
||||
+11
-6
@@ -11,6 +11,7 @@ from aiogram.types import (
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import (
|
||||
CHANNEL_EXISTS,
|
||||
CHANNEL_URL,
|
||||
@@ -21,7 +22,6 @@ from config import (
|
||||
DOWNLOAD_IOS,
|
||||
SUPPORT_CHAT_URL,
|
||||
)
|
||||
|
||||
from database import (
|
||||
add_connection,
|
||||
add_referral,
|
||||
@@ -200,17 +200,22 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL),
|
||||
InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL),
|
||||
)
|
||||
if CHANNEL_EXISTS:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL),
|
||||
InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL)
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL)
|
||||
)
|
||||
|
||||
if admin:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="🌐 О нашем VPN", callback_data="about_vpn"))
|
||||
builder.row(InlineKeyboardButton(text="🌐 О VPN", callback_data="about_vpn"))
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_from_buffer:
|
||||
|
||||
+1
-1
@@ -4,9 +4,9 @@ import re
|
||||
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
from config import DATABASE_URL
|
||||
|
||||
from bot import bot
|
||||
from config import DATABASE_URL
|
||||
from database import get_servers_from_db
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import ADMIN_ID
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Any
|
||||
import asyncpg
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import DATABASE_URL
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -5,10 +5,10 @@ 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 config import ADMIN_ID, DATABASE_URL, PING_TIME
|
||||
from database import get_servers_from_db
|
||||
from logger import logger
|
||||
|
||||
|
||||
Reference in New Issue
Block a user