snm_brightdata_client/
client.rs1use crate::{config::BrightDataConfig, error::BrightDataError};
3use reqwest::Client;
4use serde_json::Value;
5
6pub struct BrightDataClient {
7 config: BrightDataConfig,
8 client: Client,
9}
10
11impl BrightDataClient {
12 pub fn new(config: BrightDataConfig) -> Result<Self, BrightDataError> {
13 config.validate().map_err(|e| BrightDataError::ConfigError(e.to_string()))?;
15
16 let client = Client::builder()
17 .timeout(config.timeout)
18 .build()
19 .map_err(|e| BrightDataError::NetworkError(e.to_string()))?;
20
21 Ok(Self { config, client })
22 }
23
24 pub async fn get(&self, target_url: &str) -> Result<Value, BrightDataError> {
26 let payload = serde_json::json!({
27 "url": target_url,
28 "zone": self.config.web_unlocker_zone,
29 "format": "raw",
30 });
31
32 let res = self
33 .client
34 .post(&format!("{}/request", self.config.base_url))
35 .header("Authorization", format!("Bearer {}", self.config.token))
36 .json(&payload)
37 .send()
38 .await
39 .map_err(|e| BrightDataError::Request(e))?;
40
41 let status = res.status();
42 if !status.is_success() {
43 let error_text = res.text().await.unwrap_or_default();
44 return Err(BrightDataError::ApiError(format!(
45 "BrightData API error {}: {}",
46 status.as_u16(),
47 error_text
48 )));
49 }
50
51 let result = res.json::<Value>().await
52 .map_err(|e| BrightDataError::ParseError(e.to_string()))?;
53
54 Ok(result)
55 }
56
57 pub async fn search(&self, query: &str, engine: &str) -> Result<Value, BrightDataError> {
59 let search_url = self.build_search_url(engine, query);
60
61 let payload = serde_json::json!({
62 "url": search_url,
63 "zone": self.config.serp_zone,
64 "format": "raw",
65 "data_format": "markdown"
66 });
67
68 let res = self
69 .client
70 .post(&format!("{}/request", self.config.base_url))
71 .header("Authorization", format!("Bearer {}", self.config.token))
72 .json(&payload)
73 .send()
74 .await
75 .map_err(|e| BrightDataError::Request(e))?;
76
77 let status = res.status();
78 if !status.is_success() {
79 let error_text = res.text().await.unwrap_or_default();
80 return Err(BrightDataError::ApiError(format!(
81 "BrightData SERP API error {}: {}",
82 status.as_u16(),
83 error_text
84 )));
85 }
86
87 let result = res.json::<Value>().await
88 .map_err(|e| BrightDataError::ParseError(e.to_string()))?;
89
90 Ok(result)
91 }
92
93 pub async fn screenshot(&self, url: &str, width: u32, height: u32) -> Result<Value, BrightDataError> {
95 let payload = serde_json::json!({
96 "url": url,
97 "zone": self.config.browser_zone,
98 "format": "raw",
99 "data_format": "screenshot",
100 "viewport": {
101 "width": width,
102 "height": height
103 }
104 });
105
106 let res = self
107 .client
108 .post(&format!("{}/request", self.config.base_url))
109 .header("Authorization", format!("Bearer {}", self.config.token))
110 .json(&payload)
111 .send()
112 .await
113 .map_err(|e| BrightDataError::Request(e))?;
114
115 let status = res.status();
116 if !status.is_success() {
117 let error_text = res.text().await.unwrap_or_default();
118 return Err(BrightDataError::ApiError(format!(
119 "BrightData Browser API error {}: {}",
120 status.as_u16(),
121 error_text
122 )));
123 }
124
125 let result = res.json::<Value>().await
126 .map_err(|e| BrightDataError::ParseError(e.to_string()))?;
127
128 Ok(result)
129 }
130
131 fn build_search_url(&self, engine: &str, query: &str) -> String {
132 let encoded_query = urlencoding::encode(query);
133 match engine {
134 "bing" => format!("https://www.bing.com/search?q={}", encoded_query),
135 "yandex" => format!("https://yandex.com/search/?text={}", encoded_query),
136 "duckduckgo" => format!("https://duckduckgo.com/?q={}", encoded_query),
137 _ => format!("https://www.google.com/search?q={}", encoded_query),
138 }
139 }
140}