Upstream
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import aiofiles
|
||||
|
||||
from aiogram.types import BufferedInputFile
|
||||
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER, PG_HOST, PG_PORT
|
||||
|
||||
from bot import bot
|
||||
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER, PG_HOST, PG_PORT
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -8,13 +8,12 @@ from aiogram.filters import ExceptionTypeFilter
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
from aiogram.types import BufferedInputFile, ErrorEvent
|
||||
from aiogram.utils.markdown import hbold
|
||||
from config import ADMIN_ID, API_TOKEN
|
||||
|
||||
from config import ADMIN_ID, API_TOKEN
|
||||
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)
|
||||
|
||||
@@ -5,7 +5,6 @@ import httpx
|
||||
import py3xui
|
||||
|
||||
from config import LIMIT_IP, SUPERNODE
|
||||
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import json
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
@@ -7,7 +6,6 @@ import asyncpg
|
||||
import pytz
|
||||
|
||||
from config import CASHBACK, CHECK_REFERRAL_REWARD_ISSUED, DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
|
||||
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from aiogram.filters import BaseFilter
|
||||
from aiogram.types import Message
|
||||
|
||||
from config import ADMIN_ID
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ 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(
|
||||
|
||||
+13
-10
@@ -2,22 +2,25 @@ __all__ = ("router",)
|
||||
|
||||
from aiogram import Router
|
||||
|
||||
from .admin_backups import router as backups_router
|
||||
from .admin_bans import router as bans_router
|
||||
from .admin_coupons import router as coupons_router
|
||||
from .admin_panel import router as panel_router
|
||||
from .admin_restart import router as restart_router
|
||||
from .admin_sender import router as sender_router
|
||||
from .admin_servers import router as servers_router
|
||||
from .admin_stats import router as stats_router
|
||||
from .admin_users import router as users_router
|
||||
|
||||
from .backups import router as backups_router
|
||||
from .bans import router as bans_router
|
||||
from .clusters import router as clusters_router
|
||||
from .coupons import router as coupons_router
|
||||
from .management import router as management_router
|
||||
from .panel import router as panel_router
|
||||
from .restart import router as restart_router
|
||||
from .sender import router as sender_router
|
||||
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(
|
||||
panel_router,
|
||||
management_router,
|
||||
servers_router,
|
||||
clusters_router,
|
||||
users_router,
|
||||
stats_router,
|
||||
backups_router,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .backups_handler import router
|
||||
@@ -3,8 +3,7 @@ from aiogram.types import CallbackQuery
|
||||
|
||||
from backup import backup_database
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .bans_handler import router
|
||||
@@ -5,9 +5,8 @@ from aiogram.types import BufferedInputFile, CallbackQuery
|
||||
|
||||
from database import delete_user_data
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.bans_kb import build_bans_kb
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
|
||||
|
||||
from .keyboard import build_bans_kb
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_btn
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn
|
||||
|
||||
|
||||
def build_bans_kb() -> InlineKeyboardMarkup:
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .clusters_handler import router
|
||||
@@ -1,45 +1,40 @@
|
||||
import asyncio
|
||||
|
||||
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
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from backup import create_backup_and_send_to_admins
|
||||
from database import check_unique_server_name, delete_server, get_servers
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||
from database import check_unique_server_name, get_servers
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import create_key_on_cluster
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
|
||||
from keyboards.admin.servers_kb import (
|
||||
AdminServerEditorCallback,
|
||||
build_clusters_editor_kb,
|
||||
build_delete_server_kb,
|
||||
build_manage_cluster_kb,
|
||||
build_manage_server_kb,
|
||||
)
|
||||
from handlers.keys.key_utils import create_key_on_cluster, create_client_on_server
|
||||
from logger import logger
|
||||
|
||||
from .keyboard import (
|
||||
build_clusters_editor_kb,
|
||||
build_manage_cluster_kb,
|
||||
AdminClusterCallback,
|
||||
build_sync_cluster_kb,
|
||||
)
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
class AdminServersEditor(StatesGroup):
|
||||
class AdminClusterStates(StatesGroup):
|
||||
waiting_for_cluster_name = State()
|
||||
waiting_for_api_url = State()
|
||||
waiting_for_inbound_id = State()
|
||||
waiting_for_server_name = State()
|
||||
waiting_for_subscription_url = State()
|
||||
waiting_for_new_domain = State()
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
AdminPanelCallback.filter(F.action == "servers"),
|
||||
AdminPanelCallback.filter(F.action == "clusters"),
|
||||
IsAdminFilter(),
|
||||
)
|
||||
async def handle_servers(callback_query: CallbackQuery):
|
||||
@@ -60,26 +55,34 @@ async def handle_servers(callback_query: CallbackQuery):
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
AdminPanelCallback.filter(F.action == "clusters_add"),
|
||||
AdminClusterCallback.filter(F.action == "add"),
|
||||
IsAdminFilter(),
|
||||
)
|
||||
async def handle_clusters_add(callback_query: CallbackQuery, state: FSMContext):
|
||||
text = (
|
||||
"🔧 <b>Введите имя нового кластера:</b>\n\n"
|
||||
"<b>Имя кластера должно быть уникальным!</b>\n"
|
||||
"<b>Имя должно быть уникальным!</b>\n"
|
||||
"<b>Имя не должно превышать 12 символов!</b>\n"
|
||||
"<i>Пример:</i> <code>cluster1</code> или <code>us_east_1</code>"
|
||||
)
|
||||
|
||||
await callback_query.message.edit_text(text=text, reply_markup=build_admin_back_kb("servers"))
|
||||
await callback_query.message.edit_text(text=text, reply_markup=build_admin_back_kb("clusters"))
|
||||
|
||||
await state.set_state(AdminServersEditor.waiting_for_cluster_name)
|
||||
await state.set_state(AdminClusterStates.waiting_for_cluster_name)
|
||||
|
||||
|
||||
@router.message(AdminServersEditor.waiting_for_cluster_name, IsAdminFilter())
|
||||
@router.message(AdminClusterStates.waiting_for_cluster_name, IsAdminFilter())
|
||||
async def handle_cluster_name_input(message: Message, state: FSMContext):
|
||||
if not message.text:
|
||||
await message.answer(
|
||||
text="❌ Имя кластера не может быть пустым. Попробуйте снова.", reply_markup=build_admin_back_kb("servers")
|
||||
text="❌ Имя кластера не может быть пустым! Попробуйте снова.", reply_markup=build_admin_back_kb("clusters")
|
||||
)
|
||||
return
|
||||
|
||||
if len(message.text) > 12:
|
||||
await message.answer(
|
||||
text="❌ Имя кластера должно превышать 12 символов! Попробуйте снова.",
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -94,26 +97,26 @@ async def handle_cluster_name_input(message: Message, state: FSMContext):
|
||||
|
||||
await message.answer(
|
||||
text=text,
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
|
||||
await state.set_state(AdminServersEditor.waiting_for_server_name)
|
||||
await state.set_state(AdminClusterStates.waiting_for_server_name)
|
||||
|
||||
|
||||
@router.message(AdminServersEditor.waiting_for_server_name, IsAdminFilter())
|
||||
@router.message(AdminClusterStates.waiting_for_server_name, IsAdminFilter())
|
||||
async def handle_server_name_input(message: Message, state: FSMContext, session: Any):
|
||||
if not message.text:
|
||||
await message.answer(
|
||||
text="❌ Имя сервера не может быть пустым. Попробуйте снова.", reply_markup=build_admin_back_kb("servers")
|
||||
text="❌ Имя сервера не может быть пустым. Попробуйте снова.", reply_markup=build_admin_back_kb("clusters")
|
||||
)
|
||||
return
|
||||
|
||||
server_name = message.text.strip()
|
||||
|
||||
if len(server_name) > 14:
|
||||
if len(server_name) > 12:
|
||||
await message.answer(
|
||||
text="❌ Имя сервера не должно превышать 10 символов. Попробуйте снова.",
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
text="❌ Имя сервера не должно превышать 12 символов. Попробуйте снова.",
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -123,7 +126,7 @@ async def handle_server_name_input(message: Message, state: FSMContext, session:
|
||||
if not await check_unique_server_name(server_name, session, cluster_name):
|
||||
await message.answer(
|
||||
text="❌ Сервер с таким именем уже существует. Пожалуйста, выберите другое имя.",
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -131,25 +134,25 @@ async def handle_server_name_input(message: Message, state: FSMContext, session:
|
||||
|
||||
text = (
|
||||
f"<b>Введите API URL для сервера {server_name} в кластере {cluster_name}:</b>\n\n"
|
||||
"API URL должен быть в следующем формате:\n\n"
|
||||
"<code>https://your_domain:port/panel_path</code>\n\n"
|
||||
"URL должен быть без слэша на конце!\n"
|
||||
"Ссылку можно найти в поисковой строке браузера, при входе в 3X-UI.\n\n"
|
||||
"ℹ️ Формат API URL:\n"
|
||||
"<code>https://your_domain:port/panel_path/</code>"
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
text=text,
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
|
||||
await state.set_state(AdminServersEditor.waiting_for_api_url)
|
||||
await state.set_state(AdminClusterStates.waiting_for_api_url)
|
||||
|
||||
|
||||
@router.message(AdminServersEditor.waiting_for_api_url, IsAdminFilter())
|
||||
async def handle_api_url_input(message: Message, state: FSMContext, session: Any):
|
||||
@router.message(AdminClusterStates.waiting_for_api_url, IsAdminFilter())
|
||||
async def handle_api_url_input(message: Message, state: FSMContext):
|
||||
if not message.text or not message.text.strip().startswith("https://"):
|
||||
await message.answer(
|
||||
text="❌ API URL должен начинаться с <code>https://</code>. Попробуйте снова.",
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -162,26 +165,25 @@ async def handle_api_url_input(message: Message, state: FSMContext, session: Any
|
||||
|
||||
text = (
|
||||
f"<b>Введите subscription_url для сервера {server_name} в кластере {cluster_name}:</b>\n\n"
|
||||
"Subscription URL должен быть в следующем формате:\n\n"
|
||||
"<code>https://your_domain:port_sub/sub_path</code>\n\n"
|
||||
"URL должен быть без слэша и имени клиента на конце!\n"
|
||||
"Его можно увидеть в панели 3x-ui в информации о клиенте."
|
||||
"Ссылку можно найти в панели 3X-UI, в информации о клиенте.\n\n"
|
||||
"ℹ️ Формат Subscription URL:\n"
|
||||
"<code>https://your_domain:port_sub/sub_path/</code>"
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
text=text,
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
|
||||
await state.set_state(AdminServersEditor.waiting_for_subscription_url)
|
||||
await state.set_state(AdminClusterStates.waiting_for_subscription_url)
|
||||
|
||||
|
||||
@router.message(AdminServersEditor.waiting_for_subscription_url, IsAdminFilter())
|
||||
@router.message(AdminClusterStates.waiting_for_subscription_url, IsAdminFilter())
|
||||
async def handle_subscription_url_input(message: Message, state: FSMContext):
|
||||
if not message.text or not message.text.strip().startswith("https://"):
|
||||
await message.answer(
|
||||
text="❌ subscription_url должен начинаться с <code>https://</code>. Попробуйте снова.",
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -199,19 +201,19 @@ async def handle_subscription_url_input(message: Message, state: FSMContext):
|
||||
|
||||
await message.answer(
|
||||
text=text,
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
await state.set_state(AdminServersEditor.waiting_for_inbound_id)
|
||||
await state.set_state(AdminClusterStates.waiting_for_inbound_id)
|
||||
|
||||
|
||||
@router.message(AdminServersEditor.waiting_for_inbound_id, IsAdminFilter())
|
||||
@router.message(AdminClusterStates.waiting_for_inbound_id, IsAdminFilter())
|
||||
async def handle_inbound_id_input(message: Message, state: FSMContext):
|
||||
inbound_id = message.text.strip()
|
||||
|
||||
if not inbound_id.isdigit():
|
||||
await message.answer(
|
||||
text="❌ inbound_id должен быть числовым значением. Попробуйте снова.",
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -237,15 +239,15 @@ async def handle_inbound_id_input(message: Message, state: FSMContext):
|
||||
|
||||
await message.answer(
|
||||
text=f"✅ Кластер {cluster_name} и сервер {server_name} успешно добавлены!",
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
|
||||
await state.clear()
|
||||
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "clusters_manage"), IsAdminFilter())
|
||||
@router.callback_query(AdminClusterCallback.filter(F.action == "manage"), IsAdminFilter())
|
||||
async def handle_clusters_manage(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminClusterCallback, session: Any
|
||||
):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
@@ -253,14 +255,14 @@ async def handle_clusters_manage(
|
||||
cluster_servers = servers.get(cluster_name, [])
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=f"🔧 Управление серверами для кластера {cluster_name}",
|
||||
text=f"<b>🔧 Управление кластером {cluster_name}</b>",
|
||||
reply_markup=build_manage_cluster_kb(cluster_servers, cluster_name),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_availability"), IsAdminFilter())
|
||||
async def handle_servers_availability(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
@router.callback_query(AdminClusterCallback.filter(F.action == "availability"), IsAdminFilter())
|
||||
async def handle_cluster_availability(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminClusterCallback, session: Any
|
||||
):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
@@ -278,8 +280,8 @@ async def handle_servers_availability(
|
||||
|
||||
await callback_query.message.edit_text(text=text)
|
||||
|
||||
result_text = f"🖥️ Проверка доступности серверов для кластера {cluster_name} завершена:\n\n"
|
||||
total_online_users = 0
|
||||
result_text = f"<b>🖥️ Проверка доступности серверов</b>\n\n⚙️ Кластер: <b>{cluster_name}</b>\n\n"
|
||||
|
||||
for server in cluster_servers:
|
||||
xui = AsyncApi(server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD, logger=logger)
|
||||
@@ -288,93 +290,18 @@ async def handle_servers_availability(
|
||||
await xui.login()
|
||||
online_users = len(await xui.client.online())
|
||||
total_online_users += online_users
|
||||
result_text += f"🌍 {server['server_name']}: {online_users} активных пользователей.\n"
|
||||
|
||||
result_text += f"🌍 <b>{server['server_name']}</b> - онлайн: {online_users}\n"
|
||||
except Exception as e:
|
||||
result_text += f"❌ {server['server_name']}: Не удалось получить информацию. Ошибка: {e}\n"
|
||||
result_text += f"❌ <b>{server['server_name']}</b> - ошибка: {e}\n"
|
||||
|
||||
result_text += f"\n👥 Общее количество активных пользователей в кластере: {total_online_users}."
|
||||
result_text += f"\n👥 Всего пользователей онлайн: {total_online_users}"
|
||||
|
||||
await callback_query.message.edit_text(text=result_text, reply_markup=build_admin_back_kb("servers"))
|
||||
await callback_query.message.edit_text(text=result_text, reply_markup=build_admin_back_kb("clusters"))
|
||||
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_manage"), IsAdminFilter())
|
||||
async def handle_servers_manage(callback_query: CallbackQuery, callback_data: AdminServerEditorCallback):
|
||||
server_name = callback_data.data
|
||||
servers = await get_servers()
|
||||
|
||||
cluster_name, server = next(
|
||||
((c, s) for c, cs in servers.items() for s in cs if s["server_name"] == server_name), (None, None)
|
||||
)
|
||||
|
||||
if server:
|
||||
api_url = server["api_url"]
|
||||
subscription_url = server["subscription_url"]
|
||||
inbound_id = server["inbound_id"]
|
||||
|
||||
text = (
|
||||
f"<b>🔧 Информация о сервере {server_name}:</b>\n\n"
|
||||
f"<b>📡 API URL:</b> {api_url}\n"
|
||||
f"<b>🌐 Subscription URL:</b> {subscription_url}\n"
|
||||
f"<b>🔑 Inbound ID:</b> {inbound_id}"
|
||||
)
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=text,
|
||||
reply_markup=build_manage_server_kb(server_name, cluster_name),
|
||||
)
|
||||
else:
|
||||
await callback_query.message.edit_text(text="❌ Сервер не найден.")
|
||||
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_delete"), IsAdminFilter())
|
||||
async def handle_servers_delete(callback_query: CallbackQuery, callback_data: AdminServerEditorCallback):
|
||||
server_name = callback_data.data
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=f"🗑️ Вы уверены, что хотите удалить сервер {server_name}?",
|
||||
reply_markup=build_delete_server_kb(server_name),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_delete_confirm"), IsAdminFilter())
|
||||
async def handle_servers_delete_confirm(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
):
|
||||
server_name = callback_data.data
|
||||
|
||||
await delete_server(server_name, session)
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=f"🗑️ Сервер {server_name} успешно удален.", reply_markup=build_admin_back_kb("servers")
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_add"), IsAdminFilter())
|
||||
async def handle_servers_add(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, state: FSMContext
|
||||
):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
await state.update_data(cluster_name=cluster_name)
|
||||
|
||||
text = (
|
||||
f"<b>Введите имя сервера для кластера {cluster_name}:</b>\n\n"
|
||||
"Рекомендуется указать локацию сервера в имени.\n\n"
|
||||
"<i>Пример:</i> <code>server-asia</code>, <code>server-europe</code>"
|
||||
)
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=text,
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
)
|
||||
|
||||
await state.set_state(AdminServersEditor.waiting_for_server_name)
|
||||
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "clusters_backup"), IsAdminFilter())
|
||||
@router.callback_query(AdminClusterCallback.filter(F.action == "backup"), IsAdminFilter())
|
||||
async def handle_clusters_backup(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminClusterCallback, session: Any
|
||||
):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
@@ -397,14 +324,81 @@ async def handle_clusters_backup(
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=text,
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "clusters_sync"), IsAdminFilter())
|
||||
async def handle_clusters_sync(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
):
|
||||
@router.callback_query(AdminClusterCallback.filter(F.action == "sync"), IsAdminFilter())
|
||||
async def handle_sync(callback_query: types.CallbackQuery, callback_data: AdminClusterCallback, session: Any):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
servers = await get_servers(session)
|
||||
cluster_servers = servers.get(cluster_name, [])
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=f"<b>🔄 Синхронизация кластера {cluster_name}</b>",
|
||||
reply_markup=build_sync_cluster_kb(cluster_servers, cluster_name),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminClusterCallback.filter(F.action == "sync-server"), IsAdminFilter())
|
||||
async def handle_sync_server(callback_query: types.CallbackQuery, callback_data: AdminClusterCallback, session: Any):
|
||||
server_name = callback_data.data
|
||||
|
||||
try:
|
||||
query_keys = """
|
||||
SELECT s.*, k.tg_id, k.client_id, k.email, k.expiry_time
|
||||
FROM servers s
|
||||
JOIN keys k ON s.cluster_name = k.server_id
|
||||
WHERE s.server_name = $1;
|
||||
"""
|
||||
keys_to_sync = await session.fetch(query_keys, server_name)
|
||||
|
||||
if not keys_to_sync:
|
||||
await callback_query.message.answer(
|
||||
text=f"❌ Нет ключей для синхронизации в сервере {server_name}.",
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
return
|
||||
|
||||
text = f"<b>🔄 Синхронизация сервера {server_name}</b>\n\n🔑 Количество ключей: <b>{len(keys_to_sync)}</b>"
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=text,
|
||||
)
|
||||
|
||||
semaphore = asyncio.Semaphore(2)
|
||||
for key in keys_to_sync:
|
||||
try:
|
||||
await create_client_on_server(
|
||||
{
|
||||
"api_url": key["api_url"],
|
||||
"inbound_id": key["inbound_id"],
|
||||
"server_name": key["server_name"],
|
||||
},
|
||||
key["tg_id"],
|
||||
key["client_id"],
|
||||
key["email"],
|
||||
key["expiry_time"],
|
||||
semaphore,
|
||||
)
|
||||
await asyncio.sleep(0.6)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при добавлении ключа {key['client_id']} в сервер {server_name}: {e}")
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=f"✅ Ключи успешно синхронизированы для сервера {server_name}",
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка синхронизации ключей для сервера {server_name}: {e}")
|
||||
await callback_query.message.answer(
|
||||
text=f"❌ Произошла ошибка при синхронизации: {e}", reply_markup=build_admin_back_kb("clusters")
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminClusterCallback.filter(F.action == "sync-cluster"), IsAdminFilter())
|
||||
async def handle_sync_cluster(callback_query: types.CallbackQuery, callback_data: AdminClusterCallback, session: Any):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
try:
|
||||
@@ -418,10 +412,16 @@ async def handle_clusters_sync(
|
||||
if not keys_to_sync:
|
||||
await callback_query.message.answer(
|
||||
text=f"❌ Нет ключей для синхронизации в кластере {cluster_name}.",
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
return
|
||||
|
||||
text = f"<b>🔄 Синхронизация кластера {cluster_name}</b>\n\n🔑 Количество ключей: <b>{len(keys_to_sync)}</b>"
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=text,
|
||||
)
|
||||
|
||||
for key in keys_to_sync:
|
||||
try:
|
||||
await create_key_on_cluster(
|
||||
@@ -437,59 +437,10 @@ async def handle_clusters_sync(
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=f"✅ Ключи успешно синхронизированы для кластера {cluster_name}",
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
reply_markup=build_admin_back_kb("clusters"),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка синхронизации ключей в кластере {cluster_name}: {e}")
|
||||
await callback_query.message.answer(
|
||||
text=f"❌ Произошла ошибка при синхронизации: {e}", reply_markup=build_admin_back_kb("servers")
|
||||
text=f"❌ Произошла ошибка при синхронизации: {e}", reply_markup=build_admin_back_kb("clusters")
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminPanelCallback.filter(F.action == "change_domain"), IsAdminFilter())
|
||||
async def request_new_domain(callback_query: CallbackQuery, state: FSMContext):
|
||||
"""Запрашивает у администратора новый домен."""
|
||||
await state.set_state(AdminServersEditor.waiting_for_new_domain)
|
||||
await callback_query.message.edit_text(
|
||||
text="🌐 Введите новый домен (без https://):\nПример: pocomachodomen.ru",
|
||||
)
|
||||
|
||||
|
||||
@router.message(AdminServersEditor.waiting_for_new_domain)
|
||||
async def process_new_domain(message: Message, state: FSMContext, session: asyncpg.Connection):
|
||||
"""Обновляет домен в таблице keys."""
|
||||
new_domain = message.text.strip()
|
||||
logger.info(f"[DomainChange] Новый домен, введённый администратором: '{new_domain}'")
|
||||
|
||||
if not new_domain or " " in new_domain or not new_domain.replace(".", "").isalnum():
|
||||
logger.warning("[DomainChange] Некорректный домен")
|
||||
await message.answer(
|
||||
"🚫 Некорректный домен! Введите домен без http:// и без пробелов.",
|
||||
reply_markup=build_admin_back_kb("admin"),
|
||||
)
|
||||
return
|
||||
|
||||
new_domain_url = f"https://{new_domain}"
|
||||
logger.info(f"[DomainChange] Новый домен с протоколом: '{new_domain_url}'")
|
||||
|
||||
query = """
|
||||
UPDATE keys
|
||||
SET key = regexp_replace(key, '^https://[^/]+', $1::TEXT)
|
||||
WHERE key NOT LIKE $1 || '%'
|
||||
"""
|
||||
try:
|
||||
await session.execute(query, new_domain_url)
|
||||
logger.info("[DomainChange] Запрос на обновление домена выполнен успешно.")
|
||||
except Exception as e:
|
||||
logger.error(f"[DomainChange] Ошибка при выполнении запроса: {e}")
|
||||
await message.answer(f"❌ Ошибка при обновлении домена: {e}", reply_markup=build_admin_back_kb("admin"))
|
||||
return
|
||||
|
||||
try:
|
||||
sample = await session.fetchrow("SELECT key FROM keys LIMIT 1")
|
||||
logger.info(f"[DomainChange] Пример обновленной записи: {sample}")
|
||||
except Exception as e:
|
||||
logger.error(f"[DomainChange] Ошибка при выборке обновленной записи: {e}")
|
||||
|
||||
await message.answer(f"✅ Домен успешно изменен на {new_domain}!", reply_markup=build_admin_back_kb("admin"))
|
||||
await state.clear()
|
||||
@@ -0,0 +1,96 @@
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from ..panel.keyboard import build_admin_back_btn
|
||||
from ..servers.keyboard import AdminServerCallback
|
||||
|
||||
|
||||
class AdminClusterCallback(CallbackData, prefix="admin_cluster"):
|
||||
action: str
|
||||
data: str = None
|
||||
|
||||
|
||||
def build_clusters_editor_kb(servers: dict) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
cluster_names = list(servers.keys())
|
||||
for i in range(0, len(cluster_names), 2):
|
||||
builder.row(*[
|
||||
InlineKeyboardButton(
|
||||
text=f"⚙️ {name}",
|
||||
callback_data=AdminClusterCallback(action="manage", data=name).pack(),
|
||||
)
|
||||
for name in cluster_names[i : i + 2]
|
||||
])
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="➕ Добавить кластер", callback_data=AdminClusterCallback(action="add").pack())
|
||||
)
|
||||
|
||||
builder.row(build_admin_back_btn())
|
||||
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_manage_cluster_kb(cluster_servers: list, cluster_name: str) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for server in cluster_servers:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f"🌍 {server['server_name']}",
|
||||
callback_data=AdminServerCallback(action="manage", data=server["server_name"]).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="➕ Добавить сервер",
|
||||
callback_data=AdminServerCallback(action="add", data=cluster_name).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🌐 Доступность",
|
||||
callback_data=AdminClusterCallback(action="availability", data=cluster_name).pack(),
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="🔄 Синхронизация",
|
||||
callback_data=AdminClusterCallback(action="sync", data=cluster_name).pack(),
|
||||
),
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💾 Создать бэкап",
|
||||
callback_data=AdminClusterCallback(action="backup", data=cluster_name).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(build_admin_back_btn("clusters"))
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_sync_cluster_kb(cluster_servers: list, cluster_name: str) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for server in cluster_servers:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f"🔄 Синхронизировать {server['server_name']}",
|
||||
callback_data=AdminClusterCallback(action="sync-server", data=server["server_name"]).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📍 Синхронизировать кластер",
|
||||
callback_data=AdminClusterCallback(action="sync-cluster", data=cluster_name).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(build_admin_back_btn("clusters"))
|
||||
|
||||
return builder.as_markup()
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .coupons_handler import router
|
||||
@@ -4,14 +4,13 @@ from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from config import USERNAME_BOT
|
||||
|
||||
from config import USERNAME_BOT
|
||||
from database import create_coupon, delete_coupon, get_all_coupons
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.coupons_kb import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
|
||||
from .keyboard import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb
|
||||
from logger import logger
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -2,7 +2,7 @@ from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_btn
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn
|
||||
|
||||
|
||||
class AdminCouponDeleteCallback(CallbackData, prefix="admin_coupon_delete"):
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .management_handler import router
|
||||
@@ -0,0 +1,16 @@
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from ..panel.keyboard import build_admin_back_btn, AdminPanelCallback
|
||||
|
||||
|
||||
def build_management_kb() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="💾 Создать резервную копию", callback_data=AdminPanelCallback(action="backups").pack())
|
||||
builder.button(text="🚫 Заблокировавшие бота", callback_data=AdminPanelCallback(action="bans").pack())
|
||||
builder.button(text="🔄 Перезагрузить бота", callback_data=AdminPanelCallback(action="restart").pack())
|
||||
builder.button(text="🌐 Сменить домен", callback_data=AdminPanelCallback(action="change_domain").pack())
|
||||
builder.button(text="🔑 Восстановить пробники", callback_data=AdminPanelCallback(action="restore_trials").pack())
|
||||
builder.row(build_admin_back_btn())
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
@@ -0,0 +1,73 @@
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import StatesGroup, State
|
||||
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
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
class AdminManagementStates(StatesGroup):
|
||||
waiting_for_new_domain = State()
|
||||
|
||||
|
||||
@router.callback_query(AdminPanelCallback.filter(F.action == "management"), IsAdminFilter())
|
||||
async def handle_management(callback_query: CallbackQuery):
|
||||
await callback_query.message.edit_text(
|
||||
text="🤖 Управление ботом",
|
||||
reply_markup=build_management_kb(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminPanelCallback.filter(F.action == "change_domain"), IsAdminFilter())
|
||||
async def request_new_domain(callback_query: CallbackQuery, state: FSMContext):
|
||||
"""Запрашивает у администратора новый домен."""
|
||||
await state.set_state(AdminManagementStates.waiting_for_new_domain)
|
||||
await callback_query.message.edit_text(
|
||||
text="🌐 Введите новый домен (без https://):\nПример: pocomachodomen.ru",
|
||||
)
|
||||
|
||||
|
||||
@router.message(AdminManagementStates.waiting_for_new_domain)
|
||||
async def process_new_domain(message: Message, state: FSMContext, session: Connection):
|
||||
"""Обновляет домен в таблице keys."""
|
||||
new_domain = message.text.strip()
|
||||
logger.info(f"[DomainChange] Новый домен, введённый администратором: '{new_domain}'")
|
||||
|
||||
if not new_domain or " " in new_domain or not new_domain.replace(".", "").isalnum():
|
||||
logger.warning("[DomainChange] Некорректный домен")
|
||||
await message.answer(
|
||||
"🚫 Некорректный домен! Введите домен без http:// и без пробелов.",
|
||||
reply_markup=build_admin_back_kb("admin"),
|
||||
)
|
||||
return
|
||||
|
||||
new_domain_url = f"https://{new_domain}"
|
||||
logger.info(f"[DomainChange] Новый домен с протоколом: '{new_domain_url}'")
|
||||
|
||||
query = """
|
||||
UPDATE keys
|
||||
SET key = regexp_replace(key, '^https://[^/]+', $1::TEXT)
|
||||
WHERE key NOT LIKE $1 || '%'
|
||||
"""
|
||||
try:
|
||||
await session.execute(query, new_domain_url)
|
||||
logger.info("[DomainChange] Запрос на обновление домена выполнен успешно.")
|
||||
except Exception as e:
|
||||
logger.error(f"[DomainChange] Ошибка при выполнении запроса: {e}")
|
||||
await message.answer(f"❌ Ошибка при обновлении домена: {e}", reply_markup=build_admin_back_kb("admin"))
|
||||
return
|
||||
|
||||
try:
|
||||
sample = await session.fetchrow("SELECT key FROM keys LIMIT 1")
|
||||
logger.info(f"[DomainChange] Пример обновленной записи: {sample}")
|
||||
except Exception as e:
|
||||
logger.error(f"[DomainChange] Ошибка при выборке обновленной записи: {e}")
|
||||
|
||||
await message.answer(f"✅ Домен успешно изменен на {new_domain}!", reply_markup=build_admin_back_kb("admin"))
|
||||
await state.clear()
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .panel_handler import router
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
@@ -7,10 +9,10 @@ class AdminPanelCallback(CallbackData, prefix="admin_panel"):
|
||||
action: str
|
||||
page: int
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
if "page" not in kwargs or kwargs["page"] is None:
|
||||
kwargs["page"] = 1
|
||||
super().__init__(*args, **kwargs)
|
||||
def __init__(self, /, **data: Any) -> None:
|
||||
if "page" not in data or data["page"] is None:
|
||||
data["page"] = 1
|
||||
super().__init__(**data)
|
||||
|
||||
|
||||
def build_panel_kb() -> InlineKeyboardMarkup:
|
||||
@@ -18,7 +20,7 @@ def build_panel_kb() -> InlineKeyboardMarkup:
|
||||
builder.button(text="👤 Поиск пользователя", callback_data=AdminPanelCallback(action="search_user").pack())
|
||||
builder.button(text="🔑 Поиск по названию ключа", callback_data=AdminPanelCallback(action="search_key").pack())
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🖥️ Серверы", callback_data=AdminPanelCallback(action="servers").pack()),
|
||||
InlineKeyboardButton(text="🖥️ Серверы", callback_data=AdminPanelCallback(action="clusters").pack()),
|
||||
InlineKeyboardButton(text="🎟️ Купоны", callback_data=AdminPanelCallback(action="coupons").pack()),
|
||||
)
|
||||
builder.button(text="📢 Рассылка", callback_data=AdminPanelCallback(action="sender").pack())
|
||||
@@ -31,18 +33,6 @@ def build_panel_kb() -> InlineKeyboardMarkup:
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_management_kb() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="💾 Создать резервную копию", callback_data=AdminPanelCallback(action="backups").pack())
|
||||
builder.button(text="🚫 Заблокировавшие бота", callback_data=AdminPanelCallback(action="bans").pack())
|
||||
builder.button(text="🔄 Перезагрузить бота", callback_data=AdminPanelCallback(action="restart").pack())
|
||||
builder.button(text="🌐 Сменить домен", callback_data=AdminPanelCallback(action="change_domain").pack())
|
||||
builder.button(text="🔑 Восстановить пробники", callback_data=AdminPanelCallback(action="restore_trials").pack())
|
||||
builder.row(build_admin_back_btn())
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_restart_kb() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="✅ Да, перезагрузить", callback_data=AdminPanelCallback(action="restart_confirm").pack())
|
||||
@@ -5,8 +5,7 @@ from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from bot import version
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_management_kb, build_panel_kb
|
||||
|
||||
from .keyboard import AdminPanelCallback, build_panel_kb
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -39,11 +38,3 @@ async def handle_admin_message(message: Message, state: FSMContext):
|
||||
|
||||
await state.clear()
|
||||
await message.answer(text=text, reply_markup=build_panel_kb())
|
||||
|
||||
|
||||
@router.callback_query(AdminPanelCallback.filter(F.action == "management"), IsAdminFilter())
|
||||
async def handle_management(callback_query: CallbackQuery):
|
||||
await callback_query.message.edit_text(
|
||||
text="🤖 Управление ботом",
|
||||
reply_markup=build_management_kb(),
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .restart_handler import router
|
||||
@@ -4,8 +4,7 @@ from aiogram import F, Router
|
||||
from aiogram.types import CallbackQuery
|
||||
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb, build_restart_kb
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb, build_restart_kb
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .sender_handler import router
|
||||
@@ -0,0 +1,44 @@
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from ..panel.keyboard import build_admin_back_btn
|
||||
|
||||
|
||||
class AdminSenderCallback(CallbackData, prefix="admin_sender"):
|
||||
type: str
|
||||
data: str | None = None
|
||||
|
||||
|
||||
def build_sender_kb() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👥 Все пользователи", callback_data=AdminSenderCallback(type="all").pack()))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="✅ С подпиской", callback_data=AdminSenderCallback(type="subscribed").pack()),
|
||||
InlineKeyboardButton(text="❌ Без подписки", callback_data=AdminSenderCallback(type="unsubscribed").pack()),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📍 Не использовавшие триал", callback_data=AdminSenderCallback(type="untrial").pack()
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📢 Кластер", callback_data=AdminSenderCallback(type="cluster-select").pack())
|
||||
)
|
||||
builder.row(build_admin_back_btn())
|
||||
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_clusters_kb(clusters: list) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for cluster in clusters:
|
||||
name = cluster["cluster_name"]
|
||||
builder.button(text=f"🌐 {name}", callback_data=AdminSenderCallback(type="cluster", data=name).pack())
|
||||
|
||||
builder.adjust(2)
|
||||
builder.row(build_admin_back_btn())
|
||||
|
||||
return builder.as_markup()
|
||||
@@ -7,10 +7,9 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
|
||||
from keyboards.admin.sender_kb import AdminSenderCallback, build_clusters_kb, build_sender_kb
|
||||
from .keyboard import AdminSenderCallback, build_clusters_kb, build_sender_kb
|
||||
from logger import logger
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -97,6 +96,8 @@ async def handle_message_input(message: Message, state: FSMContext, session: Any
|
||||
""",
|
||||
int(datetime.utcnow().timestamp() * 1000),
|
||||
)
|
||||
elif send_to == "untrial":
|
||||
tg_ids = await session.fetch("SELECT DISTINCT tg_id FROM connections WHERE trial = 0")
|
||||
elif send_to == "cluster":
|
||||
cluster_name = state_data.get("cluster_name")
|
||||
tg_ids = await session.fetch(
|
||||
@@ -115,6 +116,10 @@ async def handle_message_input(message: Message, state: FSMContext, session: Any
|
||||
total_users = len(tg_ids)
|
||||
success_count = 0
|
||||
|
||||
text = f"📤 <b>Рассылка начата!</b>\n👥 Количество получателей: {total_users}"
|
||||
|
||||
await message.answer(text=text)
|
||||
|
||||
for record in tg_ids:
|
||||
tg_id = record["tg_id"]
|
||||
try:
|
||||
@@ -131,11 +136,10 @@ async def handle_message_input(message: Message, state: FSMContext, session: Any
|
||||
|
||||
text = (
|
||||
f"📤 <b>Рассылка завершена!</b>\n\n"
|
||||
f"👥 <b>Всего пользователей:</b> {total_users}\n"
|
||||
f"👥 <b>Количество получателей:</b> {total_users}\n"
|
||||
f"✅ <b>Доставлено:</b> {success_count}\n"
|
||||
f"❌ <b>Не доставлено:</b> {total_users - success_count}"
|
||||
)
|
||||
|
||||
await message.answer(text=text, reply_markup=build_admin_back_kb("stats"), parse_mode="HTML")
|
||||
|
||||
await message.answer(text=text, reply_markup=build_admin_back_kb("sender"))
|
||||
await state.clear()
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .servers_handler import router
|
||||
@@ -0,0 +1,26 @@
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
|
||||
class AdminServerCallback(CallbackData, prefix="admin_server"):
|
||||
action: str
|
||||
data: str
|
||||
|
||||
|
||||
def build_manage_server_kb(server_name: str, cluster_name: str) -> InlineKeyboardMarkup:
|
||||
from ..clusters.keyboard import AdminClusterCallback
|
||||
|
||||
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.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
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.adjust(1)
|
||||
return builder.as_markup()
|
||||
@@ -0,0 +1,67 @@
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import CallbackQuery
|
||||
|
||||
from database import delete_server, get_servers
|
||||
from filters.admin import IsAdminFilter
|
||||
from .keyboard import (
|
||||
AdminServerCallback,
|
||||
build_delete_server_kb,
|
||||
build_manage_server_kb,
|
||||
)
|
||||
from ..panel.keyboard import build_admin_back_kb
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(AdminServerCallback.filter(F.action == "manage"), IsAdminFilter())
|
||||
async def handle_server_manage(callback_query: CallbackQuery, callback_data: AdminServerCallback):
|
||||
server_name = callback_data.data
|
||||
servers = await get_servers()
|
||||
|
||||
cluster_name, server = next(
|
||||
((c, s) for c, cs in servers.items() for s in cs if s["server_name"] == server_name), (None, None)
|
||||
)
|
||||
|
||||
if server:
|
||||
api_url = server["api_url"]
|
||||
subscription_url = server["subscription_url"]
|
||||
inbound_id = server["inbound_id"]
|
||||
|
||||
text = (
|
||||
f"<b>🔧 Информация о сервере {server_name}:</b>\n\n"
|
||||
f"<b>📡 API URL:</b> {api_url}\n"
|
||||
f"<b>🌐 Subscription URL:</b> {subscription_url}\n"
|
||||
f"<b>🔑 Inbound ID:</b> {inbound_id}"
|
||||
)
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=text,
|
||||
reply_markup=build_manage_server_kb(server_name, cluster_name),
|
||||
)
|
||||
else:
|
||||
await callback_query.message.edit_text(text="❌ Сервер не найден.")
|
||||
|
||||
|
||||
@router.callback_query(AdminServerCallback.filter(F.action == "delete"), IsAdminFilter())
|
||||
async def handle_server_delete(callback_query: CallbackQuery, callback_data: AdminServerCallback):
|
||||
server_name = callback_data.data
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=f"🗑️ Вы уверены, что хотите удалить сервер {server_name}?",
|
||||
reply_markup=build_delete_server_kb(server_name),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(AdminServerCallback.filter(F.action == "delete_confirm"), IsAdminFilter())
|
||||
async def handle_server_delete_confirm(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerCallback, session: Any
|
||||
):
|
||||
server_name = callback_data.data
|
||||
|
||||
await delete_server(server_name, session)
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text=f"🗑️ Сервер {server_name} успешно удален.", reply_markup=build_admin_back_kb("clusters")
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .stats_handler import router
|
||||
@@ -1,7 +1,7 @@
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_btn
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn
|
||||
|
||||
|
||||
def build_stats_kb() -> InlineKeyboardMarkup:
|
||||
@@ -2,17 +2,15 @@ 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 keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
|
||||
from keyboards.admin.stats_kb import build_stats_kb
|
||||
from .keyboard import build_stats_kb
|
||||
from logger import logger
|
||||
from utils.csv_export import export_payments_csv, export_users_csv
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .users_handler import router
|
||||
@@ -3,9 +3,9 @@ from datetime import datetime, timezone
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import RENEWAL_PRICES
|
||||
|
||||
from keyboards.admin.panel_kb import build_admin_back_btn
|
||||
from config import RENEWAL_PRICES
|
||||
from ..panel.keyboard import build_admin_back_btn
|
||||
|
||||
|
||||
class AdminUserEditorCallback(CallbackData, prefix="admin_users"):
|
||||
+1
-3
@@ -1,20 +1,18 @@
|
||||
import random
|
||||
import secrets
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import CAPTCHA_EMOJIS
|
||||
|
||||
from handlers.texts import CAPTCHA_PROMPT_MSG
|
||||
from logger import logger
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
|
||||
+1
-2
@@ -3,10 +3,9 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, LabeledPrice, Message, PreCheckoutQuery
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import RUB_TO_XTR
|
||||
|
||||
from logger import logger
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from aiogram import Router
|
||||
|
||||
from .instructions import router as instructions_router
|
||||
|
||||
|
||||
router = Router(name="instructions_main_router")
|
||||
|
||||
router.include_routers(
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import os
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram import F, Router
|
||||
from aiogram.types import (
|
||||
CallbackQuery,
|
||||
InlineKeyboardButton,
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import CONNECT_MACOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
|
||||
|
||||
from config import CONNECT_MACOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
|
||||
from database import get_key_details
|
||||
from handlers.texts import (
|
||||
CONNECT_TV_TEXT,
|
||||
@@ -21,7 +20,6 @@ from handlers.texts import (
|
||||
)
|
||||
from handlers.utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from aiogram import Router
|
||||
from .key_management import router as management_router
|
||||
from .keys import router as keys_router
|
||||
|
||||
|
||||
router = Router(name="keys_main_router")
|
||||
|
||||
router.include_routers(
|
||||
|
||||
@@ -1,15 +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 client import delete_client
|
||||
from config import (
|
||||
ADMIN_PASSWORD,
|
||||
ADMIN_USERNAME,
|
||||
@@ -27,10 +30,6 @@ from config import (
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from bot import bot
|
||||
from client import delete_client
|
||||
from database import (
|
||||
add_connection,
|
||||
check_connection_exists,
|
||||
@@ -52,7 +51,6 @@ from handlers.buttons.add_subscribe import (
|
||||
)
|
||||
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,
|
||||
@@ -64,7 +62,6 @@ from handlers.texts import (
|
||||
from handlers.utils import edit_or_send_message, generate_random_email, get_least_loaded_cluster
|
||||
from logger import logger
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
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 client 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
|
||||
|
||||
+17
-18
@@ -2,16 +2,17 @@ import asyncio
|
||||
import locale
|
||||
import os
|
||||
import time
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from handlers.payments.yookassa_pay import process_custom_amount_input
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
@@ -23,13 +24,11 @@ from config import (
|
||||
ENABLE_UPDATE_SUBSCRIPTION_BUTTON,
|
||||
PUBLIC_LINK,
|
||||
RENEWAL_PLANS,
|
||||
TOGGLE_CLIENT,
|
||||
TOTAL_GB,
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
TOGGLE_CLIENT,
|
||||
)
|
||||
|
||||
from bot import bot
|
||||
from database import (
|
||||
check_server_name_by_cluster,
|
||||
create_temporary_data,
|
||||
@@ -53,32 +52,30 @@ from handlers.buttons.add_subscribe import (
|
||||
from handlers.keys.key_utils import (
|
||||
delete_key_from_cluster,
|
||||
renew_key_in_cluster,
|
||||
toggle_client_on_cluster,
|
||||
update_subscription,
|
||||
toggle_client_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 (
|
||||
DELETE_KEY_CONFIRM_MSG,
|
||||
DISCOUNTS,
|
||||
FREEZE_SUBSCRIPTION_CONFIRM_MSG,
|
||||
FROZEN_SUBSCRIPTION_MSG,
|
||||
INSUFFICIENT_FUNDS_RENEWAL_MSG,
|
||||
KEY_DELETED_MSG_SIMPLE,
|
||||
KEY_NOT_FOUND_MSG,
|
||||
NO_SUBSCRIPTIONS_MSG,
|
||||
PLAN_SELECTION_MSG,
|
||||
SUBSCRIPTION_DESCRIPTION,
|
||||
SUBSCRIPTION_FROZEN_MSG,
|
||||
SUBSCRIPTION_UNFROZEN_MSG,
|
||||
SUCCESS_RENEWAL_MSG,
|
||||
UNFREEZE_SUBSCRIPTION_CONFIRM_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,
|
||||
)
|
||||
from handlers.utils import edit_or_send_message, handle_error
|
||||
from logger import logger
|
||||
|
||||
|
||||
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
|
||||
|
||||
router = Router()
|
||||
@@ -290,6 +287,7 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
|
||||
async def process_callback_unfreeze_subscription(callback_query: CallbackQuery, session: Any):
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
confirm_text = UNFREEZE_SUBSCRIPTION_CONFIRM_MSG
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
@@ -384,6 +382,7 @@ 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
|
||||
@@ -433,7 +432,7 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ
|
||||
if time_left < 0:
|
||||
time_left = 0
|
||||
|
||||
await session.execute(
|
||||
update_result = await session.execute(
|
||||
"""
|
||||
UPDATE keys
|
||||
SET expiry_time = $1,
|
||||
|
||||
@@ -4,18 +4,16 @@ 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 (
|
||||
DATABASE_URL,
|
||||
PROJECT_NAME,
|
||||
SUB_MESSAGE,
|
||||
SUPERNODE,
|
||||
SUPPORT_CHAT_URL,
|
||||
TOTAL_GB,
|
||||
@@ -23,7 +21,6 @@ from config import (
|
||||
USERNAME_BOT,
|
||||
USE_COUNTRY_SELECTION,
|
||||
)
|
||||
|
||||
from database import get_key_details, get_servers
|
||||
from handlers.utils import convert_to_bytes
|
||||
from logger import logger
|
||||
|
||||
@@ -5,7 +5,6 @@ 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,25 +1,23 @@
|
||||
import asyncio
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
|
||||
from aiogram import Bot, Router
|
||||
|
||||
from config import (
|
||||
DATABASE_URL,
|
||||
NOTIFICATION_TIME,
|
||||
NOTIFY_DELETE_DELAY,
|
||||
NOTIFY_DELETE_KEY,
|
||||
NOTIFY_INACTIVE_TRAFFIC,
|
||||
NOTIFY_MAXPRICE,
|
||||
NOTIFY_RENEW,
|
||||
NOTIFY_RENEW_EXPIRED,
|
||||
RENEWAL_PRICES,
|
||||
TOTAL_GB,
|
||||
TRIAL_TIME_DISABLE,
|
||||
NOTIFY_INACTIVE_TRAFFIC,
|
||||
)
|
||||
|
||||
from database import (
|
||||
add_notification,
|
||||
check_notification_time,
|
||||
@@ -32,23 +30,21 @@ from database import (
|
||||
)
|
||||
from handlers.keys.key_utils import delete_key_from_cluster, renew_key_in_cluster
|
||||
from handlers.texts import (
|
||||
KEY_EXPIRY_10H,
|
||||
KEY_EXPIRY_24H,
|
||||
KEY_RENEWED,
|
||||
KEY_RENEWED_TEMP_MSG,
|
||||
KEY_DELETED_MSG,
|
||||
KEY_EXPIRED_DELAY_HOURS_MINUTES_MSG,
|
||||
KEY_EXPIRED_DELAY_HOURS_MSG,
|
||||
KEY_EXPIRED_DELAY_MINUTES_MSG,
|
||||
KEY_EXPIRED_NO_DELAY_MSG,
|
||||
KEY_EXPIRY_10H,
|
||||
KEY_EXPIRY_24H,
|
||||
KEY_RENEWED,
|
||||
KEY_RENEWED_TEMP_MSG,
|
||||
)
|
||||
from keyboards.notifications.notify_kb import build_notification_expired_kb, build_notification_kb
|
||||
from logger import logger
|
||||
|
||||
from .notify_utils import send_notification
|
||||
from .special_notifications import notify_inactive_trial_users, notify_users_no_traffic
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import os
|
||||
|
||||
import aiofiles
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.exceptions import TelegramForbiddenError
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardMarkup
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
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
|
||||
from config import NOTIFY_EXTRA_DAYS, NOTIFY_INACTIVE, NOTIFY_INACTIVE_TRAFFIC, SUPPORT_CHAT_URL, TRIAL_TIME
|
||||
|
||||
from config import NOTIFY_EXTRA_DAYS, NOTIFY_INACTIVE, NOTIFY_INACTIVE_TRAFFIC, SUPPORT_CHAT_URL, TRIAL_TIME
|
||||
from database import (
|
||||
add_notification,
|
||||
check_notification_time,
|
||||
create_blocked_user,
|
||||
)
|
||||
from handlers.keys.key_utils import get_user_traffic
|
||||
from handlers.texts import TRIAL_INACTIVE_BONUS_MSG, TRIAL_INACTIVE_FIRST_MSG, ZERO_TRAFFIC_MSG
|
||||
from handlers.texts import TRIAL_INACTIVE_FIRST_MSG, TRIAL_INACTIVE_BONUS_MSG, ZERO_TRAFFIC_MSG
|
||||
from logger import logger
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
+1
-1
@@ -1,6 +1,7 @@
|
||||
from aiogram import F, Router
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import (
|
||||
CRYPTO_BOT_ENABLE,
|
||||
DONATIONS_ENABLE,
|
||||
@@ -14,7 +15,6 @@ from handlers.texts import PAYMENT_METHODS_MSG
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from aiogram import Router
|
||||
|
||||
from config import (
|
||||
CRYPTO_BOT_ENABLE,
|
||||
ROBOKASSA_ENABLE,
|
||||
@@ -8,7 +9,6 @@ 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
|
||||
|
||||
@@ -8,6 +8,9 @@ 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,
|
||||
@@ -16,8 +19,6 @@ from config import (
|
||||
ROBOKASSA_PASSWORD2,
|
||||
ROBOKASSA_TEST_MODE,
|
||||
)
|
||||
from robokassa import HashAlgorithm, Robokassa
|
||||
|
||||
from database import (
|
||||
add_connection,
|
||||
add_payment,
|
||||
@@ -26,7 +27,6 @@ from database import (
|
||||
get_temporary_data,
|
||||
update_balance,
|
||||
)
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from handlers.utils import edit_or_send_message
|
||||
from logger import logger
|
||||
|
||||
+3
-14
@@ -1,10 +1,8 @@
|
||||
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,6 +15,7 @@ from aiogram.types import (
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import (
|
||||
DATABASE_URL,
|
||||
INLINE_MODE,
|
||||
@@ -27,7 +26,6 @@ from config import (
|
||||
TRIAL_TIME,
|
||||
USERNAME_BOT,
|
||||
)
|
||||
|
||||
from database import get_balance, get_key_count, get_last_payments, get_referral_stats, get_trial
|
||||
from handlers.buttons.profile import (
|
||||
ADD_SUB,
|
||||
@@ -40,20 +38,11 @@ from handlers.buttons.profile import (
|
||||
MY_SUBS,
|
||||
PAYMENT,
|
||||
)
|
||||
from handlers.texts import (
|
||||
BALANCE_HISTORY_HEADER,
|
||||
BALANCE_MANAGEMENT_TEXT,
|
||||
INVITE_TEXT_NON_INLINE,
|
||||
get_referral_link,
|
||||
invite_message_send,
|
||||
profile_message_send,
|
||||
)
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback
|
||||
from logger import logger
|
||||
|
||||
from .admin.panel.keyboard import AdminPanelCallback
|
||||
from .texts import profile_message_send, invite_message_send, get_referral_link
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
|
||||
+3
-5
@@ -11,6 +11,8 @@ from aiogram.types import (
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CAPTCHA_ENABLE,
|
||||
CHANNEL_EXISTS,
|
||||
@@ -20,8 +22,6 @@ from config import (
|
||||
DONATIONS_ENABLE,
|
||||
SUPPORT_CHAT_URL,
|
||||
)
|
||||
|
||||
from bot import bot
|
||||
from database import (
|
||||
add_connection,
|
||||
add_referral,
|
||||
@@ -44,12 +44,10 @@ from handlers.texts import (
|
||||
WELCOME_TEXT,
|
||||
get_about_vpn,
|
||||
)
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback
|
||||
from logger import logger
|
||||
|
||||
from .admin.panel.keyboard import AdminPanelCallback
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
|
||||
+1
-2
@@ -7,11 +7,10 @@ import string
|
||||
import aiofiles
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardMarkup, InputMediaPhoto, Message
|
||||
from config import DATABASE_URL
|
||||
|
||||
from bot import bot
|
||||
from config import DATABASE_URL
|
||||
from database import get_all_keys, get_servers
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from keyboards.admin.panel_kb import build_admin_back_btn
|
||||
|
||||
|
||||
class AdminSenderCallback(CallbackData, prefix="admin_sender"):
|
||||
type: str
|
||||
data: str | None = None
|
||||
|
||||
|
||||
def build_sender_kb() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="👥 Все пользователи", callback_data=AdminSenderCallback(type="all").pack())
|
||||
builder.button(text="✅ Пользователи с подпиской", callback_data=AdminSenderCallback(type="subscribed").pack())
|
||||
builder.button(text="❌ Пользователи без подписки", callback_data=AdminSenderCallback(type="unsubscribed").pack())
|
||||
builder.button(text="📢 Пользователи кластера", callback_data=AdminSenderCallback(type="cluster-select").pack())
|
||||
builder.row(build_admin_back_btn())
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_clusters_kb(clusters: list) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
for cluster in clusters:
|
||||
name = cluster["cluster_name"]
|
||||
builder.button(text=f"🌐 {name}", callback_data=AdminSenderCallback(type="cluster", data=name).pack())
|
||||
|
||||
builder.row(build_admin_back_btn())
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
@@ -1,81 +0,0 @@
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_btn
|
||||
|
||||
|
||||
class AdminServerEditorCallback(CallbackData, prefix="admin_servers"):
|
||||
action: str
|
||||
data: str
|
||||
|
||||
|
||||
def build_clusters_editor_kb(servers: dict) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for cluster_name in servers:
|
||||
builder.button(
|
||||
text=f"⚙️ {cluster_name}",
|
||||
callback_data=AdminServerEditorCallback(action="clusters_manage", data=cluster_name).pack(),
|
||||
)
|
||||
|
||||
builder.button(text="➕ Добавить кластер", callback_data=AdminPanelCallback(action="clusters_add").pack())
|
||||
builder.row(build_admin_back_btn())
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_manage_cluster_kb(cluster_servers, cluster_name) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for server in cluster_servers:
|
||||
builder.button(
|
||||
text=f"🌍 {server['server_name']}",
|
||||
callback_data=AdminServerEditorCallback(action="servers_manage", data=server["server_name"]).pack(),
|
||||
)
|
||||
|
||||
builder.button(
|
||||
text="➕ Добавить сервер",
|
||||
callback_data=AdminServerEditorCallback(action="servers_add", data=cluster_name).pack(),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🌐 Доступность",
|
||||
callback_data=AdminServerEditorCallback(action="servers_availability", data=cluster_name).pack(),
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="🔄 Синхронизация",
|
||||
callback_data=AdminServerEditorCallback(action="clusters_sync", data=cluster_name).pack(),
|
||||
),
|
||||
)
|
||||
builder.button(
|
||||
text="💾 Создать бэкап кластера",
|
||||
callback_data=AdminServerEditorCallback(action="clusters_backup", data=cluster_name).pack(),
|
||||
)
|
||||
builder.row(build_admin_back_btn("servers"))
|
||||
builder.adjust(1, 1, 1, 1, 1, 2, 1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_manage_server_kb(server_name: str, cluster_name: str) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(
|
||||
text="🗑️ Удалить", callback_data=AdminServerEditorCallback(action="servers_delete", data=server_name).pack()
|
||||
)
|
||||
builder.button(
|
||||
text="🔙 Назад", callback_data=AdminServerEditorCallback(action="clusters_manage", data=cluster_name).pack()
|
||||
)
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_delete_server_kb(server_name: str) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(
|
||||
text="✅ Да", callback_data=AdminServerEditorCallback(action="servers_delete_confirm", data=server_name).pack()
|
||||
)
|
||||
builder.button(
|
||||
text="🔙 Назад", callback_data=AdminServerEditorCallback(action="servers_manage", data=server_name).pack()
|
||||
)
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
@@ -1,12 +1,10 @@
|
||||
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,5 +1,4 @@
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, Optional, Type, Union
|
||||
|
||||
from aiogram import Dispatcher
|
||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import CallbackQuery, Message, TelegramObject
|
||||
|
||||
from config import ADMIN_ID
|
||||
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import DATABASE_URL
|
||||
|
||||
|
||||
|
||||
+3
-5
@@ -1,19 +1,17 @@
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import ADMIN_ID, PING_TIME
|
||||
from ping3 import ping
|
||||
|
||||
from bot import bot
|
||||
from config import ADMIN_ID, PING_TIME
|
||||
from database import get_servers
|
||||
from keyboards.admin.servers_kb import AdminServerEditorCallback
|
||||
from handlers.admin.servers.keyboard import AdminServerCallback
|
||||
from logger import logger
|
||||
|
||||
|
||||
last_ping_times = {}
|
||||
last_down_times = {}
|
||||
notified_servers = set()
|
||||
@@ -49,7 +47,7 @@ async def notify_admin(server_name: str, status: str, down_duration: timedelta =
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="Управление сервером",
|
||||
callback_data=AdminServerEditorCallback(action="servers_manage", data=server_name).pack(),
|
||||
callback_data=AdminServerCallback(action="manage", data=server_name).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import csv
|
||||
|
||||
from io import StringIO
|
||||
from typing import Any
|
||||
|
||||
|
||||
Reference in New Issue
Block a user