From 6269636e5a71629fd5599fe67aac4b16ead44e87 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:17:21 +0100 Subject: [PATCH] feat: Add favicon support; improvements & bugfixes --- Cargo.lock | 154 +++++++++++++++++++++++++++++++++ Cargo.toml | 3 + src/engines/bing.rs | 11 ++- src/engines/brave.rs | 11 ++- src/engines/duckduckgo.rs | 7 +- src/engines/engine_base.rs | 126 +++++++++++++++++++++++++-- src/main.rs | 86 +++++++++--------- src/public/css/style.css | 150 +++++++++++++++++++++++++++----- src/public/html/beginning.html | 28 +++--- src/public/html/frontpage.html | 19 ++++ src/public/html/result.html | 31 +++++++ src/rocket_requests.rs | 17 ++++ src/static_files.rs | 56 +++++++++--- 13 files changed, 597 insertions(+), 102 deletions(-) create mode 100644 src/public/html/frontpage.html create mode 100644 src/public/html/result.html create mode 100644 src/rocket_requests.rs diff --git a/Cargo.lock b/Cargo.lock index 87076ca..91a529e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,19 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -26,6 +39,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -210,6 +238,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.0", +] + [[package]] name = "clang-sys" version = "1.7.0" @@ -637,6 +679,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -905,6 +970,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -1038,6 +1112,48 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1546,6 +1662,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -1637,14 +1759,17 @@ dependencies = [ name = "tcp_test" version = "0.1.0" dependencies = [ + "ahash", "async-trait", "bytes", + "chrono", "futures", "html-escape", "lazy-regex", "lazy_static", "log", "mio", + "phf", "regex", "reqwest", "rocket", @@ -2144,6 +2269,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2304,6 +2438,26 @@ dependencies = [ "is-terminal", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 44825fd..91618dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,17 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ahash = "0.8.9" async-trait = "0.1.77" bytes = "1.5.0" +chrono = "0.4.34" futures = "0.3.30" html-escape = "0.2.13" lazy-regex = "3.1.0" lazy_static = "1.4.0" log = "0.4.20" mio = { version = "0.8.10", features = ["net", "os-poll", ] } +phf = { version = "0.11.2", features = ["macros"] } regex = "1.10.3" reqwest = { version = "0.11.23", features = ["stream"] } rocket = "0.5.0" diff --git a/src/engines/bing.rs b/src/engines/bing.rs index ade3009..cd5d185 100644 --- a/src/engines/bing.rs +++ b/src/engines/bing.rs @@ -14,9 +14,11 @@ pub mod bing { lazy_static! { static ref RESULTS_START: Regex = Regex::new(r#"id="b_results""#).unwrap(); - static ref SINGLE_RESULT: Regex = Regex::new(r#"
  • (?P.+?)</a></h2>.*?((<div class="b_caption.*?<p.*?)|(<p class="b_lineclamp.*?))><span.*?</span>(?P<description>.*?)</p>.*?</li>"#).unwrap(); + static ref SINGLE_RESULT: Regex = Regex::new(r#"<li class="b_algo".*?siteicon.*?>.*?<img src="(?P<image>.+?)"(?:.*?class="b_attribution".*?u="(?P<cache>.+?)")?.*?<h2.*?><a href="(?P<url>.+?)".*?>(?P<title>.+?)</a></h2>.*?((<div class="b_caption.*?<p.*?)|(<p class="b_lineclamp.*?))><span.*?</span>(?P<description>.*?)</p>.*?</li>"#).unwrap(); } + const DATE_FORMAT: &str = "%b %d, %Y"; + #[derive(Clone, Debug)] pub struct Bing { positions: EnginePositions, @@ -24,8 +26,11 @@ pub mod bing { impl EngineBase for Bing { fn parse_next<'a>(&mut self) -> Option<SearchResult> { - self.positions - .handle_block_using_default_method(&SINGLE_RESULT, SearchEngine::Bing) + self.positions.handle_block_using_default_method( + &SINGLE_RESULT, + SearchEngine::Bing, + Some(DATE_FORMAT), + ) } fn push_packet<'a>(&mut self, packet: impl Iterator<Item = &'a u8>) { diff --git a/src/engines/brave.rs b/src/engines/brave.rs index ac72a35..4b573d5 100644 --- a/src/engines/brave.rs +++ b/src/engines/brave.rs @@ -14,9 +14,11 @@ pub mod brave { lazy_static! { static ref RESULTS_START: Regex = Regex::new(r#"<body"#).unwrap(); - static ref SINGLE_RESULT: Regex = Regex::new(r#"<div class="snippet svelte-.+?<a href=.(?P<url>.+?)".+?<div class="title svelte-.+?">(?P<title>.+?)</div></div>.+?<div class="snippet-description.+?">(?P<description>.+?)</div></div>"#).unwrap(); + static ref SINGLE_RESULT: Regex = Regex::new(r#"<div class="snippet svelte-.+?<a href=.(?P<url>.+?)".+?(?:.+?<img.+?src="(?P<image>.+?)")?.+?<div class="title svelte-.+?">(?P<title>.+?)</div></div>.+?<div class="snippet-description.+?">(?:(?P<date>.+?) - )?(?P<description>.+?)</div>.*?</div>.*?</div>"#).unwrap(); } + const DATE_FORMAT: &str = "%m %d, %Y"; + #[derive(Clone, Debug)] pub struct Brave { positions: EnginePositions, @@ -24,8 +26,11 @@ pub mod brave { impl EngineBase for Brave { fn parse_next<'a>(&mut self) -> Option<SearchResult> { - self.positions - .handle_block_using_default_method(&SINGLE_RESULT, SearchEngine::Brave) + self.positions.handle_block_using_default_method( + &SINGLE_RESULT, + SearchEngine::Brave, + Some(DATE_FORMAT), + ) } fn push_packet<'a>(&mut self, packet: impl Iterator<Item = &'a u8>) { diff --git a/src/engines/duckduckgo.rs b/src/engines/duckduckgo.rs index b0efac4..b98fa5d 100644 --- a/src/engines/duckduckgo.rs +++ b/src/engines/duckduckgo.rs @@ -25,8 +25,11 @@ pub mod duckduckgo { impl EngineBase for DuckDuckGo { fn parse_next<'a>(&mut self) -> Option<SearchResult> { - self.positions - .handle_block_using_default_method(&SINGLE_RESULT, SearchEngine::DuckDuckGo) + self.positions.handle_block_using_default_method( + &SINGLE_RESULT, + SearchEngine::DuckDuckGo, + None, + ) } fn push_packet<'a>(&mut self, packet: impl Iterator<Item = &'a u8>) { diff --git a/src/engines/engine_base.rs b/src/engines/engine_base.rs index 7412646..b6c1fec 100644 --- a/src/engines/engine_base.rs +++ b/src/engines/engine_base.rs @@ -1,9 +1,16 @@ pub mod engine_base { use core::fmt; - use std::{fmt::Debug, fmt::Display, sync::Arc}; + use std::{ + fmt::{Debug, Display}, + hash::Hash, + ops::Sub, + sync::Arc, + }; + use chrono::{DateTime, TimeDelta, TimeZone, Utc}; use futures::{lock::Mutex, Future, StreamExt}; use lazy_static::lazy_static; + use phf::phf_map; use regex::Regex; use reqwest::{Error, Response}; use rustc_hash::FxHashMap; @@ -16,6 +23,9 @@ pub mod engine_base { static ref STRIP: Regex = Regex::new(r"[\s\n]+").unwrap(); static ref STRIP_HTML_TAGS: Regex = Regex::new(r#"<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>"#).unwrap(); + static ref RELATIVE_DATETIME_PARSER: Regex = + Regex::new(r#"(?P<amount>\d+) (?P<unit>second|minute|hour|day|week|month|year)s? ago"#) + .unwrap(); } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -35,21 +45,34 @@ pub mod engine_base { } } - #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub struct SearchResultDate { + pub date: DateTime<Utc>, + // true if original date wasn't available and only + // a relative time such as "2 hours ago" was provided + pub is_relative: bool, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct SearchResult { pub title: String, pub url: String, pub description: String, pub engine: SearchEngine, + pub image_url: Option<String>, + pub date: Option<SearchResultDate>, + } + + impl Hash for SearchResult { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.url.hash(state); + } } impl SearchResult { pub fn get_html_id(&self) -> String { - format!( - "html-id-{}-{}", - hash_string(&self.url), - self.url[self.url.len() - 5..].to_string(), - ) + // IDs must start with a letter, so we add an "h" (html ID) to the beginning + format!("h{:X}", hash_string(&self.url),) } } @@ -77,12 +100,26 @@ pub mod engine_base { let url = req.url().clone(); let mut stream = req.bytes_stream(); + let mut debug_has_fetched_once = false; + let mut debug_content = Vec::new(); + if cfg!(debug_assertions) { + println!("Requesting: {}", url); + } + while let Some(chunk) = stream.next().await { let buffer = chunk.unwrap(); self.push_packet(buffer.iter()); + if cfg!(debug_assertions) { + debug_content.extend(buffer); + } + while let Some(result) = self.parse_next() { + if cfg!(debug_assertions) { + debug_has_fetched_once = true; + } + if tx.send(result).await.is_err() { return Err(()); } @@ -90,11 +127,25 @@ pub mod engine_base { } while let Some(result) = self.parse_next() { + if cfg!(debug_assertions) { + debug_has_fetched_once = true; + } + if tx.send(result).await.is_err() { return Err(()); } } + if cfg!(debug_assertions) { + if debug_has_fetched_once { + println!("Finished fetching: {}", url); + } else { + println!("{}", "=============="); + println!("No results for: {}", url); + println!("{}", String::from_utf8_lossy(&debug_content)); + } + } + Ok(()) } } @@ -105,6 +156,16 @@ pub mod engine_base { pub started: bool, } + static UNIT_VALUES_MAP: phf::Map<&'static str, u32> = phf_map! { + "second" => 1, + "minute" => 60, + "hour" => 60 * 60, + "day" => 60 * 60 * 24, + "week" => 60 * 60 * 24 * 7, + "month" => 60 * 60 * 30, + "year" => 60 * 60 * 365, + }; + impl EnginePositions { pub fn new() -> Self { EnginePositions { @@ -113,6 +174,25 @@ pub mod engine_base { } } + pub fn parse_date(date: &str) -> Option<DateTime<Utc>> { + let raw_date_stripped = date.split_whitespace().collect::<Vec<&str>>().join(" "); + + if let Some(capture) = RELATIVE_DATETIME_PARSER.captures(&raw_date_stripped) { + let now = Utc::now(); + let amount = capture.name("amount")?.as_str().parse::<i64>().ok()?; + let unit = capture.name("unit")?.as_str(); + + let multiplier = UNIT_VALUES_MAP.get(&unit)?.clone() as i64; + let seconds_elapsed = amount * multiplier; + + let publish_date = now - TimeDelta::seconds(seconds_elapsed); + + Some(publish_date) + } else { + None + } + } + pub fn slice_remaining_block(&mut self, start_position: &usize) { let previous_block_bytes = self.previous_block.as_bytes().to_vec(); let remaining_bytes = previous_block_bytes[*start_position..].to_vec(); @@ -142,6 +222,7 @@ pub mod engine_base { &mut self, single_result_regex: &Regex, engine: SearchEngine, + date_format: Option<&str>, ) -> Option<SearchResult> { if self.started { if let Some(capture) = single_result_regex.captures(&self.previous_block.to_owned()) @@ -157,12 +238,43 @@ pub mod engine_base { let url = decode(capture.name("url").unwrap().as_str()) .unwrap() .into_owned(); + let image = match capture.name("image") { + Some(image) => Some(image.as_str().to_string()), + None => None, + }; + + let mut publish_date: Option<SearchResultDate> = None; + + if date_format.is_some() { + let date = capture.name("date"); + publish_date = match date { + Some(date) => { + match DateTime::parse_from_str(date.as_str(), date_format.unwrap()) + { + Ok(parsed_date) => Some(SearchResultDate { + date: parsed_date.to_utc(), + is_relative: false, + }), + Err(_) => match EnginePositions::parse_date(&date.as_str()) { + Some(parsed_date) => Some(SearchResultDate { + date: parsed_date.to_utc(), + is_relative: true, + }), + None => None, + }, + } + } + None => None, + }; + } let result = SearchResult { title, description, url, engine, + image_url: image, + date: publish_date, }; let end_position = capture.get(0).unwrap().end(); diff --git a/src/main.rs b/src/main.rs index e19d236..2089166 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,18 @@ use std::str; +use ahash::AHashSet; use engines::bing::bing::Bing; use engines::brave::brave::Brave; use engines::duckduckgo::duckduckgo::DuckDuckGo; use engines::engine_base::engine_base::SearchResult; use lazy_static::lazy_static; -use regex::Regex; +use rocket::form::Form; use rocket::response::content::{RawCss, RawHtml}; use rocket::response::stream::TextStream; use rocket::time::Instant; -use static_files::static_files::{render_beginning_html, render_finished_css}; +use static_files::static_files::{ + render_beginning_html, render_finished_css, render_result, render_result_engine_visibility, +}; use tokio::sync::mpsc; use crate::static_files::static_files::read_file_contents; @@ -25,26 +28,8 @@ pub mod utils; extern crate rocket; lazy_static! { - static ref HTML_BEGINNING: String = - read_file_contents("./src/public/html/beginning.html").unwrap(); - static ref SET_VALUE_REPLACE: Regex = Regex::new(r#"\{\% search_value \%\}"#).unwrap(); static ref HTML_END: String = read_file_contents("./src/public/html/end.html").unwrap(); static ref TAILWIND_CSS: String = read_file_contents("./src/public/css/style.css").unwrap(); - static ref FINISHED_CSS: String = read_file_contents("./src/public/css/finished.css").unwrap(); - static ref FINISHED_NAME_REPLACE: Regex = Regex::new(r#"(__engine__)"#).unwrap(); - static ref FINISHED_TIME_REPLACE: Regex = Regex::new(r#"{% time %}"#).unwrap(); -} - -#[get("/search")] -fn search_get() -> &'static str { - "<html> - <body> - <form method='get' action='/searchquery'> - <input name='query'> - <button type='submit'>Search</button> - </form> - </body> - </html>" } #[get("/style.css")] @@ -52,8 +37,29 @@ fn get_tailwindcss() -> RawCss<&'static str> { RawCss(&TAILWIND_CSS) } -#[get("/searchquery?<query>")] -async fn hello<'a>(query: &str) -> RawHtml<TextStream![String]> { +#[get("/")] +async fn search_get() -> RawHtml<&'static str> { + RawHtml(include_str!("./public/html/frontpage.html")) +} + +#[derive(FromForm)] +struct Body { + query: String, +} + +macro_rules! search { + ($engine:ident,$query_ref:expr,$tx_ref:expr) => {{ + tokio::spawn(async move { + let mut engine = $engine::new(); + + engine.search($query_ref, $tx_ref).await + }) + }}; +} + +#[post("/", data = "<body>")] +async fn search_post<'a>(body: Form<Body>) -> RawHtml<TextStream![String]> { + let query = &body.query; let query_brave = query.to_owned().clone(); let query_duckduckgo = query.to_owned().clone(); let query_bing = query.to_owned().clone(); @@ -72,31 +78,25 @@ async fn hello<'a>(query: &str) -> RawHtml<TextStream![String]> { let now = Instant::now(); - let brave_task = tokio::spawn(async move { - let mut brave = Brave::new(); - - brave.search(&query_brave, tx_brave).await; - }); - - let duckduckgo_task = tokio::spawn(async move { - let mut duckduckgo = DuckDuckGo::new(); - - duckduckgo.search(&query_duckduckgo, tx_duckduckgo).await; - }); - - let bing_task = tokio::spawn(async move { - let mut bing = Bing::new(); - - bing.search(&query_bing, tx_bing).await; - }); + let brave_task = search!(Brave, &query_brave, tx_brave); + let bing_task = search!(Bing, &query_bing, tx_bing); + let duckduckgo_task = search!(DuckDuckGo, &query_duckduckgo, tx_duckduckgo); let beginning_html = render_beginning_html(&query); + let mut results: AHashSet<String> = AHashSet::new(); + RawHtml(TextStream! { yield beginning_html; while !brave_task.is_finished() || !duckduckgo_task.is_finished() || !bing_task.is_finished() { while let Some(result) = rx.recv().await { + if results.contains(&result.url) { + yield render_result_engine_visibility(&result.get_html_id(), &result.engine); + + continue; + } + if !first_result_yielded { let diff = first_result_start.elapsed().whole_milliseconds(); first_result_yielded = true; @@ -123,9 +123,10 @@ async fn hello<'a>(query: &str) -> RawHtml<TextStream![String]> { yield render_finished_css("duckduckgo", now.elapsed().whole_milliseconds()); } - let text = format!("<li><h1>{}</h1><p>{}</p><i>{}</i></li>", &result.title, &result.description, &result.engine.to_string()); + yield render_result(&result); + yield render_result_engine_visibility(&result.get_html_id(), &result.engine); - yield text.to_string(); + results.insert(result.url.to_string()); } } @@ -151,7 +152,6 @@ async fn hello<'a>(query: &str) -> RawHtml<TextStream![String]> { #[launch] async fn rocket() -> _ { rocket::build() - .mount("/", routes![hello]) - .mount("/", routes![search_get]) + .mount("/", routes![search_post, search_get]) .mount("/", routes![get_tailwindcss]) } diff --git a/src/public/css/style.css b/src/public/css/style.css index d60513e..797af59 100644 --- a/src/public/css/style.css +++ b/src/public/css/style.css @@ -108,30 +108,12 @@ header { height: 2em; padding: 0.5em; border-radius: 50%; -} - -li { - list-style: none; - background: #222; - padding: 1em; - margin: 1em 0; - border-radius: 0.5em; - animation: moveIn 0.5s; -} - -li h1 { - color: #fff; text-decoration: none; - font-size: 1.2rem; -} - -li p { - color: #aaa; - line-height: 1.5; - font-size: 0.9rem; } li.fake { + background: #222; + border-radius: 0.5em; height: 6em; animation: fakeShimmer 0.8s infinite linear; } @@ -178,3 +160,131 @@ li.fake.small { opacity: 1; } } + +#front-search { + min-width: 20em; + font-size: 1.25rem; + padding: 1em 1.6em; + border: none; + border-radius: 10em; + background: #555; + color: #fff; +} + +#frontage { + display: flex; + justify-content: center; + align-items: center; +} + +.result { + list-style: none; + margin: 1em 0; + animation: moveIn 0.5s; +} + +.result article { + display: flex; + align-items: center; + gap: 1em; +} + +.result .content { + display: flex; + flex-direction: column; + gap: 0.5em; + justify-content: start; +} + +.result a { + padding: 1em; + text-decoration: none; + display: flex; + flex-direction: column; + gap: 0.5em; + + background: #222; + border-radius: 0.5em; +} + +.result .url { + /* Show max of 1 line */ + display: -webkit-box; + -webkit-line-clamp: 1; + line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.result a:visited { + background: #444; + color: red; +} + +.result h3 { + color: #fff; + text-decoration: none; + font-size: 1.2rem; +} + +.result p { + color: #aaa; + line-height: 1.5; + font-size: 0.9rem; +} + +.result small { + color: #888; +} + +.result .search-engines { + display: flex; + gap: 1em; + margin-top: 1em; +} + +.result .search-engines>li { + list-style: none; + opacity: 0; + color: #aaa; +} + +.result .image { + display: flex; + justify-content: center; + + background: #333; + border-radius: .5em; + width: 3em; + height: 3em; +} + +.result .image .round { + width: 2em; + height: 2em; + margin: .5em; + border-radius: .5em; + overflow: hidden; +} + +.result .image img { + width: 100%; + height: 100%; + position: relative; + + /* Hide broken image icon */ + -moz-force-broken-image-icon: 0; +} + +.result .image img::after { + content: " "; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: block; + background-size: contain; + background-repeat: no-repeat; + background-color: #333; +} diff --git a/src/public/html/beginning.html b/src/public/html/beginning.html index bb78c25..bfffc0b 100644 --- a/src/public/html/beginning.html +++ b/src/public/html/beginning.html @@ -10,12 +10,14 @@ <body> <header> - <form id="search-input" action="/searchquery" method="get"> - <svg id="back-button" xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--> - <path - d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z" /> - </svg> + <form id="search-input" method="post"> + <a id="back-button"> + <svg xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--> + <path + d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z" /> + </svg> + </a> <input id="search" name="query" type="search" placeholder="Search" value="{% search_value %}"> </form> <div id="search-status"> @@ -225,13 +227,13 @@ <main> <div id="results"> <ul> - <li class="fake small"></li> - <li class="fake big"></li> - <li class="fake"></li> - <li class="fake"></li> - <li class="fake small"></li> - <li class="fake"></li> - <li class="fake"></li> + <li class="result fake small"></li> + <li class="result fake big"></li> + <li class="result fake"></li> + <li class="result fake"></li> + <li class="result fake small"></li> + <li class="result fake"></li> + <li class="result fake"></li> <!-- </ul> --> <!-- </div> --> <!-- </main> --> diff --git a/src/public/html/frontpage.html b/src/public/html/frontpage.html new file mode 100644 index 0000000..21c0da2 --- /dev/null +++ b/src/public/html/frontpage.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <title>tifsep + + + + + + +
    +
    + +
    +
    + + + diff --git a/src/public/html/result.html b/src/public/html/result.html new file mode 100644 index 0000000..bb5af93 --- /dev/null +++ b/src/public/html/result.html @@ -0,0 +1,31 @@ +
  • + +
    +
    + +
    + 🌐 +
    +
    +
    + {% url %} +

    {% title %}

    +

    {% description %}

    +
    +
    + + {% date %} +
    +
  • + diff --git a/src/rocket_requests.rs b/src/rocket_requests.rs new file mode 100644 index 0000000..ed41344 --- /dev/null +++ b/src/rocket_requests.rs @@ -0,0 +1,17 @@ +struct UserAgent(String); + +// impl<'a, 'r> FromRequest<'a, 'r> for Token { +// type Error = Infallible; +// +// fn from_request(request: &'a Request<'r>) -> request::Outcome { +// let token = request.headers().get_one("token"); +// match token { +// Some(token) => { +// // check validity +// Outcome::Success(Token(token.to_string())) +// }, +// // token does not exist +// None => Outcome::Failure(Status::Unauthorized) +// } +// } +// } diff --git a/src/static_files.rs b/src/static_files.rs index 5b71e6a..cb32796 100644 --- a/src/static_files.rs +++ b/src/static_files.rs @@ -1,10 +1,18 @@ pub mod static_files { - use lazy_static::lazy_static; use std::{ + fmt::Debug, fs::File, + hash::Hash, io::{Error, Read}, }; + use reqwest::Url; + + use crate::{ + engines::engine_base::engine_base::{SearchEngine, SearchResult}, + utils::utils::hash_string, + }; + pub fn read_file_contents(path: &str) -> Result { let mut contents = String::new(); @@ -15,11 +23,7 @@ pub mod static_files { Ok(contents) } - lazy_static! { - static ref HTML_BEGINNING: String = - read_file_contents("./src/public/html/beginning.html").unwrap(); - } - + const HTML_BEGINNING: &str = include_str!("./public/html/beginning.html"); const HTML_BEGINNING_QUERY_REPLACE: &str = r#"{% search_value %}"#; pub fn render_beginning_html(query: &str) -> String { @@ -29,17 +33,47 @@ pub mod static_files { ) } - lazy_static! { - static ref FINISHED_CSS: String = - read_file_contents("./src/public/css/finished.css").unwrap(); - } + const FINISHED_CSS: &str = include_str!("./public/css/finished.css"); pub fn render_finished_css(engine: &str, time: i128) -> String { format!( "", FINISHED_CSS .replace("__engine__", engine) - .replace("{% time %}", &format!("{}ms", &time.to_string())) + .replace("{% time %}", &time.to_string()) + ) + } + + const HTML_RESULT: &str = include_str!("./public/html/result.html"); + + pub fn render_result(result: &SearchResult) -> String { + HTML_RESULT + .replace("{% title %}", &result.title) + .replace("{% url %}", &result.url) + .replace( + "{% url_host %}", + Url::parse(&result.url).unwrap().host_str().unwrap(), + ) + .replace("{% description %}", &result.description) + .replace("__ID__", &result.get_html_id()) + .replace( + "{% image_url %}", + &result.image_url.clone().unwrap_or("".to_string()), + ) + .replace( + "{% date %}", + &(match &result.date { + Some(date_info) => date_info.date.format("%d. %B %Y").to_string(), + None => "".to_string(), + }), + ) + } + + pub fn render_result_engine_visibility(id: &str, engine: &SearchEngine) -> String { + format!( + "", + id, + engine.to_string().to_lowercase() ) } }