add support remnawave 2.3.x

This commit is contained in:
Vladless
2025-12-09 23:59:31 +03:00
parent 222de4d822
commit cf8259e623
6 changed files with 114 additions and 49 deletions
-1
View File
@@ -12,7 +12,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from config import (
ADMIN_PASSWORD,
ADMIN_USERNAME,
HAPP_CRYPTOLINK,
REMNAWAVE_LOGIN,
REMNAWAVE_PASSWORD,
USE_COUNTRY_SELECTION,
+44 -3
View File
@@ -52,12 +52,53 @@ async def handle_hwid_menu(
await callback_query.message.edit_text("❌ Ошибка авторизации в Remnawave.")
return
user_info = await api.get_user_by_uuid(client_id)
devices = await api.get_user_hwid_devices(client_id)
if not devices:
text = "💻 <b>HWID устройства</b>\n\n🔌 Нет привязанных устройств."
status_emoji = "🟢"
status_text = "Онлайн"
online_at_str = ""
first_connected_str = ""
last_node_uuid = ""
if not user_info:
status_emoji = "⚪️"
status_text = "Не найден"
else:
text = f"💻 <b>HWID устройства</b>\n\nПривязано: <b>{len(devices)}</b>\n\n"
is_online = bool(user_info.get("isOnline"))
status_emoji = "🟢" if is_online else "⚪️"
status_text = "Онлайн" if is_online else "Офлайн"
online_at = user_info.get("onlineAt")
if online_at:
online_at_str = online_at[:19].replace("T", " ")
first_connected_at = user_info.get("firstConnectedAt")
if first_connected_at:
first_connected_str = first_connected_at[:19].replace("T", " ")
last_node_uuid_val = user_info.get("lastConnectedNodeUuid")
if last_node_uuid_val:
last_node_uuid = last_node_uuid_val
if not devices:
text = (
"💻 <b>HWID устройства</b>\n\n"
f"{status_emoji} <b>Статус:</b> {status_text}\n"
f"└ 🕓 <b>Онлайн был:</b> {online_at_str}\n"
f"└ 🚀 <b>Первое подключение:</b> {first_connected_str}\n"
f"└ 🛰 <b>Нода последнего подключения:</b> {last_node_uuid}\n\n"
"🔌 Нет привязанных устройств."
)
else:
text = (
"💻 <b>HWID устройства</b>\n\n"
f"{status_emoji} <b>Статус:</b> {status_text}\n"
f"└ 🕓 <b>Онлайн был:</b> {online_at_str}\n"
f"└ 🚀 <b>Первое подключение:</b> {first_connected_str}\n"
f"└ 🛰 <b>Нода последнего подключения:</b> {last_node_uuid}\n\n"
f"🔗 Привязано устройств: <b>{len(devices)}</b>\n\n"
)
for idx, device in enumerate(devices, 1):
created = device.get("createdAt", "")[:19].replace("T", " ")
updated = device.get("updatedAt", "")[:19].replace("T", " ")
+23 -25
View File
@@ -23,7 +23,7 @@ async def _is_vless_tariff(session: AsyncSession, email: str) -> bool:
return is_plan_vless(tariff)
async def _try_build_remna_vless(servers: list, email: str) -> tuple[str | None, str | None]:
async def _try_build_remna_vless(servers: list, email: str) -> tuple[str | None, str | None, str | None]:
happ_cryptolink_enabled = bool(MODES_CONFIG.get("HAPP_CRYPTOLINK_ENABLED", HAPP_CRYPTOLINK))
si = servers[0]
@@ -31,33 +31,28 @@ async def _try_build_remna_vless(servers: list, email: str) -> tuple[str | None,
ok = await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD)
if not ok:
logger.warning("[Remnawave] login failed")
return None, None
return None, None, None
data = await remna.get_subscription_by_username(email)
if not data:
logger.warning("[Remnawave] by-username empty")
return None, None
return None, None, None
links = data.get("links") or []
best = None
if links:
best = max(links, key=score_vless_url)
if score_vless_url(best) < 0:
best = None
sub_url = data.get("subscriptionUrl") or None
best_vless = None
if sub_url and sub_url.lower().startswith("vless://"):
best_vless = sub_url
happ_link = None
try:
happ = data.get("happ") or {}
if isinstance(happ, dict):
happ_link = happ.get("cryptoLink") or happ.get("link")
except Exception:
pass
if happ_cryptolink_enabled and sub_url:
try:
happ_link = await remna.encrypt_happ_crypto_link(sub_url)
except Exception as e:
logger.warning(f"[Remnawave] happ encrypt failed: {e}")
happ_link = None
if happ_cryptolink_enabled and happ_link:
return best, happ_link
sub_url = data.get("subscriptionUrl")
return best, sub_url
return best_vless, sub_url, happ_link
async def _try_build_3xui_vless(servers: list, email: str) -> str | None:
@@ -144,7 +139,7 @@ async def make_aggregated_link(
logger.info("[agg_link] choose 3x-ui VLESS")
return xui_link
if remna:
best_vless, sub_url = await _try_build_remna_vless(remna, email)
best_vless, sub_url, happ_link = await _try_build_remna_vless(remna, email)
if best_vless:
logger.info("[agg_link] choose Remnawave VLESS")
return best_vless
@@ -156,8 +151,11 @@ async def make_aggregated_link(
if stored and str(stored).lower().startswith("vless://"):
logger.info("[agg_link] choose stored Remnawave VLESS")
return stored
if happ_link:
logger.info("[agg_link] choose Remnawave cryptoLink (vless)")
return happ_link
if sub_url:
logger.info("[agg_link] choose Remnawave subscriptionUrl")
logger.info("[agg_link] choose Remnawave subscriptionUrl (vless)")
return sub_url
logger.info("[agg_link] fallback base link")
return f"{base}/{email}/{tg_id}"
@@ -166,15 +164,15 @@ async def make_aggregated_link(
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)
best_vless, sub_url, happ_link = await _try_build_remna_vless(remna, email)
if remna_link_override and (
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
if sub_url and sub_url.startswith("happ://"):
if happ_link:
logger.info("[agg_link] choose Remnawave cryptoLink (non-vless)")
return sub_url
return happ_link
kd = await get_key_details(session, email)
stored = kd.get("remnawave_link") if kd else None
if stored:
+46 -19
View File
@@ -52,6 +52,20 @@ async def ensure_on_remnawave(
traffic_bytes = bytes_from_gb(total_gb)
use_crypto_link = bool(MODES_CONFIG.get("HAPP_CRYPTOLINK_ENABLED", HAPP_CRYPTOLINK))
async def _build_link_from_subscription(sub_url: str | None) -> str | None:
if not sub_url:
return None
remna_link = None
if use_crypto_link:
try:
remna_link = await api.encrypt_happ_crypto_link(sub_url)
except Exception as e:
logger.error(f"{PANEL_REMNA} ошибка шифрования happ-ссылки: {e}")
remna_link = None
if not remna_link:
remna_link = sub_url
return remna_link
async def do_update():
try:
updated = await api.update_user(
@@ -61,16 +75,30 @@ async def ensure_on_remnawave(
traffic_limit_bytes=traffic_bytes,
hwid_device_limit=hwid_device_limit,
)
if updated:
if reset_traffic:
try:
await api.reset_user_traffic(client_id)
except Exception:
pass
return client_id, None
except Exception:
if not updated:
return None, None
if reset_traffic:
try:
await api.reset_user_traffic(client_id)
except Exception:
pass
remna_link = None
try:
sub_data = await api.get_subscription_by_username(email)
except Exception as e:
logger.error(f"{PANEL_REMNA} get_subscription_by_username при update: {e}")
sub_data = None
if sub_data:
sub_url = sub_data.get("subscriptionUrl")
remna_link = await _build_link_from_subscription(sub_url)
return client_id, remna_link
except Exception as e:
logger.error(f"{PANEL_REMNA} update_user не удалось: {e}")
return None, None
return None, None
async def do_create():
try:
@@ -84,19 +112,18 @@ async def ensure_on_remnawave(
}
if traffic_bytes > 0:
payload["trafficLimitBytes"] = traffic_bytes
payload["hwidDeviceLimit"] = hwid_device_limit
created = await api.create_user(payload)
new_uuid = created.get("uuid") if isinstance(created, dict) else None
remna_link = None
if isinstance(created, dict):
if use_crypto_link:
remna_link = (
created.get("happ", {}).get("cryptoLink") if isinstance(created.get("happ"), dict) else None
)
if not remna_link:
remna_link = created.get("subscriptionUrl")
if not created:
return None, None
user = created.get("user") or {}
new_uuid = user.get("uuid") or client_id
sub_url = created.get("subscriptionUrl")
remna_link = await _build_link_from_subscription(sub_url)
return new_uuid, remna_link
except Exception as e:
logger.error(f"{PANEL_REMNA} создание не удалось: {e}")
+1 -1
View File
@@ -1 +1 @@
DEFAULT_HOOK_TIMEOUT = 2.0
DEFAULT_HOOK_TIMEOUT = 4.0
Binary file not shown.