Merge pull request #130 from izzzzzi/main

Backlog
This commit is contained in:
Vladislav Lisitsyn
2025-01-13 02:00:32 +03:00
committed by GitHub
8 changed files with 111 additions and 48 deletions
+2 -4
View File
@@ -12,7 +12,6 @@ from middlewares.admin import AdminMiddleware
from middlewares.database import DatabaseMiddleware from middlewares.database import DatabaseMiddleware
from middlewares.delete import DeleteMessageMiddleware from middlewares.delete import DeleteMessageMiddleware
from middlewares.logging import LoggingMiddleware from middlewares.logging import LoggingMiddleware
from middlewares.throttling import ThrottlingMiddleware
from middlewares.user import UserMiddleware from middlewares.user import UserMiddleware
bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) 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.message.middleware(DatabaseMiddleware())
dp.callback_query.middleware(DatabaseMiddleware()) dp.callback_query.middleware(DatabaseMiddleware())
# Add throttling middleware # dp.message.middleware(ThrottlingMiddleware(limit=1))
dp.message.middleware(ThrottlingMiddleware(limit=1)) # 1 message per second # dp.callback_query.middleware(ThrottlingMiddleware(limit=1))
dp.callback_query.middleware(ThrottlingMiddleware(limit=1))
dp.message.outer_middleware(DeleteMessageMiddleware()) dp.message.outer_middleware(DeleteMessageMiddleware())
dp.callback_query.outer_middleware(DeleteMessageMiddleware()) dp.callback_query.outer_middleware(DeleteMessageMiddleware())
+6
View File
@@ -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)" "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( active_keys = await session.fetchval(
"SELECT COUNT(*) FROM keys WHERE expiry_time > $1", "SELECT COUNT(*) FROM keys WHERE expiry_time > $1",
int(datetime.utcnow().timestamp() * 1000), int(datetime.utcnow().timestamp() * 1000),
@@ -133,6 +137,8 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any):
f" 📆 За неделю: <b>{registrations_week}</b>\n" f" 📆 За неделю: <b>{registrations_week}</b>\n"
f" 📆 За месяц: <b>{registrations_month}</b>\n" f" 📆 За месяц: <b>{registrations_month}</b>\n"
f" 🌐 За все время: <b>{total_users}</b>\n\n" f" 🌐 За все время: <b>{total_users}</b>\n\n"
f"🌟 Активные пользователи:\n"
f" 🌟 Активных сегодня: <b>{users_updated_today}</b>\n\n"
f"👥 Рефералы:\n" f"👥 Рефералы:\n"
f" 🤝 Всего привлечено: <b>{total_referrals}</b>\n\n" f" 🤝 Всего привлечено: <b>{total_referrals}</b>\n\n"
f"🔑 Ключи:\n" f"🔑 Ключи:\n"
+2
View File
@@ -1,5 +1,7 @@
ADD_SUB = " Подписка" ADD_SUB = " Подписка"
MY_SUBS = "📱 Мои подписки" MY_SUBS = "📱 Мои подписки"
BALANCE = "💰 Баланс"
BALANCE_HISTORY = "📊 История пополнения"
PAYMENT = "💳 Пополнить баланс" PAYMENT = "💳 Пополнить баланс"
INVITE = "👥 Пригласить" INVITE = "👥 Пригласить"
GIFTS = "🎁 Подарить" GIFTS = "🎁 Подарить"
+5 -1
View File
@@ -13,7 +13,8 @@ from logger import logger
async def fetch_url_content(url, tg_id): async def fetch_url_content(url, tg_id):
try: try:
logger.info(f"Получение URL: {url} для tg_id: {tg_id}") 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: async with session.get(url, ssl=False) as response:
if response.status == 200: if response.status == 200:
content = await response.text() content = await response.text()
@@ -24,6 +25,9 @@ async def fetch_url_content(url, tg_id):
f"Не удалось получить {url} для tg_id: {tg_id}, статус: {response.status}" f"Не удалось получить {url} для tg_id: {tg_id}, статус: {response.status}"
) )
return [] return []
except asyncio.TimeoutError:
logger.error(f"Таймаут при получении {url} для tg_id: {tg_id}")
return []
except Exception as e: except Exception as e:
logger.error(f"Ошибка при получении {url} для tg_id: {tg_id}: {e}") logger.error(f"Ошибка при получении {url} для tg_id: {tg_id}: {e}")
return [] return []
+22
View File
@@ -32,6 +32,28 @@ from logger import logger
router = Router() 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): async def notify_expiring_keys(bot: Bot):
conn = None conn = None
+44 -2
View File
@@ -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 database import get_balance, get_key_count, get_referral_stats, get_trial
from handlers.buttons.profile import ( from handlers.buttons.profile import (
ADD_SUB, ADD_SUB,
BALANCE,
BALANCE_HISTORY,
GIFTS, GIFTS,
INSTRUCTIONS, INSTRUCTIONS,
INVITE, INVITE,
@@ -66,8 +68,8 @@ async def process_callback_view_profile(
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text=PAYMENT, text=BALANCE,
callback_data="pay", callback_data="balance",
) )
) )
builder.row( builder.row(
@@ -112,6 +114,46 @@ async def process_callback_view_profile(
await conn.close() 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"<b>Сумма:</b> {amount}\n<b>Способ оплаты:</b> {payment_system}\n<b>Статус:</b> {status}\n<b>Дата:</b> {date}\n\n"
await callback_query.message.answer(
history_text,
reply_markup=builder.as_markup()
)
@router.message(F.text == "/tariffs") @router.message(F.text == "/tariffs")
@router.callback_query(F.data == "view_tariffs") @router.callback_query(F.data == "view_tariffs")
async def view_tariffs_handler(callback_query: types.CallbackQuery): async def view_tariffs_handler(callback_query: types.CallbackQuery):
+15 -38
View File
@@ -1,42 +1,19 @@
import asyncio from collections.abc import Awaitable, Callable
from typing import Any
from aiogram import Dispatcher, types from aiogram import BaseMiddleware
from aiogram.dispatcher import DEFAULT_RATE_LIMIT from aiogram.types import TelegramObject
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): class ThrottleMiddleware(BaseMiddleware):
def __init__(self, limit=DEFAULT_RATE_LIMIT, key_prefix="antiflood_"): def __init__(self, limit: int):
self.rate_limit = limit self.limit = limit
self.prefix = key_prefix
super(ThrottlingMiddleware, self).__init__()
async def on_process_message(self, message: types.Message, data: dict): async def __call__(
handler = current_handler.get() self,
dispatcher = Dispatcher.get_current() handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
if handler: data: dict[str, Any],
limit = getattr(handler, "throttling_rate_limit", self.rate_limit) ) -> Any:
key = getattr(handler, "throttling_key", f"{self.prefix}_{handler.__name__}") #todo
else: return await handler(event, data)
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)
+15 -3
View File
@@ -5,13 +5,25 @@ target-version = "py310"
[tool.ruff.lint] [tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "ANN", "ASYNC", "S", "BLE", "FBT", "B", "A", "C4", "DTZ", "T10", "ISC", "ICN", "G", "PIE"] 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'] ignore = ["ANN101", "ANN102", "S101",'ANN201','ANN001','BLE001']
exclude = [
".git",
"venv",
"main.py",
"handlers/payments",
]
[tool.ruff.format] [tool.ruff.format]
quote-style = "single" quote-style = "double"
indent-style = "space" indent-style = "tab"
[tool.darker] [tool.darker]
src = ["."] src = ["."]
revision = "HEAD" revision = "HEAD"
diff = false diff = false
check = false check = false
exclude = [
".git",
"venv",
"main.py",
"handlers/payments",
]