libsubconverter/settings/external/
ini_external.rs1use serde::Deserialize;
2use std::collections::HashMap;
3
4use super::super::ini_bindings::{FromIni, FromIniWithDelimiter};
5use crate::models::ruleset::RulesetConfigs;
6use crate::models::{ProxyGroupConfigs, RegexMatchConfig, RegexMatchConfigs, RulesetConfig};
7use crate::settings::{import_items, Settings};
8use crate::utils::http::parse_proxy;
9
10#[derive(Debug, Clone, Deserialize, Default)]
12#[serde(default)]
13pub struct IniExternalSettings {
14 pub clash_rule_base: String,
16 pub surge_rule_base: String,
17 pub surfboard_rule_base: String,
18 pub mellow_rule_base: String,
19 pub quan_rule_base: String,
20 pub quanx_rule_base: String,
21 pub loon_rule_base: String,
22 pub sssub_rule_base: String,
23 pub singbox_rule_base: String,
24
25 pub enable_rule_generator: bool,
27 pub overwrite_original_rules: bool,
28
29 pub add_emoji: Option<bool>,
31 pub remove_old_emoji: Option<bool>,
32 pub emojis: Vec<String>,
33
34 pub include_remarks: Vec<String>,
36 pub exclude_remarks: Vec<String>,
37
38 pub rulesets: Vec<String>,
40 pub custom_proxy_groups: Vec<String>,
41
42 pub rename_nodes: Vec<String>,
44 pub tpl_args: Option<HashMap<String, String>>,
48
49 #[serde(skip)]
51 pub parsed_custom_proxy_groups: ProxyGroupConfigs,
52 #[serde(skip)]
53 pub parsed_rulesets: Vec<RulesetConfig>,
54 #[serde(skip)]
55 pub parsed_rename: Vec<RegexMatchConfig>,
56 #[serde(skip)]
57 pub parsed_emojis: Vec<RegexMatchConfig>,
58}
59
60impl IniExternalSettings {
61 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn load_from_ini(&mut self, content: &str) -> Result<(), Box<dyn std::error::Error>> {
67 let mut current_section = String::new();
68
69 for line in content.lines() {
70 let trimmed = line.trim();
71
72 if trimmed.is_empty() || trimmed.starts_with(';') || trimmed.starts_with('#') {
74 continue;
75 }
76
77 if trimmed.starts_with('[') && trimmed.ends_with(']') {
79 current_section = trimmed[1..trimmed.len() - 1].to_string();
80 continue;
81 }
82
83 if let Some(pos) = trimmed.find('=') {
85 let key = trimmed[..pos].trim();
86 let value = trimmed[pos + 1..].trim();
87
88 match current_section.as_str() {
89 "custom" => self.process_custom_section(key, value),
90 "template" => self.process_template_section(key, value),
91 _ => {} }
93 }
94 }
95 Ok(())
96 }
97
98 fn process_custom_section(&mut self, key: &str, value: &str) {
99 match key {
100 "clash_rule_base" => self.clash_rule_base = value.to_string(),
101 "surge_rule_base" => self.surge_rule_base = value.to_string(),
102 "surfboard_rule_base" => self.surfboard_rule_base = value.to_string(),
103 "mellow_rule_base" => self.mellow_rule_base = value.to_string(),
104 "quan_rule_base" => self.quan_rule_base = value.to_string(),
105 "quanx_rule_base" => self.quanx_rule_base = value.to_string(),
106 "loon_rule_base" => self.loon_rule_base = value.to_string(),
107 "sssub_rule_base" => self.sssub_rule_base = value.to_string(),
108 "singbox_rule_base" => self.singbox_rule_base = value.to_string(),
109 "enable_rule_generator" => {
110 self.enable_rule_generator = parse_bool_with_true_default(value)
111 }
112 "overwrite_original_rules" => self.overwrite_original_rules = parse_bool(value),
113 "add_emoji" => self.add_emoji = Some(parse_bool(value)),
114 "remove_old_emoji" => self.remove_old_emoji = Some(parse_bool(value)),
115 "include_remarks" => {
116 self.include_remarks = value.split(',').map(|s| s.trim().to_string()).collect();
117 }
118 "exclude_remarks" => {
119 self.exclude_remarks = value.split(',').map(|s| s.trim().to_string()).collect();
120 }
121 "ruleset" | "surge_ruleset" => {
122 self.rulesets.push(value.to_string());
123 }
124 "custom_proxy_group" => {
125 self.custom_proxy_groups.push(value.to_string());
126 }
127 "emoji" => {
128 self.emojis.push(value.to_string());
129 }
130 "rename" => {
131 self.rename_nodes.push(value.to_string());
132 }
133 _ => {}
134 }
135 }
136
137 fn process_template_section(&mut self, key: &str, value: &str) {
138 if self.tpl_args.is_none() {
140 self.tpl_args = Some(HashMap::new());
141 }
142
143 if let Some(ref mut args) = self.tpl_args {
145 args.insert(key.to_string(), value.to_string());
146 }
147 }
148
149 pub async fn process_imports(&mut self) -> Result<(), Box<dyn std::error::Error>> {
150 let global = Settings::current();
151 let proxy_config = parse_proxy(&global.proxy_config);
152 import_items(
154 &mut self.rename_nodes,
155 false,
156 &proxy_config,
157 &global.base_path,
158 )
159 .await?;
160 self.parsed_rename = RegexMatchConfigs::from_ini_with_delimiter(&self.rename_nodes, "@");
161
162 import_items(&mut self.emojis, false, &proxy_config, &global.base_path).await?;
164 self.parsed_emojis = RegexMatchConfigs::from_ini_with_delimiter(&self.emojis, ",");
165
166 import_items(
168 &mut self.rulesets,
169 global.api_mode,
170 &proxy_config,
171 &global.base_path,
172 )
173 .await?;
174 self.parsed_rulesets = RulesetConfigs::from_ini(&self.rulesets);
175 let mut custom_proxy_groups = self.custom_proxy_groups.clone();
177 import_items(
178 &mut custom_proxy_groups,
179 global.api_mode,
180 &proxy_config,
181 &global.base_path,
182 )
183 .await?;
184 self.parsed_custom_proxy_groups = ProxyGroupConfigs::from_ini(&custom_proxy_groups);
185
186 Ok(())
187 }
188}
189
190fn parse_bool(value: &str) -> bool {
192 value.to_lowercase() == "true" || value == "1"
193}
194
195fn parse_bool_with_true_default(value: &str) -> bool {
196 if value.is_empty() {
197 true
198 } else {
199 parse_bool(value)
200 }
201}