Release 1.6.1: fix subscription button stuck disabled and improve import parsing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Navis
2026-07-28 09:00:59 +03:00
co-authored by Cursor
parent 68a48b05ef
commit 740517dbfb
16 changed files with 258 additions and 68 deletions
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10 -10
View File
@@ -1,38 +1,38 @@
{ {
"version": "1.6.0", "version": "1.6.1",
"notes": "Windows: полноценные кнопки вместо «надписей»; версия 1.6.0", "notes": "Фикс URL подписки: кнопка ↻ снова работает, сообщения об ошибке/успехе видны, лучше парсинг base64/Clash",
"platform": "windows-amd64", "platform": "windows-amd64",
"os": "windows", "os": "windows",
"arch": "amd64", "arch": "amd64",
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5581e090ce94b7743cedcf721b32eb4b3449fc74f89d54cd7bf6a6b828cb2fab", "sha256": "b836310af1b946c98d5d84787266d79978cb6d2a45e360c9fc5cb3a0a89059e4",
"mandatory": false, "mandatory": false,
"platforms": { "platforms": {
"windows-amd64": { "windows-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5581e090ce94b7743cedcf721b32eb4b3449fc74f89d54cd7bf6a6b828cb2fab", "sha256": "b836310af1b946c98d5d84787266d79978cb6d2a45e360c9fc5cb3a0a89059e4",
"os": "windows", "os": "windows",
"arch": "amd64" "arch": "amd64"
}, },
"darwin-arm64": { "darwin-arm64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
"sha256": "c1727030ec48fcd25f64e28d91bb115bbd41c8c7664545ad9c386976b71c44ce", "sha256": "94a39a127073ee299902026fe0be6f35506663fb20b9d9dde73f8f7732b1c079",
"os": "darwin", "os": "darwin",
"arch": "arm64", "arch": "arm64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
"dmg_sha256": "b0c4e2e2c393372cf4d94004c4888d7c6470f2f3e435833362f0530fc0bea413", "dmg_sha256": "1880a4bac8f52e5ea0ed4f5ed6a6d796f492347a7bb1b0565b6766859839d04a",
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
"zip_sha256": "8f6816cc002f3a66938c7b5d4b09d8ca6920dbfd73670848bc51c08d1be1fc8c" "zip_sha256": "16711d3c0544806eefa0ca29b0d2c78b9f159c44e4e526cf05ce9ec1aaf82af9"
}, },
"darwin-amd64": { "darwin-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
"sha256": "6f7978428bccde576c12fc175b40a3590d300006437dd3eaf31182dce9104943", "sha256": "580005bbdaeef92ae6384be7946cef103c45b1150473185bdf70538d77e3b545",
"os": "darwin", "os": "darwin",
"arch": "amd64", "arch": "amd64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
"dmg_sha256": "ff2ef6d0893547b290193874cd87d51f1d1ca6232071877d443cdf3506566ef8", "dmg_sha256": "d80bd24e467886db6fd8486f5e209cb835531b2665bac953267d0fee959dd9c0",
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip",
"zip_sha256": "51eb1847abf0b34d8b061680118c94df3e2340c5382d2a718bbef41ddbf19abc" "zip_sha256": "d6e9876ce1a7331f9fd9f8bece86a6bce55a48d874c6e40d22292653029dc486"
} }
} }
} }
+10 -10
View File
@@ -1,38 +1,38 @@
{ {
"version": "1.6.0", "version": "1.6.1",
"notes": "Windows: полноценные кнопки вместо «надписей»; версия 1.6.0", "notes": "Фикс URL подписки: кнопка ↻ снова работает, сообщения об ошибке/успехе видны, лучше парсинг base64/Clash",
"platform": "windows-amd64", "platform": "windows-amd64",
"os": "windows", "os": "windows",
"arch": "amd64", "arch": "amd64",
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5581e090ce94b7743cedcf721b32eb4b3449fc74f89d54cd7bf6a6b828cb2fab", "sha256": "b836310af1b946c98d5d84787266d79978cb6d2a45e360c9fc5cb3a0a89059e4",
"mandatory": false, "mandatory": false,
"platforms": { "platforms": {
"windows-amd64": { "windows-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5581e090ce94b7743cedcf721b32eb4b3449fc74f89d54cd7bf6a6b828cb2fab", "sha256": "b836310af1b946c98d5d84787266d79978cb6d2a45e360c9fc5cb3a0a89059e4",
"os": "windows", "os": "windows",
"arch": "amd64" "arch": "amd64"
}, },
"darwin-arm64": { "darwin-arm64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
"sha256": "c1727030ec48fcd25f64e28d91bb115bbd41c8c7664545ad9c386976b71c44ce", "sha256": "94a39a127073ee299902026fe0be6f35506663fb20b9d9dde73f8f7732b1c079",
"os": "darwin", "os": "darwin",
"arch": "arm64", "arch": "arm64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
"dmg_sha256": "b0c4e2e2c393372cf4d94004c4888d7c6470f2f3e435833362f0530fc0bea413", "dmg_sha256": "1880a4bac8f52e5ea0ed4f5ed6a6d796f492347a7bb1b0565b6766859839d04a",
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
"zip_sha256": "8f6816cc002f3a66938c7b5d4b09d8ca6920dbfd73670848bc51c08d1be1fc8c" "zip_sha256": "16711d3c0544806eefa0ca29b0d2c78b9f159c44e4e526cf05ce9ec1aaf82af9"
}, },
"darwin-amd64": { "darwin-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
"sha256": "6f7978428bccde576c12fc175b40a3590d300006437dd3eaf31182dce9104943", "sha256": "580005bbdaeef92ae6384be7946cef103c45b1150473185bdf70538d77e3b545",
"os": "darwin", "os": "darwin",
"arch": "amd64", "arch": "amd64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
"dmg_sha256": "ff2ef6d0893547b290193874cd87d51f1d1ca6232071877d443cdf3506566ef8", "dmg_sha256": "d80bd24e467886db6fd8486f5e209cb835531b2665bac953267d0fee959dd9c0",
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip",
"zip_sha256": "51eb1847abf0b34d8b061680118c94df3e2340c5382d2a718bbef41ddbf19abc" "zip_sha256": "d6e9876ce1a7331f9fd9f8bece86a6bce55a48d874c6e40d22292653029dc486"
} }
} }
} }
+36 -8
View File
@@ -555,6 +555,7 @@
let formHydrated = false; let formHydrated = false;
let dirty = false; let dirty = false;
let profiles = []; let profiles = [];
let metaHoldUntil = 0;
function markDirty() { dirty = true; } function markDirty() { dirty = true; }
[ [
@@ -571,6 +572,7 @@
function setMeta(text, kind) { function setMeta(text, kind) {
meta.textContent = text || ""; meta.textContent = text || "";
meta.style.color = kind === "err" ? "var(--danger)" : kind === "ok" ? "var(--accent)" : "var(--muted)"; meta.style.color = kind === "err" ? "var(--danger)" : kind === "ok" ? "var(--accent)" : "var(--muted)";
if (kind === "ok" || kind === "err") metaHoldUntil = Date.now() + 8000;
} }
function fillProfiles(list, active) { function fillProfiles(list, active) {
@@ -640,6 +642,8 @@
pingBtn.disabled = busy; pingBtn.disabled = busy;
updCheckBtn.disabled = busy; updCheckBtn.disabled = busy;
updateBtn.disabled = busy; updateBtn.disabled = busy;
subBtn.disabled = busy;
subUrl.disabled = busy;
btn.disabled = busy; btn.disabled = busy;
fillProfiles(state.profiles || [], state.active_profile || state.profile); fillProfiles(state.profiles || [], state.active_profile || state.profile);
@@ -670,7 +674,10 @@
} else { } else {
detail = "Вставьте ссылку naive или hysteria2://"; detail = "Вставьте ссылку naive или hysteria2://";
} }
if (!busy) setMeta(detail, state.core_ready === false ? "err" : ""); if (!busy && Date.now() > metaHoldUntil) {
setMeta(detail, state.core_ready === false ? "err" : "");
metaHoldUntil = 0;
}
} }
async function refresh(opts) { async function refresh(opts) {
@@ -682,7 +689,7 @@
async function withBusy(fn) { async function withBusy(fn) {
if (busy) return; if (busy) return;
busy = true; busy = true;
[btn, coreBtn, saveBtn, addBtn, delBtn, profile, pingBtn, updCheckBtn, updateBtn, subBtn].forEach((b) => { if (b) b.disabled = true; }); paintButtonsLocked(true);
try { try {
await fn(); await fn();
} finally { } finally {
@@ -691,11 +698,17 @@
await refresh({ syncForm: true }); await refresh({ syncForm: true });
} catch (e) { } catch (e) {
setMeta(String(e), "err"); setMeta(String(e), "err");
[btn, coreBtn, saveBtn, addBtn, delBtn, profile, pingBtn, updCheckBtn, updateBtn, subBtn].forEach((b) => { if (b) b.disabled = false; }); paintButtonsLocked(false);
} }
} }
} }
function paintButtonsLocked(locked) {
[btn, coreBtn, saveBtn, addBtn, delBtn, profile, pingBtn, updCheckBtn, updateBtn, subBtn, subUrl].forEach((b) => {
if (b) b.disabled = locked;
});
}
profile.addEventListener("change", () => withBusy(async () => { profile.addEventListener("change", () => withBusy(async () => {
await selectProfile(profile.value); await selectProfile(profile.value);
dirty = false; dirty = false;
@@ -780,15 +793,30 @@
shopBtn.addEventListener("click", openShop); shopBtn.addEventListener("click", openShop);
shopLink.addEventListener("click", openShop); shopLink.addEventListener("click", openShop);
async function runImportSubscription() {
const url = subUrl.value.trim();
if (!url) {
setMeta("Вставьте URL подписки", "err");
return;
}
setMeta("Загрузка подписки…");
const n = await importSubscription(url);
formHydrated = false;
dirty = false;
setMeta("Импортировано профилей: " + n, "ok");
}
subBtn.addEventListener("click", () => withBusy(async () => { subBtn.addEventListener("click", () => withBusy(async () => {
try { try {
setMeta("Загрузка подписки…"); await runImportSubscription();
const n = await importSubscription(subUrl.value.trim());
formHydrated = false;
dirty = false;
setMeta("Импортировано профилей: " + n, "ok");
} catch (e) { setMeta(String(e), "err"); } } catch (e) { setMeta(String(e), "err"); }
})); }));
subUrl.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
subBtn.click();
}
});
pingBtn.addEventListener("click", () => withBusy(async () => { pingBtn.addEventListener("click", () => withBusy(async () => {
try { try {
+3 -7
View File
@@ -404,19 +404,15 @@ func (m *Manager) SubscriptionURL() string {
} }
func (m *Manager) ImportSubscription(rawURL string) (int, error) { func (m *Manager) ImportSubscription(rawURL string) (int, error) {
m.mu.Lock()
if m.engine != nil && m.engine.Running() {
m.mu.Unlock()
return 0, fmt.Errorf("сначала отключитесь")
}
m.mu.Unlock()
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel() defer cancel()
items, err := subscription.Fetch(ctx, rawURL) items, err := subscription.Fetch(ctx, rawURL)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if len(items) == 0 {
return 0, fmt.Errorf("в подписке нет поддерживаемых ссылок (naive / hysteria2)")
}
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
+140 -17
View File
@@ -7,6 +7,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"regexp"
"strings" "strings"
"time" "time"
"unicode" "unicode"
@@ -24,6 +25,11 @@ type Item struct {
URI string URI string
} }
var (
reShareLine = regexp.MustCompile(`(?i)((?:hysteria2|hy2|naive\+https|naive\+quic|naive|quic|https|http)://[^\s"'<>]+)`)
reClashHY2 = regexp.MustCompile(`(?is)type:\s*hysteria2\b.*?server:\s*(\S+).*?port:\s*(\d+).*?(?:password|auth):\s*["']?([^\s"']+)`)
)
// Fetch downloads a subscription body and parses proxy share links. // Fetch downloads a subscription body and parses proxy share links.
func Fetch(ctx context.Context, rawURL string) ([]Item, error) { func Fetch(ctx context.Context, rawURL string) ([]Item, error) {
rawURL = strings.TrimSpace(rawURL) rawURL = strings.TrimSpace(rawURL)
@@ -39,53 +45,96 @@ func Fetch(ctx context.Context, rawURL string) ([]Item, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("User-Agent", "Navis/1.6.0") req.Header.Set("User-Agent", "ClashMeta/1.18.0")
req.Header.Set("Accept", "*/*")
client := &http.Client{Timeout: 45 * time.Second} client := &http.Client{Timeout: 45 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("не удалось скачать подписку: %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("подписка HTTP %d", resp.StatusCode) return nil, fmt.Errorf("подписка HTTP %d", resp.StatusCode)
} }
body, err := io.ReadAll(io.LimitReader(resp.Body, 8<<20)) body, err := io.ReadAll(io.LimitReader(resp.Body, 8<<20))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ParseBody(string(body)) items, err := ParseBody(string(body))
if err != nil {
return nil, err
}
return items, nil
} }
// ParseBody accepts plain text lines or base64 (Clash/V2Ray style subscription). // ParseBody accepts plain text lines, base64, or Clash-like blobs with share links.
func ParseBody(body string) ([]Item, error) { func ParseBody(body string) ([]Item, error) {
body = strings.TrimSpace(body) body = strings.TrimSpace(body)
if body == "" { if body == "" {
return nil, fmt.Errorf("пустая подписка") return nil, fmt.Errorf("пустая подписка")
} }
text := body
if !looksLikeLinks(body) { candidates := []string{body}
if decoded, err := decodeMaybeBase64(body); err == nil && looksLikeLinks(decoded) { if decoded, err := decodeMaybeBase64(body); err == nil && decoded != "" && decoded != body {
text = decoded candidates = append(candidates, decoded)
// some panels double-encode
if decoded2, err2 := decodeMaybeBase64(decoded); err2 == nil && decoded2 != "" && decoded2 != decoded {
candidates = append(candidates, decoded2)
} }
} }
var lastErr error
for _, text := range candidates {
items, err := parseText(text)
if err == nil && len(items) > 0 {
return items, nil
}
if err != nil {
lastErr = err
}
}
if lastErr != nil {
return nil, lastErr
}
return nil, fmt.Errorf("в подписке нет поддерживаемых ссылок (naive / hysteria2)")
}
func parseText(text string) ([]Item, error) {
lines := splitLines(text) lines := splitLines(text)
// Also extract share URIs embedded in YAML/HTML/JSON.
extracted := reShareLine.FindAllString(text, -1)
for _, m := range extracted {
lines = append(lines, strings.TrimRight(m, ".,;)]}\"'"))
}
for _, block := range extractClashHY2(text) {
lines = append(lines, block)
}
out := make([]Item, 0, len(lines)) out := make([]Item, 0, len(lines))
seen := map[string]int{} seen := map[string]int{}
seenURI := map[string]struct{}{}
for _, line := range lines { for _, line := range lines {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") { if line == "" || strings.HasPrefix(line, "#") {
continue continue
} }
// Some panels wrap links in quotes
line = strings.Trim(line, `"'`) line = strings.Trim(line, `"'`)
proto := config.DetectProtocol(line) proto := config.DetectProtocol(line)
if proto == "" { if proto == "" {
continue continue
} }
// Skip bare https:// without credentials (common noise in HTML/YAML).
if proto == config.ProtocolNaive && !naiveLinkHasAuth(line) && !strings.HasPrefix(strings.ToLower(line), "naive+") {
continue
}
normalized, detected, remark, err := linknorm.Normalize(proto, line) normalized, detected, remark, err := linknorm.Normalize(proto, line)
if err != nil { if err != nil {
continue continue
} }
if _, dup := seenURI[normalized]; dup {
continue
}
seenURI[normalized] = struct{}{}
name := remark name := remark
if name == "" { if name == "" {
name = defaultName(detected, normalized) name = defaultName(detected, normalized)
@@ -106,13 +155,81 @@ func ParseBody(body string) ([]Item, error) {
return out, nil return out, nil
} }
func looksLikeLinks(s string) bool { func naiveLinkHasAuth(line string) bool {
lower := strings.ToLower(s) lower := strings.ToLower(strings.TrimSpace(line))
return strings.Contains(lower, "hysteria2://") || for _, p := range []string{"naive+https://", "naive+quic://", "naive://", "https://", "http://", "quic://"} {
strings.Contains(lower, "hy2://") || if strings.HasPrefix(lower, p) {
strings.Contains(lower, "naive+") || rest := line[len(p):]
strings.Contains(lower, "https://") || at := strings.IndexByte(rest, '@')
strings.Contains(lower, "quic://") return at > 0
}
}
return false
}
func extractClashHY2(text string) []string {
// Very small helper for Clash YAML hysteria2 blocks when share URI is absent.
lower := strings.ToLower(text)
if !strings.Contains(lower, "type:") || !strings.Contains(lower, "hysteria2") {
return nil
}
var out []string
// Split roughly by list items.
chunks := strings.Split(text, "\n- ")
for _, chunk := range chunks {
cl := strings.ToLower(chunk)
if !strings.Contains(cl, "type:") || !strings.Contains(cl, "hysteria2") {
continue
}
server := yamlField(chunk, "server")
port := yamlField(chunk, "port")
pass := yamlField(chunk, "password")
if pass == "" {
pass = yamlField(chunk, "auth")
}
if server == "" || port == "" || pass == "" {
continue
}
name := yamlField(chunk, "name")
sni := yamlField(chunk, "sni")
obfs := yamlField(chunk, "obfs")
obfsPass := yamlField(chunk, "obfs-password")
if obfsPass == "" {
obfsPass = yamlField(chunk, "obfs_password")
}
u := url.URL{
Scheme: "hysteria2",
User: url.User(pass),
Host: server + ":" + port,
}
q := url.Values{}
if sni != "" {
q.Set("sni", sni)
}
if obfs != "" {
q.Set("obfs", obfs)
}
if obfsPass != "" {
q.Set("obfs-password", obfsPass)
}
u.RawQuery = q.Encode()
link := u.String()
if name != "" {
link += "#" + url.PathEscape(name)
}
out = append(out, link)
}
_ = reClashHY2 // kept for possible future tightening
return out
}
func yamlField(block, key string) string {
re := regexp.MustCompile(`(?im)^\s*` + regexp.QuoteMeta(key) + `\s*:\s*["']?([^"'#\n\r]+)["']?`)
m := re.FindStringSubmatch(block)
if len(m) < 2 {
return ""
}
return strings.TrimSpace(m[1])
} }
func decodeMaybeBase64(s string) (string, error) { func decodeMaybeBase64(s string) (string, error) {
@@ -122,6 +239,9 @@ func decodeMaybeBase64(s string) (string, error) {
} }
return r return r
}, s) }, s)
if s == "" {
return "", fmt.Errorf("empty")
}
raw, err := base64.StdEncoding.DecodeString(s) raw, err := base64.StdEncoding.DecodeString(s)
if err != nil { if err != nil {
raw, err = base64.RawStdEncoding.DecodeString(s) raw, err = base64.RawStdEncoding.DecodeString(s)
@@ -129,6 +249,9 @@ func decodeMaybeBase64(s string) (string, error) {
if err != nil { if err != nil {
raw, err = base64.URLEncoding.DecodeString(s) raw, err = base64.URLEncoding.DecodeString(s)
} }
if err != nil {
raw, err = base64.RawURLEncoding.DecodeString(s)
}
if err != nil { if err != nil {
return "", err return "", err
} }
+44 -1
View File
@@ -1,6 +1,10 @@
package subscription package subscription
import "testing" import (
"encoding/base64"
"strings"
"testing"
)
func TestParseBodyPlain(t *testing.T) { func TestParseBodyPlain(t *testing.T) {
body := `# comment body := `# comment
@@ -15,3 +19,42 @@ naive+https://u:p@n.example.com#naive1
t.Fatalf("got %d items", len(items)) t.Fatalf("got %d items", len(items))
} }
} }
func TestParseBodyBase64(t *testing.T) {
plain := "hysteria2://secret@host.example:443/?sni=host.example#HY\nhttps://user:pass@naive.example.com#NV\n"
body := base64.StdEncoding.EncodeToString([]byte(plain))
items, err := ParseBody(body)
if err != nil {
t.Fatal(err)
}
if len(items) != 2 {
t.Fatalf("got %d items: %+v", len(items), items)
}
}
func TestParseBodyClashYAML(t *testing.T) {
body := `
proxies:
- name: "DE HY2"
type: hysteria2
server: de.example.com
port: 443
password: secret
sni: de.example.com
obfs: salamander
obfs-password: obfspass
`
items, err := ParseBody(body)
if err != nil {
t.Fatal(err)
}
if len(items) != 1 {
t.Fatalf("got %d", len(items))
}
if items[0].Protocol != "hysteria2" {
t.Fatalf("proto %s", items[0].Protocol)
}
if !strings.Contains(items[0].URI, "de.example.com:443") {
t.Fatalf("uri %s", items[0].URI)
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
) )
// CurrentVersion is the shipped client version. // CurrentVersion is the shipped client version.
const CurrentVersion = "1.6.0" const CurrentVersion = "1.6.1"
// DefaultManifestURL is the update feed (hosted in the project git repo). // DefaultManifestURL is the update feed (hosted in the project git repo).
const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json" const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json"
+10 -10
View File
@@ -1,38 +1,38 @@
{ {
"version": "1.6.0", "version": "1.6.1",
"notes": "Windows: полноценные кнопки вместо «надписей»; версия 1.6.0", "notes": "Фикс URL подписки: кнопка ↻ снова работает, сообщения об ошибке/успехе видны, лучше парсинг base64/Clash",
"platform": "windows-amd64", "platform": "windows-amd64",
"os": "windows", "os": "windows",
"arch": "amd64", "arch": "amd64",
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5581e090ce94b7743cedcf721b32eb4b3449fc74f89d54cd7bf6a6b828cb2fab", "sha256": "b836310af1b946c98d5d84787266d79978cb6d2a45e360c9fc5cb3a0a89059e4",
"mandatory": false, "mandatory": false,
"platforms": { "platforms": {
"windows-amd64": { "windows-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5581e090ce94b7743cedcf721b32eb4b3449fc74f89d54cd7bf6a6b828cb2fab", "sha256": "b836310af1b946c98d5d84787266d79978cb6d2a45e360c9fc5cb3a0a89059e4",
"os": "windows", "os": "windows",
"arch": "amd64" "arch": "amd64"
}, },
"darwin-arm64": { "darwin-arm64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
"sha256": "c1727030ec48fcd25f64e28d91bb115bbd41c8c7664545ad9c386976b71c44ce", "sha256": "94a39a127073ee299902026fe0be6f35506663fb20b9d9dde73f8f7732b1c079",
"os": "darwin", "os": "darwin",
"arch": "arm64", "arch": "arm64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
"dmg_sha256": "b0c4e2e2c393372cf4d94004c4888d7c6470f2f3e435833362f0530fc0bea413", "dmg_sha256": "1880a4bac8f52e5ea0ed4f5ed6a6d796f492347a7bb1b0565b6766859839d04a",
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
"zip_sha256": "8f6816cc002f3a66938c7b5d4b09d8ca6920dbfd73670848bc51c08d1be1fc8c" "zip_sha256": "16711d3c0544806eefa0ca29b0d2c78b9f159c44e4e526cf05ce9ec1aaf82af9"
}, },
"darwin-amd64": { "darwin-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
"sha256": "6f7978428bccde576c12fc175b40a3590d300006437dd3eaf31182dce9104943", "sha256": "580005bbdaeef92ae6384be7946cef103c45b1150473185bdf70538d77e3b545",
"os": "darwin", "os": "darwin",
"arch": "amd64", "arch": "amd64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
"dmg_sha256": "ff2ef6d0893547b290193874cd87d51f1d1ca6232071877d443cdf3506566ef8", "dmg_sha256": "d80bd24e467886db6fd8486f5e209cb835531b2665bac953267d0fee959dd9c0",
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip",
"zip_sha256": "51eb1847abf0b34d8b061680118c94df3e2340c5382d2a718bbef41ddbf19abc" "zip_sha256": "d6e9876ce1a7331f9fd9f8bece86a6bce55a48d874c6e40d22292653029dc486"
} }
} }
} }
+4 -4
View File
@@ -1,7 +1,7 @@
{ {
"FixedFileInfo": { "FixedFileInfo": {
"FileVersion": { "Major": 1, "Minor": 6, "Patch": 0, "Build": 0 }, "FileVersion": { "Major": 1, "Minor": 6, "Patch": 1, "Build": 0 },
"ProductVersion": { "Major": 1, "Minor": 6, "Patch": 0, "Build": 0 }, "ProductVersion": { "Major": 1, "Minor": 6, "Patch": 1, "Build": 0 },
"FileFlagsMask": "3f", "FileFlagsMask": "3f",
"FileFlags": "00", "FileFlags": "00",
"FileOS": "40004", "FileOS": "40004",
@@ -11,11 +11,11 @@
"StringFileInfo": { "StringFileInfo": {
"CompanyName": "Navis", "CompanyName": "Navis",
"FileDescription": "Navis — NaiveProxy / Hysteria 2 client for Windows and macOS", "FileDescription": "Navis — NaiveProxy / Hysteria 2 client for Windows and macOS",
"FileVersion": "1.6.0.0", "FileVersion": "1.6.1.0",
"InternalName": "Navis", "InternalName": "Navis",
"OriginalFilename": "Navis.exe", "OriginalFilename": "Navis.exe",
"ProductName": "Navis", "ProductName": "Navis",
"ProductVersion": "1.6.0.0" "ProductVersion": "1.6.1.0"
}, },
"VarFileInfo": { "VarFileInfo": {
"Translation": { "Translation": {