@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
):
|
||||
"""
|
||||
Добавляет информацию о подарке в базу данных.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
|
||||
from aiogram.filters import BaseFilter
|
||||
from aiogram.types import Message
|
||||
|
||||
from config import ADMIN_ID
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
@@ -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
@@ -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,7 +1,6 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from aiogram import Router
|
||||
|
||||
from config import (
|
||||
CRYPTO_BOT_ENABLE,
|
||||
ROBOKASSA_ENABLE,
|
||||
|
||||
+2101
-1530
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -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
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+391
-391
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+18
-11
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -3,7 +3,6 @@ from typing import Any
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import ADMIN_ID
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from typing import Any
|
||||
import asyncpg
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import DATABASE_URL
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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 минут."
|
||||
|
||||
Reference in New Issue
Block a user