refactoring and minor improvements

This commit is contained in:
Vladless
2025-02-25 19:56:42 +03:00
parent aded5606a8
commit bded99263a
30 changed files with 967 additions and 963 deletions
+1 -1
View File
@@ -6,9 +6,9 @@ from typing import Optional, Tuple, Union
import aiofiles
from aiogram.types import BufferedInputFile
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER, PG_HOST, PG_PORT
from bot import bot
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER, PG_HOST, PG_PORT
from logger import logger
+1 -1
View File
@@ -8,8 +8,8 @@ from aiogram.filters import ExceptionTypeFilter
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import BufferedInputFile, ErrorEvent
from aiogram.utils.markdown import hbold
from config import ADMIN_ID, API_TOKEN
from config import ADMIN_ID, API_TOKEN
from filters.private import IsPrivateFilter
from logger import logger
from middlewares import register_middleware
+1 -1
View File
@@ -2,8 +2,8 @@ from dataclasses import dataclass
from typing import Any
import py3xui
from config import LIMIT_IP, SUPERNODE
from config import LIMIT_IP, SUPERNODE
from logger import logger
+22 -16
View File
@@ -4,8 +4,8 @@ from typing import Any
import asyncpg
import pytz
from config import CASHBACK, CHECK_REFERRAL_REWARD_ISSUED, DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
from config import CASHBACK, CHECK_REFERRAL_REWARD_ISSUED, DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
from logger import logger
@@ -848,12 +848,16 @@ async def get_total_referral_bonus(conn, referrer_tg_id: int, max_levels: int) -
SELECT
COALESCE(SUM(
CASE
{" ".join([
f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]} * ep.amount"
if isinstance(REFERRAL_BONUS_PERCENTAGES[level], float)
else f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]}"
for level in REFERRAL_BONUS_PERCENTAGES
])}
{
" ".join(
[
f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]} * ep.amount"
if isinstance(REFERRAL_BONUS_PERCENTAGES[level], float)
else f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]}"
for level in REFERRAL_BONUS_PERCENTAGES
]
)
}
ELSE 0
END
), 0) AS total_bonus
@@ -890,12 +894,16 @@ async def get_total_referral_bonus(conn, referrer_tg_id: int, max_levels: int) -
SELECT
COALESCE(SUM(
CASE
{" ".join([
f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]} * p.amount"
if isinstance(REFERRAL_BONUS_PERCENTAGES[level], float)
else f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]}"
for level in REFERRAL_BONUS_PERCENTAGES
])}
{
" ".join(
[
f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]} * p.amount"
if isinstance(REFERRAL_BONUS_PERCENTAGES[level], float)
else f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]}"
for level in REFERRAL_BONUS_PERCENTAGES
]
)
}
ELSE 0
END
), 0) AS total_bonus
@@ -1133,7 +1141,7 @@ async def upsert_user(
is_bot = EXCLUDED.is_bot,
updated_at = CURRENT_TIMESTAMP
RETURNING
id, tg_id, username, first_name, last_name, language_code,
tg_id, username, first_name, last_name, language_code,
is_bot, created_at, updated_at
""",
tg_id,
@@ -1146,13 +1154,11 @@ async def upsert_user(
logger.debug(f"Успешно обновлена информация о пользователе {tg_id}")
# Преобразуем результат в словарь
return dict(user_data)
except Exception as e:
logger.error(f"Ошибка при обновлении информации о пользователе {tg_id}: {e}")
raise
finally:
# Закрываем соединение только если мы его создали
if conn and close_conn:
await conn.close()
logger.debug("Закрытие подключения к базе данных")
+1
View File
@@ -1,5 +1,6 @@
from aiogram.filters import BaseFilter
from aiogram.types import Message
from config import ADMIN_ID
+1 -1
View File
@@ -4,8 +4,8 @@ from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, Message
from config import USERNAME_BOT
from config import USERNAME_BOT
from database import create_coupon, delete_coupon, get_all_coupons
from filters.admin import IsAdminFilter
from keyboards.admin.coupons_kb import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb
+1 -1
View File
@@ -6,10 +6,10 @@ from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, Message
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
from py3xui import AsyncApi
from backup import create_backup_and_send_to_admins
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
from database import check_unique_server_name, delete_server, get_servers
from filters.admin import IsAdminFilter
from handlers.keys.key_utils import create_key_on_cluster
+1 -1
View File
@@ -8,8 +8,8 @@ from aiogram.exceptions import TelegramBadRequest
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, Message
from config import TOTAL_GB
from config import TOTAL_GB
from database import (
delete_key,
delete_user_data,
+3
View File
@@ -4,3 +4,6 @@ PROFILE = "👤 Личный кабинет"
BACK = "В меню подарков"
GIFTS_ABOUT = "<b>Дарите подарки и следите, чтобы они дошли до адресата! 🎄</b>"
SHARE_GIFT = "🎁 Поделиться подарком"
APPLY = "✅ Подтвердить"
CANCEL = "❌ Отмена"
NO_GIFTS = "У вас нет подарков."
+30 -21
View File
@@ -6,35 +6,27 @@ from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import CAPTCHA_EMOJIS
from config import CAPTCHA_EMOJIS
from logger import logger
from .utils import edit_or_send_message
router = Router()
async def generate_captcha(message: Message, state: FSMContext):
"""Генерирует новую капчу и сохраняет правильный ответ в состоянии
Пример словаря CAPTCHA_EMOJIS:
{
"🐶": "собаку", # Собака
"🐱": "кошку", # Кошка
"🐭": "мышь", # Мышь
"🐹": "хомяка", # Хомяк
"🐰": "кролика", # Кролик
...
}
"""
correct_emoji, correct_text = secrets.choice(list(CAPTCHA_EMOJIS.items()))
wrong_emojis = random.sample([e for e in CAPTCHA_EMOJIS.keys() if e != correct_emoji], 3)
all_emojis = [correct_emoji] + wrong_emojis
random.shuffle(all_emojis)
await state.update_data(correct_emoji=correct_emoji)
await state.update_data(message=message)
await state.update_data(
correct_emoji=correct_emoji,
message_id=message.message_id,
chat_id=message.chat.id,
)
builder = InlineKeyboardBuilder()
for emoji in all_emojis:
@@ -42,7 +34,7 @@ async def generate_captcha(message: Message, state: FSMContext):
builder.adjust(2, 2)
return {
"text": f"🔒 Для подтверждения что вы не робот, выберите кнопку с {correct_text}",
"text": f"🔒 Для подтверждения, что вы не робот, выберите кнопку с {correct_text}",
"markup": builder.as_markup(),
}
@@ -54,12 +46,29 @@ async def check_captcha(callback: CallbackQuery, state: FSMContext, session: Any
selected_emoji = callback.data.split("captcha_")[1]
state_data = await state.get_data()
correct_emoji = state_data.get("correct_emoji")
message = state_data.get("message", callback.message)
message_id = state_data.get("message_id")
chat_id = state_data.get("chat_id")
if not message_id or not chat_id:
target_message = callback.message
else:
try:
target_message = await callback.bot.edit_message_text(
chat_id=chat_id,
message_id=message_id,
text=callback.message.text,
)
except Exception:
target_message = callback.message
if selected_emoji == correct_emoji:
logger.info(f"Пользователь {callback.message.chat.id} успешно прошел капчу")
await start_command(message, state, session, admin, False)
await start_command(target_message, state, session, admin, False)
else:
logger.warning(f"Пользователь {callback.message.chat.id} неверно ответил на капчу")
captcha = await generate_captcha(message, state)
await callback.message.answer(text=captcha["text"], reply_markup=captcha["markup"])
captcha = await generate_captcha(target_message, state)
await edit_or_send_message(
target_message=target_message,
text=captcha["text"],
reply_markup=captcha["markup"],
)
+19 -5
View File
@@ -3,10 +3,12 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, InlineKeyboardButton, LabeledPrice, Message, PreCheckoutQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import RUB_TO_XTR
from config import RUB_TO_XTR
from logger import logger
from .utils import edit_or_send_message
class DonateState(StatesGroup):
entering_donate_amount = State()
@@ -31,10 +33,15 @@ async def process_donate(callback_query: CallbackQuery, state: FSMContext):
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await callback_query.message.answer(
text="🌟 Поддержите наш проект! 💪\n\n"
text = (
"🌟 Поддержите наш проект! 💪\n\n"
"💖 Каждый донат помогает развивать и улучшать сервис. "
"🤝 Мы ценим вашу поддержку и работаем над тем, чтобы сделать наш продукт еще лучше. 🚀💡",
"🤝 Мы ценим вашу поддержку и работаем над тем, чтобы сделать наш продукт еще лучше. 🚀💡"
)
await edit_or_send_message(
target_message=callback_query.message,
text=text,
reply_markup=builder.as_markup(),
)
@@ -43,7 +50,14 @@ async def process_donate(callback_query: CallbackQuery, state: FSMContext):
async def process_enter_donate_amount(callback_query: CallbackQuery, state: FSMContext):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="donate"))
await callback_query.message.answer("💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup())
text = "💸 Введите сумму доната в рублях:"
await edit_or_send_message(
target_message=callback_query.message,
text=text,
reply_markup=builder.as_markup(),
)
await state.set_state(DonateState.entering_donate_amount)
+1 -1
View File
@@ -8,8 +8,8 @@ from aiogram.types import (
Message,
)
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import CONNECT_MACOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
from config import CONNECT_MACOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
from database import get_key_details
from handlers.texts import (
CONNECT_TV_TEXT,
+4 -4
View File
@@ -8,6 +8,10 @@ from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from py3xui import AsyncApi
from bot import bot
from client import delete_client
from config import (
ADMIN_PASSWORD,
ADMIN_USERNAME,
@@ -25,10 +29,6 @@ from config import (
USE_COUNTRY_SELECTION,
USE_NEW_PAYMENT_FLOW,
)
from py3xui import AsyncApi
from bot import bot
from client import delete_client
from database import (
add_connection,
check_connection_exists,
+1 -1
View File
@@ -1,10 +1,10 @@
import asyncio
from typing import Any
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, USE_COUNTRY_SELECTION
from py3xui import AsyncApi
from client import ClientConfig, add_client, delete_client, extend_client_key, get_client_traffic, toggle_client
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, USE_COUNTRY_SELECTION
from database import get_servers, store_key
from handlers.utils import get_least_loaded_cluster
from logger import logger
+2 -2
View File
@@ -9,6 +9,8 @@ import pytz
from aiogram import F, Router, types
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from bot import bot
from config import (
CONNECT_ANDROID,
CONNECT_IOS,
@@ -24,8 +26,6 @@ from config import (
USE_COUNTRY_SELECTION,
USE_NEW_PAYMENT_FLOW,
)
from bot import bot
from database import (
check_server_name_by_cluster,
create_temporary_data,
+5 -17
View File
@@ -5,12 +5,12 @@ import re
import time
import urllib.parse
from datetime import datetime
from typing import Dict, List, Optional, Union
import aiohttp
import asyncpg
import pytz
from aiohttp import web
from config import (
DATABASE_URL,
PROJECT_NAME,
@@ -22,13 +22,11 @@ from config import (
USE_COUNTRY_SELECTION,
USERNAME_BOT,
)
from database import get_key_details, get_servers
from handlers.utils import convert_to_bytes
from logger import logger
# Функции для работы с URL и подписками
async def fetch_url_content(url: str, identifier: str) -> list[str]:
"""
Получает содержимое подписки по URL и декодирует его.
@@ -144,7 +142,6 @@ def get_transition_timestamp() -> int:
return transition_timestamp_ms
# Функции для обработки и форматирования данных
def calculate_traffic(cleaned_subscriptions: list[str], expiry_time_ms: int | None) -> str:
"""
Рассчитывает информацию о трафике на основе подписок.
@@ -294,7 +291,6 @@ def prepare_headers(
}
# Основные обработчики запросов
async def handle_subscription(request: web.Request, old_subscription: bool = False) -> web.Response:
"""
Обрабатывает запрос на подписку (старую или новую).
@@ -317,8 +313,8 @@ async def handle_subscription(request: web.Request, old_subscription: bool = Fal
f"Обработка запроса для {'старого' if old_subscription else 'нового'} клиента: email={email}, tg_id={tg_id}"
)
async with asyncpg.connect(DATABASE_URL) as conn:
# Получение данных клиента
conn = await asyncpg.connect(DATABASE_URL)
try:
client_data = await get_key_details(email, conn)
if not client_data:
logger.warning(f"Клиент с email {email} не найден в базе.")
@@ -327,19 +323,15 @@ async def handle_subscription(request: web.Request, old_subscription: bool = Fal
stored_tg_id = client_data.get("tg_id")
server_id = client_data["server_id"]
# Проверка tg_id для новых клиентов
if not old_subscription and str(tg_id) != str(stored_tg_id):
logger.warning(f"Неверный tg_id для клиента с email {email}.")
return web.Response(text="❌ Неверные данные. Получите свой ключ в боте.", status=403)
# Проверка даты создания для старых клиентов
if old_subscription:
created_at_ms = client_data["created_at"]
# Используем pytz для корректного сравнения времени
created_at_datetime = datetime.utcfromtimestamp(created_at_ms / 1000)
logger.info(f"created_at для {email}: {created_at_datetime}, server_id: {server_id}")
# Получаем временную метку перехода
transition_timestamp_ms = get_transition_timestamp()
logger.info(f"Время перехода (с учетом часового пояса Москвы): {transition_timestamp_ms}")
@@ -347,34 +339,30 @@ async def handle_subscription(request: web.Request, old_subscription: bool = Fal
logger.info(f"Клиент с email {email} является новым.")
return web.Response(text="❌ Эта ссылка устарела. Пожалуйста, обновите ссылку.", status=400)
# Форматирование оставшегося времени
expiry_time_ms = client_data.get("expiry_time")
time_left = format_time_left(expiry_time_ms)
# Получение URL-адресов подписки
urls = await get_subscription_urls(server_id, email, conn)
if not urls:
return web.Response(text="❌ Сервер не найден.", status=404)
# Получение и обработка подписок
query_string = request.query_string if not old_subscription else ""
combined_subscriptions = await combine_unique_lines(urls, tg_id or email, query_string)
random.shuffle(combined_subscriptions)
# Очистка строк подписки
cleaned_subscriptions = [clean_subscription_line(line) for line in combined_subscriptions]
# Кодирование подписки в base64
base64_encoded = base64.b64encode("\n".join(cleaned_subscriptions).encode("utf-8")).decode("utf-8")
subscription_info = f"📄 Подписка: {email} - {time_left}"
# Подготовка заголовков ответа
user_agent = request.headers.get("User-Agent", "")
subscription_userinfo = calculate_traffic(cleaned_subscriptions, expiry_time_ms)
headers = prepare_headers(user_agent, PROJECT_NAME, subscription_info, subscription_userinfo)
logger.info(f"Возвращаем объединенные подписки для email: {email}")
return web.Response(text=base64_encoded, headers=headers)
finally:
await conn.close()
async def handle_old_subscription(request: web.Request) -> web.Response:
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
import asyncpg
import pytz
from aiogram import Bot, Router
from config import (
DATABASE_URL,
NOTIFICATION_TIME,
@@ -16,7 +17,6 @@ from config import (
TOTAL_GB,
TRIAL_TIME_DISABLE,
)
from database import (
add_notification,
check_notification_time,
@@ -6,8 +6,8 @@ import pytz
from aiogram import Bot, Router, types
from aiogram.exceptions import TelegramForbiddenError
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import NOTIFY_EXTRA_DAYS, NOTIFY_INACTIVE, NOTIFY_INACTIVE_TRAFFIC, SUPPORT_CHAT_URL, TRIAL_TIME
from config import NOTIFY_EXTRA_DAYS, NOTIFY_INACTIVE, NOTIFY_INACTIVE_TRAFFIC, SUPPORT_CHAT_URL, TRIAL_TIME
from database import (
add_notification,
check_notification_time,
+1
View File
@@ -1,6 +1,7 @@
from aiogram import F, Router
from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import (
CRYPTO_BOT_ENABLE,
DONATIONS_ENABLE,
+851 -878
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -99,12 +99,13 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st
await add_connection(tg_id, balance=0.0, trial=0, session=session)
logger.info(f"Created new connection for user {tg_id} with balance 0.0.")
await edit_or_send_message(
target_message=callback_query.message,
await callback_query.message.delete()
new_message = await callback_query.message.answer(
text="Выберите сумму пополнения:",
reply_markup=builder.as_markup(),
force_text=True,
)
await state.update_data(message_id=new_message.message_id, chat_id=new_message.chat.id)
await state.set_state(ReplenishBalanceState.choosing_amount_robokassa)
logger.info(f"Displayed amount selection for user {tg_id}.")
+1 -1
View File
@@ -14,6 +14,7 @@ from aiogram.types import (
Message,
)
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import (
DATABASE_URL,
INLINE_MODE,
@@ -24,7 +25,6 @@ from config import (
TRIAL_TIME,
USERNAME_BOT,
)
from database import get_balance, get_key_count, get_last_payments, get_referral_stats, get_trial
from handlers.buttons.profile import (
ADD_SUB,
+8 -2
View File
@@ -10,6 +10,8 @@ from aiogram.types import (
Message,
)
from aiogram.utils.keyboard import InlineKeyboardBuilder
from bot import bot
from config import (
CAPTCHA_ENABLE,
CHANNEL_EXISTS,
@@ -19,8 +21,6 @@ from config import (
DONATIONS_ENABLE,
SUPPORT_CHAT_URL,
)
from bot import bot
from database import (
add_connection,
add_referral,
@@ -265,6 +265,12 @@ async def process_start_logic(
await add_referral(message.chat.id, referrer_tg_id, session)
logger.info(f"Реферал {message.chat.id} использовал ссылку от пользователя {referrer_tg_id}")
await message.answer(f"Вы стали рефералом пользователя с ID {referrer_tg_id}")
try:
await bot.send_message(referrer_tg_id, f"🎉 Ваш реферал {message.chat.id} успешно зарегистрировался!")
logger.info(f"Уведомление отправлено пользователю {referrer_tg_id} о новом реферале {message.chat.id}")
except Exception as e:
logger.error(f"Не удалось отправить уведомление пригласившему ({referrer_tg_id}): {e}")
return await show_start_menu(message, admin, session)
except (ValueError, IndexError) as e:
+1 -1
View File
@@ -8,9 +8,9 @@ import aiofiles
import aiohttp
import asyncpg
from aiogram.types import BufferedInputFile, InlineKeyboardMarkup, InputMediaPhoto, Message
from config import DATABASE_URL
from bot import bot
from config import DATABASE_URL
from database import get_all_keys, get_servers
from logger import logger
+1 -1
View File
@@ -3,8 +3,8 @@ from datetime import datetime, timezone
from aiogram.filters.callback_data import CallbackData
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import RENEWAL_PRICES
from config import RENEWAL_PRICES
from keyboards.admin.panel_kb import build_admin_back_btn
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -3,6 +3,7 @@ from typing import Any, Dict, Set, cast
from aiogram import BaseMiddleware
from aiogram.types import CallbackQuery, Message, TelegramObject
from config import ADMIN_ID
+1
View File
@@ -4,6 +4,7 @@ from typing import Any, Optional
import asyncpg
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
from config import DATABASE_URL
+1 -1
View File
@@ -4,10 +4,10 @@ from datetime import datetime, timedelta
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, PING_TIME
from database import get_servers
from keyboards.admin.servers_kb import AdminServerEditorCallback
from logger import logger