proxy_scraper/
shadowsocks.rs

1use base64::{engine::general_purpose::URL_SAFE, Engine as _};
2use regex::Regex;
3
4/// Represents a Shadowsocks proxy.
5#[derive(Debug)]
6pub struct Shadowsocks {
7    /// The host address of the Shadowsocks proxy.
8    pub host: String,
9    /// The port number for the Shadowsocks proxy.
10    pub port: u32,
11    /// The password associated with the Shadowsocks proxy.
12    pub password: String,
13    /// The encryption method used by the Shadowsocks proxy.
14    pub method: String,
15}
16
17impl Shadowsocks {
18    /// Converts the Shadowsocks proxy information into a Shadowsocks URL.
19    ///
20    /// # Example
21    ///
22    /// ```
23    /// use proxy_scraper::shadowsocks::Shadowsocks;
24    /// let proxy = Shadowsocks {
25    ///     host: "example.com".to_string(),
26    ///     port: 443,
27    ///     password: "password".to_string(),
28    ///     method: "aes-256-gcm".to_string(),
29    /// };
30    /// let url = proxy.to_url();
31    /// assert_eq!(url, "ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ=@example.com:443");
32    /// ```
33    pub fn to_url(&self) -> String {
34        let base64_part = URL_SAFE.encode(format!("{}:{}", self.method, self.password));
35        format!("ss://{}@{}:{}", base64_part, self.host, self.port)
36    }
37
38    /// Scrapes Shadowsocks proxy information from the provided source string and returns a vector of Shadowsocks instances.
39    ///
40    /// # Arguments
41    ///
42    /// * `source` - A string containing the source code or text from which to extract Shadowsocks proxy information.
43    ///
44    /// # Returns
45    ///
46    /// A vector of `Shadowsocks` instances parsed from the source string.
47    pub fn scrape(source: &str) -> Vec<Self> {
48        let source = crate::utils::seperate_links(source);
49        let mut proxy_list: Vec<Shadowsocks> = Vec::new();
50        let regex = Regex::new(
51            r#"ss://((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)@((.+):(\d+))#"#,
52        )
53        .unwrap();
54
55        for captures in regex.captures_iter(&source) {
56            let base64_part = captures.get(1).map(|b64| b64.as_str()).unwrap_or("");
57            let host = captures.get(3).map(|h| h.as_str()).unwrap_or("");
58            let port: u32 = captures
59                .get(4)
60                .map(|p| p.as_str())
61                .unwrap_or("0")
62                .parse::<u32>()
63                .unwrap();
64
65            if base64_part.is_empty() || host.is_empty() || port == 0 {
66                continue;
67            }
68
69            let decoded_base64_part =
70                String::from_utf8(URL_SAFE.decode(&base64_part).unwrap()).unwrap();
71            let parts: Vec<&str> = decoded_base64_part.split(":").collect();
72
73            let method = parts[0].to_string();
74            let password = parts[1].to_string();
75
76            proxy_list.push(Shadowsocks {
77                host: host.to_string(),
78                port,
79                password,
80                method,
81            });
82        }
83        proxy_list
84    }
85}