Skip to main content

rustenium_identity/
preset.rs

1//! Built-in identity presets.
2//!
3//! Provides 10 realistic browser/device identities that can be retrieved by
4//! their numeric ID or selected at random, without any external dependency.
5//!
6//! # Example
7//! ```rust,no_run
8//! use rustenium_identity::preset;
9//!
10//! let id = preset::get_by_id(1).unwrap();
11//! let rnd = preset::random();
12//! println!("{:?}", rnd.browser);
13//! ```
14
15use crate::error::IdentityError;
16use crate::identity::{Browser, Gpu, Identity, NavigatorPlatform, Os, PlatformInfo, ScreenResolution};
17
18// ---------------------------------------------------------------------------
19// Internal helpers
20// ---------------------------------------------------------------------------
21
22fn win_platform(ver: &str) -> PlatformInfo {
23    PlatformInfo {
24        bitness: Some("64".into()),
25        architecture: Some("x86".into()),
26        navigator_platform: NavigatorPlatform::Win32,
27        version: ver.into(),
28    }
29}
30
31fn mac_platform(ver: &str) -> PlatformInfo {
32    PlatformInfo {
33        bitness: Some("64".into()),
34        architecture: Some("arm".into()),
35        navigator_platform: NavigatorPlatform::MacIntel,
36        version: ver.into(),
37    }
38}
39
40fn lin_platform(ver: &str) -> PlatformInfo {
41    PlatformInfo {
42        bitness: Some("64".into()),
43        architecture: Some("x86".into()),
44        navigator_platform: NavigatorPlatform::LinuxX86_64,
45        version: ver.into(),
46    }
47}
48
49fn screen(lw: u16, lh: u16, ow: u16, oh: u16, dpr: f32) -> ScreenResolution {
50    ScreenResolution {
51        logical_width: lw,
52        logical_height: lh,
53        original_width: ow,
54        original_height: oh,
55        density_pixel_ratio: dpr,
56    }
57}
58
59fn nvidia_gpu(model: &str) -> Gpu {
60    Gpu {
61        vendor: "NVIDIA Corporation".into(),
62        webgl_renderer: format!("ANGLE (NVIDIA {} Direct3D11 vs_5_0 ps_5_0)", model),
63        webgl_vendor: "Google Inc. (NVIDIA)".into(),
64    }
65}
66
67fn intel_gpu(model: &str) -> Gpu {
68    Gpu {
69        vendor: "Intel Inc.".into(),
70        webgl_renderer: format!("ANGLE (Intel, {} Direct3D11 vs_5_0 ps_5_0)", model),
71        webgl_vendor: "Google Inc. (Intel)".into(),
72    }
73}
74
75fn apple_gpu(model: &str) -> Gpu {
76    Gpu {
77        vendor: "Apple".into(),
78        webgl_renderer: model.into(),
79        webgl_vendor: "Apple".into(),
80    }
81}
82
83fn amd_gpu(model: &str) -> Gpu {
84    Gpu {
85        vendor: "Advanced Micro Devices, Inc.".into(),
86        webgl_renderer: format!("ANGLE (AMD, {} Direct3D11 vs_5_0 ps_5_0)", model),
87        webgl_vendor: "Google Inc. (AMD)".into(),
88    }
89}
90
91// ---------------------------------------------------------------------------
92// Preset table (IDs 1-10)
93// ---------------------------------------------------------------------------
94
95fn all() -> [Identity; 10] {
96    [
97        // 1 — Windows 11 / Chrome / Desktop / NVIDIA RTX 3080
98        Identity {
99            id: Some(1),
100            device_model: None,
101            has_battery: false,
102            has_mouse: true,
103            has_touch: false,
104            os: Os::Windows,
105            os_version: "15.0.0".into(),
106            platform: win_platform("15.0.0"),
107            browser: Browser::Chrome,
108            browser_version: vec![124, 0, 6367, 78],
109            screen: screen(1920, 1080, 1920, 1080, 1.0),
110            hardware_concurrency: 16,
111            memory: 16,
112            gpu: nvidia_gpu("GeForce RTX 3080"),
113            language: vec!["en-US".into(), "en".into()],
114            history_count: Some(12),
115            proxy: None,
116            timezone: None
117        },
118        // 2 — Windows 10 / Edge / Desktop / Intel UHD 630
119        Identity {
120            id: Some(2),
121            device_model: None,
122            has_battery: false,
123            has_mouse: true,
124            has_touch: false,
125            os: Os::Windows,
126            os_version: "10.0.0".into(),
127            platform: win_platform("10.0.0"),
128            browser: Browser::Edge,
129            browser_version: vec![123, 0, 2420, 65],
130            screen: screen(1366, 768, 1366, 768, 1.0),
131            hardware_concurrency: 8,
132            memory: 8,
133            gpu: intel_gpu("Intel(R) UHD Graphics 630"),
134            language: vec!["en-US".into(), "en".into()],
135            history_count: Some(5),
136            proxy: None,
137            timezone: None
138        },
139        // 3 — macOS Ventura / Safari / Desktop / Apple M2
140        Identity {
141            id: Some(3),
142            device_model: None,
143            has_battery: true,
144            has_mouse: true,
145            has_touch: false,
146            os: Os::Macintosh,
147            os_version: "13.4.1".into(),
148            platform: mac_platform("13.4.1"),
149            browser: Browser::Safari,
150            browser_version: vec![17, 0, 0, 0],
151            screen: screen(2560, 1600, 2560, 1600, 2.0),
152            hardware_concurrency: 10,
153            memory: 16,
154            gpu: apple_gpu("Apple M2"),
155            language: vec!["en-GB".into(), "en".into()],
156            history_count: Some(20),
157            proxy: None,
158            timezone: None
159        },
160        // 4 — macOS Monterey / Chrome / Desktop / Apple M1
161        Identity {
162            id: Some(4),
163            device_model: None,
164            has_battery: true,
165            has_mouse: true,
166            has_touch: false,
167            os: Os::Macintosh,
168            os_version: "12.6.0".into(),
169            platform: mac_platform("12.6.0"),
170            browser: Browser::Chrome,
171            browser_version: vec![124, 0, 6367, 82],
172            screen: screen(1440, 900, 2880, 1800, 2.0),
173            hardware_concurrency: 8,
174            memory: 8,
175            gpu: apple_gpu("Apple M1"),
176            language: vec!["en-US".into(), "en".into()],
177            history_count: Some(7),
178            proxy: None,
179            timezone: None
180        },
181        // 5 — Ubuntu Linux / Chrome / Desktop / NVIDIA GTX 1650
182        Identity {
183            id: Some(5),
184            device_model: None,
185            has_battery: false,
186            has_mouse: true,
187            has_touch: false,
188            os: Os::Linux,
189            os_version: "22.04".into(),
190            platform: lin_platform("22.04"),
191            browser: Browser::Chrome,
192            browser_version: vec![124, 0, 6367, 79],
193            screen: screen(1920, 1080, 1920, 1080, 1.0),
194            hardware_concurrency: 12,
195            memory: 16,
196            gpu: nvidia_gpu("GeForce GTX 1650"),
197            language: vec!["en-US".into(), "en".into()],
198            history_count: Some(3),
199            proxy: None,
200            timezone: None
201        },
202        // 6 — Android / Chrome Mobile / Pixel 7 / Adreno 730
203        Identity {
204            id: Some(6),
205            device_model: Some("Pixel 7".into()),
206            has_battery: true,
207            has_mouse: false,
208            has_touch: true,
209            os: Os::Android,
210            os_version: "13".into(),
211            platform: PlatformInfo {
212                bitness: None,
213                architecture: None,
214                navigator_platform: NavigatorPlatform::LinuxArmV81,
215                version: "13".into(),
216            },
217            browser: Browser::Chrome,
218            browser_version: vec![124, 0, 6367, 82],
219            screen: screen(412, 915, 1080, 2400, 2.625),
220            hardware_concurrency: 8,
221            memory: 8,
222            gpu: Gpu {
223                vendor: "Qualcomm".into(),
224                webgl_renderer: "Adreno (TM) 730".into(),
225                webgl_vendor: "Qualcomm".into(),
226            },
227            language: vec!["en-US".into(), "en".into()],
228            history_count: Some(2),
229            proxy: None,
230            timezone: None
231        },
232        // 7 — iOS / Safari Mobile / iPhone 15 / Apple GPU
233        Identity {
234            id: Some(7),
235            device_model: Some("iPhone 15".into()),
236            has_battery: true,
237            has_mouse: false,
238            has_touch: true,
239            os: Os::Ios,
240            os_version: "17.0".into(),
241            platform: PlatformInfo {
242                bitness: None,
243                architecture: None,
244                navigator_platform: NavigatorPlatform::IPhone,
245                version: "17.0".into(),
246            },
247            browser: Browser::Safari,
248            browser_version: vec![17, 0, 0, 0],
249            screen: screen(393, 852, 1179, 2556, 3.0),
250            hardware_concurrency: 6,
251            memory: 6,
252            gpu: apple_gpu("Apple A16 GPU"),
253            language: vec!["en-US".into(), "en".into()],
254            history_count: Some(4),
255            proxy: None,
256            timezone: None
257        },
258        // 8 — Windows 11 / Chrome / Laptop + touch / AMD Radeon RX 6600
259        Identity {
260            id: Some(8),
261            device_model: None,
262            has_battery: true,
263            has_mouse: true,
264            has_touch: true,
265            os: Os::Windows,
266            os_version: "11.0.0".into(),
267            platform: win_platform("11.0.0"),
268            browser: Browser::Chrome,
269            browser_version: vec![123, 0, 6312, 122],
270            screen: screen(1920, 1200, 1920, 1200, 1.0),
271            hardware_concurrency: 8,
272            memory: 16,
273            gpu: amd_gpu("AMD Radeon RX 6600"),
274            language: vec!["fr-FR".into(), "fr".into(), "en".into()],
275            history_count: Some(9),
276            proxy: None,
277            timezone: None
278        },
279        // 9 — macOS Sonoma / Chrome / Desktop / Apple M3 Pro
280        Identity {
281            id: Some(9),
282            device_model: None,
283            has_battery: true,
284            has_mouse: true,
285            has_touch: false,
286            os: Os::Macintosh,
287            os_version: "14.2.1".into(),
288            platform: mac_platform("14.2.1"),
289            browser: Browser::Chrome,
290            browser_version: vec![124, 0, 6367, 91],
291            screen: screen(1512, 982, 3024, 1964, 2.0),
292            hardware_concurrency: 12,
293            memory: 36,
294            gpu: apple_gpu("Apple M3 Pro"),
295            language: vec!["de-DE".into(), "de".into(), "en".into()],
296            history_count: Some(15),
297            proxy: None,
298            timezone: None
299        },
300        // 10 — Android / Chrome Mobile / Samsung Galaxy S23 / Xclipse 920
301        Identity {
302            id: Some(10),
303            device_model: Some("SM-S911B".into()),
304            has_battery: true,
305            has_mouse: false,
306            has_touch: true,
307            os: Os::Android,
308            os_version: "14".into(),
309            platform: PlatformInfo {
310                bitness: None,
311                architecture: None,
312                navigator_platform: NavigatorPlatform::LinuxArmV81,
313                version: "14".into(),
314            },
315            browser: Browser::Chrome,
316            browser_version: vec![124, 0, 6367, 82],
317            screen: screen(360, 780, 1080, 2340, 3.0),
318            hardware_concurrency: 8,
319            memory: 8,
320            gpu: Gpu {
321                vendor: "Samsung".into(),
322                webgl_renderer: "Xclipse 920".into(),
323                webgl_vendor: "Samsung".into(),
324            },
325            language: vec!["en-US".into(), "en".into()],
326            history_count: Some(6),
327            proxy: None,
328            timezone: None
329        },
330    ]
331}
332
333// ---------------------------------------------------------------------------
334// Public API
335// ---------------------------------------------------------------------------
336
337/// Returns all 10 built-in preset identities.
338pub fn presets() -> Vec<Identity> {
339    all().to_vec()
340}
341
342/// Fetch a preset identity by its numeric ID (1–10).
343///
344/// # Errors
345/// Returns [`IdentityError::NotFound`] if no preset has the given ID.
346pub fn get_by_id(id: u64) -> Result<Identity, IdentityError> {
347    all()
348        .into_iter()
349        .find(|p| p.id == Some(id))
350        .ok_or(IdentityError::NotFound(id))
351}
352
353/// Return a random preset identity.
354///
355/// Uses a simple entropy source based on the current system time so it
356/// requires no extra dependencies.
357pub fn random() -> Identity {
358    let idx = entropy_index(all().len());
359    all().into_iter().nth(idx).expect("preset index in bounds")
360}
361
362// ---------------------------------------------------------------------------
363// Tiny no-dep entropy helper (modulo distribution is fine for 10 items)
364// ---------------------------------------------------------------------------
365
366fn entropy_index(len: usize) -> usize {
367    use std::time::{SystemTime, UNIX_EPOCH};
368    let nanos = SystemTime::now()
369        .duration_since(UNIX_EPOCH)
370        .map(|d| d.subsec_nanos())
371        .unwrap_or(42);
372    (nanos as usize) % len
373}