mirror of
https://github.com/bestnite/sub2clash.git
synced 2026-04-26 12:51:52 +00:00
refactor: preserve template yaml structure
This commit is contained in:
+407
-120
@@ -93,11 +93,85 @@ func FetchSubscriptionFromAPI(url string, userAgent string, retryTimes int) ([]b
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// BuildSub 是当前配置转换链路的核心入口。
|
||||
//
|
||||
// 当前设计分为三层:
|
||||
// 1. templateDoc:模板 YAML 的完整语法树,也是最终输出真源
|
||||
// 2. generatedConfig:本项目运行期最小叠加层,只保存参与业务计算的字段
|
||||
// 3. proxy.Proxy:节点解析后的 typed 模型,用于过滤、去重、重命名和输出
|
||||
//
|
||||
// 这个函数的目标不是“重建一整份 mihomo 配置”,而是:
|
||||
// - 保留模板中绝大部分原始字段
|
||||
// - 只对 proxies / proxy-groups / rules / rule-providers 做定点 patch
|
||||
func BuildSub(clashType model.ClashType, query model.ConvertConfig, template string, cacheExpire int64, retryTimes int) (
|
||||
*model.Subscription, error,
|
||||
*BuiltSub, error,
|
||||
) {
|
||||
var temp = &model.Subscription{}
|
||||
var sub = &model.Subscription{}
|
||||
templateDoc, templateBytes, err := loadTemplateDocument(query, template, cacheExpire, retryTimes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
temp, err := extractTemplateOverlay(templateDoc)
|
||||
if err != nil {
|
||||
logger.Logger.Debug("extract template overlay failed", zap.Error(err))
|
||||
return nil, NewTemplateParseError(templateBytes, err)
|
||||
}
|
||||
proxyList, err := collectQueryProxies(query, cacheExpire, retryTimes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyList, err = normalizeProxyList(query, proxyList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// t 仅承载“由节点生成出来的新内容”,例如国家组。
|
||||
// 模板里原有的组、规则等则保存在 temp 中。
|
||||
generated, err := buildGeneratedConfig(clashType, query, proxyList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
MergeSubAndTemplate(temp, generated, query.IgnoreCountryGrooup)
|
||||
|
||||
applyRulePatches(temp, query)
|
||||
addedRuleProviders := buildRuleProviderPatches(query)
|
||||
|
||||
if err := mergeTemplateProxies(templateDoc, generated.Proxy); err != nil {
|
||||
return nil, NewError(ErrConfigInvalid, "failed to update template path: proxies", err)
|
||||
}
|
||||
|
||||
if temp.ProxyGroup == nil {
|
||||
temp.ProxyGroup = make([]generatedGroup, 0)
|
||||
}
|
||||
if err := mergeTemplateProxyGroups(templateDoc, temp.ProxyGroup); err != nil {
|
||||
return nil, NewError(ErrConfigInvalid, "failed to update template path: proxy-groups", err)
|
||||
}
|
||||
|
||||
rulesChanged := len(query.Rules) != 0 || len(query.RuleProviders) != 0
|
||||
if rulesChanged {
|
||||
if temp.Rule == nil {
|
||||
temp.Rule = make([]string, 0)
|
||||
}
|
||||
if err := SetYAMLPath(templateDoc, "rules", temp.Rule); err != nil {
|
||||
return nil, NewError(ErrConfigInvalid, "failed to update template path: rules", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(query.RuleProviders) != 0 {
|
||||
if err := mergeTemplateRuleProviders(templateDoc, addedRuleProviders); err != nil {
|
||||
return nil, NewError(ErrConfigInvalid, "failed to update template path: rule-providers", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &BuiltSub{root: templateDoc}, nil
|
||||
}
|
||||
|
||||
// loadTemplateDocument 负责统一加载模板来源,并返回:
|
||||
// 1. 解析后的 YAML 语法树
|
||||
// 2. 原始模板字节,用于错误报告
|
||||
func loadTemplateDocument(query model.ConvertConfig, template string, cacheExpire int64, retryTimes int) (*yaml.Node, []byte, error) {
|
||||
var err error
|
||||
var templateBytes []byte
|
||||
|
||||
@@ -110,79 +184,38 @@ func BuildSub(clashType model.ClashType, query model.ConvertConfig, template str
|
||||
logger.Logger.Debug(
|
||||
"load template failed", zap.String("template", template), zap.Error(err),
|
||||
)
|
||||
return nil, NewTemplateLoadError(template, err)
|
||||
return nil, nil, NewTemplateLoadError(template, err)
|
||||
}
|
||||
} else {
|
||||
unescape, err := url.QueryUnescape(template)
|
||||
if err != nil {
|
||||
return nil, NewTemplateLoadError(template, err)
|
||||
return nil, nil, NewTemplateLoadError(template, err)
|
||||
}
|
||||
templateBytes, err = LoadTemplate(unescape)
|
||||
if err != nil {
|
||||
logger.Logger.Debug(
|
||||
"load template failed", zap.String("template", template), zap.Error(err),
|
||||
)
|
||||
return nil, NewTemplateLoadError(unescape, err)
|
||||
return nil, nil, NewTemplateLoadError(unescape, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(templateBytes, &temp)
|
||||
templateDoc, err := ParseYAMLDocument(templateBytes)
|
||||
if err != nil {
|
||||
logger.Logger.Debug("parse template failed", zap.Error(err))
|
||||
return nil, NewTemplateParseError(templateBytes, err)
|
||||
logger.Logger.Debug("parse template yaml node failed", zap.Error(err))
|
||||
return nil, templateBytes, NewTemplateParseError(templateBytes, err)
|
||||
}
|
||||
var proxyList []P.Proxy
|
||||
|
||||
return templateDoc, templateBytes, nil
|
||||
}
|
||||
|
||||
// collectQueryProxies 汇总来自订阅链接和直接传入代理链接的所有节点。
|
||||
func collectQueryProxies(query model.ConvertConfig, cacheExpire int64, retryTimes int) ([]P.Proxy, error) {
|
||||
proxyList := make([]P.Proxy, 0)
|
||||
for i := range query.Subs {
|
||||
data, err := LoadSubscription(query.Subs[i], query.Refresh, query.UserAgent, cacheExpire, retryTimes)
|
||||
newProxies, err := loadSubscriptionProxies(query, query.Subs[i], cacheExpire, retryTimes)
|
||||
if err != nil {
|
||||
logger.Logger.Debug(
|
||||
"load subscription failed", zap.String("url", query.Subs[i]), zap.Error(err),
|
||||
)
|
||||
return nil, NewSubscriptionLoadError(query.Subs[i], err)
|
||||
}
|
||||
subName := ""
|
||||
if strings.Contains(query.Subs[i], "#") {
|
||||
subName = query.Subs[i][strings.LastIndex(query.Subs[i], "#")+1:]
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(data, &sub)
|
||||
var newProxies []P.Proxy
|
||||
if err != nil {
|
||||
reg, err := regexp.Compile("(" + strings.Join(parser.GetAllPrefixes(), "|") + ")://")
|
||||
if err != nil {
|
||||
logger.Logger.Debug("compile regex failed", zap.Error(err))
|
||||
return nil, NewRegexInvalidError("prefix", err)
|
||||
}
|
||||
if reg.Match(data) {
|
||||
p, err := parser.ParseProxies(parser.ParseConfig{UseUDP: query.UseUDP}, strings.Split(string(data), "\n")...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newProxies = p
|
||||
} else {
|
||||
base64, err := utils.DecodeBase64(string(data), false)
|
||||
if err != nil {
|
||||
logger.Logger.Debug(
|
||||
"parse subscription failed", zap.String("url", query.Subs[i]),
|
||||
zap.String("data", string(data)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, NewSubscriptionParseError(data, err)
|
||||
}
|
||||
p, err := parser.ParseProxies(parser.ParseConfig{UseUDP: query.UseUDP}, strings.Split(base64, "\n")...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newProxies = p
|
||||
}
|
||||
} else {
|
||||
newProxies = sub.Proxy
|
||||
}
|
||||
if subName != "" {
|
||||
for i := range newProxies {
|
||||
newProxies[i].SubName = subName
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
proxyList = append(proxyList, newProxies...)
|
||||
}
|
||||
@@ -195,13 +228,103 @@ func BuildSub(clashType model.ClashType, query model.ConvertConfig, template str
|
||||
proxyList = append(proxyList, p...)
|
||||
}
|
||||
|
||||
return proxyList, nil
|
||||
}
|
||||
|
||||
// loadSubscriptionProxies 负责加载单条订阅并应用订阅名作为节点前缀。
|
||||
func loadSubscriptionProxies(query model.ConvertConfig, subscriptionURL string, cacheExpire int64, retryTimes int) ([]P.Proxy, error) {
|
||||
data, err := LoadSubscription(subscriptionURL, query.Refresh, query.UserAgent, cacheExpire, retryTimes)
|
||||
if err != nil {
|
||||
logger.Logger.Debug(
|
||||
"load subscription failed", zap.String("url", subscriptionURL), zap.Error(err),
|
||||
)
|
||||
return nil, NewSubscriptionLoadError(subscriptionURL, err)
|
||||
}
|
||||
|
||||
subName := ""
|
||||
if strings.Contains(subscriptionURL, "#") {
|
||||
subName = subscriptionURL[strings.LastIndex(subscriptionURL, "#")+1:]
|
||||
}
|
||||
|
||||
newProxies, err := parseSubscriptionProxies(data, query.UseUDP, subscriptionURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if subName != "" {
|
||||
for i := range newProxies {
|
||||
newProxies[i].SubName = subName
|
||||
}
|
||||
}
|
||||
|
||||
return newProxies, nil
|
||||
}
|
||||
|
||||
// parseSubscriptionProxies 按“Clash YAML -> URI 列表 -> Base64 文本”的顺序容错解析节点。
|
||||
func parseSubscriptionProxies(data []byte, useUDP bool, subscriptionURL string) ([]P.Proxy, error) {
|
||||
sub := &proxyListDoc{}
|
||||
if err := yaml.Unmarshal(data, sub); err == nil {
|
||||
return sub.Proxy, nil
|
||||
}
|
||||
|
||||
reg, err := regexp.Compile("(" + strings.Join(parser.GetAllPrefixes(), "|") + ")://")
|
||||
if err != nil {
|
||||
logger.Logger.Debug("compile regex failed", zap.Error(err))
|
||||
return nil, NewRegexInvalidError("prefix", err)
|
||||
}
|
||||
|
||||
if reg.Match(data) {
|
||||
return parser.ParseProxies(parser.ParseConfig{UseUDP: useUDP}, strings.Split(string(data), "\n")...)
|
||||
}
|
||||
|
||||
base64, err := utils.DecodeBase64(string(data), false)
|
||||
if err != nil {
|
||||
logger.Logger.Debug(
|
||||
"parse subscription failed", zap.String("url", subscriptionURL),
|
||||
zap.String("data", string(data)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, NewSubscriptionParseError(data, err)
|
||||
}
|
||||
|
||||
return parser.ParseProxies(parser.ParseConfig{UseUDP: useUDP}, strings.Split(base64, "\n")...)
|
||||
}
|
||||
|
||||
// normalizeProxyList 汇总所有节点标准化步骤,确保后续分组和 patch 使用的是稳定结果。
|
||||
func normalizeProxyList(query model.ConvertConfig, proxyList []P.Proxy) ([]P.Proxy, error) {
|
||||
applySubscriptionPrefixes(proxyList)
|
||||
|
||||
var err error
|
||||
proxyList, err = dedupeProxies(proxyList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyList, err = removeProxiesByPattern(proxyList, query.Remove)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyList, err = replaceProxyNames(proxyList, query.Replace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ensureUniqueProxyNames(proxyList)
|
||||
trimProxyNames(proxyList)
|
||||
return proxyList, nil
|
||||
}
|
||||
|
||||
func applySubscriptionPrefixes(proxyList []P.Proxy) {
|
||||
for i := range proxyList {
|
||||
if proxyList[i].SubName != "" {
|
||||
proxyList[i].Name = strings.TrimSpace(proxyList[i].SubName) + " " + strings.TrimSpace(proxyList[i].Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 去重
|
||||
// dedupeProxies 通过 YAML 序列化结果判定两个节点是否完全相同。
|
||||
func dedupeProxies(proxyList []P.Proxy) ([]P.Proxy, error) {
|
||||
proxies := make(map[string]*P.Proxy)
|
||||
newProxies := make([]P.Proxy, 0, len(proxyList))
|
||||
for i := range proxyList {
|
||||
@@ -216,45 +339,52 @@ func BuildSub(clashType model.ClashType, query model.ConvertConfig, template str
|
||||
newProxies = append(newProxies, proxyList[i])
|
||||
}
|
||||
}
|
||||
proxyList = newProxies
|
||||
return newProxies, nil
|
||||
}
|
||||
|
||||
// 移除
|
||||
if strings.TrimSpace(query.Remove) != "" {
|
||||
newProxyList := make([]P.Proxy, 0, len(proxyList))
|
||||
func removeProxiesByPattern(proxyList []P.Proxy, pattern string) ([]P.Proxy, error) {
|
||||
if strings.TrimSpace(pattern) == "" {
|
||||
return proxyList, nil
|
||||
}
|
||||
|
||||
removeReg, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
logger.Logger.Debug("remove regexp compile failed", zap.Error(err))
|
||||
return nil, NewRegexInvalidError("remove", err)
|
||||
}
|
||||
|
||||
newProxyList := make([]P.Proxy, 0, len(proxyList))
|
||||
for i := range proxyList {
|
||||
if removeReg.MatchString(proxyList[i].Name) {
|
||||
continue
|
||||
}
|
||||
newProxyList = append(newProxyList, proxyList[i])
|
||||
}
|
||||
return newProxyList, nil
|
||||
}
|
||||
|
||||
func replaceProxyNames(proxyList []P.Proxy, replacements map[string]string) ([]P.Proxy, error) {
|
||||
if len(replacements) == 0 {
|
||||
return proxyList, nil
|
||||
}
|
||||
|
||||
for pattern, replacement := range replacements {
|
||||
replaceReg, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
logger.Logger.Debug("replace regexp compile failed", zap.Error(err))
|
||||
return nil, NewRegexInvalidError("replace", err)
|
||||
}
|
||||
for i := range proxyList {
|
||||
removeReg, err := regexp.Compile(query.Remove)
|
||||
if err != nil {
|
||||
logger.Logger.Debug("remove regexp compile failed", zap.Error(err))
|
||||
return nil, NewRegexInvalidError("remove", err)
|
||||
}
|
||||
|
||||
if removeReg.MatchString(proxyList[i].Name) {
|
||||
continue
|
||||
}
|
||||
newProxyList = append(newProxyList, proxyList[i])
|
||||
}
|
||||
proxyList = newProxyList
|
||||
}
|
||||
|
||||
// 替换
|
||||
if len(query.Replace) != 0 {
|
||||
for k, v := range query.Replace {
|
||||
replaceReg, err := regexp.Compile(k)
|
||||
if err != nil {
|
||||
logger.Logger.Debug("replace regexp compile failed", zap.Error(err))
|
||||
return nil, NewRegexInvalidError("replace", err)
|
||||
}
|
||||
for i := range proxyList {
|
||||
if replaceReg.MatchString(proxyList[i].Name) {
|
||||
proxyList[i].Name = replaceReg.ReplaceAllString(
|
||||
proxyList[i].Name, v,
|
||||
)
|
||||
}
|
||||
if replaceReg.MatchString(proxyList[i].Name) {
|
||||
proxyList[i].Name = replaceReg.ReplaceAllString(proxyList[i].Name, replacement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重命名有相同名称的节点
|
||||
return proxyList, nil
|
||||
}
|
||||
|
||||
func ensureUniqueProxyNames(proxyList []P.Proxy) {
|
||||
names := make(map[string]int)
|
||||
for i := range proxyList {
|
||||
if _, exist := names[proxyList[i].Name]; exist {
|
||||
@@ -264,30 +394,39 @@ func BuildSub(clashType model.ClashType, query model.ConvertConfig, template str
|
||||
names[proxyList[i].Name] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func trimProxyNames(proxyList []P.Proxy) {
|
||||
for i := range proxyList {
|
||||
proxyList[i].Name = strings.TrimSpace(proxyList[i].Name)
|
||||
}
|
||||
}
|
||||
|
||||
var t = &model.Subscription{}
|
||||
AddProxy(t, query.AutoTest, query.Lazy, clashType, proxyList...)
|
||||
// buildGeneratedConfig 只生成“新增内容”,例如国家组和最终可输出的节点集合。
|
||||
func buildGeneratedConfig(clashType model.ClashType, query model.ConvertConfig, proxyList []P.Proxy) (*generatedConfig, error) {
|
||||
generated := &generatedConfig{}
|
||||
AddProxy(generated, query.AutoTest, query.Lazy, clashType, proxyList...)
|
||||
sortGeneratedGroups(generated, query.Sort)
|
||||
return generated, nil
|
||||
}
|
||||
|
||||
// 排序
|
||||
switch query.Sort {
|
||||
func sortGeneratedGroups(generated *generatedConfig, sortMode string) {
|
||||
switch sortMode {
|
||||
case "sizeasc":
|
||||
sort.Sort(model.ProxyGroupsSortBySize(t.ProxyGroup))
|
||||
sort.Sort(generatedGroupsSortBySize(generated.ProxyGroup))
|
||||
case "sizedesc":
|
||||
sort.Sort(sort.Reverse(model.ProxyGroupsSortBySize(t.ProxyGroup)))
|
||||
sort.Sort(sort.Reverse(generatedGroupsSortBySize(generated.ProxyGroup)))
|
||||
case "nameasc":
|
||||
sort.Sort(model.ProxyGroupsSortByName(t.ProxyGroup))
|
||||
sort.Sort(generatedGroupsSortByName(generated.ProxyGroup))
|
||||
case "namedesc":
|
||||
sort.Sort(sort.Reverse(model.ProxyGroupsSortByName(t.ProxyGroup)))
|
||||
sort.Sort(sort.Reverse(generatedGroupsSortByName(generated.ProxyGroup)))
|
||||
default:
|
||||
sort.Sort(model.ProxyGroupsSortByName(t.ProxyGroup))
|
||||
sort.Sort(generatedGroupsSortByName(generated.ProxyGroup))
|
||||
}
|
||||
}
|
||||
|
||||
MergeSubAndTemplate(temp, t, query.IgnoreCountryGrooup)
|
||||
|
||||
// applyRulePatches 只修改运行期 overlay 中的 rules 切片,不直接写 YAML。
|
||||
func applyRulePatches(temp *generatedConfig, query model.ConvertConfig) {
|
||||
for _, v := range query.Rules {
|
||||
if v.Prepend {
|
||||
PrependRules(temp, v.Rule)
|
||||
@@ -295,28 +434,176 @@ func BuildSub(clashType model.ClashType, query model.ConvertConfig, template str
|
||||
AppendRules(temp, v.Rule)
|
||||
}
|
||||
}
|
||||
for _, v := range query.RuleProviders {
|
||||
if v.Prepend {
|
||||
PrependRuleProvider(temp, v.Name, v.Group)
|
||||
} else {
|
||||
AppenddRuleProvider(temp, v.Name, v.Group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildRuleProviderPatches 把 API 请求中的 rule-provider 参数转换成 YAML patch payload。
|
||||
func buildRuleProviderPatches(query model.ConvertConfig) map[string]generatedRulePatch {
|
||||
if len(query.RuleProviders) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
patches := make(map[string]generatedRulePatch, len(query.RuleProviders))
|
||||
for _, v := range query.RuleProviders {
|
||||
hash := sha256.Sum224([]byte(v.Url))
|
||||
name := hex.EncodeToString(hash[:])
|
||||
provider := model.RuleProvider{
|
||||
patches[v.Name] = generatedRulePatch{
|
||||
Type: "http",
|
||||
Behavior: v.Behavior,
|
||||
Url: v.Url,
|
||||
Path: "./" + name + ".yaml",
|
||||
Interval: 3600,
|
||||
}
|
||||
if v.Prepend {
|
||||
PrependRuleProvider(
|
||||
temp, v.Name, v.Group, provider,
|
||||
)
|
||||
} else {
|
||||
AppenddRuleProvider(
|
||||
temp, v.Name, v.Group, provider,
|
||||
)
|
||||
}
|
||||
return patches
|
||||
}
|
||||
|
||||
// extractTemplateOverlay 只从模板 YAML 树中提取本项目真正会参与计算的局部字段。
|
||||
// 这让模板读取完全基于 yaml.Node,而不再依赖任何整份配置的 typed unmarshal。
|
||||
func extractTemplateOverlay(templateDoc *yaml.Node) (*generatedConfig, error) {
|
||||
overlay := &generatedConfig{}
|
||||
|
||||
if err := decodeOptionalYAMLPath(templateDoc, "proxy-groups", &overlay.ProxyGroup); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := decodeOptionalYAMLPath(templateDoc, "rules", &overlay.Rule); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return overlay, nil
|
||||
}
|
||||
|
||||
// decodeOptionalYAMLPath 在路径存在且非 null 时才执行 Decode,
|
||||
// 路径不存在时保持目标值为零值。
|
||||
func decodeOptionalYAMLPath(doc *yaml.Node, path string, target any) error {
|
||||
node, err := GetYAMLPath(doc, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if node == nil || isNullYAMLNode(node) {
|
||||
return nil
|
||||
}
|
||||
if err := node.Decode(target); err != nil {
|
||||
return fmt.Errorf("decode template path %q failed: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeTemplateProxies 只负责把本项目生成出的代理追加到模板现有 proxies 后面。
|
||||
// 模板中已有代理节点原样保留,不做 struct round-trip。
|
||||
func mergeTemplateProxies(templateDoc *yaml.Node, generated []P.Proxy) error {
|
||||
if len(generated) == 0 && !HasYAMLPath(templateDoc, "proxies") {
|
||||
return nil
|
||||
}
|
||||
|
||||
proxiesNode, err := EnsureYAMLSequencePath(templateDoc, "proxies")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, proxy := range generated {
|
||||
if err := AppendYAMLSequenceValue(proxiesNode, proxy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return temp, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeTemplateProxyGroups 负责两类更新:
|
||||
// 1. 对模板中同名组,仅覆盖 proxies 字段,保留其他字段
|
||||
// 2. 追加本项目新生成的国家组
|
||||
func mergeTemplateProxyGroups(templateDoc *yaml.Node, groups []generatedGroup) error {
|
||||
if len(groups) == 0 && !HasYAMLPath(templateDoc, "proxy-groups") {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupNodes, err := EnsureYAMLSequencePath(templateDoc, "proxy-groups")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if group.IsCountry {
|
||||
if existing := FindYAMLSequenceMappingByStringField(groupNodes, "name", group.Name); existing != nil {
|
||||
continue
|
||||
}
|
||||
if err := AppendYAMLSequenceValue(groupNodes, group); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
existing := FindYAMLSequenceMappingByStringField(groupNodes, "name", group.Name)
|
||||
if existing == nil {
|
||||
if err := AppendYAMLSequenceValue(groupNodes, group); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if findMappingValue(existing, "proxies") == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := SetYAMLMappingField(existing, "proxies", group.Proxies); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeTemplateRuleProviders 以字段级 patch 的方式更新/插入 rule-provider,
|
||||
// 以避免覆盖模板中已有 provider 的未知字段。
|
||||
func mergeTemplateRuleProviders(templateDoc *yaml.Node, providers map[string]generatedRulePatch) error {
|
||||
if len(providers) == 0 && !HasYAMLPath(templateDoc, "rule-providers") {
|
||||
return nil
|
||||
}
|
||||
|
||||
providerNodes, err := EnsureYAMLMappingPath(templateDoc, "rule-providers")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for name, provider := range providers {
|
||||
existing := findMappingValue(providerNodes, name)
|
||||
if existing != nil && existing.Kind == yaml.MappingNode {
|
||||
if err := SetYAMLMappingField(existing, "type", provider.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SetYAMLMappingField(existing, "behavior", provider.Behavior); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SetYAMLMappingField(existing, "url", provider.Url); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SetYAMLMappingField(existing, "path", provider.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SetYAMLMappingField(existing, "interval", provider.Interval); err != nil {
|
||||
return err
|
||||
}
|
||||
if provider.Format != "" {
|
||||
if err := SetYAMLMappingField(existing, "format", provider.Format); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := SetYAMLMappingField(providerNodes, name, provider); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func FetchSubscriptionUserInfo(url string, userAgent string, retryTimes int) (string, error) {
|
||||
@@ -336,10 +623,12 @@ func FetchSubscriptionUserInfo(url string, userAgent string, retryTimes int) (st
|
||||
return "", NewNetworkResponseError("subscription-userinfo header not found", nil)
|
||||
}
|
||||
|
||||
func MergeSubAndTemplate(temp *model.Subscription, sub *model.Subscription, igcg bool) {
|
||||
// MergeSubAndTemplate 把“模板侧需要参与计算的最小叠加层”和“本项目生成结果”合并。
|
||||
// 它只处理本项目关心的运行期结构,不负责最终 YAML 输出。
|
||||
func MergeSubAndTemplate(temp *generatedConfig, sub *generatedConfig, igcg bool) {
|
||||
var countryGroupNames []string
|
||||
for _, proxyGroup := range sub.ProxyGroup {
|
||||
if proxyGroup.IsCountryGrop {
|
||||
if proxyGroup.IsCountry {
|
||||
countryGroupNames = append(
|
||||
countryGroupNames, proxyGroup.Name,
|
||||
)
|
||||
@@ -350,16 +639,14 @@ func MergeSubAndTemplate(temp *model.Subscription, sub *model.Subscription, igcg
|
||||
proxyNames = append(proxyNames, proxy.Name)
|
||||
}
|
||||
|
||||
temp.Proxy = append(temp.Proxy, sub.Proxy...)
|
||||
|
||||
for i := range temp.ProxyGroup {
|
||||
if temp.ProxyGroup[i].IsCountryGrop {
|
||||
if temp.ProxyGroup[i].IsCountry {
|
||||
continue
|
||||
}
|
||||
newProxies := make([]string, 0)
|
||||
countryGroupMap := make(map[string]model.ProxyGroup)
|
||||
countryGroupMap := make(map[string]generatedGroup)
|
||||
for _, v := range sub.ProxyGroup {
|
||||
if v.IsCountryGrop {
|
||||
if v.IsCountry {
|
||||
countryGroupMap[v.Name] = v
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user