runtime settings loaded from DB/ refactoring admin-panel/ minor fixes and improvements
This commit is contained in:
@@ -7,7 +7,6 @@ import pytz
|
||||
from aiogram import Router
|
||||
from aiogram.types import (
|
||||
CallbackQuery,
|
||||
FSInputFile,
|
||||
InlineKeyboardButton,
|
||||
Message,
|
||||
WebAppInfo,
|
||||
@@ -15,7 +14,8 @@ from aiogram.types import (
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import CONNECT_PHONE_BUTTON, REMNAWAVE_WEBAPP, SUPPORT_CHAT_URL
|
||||
from config import REMNAWAVE_WEBAPP, SUPPORT_CHAT_URL
|
||||
from core.bootstrap import MODES_CONFIG
|
||||
from database import (
|
||||
get_key_details,
|
||||
get_tariff_by_id,
|
||||
@@ -25,10 +25,8 @@ from database import (
|
||||
)
|
||||
from handlers.buttons import (
|
||||
CONNECT_DEVICE,
|
||||
CONNECT_PHONE,
|
||||
MAIN_MENU,
|
||||
MY_SUB,
|
||||
PC_BUTTON,
|
||||
ROUTER_BUTTON,
|
||||
SUPPORT,
|
||||
TV_BUTTON,
|
||||
@@ -179,12 +177,12 @@ async def key_cluster_mode(
|
||||
builder.row(InlineKeyboardButton(text=ROUTER_BUTTON, callback_data=f"connect_router|{key_name}"))
|
||||
else:
|
||||
if await is_full_remnawave_cluster(least_loaded_cluster, session):
|
||||
use_webapp = REMNAWAVE_WEBAPP
|
||||
if REMNAWAVE_WEBAPP and final_link:
|
||||
use_webapp = bool(MODES_CONFIG.get("REMNAWAVE_WEBAPP_ENABLED", REMNAWAVE_WEBAPP))
|
||||
if use_webapp and final_link:
|
||||
try:
|
||||
webapp_override_results = await run_hooks(
|
||||
"remnawave_webapp_override",
|
||||
remnawave_webapp=REMNAWAVE_WEBAPP,
|
||||
remnawave_webapp=use_webapp,
|
||||
final_link=final_link,
|
||||
session=session,
|
||||
)
|
||||
@@ -196,20 +194,20 @@ async def key_cluster_mode(
|
||||
use_webapp = hook_result["override"]
|
||||
except Exception as e:
|
||||
logger.warning(f"[REMNAWAVE_WEBAPP_OVERRIDE] Ошибка при применении хуков: {e}")
|
||||
|
||||
if use_webapp and final_link:
|
||||
|
||||
if (
|
||||
use_webapp
|
||||
and final_link
|
||||
and isinstance(final_link, str)
|
||||
and final_link.startswith(("http://", "https://"))
|
||||
):
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, web_app=WebAppInfo(url=final_link)))
|
||||
builder.row(InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"))
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, callback_data=f"connect_device|{key_name}"))
|
||||
elif CONNECT_PHONE_BUTTON:
|
||||
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}"),
|
||||
)
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, callback_data=f"connect_device|{key_name}"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text=MY_SUB, callback_data=f"view_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
@@ -19,13 +19,13 @@ from bot import bot
|
||||
from config import (
|
||||
ADMIN_PASSWORD,
|
||||
ADMIN_USERNAME,
|
||||
CONNECT_PHONE_BUTTON,
|
||||
HAPP_CRYPTOLINK,
|
||||
REMNAWAVE_LOGIN,
|
||||
REMNAWAVE_PASSWORD,
|
||||
REMNAWAVE_WEBAPP,
|
||||
SUPPORT_CHAT_URL,
|
||||
)
|
||||
from core.bootstrap import MODES_CONFIG
|
||||
from database import (
|
||||
add_user,
|
||||
check_server_name_by_cluster,
|
||||
@@ -41,10 +41,8 @@ from database.models import Key, Server, Tariff
|
||||
from handlers.buttons import (
|
||||
BACK,
|
||||
CONNECT_DEVICE,
|
||||
CONNECT_PHONE,
|
||||
MAIN_MENU,
|
||||
MY_SUB,
|
||||
PC_BUTTON,
|
||||
ROUTER_BUTTON,
|
||||
SUPPORT,
|
||||
TV_BUTTON,
|
||||
@@ -488,7 +486,7 @@ async def finalize_key_creation(
|
||||
)
|
||||
|
||||
if not remnawave_link:
|
||||
if HAPP_CRYPTOLINK:
|
||||
if bool(MODES_CONFIG.get("HAPP_CRYPTOLINK_ENABLED", HAPP_CRYPTOLINK)):
|
||||
happ = sub.get("happ") or {}
|
||||
remnawave_link = happ.get("cryptoLink") or happ.get("link")
|
||||
if not remnawave_link:
|
||||
@@ -593,12 +591,12 @@ async def finalize_key_creation(
|
||||
else None
|
||||
)
|
||||
|
||||
use_webapp = REMNAWAVE_WEBAPP
|
||||
if REMNAWAVE_WEBAPP and webapp_url:
|
||||
use_webapp = bool(MODES_CONFIG.get("REMNAWAVE_WEBAPP_ENABLED", REMNAWAVE_WEBAPP))
|
||||
if use_webapp and webapp_url:
|
||||
try:
|
||||
webapp_override_results = await run_hooks(
|
||||
"remnawave_webapp_override",
|
||||
remnawave_webapp=REMNAWAVE_WEBAPP,
|
||||
remnawave_webapp=use_webapp,
|
||||
final_link=final_link,
|
||||
session=session,
|
||||
)
|
||||
@@ -617,15 +615,9 @@ async def finalize_key_creation(
|
||||
else:
|
||||
if use_webapp and webapp_url:
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, web_app=WebAppInfo(url=webapp_url)))
|
||||
builder.row(InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"))
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, callback_data=f"connect_device|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"))
|
||||
elif CONNECT_PHONE_BUTTON:
|
||||
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}"),
|
||||
)
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, callback_data=f"connect_device|{key_name}"))
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from config import (
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
from core.bootstrap import MODES_CONFIG, NOTIFICATIONS_CONFIG
|
||||
from database import (
|
||||
add_user,
|
||||
check_user_exists,
|
||||
@@ -89,7 +90,8 @@ async def handle_key_creation(
|
||||
try:
|
||||
current_time = datetime.now(moscow_tz)
|
||||
|
||||
if not TRIAL_TIME_DISABLE:
|
||||
trial_time_disabled = bool(MODES_CONFIG.get("TRIAL_TIME_DISABLED", TRIAL_TIME_DISABLE))
|
||||
if not trial_time_disabled:
|
||||
trial_status = await get_trial(session, tg_id)
|
||||
if trial_status in [0, -1]:
|
||||
trial_tariffs = await get_tariffs(session, group_code="trial")
|
||||
@@ -109,7 +111,8 @@ async def handle_key_creation(
|
||||
|
||||
trial_tariff = trial_tariffs[0]
|
||||
base_days = trial_tariff["duration_days"]
|
||||
extra_days = NOTIFY_EXTRA_DAYS if trial_status == -1 else 0
|
||||
extra_days_value = int(NOTIFICATIONS_CONFIG.get("EXTRA_DAYS_AFTER_EXPIRY", NOTIFY_EXTRA_DAYS))
|
||||
extra_days = extra_days_value if trial_status == -1 else 0
|
||||
total_days = base_days + extra_days
|
||||
expiry_time = current_time + timedelta(days=total_days)
|
||||
|
||||
@@ -156,8 +159,6 @@ async def handle_key_creation(
|
||||
if tariffs:
|
||||
group_code = tariffs[0].get("group_code")
|
||||
if group_code:
|
||||
from database.notifications import check_hot_lead_discount
|
||||
|
||||
discount_info = await check_hot_lead_discount(session, tg_id)
|
||||
|
||||
if discount_info and discount_info.get("available"):
|
||||
@@ -278,8 +279,10 @@ async def handle_key_creation(
|
||||
if discount_info and discount_info.get("available"):
|
||||
offer_text = DISCOUNT_OFFER_STEP2 if discount_info["type"] == "hot_lead_step_2" else DISCOUNT_OFFER_STEP3
|
||||
expires_at = discount_info["expires_at"]
|
||||
discount_active_hours = int(NOTIFICATIONS_CONFIG.get("DISCOUNT_ACTIVE_HOURS", DISCOUNT_ACTIVE_HOURS))
|
||||
time_left = format_discount_time_left(
|
||||
expires_at - timedelta(hours=DISCOUNT_ACTIVE_HOURS), DISCOUNT_ACTIVE_HOURS
|
||||
expires_at - timedelta(hours=discount_active_hours),
|
||||
discount_active_hours,
|
||||
)
|
||||
discount_message = DISCOUNT_OFFER_MESSAGE.format(offer_text=offer_text, time_left=time_left)
|
||||
|
||||
@@ -486,7 +489,9 @@ async def create_key(
|
||||
session=session,
|
||||
)
|
||||
|
||||
if USE_COUNTRY_SELECTION:
|
||||
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
|
||||
|
||||
if use_country_selection:
|
||||
await key_country_mode(
|
||||
tg_id=tg_id,
|
||||
expiry_time=expiry_time,
|
||||
|
||||
@@ -7,6 +7,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import DISCOUNT_ACTIVE_HOURS
|
||||
from core.bootstrap import NOTIFICATIONS_CONFIG
|
||||
from database import get_keys, get_tariffs
|
||||
from database.models import Notification
|
||||
from handlers.buttons import MAIN_MENU, RENEW_KEY_NOTIFICATION
|
||||
@@ -37,8 +38,10 @@ async def handle_discount_entry(callback: CallbackQuery, session: AsyncSession):
|
||||
await callback.message.edit_text("❌ Скидка недоступна.")
|
||||
return
|
||||
|
||||
discount_active_hours = int(NOTIFICATIONS_CONFIG.get("DISCOUNT_ACTIVE_HOURS", DISCOUNT_ACTIVE_HOURS))
|
||||
|
||||
now = datetime.utcnow()
|
||||
if now - last_time > timedelta(hours=DISCOUNT_ACTIVE_HOURS):
|
||||
if now - last_time > timedelta(hours=discount_active_hours):
|
||||
await callback.message.edit_text("⏳ Срок действия скидки истёк.")
|
||||
return
|
||||
|
||||
@@ -49,13 +52,13 @@ async def handle_discount_entry(callback: CallbackQuery, session: AsyncSession):
|
||||
builder.row(InlineKeyboardButton(text=RENEW_KEY_NOTIFICATION, callback_data=f"renew_key|{keys[0].email}"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
expires_at = last_time + timedelta(hours=DISCOUNT_ACTIVE_HOURS)
|
||||
expires_at = last_time + timedelta(hours=discount_active_hours)
|
||||
await callback.message.edit_text(
|
||||
f"🎯 <b>ЭКСКЛЮЗИВНОЕ ПРЕДЛОЖЕНИЕ!</b>\n\n<blockquote>"
|
||||
f"💎 <b>Специальные тарифы</b> — доступные только для вас!\n"
|
||||
f"🚀 <b>Получите максимум возможностей</b> по выгодной цене!\n"
|
||||
f"</blockquote>\n"
|
||||
f"⏰ <b>Предложение действует всего: {format_discount_time_left(expires_at, DISCOUNT_ACTIVE_HOURS)} — не упустите свой шанс!</b>",
|
||||
f"⏰ <b>Предложение действует всего: {format_discount_time_left(expires_at, discount_active_hours)} — не упустите свой шанс!</b>",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
@@ -82,7 +85,6 @@ async def handle_discount_tariff_selection(callback: CallbackQuery, session, sta
|
||||
data=f"select_tariff_plan|{tariff_id}",
|
||||
)
|
||||
await select_tariff_plan(fake_callback, session=session, state=state)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при выборе скидочного тарифа: {e}")
|
||||
await callback.message.answer("❌ Произошла ошибка при выборе тарифа.")
|
||||
@@ -104,8 +106,10 @@ async def handle_ultra_discount(callback: CallbackQuery, session: AsyncSession):
|
||||
await callback.message.edit_text("❌ Скидка недоступна.")
|
||||
return
|
||||
|
||||
discount_active_hours = int(NOTIFICATIONS_CONFIG.get("DISCOUNT_ACTIVE_HOURS", DISCOUNT_ACTIVE_HOURS))
|
||||
|
||||
now = datetime.utcnow()
|
||||
if now - last_time > timedelta(hours=DISCOUNT_ACTIVE_HOURS):
|
||||
if now - last_time > timedelta(hours=discount_active_hours):
|
||||
await callback.message.edit_text("⏳ Срок действия финальной скидки истёк.")
|
||||
return
|
||||
|
||||
@@ -121,7 +125,7 @@ async def handle_ultra_discount(callback: CallbackQuery, session: AsyncSession):
|
||||
f"💎 <b>Доступ к тарифам с МАКСИМАЛЬНОЙ выгодой</b> — только для вас!\n"
|
||||
f"🚀 <b>Уникальные условия</b> — получите максимум преимуществ по минимальной цене!\n"
|
||||
f"</blockquote>\n"
|
||||
f"⏰ <b>Время ограничено: {format_discount_time_left(last_time, DISCOUNT_ACTIVE_HOURS)} — не упустите шанс!</b>",
|
||||
f"⏰ <b>Время ограничено: {format_discount_time_left(last_time, discount_active_hours)} — не упустите шанс!</b>",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -14,13 +14,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from bot import bot
|
||||
from config import DISCOUNT_ACTIVE_HOURS, RENEW_BUTTON_BEFORE_DAYS, USE_NEW_PAYMENT_FLOW
|
||||
from core.bootstrap import NOTIFICATIONS_CONFIG
|
||||
from database import (
|
||||
check_tariff_exists,
|
||||
get_balance,
|
||||
get_key_by_server,
|
||||
get_key_details,
|
||||
get_tariff_by_id,
|
||||
get_tariffs,
|
||||
update_balance,
|
||||
update_key_expiry,
|
||||
)
|
||||
@@ -179,7 +179,10 @@ async def process_callback_renew_key(callback_query: CallbackQuery, state: FSMCo
|
||||
)
|
||||
)
|
||||
|
||||
sorted_subgroups = sorted([k for k in grouped_tariffs if k], key=lambda x: (subgroup_weights.get(x, 999999), x))
|
||||
sorted_subgroups = sorted(
|
||||
[k for k in grouped_tariffs if k],
|
||||
key=lambda x: (subgroup_weights.get(x, 999999), x),
|
||||
)
|
||||
|
||||
for subgroup in sorted_subgroups:
|
||||
subgroup_hash = create_subgroup_hash(subgroup, group_code)
|
||||
@@ -210,10 +213,12 @@ async def process_callback_renew_key(callback_query: CallbackQuery, state: FSMCo
|
||||
|
||||
discount_message = ""
|
||||
if discount_info.get("available"):
|
||||
discount_active_hours = int(NOTIFICATIONS_CONFIG.get("DISCOUNT_ACTIVE_HOURS", DISCOUNT_ACTIVE_HOURS))
|
||||
offer_text = DISCOUNT_OFFER_STEP2 if discount_info["type"] == "hot_lead_step_2" else DISCOUNT_OFFER_STEP3
|
||||
expires_at = discount_info["expires_at"]
|
||||
time_left = format_discount_time_left(
|
||||
expires_at - timedelta(hours=DISCOUNT_ACTIVE_HOURS), DISCOUNT_ACTIVE_HOURS
|
||||
expires_at - timedelta(hours=discount_active_hours),
|
||||
discount_active_hours,
|
||||
)
|
||||
discount_message = DISCOUNT_OFFER_MESSAGE.format(offer_text=offer_text, time_left=time_left)
|
||||
|
||||
@@ -362,10 +367,12 @@ async def show_tariffs_in_renew_subgroup(callback: CallbackQuery, state: FSMCont
|
||||
|
||||
discount_message = ""
|
||||
if discount_info.get("available"):
|
||||
discount_active_hours = int(NOTIFICATIONS_CONFIG.get("DISCOUNT_ACTIVE_HOURS", DISCOUNT_ACTIVE_HOURS))
|
||||
offer_text = DISCOUNT_OFFER_STEP2 if discount_info["type"] == "hot_lead_step_2" else DISCOUNT_OFFER_STEP3
|
||||
expires_at = discount_info["expires_at"]
|
||||
time_left = format_discount_time_left(
|
||||
expires_at - timedelta(hours=DISCOUNT_ACTIVE_HOURS), DISCOUNT_ACTIVE_HOURS
|
||||
expires_at - timedelta(hours=discount_active_hours),
|
||||
discount_active_hours,
|
||||
)
|
||||
discount_message = DISCOUNT_OFFER_MESSAGE.format(offer_text=offer_text, time_left=time_left)
|
||||
|
||||
@@ -383,8 +390,7 @@ async def show_tariffs_in_renew_subgroup(callback: CallbackQuery, state: FSMCont
|
||||
@router.callback_query(F.data.startswith("renew_plan|"))
|
||||
async def process_callback_renew_plan(callback_query: CallbackQuery, state: FSMContext, session: Any):
|
||||
tg_id = callback_query.from_user.id
|
||||
tariff_id = callback_query.data.split("|")[1]
|
||||
tariff_id = int(tariff_id)
|
||||
tariff_id = int(callback_query.data.split("|")[1])
|
||||
|
||||
data = await state.get_data()
|
||||
client_id = data.get("renew_client_id")
|
||||
|
||||
+40
-46
@@ -17,7 +17,6 @@ from sqlalchemy import update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import (
|
||||
CONNECT_PHONE_BUTTON,
|
||||
ENABLE_DELETE_KEY_BUTTON,
|
||||
HAPP_CRYPTOLINK,
|
||||
HWID_RESET_BUTTON,
|
||||
@@ -28,6 +27,7 @@ from config import (
|
||||
TOGGLE_CLIENT,
|
||||
USE_COUNTRY_SELECTION,
|
||||
)
|
||||
from core.bootstrap import BUTTONS_CONFIG, MODES_CONFIG
|
||||
from database import get_key_details, get_keys, get_servers, get_tariff_by_id
|
||||
from database.models import Key
|
||||
from handlers.buttons import (
|
||||
@@ -35,12 +35,10 @@ from handlers.buttons import (
|
||||
BACK,
|
||||
CHANGE_LOCATION,
|
||||
CONNECT_DEVICE,
|
||||
CONNECT_PHONE,
|
||||
DELETE,
|
||||
FREEZE,
|
||||
HWID_BUTTON,
|
||||
MAIN_MENU,
|
||||
PC_BUTTON,
|
||||
QR,
|
||||
RENEW_KEY,
|
||||
ROUTER_BUTTON,
|
||||
@@ -71,6 +69,7 @@ from panels.remnawave import RemnawaveAPI
|
||||
|
||||
|
||||
router = Router()
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
|
||||
class RenameKeyState(StatesGroup):
|
||||
@@ -105,17 +104,13 @@ async def process_callback_or_message_view_keys(callback_query_or_message: Messa
|
||||
reply_markup=inline_keyboard,
|
||||
media_path=image_path,
|
||||
)
|
||||
except Exception as e:
|
||||
error_message = f"Ошибка при получении ключей: {e}"
|
||||
except Exception as error:
|
||||
error_message = f"Ошибка при получении ключей: {error}"
|
||||
await target_message.answer(text=error_message)
|
||||
|
||||
|
||||
async def build_keys_response(records, session):
|
||||
"""
|
||||
Формирует сообщение и клавиатуру для устройств с указанием срока действия подписки.
|
||||
"""
|
||||
builder = InlineKeyboardBuilder()
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
if records:
|
||||
response_message = KEYS_HEADER
|
||||
@@ -139,7 +134,7 @@ async def build_keys_response(records, session):
|
||||
tariff = await get_tariff_by_id(session, record.tariff_id)
|
||||
if tariff and tariff.get("vless"):
|
||||
is_vless = True
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
icon = "📶" if is_vless else "🔑"
|
||||
@@ -204,10 +199,9 @@ async def handle_new_alias_input(message: Message, state: FSMContext, session: A
|
||||
update(Key).where(Key.tg_id == message.chat.id, Key.client_id == client_id).values(alias=alias)
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
except Exception as e:
|
||||
except Exception as error:
|
||||
await message.answer("❌ Не удалось переименовать подписку.")
|
||||
logger.error(f"Ошибка при обновлении alias: {e}")
|
||||
logger.error(f"Ошибка при обновлении alias: {error}")
|
||||
finally:
|
||||
await state.clear()
|
||||
|
||||
@@ -293,8 +287,8 @@ async def render_key_info(message: Message, session: Any, key_name: str, image_p
|
||||
if user_data:
|
||||
used_bytes = user_data.get("usedTrafficBytes", 0)
|
||||
remna_used_gb = round(used_bytes / 1073741824, 1)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении данных Remnawave для {client_id}: {e}")
|
||||
except Exception as error:
|
||||
logger.error(f"Ошибка при получении данных Remnawave для {client_id}: {error}")
|
||||
|
||||
tariff_name = ""
|
||||
traffic_limit = 0
|
||||
@@ -310,12 +304,16 @@ async def render_key_info(message: Message, session: Any, key_name: str, image_p
|
||||
|
||||
tariff_duration = tariff_name
|
||||
|
||||
country_selection_enabled = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
|
||||
remnawave_webapp_enabled = bool(MODES_CONFIG.get("REMNAWAVE_WEBAPP_ENABLED", REMNAWAVE_WEBAPP))
|
||||
happ_cryptolink_enabled = bool(MODES_CONFIG.get("HAPP_CRYPTOLINK_ENABLED", HAPP_CRYPTOLINK))
|
||||
|
||||
response_message = key_message(
|
||||
final_link,
|
||||
formatted_expiry_date,
|
||||
days_left_message,
|
||||
server_name,
|
||||
server_name if USE_COUNTRY_SELECTION else None,
|
||||
server_name if country_selection_enabled else None,
|
||||
hwid_count=hwid_count if device_limit is not None else 0,
|
||||
tariff_name=tariff_duration,
|
||||
traffic_limit=traffic_limit,
|
||||
@@ -325,12 +323,12 @@ async def render_key_info(message: Message, session: Any, key_name: str, image_p
|
||||
remna_used_gb=remna_used_gb,
|
||||
)
|
||||
|
||||
use_webapp = REMNAWAVE_WEBAPP
|
||||
if is_full_remnawave and final_link and REMNAWAVE_WEBAPP and not HAPP_CRYPTOLINK:
|
||||
use_webapp = remnawave_webapp_enabled
|
||||
if is_full_remnawave and final_link and remnawave_webapp_enabled and not happ_cryptolink_enabled:
|
||||
try:
|
||||
webapp_override_results = await run_hooks(
|
||||
"remnawave_webapp_override",
|
||||
remnawave_webapp=REMNAWAVE_WEBAPP,
|
||||
remnawave_webapp=remnawave_webapp_enabled,
|
||||
final_link=final_link,
|
||||
session=session,
|
||||
)
|
||||
@@ -340,47 +338,41 @@ async def render_key_info(message: Message, session: Any, key_name: str, image_p
|
||||
use_webapp = hook_result
|
||||
elif isinstance(hook_result, dict) and "override" in hook_result:
|
||||
use_webapp = hook_result["override"]
|
||||
except Exception as e:
|
||||
logger.warning(f"[REMNAWAVE_WEBAPP_OVERRIDE] Ошибка при применении хуков: {e}")
|
||||
|
||||
if is_full_remnawave and final_link and use_webapp and not HAPP_CRYPTOLINK:
|
||||
except Exception as error:
|
||||
logger.warning(f"[REMNAWAVE_WEBAPP_OVERRIDE] Ошибка при применении хуков: {error}")
|
||||
|
||||
if is_full_remnawave and final_link and use_webapp and not happ_cryptolink_enabled:
|
||||
if vless_enabled:
|
||||
builder.row(InlineKeyboardButton(text=ROUTER_BUTTON, callback_data=f"connect_router|{key_name}"))
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, web_app=WebAppInfo(url=final_link)))
|
||||
builder.row(InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"))
|
||||
else:
|
||||
if CONNECT_PHONE_BUTTON:
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_PHONE, callback_data=f"connect_phone|{key_name}"))
|
||||
if vless_enabled:
|
||||
builder.row(InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=ROUTER_BUTTON, callback_data=f"connect_router|{key_name}"))
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"),
|
||||
)
|
||||
if vless_enabled:
|
||||
builder.row(InlineKeyboardButton(text=ROUTER_BUTTON, callback_data=f"connect_router|{key_name}"))
|
||||
else:
|
||||
if vless_enabled:
|
||||
builder.row(InlineKeyboardButton(text=ROUTER_BUTTON, callback_data=f"connect_router|{key_name}"))
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, callback_data=f"connect_device|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, callback_data=f"connect_device|{key_name}"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text=RENEW_KEY, callback_data=f"renew_key|{key_name}"))
|
||||
|
||||
if HWID_RESET_BUTTON and hwid_count > 0:
|
||||
hwid_reset_enabled = bool(BUTTONS_CONFIG.get("HWID_RESET_BUTTON_ENABLE", HWID_RESET_BUTTON))
|
||||
qrcode_enabled = bool(BUTTONS_CONFIG.get("QRCODE_BUTTON_ENABLE", QRCODE))
|
||||
delete_key_enabled = bool(BUTTONS_CONFIG.get("DELETE_KEY_BUTTON_ENABLE", ENABLE_DELETE_KEY_BUTTON))
|
||||
toggle_client_enabled = bool(BUTTONS_CONFIG.get("TOGGLE_CLIENT_BUTTON_ENABLE", TOGGLE_CLIENT))
|
||||
|
||||
if hwid_reset_enabled and hwid_count > 0:
|
||||
builder.row(InlineKeyboardButton(text=HWID_BUTTON, callback_data=f"reset_hwid|{key_name}"))
|
||||
|
||||
if QRCODE:
|
||||
if qrcode_enabled:
|
||||
builder.row(InlineKeyboardButton(text=QR, callback_data=f"show_qr|{key_name}"))
|
||||
|
||||
if ENABLE_DELETE_KEY_BUTTON:
|
||||
if delete_key_enabled:
|
||||
builder.row(InlineKeyboardButton(text=DELETE, callback_data=f"delete_key|{key_name}"))
|
||||
|
||||
if USE_COUNTRY_SELECTION:
|
||||
if country_selection_enabled:
|
||||
builder.row(InlineKeyboardButton(text=CHANGE_LOCATION, callback_data=f"change_location|{key_name}"))
|
||||
|
||||
if TOGGLE_CLIENT:
|
||||
if toggle_client_enabled:
|
||||
builder.row(InlineKeyboardButton(text=FREEZE, callback_data=f"freeze_subscription|{key_name}"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
@@ -437,12 +429,14 @@ async def handle_reset_hwid(callback_query: CallbackQuery, session: Any):
|
||||
"after_hwid_reset", chat_id=callback_query.from_user.id, admin=False, session=session, key_name=key_name
|
||||
)
|
||||
if hook_result and any("redirect_to_profile" in str(result) for result in hook_result):
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
if callback_query.message.text:
|
||||
await callback_query.message.edit_text("✅ Устройства сброшены", reply_markup=kb.as_markup())
|
||||
await callback_query.message.edit_text("✅ Устройства сброшены", reply_markup=builder.as_markup())
|
||||
else:
|
||||
await callback_query.message.edit_caption(caption="✅ Устройства сброшены", reply_markup=kb.as_markup())
|
||||
await callback_query.message.edit_caption(
|
||||
caption="✅ Устройства сброшены", reply_markup=builder.as_markup()
|
||||
)
|
||||
return
|
||||
|
||||
image_path = os.path.join("img", "pic_view.jpg")
|
||||
|
||||
@@ -4,6 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import HAPP_CRYPTOLINK, LEGACY_LINKS, PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE
|
||||
from database import filter_cluster_by_subgroup, get_key_details, get_tariff_by_id
|
||||
from core.bootstrap import MODES_CONFIG
|
||||
from logger import logger
|
||||
from panels._3xui import get_vless_link_for_client, get_xui_instance
|
||||
from panels.remnawave import RemnawaveAPI
|
||||
@@ -23,6 +24,8 @@ async def _is_vless_tariff(session: AsyncSession, email: str) -> bool:
|
||||
|
||||
|
||||
async def _try_build_remna_vless(servers: list, email: str) -> tuple[str | None, str | None]:
|
||||
happ_cryptolink_enabled = bool(MODES_CONFIG.get("HAPP_CRYPTOLINK_ENABLED", HAPP_CRYPTOLINK))
|
||||
|
||||
si = servers[0]
|
||||
remna = RemnawaveAPI(si["api_url"])
|
||||
ok = await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD)
|
||||
@@ -50,7 +53,7 @@ async def _try_build_remna_vless(servers: list, email: str) -> tuple[str | None,
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if HAPP_CRYPTOLINK and happ_link:
|
||||
if happ_cryptolink_enabled and happ_link:
|
||||
return best, happ_link
|
||||
|
||||
sub_url = data.get("subscriptionUrl")
|
||||
@@ -102,6 +105,8 @@ async def make_aggregated_link(
|
||||
remna_link_override: str | None = None,
|
||||
plan=None,
|
||||
) -> str | None:
|
||||
legacy_links_enabled = bool(MODES_CONFIG.get("LEGACY_LINKS_ENABLED", LEGACY_LINKS))
|
||||
|
||||
servers = (
|
||||
await filter_cluster_by_subgroup(session, cluster_all, subgroup_code, cluster_id)
|
||||
if subgroup_code
|
||||
@@ -125,7 +130,7 @@ async def make_aggregated_link(
|
||||
base = PUBLIC_LINK.rstrip("/")
|
||||
|
||||
if vless_needed:
|
||||
if LEGACY_LINKS:
|
||||
if legacy_links_enabled:
|
||||
if xui:
|
||||
xui_link = await _try_build_3xui_vless(xui, email)
|
||||
if xui_link:
|
||||
@@ -158,14 +163,12 @@ async def make_aggregated_link(
|
||||
return f"{base}/{email}/{tg_id}"
|
||||
|
||||
if remna and not xui:
|
||||
if LEGACY_LINKS:
|
||||
if legacy_links_enabled:
|
||||
logger.info("[agg_link] LEGACY non-vless -> base link")
|
||||
return f"{base}/{email}/{tg_id}"
|
||||
best_vless, sub_url = await _try_build_remna_vless(remna, email)
|
||||
if remna_link_override and (
|
||||
remna_link_override.lower().startswith("vless://") or
|
||||
remna_link_override.startswith("http") or
|
||||
remna_link_override.startswith("happ://")
|
||||
remna_link_override.lower().startswith("vless://") or remna_link_override.startswith(("http", "happ://"))
|
||||
):
|
||||
logger.info("[agg_link] choose override Remnawave (non-vless)")
|
||||
return remna_link_override
|
||||
|
||||
@@ -8,6 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config import HAPP_CRYPTOLINK, PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE
|
||||
from database import get_servers, get_tariff_by_id, store_key
|
||||
from database.models import User
|
||||
from core.bootstrap import MODES_CONFIG
|
||||
from handlers.utils import ALLOWED_GROUP_CODES, check_server_key_limit
|
||||
from hooks.hooks import run_hooks
|
||||
from logger import (
|
||||
@@ -152,7 +153,10 @@ async def create_key_on_cluster(
|
||||
except Exception as e:
|
||||
logger.error(f"{PANEL_REMNA} Ошибка сборки VLESS: {e}")
|
||||
|
||||
use_crypto_link = HAPP_CRYPTOLINK
|
||||
base_use_crypto_link = bool(
|
||||
MODES_CONFIG.get("HAPP_CRYPTOLINK_ENABLED", HAPP_CRYPTOLINK)
|
||||
)
|
||||
use_crypto_link = base_use_crypto_link
|
||||
try:
|
||||
hook_results = await run_hooks(
|
||||
"happ_cryptolink_override",
|
||||
@@ -161,7 +165,7 @@ async def create_key_on_cluster(
|
||||
session=session,
|
||||
email=email,
|
||||
tg_id=tg_id,
|
||||
happ_cryptolink=HAPP_CRYPTOLINK,
|
||||
happ_cryptolink=base_use_crypto_link,
|
||||
)
|
||||
if hook_results:
|
||||
for hook_result in hook_results:
|
||||
@@ -170,7 +174,7 @@ async def create_key_on_cluster(
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(f"[HAPP_CRYPTOLINK_OVERRIDE] Ошибка при применении хуков: {e}")
|
||||
|
||||
|
||||
remnawave_key = link_vless or (
|
||||
result["happ"]["cryptoLink"] if use_crypto_link else result.get("subscriptionUrl")
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import HAPP_CRYPTOLINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE
|
||||
from database import filter_cluster_by_subgroup, update_key_client_id
|
||||
from core.bootstrap import MODES_CONFIG
|
||||
from logger import (
|
||||
CLOGGER as logger,
|
||||
PANEL_REMNA,
|
||||
@@ -42,6 +43,7 @@ async def ensure_on_remnawave(
|
||||
|
||||
expire_iso = datetime.utcfromtimestamp(new_expiry_time // 1000).isoformat() + "Z"
|
||||
traffic_bytes = bytes_from_gb(total_gb)
|
||||
use_crypto_link = bool(MODES_CONFIG.get("HAPP_CRYPTOLINK_ENABLED", HAPP_CRYPTOLINK))
|
||||
|
||||
async def do_update():
|
||||
try:
|
||||
@@ -82,7 +84,7 @@ async def ensure_on_remnawave(
|
||||
new_uuid = created.get("uuid") if isinstance(created, dict) else None
|
||||
remna_link = None
|
||||
if isinstance(created, dict):
|
||||
if HAPP_CRYPTOLINK:
|
||||
if use_crypto_link:
|
||||
remna_link = (
|
||||
created.get("happ", {}).get("cryptoLink") if isinstance(created.get("happ"), dict) else None
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ async def fetch_url_content(url: str, identifier: str) -> tuple[list[str], dict[
|
||||
return lines, headers
|
||||
return [], {}
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching URL {url}: {e}")
|
||||
logger.debug(f"Error fetching URL {url}: {e}")
|
||||
return [], {}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user