rustenium_identity/
cdp.rs1use crate::error::IdentityError;
2use crate::identity::{Browser, Identity, Os};
3
4use crate::script;
5use crate::ua;
6
7use rustenium::browsers::chrome::browser::ChromeBrowser;
8use rustenium::browsers::cdp_browser::CdpBrowser;
9use rustenium_cdp_definitions::browser_protocol::emulation::commands::{
10 SetDeviceMetricsOverride, SetHardwareConcurrencyOverride, SetLocaleOverride,
11 SetTimezoneOverride, SetTouchEmulationEnabled, SetUserAgentOverride,
12};
13use rustenium_cdp_definitions::browser_protocol::emulation::types::{
14 UserAgentBrandVersion, UserAgentMetadata,
15};
16use rustenium_cdp_definitions::browser_protocol::network::commands::SetUserAgentOverride as NetworkSetUserAgentOverride;
17use rustenium_cdp_definitions::browser_protocol::page::commands::{
18 AddScriptToEvaluateOnNewDocument, AddScriptToEvaluateOnNewDocumentMethod,
19 AddScriptToEvaluateOnNewDocumentParams,
20};
21
22fn build_client_hints(identity: &Identity) -> UserAgentMetadata {
24 let major = identity.browser_version.first().copied().unwrap_or(0).to_string();
25 let full_version = identity
26 .browser_version
27 .iter()
28 .map(|v| v.to_string())
29 .collect::<Vec<_>>()
30 .join(".");
31 let is_mobile = identity.device_model.is_some();
32
33 let (brand_name, brand_name_full) = match identity.browser {
34 Browser::Edge => ("Microsoft Edge", "Microsoft Edge"),
35 _ => ("Google Chrome", "Google Chrome"),
36 };
37
38 let grease_brand = match identity.os {
39 Os::Android => "Not-A.Brand",
40 _ => "Not;A=Brand",
41 };
42
43 let brands = vec![
44 UserAgentBrandVersion::new("Chromium", &major),
45 UserAgentBrandVersion::new(grease_brand, "24"),
46 UserAgentBrandVersion::new(brand_name, &major),
47 ];
48
49 let full_version_list = vec![
50 UserAgentBrandVersion::new("Chromium", &full_version),
51 UserAgentBrandVersion::new(grease_brand, "24.0.0.0"),
52 UserAgentBrandVersion::new(brand_name_full, &full_version),
53 ];
54
55 let platform = match identity.os {
56 Os::Windows => "Windows",
57 Os::Macintosh => "macOS",
58 Os::Linux => "Linux",
59 Os::Android => "Android",
60 Os::Ios => "iOS",
61 };
62
63 let (architecture, bitness) = if is_mobile {
65 (String::new(), String::new())
66 } else {
67 (
68 identity.platform.architecture.clone().unwrap_or_else(|| "x86".into()),
69 identity.platform.bitness.clone().unwrap_or_else(|| "64".into()),
70 )
71 };
72 let model = identity.device_model.clone().unwrap_or_default();
73
74 UserAgentMetadata::builder()
75 .brands(brands)
76 .full_version_lists(full_version_list)
77 .platform(platform)
78 .platform_version(&identity.platform.version)
79 .architecture(&architecture)
80 .model(&model)
81 .mobile(is_mobile)
82 .bitness(bitness)
83 .build()
84 .unwrap()
85}
86
87pub async fn apply_identity(
89 browser: &mut ChromeBrowser,
90 identity: &Identity,
91 timezone: &str,
92) -> Result<(), IdentityError> {
93 let user_agent = ua::build_user_agent(identity)?;
94 let lang0 = identity
95 .language
96 .first()
97 .cloned()
98 .unwrap_or_else(|| "en-US".into());
99 let is_mobile = identity.device_model.is_some();
100
101 let client_hints = if matches!(identity.browser, Browser::Chrome | Browser::Edge)
103 && !matches!(identity.os, Os::Ios)
104 {
105 Some(build_client_hints(identity))
106 } else {
107 None
108 };
109
110 let mut ua_builder = SetUserAgentOverride::builder()
112 .user_agent(&user_agent)
113 .accept_language(&lang0)
114 .platform(identity.platform.navigator_platform.as_str());
115 if let Some(ref metadata) = client_hints {
116 ua_builder = ua_builder.user_agent_metadata(metadata.clone());
117 }
118 let ua_cmd = ua_builder.build().unwrap();
119 browser
120 .send_command(ua_cmd.into())
121 .await
122 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
123
124 let mut net_builder = NetworkSetUserAgentOverride::builder()
126 .user_agent(&user_agent)
127 .accept_language(&lang0);
128 if let Some(ref metadata) = client_hints {
129 net_builder = net_builder.user_agent_metadata(metadata.clone());
130 }
131 let net_ua_cmd = net_builder.build().unwrap();
132 browser
133 .send_command(net_ua_cmd.into())
134 .await
135 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
136
137 let screen_width = (identity.screen.logical_width as f64 * identity.screen.density_pixel_ratio as f64) as i64;
139 let screen_height = (identity.screen.logical_height as f64 * identity.screen.density_pixel_ratio as f64) as i64;
140 let device_cmd = SetDeviceMetricsOverride::builder()
141 .width(identity.screen.logical_width as i64)
142 .height(identity.screen.logical_height as i64)
143 .device_scale_factor(identity.screen.density_pixel_ratio as f64)
144 .mobile(is_mobile)
145 .screen_width(screen_width)
146 .screen_height(screen_height)
147 .build()
148 .unwrap();
149 browser
150 .send_command(device_cmd.into())
151 .await
152 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
153
154 if identity.has_touch {
156 let touch_cmd = SetTouchEmulationEnabled::builder()
157 .enabled(true)
158 .max_touch_points(5i64)
159 .build()
160 .unwrap();
161 browser
162 .send_command(touch_cmd.into())
163 .await
164 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
165 }
166
167 let locale_cmd = SetLocaleOverride::builder()
169 .locale(&lang0)
170 .build();
171 browser
172 .send_command(locale_cmd.into())
173 .await
174 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
175
176 let tz_cmd = SetTimezoneOverride::builder()
178 .timezone_id(timezone)
179 .build()
180 .unwrap();
181 browser
182 .send_command(tz_cmd.into())
183 .await
184 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
185
186 let hc_cmd = SetHardwareConcurrencyOverride::builder()
188 .hardware_concurrency(identity.hardware_concurrency as i64)
189 .build()
190 .unwrap();
191 browser
192 .send_command(hc_cmd.into())
193 .await
194 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
195
196 let stealth_script = script::build_stealth_script(identity);
198 let cmd = AddScriptToEvaluateOnNewDocument {
199 method: AddScriptToEvaluateOnNewDocumentMethod::AddScriptToEvaluateOnNewDocument,
200 params: AddScriptToEvaluateOnNewDocumentParams::new(stealth_script),
201 };
202 browser
203 .send_command(cmd.into())
204 .await
205 .map_err(|e| IdentityError::InjectionError(e.to_string()))?;
206
207 Ok(())
208}