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