From d935a9cd49c970873669233b2c765ecf8509d6bd Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Sat, 11 Jan 2025 17:51:24 +0300 Subject: [PATCH 1/6] Update config lint --- pyproject.toml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1ea6e345..e8e30a7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,13 +5,25 @@ target-version = "py310" [tool.ruff.lint] select = ["E", "F", "W", "I", "N", "UP", "ANN", "ASYNC", "S", "BLE", "FBT", "B", "A", "C4", "DTZ", "T10", "ISC", "ICN", "G", "PIE"] ignore = ["ANN101", "ANN102", "S101",'ANN201','ANN001','BLE001'] +exclude = [ + ".git", + "venv", + "main.py", + "handlers/payments", +] [tool.ruff.format] -quote-style = "single" -indent-style = "space" +quote-style = "double" +indent-style = "tab" [tool.darker] src = ["."] revision = "HEAD" diff = false -check = false \ No newline at end of file +check = false +exclude = [ + ".git", + "venv", + "main.py", + "handlers/payments", +] \ No newline at end of file From 1057fd6ef6a9c56b1e159e01b5514def41562f70 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Sat, 11 Jan 2025 18:03:59 +0300 Subject: [PATCH 2/6] Add balance_history --- handlers/buttons/profile.py | 2 ++ handlers/profile.py | 46 +++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/handlers/buttons/profile.py b/handlers/buttons/profile.py index dea0cdd0..3b6d80ab 100644 --- a/handlers/buttons/profile.py +++ b/handlers/buttons/profile.py @@ -1,5 +1,7 @@ ADD_SUB = "➕ Подписка" MY_SUBS = "📱 Мои подписки" +BALANCE = "💰 Баланс" +BALANCE_HISTORY = "📊 История пополнения" PAYMENT = "💳 Пополнить баланс" INVITE = "👥 Пригласить" GIFTS = "🎁 Подарить" diff --git a/handlers/profile.py b/handlers/profile.py index 243f3794..1f5f2fd4 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -10,6 +10,8 @@ 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, + BALANCE, + BALANCE_HISTORY, GIFTS, INSTRUCTIONS, INVITE, @@ -66,8 +68,8 @@ async def process_callback_view_profile( builder.row( InlineKeyboardButton( - text=PAYMENT, - callback_data="pay", + text=BALANCE, + callback_data="balance", ) ) builder.row( @@ -112,6 +114,46 @@ async def process_callback_view_profile( await conn.close() +@router.callback_query(F.data == "balance") +async def balance_handler(callback_query: types.CallbackQuery): + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay")) + builder.row(InlineKeyboardButton(text=BALANCE_HISTORY, callback_data="balance_history")) + builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) + + await callback_query.message.answer( + "💰 Управление балансом:", + reply_markup=builder.as_markup() + ) + +@router.callback_query(F.data == "balance_history") +async def balance_history_handler(callback_query: types.CallbackQuery, session: Any): + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay")) + builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) + + query = """ + SELECT amount, payment_system, status, created_at + FROM payments + WHERE tg_id = $1 + ORDER BY created_at DESC + """ + records = await session.fetch(query, callback_query.from_user.id) + + history_text = "📊 История операций с балансом:\n\n" + for record in records: + amount = record['amount'] + payment_system = record['payment_system'] + status = record['status'] + date = record['created_at'].strftime('%Y-%m-%d %H:%M:%S') + history_text += f"Сумма: {amount}\nСпособ оплаты: {payment_system}\nСтатус: {status}\nДата: {date}\n\n" + + await callback_query.message.answer( + history_text, + reply_markup=builder.as_markup() + ) + + @router.message(F.text == "/tariffs") @router.callback_query(F.data == "view_tariffs") async def view_tariffs_handler(callback_query: types.CallbackQuery): From 287d0a635b18c27f53058dd9241306480247b862 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Sat, 11 Jan 2025 18:10:43 +0300 Subject: [PATCH 3/6] Update stats --- handlers/admin/admin_panel.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/handlers/admin/admin_panel.py b/handlers/admin/admin_panel.py index 699bef1b..2b05b337 100644 --- a/handlers/admin/admin_panel.py +++ b/handlers/admin/admin_panel.py @@ -120,6 +120,10 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any): "SELECT COUNT(*) FROM connections WHERE created_at >= date_trunc('month', CURRENT_DATE)" ) + users_updated_today = await session.fetchval( + "SELECT COUNT(*) FROM users WHERE updated_at >= CURRENT_DATE" + ) + active_keys = await session.fetchval( "SELECT COUNT(*) FROM keys WHERE expiry_time > $1", int(datetime.utcnow().timestamp() * 1000), @@ -133,6 +137,8 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any): f" 📆 За неделю: {registrations_week}\n" f" 📆 За месяц: {registrations_month}\n" f" 🌐 За все время: {total_users}\n\n" + f"🌟 Активные пользователи:\n" + f" 🌟 Активных сегодня: {users_updated_today}\n\n" f"👥 Рефералы:\n" f" 🤝 Всего привлечено: {total_referrals}\n\n" f"🔑 Ключи:\n" From 52d124c8792cf3721e2966615131e0dcad1435e9 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Sat, 11 Jan 2025 18:43:14 +0300 Subject: [PATCH 4/6] Add timeout --- handlers/keys/subscriptions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 38ae40cc..a8be7c15 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -13,7 +13,8 @@ from logger import logger async def fetch_url_content(url, tg_id): try: logger.info(f"Получение URL: {url} для tg_id: {tg_id}") - async with aiohttp.ClientSession() as session: + timeout = aiohttp.ClientTimeout(total=5) + async with aiohttp.ClientSession(timeout=timeout) as session: async with session.get(url, ssl=False) as response: if response.status == 200: content = await response.text() @@ -24,6 +25,9 @@ async def fetch_url_content(url, tg_id): f"Не удалось получить {url} для tg_id: {tg_id}, статус: {response.status}" ) return [] + except asyncio.TimeoutError: + logger.error(f"Таймаут при получении {url} для tg_id: {tg_id}") + return [] except Exception as e: logger.error(f"Ошибка при получении {url} для tg_id: {tg_id}: {e}") return [] From 9a823eddf3ece60f8961263d4048c5d62d5760f8 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Sat, 11 Jan 2025 20:29:01 +0300 Subject: [PATCH 5/6] Add check_users_and_update_blocked --- handlers/notifications.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/handlers/notifications.py b/handlers/notifications.py index 355eb199..13b761a1 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -32,6 +32,28 @@ from logger import logger router = Router() +async def check_users_and_update_blocked(bot: Bot): + conn = None + try: + conn = await asyncpg.connect(DATABASE_URL) + users = await conn.fetch("SELECT tg_id FROM users") + + for user in users: + try: + await bot.send_chat_action(user['tg_id'], "typing") + except (TelegramForbiddenError,Exception): + await conn.execute( + "INSERT INTO blocked_users (tg_id) VALUES ($1) ON CONFLICT (tg_id) DO NOTHING", + user['tg_id'] + ) + logger.info(f"User {user['tg_id']} added to blocked_users") + except Exception as e: + logger.error(f"Error in check_users_and_update_blocked: {e}") + finally: + if conn: + await conn.close() + + async def notify_expiring_keys(bot: Bot): conn = None From 283016f043e35bd8f125c2e2a373378e59f002c6 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Sun, 12 Jan 2025 18:19:49 +0300 Subject: [PATCH 6/6] Delete Throttle --- bot.py | 6 ++--- middlewares/throttling.py | 53 +++++++++++---------------------------- 2 files changed, 17 insertions(+), 42 deletions(-) diff --git a/bot.py b/bot.py index 9c98ab3d..ee0ce957 100644 --- a/bot.py +++ b/bot.py @@ -12,7 +12,6 @@ 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)) @@ -30,9 +29,8 @@ 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.middleware(ThrottlingMiddleware(limit=1)) +# dp.callback_query.middleware(ThrottlingMiddleware(limit=1)) dp.message.outer_middleware(DeleteMessageMiddleware()) dp.callback_query.outer_middleware(DeleteMessageMiddleware()) diff --git a/middlewares/throttling.py b/middlewares/throttling.py index be6e64de..2761920c 100644 --- a/middlewares/throttling.py +++ b/middlewares/throttling.py @@ -1,42 +1,19 @@ -import asyncio +from collections.abc import Awaitable, Callable +from typing import Any -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 +from aiogram import BaseMiddleware +from aiogram.types import TelegramObject -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__() +class ThrottleMiddleware(BaseMiddleware): + def __init__(self, limit: int): + self.limit = limit - 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) + async def __call__( + self, + handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: dict[str, Any], + ) -> Any: + #todo + return await handler(event, data)