github.com/metacubex/mihomo@v1.18.5/adapter/outboundgroup/parser.go (about)

     1  package outboundgroup
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/dlclark/regexp2"
     9  
    10  	"github.com/metacubex/mihomo/adapter/outbound"
    11  	"github.com/metacubex/mihomo/adapter/provider"
    12  	"github.com/metacubex/mihomo/common/structure"
    13  	"github.com/metacubex/mihomo/common/utils"
    14  	C "github.com/metacubex/mihomo/constant"
    15  	types "github.com/metacubex/mihomo/constant/provider"
    16  )
    17  
    18  var (
    19  	errFormat            = errors.New("format error")
    20  	errType              = errors.New("unsupported type")
    21  	errMissProxy         = errors.New("`use` or `proxies` missing")
    22  	errDuplicateProvider = errors.New("duplicate provider name")
    23  )
    24  
    25  type GroupCommonOption struct {
    26  	outbound.BasicOption
    27  	Name                string   `group:"name"`
    28  	Type                string   `group:"type"`
    29  	Proxies             []string `group:"proxies,omitempty"`
    30  	Use                 []string `group:"use,omitempty"`
    31  	URL                 string   `group:"url,omitempty"`
    32  	Interval            int      `group:"interval,omitempty"`
    33  	TestTimeout         int      `group:"timeout,omitempty"`
    34  	MaxFailedTimes      int      `group:"max-failed-times,omitempty"`
    35  	Lazy                bool     `group:"lazy,omitempty"`
    36  	DisableUDP          bool     `group:"disable-udp,omitempty"`
    37  	Filter              string   `group:"filter,omitempty"`
    38  	ExcludeFilter       string   `group:"exclude-filter,omitempty"`
    39  	ExcludeType         string   `group:"exclude-type,omitempty"`
    40  	ExpectedStatus      string   `group:"expected-status,omitempty"`
    41  	IncludeAll          bool     `group:"include-all,omitempty"`
    42  	IncludeAllProxies   bool     `group:"include-all-proxies,omitempty"`
    43  	IncludeAllProviders bool     `group:"include-all-providers,omitempty"`
    44  	Hidden              bool     `group:"hidden,omitempty"`
    45  	Icon                string   `group:"icon,omitempty"`
    46  }
    47  
    48  func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider, AllProxies []string, AllProviders []string) (C.ProxyAdapter, error) {
    49  	decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
    50  
    51  	groupOption := &GroupCommonOption{
    52  		Lazy: true,
    53  	}
    54  	if err := decoder.Decode(config, groupOption); err != nil {
    55  		return nil, errFormat
    56  	}
    57  
    58  	if groupOption.Type == "" || groupOption.Name == "" {
    59  		return nil, errFormat
    60  	}
    61  
    62  	groupName := groupOption.Name
    63  
    64  	providers := []types.ProxyProvider{}
    65  
    66  	if groupOption.IncludeAll {
    67  		groupOption.IncludeAllProviders = true
    68  		groupOption.IncludeAllProxies = true
    69  	}
    70  
    71  	if groupOption.IncludeAllProviders {
    72  		groupOption.Use = append(groupOption.Use, AllProviders...)
    73  	}
    74  	if groupOption.IncludeAllProxies {
    75  		if groupOption.Filter != "" {
    76  			var filterRegs []*regexp2.Regexp
    77  			for _, filter := range strings.Split(groupOption.Filter, "`") {
    78  				filterReg := regexp2.MustCompile(filter, regexp2.None)
    79  				filterRegs = append(filterRegs, filterReg)
    80  			}
    81  			for _, p := range AllProxies {
    82  				for _, filterReg := range filterRegs {
    83  					if mat, _ := filterReg.MatchString(p); mat {
    84  						groupOption.Proxies = append(groupOption.Proxies, p)
    85  					}
    86  				}
    87  			}
    88  		} else {
    89  			groupOption.Proxies = append(groupOption.Proxies, AllProxies...)
    90  		}
    91  	}
    92  
    93  	if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
    94  		return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
    95  	}
    96  
    97  	expectedStatus, err := utils.NewUnsignedRanges[uint16](groupOption.ExpectedStatus)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("%s: %w", groupName, err)
   100  	}
   101  
   102  	status := strings.TrimSpace(groupOption.ExpectedStatus)
   103  	if status == "" {
   104  		status = "*"
   105  	}
   106  	groupOption.ExpectedStatus = status
   107  
   108  	if len(groupOption.Use) != 0 {
   109  		PDs, err := getProviders(providersMap, groupOption.Use)
   110  		if err != nil {
   111  			return nil, fmt.Errorf("%s: %w", groupName, err)
   112  		}
   113  
   114  		// if test URL is empty, use the first health check URL of providers
   115  		if groupOption.URL == "" {
   116  			for _, pd := range PDs {
   117  				if pd.HealthCheckURL() != "" {
   118  					groupOption.URL = pd.HealthCheckURL()
   119  					break
   120  				}
   121  			}
   122  			if groupOption.URL == "" {
   123  				groupOption.URL = C.DefaultTestURL
   124  			}
   125  		} else {
   126  			addTestUrlToProviders(PDs, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
   127  		}
   128  		providers = append(providers, PDs...)
   129  	}
   130  
   131  	if len(groupOption.Proxies) != 0 {
   132  		ps, err := getProxies(proxyMap, groupOption.Proxies)
   133  		if err != nil {
   134  			return nil, fmt.Errorf("%s: %w", groupName, err)
   135  		}
   136  
   137  		if _, ok := providersMap[groupName]; ok {
   138  			return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
   139  		}
   140  
   141  		if groupOption.URL == "" {
   142  			groupOption.URL = C.DefaultTestURL
   143  		}
   144  
   145  		// select don't need auto health check
   146  		if groupOption.Type != "select" && groupOption.Type != "relay" {
   147  			if groupOption.Interval == 0 {
   148  				groupOption.Interval = 300
   149  			}
   150  		}
   151  
   152  		hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus)
   153  
   154  		pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
   155  		if err != nil {
   156  			return nil, fmt.Errorf("%s: %w", groupName, err)
   157  		}
   158  
   159  		providers = append([]types.ProxyProvider{pd}, providers...)
   160  		providersMap[groupName] = pd
   161  	}
   162  
   163  	var group C.ProxyAdapter
   164  	switch groupOption.Type {
   165  	case "url-test":
   166  		opts := parseURLTestOption(config)
   167  		group = NewURLTest(groupOption, providers, opts...)
   168  	case "select":
   169  		group = NewSelector(groupOption, providers)
   170  	case "fallback":
   171  		group = NewFallback(groupOption, providers)
   172  	case "load-balance":
   173  		strategy := parseStrategy(config)
   174  		return NewLoadBalance(groupOption, providers, strategy)
   175  	case "relay":
   176  		group = NewRelay(groupOption, providers)
   177  	default:
   178  		return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
   179  	}
   180  
   181  	return group, nil
   182  }
   183  
   184  func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
   185  	var ps []C.Proxy
   186  	for _, name := range list {
   187  		p, ok := mapping[name]
   188  		if !ok {
   189  			return nil, fmt.Errorf("'%s' not found", name)
   190  		}
   191  		ps = append(ps, p)
   192  	}
   193  	return ps, nil
   194  }
   195  
   196  func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
   197  	var ps []types.ProxyProvider
   198  	for _, name := range list {
   199  		p, ok := mapping[name]
   200  		if !ok {
   201  			return nil, fmt.Errorf("'%s' not found", name)
   202  		}
   203  
   204  		if p.VehicleType() == types.Compatible {
   205  			return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
   206  		}
   207  		ps = append(ps, p)
   208  	}
   209  	return ps, nil
   210  }
   211  
   212  func addTestUrlToProviders(providers []types.ProxyProvider, url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
   213  	if len(providers) == 0 || len(url) == 0 {
   214  		return
   215  	}
   216  
   217  	for _, pd := range providers {
   218  		pd.RegisterHealthCheckTask(url, expectedStatus, filter, interval)
   219  	}
   220  }