diff --git a/model/proxy.go b/model/proxy.go index 17e5b20..290101d 100644 --- a/model/proxy.go +++ b/model/proxy.go @@ -1,83 +1,81 @@ package model -type PluginOptsStruct struct { - Mode string `yaml:"mode"` -} - type SmuxStruct struct { Enabled bool `yaml:"enable"` } -type HeaderStruct struct { - Host string `yaml:"Host"` -} - -type WSOptsStruct struct { - Path string `yaml:"path,omitempty"` - Headers HeaderStruct `yaml:"headers,omitempty"` - MaxEarlyData int `yaml:"max-early-data,omitempty"` - EarlyDataHeaderName string `yaml:"early-data-header-name,omitempty"` -} - -type Vmess struct { - V string `json:"v"` - Ps string `json:"ps"` - Add string `json:"add"` - Port string `json:"port"` - Id string `json:"id"` - Aid string `json:"aid"` - Scy string `json:"scy"` - Net string `json:"net"` - Type string `json:"type"` - Host string `json:"host"` - Path string `json:"path"` - Tls string `json:"tls"` - Sni string `json:"sni"` - Alpn string `json:"alpn"` - Fp string `json:"fp"` -} - -type GRPCOptsStruct struct { - GRPCServiceName string `yaml:"grpc-service-name,omitempty"` -} - -type RealityOptsStruct struct { - PublicKey string `yaml:"public-key,omitempty"` - ShortId string `yaml:"short-id,omitempty"` +type VmessJson struct { + V string `json:"v"` + Ps string `json:"ps"` + Add string `json:"add"` + Port interface{} `json:"port"` + Id string `json:"id"` + Aid interface{} `json:"aid"` + Scy string `json:"scy"` + Net string `json:"net"` + Type string `json:"type"` + Host string `json:"host"` + Path string `json:"path"` + Tls string `json:"tls"` + Sni string `json:"sni"` + Alpn string `json:"alpn"` + Fp string `json:"fp"` } type Proxy struct { - Name string `yaml:"name,omitempty"` - Server string `yaml:"server,omitempty"` - Port int `yaml:"port,omitempty"` - Type string `yaml:"type,omitempty"` - Cipher string `yaml:"cipher,omitempty"` - Password string `yaml:"password,omitempty"` - UDP bool `yaml:"udp,omitempty"` - UUID string `yaml:"uuid,omitempty"` - Network string `yaml:"network,omitempty"` - Flow string `yaml:"flow,omitempty"` - TLS bool `yaml:"tls,omitempty"` - ClientFingerprint string `yaml:"client-fingerprint,omitempty"` - UdpOverTcp bool `yaml:"udp-over-tcp,omitempty"` - UdpOverTcpVersion string `yaml:"udp-over-tcp-version,omitempty"` - Plugin string `yaml:"plugin,omitempty"` - PluginOpts PluginOptsStruct `yaml:"plugin-opts,omitempty"` - Smux SmuxStruct `yaml:"smux,omitempty"` - Sni string `yaml:"sni,omitempty"` - AllowInsecure bool `yaml:"allow-insecure,omitempty"` - Fingerprint string `yaml:"fingerprint,omitempty"` - SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"` - Alpn []string `yaml:"alpn,omitempty"` - XUDP bool `yaml:"xudp,omitempty"` - Servername string `yaml:"servername,omitempty"` - WSOpts WSOptsStruct `yaml:"ws-opts,omitempty"` - AlterID string `yaml:"alterId,omitempty"` - GRPCOpts GRPCOptsStruct `yaml:"grpc-opts,omitempty"` - RealityOpts RealityOptsStruct `yaml:"reality-opts,omitempty"` - Protocol string `yaml:"protocol,omitempty"` - Obfs string `yaml:"obfs,omitempty"` - ObfsParam string `yaml:"obfs-param,omitempty"` - ProtocolParam string `yaml:"protocol-param,omitempty"` - Remarks []string `yaml:"remarks,omitempty"` + Name string `yaml:"name,omitempty"` + Server string `yaml:"server,omitempty"` + Port int `yaml:"port,omitempty"` + Type string `yaml:"type,omitempty"` + Cipher string `yaml:"cipher,omitempty"` + Password string `yaml:"password,omitempty"` + UDP bool `yaml:"udp,omitempty"` + UUID string `yaml:"uuid,omitempty"` + Network string `yaml:"network,omitempty"` + Flow string `yaml:"flow,omitempty"` + TLS bool `yaml:"tls,omitempty"` + ClientFingerprint string `yaml:"client-fingerprint,omitempty"` + Plugin string `yaml:"plugin,omitempty"` + PluginOpts map[string]any `yaml:"plugin-opts,omitempty"` + Smux SmuxStruct `yaml:"smux,omitempty"` + Sni string `yaml:"sni,omitempty"` + AllowInsecure bool `yaml:"allow-insecure,omitempty"` + Fingerprint string `yaml:"fingerprint,omitempty"` + SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"` + Alpn []string `yaml:"alpn,omitempty"` + XUDP bool `yaml:"xudp,omitempty"` + Servername string `yaml:"servername,omitempty"` + WSOpts WSOptions `yaml:"ws-opts,omitempty"` + AlterID int `yaml:"alterId,omitempty"` + GrpcOpts GrpcOptions `yaml:"grpc-opts,omitempty"` + RealityOpts RealityOptions `yaml:"reality-opts,omitempty"` + Protocol string `yaml:"protocol,omitempty"` + Obfs string `yaml:"obfs,omitempty"` + ObfsParam string `yaml:"obfs-param,omitempty"` + ProtocolParam string `yaml:"protocol-param,omitempty"` + Remarks []string `yaml:"remarks,omitempty"` + HTTPOpts HTTPOptions `yaml:"http-opts,omitempty"` + HTTP2Opts HTTP2Options `yaml:"h2-opts,omitempty"` + PacketAddr bool `yaml:"packet-addr,omitempty"` + PacketEncoding string `yaml:"packet-encoding,omitempty"` + GlobalPadding bool `yaml:"global-padding,omitempty"` + AuthenticatedLength bool `yaml:"authenticated-length,omitempty"` + UDPOverTCP bool `yaml:"udp-over-tcp,omitempty"` + UDPOverTCPVersion int `yaml:"udp-over-tcp-version,omitempty"` +} + +func (p Proxy) MarshalYAML() (interface{}, error) { + switch p.Type { + case "vmess": + return ProxyToVmess(p), nil + case "ss": + return ProxyToShadowSocks(p), nil + case "ssr": + return ProxyToShadowSocksR(p), nil + case "vless": + return ProxyToVless(p), nil + case "trojan": + return ProxyToTrojan(p), nil + } + return nil, nil } diff --git a/model/proxy_group.go b/model/proxy_group.go index 14ba7cf..2bbba1c 100644 --- a/model/proxy_group.go +++ b/model/proxy_group.go @@ -17,6 +17,44 @@ type ProxyGroup struct { Size int `yaml:"-"` } +type SelectProxyGroup struct { + Name string `yaml:"name,omitempty"` + Type string `yaml:"type,omitempty"` + Proxies []string `yaml:"proxies,omitempty"` +} + +type UrlTestProxyGroup struct { + Name string `yaml:"name,omitempty"` + Type string `yaml:"type,omitempty"` + Proxies []string `yaml:"proxies,omitempty"` + Url string `yaml:"url,omitempty"` + Interval int `yaml:"interval,omitempty"` + Tolerance int `yaml:"tolerance,omitempty"` + Lazy bool `yaml:"lazy"` +} + +func (p ProxyGroup) MarshalYAML() (interface{}, error) { + switch p.Type { + case "select": + return SelectProxyGroup{ + Name: p.Name, + Type: p.Type, + Proxies: p.Proxies, + }, nil + case "url-test": + return UrlTestProxyGroup{ + Name: p.Name, + Type: p.Type, + Proxies: p.Proxies, + Url: p.Url, + Interval: p.Interval, + Tolerance: p.Tolerance, + Lazy: p.Lazy, + }, nil + } + return nil, nil +} + type ProxyGroupsSortByName []ProxyGroup type ProxyGroupsSortBySize []ProxyGroup diff --git a/model/proxy_shadowsocks.go b/model/proxy_shadowsocks.go new file mode 100644 index 0000000..18602ab --- /dev/null +++ b/model/proxy_shadowsocks.go @@ -0,0 +1,33 @@ +package model + +type ShadowSocks struct { + Type string `yaml:"type"` + Name string `yaml:"name"` + Server string `yaml:"server"` + Port int `yaml:"port"` + Password string `yaml:"password"` + Cipher string `yaml:"cipher"` + UDP bool `yaml:"udp,omitempty"` + Plugin string `yaml:"plugin,omitempty"` + PluginOpts map[string]any `yaml:"plugin-opts,omitempty"` + UDPOverTCP bool `yaml:"udp-over-tcp,omitempty"` + UDPOverTCPVersion int `yaml:"udp-over-tcp-version,omitempty"` + ClientFingerprint string `yaml:"client-fingerprint,omitempty"` +} + +func ProxyToShadowSocks(p Proxy) ShadowSocks { + return ShadowSocks{ + Type: "ss", + Name: p.Name, + Server: p.Server, + Port: p.Port, + Password: p.Password, + Cipher: p.Cipher, + UDP: p.UDP, + Plugin: p.Plugin, + PluginOpts: p.PluginOpts, + UDPOverTCP: p.UDPOverTCP, + UDPOverTCPVersion: p.UDPOverTCPVersion, + ClientFingerprint: p.ClientFingerprint, + } +} diff --git a/model/proxy_shadowsocksr.go b/model/proxy_shadowsocksr.go new file mode 100644 index 0000000..6b33ce8 --- /dev/null +++ b/model/proxy_shadowsocksr.go @@ -0,0 +1,31 @@ +package model + +type ShadowSocksR struct { + Type string `yaml:"type"` + Name string `yaml:"name"` + Server string `yaml:"server"` + Port int `yaml:"port"` + Password string `yaml:"password"` + Cipher string `yaml:"cipher"` + Obfs string `yaml:"obfs"` + ObfsParam string `yaml:"obfs-param,omitempty"` + Protocol string `yaml:"protocol"` + ProtocolParam string `yaml:"protocol-param,omitempty"` + UDP bool `yaml:"udp,omitempty"` +} + +func ProxyToShadowSocksR(p Proxy) ShadowSocksR { + return ShadowSocksR{ + Type: "ssr", + Name: p.Name, + Server: p.Server, + Port: p.Port, + Password: p.Password, + Cipher: p.Cipher, + Obfs: p.Obfs, + ObfsParam: p.ObfsParam, + Protocol: p.Protocol, + ProtocolParam: p.ProtocolParam, + UDP: p.UDP, + } +} diff --git a/model/proxy_trojan.go b/model/proxy_trojan.go new file mode 100644 index 0000000..7d54831 --- /dev/null +++ b/model/proxy_trojan.go @@ -0,0 +1,39 @@ +package model + +type Trojan struct { + Type string `yaml:"type"` + Name string `yaml:"name"` + Server string `yaml:"server"` + Port int `yaml:"port"` + Password string `yaml:"password"` + ALPN []string `yaml:"alpn,omitempty"` + SNI string `yaml:"sni,omitempty"` + SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"` + Fingerprint string `yaml:"fingerprint,omitempty"` + UDP bool `yaml:"udp,omitempty"` + Network string `yaml:"network,omitempty"` + RealityOpts RealityOptions `yaml:"reality-opts,omitempty"` + GrpcOpts GrpcOptions `yaml:"grpc-opts,omitempty"` + WSOpts WSOptions `yaml:"ws-opts,omitempty"` + ClientFingerprint string `yaml:"client-fingerprint,omitempty"` +} + +func ProxyToTrojan(p Proxy) Trojan { + return Trojan{ + Type: "trojan", + Name: p.Name, + Server: p.Server, + Port: p.Port, + Password: p.Password, + ALPN: p.Alpn, + SNI: p.Sni, + SkipCertVerify: p.SkipCertVerify, + Fingerprint: p.Fingerprint, + UDP: p.UDP, + Network: p.Network, + RealityOpts: p.RealityOpts, + GrpcOpts: p.GrpcOpts, + WSOpts: p.WSOpts, + ClientFingerprint: p.ClientFingerprint, + } +} diff --git a/model/proxy_vless.go b/model/proxy_vless.go new file mode 100644 index 0000000..e143296 --- /dev/null +++ b/model/proxy_vless.go @@ -0,0 +1,57 @@ +package model + +type Vless struct { + Type string `yaml:"type"` + Name string `yaml:"name"` + Server string `yaml:"server"` + Port int `yaml:"port"` + UUID string `yaml:"uuid"` + Flow string `yaml:"flow,omitempty"` + TLS bool `yaml:"tls,omitempty"` + ALPN []string `yaml:"alpn,omitempty"` + UDP bool `yaml:"udp,omitempty"` + PacketAddr bool `yaml:"packet-addr,omitempty"` + XUDP bool `yaml:"xudp,omitempty"` + PacketEncoding string `yaml:"packet-encoding,omitempty"` + Network string `yaml:"network,omitempty"` + RealityOpts RealityOptions `yaml:"reality-opts,omitempty"` + HTTPOpts HTTPOptions `yaml:"http-opts,omitempty"` + HTTP2Opts HTTP2Options `yaml:"h2-opts,omitempty"` + GrpcOpts GrpcOptions `yaml:"grpc-opts,omitempty"` + WSOpts WSOptions `yaml:"ws-opts,omitempty"` + WSPath string `yaml:"ws-path,omitempty"` + WSHeaders map[string]string `yaml:"ws-headers,omitempty"` + SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"` + Fingerprint string `yaml:"fingerprint,omitempty"` + ServerName string `yaml:"servername,omitempty"` + ClientFingerprint string `yaml:"client-fingerprint,omitempty"` +} + +func ProxyToVless(p Proxy) Vless { + return Vless{ + Type: "vless", + Name: p.Name, + Server: p.Server, + Port: p.Port, + UUID: p.UUID, + Flow: p.Flow, + TLS: p.TLS, + ALPN: p.Alpn, + UDP: p.UDP, + PacketAddr: p.PacketAddr, + XUDP: p.XUDP, + PacketEncoding: p.PacketEncoding, + Network: p.Network, + RealityOpts: p.RealityOpts, + HTTPOpts: p.HTTPOpts, + HTTP2Opts: p.HTTP2Opts, + GrpcOpts: p.GrpcOpts, + WSOpts: p.WSOpts, + WSPath: p.WSOpts.Path, + WSHeaders: p.WSOpts.Headers, + SkipCertVerify: p.SkipCertVerify, + Fingerprint: p.Fingerprint, + ServerName: p.Servername, + ClientFingerprint: p.ClientFingerprint, + } +} diff --git a/model/proxy_vmess.go b/model/proxy_vmess.go new file mode 100644 index 0000000..5753a3e --- /dev/null +++ b/model/proxy_vmess.go @@ -0,0 +1,86 @@ +package model + +type HTTPOptions struct { + Method string `proxy:"method,omitempty"` + Path []string `proxy:"path,omitempty"` + Headers map[string][]string `proxy:"headers,omitempty"` +} + +type HTTP2Options struct { + Host []string `proxy:"host,omitempty"` + Path string `proxy:"path,omitempty"` +} + +type GrpcOptions struct { + GrpcServiceName string `proxy:"grpc-service-name,omitempty"` +} + +type RealityOptions struct { + PublicKey string `proxy:"public-key"` + ShortID string `proxy:"short-id"` +} + +type WSOptions struct { + Path string `proxy:"path,omitempty"` + Headers map[string]string `proxy:"headers,omitempty"` + MaxEarlyData int `proxy:"max-early-data,omitempty"` + EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"` +} + +type Vmess struct { + Type string `yaml:"type"` + Name string `yaml:"name"` + Server string `yaml:"server"` + Port int `yaml:"port"` + UUID string `yaml:"uuid"` + AlterID int `yaml:"alterId"` + Cipher string `yaml:"cipher"` + UDP bool `yaml:"udp,omitempty"` + Network string `yaml:"network,omitempty"` + TLS bool `yaml:"tls,omitempty"` + ALPN []string `yaml:"alpn,omitempty"` + SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"` + Fingerprint string `yaml:"fingerprint,omitempty"` + ServerName string `yaml:"servername,omitempty"` + RealityOpts RealityOptions `yaml:"reality-opts,omitempty"` + HTTPOpts HTTPOptions `yaml:"http-opts,omitempty"` + HTTP2Opts HTTP2Options `yaml:"h2-opts,omitempty"` + GrpcOpts GrpcOptions `yaml:"grpc-opts,omitempty"` + WSOpts WSOptions `yaml:"ws-opts,omitempty"` + PacketAddr bool `yaml:"packet-addr,omitempty"` + XUDP bool `yaml:"xudp,omitempty"` + PacketEncoding string `yaml:"packet-encoding,omitempty"` + GlobalPadding bool `yaml:"global-padding,omitempty"` + AuthenticatedLength bool `yaml:"authenticated-length,omitempty"` + ClientFingerprint string `yaml:"client-fingerprint,omitempty"` +} + +func ProxyToVmess(p Proxy) Vmess { + return Vmess{ + Type: "vmess", + Name: p.Name, + Server: p.Server, + Port: p.Port, + UUID: p.UUID, + AlterID: p.AlterID, + Cipher: p.Cipher, + UDP: p.UDP, + Network: p.Network, + TLS: p.TLS, + ALPN: p.Alpn, + SkipCertVerify: p.SkipCertVerify, + Fingerprint: p.Fingerprint, + ServerName: p.Servername, + RealityOpts: p.RealityOpts, + HTTPOpts: p.HTTPOpts, + HTTP2Opts: p.HTTP2Opts, + GrpcOpts: p.GrpcOpts, + WSOpts: p.WSOpts, + PacketAddr: p.PacketAddr, + XUDP: p.XUDP, + PacketEncoding: p.PacketEncoding, + GlobalPadding: p.GlobalPadding, + AuthenticatedLength: p.AuthenticatedLength, + ClientFingerprint: p.ClientFingerprint, + } +} diff --git a/parser/ss.go b/parser/ss.go index a654fdf..11eceb2 100644 --- a/parser/ss.go +++ b/parser/ss.go @@ -1,7 +1,7 @@ package parser import ( - "fmt" + "errors" "net/url" "strconv" "strings" @@ -12,35 +12,35 @@ import ( func ParseSS(proxy string) (model.Proxy, error) { // 判断是否以 ss:// 开头 if !strings.HasPrefix(proxy, "ss://") { - return model.Proxy{}, fmt.Errorf("invalid ss Url") + return model.Proxy{}, errors.New("invalid ss Url") } // 分割 parts := strings.SplitN(strings.TrimPrefix(proxy, "ss://"), "@", 2) if len(parts) != 2 { - return model.Proxy{}, fmt.Errorf("invalid ss Url") + return model.Proxy{}, errors.New("invalid ss Url") } if !strings.Contains(parts[0], ":") { // 解码 decoded, err := DecodeBase64(parts[0]) if err != nil { - return model.Proxy{}, err + return model.Proxy{}, errors.New("invalid ss Url" + err.Error()) } parts[0] = decoded } credentials := strings.SplitN(parts[0], ":", 2) if len(credentials) != 2 { - return model.Proxy{}, fmt.Errorf("invalid ss Url") + return model.Proxy{}, errors.New("invalid ss Url") } // 分割 serverInfo := strings.SplitN(parts[1], "#", 2) serverAndPort := strings.SplitN(serverInfo[0], ":", 2) if len(serverAndPort) != 2 { - return model.Proxy{}, fmt.Errorf("invalid ss Url") + return model.Proxy{}, errors.New("invalid ss Url") } // 转换端口字符串为数字 port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1])) if err != nil { - return model.Proxy{}, err + return model.Proxy{}, errors.New("invalid ss Url" + err.Error()) } // 返回结果 result := model.Proxy{ @@ -56,7 +56,7 @@ func ParseSS(proxy string) (model.Proxy, error) { if len(serverInfo) == 2 { unescape, err := url.QueryUnescape(serverInfo[1]) if err != nil { - return model.Proxy{}, err + return model.Proxy{}, errors.New("invalid ss Url" + err.Error()) } result.Name = strings.TrimSpace(unescape) } else { diff --git a/parser/vless.go b/parser/vless.go index 5e56467..1a0c326 100644 --- a/parser/vless.go +++ b/parser/vless.go @@ -47,7 +47,7 @@ func ParseVless(proxy string) (model.Proxy, error) { Flow: params.Get("flow"), Fingerprint: params.Get("fp"), Servername: params.Get("sni"), - RealityOpts: model.RealityOptsStruct{ + RealityOpts: model.RealityOptions{ PublicKey: params.Get("pbk"), }, } @@ -55,16 +55,16 @@ func ParseVless(proxy string) (model.Proxy, error) { result.Alpn = strings.Split(params.Get("alpn"), ",") } if params.Get("type") == "ws" { - result.WSOpts = model.WSOptsStruct{ + result.WSOpts = model.WSOptions{ Path: params.Get("path"), - Headers: model.HeaderStruct{ - Host: params.Get("host"), + Headers: map[string]string{ + "Host": params.Get("host"), }, } } if params.Get("type") == "grpc" { - result.GRPCOpts = model.GRPCOptsStruct{ - GRPCServiceName: params.Get("serviceName"), + result.GrpcOpts = model.GrpcOptions{ + GrpcServiceName: params.Get("serviceName"), } } // 如果有节点名称 diff --git a/parser/vmess.go b/parser/vmess.go index 60b846c..b7a1e9b 100644 --- a/parser/vmess.go +++ b/parser/vmess.go @@ -3,7 +3,6 @@ package parser import ( "encoding/json" "errors" - "fmt" "strconv" "strings" "sub2clash/model" @@ -12,24 +11,42 @@ import ( func ParseVmess(proxy string) (model.Proxy, error) { // 判断是否以 vmess:// 开头 if !strings.HasPrefix(proxy, "vmess://") { - return model.Proxy{}, fmt.Errorf("invalid vmess Url") + return model.Proxy{}, errors.New("invalid vmess url") } // 解码 base64, err := DecodeBase64(strings.TrimPrefix(proxy, "vmess://")) if err != nil { - return model.Proxy{}, errors.New("无效的 vmess Url") + return model.Proxy{}, errors.New("invalid vmess url" + err.Error()) } // 解析 - var vmess model.Vmess + var vmess model.VmessJson err = json.Unmarshal([]byte(base64), &vmess) if err != nil { - return model.Proxy{}, errors.New("无效的 vmess Url") + return model.Proxy{}, errors.New("invalid vmess url" + err.Error()) } - // 处理端口 - port, err := strconv.Atoi(strings.TrimSpace(vmess.Port)) - if err != nil { - return model.Proxy{}, errors.New("无效的 vmess Url") + // 解析端口 + port := 0 + switch vmess.Port.(type) { + case string: + port, err = strconv.Atoi(vmess.Port.(string)) + if err != nil { + return model.Proxy{}, errors.New("invalid vmess url" + err.Error()) + } + case float64: + port = int(vmess.Port.(float64)) } + // 解析Aid + aid := 0 + switch vmess.Aid.(type) { + case string: + aid, err = strconv.Atoi(vmess.Aid.(string)) + if err != nil { + return model.Proxy{}, errors.New("invalid vmess url" + err.Error()) + } + case float64: + aid = int(vmess.Aid.(float64)) + } + // 设置默认值 if vmess.Scy == "" { vmess.Scy = "auto" } @@ -46,7 +63,7 @@ func ParseVmess(proxy string) (model.Proxy, error) { Server: vmess.Add, Port: port, UUID: vmess.Id, - AlterID: vmess.Aid, + AlterID: aid, Cipher: vmess.Scy, UDP: true, TLS: vmess.Tls == "tls", @@ -57,10 +74,10 @@ func ParseVmess(proxy string) (model.Proxy, error) { Network: vmess.Net, } if vmess.Net == "ws" { - result.WSOpts = model.WSOptsStruct{ + result.WSOpts = model.WSOptions{ Path: vmess.Path, - Headers: model.HeaderStruct{ - Host: vmess.Host, + Headers: map[string]string{ + "Host": vmess.Host, }, } } diff --git a/utils/proxy.go b/utils/proxy.go index 6822600..b16f8d8 100644 --- a/utils/proxy.go +++ b/utils/proxy.go @@ -1,7 +1,9 @@ package utils import ( + "go.uber.org/zap" "strings" + "sub2clash/logger" "sub2clash/model" "sub2clash/parser" ) @@ -127,6 +129,10 @@ func ParseProxy(proxies ...string) []model.Proxy { } if err == nil { result = append(result, proxyItem) + } else { + logger.Logger.Debug( + "parse proxy failed", zap.String("proxy", proxy), zap.Error(err), + ) } } }