Files
RemnaWaveBOT/handlers/__init__.py
2026-04-05 12:18:33 +03:00

1794 lines
68 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from aiogram import types, Router, F
from aiogram.filters import Command
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from core import db
from core.remnawave import remnawave
from keyboards import Keyboards
from keyboards.payment import PaymentKeyboards
from config import config
from locales import localization
# Роутер для основного меню
main_router = Router()
# Роутер для админ-панели
admin_router = Router()
# Временное хранилище для тарифов пользователей (user_id -> tariff_info)
payment_temp = {}
# Временное хранилище для создания тикетов (user_id -> True)
ticket_creation = {}
# Временное хранилище для дополнения тикетов (user_id -> ticket_id)
ticket_append_mode = {}
def is_admin(user_id: int) -> bool:
"""Проверка, является ли пользователь администратором"""
return user_id in config.admin_ids
async def get_user_locale(user_id: int) -> str:
"""Получение языка пользователя из БД"""
return await db.get_user_language(user_id)
@main_router.message(Command("start"))
async def handle_start(message: types.Message):
"""Обработчик команды /start"""
user_id = message.from_user.id
username = message.from_user.username or message.from_user.first_name
locale = await get_user_locale(user_id)
# Создаём пользователя в БД если нет
await db.create_user(user_id, username)
# Проверяем статус подписки
is_active = await db.get_user_subscription(user_id)
subscription_status = (
localization.get("subscription_active", locale) if is_active
else localization.get("subscription_inactive", locale)
)
# Формируем сообщение
text = (
f"{localization.get('welcome', locale, username=username)}\n\n"
f"{localization.get('subscription_status', locale, status=subscription_status)}\n\n"
f"{localization.get('select_action', locale)}"
)
# Проверяем, является ли пользователь администратором
if is_admin(user_id):
# Добавляем кнопку админ-панели
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="💳 Купить подписку",
callback_data="buy_subscription"
)
],
[
InlineKeyboardButton(
text="📞 Техподдержка",
callback_data="support"
),
InlineKeyboardButton(
text="📜 Правила сервиса",
callback_data="rules"
)
],
[
InlineKeyboardButton(
text="🌐 Язык",
callback_data="language"
),
InlineKeyboardButton(
text="⚙️ Админ",
callback_data="admin_panel"
)
]
]
)
else:
keyboard = Keyboards.get_main_keyboard(locale)
await message.answer(
text,
reply_markup=keyboard
)
@main_router.callback_query(lambda c: c.data == "buy_subscription")
async def handle_callback_buy_subscription(callback: types.CallbackQuery):
"""Обработчик кнопки 'Купить подписку'"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
subscription_title = localization.get("subscription.standard_title", locale)
subscription_desc = localization.get("subscription.standard_description", locale)
await callback.message.edit_text(
f"{subscription_title}\n\n{subscription_desc}",
reply_markup=Keyboards.get_buy_subscription_keyboard(locale)
)
await callback.answer()
@main_router.callback_query(lambda c: c.data == "back_to_buy")
async def handle_callback_back_to_buy(callback: types.CallbackQuery):
"""Возврат к выбору тарифа"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
subscription_title = localization.get("subscription.standard_title", locale)
subscription_desc = localization.get("subscription.standard_description", locale)
await callback.message.edit_text(
f"{subscription_title}\n\n{subscription_desc}",
reply_markup=Keyboards.get_buy_subscription_keyboard(locale)
)
await callback.answer()
@main_router.callback_query(lambda c: c.data == "support")
async def handle_callback_support(callback: types.CallbackQuery):
"""Обработчик кнопки 'Техподдержка'"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
support_title = localization.get("messages.support_title", locale)
support_desc = localization.get("messages.support_description", locale)
await callback.message.edit_text(
f"{support_title}\n\n{support_desc}",
reply_markup=Keyboards.get_support_keyboard(locale)
)
await callback.answer()
@main_router.callback_query(lambda c: c.data == "support_create_ticket")
async def handle_support_create_ticket(callback: types.CallbackQuery):
"""Обработчик кнопки 'Создать тикет'"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
# Устанавливаем флаг, что пользователь создаёт тикет
ticket_creation[user_id] = True
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="❌ Отмена",
callback_data="cancel_ticket"
),
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_support"
)
]
]
)
await callback.message.edit_text(
"📝 Создание тикета\n\n"
"Опишите вашу проблему или вопрос одним сообщением.\n\n"
"Пример: 'Не работает VPN, ошибка подключения'\n\n"
"Отправьте сообщение ниже или нажмите 'Отмена':",
reply_markup=keyboard
)
await callback.answer()
@main_router.callback_query(lambda c: c.data == "cancel_ticket")
async def handle_cancel_ticket(callback: types.CallbackQuery):
"""Отмена создания тикета"""
user_id = callback.from_user.id
# Удаляем флаг создания тикета
if user_id in ticket_creation:
del ticket_creation[user_id]
locale = await get_user_locale(user_id)
await callback.message.edit_text(
"❌ Создание тикета отменено.\n\n"
"Если у вас возникнут вопросы, вы всегда можете создать тикет позже.",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_support"
)
]
]
)
)
await callback.answer()
@main_router.callback_query(lambda c: c.data == "support_my_tickets")
async def handle_support_my_tickets(callback: types.CallbackQuery):
"""Обработчик кнопки 'Мои тикеты'"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
# Получаем тикеты пользователя из БД
tickets = await db.get_user_tickets(user_id)
if not tickets:
await callback.message.edit_text(
"📋 Мои тикеты\n\n"
"У вас пока нет активных тикетов.\n\n"
"Создайте новый тикет, если у вас есть вопрос или проблема.",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_support"
)
]
]
)
)
else:
# Формируем список тикетов
tickets_text = "📋 Мои тикеты\n\n"
for ticket in tickets[:5]: # Показываем последние 5 тикетов
status_emoji = "🟢" if ticket['status'] == 'open' else "🔵" if ticket['status'] == 'answered' else ""
created_at = ticket['created_at'].strftime("%d.%m.%Y %H:%M") if ticket['created_at'] else 'Неизвестно'
tickets_text += (
f"{status_emoji} Тикет #{ticket['id']} - {ticket['status']}\n"
f"Дата: {created_at}\n"
f"Вопрос: {ticket['message'][:50]}...\n\n"
)
if len(tickets) > 5:
tickets_text += f"... и ещё {len(tickets) - 5} тикетов\n"
# Клавиатура с кнопками тикетов
ticket_buttons = []
for ticket in tickets[:5]:
status_emoji = "🟢" if ticket['status'] == 'open' else "🔵" if ticket['status'] == 'answered' else ""
ticket_buttons.append([
InlineKeyboardButton(
text=f"{status_emoji} #{ticket['id']} - {ticket['status']}",
callback_data=f"ticket_view_{ticket['id']}"
)
])
ticket_buttons.append([
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_support"
)
])
await callback.message.edit_text(
tickets_text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=ticket_buttons)
)
await callback.answer()
@main_router.callback_query(lambda c: c.data.startswith("ticket_view_"))
async def handle_ticket_view(callback: types.CallbackQuery):
"""Просмотр тикета"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
ticket_id = int(callback.data.replace("ticket_view_", ""))
await show_ticket_view(callback, user_id, locale, ticket_id)
async def show_ticket_view(callback, user_id, locale, ticket_id):
"""Отображение просмотра тикета"""
ticket = await db.get_ticket(ticket_id)
if not ticket or ticket['user_id'] != user_id:
await callback.answer("Тикет не найден", show_alert=True)
return
# Статус тикета
status_text = {
'open': '🟢 Открыт',
'answered': '🔵 Дан ответ',
'closed': '⚫ Закрыт'
}.get(ticket['status'], ticket['status'])
# Формируем сообщение
ticket_text = f"📋 Тикет #{ticket['id']}\n\n"
ticket_text += f"Статус: {status_text}\n"
ticket_text += f"Дата создания: {ticket['created_at'].strftime('%d.%m.%Y %H:%M') if ticket['created_at'] else 'Неизвестно'}\n\n"
ticket_text += f"❓ Ваш вопрос:\n{ticket['message']}\n\n"
if ticket['admin_response']:
ticket_text += f"💬 Ответ поддержки:\n{ticket['admin_response']}\n\n"
# Информация о закрытии
if ticket['status'] == 'closed':
closed_by = ticket.get('closed_by', 'Неизвестно')
closed_at = ticket.get('closed_at')
closed_info = f"🔒 Закрыл: {closed_by}"
if closed_at:
closed_info += f"\n🕒 Дата: {closed_at.strftime('%d.%m.%Y %H:%M') if hasattr(closed_at, 'strftime') else closed_at}"
ticket_text += f"{closed_info}\n\n"
if not ticket['admin_response'] and ticket['status'] != 'closed':
ticket_text += "⏳ Ожидайте ответа поддержки...\n\n"
# Кнопки
keyboard_buttons = [
[
InlineKeyboardButton(
text="✏️ Дополнить тикет",
callback_data=f"ticket_append_{ticket['id']}"
)
],
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="support_my_tickets"
)
]
]
# Если тикет открыт, можно закрыть
if ticket['status'] == 'open':
keyboard_buttons.insert(0, [
InlineKeyboardButton(
text="✅ Закрыть тикет",
callback_data=f"ticket_close_{ticket['id']}"
)
])
await callback.message.edit_text(
ticket_text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
)
await callback.answer()
@main_router.callback_query(lambda c: c.data.startswith("ticket_close_"))
async def handle_ticket_close(callback: types.CallbackQuery):
"""Закрытие тикета"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
username = callback.from_user.username or callback.from_user.first_name
ticket_id = int(callback.data.replace("ticket_close_", ""))
ticket = await db.get_ticket(ticket_id)
if not ticket or ticket['user_id'] != user_id:
await callback.answer("Тикет не найден", show_alert=True)
return
# Закрываем тикет с указанием кто закрыл (передаём позиционно, не именованно)
await db.close_ticket(ticket_id, f"@{username}" if callback.from_user.username else f"{username}")
await callback.answer("Тикет закрыт")
# Обновляем сообщение с информацией кто закрыл (передаём ticket_id)
try:
await handle_ticket_view_by_id(callback, ticket_id)
except Exception as e:
if "message is not modified" not in str(e):
print(f"Ошибка обновления сообщения: {e}")
async def handle_ticket_view_by_id(callback, ticket_id):
"""Просмотр тикета по ID (для вызова из других функций)"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
await show_ticket_view(callback, user_id, locale, ticket_id)
@main_router.callback_query(lambda c: c.data.startswith("ticket_append_"))
async def handle_ticket_append(callback: types.CallbackQuery):
"""Режим дополнения тикета"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
ticket_id = int(callback.data.replace("ticket_append_", ""))
ticket = await db.get_ticket(ticket_id)
if not ticket or ticket['user_id'] != user_id:
await callback.answer("Тикет не найден", show_alert=True)
return
# Устанавливаем режим дополнения
ticket_append_mode[user_id] = ticket_id
await callback.message.edit_text(
f"✏️ Дополнение тикета #{ticket_id}\n\n"
f"Напишите дополнение к вашему тикету.\n\n"
f"Текущий статус: {ticket['status']}\n\n"
f"Отправьте сообщение ниже или нажмите 'Отмена':",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="❌ Отмена",
callback_data=f"ticket_append_cancel_{ticket_id}"
)
]
]
)
)
await callback.answer()
@main_router.callback_query(lambda c: c.data.startswith("ticket_append_cancel_"))
async def handle_ticket_append_cancel(callback: types.CallbackQuery):
"""Отмена дополнения тикета"""
user_id = callback.from_user.id
# Удаляем из режима дополнения
if user_id in ticket_append_mode:
del ticket_append_mode[user_id]
# Возвращаемся к просмотру тикета
ticket_id = int(callback.data.replace("ticket_append_cancel_", ""))
# Обновляем сообщение
await handle_ticket_view(callback)
await callback.answer("Отменено")
@main_router.callback_query(lambda c: c.data == "back_to_support")
async def handle_callback_back_to_support(callback: types.CallbackQuery):
"""Возврат в меню поддержки"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
support_title = localization.get("messages.support_title", locale)
support_desc = localization.get("messages.support_description", locale)
await callback.message.edit_text(
f"{support_title}\n\n{support_desc}",
reply_markup=Keyboards.get_support_keyboard(locale)
)
await callback.answer()
@main_router.callback_query(lambda c: c.data == "rules")
async def handle_callback_rules(callback: types.CallbackQuery):
"""Обработчик кнопки 'Правила сервиса'"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
rules_title = localization.get("messages.rules_title", locale)
rules_text = localization.get("messages.rules_text", locale)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_main"
)
]
]
)
await callback.message.edit_text(
f"{rules_title}\n\n{rules_text}",
reply_markup=keyboard
)
await callback.answer()
@main_router.callback_query(lambda c: c.data == "language")
async def handle_callback_language(callback: types.CallbackQuery):
"""Обработчик кнопки 'Язык'"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
await callback.message.edit_text(
localization.get("messages.select_language", locale),
reply_markup=Keyboards.get_language_keyboard(locale)
)
await callback.answer()
@main_router.callback_query(lambda c: c.data == "back_to_main")
async def handle_callback_back_to_main(callback: types.CallbackQuery):
"""Возврат в главное меню"""
user_id = callback.from_user.id
username = callback.from_user.username or callback.from_user.first_name
locale = await get_user_locale(user_id)
is_active = await db.get_user_subscription(user_id)
subscription_status = (
localization.get("subscription_active", locale) if is_active
else localization.get("subscription_inactive", locale)
)
text = (
f"{localization.get('welcome', locale, username=username)}\n\n"
f"{localization.get('subscription_status', locale, status=subscription_status)}\n\n"
f"{localization.get('select_action', locale)}"
)
# Проверяем, является ли пользователь администратором
if is_admin(user_id):
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="💳 Купить подписку",
callback_data="buy_subscription"
)
],
[
InlineKeyboardButton(
text="📞 Техподдержка",
callback_data="support"
),
InlineKeyboardButton(
text="📜 Правила сервиса",
callback_data="rules"
)
],
[
InlineKeyboardButton(
text="🌐 Язык",
callback_data="language"
),
InlineKeyboardButton(
text="⚙️ Админ",
callback_data="admin_panel"
)
]
]
)
else:
keyboard = Keyboards.get_main_keyboard(locale)
await callback.message.edit_text(
text=text,
reply_markup=keyboard
)
await callback.answer()
@main_router.callback_query(lambda c: c.data.startswith("lang_"))
async def handle_callback_language_change(callback: types.CallbackQuery):
"""Обработчик смены языка"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
lang = callback.data.replace("lang_", "")
# Сохраняем язык в БД
await db.set_user_language(user_id, lang)
await callback.answer(
localization.get("messages.language_changed", locale, language=localization.get_locale_name(lang))
)
# Возвращаемся в главное меню
await handle_callback_back_to_main(callback)
@main_router.callback_query(lambda c: c.data in ["buy_14_days", "buy_30_days", "buy_60_days", "buy_90_days", "buy_180_days", "buy_360_days"])
async def handle_callback_buy_tariff(callback: types.CallbackQuery):
"""Обработчик выбора тарифа"""
user_id = callback.from_user.id
locale = await get_user_locale(user_id)
# Карта тарифов: callback_data -> (days, price)
tariffs_map = {
"buy_14_days": (14, "150₽"),
"buy_30_days": (30, "300₽"),
"buy_60_days": (60, "600₽"),
"buy_90_days": (90, "900₽"),
"buy_180_days": (180, "1800₽"),
"buy_360_days": (360, "3600₽")
}
tariff_info = tariffs_map.get(callback.data)
if tariff_info:
days, price = tariff_info
# Сохраняем тариф во временное хранилище
payment_temp[user_id] = {"days": days, "price": price, "tariff": callback.data}
payment_title = localization.get("messages.payment_instruction_title", locale)
payment_text = localization.get("messages.payment_instruction_text", locale, phone=config.payment_phone)
# Клавиатура с кнопкой "Назад"
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_buy"
)
]
]
)
await callback.message.edit_text(
f"{payment_title}\n\n{payment_text}",
reply_markup=keyboard
)
await callback.answer()
@main_router.message(F.photo)
async def handle_payment_photo(message: types.Message):
"""Обработчик фото чека"""
user_id = message.from_user.id
username = message.from_user.username or message.from_user.first_name
# Получаем информацию о тарифе из временного хранилища
tariff_info = payment_temp.get(user_id, {})
days = tariff_info.get("days", 0)
tariff = tariff_info.get("tariff", "unknown")
# Отправляем фото в канал (в тему 5910) с кнопками аппрува
if config.payment_channel_id:
try:
await message.bot.copy_message(
chat_id=config.payment_channel_id,
from_chat_id=message.chat.id,
message_id=message.message_id,
caption=f"💳 Оплата от @{username} ({user_id})\nДней: {days}\nТариф: {tariff}\n{message.caption or ''}",
message_thread_id=5910,
reply_markup=PaymentKeyboards.get_payment_approval_keyboard(user_id, tariff, days)
)
print(f"✅ Фото отправлено в канал {config.payment_channel_id}, тема 5910")
except Exception as e:
print(f"❌ Ошибка отправки фото в канал: {e}")
# Отвечаем пользователю с кнопкой "В главное меню"
locale = await get_user_locale(user_id)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_main"
)
]
]
)
await message.answer(
localization.get("messages.payment_receipt_sent", locale),
reply_markup=keyboard
)
@main_router.message(F.text)
async def handle_ticket_message(message: types.Message):
"""Обработчик текстовых сообщений для создания/дополнения тикета"""
user_id = message.from_user.id
username = message.from_user.username or message.from_user.first_name
text = message.text
# Проверяем, отвечает ли админ на тикет
admin_reply_mode = ticket_append_mode.get(user_id)
if admin_reply_mode and isinstance(admin_reply_mode, str) and admin_reply_mode.startswith("admin_reply_"):
ticket_id = int(admin_reply_mode.replace("admin_reply_", ""))
# Сохраняем ответ в БД
await db.update_ticket_response(ticket_id, text)
# Отправляем ответ в канал
ticket_channel_id = config.payment_ticket_channel_id or config.payment_channel_id
ticket_thread_id = config.payment_ticket_thread_id
if ticket_channel_id:
try:
if ticket_thread_id:
await message.bot.send_message(
chat_id=ticket_channel_id,
text=f"💬 Ответ администратора в тикет #{ticket_id}:\n\n{text}",
message_thread_id=int(ticket_thread_id)
)
else:
await message.bot.send_message(
chat_id=ticket_channel_id,
text=f"💬 Ответ администратора в тикет #{ticket_id}:\n\n{text}"
)
print(f"✅ Ответ админа отправлен в тикет #{ticket_id}")
except Exception as e:
print(f"Ошибка отправки ответа админа: {e}")
# Отвечаем пользователю
try:
ticket = await db.get_ticket(ticket_id)
if ticket:
await message.bot.send_message(
chat_id=ticket['user_id'],
text=f"📋 Ответ поддержки в вашем тикете #{ticket_id}:\n\n{text}"
)
except Exception as e:
print(f"Не удалось отправить ответ пользователю: {e}")
# Удаляем режим ответа
del ticket_append_mode[user_id]
await message.answer(
f"✅ Ответ отправлен в тикет #{ticket_id}!\n\nПользователь получит уведомление.",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="🔙 Назад в админку",
callback_data="admin_back"
)
]
]
)
)
return
# Проверяем, создаёт ли пользователь тикет
if ticket_creation.get(user_id):
# Сохраняем тикет в БД
ticket_id = await db.create_ticket(user_id, username, text)
# Отправляем тикет в канал (с thread_id)
ticket_channel_id = config.payment_ticket_channel_id or config.payment_channel_id
ticket_thread_id = config.payment_ticket_thread_id
if ticket_channel_id:
try:
if ticket_thread_id:
# Отправляем в тему (форум)
await message.bot.send_message(
chat_id=ticket_channel_id,
text=f"📝 Новый тикет #{ticket_id} от @{username} ({user_id})\n\n❓ Вопрос:\n{text}",
message_thread_id=int(ticket_thread_id)
)
else:
# Отправляем просто в канал
await message.bot.send_message(
chat_id=ticket_channel_id,
text=f"📝 Новый тикет #{ticket_id} от @{username} ({user_id})\n\n❓ Вопрос:\n{text}"
)
print(f"✅ Тикет #{ticket_id} отправлен в канал {ticket_channel_id}, тема {ticket_thread_id}")
except Exception as e:
print(f"Ошибка отправки тикета в канал: {e}")
# Отвечаем пользователю
locale = await get_user_locale(user_id)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_main"
)
]
]
)
# Отправляем сообщение и сразу удаляем флаг
del ticket_creation[user_id] # Удаляем флаг ПЕРЕД отправкой
await message.answer(
f"✅ Тикет #{ticket_id} создан!\n\nНаша поддержка ответит вам в ближайшее время.\n\nОжидайте ответа в этом чате.",
reply_markup=keyboard
)
# Проверяем, дополняет ли пользователь тикет
elif ticket_append_mode.get(user_id):
ticket_id = ticket_append_mode[user_id]
# Добавляем сообщение к тикету
await db.append_ticket_message(ticket_id, text)
# Отправляем уведомление в канал
ticket_channel_id = config.payment_ticket_channel_id or config.payment_channel_id
ticket_thread_id = config.payment_ticket_thread_id
if ticket_channel_id:
try:
if ticket_thread_id:
await message.bot.send_message(
chat_id=ticket_channel_id,
text=f" Тикет #{ticket_id} дополнен от @{username} ({user_id})\n\n📝 Дополнение:\n{text}",
message_thread_id=int(ticket_thread_id)
)
else:
await message.bot.send_message(
chat_id=ticket_channel_id,
text=f" Тикет #{ticket_id} дополнен от @{username} ({user_id})\n\n📝 Дополнение:\n{text}"
)
print(f"✅ Дополнение к тикуту #{ticket_id} отправлено в канал")
except Exception as e:
print(f"Ошибка отправки дополнения в канал: {e}")
# Отвечаем пользователю
locale = await get_user_locale(user_id)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_main"
)
]
]
)
# Удаляем режим дополнения
del ticket_append_mode[user_id]
await message.answer(
f"✅ Тикет #{ticket_id} дополнен!\n\nПоддержка увидит ваше сообщение.",
reply_markup=keyboard
)
# ==================== АДМИН ПАНЕЛЬ ====================
@admin_router.callback_query(lambda c: c.data == "admin_panel")
async def handle_admin_panel_button(callback: types.CallbackQuery):
"""Админ-панель из кнопки"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="📋 Все тикеты",
callback_data="admin_all_tickets"
)
],
[
InlineKeyboardButton(
text="🔍 Тикет по ID",
callback_data="admin_ticket_by_id"
)
],
[
InlineKeyboardButton(
text="👥 Пользователи",
callback_data="admin_users"
)
],
[
InlineKeyboardButton(
text="⚙️ Активировать подписку",
callback_data="admin_activate_sub"
)
],
[
InlineKeyboardButton(
text="🔙 Назад",
callback_data="back_to_main"
)
]
]
)
await callback.message.edit_text(
"⚙️ Админ-панель\n\nВыберите действие:",
reply_markup=keyboard
)
await callback.answer()
@admin_router.callback_query(lambda c: c.data == "admin_users")
async def handle_admin_users(callback: types.CallbackQuery):
"""Управление пользователями - статистика"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
# Получаем статистику из БД
connection = db.get_connection()
cursor = connection.cursor(dictionary=True)
# Всего пользователей
cursor.execute("SELECT COUNT(*) as count FROM subscriptions")
total_users = cursor.fetchone()['count']
# Активных пользователей
cursor.execute("SELECT COUNT(*) as count FROM subscriptions WHERE is_active = TRUE")
active_users = cursor.fetchone()['count']
# Заблокированных (пока нет такой колонки, считаем неактивных)
blocked_users = total_users - active_users
# Новые пользователи сегодня
cursor.execute("""
SELECT COUNT(*) as count FROM subscriptions
WHERE DATE(created_at) = CURDATE()
""")
today_users = cursor.fetchone()['count']
# Новые пользователи за неделю
cursor.execute("""
SELECT COUNT(*) as count FROM subscriptions
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
""")
week_users = cursor.fetchone()['count']
# Новые пользователи за месяц
cursor.execute("""
SELECT COUNT(*) as count FROM subscriptions
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
""")
month_users = cursor.fetchone()['count']
cursor.close()
connection.close()
stats_text = (
"👥 Управление пользователями\n\n"
"📊 Статистика:\n"
f"Всего: {total_users}\n"
f"• Активных: {active_users}\n"
f"• Заблокированных: {blocked_users}\n\n"
"📈 Новые пользователи:\n"
f"• Сегодня: {today_users}\n"
f"За неделю: {week_users}\n"
f"За месяц: {month_users}"
)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="<EFBFBD> Все пользователи",
callback_data="admin_users_list_1"
)
],
[
InlineKeyboardButton(
text="<EFBFBD>📥 Импорт из Remnawave",
callback_data="admin_sync_remnawave"
)
],
[
InlineKeyboardButton(
text="🔙 Назад в админку",
callback_data="admin_back"
)
]
]
)
await callback.message.edit_text(
stats_text,
reply_markup=keyboard
)
await callback.answer()
@admin_router.callback_query(lambda c: c.data.startswith("admin_users_list_"))
async def handle_admin_users_list(callback: types.CallbackQuery):
"""Отображение списка всех пользователей с пагинацией"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
# Извлекаем номер страницы
page = int(callback.data.replace("admin_users_list_", ""))
per_page = 10 # Пользователей на странице
# Получаем пользователей из БД
result = await db.get_all_users_paginated(page, per_page)
users = result.get('users', [])
total = result.get('total', 0)
total_pages = result.get('total_pages', 0)
current_page = result.get('current_page', 1)
if not users:
await callback.message.edit_text(
"Нажмите на пользователя для управления:",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="🔙 Назад к пользователям",
callback_data="admin_users"
)
]
]
)
)
await callback.answer()
return
# Формируем список пользователей
keyboard_buttons = []
from datetime import datetime, timezone
for user in users:
user_id = user['user_id']
username = user.get('username') or f"User {user_id}"
is_active = user.get('is_active', False)
created_at = user.get('created_at')
# Формируем статус активности
status_emoji = "" if is_active else ""
# Формируем иконку подписки (если активна)
sub_emoji = "💎" if is_active else ""
# Подарок для новых пользователей (созданы сегодня)
gift_emoji = ""
if created_at:
if hasattr(created_at, 'date'):
if created_at.date() == datetime.now().date():
gift_emoji = "🎁"
elif isinstance(created_at, str):
try:
created_date = datetime.fromisoformat(created_at)
if created_date.date() == datetime.now().date():
gift_emoji = "🎁"
except:
pass
# Вычисляем время с момента регистрации
time_ago = ""
if created_at:
if hasattr(created_at, 'replace'):
try:
now = datetime.now()
if hasattr(created_at, 'tzinfo') and created_at.tzinfo is not None:
now = now.replace(tzinfo=timezone.utc)
diff = now - created_at
seconds = int(diff.total_seconds())
if seconds < 60:
time_ago = f"{seconds} сек. назад"
elif seconds < 3600:
minutes = seconds // 60
time_ago = f"{minutes} мин. назад"
elif seconds < 86400:
hours = seconds // 3600
time_ago = f"{hours} ч. назад"
else:
days = seconds // 86400
time_ago = f"{days} дн. назад"
except:
time_ago = str(created_at)[:10]
else:
time_ago = str(created_at)[:10]
# Формируем текст для кнопки пользователя
user_button_text = f"{status_emoji} {gift_emoji}{sub_emoji} {username} | {time_ago}"
# Ограничиваем длину текста кнопки (максимум 64 символа)
if len(user_button_text) > 64:
user_button_text = user_button_text[:61] + "..."
keyboard_buttons.append([
InlineKeyboardButton(
text=user_button_text,
callback_data=f"admin_user_view_{user_id}"
)
])
# Добавляем пагинацию
pagination_buttons = []
# Кнопка предыдущей страницы
if current_page > 1:
pagination_buttons.append(
InlineKeyboardButton(
text="⬅️",
callback_data=f"admin_users_list_{current_page - 1}"
)
)
# Номер текущей страницы
pagination_buttons.append(
InlineKeyboardButton(
text=f"{current_page}/{total_pages}",
callback_data="noop"
)
)
# Кнопка следующей страницы
if current_page < total_pages:
pagination_buttons.append(
InlineKeyboardButton(
text="➡️",
callback_data=f"admin_users_list_{current_page + 1}"
)
)
keyboard_buttons.append(pagination_buttons)
# Кнопка поиска
keyboard_buttons.append([
InlineKeyboardButton(
text="🔍 Поиск",
callback_data="admin_user_search"
)
])
# Кнопка назад
keyboard_buttons.append([
InlineKeyboardButton(
text="🔙 Назад",
callback_data="admin_users"
)
])
await callback.message.edit_text(
"Нажмите на пользователя для управления:",
reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
)
await callback.answer()
@admin_router.callback_query(lambda c: c.data == "admin_sync_remnawave")
async def handle_admin_sync_remnawave(callback: types.CallbackQuery):
"""Синхронизация всех пользователей из Remnawave в БД"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
# Отправляем сообщение о начале синхронизации
progress_message = await callback.message.answer("🔄 Начинаю синхронизацию из Remnawave...\n\nЭто может занять несколько минут.")
# Запускаем синхронизацию (импорт из Remnawave в БД)
result = await remnawave.sync_all_users()
if result.get("success"):
result_text = (
"✅ Синхронизация завершена!\n\n"
f"📊 Результаты:\n"
f"Всего пользователей в Remnawave: {result['total']}\n"
f"• Импортировано новых: {result['imported']}\n"
f"• Обновлено существующих: {result['updated']}\n"
f"• Ошибок: {result['errors']}"
)
if result.get("error_details"):
result_text += f"\n\n⚠️ Ошибки (первые 10):\n"
for error in result["error_details"]:
result_text += f"{error}\n"
else:
result_text = f"❌ Ошибка синхронизации: {result.get('error', 'Неизвестная ошибка')}"
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="🔙 Назад к пользователям",
callback_data="admin_users"
)
]
]
)
try:
await progress_message.edit_text(
result_text,
reply_markup=keyboard
)
except:
await callback.message.answer(
result_text,
reply_markup=keyboard
)
await callback.answer()
@admin_router.callback_query(lambda c: c.data.startswith("admin_user_view_"))
async def handle_admin_user_view(callback: types.CallbackQuery):
"""Просмотр информации о пользователе"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
user_id = int(callback.data.replace("admin_user_view_", ""))
# Получаем информацию о пользователе из БД
connection = db.get_connection()
cursor = connection.cursor(dictionary=True)
cursor.execute(
"SELECT * FROM subscriptions WHERE user_id = %s",
(user_id,)
)
user = cursor.fetchone()
cursor.close()
connection.close()
if not user:
await callback.answer("Пользователь не найден", show_alert=True)
return
# Формируем текст с информацией о пользователе
user_text = f"👤 Информация о пользователе\n\n"
user_text += f"ID: {user['user_id']}\n"
user_text += f"Username: @{user['username'] or 'Не указан'}\n"
user_text += f"Статус: {'✅ Активен' if user['is_active'] else '❌ Неактивен'}\n\n"
if user.get('subscription_start'):
user_text += f"Начало подписки: {user['subscription_start'].strftime('%d.%m.%Y %H:%M') if hasattr(user['subscription_start'], 'strftime') else user['subscription_start']}\n"
if user.get('subscription_end'):
user_text += f"Окончание подписки: {user['subscription_end'].strftime('%d.%m.%Y %H:%M') if hasattr(user['subscription_end'], 'strftime') else user['subscription_end']}\n"
user_text += f"\nДата регистрации: {user['created_at'].strftime('%d.%m.%Y %H:%M') if hasattr(user['created_at'], 'strftime') else user['created_at']}\n"
# Кнопки управления пользователем
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="✅ Активировать подписку" if not user['is_active'] else "❌ Деактивировать",
callback_data=f"admin_user_toggle_sub_{user_id}"
)
],
[
InlineKeyboardButton(
text="📋 Тикеты пользователя",
callback_data=f"admin_user_tickets_{user_id}"
)
],
[
InlineKeyboardButton(
text="🔙 Назад к списку",
callback_data="admin_users_list_1"
)
]
]
)
await callback.message.edit_text(
user_text,
reply_markup=keyboard
)
await callback.answer()
@admin_router.callback_query(lambda c: c.data == "admin_user_search")
async def handle_admin_user_search(callback: types.CallbackQuery):
"""Заглушка для поиска пользователей"""
await callback.answer("Функция поиска скоро будет доступна", show_alert=True)
@admin_router.callback_query(lambda c: c.data == "noop")
async def handle_noop(callback: types.CallbackQuery):
"""Заглушка для неактивных кнопок"""
await callback.answer()
@admin_router.callback_query(lambda c: c.data.startswith("admin_user_toggle_sub_"))
async def handle_admin_user_toggle_sub(callback: types.CallbackQuery):
"""Переключение статуса подписки пользователя"""
user_id = int(callback.data.replace("admin_user_toggle_sub_", ""))
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
# Получаем текущего пользователя
connection = db.get_connection()
cursor = connection.cursor(dictionary=True)
cursor.execute("SELECT is_active FROM subscriptions WHERE user_id = %s", (user_id,))
user = cursor.fetchone()
if not user:
await callback.answer("Пользователь не найден", show_alert=True)
cursor.close()
connection.close()
return
# Переключаем статус
new_status = not user['is_active']
if new_status:
# Активируем подписку на 30 дней
cursor.execute(
"""UPDATE subscriptions
SET is_active = TRUE,
subscription_start = NOW(),
subscription_end = DATE_ADD(NOW(), INTERVAL 30 DAY)
WHERE user_id = %s""",
(user_id,)
)
else:
# Деактивируем подписку
cursor.execute(
"UPDATE subscriptions SET is_active = FALSE WHERE user_id = %s",
(user_id,)
)
connection.commit()
cursor.close()
connection.close()
await callback.answer(f"Подписка {'активирована' if new_status else 'деактивирована'}")
# Обновляем экран
await handle_admin_user_view(callback)
@admin_router.callback_query(lambda c: c.data.startswith("admin_user_tickets_"))
async def handle_admin_user_tickets(callback: types.CallbackQuery):
"""Просмотр тикетов пользователя"""
user_id = int(callback.data.replace("admin_user_tickets_", ""))
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
# Получаем тикеты пользователя
tickets = await db.get_user_tickets(user_id)
if not tickets:
await callback.message.answer(
"📋 Тикеты пользователя\n\n"
"У пользователя нет тикетов.",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="🔙 Назад к пользователю",
callback_data=f"admin_user_view_{user_id}"
)
]
]
)
)
await callback.answer()
return
# Формируем список тикетов
tickets_text = f"📋 Тикеты пользователя\n\n"
keyboard_buttons = []
for ticket in tickets[:10]:
status_emoji = "🟢" if ticket['status'] == 'open' else "🔵" if ticket['status'] == 'answered' else ""
keyboard_buttons.append([
InlineKeyboardButton(
text=f"{status_emoji} #{ticket['id']} - {ticket['status']}",
callback_data=f"admin_ticket_view_{ticket['id']}"
)
])
keyboard_buttons.append([
InlineKeyboardButton(
text="🔙 Назад к пользователю",
callback_data=f"admin_user_view_{user_id}"
)
])
await callback.message.answer(
tickets_text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
)
await callback.answer()
@admin_router.callback_query(lambda c: c.data == "admin_all_tickets")
async def handle_admin_all_tickets(callback: types.CallbackQuery):
"""Все тикеты (последние 10)"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
# Получаем все тикеты (последние 10)
connection = db.get_connection()
cursor = connection.cursor(dictionary=True)
cursor.execute(
"""SELECT id, user_id, username, message, status, created_at
FROM tickets ORDER BY created_at DESC LIMIT 10"""
)
tickets = cursor.fetchall()
cursor.close()
connection.close()
if not tickets:
await callback.answer("Тикетов нет", show_alert=True)
return
tickets_text = "📋 Последние тикеты (10):\n\n"
keyboard_buttons = []
for ticket in tickets:
status_emoji = "🟢" if ticket['status'] == 'open' else "🔵" if ticket['status'] == 'answered' else ""
created_at = ticket['created_at'].strftime("%d.%m.%Y %H:%M") if ticket['created_at'] else 'Неизвестно'
tickets_text += (
f"{status_emoji} #{ticket['id']} | @{ticket['username']} | {ticket['status']}\n"
)
keyboard_buttons.append([
InlineKeyboardButton(
text=f"#{ticket['id']} - @{ticket['username']}",
callback_data=f"admin_ticket_view_{ticket['id']}"
)
])
keyboard_buttons.append([
InlineKeyboardButton(
text="🔙 Назад",
callback_data="admin_back"
)
])
await callback.message.answer(
tickets_text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
)
await callback.answer()
@admin_router.callback_query(lambda c: c.data.startswith("admin_ticket_view_"))
async def handle_admin_ticket_view(callback: types.CallbackQuery):
"""Просмотр тикета админом"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
ticket_id = int(callback.data.replace("admin_ticket_view_", ""))
ticket = await db.get_ticket(ticket_id)
if not ticket:
await callback.answer("Тикет не найден", show_alert=True)
return
status_text = {
'open': '🟢 Открыт',
'answered': '🔵 Дан ответ',
'closed': '⚫ Закрыт'
}.get(ticket['status'], ticket['status'])
ticket_text = f"📋 Тикет #{ticket['id']}\n\n"
ticket_text += f"Пользователь: @{ticket['username']} ({ticket['user_id']})\n"
ticket_text += f"Статус: {status_text}\n"
ticket_text += f"Дата создания: {ticket['created_at'].strftime('%d.%m.%Y %H:%M') if ticket['created_at'] else 'Неизвестно'}\n\n"
ticket_text += f"❓ Вопрос:\n{ticket['message']}\n\n"
if ticket['admin_response']:
ticket_text += f"💬 Ответ поддержки:\n{ticket['admin_response']}\n\n"
if ticket['status'] == 'closed':
closed_by = ticket.get('closed_by', 'Неизвестно')
closed_at = ticket.get('closed_at')
ticket_text += f"🔒 Закрыл: {closed_by}\n"
if closed_at:
ticket_text += f"🕒 Дата: {closed_at.strftime('%d.%m.%Y %H:%M') if hasattr(closed_at, 'strftime') else closed_at}\n"
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="✏️ Ответить",
callback_data=f"admin_reply_{ticket['id']}"
)
],
[
InlineKeyboardButton(
text="✅ Закрыть",
callback_data=f"admin_close_{ticket['id']}"
)
],
[
InlineKeyboardButton(
text="🔙 Назад",
callback_data="admin_all_tickets"
)
]
]
)
await callback.message.answer(ticket_text, reply_markup=keyboard)
await callback.answer()
@admin_router.callback_query(lambda c: c.data.startswith("admin_reply_"))
async def handle_admin_reply(callback: types.CallbackQuery):
"""Ответ на тикет"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
ticket_id = int(callback.data.replace("admin_reply_", ""))
# Устанавливаем режим ответа
ticket_append_mode[callback.from_user.id] = f"admin_reply_{ticket_id}"
await callback.message.answer(
f"✏️ Ответ на тикет #{ticket_id}\n\n"
f"Напишите ваш ответ одним сообщением:",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="❌ Отмена",
callback_data="admin_cancel_reply"
)
]
]
)
)
await callback.answer()
@admin_router.callback_query(lambda c: c.data == "admin_cancel_reply")
async def handle_admin_cancel_reply(callback: types.CallbackQuery):
"""Отмена ответа админа"""
if callback.from_user.id in ticket_append_mode:
del ticket_append_mode[callback.from_user.id]
await callback.message.edit_text(
"❌ Отменено\n\nВыберите действие:",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="🔙 Назад в админку",
callback_data="admin_back"
)
]
]
)
)
await callback.answer()
@admin_router.callback_query(lambda c: c.data.startswith("admin_close_"))
async def handle_admin_close(callback: types.CallbackQuery):
"""Закрытие тикета админом"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
ticket_id = int(callback.data.replace("admin_close_", ""))
admin_username = callback.from_user.username or callback.from_user.first_name
await db.close_ticket(ticket_id, f"admin:{admin_username}")
# Уведомляем пользователя
ticket = await db.get_ticket(ticket_id)
if ticket:
try:
await callback.bot.send_message(
chat_id=ticket['user_id'],
text=f"📋 Ваш тикет #{ticket_id} был закрыт администратором.\n\n"
f"Если у вас остались вопросы, создайте новый тикет."
)
except:
pass
await callback.answer("Тикет закрыт")
# Обновляем сообщение
try:
await callback.message.delete()
except:
pass
@admin_router.callback_query(lambda c: c.data == "admin_back")
async def handle_admin_back(callback: types.CallbackQuery):
"""Назад в админку"""
if not is_admin(callback.from_user.id):
await callback.answer("Доступ запрещён", show_alert=True)
return
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="📋 Все тикеты",
callback_data="admin_all_tickets"
)
],
[
InlineKeyboardButton(
text="🔍 Тикет по ID",
callback_data="admin_ticket_by_id"
)
],
[
InlineKeyboardButton(
text="👥 Пользователи",
callback_data="admin_users"
)
],
[
InlineKeyboardButton(
text="⚙️ Активировать подписку",
callback_data="admin_activate_sub"
)
],
[
InlineKeyboardButton(
text="🔙 Назад",
callback_data="back_to_main"
)
]
]
)
await callback.message.edit_text(
"⚙️ Админ-панель\n\nВыберите действие:",
reply_markup=keyboard
)
await callback.answer()
@main_router.callback_query(lambda c: c.data.startswith("payment_approve_"))
async def handle_payment_approve(callback: types.CallbackQuery):
"""Обработчик кнопки 'Аппрув'"""
# payment_approve_{user_id}_{tariff}_{days}
data = callback.data.replace("payment_approve_", "").split("_")
# Получаем locale для клавиатур
locale = await get_user_locale(callback.from_user.id)
if len(data) >= 3:
user_id = int(data[0])
tariff = data[1]
days = int(data[2])
# Создаём подписку через Remnawave API
api_result = await remnawave.create_subscription(user_id, days, tariff)
if api_result.get("success"):
# Активируем подписку в локальной БД
await db.activate_subscription(user_id, days)
# Формируем сообщение для пользователя
subscription_url = api_result.get("subscription_url")
username = api_result.get("username", f"subs{user_id}")
print(f"🔗 subscription_url: {subscription_url}")
print(f"👤 username: {username}")
if subscription_url:
user_message = (
f"✅ Оплата подтверждена!\n\n"
f"Ваша подписка активирована на {days} дней.\n\n"
f"Ссылка на подключение : {subscription_url}\n\n"
f"Нажмите на ссылку выше, чтобы добавить VPN в приложение.\n\n"
f"Спасибо за покупку! 🎉"
)
else:
user_message = (
f"✅ Оплата подтверждена!\n\n"
f"Ваша подписка активирована на {days} дней.\n"
f"Username: <code>{username}</code>\n\n"
f"Ссылка на подключение будет отправлена администратором.\n\n"
f"Спасибо за покупку! 🎉"
)
# Клавиатура с кнопкой "В главное меню"
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_main"
)
]
]
)
# Отправляем уведомление пользователю
try:
await callback.bot.send_message(
chat_id=user_id,
text=user_message,
reply_markup=keyboard
)
print(f"✅ Сообщение отправлено пользователю {user_id}")
except Exception as e:
print(f"Не удалось отправить уведомление пользователю {user_id}: {e}")
else:
# Ошибка API
error_msg = f"⚠️ Подписка не создана\n\nОшибка: {api_result.get('error', 'Неизвестная ошибка')}\n\nОбратитесь в техподдержку."
# Клавиатура с кнопкой "В главное меню"
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_main"
)
]
]
)
try:
await callback.bot.send_message(
chat_id=user_id,
text=error_msg,
reply_markup=keyboard
)
except:
pass
# Удаляем кнопки из сообщения в канале
try:
await callback.message.edit_reply_markup(reply_markup=None)
except:
pass
# Добавляем пометку в канал
try:
status_text = "✅ ОПЛАЧЕНО" if api_result.get("success") else "⚠️ ОШИБКА API"
await callback.message.edit_caption(
caption=f"{callback.message.caption}\n\n{status_text}",
reply_markup=None
)
except:
pass
await callback.answer("Готово!")
@main_router.callback_query(lambda c: c.data.startswith("payment_decline_"))
async def handle_payment_decline(callback: types.CallbackQuery):
"""Обработчик кнопки 'Отклонить'"""
# payment_decline_{user_id}_{tariff}
data = callback.data.replace("payment_decline_", "").split("_")
# Получаем locale для клавиатур
locale = await get_user_locale(callback.from_user.id)
if len(data) >= 2:
user_id = int(data[0])
# Клавиатура с кнопкой "В главное меню"
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=localization.get("buttons.back", locale),
callback_data="back_to_main"
)
]
]
)
# Отправляем уведомление пользователю
try:
await callback.bot.send_message(
chat_id=user_id,
text=f"❌ Оплата отклонена.\n\nПо вопросам обращайтесь в техподдержку.",
reply_markup=keyboard
)
except Exception as e:
print(f"Не удалось отправить уведомление пользователю {user_id}: {e}")
# Удаляем кнопки из сообщения в канале
try:
await callback.message.edit_reply_markup(reply_markup=None)
except:
pass
# Добавляем пометку в канал
try:
await callback.message.edit_caption(
caption=f"{callback.message.caption}\n\n❌ ОТКЛОНЕНО",
reply_markup=None
)
except:
pass
await callback.answer("Оплата отклонена")