1use crate::utils::is_empty_option_string;
2use crate::{generator::yaml::clash::output_proxy_types::*, Proxy, ProxyType};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use super::output_proxy_types::clash_output_anytls::ClashOutputAnyTLS;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "kebab-case")]
11pub struct ClashYamlOutput {
12 #[serde(skip_serializing_if = "Option::is_none")]
14 pub port: Option<u16>,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub socks_port: Option<u16>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub redir_port: Option<u16>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub tproxy_port: Option<u16>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub mixed_port: Option<u16>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub allow_lan: Option<bool>,
25 #[serde(skip_serializing_if = "is_empty_option_string")]
26 pub bind_address: Option<String>,
27 #[serde(skip_serializing_if = "is_empty_option_string")]
28 pub mode: Option<String>,
29 #[serde(skip_serializing_if = "is_empty_option_string")]
30 pub log_level: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub ipv6: Option<bool>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub dns: Option<ClashDns>,
37
38 #[serde(skip_serializing_if = "Vec::is_empty")]
40 pub proxies: Vec<ClashProxyOutput>,
41
42 #[serde(skip_serializing_if = "Vec::is_empty")]
43 pub proxy_groups: Vec<ClashProxyGroup>,
44
45 #[serde(skip_serializing_if = "Vec::is_empty")]
46 pub rules: Vec<String>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub tun: Option<ClashTun>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub profile: Option<ClashProfile>,
54
55 #[serde(flatten)]
56 pub extra_options: HashMap<String, serde_yaml::Value>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "kebab-case")]
62pub struct ClashDns {
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub enable: Option<bool>,
65 #[serde(skip_serializing_if = "is_empty_option_string")]
66 pub listen: Option<String>,
67 #[serde(skip_serializing_if = "is_empty_option_string")]
68 pub enhanced_mode: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub nameserver: Option<Vec<String>>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub fallback: Option<Vec<String>>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub fallback_filter: Option<ClashDnsFallbackFilter>,
75 #[serde(flatten)]
76 pub extra_options: HashMap<String, serde_yaml::Value>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81#[serde(rename_all = "kebab-case")]
82pub struct ClashDnsFallbackFilter {
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub geoip: Option<bool>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub ipcidr: Option<Vec<String>>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub domain: Option<Vec<String>>,
89 #[serde(flatten)]
90 pub extra_options: HashMap<String, serde_yaml::Value>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(rename_all = "kebab-case")]
96pub struct ClashTun {
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub enable: Option<bool>,
99 #[serde(skip_serializing_if = "is_empty_option_string")]
100 pub device: Option<String>,
101 #[serde(skip_serializing_if = "is_empty_option_string")]
102 pub stack: Option<String>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub dns_hijack: Option<Vec<String>>,
105 #[serde(skip_serializing_if = "Option::is_none")]
106 pub auto_route: Option<bool>,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub auto_detect_interface: Option<bool>,
109 #[serde(flatten)]
110 pub extra_options: HashMap<String, serde_yaml::Value>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115#[serde(rename_all = "kebab-case")]
116pub struct ClashProfile {
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub store_selected: Option<bool>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub store_fake_ip: Option<bool>,
121 #[serde(flatten)]
122 pub extra_options: HashMap<String, serde_yaml::Value>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(tag = "type", rename_all = "kebab-case")]
128pub enum ClashProxyOutput {
129 #[serde(rename = "ss")]
130 Shadowsocks(ShadowsocksProxy),
131 #[serde(rename = "ssr")]
132 ShadowsocksR(ShadowsocksRProxy),
133 #[serde(rename = "vmess")]
134 VMess(VmessProxy),
135 #[serde(rename = "trojan")]
136 Trojan(TrojanProxy),
137 #[serde(rename = "http")]
138 Http(HttpProxy),
139 #[serde(rename = "socks5")]
140 Socks5(Socks5Proxy),
141 #[serde(rename = "snell")]
142 Snell(SnellProxy),
143 #[serde(rename = "wireguard")]
144 WireGuard(WireGuardProxy),
145 #[serde(rename = "hysteria")]
146 Hysteria(HysteriaProxy),
147 #[serde(rename = "hysteria2")]
148 Hysteria2(Hysteria2Proxy),
149 #[serde(rename = "vless")]
150 VLess(VLessProxy),
151 #[serde(rename = "anytls")]
152 AnyTls(ClashOutputAnyTLS),
153}
154
155impl ClashProxyOutput {
157 pub fn new_shadowsocks(common: CommonProxyOptions) -> Self {
159 ClashProxyOutput::Shadowsocks(ShadowsocksProxy::new(common))
160 }
161
162 pub fn new_shadowsocksr(common: CommonProxyOptions) -> Self {
164 ClashProxyOutput::ShadowsocksR(ShadowsocksRProxy::new(common))
165 }
166
167 pub fn new_vmess(common: CommonProxyOptions) -> Self {
169 ClashProxyOutput::VMess(VmessProxy::new(common))
170 }
171
172 pub fn new_http(common: CommonProxyOptions) -> Self {
174 ClashProxyOutput::Http(HttpProxy::new(common))
175 }
176
177 pub fn new_trojan(common: CommonProxyOptions) -> Self {
179 ClashProxyOutput::Trojan(TrojanProxy::new(common))
180 }
181
182 pub fn new_socks5(common: CommonProxyOptions) -> Self {
184 ClashProxyOutput::Socks5(Socks5Proxy::new(common))
185 }
186
187 pub fn new_snell(common: CommonProxyOptions) -> Self {
189 ClashProxyOutput::Snell(SnellProxy::new(common))
190 }
191
192 pub fn new_wireguard(common: CommonProxyOptions) -> Self {
194 ClashProxyOutput::WireGuard(WireGuardProxy::new(common))
195 }
196
197 pub fn new_hysteria(common: CommonProxyOptions) -> Self {
199 ClashProxyOutput::Hysteria(HysteriaProxy::new(common))
200 }
201
202 pub fn new_hysteria2(common: CommonProxyOptions) -> Self {
204 ClashProxyOutput::Hysteria2(Hysteria2Proxy::new(common))
205 }
206
207 pub fn new_vless(common: CommonProxyOptions) -> Self {
209 ClashProxyOutput::VLess(VLessProxy::new(common))
210 }
211
212 pub fn new_anytls(common: CommonProxyOptions) -> Self {
213 ClashProxyOutput::AnyTls(ClashOutputAnyTLS::new(common))
214 }
215}
216
217pub trait ClashProxyCommon {
219 fn common(&self) -> &CommonProxyOptions;
221
222 fn common_mut(&mut self) -> &mut CommonProxyOptions;
224
225 fn set_tfo(&mut self, value: bool) {
227 self.common_mut().tfo = Some(value);
228 }
229
230 fn set_udp(&mut self, value: bool) {
232 self.common_mut().udp = Some(value);
233 }
234
235 fn set_skip_cert_verify(&mut self, value: bool) {
237 self.common_mut().skip_cert_verify = Some(value);
238 }
239
240 fn set_tls(&mut self, value: bool) {
242 self.common_mut().tls = Some(value);
243 }
244
245 fn set_sni(&mut self, value: String) {
247 self.common_mut().sni = Some(value);
248 }
249
250 fn set_fingerprint(&mut self, value: String) {
252 self.common_mut().fingerprint = Some(value);
253 }
254}
255
256impl ClashProxyCommon for ClashProxyOutput {
257 fn common(&self) -> &CommonProxyOptions {
258 match self {
259 ClashProxyOutput::Shadowsocks(proxy) => &proxy.common,
260 ClashProxyOutput::ShadowsocksR(proxy) => &proxy.common,
261 ClashProxyOutput::VMess(proxy) => &proxy.common,
262 ClashProxyOutput::Trojan(proxy) => &proxy.common,
263 ClashProxyOutput::Http(proxy) => &proxy.common,
264 ClashProxyOutput::Socks5(proxy) => &proxy.common,
265 ClashProxyOutput::Snell(proxy) => &proxy.common,
266 ClashProxyOutput::WireGuard(proxy) => &proxy.common,
267 ClashProxyOutput::Hysteria(proxy) => &proxy.common,
268 ClashProxyOutput::Hysteria2(proxy) => &proxy.common,
269 ClashProxyOutput::VLess(proxy) => &proxy.common,
270 ClashProxyOutput::AnyTls(proxy) => &proxy.common,
271 }
272 }
273
274 fn common_mut(&mut self) -> &mut CommonProxyOptions {
275 match self {
276 ClashProxyOutput::Shadowsocks(proxy) => &mut proxy.common,
277 ClashProxyOutput::ShadowsocksR(proxy) => &mut proxy.common,
278 ClashProxyOutput::VMess(proxy) => &mut proxy.common,
279 ClashProxyOutput::Trojan(proxy) => &mut proxy.common,
280 ClashProxyOutput::Http(proxy) => &mut proxy.common,
281 ClashProxyOutput::Socks5(proxy) => &mut proxy.common,
282 ClashProxyOutput::Snell(proxy) => &mut proxy.common,
283 ClashProxyOutput::WireGuard(proxy) => &mut proxy.common,
284 ClashProxyOutput::Hysteria(proxy) => &mut proxy.common,
285 ClashProxyOutput::Hysteria2(proxy) => &mut proxy.common,
286 ClashProxyOutput::VLess(proxy) => &mut proxy.common,
287 ClashProxyOutput::AnyTls(proxy) => &mut proxy.common,
288 }
289 }
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294#[serde(tag = "type", rename_all = "kebab-case")]
295pub enum ClashProxyGroup {
296 #[serde(rename = "select")]
297 Select {
298 name: String,
299 proxies: Vec<String>,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 r#use: Option<Vec<String>>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 disable_udp: Option<bool>,
304 },
305 #[serde(rename = "url-test")]
306 UrlTest {
307 name: String,
308 proxies: Vec<String>,
309 url: String,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 interval: Option<u32>,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 tolerance: Option<u32>,
314 #[serde(skip_serializing_if = "Option::is_none")]
315 lazy: Option<bool>,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 disable_udp: Option<bool>,
318 #[serde(skip_serializing_if = "Option::is_none")]
319 r#use: Option<Vec<String>>,
320 },
321 #[serde(rename = "fallback")]
322 Fallback {
323 name: String,
324 proxies: Vec<String>,
325 url: String,
326 #[serde(skip_serializing_if = "Option::is_none")]
327 interval: Option<u32>,
328 #[serde(skip_serializing_if = "Option::is_none")]
329 tolerance: Option<u32>,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 disable_udp: Option<bool>,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 r#use: Option<Vec<String>>,
334 },
335 #[serde(rename = "load-balance")]
336 LoadBalance {
337 name: String,
338 proxies: Vec<String>,
339 strategy: String,
340 #[serde(skip_serializing_if = "is_empty_option_string")]
341 url: Option<String>,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 interval: Option<u32>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 tolerance: Option<u32>,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 lazy: Option<bool>,
348 #[serde(skip_serializing_if = "Option::is_none")]
349 disable_udp: Option<bool>,
350 #[serde(skip_serializing_if = "Option::is_none")]
351 r#use: Option<Vec<String>>,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 persistent: Option<bool>,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 evaluate_before_use: Option<bool>,
356 },
357 #[serde(rename = "relay")]
358 Relay {
359 name: String,
360 proxies: Vec<String>,
361 #[serde(skip_serializing_if = "Option::is_none")]
362 disable_udp: Option<bool>,
363 #[serde(skip_serializing_if = "Option::is_none")]
364 r#use: Option<Vec<String>>,
365 },
366}
367
368impl Default for ClashYamlOutput {
370 fn default() -> Self {
371 Self {
372 port: None,
373 socks_port: None,
374 redir_port: None,
375 tproxy_port: None,
376 mixed_port: None,
377 allow_lan: None,
378 bind_address: None,
379 mode: Some("rule".to_string()),
380 log_level: Some("info".to_string()),
381 ipv6: None,
382 dns: None,
383 proxies: Vec::new(),
384 proxy_groups: Vec::new(),
385 rules: Vec::new(),
386 tun: None,
387 profile: None,
388 extra_options: HashMap::new(),
389 }
390 }
391}
392
393impl From<Proxy> for ClashProxyOutput {
395 fn from(proxy: Proxy) -> Self {
396 match proxy.proxy_type {
397 ProxyType::Shadowsocks => ClashProxyOutput::Shadowsocks(ShadowsocksProxy::from(proxy)),
398 ProxyType::ShadowsocksR => {
399 ClashProxyOutput::ShadowsocksR(ShadowsocksRProxy::from(proxy))
400 }
401 ProxyType::VMess => ClashProxyOutput::VMess(VmessProxy::from(proxy)),
402 ProxyType::Vless => ClashProxyOutput::VLess(VLessProxy::from(proxy)),
403 ProxyType::Trojan => ClashProxyOutput::Trojan(TrojanProxy::from(proxy)),
404 ProxyType::HTTP | ProxyType::HTTPS => ClashProxyOutput::Http(HttpProxy::from(proxy)),
405 ProxyType::Socks5 => ClashProxyOutput::Socks5(Socks5Proxy::from(proxy)),
406 ProxyType::Snell => {
407 if proxy.snell_version >= 4 {
409 let common = CommonProxyOptions::builder(
412 proxy.remark.clone(),
413 proxy.hostname.clone(),
414 proxy.port,
415 )
416 .build();
417 ClashProxyOutput::Snell(SnellProxy::new(common))
418 } else {
419 ClashProxyOutput::Snell(SnellProxy::from(proxy))
420 }
421 }
422 ProxyType::WireGuard => ClashProxyOutput::WireGuard(WireGuardProxy::from(proxy)),
423 ProxyType::Hysteria => ClashProxyOutput::Hysteria(HysteriaProxy::from(proxy)),
424 ProxyType::Hysteria2 => ClashProxyOutput::Hysteria2(Hysteria2Proxy::from(proxy)),
425 ProxyType::AnyTls => ClashProxyOutput::AnyTls(ClashOutputAnyTLS::from(proxy)),
426 _ => {
427 let common = CommonProxyOptions::builder(
430 proxy.remark.clone(),
431 proxy.hostname.clone(),
432 proxy.port,
433 )
434 .build();
435 ClashProxyOutput::Http(HttpProxy::new(common))
436 }
437 }
438 }
439}