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.