@@ -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())
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
ADD_SUB = "➕ Подписка"
|
ADD_SUB = "➕ Подписка"
|
||||||
MY_SUBS = "📱 Мои подписки"
|
MY_SUBS = "📱 Мои подписки"
|
||||||
|
BALANCE = "💰 Баланс"
|
||||||
|
BALANCE_HISTORY = "📊 История пополнения"
|
||||||
PAYMENT = "💳 Пополнить баланс"
|
PAYMENT = "💳 Пополнить баланс"
|
||||||
INVITE = "👥 Пригласить"
|
INVITE = "👥 Пригласить"
|
||||||
GIFTS = "🎁 Подарить"
|
GIFTS = "🎁 Подарить"
|
||||||
|
|||||||
@@ -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 []
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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",
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user