libsubconverter/parser/
subparser.rs1use crate::models::Proxy;
2use crate::parser::explodes::*;
3use crate::parser::infoparser::{get_sub_info_from_nodes, get_sub_info_from_ssd};
4use crate::parser::parse_settings::ParseSettings;
5use crate::utils::http::get_sub_info_from_header;
6use crate::utils::matcher::{apply_matcher, reg_find};
7use crate::utils::network::is_link;
8use crate::utils::url::url_decode;
9use crate::utils::{file_exists, file_get_async, web_get_async};
10use log::warn;
11
12#[derive(Debug, PartialEq, Eq)]
14pub enum ConfType {
15 SOCKS,
16 HTTP,
17 SUB,
18 Netch,
19 Local,
20 Unknown,
21}
22
23pub async fn add_nodes(
36 mut link: String,
37 all_nodes: &mut Vec<Proxy>,
38 group_id: i32,
39 parse_settings: &mut ParseSettings,
40) -> Result<(), String> {
41 let proxy = &parse_settings.proxy;
43 let exclude_remarks = parse_settings.exclude_remarks.as_ref();
44 let include_remarks = parse_settings.include_remarks.as_ref();
45 let stream_rules = parse_settings.stream_rules.as_ref();
46 let time_rules = parse_settings.time_rules.as_ref();
47 let request_header = parse_settings.request_header.as_ref();
48 let authorized = parse_settings.authorized;
49
50 let mut nodes: Vec<Proxy> = Vec::new();
52 let mut node = Proxy::default();
53 let mut custom_group = String::new();
54
55 link = link.replace("\"", "");
57
58 #[cfg(feature = "js_runtime")]
60 if authorized && link.starts_with("script:") {
61 return Err("Script processing not implemented".to_string());
63 }
64
65 if link.starts_with("tag:") {
67 if let Some(pos) = link.find(',') {
68 custom_group = link[4..pos].to_string();
69 link = link[pos + 1..].to_string();
70 }
71 }
72
73 if link == "nullnode" {
75 let mut null_node = Proxy::default();
76 null_node.group_id = 0;
77 all_nodes.push(null_node);
78 return Ok(());
79 }
80
81 let link_type = if link.starts_with("https://t.me/socks") || link.starts_with("tg://socks") {
83 ConfType::SOCKS
84 } else if link.starts_with("https://t.me/http") || link.starts_with("tg://http") {
85 ConfType::HTTP
86 } else if is_link(&link) || link.starts_with("surge:///install-config") {
87 ConfType::SUB
88 } else if link.starts_with("Netch://") {
89 ConfType::Netch
90 } else if file_exists(&link).await {
91 ConfType::Local
92 } else {
93 ConfType::Unknown
95 };
96
97 match link_type {
98 ConfType::SUB => {
99 if link.starts_with("surge:///install-config") {
101 if let Some(url_arg) = get_url_arg(&link, "url") {
103 link = url_decode(&url_arg);
104 }
105 }
106
107 let response = match web_get_async(&link, proxy, request_header).await {
109 Ok(response) => response,
110 Err(e) => {
111 warn!("Failed to get subscription content from {}: {}", link, e);
112 return Err(format!("HTTP request failed: {}", e));
113 }
114 };
115
116 let sub_content = response.body;
117 let headers = response.headers;
118
119 if !sub_content.is_empty() {
120 let result = explode_conf_content(&sub_content, &mut nodes);
122 if result > 0 {
123 if sub_content.starts_with("ssd://") {
125 if let Some(info) = get_sub_info_from_ssd(&sub_content) {
127 parse_settings.sub_info = Some(info);
128 }
129 } else {
130 let header_info = get_sub_info_from_header(&headers);
132 if !header_info.is_empty() {
133 parse_settings.sub_info = Some(header_info);
134 } else {
135 if let (Some(stream_rules_unwrapped), Some(time_rules_unwrapped)) =
137 (stream_rules, time_rules)
138 {
139 if let Some(info) = get_sub_info_from_nodes(
140 &nodes,
141 stream_rules_unwrapped,
142 time_rules_unwrapped,
143 ) {
144 parse_settings.sub_info = Some(info);
145 }
146 }
147 }
148 }
149
150 filter_nodes(&mut nodes, exclude_remarks, include_remarks, group_id);
152
153 for node in &mut nodes {
155 node.group_id = group_id;
156 if !custom_group.is_empty() {
157 node.group = custom_group.clone();
158 }
159 }
160
161 all_nodes.append(&mut nodes);
163 Ok(())
164 } else {
165 Err(format!("Invalid subscription: '{}'", sub_content))
166 }
167 } else {
168 Err("Cannot download subscription data".to_string())
169 }
170 }
171 ConfType::Local => {
172 if !authorized {
173 return Err("Not authorized to access local files".to_string());
174 }
175
176 let result = explode_conf(&link, &mut nodes).await;
178 if result > 0 {
179 if link.starts_with("ssd://") {
182 if let Some(info) = get_sub_info_from_ssd(&link) {
184 parse_settings.sub_info = Some(info);
185 }
186 } else {
187 if let (Some(stream_rules_unwrapped), Some(time_rules_unwrapped)) =
189 (stream_rules, time_rules)
190 {
191 if let Some(info) = get_sub_info_from_nodes(
192 &nodes,
193 stream_rules_unwrapped,
194 time_rules_unwrapped,
195 ) {
196 parse_settings.sub_info = Some(info);
197 }
198 }
199 }
200
201 filter_nodes(&mut nodes, exclude_remarks, include_remarks, group_id);
202
203 for node in &mut nodes {
205 node.group_id = group_id;
206 if !custom_group.is_empty() {
207 node.group = custom_group.clone();
208 }
209 }
210
211 all_nodes.append(&mut nodes);
212 Ok(())
213 } else {
214 Err("Invalid configuration file".to_string())
215 }
216 }
217 _ => {
218 if explode(&link, &mut node) {
220 if node.proxy_type == crate::models::ProxyType::Unknown {
221 return Err("No valid link found".to_string());
222 }
223 node.group_id = group_id;
224 if !custom_group.is_empty() {
225 node.group = custom_group;
226 }
227 all_nodes.push(node);
228 Ok(())
229 } else {
230 Err("No valid link found".to_string())
231 }
232 }
233 }
234}
235
236fn get_url_arg(url: &str, arg_name: &str) -> Option<String> {
238 if let Some(query_start) = url.find('?') {
239 let query = &url[query_start + 1..];
240 for pair in query.split('&') {
241 let parts: Vec<&str> = pair.split('=').collect();
242 if parts.len() == 2 && parts[0] == arg_name {
243 return Some(parts[1].to_string());
244 }
245 }
246 }
247 None
248}
249
250async fn explode_conf(path: &str, nodes: &mut Vec<Proxy>) -> i32 {
253 match file_get_async(path, None).await {
255 Ok(content) => explode_conf_content(&content, nodes),
256 Err(_) => 0,
257 }
258}
259
260fn filter_nodes(
262 nodes: &mut Vec<Proxy>,
263 exclude_remarks: Option<&Vec<String>>,
264 include_remarks: Option<&Vec<String>>,
265 group_id: i32,
266) {
267 let mut node_index = 0;
268 let mut i = 0;
269
270 while i < nodes.len() {
271 if should_ignore(&nodes[i], exclude_remarks, include_remarks) {
272 println!(
274 "Node {} - {} has been ignored and will not be added.",
275 nodes[i].group, nodes[i].remark
276 );
277 nodes.remove(i);
278 } else {
279 println!(
281 "Node {} - {} has been added.",
282 nodes[i].group, nodes[i].remark
283 );
284 nodes[i].id = node_index;
285 nodes[i].group_id = group_id;
286 node_index += 1;
287 i += 1;
288 }
289 }
290}
291
292fn should_ignore(
294 node: &Proxy,
295 exclude_remarks: Option<&Vec<String>>,
296 include_remarks: Option<&Vec<String>>,
297) -> bool {
298 let mut excluded = false;
299 let mut included = true; if let Some(excludes) = exclude_remarks {
303 excluded = excludes.iter().any(|pattern| {
304 let mut real_rule = String::new();
305 if apply_matcher(pattern, &mut real_rule, node) {
306 if !real_rule.is_empty() {
307 reg_find(&node.remark, &real_rule)
308 } else {
309 pattern == &node.remark
310 }
311 } else {
312 false
313 }
314 });
315 }
316
317 if let Some(includes) = include_remarks {
319 if !includes.is_empty() {
320 included = includes.iter().any(|pattern| {
321 let mut real_rule = String::new();
322 if apply_matcher(pattern, &mut real_rule, node) {
323 if !real_rule.is_empty() {
324 reg_find(&node.remark, &real_rule)
325 } else {
326 pattern == &node.remark
327 }
328 } else {
329 false
330 }
331 });
332 }
333 }
334
335 excluded || !included
337}