runtime settings loaded from DB/ refactoring admin-panel/ minor fixes and improvements

This commit is contained in:
Vladless
2025-11-15 23:14:34 +03:00
parent 76390df4bc
commit 041e1c898d
97 changed files with 8554 additions and 6514 deletions
+13 -15
View File
@@ -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"))
+6 -14
View File
@@ -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}"))
+11 -6
View File
@@ -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,
+10 -6
View File
@@ -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:
+12 -6
View File
@@ -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
View File
@@ -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")
+9 -6
View File
@@ -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
+7 -3
View File
@@ -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
)
+1 -1
View File
@@ -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 [], {}