1use crate::error::{Error, Result};
2use crate::ir::{Node, Protocol, Tls};
3use serde::{Deserialize, Serialize};
4use serde_yaml::Value;
5use std::collections::BTreeMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(tag = "type")]
10#[serde(rename_all = "lowercase")]
11pub enum ClashProxy {
12 #[serde(rename = "ss")]
13 Ss {
14 name: String,
15 server: String,
16 port: u16,
17 cipher: String,
18 password: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 udp: Option<bool>,
21 },
22 Trojan {
23 name: String,
24 server: String,
25 port: u16,
26 password: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 sni: Option<String>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 alpn: Option<Vec<String>>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 #[serde(rename = "skip-cert-verify")]
33 skip_cert_verify: Option<bool>,
34 },
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ProxyGroup {
40 pub name: String,
41 #[serde(rename = "type")]
42 pub group_type: String,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub proxies: Option<Vec<String>>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub url: Option<String>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub interval: Option<u32>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub tolerance: Option<u32>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub lazy: Option<bool>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 #[serde(rename = "use")]
55 pub use_provider: Option<Vec<String>>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub filter: Option<String>,
58 #[serde(flatten)]
59 pub other: serde_yaml::Mapping,
60}
61
62#[derive(Debug, Clone, Default)]
64pub struct ClashConfig {
65 pub general: Option<Value>,
67 pub proxies: Vec<ClashProxy>,
69 pub proxy_groups: Option<Vec<ProxyGroup>>,
71 pub rules: Option<Value>,
73 pub dns: Option<Value>,
75}
76
77impl<'de> Deserialize<'de> for ClashConfig {
78 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
79 where
80 D: serde::Deserializer<'de>,
81 {
82 let mut map = BTreeMap::<String, Value>::deserialize(deserializer)?;
83
84 let proxies = map
85 .remove("proxies")
86 .map(serde_yaml::from_value)
87 .transpose()
88 .map_err(serde::de::Error::custom)?
89 .unwrap_or_default();
90
91 let proxy_groups = map
92 .remove("proxy-groups")
93 .map(serde_yaml::from_value)
94 .transpose()
95 .map_err(serde::de::Error::custom)?;
96
97 let rules = map.remove("rules");
98 let dns = map.remove("dns");
99
100 let general = if map.is_empty() {
102 None
103 } else {
104 Some(Value::Mapping(
105 map.into_iter()
106 .map(|(k, v)| (Value::String(k), v))
107 .collect(),
108 ))
109 };
110
111 Ok(ClashConfig {
112 general,
113 proxies,
114 proxy_groups,
115 rules,
116 dns,
117 })
118 }
119}
120
121impl Serialize for ClashConfig {
122 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
123 where
124 S: serde::Serializer,
125 {
126 use serde::ser::SerializeMap;
127
128 let mut map = serializer.serialize_map(None)?;
129
130 if let Some(Value::Mapping(m)) = &self.general {
131 for (k, v) in m {
132 map.serialize_entry(k, v)?;
133 }
134 }
135
136 if !self.proxies.is_empty() {
137 map.serialize_entry("proxies", &self.proxies)?;
138 }
139
140 if let Some(pg) = &self.proxy_groups {
141 map.serialize_entry("proxy-groups", pg)?;
142 }
143
144 if let Some(r) = &self.rules {
145 map.serialize_entry("rules", r)?;
146 }
147
148 if let Some(d) = &self.dns {
149 map.serialize_entry("dns", d)?;
150 }
151
152 map.end()
153 }
154}
155
156impl From<ClashProxy> for Node {
158 fn from(proxy: ClashProxy) -> Self {
159 match proxy {
160 ClashProxy::Ss {
161 name,
162 server,
163 port,
164 cipher,
165 password,
166 ..
167 } => Node {
168 name,
169 server,
170 port,
171 protocol: Protocol::Shadowsocks {
172 method: cipher,
173 password,
174 },
175 transport: None,
176 tls: None,
177 tags: Vec::new(),
178 },
179 ClashProxy::Trojan {
180 name,
181 server,
182 port,
183 password,
184 sni,
185 alpn,
186 skip_cert_verify,
187 } => Node {
188 name,
189 server,
190 port,
191 protocol: Protocol::Trojan { password },
192 transport: None,
193 tls: Some(Tls {
194 enabled: true,
195 server_name: sni,
196 alpn,
197 insecure: skip_cert_verify,
198 utls_fingerprint: None,
199 }),
200 tags: Vec::new(),
201 },
202 }
203 }
204}
205
206impl TryFrom<Node> for ClashProxy {
208 type Error = Error;
209
210 fn try_from(node: Node) -> Result<Self> {
211 let Node {
212 name,
213 server,
214 port,
215 protocol,
216 tls,
217 ..
218 } = node;
219
220 match protocol {
221 Protocol::Shadowsocks { method, password } => {
222 if method.is_empty() || password.is_empty() {
223 return Err(Error::ValidationError {
224 reason: format!("Empty method or password for Shadowsocks node: {}", name),
225 });
226 }
227
228 Ok(ClashProxy::Ss {
229 name,
230 server,
231 port,
232 cipher: method,
233 password,
234 udp: None,
235 })
236 }
237 Protocol::Trojan { password } => {
238 if password.is_empty() {
239 return Err(Error::ValidationError {
240 reason: format!("Empty password for Trojan node: {}", name),
241 });
242 }
243
244 let (sni, alpn, skip_cert_verify) = tls
245 .map(|tls| (tls.server_name, tls.alpn, tls.insecure))
246 .unwrap_or((None, None, None));
247
248 Ok(ClashProxy::Trojan {
249 name,
250 server,
251 port,
252 password,
253 sni,
254 alpn,
255 skip_cert_verify,
256 })
257 }
258 _ => Err(Error::Unsupported {
259 what: format!("protocol '{}' for clash", protocol.name()),
260 }),
261 }
262 }
263}