ruff, button file, castomize ping and backup
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import aiofiles
|
||||
|
||||
from aiogram.types import BufferedInputFile
|
||||
|
||||
from bot import bot
|
||||
|
||||
@@ -14,11 +14,12 @@ from filters.private import IsPrivateFilter
|
||||
from logger import logger
|
||||
from middlewares import register_middleware
|
||||
|
||||
|
||||
bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot=bot, storage=storage)
|
||||
|
||||
version = "4.1-beta(12)"
|
||||
version = "4.1-beta(28)"
|
||||
|
||||
register_middleware(dp)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from .payments import router as payments_router
|
||||
from .profile import router as profile_router
|
||||
from .start import router as start_router
|
||||
|
||||
|
||||
router = Router(name="handlers_main_router")
|
||||
|
||||
router.include_routers(
|
||||
|
||||
@@ -14,6 +14,7 @@ from .servers import router as servers_router
|
||||
from .stats import router as stats_router
|
||||
from .users import router as users_router
|
||||
|
||||
|
||||
router = Router(name="admins_main_router")
|
||||
|
||||
router.include_routers(
|
||||
|
||||
@@ -3,8 +3,10 @@ from aiogram.types import CallbackQuery
|
||||
|
||||
from backup import backup_database
|
||||
from filters.admin import IsAdminFilter
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ from aiogram.types import BufferedInputFile, CallbackQuery
|
||||
|
||||
from database import delete_user_data
|
||||
from filters.admin import IsAdminFilter
|
||||
from .keyboard import build_bans_kb
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
from .keyboard import build_bans_kb
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
@@ -12,18 +15,19 @@ from backup import create_backup_and_send_to_admins
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, TOTAL_GB
|
||||
from database import check_unique_server_name, get_servers, update_key_expiry
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import create_key_on_cluster, create_client_on_server, renew_key_in_cluster
|
||||
from handlers.keys.key_utils import create_client_on_server, create_key_on_cluster, renew_key_in_cluster
|
||||
from logger import logger
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
from .keyboard import (
|
||||
build_clusters_editor_kb,
|
||||
build_manage_cluster_kb,
|
||||
AdminClusterCallback,
|
||||
AdminServerCallback,
|
||||
build_cluster_management_kb,
|
||||
build_clusters_editor_kb,
|
||||
build_manage_cluster_kb,
|
||||
build_sync_cluster_kb,
|
||||
build_cluster_management_kb
|
||||
)
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
import time
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -523,7 +527,9 @@ async def handle_days_input(message: Message, state: FSMContext, session: Any):
|
||||
)
|
||||
await update_key_expiry(key["client_id"], new_expiry, session)
|
||||
|
||||
await message.answer(f"✅ Время подписки продлено на <b>{days} дней</b> всем пользователям в кластере <b>{cluster_name}</b>.")
|
||||
await message.answer(
|
||||
f"✅ Время подписки продлено на <b>{days} дней</b> всем пользователям в кластере <b>{cluster_name}</b>."
|
||||
)
|
||||
except ValueError:
|
||||
await message.answer("❌ Введите корректное число дней.")
|
||||
return
|
||||
|
||||
@@ -4,13 +4,15 @@ from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from handlers.buttons import BACK
|
||||
|
||||
from ..panel.keyboard import build_admin_back_btn
|
||||
from ..servers.keyboard import AdminServerCallback
|
||||
|
||||
|
||||
class AdminClusterCallback(CallbackData, prefix="admin_cluster"):
|
||||
action: str
|
||||
data: Optional[str] = None
|
||||
data: str | None = None
|
||||
|
||||
|
||||
def build_clusters_editor_kb(servers: dict) -> InlineKeyboardMarkup:
|
||||
|
||||
@@ -8,9 +8,11 @@ from aiogram.types import CallbackQuery, Message
|
||||
from config import USERNAME_BOT
|
||||
from database import create_coupon, delete_coupon, get_all_coupons
|
||||
from filters.admin import IsAdminFilter
|
||||
from .keyboard import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb
|
||||
from logger import logger
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
from .keyboard import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from handlers.buttons import BACK
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn
|
||||
|
||||
|
||||
@@ -31,7 +33,7 @@ def build_coupons_list_kb(coupons: list, current_page: int, total_pages: int) ->
|
||||
if current_page > 1:
|
||||
pagination_buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text="⬅️ Назад",
|
||||
text=BACK,
|
||||
callback_data=AdminPanelCallback(action="coupons_list", page=current_page - 1).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from ..panel.keyboard import build_admin_back_btn, AdminPanelCallback
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn
|
||||
|
||||
|
||||
def build_management_kb() -> InlineKeyboardMarkup:
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import StatesGroup, State
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from asyncpg import Connection
|
||||
|
||||
from filters.admin import IsAdminFilter
|
||||
from .keyboard import AdminPanelCallback, build_management_kb
|
||||
from logger import logger
|
||||
|
||||
from ..panel.keyboard import build_admin_back_kb
|
||||
from .keyboard import AdminPanelCallback, build_management_kb
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from handlers.buttons import BACK, MAIN_MENU
|
||||
|
||||
|
||||
class AdminPanelCallback(CallbackData, prefix="admin_panel"):
|
||||
action: str
|
||||
@@ -28,7 +30,7 @@ def build_panel_kb() -> InlineKeyboardMarkup:
|
||||
InlineKeyboardButton(text="📊 Статистика", callback_data=AdminPanelCallback(action="stats").pack()),
|
||||
InlineKeyboardButton(text="🤖 Управление", callback_data=AdminPanelCallback(action="management").pack()),
|
||||
)
|
||||
builder.button(text="Личный кабинет", callback_data="profile")
|
||||
builder.button(text=MAIN_MENU, callback_data="profile")
|
||||
builder.adjust(1, 1, 2, 1, 2, 1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -42,7 +44,7 @@ def build_restart_kb() -> InlineKeyboardMarkup:
|
||||
|
||||
|
||||
def build_admin_back_kb(action: str = "admin") -> InlineKeyboardMarkup:
|
||||
return build_admin_singleton_kb("🔙 Назад", action)
|
||||
return build_admin_singleton_kb(BACK, action)
|
||||
|
||||
|
||||
def build_admin_singleton_kb(text: str, action: str) -> InlineKeyboardMarkup:
|
||||
@@ -52,7 +54,7 @@ def build_admin_singleton_kb(text: str, action: str) -> InlineKeyboardMarkup:
|
||||
|
||||
|
||||
def build_admin_back_btn(action: str = "admin") -> InlineKeyboardButton:
|
||||
return build_admin_btn("🔙 Назад", action)
|
||||
return build_admin_btn(BACK, action)
|
||||
|
||||
|
||||
def build_admin_btn(text: str, action: str) -> InlineKeyboardButton:
|
||||
|
||||
@@ -5,8 +5,10 @@ from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from bot import version
|
||||
from filters.admin import IsAdminFilter
|
||||
|
||||
from .keyboard import AdminPanelCallback, build_panel_kb
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ from aiogram import F, Router
|
||||
from aiogram.types import CallbackQuery
|
||||
|
||||
from filters.admin import IsAdminFilter
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb, build_restart_kb
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from ..panel.keyboard import build_admin_back_btn
|
||||
|
||||
@@ -7,9 +7,11 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from filters.admin import IsAdminFilter
|
||||
from .keyboard import AdminSenderCallback, build_clusters_kb, build_sender_kb
|
||||
from logger import logger
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
from .keyboard import AdminSenderCallback, build_clusters_kb, build_sender_kb
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from handlers.buttons import BACK
|
||||
|
||||
|
||||
class AdminServerCallback(CallbackData, prefix="admin_server"):
|
||||
action: str
|
||||
@@ -13,7 +15,7 @@ def build_manage_server_kb(server_name: str, cluster_name: str) -> InlineKeyboar
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🗑️ Удалить", callback_data=AdminServerCallback(action="delete", data=server_name).pack())
|
||||
builder.button(text="🔙 Назад", callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack())
|
||||
builder.button(text=BACK, callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack())
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -21,6 +23,6 @@ def build_manage_server_kb(server_name: str, cluster_name: str) -> InlineKeyboar
|
||||
def build_delete_server_kb(server_name: str) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="✅ Да", callback_data=AdminServerCallback(action="delete_confirm", data=server_name).pack())
|
||||
builder.button(text="🔙 Назад", callback_data=AdminServerCallback(action="manage", data=server_name).pack())
|
||||
builder.button(text=BACK, callback_data=AdminServerCallback(action="manage", data=server_name).pack())
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -5,12 +5,14 @@ from aiogram.types import CallbackQuery
|
||||
|
||||
from database import delete_server, get_servers
|
||||
from filters.admin import IsAdminFilter
|
||||
|
||||
from ..panel.keyboard import build_admin_back_kb
|
||||
from .keyboard import (
|
||||
AdminServerCallback,
|
||||
build_delete_server_kb,
|
||||
build_manage_server_kb,
|
||||
)
|
||||
from ..panel.keyboard import build_admin_back_kb
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -2,18 +2,22 @@ from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
import pytz
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.types import CallbackQuery
|
||||
|
||||
from filters.admin import IsAdminFilter
|
||||
from .keyboard import build_stats_kb
|
||||
from logger import logger
|
||||
from utils.csv_export import export_payments_csv, export_users_csv, export_hot_leads_csv, export_keys_csv
|
||||
from utils.csv_export import export_hot_leads_csv, export_keys_csv, export_payments_csv, export_users_csv
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
from .keyboard import build_stats_kb
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
AdminPanelCallback.filter(F.action == "stats"),
|
||||
IsAdminFilter(),
|
||||
@@ -40,28 +44,28 @@ async def handle_stats(callback_query: CallbackQuery, session: Any):
|
||||
total_payments_all_time = int(await session.fetchval("SELECT COALESCE(SUM(amount), 0) FROM payments"))
|
||||
|
||||
all_keys = await session.fetch("SELECT created_at, expiry_time FROM keys")
|
||||
|
||||
|
||||
def count_subscriptions_by_duration(keys):
|
||||
periods = {'trial': 0, '1': 0, '3': 0, '6': 0, '12': 0}
|
||||
periods = {"trial": 0, "1": 0, "3": 0, "6": 0, "12": 0}
|
||||
for key in keys:
|
||||
try:
|
||||
duration_days = (key['expiry_time'] - key['created_at']) / (1000 * 60 * 60 * 24)
|
||||
|
||||
duration_days = (key["expiry_time"] - key["created_at"]) / (1000 * 60 * 60 * 24)
|
||||
|
||||
if duration_days <= 29:
|
||||
periods['trial'] += 1
|
||||
periods["trial"] += 1
|
||||
elif duration_days <= 89:
|
||||
periods['1'] += 1
|
||||
periods["1"] += 1
|
||||
elif duration_days <= 179:
|
||||
periods['3'] += 1
|
||||
periods["3"] += 1
|
||||
elif duration_days <= 359:
|
||||
periods['6'] += 1
|
||||
periods["6"] += 1
|
||||
else:
|
||||
periods['12'] += 1
|
||||
periods["12"] += 1
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing key duration: {e}")
|
||||
continue
|
||||
return periods
|
||||
|
||||
|
||||
subs_all_time = count_subscriptions_by_duration(all_keys)
|
||||
|
||||
registrations_today = await session.fetchval("SELECT COUNT(*) FROM users WHERE created_at >= CURRENT_DATE")
|
||||
@@ -93,19 +97,15 @@ async def handle_stats(callback_query: CallbackQuery, session: Any):
|
||||
|
||||
stats_message = (
|
||||
"📊 <b>Статистика проекта</b>\n\n"
|
||||
|
||||
"👤 <b>Пользователи:</b>\n"
|
||||
f"├ 🗓️ За день: <b>{registrations_today}</b>\n"
|
||||
f"├ 📆 За неделю: <b>{registrations_week}</b>\n"
|
||||
f"├ 🗓️ За месяц: <b>{registrations_month}</b>\n"
|
||||
f"└ 🌐 Всего: <b>{total_users}</b>\n\n"
|
||||
|
||||
"💡 <b>Активность:</b>\n"
|
||||
f"└ 👥 Сегодня были активны: <b>{users_updated_today}</b>\n\n"
|
||||
|
||||
"🤝 <b>Реферальная система:</b>\n"
|
||||
f"└ 👥 Всего привлечено: <b>{total_referrals}</b>\n\n"
|
||||
|
||||
"🔐 <b>Подписки:</b>\n"
|
||||
f"├ 📦 Всего сгенерировано: <b>{total_keys}</b>\n"
|
||||
f"├ ✅ Активных: <b>{active_keys}</b>\n"
|
||||
@@ -116,13 +116,11 @@ async def handle_stats(callback_query: CallbackQuery, session: Any):
|
||||
f" • 🗓️ 3 мес: <b>{subs_all_time['3']}</b>\n"
|
||||
f" • 🗓️ 6 мес: <b>{subs_all_time['6']}</b>\n"
|
||||
f" • 🗓️ 12 мес: <b>{subs_all_time['12']}</b>\n\n"
|
||||
|
||||
"💰 <b>Финансы:</b>\n"
|
||||
f"├ 📅 За день: <b>{total_payments_today} ₽</b>\n"
|
||||
f"├ 📆 За неделю: <b>{total_payments_week} ₽</b>\n"
|
||||
f"├ 📆 За месяц: <b>{total_payments_month} ₽</b>\n"
|
||||
f"└ 🏦 Всего: <b>{total_payments_all_time} ₽</b>\n\n"
|
||||
|
||||
f"🔥 <b>Горящие лиды</b>: <b>{hot_leads_count}</b> (платили, но не активировали ключи)\n\n"
|
||||
f"⏱️ <i>Последнее обновление:</i> <code>{update_time}</code>"
|
||||
)
|
||||
@@ -135,6 +133,7 @@ async def handle_stats(callback_query: CallbackQuery, session: Any):
|
||||
logger.error(f"Error in user_stats_menu: {e}")
|
||||
await callback_query.answer("Произошла ошибка при получении статистики", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
AdminPanelCallback.filter(F.action == "stats_export_users_csv"),
|
||||
IsAdminFilter(),
|
||||
@@ -148,6 +147,7 @@ async def handle_export_users_csv(callback_query: CallbackQuery, session: Any):
|
||||
logger.error(f"Ошибка при экспорте пользователей в CSV: {e}")
|
||||
await callback_query.message.edit_text(text=f"❗ Произошла ошибка при экспорте: {e}", reply_markup=kb)
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
AdminPanelCallback.filter(F.action == "stats_export_payments_csv"),
|
||||
IsAdminFilter(),
|
||||
@@ -161,6 +161,7 @@ async def handle_export_payments_csv(callback_query: CallbackQuery, session: Any
|
||||
logger.error(f"Ошибка при экспорте платежей в CSV: {e}")
|
||||
await callback_query.message.edit_text(text=f"❗ Произошла ошибка при экспорте: {e}", reply_markup=kb)
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
AdminPanelCallback.filter(F.action == "stats_export_hot_leads_csv"),
|
||||
IsAdminFilter(),
|
||||
@@ -169,16 +170,10 @@ async def handle_export_hot_leads_csv(callback_query: CallbackQuery, session: An
|
||||
kb = build_admin_back_kb("stats")
|
||||
try:
|
||||
export = await export_hot_leads_csv(session)
|
||||
await callback_query.message.answer_document(
|
||||
document=export,
|
||||
caption="📥 Экспорт горящих лидов"
|
||||
)
|
||||
await callback_query.message.answer_document(document=export, caption="📥 Экспорт горящих лидов")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при экспорте 'горящих лидов': {e}")
|
||||
await callback_query.message.edit_text(
|
||||
text=f"❗ Произошла ошибка при экспорте: {e}",
|
||||
reply_markup=kb
|
||||
)
|
||||
await callback_query.message.edit_text(text=f"❗ Произошла ошибка при экспорте: {e}", reply_markup=kb)
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
|
||||
@@ -5,8 +5,10 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import RENEWAL_PRICES, TOTAL_GB
|
||||
from database import get_clusters
|
||||
from handlers.buttons import BACK
|
||||
|
||||
from ..panel.keyboard import build_admin_back_btn
|
||||
from database import get_clusters
|
||||
|
||||
|
||||
class AdminUserEditorCallback(CallbackData, prefix="admin_users"):
|
||||
@@ -69,7 +71,7 @@ def build_user_edit_kb(tg_id: int, key_records: list) -> InlineKeyboardMarkup:
|
||||
def build_users_balance_change_kb(tg_id: int) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(
|
||||
text="🔙 Назад", # todo: fix magic text was set
|
||||
text=BACK, # todo: fix magic text was set
|
||||
callback_data=AdminUserEditorCallback(action="users_balance_edit", tg_id=tg_id).pack(),
|
||||
)
|
||||
return builder.as_markup()
|
||||
@@ -104,7 +106,7 @@ def build_users_balance_kb(tg_id: int) -> InlineKeyboardMarkup:
|
||||
def build_users_key_show_kb(tg_id: int, email: str) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(
|
||||
text="🔙 Назад", # todo: fix magic text was set
|
||||
text=BACK, # todo: fix magic text was set
|
||||
callback_data=AdminUserEditorCallback(action="users_key_edit", tg_id=tg_id, data=email, edit=True).pack(),
|
||||
)
|
||||
return builder.as_markup()
|
||||
@@ -133,7 +135,7 @@ def build_users_key_expiry_kb(tg_id: int, email: str) -> InlineKeyboardMarkup:
|
||||
callback_data=AdminUserKeyEditorCallback(action="set", tg_id=tg_id, data=email).pack(),
|
||||
)
|
||||
builder.button(
|
||||
text="🔙 Назад", # todo: fix magic text was set
|
||||
text=BACK, # todo: fix magic text was set
|
||||
callback_data=AdminUserEditorCallback(action="users_key_edit", tg_id=tg_id, data=email).pack(),
|
||||
)
|
||||
builder.adjust(2, 2, 2, 2, 2, 1)
|
||||
@@ -154,7 +156,7 @@ def build_user_delete_kb(tg_id: int):
|
||||
def build_user_key_kb(tg_id: int, email: str) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(
|
||||
text="🔙 Назад", callback_data=AdminUserEditorCallback(action="users_key_edit", tg_id=tg_id, data=email).pack()
|
||||
text=BACK, callback_data=AdminUserEditorCallback(action="users_key_edit", tg_id=tg_id, data=email).pack()
|
||||
)
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
@@ -206,7 +208,7 @@ def build_key_delete_kb(tg_id: int, email: str) -> InlineKeyboardMarkup:
|
||||
|
||||
|
||||
def build_editor_kb(tg_id: int, edit: bool = False) -> InlineKeyboardMarkup:
|
||||
return build_editor_singleton_kb("🔙 Назад", tg_id, edit)
|
||||
return build_editor_singleton_kb(BACK, tg_id, edit)
|
||||
|
||||
|
||||
def build_editor_singleton_kb(text: str, tg_id: int, edit: bool = False) -> InlineKeyboardMarkup:
|
||||
@@ -216,7 +218,7 @@ def build_editor_singleton_kb(text: str, tg_id: int, edit: bool = False) -> Inli
|
||||
|
||||
|
||||
def build_editor_back_btn(tg_id: int, edit: bool = False) -> InlineKeyboardButton:
|
||||
return build_editor_btn("🔙 Назад", tg_id, edit)
|
||||
return build_editor_btn(BACK, tg_id, edit)
|
||||
|
||||
|
||||
def build_editor_btn(text: str, tg_id: int, edit: bool = False) -> InlineKeyboardButton:
|
||||
@@ -230,11 +232,8 @@ async def build_cluster_selection_kb(session, tg_id: int, email: str, action: st
|
||||
clusters = await get_clusters(session)
|
||||
|
||||
for cluster_id in clusters:
|
||||
builder.button(
|
||||
text=cluster_id,
|
||||
callback_data=f"{action}|{tg_id}|{email}|{cluster_id}"
|
||||
)
|
||||
builder.button(text=cluster_id, callback_data=f"{action}|{tg_id}|{email}|{cluster_id}")
|
||||
|
||||
builder.button(text="⬅️ Назад", callback_data=f"edit_user_key|{tg_id}|{email}")
|
||||
builder.button(text=BACK, callback_data=f"edit_user_key|{tg_id}|{email}")
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -12,26 +12,37 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import TOTAL_GB, PUBLIC_LINK, RENEWAL_PRICES
|
||||
|
||||
from config import PUBLIC_LINK, RENEWAL_PRICES, TOTAL_GB
|
||||
from database import (
|
||||
delete_key,
|
||||
delete_user_data,
|
||||
get_balance,
|
||||
get_client_id_by_email,
|
||||
get_servers,
|
||||
store_key,
|
||||
update_balance,
|
||||
update_key_expiry,
|
||||
update_trial,
|
||||
store_key
|
||||
)
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import delete_key_from_cluster, get_user_traffic, renew_key_in_cluster, update_subscription, create_key_on_cluster, reset_traffic_in_cluster
|
||||
from handlers.utils import sanitize_key_name
|
||||
from handlers.keys.key_utils import (
|
||||
create_key_on_cluster,
|
||||
delete_key_from_cluster,
|
||||
get_user_traffic,
|
||||
renew_key_in_cluster,
|
||||
reset_traffic_in_cluster,
|
||||
update_subscription,
|
||||
)
|
||||
from handlers.utils import generate_random_email, sanitize_key_name
|
||||
from logger import logger
|
||||
from utils.csv_export import export_referrals_csv
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn, build_admin_back_kb
|
||||
from .keyboard import (
|
||||
AdminUserEditorCallback,
|
||||
AdminUserKeyEditorCallback,
|
||||
build_cluster_selection_kb,
|
||||
build_editor_kb,
|
||||
build_key_delete_kb,
|
||||
build_key_edit_kb,
|
||||
@@ -42,11 +53,7 @@ from .keyboard import (
|
||||
build_users_balance_kb,
|
||||
build_users_key_expiry_kb,
|
||||
build_users_key_show_kb,
|
||||
build_cluster_selection_kb
|
||||
)
|
||||
from logger import logger
|
||||
from utils.csv_export import export_referrals_csv
|
||||
from handlers.utils import generate_random_email
|
||||
|
||||
|
||||
MOSCOW_TZ = pytz.timezone("Europe/Moscow")
|
||||
@@ -479,7 +486,7 @@ async def handle_update_key(callback_query: CallbackQuery, callback_data: AdminU
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=f"📡 Выберите кластер, на котором пересоздать ключ <b>{email}</b>:",
|
||||
reply_markup=await build_cluster_selection_kb(session, tg_id, email, action="confirm_admin_key_reissue")
|
||||
reply_markup=await build_cluster_selection_kb(session, tg_id, email, action="confirm_admin_key_reissue"),
|
||||
)
|
||||
|
||||
|
||||
@@ -490,7 +497,9 @@ async def confirm_admin_key_reissue(callback_query: CallbackQuery, session: Any)
|
||||
|
||||
try:
|
||||
await update_subscription(tg_id, email, session, cluster_override=cluster_id)
|
||||
await handle_key_edit(callback_query, AdminUserEditorCallback(tg_id=tg_id, data=email, action="view_key"), session, True)
|
||||
await handle_key_edit(
|
||||
callback_query, AdminUserEditorCallback(tg_id=tg_id, data=email, action="view_key"), session, True
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при перевыпуске ключа {email}: {e}")
|
||||
await callback_query.message.answer(f"❗ Ошибка: {e}")
|
||||
@@ -798,7 +807,9 @@ async def handle_users_export_referrals(
|
||||
|
||||
|
||||
@router.callback_query(AdminUserEditorCallback.filter(F.action == "users_create_key"), IsAdminFilter())
|
||||
async def handle_create_key_select_cluster(callback_query: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext, session: Any):
|
||||
async def handle_create_key_select_cluster(
|
||||
callback_query: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext, session: Any
|
||||
):
|
||||
tg_id = callback_data.tg_id
|
||||
await state.update_data(tg_id=tg_id)
|
||||
|
||||
@@ -806,7 +817,9 @@ async def handle_create_key_select_cluster(callback_query: CallbackQuery, callba
|
||||
cluster_names = list(servers.keys())
|
||||
|
||||
if not cluster_names:
|
||||
await callback_query.message.edit_text("❌ Нет доступных кластеров для создания ключа.", reply_markup=build_editor_kb(tg_id))
|
||||
await callback_query.message.edit_text(
|
||||
"❌ Нет доступных кластеров для создания ключа.", reply_markup=build_editor_kb(tg_id)
|
||||
)
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -818,13 +831,14 @@ async def handle_create_key_select_cluster(callback_query: CallbackQuery, callba
|
||||
builder.row(build_admin_back_btn())
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
"🌐 <b>Выберите кластер для создания ключа:</b>",
|
||||
reply_markup=builder.as_markup()
|
||||
"🌐 <b>Выберите кластер для создания ключа:</b>", reply_markup=builder.as_markup()
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminUserEditorCallback.filter(F.action == "users_create_key_cluster"), IsAdminFilter())
|
||||
async def handle_create_key_cluster(callback_query: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext):
|
||||
async def handle_create_key_cluster(
|
||||
callback_query: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext
|
||||
):
|
||||
tg_id = callback_data.tg_id
|
||||
cluster_name = callback_data.data
|
||||
|
||||
@@ -832,16 +846,12 @@ async def handle_create_key_cluster(callback_query: CallbackQuery, callback_data
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
for months, _ in RENEWAL_PRICES.items():
|
||||
builder.button(
|
||||
text=f"{months} мес.",
|
||||
callback_data=f"create_key_duration|{tg_id}|{cluster_name}|{months}"
|
||||
)
|
||||
builder.button(text=f"{months} мес.", callback_data=f"create_key_duration|{tg_id}|{cluster_name}|{months}")
|
||||
builder.adjust(1)
|
||||
builder.row(build_admin_back_btn())
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=f"🕒 <b>Выберите срок действия ключа для кластера {cluster_name}:</b>",
|
||||
reply_markup=builder.as_markup()
|
||||
text=f"🕒 <b>Выберите срок действия ключа для кластера {cluster_name}:</b>", reply_markup=builder.as_markup()
|
||||
)
|
||||
|
||||
|
||||
@@ -874,14 +884,13 @@ async def handle_create_key_duration(callback_query: CallbackQuery, session: Any
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
f"✅ Ключ успешно создан в кластере <b>{cluster_name}</b> на {months} мес.!",
|
||||
reply_markup=build_editor_kb(tg_id)
|
||||
reply_markup=build_editor_kb(tg_id),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании ключа: {e}")
|
||||
await callback_query.message.edit_text(
|
||||
"❌ Не удалось создать ключ. Попробуйте позже.",
|
||||
reply_markup=build_editor_kb(tg_id)
|
||||
"❌ Не удалось создать ключ. Попробуйте позже.", reply_markup=build_editor_kb(tg_id)
|
||||
)
|
||||
|
||||
|
||||
@@ -897,10 +906,7 @@ async def handle_reset_traffic(callback_query: CallbackQuery, callback_data: Adm
|
||||
)
|
||||
|
||||
if not record:
|
||||
await callback_query.message.edit_text(
|
||||
"❌ Ключ не найден в базе данных.",
|
||||
reply_markup=build_editor_kb(tg_id)
|
||||
)
|
||||
await callback_query.message.edit_text("❌ Ключ не найден в базе данных.", reply_markup=build_editor_kb(tg_id))
|
||||
return
|
||||
|
||||
cluster_id = record["server_id"]
|
||||
@@ -908,12 +914,10 @@ async def handle_reset_traffic(callback_query: CallbackQuery, callback_data: Adm
|
||||
try:
|
||||
await reset_traffic_in_cluster(cluster_id, email)
|
||||
await callback_query.message.edit_text(
|
||||
f"✅ Трафик для ключа <b>{email}</b> успешно сброшен.",
|
||||
reply_markup=build_editor_kb(tg_id)
|
||||
f"✅ Трафик для ключа <b>{email}</b> успешно сброшен.", reply_markup=build_editor_kb(tg_id)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при сбросе трафика: {e}")
|
||||
await callback_query.message.edit_text(
|
||||
"❌ Произошла ошибка при сбросе трафика. Попробуйте позже.",
|
||||
reply_markup=build_editor_kb(tg_id)
|
||||
)
|
||||
"❌ Произошла ошибка при сбросе трафика. Попробуйте позже.", reply_markup=build_editor_kb(tg_id)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# Основные кнопки меню:
|
||||
|
||||
MAIN_MENU = "👤 Личный кабинет"
|
||||
BACK = "⬅️ Назад"
|
||||
SUPPORT = "💬 Поддержка"
|
||||
CHANNEL = "📢 Канал"
|
||||
ABOUT_VPN = "💬 О сервисе"
|
||||
APPLY = "✅ Подтвердить"
|
||||
CANCEL = "❌ Отмена"
|
||||
|
||||
# Профиль
|
||||
|
||||
ADD_SUB = "➕ Подписка"
|
||||
MY_SUBS = "📱 Мои подписки"
|
||||
BALANCE = "💰 Баланс"
|
||||
INVITE = "👥 Пригласить"
|
||||
GIFTS = "🎁 Подарить"
|
||||
TARRIFS = "💡 Тарифы"
|
||||
INSTRUCTIONS = "📘 Инструкции"
|
||||
INVITE = "👥 Пригласить друга"
|
||||
TOP_FIVE = "🏆 Топ-5"
|
||||
|
||||
# Меню Оплат и баланса
|
||||
|
||||
BALANCE_HISTORY = "📊 История пополнения"
|
||||
PAYMENT = "💳 Пополнить баланс"
|
||||
COUPON = "🎟️ Активировать купон"
|
||||
YOOKASSA = "💳 ЮКасса: быстрая оплата"
|
||||
YOOMONEY = "💳 ЮМани: перевод по карте"
|
||||
CRYPTOBOT = "💰 CryptoBot: криптовалюта"
|
||||
STARS = "⭐ Оплата Звездами"
|
||||
ROBOKASSA = "⭐ RoboKassa"
|
||||
|
||||
|
||||
# Подарки
|
||||
|
||||
GIFT = "🎁 Подарить подписку"
|
||||
MY_GIFTS = "🎁 Мои подарки"
|
||||
GIFTS_MENU = "В меню подарков"
|
||||
SHARE_GIFT = "🎁 Поделиться подарком"
|
||||
APPLY = "✅ Подтвердить"
|
||||
CANCEL = "❌ Отмена"
|
||||
|
||||
# Создание и подключение ключей
|
||||
|
||||
DOWNLOAD_IOS_BUTTON = "🍏 Скачать iOS"
|
||||
DOWNLOAD_ANDROID_BUTTON = "🤖 Скачать Android"
|
||||
IMPORT_IOS = "🍏 Подключить"
|
||||
IMPORT_ANDROID = "🤖 Подключить"
|
||||
PC_BUTTON = "💻 Компьютеры"
|
||||
TV_BUTTON = "📺 Андроид TV"
|
||||
CONNECT_PHONE = "📱 Подключить телефон"
|
||||
CONNECT_DEVICE = "📲 Подключить устройство"
|
||||
CONNECT_WINDOWS_BUTTON = "💻 Подключить Windows"
|
||||
CONNECT_MACOS_BUTTON = "💻 Подключить MacOS"
|
||||
ALIAS = "✏️"
|
||||
UNFREEZE = "🟢 Разморозить подписку"
|
||||
FREEZE = "🛑 Заморозить подписку"
|
||||
RENEW = "⏳ Продлить"
|
||||
RENEW_FULL = "⏳ Продлить подписку"
|
||||
DELETE = "❌ Удалить"
|
||||
CHANGE_LOCATION = "🌍 Сменить локацию"
|
||||
QR = "📷 Показать QR-код"
|
||||
IPHONE = "🍏 Айфон"
|
||||
ANDROID = "🤖 Андроид"
|
||||
PC = "💻 Компьютер"
|
||||
TV = "📺 Телевизор"
|
||||
ROUTER = "📶 Роутер"
|
||||
MANUAL_INSTRUCTIONS = "📖 Ручная установка"
|
||||
RENEW_KEY = "🔄 Продлить подписку"
|
||||
TV_CONTINUE = "▶ Продолжить"
|
||||
TV_INSTRUCTIONS = "📖 Полная инструкция"
|
||||
|
||||
|
||||
# Кнопки касс
|
||||
|
||||
PAY = "Пополнить"
|
||||
PAY_2 = "Оплатить"
|
||||
CUSTOM_AMOUNT = "💰 Ввести свою сумму"
|
||||
PAY = "Пополнить"
|
||||
PAY_2 = "Оплатить"
|
||||
STARS_BOT = "🤖 Бот для покупки звезд"
|
||||
@@ -1,6 +0,0 @@
|
||||
DOWNLOAD_IOS_BUTTON = "🍏 Скачать iOS"
|
||||
DOWNLOAD_ANDROID_BUTTON = "🤖 Скачать Android"
|
||||
IMPORT_IOS = "🍏 Подключить"
|
||||
IMPORT_ANDROID = "🤖 Подключить"
|
||||
PC_BUTTON = "💻 Компьютеры"
|
||||
TV_BUTTON = "📺 Андроид TV"
|
||||
@@ -1,9 +0,0 @@
|
||||
GIFT = "🎁 Подарить подписку"
|
||||
MY_GIFTS = "🎁 Мои подарки"
|
||||
PROFILE = "👤 Личный кабинет"
|
||||
BACK = "В меню подарков"
|
||||
GIFTS_ABOUT = "<b>Дарите подарки и следите, чтобы они дошли до адресата! 🎄</b>"
|
||||
SHARE_GIFT = "🎁 Поделиться подарком"
|
||||
APPLY = "✅ Подтвердить"
|
||||
CANCEL = "❌ Отмена"
|
||||
NO_GIFTS = "У вас нет подарков."
|
||||
@@ -1,3 +0,0 @@
|
||||
RENEW_KEY = "🔄 Продлить подписку"
|
||||
ADD_KEY = "➕ Добавить подписку"
|
||||
PROFILE = "👤 Личный кабинет"
|
||||
@@ -1,10 +0,0 @@
|
||||
ADD_SUB = "➕ Подписка"
|
||||
MY_SUBS = "📱 Мои подписки"
|
||||
BALANCE = "💰 Баланс"
|
||||
BALANCE_HISTORY = "📊 История пополнений"
|
||||
PAYMENT = "💳 Пополнить баланс"
|
||||
INVITE = "👥 Пригласить"
|
||||
GIFTS = "🎁 Подарить"
|
||||
TARRIFS = "💡 Тарифы"
|
||||
INSTRUCTIONS = "📘 Инструкции"
|
||||
MAIN_MENU = "⬅️ Главное меню"
|
||||
@@ -1,7 +0,0 @@
|
||||
PAY = "Пополнить"
|
||||
PAY_2 = "Оплатить"
|
||||
BACK = "⬅️ Назад"
|
||||
CUSTOM_SUM = "💰 Ввести свою сумму"
|
||||
CUSTOM_SUM_ANSWER = "Пожалуйста, введите сумму пополнения."
|
||||
PROFILE = "👤 Личный кабинет"
|
||||
DEFAULT_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей. Перейдите по ссылке для оплаты:"
|
||||
+3
-1
@@ -1,5 +1,6 @@
|
||||
import random
|
||||
import secrets
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router
|
||||
@@ -8,11 +9,12 @@ 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
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
|
||||
+3
-2
@@ -13,6 +13,7 @@ from database import (
|
||||
update_balance,
|
||||
update_coupon_usage_count,
|
||||
)
|
||||
from handlers.buttons import MAIN_MENU
|
||||
from handlers.texts import (
|
||||
COUPON_ACTIVATED_SUCCESS_MSG,
|
||||
COUPON_ALREADY_USED_MSG,
|
||||
@@ -34,7 +35,7 @@ router = Router()
|
||||
@router.message(F.text == "/activate_coupon")
|
||||
async def handle_activate_coupon(callback_query_or_message: Message | CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
target_message = callback_query_or_message.message
|
||||
@@ -56,7 +57,7 @@ async def process_coupon_code(message: Message, state: FSMContext, session: Any)
|
||||
activation_result = await activate_coupon(message.chat.id, coupon_code, session)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await message.answer(activation_result, reply_markup=builder.as_markup())
|
||||
await state.clear()
|
||||
|
||||
+6
-4
@@ -5,7 +5,9 @@ from aiogram.types import CallbackQuery, InlineKeyboardButton, LabeledPrice, Mes
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import RUB_TO_XTR
|
||||
from handlers.buttons import BACK, MAIN_MENU
|
||||
from logger import logger
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
@@ -30,7 +32,7 @@ async def process_donate(callback_query: CallbackQuery, state: FSMContext):
|
||||
callback_data="enter_custom_donate_amount",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
text = (
|
||||
"🌟 Поддержите наш проект! 💪\n\n"
|
||||
@@ -48,7 +50,7 @@ async def process_donate(callback_query: CallbackQuery, state: FSMContext):
|
||||
@router.callback_query(F.data == "enter_custom_donate_amount")
|
||||
async def process_enter_donate_amount(callback_query: CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="donate"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="donate"))
|
||||
text = "💸 Введите сумму доната в рублях:"
|
||||
|
||||
await edit_or_send_message(
|
||||
@@ -74,7 +76,7 @@ async def process_donate_amount_input(message: Message, state: FSMContext):
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Задонатить", pay=True))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="donate"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="donate"))
|
||||
|
||||
await message.answer_invoice(
|
||||
title=f"Донат проекту {amount} рублей",
|
||||
@@ -102,7 +104,7 @@ async def on_successful_donate(message: Message, state: FSMContext):
|
||||
try:
|
||||
amount = float(message.successful_payment.invoice_payload.split("_")[0])
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
await message.answer(
|
||||
text=f"🙏 Спасибо за донат {amount} рублей! Ваша поддержка очень важна для нас. 💖",
|
||||
reply_markup=builder.as_markup(),
|
||||
|
||||
@@ -4,6 +4,7 @@ from aiogram import Router
|
||||
|
||||
from .instructions import router as instructions_router
|
||||
|
||||
|
||||
router = Router(name="instructions_main_router")
|
||||
|
||||
router.include_routers(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router
|
||||
@@ -11,6 +12,15 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import CONNECT_MACOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
|
||||
from database import get_key_details
|
||||
from handlers.buttons import (
|
||||
BACK,
|
||||
CONNECT_MACOS_BUTTON,
|
||||
CONNECT_WINDOWS_BUTTON,
|
||||
MAIN_MENU,
|
||||
SUPPORT,
|
||||
TV_CONTINUE,
|
||||
TV_INSTRUCTIONS,
|
||||
)
|
||||
from handlers.texts import (
|
||||
CONNECT_TV_TEXT,
|
||||
INSTRUCTIONS,
|
||||
@@ -20,6 +30,7 @@ from handlers.texts import (
|
||||
)
|
||||
from handlers.utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -30,8 +41,8 @@ async def send_instructions(callback_query_or_message: CallbackQuery | Message):
|
||||
image_path = os.path.join("img", "instructions.jpg")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
target_message = callback_query_or_message.message
|
||||
@@ -52,7 +63,7 @@ async def process_connect_pc(callback_query: CallbackQuery, session: Any):
|
||||
record = await get_key_details(key_name, session)
|
||||
if not record:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="❌ <b>Ключ не найден. Проверьте имя ключа.</b> 🔍",
|
||||
@@ -66,11 +77,11 @@ async def process_connect_pc(callback_query: CallbackQuery, session: Any):
|
||||
instruction_message = f"{key_message_text}{INSTRUCTION_PC}"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}"))
|
||||
builder.row(InlineKeyboardButton(text="💻 Подключить MacOS", url=f"{CONNECT_MACOS}{key}"))
|
||||
builder.row(InlineKeyboardButton(text="🆘 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_WINDOWS_BUTTON, url=f"{CONNECT_WINDOWS}{key}"))
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_MACOS_BUTTON, url=f"{CONNECT_MACOS}{key}"))
|
||||
builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
@@ -85,9 +96,9 @@ async def process_connect_tv(callback_query: CallbackQuery):
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="▶ Продолжить", callback_data=f"continue_tv|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=TV_CONTINUE, callback_data=f"continue_tv|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
@@ -107,9 +118,9 @@ async def process_continue_tv(callback_query: CallbackQuery, session: Any):
|
||||
message_text = SUBSCRIPTION_DETAILS_TEXT.format(subscription_link=subscription_link)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="📖 Полная инструкция", url="https://vpn4tv.com/quick-guide.html"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"connect_tv|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=TV_INSTRUCTIONS, url="https://vpn4tv.com/quick-guide.html"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"connect_tv|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message, text=message_text, reply_markup=builder.as_markup(), media_path=None
|
||||
|
||||
@@ -5,6 +5,7 @@ from aiogram import Router
|
||||
from .key_management import router as management_router
|
||||
from .keys import router as keys_router
|
||||
|
||||
|
||||
router = Router(name="keys_main_router")
|
||||
|
||||
router.include_routers(
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
import pytz
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from handlers.payments.yookassa_pay import process_custom_amount_input
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from bot import bot
|
||||
from panels.three_xui import delete_client
|
||||
from config import (
|
||||
ADMIN_PASSWORD,
|
||||
ADMIN_USERNAME,
|
||||
@@ -41,16 +41,23 @@ from database import (
|
||||
update_balance,
|
||||
update_trial,
|
||||
)
|
||||
from handlers.buttons.add_subscribe import (
|
||||
from handlers.buttons import (
|
||||
BACK,
|
||||
CONNECT_DEVICE,
|
||||
CONNECT_PHONE,
|
||||
DOWNLOAD_ANDROID_BUTTON,
|
||||
DOWNLOAD_IOS_BUTTON,
|
||||
IMPORT_ANDROID,
|
||||
IMPORT_IOS,
|
||||
MAIN_MENU,
|
||||
PAYMENT,
|
||||
PC_BUTTON,
|
||||
SUPPORT,
|
||||
TV_BUTTON,
|
||||
)
|
||||
from handlers.keys.key_utils import create_client_on_server, create_key_on_cluster
|
||||
from handlers.payments.robokassa_pay import handle_custom_amount_input
|
||||
from handlers.payments.yookassa_pay import process_custom_amount_input
|
||||
from handlers.texts import (
|
||||
CREATING_CONNECTION_MSG,
|
||||
DISCOUNTS,
|
||||
@@ -61,6 +68,8 @@ from handlers.texts import (
|
||||
)
|
||||
from handlers.utils import edit_or_send_message, generate_random_email, get_least_loaded_cluster
|
||||
from logger import logger
|
||||
from panels.three_xui import delete_client
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -124,7 +133,7 @@ async def handle_key_creation(
|
||||
callback_data=f"select_plan_{plan_id}",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
if isinstance(message_or_query, CallbackQuery):
|
||||
target_message = message_or_query.message
|
||||
@@ -171,8 +180,8 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
|
||||
await handle_custom_amount_input(callback_query, session)
|
||||
else:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=INSUFFICIENT_FUNDS_MSG.format(required_amount=required_amount),
|
||||
@@ -231,7 +240,7 @@ async def create_key(
|
||||
else:
|
||||
callback_data = f"select_country|{country}|{ts}"
|
||||
builder.row(InlineKeyboardButton(text=country, callback_data=callback_data))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
if target_message:
|
||||
await edit_or_send_message(
|
||||
@@ -293,9 +302,9 @@ async def create_key(
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL))
|
||||
if CONNECT_PHONE_BUTTON:
|
||||
builder.row(InlineKeyboardButton(text="📱 Подключить телефон", callback_data=f"connect_phone|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_PHONE, callback_data=f"connect_phone|{key_name}"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{email}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"),
|
||||
@@ -303,11 +312,11 @@ async def create_key(
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📲 Подключить устройство",
|
||||
text=CONNECT_DEVICE,
|
||||
callback_data=f"connect_device|{key_name}",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
expiry_time_local = expiry_time.replace(tzinfo=None).astimezone(moscow_tz)
|
||||
remaining_time = expiry_time_local - datetime.now(moscow_tz)
|
||||
@@ -354,7 +363,7 @@ async def change_location_callback(callback_query: CallbackQuery, session: Any):
|
||||
for country in countries:
|
||||
callback_data = f"select_country|{country}|{ts}|{old_key_name}"
|
||||
builder.row(InlineKeyboardButton(text=country, callback_data=callback_data))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{old_key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{old_key_name}"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
@@ -516,9 +525,9 @@ async def finalize_key_creation(
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL))
|
||||
if CONNECT_PHONE_BUTTON:
|
||||
builder.row(InlineKeyboardButton(text="📱 Подключить телефон", callback_data=f"connect_phone|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_PHONE, callback_data=f"connect_phone|{key_name}"))
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
|
||||
@@ -532,7 +541,7 @@ async def finalize_key_creation(
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{email}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
remaining_time = expiry_time - datetime.now(moscow_tz)
|
||||
days = remaining_time.days
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import asyncio
|
||||
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from panels.three_xui import ClientConfig, add_client, delete_client, extend_client_key, get_client_traffic, toggle_client
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, DATABASE_URL
|
||||
from database import get_servers, store_key
|
||||
from handlers.utils import get_least_loaded_cluster
|
||||
from logger import logger
|
||||
from panels.three_xui import (
|
||||
ClientConfig,
|
||||
add_client,
|
||||
delete_client,
|
||||
extend_client_key,
|
||||
get_client_traffic,
|
||||
toggle_client,
|
||||
)
|
||||
|
||||
|
||||
async def create_key_on_cluster(
|
||||
|
||||
+99
-86
@@ -2,19 +2,21 @@ import asyncio
|
||||
import locale
|
||||
import os
|
||||
import time
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from handlers.payments.yookassa_pay import process_custom_amount_input
|
||||
|
||||
import qrcode
|
||||
from io import BytesIO
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
@@ -27,12 +29,12 @@ from config import (
|
||||
ENABLE_DELETE_KEY_BUTTON,
|
||||
ENABLE_UPDATE_SUBSCRIPTION_BUTTON,
|
||||
PUBLIC_LINK,
|
||||
QRCODE,
|
||||
RENEWAL_PLANS,
|
||||
TOGGLE_CLIENT,
|
||||
TOTAL_GB,
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
TOGGLE_CLIENT,
|
||||
QRCODE
|
||||
)
|
||||
from database import (
|
||||
check_server_name_by_cluster,
|
||||
@@ -46,44 +48,65 @@ from database import (
|
||||
update_balance,
|
||||
update_key_expiry,
|
||||
)
|
||||
from handlers.buttons.add_subscribe import (
|
||||
from handlers.buttons import (
|
||||
ADD_SUB,
|
||||
ALIAS,
|
||||
ANDROID,
|
||||
APPLY,
|
||||
BACK,
|
||||
CANCEL,
|
||||
CHANGE_LOCATION,
|
||||
CONNECT_DEVICE,
|
||||
CONNECT_PHONE,
|
||||
DELETE,
|
||||
DOWNLOAD_ANDROID_BUTTON,
|
||||
DOWNLOAD_IOS_BUTTON,
|
||||
FREEZE,
|
||||
IMPORT_ANDROID,
|
||||
IMPORT_IOS,
|
||||
IPHONE,
|
||||
MAIN_MENU,
|
||||
MANUAL_INSTRUCTIONS,
|
||||
PAYMENT,
|
||||
PC,
|
||||
PC_BUTTON,
|
||||
QR,
|
||||
RENEW,
|
||||
RENEW_FULL,
|
||||
TV,
|
||||
TV_BUTTON,
|
||||
UNFREEZE,
|
||||
)
|
||||
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 (
|
||||
ANDROID_DESCRIPTION_TEMPLATE,
|
||||
DELETE_KEY_CONFIRM_MSG,
|
||||
DISCOUNTS,
|
||||
FREEZE_SUBSCRIPTION_CONFIRM_MSG,
|
||||
FROZEN_SUBSCRIPTION_MSG,
|
||||
INSUFFICIENT_FUNDS_RENEWAL_MSG,
|
||||
IOS_DESCRIPTION_TEMPLATE,
|
||||
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,
|
||||
ANDROID_DESCRIPTION_TEMPLATE,
|
||||
IOS_DESCRIPTION_TEMPLATE
|
||||
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
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
|
||||
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
|
||||
|
||||
@@ -139,7 +162,7 @@ def build_keys_response(records):
|
||||
formatted_date_full = "без срока действия"
|
||||
|
||||
key_button = InlineKeyboardButton(text=f"🔑 {key_display}", callback_data=f"view_key|{email}")
|
||||
rename_button = InlineKeyboardButton(text="✏️", callback_data=f"rename_key|{client_id}")
|
||||
rename_button = InlineKeyboardButton(text=ALIAS, callback_data=f"rename_key|{client_id}")
|
||||
builder.row(key_button, rename_button)
|
||||
|
||||
response_message += f"• <b>{key_display}</b> ({formatted_date_full})\n"
|
||||
@@ -148,8 +171,8 @@ def build_keys_response(records):
|
||||
else:
|
||||
response_message = NO_SUBSCRIPTIONS_MSG
|
||||
|
||||
builder.row(InlineKeyboardButton(text="➕ Добавить подписку", callback_data="create_key"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=ADD_SUB, callback_data="create_key"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
inline_keyboard = builder.as_markup()
|
||||
return inline_keyboard, response_message
|
||||
@@ -163,9 +186,7 @@ async def handle_rename_key(callback: CallbackQuery, state: FSMContext):
|
||||
await state.update_data(client_id=client_id, target_message=callback.message)
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback.message,
|
||||
text="✏️ Введите новое имя подписки (до 10 символов):",
|
||||
reply_markup=None
|
||||
target_message=callback.message, text="✏️ Введите новое имя подписки (до 10 символов):", reply_markup=None
|
||||
)
|
||||
|
||||
|
||||
@@ -211,12 +232,12 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🟢 Разморозить подписку",
|
||||
text=UNFREEZE,
|
||||
callback_data=f"unfreeze_subscription|{key_name}",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="view_keys"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
keyboard = builder.as_markup()
|
||||
image_path = os.path.join("img", "pic_view.jpg")
|
||||
@@ -272,53 +293,49 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
|
||||
if CONNECT_PHONE_BUTTON:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📱 Подключить телефон",
|
||||
text=CONNECT_PHONE,
|
||||
callback_data=f"connect_phone|{key_name}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"),
|
||||
)
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📲 Подключить устройство",
|
||||
text=CONNECT_DEVICE,
|
||||
callback_data=f"connect_device|{key_name}",
|
||||
)
|
||||
)
|
||||
if QRCODE:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📷 Показать QR-код",
|
||||
text=QR,
|
||||
callback_data=f"show_qr|{key}",
|
||||
)
|
||||
)
|
||||
if ENABLE_DELETE_KEY_BUTTON:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}"),
|
||||
InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete_key|{key_name}"),
|
||||
InlineKeyboardButton(text=RENEW, callback_data=f"renew_key|{key_name}"),
|
||||
InlineKeyboardButton(text=DELETE, callback_data=f"delete_key|{key_name}"),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⏳ Продлить подписку", callback_data=f"renew_key|{key_name}")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=RENEW_FULL, callback_data=f"renew_key|{key_name}"))
|
||||
|
||||
if USE_COUNTRY_SELECTION:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🌍 Сменить локацию", callback_data=f"change_location|{key_name}")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=CHANGE_LOCATION, callback_data=f"change_location|{key_name}"))
|
||||
|
||||
if TOGGLE_CLIENT:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🛑 Заморозить подписку",
|
||||
text=FREEZE,
|
||||
callback_data=f"freeze_subscription|{key_name}",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="view_keys"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
keyboard = builder.as_markup()
|
||||
image_path = os.path.join("img", "pic_view.jpg")
|
||||
@@ -371,8 +388,8 @@ async def show_qr_code(callback_query: types.CallbackQuery, session: Any):
|
||||
f.write(buffer.read())
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{record['email']}"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{record['email']}"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
@@ -388,20 +405,18 @@ async def show_qr_code(callback_query: types.CallbackQuery, session: Any):
|
||||
await callback_query.message.answer("❌ Произошла ошибка при создании QR-кода.")
|
||||
|
||||
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("connect_device|"))
|
||||
async def handle_connect_device(callback_query: CallbackQuery):
|
||||
try:
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🍏 Айфон", callback_data=f"connect_ios|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="🤖 Андроид", callback_data=f"connect_android|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="💻 Компьютер", callback_data=f"connect_pc|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="📺 Телевизор", callback_data=f"connect_tv|{key_name}"))
|
||||
# builder.row(InlineKeyboardButton(text="📶 Роутер", callback_data=f"connect_router|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=IPHONE, callback_data=f"connect_ios|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=ANDROID, callback_data=f"connect_android|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=PC, callback_data=f"connect_pc|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=TV, callback_data=f"connect_tv|{key_name}"))
|
||||
# builder.row(InlineKeyboardButton(text=ROUTER, callback_data=f"connect_router|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{key_name}"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
@@ -422,11 +437,11 @@ async def process_callback_unfreeze_subscription(callback_query: CallbackQuery,
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="✅ Подтвердить",
|
||||
text=APPLY,
|
||||
callback_data=f"unfreeze_subscription_confirm|{key_name}",
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="❌ Отмена",
|
||||
text=CANCEL,
|
||||
callback_data=f"view_key|{key_name}",
|
||||
),
|
||||
)
|
||||
@@ -486,7 +501,7 @@ async def process_callback_unfreeze_subscription_confirm(callback_query: Callbac
|
||||
)
|
||||
text_ok = SUBSCRIPTION_UNFROZEN_MSG
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{key_name}"))
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=text_ok,
|
||||
@@ -497,7 +512,7 @@ async def process_callback_unfreeze_subscription_confirm(callback_query: Callbac
|
||||
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=BACK, callback_data=f"view_key|{key_name}"))
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=text_error,
|
||||
@@ -513,7 +528,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
|
||||
@@ -521,11 +535,11 @@ async def process_callback_freeze_subscription(callback_query: CallbackQuery, se
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="✅ Подтвердить",
|
||||
text=APPLY,
|
||||
callback_data=f"freeze_subscription_confirm|{key_name}",
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="❌ Отмена",
|
||||
text=CANCEL,
|
||||
callback_data=f"view_key|{key_name}",
|
||||
),
|
||||
)
|
||||
@@ -563,7 +577,7 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ
|
||||
if time_left < 0:
|
||||
time_left = 0
|
||||
|
||||
update_result = await session.execute(
|
||||
await session.execute(
|
||||
"""
|
||||
UPDATE keys
|
||||
SET expiry_time = $1,
|
||||
@@ -578,7 +592,7 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ
|
||||
|
||||
text_ok = SUBSCRIPTION_FROZEN_MSG
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{key_name}"))
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=text_ok,
|
||||
@@ -589,7 +603,7 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ
|
||||
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=BACK, callback_data=f"view_key|{key_name}"))
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=text_error,
|
||||
@@ -638,8 +652,8 @@ async def process_callback_connect_phone(callback_query: CallbackQuery):
|
||||
InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{key_link}"),
|
||||
InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key_link}"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📖 Ручная установка", callback_data="instructions"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{email}"))
|
||||
builder.row(InlineKeyboardButton(text=MANUAL_INSTRUCTIONS, callback_data="instructions"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{email}"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message, text=description, reply_markup=builder.as_markup(), media_path=None
|
||||
@@ -670,12 +684,11 @@ async def process_callback_connect_ios(callback_query: CallbackQuery):
|
||||
|
||||
description = IOS_DESCRIPTION_TEMPLATE.format(key_link=key_link)
|
||||
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS))
|
||||
builder.row(InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{key_link}"))
|
||||
builder.row(InlineKeyboardButton(text="📖 Ручная установка", callback_data="instructions"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MANUAL_INSTRUCTIONS, callback_data="instructions"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
@@ -712,8 +725,8 @@ async def process_callback_connect_android(callback_query: CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID))
|
||||
builder.row(InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key_link}"))
|
||||
builder.row(InlineKeyboardButton(text="📖 Ручная установка", callback_data="instructions"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MANUAL_INSTRUCTIONS, callback_data="instructions"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
@@ -750,11 +763,11 @@ async def process_callback_delete_key(callback_query: CallbackQuery):
|
||||
inline_keyboard=[
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="✅ Да, удалить",
|
||||
text=APPLY,
|
||||
callback_data=f"confirm_delete|{client_id}",
|
||||
)
|
||||
],
|
||||
[types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="view_keys")],
|
||||
[types.InlineKeyboardButton(text=CANCEL, callback_data="view_keys")],
|
||||
]
|
||||
)
|
||||
|
||||
@@ -777,7 +790,7 @@ async def process_callback_confirm_delete(callback_query: CallbackQuery, session
|
||||
if record:
|
||||
client_id = record["client_id"]
|
||||
response_message = KEY_DELETED_MSG_SIMPLE
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
back_button = types.InlineKeyboardButton(text=BACK, callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await delete_key(client_id, session)
|
||||
@@ -802,7 +815,7 @@ async def process_callback_confirm_delete(callback_query: CallbackQuery, session
|
||||
await delete_key(client_id, session)
|
||||
else:
|
||||
response_message = "Ключ не найден или уже удален."
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
back_button = types.InlineKeyboardButton(text=BACK, callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message, text=response_message, reply_markup=keyboard, media_path=None
|
||||
@@ -837,7 +850,7 @@ async def process_callback_renew_key(callback_query: CallbackQuery, session: Any
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="view_keys"))
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
@@ -914,8 +927,8 @@ async def process_callback_renew_plan(callback_query: CallbackQuery, session: An
|
||||
else:
|
||||
logger.info(f"[RENEW] Отправка сообщения о доплате пользователю {tg_id}")
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
@@ -946,7 +959,7 @@ async def complete_key_renewal(tg_id, client_id, email, new_expiry_time, total_g
|
||||
response_message = SUCCESS_RENEWAL_MSG.format(months=plan)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
if callback_query:
|
||||
try:
|
||||
|
||||
@@ -4,11 +4,13 @@ import random
|
||||
import re
|
||||
import time
|
||||
import urllib.parse
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
import pytz
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from config import (
|
||||
|
||||
@@ -5,6 +5,7 @@ from aiogram import Router
|
||||
from .general_notifications import router as general_notifications_router
|
||||
from .special_notifications import router as special_notifications_router
|
||||
|
||||
|
||||
router = Router(name="notifications_main_router")
|
||||
|
||||
router.include_routers(general_notifications_router, special_notifications_router)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import asyncio
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
|
||||
from aiogram import Bot, Router
|
||||
|
||||
from config import (
|
||||
@@ -10,13 +12,13 @@ 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,
|
||||
@@ -29,22 +31,24 @@ from database import (
|
||||
update_key_expiry,
|
||||
)
|
||||
from handlers.keys.key_utils import delete_key_from_cluster, renew_key_in_cluster
|
||||
from handlers.notifications.notify_kb import build_notification_expired_kb, build_notification_kb
|
||||
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 handlers.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")
|
||||
@@ -274,7 +278,9 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
tg_id,
|
||||
notification_id,
|
||||
)
|
||||
logger.info(f"⛔ Уведомление {notification_id} для {tg_id} удалено (прошло больше половины задержки).")
|
||||
logger.info(
|
||||
f"⛔ Уведомление {notification_id} для {tg_id} удалено (прошло больше половины задержки)."
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении уведомления: {e}")
|
||||
continue
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
|
||||
from handlers.buttons import MAIN_MENU, RENEW_KEY
|
||||
|
||||
|
||||
def build_notification_kb(email: str) -> InlineKeyboardMarkup:
|
||||
"""
|
||||
@@ -9,8 +11,8 @@ def build_notification_kb(email: str) -> InlineKeyboardMarkup:
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔄 Продлить VPN", callback_data=f"renew_key|{email}")
|
||||
builder.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
builder.button(text=RENEW_KEY, callback_data=f"renew_key|{email}")
|
||||
builder.button(text=MAIN_MENU, callback_data="profile")
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -23,5 +25,5 @@ def build_notification_expired_kb() -> InlineKeyboardMarkup:
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
builder.button(text=MAIN_MENU, callback_data="profile")
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import os
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
import aiofiles
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.exceptions import TelegramForbiddenError, TelegramRetryAfter
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardMarkup
|
||||
@@ -26,6 +27,7 @@ def rate_limited_send(func):
|
||||
tg_id = kwargs.get("tg_id") or args[1]
|
||||
logger.error(f"❌ Ошибка отправки сообщения пользователю {tg_id}: {e}")
|
||||
return False
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import asyncio
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
|
||||
from aiogram import Bot, Router, types
|
||||
from aiogram.exceptions import TelegramForbiddenError
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
@@ -13,10 +15,12 @@ from database import (
|
||||
check_notification_time,
|
||||
create_blocked_user,
|
||||
)
|
||||
from handlers.buttons import MAIN_MENU
|
||||
from handlers.keys.key_utils import get_user_traffic
|
||||
from handlers.texts import TRIAL_INACTIVE_FIRST_MSG, TRIAL_INACTIVE_BONUS_MSG, ZERO_TRAFFIC_MSG
|
||||
from handlers.texts import TRIAL_INACTIVE_BONUS_MSG, TRIAL_INACTIVE_FIRST_MSG, ZERO_TRAFFIC_MSG
|
||||
from logger import logger
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
@@ -65,7 +69,7 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
callback_data="create_key",
|
||||
)
|
||||
)
|
||||
builder.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(types.InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
keyboard = builder.as_markup()
|
||||
|
||||
trial_extended = await conn.fetchval(
|
||||
@@ -165,7 +169,7 @@ async def notify_users_no_traffic(bot: Bot, conn: asyncpg.Connection, current_ti
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(types.InlineKeyboardButton(text="🔧 Написать в поддержку", url=SUPPORT_CHAT_URL))
|
||||
builder.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(types.InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
keyboard = builder.as_markup()
|
||||
|
||||
message = ZERO_TRAFFIC_MSG.format(email=email)
|
||||
|
||||
+8
-7
@@ -10,11 +10,12 @@ from config import (
|
||||
YOOKASSA_ENABLE,
|
||||
YOOMONEY_ENABLE,
|
||||
)
|
||||
|
||||
from handlers.buttons import CRYPTOBOT, MAIN_MENU, ROBOKASSA, STARS, YOOKASSA, YOOMONEY
|
||||
from handlers.texts import PAYMENT_METHODS_MSG
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -25,41 +26,41 @@ async def handle_pay(callback_query: CallbackQuery):
|
||||
if YOOKASSA_ENABLE:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💳 ЮКасса: быстрая оплата",
|
||||
text=YOOKASSA,
|
||||
callback_data="pay_yookassa",
|
||||
)
|
||||
)
|
||||
if YOOMONEY_ENABLE:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💳 ЮМани: перевод по карте",
|
||||
text=YOOMONEY,
|
||||
callback_data="pay_yoomoney",
|
||||
)
|
||||
)
|
||||
if CRYPTO_BOT_ENABLE:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💰 CryptoBot: криптовалюта",
|
||||
text=CRYPTOBOT,
|
||||
callback_data="pay_cryptobot",
|
||||
)
|
||||
)
|
||||
if STARS_ENABLE:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="⭐ Оплата Звездами",
|
||||
text=STARS,
|
||||
callback_data="pay_stars",
|
||||
)
|
||||
)
|
||||
if ROBOKASSA_ENABLE:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="⭐ RoboKassa",
|
||||
text=ROBOKASSA,
|
||||
callback_data="pay_robokassa",
|
||||
)
|
||||
)
|
||||
if DONATIONS_ENABLE:
|
||||
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from aiogram import Router
|
||||
|
||||
from config import (
|
||||
CRYPTO_BOT_ENABLE,
|
||||
ROBOKASSA_ENABLE,
|
||||
@@ -9,6 +8,7 @@ from config import (
|
||||
YOOKASSA_ENABLE,
|
||||
YOOMONEY_ENABLE,
|
||||
)
|
||||
|
||||
from .cryprobot_pay import router as cryprobot_router
|
||||
from .gift import router as gift_router
|
||||
from .robokassa_pay import router as robokassa_router
|
||||
|
||||
+1428
-1392
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+2432
-1878
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -8,9 +8,6 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiohttp import web
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
from robokassa import HashAlgorithm, Robokassa
|
||||
|
||||
from config import (
|
||||
DATABASE_URL,
|
||||
ROBOKASSA_ENABLE,
|
||||
@@ -19,6 +16,8 @@ from config import (
|
||||
ROBOKASSA_PASSWORD2,
|
||||
ROBOKASSA_TEST_MODE,
|
||||
)
|
||||
from robokassa import HashAlgorithm, Robokassa
|
||||
|
||||
from database import (
|
||||
add_connection,
|
||||
add_payment,
|
||||
@@ -27,10 +26,13 @@ from database import (
|
||||
get_temporary_data,
|
||||
update_balance,
|
||||
)
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
from handlers.texts import PAYMENT_OPTIONS, ENTER_SUM, DEFAULT_PAYMENT_MESSAGE
|
||||
from handlers.utils import edit_or_send_message
|
||||
from logger import logger
|
||||
|
||||
from handlers.buttons import BACK, PAY_2
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -89,7 +91,7 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="pay"))
|
||||
|
||||
key_count = await get_key_count(tg_id)
|
||||
|
||||
@@ -151,14 +153,14 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
|
||||
confirm_keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Оплатить", url=payment_url)],
|
||||
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay_robokassa")],
|
||||
[InlineKeyboardButton(text=PAY_2, url=payment_url)],
|
||||
[InlineKeyboardButton(text=BACK, callback_data="pay_robokassa")],
|
||||
]
|
||||
)
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=f"Вы выбрали пополнение на {amount} рублей. Для оплаты перейдите по ссылке ниже:",
|
||||
text=DEFAULT_PAYMENT_MESSAGE.format(amount=amount),
|
||||
reply_markup=confirm_keyboard,
|
||||
force_text=True,
|
||||
)
|
||||
@@ -230,11 +232,11 @@ async def process_custom_amount_selection(callback_query: types.CallbackQuery, s
|
||||
logger.info(f"User {tg_id} chose to enter a custom amount.")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_robokassa"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="pay_robokassa"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="Пожалуйста, введите сумму пополнения.",
|
||||
text=ENTER_SUM,
|
||||
reply_markup=builder.as_markup(),
|
||||
force_text=True,
|
||||
)
|
||||
@@ -285,7 +287,7 @@ async def handle_custom_amount_input(
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💳 Оплатить", url=payment_url))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay_robokassa"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="pay_robokassa"))
|
||||
|
||||
if state_type == "waiting_for_payment":
|
||||
message_text = (
|
||||
|
||||
+1327
-1296
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+32
-32
@@ -2410,9 +2410,8 @@ static const char __pyx_k_enable[] = "enable";
|
||||
static const char __pyx_k_format[] = "format";
|
||||
static const char __pyx_k_import[] = "__import__";
|
||||
static const char __pyx_k_logger[] = "logger";
|
||||
static const char __pyx_k_ADD_KEY[] = "ADD_KEY";
|
||||
static const char __pyx_k_ADD_SUB[] = "ADD_SUB";
|
||||
static const char __pyx_k_PAYMENT[] = "[PAYMENT] \320\237\320\273\320\260\321\202\320\265\320\266 ";
|
||||
static const char __pyx_k_PROFILE[] = "PROFILE";
|
||||
static const char __pyx_k_asyncpg[] = "asyncpg";
|
||||
static const char __pyx_k_balance[] = "balance";
|
||||
static const char __pyx_k_builder[] = "builder";
|
||||
@@ -2428,6 +2427,7 @@ static const char __pyx_k_database[] = "database";
|
||||
static const char __pyx_k_datetime[] = "datetime";
|
||||
static const char __pyx_k_timezone[] = "timezone";
|
||||
static const char __pyx_k_total_gb[] = "total_gb";
|
||||
static const char __pyx_k_MAIN_MENU[] = "MAIN_MENU";
|
||||
static const char __pyx_k_RENEW_KEY[] = "RENEW_KEY";
|
||||
static const char __pyx_k_as_markup[] = "as_markup";
|
||||
static const char __pyx_k_client_id[] = "client_id";
|
||||
@@ -2456,6 +2456,7 @@ static const char __pyx_k_update_balance[] = "update_balance";
|
||||
static const char __pyx_k_cashback_amount[] = "cashback_amount";
|
||||
static const char __pyx_k_new_expiry_time[] = "new_expiry_time";
|
||||
static const char __pyx_k_required_amount[] = "required_amount";
|
||||
static const char __pyx_k_handlers_buttons[] = "handlers.buttons";
|
||||
static const char __pyx_k_asyncio_coroutines[] = "asyncio.coroutines";
|
||||
static const char __pyx_k_cline_in_traceback[] = "cline_in_traceback";
|
||||
static const char __pyx_k_get_temporary_data[] = "get_temporary_data";
|
||||
@@ -2472,7 +2473,6 @@ static const char __pyx_k_handlers_payments_utils[] = "handlers.payments.utils";
|
||||
static const char __pyx_k_handlers_payments_utils_py[] = "handlers/payments/utils.py";
|
||||
static const char __pyx_k_waiting_for_renewal_payment[] = "waiting_for_renewal_payment";
|
||||
static const char __pyx_k_handlers_keys_key_management[] = "handlers.keys.key_management";
|
||||
static const char __pyx_k_handlers_buttons_notification[] = "handlers.buttons.notification";
|
||||
static const char __pyx_k_send_payment_success_notificatio[] = "send_payment_success_notification";
|
||||
/* #### Code section: decls ### */
|
||||
static PyObject *__pyx_pf_8handlers_8payments_5utils_send_payment_success_notification(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_user_id, double __pyx_v_amount); /* proto */
|
||||
@@ -2508,15 +2508,15 @@ typedef struct {
|
||||
PyObject *__pyx_type_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification;
|
||||
#endif
|
||||
PyTypeObject *__pyx_ptype_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification;
|
||||
PyObject *__pyx_n_s_ADD_KEY;
|
||||
PyObject *__pyx_n_s_ADD_SUB;
|
||||
PyObject *__pyx_n_s_CASHBACK;
|
||||
PyObject *__pyx_n_s_DATABASE_URL;
|
||||
PyObject *__pyx_kp_u_Europe_Moscow;
|
||||
PyObject *__pyx_n_s_InlineKeyboardBuilder;
|
||||
PyObject *__pyx_n_s_InlineKeyboardButton;
|
||||
PyObject *__pyx_n_s_MAIN_MENU;
|
||||
PyObject *__pyx_kp_u_PAYMENT;
|
||||
PyObject *__pyx_n_s_PAYMENT_SUCCESS_MESSAGE;
|
||||
PyObject *__pyx_n_s_PROFILE;
|
||||
PyObject *__pyx_kp_u_RENEW;
|
||||
PyObject *__pyx_n_s_RENEW_KEY;
|
||||
PyObject *__pyx_n_s_USE_NEW_PAYMENT_FLOW;
|
||||
@@ -2607,7 +2607,7 @@ typedef struct {
|
||||
PyObject *__pyx_n_s_get;
|
||||
PyObject *__pyx_n_s_get_balance;
|
||||
PyObject *__pyx_n_s_get_temporary_data;
|
||||
PyObject *__pyx_n_s_handlers_buttons_notification;
|
||||
PyObject *__pyx_n_s_handlers_buttons;
|
||||
PyObject *__pyx_n_s_handlers_keys_key_management;
|
||||
PyObject *__pyx_n_s_handlers_keys_keys;
|
||||
PyObject *__pyx_n_s_handlers_payments_utils;
|
||||
@@ -2709,15 +2709,15 @@ static int __pyx_m_clear(PyObject *m) {
|
||||
#endif
|
||||
Py_CLEAR(clear_module_state->__pyx_ptype_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification);
|
||||
Py_CLEAR(clear_module_state->__pyx_type_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_ADD_KEY);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_ADD_SUB);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_CASHBACK);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_DATABASE_URL);
|
||||
Py_CLEAR(clear_module_state->__pyx_kp_u_Europe_Moscow);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_InlineKeyboardBuilder);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_InlineKeyboardButton);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_MAIN_MENU);
|
||||
Py_CLEAR(clear_module_state->__pyx_kp_u_PAYMENT);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_PAYMENT_SUCCESS_MESSAGE);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_PROFILE);
|
||||
Py_CLEAR(clear_module_state->__pyx_kp_u_RENEW);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_RENEW_KEY);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_USE_NEW_PAYMENT_FLOW);
|
||||
@@ -2808,7 +2808,7 @@ static int __pyx_m_clear(PyObject *m) {
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_get);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_get_balance);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_get_temporary_data);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_handlers_buttons_notification);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_handlers_buttons);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_handlers_keys_key_management);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_handlers_keys_keys);
|
||||
Py_CLEAR(clear_module_state->__pyx_n_s_handlers_payments_utils);
|
||||
@@ -2888,15 +2888,15 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
|
||||
#endif
|
||||
Py_VISIT(traverse_module_state->__pyx_ptype_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification);
|
||||
Py_VISIT(traverse_module_state->__pyx_type_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_ADD_KEY);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_ADD_SUB);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_CASHBACK);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_DATABASE_URL);
|
||||
Py_VISIT(traverse_module_state->__pyx_kp_u_Europe_Moscow);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_InlineKeyboardBuilder);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_InlineKeyboardButton);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_MAIN_MENU);
|
||||
Py_VISIT(traverse_module_state->__pyx_kp_u_PAYMENT);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_PAYMENT_SUCCESS_MESSAGE);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_PROFILE);
|
||||
Py_VISIT(traverse_module_state->__pyx_kp_u_RENEW);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_RENEW_KEY);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_USE_NEW_PAYMENT_FLOW);
|
||||
@@ -2987,7 +2987,7 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_get);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_get_balance);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_get_temporary_data);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_handlers_buttons_notification);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_handlers_buttons);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_handlers_keys_key_management);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_handlers_keys_keys);
|
||||
Py_VISIT(traverse_module_state->__pyx_n_s_handlers_payments_utils);
|
||||
@@ -3077,15 +3077,15 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
|
||||
#define __pyx_type_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification __pyx_mstate_global->__pyx_type_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification
|
||||
#endif
|
||||
#define __pyx_ptype_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification __pyx_mstate_global->__pyx_ptype_8handlers_8payments_5utils___pyx_scope_struct__send_payment_success_notification
|
||||
#define __pyx_n_s_ADD_KEY __pyx_mstate_global->__pyx_n_s_ADD_KEY
|
||||
#define __pyx_n_s_ADD_SUB __pyx_mstate_global->__pyx_n_s_ADD_SUB
|
||||
#define __pyx_n_s_CASHBACK __pyx_mstate_global->__pyx_n_s_CASHBACK
|
||||
#define __pyx_n_s_DATABASE_URL __pyx_mstate_global->__pyx_n_s_DATABASE_URL
|
||||
#define __pyx_kp_u_Europe_Moscow __pyx_mstate_global->__pyx_kp_u_Europe_Moscow
|
||||
#define __pyx_n_s_InlineKeyboardBuilder __pyx_mstate_global->__pyx_n_s_InlineKeyboardBuilder
|
||||
#define __pyx_n_s_InlineKeyboardButton __pyx_mstate_global->__pyx_n_s_InlineKeyboardButton
|
||||
#define __pyx_n_s_MAIN_MENU __pyx_mstate_global->__pyx_n_s_MAIN_MENU
|
||||
#define __pyx_kp_u_PAYMENT __pyx_mstate_global->__pyx_kp_u_PAYMENT
|
||||
#define __pyx_n_s_PAYMENT_SUCCESS_MESSAGE __pyx_mstate_global->__pyx_n_s_PAYMENT_SUCCESS_MESSAGE
|
||||
#define __pyx_n_s_PROFILE __pyx_mstate_global->__pyx_n_s_PROFILE
|
||||
#define __pyx_kp_u_RENEW __pyx_mstate_global->__pyx_kp_u_RENEW
|
||||
#define __pyx_n_s_RENEW_KEY __pyx_mstate_global->__pyx_n_s_RENEW_KEY
|
||||
#define __pyx_n_s_USE_NEW_PAYMENT_FLOW __pyx_mstate_global->__pyx_n_s_USE_NEW_PAYMENT_FLOW
|
||||
@@ -3176,7 +3176,7 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
|
||||
#define __pyx_n_s_get __pyx_mstate_global->__pyx_n_s_get
|
||||
#define __pyx_n_s_get_balance __pyx_mstate_global->__pyx_n_s_get_balance
|
||||
#define __pyx_n_s_get_temporary_data __pyx_mstate_global->__pyx_n_s_get_temporary_data
|
||||
#define __pyx_n_s_handlers_buttons_notification __pyx_mstate_global->__pyx_n_s_handlers_buttons_notification
|
||||
#define __pyx_n_s_handlers_buttons __pyx_mstate_global->__pyx_n_s_handlers_buttons
|
||||
#define __pyx_n_s_handlers_keys_key_management __pyx_mstate_global->__pyx_n_s_handlers_keys_key_management
|
||||
#define __pyx_n_s_handlers_keys_keys __pyx_mstate_global->__pyx_n_s_handlers_keys_keys
|
||||
#define __pyx_n_s_handlers_payments_utils __pyx_mstate_global->__pyx_n_s_handlers_payments_utils
|
||||
@@ -3752,7 +3752,7 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
__Pyx_GOTREF(__pyx_t_6);
|
||||
__pyx_t_4 = __Pyx_PyDict_NewPresized(2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 35, __pyx_L4_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_PROFILE); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 35, __pyx_L4_error)
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_MAIN_MENU); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 35, __pyx_L4_error)
|
||||
__Pyx_GOTREF(__pyx_t_5);
|
||||
if (PyDict_SetItem(__pyx_t_4, __pyx_n_s_text, __pyx_t_5) < 0) __PYX_ERR(0, 35, __pyx_L4_error)
|
||||
__Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
|
||||
@@ -5569,7 +5569,7 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
__Pyx_GOTREF(__pyx_t_6);
|
||||
__pyx_t_10 = __Pyx_PyDict_NewPresized(2); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 100, __pyx_L16_error)
|
||||
__Pyx_GOTREF(__pyx_t_10);
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_9, __pyx_n_s_ADD_KEY); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 100, __pyx_L16_error)
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_9, __pyx_n_s_ADD_SUB); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 100, __pyx_L16_error)
|
||||
__Pyx_GOTREF(__pyx_t_9);
|
||||
if (PyDict_SetItem(__pyx_t_10, __pyx_n_s_text, __pyx_t_9) < 0) __PYX_ERR(0, 100, __pyx_L16_error)
|
||||
__Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
|
||||
@@ -6390,7 +6390,7 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
__Pyx_GOTREF(__pyx_t_14);
|
||||
__pyx_t_5 = __Pyx_PyDict_NewPresized(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 118, __pyx_L4_error)
|
||||
__Pyx_GOTREF(__pyx_t_5);
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_ADD_KEY); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 118, __pyx_L4_error)
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_ADD_SUB); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 118, __pyx_L4_error)
|
||||
__Pyx_GOTREF(__pyx_t_6);
|
||||
if (PyDict_SetItem(__pyx_t_5, __pyx_n_s_text, __pyx_t_6) < 0) __PYX_ERR(0, 118, __pyx_L4_error)
|
||||
__Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
|
||||
@@ -7086,15 +7086,15 @@ static PyMethodDef __pyx_methods[] = {
|
||||
|
||||
static int __Pyx_CreateStringTabAndInitStrings(void) {
|
||||
__Pyx_StringTabEntry __pyx_string_tab[] = {
|
||||
{&__pyx_n_s_ADD_KEY, __pyx_k_ADD_KEY, sizeof(__pyx_k_ADD_KEY), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_ADD_SUB, __pyx_k_ADD_SUB, sizeof(__pyx_k_ADD_SUB), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_CASHBACK, __pyx_k_CASHBACK, sizeof(__pyx_k_CASHBACK), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_DATABASE_URL, __pyx_k_DATABASE_URL, sizeof(__pyx_k_DATABASE_URL), 0, 0, 1, 1},
|
||||
{&__pyx_kp_u_Europe_Moscow, __pyx_k_Europe_Moscow, sizeof(__pyx_k_Europe_Moscow), 0, 1, 0, 0},
|
||||
{&__pyx_n_s_InlineKeyboardBuilder, __pyx_k_InlineKeyboardBuilder, sizeof(__pyx_k_InlineKeyboardBuilder), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_InlineKeyboardButton, __pyx_k_InlineKeyboardButton, sizeof(__pyx_k_InlineKeyboardButton), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_MAIN_MENU, __pyx_k_MAIN_MENU, sizeof(__pyx_k_MAIN_MENU), 0, 0, 1, 1},
|
||||
{&__pyx_kp_u_PAYMENT, __pyx_k_PAYMENT, sizeof(__pyx_k_PAYMENT), 0, 1, 0, 0},
|
||||
{&__pyx_n_s_PAYMENT_SUCCESS_MESSAGE, __pyx_k_PAYMENT_SUCCESS_MESSAGE, sizeof(__pyx_k_PAYMENT_SUCCESS_MESSAGE), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_PROFILE, __pyx_k_PROFILE, sizeof(__pyx_k_PROFILE), 0, 0, 1, 1},
|
||||
{&__pyx_kp_u_RENEW, __pyx_k_RENEW, sizeof(__pyx_k_RENEW), 0, 1, 0, 0},
|
||||
{&__pyx_n_s_RENEW_KEY, __pyx_k_RENEW_KEY, sizeof(__pyx_k_RENEW_KEY), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_USE_NEW_PAYMENT_FLOW, __pyx_k_USE_NEW_PAYMENT_FLOW, sizeof(__pyx_k_USE_NEW_PAYMENT_FLOW), 0, 0, 1, 1},
|
||||
@@ -7185,7 +7185,7 @@ static int __Pyx_CreateStringTabAndInitStrings(void) {
|
||||
{&__pyx_n_s_get, __pyx_k_get, sizeof(__pyx_k_get), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_get_balance, __pyx_k_get_balance, sizeof(__pyx_k_get_balance), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_get_temporary_data, __pyx_k_get_temporary_data, sizeof(__pyx_k_get_temporary_data), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_handlers_buttons_notification, __pyx_k_handlers_buttons_notification, sizeof(__pyx_k_handlers_buttons_notification), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_handlers_buttons, __pyx_k_handlers_buttons, sizeof(__pyx_k_handlers_buttons), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_handlers_keys_key_management, __pyx_k_handlers_keys_key_management, sizeof(__pyx_k_handlers_keys_key_management), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_handlers_keys_keys, __pyx_k_handlers_keys_keys, sizeof(__pyx_k_handlers_keys_keys), 0, 0, 1, 1},
|
||||
{&__pyx_n_s_handlers_payments_utils, __pyx_k_handlers_payments_utils, sizeof(__pyx_k_handlers_payments_utils), 0, 0, 1, 1},
|
||||
@@ -7788,25 +7788,25 @@ if (!__Pyx_RefNanny) {
|
||||
|
||||
__pyx_t_2 = PyList_New(3); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_2);
|
||||
__Pyx_INCREF(__pyx_n_s_ADD_KEY);
|
||||
__Pyx_GIVEREF(__pyx_n_s_ADD_KEY);
|
||||
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 0, __pyx_n_s_ADD_KEY)) __PYX_ERR(0, 10, __pyx_L1_error);
|
||||
__Pyx_INCREF(__pyx_n_s_PROFILE);
|
||||
__Pyx_GIVEREF(__pyx_n_s_PROFILE);
|
||||
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 1, __pyx_n_s_PROFILE)) __PYX_ERR(0, 10, __pyx_L1_error);
|
||||
__Pyx_INCREF(__pyx_n_s_MAIN_MENU);
|
||||
__Pyx_GIVEREF(__pyx_n_s_MAIN_MENU);
|
||||
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 0, __pyx_n_s_MAIN_MENU)) __PYX_ERR(0, 10, __pyx_L1_error);
|
||||
__Pyx_INCREF(__pyx_n_s_ADD_SUB);
|
||||
__Pyx_GIVEREF(__pyx_n_s_ADD_SUB);
|
||||
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 1, __pyx_n_s_ADD_SUB)) __PYX_ERR(0, 10, __pyx_L1_error);
|
||||
__Pyx_INCREF(__pyx_n_s_RENEW_KEY);
|
||||
__Pyx_GIVEREF(__pyx_n_s_RENEW_KEY);
|
||||
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 2, __pyx_n_s_RENEW_KEY)) __PYX_ERR(0, 10, __pyx_L1_error);
|
||||
__pyx_t_3 = __Pyx_Import(__pyx_n_s_handlers_buttons_notification, __pyx_t_2, 0); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__pyx_t_3 = __Pyx_Import(__pyx_n_s_handlers_buttons, __pyx_t_2, 0); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_3);
|
||||
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
|
||||
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_ADD_KEY); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_MAIN_MENU); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_2);
|
||||
if (PyDict_SetItem(__pyx_d, __pyx_n_s_ADD_KEY, __pyx_t_2) < 0) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
if (PyDict_SetItem(__pyx_d, __pyx_n_s_MAIN_MENU, __pyx_t_2) < 0) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
|
||||
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_PROFILE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_ADD_SUB); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_2);
|
||||
if (PyDict_SetItem(__pyx_d, __pyx_n_s_PROFILE, __pyx_t_2) < 0) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
if (PyDict_SetItem(__pyx_d, __pyx_n_s_ADD_SUB, __pyx_t_2) < 0) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
|
||||
__pyx_t_2 = __Pyx_ImportFrom(__pyx_t_3, __pyx_n_s_RENEW_KEY); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 10, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_2);
|
||||
|
||||
Binary file not shown.
+1060
-1064
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+958
-913
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+28
-20
@@ -1,8 +1,10 @@
|
||||
import html
|
||||
import os
|
||||
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.enums import ParseMode
|
||||
from aiogram.fsm.context import FSMContext
|
||||
@@ -17,38 +19,44 @@ from aiogram.types import (
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import (
|
||||
ADMIN_ID,
|
||||
DATABASE_URL,
|
||||
GIFT_BUTTON,
|
||||
INLINE_MODE,
|
||||
INSTRUCTIONS_BUTTON,
|
||||
NEWS_MESSAGE,
|
||||
REFERRAL_BUTTON,
|
||||
REFERRAL_OFFERS,
|
||||
RENEWAL_PLANS,
|
||||
SHOW_START_MENU_ONCE,
|
||||
TOP_REFERRAL_BUTTON,
|
||||
TRIAL_TIME,
|
||||
USERNAME_BOT,
|
||||
REFERRAL_BUTTON,
|
||||
GIFT_BUTTON,
|
||||
ADMIN_ID,
|
||||
TOP_REFERRAL_BUTTON,
|
||||
SHOW_START_MENU_ONCE
|
||||
)
|
||||
from database import get_balance, get_key_count, get_last_payments, get_referral_stats, get_trial
|
||||
from handlers.buttons.profile import (
|
||||
from handlers.buttons import (
|
||||
ABOUT_VPN,
|
||||
ADD_SUB,
|
||||
BACK,
|
||||
BALANCE,
|
||||
BALANCE_HISTORY,
|
||||
COUPON,
|
||||
GIFTS,
|
||||
INSTRUCTIONS,
|
||||
INVITE,
|
||||
MAIN_MENU,
|
||||
MY_SUBS,
|
||||
PAYMENT,
|
||||
TOP_FIVE,
|
||||
)
|
||||
from handlers.texts import BALANCE_MANAGEMENT_TEXT, BALANCE_HISTORY_HEADER, INVITE_TEXT_NON_INLINE, TOP_REFERRALS_TEXT
|
||||
from handlers.texts import BALANCE_HISTORY_HEADER, BALANCE_MANAGEMENT_TEXT, INVITE_TEXT_NON_INLINE, TOP_REFERRALS_TEXT
|
||||
from logger import logger
|
||||
|
||||
from .admin.panel.keyboard import AdminPanelCallback
|
||||
from .texts import profile_message_send, invite_message_send, get_referral_link
|
||||
from .texts import get_referral_link, invite_message_send, profile_message_send
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -108,9 +116,9 @@ async def process_callback_view_profile(
|
||||
InlineKeyboardButton(text="🔧 Администратор", callback_data=AdminPanelCallback(action="admin").pack())
|
||||
)
|
||||
if SHOW_START_MENU_ONCE:
|
||||
builder.row(InlineKeyboardButton(text="💬 О сервисе", callback_data="about_vpn"))
|
||||
builder.row(InlineKeyboardButton(text=ABOUT_VPN, callback_data="about_vpn"))
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="start"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="start"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
@@ -136,7 +144,7 @@ async def balance_handler(callback_query: CallbackQuery, session: Any):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text=BALANCE_HISTORY, callback_data="balance_history"))
|
||||
builder.row(InlineKeyboardButton(text="🎟️ Активировать купон", callback_data="activate_coupon"))
|
||||
builder.row(InlineKeyboardButton(text=COUPON, callback_data="activate_coupon"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
text = BALANCE_MANAGEMENT_TEXT.format(balance=balance)
|
||||
@@ -187,7 +195,7 @@ async def balance_history_handler(callback_query: CallbackQuery, session: Any):
|
||||
@router.callback_query(F.data == "view_tariffs")
|
||||
async def view_tariffs_handler(callback_query: CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
image_path = os.path.join("img", "tariffs.jpg")
|
||||
tariffs_message = "<b>🚀 Доступные тарифы VPN:</b>\n\n" + "\n".join([
|
||||
@@ -224,13 +232,13 @@ async def invite_handler(callback_query_or_message: Message | CallbackQuery):
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
if INLINE_MODE:
|
||||
builder.button(text="👥 Пригласить друга", switch_inline_query="invite")
|
||||
builder.button(text=INVITE, switch_inline_query="invite")
|
||||
else:
|
||||
invite_text = INVITE_TEXT_NON_INLINE.format(referral_link=referral_link)
|
||||
builder.button(text="👥 Пригласить друга", switch_inline_query=invite_text)
|
||||
builder.button(text=INVITE, switch_inline_query=invite_text)
|
||||
if TOP_REFERRAL_BUTTON:
|
||||
builder.button(text="🏆 Топ-5", callback_data="top_referrals")
|
||||
builder.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
builder.button(text=TOP_FIVE, callback_data="top_referrals")
|
||||
builder.button(text=MAIN_MENU, callback_data="profile")
|
||||
builder.adjust(1)
|
||||
|
||||
await edit_or_send_message(
|
||||
@@ -286,16 +294,16 @@ async def top_referrals_handler(callback_query: CallbackQuery):
|
||||
rows = ""
|
||||
|
||||
for i, row in enumerate(top_referrals, 1):
|
||||
tg_id = str(row['referrer_tg_id'])
|
||||
count = row['referral_count']
|
||||
tg_id = str(row["referrer_tg_id"])
|
||||
count = row["referral_count"]
|
||||
display_id = tg_id if is_admin else f"{tg_id[:5]}*****"
|
||||
rows += f"{i}. {display_id} - {count} чел.\n"
|
||||
|
||||
text = TOP_REFERRALS_TEXT.format(rows=rows)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="invite"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="invite"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
|
||||
+20
-22
@@ -20,8 +20,8 @@ from config import (
|
||||
CHANNEL_REQUIRED,
|
||||
CHANNEL_URL,
|
||||
DONATIONS_ENABLE,
|
||||
SHOW_START_MENU_ONCE,
|
||||
SUPPORT_CHAT_URL,
|
||||
SHOW_START_MENU_ONCE
|
||||
)
|
||||
from database import (
|
||||
add_connection,
|
||||
@@ -31,8 +31,10 @@ from database import (
|
||||
get_trial,
|
||||
update_balance,
|
||||
)
|
||||
from handlers.buttons import ABOUT_VPN, BACK, CHANNEL, MAIN_MENU, SUPPORT
|
||||
from handlers.captcha import generate_captcha
|
||||
from handlers.keys.key_management import create_key
|
||||
from handlers.profile import process_callback_view_profile
|
||||
from handlers.texts import (
|
||||
COUPON_SUCCESS_MSG,
|
||||
GIFT_ALREADY_USED_OR_NOT_EXISTS_MSG,
|
||||
@@ -46,9 +48,10 @@ from handlers.texts import (
|
||||
get_about_vpn,
|
||||
)
|
||||
from logger import logger
|
||||
|
||||
from .admin.panel.keyboard import AdminPanelCallback
|
||||
from .utils import edit_or_send_message
|
||||
from handlers.profile import process_callback_view_profile
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -128,7 +131,8 @@ async def process_start_logic(
|
||||
|
||||
usage_exists = await session.fetchval(
|
||||
"SELECT 1 FROM coupon_usages WHERE coupon_id = $1 AND user_id = $2",
|
||||
coupon["id"], message.chat.id,
|
||||
coupon["id"],
|
||||
message.chat.id,
|
||||
)
|
||||
if usage_exists:
|
||||
await message.answer("❌ Вы уже использовали этот купон!")
|
||||
@@ -147,7 +151,8 @@ async def process_start_logic(
|
||||
)
|
||||
await session.execute(
|
||||
"INSERT INTO coupon_usages (coupon_id, user_id, used_at) VALUES ($1, $2, NOW())",
|
||||
coupon["id"], message.chat.id,
|
||||
coupon["id"],
|
||||
message.chat.id,
|
||||
)
|
||||
await message.answer(COUPON_SUCCESS_MSG.format(amount=coupon["amount"]))
|
||||
return await process_callback_view_profile(message, state, admin)
|
||||
@@ -188,9 +193,7 @@ async def process_start_logic(
|
||||
if not existing_referral:
|
||||
await add_referral(message.chat.id, gift_info["sender_tg_id"], session)
|
||||
|
||||
await session.execute(
|
||||
"UPDATE connections SET trial = 1 WHERE tg_id = $1", message.chat.id
|
||||
)
|
||||
await session.execute("UPDATE connections SET trial = 1 WHERE tg_id = $1", message.chat.id)
|
||||
|
||||
await create_key(
|
||||
message.chat.id,
|
||||
@@ -255,7 +258,6 @@ async def process_start_logic(
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
|
||||
|
||||
@router.callback_query(F.data == "check_subscription")
|
||||
async def check_subscription_callback(callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool):
|
||||
user_id = callback_query.from_user.id
|
||||
@@ -300,24 +302,24 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
|
||||
builder.row(InlineKeyboardButton(text="🎁 Пробная подписка", callback_data="create_key"))
|
||||
else:
|
||||
logger.warning(f"Сессия базы данных отсутствует, пропускаем проверку триала для {message.chat.id}")
|
||||
|
||||
|
||||
if not SHOW_START_MENU_ONCE:
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
if CHANNEL_EXISTS:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL),
|
||||
InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL),
|
||||
InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL),
|
||||
InlineKeyboardButton(text=CHANNEL, url=CHANNEL_URL),
|
||||
)
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL))
|
||||
|
||||
if admin:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔧 Администратор", callback_data=AdminPanelCallback(action="admin").pack())
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="🌐 О VPN", callback_data="about_vpn"))
|
||||
builder.row(InlineKeyboardButton(text=ABOUT_VPN, callback_data="about_vpn"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=message,
|
||||
@@ -331,19 +333,15 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
|
||||
async def handle_about_vpn(callback_query: CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
if DONATIONS_ENABLE:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
|
||||
)
|
||||
support_btn = InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL)
|
||||
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
|
||||
support_btn = InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL)
|
||||
if CHANNEL_EXISTS:
|
||||
channel_btn = InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL)
|
||||
channel_btn = InlineKeyboardButton(text=CHANNEL, url=CHANNEL_URL)
|
||||
builder.row(support_btn, channel_btn)
|
||||
else:
|
||||
builder.row(support_btn)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="← Назад", callback_data="start")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="start"))
|
||||
text = get_about_vpn("3.2.3-minor")
|
||||
|
||||
await edit_or_send_message(
|
||||
|
||||
@@ -7,6 +7,7 @@ import string
|
||||
import aiofiles
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardMarkup, InputMediaPhoto, Message
|
||||
|
||||
from bot import bot
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
log_folder = "logs"
|
||||
|
||||
if not os.path.exists(log_folder):
|
||||
|
||||
@@ -2,6 +2,7 @@ from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
@@ -12,6 +13,7 @@ from database import get_servers
|
||||
from handlers.admin.servers.keyboard import AdminServerCallback
|
||||
from logger import logger
|
||||
|
||||
|
||||
last_ping_times = {}
|
||||
last_down_times = {}
|
||||
notified_servers = set()
|
||||
|
||||
+5
-4
@@ -1,4 +1,5 @@
|
||||
import csv
|
||||
|
||||
from io import StringIO
|
||||
from typing import Any
|
||||
|
||||
@@ -153,19 +154,19 @@ async def export_hot_leads_csv(session: Any) -> BufferedInputFile:
|
||||
AND k.tg_id IS NULL
|
||||
ORDER BY u.updated_at DESC
|
||||
"""
|
||||
|
||||
|
||||
users = await session.fetch(query)
|
||||
|
||||
|
||||
buffer = StringIO()
|
||||
buffer.write("tg_id,username,first_name,last_name,updated_at\n")
|
||||
|
||||
|
||||
for user in users:
|
||||
buffer.write(
|
||||
f"{user['tg_id']},{user['username'] or ''},"
|
||||
f"{user['first_name'] or ''},{user['last_name'] or ''},"
|
||||
f"{user['updated_at']}\n"
|
||||
)
|
||||
|
||||
|
||||
buffer.seek(0)
|
||||
return BufferedInputFile(file=buffer.getvalue().encode("utf-8-sig"), filename="hot_leads_export.csv")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user