ruff, button file, castomize ping and backup

This commit is contained in:
Vladless
2025-03-28 20:39:06 +03:00
parent 4fb13a089a
commit 4295e4b5ec
67 changed files with 7732 additions and 6910 deletions
+2
View File
@@ -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
+2 -1
View File
@@ -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
View File
@@ -1,4 +1,5 @@
import json
from datetime import datetime
from typing import Any
+1
View File
@@ -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(
+1
View File
@@ -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()
+3 -1
View File
@@ -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()
+13 -7
View File
@@ -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
+3 -1
View File
@@ -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:
+3 -1
View File
@@ -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()
+3 -1
View File
@@ -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 -1
View File
@@ -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()
+5 -3
View File
@@ -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:
+2
View File
@@ -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 -1
View File
@@ -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
+3 -1
View File
@@ -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()
+4 -2
View File
@@ -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()
+3 -1
View File
@@ -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()
+21 -26
View File
@@ -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(
+11 -12
View File
@@ -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()
+37 -33
View File
@@ -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)
)
+82
View File
@@ -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 = "🤖 Бот для покупки звезд"
-6
View File
@@ -1,6 +0,0 @@
DOWNLOAD_IOS_BUTTON = "🍏 Скачать iOS"
DOWNLOAD_ANDROID_BUTTON = "🤖 Скачать Android"
IMPORT_IOS = "🍏 Подключить"
IMPORT_ANDROID = "🤖 Подключить"
PC_BUTTON = "💻 Компьютеры"
TV_BUTTON = "📺 Андроид TV"
-9
View File
@@ -1,9 +0,0 @@
GIFT = "🎁 Подарить подписку"
MY_GIFTS = "🎁 Мои подарки"
PROFILE = "👤 Личный кабинет"
BACK = "В меню подарков"
GIFTS_ABOUT = "<b>Дарите подарки и следите, чтобы они дошли до адресата! 🎄</b>"
SHARE_GIFT = "🎁 Поделиться подарком"
APPLY = "✅ Подтвердить"
CANCEL = "❌ Отмена"
NO_GIFTS = "У вас нет подарков."
-3
View File
@@ -1,3 +0,0 @@
RENEW_KEY = "🔄 Продлить подписку"
ADD_KEY = " Добавить подписку"
PROFILE = "👤 Личный кабинет"
-10
View File
@@ -1,10 +0,0 @@
ADD_SUB = " Подписка"
MY_SUBS = "📱 Мои подписки"
BALANCE = "💰 Баланс"
BALANCE_HISTORY = "📊 История пополнений"
PAYMENT = "💳 Пополнить баланс"
INVITE = "👥 Пригласить"
GIFTS = "🎁 Подарить"
TARRIFS = "💡 Тарифы"
INSTRUCTIONS = "📘 Инструкции"
MAIN_MENU = "⬅️ Главное меню"
-7
View File
@@ -1,7 +0,0 @@
PAY = "Пополнить"
PAY_2 = "Оплатить"
BACK = "⬅️ Назад"
CUSTOM_SUM = "💰 Ввести свою сумму"
CUSTOM_SUM_ANSWER = "Пожалуйста, введите сумму пополнения."
PROFILE = "👤 Личный кабинет"
DEFAULT_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей. Перейдите по ссылке для оплаты:"
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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(),
+1
View File
@@ -4,6 +4,7 @@ from aiogram import Router
from .instructions import router as instructions_router
router = Router(name="instructions_main_router")
router.include_routers(
+25 -14
View File
@@ -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
+1
View File
@@ -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(
+24 -15
View File
@@ -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
+10 -4
View File
@@ -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
View File
@@ -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:
+2
View File
@@ -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 (
+1
View File
@@ -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
+5 -3
View File
@@ -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()
+3 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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
File diff suppressed because it is too large Load Diff
+2432 -1878
View File
File diff suppressed because it is too large Load Diff
+13 -11
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+32 -32
View File
@@ -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);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+28 -20
View File
@@ -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
View File
@@ -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(
+1
View File
@@ -7,6 +7,7 @@ import string
import aiofiles
import aiohttp
import asyncpg
from aiogram.types import BufferedInputFile, InlineKeyboardMarkup, InputMediaPhoto, Message
from bot import bot
+2
View File
@@ -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):
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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")