libsubconverter/models/
extra_settings.rs

1use std::{cmp::Ordering, str::FromStr};
2
3use crate::{utils::file_get_async, Settings};
4
5use super::{Proxy, ProxyType, RegexMatchConfig, RegexMatchConfigs};
6
7/// Settings for subscription export operations
8pub struct ExtraSettings {
9    /// Whether to enable the rule generator
10    pub enable_rule_generator: bool,
11    /// Whether to overwrite original rules
12    pub overwrite_original_rules: bool,
13    /// Rename operations to apply
14    pub rename_array: RegexMatchConfigs,
15    /// Emoji operations to apply
16    pub emoji_array: RegexMatchConfigs,
17    /// Whether to add emoji
18    pub add_emoji: bool,
19    /// Whether to remove emoji
20    pub remove_emoji: bool,
21    /// Whether to append proxy type
22    pub append_proxy_type: bool,
23    /// Whether to output as node list
24    pub nodelist: bool,
25    /// Whether to sort nodes
26    pub sort_flag: bool,
27    /// Whether to filter deprecated nodes
28    pub filter_deprecated: bool,
29    /// Whether to use new field names in Clash
30    pub clash_new_field_name: bool,
31    /// Whether to use scripts in Clash
32    pub clash_script: bool,
33    /// Path to Surge SSR binary
34    pub surge_ssr_path: String,
35    /// Prefix for managed configs
36    pub managed_config_prefix: String,
37    /// QuantumultX device ID
38    pub quanx_dev_id: String,
39    /// UDP support flag
40    pub udp: Option<bool>,
41    /// TCP Fast Open support flag
42    pub tfo: Option<bool>,
43    /// Skip certificate verification flag
44    pub skip_cert_verify: Option<bool>,
45    /// TLS 1.3 support flag
46    pub tls13: Option<bool>,
47    /// Whether to use classical ruleset in Clash
48    pub clash_classical_ruleset: bool,
49    /// Script for sorting nodes
50    pub sort_script: String,
51    /// Style for Clash proxies output
52    pub clash_proxies_style: String,
53    /// Style for Clash proxy groups output
54    pub clash_proxy_groups_style: String,
55    /// Whether the export is authorized
56    pub authorized: bool,
57    /// JavaScript runtime context (not implemented in Rust version)
58    #[cfg(feature = "js-runtime")]
59    pub js_context: Option<rquickjs::Context>,
60    /// JavaScript runtime
61    #[cfg(feature = "js-runtime")]
62    pub js_runtime: Option<rquickjs::Runtime>,
63}
64
65impl std::fmt::Debug for ExtraSettings {
66    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
67        f.debug_struct("ExtraSettings")
68            .field("enable_rule_generator", &self.enable_rule_generator)
69            .field("overwrite_original_rules", &self.overwrite_original_rules)
70            .field("rename_array", &self.rename_array)
71            .field("emoji_array", &self.emoji_array)
72            .field("add_emoji", &self.add_emoji)
73            .field("remove_emoji", &self.remove_emoji)
74            .field("append_proxy_type", &self.append_proxy_type)
75            .field("nodelist", &self.nodelist)
76            .field("sort_flag", &self.sort_flag)
77            .field("filter_deprecated", &self.filter_deprecated)
78            .field("clash_new_field_name", &self.clash_new_field_name)
79            .field("clash_script", &self.clash_script)
80            .field("surge_ssr_path", &self.surge_ssr_path)
81            .field("managed_config_prefix", &self.managed_config_prefix)
82            .field("quanx_dev_id", &self.quanx_dev_id)
83            .field("udp", &self.udp)
84            .field("tfo", &self.tfo)
85            .field("skip_cert_verify", &self.skip_cert_verify)
86            .field("tls13", &self.tls13)
87            .field("clash_classical_ruleset", &self.clash_classical_ruleset)
88            .field("sort_script", &self.sort_script)
89            .field("clash_proxies_style", &self.clash_proxies_style)
90            .field("clash_proxy_groups_style", &self.clash_proxy_groups_style)
91            .field("authorized", &self.authorized)
92            .finish()
93    }
94}
95
96impl Default for ExtraSettings {
97    fn default() -> Self {
98        let global = Settings::current();
99
100        ExtraSettings {
101            enable_rule_generator: global.enable_rule_gen,
102            overwrite_original_rules: global.overwrite_original_rules,
103            rename_array: Vec::new(),
104            emoji_array: Vec::new(),
105            add_emoji: false,
106            remove_emoji: false,
107            append_proxy_type: false,
108            nodelist: false,
109            sort_flag: false,
110            filter_deprecated: false,
111            clash_new_field_name: true,
112            clash_script: false,
113            surge_ssr_path: global.surge_ssr_path.clone(),
114            managed_config_prefix: String::new(),
115            quanx_dev_id: String::new(),
116            udp: None,
117            tfo: None,
118            skip_cert_verify: None,
119            tls13: None,
120            clash_classical_ruleset: false,
121            sort_script: String::new(),
122            clash_proxies_style: if global.clash_proxies_style.is_empty() {
123                "flow".to_string()
124            } else {
125                global.clash_proxies_style.clone()
126            },
127            clash_proxy_groups_style: if global.clash_proxy_groups_style.is_empty() {
128                "flow".to_string()
129            } else {
130                global.clash_proxy_groups_style.clone()
131            },
132            authorized: false,
133            #[cfg(feature = "js-runtime")]
134            js_context: None,
135            #[cfg(feature = "js-runtime")]
136            js_runtime: None,
137        }
138    }
139}
140
141#[cfg(feature = "js-runtime")]
142impl ExtraSettings {
143    pub fn init_js_context(&mut self) {
144        if self.js_runtime.is_none() {
145            self.js_runtime = Some(rquickjs::Runtime::new().unwrap());
146            self.js_context =
147                Some(rquickjs::Context::base(&self.js_runtime.as_ref().unwrap()).unwrap());
148        }
149    }
150
151    pub fn eval_filter_function(
152        &mut self,
153        nodes: &mut Vec<Proxy>,
154        source_str: &str,
155    ) -> Result<(), Box<dyn std::error::Error>> {
156        self.init_js_context();
157        if let Some(context) = &mut self.js_context {
158            let mut error_thrown = None;
159            context.with(|ctx| {
160                match ctx.eval::<(), &str>(source_str) {
161                    Ok(_) => (),
162                    Err(e) => {
163                        match e {
164                            rquickjs::Error::Exception => {
165                                log::error!(
166                                    "JavaScript eval throw exception: {}",
167                                    ctx.catch()
168                                        .try_into_string()
169                                        .unwrap()
170                                        .to_string()
171                                        .unwrap_or_default()
172                                );
173                            }
174                            _ => {
175                                log::error!("JavaScript eval error: {}", e);
176                            }
177                        }
178                        error_thrown = Some(e);
179                        return;
180                    }
181                };
182                let filter_evaluated: rquickjs::Function =
183                    match ctx.globals().get::<_, rquickjs::Function>("filter") {
184                        Ok(value) => value,
185                        Err(e) => {
186                            log::error!("JavaScript eval get function error: {}", e);
187                            return;
188                        }
189                    };
190
191                nodes.retain_mut(|node| {
192                    match filter_evaluated.call::<(Proxy,), bool>((node.clone(),)) {
193                        Ok(value) => value,
194                        Err(e) => {
195                            log::error!("JavaScript eval call function error: {}", e);
196                            false
197                        }
198                    }
199                });
200            });
201            match error_thrown {
202                Some(e) => Err(e.into()),
203                None => {
204                    log::info!("Filter function evaluated successfully");
205                    Ok(())
206                }
207            }
208        } else {
209            Err("JavaScript context not initialized".into())
210        }
211    }
212
213    /// Sorts nodes by a specified criterion
214    pub async fn eval_sort_nodes(
215        &mut self,
216        nodes: &mut Vec<Proxy>,
217    ) -> Result<(), Box<dyn std::error::Error>> {
218        if !self.sort_script.is_empty() {
219            let sort_script;
220            if self.sort_script.starts_with("path:") {
221                sort_script = file_get_async(&self.sort_script[5..], None).await?;
222            } else {
223                sort_script = self.sort_script.clone();
224            }
225            self.init_js_context();
226            let mut error_thrown = None;
227            if let Some(context) = &mut self.js_context {
228                context.with(|ctx| {
229                    match ctx.eval::<(), &str>(&sort_script) {
230                        Ok(_) => (),
231                        Err(e) => match e {
232                            rquickjs::Error::Exception => {
233                                error_thrown = Some(e);
234                                return;
235                            }
236                            _ => {
237                                error_thrown = Some(e);
238                                return;
239                            }
240                        },
241                    }
242                    let compare = match ctx.globals().get::<_, rquickjs::Function>("compare") {
243                        Ok(value) => value,
244                        Err(e) => {
245                            log::error!("JavaScript eval get function error: {}", e);
246                            return;
247                        }
248                    };
249                    nodes.sort_by(|a, b| {
250                        match compare.call::<(Proxy, Proxy), i32>((a.clone(), b.clone())) {
251                            Ok(value) => {
252                                if value > 0 {
253                                    Ordering::Greater
254                                } else if value < 0 {
255                                    Ordering::Less
256                                } else {
257                                    Ordering::Equal
258                                }
259                            }
260                            Err(e) => {
261                                log::error!("JavaScript eval call function error: {}", e);
262                                return Ordering::Equal;
263                            }
264                        }
265                    });
266                });
267            }
268            if let Some(e) = error_thrown {
269                return Err(e.into());
270            }
271        } else {
272            // Default sort by remark
273            nodes.sort_by(|a, b| {
274                if a.proxy_type == ProxyType::Unknown {
275                    return Ordering::Greater;
276                }
277                if b.proxy_type == ProxyType::Unknown {
278                    return Ordering::Less;
279                }
280                a.remark.cmp(&b.remark)
281            });
282        }
283        Ok(())
284    }
285
286    pub async fn eval_get_rename_node_remark(
287        &self,
288        node: &Proxy,
289        match_script: String,
290    ) -> Result<String, Box<dyn std::error::Error>> {
291        let mut node_name = String::new();
292        if !match_script.is_empty() {
293            let mut error_thrown = None;
294            if let Some(context) = &self.js_context {
295                context.with(|ctx| {
296                    match ctx.eval::<(), &str>(&match_script) {
297                        Ok(_) => (),
298                        Err(e) => {
299                            error_thrown = Some(e);
300                            return;
301                        }
302                    }
303                    let rename = match ctx.globals().get::<_, rquickjs::Function>("rename") {
304                        Ok(value) => value,
305                        Err(e) => {
306                            log::error!("JavaScript eval get function error: {}", e);
307                            error_thrown = Some(e);
308                            return;
309                        }
310                    };
311                    match rename.call::<(Proxy,), String>((node.clone(),)) {
312                        Ok(value) => {
313                            if !value.is_empty() {
314                                node_name = value;
315                            }
316                        }
317                        Err(e) => {
318                            log::error!("JavaScript eval call function error: {}", e);
319                            error_thrown = Some(e);
320                            return;
321                        }
322                    }
323                })
324            }
325            if let Some(e) = error_thrown {
326                return Err(e.into());
327            }
328        }
329        Ok(node_name)
330    }
331
332    pub async fn eval_get_emoji_node_remark(
333        &self,
334        node: &Proxy,
335        match_script: String,
336    ) -> Result<String, Box<dyn std::error::Error>> {
337        let mut node_emoji = String::new();
338        if !match_script.is_empty() {
339            let mut error_thrown = None;
340            if let Some(context) = &self.js_context {
341                context.with(|ctx| {
342                    match ctx.eval::<(), &str>(&match_script) {
343                        Ok(_) => (),
344                        Err(e) => {
345                            error_thrown = Some(e);
346                            return;
347                        }
348                    }
349                    let get_emoji = match ctx.globals().get::<_, rquickjs::Function>("getEmoji") {
350                        Ok(value) => value,
351                        Err(e) => {
352                            log::error!("JavaScript eval get function error: {}", e);
353                            error_thrown = Some(e);
354                            return;
355                        }
356                    };
357                    match get_emoji.call::<(Proxy,), String>((node.clone(),)) {
358                        Ok(value) => {
359                            if !value.is_empty() {
360                                node_emoji = value;
361                            }
362                        }
363                        Err(e) => {
364                            log::error!("JavaScript eval call function error: {}", e);
365                            error_thrown = Some(e);
366                            return;
367                        }
368                    }
369                })
370            }
371            if let Some(e) = error_thrown {
372                return Err(e.into());
373            }
374        }
375        Ok(node_emoji)
376    }
377}
378
379#[cfg(not(feature = "js-runtime"))]
380impl ExtraSettings {
381    pub fn init_js_context(&mut self) {}
382    pub fn eval_filter_function(
383        &mut self,
384        _nodes: &mut Vec<Proxy>,
385        _source_str: &str,
386    ) -> Result<(), Box<dyn std::error::Error>> {
387        Err(
388            "JavaScript is not supported in this build, please enable js-runtime feature in cargo build"
389                .into(),
390        )
391    }
392    pub async fn eval_sort_nodes(
393        &mut self,
394        _nodes: &mut Vec<Proxy>,
395    ) -> Result<(), Box<dyn std::error::Error>> {
396        Err(
397            "JavaScript is not supported in this build, please enable js-runtime feature in cargo build"
398                .into(),
399        )
400    }
401    pub async fn eval_get_rename_node_remark(
402        &self,
403        _node: &Proxy,
404        _match_script: String,
405    ) -> Result<String, Box<dyn std::error::Error>> {
406        Err(
407            "JavaScript is not supported in this build, please enable js-runtime feature in cargo build"
408                .into(),
409        )
410    }
411    pub async fn eval_get_emoji_node_remark(
412        &self,
413        _node: &Proxy,
414        _match_script: String,
415    ) -> Result<String, Box<dyn std::error::Error>> {
416        Err(
417            "JavaScript is not supported in this build, please enable js-runtime feature in cargo build"
418                .into(),
419        )
420    }
421}