Merge pull request #169 from izzzzzi/main

Исправление сравнения tg_id в обработчике подписок.
This commit is contained in:
Vladislav Lisitsyn
2025-03-25 20:21:01 +03:00
committed by GitHub
19 changed files with 106 additions and 116 deletions
+8 -1
View File
@@ -72,7 +72,14 @@ async def add_client(xui: py3xui.AsyncApi, config: ClientConfig) -> dict[str, An
async def extend_client_key(
xui: py3xui.AsyncApi, inbound_id: int, email: str, new_expiry_time: int, client_id: str, total_gb: int, sub_id: str, tg_id: int
xui: py3xui.AsyncApi,
inbound_id: int,
email: str,
new_expiry_time: int,
client_id: str,
total_gb: int,
sub_id: str,
tg_id: int,
) -> bool | None:
"""
Обновляет срок действия ключа клиента.
+9 -11
View File
@@ -12,7 +12,6 @@ from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import TOTAL_GB
from utils.csv_export import export_referrals_csv
from database import (
delete_key,
@@ -27,7 +26,7 @@ from database import (
from filters.admin import IsAdminFilter
from handlers.keys.key_utils import delete_key_from_cluster, get_user_traffic, renew_key_in_cluster, update_subscription
from handlers.utils import sanitize_key_name
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb, build_admin_back_btn
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_btn, build_admin_back_kb
from keyboards.admin.users_kb import (
AdminUserEditorCallback,
AdminUserKeyEditorCallback,
@@ -43,6 +42,7 @@ from keyboards.admin.users_kb import (
build_users_key_show_kb,
)
from logger import logger
from utils.csv_export import export_referrals_csv
MOSCOW_TZ = pytz.timezone("Europe/Moscow")
@@ -733,10 +733,11 @@ async def confirm_restore_trials(callback_query: types.CallbackQuery):
await callback_query.message.edit_text(
text="⚠ Вы уверены, что хотите восстановить пробники для пользователей? \n\n"
"Только для тех, у кого нет активной подписки!",
reply_markup=builder.as_markup()
"Только для тех, у кого нет активной подписки!",
reply_markup=builder.as_markup(),
)
@router.callback_query(AdminPanelCallback.filter(F.action == "confirm_restore_trials"), IsAdminFilter())
async def restore_trials(callback_query: types.CallbackQuery, session: Any):
"""
@@ -759,15 +760,13 @@ async def restore_trials(callback_query: types.CallbackQuery, session: Any):
await callback_query.message.edit_text(
text="✅ Пробники успешно восстановлены для пользователей, у которых нет активных подписок.",
reply_markup=builder.as_markup()
reply_markup=builder.as_markup(),
)
@router.callback_query(AdminUserEditorCallback.filter(F.action == "users_export_referrals"), IsAdminFilter())
async def handle_users_export_referrals(
callback_query: types.CallbackQuery,
callback_data: AdminUserEditorCallback,
session: Any
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, session: Any
):
"""
Обработчик: получает tg_id реферера из callback_data,
@@ -783,6 +782,5 @@ async def handle_users_export_referrals(
return
await callback_query.message.answer_document(
document=csv_file,
caption=f"Список рефералов для пользователя {referrer_tg_id}."
)
document=csv_file, caption=f"Список рефералов для пользователя {referrer_tg_id}."
)
+1 -1
View File
@@ -4,4 +4,4 @@ BACK = "⬅️ Назад"
CUSTOM_SUM = "💰 Ввести свою сумму"
CUSTOM_SUM_ANSWER = "Пожалуйста, введите сумму пополнения."
PROFILE = "👤 Личный кабинет"
DEFAULT_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей. Перейдите по ссылке для оплаты:"
DEFAULT_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей. Перейдите по ссылке для оплаты:"
+1
View File
@@ -8,6 +8,7 @@ from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import CAPTCHA_EMOJIS
from handlers.texts import CAPTCHA_PROMPT_MSG
from logger import logger
+3 -2
View File
@@ -14,11 +14,12 @@ from database import (
update_coupon_usage_count,
)
from handlers.texts import (
COUPON_ACTIVATED_SUCCESS_MSG,
COUPON_ALREADY_USED_MSG,
COUPON_INPUT_PROMPT,
COUPON_NOT_FOUND_MSG,
COUPON_ALREADY_USED_MSG,
COUPON_ACTIVATED_SUCCESS_MSG,
)
from .utils import edit_or_send_message
+3 -3
View File
@@ -54,12 +54,12 @@ from handlers.keys.key_utils import create_client_on_server, create_key_on_clust
from handlers.payments.robokassa_pay import handle_custom_amount_input
from handlers.payments.yookassa_pay import process_custom_amount_input
from handlers.texts import (
DISCOUNTS,
key_message_success,
CREATING_CONNECTION_MSG,
SELECT_TARIFF_PLAN_MSG,
DISCOUNTS,
INSUFFICIENT_FUNDS_MSG,
SELECT_COUNTRY_MSG,
SELECT_TARIFF_PLAN_MSG,
key_message_success,
)
from handlers.utils import edit_or_send_message, generate_random_email, get_least_loaded_cluster
from logger import logger
+3 -2
View File
@@ -2,10 +2,11 @@ import asyncio
from typing import Any
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, DATABASE_URL
from py3xui import AsyncApi
import asyncpg
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB
from py3xui import AsyncApi
from client import ClientConfig, add_client, delete_client, extend_client_key, get_client_traffic, toggle_client
from database import get_servers, store_key
from handlers.utils import get_least_loaded_cluster
+30 -55
View File
@@ -23,10 +23,10 @@ from config import (
ENABLE_UPDATE_SUBSCRIPTION_BUTTON,
PUBLIC_LINK,
RENEWAL_PLANS,
TOGGLE_CLIENT,
TOTAL_GB,
USE_COUNTRY_SELECTION,
USE_NEW_PAYMENT_FLOW,
TOGGLE_CLIENT
)
from bot import bot
@@ -53,27 +53,27 @@ from handlers.buttons.add_subscribe import (
from handlers.keys.key_utils import (
delete_key_from_cluster,
renew_key_in_cluster,
update_subscription,
toggle_client_on_cluster,
update_subscription,
)
from handlers.payments.robokassa_pay import handle_custom_amount_input
from handlers.payments.yookassa_pay import process_custom_amount_input
from handlers.texts import (
DELETE_KEY_CONFIRM_MSG,
DISCOUNTS,
FREEZE_SUBSCRIPTION_CONFIRM_MSG,
FROZEN_SUBSCRIPTION_MSG,
INSUFFICIENT_FUNDS_RENEWAL_MSG,
KEY_DELETED_MSG_SIMPLE,
KEY_NOT_FOUND_MSG,
NO_SUBSCRIPTIONS_MSG,
PLAN_SELECTION_MSG,
SUBSCRIPTION_DESCRIPTION,
SUCCESS_RENEWAL_MSG,
key_message,
NO_SUBSCRIPTIONS_MSG,
FROZEN_SUBSCRIPTION_MSG,
UNFREEZE_SUBSCRIPTION_CONFIRM_MSG,
SUBSCRIPTION_UNFROZEN_MSG,
FREEZE_SUBSCRIPTION_CONFIRM_MSG,
SUBSCRIPTION_FROZEN_MSG,
DELETE_KEY_CONFIRM_MSG,
KEY_DELETED_MSG_SIMPLE,
INSUFFICIENT_FUNDS_RENEWAL_MSG,
SUBSCRIPTION_UNFROZEN_MSG,
SUCCESS_RENEWAL_MSG,
UNFREEZE_SUBSCRIPTION_CONFIRM_MSG,
key_message,
)
from handlers.utils import edit_or_send_message, handle_error
from logger import logger
@@ -177,7 +177,7 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
reply_markup=keyboard,
media_path=image_path,
)
else:
key = record["key"]
expiry_time = record["expiry_time"]
@@ -188,17 +188,13 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
time_left = expiry_date - current_date
if time_left.total_seconds() <= 0:
days_left_message = (
"<b>🕒 Статус подписки:</b>\n🔴 Истекла\nОсталось часов: 0\nОсталось минут: 0"
)
days_left_message = "<b>🕒 Статус подписки:</b>\n🔴 Истекла\nОсталось часов: 0\nОсталось минут: 0"
else:
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>{days}</b> дней, <b>{hours}</b> часов, <b>{minutes}</b> минут"
)
days_left_message = f"Осталось: <b>{days}</b> дней, <b>{hours}</b> часов, <b>{minutes}</b> минут"
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
response_message = key_message(
@@ -248,18 +244,12 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
)
else:
builder.row(
InlineKeyboardButton(
text="⏳ Продлить подписку",
callback_data=f"renew_key|{key_name}"
)
InlineKeyboardButton(text="⏳ Продлить подписку", callback_data=f"renew_key|{key_name}")
)
if USE_COUNTRY_SELECTION:
builder.row(
InlineKeyboardButton(
text="🌍 Сменить локацию",
callback_data=f"change_location|{key_name}"
)
InlineKeyboardButton(text="🌍 Сменить локацию", callback_data=f"change_location|{key_name}")
)
if TOGGLE_CLIENT:
@@ -355,7 +345,7 @@ async def process_callback_unfreeze_subscription_confirm(callback_query: Callbac
""",
new_expiry_time,
record["tg_id"],
client_id
client_id,
)
await renew_key_in_cluster(
@@ -363,13 +353,11 @@ async def process_callback_unfreeze_subscription_confirm(callback_query: Callbac
email=email,
client_id=client_id,
new_expiry_time=new_expiry_time,
total_gb=TOTAL_GB
total_gb=TOTAL_GB,
)
text_ok = SUBSCRIPTION_UNFROZEN_MSG
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}")
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
await edit_or_send_message(
target_message=callback_query.message,
text=text_ok,
@@ -377,13 +365,10 @@ async def process_callback_unfreeze_subscription_confirm(callback_query: Callbac
)
else:
text_error = (
"Произошла ошибка при включении подписки.\n"
f"Детали: {result.get('error') or result.get('results')}"
f"Произошла ошибка при включении подписки.\nДетали: {result.get('error') or result.get('results')}"
)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}")
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
await edit_or_send_message(
target_message=callback_query.message,
text=text_error,
@@ -399,7 +384,6 @@ async def process_callback_freeze_subscription(callback_query: CallbackQuery, se
"""
Показывает пользователю диалог подтверждения заморозки (отключения) подписки.
"""
tg_id = callback_query.message.chat.id
key_name = callback_query.data.split("|")[1]
confirm_text = FREEZE_SUBSCRIPTION_CONFIRM_MSG
@@ -447,9 +431,9 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ
now_ms = int(time.time() * 1000)
time_left = record["expiry_time"] - now_ms
if time_left < 0:
time_left = 0
time_left = 0
update_result = await session.execute(
await session.execute(
"""
UPDATE keys
SET expiry_time = $1,
@@ -459,14 +443,12 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ
""",
time_left,
record["tg_id"],
client_id
client_id,
)
text_ok = SUBSCRIPTION_FROZEN_MSG
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}")
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
await edit_or_send_message(
target_message=callback_query.message,
text=text_ok,
@@ -474,13 +456,10 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ
)
else:
text_error = (
"Произошла ошибка при заморозке подписки.\n"
f"Детали: {result.get('error') or result.get('results')}"
f"Произошла ошибка при заморозке подписки.\nДетали: {result.get('error') or result.get('results')}"
)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}")
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
await edit_or_send_message(
target_message=callback_query.message,
text=text_error,
@@ -568,14 +547,10 @@ async def process_callback_delete_key(callback_query: CallbackQuery):
if callback_query.message.caption:
await callback_query.message.edit_caption(
caption=DELETE_KEY_CONFIRM_MSG,
reply_markup=confirmation_keyboard
caption=DELETE_KEY_CONFIRM_MSG, reply_markup=confirmation_keyboard
)
else:
await callback_query.message.edit_text(
text=DELETE_KEY_CONFIRM_MSG,
reply_markup=confirmation_keyboard
)
await callback_query.message.edit_text(text=DELETE_KEY_CONFIRM_MSG, reply_markup=confirmation_keyboard)
except Exception as e:
logger.error(f"Ошибка при обработке запроса на удаление ключа {client_id}: {e}")
+1 -1
View File
@@ -328,7 +328,7 @@ 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"]
if not old_subscription and str(tg_id) != str(stored_tg_id):
if not old_subscription and int(tg_id) != int(stored_tg_id):
logger.warning(f"Неверный tg_id для клиента с email {email}.")
return web.Response(text="❌ Неверные данные. Получите свой ключ в боте.", status=403)
@@ -1,5 +1,7 @@
import asyncio
from datetime import datetime, timedelta
import asyncpg
import pytz
@@ -9,14 +11,15 @@ from config import (
NOTIFICATION_TIME,
NOTIFY_DELETE_DELAY,
NOTIFY_DELETE_KEY,
NOTIFY_INACTIVE_TRAFFIC,
NOTIFY_MAXPRICE,
NOTIFY_RENEW,
NOTIFY_RENEW_EXPIRED,
RENEWAL_PRICES,
TOTAL_GB,
TRIAL_TIME_DISABLE,
NOTIFY_INACTIVE_TRAFFIC
)
from database import (
add_notification,
check_notification_time,
@@ -29,25 +32,28 @@ from database import (
)
from handlers.keys.key_utils import delete_key_from_cluster, renew_key_in_cluster
from handlers.texts import (
KEY_EXPIRY_10H,
KEY_EXPIRY_24H,
KEY_RENEWED,
KEY_RENEWED_TEMP_MSG,
KEY_DELETED_MSG,
KEY_EXPIRED_DELAY_HOURS_MINUTES_MSG,
KEY_EXPIRED_DELAY_HOURS_MSG,
KEY_EXPIRED_DELAY_MINUTES_MSG,
KEY_EXPIRED_NO_DELAY_MSG,
KEY_EXPIRY_10H,
KEY_EXPIRY_24H,
KEY_RENEWED,
KEY_RENEWED_TEMP_MSG,
)
from keyboards.notifications.notify_kb import build_notification_expired_kb, build_notification_kb
from logger import logger
from .notify_utils import send_notification
from .special_notifications import notify_inactive_trial_users, notify_users_no_traffic
router = Router()
moscow_tz = pytz.timezone("Europe/Moscow")
async def periodic_notifications(bot: Bot):
"""
Обработчик, который:
@@ -98,6 +104,7 @@ async def periodic_notifications(bot: Bot):
await asyncio.sleep(NOTIFICATION_TIME)
async def notify_24h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int, threshold_time_24h: int, keys: list):
logger.info("Начало проверки подписок, истекающих через 24 часа.")
@@ -149,6 +156,7 @@ async def notify_24h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int,
logger.info("✅ Обработка всех уведомлений за 24 часа завершена.")
await asyncio.sleep(1)
async def notify_10h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int, threshold_time_10h: int, keys: list):
"""
Отправляет уведомления пользователям о том, что их подписка истекает через 10 часов.
@@ -208,6 +216,7 @@ async def notify_10h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int,
logger.info("✅ Обработка всех уведомлений за 10 часов завершена.")
await asyncio.sleep(1)
async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: int, keys: list):
"""
Обрабатывает истекшие ключи, проверяя продление или удаление.
@@ -319,6 +328,7 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
logger.info("✅ Обработка истекших ключей завершена.")
await asyncio.sleep(1)
async def process_auto_renew_or_notify(
bot, conn, key: dict, notification_id: str, renewal_period_months: int, standard_photo: str, standard_caption: str
):
@@ -15,8 +15,8 @@ from database import (
check_notification_time,
create_blocked_user,
)
from handlers.texts import TRIAL_INACTIVE_FIRST_MSG, TRIAL_INACTIVE_BONUS_MSG, ZERO_TRAFFIC_MSG
from handlers.keys.key_utils import get_user_traffic
from handlers.texts import TRIAL_INACTIVE_BONUS_MSG, TRIAL_INACTIVE_FIRST_MSG, ZERO_TRAFFIC_MSG
from logger import logger
@@ -84,16 +84,11 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
if trial_extended:
total_days = NOTIFY_EXTRA_DAYS + TRIAL_TIME
message = TRIAL_INACTIVE_BONUS_MSG.format(
display_name=display_name,
NOTIFY_EXTRA_DAYS=NOTIFY_EXTRA_DAYS,
total_days=total_days
display_name=display_name, NOTIFY_EXTRA_DAYS=NOTIFY_EXTRA_DAYS, total_days=total_days
)
await conn.execute("UPDATE connections SET trial = -1 WHERE tg_id = $1", tg_id)
else:
message = TRIAL_INACTIVE_FIRST_MSG.format(
display_name=display_name,
TRIAL_TIME=TRIAL_TIME
)
message = TRIAL_INACTIVE_FIRST_MSG.format(display_name=display_name, TRIAL_TIME=TRIAL_TIME)
try:
await bot.send_message(tg_id, message, reply_markup=keyboard)
+2
View File
@@ -9,7 +9,9 @@ from config import (
YOOKASSA_ENABLE,
YOOMONEY_ENABLE,
)
from handlers.texts import PAYMENT_METHODS_MSG
from .utils import edit_or_send_message
+3 -3
View File
@@ -41,12 +41,12 @@ from handlers.buttons.profile import (
PAYMENT,
)
from handlers.texts import (
BALANCE_HISTORY_HEADER,
BALANCE_MANAGEMENT_TEXT,
INVITE_TEXT_NON_INLINE,
get_referral_link,
invite_message_send,
profile_message_send,
BALANCE_MANAGEMENT_TEXT,
BALANCE_HISTORY_HEADER,
INVITE_TEXT_NON_INLINE
)
from keyboards.admin.panel_kb import AdminPanelCallback
from logger import logger
+10 -10
View File
@@ -1,4 +1,5 @@
import os
from typing import Any
from aiogram import F, Router
@@ -32,16 +33,16 @@ from database import (
from handlers.captcha import generate_captcha
from handlers.keys.key_management import create_key
from handlers.texts import (
COUPON_SUCCESS_MSG,
GIFT_ALREADY_USED_OR_NOT_EXISTS_MSG,
NEW_REFERRAL_NOTIFICATION,
NOT_SUBSCRIBED_YET_MSG,
REFERRAL_SUCCESS_MSG,
SUBSCRIPTION_CHECK_ERROR_MSG,
SUBSCRIPTION_CONFIRMED_MSG,
SUBSCRIPTION_REQUIRED_MSG,
WELCOME_TEXT,
get_about_vpn,
SUBSCRIPTION_REQUIRED_MSG,
NOT_SUBSCRIBED_YET_MSG,
SUBSCRIPTION_CONFIRMED_MSG,
SUBSCRIPTION_CHECK_ERROR_MSG,
GIFT_ALREADY_USED_OR_NOT_EXISTS_MSG,
REFERRAL_SUCCESS_MSG,
NEW_REFERRAL_NOTIFICATION,
COUPON_SUCCESS_MSG,
)
from keyboards.admin.panel_kb import AdminPanelCallback
from logger import logger
@@ -274,8 +275,7 @@ async def process_start_logic(
await message.answer(REFERRAL_SUCCESS_MSG.format(referrer_tg_id=referrer_tg_id))
try:
await bot.send_message(
referrer_tg_id,
NEW_REFERRAL_NOTIFICATION.format(referred_id=message.chat.id)
referrer_tg_id, NEW_REFERRAL_NOTIFICATION.format(referred_id=message.chat.id)
)
logger.info(
f"Уведомление отправлено пользователю {referrer_tg_id} о новом реферале {message.chat.id}"
+1 -1
View File
@@ -66,7 +66,7 @@ async def get_least_loaded_cluster() -> str:
"""
servers = await get_servers()
server_to_cluster = {}
cluster_loads = {cluster: 0 for cluster in servers.keys()}
cluster_loads = dict.fromkeys(servers.keys(), 0)
for cluster_name, cluster_servers in servers.items():
for server in cluster_servers:
server_to_cluster[server["server_name"]] = cluster_name
+1 -1
View File
@@ -1,5 +1,5 @@
from collections.abc import Awaitable, Callable
from typing import Any,TypedDict
from typing import Any, TypedDict
from aiogram import BaseMiddleware
from aiogram.types import CallbackQuery, InlineQuery, Message, TelegramObject, User
+1 -1
View File
@@ -28,4 +28,4 @@ class ThrottlingMiddleware(BaseMiddleware):
else:
self.cache[user_id] = current_count + 1
return await handler(event, data)
return await handler(event, data)
+6 -7
View File
@@ -60,10 +60,7 @@ async def notify_admin(server_name: str, status: str, down_duration: timedelta =
)
else:
downtime = str(down_duration).split(".")[0]
message = (
f"✅ <b>Сервер '{server_name}' снова в сети!</b>\n\n"
f"⏳ Время простоя: {downtime}."
)
message = f"✅ <b>Сервер '{server_name}' снова в сети!</b>\n\n⏳ Время простоя: {downtime}."
for admin_id in ADMIN_ID:
logger.info(f"📨 Отправляем уведомление '{status}' администратору {admin_id} о сервере {server_name}")
@@ -119,11 +116,13 @@ async def check_servers():
if last_ping_time is None:
last_ping_times[server_name] = current_time
last_down_times[server_name] = current_time
last_down_times[server_name] = current_time
if last_ping_time and (current_time - last_ping_time > timedelta(seconds=PING_TIME * 3)):
if server_name not in notified_servers:
logger.warning(f"🚨 Уведомление: сервер {server_name} не отвечает более {PING_TIME * 3} секунд!")
logger.warning(
f"🚨 Уведомление: сервер {server_name} не отвечает более {PING_TIME * 3} секунд!"
)
await notify_admin(server_name, "down")
notified_servers.add(server_name)
last_down_times[server_name] = current_time
@@ -145,4 +144,4 @@ async def check_servers():
def extract_host(api_url: str) -> str:
"""Извлекает хост из `api_url`."""
match = re.match(r"(https?://)?([^:/]+)", api_url)
return match.group(2) if match else api_url
return match.group(2) if match else api_url
+5 -4
View File
@@ -1,6 +1,7 @@
import csv
from io import StringIO
from typing import Any
import csv
from aiogram.types import BufferedInputFile
@@ -115,14 +116,14 @@ async def export_referrals_csv(referrer_tg_id: int, session: Any) -> BufferedInp
WHERE r.referrer_tg_id = $1
ORDER BY r.referred_tg_id
""",
referrer_tg_id
referrer_tg_id,
)
if not rows:
return None
output = StringIO()
writer = csv.writer(output, delimiter=';')
writer = csv.writer(output, delimiter=";")
writer.writerow(["Приглашённый (tg_id)", "Имя"])
for row in rows:
@@ -136,4 +137,4 @@ async def export_referrals_csv(referrer_tg_id: int, session: Any) -> BufferedInp
csv_data = output.getvalue().encode("utf-8")
filename = f"referrals_{referrer_tg_id}.csv"
return BufferedInputFile(file=csv_data, filename=filename)
return BufferedInputFile(file=csv_data, filename=filename)