1use crate::types::{ClashConfig, ProxyGroup, ProxyNode, RuleMode};
2
3const BUILTIN_RULES: &[&str] = &[
4 "DOMAIN-SUFFIX,ad.com,π εΉΏεζ¦ζͺ",
5 "DOMAIN-SUFFIX,doubleclick.net,π εΉΏεζ¦ζͺ",
6 "DOMAIN-SUFFIX,google.com,π ε½ε€η½η«",
7 "DOMAIN-SUFFIX,youtube.com,π ε½ε€η½η«",
8 "DOMAIN-SUFFIX,github.com,π ε½ε€η½η«",
9 "DOMAIN-SUFFIX,twitter.com,π ε½ε€η½η«",
10 "DOMAIN-SUFFIX,x.com,π ε½ε€η½η«",
11 "DOMAIN-SUFFIX,telegram.org,π ε½ε€η½η«",
12 "DOMAIN-SUFFIX,steampowered.com,π ε½ε€η½η«",
13 "DOMAIN-SUFFIX,openai.com,π ε½ε€η½η«",
14 "DOMAIN-SUFFIX,anthropic.com,π ε½ε€η½η«",
15 "DOMAIN-SUFFIX,claude.ai,π ε½ε€η½η«",
16 "DOMAIN-SUFFIX,cursor.com,π ε½ε€η½η«",
17 "DOMAIN-SUFFIX,cloudflare.com,π ε½ε€η½η«",
18 "DOMAIN-SUFFIX,netflix.com,π ε½ε€η½η«",
19 "DOMAIN-SUFFIX,spotify.com,π ε½ε€η½η«",
20 "DOMAIN-SUFFIX,disneyplus.com,π ε½ε€η½η«",
21 "DOMAIN-SUFFIX,cn,π¨π³ ε½ε
η½η«",
22 "DOMAIN-SUFFIX,baidu.com,π¨π³ ε½ε
η½η«",
23 "DOMAIN-SUFFIX,qq.com,π¨π³ ε½ε
η½η«",
24 "DOMAIN-SUFFIX,weixin.com,π¨π³ ε½ε
η½η«",
25 "DOMAIN-SUFFIX,alipay.com,π¨π³ ε½ε
η½η«",
26 "GEOIP,CN,π― η΄θΏ",
27 "MATCH,π ε½ε€η½η«",
28];
29
30const EXT_RULES: &[&str] = &[
31 "RULE-SET,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/BanEasyAD.list,π εΉΏεζ¦ζͺ",
32 "RULE-SET,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ProxyMedia.list,π ε½ε€η½η«",
33 "RULE-SET,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/ChinaDomain.list,π¨π³ ε½ε
η½η«",
34 "GEOIP,CN,π― η΄θΏ",
35 "MATCH,π ε½ε€η½η«",
36];
37
38pub fn build_config(nodes: &[ProxyNode], rule_mode: RuleMode) -> ClashConfig {
39 let names: Vec<String> = nodes.iter().map(|n| n.name.clone()).collect();
40 let mut foreign_proxies = vec![
41 "π θηΉιζ©".to_string(),
42 "β»οΈ θͺε¨ιζ©".to_string(),
43 "π― η΄θΏ".to_string(),
44 "DIRECT".to_string(),
45 ];
46 foreign_proxies.extend(names.clone());
47
48 let rules = match rule_mode {
49 RuleMode::External => EXT_RULES.iter().map(|s| (*s).to_string()).collect(),
50 RuleMode::Builtin => BUILTIN_RULES.iter().map(|s| (*s).to_string()).collect(),
51 };
52
53 ClashConfig {
54 port: 7890,
55 socks_port: 7891,
56 allow_lan: true,
57 mode: "Rule".to_string(),
58 log_level: "info".to_string(),
59 external_controller: "127.0.0.1:9090".to_string(),
60 proxies: nodes.to_vec(),
61 proxy_groups: vec![
62 ProxyGroup {
63 name: "π θηΉιζ©".to_string(),
64 group_type: "select".to_string(),
65 proxies: {
66 let mut p = vec![
67 "β»οΈ θͺε¨ιζ©".to_string(),
68 "π― η΄θΏ".to_string(),
69 "DIRECT".to_string(),
70 ];
71 p.extend(names.clone());
72 p
73 },
74 url: None,
75 interval: None,
76 },
77 ProxyGroup {
78 name: "β»οΈ θͺε¨ιζ©".to_string(),
79 group_type: "url-test".to_string(),
80 proxies: names.clone(),
81 url: Some("http://www.gstatic.com/generate_204".to_string()),
82 interval: Some(300),
83 },
84 ProxyGroup {
85 name: "π― η΄θΏ".to_string(),
86 group_type: "select".to_string(),
87 proxies: {
88 let mut p = vec!["DIRECT".to_string()];
89 p.extend(names.clone());
90 p
91 },
92 url: None,
93 interval: None,
94 },
95 ProxyGroup {
96 name: "π εΉΏεζ¦ζͺ".to_string(),
97 group_type: "select".to_string(),
98 proxies: {
99 let mut p = vec!["REJECT".to_string(), "DIRECT".to_string()];
100 p.extend(names.clone());
101 p
102 },
103 url: None,
104 interval: None,
105 },
106 ProxyGroup {
107 name: "π ε½ε€η½η«".to_string(),
108 group_type: "select".to_string(),
109 proxies: foreign_proxies,
110 url: None,
111 interval: None,
112 },
113 ProxyGroup {
114 name: "π¨π³ ε½ε
η½η«".to_string(),
115 group_type: "select".to_string(),
116 proxies: {
117 let mut p = vec!["π― η΄θΏ".to_string()];
118 p.extend(names);
119 p
120 },
121 url: None,
122 interval: None,
123 },
124 ],
125 rules,
126 }
127}
128
129pub fn to_yaml(config: &ClashConfig) -> crate::Result<String> {
130 Ok(serde_yaml::to_string(config)?)
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::types::ProxyType;
137
138 fn sample_nodes() -> Vec<ProxyNode> {
139 vec![
140 ProxyNode::new("θηΉ-A".into(), ProxyType::Ss, "1.2.3.4".into(), 443),
141 ProxyNode::new("θηΉ-B".into(), ProxyType::Ss, "5.6.7.8".into(), 443),
142 ]
143 }
144
145 #[test]
146 fn foreign_group_follows_select() {
147 let config = build_config(&sample_nodes(), RuleMode::Builtin);
148 let foreign = config
149 .proxy_groups
150 .iter()
151 .find(|g| g.name == "π ε½ε€η½η«")
152 .unwrap();
153 assert_eq!(foreign.proxies[0], "π θηΉιζ©");
154 }
155
156 #[test]
157 fn select_group_contains_all_nodes() {
158 let config = build_config(&sample_nodes(), RuleMode::Builtin);
159 let select = config
160 .proxy_groups
161 .iter()
162 .find(|g| g.name == "π θηΉιζ©")
163 .unwrap();
164 assert!(select.proxies.contains(&"θηΉ-A".to_string()));
165 assert!(select.proxies.contains(&"θηΉ-B".to_string()));
166 }
167
168 #[test]
169 fn external_rules_use_rule_set() {
170 let config = build_config(&sample_nodes(), RuleMode::External);
171 assert!(config.rules[0].starts_with("RULE-SET"));
172 }
173}