Release 4.2.0+1: store-compliance формулировки и UX — убраны все цены/продажи из приложения (оффер «3 дня бесплатно» без ₽), «Активировать ключ» → «Добавить конфигурацию» (URL-ссылка, placeholder https://), блок «Ключ доступа» заменён статусом «Конфигурация добавлена/не добавлена» (URL не отображается), глобально Подписка→Конфигурация/Ваучер→URL-ссылка/Тариф→Профиль в UI и Go-строках; большая круглая кнопка ПОДКЛЮЧИТЬСЯ/ОТКЛЮЧИТЬСЯ со статусом, убран футер «Готово к подключению», протокол в списке — буквенный бейдж только при отличии от активного, трафик «ДОСТУПНО X ГБ» + «Активен до DD.MM.YYYY» + процент; пинг отдельного сервера кнопкой в строке, экран Статистика (за сегодня: трафик/время в сети/сессии + график 7 дней из localStorage), Kill Switch для VPN-режима (blackhole-маршруты сохраняются при обрыве туннеля, для прокси — системный прокси остаётся на мёртвый порт), тумблер «Авто-лучший сервер» в Настройках. Windows 4.2.0.1.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Navis
2026-08-01 22:34:43 +03:00
co-authored by Cursor
parent 74ffbd87d7
commit 2a57987aea
22 changed files with 670 additions and 127 deletions
+7 -7
View File
@@ -67,11 +67,11 @@ var userAgents = []string{
func Fetch(ctx context.Context, rawURL string) (*Result, error) {
rawURL = strings.TrimSpace(rawURL)
if rawURL == "" {
return nil, fmt.Errorf("пустой URL подписки")
return nil, fmt.Errorf("пустой URL конфигурации")
}
u, err := url.Parse(rawURL)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
return nil, fmt.Errorf("нужен http(s) URL подписки")
return nil, fmt.Errorf("нужен http(s) URL конфигурации")
}
var (
@@ -104,7 +104,7 @@ func Fetch(ctx context.Context, rawURL string) (*Result, error) {
if lastErr != nil {
return nil, lastErr
}
return nil, fmt.Errorf(подписке нет поддерживаемых ссылок")
return nil, fmt.Errorf(конфигурации нет поддерживаемых ссылок")
}
best.Info = info
return best, nil
@@ -120,11 +120,11 @@ func download(ctx context.Context, rawURL, userAgent string) (string, http.Heade
client := &http.Client{Timeout: 45 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", nil, fmt.Errorf("не удалось скачать подписку: %w", err)
return "", nil, fmt.Errorf("не удалось загрузить конфигурацию: %w", err)
}
defer resp.Body.Close()
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))
if err != nil {
@@ -183,7 +183,7 @@ func ParseBody(body string) ([]Item, error) {
func ParseBodyDetailed(body string) (*Result, error) {
body = strings.TrimSpace(body)
if body == "" {
return &Result{}, fmt.Errorf("пустая подписка")
return &Result{}, fmt.Errorf("пустая конфигурация")
}
candidates := []string{body}
@@ -212,7 +212,7 @@ func ParseBodyDetailed(body string) (*Result, error) {
return best, fmt.Errorf("нет поддерживаемых ссылок: %d записей пропущено (%s)",
best.Skipped, strings.Join(best.Warnings, "; "))
}
return best, fmt.Errorf(подписке нет поддерживаемых ссылок (naive / hysteria2 / awg / vless / vmess / trojan)")
return best, fmt.Errorf(конфигурации нет поддерживаемых ссылок (naive / hysteria2 / awg / vless / vmess / trojan)")
}
func parseText(text string) *Result {