libsubconverter/parser/explodes/
common.rs

1use crate::utils::base64::url_safe_base64_decode;
2use crate::Proxy;
3
4/// Explode a proxy link into a Proxy object
5///
6/// This function detects the type of proxy link and calls the appropriate
7/// parser
8pub fn explode(link: &str, node: &mut Proxy) -> bool {
9    // Trim the link
10    let link = link.trim();
11
12    // Check for empty link
13    if link.is_empty() {
14        return false;
15    }
16
17    // Detect link type and call appropriate parser
18    if link.starts_with("vmess://") {
19        // Try new VMess parser first
20        if super::vmess::explode_std_vmess_new(link, node) {
21            return true;
22        }
23
24        // Try standard VMess parser first
25        if super::vmess::explode_vmess(link, node) {
26            return true;
27        }
28
29        // Try alternative VMess formats if standard parser fails
30        if super::vmess::explode_std_vmess(link, node) {
31            return true;
32        }
33
34        if super::vmess::explode_shadowrocket(link, node) {
35            return true;
36        }
37
38        if super::vmess::explode_kitsunebi(link, node) {
39            return true;
40        }
41
42        log::warn!("Failed to explode link: {}", link);
43
44        return false;
45    } else if link.starts_with("ss://") {
46        super::ss::explode_ss(link, node)
47    } else if link.starts_with("ssr://") {
48        // super::ssr::explode_ssr(link, node)
49        false
50    } else if link.starts_with("socks://")
51        || link.starts_with("https://t.me/socks")
52        || link.starts_with("tg://socks")
53    {
54        super::socks::explode_socks(link, node)
55    } else if link.starts_with("http://") || link.starts_with("https://") {
56        // Try HTTP parser first
57        if super::http::explode_http(link, node) {
58            return true;
59        }
60
61        // If that fails, try HTTP subscription format
62        super::httpsub::explode_http_sub(link, node)
63    } else if link.starts_with("trojan://") {
64        super::trojan::explode_trojan(link, node)
65    } else if link.starts_with("snell://") {
66        super::snell::explode_snell(link, node)
67    } else if link.starts_with("wg://") || link.starts_with("wireguard://") {
68        super::wireguard::explode_wireguard(link, node)
69    } else if link.starts_with("hysteria://") {
70        super::hysteria::explode_hysteria(link, node)
71    } else if link.starts_with("hysteria2://") || link.starts_with("hy2://") {
72        super::hysteria2::explode_hysteria2(link, node)
73    } else if link.starts_with("vmess+") {
74        false
75        // super::vmess::explode_std_vmess(link, node)
76    } else if link.starts_with("vless://") {
77        super::vless::explode_vless(link, node)
78    } else {
79        false
80    }
81}
82
83/// Explode a subscription content into a vector of Proxy objects
84///
85/// This function parses a subscription content (which may contain multiple
86/// proxy links) and returns a vector of Proxy objects
87pub fn explode_sub(sub: &str, nodes: &mut Vec<Proxy>) -> bool {
88    // Trim the subscription content
89    let sub = sub.trim();
90
91    // Check for empty subscription
92    if sub.is_empty() {
93        return false;
94    }
95
96    let mut processed = false;
97
98    // Try to parse as SSD configuration
99    if sub.starts_with("ssd://") {
100        if super::ss::explode_ssd(sub, nodes) {
101            processed = true;
102        }
103    }
104
105    // Try to parse as Clash configuration
106    if !processed
107        && (sub.contains("\"Proxy\":")
108            || sub.contains("\"proxies\":")
109            || sub.contains("Proxy:")
110            || sub.contains("proxies:"))
111    {
112        if super::explode_clash::explode_clash(sub, nodes) {
113            processed = true;
114        }
115    }
116
117    // Try to parse as Surge configuration
118    if !processed && super::surge::explode_surge(sub, nodes) {
119        processed = true;
120    }
121
122    // If no specific format was detected, try as a normal subscription
123    if !processed {
124        // Try to decode as base64
125        let decoded = url_safe_base64_decode(sub);
126
127        // Check if it's a Surge format after decoding
128        if decoded.contains("vmess=")
129            || decoded.contains("shadowsocks=")
130            || decoded.contains("http=")
131            || decoded.contains("trojan=")
132        {
133            if super::surge::explode_surge(&decoded, nodes) {
134                return true;
135            }
136        }
137
138        // Split by newlines or spaces depending on content
139        let delimiter = if decoded.contains('\n') {
140            '\n'
141        } else if decoded.contains('\r') {
142            '\r'
143        } else {
144            ' '
145        };
146
147        let lines: Vec<&str> = decoded.split(delimiter).collect();
148
149        log::info!("Found {} lines in explode_sub process", lines.len());
150
151        for line in lines {
152            let line = line.trim().trim_end_matches('\r');
153            if line.is_empty() {
154                continue;
155            }
156
157            let mut node = Proxy::default();
158            if explode(line, &mut node) {
159                nodes.push(node);
160            }
161        }
162    }
163
164    !nodes.is_empty()
165}
166
167/// Explodes a configuration file content into a vector of Proxy objects
168///
169/// Attempts to detect and parse various configuration formats like
170/// Clash, SSD, Surge, Quantumult, etc., and converts them to Proxy objects.
171///
172/// # Arguments
173/// * `content` - The configuration content as a string
174/// * `nodes` - Vector to store the parsed Proxy objects
175///
176/// # Returns
177/// Number of nodes successfully parsed, or 0 if parsing failed
178pub fn explode_conf_content(content: &str, nodes: &mut Vec<Proxy>) -> i32 {
179    // Trim the content
180    let content = content.trim();
181
182    // Check for empty content
183    if content.is_empty() {
184        return 0;
185    }
186
187    let orig_size = nodes.len();
188    let mut parsed = false;
189
190    // Try to parse as JSON
191    if content.starts_with('{') {
192        // Try to parse as V2Ray configuration
193        if super::vmess::explode_vmess_conf(content, nodes) {
194            parsed = true;
195        }
196        // Try Netch configuration
197        else if content.contains("\"server\"") && content.contains("\"port\"") {
198            if super::netch::explode_netch_conf(content, nodes) {
199                parsed = true;
200            }
201        }
202    }
203    // Try to parse as YAML/Clash
204    else if content.contains("proxies:") || content.contains("Proxy:") {
205        if super::explode_clash::explode_clash(content, nodes) {
206            parsed = true;
207        }
208    }
209    // Try to parse as SSD
210    else if content.starts_with("ssd://") {
211        if super::ss::explode_ssd(content, nodes) {
212            parsed = true;
213        }
214    }
215    // Try to parse as SSTap configuration
216    else if content.contains("\"servers\":") || content.contains("\"configs\":") {
217        if super::sstap::explode_sstap(content, nodes) {
218            parsed = true;
219        }
220    }
221    // Try to parse as Surge configuration
222    else if content.contains("[Proxy]") {
223        if super::surge::explode_surge(content, nodes) {
224            parsed = true;
225        }
226    }
227    // Try to parse as Quantumult configuration
228    else if content.contains(" = vmess")
229        || content.contains(" = shadowsocks")
230        || content.contains(" = shadowsocksr")
231        || content.contains(" = http")
232        || content.contains(" = trojan")
233    {
234        if super::quan::explode_quan(content, nodes) {
235            parsed = true;
236        }
237    }
238
239    // If no specific format was detected, try as a simple subscription
240    if !parsed && explode_sub(content, nodes) {
241        parsed = true;
242    }
243
244    if parsed {
245        (nodes.len() - orig_size) as i32
246    } else {
247        0
248    }
249}