feat: Add favicon support; improvements & bugfixes

This commit is contained in:
Myzel394 2024-02-25 16:17:21 +01:00
parent 877ecd1fee
commit 6269636e5a
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
13 changed files with 597 additions and 102 deletions

154
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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#"<li class="b_algo".*?<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();
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>) {

View File

@ -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>) {

View File

@ -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>) {

View File

@ -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();

View File

@ -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])
}

View File

@ -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;
}

View File

@ -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> -->

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>tifsep</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="style.css" rel="stylesheet">
</head>
<body>
<main id="frontpage">
<form method="post">
<input id="front-search" name="query" type="search" autofocus>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,31 @@
<li class="result" id="__ID__">
<a href="{% url %}" target="_blank" rel="noopener noreferrer nofollower">
<article>
<div class="image">
<!-- We need to add a separate div that adds the roundness,
as directly rounding the <img> tag will cause a the
broken, misaligned image to be visible when
the fallback image is shown. -->
<div class="round">
<img src="{% image_url %}" alt="🌐" loading="lazy">
</div>
</div>
<div class="content">
<small class="url">{% url %}</small>
<h3>{% title %}</h3>
<p>{% description %}</p>
</div>
</article>
<ul class="search-engines">
<li class="bing">Bing</li>
<li class="duckduckgo">DuckDuckGo</li>
<li class="brave">Brave</li>
</ul>
<small>{% date %}</small>
</a>
</li>
<style>
#__ID__ .image img::after {
background-image: url("https://{% url_host %}/favicon.ico");
}
</style>

17
src/rocket_requests.rs Normal file
View File

@ -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<Self, Self::Error> {
// 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)
// }
// }
// }

View File

@ -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<String, Error> {
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!(
"<style>{}</style>",
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!(
"<style>#{} .search-engines .{} {{ opacity: 1 !important; }}</style>",
id,
engine.to_string().to_lowercase()
)
}
}