package template // This file defines the 6 built-in routing templates. // Per review-resolution #41: DefaultProxyGroups returns 3 groups when no template configured. const testURL = "https://www.gstatic.com/generate_204" // BuiltinTemplateIDs is the set of IDs that cannot be modified or deleted. var BuiltinTemplateIDs = map[string]bool{ "mihomo-basic": true, "acl4ssr-mihomo": true, "acl4ssr-mihomo-no-emoji": true, "loyalsoldier-whitelist": true, "loyalsoldier-blacklist": true, "ai-streaming-mihomo": true, } const DefaultTemplateID = "acl4ssr-mihomo" // BuiltinTemplates returns all 6 built-in templates as config maps. var BuiltinTemplates = []BuiltinTemplate{ {ID: "mihomo-basic", Name: "Mihomo Basic", Target: "mihomo", Config: mihomoBasicConfig()}, {ID: "acl4ssr-mihomo", Name: "ACL4SSR Mihomo", Target: "mihomo", Config: acl4ssrConfig()}, {ID: "acl4ssr-mihomo-no-emoji", Name: "ACL4SSR Mihomo 无 Emoji", Target: "mihomo", Config: acl4ssrNoEmojiConfig()}, {ID: "loyalsoldier-whitelist", Name: "Loyalsoldier 白名单", Target: "mihomo", Config: loyalsoldierWhitelistConfig()}, {ID: "loyalsoldier-blacklist", Name: "Loyalsoldier 黑名单", Target: "mihomo", Config: loyalsoldierBlacklistConfig()}, {ID: "ai-streaming-mihomo", Name: "AI + Streaming", Target: "mihomo", Config: aiStreamingConfig()}, } type BuiltinTemplate struct { ID string Name string Target string Config map[string]any } // DefaultProxyGroups returns 3 default groups per review-resolution #41. func DefaultProxyGroups() []map[string]any { return []map[string]any{ {"name": "🚀 节点选择", "type": "select", "proxies": []any{"♻️ 自动选择", "🚀 手动切换", "DIRECT"}}, {"name": "♻️ 自动选择", "type": "url-test", "proxies": []any{"$all"}, "url": testURL, "interval": 300, "tolerance": 50}, {"name": "🚀 手动切换", "type": "select", "proxies": []any{"$all"}}, } } // NormalizeMihomoTemplateConfig copies kebab-case aliases to camelCase keys. func NormalizeMihomoTemplateConfig(input map[string]any) map[string]any { output := make(map[string]any, len(input)) for k, v := range input { output[k] = v } copyAlias(output, "mixed-port", "mixedPort") copyAlias(output, "allow-lan", "allowLan") copyAlias(output, "log-level", "logLevel") copyAlias(output, "proxy-groups", "proxyGroups") copyAlias(output, "rule-providers", "ruleProviders") return output } func copyAlias(input map[string]any, from, to string) { if input[to] == nil && input[from] != nil { input[to] = input[from] } delete(input, from) } func provider(url, behavior string) map[string]any { if behavior == "" { behavior = "classical" } return map[string]any{ "type": "http", "behavior": behavior, "url": url, "path": "./ruleset/" + lastPathSegment(url), "interval": 86400, } } func lastPathSegment(url string) string { for i := len(url) - 1; i >= 0; i-- { if url[i] == '/' { return url[i+1:] } } return "ruleset" } func acl4ssrRaw(name string) string { return "https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/" + name + ".list" } func loyalSoldier(name string) string { return "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/" + name + ".txt" } func blackmatrix(name string) string { return "https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Clash/" + name + "/" + name + ".yaml" } func baseGroups() []map[string]any { return []map[string]any{ {"name": "🚀 节点选择", "type": "select", "proxies": []any{"♻️ 自动选择", "🚀 手动切换", "DIRECT"}}, {"name": "♻️ 自动选择", "type": "url-test", "proxies": []any{"$all"}, "url": testURL, "interval": 300, "tolerance": 50}, {"name": "🚀 手动切换", "type": "select", "proxies": []any{"$all"}}, {"name": "🌏 国外媒体", "type": "select", "proxies": []any{"🚀 节点选择", "♻️ 自动选择", "🚀 手动切换", "DIRECT"}}, {"name": "💬 AI 服务", "type": "select", "proxies": []any{"🚀 节点选择", "♻️ 自动选择", "🚀 手动切换", "DIRECT"}}, {"name": "Ⓜ️ 微软服务", "type": "select", "proxies": []any{"DIRECT", "🚀 节点选择", "♻️ 自动选择"}}, {"name": "🍎 苹果服务", "type": "select", "proxies": []any{"DIRECT", "🚀 节点选择", "♻️ 自动选择"}}, {"name": "🎯 全球直连", "type": "select", "proxies": []any{"DIRECT", "🚀 节点选择"}}, {"name": "🛑 全球拦截", "type": "select", "proxies": []any{"REJECT", "DIRECT"}}, {"name": "🐟 漏网之鱼", "type": "select", "proxies": []any{"🚀 节点选择", "DIRECT"}}, } } func defaultDNS() map[string]any { return map[string]any{ "enable": true, "ipv6": false, "enhanced-mode": "fake-ip", "nameserver": []any{"https://doh.pub/dns-query", "https://dns.alidns.com/dns-query"}, } } func mihomoBase() map[string]any { return map[string]any{ "mixedPort": 7890, "allowLan": false, "mode": "rule", "logLevel": "info", "dns": defaultDNS(), "proxyGroups": baseGroups(), } } func mihomoBasicConfig() map[string]any { c := mihomoBase() c["ruleProviders"] = map[string]any{} c["rules"] = []any{ "DOMAIN-SUFFIX,openai.com,💬 AI 服务", "DOMAIN-SUFFIX,chatgpt.com,💬 AI 服务", "DOMAIN-SUFFIX,anthropic.com,💬 AI 服务", "DOMAIN-SUFFIX,claude.ai,💬 AI 服务", "DOMAIN-SUFFIX,netflix.com,🌏 国外媒体", "DOMAIN-SUFFIX,youtube.com,🌏 国外媒体", "DOMAIN-SUFFIX,googlevideo.com,🌏 国外媒体", "GEOIP,CN,🎯 全球直连", "MATCH,🐟 漏网之鱼", } return c } func acl4ssrConfig() map[string]any { c := mihomoBase() c["ruleProviders"] = map[string]any{ "LocalAreaNetwork": provider(acl4ssrRaw("LocalAreaNetwork"), ""), "UnBan": provider(acl4ssrRaw("UnBan"), ""), "BanAD": provider(acl4ssrRaw("BanAD"), ""), "BanProgramAD": provider(acl4ssrRaw("BanProgramAD"), ""), "GoogleCN": provider(acl4ssrRaw("GoogleCN"), ""), "SteamCN": provider(acl4ssrRaw("SteamCN"), ""), "Microsoft": provider(acl4ssrRaw("Microsoft"), ""), "Apple": provider(acl4ssrRaw("Apple"), ""), "Telegram": provider(acl4ssrRaw("Telegram"), ""), "OpenAI": provider(blackmatrix("OpenAI"), ""), "YouTube": provider(acl4ssrRaw("Ruleset/YouTube"), ""), "Netflix": provider(acl4ssrRaw("Ruleset/Netflix"), ""), "DisneyPlus": provider(acl4ssrRaw("Ruleset/DisneyPlus"), ""), "ProxyGFWlist": provider(acl4ssrRaw("ProxyGFWlist"), ""), "ChinaDomain": provider(acl4ssrRaw("ChinaDomain"), ""), "ChinaCompanyIp": provider(acl4ssrRaw("ChinaCompanyIp"), ""), "Download": provider(acl4ssrRaw("Download"), ""), } c["rules"] = []any{ "RULE-SET,LocalAreaNetwork,DIRECT", "RULE-SET,UnBan,DIRECT", "RULE-SET,BanAD,🛑 全球拦截", "RULE-SET,BanProgramAD,🛑 全球拦截", "RULE-SET,GoogleCN,DIRECT", "RULE-SET,SteamCN,DIRECT", "RULE-SET,Microsoft,Ⓜ️ 微软服务", "RULE-SET,Apple,🍎 苹果服务", "RULE-SET,Telegram,🚀 节点选择", "RULE-SET,OpenAI,💬 AI 服务", "RULE-SET,YouTube,🌏 国外媒体", "RULE-SET,Netflix,🌏 国外媒体", "RULE-SET,DisneyPlus,🌏 国外媒体", "RULE-SET,ProxyGFWlist,🚀 节点选择", "RULE-SET,ChinaDomain,DIRECT", "RULE-SET,ChinaCompanyIp,DIRECT", "RULE-SET,Download,DIRECT", "GEOIP,CN,🎯 全球直连", "MATCH,🐟 漏网之鱼", } return c } func loyalsoldierProviders() map[string]any { return map[string]any{ "reject": provider(loyalSoldier("reject"), "domain"), "icloud": provider(loyalSoldier("icloud"), "domain"), "apple": provider(loyalSoldier("apple"), "domain"), "google": provider(loyalSoldier("google"), "domain"), "proxy": provider(loyalSoldier("proxy"), "domain"), "direct": provider(loyalSoldier("direct"), "domain"), "private": provider(loyalSoldier("private"), "domain"), "gfw": provider(loyalSoldier("gfw"), "domain"), "greatfire": provider(loyalSoldier("greatfire"), "domain"), "tld-not-cn": provider(loyalSoldier("tld-not-cn"), "domain"), "telegramcidr": provider(loyalSoldier("telegramcidr"), "ipcidr"), "cncidr": provider(loyalSoldier("cncidr"), "ipcidr"), "lancidr": provider(loyalSoldier("lancidr"), "ipcidr"), "applications": provider(loyalSoldier("applications"), "classical"), } } func loyalsoldierWhitelistConfig() map[string]any { c := mihomoBase() c["ruleProviders"] = loyalsoldierProviders() c["rules"] = []any{ "RULE-SET,reject,🛑 全球拦截", "RULE-SET,icloud,DIRECT", "RULE-SET,apple,DIRECT", "RULE-SET,google,🚀 节点选择", "RULE-SET,proxy,🚀 节点选择", "RULE-SET,direct,DIRECT", "RULE-SET,private,DIRECT", "RULE-SET,gfw,🚀 节点选择", "RULE-SET,greatfire,🚀 节点选择", "RULE-SET,tld-not-cn,🚀 节点选择", "RULE-SET,telegramcidr,🚀 节点选择", "RULE-SET,cncidr,DIRECT", "RULE-SET,lancidr,DIRECT", "RULE-SET,applications,DIRECT", "GEOIP,CN,DIRECT", "MATCH,🚀 节点选择", } return c } func loyalsoldierBlacklistConfig() map[string]any { c := mihomoBase() c["ruleProviders"] = loyalsoldierProviders() c["rules"] = []any{ "RULE-SET,reject,🛑 全球拦截", "RULE-SET,private,DIRECT", "RULE-SET,lancidr,DIRECT", "RULE-SET,cncidr,DIRECT", "RULE-SET,direct,DIRECT", "RULE-SET,applications,DIRECT", "RULE-SET,icloud,DIRECT", "RULE-SET,apple,DIRECT", "RULE-SET,google,🚀 节点选择", "RULE-SET,proxy,🚀 节点选择", "RULE-SET,gfw,🚀 节点选择", "RULE-SET,greatfire,🚀 节点选择", "RULE-SET,tld-not-cn,🚀 节点选择", "RULE-SET,telegramcidr,🚀 节点选择", "GEOIP,CN,DIRECT", "MATCH,DIRECT", } return c } func aiStreamingConfig() map[string]any { c := mihomoBase() c["ruleProviders"] = map[string]any{ "OpenAI": provider(blackmatrix("OpenAI"), ""), "Claude": provider(blackmatrix("Claude"), ""), "Gemini": provider(blackmatrix("Gemini"), ""), "YouTube": provider(blackmatrix("YouTube"), ""), "Netflix": provider(blackmatrix("Netflix"), ""), "Disney": provider(blackmatrix("Disney"), ""), "Spotify": provider(blackmatrix("Spotify"), ""), "Telegram": provider(blackmatrix("Telegram"), ""), "GitHub": provider(blackmatrix("GitHub"), ""), "China": provider(blackmatrix("China"), ""), } c["rules"] = []any{ "RULE-SET,OpenAI,💬 AI 服务", "RULE-SET,Claude,💬 AI 服务", "RULE-SET,Gemini,💬 AI 服务", "RULE-SET,YouTube,🌏 国外媒体", "RULE-SET,Netflix,🌏 国外媒体", "RULE-SET,Disney,🌏 国外媒体", "RULE-SET,Spotify,🌏 国外媒体", "RULE-SET,Telegram,🚀 节点选择", "RULE-SET,GitHub,🚀 节点选择", "RULE-SET,China,DIRECT", "GEOIP,CN,🎯 全球直连", "MATCH,🐟 漏网之鱼", } return c } // acl4ssrNoEmojiConfig is the ACL4SSR config with emoji labels stripped. func acl4ssrNoEmojiConfig() map[string]any { c := acl4ssrConfig() if stripped, ok := stripEmoji(c).(map[string]any); ok { return stripped } return c } var emojiLabelMap = map[string]string{ "🚀 节点选择": "节点选择", "♻️ 自动选择": "自动选择", "🚀 手动切换": "手动切换", "🌏 国外媒体": "国外媒体", "💬 AI 服务": "AI 服务", "Ⓜ️ 微软服务": "微软服务", "🍎 苹果服务": "苹果服务", "🎯 全球直连": "全球直连", "🛑 全球拦截": "全球拦截", "🐟 漏网之鱼": "漏网之鱼", } func stripEmoji(v any) any { switch val := v.(type) { case string: result := val for from, to := range emojiLabelMap { result = replaceAll(result, from, to) } return result case map[string]any: result := make(map[string]any, len(val)) for k, vv := range val { result[k] = stripEmoji(vv) } return result case []any: result := make([]any, len(val)) for i, vv := range val { result[i] = stripEmoji(vv) } return result case []map[string]any: result := make([]map[string]any, len(val)) for i, vv := range val { result[i] = stripEmoji(vv).(map[string]any) } return result default: return v } } func replaceAll(s, old, new string) string { result := "" for { idx := indexOf(s, old) if idx < 0 { result += s break } result += s[:idx] + new s = s[idx+len(old):] } return result } func indexOf(s, sub string) int { for i := 0; i <= len(s)-len(sub); i++ { if s[i:i+len(sub)] == sub { return i } } return -1 }