Skip to main content

stealthscraper_rs/
solver.rs

1#![cfg(feature = "browser")]
2//! Interactive challenge solving — e.g. locating and clicking a Cloudflare
3//! Turnstile widget with human-like mouse movement.
4
5use crate::Error;
6use crate::scraper::CloudScraper;
7use headless_chrome::Tab;
8use std::sync::Arc;
9use std::time::Duration;
10
11/// A utility for bypassing automated bot detections and CAPTCHAs.
12///
13/// `GenericSolver` contains various methods to solve or evade generic security puzzles,
14/// such as Cloudflare Turnstile or similar challenges.
15pub struct GenericSolver;
16
17impl GenericSolver {
18    /// Attempts to solve a standard JS challenge (e.g. Cloudflare Turnstile or generic checkbox)
19    /// by locating the challenge element, simulating realistic mouse movement to it, and clicking.
20    pub fn solve_cloudflare_turnstile(tab: &Arc<Tab>) -> Result<(), Error> {
21        // Wait for turnstile checkbox to appear (usually in an iframe, but sometimes standard DOM)
22        // We look for a generic challenge wrapper
23        let challenge_selectors = vec![
24            ".cf-turnstile",
25            "#challenge-stage",
26            "input[type='checkbox']",
27        ];
28
29        for selector in challenge_selectors {
30            if let Ok(element) = tab.wait_for_element(selector) {
31                // If found, get the box coordinates
32                let box_model = element
33                    .get_box_model()
34                    .map_err(|e| Error::BrowserError(format!("Box model failed: {}", e)))?;
35                let center_x = box_model.content.most_left();
36                let center_y = box_model.content.most_top();
37
38                // Move mouse there slowly
39                CloudScraper::human_move_mouse(tab, center_x, center_y)?;
40
41                // Add a small hesitation before clicking
42                std::thread::sleep(Duration::from_millis(150));
43
44                // Click
45                tab.click_point(headless_chrome::browser::tab::point::Point {
46                    x: center_x,
47                    y: center_y,
48                })
49                .map_err(|e| Error::InteractionError(format!("Click failed: {}", e)))?;
50
51                // Wait for the challenge to resolve
52                std::thread::sleep(Duration::from_secs(3));
53                return Ok(());
54            }
55        }
56
57        Err(Error::InteractionError(
58            "Could not find a challenge element to solve".to_string(),
59        ))
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use headless_chrome::Browser;
67
68    #[cfg(feature = "browser")]
69    #[test]
70    fn test_solve_cloudflare_turnstile_not_found() {
71        // Just launch a normal browser to get a tab
72        let browser = Browser::default().expect("Expected to get a browser");
73        let tab = browser.new_tab().expect("Expected to get a tab");
74
75        let result = GenericSolver::solve_cloudflare_turnstile(&tab);
76        assert!(result.is_err());
77    }
78
79    #[cfg(feature = "browser")]
80    #[test]
81    fn test_solve_cloudflare_turnstile_success() {
82        let browser = Browser::default().expect("Failed to launch");
83        let tab = browser.new_tab().expect("Failed to create tab");
84
85        // Load a mock page with a challenge turnstile element
86        let html_content = "<html><body><div class='cf-turnstile' style='width: 300px; height: 65px;'></div></body></html>";
87        let file_path = std::env::temp_dir().join("test_solver.html");
88        std::fs::write(&file_path, html_content).expect("Failed to write mock HTML");
89        let file_url = format!("file://{}", file_path.display());
90
91        tab.navigate_to(&file_url).expect("Failed to navigate");
92        tab.wait_until_navigated().expect("Failed to wait");
93
94        let result = GenericSolver::solve_cloudflare_turnstile(&tab);
95        assert!(result.is_ok());
96    }
97}