subconverter/generator/ruleconvert/
convert_ruleset.rs1use crate::models::RulesetType;
6use crate::utils::network::is_ipv4;
7use crate::utils::string::ends_with;
8use regex::Regex;
9
10pub fn convert_ruleset(content: &str, ruleset_type: RulesetType) -> String {
21 if ruleset_type == RulesetType::Surge {
23 return content.to_string();
24 }
25
26 let mut output = String::new();
27 let payload_regex = Regex::new(r"^payload:\r?\n").unwrap();
28
29 if payload_regex.is_match(content) {
30 let content_without_header = content.replace("payload:", "").trim().to_string();
34
35 let mut rule_items_formatted = String::new();
37 for line in content_without_header.lines() {
38 let line = line.trim();
39 if line.starts_with('-') {
40 let mut rule_content = line[1..].trim().to_string();
42 if rule_content.starts_with('\'') && rule_content.ends_with('\'') {
43 rule_content = rule_content[1..rule_content.len() - 1].to_string();
44 } else if rule_content.starts_with('"') && rule_content.ends_with('"') {
45 rule_content = rule_content[1..rule_content.len() - 1].to_string();
46 }
47 rule_items_formatted.push_str(&rule_content);
48 rule_items_formatted.push('\n');
49 }
50 }
51
52 if ruleset_type == RulesetType::ClashClassical {
54 return rule_items_formatted;
55 }
56
57 for line in rule_items_formatted.lines() {
59 let mut line = line.trim().to_string();
60
61 if line.ends_with('\r') {
63 line.pop();
64 }
65
66 if let Some(comment_pos) = line.find("//") {
68 line = line[..comment_pos].trim().to_string();
69 }
70
71 if line.is_empty()
73 || line.starts_with(';')
74 || line.starts_with('#')
75 || (line.len() >= 2 && line.starts_with("//"))
76 {
77 continue;
78 }
79
80 if let Some(pos) = line.find('/') {
82 if is_ipv4(&line[..pos]) {
84 output.push_str("IP-CIDR,");
85 } else {
86 output.push_str("IP-CIDR6,");
87 }
88 output.push_str(&line);
89 } else if line.starts_with('.') || (line.len() >= 2 && line.starts_with("+.")) {
90 let mut keyword_flag = false;
92 let mut rule_content = line.clone();
93
94 while ends_with(&rule_content, ".*") {
96 keyword_flag = true;
97 rule_content = rule_content[..rule_content.len() - 2].to_string();
98 }
99
100 output.push_str("DOMAIN-");
101 if keyword_flag {
102 output.push_str("KEYWORD,");
103 } else {
104 output.push_str("SUFFIX,");
105 }
106
107 if rule_content.starts_with("+.") {
109 rule_content = rule_content[2..].to_string();
110 } else if rule_content.starts_with('.') {
111 rule_content = rule_content[1..].to_string();
112 }
113
114 output.push_str(&rule_content);
115 } else {
116 output.push_str("DOMAIN,");
118 output.push_str(&line);
119 }
120
121 output.push('\n');
122 }
123 } else {
124 let host_regex = Regex::new(r"(?i)^host").unwrap();
128 let ip6_cidr_regex = Regex::new(r"(?i)^ip6-cidr").unwrap();
129
130 let mut processed = host_regex.replace_all(content, "DOMAIN").to_string();
131 processed = ip6_cidr_regex
132 .replace_all(&processed, "IP-CIDR6")
133 .to_string();
134
135 let rule_format_regex = Regex::new(
138 r"^((?i:DOMAIN(?:-(?:SUFFIX|KEYWORD))?|IP-CIDR6?|USER-AGENT),)\s*?(\S*?)(?:,(?!no-resolve).*?)(,no-resolve)?$"
139 ).unwrap();
140
141 output = rule_format_regex
142 .replace_all(&processed, "$1$2$3")
143 .to_string();
144
145 let domain_regex = Regex::new(r"^(domain)").unwrap();
147 let domain_suffix_regex = Regex::new(r"^(domain-suffix)").unwrap();
148 let domain_keyword_regex = Regex::new(r"^(domain-keyword)").unwrap();
149 let ip_cidr_regex = Regex::new(r"^(ip-cidr)").unwrap();
150 let ip_cidr6_regex = Regex::new(r"^(ip-cidr6)").unwrap();
151 let user_agent_regex = Regex::new(r"^(user-agent)").unwrap();
152
153 output = domain_regex.replace_all(&output, "DOMAIN").to_string();
154 output = domain_suffix_regex
155 .replace_all(&output, "DOMAIN-SUFFIX")
156 .to_string();
157 output = domain_keyword_regex
158 .replace_all(&output, "DOMAIN-KEYWORD")
159 .to_string();
160 output = ip_cidr_regex.replace_all(&output, "IP-CIDR").to_string();
161 output = ip_cidr6_regex.replace_all(&output, "IP-CIDR6").to_string();
162 output = user_agent_regex
163 .replace_all(&output, "USER-AGENT")
164 .to_string();
165 }
166
167 output
168}