mirror of
https://github.com/Myzel394/tifsep.git
synced 2025-06-18 15:35:26 +02:00
feat: Add favicon support; improvements & bugfixes
This commit is contained in:
parent
877ecd1fee
commit
6269636e5a
154
Cargo.lock
generated
154
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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>) {
|
||||
|
@ -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>) {
|
||||
|
@ -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>) {
|
||||
|
@ -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();
|
||||
|
86
src/main.rs
86
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])
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"
|
||||
<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> -->
|
||||
|
19
src/public/html/frontpage.html
Normal file
19
src/public/html/frontpage.html
Normal 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>
|
31
src/public/html/result.html
Normal file
31
src/public/html/result.html
Normal 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
17
src/rocket_requests.rs
Normal 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)
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user