-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathyaml.go
More file actions
356 lines (321 loc) · 9.64 KB
/
Copy pathyaml.go
File metadata and controls
356 lines (321 loc) · 9.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
package util
import (
"fmt"
"strings"
"gopkg.in/yaml.v3"
)
// MihomoConfig 表示完整的 mihomo 配置文件结构(使用 map 保持灵活性)
type MihomoConfig map[string]interface{}
// RuleProviderInput BuildMihomoConfig 接收的规则集描述(来自 model.RuleProvider)
type RuleProviderInput struct {
Name string // 在 rule-providers 中的键名,RULE-SET 规则引用此名
Type string // http | file
URL string
Behavior string // domain | ipcidr | classical
Format string // yaml | text | mrs
Interval int
}
// ParseProxiesFromContent 从订阅内容中解析 proxies 列表
func ParseProxiesFromContent(content string) ([]interface{}, error) {
if content == "" {
return nil, nil
}
var raw map[string]interface{}
if err := yaml.Unmarshal([]byte(content), &raw); err != nil {
return nil, fmt.Errorf("解析 YAML 失败: %w", err)
}
proxiesRaw, ok := raw["proxies"]
if !ok {
return nil, nil
}
proxies, ok := proxiesRaw.([]interface{})
if !ok {
return nil, nil
}
return proxies, nil
}
// PrefixProxies 为代理节点名称添加供应商前缀
func PrefixProxies(proxies []interface{}, providerName string) []interface{} {
prefix := fmt.Sprintf("[%s] ", providerName)
result := make([]interface{}, 0, len(proxies))
for _, p := range proxies {
pm, ok := p.(map[string]interface{})
if !ok {
result = append(result, p)
continue
}
copied := make(map[string]interface{}, len(pm))
for k, v := range pm {
copied[k] = v
}
if name, ok := copied["name"].(string); ok {
copied["name"] = prefix + name
}
result = append(result, copied)
}
return result
}
// ExpandCustomProxies 展开自定义代理节点列表
// type="custom" 的节点从 __raw__ 字段解析 YAML,其余直接使用
func ExpandCustomProxies(proxies []map[string]interface{}) []interface{} {
result := make([]interface{}, 0, len(proxies))
for _, p := range proxies {
typ, _ := p["type"].(string)
if strings.ToLower(typ) == "custom" {
raw, _ := p["__raw__"].(string)
if raw = strings.TrimSpace(raw); raw != "" {
var parsed map[string]interface{}
if err := yaml.Unmarshal([]byte(raw), &parsed); err == nil && parsed != nil {
result = append(result, parsed)
continue
}
}
// 解析失败则跳过该节点
continue
}
// 普通节点:去除 __raw__ 字段后直接使用
clean := make(map[string]interface{}, len(p))
for k, v := range p {
if k != "__raw__" {
clean[k] = v
}
}
result = append(result, clean)
}
return result
}
// BuildMihomoConfig 构建完整的 mihomo 配置
//
// configTemplateContent: ConfigTemplate.Content(YAML 文本,顶层字段)
// providerProxies: 来自订阅源的代理节点(已处理前缀)
// customProxies: CustomConfig.Proxies(结构化,含 custom 类型)
// customGroups: CustomConfig.ProxyGroups(结构化)
// customRules: CustomConfig.Rules(字符串列表)
// ruleInsertMode: prepend | append | replace(相对于模板内的 rules)
// ruleProviders: 需注入 rule-providers 的规则集列表
// providerNodeNames: 订阅源名称 → 该源的节点名列表(用于展开 use: 字段)
func BuildMihomoConfig(
configTemplateContent string,
providerProxies []interface{},
customProxies []map[string]interface{},
customGroups []map[string]interface{},
customRules []string,
ruleInsertMode string,
ruleProviders []RuleProviderInput,
providerNodeNames map[string][]string,
) ([]byte, error) {
// 从 ConfigTemplate YAML 构建初始配置 map
cfg := make(MihomoConfig)
if configTemplateContent != "" {
var tmplMap map[string]interface{}
if err := yaml.Unmarshal([]byte(configTemplateContent), &tmplMap); err == nil && tmplMap != nil {
for k, v := range tmplMap {
cfg[k] = v
}
}
}
// 设置默认值
setDefault(cfg, "mixed-port", 7890)
setDefault(cfg, "allow-lan", false)
setDefault(cfg, "mode", "rule")
setDefault(cfg, "log-level", "info")
// 合并所有代理节点:provider 节点 + 自定义节点
expandedCustom := ExpandCustomProxies(customProxies)
allProxies := make([]interface{}, 0, len(providerProxies)+len(expandedCustom))
allProxies = append(allProxies, providerProxies...)
allProxies = append(allProxies, expandedCustom...)
cfg["proxies"] = allProxies
// 写入 proxy-groups,并将 use: [providerName] 展开为具体节点名
if len(customGroups) > 0 {
groups := make([]interface{}, len(customGroups))
for i, g := range customGroups {
groups[i] = expandGroupUse(g, providerNodeNames)
}
cfg["proxy-groups"] = groups
}
// 提取模板中已有的 rules(在插入模式下作为"base rules")
var baseRules []string
for _, key := range []string{"rules", "rule"} {
if raw, ok := cfg[key]; ok {
baseRules = rulesFromConfigValue(raw)
delete(cfg, key)
if len(baseRules) > 0 {
break
}
}
}
// 构建 rule-providers map
rpMap := buildRuleProvidersMap(ruleProviders)
// 合并模板中已有的 rule-providers(用户覆盖优先)
if raw, ok := cfg["rule-providers"]; ok {
if existing, ok := raw.(map[string]interface{}); ok {
for k, v := range existing {
rpMap[k] = v
}
}
delete(cfg, "rule-providers")
}
if len(rpMap) > 0 {
cfg["rule-providers"] = rpMap
}
// 规则合并:baseRules → customRules(按 ruleInsertMode)
finalRules := mergeRules(baseRules, customRules, ruleInsertMode)
if len(finalRules) > 0 {
cfg["rules"] = finalRules
}
return yaml.Marshal(cfg)
}
// expandGroupUse 将代理组中的 use:[providerName,...] 展开为具体节点名追加到 proxies 中
// 展开后从输出 map 里移除 use 字段,避免 Mihomo 找不到对应的 proxy-provider 报错
func expandGroupUse(g map[string]interface{}, providerNodeNames map[string][]string) map[string]interface{} {
useRaw, hasUse := g["use"]
if !hasUse || providerNodeNames == nil {
return g
}
// 取出已有的 proxies 列表
existing := toStringSlice(g["proxies"])
// 按 use 中的每个 provider 名称展开节点
switch u := useRaw.(type) {
case []interface{}:
for _, item := range u {
name, _ := item.(string)
if nodes, ok := providerNodeNames[name]; ok {
existing = append(existing, nodes...)
}
}
case []string:
for _, name := range u {
if nodes, ok := providerNodeNames[name]; ok {
existing = append(existing, nodes...)
}
}
}
// 构造不含 use 字段的新 map
out := make(map[string]interface{}, len(g))
for k, v := range g {
if k != "use" {
out[k] = v
}
}
if len(existing) > 0 {
out["proxies"] = existing
}
return out
}
// toStringSlice 将 interface{} 类型的切片转为 []string
func toStringSlice(v interface{}) []string {
if v == nil {
return nil
}
switch s := v.(type) {
case []string:
return s
case []interface{}:
out := make([]string, 0, len(s))
for _, item := range s {
if str, ok := item.(string); ok {
out = append(out, str)
}
}
return out
}
return nil
}
// buildRuleProvidersMap 将 RuleProviderInput 列表转为 rule-providers map
func buildRuleProvidersMap(providers []RuleProviderInput) map[string]interface{} {
if len(providers) == 0 {
return make(map[string]interface{})
}
m := make(map[string]interface{}, len(providers))
for _, rp := range providers {
entry := map[string]interface{}{
"type": rp.Type,
"behavior": rp.Behavior,
"interval": rp.Interval,
"path": fmt.Sprintf("./ruleset/%s.yaml", rp.Name),
}
if rp.URL != "" {
entry["url"] = rp.URL
}
if rp.Format != "" && rp.Format != "yaml" {
entry["format"] = rp.Format
}
m[rp.Name] = entry
}
return m
}
// rulesFromConfigValue 将 config map 中取出的 rules/rule 值转为字符串列表
func rulesFromConfigValue(v interface{}) []string {
if v == nil {
return nil
}
switch x := v.(type) {
case []string:
return filterEmptyStrings(x)
case []interface{}:
out := make([]string, 0, len(x))
for _, it := range x {
if s, ok := it.(string); ok {
out = append(out, s)
}
}
return filterEmptyStrings(out)
default:
return nil
}
}
// mergeRules 根据插入模式合并规则
func mergeRules(baseRules, customRules []string, mode string) []string {
switch mode {
case "replace":
return customRules
case "append":
combined := make([]string, 0, len(baseRules)+len(customRules))
combined = append(combined, baseRules...)
combined = append(combined, customRules...)
return combined
default: // prepend
combined := make([]string, 0, len(customRules)+len(baseRules))
combined = append(combined, customRules...)
combined = append(combined, baseRules...)
return combined
}
}
// ParseYAMLList 将 YAML 格式的数组文本解析为 []interface{}(兼容旧逻辑,保留备用)
func ParseYAMLList(yamlText string) ([]interface{}, error) {
if strings.TrimSpace(yamlText) == "" {
return nil, nil
}
var list []interface{}
if err := yaml.Unmarshal([]byte(yamlText), &list); err != nil {
return nil, err
}
return list, nil
}
// ParseRulesList 将 YAML 或换行分隔的规则文本解析为字符串列表(兼容旧逻辑,保留备用)
func ParseRulesList(rulesText string) []string {
if strings.TrimSpace(rulesText) == "" {
return nil
}
var list []string
if err := yaml.Unmarshal([]byte(rulesText), &list); err == nil && len(list) > 0 {
return filterEmptyStrings(list)
}
lines := strings.Split(rulesText, "\n")
return filterEmptyStrings(lines)
}
func filterEmptyStrings(s []string) []string {
result := make([]string, 0, len(s))
for _, v := range s {
v = strings.TrimSpace(v)
if v != "" && !strings.HasPrefix(v, "#") {
result = append(result, v)
}
}
return result
}
func setDefault(m map[string]interface{}, key string, value interface{}) {
if _, exists := m[key]; !exists {
m[key] = value
}
}