pub struct Browser { /* private fields */ }Implementations§
Source§impl Browser
impl Browser
Sourcepub fn builder() -> BrowserBuilder
pub fn builder() -> BrowserBuilder
Examples found in repository?
examples/youtube_test.rs (line 7)
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 7)
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 52)
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 25)
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 175)
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 async fn connect(ws_url: &str) -> Result<Self>
pub async fn connect(ws_url: &str) -> Result<Self>
Connect to an already-running Chrome at ws_url.
Sourcepub async fn new_page(&self, url: &str) -> Result<Page>
pub async fn new_page(&self, url: &str) -> Result<Page>
Open a new tab and navigate to URL. Background human simulation (mouse movement + scrolling) starts automatically.
Examples found in repository?
examples/youtube_test.rs (line 14)
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 14)
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 60)
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 32)
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 187)
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 async fn pages(&self) -> Result<Vec<Page>>
pub async fn pages(&self) -> Result<Vec<Page>>
Get all open pages (each gets its own background human simulation).
Get all browser cookies.
Sourcepub fn cdp(&self) -> &CdpBrowser
pub fn cdp(&self) -> &CdpBrowser
Access underlying chromiumoxide Browser for raw CDP.
Sourcepub async fn close(self) -> Result<()>
pub async fn close(self) -> Result<()>
Gracefully shut down the browser.
Examples found in repository?
examples/batch_test.rs (line 237)
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}Auto Trait Implementations§
impl Freeze for Browser
impl !RefUnwindSafe for Browser
impl Send for Browser
impl Sync for Browser
impl Unpin for Browser
impl UnsafeUnpin for Browser
impl !UnwindSafe for Browser
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