package hysteria2 import ( "fmt" "net/url" "strconv" "strings" ) // Endpoint is a parsed Hysteria 2 share link plus optional overrides. type Endpoint struct { Server string // host:port or multi-port host Auth string SNI string Insecure bool PinSHA256 string Obfs string // salamander | gecko ObfsPassword string Remark string RawURI string } // ParseEndpoint parses hysteria2:// / hy2:// into discrete fields. func ParseEndpoint(raw string) (Endpoint, error) { normalized, remark, err := NormalizeShareLink(raw) if err != nil { return Endpoint{}, err } u, err := url.Parse(normalized) if err != nil { return Endpoint{}, err } ep := Endpoint{Remark: remark, RawURI: normalized} if u.User != nil { user := u.User.Username() if pass, ok := u.User.Password(); ok { ep.Auth = user + ":" + pass } else { ep.Auth, _ = url.QueryUnescape(user) } } ep.Server = u.Host if ep.Server == "" { return Endpoint{}, fmt.Errorf("hysteria2 URI missing host") } q := u.Query() ep.SNI = first(q, "sni", "peer") ep.PinSHA256 = first(q, "pinSHA256", "pinsha256") ep.Obfs = first(q, "obfs") ep.ObfsPassword = first(q, "obfs-password", "obfs_password") if v := first(q, "insecure"); v == "1" || strings.EqualFold(v, "true") { ep.Insecure = true } return ep, nil } func first(q url.Values, keys ...string) string { for _, k := range keys { if v := strings.TrimSpace(q.Get(k)); v != "" { return v } } return "" } // ApplyOverrides merges non-empty profile overrides into the endpoint. func (ep *Endpoint) ApplyOverrides(sni, obfs, obfsPass, pin string, insecure *bool) { if sni != "" { ep.SNI = sni } if obfs != "" { ep.Obfs = obfs } if obfsPass != "" { ep.ObfsPassword = obfsPass } if pin != "" { ep.PinSHA256 = pin } if insecure != nil { ep.Insecure = *insecure } } // BoolPtr helper for optional bool overrides. func BoolPtr(v bool) *bool { return &v } // HasTLSExtras reports whether discrete TLS/obfs fields should be written. func (ep Endpoint) HasTLSExtras() bool { return ep.SNI != "" || ep.Insecure || ep.PinSHA256 != "" || ep.Obfs != "" } // PortHint returns a single port for ping. func (ep Endpoint) PortHint() string { _, port, err := HostPort(ep.RawURI) if err != nil || port == "" { return "443" } return port } // ParseBandwidthMbps tries to normalize user input like "100" or "100 mbps". func ParseBandwidthMbps(raw string) string { raw = strings.TrimSpace(strings.ToLower(raw)) if raw == "" || raw == "0" || raw == "auto" || raw == "bbr" { return "" } if strings.ContainsAny(raw, "bkmgt") { return raw } if _, err := strconv.Atoi(raw); err == nil { return raw + " mbps" } return raw }