diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index d775021..1bcdb81 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -3,6 +3,7 @@ package outboundgroup import ( "errors" "fmt" + "regexp" "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/provider" @@ -29,6 +30,7 @@ type GroupCommonOption struct { Interval int `group:"interval,omitempty"` Lazy bool `group:"lazy,omitempty"` DisableUDP bool `group:"disable-udp,omitempty"` + Filter string `group:"filter,omitempty"` } func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { @@ -45,14 +47,25 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide return nil, errFormat } - groupName := groupOption.Name + var ( + groupName = groupOption.Name + filterReg *regexp.Regexp + ) - providers := []types.ProxyProvider{} + if groupOption.Filter != "" { + f, err := regexp.Compile(groupOption.Filter) + if err != nil { + return nil, fmt.Errorf("%s: invalid filter regex: %w", groupName, err) + } + filterReg = f + } if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { return nil, fmt.Errorf("%s: %w", groupName, errMissProxy) } + providers := []types.ProxyProvider{} + if len(groupOption.Proxies) != 0 { ps, err := getProxies(proxyMap, groupOption.Proxies) if err != nil { @@ -94,7 +107,12 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide if err != nil { return nil, fmt.Errorf("%s: %w", groupName, err) } - providers = append(providers, list...) + if filterReg != nil { + pd := provider.NewFilterableProvider(groupName, list, filterReg) + providers = append(providers, pd) + } else { + providers = append(providers, list...) + } } var group C.ProxyAdapter diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 80611ac..70743c8 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -9,12 +9,17 @@ import ( "time" "github.com/Dreamacro/clash/adapter" + "github.com/Dreamacro/clash/adapter/outbound" + "github.com/Dreamacro/clash/common/singledo" C "github.com/Dreamacro/clash/constant" types "github.com/Dreamacro/clash/constant/provider" + "github.com/samber/lo" "gopkg.in/yaml.v3" ) +var reject = adapter.NewProxy(outbound.NewReject()) + const ( ReservedName = "default" ) @@ -231,3 +236,77 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co runtime.SetFinalizer(wrapper, stopCompatibleProvider) return wrapper, nil } + +var _ types.ProxyProvider = (*FilterableProvider)(nil) + +type FilterableProvider struct { + name string + providers []types.ProxyProvider + filterReg *regexp.Regexp + single *singledo.Single +} + +func (fp *FilterableProvider) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]any{ + "name": fp.Name(), + "type": fp.Type().String(), + "vehicleType": fp.VehicleType().String(), + "proxies": fp.Proxies(), + }) +} + +func (fp *FilterableProvider) Name() string { + return fp.name +} + +func (fp *FilterableProvider) HealthCheck() { +} + +func (fp *FilterableProvider) Update() error { + return nil +} + +func (fp *FilterableProvider) Initial() error { + return nil +} + +func (fp *FilterableProvider) VehicleType() types.VehicleType { + return types.Compatible +} + +func (fp *FilterableProvider) Type() types.ProviderType { + return types.Proxy +} + +func (fp *FilterableProvider) Proxies() []C.Proxy { + elm, _, _ := fp.single.Do(func() (any, error) { + proxies := lo.FlatMap( + fp.providers, + func(item types.ProxyProvider, _ int) []C.Proxy { + return lo.Filter( + item.Proxies(), + func(item C.Proxy, _ int) bool { + return fp.filterReg.MatchString(item.Name()) + }) + }) + + if len(proxies) == 0 { + proxies = append(proxies, reject) + } + return proxies, nil + }) + + return elm.([]C.Proxy) +} + +func (fp *FilterableProvider) Touch() { +} + +func NewFilterableProvider(name string, providers []types.ProxyProvider, filterReg *regexp.Regexp) *FilterableProvider { + return &FilterableProvider{ + name: name, + providers: providers, + filterReg: filterReg, + single: singledo.NewSingle(time.Second * 10), + } +}