diff --git a/Cargo.lock b/Cargo.lock index 0bc2c36..3d147af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4066,7 +4066,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "websurfx" -version = "1.4.0" +version = "1.5.2" dependencies = [ "actix-cors", "actix-files", diff --git a/Cargo.toml b/Cargo.toml index 3a0beb7..cd19790 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "websurfx" -version = "1.4.0" +version = "1.5.2" edition = "2021" description = "An open-source alternative to Searx that provides clean, ad-free, and organic results with incredible speed while keeping privacy and security in mind." repository = "https://github.com/neon-mmd/websurfx" diff --git a/images/websurfx_logo.png b/images/websurfx_logo.png index ad810b3..07157f1 100644 Binary files a/images/websurfx_logo.png and b/images/websurfx_logo.png differ diff --git a/public/images/websurfx_logo.png b/public/images/websurfx_logo.png deleted file mode 100644 index 24d39e1..0000000 Binary files a/public/images/websurfx_logo.png and /dev/null differ diff --git a/public/images/websurfx_logo.svg b/public/images/websurfx_logo.svg deleted file mode 100644 index 2574345..0000000 --- a/public/images/websurfx_logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/public/static/colorschemes/catppuccin-mocha.css b/public/static/colorschemes/catppuccin-mocha.css index 41cc9e0..2394f0a 100644 --- a/public/static/colorschemes/catppuccin-mocha.css +++ b/public/static/colorschemes/catppuccin-mocha.css @@ -1,6 +1,7 @@ :root { --background-color: #1e1e2e; --foreground-color: #cdd6f4; + --logo-color: #f5c2e7; --color-one: #45475a; --color-two: #f38ba8; --color-three: #a6e3a1; diff --git a/public/static/colorschemes/dark-chocolate.css b/public/static/colorschemes/dark-chocolate.css index 32f1f0b..5dacf88 100644 --- a/public/static/colorschemes/dark-chocolate.css +++ b/public/static/colorschemes/dark-chocolate.css @@ -1,6 +1,7 @@ :root { --background-color: #000; --foreground-color: #fff; + --logo-color: #e0e0e0; --color-one: #121212; --color-two: #808080; --color-three: #999; diff --git a/public/static/colorschemes/dracula.css b/public/static/colorschemes/dracula.css index bb15d4c..5933506 100644 --- a/public/static/colorschemes/dracula.css +++ b/public/static/colorschemes/dracula.css @@ -1,6 +1,7 @@ :root { --background-color: #44475a; --foreground-color: #8be9fd; + --logo-color: #ffb86c; --color-one: #f55; --color-two: #50fa7b; --color-three: #ffb86c; diff --git a/public/static/colorschemes/gruvbox-dark.css b/public/static/colorschemes/gruvbox-dark.css index ca89eb2..8120bdb 100644 --- a/public/static/colorschemes/gruvbox-dark.css +++ b/public/static/colorschemes/gruvbox-dark.css @@ -1,6 +1,7 @@ :root { --background-color: #1d2021; --foreground-color: #ebdbb2; + --logo-color: #ebdbb2; --color-one: #282828; --color-two: #98971a; --color-three: #d79921; diff --git a/public/static/colorschemes/monokai.css b/public/static/colorschemes/monokai.css index ea1b58e..c89e570 100644 --- a/public/static/colorschemes/monokai.css +++ b/public/static/colorschemes/monokai.css @@ -1,6 +1,7 @@ :root { --background-color: #49483Eff; --foreground-color: #FFB269; + --logo-color: #ffd866; --color-one: #272822ff; --color-two: #61AFEF; --color-three: #ffd866; diff --git a/public/static/colorschemes/nord.css b/public/static/colorschemes/nord.css index 234b57b..dde536b 100644 --- a/public/static/colorschemes/nord.css +++ b/public/static/colorschemes/nord.css @@ -1,6 +1,7 @@ :root { --background-color: #122736ff; --foreground-color: #a2e2a9; + --logo-color: #e2ecd6; --color-one: #121B2Cff; --color-two: #f08282; --color-three: #ABC5AAff; diff --git a/public/static/colorschemes/oceanic-next.css b/public/static/colorschemes/oceanic-next.css index 896bae1..e7753ae 100644 --- a/public/static/colorschemes/oceanic-next.css +++ b/public/static/colorschemes/oceanic-next.css @@ -1,6 +1,7 @@ :root { --background-color: #1b2b34; --foreground-color: #d8dee9; + --logo-color: #d8dee9; --color-one: #343d46; --color-two: #5FB3B3ff; --color-three: #69Cf; diff --git a/public/static/colorschemes/one-dark.css b/public/static/colorschemes/one-dark.css index 30f858e..5a0a3e8 100644 --- a/public/static/colorschemes/one-dark.css +++ b/public/static/colorschemes/one-dark.css @@ -1,6 +1,7 @@ :root { --background-color: #282c34; --foreground-color: #abb2bf; + --logo-color: #c8ccd4; --color-one: #3b4048; --color-two: #a3be8c; --color-three: #b48ead; diff --git a/public/static/colorschemes/solarized-dark.css b/public/static/colorschemes/solarized-dark.css index 44494f9..842b254 100644 --- a/public/static/colorschemes/solarized-dark.css +++ b/public/static/colorschemes/solarized-dark.css @@ -1,6 +1,7 @@ :root { --background-color: #002b36; --foreground-color: #c9e0e6; + --logo-color: #EEE8D5ff; --color-one: #073642; --color-two: #2AA198ff; --color-three: #2AA198ff; diff --git a/public/static/colorschemes/solarized-light.css b/public/static/colorschemes/solarized-light.css index 7434b37..7a8f67a 100644 --- a/public/static/colorschemes/solarized-light.css +++ b/public/static/colorschemes/solarized-light.css @@ -1,6 +1,7 @@ :root { --background-color: #EEE8D5ff; --foreground-color: #b1ab97; + --logo-color: #586E75; --color-one: #fdf6e3; --color-two: #DC322Fff; --color-three: #586E75ff; diff --git a/public/static/colorschemes/tokyo-night.css b/public/static/colorschemes/tokyo-night.css index 16c54bd..66ad547 100644 --- a/public/static/colorschemes/tokyo-night.css +++ b/public/static/colorschemes/tokyo-night.css @@ -1,6 +1,7 @@ :root { --background-color: #1a1b26; --foreground-color: #c0caf5; + --logo-color: #e2afff; --color-one: #32364a; --color-two: #a9b1d6; --color-three: #5a5bb8; diff --git a/public/static/colorschemes/tomorrow-night.css b/public/static/colorschemes/tomorrow-night.css index 2f2c29c..8b462a0 100644 --- a/public/static/colorschemes/tomorrow-night.css +++ b/public/static/colorschemes/tomorrow-night.css @@ -1,6 +1,7 @@ :root { --background-color: #35383Cff; --foreground-color: #D7DAD8ff; + --logo-color: #D7DAD8ff; --color-one: #1d1f21; --color-two: #D77C79ff; --color-three: #f0c674; diff --git a/public/static/themes/simple.css b/public/static/themes/simple.css index 5eb8949..9aa1ef7 100644 --- a/public/static/themes/simple.css +++ b/public/static/themes/simple.css @@ -1,4 +1,9 @@ /* @import url('./catppuccin-mocha.css'); */ +@font-face { + font-family: Rubik; + src: url('https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600;700;800&display=swap'); + fallback: sans-serif; +} * { padding: 0; @@ -16,7 +21,13 @@ body { justify-content: space-between; align-items: center; height: 100vh; - background: var(--color-one); + font-family: Rubik, sans-serif; + background-color: var(--background-color); +} + +/* enforce font for buttons */ +button { + font-family: Rubik, sans-serif; } /* styles for the index page */ @@ -29,46 +40,67 @@ body { align-items: center; } -.search-container div { - display: flex; +.search-container svg { + color: var(--logo-color); } -.websurfx-logo { - width: clamp(12rem, 40rem, 48rem); +.search-container div { + display: flex; } /* styles for the search box and search button */ .search_bar { display: flex; + gap: 10px; + align-items: center; } .search_bar input { - padding: 1rem; + border-radius: 6px; + padding: 2.6rem 2.2rem; width: 50rem; height: 3rem; outline: none; border: none; box-shadow: rgb(0 0 0 / 1); - background: var(--foreground-color); + background-color: var(--color-one); + color: var(--foreground-color); + outline-offset: 3px; + font-size: 1.6rem; +} + +.search_bar input:focus { + outline: 2px solid var(--foreground-color); +} + +.search_bar input::placeholder { + color: var(--foreground-color); + opacity: 1; } .search_bar button { - padding: 1rem; - border-radius: 0; + padding: 2.6rem 3.2rem; + border-radius: 6px; height: 3rem; display: flex; justify-content: center; align-items: center; - outline: none; + outline-offset: 3px; + outline: 2px solid transparent; border: none; + transition: .1s; gap: 0; - background: var(--background-color); - color: var(--color-three); + background-color: var(--color-six); + color: var(--background-color); font-weight: 600; letter-spacing: 0.1rem; } +.search_bar button:active { + outline: 2px solid var(--color-three); +} + .search_bar button:active, .search_bar button:hover { filter: brightness(1.2); @@ -85,13 +117,19 @@ body { width: 20rem; background-color: var(--color-one); color: var(--foreground-color); - padding: 1rem 2rem; + padding: 1.2rem 2rem; border-radius: 0.5rem; - outline: none; + outline-offset: 3px; + outline: 2px solid transparent; border: none; text-transform: capitalize; } +.search_area .search_options select:active, +.search_area .search_options select:hover { + outline: 2px solid var(--color-three); +} + .search_area .search_options option:hover { background-color: var(--color-one); } @@ -199,17 +237,25 @@ body { /* styles for the footer and header */ -header, + +header { + width: 100%; + background: var(--background-color); + display: flex; + align-items: center; + justify-content: space-between; + padding: 2rem 3rem; +} + footer { width: 100%; background: var(--background-color); display: flex; - padding: 1rem; align-items: center; -} - -header { - justify-content: space-between; + padding: 1.7rem 1.7rem 4rem; + gap: 1.8rem; + flex-direction: column; + justify-content: center; } header h1 a { @@ -217,7 +263,6 @@ header h1 a { text-decoration: none; color: var(--foreground-color); letter-spacing: 0.1rem; - margin-left: 1rem; } header ul, @@ -259,11 +304,6 @@ footer div { gap: 1rem; } -footer { - flex-direction: column; - justify-content: center; -} - /* Styles for the search page */ .results { @@ -271,6 +311,11 @@ footer { display: flex; flex-direction: column; justify-content: space-around; + gap: 1rem; +} + +.result { + gap: 1rem; } .results .search_bar { @@ -280,7 +325,7 @@ footer { .results_aggregated { display: flex; flex-direction: column; - justify-content: space-between; + justify-content: space-between; margin: 2rem 0; content-visibility: auto; } @@ -292,10 +337,10 @@ footer { } .results_aggregated .result h1 a { - font-size: 1.5rem; + font-size: 1.7rem; + font-weight: normal; color: var(--color-two); text-decoration: none; - letter-spacing: 0.1rem; } .results_aggregated .result h1 a:hover { @@ -308,14 +353,15 @@ footer { .results_aggregated .result small { color: var(--color-three); - font-size: 1.1rem; + font-size: 1.3rem; word-wrap: break-word; line-break: anywhere; } .results_aggregated .result p { color: var(--foreground-color); - font-size: 1.2rem; + font-size: 1.4rem; + line-height: 2.4rem; margin-top: 0.3rem; word-wrap: break-word; line-break: anywhere; @@ -326,6 +372,9 @@ footer { font-size: 1.2rem; padding: 1rem; color: var(--color-five); + display: flex; + gap: 1rem; + justify-content: right; } /* Styles for the 404 page */ @@ -442,6 +491,7 @@ footer { display: flex; justify-content: space-around; width: 80dvw; + margin: 5rem 0; } .settings h1 { @@ -449,11 +499,20 @@ footer { font-size: 2.5rem; } +.settings > h1 { + margin-bottom: 4rem; + margin-left: 2rem; +} + .settings hr { border-color: var(--color-three); margin: 0.3rem 0 1rem; } +.settings > hr { + margin-left: 2rem; +} + .settings_container .sidebar { width: 30%; cursor: pointer; @@ -464,7 +523,6 @@ footer { margin-left: -0.7rem; padding: 0.7rem; border-radius: 5px; - font-weight: bold; margin-bottom: 0.5rem; color: var(--foreground-color); text-transform: capitalize; @@ -472,18 +530,30 @@ footer { } .settings_container .sidebar .btn { - padding: 0.5rem; + padding: 2rem; border-radius: 0.5rem; + outline-offset: 3px; + outline: 2px solid transparent; +} + +.settings_container .sidebar .btn:active { + outline: 2px solid var(--color-two); +} + +.settings_container .sidebar .btn:not(.active):hover { + color: var(--color-two); } .settings_container .sidebar .btn.active { background-color: var(--color-two); + color: var(--background-color); } .settings_container .main_container { width: 70%; border-left: 1.5px solid var(--color-three); padding-left: 3rem; + border: none; } .settings_container .tab { @@ -492,6 +562,7 @@ footer { .settings_container .tab.active { display: flex; + gap: 1.2rem; flex-direction: column; justify-content: space-around; } @@ -539,7 +610,7 @@ footer { .settings_container .general select { margin: 0.7rem 0; width: 20rem; - background-color: var(--background-color); + background-color: var(--color-one); color: var(--foreground-color); padding: 1rem 2rem; border-radius: 0.5rem; @@ -557,16 +628,19 @@ footer { display: flex; flex-direction: column; justify-content: center; - gap: 1rem; padding: 1rem 0; + margin-bottom: 2rem; + gap: 2rem; } .settings_container .engines .toggle_btn { color: var(--foreground-color); font-size: 1.5rem; display: flex; - gap: 0.5rem; align-items: center; + border-radius: 100px; + gap: 1.5rem; + letter-spacing: 1px; } .settings_container .engines hr { @@ -599,8 +673,14 @@ footer { position: absolute; cursor: pointer; inset: 0; - background-color: var(--background-color); - transition: 0.4s; + background-color: var(--foreground-color); + transition: 0.2s; + outline-offset: 3px; + outline: 2px solid transparent; +} + +.slider:active { + outline: 2px solid var(--foreground-color); } .slider::before { @@ -610,8 +690,8 @@ footer { width: 2.6rem; left: 0.4rem; bottom: 0.4rem; - background-color: var(--foreground-color); - transition: 0.4s; + background-color: var(--background-color); + transition: .2s; } input:checked + .slider { diff --git a/src/engines/librex.rs b/src/engines/librex.rs new file mode 100644 index 0000000..933c7f2 --- /dev/null +++ b/src/engines/librex.rs @@ -0,0 +1,111 @@ +//! The `librex` module contains the implementation of a search engine for LibreX using the reqwest and scraper libraries. +//! It includes a `SearchEngine` trait implementation for interacting with the search engine and retrieving search results. + +use std::collections::HashMap; + +use reqwest::header::HeaderMap; +use reqwest::Client; +use scraper::Html; + +use crate::models::aggregation_models::SearchResult; +use crate::models::engine_models::{EngineError, SearchEngine}; + +use error_stack::{Report, Result, ResultExt}; + +use super::search_result_parser::SearchResultParser; + +/// Represents the LibreX search engine. +pub struct LibreX { + /// The parser used to extract search results from HTML documents. + parser: SearchResultParser, +} + +impl LibreX { + /// Creates a new instance of LibreX with a default configuration. + /// + /// # Returns + /// + /// Returns a `Result` containing `LibreX` if successful, otherwise an `EngineError`. + pub fn new() -> Result { + Ok(Self { + parser: SearchResultParser::new( + ".text-result-container>p", + ".text-result-container", + ".text-result-wrapper>a>h2", + ".text-result-wrapper>a", + ".text-result-wrapper>span", + )?, + }) + } +} + +#[async_trait::async_trait] +impl SearchEngine for LibreX { + /// Retrieves search results from LibreX based on the provided query, page, user agent, and client. + /// + /// # Arguments + /// + /// * `query` - The search query. + /// * `page` - The page number for pagination. + /// * `user_agent` - The user agent string. + /// * `client` - The reqwest client for making HTTP requests. + /// * `_safe_search` - A parameter for safe search (not currently used). + /// + /// # Returns + /// + /// Returns a `Result` containing a `HashMap` of search results if successful, otherwise an `EngineError`. + /// The `Err` variant is explicit for better documentation. + async fn results( + &self, + query: &str, + page: u32, + user_agent: &str, + client: &Client, + _safe_search: u8, + ) -> Result, EngineError> { + // Page number can be missing or empty string and so appropriate handling is required + // so that upstream server recieves valid page number. + let url: String = match page { + 1 | 0 => { + format!("https://search.ahwx.org/search.php?q={query}&p=0&t=10") + } + _ => { + format!( + "https://search.ahwx.org/search.php?q={query}&p={}&t=10", + page * 10, + ) + } + }; + + // initializing HeaderMap and adding appropriate headers. + let header_map = HeaderMap::try_from(&HashMap::from([ + ("USER_AGENT".to_string(), user_agent.to_string()), + ("REFERER".to_string(), "https://google.com/".to_string()), + ("CONTENT_TYPE".to_string(), "application/x-www-form-urlencoded".to_string()), + ( + "COOKIE".to_string(), + "theme=amoled; disable_special=on; disable_frontends=on; language=en; number_of_results=10; safe_search=on; save=1".to_string(), + ), + ])) + .change_context(EngineError::UnexpectedError)?; + + let document: Html = Html::parse_document( + &LibreX::fetch_html_from_upstream(self, &url, header_map, client).await?, + ); + + if self.parser.parse_for_no_results(&document).next().is_some() { + return Err(Report::new(EngineError::EmptyResultSet)); + } + + // scrape all the results from the html + self.parser + .parse_for_results(&document, |title, url, desc| { + Some(SearchResult::new( + title.inner_html().trim(), + url.inner_html().trim(), + desc.inner_html().trim(), + &["librex"], + )) + }) + } +} diff --git a/src/engines/mod.rs b/src/engines/mod.rs index 53d720b..b6a50f5 100644 --- a/src/engines/mod.rs +++ b/src/engines/mod.rs @@ -5,6 +5,7 @@ pub mod brave; pub mod duckduckgo; +pub mod librex; pub mod search_result_parser; pub mod searx; pub mod startpage; diff --git a/src/engines/searx.rs b/src/engines/searx.rs index 7bf0431..7f20b15 100644 --- a/src/engines/searx.rs +++ b/src/engines/searx.rs @@ -52,11 +52,11 @@ impl SearchEngine for Searx { let url: String = match page { 0 | 1 => { - format!("https://searx.work/search?q={query}&pageno=1&safesearch={safe_search}") + format!("https://searx.be/search?q={query}&pageno=1&safesearch={safe_search}") + } + _ => { + format!("https://searx.be/search?q={query}&pageno={page}&safesearch={safe_search}") } - _ => format!( - "https://searx.work/search?q={query}&pageno={page}&safesearch={safe_search}" - ), }; // initializing headers and adding appropriate headers. diff --git a/src/engines/startpage.rs b/src/engines/startpage.rs index 44135e1..c7c292d 100644 --- a/src/engines/startpage.rs +++ b/src/engines/startpage.rs @@ -1,5 +1,5 @@ -//! The `duckduckgo` module handles the scraping of results from the duckduckgo search engine -//! by querying the upstream duckduckgo search engine with user provided query and with a page +//! The `startpage` module handles the scraping of results from the startpage search engine +//! by querying the upstream startpage search engine with user provided query and with a page //! number if provided. use std::collections::HashMap; diff --git a/src/models/engine_models.rs b/src/models/engine_models.rs index 1ab04ed..70496cd 100644 --- a/src/models/engine_models.rs +++ b/src/models/engine_models.rs @@ -158,6 +158,10 @@ impl EngineHandler { let engine = crate::engines::startpage::Startpage::new()?; ("startpage", Box::new(engine)) } + "librex" => { + let engine = crate::engines::librex::LibreX::new()?; + ("librex", Box::new(engine)) + } _ => { return Err(Report::from(EngineError::NoSuchEngineFound( engine_name.to_string(), diff --git a/src/templates/views/index.rs b/src/templates/views/index.rs index cfa1eb6..d55ef7d 100644 --- a/src/templates/views/index.rs +++ b/src/templates/views/index.rs @@ -15,10 +15,20 @@ use crate::templates::partials::{bar::bar, footer::footer, header::header}; /// /// It returns the compiled html markup code as a result. pub fn index(colorscheme: &str, theme: &str) -> Markup { + let logo_svg = r#" + + + + + + + + "#; + html!( (header(colorscheme, theme)) main class="search-container"{ - img class="websurfx-logo" src="../images/websurfx_logo.svg" alt="Websurfx meta-search engine logo"; + (PreEscaped(logo_svg)) (bar(&String::default())) (PreEscaped("")) } diff --git a/websurfx/config.lua b/websurfx/config.lua index 22e2c4f..62ae412 100644 --- a/websurfx/config.lua +++ b/websurfx/config.lua @@ -54,4 +54,5 @@ upstream_search_engines = { Searx = false, Brave = false, Startpage = false, + LibreX = false, } -- select the upstream search engines from which the results should be fetched.