From 646e6b13ace34901c2f2e0a7729dea3207e844bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Nov 2024 13:42:43 +0800 Subject: [PATCH] Append subscription groups to other groups --- cmd/serenity/cmd_run.go | 2 +- template/render_outbounds.go | 191 ++++++++++++++++++++--------------- 2 files changed, 113 insertions(+), 80 deletions(-) diff --git a/cmd/serenity/cmd_run.go b/cmd/serenity/cmd_run.go index 418e13a..2d098a8 100644 --- a/cmd/serenity/cmd_run.go +++ b/cmd/serenity/cmd_run.go @@ -203,5 +203,5 @@ func closeMonitor(ctx context.Context) { return default: } - log.Fatal("sing-box did not close!") + log.Fatal("serenity did not close!") } diff --git a/template/render_outbounds.go b/template/render_outbounds.go index 4a68ff2..9486f02 100644 --- a/template/render_outbounds.go +++ b/template/render_outbounds.go @@ -3,6 +3,7 @@ package template import ( "bytes" "regexp" + "sort" "text/template" M "github.com/sagernet/serenity/common/metadata" @@ -129,95 +130,128 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *boxOption.Optio subscriptionGroups = make(map[string][]boxOption.Outbound) ) for _, extraGroup := range t.groups { - myFilter := func(outboundTag string) bool { - if len(extraGroup.filter) > 0 { - if !common.Any(extraGroup.filter, func(it *regexp.Regexp) bool { - return it.MatchString(outboundTag) - }) { - return false - } - } - if len(extraGroup.exclude) > 0 { - if common.Any(extraGroup.exclude, func(it *regexp.Regexp) bool { - return it.MatchString(outboundTag) - }) { - return false - } - } - return true - } if extraGroup.Target != option.ExtraGroupTargetSubscription { - extraTags := common.Filter(common.FlatMap(subscriptions, func(it *subscription.Subscription) []string { - return common.Map(it.Servers, outboundToString) - }), myFilter) - if len(extraTags) == 0 { - continue + continue + } + tmpl := template.New("tag") + if extraGroup.TagPerSubscription != "" { + _, err := tmpl.Parse(extraGroup.TagPerSubscription) + if err != nil { + return E.Cause(err, "parse `tag_per_subscription`: ", extraGroup.TagPerSubscription) } - groupOutbound := boxOption.Outbound{ - Tag: extraGroup.Tag, + } else { + common.Must1(tmpl.Parse("{{ .tag }} ({{ .subscription_name }})")) + } + var outboundTags []string + for _, it := range subscriptions { + subscriptionTags := common.Filter(common.Map(it.Servers, outboundToString), func(outboundTag string) bool { + if len(extraGroup.filter) > 0 { + if !common.Any(extraGroup.filter, func(it *regexp.Regexp) bool { + return it.MatchString(outboundTag) + }) { + return false + } + } + if len(extraGroup.exclude) > 0 { + if common.Any(extraGroup.exclude, func(it *regexp.Regexp) bool { + return it.MatchString(outboundTag) + }) { + return false + } + } + return true + }) + var tagPerSubscription string + if len(outboundTags) == 0 && len(subscriptions) == 1 { + tagPerSubscription = extraGroup.Tag + } else { + var buffer bytes.Buffer + err := tmpl.Execute(&buffer, map[string]interface{}{ + "tag": extraGroup.Tag, + "subscription_name": it.Name, + }) + if err != nil { + return E.Cause(err, "generate tag for extra group: tag=", extraGroup.Tag, ", subscription=", it.Name) + } + tagPerSubscription = buffer.String() + } + groupOutboundPerSubscription := boxOption.Outbound{ + Tag: tagPerSubscription, Type: extraGroup.Type, } switch extraGroup.Type { case C.TypeSelector: selectorOptions := common.PtrValueOrDefault(extraGroup.CustomSelector) - groupOutbound.Options = &selectorOptions - selectorOptions.Outbounds = append(selectorOptions.Outbounds, extraTags...) - case C.TypeURLTest: - urltestOptions := common.PtrValueOrDefault(extraGroup.CustomURLTest) - groupOutbound.Options = &urltestOptions - urltestOptions.Outbounds = append(urltestOptions.Outbounds, extraTags...) - } - if extraGroup.Target == option.ExtraGroupTargetDefault { - defaultGroups = append(defaultGroups, groupOutbound) - } else { - globalGroups = append(globalGroups, groupOutbound) - } - } else { - tmpl := template.New("tag") - if extraGroup.TagPerSubscription != "" { - _, err := tmpl.Parse(extraGroup.TagPerSubscription) - if err != nil { - return E.Cause(err, "parse `tag_per_subscription`: ", extraGroup.TagPerSubscription) - } - } else { - common.Must1(tmpl.Parse("{{ .tag }} ({{ .subscription_name }})")) - } - var outboundTags []string - for _, it := range subscriptions { - subscriptionTags := common.Filter(common.Map(it.Servers, outboundToString), myFilter) - if len(subscriptionTags) == 0 { + groupOutboundPerSubscription.Options = &selectorOptions + selectorOptions.Outbounds = common.Uniq(append(selectorOptions.Outbounds, subscriptionTags...)) + if len(selectorOptions.Outbounds) == 0 { continue } - var tagPerSubscription string - if len(outboundTags) == 0 && len(subscriptions) == 1 { - tagPerSubscription = extraGroup.Tag - } else { - var buffer bytes.Buffer - err := tmpl.Execute(&buffer, map[string]interface{}{ - "tag": extraGroup.Tag, - "subscription_name": it.Name, - }) - if err != nil { - return E.Cause(err, "generate tag for extra group: tag=", extraGroup.Tag, ", subscription=", it.Name) - } - tagPerSubscription = buffer.String() + case C.TypeURLTest: + urltestOptions := common.PtrValueOrDefault(extraGroup.CustomURLTest) + groupOutboundPerSubscription.Options = &urltestOptions + urltestOptions.Outbounds = common.Uniq(append(urltestOptions.Outbounds, subscriptionTags...)) + if len(urltestOptions.Outbounds) == 0 { + continue } - groupOutboundPerSubscription := boxOption.Outbound{ - Tag: tagPerSubscription, - Type: extraGroup.Type, - } - switch extraGroup.Type { - case C.TypeSelector: - selectorOptions := common.PtrValueOrDefault(extraGroup.CustomSelector) - groupOutboundPerSubscription.Options = &selectorOptions - selectorOptions.Outbounds = append(selectorOptions.Outbounds, subscriptionTags...) - case C.TypeURLTest: - urltestOptions := common.PtrValueOrDefault(extraGroup.CustomURLTest) - groupOutboundPerSubscription.Options = &urltestOptions - urltestOptions.Outbounds = append(urltestOptions.Outbounds, subscriptionTags...) - } - subscriptionGroups[it.Name] = append(subscriptionGroups[it.Name], groupOutboundPerSubscription) } + subscriptionGroups[it.Name] = append(subscriptionGroups[it.Name], groupOutboundPerSubscription) + } + } + for _, extraGroup := range t.groups { + if extraGroup.Target == option.ExtraGroupTargetSubscription { + continue + } + extraTags := groupTags + for _, group := range subscriptionGroups { + extraTags = append(extraTags, common.Map(group, outboundToString)...) + } + sort.Strings(extraTags) + if len(extraTags) == 0 || extraGroup.filter != nil || extraGroup.exclude != nil { + extraTags = append(extraTags, common.Filter(common.FlatMap(subscriptions, func(it *subscription.Subscription) []string { + return common.Map(it.Servers, outboundToString) + }), func(outboundTag string) bool { + if len(extraGroup.filter) > 0 { + if !common.Any(extraGroup.filter, func(it *regexp.Regexp) bool { + return it.MatchString(outboundTag) + }) { + return false + } + } + if len(extraGroup.exclude) > 0 { + if common.Any(extraGroup.exclude, func(it *regexp.Regexp) bool { + return it.MatchString(outboundTag) + }) { + return false + } + } + return true + })...) + } + groupOutbound := boxOption.Outbound{ + Tag: extraGroup.Tag, + Type: extraGroup.Type, + } + switch extraGroup.Type { + case C.TypeSelector: + selectorOptions := common.PtrValueOrDefault(extraGroup.CustomSelector) + groupOutbound.Options = &selectorOptions + selectorOptions.Outbounds = common.Uniq(append(selectorOptions.Outbounds, extraTags...)) + if len(selectorOptions.Outbounds) == 0 { + continue + } + case C.TypeURLTest: + urltestOptions := common.PtrValueOrDefault(extraGroup.CustomURLTest) + groupOutbound.Options = &urltestOptions + urltestOptions.Outbounds = common.Uniq(append(urltestOptions.Outbounds, extraTags...)) + if len(urltestOptions.Outbounds) == 0 { + continue + } + } + if extraGroup.Target == option.ExtraGroupTargetDefault { + defaultGroups = append(defaultGroups, groupOutbound) + } else { + globalGroups = append(globalGroups, groupOutbound) } } @@ -238,7 +272,6 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *boxOption.Optio } options.Outbounds = groupJoin(options.Outbounds, defaultTag, false, groupTags...) options.Outbounds = groupJoin(options.Outbounds, defaultTag, false, globalOutboundTags...) - options.Outbounds = append(options.Outbounds, allGroupOutbounds...) return nil }