Skip to main content

wreq_util/
emulate.rs

1#[macro_use]
2mod macros;
3pub mod compress;
4pub mod profile;
5
6use profile::{chrome::*, firefox::*, okhttp::*, opera::*, safari::*};
7#[cfg(feature = "emulation-serde")]
8use serde::{Deserialize, Serialize};
9use typed_builder::TypedBuilder;
10
11define_enum!(
12    /// Selects which client profile the request should look like.
13    ///
14    /// This controls the built-in TLS, HTTP/2, and header presets used for the
15    /// request. Variants cover browser-style profiles as well as other clients,
16    /// such as OkHttp.
17    dispatch,
18    Profile, Chrome100,
19    Emulation,
20
21    // Chrome versions
22    Chrome100 => ("chrome_100", v100::emulation),
23    Chrome101 => ("chrome_101", v101::emulation),
24    Chrome104 => ("chrome_104", v104::emulation),
25    Chrome105 => ("chrome_105", v105::emulation),
26    Chrome106 => ("chrome_106", v106::emulation),
27    Chrome107 => ("chrome_107", v107::emulation),
28    Chrome108 => ("chrome_108", v108::emulation),
29    Chrome109 => ("chrome_109", v109::emulation),
30    Chrome110 => ("chrome_110", v110::emulation),
31    Chrome114 => ("chrome_114", v114::emulation),
32    Chrome116 => ("chrome_116", v116::emulation),
33    Chrome117 => ("chrome_117", v117::emulation),
34    Chrome118 => ("chrome_118", v118::emulation),
35    Chrome119 => ("chrome_119", v119::emulation),
36    Chrome120 => ("chrome_120", v120::emulation),
37    Chrome123 => ("chrome_123", v123::emulation),
38    Chrome124 => ("chrome_124", v124::emulation),
39    Chrome126 => ("chrome_126", v126::emulation),
40    Chrome127 => ("chrome_127", v127::emulation),
41    Chrome128 => ("chrome_128", v128::emulation),
42    Chrome129 => ("chrome_129", v129::emulation),
43    Chrome130 => ("chrome_130", v130::emulation),
44    Chrome131 => ("chrome_131", v131::emulation),
45    Chrome132 => ("chrome_132", v132::emulation),
46    Chrome133 => ("chrome_133", v133::emulation),
47    Chrome134 => ("chrome_134", v134::emulation),
48    Chrome135 => ("chrome_135", v135::emulation),
49    Chrome136 => ("chrome_136", v136::emulation),
50    Chrome137 => ("chrome_137", v137::emulation),
51    Chrome138 => ("chrome_138", v138::emulation),
52    Chrome139 => ("chrome_139", v139::emulation),
53    Chrome140 => ("chrome_140", v140::emulation),
54    Chrome141 => ("chrome_141", v141::emulation),
55    Chrome142 => ("chrome_142", v142::emulation),
56    Chrome143 => ("chrome_143", v143::emulation),
57    Chrome144 => ("chrome_144", v144::emulation),
58    Chrome145 => ("chrome_145", v145::emulation),
59    Chrome146 => ("chrome_146", v146::emulation),
60    Chrome147 => ("chrome_147", v147::emulation),
61    Chrome148 => ("chrome_148", v148::emulation),
62    Chrome149 => ("chrome_149", v149::emulation),
63
64    // Edge versions
65    Edge101 => ("edge_101", edge101::emulation),
66    Edge122 => ("edge_122", edge122::emulation),
67    Edge127 => ("edge_127", edge127::emulation),
68    Edge131 => ("edge_131", edge131::emulation),
69    Edge134 => ("edge_134", edge134::emulation),
70    Edge135 => ("edge_135", edge135::emulation),
71    Edge136 => ("edge_136", edge136::emulation),
72    Edge137 => ("edge_137", edge137::emulation),
73    Edge138 => ("edge_138", edge138::emulation),
74    Edge139 => ("edge_139", edge139::emulation),
75    Edge140 => ("edge_140", edge140::emulation),
76    Edge141 => ("edge_141", edge141::emulation),
77    Edge142 => ("edge_142", edge142::emulation),
78    Edge143 => ("edge_143", edge143::emulation),
79    Edge144 => ("edge_144", edge144::emulation),
80    Edge145 => ("edge_145", edge145::emulation),
81    Edge146 => ("edge_146", edge146::emulation),
82    Edge147 => ("edge_147", edge147::emulation),
83    Edge148 => ("edge_148", edge148::emulation),
84
85    // Opera versions
86    Opera116 => ("opera_116", opera116::emulation),
87    Opera117 => ("opera_117", opera117::emulation),
88    Opera118 => ("opera_118", opera118::emulation),
89    Opera119 => ("opera_119", opera119::emulation),
90    Opera120 => ("opera_120", opera120::emulation),
91    Opera121 => ("opera_121", opera121::emulation),
92    Opera122 => ("opera_122", opera122::emulation),
93    Opera123 => ("opera_123", opera123::emulation),
94    Opera124 => ("opera_124", opera124::emulation),
95    Opera125 => ("opera_125", opera125::emulation),
96    Opera126 => ("opera_126", opera126::emulation),
97    Opera127 => ("opera_127", opera127::emulation),
98    Opera128 => ("opera_128", opera128::emulation),
99    Opera129 => ("opera_129", opera129::emulation),
100    Opera130 => ("opera_130", opera130::emulation),
101    Opera131 => ("opera_131", opera131::emulation),
102
103    // Firefox versions
104    Firefox109 => ("firefox_109", ff109::emulation),
105    Firefox117 => ("firefox_117", ff117::emulation),
106    Firefox128 => ("firefox_128", ff128::emulation),
107    Firefox133 => ("firefox_133", ff133::emulation),
108    Firefox135 => ("firefox_135", ff135::emulation),
109    FirefoxPrivate135 => ("firefox_private_135", ff_private_135::emulation),
110    FirefoxAndroid135 => ("firefox_android_135", ff_android_135::emulation),
111    Firefox136 => ("firefox_136", ff136::emulation),
112    FirefoxPrivate136 => ("firefox_private_136", ff_private_136::emulation),
113    Firefox139 => ("firefox_139", ff139::emulation),
114    Firefox142 => ("firefox_142", ff142::emulation),
115    Firefox143 => ("firefox_143", ff143::emulation),
116    Firefox144 => ("firefox_144", ff144::emulation),
117    Firefox145 => ("firefox_145", ff145::emulation),
118    Firefox146 => ("firefox_146", ff146::emulation),
119    Firefox147 => ("firefox_147", ff147::emulation),
120    Firefox148 => ("firefox_148", ff148::emulation),
121    Firefox149 => ("firefox_149", ff149::emulation),
122    Firefox150 => ("firefox_150", ff150::emulation),
123    Firefox151 => ("firefox_151", ff151::emulation),
124
125    // Safari versions
126    SafariIos17_2 => ("safari_ios_17.2", safari_ios_17_2::emulation),
127    SafariIos17_4_1 => ("safari_ios_17.4.1", safari_ios_17_4_1::emulation),
128    SafariIos16_5 => ("safari_ios_16.5", safari_ios_16_5::emulation),
129    Safari15_3 => ("safari_15.3", safari15_3::emulation),
130    Safari15_5 => ("safari_15.5", safari15_5::emulation),
131    Safari15_6_1 => ("safari_15.6.1", safari15_6_1::emulation),
132    Safari16 => ("safari_16", safari16::emulation),
133    Safari16_5 => ("safari_16.5", safari16_5::emulation),
134    Safari17_0 => ("safari_17.0", safari17_0::emulation),
135    Safari17_2_1 => ("safari_17.2.1", safari17_2_1::emulation),
136    Safari17_4_1 => ("safari_17.4.1", safari17_4_1::emulation),
137    Safari17_5 => ("safari_17.5", safari17_5::emulation),
138    Safari17_6 => ("safari_17.6", safari17_6::emulation),
139    Safari18 => ("safari_18", safari18::emulation),
140    SafariIPad18 => ("safari_ipad_18", safari_ipad_18::emulation),
141    Safari18_2 => ("safari_18.2", safari18_2::emulation),
142    SafariIos18_1_1 => ("safari_ios_18.1.1", safari_ios_18_1_1::emulation),
143    Safari18_3 => ("safari_18.3", safari18_3::emulation),
144    Safari18_3_1 => ("safari_18.3.1", safari18_3_1::emulation),
145    Safari18_5 => ("safari_18.5", safari18_5::emulation),
146    Safari26 => ("safari_26", safari26::emulation),
147    Safari26_1 => ("safari_26.1", safari26_1::emulation),
148    Safari26_2 => ("safari_26.2", safari26_2::emulation),
149    Safari26_3 => ("safari_26.3", safari26_3::emulation),
150    Safari26_4 => ("safari_26.4", safari26_4::emulation),
151    SafariIPad26 => ("safari_ipad_26", safari_ipad_26::emulation),
152    SafariIpad26_2 => ("safari_ipad_26.2", safari_ipad_26_2::emulation),
153    SafariIos26 => ("safari_ios_26", safari_ios_26::emulation),
154    SafariIos26_2 => ("safari_ios_26.2", safari_ios_26_2::emulation),
155
156    // OkHttp versions
157    OkHttp3_9 => ("okhttp_3.9", okhttp3_9::emulation),
158    OkHttp3_11 => ("okhttp_3.11", okhttp3_11::emulation),
159    OkHttp3_13 => ("okhttp_3.13", okhttp3_13::emulation),
160    OkHttp3_14 => ("okhttp_3.14", okhttp3_14::emulation),
161    OkHttp4_9 => ("okhttp_4.9", okhttp4_9::emulation),
162    OkHttp4_10 => ("okhttp_4.10", okhttp4_10::emulation),
163    OkHttp4_12 => ("okhttp_4.12", okhttp4_12::emulation),
164    OkHttp5 => ("okhttp_5", okhttp5::emulation)
165
166);
167
168impl wreq::IntoEmulation for Profile {
169    #[inline]
170    fn into_emulation(self) -> wreq::Emulation {
171        Emulation::builder().profile(self).build().into_emulation()
172    }
173}
174
175define_enum!(
176    /// Selects which platform the client should look like.
177    ///
178    /// This mainly affects platform-specific headers and user-agent details.
179    /// In most cases you can keep the default unless you need to match a
180    /// specific Windows, macOS, Linux, Android, or iOS profile.
181    plain,
182    Platform, MacOS,
183    Windows => "windows",
184    MacOS => "macos",
185    Linux => "linux",
186    Android => "android",
187    IOS => "ios"
188);
189
190impl Platform {
191    #[inline]
192    const fn platform(&self) -> &'static str {
193        match self {
194            Platform::MacOS => "\"macOS\"",
195            Platform::Linux => "\"Linux\"",
196            Platform::Windows => "\"Windows\"",
197            Platform::Android => "\"Android\"",
198            Platform::IOS => "\"iOS\"",
199        }
200    }
201
202    #[inline]
203    const fn is_mobile(&self) -> bool {
204        matches!(self, Platform::Android | Platform::IOS)
205    }
206}
207
208/// Represents the configuration options for emulating a client profile and platform.
209///
210/// The `Emulation` struct allows you to configure various aspects of profile and platform
211/// emulation, including the profile, platform, and whether to enable certain features
212/// like HTTP/2 or headers.
213#[derive(Default, Clone, TypedBuilder)]
214pub struct Emulation {
215    /// Whether to change the profile (browser/okhttp) information.
216    #[builder(default)]
217    profile: Profile,
218
219    /// Whether to change the platform (Windows/macOS/Linux/Android/iOS) information.
220    #[builder(default)]
221    platform: Platform,
222
223    /// Whether to enable HTTP/2.
224    #[builder(default = true)]
225    http2: bool,
226
227    /// Whether to include default headers.
228    #[builder(default = true)]
229    headers: bool,
230}
231
232impl Emulation {
233    /// Returns a random variant of the `Profile` enum.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// use wreq_util::Emulation;
239    ///
240    /// let random_emulation = Emulation::random();
241    /// println!("{:?}", random_emulation);
242    /// ```
243    pub fn random() -> Emulation {
244        let rand = crate::rand::fast_random();
245        Emulation::builder()
246            .profile(Profile::VARIANTS[(rand as usize) % Profile::VARIANTS.len()])
247            .platform(Platform::VARIANTS[((rand >> 32) as usize) % Platform::VARIANTS.len()])
248            .build()
249    }
250
251    /// Returns a market-share weighted random `Emulation`.
252    ///
253    /// Unlike [`Emulation::random`], selection is biased toward popular browser
254    /// families and their most recent versions, and each profile is only paired
255    /// with platforms it ships on.
256    ///
257    /// Browser family and version weights are derived from StatCounter Global
258    /// Stats data retrieved in June 2026:
259    ///
260    /// Browser market share:
261    /// <https://gs.statcounter.com/browser-market-share#monthly-202506-202606>
262    ///
263    /// Browser version market share:
264    /// <https://gs.statcounter.com/browser-version-market-share#monthly-202506-202606>
265    ///
266    /// StatCounter requests attribution for use of its data. See:
267    /// <https://creativecommons.org/licenses/by-sa/3.0/>
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// use wreq_util::Emulation;
273    ///
274    /// let random_emulation = Emulation::weighted_random();
275    /// println!("{:?}", random_emulation);
276    /// ```
277    pub fn weighted_random() -> Emulation {
278        use Platform::*;
279        use Profile::*;
280
281        struct Class {
282            weight: u32,
283            platforms: &'static [Platform],
284            profiles: &'static [Profile],
285        }
286
287        // Weights based on StatCounter June 2026 data.
288        //
289        // Each weight is the family's share percentage multiplied by 100 and
290        // rounded to an integer (e.g. Chrome 71.41% -> 7141, Edge 5.02% -> 502,
291        // Firefox 2.35% -> 235, Opera 1.73% -> 173). Only relative magnitudes
292        // matter, so the common x100 scale is arbitrary but keeps two decimals
293        // of precision without floats.
294        //
295        // Safari's 14.77% is split by platform using the browser-version data:
296        // iPhone 11.96% + iPad 0.44% = 12.40% mobile (-> 1240), leaving the
297        // remaining 2.37% for desktop/macOS (-> 237).
298        const CLASSES: &[Class] = &[
299            Class {
300                weight: 7141,
301                platforms: &[Windows, MacOS, Linux, Android],
302                profiles: &[
303                    Chrome149, Chrome148, Chrome147, Chrome146, Chrome145, Chrome144, Chrome143,
304                ],
305            },
306            Class {
307                weight: 1240,
308                platforms: &[IOS],
309                profiles: &[
310                    SafariIos26_2,
311                    SafariIos26,
312                    SafariIpad26_2,
313                    SafariIPad26,
314                    SafariIos18_1_1,
315                    SafariIPad18,
316                ],
317            },
318            Class {
319                weight: 502,
320                platforms: &[Windows, MacOS],
321                profiles: &[Edge148, Edge147, Edge146, Edge145, Edge144, Edge143],
322            },
323            Class {
324                weight: 237,
325                platforms: &[MacOS],
326                profiles: &[
327                    Safari26_4, Safari26_3, Safari26_2, Safari26_1, Safari26, Safari18_5,
328                ],
329            },
330            Class {
331                weight: 235,
332                platforms: &[Windows, MacOS, Linux],
333                profiles: &[
334                    Firefox151, Firefox150, Firefox149, Firefox148, Firefox147, Firefox146,
335                ],
336            },
337            Class {
338                weight: 173,
339                platforms: &[Windows, MacOS, Linux, Android],
340                profiles: &[Opera131, Opera130, Opera129, Opera128, Opera127, Opera126],
341            },
342        ];
343
344        let (r1, r2) = (crate::rand::fast_random(), crate::rand::fast_random());
345        let total: u32 = CLASSES.iter().map(|c| c.weight).sum();
346        let mut t = (r1 % total as u64) as u32;
347        let class = CLASSES
348            .iter()
349            .find(|c| {
350                t = t.checked_sub(c.weight).unwrap_or(u32::MAX);
351                t == u32::MAX
352            })
353            .unwrap_or(&CLASSES[0]);
354        let n = class.profiles.len();
355        let idx = ((r1 >> 32) as usize % n).min((r2 >> 32) as usize % n);
356        Emulation::builder()
357            .profile(class.profiles[idx])
358            .platform(class.platforms[(r2 as usize) % class.platforms.len()])
359            .build()
360    }
361}
362
363impl wreq::IntoEmulation for Emulation {
364    #[inline]
365    fn into_emulation(self) -> wreq::Emulation {
366        self.profile.match_emulation(self)
367    }
368}