pub use crate::chrome_linux_ua_list::STATIC_CHROME_LINUX_AGENTS;
pub use crate::chrome_mac_ua_list::STATIC_CHROME_MAC_AGENTS;
pub use crate::chrome_mobile_ua_list::STATIC_CHROME_MOBILE_AGENTS;
pub use crate::chrome_tablet_ua_list::STATIC_CHROME_TABLET_AGENTS;
pub use crate::chrome_ua_list::STATIC_CHROME_AGENTS;
pub use crate::chrome_windows_ua_list::STATIC_CHROME_WINDOWS_AGENTS;
pub use crate::firefox_linux_ua_list::STATIC_FIREFOX_LINUX_AGENTS;
pub use crate::firefox_mac_ua_list::STATIC_FIREFOX_MAC_AGENTS;
pub use crate::firefox_mobile_ua_list::STATIC_FIREFOX_MOBILE_AGENTS;
pub use crate::firefox_tablet_ua_list::STATIC_FIREFOX_TABLET_AGENTS;
pub use crate::firefox_ua_list::STATIC_FIREFOX_AGENTS;
pub use crate::firefox_windows_ua_list::STATIC_FIREFOX_WINDOWS_AGENTS;
pub use crate::safari_mac_ua_list::STATIC_SAFARI_MAC_AGENTS;
pub use crate::safari_mobile_ua_list::STATIC_SAFARI_MOBILE_AGENTS;
pub use crate::safari_tablet_ua_list::STATIC_SAFARI_TABLET_AGENTS;
pub use crate::safari_ua_list::STATIC_SAFARI_AGENTS;
pub use crate::ua_list::STATIC_AGENTS;
use fastrand::{self, Rng};
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Os {
Windows,
Mac,
Linux,
Android,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FormFactor {
Desktop,
Mobile,
Tablet,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Browser {
Chrome,
Firefox,
Ie,
Safari,
}
pub fn spoof_ua() -> &'static str {
pick_rand(None, STATIC_AGENTS)
}
pub fn spoof_chrome_ua() -> &'static str {
pick_rand(None, STATIC_CHROME_AGENTS)
}
pub fn spoof_chrome_mac_ua() -> &'static str {
pick_rand(None, STATIC_CHROME_MAC_AGENTS)
}
pub fn spoof_chrome_linux_ua() -> &'static str {
pick_rand(None, STATIC_CHROME_LINUX_AGENTS)
}
pub fn spoof_chrome_mobile_ua() -> &'static str {
pick_rand(None, STATIC_CHROME_MOBILE_AGENTS)
}
pub fn spoof_chrome_tablet_ua() -> &'static str {
pick_rand(None, STATIC_CHROME_TABLET_AGENTS)
}
pub fn spoof_ua_with_randomizer(thread_rng: &mut Rng) -> &'static str {
pick_rand(Some(thread_rng), STATIC_AGENTS)
}
pub fn spoof_chrome_ua_with_randomizer(thread_rng: &mut Rng) -> &'static str {
pick_rand(Some(thread_rng), STATIC_CHROME_AGENTS)
}
pub fn spoof_chrome_mac_ua_with_randomizer(thread_rng: &mut Rng) -> &'static str {
pick_rand(Some(thread_rng), STATIC_CHROME_MAC_AGENTS)
}
pub fn spoof_chrome_linux_ua_with_randomizer(thread_rng: &mut Rng) -> &'static str {
pick_rand(Some(thread_rng), STATIC_CHROME_LINUX_AGENTS)
}
#[inline]
pub fn chrome_agents() -> &'static [&'static str] {
STATIC_CHROME_AGENTS
}
#[inline]
pub fn chrome_windows_agents() -> &'static [&'static str] {
STATIC_CHROME_WINDOWS_AGENTS
}
#[inline]
pub fn chrome_mac_agents() -> &'static [&'static str] {
STATIC_CHROME_MAC_AGENTS
}
#[inline]
pub fn chrome_linux_agents() -> &'static [&'static str] {
STATIC_CHROME_LINUX_AGENTS
}
#[inline]
pub fn chrome_mobile_agents() -> &'static [&'static str] {
STATIC_CHROME_MOBILE_AGENTS
}
#[inline]
pub fn chrome_tablet_agents() -> &'static [&'static str] {
STATIC_CHROME_TABLET_AGENTS
}
pub fn spoof_firefox_ua() -> &'static str {
pick_rand(None, STATIC_FIREFOX_AGENTS)
}
pub fn spoof_firefox_windows_ua() -> &'static str {
pick_rand(None, STATIC_FIREFOX_WINDOWS_AGENTS)
}
pub fn spoof_firefox_mac_ua() -> &'static str {
pick_rand(None, STATIC_FIREFOX_MAC_AGENTS)
}
pub fn spoof_firefox_linux_ua() -> &'static str {
pick_rand(None, STATIC_FIREFOX_LINUX_AGENTS)
}
pub fn spoof_firefox_mobile_ua() -> &'static str {
pick_rand(None, STATIC_FIREFOX_MOBILE_AGENTS)
}
pub fn spoof_firefox_tablet_ua() -> &'static str {
pick_rand(None, STATIC_FIREFOX_TABLET_AGENTS)
}
pub fn spoof_firefox_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_FIREFOX_AGENTS)
}
pub fn spoof_firefox_windows_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_FIREFOX_WINDOWS_AGENTS)
}
pub fn spoof_firefox_mac_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_FIREFOX_MAC_AGENTS)
}
pub fn spoof_firefox_linux_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_FIREFOX_LINUX_AGENTS)
}
pub fn spoof_firefox_mobile_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_FIREFOX_MOBILE_AGENTS)
}
pub fn spoof_firefox_tablet_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_FIREFOX_TABLET_AGENTS)
}
#[inline]
pub fn firefox_agents() -> &'static [&'static str] {
STATIC_FIREFOX_AGENTS
}
#[inline]
pub fn firefox_windows_agents() -> &'static [&'static str] {
STATIC_FIREFOX_WINDOWS_AGENTS
}
#[inline]
pub fn firefox_mac_agents() -> &'static [&'static str] {
STATIC_FIREFOX_MAC_AGENTS
}
#[inline]
pub fn firefox_linux_agents() -> &'static [&'static str] {
STATIC_FIREFOX_LINUX_AGENTS
}
#[inline]
pub fn firefox_mobile_agents() -> &'static [&'static str] {
STATIC_FIREFOX_MOBILE_AGENTS
}
#[inline]
pub fn firefox_tablet_agents() -> &'static [&'static str] {
STATIC_FIREFOX_TABLET_AGENTS
}
pub fn spoof_safari_ua() -> &'static str {
pick_rand(None, STATIC_SAFARI_AGENTS)
}
pub fn spoof_safari_mac_ua() -> &'static str {
pick_rand(None, STATIC_SAFARI_MAC_AGENTS)
}
pub fn spoof_safari_mobile_ua() -> &'static str {
pick_rand(None, STATIC_SAFARI_MOBILE_AGENTS)
}
pub fn spoof_safari_tablet_ua() -> &'static str {
pick_rand(None, STATIC_SAFARI_TABLET_AGENTS)
}
pub fn spoof_safari_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_SAFARI_AGENTS)
}
pub fn spoof_safari_mac_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_SAFARI_MAC_AGENTS)
}
pub fn spoof_safari_mobile_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_SAFARI_MOBILE_AGENTS)
}
pub fn spoof_safari_tablet_ua_with_randomizer(rng: &mut Rng) -> &'static str {
pick_rand(Some(rng), STATIC_SAFARI_TABLET_AGENTS)
}
#[inline]
pub fn safari_agents() -> &'static [&'static str] {
STATIC_SAFARI_AGENTS
}
#[inline]
pub fn safari_mac_agents() -> &'static [&'static str] {
STATIC_SAFARI_MAC_AGENTS
}
#[inline]
pub fn safari_mobile_agents() -> &'static [&'static str] {
STATIC_SAFARI_MOBILE_AGENTS
}
#[inline]
pub fn safari_tablet_agents() -> &'static [&'static str] {
STATIC_SAFARI_TABLET_AGENTS
}
#[inline]
pub fn mixed_static_agents() -> &'static [&'static str] {
STATIC_AGENTS
}
pub fn spoof_by(
os: Option<Os>,
form: Option<FormFactor>,
browser: Option<Browser>,
rng: Option<&mut Rng>,
) -> &'static str {
match (os, form, browser) {
(Some(Os::Windows), Some(FormFactor::Desktop), Some(Browser::Chrome)) => {
pick_rand(rng, STATIC_CHROME_WINDOWS_AGENTS)
}
(Some(Os::Mac), Some(FormFactor::Desktop), Some(Browser::Chrome)) => {
pick_rand(rng, STATIC_CHROME_MAC_AGENTS)
}
(Some(Os::Linux), Some(FormFactor::Desktop), Some(Browser::Chrome)) => {
pick_rand(rng, STATIC_CHROME_LINUX_AGENTS)
}
(_, Some(FormFactor::Mobile), Some(Browser::Chrome)) => {
pick_rand(rng, STATIC_CHROME_MOBILE_AGENTS)
}
(_, Some(FormFactor::Tablet), Some(Browser::Chrome)) => {
pick_rand(rng, STATIC_CHROME_TABLET_AGENTS)
}
(_, _, Some(Browser::Chrome)) => pick_rand(rng, STATIC_CHROME_AGENTS),
(Some(Os::Windows), Some(FormFactor::Desktop), Some(Browser::Firefox)) => {
pick_rand(rng, STATIC_FIREFOX_WINDOWS_AGENTS)
}
(Some(Os::Mac), Some(FormFactor::Desktop), Some(Browser::Firefox)) => {
pick_rand(rng, STATIC_FIREFOX_MAC_AGENTS)
}
(Some(Os::Linux), Some(FormFactor::Desktop), Some(Browser::Firefox)) => {
pick_rand(rng, STATIC_FIREFOX_LINUX_AGENTS)
}
(_, Some(FormFactor::Mobile), Some(Browser::Firefox)) => {
pick_rand(rng, STATIC_FIREFOX_MOBILE_AGENTS)
}
(_, Some(FormFactor::Tablet), Some(Browser::Firefox)) => {
pick_rand(rng, STATIC_FIREFOX_TABLET_AGENTS)
}
(_, _, Some(Browser::Firefox)) => pick_rand(rng, STATIC_FIREFOX_AGENTS),
(Some(Os::Mac), Some(FormFactor::Desktop), Some(Browser::Safari)) => {
pick_rand(rng, STATIC_SAFARI_MAC_AGENTS)
}
(_, Some(FormFactor::Mobile), Some(Browser::Safari)) => {
pick_rand(rng, STATIC_SAFARI_MOBILE_AGENTS)
}
(_, Some(FormFactor::Tablet), Some(Browser::Safari)) => {
pick_rand(rng, STATIC_SAFARI_TABLET_AGENTS)
}
(_, _, Some(Browser::Safari)) => pick_rand(rng, STATIC_SAFARI_AGENTS),
(_, Some(FormFactor::Desktop), _) => pick_rand_multi(
rng,
&[
STATIC_CHROME_WINDOWS_AGENTS,
STATIC_CHROME_MAC_AGENTS,
STATIC_CHROME_LINUX_AGENTS,
STATIC_FIREFOX_WINDOWS_AGENTS,
STATIC_FIREFOX_MAC_AGENTS,
STATIC_FIREFOX_LINUX_AGENTS,
STATIC_SAFARI_MAC_AGENTS,
],
),
(_, Some(FormFactor::Mobile), _) => pick_rand_multi(
rng,
&[
STATIC_CHROME_MOBILE_AGENTS,
STATIC_FIREFOX_MOBILE_AGENTS,
STATIC_SAFARI_MOBILE_AGENTS,
],
),
(_, Some(FormFactor::Tablet), _) => pick_rand_multi(
rng,
&[
STATIC_CHROME_TABLET_AGENTS,
STATIC_FIREFOX_TABLET_AGENTS,
STATIC_SAFARI_TABLET_AGENTS,
],
),
_ => pick_rand(rng, STATIC_AGENTS),
}
}
pub fn all_static_agents() -> &'static Vec<&'static str> {
static AGENTS: std::sync::OnceLock<Vec<&'static str>> = std::sync::OnceLock::new();
AGENTS.get_or_init(|| {
STATIC_AGENTS
.iter()
.chain(STATIC_CHROME_AGENTS.iter())
.chain(STATIC_CHROME_MAC_AGENTS.iter())
.chain(STATIC_CHROME_LINUX_AGENTS.iter())
.chain(STATIC_CHROME_MOBILE_AGENTS.iter())
.chain(STATIC_CHROME_TABLET_AGENTS.iter())
.chain(STATIC_FIREFOX_AGENTS.iter())
.chain(STATIC_FIREFOX_WINDOWS_AGENTS.iter())
.chain(STATIC_FIREFOX_MAC_AGENTS.iter())
.chain(STATIC_FIREFOX_LINUX_AGENTS.iter())
.chain(STATIC_FIREFOX_MOBILE_AGENTS.iter())
.chain(STATIC_FIREFOX_TABLET_AGENTS.iter())
.chain(STATIC_SAFARI_AGENTS.iter())
.chain(STATIC_SAFARI_MAC_AGENTS.iter())
.chain(STATIC_SAFARI_MOBILE_AGENTS.iter())
.chain(STATIC_SAFARI_TABLET_AGENTS.iter())
.copied()
.collect()
})
}
pub fn spoof_random_agent(thread_rng: &mut Rng) -> &'static str {
let agents = all_static_agents();
agents[thread_rng.usize(..agents.len())]
}
#[derive(Default, Clone)]
pub struct UserAgents {
list: Vec<Rc<String>>,
list_map: HashMap<Rc<String>, usize>,
}
impl UserAgents {
pub fn new<I>(iter: I) -> UserAgents
where
I: IntoIterator<Item = String>,
{
let mut list = Vec::new();
let mut list_map = HashMap::new();
for (i, item) in iter.into_iter().enumerate() {
let rc_item = Rc::new(item);
list.push(Rc::clone(&rc_item));
list_map.insert(rc_item, i);
}
UserAgents { list, list_map }
}
pub fn get_agent_list(&self) -> &Vec<Rc<String>> {
&self.list
}
pub fn get_agent_map(&self) -> &HashMap<Rc<String>, usize> {
&self.list_map
}
pub fn add_agent(&mut self, agent: String) {
let rc_agent = Rc::new(agent);
if !self.list_map.contains_key(&rc_agent) {
let index = self.list.len();
self.list.push(Rc::clone(&rc_agent));
self.list_map.insert(rc_agent, index);
}
}
pub fn remove_agent(&mut self, agent_name: &str) {
let key_to_remove = self.list_map.iter().find_map(|(key, _)| {
if key.as_str() == agent_name {
Some(key.clone())
} else {
None
}
});
if let Some(key) = key_to_remove {
if let Some(&index) = self.list_map.get(&key) {
self.list.remove(index);
self.list_map.remove(&key);
for value in self.list_map.values_mut() {
if *value > index {
*value -= 1;
}
}
}
}
}
pub fn spoof(&self) -> &str {
if self.list.is_empty() {
spoof_ua()
} else {
&self.list[fastrand::usize(..self.list.len())]
}
}
pub fn spoof_with_randomizer(&self, thread_rng: &mut Rng) -> &str {
if self.list.is_empty() {
spoof_ua()
} else {
&self.list[thread_rng.usize(..self.list.len())]
}
}
}
#[inline]
fn pick_rand<'a>(rng: Option<&mut Rng>, candidates: &'a [&'a str]) -> &'a str {
if candidates.is_empty() {
return "";
}
match rng {
Some(r) => candidates[r.usize(..candidates.len())],
None => candidates[fastrand::usize(..candidates.len())],
}
}
#[inline]
fn pick_rand_multi<'a>(rng: Option<&mut Rng>, groups: &[&'a [&'a str]]) -> &'a str {
let total: usize = groups.iter().map(|g| g.len()).sum();
if total == 0 {
return "";
}
let mut idx = match rng {
Some(r) => r.usize(..total),
None => fastrand::usize(..total),
};
for g in groups {
if idx < g.len() {
return g[idx];
}
idx -= g.len();
}
""
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spoof_ua() {
let ua = spoof_ua();
assert!(!ua.is_empty());
}
#[test]
fn test_user_agents() {
let agents = vec![
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36".to_string(),
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15".to_string(),
];
let ua_instance = UserAgents::new(agents.clone());
let random_ua = ua_instance.spoof();
assert!(agents.contains(&random_ua.to_string()));
}
#[test]
fn test_add_and_remove_user_agent() {
let mut ua_instance = UserAgents::new(vec!["Agent1".to_string(), "Agent2".to_string()]);
assert_eq!(ua_instance.list.len(), 2);
assert_eq!(ua_instance.list_map.len(), 2);
ua_instance.add_agent("Agent3".to_string());
assert_eq!(ua_instance.list.len(), 3);
assert_eq!(ua_instance.list_map.len(), 3);
let agent3_rc = ua_instance
.get_agent_list()
.iter()
.find(|rc| rc.as_str() == "Agent3")
.expect("Agent3 should be in the list.");
assert!(
Rc::ptr_eq(
agent3_rc,
ua_instance
.list_map
.iter()
.find_map(|(rc, _)| if **rc == "Agent3" { Some(rc) } else { None })
.unwrap()
),
"Agent3 should point to the same Rc instance in both map and list."
);
ua_instance.remove_agent("Agent1");
assert_eq!(ua_instance.list.len(), 2);
assert_eq!(ua_instance.list_map.len(), 2);
assert!(!ua_instance
.list_map
.contains_key(&Rc::new("Agent1".to_string())));
assert!(
!ua_instance
.get_agent_list()
.iter()
.any(|rc| rc.as_str() == "Agent1"),
"Agent1 should be removed from the list."
);
assert_eq!(
ua_instance.list_map.get(&Rc::new("Agent3".to_string())),
Some(&1)
);
}
#[test]
fn test_spoof_by_fallbacks() {
let _ = spoof_by(
Some(Os::Windows),
Some(FormFactor::Desktop),
Some(Browser::Chrome),
None,
);
let _ = spoof_by(
Some(Os::Mac),
Some(FormFactor::Desktop),
Some(Browser::Chrome),
None,
);
let _ = spoof_by(
Some(Os::Linux),
Some(FormFactor::Desktop),
Some(Browser::Chrome),
None,
);
let _ = spoof_by(None, Some(FormFactor::Mobile), Some(Browser::Chrome), None);
let _ = spoof_by(None, Some(FormFactor::Tablet), Some(Browser::Chrome), None);
let _ = spoof_by(
Some(Os::Windows),
Some(FormFactor::Desktop),
Some(Browser::Firefox),
None,
);
let _ = spoof_by(
Some(Os::Windows),
Some(FormFactor::Desktop),
Some(Browser::Ie),
None,
);
let _ = spoof_by(None, Some(FormFactor::Desktop), None, None);
let _ = spoof_by(None, Some(FormFactor::Mobile), None, None);
let _ = spoof_by(None, Some(FormFactor::Tablet), None, None);
}
}