package config import ( "fmt" "strings" ) // ProfileInfo is a safe summary for UI lists (no secrets beyond proxy URI the user already owns). type ProfileInfo struct { Name string `json:"name"` Protocol string `json:"protocol"` Proxy string `json:"proxy"` Host string `json:"host"` Active bool `json:"active"` } // ListProfiles returns UI-friendly profile summaries. func (c *Config) ListProfiles() []ProfileInfo { out := make([]ProfileInfo, 0, len(c.Profiles)) for _, p := range c.Profiles { out = append(out, ProfileInfo{ Name: p.Name, Protocol: string(p.Protocol), Proxy: p.Proxy, Host: proxyHost(p.Proxy), Active: p.Name == c.Active, }) } return out } func proxyHost(proxy string) string { proxy = strings.TrimSpace(proxy) if proxy == "" { return "" } if i := strings.Index(proxy, "://"); i >= 0 { proxy = proxy[i+3:] } if at := strings.LastIndex(proxy, "@"); at >= 0 { proxy = proxy[at+1:] } if i := strings.IndexAny(proxy, "/?#"); i >= 0 { proxy = proxy[:i] } return proxy } // SetActive switches the active profile name. func (c *Config) SetActive(name string) error { name = strings.TrimSpace(name) for _, p := range c.Profiles { if p.Name == name { c.Active = name return nil } } return fmt.Errorf("profile %q not found", name) } // UpsertProfile creates or updates a profile by name. func (c *Config) UpsertProfile(name, proxy string) error { name = strings.TrimSpace(name) if name == "" { return fmt.Errorf("имя профиля пустое") } proxy = strings.TrimSpace(proxy) for i := range c.Profiles { if c.Profiles[i].Name == name { c.Profiles[i].Proxy = proxy if c.Profiles[i].Protocol == "" { c.Profiles[i].Protocol = ProtocolNaive } if len(c.Profiles[i].Listen) == 0 { c.Profiles[i].Listen = defaultListen() } c.Active = name return nil } } c.Profiles = append(c.Profiles, Profile{ Name: name, Protocol: ProtocolNaive, Proxy: proxy, Listen: defaultListen(), }) c.Active = name return nil } // RenameProfile renames a profile and keeps active pointer in sync. func (c *Config) RenameProfile(oldName, newName string) error { oldName = strings.TrimSpace(oldName) newName = strings.TrimSpace(newName) if oldName == "" || newName == "" { return fmt.Errorf("имя профиля пустое") } if oldName == newName { return nil } for _, p := range c.Profiles { if p.Name == newName { return fmt.Errorf("профиль %q уже существует", newName) } } for i := range c.Profiles { if c.Profiles[i].Name == oldName { c.Profiles[i].Name = newName if c.Active == oldName { c.Active = newName } return nil } } return fmt.Errorf("profile %q not found", oldName) } // DeleteProfile removes a profile. Keeps at least one profile. func (c *Config) DeleteProfile(name string) error { name = strings.TrimSpace(name) if len(c.Profiles) <= 1 { return fmt.Errorf("нельзя удалить последний профиль") } idx := -1 for i, p := range c.Profiles { if p.Name == name { idx = i break } } if idx < 0 { return fmt.Errorf("profile %q not found", name) } c.Profiles = append(c.Profiles[:idx], c.Profiles[idx+1:]...) if c.Active == name { c.Active = c.Profiles[0].Name } return nil } func defaultListen() []string { return []string{ "socks://127.0.0.1:1080", "http://127.0.0.1:1081", } }