pub struct BrowserBuilder { /* private fields */ }Implementations§
Source§impl BrowserBuilder
impl BrowserBuilder
pub fn new() -> Self
Sourcepub fn headful(self) -> Self
pub fn headful(self) -> Self
Run with visible window.
Examples found in repository?
examples/youtube_test.rs (line 8)
4async fn main() {
5 println!("=== YouTube Test ===\n");
6
7 let browser = Browser::builder()
8 .headful()
9 .profile(7, 777)
10 .build()
11 .await
12 .expect("launch failed");
13
14 let page = browser.new_page("https://www.youtube.com").await.expect("nav failed");
15 page.wait(3000).await;
16
17 let title = page.js("document.title").await.unwrap_or_default();
18 let url = page.url().await.unwrap_or_default();
19 println!("Title: {title}");
20 println!("URL: {url}");
21
22 println!("\nBrowser open. Ctrl+C to exit.");
23 loop {
24 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
25 }
26}More examples
examples/smoke_test.rs (line 8)
4async fn main() {
5 println!("=== Smoke Test ===\n");
6
7 let browser = Browser::builder()
8 .headful()
9 .profile(7, 777)
10 .build()
11 .await
12 .expect("launch failed");
13
14 let page = browser.new_page("about:blank").await.expect("page failed");
15
16 let checks = &[
17 ("webdriver", "navigator.webdriver.toString()"),
18 ("timezone", "Intl.DateTimeFormat().resolvedOptions().timeZone"),
19 ("cores", "navigator.hardwareConcurrency.toString()"),
20 ("screen", "screen.width+'x'+screen.height"),
21 ("UA", "navigator.userAgent"),
22 ];
23 for (name, expr) in checks {
24 let val = page.js(expr).await.unwrap_or_default();
25 println!("{name}: {val}");
26 }
27
28 println!("\nBrowser open. Ctrl+C to exit.");
29 loop {
30 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
31 }
32}examples/game_test.rs (line 53)
30async fn main() {
31 load_dotenv();
32 println!("=== Game Test + WS Detection ===\n");
33
34 // 1. Fetch game URL
35 println!("Fetching game URL...");
36 let client = reqwest::Client::new();
37 let resp = client
38 .post(api_url())
39 .header("Authorization", format!("Bearer {}", api_token()))
40 .header("Content-Type", "application/json")
41 .header("Accept", "application/json")
42 .body(r#"{"user_id": "beezsbee"}"#)
43 .send()
44 .await
45 .expect("API request failed");
46
47 let body: serde_json::Value = resp.json().await.expect("JSON parse failed");
48 let game_url = body["url"].as_str().expect("no 'url' in response");
49 println!("Game URL: {game_url}\n");
50
51 // 2. Launch browser
52 let browser = Browser::builder()
53 .headful()
54 .profile(42, 12345)
55 .build()
56 .await
57 .expect("browser launch failed");
58
59 // 3. Open blank page first so we can set up listeners before navigation
60 let page = browser.new_page("about:blank").await.expect("page failed");
61
62 // Enable network domain for WS events
63 page.cdp().execute(EnableParams::default()).await.expect("network enable failed");
64
65 // Set up WS event listeners
66 let mut ws_created = page.cdp().event_listener::<EventWebSocketCreated>().await.expect("listener failed");
67 let mut ws_closed = page.cdp().event_listener::<EventWebSocketClosed>().await.expect("listener failed");
68
69 // Spawn listener tasks
70 tokio::spawn(async move {
71 while let Some(ev) = ws_created.next().await {
72 println!("[WS CREATED] request_id={:?} url={}", ev.request_id, ev.url);
73 }
74 });
75
76 tokio::spawn(async move {
77 while let Some(ev) = ws_closed.next().await {
78 println!("[WS CLOSED] request_id={:?}", ev.request_id);
79 }
80 });
81
82 // 4. Navigate to game (human simulation runs automatically)
83 println!("Navigating to game...");
84 page.navigate(game_url).await.expect("nav failed");
85
86 // 5. Wait and monitor
87 println!("Watching for WebSocket connections...\n");
88 loop {
89 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
90 }
91}examples/tls_check.rs (line 26)
21async fn main() {
22 load_dotenv();
23 println!("=== TLS/HTTP2 Fingerprint Check ===\n");
24
25 let browser = Browser::builder()
26 .headful()
27 .random()
28 .build()
29 .await
30 .expect("browser launch failed");
31
32 let page = browser.new_page("about:blank").await.expect("page failed");
33
34 // 1. Check TLS fingerprint
35 println!("--- tls.peet.ws ---");
36 page.goto("https://tls.peet.ws/api/all").await.expect("nav failed");
37 tokio::time::sleep(std::time::Duration::from_secs(3)).await;
38
39 let body = page.js("document.body.innerText").await.unwrap_or_default();
40 if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
41 // TLS
42 if let Some(tls) = v.get("tls") {
43 println!("JA3 hash: {}", tls.get("ja3_hash").and_then(|v| v.as_str()).unwrap_or("N/A"));
44 println!("JA3: {}", tls.get("ja3").and_then(|v| v.as_str()).unwrap_or("N/A"));
45 println!("JA4: {}", tls.get("ja4").and_then(|v| v.as_str()).unwrap_or("N/A"));
46 println!("TLS version: {}", tls.get("version").and_then(|v| v.as_str()).unwrap_or("N/A"));
47 }
48
49 // HTTP2
50 println!();
51 if let Some(h2) = v.get("http2") {
52 println!("HTTP2 fingerprint: {}", h2.get("akamai_fingerprint").and_then(|v| v.as_str()).unwrap_or("N/A"));
53 println!("Pseudo-header order: {}", h2.get("sent_pseudo_header_fields_order").unwrap_or(&serde_json::Value::Null));
54 if let Some(settings) = h2.get("sent_settings") {
55 println!("SETTINGS: {}", settings);
56 }
57 }
58
59 // HTTP version used
60 println!();
61 if let Some(ip) = v.get("ip") {
62 println!("Client IP: {}", ip.as_str().unwrap_or("N/A"));
63 }
64 if let Some(http_version) = v.get("http_version") {
65 println!("HTTP version: {}", http_version.as_str().unwrap_or("N/A"));
66 }
67
68 // User-Agent
69 println!();
70 if let Some(h1) = v.get("http1") {
71 if let Some(headers) = h1.get("headers") {
72 if let Some(arr) = headers.as_array() {
73 for h in arr {
74 let name = h.get("name").and_then(|n| n.as_str()).unwrap_or("");
75 let val = h.get("value").and_then(|n| n.as_str()).unwrap_or("");
76 if name.to_lowercase() == "user-agent" {
77 println!("User-Agent: {}", val);
78 }
79 }
80 }
81 }
82 }
83 } else {
84 println!("Failed to parse response: {}", &body[..body.len().min(500)]);
85 }
86
87 // 2. Check known Chrome 134 reference values
88 println!("\n--- Expected Chrome 134 stock values ---");
89 println!("JA3 hash: cd49876058a26ccf43e241ccc4e4b5d3 (Chrome 134 Windows)");
90 println!("JA4: t13d1517h2_8daaf6152771_02713d6af8 (Chrome 134 Windows)");
91 println!("HTTP2 Akamai: 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p");
92
93 println!("\nBrowser still open. Press Ctrl+C to exit.");
94 loop {
95 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
96 }
97}examples/batch_test.rs (line 176)
110async fn main() {
111 load_dotenv();
112 let total = PER_ROUND * ROUNDS;
113 println!("=== Proxy batch: {}×{} = {} | live {}s | rotate {}s cooldown ===\n",
114 PER_ROUND, ROUNDS, total, BROWSER_LIVE_SECS, ROTATE_COOLDOWN_SECS);
115
116 let start = Instant::now();
117 let mut ok = 0u32;
118 let mut fail = 0u32;
119
120 for round in 0..ROUNDS {
121 println!("--- Round {}/{} ---", round + 1, ROUNDS);
122
123 // 1. Rotate IP and get proxy
124 let proxy = match rotate_and_get_proxy().await {
125 Some(p) => p,
126 None => {
127 println!(" Skip round — no proxy");
128 fail += PER_ROUND as u32;
129 continue;
130 }
131 };
132
133 // 2. Verify proxy works
134 if !verify_proxy(&proxy).await {
135 println!(" Skip round — proxy dead");
136 fail += PER_ROUND as u32;
137 continue;
138 }
139
140 // 3. Launch 3 browsers in parallel
141 let offset = round * PER_ROUND;
142 let mut handles = Vec::new();
143
144 for i in 0..PER_ROUND {
145 let idx = offset + i;
146 let user_id = USERS[idx % USERS.len()].to_string();
147 let proxy = proxy.clone();
148 let api = api_url();
149 let token = api_token();
150
151 handles.push(tokio::spawn(async move {
152 let label = format!("[{:02}/{}]", idx + 1, total);
153 let t = Instant::now();
154
155 // Get game URL (direct, no proxy)
156 let game_url = match reqwest::Client::new()
157 .post(&api)
158 .header("Authorization", format!("Bearer {}", token))
159 .header("Content-Type", "application/json")
160 .body(format!(r#"{{"user_id": "{}"}}"#, user_id))
161 .send().await
162 {
163 Ok(resp) => {
164 let body: serde_json::Value = resp.json().await.unwrap_or_default();
165 match body["url"].as_str() {
166 Some(u) if !u.is_empty() => u.to_string(),
167 _ => return format!("{label} FAIL api: {body}"),
168 }
169 }
170 Err(e) => return format!("{label} FAIL api: {e}"),
171 };
172 let api_ms = t.elapsed().as_millis();
173
174 // Launch browser with proxy
175 let browser = match Browser::builder()
176 .headful()
177 .random()
178 .proxy(&proxy)
179 .build().await
180 {
181 Ok(b) => b,
182 Err(e) => return format!("{label} FAIL launch: {e}"),
183 };
184 let launch_ms = t.elapsed().as_millis() - api_ms;
185
186 // Navigate (human simulation auto-starts with aggressive scroll)
187 let page = match browser.new_page("about:blank").await {
188 Ok(p) => p,
189 Err(e) => return format!("{label} FAIL page: {e}"),
190 };
191
192 // Aggressive scrolling in background
193 let scroll_page = page.cdp().clone();
194 let scroll_handle = tokio::spawn(async move {
195 use chromiumoxide::cdp::browser_protocol::input::{
196 DispatchMouseEventParams, DispatchMouseEventType,
197 };
198 let mut y = 300.0f64;
199 loop {
200 // Scroll down aggressively
201 let mut ev = DispatchMouseEventParams::new(
202 DispatchMouseEventType::MouseWheel, 500.0, y,
203 );
204 ev.delta_x = Some(0.0);
205 ev.delta_y = Some(300.0);
206 let _ = scroll_page.execute(ev).await;
207 y = ((y + 50.0) % 800.0) + 100.0;
208 tokio::time::sleep(std::time::Duration::from_millis(800)).await;
209 }
210 });
211
212 let t_nav = Instant::now();
213 if let Err(e) = page.goto(&game_url).await {
214 scroll_handle.abort();
215 return format!("{label} FAIL nav: {e}");
216 }
217 let nav_ms = t_nav.elapsed().as_millis();
218
219 // Wait for cookies
220 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
221
222 let cookies = page.cdp().get_cookies().await.unwrap_or_default();
223 let has_evo = cookies.iter().any(|c| c.name == "EVOSESSIONID");
224 let evo = cookies.iter().find(|c| c.name == "EVOSESSIONID")
225 .map(|c| c.value.clone())
226 .unwrap_or_else(|| "N/A".into());
227 let cookie_names: Vec<String> = cookies.iter().map(|c| c.name.clone()).collect();
228
229 // Stay alive for remaining time (human sim + aggressive scroll running)
230 let elapsed = t.elapsed().as_secs();
231 if elapsed < BROWSER_LIVE_SECS {
232 tokio::time::sleep(std::time::Duration::from_secs(BROWSER_LIVE_SECS - elapsed)).await;
233 }
234
235 // Close browser
236 scroll_handle.abort();
237 let _ = browser.close().await;
238 let total_ms = t.elapsed().as_millis();
239
240 let status = if has_evo { "OK" } else { "MISS" };
241 format!("{label} {status} | {user_id:<18} | total:{total_ms}ms api:{api_ms}ms launch:{launch_ms}ms nav:{nav_ms}ms | cookies:{} | EVO:{evo}\n {:?}",
242 cookies.len(), cookie_names)
243 }));
244 }
245
246 for h in handles {
247 match h.await {
248 Ok(msg) => {
249 if msg.contains("] OK ") { ok += 1; } else { fail += 1; }
250 println!("{msg}");
251 }
252 Err(e) => { println!(" task panic: {e}"); fail += 1; }
253 }
254 }
255
256 println!();
257 }
258
259 let elapsed = start.elapsed().as_secs_f64();
260 println!("=== OK:{ok} FAIL:{fail} | {elapsed:.0}s total | {:.1}s/profile ===",
261 elapsed / total as f64);
262}Sourcepub fn chrome_path(self, path: impl Into<String>) -> Self
pub fn chrome_path(self, path: impl Into<String>) -> Self
Path to chrome.exe. Falls back to CLAWSER_CHROME_PATH env.
Sourcepub fn profile(self, index: usize, seed: u64) -> Self
pub fn profile(self, index: usize, seed: u64) -> Self
Use hardware profile index (0..99) with deterministic seed.
Examples found in repository?
examples/youtube_test.rs (line 9)
4async fn main() {
5 println!("=== YouTube Test ===\n");
6
7 let browser = Browser::builder()
8 .headful()
9 .profile(7, 777)
10 .build()
11 .await
12 .expect("launch failed");
13
14 let page = browser.new_page("https://www.youtube.com").await.expect("nav failed");
15 page.wait(3000).await;
16
17 let title = page.js("document.title").await.unwrap_or_default();
18 let url = page.url().await.unwrap_or_default();
19 println!("Title: {title}");
20 println!("URL: {url}");
21
22 println!("\nBrowser open. Ctrl+C to exit.");
23 loop {
24 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
25 }
26}More examples
examples/smoke_test.rs (line 9)
4async fn main() {
5 println!("=== Smoke Test ===\n");
6
7 let browser = Browser::builder()
8 .headful()
9 .profile(7, 777)
10 .build()
11 .await
12 .expect("launch failed");
13
14 let page = browser.new_page("about:blank").await.expect("page failed");
15
16 let checks = &[
17 ("webdriver", "navigator.webdriver.toString()"),
18 ("timezone", "Intl.DateTimeFormat().resolvedOptions().timeZone"),
19 ("cores", "navigator.hardwareConcurrency.toString()"),
20 ("screen", "screen.width+'x'+screen.height"),
21 ("UA", "navigator.userAgent"),
22 ];
23 for (name, expr) in checks {
24 let val = page.js(expr).await.unwrap_or_default();
25 println!("{name}: {val}");
26 }
27
28 println!("\nBrowser open. Ctrl+C to exit.");
29 loop {
30 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
31 }
32}examples/game_test.rs (line 54)
30async fn main() {
31 load_dotenv();
32 println!("=== Game Test + WS Detection ===\n");
33
34 // 1. Fetch game URL
35 println!("Fetching game URL...");
36 let client = reqwest::Client::new();
37 let resp = client
38 .post(api_url())
39 .header("Authorization", format!("Bearer {}", api_token()))
40 .header("Content-Type", "application/json")
41 .header("Accept", "application/json")
42 .body(r#"{"user_id": "beezsbee"}"#)
43 .send()
44 .await
45 .expect("API request failed");
46
47 let body: serde_json::Value = resp.json().await.expect("JSON parse failed");
48 let game_url = body["url"].as_str().expect("no 'url' in response");
49 println!("Game URL: {game_url}\n");
50
51 // 2. Launch browser
52 let browser = Browser::builder()
53 .headful()
54 .profile(42, 12345)
55 .build()
56 .await
57 .expect("browser launch failed");
58
59 // 3. Open blank page first so we can set up listeners before navigation
60 let page = browser.new_page("about:blank").await.expect("page failed");
61
62 // Enable network domain for WS events
63 page.cdp().execute(EnableParams::default()).await.expect("network enable failed");
64
65 // Set up WS event listeners
66 let mut ws_created = page.cdp().event_listener::<EventWebSocketCreated>().await.expect("listener failed");
67 let mut ws_closed = page.cdp().event_listener::<EventWebSocketClosed>().await.expect("listener failed");
68
69 // Spawn listener tasks
70 tokio::spawn(async move {
71 while let Some(ev) = ws_created.next().await {
72 println!("[WS CREATED] request_id={:?} url={}", ev.request_id, ev.url);
73 }
74 });
75
76 tokio::spawn(async move {
77 while let Some(ev) = ws_closed.next().await {
78 println!("[WS CLOSED] request_id={:?}", ev.request_id);
79 }
80 });
81
82 // 4. Navigate to game (human simulation runs automatically)
83 println!("Navigating to game...");
84 page.navigate(game_url).await.expect("nav failed");
85
86 // 5. Wait and monitor
87 println!("Watching for WebSocket connections...\n");
88 loop {
89 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
90 }
91}Sourcepub fn random(self) -> Self
pub fn random(self) -> Self
Use a random hardware profile.
Examples found in repository?
examples/tls_check.rs (line 27)
21async fn main() {
22 load_dotenv();
23 println!("=== TLS/HTTP2 Fingerprint Check ===\n");
24
25 let browser = Browser::builder()
26 .headful()
27 .random()
28 .build()
29 .await
30 .expect("browser launch failed");
31
32 let page = browser.new_page("about:blank").await.expect("page failed");
33
34 // 1. Check TLS fingerprint
35 println!("--- tls.peet.ws ---");
36 page.goto("https://tls.peet.ws/api/all").await.expect("nav failed");
37 tokio::time::sleep(std::time::Duration::from_secs(3)).await;
38
39 let body = page.js("document.body.innerText").await.unwrap_or_default();
40 if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
41 // TLS
42 if let Some(tls) = v.get("tls") {
43 println!("JA3 hash: {}", tls.get("ja3_hash").and_then(|v| v.as_str()).unwrap_or("N/A"));
44 println!("JA3: {}", tls.get("ja3").and_then(|v| v.as_str()).unwrap_or("N/A"));
45 println!("JA4: {}", tls.get("ja4").and_then(|v| v.as_str()).unwrap_or("N/A"));
46 println!("TLS version: {}", tls.get("version").and_then(|v| v.as_str()).unwrap_or("N/A"));
47 }
48
49 // HTTP2
50 println!();
51 if let Some(h2) = v.get("http2") {
52 println!("HTTP2 fingerprint: {}", h2.get("akamai_fingerprint").and_then(|v| v.as_str()).unwrap_or("N/A"));
53 println!("Pseudo-header order: {}", h2.get("sent_pseudo_header_fields_order").unwrap_or(&serde_json::Value::Null));
54 if let Some(settings) = h2.get("sent_settings") {
55 println!("SETTINGS: {}", settings);
56 }
57 }
58
59 // HTTP version used
60 println!();
61 if let Some(ip) = v.get("ip") {
62 println!("Client IP: {}", ip.as_str().unwrap_or("N/A"));
63 }
64 if let Some(http_version) = v.get("http_version") {
65 println!("HTTP version: {}", http_version.as_str().unwrap_or("N/A"));
66 }
67
68 // User-Agent
69 println!();
70 if let Some(h1) = v.get("http1") {
71 if let Some(headers) = h1.get("headers") {
72 if let Some(arr) = headers.as_array() {
73 for h in arr {
74 let name = h.get("name").and_then(|n| n.as_str()).unwrap_or("");
75 let val = h.get("value").and_then(|n| n.as_str()).unwrap_or("");
76 if name.to_lowercase() == "user-agent" {
77 println!("User-Agent: {}", val);
78 }
79 }
80 }
81 }
82 }
83 } else {
84 println!("Failed to parse response: {}", &body[..body.len().min(500)]);
85 }
86
87 // 2. Check known Chrome 134 reference values
88 println!("\n--- Expected Chrome 134 stock values ---");
89 println!("JA3 hash: cd49876058a26ccf43e241ccc4e4b5d3 (Chrome 134 Windows)");
90 println!("JA4: t13d1517h2_8daaf6152771_02713d6af8 (Chrome 134 Windows)");
91 println!("HTTP2 Akamai: 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p");
92
93 println!("\nBrowser still open. Press Ctrl+C to exit.");
94 loop {
95 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
96 }
97}More examples
examples/batch_test.rs (line 177)
110async fn main() {
111 load_dotenv();
112 let total = PER_ROUND * ROUNDS;
113 println!("=== Proxy batch: {}×{} = {} | live {}s | rotate {}s cooldown ===\n",
114 PER_ROUND, ROUNDS, total, BROWSER_LIVE_SECS, ROTATE_COOLDOWN_SECS);
115
116 let start = Instant::now();
117 let mut ok = 0u32;
118 let mut fail = 0u32;
119
120 for round in 0..ROUNDS {
121 println!("--- Round {}/{} ---", round + 1, ROUNDS);
122
123 // 1. Rotate IP and get proxy
124 let proxy = match rotate_and_get_proxy().await {
125 Some(p) => p,
126 None => {
127 println!(" Skip round — no proxy");
128 fail += PER_ROUND as u32;
129 continue;
130 }
131 };
132
133 // 2. Verify proxy works
134 if !verify_proxy(&proxy).await {
135 println!(" Skip round — proxy dead");
136 fail += PER_ROUND as u32;
137 continue;
138 }
139
140 // 3. Launch 3 browsers in parallel
141 let offset = round * PER_ROUND;
142 let mut handles = Vec::new();
143
144 for i in 0..PER_ROUND {
145 let idx = offset + i;
146 let user_id = USERS[idx % USERS.len()].to_string();
147 let proxy = proxy.clone();
148 let api = api_url();
149 let token = api_token();
150
151 handles.push(tokio::spawn(async move {
152 let label = format!("[{:02}/{}]", idx + 1, total);
153 let t = Instant::now();
154
155 // Get game URL (direct, no proxy)
156 let game_url = match reqwest::Client::new()
157 .post(&api)
158 .header("Authorization", format!("Bearer {}", token))
159 .header("Content-Type", "application/json")
160 .body(format!(r#"{{"user_id": "{}"}}"#, user_id))
161 .send().await
162 {
163 Ok(resp) => {
164 let body: serde_json::Value = resp.json().await.unwrap_or_default();
165 match body["url"].as_str() {
166 Some(u) if !u.is_empty() => u.to_string(),
167 _ => return format!("{label} FAIL api: {body}"),
168 }
169 }
170 Err(e) => return format!("{label} FAIL api: {e}"),
171 };
172 let api_ms = t.elapsed().as_millis();
173
174 // Launch browser with proxy
175 let browser = match Browser::builder()
176 .headful()
177 .random()
178 .proxy(&proxy)
179 .build().await
180 {
181 Ok(b) => b,
182 Err(e) => return format!("{label} FAIL launch: {e}"),
183 };
184 let launch_ms = t.elapsed().as_millis() - api_ms;
185
186 // Navigate (human simulation auto-starts with aggressive scroll)
187 let page = match browser.new_page("about:blank").await {
188 Ok(p) => p,
189 Err(e) => return format!("{label} FAIL page: {e}"),
190 };
191
192 // Aggressive scrolling in background
193 let scroll_page = page.cdp().clone();
194 let scroll_handle = tokio::spawn(async move {
195 use chromiumoxide::cdp::browser_protocol::input::{
196 DispatchMouseEventParams, DispatchMouseEventType,
197 };
198 let mut y = 300.0f64;
199 loop {
200 // Scroll down aggressively
201 let mut ev = DispatchMouseEventParams::new(
202 DispatchMouseEventType::MouseWheel, 500.0, y,
203 );
204 ev.delta_x = Some(0.0);
205 ev.delta_y = Some(300.0);
206 let _ = scroll_page.execute(ev).await;
207 y = ((y + 50.0) % 800.0) + 100.0;
208 tokio::time::sleep(std::time::Duration::from_millis(800)).await;
209 }
210 });
211
212 let t_nav = Instant::now();
213 if let Err(e) = page.goto(&game_url).await {
214 scroll_handle.abort();
215 return format!("{label} FAIL nav: {e}");
216 }
217 let nav_ms = t_nav.elapsed().as_millis();
218
219 // Wait for cookies
220 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
221
222 let cookies = page.cdp().get_cookies().await.unwrap_or_default();
223 let has_evo = cookies.iter().any(|c| c.name == "EVOSESSIONID");
224 let evo = cookies.iter().find(|c| c.name == "EVOSESSIONID")
225 .map(|c| c.value.clone())
226 .unwrap_or_else(|| "N/A".into());
227 let cookie_names: Vec<String> = cookies.iter().map(|c| c.name.clone()).collect();
228
229 // Stay alive for remaining time (human sim + aggressive scroll running)
230 let elapsed = t.elapsed().as_secs();
231 if elapsed < BROWSER_LIVE_SECS {
232 tokio::time::sleep(std::time::Duration::from_secs(BROWSER_LIVE_SECS - elapsed)).await;
233 }
234
235 // Close browser
236 scroll_handle.abort();
237 let _ = browser.close().await;
238 let total_ms = t.elapsed().as_millis();
239
240 let status = if has_evo { "OK" } else { "MISS" };
241 format!("{label} {status} | {user_id:<18} | total:{total_ms}ms api:{api_ms}ms launch:{launch_ms}ms nav:{nav_ms}ms | cookies:{} | EVO:{evo}\n {:?}",
242 cookies.len(), cookie_names)
243 }));
244 }
245
246 for h in handles {
247 match h.await {
248 Ok(msg) => {
249 if msg.contains("] OK ") { ok += 1; } else { fail += 1; }
250 println!("{msg}");
251 }
252 Err(e) => { println!(" task panic: {e}"); fail += 1; }
253 }
254 }
255
256 println!();
257 }
258
259 let elapsed = start.elapsed().as_secs_f64();
260 println!("=== OK:{ok} FAIL:{fail} | {elapsed:.0}s total | {:.1}s/profile ===",
261 elapsed / total as f64);
262}Sourcepub fn config(self, path: impl Into<String>) -> Self
pub fn config(self, path: impl Into<String>) -> Self
Use a custom clawser config JSON file path.
Sourcepub fn user_data_dir(self, path: impl Into<String>) -> Self
pub fn user_data_dir(self, path: impl Into<String>) -> Self
Persistent user data dir for cookies/storage.
Sourcepub fn window_size(self, w: u32, h: u32) -> Self
pub fn window_size(self, w: u32, h: u32) -> Self
Window size (default 1920x1080).
Sourcepub fn proxy(self, proxy: impl Into<String>) -> Self
pub fn proxy(self, proxy: impl Into<String>) -> Self
Set SOCKS5 proxy. Format: "socks5://user:pass@host:port".
Also accepts "socks5://host:port" (no auth).
Examples found in repository?
examples/batch_test.rs (line 178)
110async fn main() {
111 load_dotenv();
112 let total = PER_ROUND * ROUNDS;
113 println!("=== Proxy batch: {}×{} = {} | live {}s | rotate {}s cooldown ===\n",
114 PER_ROUND, ROUNDS, total, BROWSER_LIVE_SECS, ROTATE_COOLDOWN_SECS);
115
116 let start = Instant::now();
117 let mut ok = 0u32;
118 let mut fail = 0u32;
119
120 for round in 0..ROUNDS {
121 println!("--- Round {}/{} ---", round + 1, ROUNDS);
122
123 // 1. Rotate IP and get proxy
124 let proxy = match rotate_and_get_proxy().await {
125 Some(p) => p,
126 None => {
127 println!(" Skip round — no proxy");
128 fail += PER_ROUND as u32;
129 continue;
130 }
131 };
132
133 // 2. Verify proxy works
134 if !verify_proxy(&proxy).await {
135 println!(" Skip round — proxy dead");
136 fail += PER_ROUND as u32;
137 continue;
138 }
139
140 // 3. Launch 3 browsers in parallel
141 let offset = round * PER_ROUND;
142 let mut handles = Vec::new();
143
144 for i in 0..PER_ROUND {
145 let idx = offset + i;
146 let user_id = USERS[idx % USERS.len()].to_string();
147 let proxy = proxy.clone();
148 let api = api_url();
149 let token = api_token();
150
151 handles.push(tokio::spawn(async move {
152 let label = format!("[{:02}/{}]", idx + 1, total);
153 let t = Instant::now();
154
155 // Get game URL (direct, no proxy)
156 let game_url = match reqwest::Client::new()
157 .post(&api)
158 .header("Authorization", format!("Bearer {}", token))
159 .header("Content-Type", "application/json")
160 .body(format!(r#"{{"user_id": "{}"}}"#, user_id))
161 .send().await
162 {
163 Ok(resp) => {
164 let body: serde_json::Value = resp.json().await.unwrap_or_default();
165 match body["url"].as_str() {
166 Some(u) if !u.is_empty() => u.to_string(),
167 _ => return format!("{label} FAIL api: {body}"),
168 }
169 }
170 Err(e) => return format!("{label} FAIL api: {e}"),
171 };
172 let api_ms = t.elapsed().as_millis();
173
174 // Launch browser with proxy
175 let browser = match Browser::builder()
176 .headful()
177 .random()
178 .proxy(&proxy)
179 .build().await
180 {
181 Ok(b) => b,
182 Err(e) => return format!("{label} FAIL launch: {e}"),
183 };
184 let launch_ms = t.elapsed().as_millis() - api_ms;
185
186 // Navigate (human simulation auto-starts with aggressive scroll)
187 let page = match browser.new_page("about:blank").await {
188 Ok(p) => p,
189 Err(e) => return format!("{label} FAIL page: {e}"),
190 };
191
192 // Aggressive scrolling in background
193 let scroll_page = page.cdp().clone();
194 let scroll_handle = tokio::spawn(async move {
195 use chromiumoxide::cdp::browser_protocol::input::{
196 DispatchMouseEventParams, DispatchMouseEventType,
197 };
198 let mut y = 300.0f64;
199 loop {
200 // Scroll down aggressively
201 let mut ev = DispatchMouseEventParams::new(
202 DispatchMouseEventType::MouseWheel, 500.0, y,
203 );
204 ev.delta_x = Some(0.0);
205 ev.delta_y = Some(300.0);
206 let _ = scroll_page.execute(ev).await;
207 y = ((y + 50.0) % 800.0) + 100.0;
208 tokio::time::sleep(std::time::Duration::from_millis(800)).await;
209 }
210 });
211
212 let t_nav = Instant::now();
213 if let Err(e) = page.goto(&game_url).await {
214 scroll_handle.abort();
215 return format!("{label} FAIL nav: {e}");
216 }
217 let nav_ms = t_nav.elapsed().as_millis();
218
219 // Wait for cookies
220 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
221
222 let cookies = page.cdp().get_cookies().await.unwrap_or_default();
223 let has_evo = cookies.iter().any(|c| c.name == "EVOSESSIONID");
224 let evo = cookies.iter().find(|c| c.name == "EVOSESSIONID")
225 .map(|c| c.value.clone())
226 .unwrap_or_else(|| "N/A".into());
227 let cookie_names: Vec<String> = cookies.iter().map(|c| c.name.clone()).collect();
228
229 // Stay alive for remaining time (human sim + aggressive scroll running)
230 let elapsed = t.elapsed().as_secs();
231 if elapsed < BROWSER_LIVE_SECS {
232 tokio::time::sleep(std::time::Duration::from_secs(BROWSER_LIVE_SECS - elapsed)).await;
233 }
234
235 // Close browser
236 scroll_handle.abort();
237 let _ = browser.close().await;
238 let total_ms = t.elapsed().as_millis();
239
240 let status = if has_evo { "OK" } else { "MISS" };
241 format!("{label} {status} | {user_id:<18} | total:{total_ms}ms api:{api_ms}ms launch:{launch_ms}ms nav:{nav_ms}ms | cookies:{} | EVO:{evo}\n {:?}",
242 cookies.len(), cookie_names)
243 }));
244 }
245
246 for h in handles {
247 match h.await {
248 Ok(msg) => {
249 if msg.contains("] OK ") { ok += 1; } else { fail += 1; }
250 println!("{msg}");
251 }
252 Err(e) => { println!(" task panic: {e}"); fail += 1; }
253 }
254 }
255
256 println!();
257 }
258
259 let elapsed = start.elapsed().as_secs_f64();
260 println!("=== OK:{ok} FAIL:{fail} | {elapsed:.0}s total | {:.1}s/profile ===",
261 elapsed / total as f64);
262}Sourcepub fn proxy_socks5(self, host: &str, port: u16, user: &str, pass: &str) -> Self
pub fn proxy_socks5(self, host: &str, port: u16, user: &str, pass: &str) -> Self
Set SOCKS5 proxy from host, port, user, pass components.
Sourcepub fn arg(self, arg: impl Into<String>) -> Self
pub fn arg(self, arg: impl Into<String>) -> Self
Add a Chrome launch argument (e.g. "disable-gpu").
Sourcepub async fn build(self) -> Result<Browser>
pub async fn build(self) -> Result<Browser>
Launch the browser.
Examples found in repository?
examples/youtube_test.rs (line 10)
4async fn main() {
5 println!("=== YouTube Test ===\n");
6
7 let browser = Browser::builder()
8 .headful()
9 .profile(7, 777)
10 .build()
11 .await
12 .expect("launch failed");
13
14 let page = browser.new_page("https://www.youtube.com").await.expect("nav failed");
15 page.wait(3000).await;
16
17 let title = page.js("document.title").await.unwrap_or_default();
18 let url = page.url().await.unwrap_or_default();
19 println!("Title: {title}");
20 println!("URL: {url}");
21
22 println!("\nBrowser open. Ctrl+C to exit.");
23 loop {
24 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
25 }
26}More examples
examples/smoke_test.rs (line 10)
4async fn main() {
5 println!("=== Smoke Test ===\n");
6
7 let browser = Browser::builder()
8 .headful()
9 .profile(7, 777)
10 .build()
11 .await
12 .expect("launch failed");
13
14 let page = browser.new_page("about:blank").await.expect("page failed");
15
16 let checks = &[
17 ("webdriver", "navigator.webdriver.toString()"),
18 ("timezone", "Intl.DateTimeFormat().resolvedOptions().timeZone"),
19 ("cores", "navigator.hardwareConcurrency.toString()"),
20 ("screen", "screen.width+'x'+screen.height"),
21 ("UA", "navigator.userAgent"),
22 ];
23 for (name, expr) in checks {
24 let val = page.js(expr).await.unwrap_or_default();
25 println!("{name}: {val}");
26 }
27
28 println!("\nBrowser open. Ctrl+C to exit.");
29 loop {
30 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
31 }
32}examples/game_test.rs (line 55)
30async fn main() {
31 load_dotenv();
32 println!("=== Game Test + WS Detection ===\n");
33
34 // 1. Fetch game URL
35 println!("Fetching game URL...");
36 let client = reqwest::Client::new();
37 let resp = client
38 .post(api_url())
39 .header("Authorization", format!("Bearer {}", api_token()))
40 .header("Content-Type", "application/json")
41 .header("Accept", "application/json")
42 .body(r#"{"user_id": "beezsbee"}"#)
43 .send()
44 .await
45 .expect("API request failed");
46
47 let body: serde_json::Value = resp.json().await.expect("JSON parse failed");
48 let game_url = body["url"].as_str().expect("no 'url' in response");
49 println!("Game URL: {game_url}\n");
50
51 // 2. Launch browser
52 let browser = Browser::builder()
53 .headful()
54 .profile(42, 12345)
55 .build()
56 .await
57 .expect("browser launch failed");
58
59 // 3. Open blank page first so we can set up listeners before navigation
60 let page = browser.new_page("about:blank").await.expect("page failed");
61
62 // Enable network domain for WS events
63 page.cdp().execute(EnableParams::default()).await.expect("network enable failed");
64
65 // Set up WS event listeners
66 let mut ws_created = page.cdp().event_listener::<EventWebSocketCreated>().await.expect("listener failed");
67 let mut ws_closed = page.cdp().event_listener::<EventWebSocketClosed>().await.expect("listener failed");
68
69 // Spawn listener tasks
70 tokio::spawn(async move {
71 while let Some(ev) = ws_created.next().await {
72 println!("[WS CREATED] request_id={:?} url={}", ev.request_id, ev.url);
73 }
74 });
75
76 tokio::spawn(async move {
77 while let Some(ev) = ws_closed.next().await {
78 println!("[WS CLOSED] request_id={:?}", ev.request_id);
79 }
80 });
81
82 // 4. Navigate to game (human simulation runs automatically)
83 println!("Navigating to game...");
84 page.navigate(game_url).await.expect("nav failed");
85
86 // 5. Wait and monitor
87 println!("Watching for WebSocket connections...\n");
88 loop {
89 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
90 }
91}examples/tls_check.rs (line 28)
21async fn main() {
22 load_dotenv();
23 println!("=== TLS/HTTP2 Fingerprint Check ===\n");
24
25 let browser = Browser::builder()
26 .headful()
27 .random()
28 .build()
29 .await
30 .expect("browser launch failed");
31
32 let page = browser.new_page("about:blank").await.expect("page failed");
33
34 // 1. Check TLS fingerprint
35 println!("--- tls.peet.ws ---");
36 page.goto("https://tls.peet.ws/api/all").await.expect("nav failed");
37 tokio::time::sleep(std::time::Duration::from_secs(3)).await;
38
39 let body = page.js("document.body.innerText").await.unwrap_or_default();
40 if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
41 // TLS
42 if let Some(tls) = v.get("tls") {
43 println!("JA3 hash: {}", tls.get("ja3_hash").and_then(|v| v.as_str()).unwrap_or("N/A"));
44 println!("JA3: {}", tls.get("ja3").and_then(|v| v.as_str()).unwrap_or("N/A"));
45 println!("JA4: {}", tls.get("ja4").and_then(|v| v.as_str()).unwrap_or("N/A"));
46 println!("TLS version: {}", tls.get("version").and_then(|v| v.as_str()).unwrap_or("N/A"));
47 }
48
49 // HTTP2
50 println!();
51 if let Some(h2) = v.get("http2") {
52 println!("HTTP2 fingerprint: {}", h2.get("akamai_fingerprint").and_then(|v| v.as_str()).unwrap_or("N/A"));
53 println!("Pseudo-header order: {}", h2.get("sent_pseudo_header_fields_order").unwrap_or(&serde_json::Value::Null));
54 if let Some(settings) = h2.get("sent_settings") {
55 println!("SETTINGS: {}", settings);
56 }
57 }
58
59 // HTTP version used
60 println!();
61 if let Some(ip) = v.get("ip") {
62 println!("Client IP: {}", ip.as_str().unwrap_or("N/A"));
63 }
64 if let Some(http_version) = v.get("http_version") {
65 println!("HTTP version: {}", http_version.as_str().unwrap_or("N/A"));
66 }
67
68 // User-Agent
69 println!();
70 if let Some(h1) = v.get("http1") {
71 if let Some(headers) = h1.get("headers") {
72 if let Some(arr) = headers.as_array() {
73 for h in arr {
74 let name = h.get("name").and_then(|n| n.as_str()).unwrap_or("");
75 let val = h.get("value").and_then(|n| n.as_str()).unwrap_or("");
76 if name.to_lowercase() == "user-agent" {
77 println!("User-Agent: {}", val);
78 }
79 }
80 }
81 }
82 }
83 } else {
84 println!("Failed to parse response: {}", &body[..body.len().min(500)]);
85 }
86
87 // 2. Check known Chrome 134 reference values
88 println!("\n--- Expected Chrome 134 stock values ---");
89 println!("JA3 hash: cd49876058a26ccf43e241ccc4e4b5d3 (Chrome 134 Windows)");
90 println!("JA4: t13d1517h2_8daaf6152771_02713d6af8 (Chrome 134 Windows)");
91 println!("HTTP2 Akamai: 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p");
92
93 println!("\nBrowser still open. Press Ctrl+C to exit.");
94 loop {
95 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
96 }
97}examples/batch_test.rs (line 179)
110async fn main() {
111 load_dotenv();
112 let total = PER_ROUND * ROUNDS;
113 println!("=== Proxy batch: {}×{} = {} | live {}s | rotate {}s cooldown ===\n",
114 PER_ROUND, ROUNDS, total, BROWSER_LIVE_SECS, ROTATE_COOLDOWN_SECS);
115
116 let start = Instant::now();
117 let mut ok = 0u32;
118 let mut fail = 0u32;
119
120 for round in 0..ROUNDS {
121 println!("--- Round {}/{} ---", round + 1, ROUNDS);
122
123 // 1. Rotate IP and get proxy
124 let proxy = match rotate_and_get_proxy().await {
125 Some(p) => p,
126 None => {
127 println!(" Skip round — no proxy");
128 fail += PER_ROUND as u32;
129 continue;
130 }
131 };
132
133 // 2. Verify proxy works
134 if !verify_proxy(&proxy).await {
135 println!(" Skip round — proxy dead");
136 fail += PER_ROUND as u32;
137 continue;
138 }
139
140 // 3. Launch 3 browsers in parallel
141 let offset = round * PER_ROUND;
142 let mut handles = Vec::new();
143
144 for i in 0..PER_ROUND {
145 let idx = offset + i;
146 let user_id = USERS[idx % USERS.len()].to_string();
147 let proxy = proxy.clone();
148 let api = api_url();
149 let token = api_token();
150
151 handles.push(tokio::spawn(async move {
152 let label = format!("[{:02}/{}]", idx + 1, total);
153 let t = Instant::now();
154
155 // Get game URL (direct, no proxy)
156 let game_url = match reqwest::Client::new()
157 .post(&api)
158 .header("Authorization", format!("Bearer {}", token))
159 .header("Content-Type", "application/json")
160 .body(format!(r#"{{"user_id": "{}"}}"#, user_id))
161 .send().await
162 {
163 Ok(resp) => {
164 let body: serde_json::Value = resp.json().await.unwrap_or_default();
165 match body["url"].as_str() {
166 Some(u) if !u.is_empty() => u.to_string(),
167 _ => return format!("{label} FAIL api: {body}"),
168 }
169 }
170 Err(e) => return format!("{label} FAIL api: {e}"),
171 };
172 let api_ms = t.elapsed().as_millis();
173
174 // Launch browser with proxy
175 let browser = match Browser::builder()
176 .headful()
177 .random()
178 .proxy(&proxy)
179 .build().await
180 {
181 Ok(b) => b,
182 Err(e) => return format!("{label} FAIL launch: {e}"),
183 };
184 let launch_ms = t.elapsed().as_millis() - api_ms;
185
186 // Navigate (human simulation auto-starts with aggressive scroll)
187 let page = match browser.new_page("about:blank").await {
188 Ok(p) => p,
189 Err(e) => return format!("{label} FAIL page: {e}"),
190 };
191
192 // Aggressive scrolling in background
193 let scroll_page = page.cdp().clone();
194 let scroll_handle = tokio::spawn(async move {
195 use chromiumoxide::cdp::browser_protocol::input::{
196 DispatchMouseEventParams, DispatchMouseEventType,
197 };
198 let mut y = 300.0f64;
199 loop {
200 // Scroll down aggressively
201 let mut ev = DispatchMouseEventParams::new(
202 DispatchMouseEventType::MouseWheel, 500.0, y,
203 );
204 ev.delta_x = Some(0.0);
205 ev.delta_y = Some(300.0);
206 let _ = scroll_page.execute(ev).await;
207 y = ((y + 50.0) % 800.0) + 100.0;
208 tokio::time::sleep(std::time::Duration::from_millis(800)).await;
209 }
210 });
211
212 let t_nav = Instant::now();
213 if let Err(e) = page.goto(&game_url).await {
214 scroll_handle.abort();
215 return format!("{label} FAIL nav: {e}");
216 }
217 let nav_ms = t_nav.elapsed().as_millis();
218
219 // Wait for cookies
220 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
221
222 let cookies = page.cdp().get_cookies().await.unwrap_or_default();
223 let has_evo = cookies.iter().any(|c| c.name == "EVOSESSIONID");
224 let evo = cookies.iter().find(|c| c.name == "EVOSESSIONID")
225 .map(|c| c.value.clone())
226 .unwrap_or_else(|| "N/A".into());
227 let cookie_names: Vec<String> = cookies.iter().map(|c| c.name.clone()).collect();
228
229 // Stay alive for remaining time (human sim + aggressive scroll running)
230 let elapsed = t.elapsed().as_secs();
231 if elapsed < BROWSER_LIVE_SECS {
232 tokio::time::sleep(std::time::Duration::from_secs(BROWSER_LIVE_SECS - elapsed)).await;
233 }
234
235 // Close browser
236 scroll_handle.abort();
237 let _ = browser.close().await;
238 let total_ms = t.elapsed().as_millis();
239
240 let status = if has_evo { "OK" } else { "MISS" };
241 format!("{label} {status} | {user_id:<18} | total:{total_ms}ms api:{api_ms}ms launch:{launch_ms}ms nav:{nav_ms}ms | cookies:{} | EVO:{evo}\n {:?}",
242 cookies.len(), cookie_names)
243 }));
244 }
245
246 for h in handles {
247 match h.await {
248 Ok(msg) => {
249 if msg.contains("] OK ") { ok += 1; } else { fail += 1; }
250 println!("{msg}");
251 }
252 Err(e) => { println!(" task panic: {e}"); fail += 1; }
253 }
254 }
255
256 println!();
257 }
258
259 let elapsed = start.elapsed().as_secs_f64();
260 println!("=== OK:{ok} FAIL:{fail} | {elapsed:.0}s total | {:.1}s/profile ===",
261 elapsed / total as f64);
262}Trait Implementations§
Auto Trait Implementations§
impl Freeze for BrowserBuilder
impl RefUnwindSafe for BrowserBuilder
impl Send for BrowserBuilder
impl Sync for BrowserBuilder
impl Unpin for BrowserBuilder
impl UnsafeUnpin for BrowserBuilder
impl UnwindSafe for BrowserBuilder
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
Converts
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
Converts
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more