undetected_chromedriver/
lib.rs1use rand::Rng;
2#[cfg(any(target_os = "linux", target_os = "macos"))]
3use std::os::unix::fs::PermissionsExt;
4use std::process::Command;
5use thirtyfour::{DesiredCapabilities, WebDriver};
6
7pub async fn chrome() -> Result<WebDriver, Box<dyn std::error::Error>> {
10 let os = std::env::consts::OS;
11 if std::path::Path::new("chromedriver").exists()
12 || std::path::Path::new("chromedriver.exe").exists()
13 {
14 println!("ChromeDriver already exists!");
15 } else {
16 println!("ChromeDriver does not exist! Fetching...");
17 let client = reqwest::Client::new();
18 fetch_chromedriver(&client).await.unwrap();
19 }
20 let chromedriver_executable = match os {
21 "linux" => "chromedriver_PATCHED",
22 "macos" => "chromedriver_PATCHED",
23 "windows" => "chromedriver_PATCHED.exe",
24 _ => panic!("Unsupported OS!"),
25 };
26 match !std::path::Path::new(chromedriver_executable).exists() {
27 true => {
28 println!("Starting ChromeDriver executable patch...");
29 let file_name = if cfg!(windows) {
30 "chromedriver.exe"
31 } else {
32 "chromedriver"
33 };
34 let f = std::fs::read(file_name).unwrap();
35 let mut new_chromedriver_bytes = f.clone();
36 let mut total_cdc = String::from("");
37 let mut cdc_pos_list = Vec::new();
38 let mut is_cdc_present = false;
39 let mut patch_ct = 0;
40 for i in 0..f.len() - 3 {
41 if "cdc_"
42 == format!(
43 "{}{}{}{}",
44 f[i] as char,
45 f[i + 1] as char,
46 f[i + 2] as char,
47 f[i + 3] as char
48 )
49 .as_str()
50 {
51 for x in i + 4..i + 22 {
52 total_cdc.push_str(&(f[x] as char).to_string());
53 }
54 is_cdc_present = true;
55 cdc_pos_list.push(i);
56 total_cdc = String::from("");
57 }
58 }
59 match is_cdc_present {
60 true => println!("Found cdcs!"),
61 false => println!("No cdcs were found!"),
62 }
63 let get_random_char = || -> char {
64 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
65 .chars()
66 .collect::<Vec<char>>()[rand::thread_rng().gen_range(0..48)]
67 };
68
69 for i in cdc_pos_list {
70 for x in i + 4..i + 22 {
71 new_chromedriver_bytes[x] = get_random_char() as u8;
72 }
73 patch_ct += 1;
74 }
75 println!("Patched {} cdcs!", patch_ct);
76
77 println!("Starting to write to binary file...");
78 let _file = std::fs::File::create(chromedriver_executable).unwrap();
79 match std::fs::write(chromedriver_executable, new_chromedriver_bytes) {
80 Ok(_res) => {
81 println!("Successfully wrote patched executable to 'chromedriver_PATCHED'!",)
82 }
83 Err(err) => println!("Error when writing patch to file! Error: {}", err),
84 };
85 }
86 false => {
87 println!("Detected patched chromedriver executable!");
88 }
89 }
90 #[cfg(any(target_os = "linux", target_os = "macos"))]
91 {
92 let mut perms = std::fs::metadata(chromedriver_executable)
93 .unwrap()
94 .permissions();
95 perms.set_mode(0o755);
96 std::fs::set_permissions(chromedriver_executable, perms).unwrap();
97 }
98 println!("Starting chromedriver...");
99 let port: usize = rand::thread_rng().gen_range(2000..5000);
100 Command::new(format!("./{}", chromedriver_executable))
101 .arg(format!("--port={}", port))
102 .spawn()
103 .expect("Failed to start chromedriver!");
104 let mut caps = DesiredCapabilities::chrome();
105 caps.set_no_sandbox().unwrap();
106 caps.set_disable_dev_shm_usage().unwrap();
107 caps.add_chrome_arg("--disable-blink-features=AutomationControlled")
108 .unwrap();
109 caps.add_chrome_arg("window-size=1920,1080").unwrap();
110 caps.add_chrome_arg("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36").unwrap();
111 caps.add_chrome_arg("disable-infobars").unwrap();
112 caps.add_chrome_option("excludeSwitches", ["enable-automation"])
113 .unwrap();
114 let mut driver = None;
115 let mut attempt = 0;
116 while driver.is_none() && attempt < 20 {
117 attempt += 1;
118 match WebDriver::new(&format!("http://localhost:{}", port), caps.clone()).await {
119 Ok(d) => driver = Some(d),
120 Err(_) => std::thread::sleep(std::time::Duration::from_millis(250)),
121 }
122 }
123 let driver = driver.unwrap();
124 Ok(driver)
125}
126
127async fn fetch_chromedriver(client: &reqwest::Client) -> Result<(), Box<dyn std::error::Error>> {
128 let os = std::env::consts::OS;
129
130 let installed_version = get_chrome_version(os).await?;
131 let chromedriver_url: String;
132 if installed_version.as_str() >= "114" {
133 let url = "https://googlechromelabs.github.io/chrome-for-testing/latest-versions-per-milestone.json";
135 let resp = client.get(url).send().await?;
136 let body = resp.bytes().await?;
137 let json = serde_json::from_slice::<serde_json::Value>(&body)?;
138 let version = json["milestones"][installed_version]["version"]
139 .as_str()
140 .unwrap();
141
142 chromedriver_url = match os {
144 "linux" => format!(
145 "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/{}/{}/{}",
146 version, "linux64", "chromedriver-linux64.zip"
147 ),
148 "macos" => format!(
149 "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/{}/{}/{}",
150 version, "mac-x64", "chromedriver-mac-x64.zip"
151 ),
152 "windows" => format!(
153 "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/{}/{}/{}",
154 version, "win64", "chromedriver-win64.zip"
155 ),
156 _ => panic!("Unsupported OS!"),
157 };
158 } else {
159 let resp = client
160 .get(format!(
161 "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{}",
162 installed_version
163 ))
164 .send()
165 .await?;
166 let body = resp.text().await?;
167 chromedriver_url = match os {
168 "linux" => format!(
169 "https://chromedriver.storage.googleapis.com/{}/chromedriver_linux64.zip",
170 body
171 ),
172 "windows" => format!(
173 "https://chromedriver.storage.googleapis.com/{}/chromedriver_win32.zip",
174 body
175 ),
176 "macos" => format!(
177 "https://chromedriver.storage.googleapis.com/{}/chromedriver_mac64.zip",
178 body
179 ),
180 _ => panic!("Unsupported OS!"),
181 };
182 }
183
184 let resp = client.get(&chromedriver_url).send().await?;
185 let body = resp.bytes().await?;
186
187 let mut archive = zip::ZipArchive::new(std::io::Cursor::new(body))?;
188 for i in 0..archive.len() {
189 let mut file = archive.by_index(i)?;
190 let outpath = file.mangled_name();
191 if file.name().ends_with('/') {
192 std::fs::create_dir_all(&outpath)?;
193 } else {
194 let outpath_relative = outpath.file_name().unwrap();
195 let mut outfile = std::fs::File::create(outpath_relative)?;
196 std::io::copy(&mut file, &mut outfile)?;
197 }
198 }
199 Ok(())
200}
201
202async fn get_chrome_version(os: &str) -> Result<String, Box<dyn std::error::Error>> {
203 println!("Getting installed Chrome version...");
204 let command = match os {
205 "linux" => Command::new("/usr/bin/google-chrome")
206 .arg("--version")
207 .output()?,
208 "macos" => Command::new("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")
209 .arg("--version")
210 .output()?,
211 "windows" => Command::new("powershell")
212 .arg("-c")
213 .arg("(Get-Item 'C:/Program Files/Google/Chrome/Application/chrome.exe').VersionInfo")
214 .output()?,
215 _ => panic!("Unsupported OS!"),
216 };
217 let output = String::from_utf8(command.stdout)?;
218
219 let version = output
220 .lines()
221 .flat_map(|line| line.chars().filter(|&ch| ch.is_ascii_digit()))
222 .take(3)
223 .collect::<String>();
224
225 println!("Currently installed Chrome version: {}", version);
226 Ok(version)
227}