//! Search macros for common websites. //! //! Camofox-browser supports 14+ built-in search macros that map to //! site-specific search URLs. Using macros is more reliable than //! manually constructing URLs because the browser handles all //! redirects, cookies, and anti-bot challenges. use serde::{Deserialize, Serialize}; use std::fmt; /// All supported search macros. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SearchMacro { /// `@google_search` — Search Google GoogleSearch, /// `@youtube_search` — Search YouTube YoutubeSearch, /// `@amazon_search` — Search Amazon AmazonSearch, /// `@reddit_search` — Search Reddit (returns JSON) RedditSearch, /// `@reddit_subreddit` — Browse a subreddit (returns JSON) RedditSubreddit, /// `@wikipedia_search` — Search Wikipedia WikipediaSearch, /// `@twitter_search` — Search Twitter/X TwitterSearch, /// `@yelp_search` — Search Yelp YelpSearch, /// `@spotify_search` — Search Spotify SpotifySearch, /// `@netflix_search` — Search Netflix NetflixSearch, /// `@linkedin_search` — Search LinkedIn LinkedinSearch, /// `@instagram_search` — Search Instagram InstagramSearch, /// `@tiktok_search` — Search TikTok TiktokSearch, /// `@twitch_search` — Search Twitch TwitchSearch, } impl SearchMacro { /// All supported macros as a static slice. pub const ALL: &'static [SearchMacro] = &[ SearchMacro::GoogleSearch, SearchMacro::YoutubeSearch, SearchMacro::AmazonSearch, SearchMacro::RedditSearch, SearchMacro::RedditSubreddit, SearchMacro::WikipediaSearch, SearchMacro::TwitterSearch, SearchMacro::YelpSearch, SearchMacro::SpotifySearch, SearchMacro::NetflixSearch, SearchMacro::LinkedinSearch, SearchMacro::InstagramSearch, SearchMacro::TiktokSearch, SearchMacro::TwitchSearch, ]; /// The `@name` string sent to the camofox API. pub fn as_macro_str(&self) -> &'static str { match self { SearchMacro::GoogleSearch => "@google_search", SearchMacro::YoutubeSearch => "@youtube_search", SearchMacro::AmazonSearch => "@amazon_search", SearchMacro::RedditSearch => "@reddit_search", SearchMacro::RedditSubreddit => "@reddit_subreddit", SearchMacro::WikipediaSearch => "@wikipedia_search", SearchMacro::TwitterSearch => "@twitter_search", SearchMacro::YelpSearch => "@yelp_search", SearchMacro::SpotifySearch => "@spotify_search", SearchMacro::NetflixSearch => "@netflix_search", SearchMacro::LinkedinSearch => "@linkedin_search", SearchMacro::InstagramSearch => "@instagram_search", SearchMacro::TiktokSearch => "@tiktok_search", SearchMacro::TwitchSearch => "@twitch_search", } } /// Human-readable site name. pub fn site_name(&self) -> &'static str { match self { SearchMacro::GoogleSearch => "Google", SearchMacro::YoutubeSearch => "YouTube", SearchMacro::AmazonSearch => "Amazon", SearchMacro::RedditSearch => "Reddit", SearchMacro::RedditSubreddit => "Reddit (subreddit)", SearchMacro::WikipediaSearch => "Wikipedia", SearchMacro::TwitterSearch => "Twitter/X", SearchMacro::YelpSearch => "Yelp", SearchMacro::SpotifySearch => "Spotify", SearchMacro::NetflixSearch => "Netflix", SearchMacro::LinkedinSearch => "LinkedIn", SearchMacro::InstagramSearch => "Instagram", SearchMacro::TiktokSearch => "TikTok", SearchMacro::TwitchSearch => "Twitch", } } /// Parse a `@name` string into a `SearchMacro`. pub fn from_str(s: &str) -> Option { match s { "@google_search" => Some(SearchMacro::GoogleSearch), "@youtube_search" => Some(SearchMacro::YoutubeSearch), "@amazon_search" => Some(SearchMacro::AmazonSearch), "@reddit_search" => Some(SearchMacro::RedditSearch), "@reddit_subreddit" => Some(SearchMacro::RedditSubreddit), "@wikipedia_search" => Some(SearchMacro::WikipediaSearch), "@twitter_search" => Some(SearchMacro::TwitterSearch), "@yelp_search" => Some(SearchMacro::YelpSearch), "@spotify_search" => Some(SearchMacro::SpotifySearch), "@netflix_search" => Some(SearchMacro::NetflixSearch), "@linkedin_search" => Some(SearchMacro::LinkedinSearch), "@instagram_search" => Some(SearchMacro::InstagramSearch), "@tiktok_search" => Some(SearchMacro::TiktokSearch), "@twitch_search" => Some(SearchMacro::TwitchSearch), _ => None, } } } impl fmt::Display for SearchMacro { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_macro_str()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_all_macros_have_valid_names() { for m in SearchMacro::ALL { let name = m.as_macro_str(); assert!(name.starts_with('@'), "Macro should start with @"); assert!(name.len() > 2, "Macro should have a name"); } } #[test] fn test_roundtrip_parse() { for m in SearchMacro::ALL { let name = m.as_macro_str(); let parsed = SearchMacro::from_str(name); assert_eq!(parsed, Some(*m), "Roundtrip failed for {name}"); } } #[test] fn test_invalid_macro() { assert_eq!(SearchMacro::from_str("@invalid_search"), None); assert_eq!(SearchMacro::from_str("google_search"), None); } #[test] fn test_site_names() { assert_eq!(SearchMacro::GoogleSearch.site_name(), "Google"); assert_eq!(SearchMacro::YoutubeSearch.site_name(), "YouTube"); } }