Merge pull request #5 from hteppl/upstream1

Changes from upstream
This commit is contained in:
Oleg Kraev
2025-01-12 16:56:52 +03:00
committed by GitHub
32 changed files with 3521 additions and 2653 deletions
+1 -1
View File
@@ -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 logger import logger
+6 -1
View File
@@ -5,13 +5,14 @@ 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 logger import logger
from middlewares.admin import AdminMiddleware
from middlewares.database import DatabaseMiddleware
from middlewares.delete import DeleteMessageMiddleware
from middlewares.logging import LoggingMiddleware
from middlewares.throttling import ThrottlingMiddleware
from middlewares.user import UserMiddleware
bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
@@ -29,6 +30,10 @@ dp.callback_query.middleware(UserMiddleware())
dp.message.middleware(DatabaseMiddleware())
dp.callback_query.middleware(DatabaseMiddleware())
# Add throttling middleware
dp.message.middleware(ThrottlingMiddleware(limit=1)) # 1 message per second
dp.callback_query.middleware(ThrottlingMiddleware(limit=1))
dp.message.outer_middleware(DeleteMessageMiddleware())
dp.callback_query.outer_middleware(DeleteMessageMiddleware())
+11 -3
View File
@@ -1,6 +1,6 @@
import py3xui
from config import LIMIT_IP
from logger import logger
@@ -41,8 +41,16 @@ async def add_client(
return response if response else {"status": "failed"}
except Exception as e:
logger.error(f"Ошибка при добавлении клиента {email}: {e}")
return {"status": "failed", "error": str(e)}
error_message = str(e)
if "Duplicate email" in error_message:
logger.warning(
f"Дублированный email: {email}. Пропуск. Сообщение: {error_message}"
)
return {"status": "duplicate", "email": email}
logger.error(f"Ошибка при добавлении клиента {email}: {error_message}")
return {"status": "failed", "error": error_message}
async def extend_client_key(
+25 -20
View File
@@ -3,8 +3,8 @@ from datetime import datetime
from typing import Any
import asyncpg
from config import DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
from logger import logger
@@ -17,36 +17,34 @@ async def save_temporary_data(session, tg_id: int, state: str, data: dict):
ON CONFLICT (tg_id)
DO UPDATE SET state = $2, data = $3, updated_at = $4
""",
tg_id, state, json.dumps(data), datetime.utcnow()
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
"SELECT state, data FROM temporary_data WHERE tg_id = $1", tg_id
)
if result:
return {
"state": result["state"],
"data": json.loads(result["data"])
}
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
)
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
tg_id,
)
async def init_db(file_path: str = "assets/schema.sql"):
with open(file_path) as file:
sql_content = file.read()
@@ -610,7 +608,9 @@ async def add_referral(referred_tg_id: int, referrer_tg_id: int, session: Any):
try:
if referred_tg_id == referrer_tg_id:
logger.warning(f"Пользователь {referred_tg_id} попытался использовать свою собственную реферальную ссылку.")
logger.warning(
f"Пользователь {referred_tg_id} попытался использовать свою собственную реферальную ссылку."
)
return
await session.execute(
@@ -1244,11 +1244,11 @@ async def get_servers_from_db():
async def delete_user_data(session: Any, tg_id: int):
try:
await session.execute("DELETE FROM gifts WHERE sender_tg_id = $1 OR recipient_tg_id = $1", tg_id)
except Exception as e:
logger.warning(
f"У Вас версия без подарков для {tg_id}: {e}"
await session.execute(
"DELETE FROM gifts WHERE sender_tg_id = $1 OR recipient_tg_id = $1", tg_id
)
except Exception as e:
logger.warning(f"У Вас версия без подарков для {tg_id}: {e}")
await session.execute("DELETE FROM payments WHERE tg_id = $1", tg_id)
await session.execute("DELETE FROM users WHERE tg_id = $1", tg_id)
await session.execute("DELETE FROM connections WHERE tg_id = $1", tg_id)
@@ -1257,7 +1257,12 @@ async def delete_user_data(session: Any, tg_id: int):
async def store_gift_link(
gift_id: str, sender_tg_id: int, selected_months: int, expiry_time: datetime, gift_link: str, session: Any = None
gift_id: str,
sender_tg_id: int,
selected_months: int,
expiry_time: datetime,
gift_link: str,
session: Any = None,
):
"""
Добавляет информацию о подарке в базу данных.
-2
View File
@@ -1,7 +1,5 @@
from aiogram.filters import BaseFilter
from aiogram.types import Message
from config import ADMIN_ID
+73 -1
View File
@@ -1,15 +1,19 @@
import asyncio
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
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
from logger import logger
router = Router()
@@ -328,6 +332,13 @@ async def handle_manage_cluster(callback_query: types.CallbackQuery, state: FSMC
)
)
builder.row(
InlineKeyboardButton(
text="🔄 Синхронизировать",
callback_data=f"sync_cluster|{cluster_name}",
)
)
builder.row(
InlineKeyboardButton(
text="🔙 Назад в управление кластерами", callback_data="servers_editor"
@@ -340,6 +351,67 @@ async def handle_manage_cluster(callback_query: types.CallbackQuery, state: FSMC
)
@router.callback_query(F.data.startswith("sync_cluster|"), IsAdminFilter())
async def sync_cluster_handler(callback_query: types.CallbackQuery):
"""Обработчик для синхронизации ключей на всех серверах выбранного кластера."""
cluster_name = callback_query.data.split("|")[1]
conn = await asyncpg.connect(DATABASE_URL)
try:
query_keys = """
SELECT tg_id, client_id, email, expiry_time
FROM keys
WHERE server_id = $1
"""
keys_to_sync = await conn.fetch(query_keys, cluster_name)
if not keys_to_sync:
await callback_query.message.answer(
f"❌ Нет ключей для синхронизации в кластере {cluster_name}.",
reply_markup=InlineKeyboardBuilder()
.row(
InlineKeyboardButton(
text="🔙 Назад", callback_data="servers_editor"
)
)
.as_markup(),
)
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"],
)
)
)
await asyncio.gather(*tasks)
await callback_query.message.answer(
f"✅ Ключи успешно синхронизированы для кластера {cluster_name}.",
reply_markup=InlineKeyboardBuilder()
.row(InlineKeyboardButton(text="🔙 Назад", callback_data="servers_editor"))
.as_markup(),
)
except Exception as e:
logger.error(f"Ошибка синхронизации ключей в кластере {cluster_name}: {e}")
await callback_query.message.answer(
f"❌ Произошла ошибка при синхронизации: {e}",
reply_markup=InlineKeyboardBuilder()
.row(InlineKeyboardButton(text="🔙 Назад", callback_data="servers_editor"))
.as_markup(),
)
finally:
await conn.close()
@router.callback_query(F.data.startswith("server_availability|"), IsAdminFilter())
async def handle_check_server_availability(callback_query: types.CallbackQuery):
cluster_name = callback_query.data.split("|")[1]
+74 -24
View File
@@ -7,9 +7,15 @@ 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 database import delete_user_data, get_client_id_by_email, get_servers_from_db, restore_trial, update_key_expiry
from database import (
delete_user_data,
get_client_id_by_email,
get_servers_from_db,
restore_trial,
update_key_expiry,
)
from filters.admin import IsAdminFilter
from handlers.keys.key_utils import (
delete_key_from_cluster,
@@ -56,7 +62,7 @@ async def prompt_username(callback_query: CallbackQuery, state: FSMContext):
async def handle_username_input(
message: types.Message, state: FSMContext, session: Any
):
username = message.text.strip().lstrip('@').replace('https://t.me/', '')
username = message.text.strip().lstrip("@").replace("https://t.me/", "")
user_record = await session.fetchrow(
"SELECT tg_id FROM users WHERE username = $1", username
)
@@ -113,12 +119,19 @@ async def handle_username_input(
callback_data=f"restore_trial_{tg_id}",
)
)
builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"))
builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"))
builder.row(
InlineKeyboardButton(
text="✉️ Отправить сообщение",
callback_data=f"send_message_{tg_id}"
text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"
)
)
builder.row(
InlineKeyboardButton(
text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"
)
)
builder.row(
InlineKeyboardButton(
text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}"
)
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
@@ -201,8 +214,16 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext, session:
callback_data=f"change_balance_{tg_id}",
)
)
builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"))
builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"))
builder.row(
InlineKeyboardButton(
text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"
)
)
builder.row(
InlineKeyboardButton(
text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"
)
)
builder.row(
InlineKeyboardButton(
text="🔄 Восстановить пробник",
@@ -211,8 +232,7 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext, session:
)
builder.row(
InlineKeyboardButton(
text="✉️ Отправить сообщение",
callback_data=f"send_message_{tg_id}"
text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}"
)
)
@@ -645,14 +665,29 @@ async def handle_user_info(
InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}")
)
builder.row(InlineKeyboardButton(text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}"))
builder.row(InlineKeyboardButton(text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}"))
builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"))
builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"))
builder.row(
InlineKeyboardButton(
text="✉️ Отправить сообщение",
callback_data=f"send_message_{tg_id}"
text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}"
)
)
builder.row(
InlineKeyboardButton(
text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}"
)
)
builder.row(
InlineKeyboardButton(
text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"
)
)
builder.row(
InlineKeyboardButton(
text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"
)
)
builder.row(
InlineKeyboardButton(
text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}"
)
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
@@ -671,19 +706,26 @@ async def handle_user_info(
@router.callback_query(F.data.startswith("confirm_delete_user_"), IsAdminFilter())
async def confirm_delete_user(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
async def confirm_delete_user(
callback_query: types.CallbackQuery, state: FSMContext, session: Any
):
tg_id = int(callback_query.data.split("_")[3])
confirmation_markup = InlineKeyboardMarkup(
row_width=2,
inline_keyboard=[
[InlineKeyboardButton(text="✅ Подтвердить", callback_data=f"delete_user_{tg_id}")],
[
InlineKeyboardButton(
text="✅ Подтвердить", callback_data=f"delete_user_{tg_id}"
)
],
[InlineKeyboardButton(text="❌ Отменить", callback_data="user_editor")],
],
)
await callback_query.message.answer(
f"Вы уверены, что хотите удалить пользователя с ID {tg_id}?", reply_markup=confirmation_markup
f"Вы уверены, что хотите удалить пользователя с ID {tg_id}?",
reply_markup=confirmation_markup,
)
@@ -691,7 +733,9 @@ async def confirm_delete_user(callback_query: types.CallbackQuery, state: FSMCon
async def delete_user(callback_query: types.CallbackQuery, session: Any):
tg_id = int(callback_query.data.split("_")[2])
key_records = await session.fetch("SELECT email, client_id FROM keys WHERE tg_id = $1", tg_id)
key_records = await session.fetch(
"SELECT email, client_id FROM keys WHERE tg_id = $1", tg_id
)
async def delete_keys_from_servers():
try:
@@ -702,7 +746,9 @@ async def delete_user(callback_query: types.CallbackQuery, session: Any):
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
await asyncio.gather(*tasks)
except Exception as e:
logger.error(f"Ошибка при удалении ключей с серверов для пользователя {tg_id}: {e}")
logger.error(
f"Ошибка при удалении ключей с серверов для пользователя {tg_id}: {e}"
)
await delete_keys_from_servers()
@@ -712,9 +758,13 @@ async def delete_user(callback_query: types.CallbackQuery, session: Any):
back_button = InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
await callback_query.message.answer(f"🗑️ Пользователь с ID {tg_id} был удален.", reply_markup=keyboard)
await callback_query.message.answer(
f"🗑️ Пользователь с ID {tg_id} был удален.", reply_markup=keyboard
)
except Exception as e:
logger.error(f"Ошибка при удалении данных из базы данных для пользователя {tg_id}: {e}")
logger.error(
f"Ошибка при удалении данных из базы данных для пользователя {tg_id}: {e}"
)
await callback_query.message.answer(
f"❌ Произошла ошибка при удалении пользователя с ID {tg_id}. Попробуйте снова."
)
+1 -1
View File
@@ -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 logger import logger
+19 -17
View File
@@ -5,9 +5,15 @@ 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 handlers.texts import CONNECT_TV_TEXT, INSTRUCTION_PC, INSTRUCTIONS, KEY_MESSAGE, SUBSCRIPTION_DETAILS_TEXT
from handlers.texts import (
CONNECT_TV_TEXT,
INSTRUCTION_PC,
INSTRUCTIONS,
KEY_MESSAGE,
SUBSCRIPTION_DETAILS_TEXT,
)
from logger import logger
router = Router()
@@ -15,13 +21,17 @@ router = Router()
@router.callback_query(F.data == "instructions")
@router.message(F.text == "/instructions")
async def send_instructions(callback_query_or_message: types.CallbackQuery | types.Message):
async def send_instructions(
callback_query_or_message: types.CallbackQuery | types.Message,
):
instructions_message = INSTRUCTIONS
image_path = os.path.join("img", "instructions.jpg")
if not os.path.isfile(image_path):
if isinstance(callback_query_or_message, types.CallbackQuery):
await callback_query_or_message.message.answer("Файл изображения не найден.")
await callback_query_or_message.message.answer(
"Файл изображения не найден."
)
else:
await callback_query_or_message.answer("Файл изображения не найден.")
return
@@ -45,7 +55,6 @@ async def send_instructions(callback_query_or_message: types.CallbackQuery | typ
)
@router.callback_query(F.data.startswith("connect_pc|"))
async def process_connect_pc(callback_query: types.CallbackQuery, session: Any):
tg_id = callback_query.message.chat.id
@@ -101,15 +110,13 @@ async def process_connect_tv(callback_query: types.CallbackQuery):
text="▶ Продолжить", callback_data=f"continue_tv|{key_name}"
)
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await callback_query.message.answer(
text=CONNECT_TV_TEXT,
reply_markup=builder.as_markup(),
parse_mode="HTML",
disable_web_page_preview=True
disable_web_page_preview=True,
)
@@ -136,8 +143,7 @@ async def process_continue_tv(callback_query: types.CallbackQuery):
finally:
await conn.close()
subscription_link = record['key']
subscription_link = record["key"]
message_text = SUBSCRIPTION_DETAILS_TEXT.format(subscription_link=subscription_link)
@@ -147,12 +153,8 @@ async def process_continue_tv(callback_query: types.CallbackQuery):
text="📖 Полная инструкция", url="https://vpn4tv.com/quick-guide.html"
)
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await callback_query.message.answer(
text=message_text,
reply_markup=builder.as_markup(),
parse_mode="HTML"
text=message_text, reply_markup=builder.as_markup(), parse_mode="HTML"
)
+63 -21
View File
@@ -8,8 +8,6 @@ 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,
@@ -21,7 +19,15 @@ from config import (
TRIAL_TIME,
USE_NEW_PAYMENT_FLOW,
)
from database import get_balance, get_trial, save_temporary_data, store_key, update_balance
from bot import bot
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,
@@ -47,18 +53,25 @@ class Form(StatesGroup):
@router.callback_query(F.data == "create_key")
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext, session: Any):
async def confirm_create_new_key(
callback_query: CallbackQuery, state: FSMContext, session: Any
):
tg_id = callback_query.message.chat.id
logger.info(f"User {tg_id} confirmed creation of a new key.")
logger.info(f"Balance for user {tg_id} is sufficient. Proceeding with key creation.")
logger.info(
f"Balance for user {tg_id} is sufficient. Proceeding with key creation."
)
await handle_key_creation(tg_id, state, session, callback_query)
async def handle_key_creation(
tg_id: int, state: FSMContext, session: Any, message_or_query: Message | CallbackQuery
tg_id: int,
state: FSMContext,
session: Any,
message_or_query: Message | CallbackQuery,
):
"""Создание ключа с учётом выбора тарифного плана."""
current_time = datetime.utcnow()
@@ -68,7 +81,9 @@ async def handle_key_creation(
expiry_time = current_time + timedelta(days=TRIAL_TIME)
logger.info(f"Assigned 1-day trial to user {tg_id}.")
await session.execute("UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id)
await session.execute(
"UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id
)
await create_key(tg_id, expiry_time, state, session, message_or_query)
else:
builder = InlineKeyboardBuilder()
@@ -85,14 +100,18 @@ async def handle_key_creation(
builder.row(
InlineKeyboardButton(
text=f"📅 {plan_id} мес. - {price}{discount_text}", callback_data=f"select_plan_{plan_id}"
text=f"📅 {plan_id} мес. - {price}{discount_text}",
callback_data=f"select_plan_{plan_id}",
)
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
await message_or_query.message.answer(
"💳 Выберите тарифный план для создания нового ключа:", reply_markup=builder.as_markup()
"💳 Выберите тарифный план для создания нового ключа:",
reply_markup=builder.as_markup(),
)
await state.update_data(tg_id=tg_id)
await state.set_state(Form.waiting_for_server_selection)
@@ -120,7 +139,7 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
"plan_price": plan_price,
"duration_days": duration_days,
"required_amount": max(0, plan_price - balance),
}
},
)
if balance < plan_price:
@@ -130,8 +149,12 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
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"))
builder.row(
InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay")
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
await callback_query.message.answer(
f"💳 Недостаточно средств. Для продолжения необходимо пополнить баланс на {required_amount}₽.",
@@ -143,6 +166,7 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
expiry_time = datetime.utcnow() + timedelta(days=duration_days)
await create_key(tg_id, expiry_time, None, session, callback_query)
async def create_key(
tg_id: int,
expiry_time: datetime,
@@ -162,7 +186,9 @@ async def create_key(
)
if not existing_key:
break
logger.warning(f"Key name '{key_name}' already exists for user {tg_id}. Generating a new one.")
logger.warning(
f"Key name '{key_name}' already exists for user {tg_id}. Generating a new one."
)
client_id = str(uuid.uuid4())
email = key_name.lower()
@@ -187,13 +213,23 @@ async def create_key(
await asyncio.gather(*tasks)
logger.info(f"Key created on cluster {least_loaded_cluster} for user {tg_id}.")
await store_key(tg_id, client_id, email, expiry_timestamp, public_link, least_loaded_cluster, session)
await store_key(
tg_id,
client_id,
email,
expiry_timestamp,
public_link,
least_loaded_cluster,
session,
)
except Exception as e:
logger.error(f"Error while creating the key for user {tg_id} on cluster: {e}")
if isinstance(message_or_query, Message):
await message_or_query.answer("❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова.")
await message_or_query.answer(
"❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова."
)
elif isinstance(message_or_query, CallbackQuery):
await message_or_query.message.answer(
"❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова."
@@ -201,7 +237,7 @@ async def create_key(
else:
await bot.send_message(
chat_id=tg_id,
text="❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова."
text="❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова.",
)
return
@@ -213,12 +249,14 @@ async def create_key(
)
builder.row(
InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{public_link}"),
InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{public_link}"),
InlineKeyboardButton(
text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{public_link}"
),
)
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()
@@ -228,9 +266,13 @@ async def create_key(
if isinstance(message_or_query, Message):
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())
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 bot.send_message(
chat_id=tg_id, text=key_message, reply_markup=builder.as_markup()
)
if state:
await state.clear()
+11 -11
View File
@@ -1,15 +1,24 @@
import asyncio
import asyncpg
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, DATABASE_URL, LIMIT_IP, TOTAL_GB
from database import get_servers_from_db
from logger import logger
async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_timestamp):
"""
Создает ключ на всех серверах указанного кластера.
:param cluster_id: ID кластера.
:param tg_id: Telegram ID пользователя.
:param client_id: Уникальный идентификатор клиента.
:param email: Email клиента.
:param expiry_timestamp: Время истечения ключа (timestamp в миллисекундах).
:param allow_existing: Игнорируется, ключи всегда продолжают выполнение.
"""
try:
tasks = []
servers = await get_servers_from_db()
@@ -32,14 +41,6 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
)
continue
conn = await asyncpg.connect(DATABASE_URL)
existing_key = await conn.fetchrow(
"SELECT 1 FROM keys WHERE email = $1", email
)
if existing_key:
raise ValueError(f"Email {email} уже существует в базе данных.")
tasks.append(
add_client(
xui,
@@ -54,7 +55,6 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
inbound_id=int(inbound_id),
)
)
await conn.close()
await asyncio.gather(*tasks)
+24 -23
View File
@@ -7,16 +7,17 @@ from typing import Any
from aiogram import F, Router, types
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import (
CONNECT_ANDROID,
CONNECT_IOS,
DOWNLOAD_ANDROID,
DOWNLOAD_IOS,
ENABLE_UPDATE_SUBSCRIPTION_BUTTON,
PUBLIC_LINK,
RENEWAL_PLANS,
TOTAL_GB,
)
from database import (
delete_key,
get_balance,
@@ -104,14 +105,10 @@ def build_keys_response(records):
)
builder.row(
InlineKeyboardButton(
text=" Добавить подписку", callback_data="create_key"
)
InlineKeyboardButton(text=" Добавить подписку", callback_data="create_key")
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
inline_keyboard = builder.as_markup()
response_message = (
@@ -121,14 +118,18 @@ def build_keys_response(records):
return inline_keyboard, response_message
async def send_with_optional_image(send_message, send_photo, image_path, text, keyboard):
async def send_with_optional_image(
send_message, send_photo, image_path, text, keyboard
):
"""
Отправляет сообщение с изображением, если файл существует. В противном случае отправляет только текст.
"""
if os.path.isfile(image_path):
with open(image_path, "rb") as image_file:
await send_photo(
photo=BufferedInputFile(image_file.read(), filename=os.path.basename(image_path)),
photo=BufferedInputFile(
image_file.read(), filename=os.path.basename(image_path)
),
caption=text,
reply_markup=keyboard,
)
@@ -139,8 +140,6 @@ async def send_with_optional_image(send_message, send_photo, image_path, text, k
)
@router.callback_query(F.data.startswith("view_key|"))
async def process_callback_view_key(callback_query: types.CallbackQuery, session: Any):
tg_id = callback_query.message.chat.id
@@ -165,14 +164,17 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
time_left = expiry_date - current_date
if time_left.total_seconds() <= 0:
days_left_message = (
"<b>🕒 Статус подписки:</b>\n🔴 Истекла\nОсталось часов: 0"
)
elif time_left.days > 0:
days_left_message = f"Осталось дней: <b>{time_left.days}</b>"
days_left_message = "<b>🕒 Статус подписки:</b>\n🔴 Истекла\nОсталось часов: 0\nОсталось минут: 0"
else:
hours_left = time_left.seconds // 3600
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
total_seconds = int(time_left.total_seconds())
days = total_seconds // 86400
hours = (total_seconds % 86400) // 3600
minutes = (total_seconds % 3600) // 60
days_left_message = (
f"<b>🕒 Статус подписки:</b>\n"
f"Осталось: <b>{days}</b> дней, <b>{hours}</b> часов, <b>{minutes}</b> минут"
)
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
response_message = key_message(
@@ -181,7 +183,8 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
builder = InlineKeyboardBuilder()
builder.row(
if not key.startswith(PUBLIC_LINK) or ENABLE_UPDATE_SUBSCRIPTION_BUTTON:
builder.row(
InlineKeyboardButton(
text="🔄 Обновить подписку",
callback_data=f"update_subscription|{key_name}",
@@ -196,9 +199,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
)
builder.row(
InlineKeyboardButton(
text=IMPORT_IOS, url=f"{CONNECT_IOS}{key}"
),
InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{key}"),
InlineKeyboardButton(
text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key}"
),
@@ -210,7 +211,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
),
InlineKeyboardButton(
text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"
)
),
)
builder.row(
+8 -9
View File
@@ -4,8 +4,8 @@ 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 database import get_servers_from_db
from logger import logger
@@ -95,7 +95,9 @@ async def handle_old_subscription(request):
status=400,
)
logger.info(f"Значение created_at для клиента с email {email}: {created_at_ms}, кластер: {cluster_name}")
logger.info(
f"Значение created_at для клиента с email {email}: {created_at_ms}, кластер: {cluster_name}"
)
created_at_datetime = datetime.utcfromtimestamp(created_at_ms / 1000)
logger.info(
@@ -129,9 +131,8 @@ async def handle_old_subscription(request):
"Content-Type": "text/plain; charset=utf-8",
"Content-Disposition": "inline",
"profile-update-interval": "7",
"profile-title": "base64:" + base64.b64encode(
encoded_project_name.encode("utf-8")
).decode("utf-8"),
"profile-title": "base64:"
+ base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"),
}
logger.info(f"Возвращаем объединенные подписки для email: {email}")
@@ -141,7 +142,6 @@ async def handle_old_subscription(request):
await conn.close()
async def handle_new_subscription(request):
email = request.match_info.get("email")
tg_id = request.match_info.get("tg_id")
@@ -205,9 +205,8 @@ async def handle_new_subscription(request):
"Content-Type": "text/plain; charset=utf-8",
"Content-Disposition": "inline",
"profile-update-interval": "7",
"profile-title": "base64:" + base64.b64encode(
encoded_project_name.encode("utf-8")
).decode("utf-8"),
"profile-title": "base64:"
+ base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"),
}
logger.info(f"Возвращаем объединенные подписки для email: {email}")
+9 -3
View File
@@ -4,10 +4,17 @@ from datetime import datetime, timedelta
from typing import Any
import pytz
from config import (
ADMIN_PASSWORD,
ADMIN_USERNAME,
LIMIT_IP,
PUBLIC_LINK,
TOTAL_GB,
TRIAL_TIME,
)
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
@@ -17,8 +24,7 @@ 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
"SELECT trial FROM connections WHERE tg_id = $1", tg_id
)
if trial_status == 1:
+43 -22
View File
@@ -5,8 +5,6 @@ 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,
@@ -16,6 +14,8 @@ from config import (
TOTAL_GB,
TRIAL_TIME,
)
from py3xui import AsyncApi
from database import (
add_blocked_user,
add_notification,
@@ -140,7 +140,11 @@ async def notify_10h_keys(
for cluster_id in servers:
await renew_key_in_cluster(
cluster_id, email, record["client_id"], new_expiry_time, TOTAL_GB
cluster_id,
email,
record["client_id"],
new_expiry_time,
TOTAL_GB,
)
logger.info(
f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}."
@@ -169,7 +173,9 @@ async def notify_10h_keys(
f"Уведомление об успешном продлении отправлено клиенту {tg_id}."
)
except TelegramForbiddenError:
logger.warning(f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users.")
logger.warning(
f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users."
)
await add_blocked_user(tg_id, conn)
except Exception as e:
logger.error(f"Ошибка при продлении подписки для клиента {tg_id}: {e}")
@@ -183,7 +189,9 @@ async def notify_10h_keys(
keyboard.button(text="👤 Личный кабинет", callback_data="profile")
keyboard.adjust(1)
await bot.send_message(tg_id, message, reply_markup=keyboard.as_markup())
await bot.send_message(
tg_id, message, reply_markup=keyboard.as_markup()
)
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
await conn.execute(
@@ -191,9 +199,12 @@ async def notify_10h_keys(
record["client_id"],
)
logger.info(
f"Обновлено поле notified для клиента {record['client_id']}.")
f"Обновлено поле notified для клиента {record['client_id']}."
)
except TelegramForbiddenError:
logger.warning(f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users.")
logger.warning(
f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users."
)
await add_blocked_user(tg_id, conn)
except Exception as e:
logger.debug(
@@ -260,7 +271,11 @@ async def notify_24h_keys(
for cluster_id in servers:
await renew_key_in_cluster(
cluster_id, email, record["client_id"], new_expiry_time, TOTAL_GB
cluster_id,
email,
record["client_id"],
new_expiry_time,
TOTAL_GB,
)
logger.info(
f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}."
@@ -290,7 +305,9 @@ async def notify_24h_keys(
f"Уведомление об успешном продлении отправлено клиенту {tg_id}."
)
except TelegramForbiddenError:
logger.warning(f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users.")
logger.warning(
f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users."
)
await add_blocked_user(tg_id, conn)
except Exception as e:
logger.error(f"Ошибка при продлении подписки для клиента {tg_id}: {e}")
@@ -317,11 +334,11 @@ 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.")
logger.warning(
f"Бот заблокирован пользователем {tg_id}. Записываем в blocked_users."
)
await add_blocked_user(tg_id, conn)
except Exception as e:
logger.error(
@@ -332,9 +349,7 @@ async def notify_24h_keys(
"UPDATE keys SET notified_24h = TRUE WHERE client_id = $1",
record["client_id"],
)
logger.info(
f"Обновлено поле notified_24h для клиента {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 часа завершена.")
@@ -389,14 +404,20 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
try:
await bot.send_message(tg_id, message, reply_markup=keyboard)
logger.info(f"Отправлено уведомление неактивному пользователю {tg_id}.")
logger.info(
f"Отправлено уведомление неактивному пользователю {tg_id}."
)
await add_notification(tg_id, "inactive_trial", session=conn)
except TelegramForbiddenError:
logger.warning(f"Бот заблокирован пользователем {tg_id}. Добавляем в blocked_users.")
logger.warning(
f"Бот заблокирован пользователем {tg_id}. Добавляем в blocked_users."
)
await add_blocked_user(tg_id, conn)
except Exception as e:
logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")
logger.error(
f"Ошибка при отправке уведомления пользователю {tg_id}: {e}"
)
except Exception as e:
logger.error(f"Ошибка при обработке пользователя {tg_id}: {e}")
@@ -418,7 +439,9 @@ 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:
@@ -427,7 +450,6 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
logger.error(f"Ошибка при обработке подписки {record['client_id']}: {e}")
async def process_key(record, bot, conn):
tg_id = record["tg_id"]
client_id = record["client_id"]
@@ -519,7 +541,6 @@ async def process_key(record, bot, conn):
logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}")
async def check_online_users():
servers = await get_servers_from_db()
+8 -3
View File
@@ -1,7 +1,6 @@
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,
@@ -54,9 +53,15 @@ async def handle_pay(callback_query: CallbackQuery):
)
)
builder.row(InlineKeyboardButton(text="🎟️ Активировать купон", callback_data="activate_coupon"))
builder.row(
InlineKeyboardButton(
text="🎟️ Активировать купон", callback_data="activate_coupon"
)
)
if DONATIONS_ENABLE:
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
builder.row(
InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
-1
View File
@@ -1,7 +1,6 @@
__all__ = ("router",)
from aiogram import Router
from config import (
CRYPTO_BOT_ENABLE,
ROBOKASSA_ENABLE,
+2101 -1530
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -7,8 +7,6 @@ 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 (
ROBOKASSA_ENABLE,
ROBOKASSA_LOGIN,
@@ -16,6 +14,8 @@ from config import (
ROBOKASSA_PASSWORD2,
ROBOKASSA_TEST_MODE,
)
from robokassa import HashAlgorithm, Robokassa
from database import (
add_connection,
add_payment,
+514 -517
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18 -11
View File
@@ -5,10 +5,18 @@ 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 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.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()
@@ -17,7 +25,9 @@ router = Router()
@router.callback_query(F.data == "profile")
@router.message(F.text == "/profile")
async def process_callback_view_profile(
callback_query_or_message: types.Message | types.CallbackQuery, state: FSMContext, admin: bool
callback_query_or_message: types.Message | types.CallbackQuery,
state: FSMContext,
admin: bool,
):
if isinstance(callback_query_or_message, types.CallbackQuery):
chat_id = callback_query_or_message.message.chat.id
@@ -38,7 +48,9 @@ async def process_callback_view_profile(
try:
trial_status = await get_trial(chat_id, conn)
profile_message = profile_message_send(username, chat_id, int(balance), key_count)
profile_message = profile_message_send(
username, chat_id, int(balance), key_count
)
if key_count == 0:
profile_message += "\n<pre>🔧 <i>Нажмите кнопку ➕ Устройство, чтобы настроить VPN-подключение</i></pre>"
@@ -47,15 +59,10 @@ async def process_callback_view_profile(
builder = InlineKeyboardBuilder()
if trial_status == 0 or key_count == 0:
builder.row(
InlineKeyboardButton(text=ADD_SUB, callback_data="create_key")
)
builder.row(InlineKeyboardButton(text=ADD_SUB, callback_data="create_key"))
else:
builder.row(
InlineKeyboardButton(text=MY_SUBS, callback_data="view_keys")
)
builder.row(InlineKeyboardButton(text=MY_SUBS, callback_data="view_keys"))
builder.row(
InlineKeyboardButton(
+68 -31
View File
@@ -11,7 +11,6 @@ from aiogram.types import (
Message,
)
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import (
CHANNEL_EXISTS,
CHANNEL_URL,
@@ -22,7 +21,14 @@ from config import (
DOWNLOAD_IOS,
SUPPORT_CHAT_URL,
)
from database import add_connection, add_referral, check_connection_exists, get_trial, use_trial
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,
@@ -58,10 +64,11 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
connection_exists = await check_connection_exists(message.chat.id)
logger.info(f"Проверка существования подключения: {connection_exists}")
if not connection_exists:
await add_connection(tg_id=message.chat.id, session=session)
logger.info(f"Пользователь {message.chat.id} успешно добавлен в базу данных.")
logger.info(
f"Пользователь {message.chat.id} успешно добавлен в базу данных."
)
if "gift_" in message.text:
logger.info(f"Обнаружена ссылка на подарок: {message.text}")
@@ -71,28 +78,41 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
recipient_tg_id = message.chat.id
gift_info = await session.fetchrow(
"SELECT * FROM gifts WHERE gift_id = $1 AND is_used = FALSE", gift_id
"SELECT * FROM gifts WHERE gift_id = $1 AND is_used = FALSE",
gift_id,
)
if gift_info is None:
logger.warning(f"Подарок с ID {gift_id} уже был использован или не существует.")
await message.answer("Этот подарок уже был использован или не существует.")
logger.warning(
f"Подарок с ID {gift_id} уже был использован или не существует."
)
await message.answer(
"Этот подарок уже был использован или не существует."
)
return await show_start_menu(message, admin, session)
if gift_info['sender_tg_id'] == recipient_tg_id:
if gift_info["sender_tg_id"] == recipient_tg_id:
logger.warning(
f"Пользователь {recipient_tg_id} попытался активировать подарок, который был отправлен им самим."
)
await message.answer("❌ Вы не можете получить подарок от самого себя.")
await message.answer(
"❌ Вы не можете получить подарок от самого себя."
)
return await show_start_menu(message, admin, session)
selected_months = gift_info['selected_months']
expiry_time = gift_info['expiry_time']
selected_months = gift_info["selected_months"]
expiry_time = gift_info["expiry_time"]
expiry_time_naive = expiry_time.replace(tzinfo=None)
logger.info(f"Подарок с ID {gift_id} успешно найден для пользователя {recipient_tg_id}.")
logger.info(
f"Подарок с ID {gift_id} успешно найден для пользователя {recipient_tg_id}."
)
await create_key(recipient_tg_id, expiry_time_naive, state, session, message)
logger.info(f"Ключ создан для пользователя {recipient_tg_id} на срок {selected_months} месяцев.")
await create_key(
recipient_tg_id, expiry_time_naive, state, session, message
)
logger.info(
f"Ключ создан для пользователя {recipient_tg_id} на срок {selected_months} месяцев."
)
await session.execute(
"UPDATE gifts SET is_used = TRUE, recipient_tg_id = $1 WHERE gift_id = $2",
@@ -103,7 +123,9 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
await message.answer(
f"🎉 Ваш подарок на {selected_months} {'месяц' if selected_months == 1 else 'месяца' if selected_months in [2, 3, 4] else 'месяцев'} активирован!"
)
logger.info(f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}.")
logger.info(
f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}."
)
return
elif "referral_" in message.text:
@@ -114,16 +136,23 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
logger.info(
f"Пользователь {message.chat.id} уже зарегистрирован и не может стать рефералом."
)
await message.answer("❌ Вы уже зарегистрированы и не можете использовать реферальную ссылку.")
await message.answer(
"❌ Вы уже зарегистрированы и не можете использовать реферальную ссылку."
)
return await show_start_menu(message, admin, session)
if referrer_tg_id == message.chat.id:
logger.warning(f"Пользователь {message.chat.id} попытался стать рефералом самого себя.")
await message.answer("❌ Вы не можете быть рефералом самого себя.")
logger.warning(
f"Пользователь {message.chat.id} попытался стать рефералом самого себя."
)
await message.answer(
"❌ Вы не можете быть рефералом самого себя."
)
return await show_start_menu(message, admin, session)
existing_referral = await session.fetchrow(
"SELECT * FROM referrals WHERE referred_tg_id = $1", message.chat.id
"SELECT * FROM referrals WHERE referred_tg_id = $1",
message.chat.id,
)
if existing_referral:
@@ -131,7 +160,9 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
return await show_start_menu(message, admin, session)
await add_referral(message.chat.id, referrer_tg_id, session)
logger.info(f"Реферал {message.chat.id} использовал ссылку от пользователя {referrer_tg_id}")
logger.info(
f"Реферал {message.chat.id} использовал ссылку от пользователя {referrer_tg_id}"
)
return await show_start_menu(message, admin, session)
except (ValueError, IndexError) as e:
@@ -139,12 +170,16 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
return
else:
logger.info(f"Пользователь {message.chat.id} зашел без реферальной ссылки или подарка.")
logger.info(
f"Пользователь {message.chat.id} зашел без реферальной ссылки или подарка."
)
await show_start_menu(message, admin, session)
except (ValueError, IndexError) as e:
logger.error(f"Ошибка при обработке сообщения пользователя {message.chat.id}: {e}")
logger.error(
f"Ошибка при обработке сообщения пользователя {message.chat.id}: {e}"
)
await message.answer("❌ Произошла ошибка. Пожалуйста, попробуйте снова.")
else:
await show_start_menu(message, admin, session)
@@ -159,7 +194,9 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
builder = InlineKeyboardBuilder()
if trial_status == 0:
builder.row(InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn"))
builder.row(
InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn")
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
@@ -169,7 +206,9 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
)
if admin:
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
builder.row(
InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")
)
builder.row(InlineKeyboardButton(text="🌐 О нашем VPN", callback_data="about_vpn"))
@@ -223,12 +262,8 @@ async def handle_connect_vpn(callback_query: CallbackQuery, session: Any):
),
)
builder.row(
InlineKeyboardButton(
text=PC_BUTTON, callback_data=f"connect_pc|{email}"
),
InlineKeyboardButton(
text=TV_BUTTON, callback_data=f"connect_tv|{email}"
)
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")
@@ -244,7 +279,9 @@ async def handle_about_vpn(callback_query: CallbackQuery):
builder = InlineKeyboardBuilder()
if DONATIONS_ENABLE:
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
builder.row(
InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
)
builder.row(
InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL),
+5 -3
View File
@@ -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
@@ -14,10 +14,12 @@ from logger import logger
async def get_usd_rate():
try:
async with aiohttp.ClientSession() as session:
async with session.get('https://www.cbr-xml-daily.ru/daily_json.js') as response:
async with session.get(
"https://www.cbr-xml-daily.ru/daily_json.js"
) as response:
if response.status == 200:
data = await response.text()
usd = float(json.loads(data)['Valute']['USD']['Value'])
usd = float(json.loads(data)["Valute"]["USD"]["Value"])
else:
usd = float(100) # Default value if request fails
except Exception as e:
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

-1
View File
@@ -3,7 +3,6 @@ from typing import Any
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
from config import ADMIN_ID
-1
View File
@@ -4,7 +4,6 @@ from typing import Any
import asyncpg
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
from config import DATABASE_URL
+42
View File
@@ -0,0 +1,42 @@
import asyncio
from aiogram import Dispatcher, types
from aiogram.dispatcher import DEFAULT_RATE_LIMIT
from aiogram.dispatcher.handler import CancelHandler, current_handler
from aiogram.dispatcher.middlewares import BaseMiddleware
from aiogram.utils.exceptions import Throttled
from aiogram.utils.keyboard import InlineKeyboardBuilder
class ThrottlingMiddleware(BaseMiddleware):
def __init__(self, limit=DEFAULT_RATE_LIMIT, key_prefix="antiflood_"):
self.rate_limit = limit
self.prefix = key_prefix
super(ThrottlingMiddleware, self).__init__()
async def on_process_message(self, message: types.Message, data: dict):
handler = current_handler.get()
dispatcher = Dispatcher.get_current()
if handler:
limit = getattr(handler, "throttling_rate_limit", self.rate_limit)
key = getattr(handler, "throttling_key", f"{self.prefix}_{handler.__name__}")
else:
limit = self.rate_limit
key = f"{self.prefix}_message"
try:
await dispatcher.throttle(key, rate=limit)
except Throttled as t:
await self.message_throttled(message, t)
raise CancelHandler()
async def message_throttled(self, message: types.Message, throttled: Throttled):
delta = throttled.rate - throttled.delta
if throttled.exceeded_count <= 2:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await message.reply("🚫 Слишком много запросов! Пожалуйста, не торопитесь!", reply_markup=builder.as_markup())
await asyncio.sleep(delta)
+4 -3
View File
@@ -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
@@ -104,8 +104,9 @@ async def notify_admin(server_name: str):
current_time = datetime.now()
last_notification_time = last_notification_times.get(server_name)
if last_notification_time and current_time - last_notification_time < timedelta(
minutes=3
if (
last_notification_time
and current_time - last_notification_time < timedelta(minutes=3)
):
logger.info(
f"Не отправляем уведомление для сервера {server_name}, так как прошло менее 3 минут."