thirtyfour_stealth/
lib.rs1pub use capabilities::DefaultCapabilitiesBuilder;
2use fetch_chromedriver::fetch_chromedriver;
3use patch_chromedriver::patch_chromedriver;
4use rand::Rng;
5use spawn_chromedriver::spawn_chromedriver;
6use std::{error::Error, fmt::Display, process::Child};
7pub use thirtyfour;
8use thirtyfour::WebDriver;
9mod capabilities;
10mod driver_ext;
11mod fetch_chromedriver;
12mod get_chrome_version;
13mod patch_chromedriver;
14mod spawn_chromedriver;
15pub use driver_ext::Chrome;
16
17pub async fn chrome() -> Result<(WebDriver, Child), Box<dyn std::error::Error + Send + Sync>> {
20 chrome_with_capabilities(DefaultCapabilitiesBuilder::new().into_chrome_caps()).await
21}
22
23pub async fn chrome_with_capabilities(
27 capabilities: thirtyfour::ChromeCapabilities,
28) -> Result<(WebDriver, Child), Box<dyn std::error::Error + Send + Sync>> {
29 let res = try_start_chrome(capabilities.clone(), 3).await;
30 if res.is_ok() {
31 return res;
32 }
33 let os = std::env::consts::OS;
34 let chromedriver_executable = match os {
35 "linux" => "chromedriver",
36 "macos" => "chromedriver",
37 "windows" => "chromedriver.exe",
38 _ => return Err(UnsupportedOS.into()),
39 };
40 let _ = std::fs::remove_file(chromedriver_executable);
41 let chromedriver_executable = get_patched_chrome_driver_executable()?;
42 let _ = std::fs::remove_file(chromedriver_executable);
43 try_start_chrome(capabilities, 3).await
44}
45
46pub async fn try_start_chrome(
49 capabilities: thirtyfour::ChromeCapabilities,
50 num_attempts: u8,
51) -> Result<(WebDriver, Child), Box<dyn std::error::Error + Send + Sync>> {
52 if std::path::Path::new("chromedriver").exists()
53 || std::path::Path::new("chromedriver.exe").exists()
54 {
55 tracing::info!("ChromeDriver already exists!");
56 } else {
57 tracing::info!("ChromeDriver does not exist! Fetching...");
58 fetch_chromedriver().await?;
59 }
60
61 if !is_existing_chromedriver_compatible()? {
64 tracing::warn!(
65 "Existing ChromeDriver appears incompatible with host arch. Re-downloading..."
66 );
67 let _ = std::fs::remove_file("chromedriver");
68 let _ = std::fs::remove_file("chromedriver.exe");
69 let _ = std::fs::remove_file("chromedriver_PATCHED");
70 let _ = std::fs::remove_file("chromedriver_PATCHED.exe");
71 fetch_chromedriver().await?;
72 }
73 let chromedriver_executable = get_patched_chrome_driver_executable()?;
74 if std::path::Path::new(chromedriver_executable).exists() {
75 tracing::info!("Detected patched chromedriver executable!");
76 } else {
77 patch_chromedriver(chromedriver_executable)?;
78 }
79 tracing::info!("Starting chromedriver...");
80 let port: u16 = rand::rng().random_range(2000..5000);
81 let mut chrome_driver_handle = spawn_chromedriver(chromedriver_executable, port)?;
82 let mut driver = None;
83 let mut attempt: u8 = 0u8;
84 while driver.is_none() && attempt < num_attempts {
85 attempt += 1;
86 match WebDriver::new(&format!("http://127.0.0.1:{}", port), capabilities.clone()).await {
87 Ok(d) => driver = Some(d),
88 Err(_) => tokio::time::sleep(std::time::Duration::from_millis(250)).await,
89 }
90 }
91 let driver = driver.ok_or_else(|| {
92 let _ = chrome_driver_handle.kill();
93 let _ = chrome_driver_handle.wait();
94 DriverCreationFailed
95 })?;
96 Ok((driver, chrome_driver_handle))
97}
98
99fn is_existing_chromedriver_compatible() -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
100 #[cfg(target_os = "macos")]
101 {
102 let arch = std::env::consts::ARCH; let candidate = if std::path::Path::new("chromedriver_PATCHED").exists() {
104 "chromedriver_PATCHED"
105 } else if std::path::Path::new("chromedriver").exists() {
106 "chromedriver"
107 } else {
108 return Ok(true);
109 };
110 let output = std::process::Command::new("/usr/bin/file")
111 .arg(candidate)
112 .output();
113 if let Ok(out) = output {
114 let stdout = String::from_utf8_lossy(&out.stdout).to_string();
115 let is_arm_expected = arch == "aarch64";
117 let mentions_arm64 = stdout.contains("arm64");
118 let mentions_x86_64 = stdout.contains("x86_64");
119 if is_arm_expected && mentions_x86_64 {
120 return Ok(false);
121 }
122 if !is_arm_expected && mentions_arm64 {
123 return Ok(false);
124 }
125 }
126 return Ok(true);
127 }
128 #[cfg(not(target_os = "macos"))]
129 {
130 Ok(true)
131 }
132}
133
134fn get_patched_chrome_driver_executable(
135) -> Result<&'static str, Box<dyn std::error::Error + Send + Sync>> {
136 let os = std::env::consts::OS;
137 let chromedriver_executable = match os {
138 "linux" => "chromedriver_PATCHED",
139 "macos" => "chromedriver_PATCHED",
140 "windows" => "chromedriver_PATCHED.exe",
141 _ => return Err(UnsupportedOS.into()),
142 };
143 Ok(chromedriver_executable)
144}
145
146#[derive(Debug)]
147struct DriverCreationFailed;
148
149impl Display for DriverCreationFailed {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 write!(f, "Driver creation failed.")
152 }
153}
154impl Error for DriverCreationFailed {}
155
156#[derive(Debug)]
157struct UnsupportedOS;
158
159impl Display for UnsupportedOS {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 write!(f, "Your OS is not supported.")
162 }
163}
164impl Error for UnsupportedOS {}