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