fiatCryptobot/flowPayments/Buttons/fixNotify/BanOfUsers/fixTrial and more

This commit is contained in:
Vladless
2025-01-09 02:40:39 +03:00
parent 6cd7e8acee
commit 10df771a15
27 changed files with 7024 additions and 3310 deletions
+13 -1
View File
@@ -99,4 +99,16 @@ CREATE TABLE IF NOT EXISTS gifts
recipient_tg_id BIGINT,
CONSTRAINT fk_sender FOREIGN KEY (sender_tg_id) REFERENCES users (tg_id),
CONSTRAINT fk_recipient FOREIGN KEY (recipient_tg_id) REFERENCES users (tg_id)
);
);
CREATE TABLE IF NOT EXISTS temporary_data (
tg_id BIGINT PRIMARY KEY NOT NULL,
state TEXT NOT NULL,
data JSONB NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS blocked_users (
tg_id BIGINT PRIMARY KEY,
blocked_at TIMESTAMP DEFAULT NOW()
);
+40
View File
@@ -1,3 +1,4 @@
import json
from datetime import datetime
from typing import Any
@@ -7,6 +8,45 @@ from config import DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
from logger import logger
async def save_temporary_data(session, tg_id: int, state: str, data: dict):
"""Сохраняет временные данные пользователя."""
await session.execute(
"""
INSERT INTO temporary_data (tg_id, state, data, updated_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT (tg_id)
DO UPDATE SET state = $2, data = $3, updated_at = $4
""",
tg_id, state, json.dumps(data), datetime.utcnow()
)
async def get_temporary_data(session, tg_id: int) -> dict | None:
"""Извлекает временные данные пользователя."""
result = await session.fetchrow(
"SELECT state, data FROM temporary_data WHERE tg_id = $1",
tg_id
)
if result:
return {
"state": result["state"],
"data": json.loads(result["data"])
}
return None
async def clear_temporary_data(session, tg_id: int):
await session.execute(
"DELETE FROM temporary_data WHERE tg_id = $1",
tg_id
)
async def add_blocked_user(tg_id: int, conn: asyncpg.Connection):
await conn.execute(
"INSERT INTO blocked_users (tg_id) VALUES ($1) ON CONFLICT (tg_id) DO NOTHING",
tg_id
)
async def init_db(file_path: str = "assets/schema.sql"):
with open(file_path) as file:
sql_content = file.read()
+98 -1
View File
@@ -3,6 +3,7 @@ from datetime import datetime
from io import BytesIO
from typing import Any
import asyncpg
from aiogram import F, Router, types
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
@@ -12,6 +13,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from backup import backup_database
from bot import bot
from config import DATABASE_URL
from filters.admin import IsAdminFilter
from logger import logger
@@ -34,7 +36,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 = "3.2.5-beta" # Укажите текущую версию бота
BOT_VERSION = "4.0.0-preAlpha" # Укажите текущую версию бота
builder = InlineKeyboardBuilder()
builder.row(
@@ -84,6 +86,9 @@ async def handle_bot_management(callback_query: types.CallbackQuery):
builder.row(
InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot")
)
builder.row(
InlineKeyboardButton(text="🚫 Баны", callback_data="ban_user")
)
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="admin")
)
@@ -93,6 +98,7 @@ async def handle_bot_management(callback_query: types.CallbackQuery):
)
@router.callback_query(F.data == "user_stats", IsAdminFilter())
async def user_stats_menu(callback_query: CallbackQuery, session: Any):
try:
@@ -434,3 +440,94 @@ async def user_editor_menu(callback_query: CallbackQuery):
await callback_query.message.answer(
"👇 Выберите способ поиска пользователя:", reply_markup=builder.as_markup()
)
@router.callback_query(F.data == "ban_user")
async def handle_ban_user(callback_query: types.CallbackQuery):
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="📄 Выгрузить в CSV", callback_data="export_to_csv")
)
builder.row(
InlineKeyboardButton(text="🗑️ Удалить из БД", callback_data="delete_banned_users")
)
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")
)
await callback_query.message.answer(
"🚫 Заблокировавшие бота\n\n"
"Здесь можно просматривать и удалять пользователей, которые забанили вашего бота!",
reply_markup=builder.as_markup(),
)
@router.callback_query(F.data == "export_to_csv")
async def export_banned_users_to_csv(callback_query: types.CallbackQuery):
conn = await asyncpg.connect(DATABASE_URL)
try:
banned_users = await conn.fetch("SELECT tg_id, blocked_at FROM blocked_users")
import csv
import io
csv_output = io.StringIO()
writer = csv.writer(csv_output)
writer.writerow(["tg_id", "blocked_at"])
for user in banned_users:
writer.writerow([user["tg_id"], user["blocked_at"]])
csv_output.seek(0)
document = BufferedInputFile(
file=csv_output.getvalue().encode("utf-8"),
filename="banned_users.csv"
)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")
)
await callback_query.message.answer_document(
document=document,
caption="📄 Список заблокировавших бота пользователей",
reply_markup=builder.as_markup(),
)
except Exception as e:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")
)
await callback_query.message.answer(
text=f"Ошибка при выгрузке CSV: {e}",
reply_markup=builder.as_markup(),
)
finally:
await conn.close()
@router.callback_query(F.data == "delete_banned_users")
async def delete_banned_users(callback_query: types.CallbackQuery):
conn = await asyncpg.connect(DATABASE_URL)
try:
deleted_count = await conn.execute("DELETE FROM blocked_users")
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")
)
await callback_query.message.answer(
text=f"🗑️ Удалено {deleted_count} записей о заблокировавших пользователях.",
reply_markup=builder.as_markup(),
)
except Exception as e:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")
)
await callback_query.message.answer(
text=f"Ошибка при удалении записей: {e}",
reply_markup=builder.as_markup(),
)
finally:
await conn.close()
-1
View File
@@ -56,7 +56,6 @@ async def prompt_username(callback_query: CallbackQuery, state: FSMContext):
async def handle_username_input(
message: types.Message, state: FSMContext, session: Any
):
# Extract the username from a message text by removing leading '@' and the Telegram URL prefix
username = message.text.strip().lstrip('@').replace('https://t.me/', '')
user_record = await session.fetchrow(
"SELECT tg_id FROM users WHERE username = $1", username
+6
View File
@@ -0,0 +1,6 @@
DOWNLOAD_IOS_BUTTON = "🍏 Скачать для iOS"
DOWNLOAD_ANDROID_BUTTON = "🤖 Скачать для Android"
IMPORT_IOS = "🍏 Подключить на iOS"
IMPORT_ANDROID = "🤖 Подключить на Android"
PC_BUTTON = "💻 Компьютеры"
TV_BUTTON = "📺 Андроид TV"
+3
View File
@@ -0,0 +1,3 @@
RENEW_KEY = "🔄 Продлить подписку"
ADD_KEY = " Добавить подписку"
PROFILE = "👤 Личный кабинет"
+2 -2
View File
@@ -1,5 +1,5 @@
ADD_SUB = " Устройство"
MY_SUBS = "📱 Мои устройства"
ADD_SUB = " Подписка"
MY_SUBS = "📱 Мои подписки"
PAYMENT = "💳 Пополнить баланс"
INVITE = "👥 Пригласить"
GIFTS = "🎁 Подарить"
+3 -2
View File
@@ -8,6 +8,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import CONNECT_MACOS, CONNECT_WINDOWS, DATABASE_URL, SUPPORT_CHAT_URL
from handlers.texts import CONNECT_TV_TEXT, INSTRUCTION_PC, INSTRUCTIONS, KEY_MESSAGE, SUBSCRIPTION_DETAILS_TEXT
from logger import logger
router = Router()
@@ -117,7 +118,7 @@ async def process_continue_tv(callback_query: types.CallbackQuery):
key_name = callback_query.data.split("|")[1]
tg_id = callback_query.from_user.id
print(f"tg_id: {tg_id}, key_name: {key_name}") # Отладочный вывод
logger.info(f"tg_id: {tg_id}, key_name: {key_name}")
conn = await asyncpg.connect(DATABASE_URL)
try:
@@ -131,7 +132,7 @@ async def process_continue_tv(callback_query: types.CallbackQuery):
key_name,
)
print(f"Query result: {record}") # Отладочный вывод результата
logger.info(f"Query result: {record}")
finally:
await conn.close()
+62 -22
View File
@@ -9,6 +9,7 @@ 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,
@@ -18,9 +19,19 @@ from config import (
RENEWAL_PRICES,
SUPPORT_CHAT_URL,
TRIAL_TIME,
USE_NEW_PAYMENT_FLOW,
)
from database import get_balance, get_trial, save_temporary_data, store_key, update_balance
from handlers.buttons.add_subscribe import (
DOWNLOAD_ANDROID_BUTTON,
DOWNLOAD_IOS_BUTTON,
IMPORT_ANDROID,
IMPORT_IOS,
PC_BUTTON,
TV_BUTTON,
)
from database import get_balance, get_trial, store_key, update_balance
from handlers.keys.key_utils import create_key_on_cluster
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 logger import logger
@@ -88,7 +99,7 @@ async def handle_key_creation(
@router.callback_query(F.data.startswith("select_plan_"))
async def select_tariff_plan(callback_query: CallbackQuery, state: FSMContext, session: Any):
async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
tg_id = callback_query.message.chat.id
plan_id = callback_query.data.split("_")[-1]
plan_price = RENEWAL_PRICES.get(plan_id)
@@ -98,28 +109,46 @@ async def select_tariff_plan(callback_query: CallbackQuery, state: FSMContext, s
return
duration_days = int(plan_id) * 30
balance = await get_balance(tg_id)
await save_temporary_data(
session,
tg_id,
"waiting_for_payment",
{
"plan_id": plan_id,
"plan_price": plan_price,
"duration_days": duration_days,
"required_amount": max(0, plan_price - balance),
}
)
if balance < plan_price:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await callback_query.message.answer(
"💳 Недостаточно средств для создания подписки. Пополните баланс в личном кабинете.",
reply_markup=builder.as_markup(),
)
await state.clear()
required_amount = plan_price - balance
if USE_NEW_PAYMENT_FLOW:
await process_custom_amount_input(callback_query, session)
else:
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
await update_balance(tg_id, -plan_price)
expiry_time = datetime.utcnow() + timedelta(days=duration_days)
await create_key(tg_id, expiry_time, state, session, callback_query)
await create_key(tg_id, expiry_time, None, session, callback_query)
async def create_key(
tg_id: int, expiry_time: datetime, state: FSMContext, session: Any, message_or_query: Message | CallbackQuery
tg_id: int,
expiry_time: datetime,
state: FSMContext | None,
session: Any,
message_or_query: Message | CallbackQuery | None = None,
):
"""Создаёт ключ с заданным сроком действия."""
while True:
@@ -169,19 +198,27 @@ async def create_key(
await message_or_query.message.answer(
"❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова."
)
else:
await bot.send_message(
chat_id=tg_id,
text="❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова."
)
return
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
builder.row(
InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS),
InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID),
)
builder.row(
InlineKeyboardButton(text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{public_link}"),
InlineKeyboardButton(text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{public_link}"),
InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{public_link}"),
InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{public_link}"),
)
builder.row(InlineKeyboardButton(text="💻 Windows/Linux/MacOS", callback_data=f"connect_pc|{email}"))
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"))
remaining_time = expiry_time - datetime.utcnow()
@@ -192,5 +229,8 @@ async def create_key(
await message_or_query.answer(key_message, reply_markup=builder.as_markup())
elif isinstance(message_or_query, CallbackQuery):
await message_or_query.message.answer(key_message, reply_markup=builder.as_markup())
else:
await bot.send_message(chat_id=tg_id, text=key_message, reply_markup=builder.as_markup())
await state.clear()
if state:
await state.clear()
+29 -38
View File
@@ -25,6 +25,14 @@ from database import (
update_balance,
update_key_expiry,
)
from handlers.buttons.add_subscribe import (
DOWNLOAD_ANDROID_BUTTON,
DOWNLOAD_IOS_BUTTON,
IMPORT_ANDROID,
IMPORT_IOS,
PC_BUTTON,
TV_BUTTON,
)
from handlers.keys.key_utils import (
delete_key_from_cluster,
delete_key_from_db,
@@ -69,10 +77,7 @@ async def process_callback_or_message_view_keys(
chat_id,
)
if records:
inline_keyboard, response_message = build_keys_response(records)
else:
inline_keyboard, response_message = build_no_keys_response()
inline_keyboard, response_message = build_keys_response(records)
image_path = os.path.join("img", "pic_keys.jpg")
await send_with_optional_image(
@@ -85,47 +90,33 @@ async def process_callback_or_message_view_keys(
def build_keys_response(records):
"""
Формирует сообщение и клавиатуру, если у пользователя есть устройства.
Формирует сообщение и клавиатуру для устройств.
"""
builder = InlineKeyboardBuilder()
for record in records:
key_name = record["email"]
builder.row(
InlineKeyboardButton(
text=f"🔑 {key_name}", callback_data=f"view_key|{key_name}"
if records:
for record in records:
key_name = record["email"]
builder.row(
InlineKeyboardButton(
text=f"🔑 {key_name}", callback_data=f"view_key|{key_name}"
)
)
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
inline_keyboard = builder.as_markup()
response_message = (
"<b>🔑 Список ваших устройств</b>\n\n"
"<i>👇 Выберите устройство для управления подпиской:</i>"
)
return inline_keyboard, response_message
def build_no_keys_response():
"""
Формирует сообщение и клавиатуру, если у пользователя нет устройств.
"""
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text=" Создать подписку", callback_data="create_key"
text=" Добавить подписку", callback_data="create_key"
)
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
inline_keyboard = builder.as_markup()
response_message = (
"<b>❌ У вас пока нет активных устройств</b>\n\n"
"<i>Нажмите кнопку ниже, чтобы создать устройство:</i>"
"<b>🔑 Список ваших подписок</b>\n\n"
"<i>👇 Выберите подписку для управления или добавьте новую (например, для подключения нового устройства):</i>"
)
return inline_keyboard, response_message
@@ -198,27 +189,27 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
)
builder.row(
InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS),
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
InlineKeyboardButton(
text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID
text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID
),
)
builder.row(
InlineKeyboardButton(
text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{key}"
text=IMPORT_IOS, url=f"{CONNECT_IOS}{key}"
),
InlineKeyboardButton(
text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{key}"
text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key}"
),
)
builder.row(
InlineKeyboardButton(
text="💻 Компьютеры", callback_data=f"connect_pc|{key_name}"
text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"
),
InlineKeyboardButton(
text="📺 Андроид TV", callback_data=f"connect_tv|{key_name}"
text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"
)
)
@@ -229,7 +220,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
InlineKeyboardButton(
text="❌ Удалить", callback_data=f"delete_key|{key_name}"
),
)
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
+7 -5
View File
@@ -3,19 +3,19 @@ import uuid
from datetime import datetime, timedelta
from typing import Any
from config import ADMIN_PASSWORD, ADMIN_USERNAME, PUBLIC_LINK, TOTAL_GB, TRIAL_TIME
from handlers.texts import INSTRUCTIONS
import pytz
from py3xui import AsyncApi
from client import add_client
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, TOTAL_GB, TRIAL_TIME
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
from logger import logger
async def create_trial_key(tg_id: int, session: Any):
try:
# Проверка статуса триала
trial_status = await session.fetchval(
"SELECT trial FROM connections WHERE tg_id = $1",
tg_id
@@ -32,7 +32,9 @@ async def create_trial_key(tg_id: int, session: Any):
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
instructions = INSTRUCTIONS
result = {"key": public_link, "instructions": instructions, "email": email}
current_time = datetime.utcnow()
moscow_tz = pytz.timezone("Europe/Moscow")
current_time = datetime.now(moscow_tz)
expiry_time = current_time + timedelta(days=TRIAL_TIME)
expiry_timestamp = int(expiry_time.timestamp() * 1000)
@@ -56,7 +58,7 @@ async def create_trial_key(tg_id: int, session: Any):
client_id,
email,
tg_id,
limit_ip=1,
limit_ip=LIMIT_IP,
total_gb=TOTAL_GB,
expiry_time=expiry_timestamp,
enable=True,
+165 -66
View File
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
import asyncpg
from aiogram import Bot, Router, types
from aiogram.exceptions import TelegramForbiddenError
from aiogram.utils.keyboard import InlineKeyboardBuilder
from py3xui import AsyncApi
@@ -16,6 +17,7 @@ from config import (
TRIAL_TIME,
)
from database import (
add_blocked_user,
add_notification,
check_notification_time,
delete_key,
@@ -93,13 +95,14 @@ async def notify_10h_keys(
"""
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
WHERE expiry_time <= $1 AND expiry_time > $2 AND notified = FALSE
""",
""",
threshold_time_10h,
current_time,
)
logger.info(f"Найдено {len(records)} ключей для уведомления за 10 часов.")
for record in records:
async def process_record(record):
tg_id = record["tg_id"]
email = record["email"]
expiry_time = record["expiry_time"]
@@ -123,7 +126,54 @@ async def notify_10h_keys(
price=RENEWAL_PLANS["1"]["price"],
)
if not await is_bot_blocked(bot, tg_id) and not DEV_MODE:
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(
@@ -132,22 +182,26 @@ async def notify_10h_keys(
keyboard.button(text="💳 Пополнить баланс", callback_data="pay")
keyboard.button(text="👤 Личный кабинет", callback_data="profile")
keyboard.adjust(1)
keyboard = keyboard.as_markup()
await bot.send_message(tg_id, message, reply_markup=keyboard)
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.error(
logger.debug(
f"Ошибка при отправке уведомления пользователю {tg_id}: {e}"
)
continue
await conn.execute(
"UPDATE keys SET notified = TRUE WHERE client_id = $1",
record["client_id"],
)
logger.info(f"Обновлено поле notified для клиента {record['client_id']}.")
await asyncio.sleep(1)
await asyncio.gather(*(process_record(record) for record in records))
logger.info("Обработка всех уведомлений за 10 часов завершена.")
async def notify_24h_keys(
@@ -162,13 +216,14 @@ async def notify_24h_keys(
"""
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
WHERE expiry_time <= $1 AND expiry_time > $2 AND notified_24h = FALSE
""",
""",
threshold_time_24h,
current_time,
)
logger.info(f"Найдено {len(records_24h)} ключей для уведомления за 24 часа.")
for record in records_24h:
async def process_record(record):
tg_id = record["tg_id"]
email = record["email"]
expiry_time = record["expiry_time"]
@@ -191,7 +246,55 @@ async def notify_24h_keys(
expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"),
)
if not await is_bot_blocked(bot, tg_id) and not DEV_MODE:
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(
@@ -214,22 +317,27 @@ async def notify_24h_keys(
)
keyboard = builder.as_markup()
await bot.send_message(tg_id, message_24h, reply_markup=keyboard)
logger.info(f"Уведомление за 24 часа отправлено пользователю {tg_id}.")
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}"
)
continue
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 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.sleep(1)
await asyncio.gather(*(process_record(record) for record in records_24h))
logger.info("Обработка всех уведомлений за 24 часа завершена.")
async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
@@ -257,7 +365,7 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
tg_id, "inactive_trial", hours=24, session=conn
)
if can_notify and not await is_bot_blocked(bot, tg_id):
if can_notify:
builder = InlineKeyboardBuilder()
builder.row(
types.InlineKeyboardButton(
@@ -279,32 +387,45 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
"💡 Нажми на кнопку ниже, чтобы активировать пробный доступ."
)
await bot.send_message(tg_id, message, reply_markup=keyboard)
logger.info(f"Отправлено уведомление неактивному пользователю {tg_id}.")
try:
await bot.send_message(tg_id, message, reply_markup=keyboard)
logger.info(f"Отправлено уведомление неактивному пользователю {tg_id}.")
await add_notification(tg_id, "inactive_trial", session=conn)
await add_notification(tg_id, "inactive_trial", session=conn)
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}")
except Exception as e:
logger.error(
f"Ошибка при отправке уведомления неактивному пользователю {tg_id}: {e}"
)
logger.error(f"Ошибка при обработке пользователя {tg_id}: {e}")
await asyncio.sleep(1)
async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: float):
logger.info("Проверка истекших ключей...")
logger.info("Проверка подписок, срок действия которых скоро истекает...")
threshold_time = int((datetime.utcnow() + timedelta(minutes=45)).timestamp() * 1000)
expiring_keys = await conn.fetch(
"""
SELECT tg_id, client_id, expiry_time, email FROM keys
WHERE expiry_time <= $1
WHERE expiry_time <= $1 AND expiry_time > $2
""",
threshold_time,
current_time,
)
logger.info(f"current_time {current_time}")
logger.info(f"Найдено {len(expiring_keys)} истекающих ключей.")
await asyncio.gather(*[process_key(record, bot, conn) for record in expiring_keys])
logger.info(f"Найдено {len(expiring_keys)} подписок, срок действия которых скоро истекает.")
for record in expiring_keys:
try:
await process_key(record, bot, conn)
except Exception as e:
logger.error(f"Ошибка при обработке подписки {record['client_id']}: {e}")
async def process_key(record, bot, conn):
@@ -318,8 +439,11 @@ async def process_key(record, bot, conn):
time_left = expiry_date - current_date
logger.info(
f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}"
f"Время истечения ключа: {expiry_time} (UTC: {expiry_date}), "
f"Текущее время (UTC): {current_date}, "
f"Оставшееся время: {time_left}"
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
@@ -395,6 +519,7 @@ async def process_key(record, bot, conn):
logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}")
async def check_online_users():
servers = await get_servers_from_db()
@@ -413,29 +538,3 @@ async def check_online_users():
logger.error(
f"Не удалось проверить пользователей на сервере {server_id}: {e}"
)
async def update_all_keys():
try:
conn = await asyncpg.connect(DATABASE_URL)
keys = await conn.fetch("SELECT tg_id, client_id, email, expiry_time, server_id FROM keys")
for key in keys:
tg_id = key['tg_id']
client_id = key['client_id']
email = key['email']
expiry_time = key['expiry_time']
cluster_id = key['server_id']
try:
await update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id)
await store_key(tg_id, client_id, email, expiry_time, key['key'], cluster_id, conn)
logger.info(f"Ключ {client_id} успешно обновлен и сохранен")
except Exception as e:
logger.error(f"Ошибка при обновлении и сохранении ключа {client_id}: {e}")
logger.info("Все ключи успешно обновлены и сохранены")
except Exception as e:
logger.error(f"Ошибка при обновлении всех ключей: {e}")
finally:
await conn.close()
-8
View File
@@ -5,7 +5,6 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import (
CRYPTO_BOT_ENABLE,
DONATIONS_ENABLE,
FREEKASSA_ENABLE,
ROBOKASSA_ENABLE,
STARS_ENABLE,
YOOKASSA_ENABLE,
@@ -33,13 +32,6 @@ async def handle_pay(callback_query: CallbackQuery):
callback_data="pay_yoomoney",
)
)
if FREEKASSA_ENABLE:
builder.row(
InlineKeyboardButton(
text="🌐 FreeKassa: множество способов",
callback_data="pay_freekassa",
)
)
if CRYPTO_BOT_ENABLE:
builder.row(
InlineKeyboardButton(
-1
View File
@@ -4,7 +4,6 @@ from aiogram import Router
from config import (
CRYPTO_BOT_ENABLE,
FREEKASSA_ENABLE,
ROBOKASSA_ENABLE,
STARS_ENABLE,
YOOKASSA_ENABLE,
File diff suppressed because it is too large Load Diff
+92 -92
View File
@@ -10076,7 +10076,7 @@ static PyObject *__pyx_gb_8handlers_8payments_4gift_22generator6(__pyx_Coroutine
* logger.info(f": {data.get('message', ' .')}")
* return False # <<<<<<<<<<<<<<
* elif response.status in [502, 404]:
* logger.info(f" .")
* logger.info(" .")
*/
__Pyx_XDECREF(__pyx_r);
__pyx_r = NULL; __Pyx_ReturnWithStopIteration(Py_False);
@@ -10096,7 +10096,7 @@ static PyObject *__pyx_gb_8handlers_8payments_4gift_22generator6(__pyx_Coroutine
* logger.info(f": {data.get('message', ' .')}")
* return False
* elif response.status in [502, 404]: # <<<<<<<<<<<<<<
* logger.info(f" .")
* logger.info(" .")
* return True
*/
__pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_cur_scope->__pyx_v_response, __pyx_n_s_status); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 260, __pyx_L27_error)
@@ -10117,7 +10117,7 @@ static PyObject *__pyx_gb_8handlers_8payments_4gift_22generator6(__pyx_Coroutine
/* "handlers/payments/gift.py":261
* return False
* elif response.status in [502, 404]:
* logger.info(f" .") # <<<<<<<<<<<<<<
* logger.info(" .") # <<<<<<<<<<<<<<
* return True
* else:
*/
@@ -10152,7 +10152,7 @@ static PyObject *__pyx_gb_8handlers_8payments_4gift_22generator6(__pyx_Coroutine
/* "handlers/payments/gift.py":262
* elif response.status in [502, 404]:
* logger.info(f" .")
* logger.info(" .")
* return True # <<<<<<<<<<<<<<
* else:
* logger.info(f" : {response.status}")
@@ -10165,7 +10165,7 @@ static PyObject *__pyx_gb_8handlers_8payments_4gift_22generator6(__pyx_Coroutine
* logger.info(f": {data.get('message', ' .')}")
* return False
* elif response.status in [502, 404]: # <<<<<<<<<<<<<<
* logger.info(f" .")
* logger.info(" .")
* return True
*/
}
@@ -14036,7 +14036,7 @@ if (!__Pyx_RefNanny) {
* from aiogram import F, Router, types
* from aiogram.fsm.context import FSMContext # <<<<<<<<<<<<<<
* from aiogram.utils.keyboard import InlineKeyboardBuilder
* from handlers.buttons.gifts import GIFT, BACK, PROFILE, MY_GIFTS, GIFTS_ABOUT
*
*/
__pyx_t_3 = PyList_New(1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 10, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
@@ -14056,8 +14056,8 @@ if (!__Pyx_RefNanny) {
* from aiogram import F, Router, types
* from aiogram.fsm.context import FSMContext
* from aiogram.utils.keyboard import InlineKeyboardBuilder # <<<<<<<<<<<<<<
* from handlers.buttons.gifts import GIFT, BACK, PROFILE, MY_GIFTS, GIFTS_ABOUT
*
* from config import CLIENT_CODE, RENEWAL_PRICES
*/
__pyx_t_2 = PyList_New(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 11, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
@@ -14073,149 +14073,149 @@ if (!__Pyx_RefNanny) {
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
/* "handlers/payments/gift.py":12
* from aiogram.fsm.context import FSMContext
/* "handlers/payments/gift.py":13
* from aiogram.utils.keyboard import InlineKeyboardBuilder
* from handlers.buttons.gifts import GIFT, BACK, PROFILE, MY_GIFTS, GIFTS_ABOUT # <<<<<<<<<<<<<<
*
* from config import CLIENT_CODE, RENEWAL_PRICES
* from config import CLIENT_CODE, RENEWAL_PRICES # <<<<<<<<<<<<<<
* from database import get_balance, store_gift_link, update_balance
* from handlers.buttons.gifts import BACK, GIFT, GIFTS_ABOUT, MY_GIFTS, PROFILE
*/
__pyx_t_3 = PyList_New(5); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 12, __pyx_L1_error)
__pyx_t_3 = PyList_New(2); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 13, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
__Pyx_INCREF(__pyx_n_s_GIFT);
__Pyx_GIVEREF(__pyx_n_s_GIFT);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 0, __pyx_n_s_GIFT)) __PYX_ERR(0, 12, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_BACK);
__Pyx_GIVEREF(__pyx_n_s_BACK);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 1, __pyx_n_s_BACK)) __PYX_ERR(0, 12, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_PROFILE);
__Pyx_GIVEREF(__pyx_n_s_PROFILE);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 2, __pyx_n_s_PROFILE)) __PYX_ERR(0, 12, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_MY_GIFTS);
__Pyx_GIVEREF(__pyx_n_s_MY_GIFTS);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 3, __pyx_n_s_MY_GIFTS)) __PYX_ERR(0, 12, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_GIFTS_ABOUT);
__Pyx_GIVEREF(__pyx_n_s_GIFTS_ABOUT);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 4, __pyx_n_s_GIFTS_ABOUT)) __PYX_ERR(0, 12, __pyx_L1_error);
__pyx_t_2 = __Pyx_Import(__pyx_n_s_handlers_buttons_gifts, __pyx_t_3, 0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 12, __pyx_L1_error)
__Pyx_INCREF(__pyx_n_s_CLIENT_CODE);
__Pyx_GIVEREF(__pyx_n_s_CLIENT_CODE);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 0, __pyx_n_s_CLIENT_CODE)) __PYX_ERR(0, 13, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_RENEWAL_PRICES);
__Pyx_GIVEREF(__pyx_n_s_RENEWAL_PRICES);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 1, __pyx_n_s_RENEWAL_PRICES)) __PYX_ERR(0, 13, __pyx_L1_error);
__pyx_t_2 = __Pyx_Import(__pyx_n_s_config, __pyx_t_3, 0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 13, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_GIFT); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 12, __pyx_L1_error)
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_CLIENT_CODE); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 13, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_GIFT, __pyx_t_3) < 0) __PYX_ERR(0, 12, __pyx_L1_error)
if (PyDict_SetItem(__pyx_d, __pyx_n_s_CLIENT_CODE, __pyx_t_3) < 0) __PYX_ERR(0, 13, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_BACK); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 12, __pyx_L1_error)
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_RENEWAL_PRICES); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 13, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_BACK, __pyx_t_3) < 0) __PYX_ERR(0, 12, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_PROFILE); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 12, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_PROFILE, __pyx_t_3) < 0) __PYX_ERR(0, 12, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_MY_GIFTS); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 12, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_MY_GIFTS, __pyx_t_3) < 0) __PYX_ERR(0, 12, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_GIFTS_ABOUT); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 12, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_GIFTS_ABOUT, __pyx_t_3) < 0) __PYX_ERR(0, 12, __pyx_L1_error)
if (PyDict_SetItem(__pyx_d, __pyx_n_s_RENEWAL_PRICES, __pyx_t_3) < 0) __PYX_ERR(0, 13, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
/* "handlers/payments/gift.py":14
* from handlers.buttons.gifts import GIFT, BACK, PROFILE, MY_GIFTS, GIFTS_ABOUT
*
* from config import CLIENT_CODE, RENEWAL_PRICES # <<<<<<<<<<<<<<
* from database import get_balance, store_gift_link, update_balance
* from handlers.texts import get_gift_link, GIFTS_TEXT_TEMPLATE
* from config import CLIENT_CODE, RENEWAL_PRICES
* from database import get_balance, store_gift_link, update_balance # <<<<<<<<<<<<<<
* from handlers.buttons.gifts import BACK, GIFT, GIFTS_ABOUT, MY_GIFTS, PROFILE
* from handlers.texts import GIFTS_TEXT_TEMPLATE, get_gift_link
*/
__pyx_t_2 = PyList_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error)
__pyx_t_2 = PyList_New(3); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
__Pyx_INCREF(__pyx_n_s_CLIENT_CODE);
__Pyx_GIVEREF(__pyx_n_s_CLIENT_CODE);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 0, __pyx_n_s_CLIENT_CODE)) __PYX_ERR(0, 14, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_RENEWAL_PRICES);
__Pyx_GIVEREF(__pyx_n_s_RENEWAL_PRICES);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 1, __pyx_n_s_RENEWAL_PRICES)) __PYX_ERR(0, 14, __pyx_L1_error);
__pyx_t_3 = __Pyx_Import(__pyx_n_s_config, __pyx_t_2, 0); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 14, __pyx_L1_error)
__Pyx_INCREF(__pyx_n_s_get_balance);
__Pyx_GIVEREF(__pyx_n_s_get_balance);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 0, __pyx_n_s_get_balance)) __PYX_ERR(0, 14, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_store_gift_link);
__Pyx_GIVEREF(__pyx_n_s_store_gift_link);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 1, __pyx_n_s_store_gift_link)) __PYX_ERR(0, 14, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_update_balance);
__Pyx_GIVEREF(__pyx_n_s_update_balance);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 2, __pyx_n_s_update_balance)) __PYX_ERR(0, 14, __pyx_L1_error);
__pyx_t_3 = __Pyx_Import(__pyx_n_s_database, __pyx_t_2, 0); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 14, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_CLIENT_CODE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error)
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_get_balance); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_CLIENT_CODE, __pyx_t_2) < 0) __PYX_ERR(0, 14, __pyx_L1_error)
if (PyDict_SetItem(__pyx_d, __pyx_n_s_get_balance, __pyx_t_2) < 0) __PYX_ERR(0, 14, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_RENEWAL_PRICES); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error)
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_store_gift_link); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_RENEWAL_PRICES, __pyx_t_2) < 0) __PYX_ERR(0, 14, __pyx_L1_error)
if (PyDict_SetItem(__pyx_d, __pyx_n_s_store_gift_link, __pyx_t_2) < 0) __PYX_ERR(0, 14, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_update_balance); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_update_balance, __pyx_t_2) < 0) __PYX_ERR(0, 14, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
/* "handlers/payments/gift.py":15
*
* from config import CLIENT_CODE, RENEWAL_PRICES
* from database import get_balance, store_gift_link, update_balance # <<<<<<<<<<<<<<
* from handlers.texts import get_gift_link, GIFTS_TEXT_TEMPLATE
* from database import get_balance, store_gift_link, update_balance
* from handlers.buttons.gifts import BACK, GIFT, GIFTS_ABOUT, MY_GIFTS, PROFILE # <<<<<<<<<<<<<<
* from handlers.texts import GIFTS_TEXT_TEMPLATE, get_gift_link
* from logger import logger
*/
__pyx_t_3 = PyList_New(3); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__pyx_t_3 = PyList_New(5); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
__Pyx_INCREF(__pyx_n_s_get_balance);
__Pyx_GIVEREF(__pyx_n_s_get_balance);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 0, __pyx_n_s_get_balance)) __PYX_ERR(0, 15, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_store_gift_link);
__Pyx_GIVEREF(__pyx_n_s_store_gift_link);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 1, __pyx_n_s_store_gift_link)) __PYX_ERR(0, 15, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_update_balance);
__Pyx_GIVEREF(__pyx_n_s_update_balance);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 2, __pyx_n_s_update_balance)) __PYX_ERR(0, 15, __pyx_L1_error);
__pyx_t_2 = __Pyx_Import(__pyx_n_s_database, __pyx_t_3, 0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_INCREF(__pyx_n_s_BACK);
__Pyx_GIVEREF(__pyx_n_s_BACK);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 0, __pyx_n_s_BACK)) __PYX_ERR(0, 15, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_GIFT);
__Pyx_GIVEREF(__pyx_n_s_GIFT);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 1, __pyx_n_s_GIFT)) __PYX_ERR(0, 15, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_GIFTS_ABOUT);
__Pyx_GIVEREF(__pyx_n_s_GIFTS_ABOUT);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 2, __pyx_n_s_GIFTS_ABOUT)) __PYX_ERR(0, 15, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_MY_GIFTS);
__Pyx_GIVEREF(__pyx_n_s_MY_GIFTS);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 3, __pyx_n_s_MY_GIFTS)) __PYX_ERR(0, 15, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_PROFILE);
__Pyx_GIVEREF(__pyx_n_s_PROFILE);
if (__Pyx_PyList_SET_ITEM(__pyx_t_3, 4, __pyx_n_s_PROFILE)) __PYX_ERR(0, 15, __pyx_L1_error);
__pyx_t_2 = __Pyx_Import(__pyx_n_s_handlers_buttons_gifts, __pyx_t_3, 0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_get_balance); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_BACK); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_get_balance, __pyx_t_3) < 0) __PYX_ERR(0, 15, __pyx_L1_error)
if (PyDict_SetItem(__pyx_d, __pyx_n_s_BACK, __pyx_t_3) < 0) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_store_gift_link); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_GIFT); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_store_gift_link, __pyx_t_3) < 0) __PYX_ERR(0, 15, __pyx_L1_error)
if (PyDict_SetItem(__pyx_d, __pyx_n_s_GIFT, __pyx_t_3) < 0) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_update_balance); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_GIFTS_ABOUT); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_update_balance, __pyx_t_3) < 0) __PYX_ERR(0, 15, __pyx_L1_error)
if (PyDict_SetItem(__pyx_d, __pyx_n_s_GIFTS_ABOUT, __pyx_t_3) < 0) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_MY_GIFTS); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_MY_GIFTS, __pyx_t_3) < 0) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_PROFILE); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_PROFILE, __pyx_t_3) < 0) __PYX_ERR(0, 15, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
/* "handlers/payments/gift.py":16
* from config import CLIENT_CODE, RENEWAL_PRICES
* from database import get_balance, store_gift_link, update_balance
* from handlers.texts import get_gift_link, GIFTS_TEXT_TEMPLATE # <<<<<<<<<<<<<<
* from handlers.buttons.gifts import BACK, GIFT, GIFTS_ABOUT, MY_GIFTS, PROFILE
* from handlers.texts import GIFTS_TEXT_TEMPLATE, get_gift_link # <<<<<<<<<<<<<<
* from logger import logger
*
*/
__pyx_t_2 = PyList_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 16, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
__Pyx_INCREF(__pyx_n_s_get_gift_link);
__Pyx_GIVEREF(__pyx_n_s_get_gift_link);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 0, __pyx_n_s_get_gift_link)) __PYX_ERR(0, 16, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_GIFTS_TEXT_TEMPLATE);
__Pyx_GIVEREF(__pyx_n_s_GIFTS_TEXT_TEMPLATE);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 1, __pyx_n_s_GIFTS_TEXT_TEMPLATE)) __PYX_ERR(0, 16, __pyx_L1_error);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 0, __pyx_n_s_GIFTS_TEXT_TEMPLATE)) __PYX_ERR(0, 16, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_get_gift_link);
__Pyx_GIVEREF(__pyx_n_s_get_gift_link);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 1, __pyx_n_s_get_gift_link)) __PYX_ERR(0, 16, __pyx_L1_error);
__pyx_t_3 = __Pyx_Import(__pyx_n_s_handlers_texts, __pyx_t_2, 0); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 16, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_get_gift_link); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 16, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_get_gift_link, __pyx_t_2) < 0) __PYX_ERR(0, 16, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_GIFTS_TEXT_TEMPLATE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 16, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_GIFTS_TEXT_TEMPLATE, __pyx_t_2) < 0) __PYX_ERR(0, 16, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_get_gift_link); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 16, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
if (PyDict_SetItem(__pyx_d, __pyx_n_s_get_gift_link, __pyx_t_2) < 0) __PYX_ERR(0, 16, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
/* "handlers/payments/gift.py":17
* from database import get_balance, store_gift_link, update_balance
* from handlers.texts import get_gift_link, GIFTS_TEXT_TEMPLATE
* from handlers.buttons.gifts import BACK, GIFT, GIFTS_ABOUT, MY_GIFTS, PROFILE
* from handlers.texts import GIFTS_TEXT_TEMPLATE, get_gift_link
* from logger import logger # <<<<<<<<<<<<<<
*
* router = Router()
+2435 -460
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+62 -50
View File
@@ -1,13 +1,14 @@
import os
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 NEWS_MESSAGE, RENEWAL_PLANS
from database import get_balance, get_key_count, get_referral_stats
from handlers.buttons.profile import ADD_SUB, GIFTS, INSTRUCTIONS, INVITE, MAIN_MENU, MY_SUBS, PAYMENT, TARRIFS
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, GIFTS, INSTRUCTIONS, INVITE, MAIN_MENU, MY_SUBS, PAYMENT
from handlers.texts import get_referral_link, invite_message_send, profile_message_send
router = Router()
@@ -33,64 +34,75 @@ async def process_callback_view_profile(
if balance is None:
balance = 0
profile_message = profile_message_send(username, chat_id, int(balance), key_count)
conn = await asyncpg.connect(DATABASE_URL)
try:
trial_status = await get_trial(chat_id, conn)
if key_count == 0:
profile_message += "\n<pre>🔧 <i>Нажмите кнопку ➕ Устройство, чтобы настроить VPN-подключение</i></pre>"
else:
profile_message += f"\n<pre> <i>{NEWS_MESSAGE}</i></pre>"
profile_message = profile_message_send(username, chat_id, int(balance), key_count)
if key_count == 0:
profile_message += "\n<pre>🔧 <i>Нажмите кнопку ➕ Устройство, чтобы настроить VPN-подключение</i></pre>"
else:
profile_message += f"\n<pre> <i>{NEWS_MESSAGE}</i></pre>"
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 = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text=ADD_SUB, callback_data="create_key"),
InlineKeyboardButton(text=MY_SUBS, callback_data="view_keys"),
)
builder.row(
InlineKeyboardButton(
text=PAYMENT,
callback_data="pay",
)
)
builder.row()
builder.row(
InlineKeyboardButton(text=INVITE, callback_data="invite"),
InlineKeyboardButton(text=GIFTS, callback_data="gifts"),
)
builder.row(
InlineKeyboardButton(text=TARRIFS, callback_data="view_tariffs"),
InlineKeyboardButton(text=INSTRUCTIONS, callback_data="instructions"),
)
if admin:
builder.row(
InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")
InlineKeyboardButton(
text=PAYMENT,
callback_data="pay",
)
)
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="start"))
builder.row(
InlineKeyboardButton(text=INVITE, callback_data="invite"),
InlineKeyboardButton(text=GIFTS, callback_data="gifts"),
)
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):
with open(image_path, "rb") as image_file:
if os.path.isfile(image_path):
with open(image_path, "rb") as image_file:
if is_callback:
await callback_query_or_message.message.answer_photo(
photo=BufferedInputFile(image_file.read(), filename="pic.jpg"),
caption=profile_message,
reply_markup=builder.as_markup(),
)
else:
await callback_query_or_message.answer_photo(
photo=BufferedInputFile(image_file.read(), filename="pic.jpg"),
caption=profile_message,
reply_markup=builder.as_markup(),
)
else:
if is_callback:
await callback_query_or_message.message.answer_photo(
photo=BufferedInputFile(image_file.read(), filename="pic.jpg"),
caption=profile_message,
await callback_query_or_message.message.answer(
text=profile_message,
reply_markup=builder.as_markup(),
)
else:
await callback_query_or_message.answer_photo(
photo=BufferedInputFile(image_file.read(), filename="pic.jpg"),
caption=profile_message,
await callback_query_or_message.answer(
text=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(),
)
finally:
await conn.close()
+14 -6
View File
@@ -23,6 +23,14 @@ from config import (
SUPPORT_CHAT_URL,
)
from database import add_connection, add_referral, check_connection_exists, get_trial, use_trial
from handlers.buttons.add_subscribe import (
DOWNLOAD_ANDROID_BUTTON,
DOWNLOAD_IOS_BUTTON,
IMPORT_ANDROID,
IMPORT_IOS,
PC_BUTTON,
TV_BUTTON,
)
from handlers.keys.key_management import create_key
from handlers.keys.trial_key import create_trial_key
from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn
@@ -201,25 +209,25 @@ async def handle_connect_vpn(callback_query: CallbackQuery, session: Any):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
builder.row(
InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS),
InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID),
)
builder.row(
InlineKeyboardButton(
text="🍏 Подключить на iOS",
text=IMPORT_IOS,
url=f'{CONNECT_IOS}{trial_key_info["key"]}',
),
InlineKeyboardButton(
text="🤖 Подключить на Android",
text=IMPORT_ANDROID,
url=f'{CONNECT_ANDROID}{trial_key_info["key"]}',
),
)
builder.row(
InlineKeyboardButton(
text="💻 Компьютеры", callback_data=f"connect_pc|{email}"
text=PC_BUTTON, callback_data=f"connect_pc|{email}"
),
InlineKeyboardButton(
text="📺 Андроид TV", callback_data=f"connect_tv|{email}"
text=TV_BUTTON, callback_data=f"connect_tv|{email}"
)
)
builder.row(
+1 -1
View File
@@ -1,6 +1,6 @@
import json
import random
import re
import json
import aiohttp
import asyncpg
+2 -1
View File
@@ -31,4 +31,5 @@ py3xui
sqlalchemy
robokassa
ping3
ruff
ruff
pytz
+2 -2
View File
@@ -8,7 +8,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from ping3 import ping
from bot import bot
from config import ADMIN_ID, DATABASE_URL
from config import ADMIN_ID, DATABASE_URL, PING_TIME
from database import get_servers_from_db
from logger import logger
@@ -184,7 +184,7 @@ async def check_servers():
)
logger.info("Завершена проверка всех серверов.")
await asyncio.sleep(30)
await asyncio.sleep(PING_TIME)
def extract_host(api_url: str) -> str: