Release 3.9.0+3: subscription-URL routing, emoji flags, Remnawave provisioning.

Route credential-less http(s) URLs pasted into the add-link field to
subscription import (fixes remaining 'proxy URI missing username').
Extend geoflag with RU country names and city hints; live Remnawave
names already carrying emoji flags are kept as-is. Add admin
provisioning via configs/remnawave-api.json (GET by-username / POST
users, 50 GB MONTH plan) and the «Выдать доступ» UI panel.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Navis
2026-08-01 17:36:54 +03:00
co-authored by Cursor
parent e34312ef9c
commit 77bd7da861
24 changed files with 974 additions and 110 deletions
+24
View File
@@ -2,6 +2,7 @@ package linknorm
import (
"fmt"
"net/url"
"strings"
"vpnclient/internal/config"
@@ -11,6 +12,23 @@ import (
"vpnclient/internal/protocols/xray"
)
// LooksLikeSubscriptionURL reports whether raw is a plain http(s) URL without
// userinfo — i.e. a subscription URL rather than a naive proxy share link
// (which always carries user:pass@). Callers should route such input into the
// subscription import flow instead of the proxy-URI parser.
func LooksLikeSubscriptionURL(raw string) bool {
raw = strings.TrimSpace(raw)
lower := strings.ToLower(raw)
if !strings.HasPrefix(lower, "http://") && !strings.HasPrefix(lower, "https://") {
return false
}
u, err := url.Parse(raw)
if err != nil || u.Host == "" {
return false
}
return u.User == nil
}
// Normalize normalizes a share/proxy URI for the given protocol (or auto-detect).
func Normalize(proto config.Protocol, raw string) (normalized string, detected config.Protocol, remark string, err error) {
raw = strings.TrimSpace(raw)
@@ -42,6 +60,12 @@ func Normalize(proto config.Protocol, raw string) (normalized string, detected c
u, rem, err := awg.NormalizeShareLink(raw)
return u, config.ProtocolAWG, rem, err
}
// Safety net: a bare http(s) URL without user:pass@ is a subscription
// URL, not a naive proxy link — callers should have routed it to the
// subscription import flow (see LooksLikeSubscriptionURL).
if LooksLikeSubscriptionURL(raw) {
return "", config.ProtocolNaive, "", fmt.Errorf("это ссылка подписки (нет логина и пароля) — вставьте её в поле «URL подписки» или сохраните ещё раз: импорт запустится автоматически")
}
if xray.Detect(raw) {
u, rem, err := xray.NormalizeShareLink(raw)
return u, xray.DetectProtocol(raw), rem, err