rewrite readme

This commit is contained in:
mat 2024-06-30 23:04:04 -05:00
parent a02ea0d9d4
commit 2a029c4dd7
5 changed files with 257 additions and 161 deletions

20
Cargo.lock generated
View File

@ -90,9 +90,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.4.6" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5"
dependencies = [ dependencies = [
"brotli", "brotli",
"flate2", "flate2",
@ -275,9 +275,9 @@ dependencies = [
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "3.4.0" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
"alloc-stdlib", "alloc-stdlib",
@ -286,9 +286,9 @@ dependencies = [
[[package]] [[package]]
name = "brotli-decompressor" name = "brotli-decompressor"
version = "2.5.1" version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
"alloc-stdlib", "alloc-stdlib",
@ -422,9 +422,9 @@ dependencies = [
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.0" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -564,9 +564,9 @@ checksum = "09078d60b5387e99317a3ecadd61b5a521deab55186e9dab76d7f0ff66838670"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.28" version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",

79
README
View File

@ -1,25 +1,72 @@
a cute metasearch engine ==========
metasearch
==========
it sources from google, bing, brave, and a few others. https://github.com/mat-1/metasearch2
it's written in rust, using as little client-side javascript as possible. ----
INFO
----
there's a demo instance at https://s.matdoes.dev, but don't use it as your metasearch (aka metasearch2) is a cute metasearch engine. It sources its results
default or rely on it, please (so i don't get ratelimited by google). from Google, Bing, Brave, and several others. It's designed to be as lightweight
as possible, both on the server and client. There is no required client-side
JavaScript.
USAGE There's a public demo instance at https://s.matdoes.dev, but please do not use
it as your default or rely on it. This is so I don't get ratelimited by Google
or other engines. Run your own instance instead!
build it with `cargo b -r`, the resulting binary will be at ------------
`target/release/metasearch2`. INSTALLATION
------------
the config.toml file is created in your current working directory on the first The easiest way to install metasearch is with `cargo install metasearch`. To get
run of metasearch2. alternatively, you can copy the config-default.toml in the the unstable version with the latest features, you can install it with
repo and rename it to config.toml. `cargo install --git https://github.com/mat-1/metasearch2`.
the default port is 28019. Usage: `metasearch [config_file]`
API The config_file argument is optional; if it's not specified then it'll be
checked at the following locations:
metasearch has a JSON API that can be enabled by setting `api = true` in your config. to use it, - $XDG_CONFIG_HOME/metasearch/config.toml
just set the `Accept: application/json` header. as the api works by serializing internal structs, - $HOME/.config/metasearch/config.toml
it's not guaranteed to be stable across metasearch versions. - ./config.toml
If no config file exists, it'll be created at the first valid path in the list.
By default, metasearch runs on the port 28019. You are recommended to use a
reverse proxy.
-------------
CONFIGURATION
-------------
You can see all the default config options at `src/config.rs`. Some interesting
options you may want to change are:
- bind - the host and port that the web server runs on, for example
`0.0.0.0:28019`.
- api - whether your instance is accessible through a JSON API. See below for
more details.
- ui.stylesheet_url - a link to a stylesheet that will be loaded alongside the
main one, for example `/themes/catppuccin-mocha.css`.
- image_search.enabled - add a tab for viewing image results for your query.
this is disabled by default as the image proxy could be used to make GET
requests to arbitrary URLs from your server.
- engines.google.weight - the ranking score multiplier for an engine, you can
modify this if you prefer the results from certain engines.
--------
JSON API
--------
metasearch has a JSON API that can be enabled by setting `api = true` in your
config. To use it, set the `Accept: application/json` header in your requests.
For example:
curl 'http://localhost:28019/search?q=sandcats' -H 'Accept: application/json'
The structure of the API is not guaranteed to be stable, as it relies on
serializing internal structs. It may break across versions!

View File

@ -11,6 +11,134 @@ use tracing::info;
use crate::engines::Engine; use crate::engines::Engine;
impl Default for Config {
fn default() -> Self {
Config {
bind: "0.0.0.0:28019".parse().unwrap(),
api: false,
ui: UiConfig {
show_engine_list_separator: false,
show_version_info: false,
site_name: "metasearch".to_string(),
show_settings_link: true,
stylesheet_url: "".to_string(),
stylesheet_str: "".to_string(),
},
image_search: ImageSearchConfig {
enabled: false,
show_engines: true,
proxy: ImageProxyConfig {
enabled: true,
max_download_size: 10_000_000,
},
},
engines: Arc::new(EnginesConfig::default()),
}
}
}
impl Default for EnginesConfig {
fn default() -> Self {
use toml::value::Value;
let mut map = HashMap::new();
// engines are enabled by default, so engines that aren't listed here are
// enabled
// main search engines
map.insert(Engine::Google, EngineConfig::new().with_weight(1.05));
map.insert(Engine::Bing, EngineConfig::new().with_weight(1.0));
map.insert(Engine::Brave, EngineConfig::new().with_weight(1.25));
map.insert(
Engine::Marginalia,
EngineConfig::new().with_weight(0.15).with_extra(
vec![(
"args".to_string(),
Value::Table(
vec![
("profile".to_string(), Value::String("corpo".to_string())),
("js".to_string(), Value::String("default".to_string())),
("adtech".to_string(), Value::String("default".to_string())),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect(),
),
);
// additional search engines
map.insert(
Engine::GoogleScholar,
EngineConfig::new().with_weight(0.50).disabled(),
);
map.insert(
Engine::RightDao,
EngineConfig::new().with_weight(0.10).disabled(),
);
map.insert(
Engine::Stract,
EngineConfig::new().with_weight(0.15).disabled(),
);
map.insert(
Engine::Yep,
EngineConfig::new().with_weight(0.10).disabled(),
);
// calculators (give them a high weight so they're always the first thing in
// autocomplete)
map.insert(Engine::Numbat, EngineConfig::new().with_weight(10.0));
map.insert(
Engine::Fend,
EngineConfig::new().with_weight(10.0).disabled(),
);
// other engines
map.insert(
Engine::Mdn,
EngineConfig::new().with_extra(
vec![("max_sections".to_string(), Value::Integer(1))]
.into_iter()
.collect(),
),
);
Self { map }
}
}
impl Default for EngineConfig {
fn default() -> Self {
Self {
enabled: true,
weight: 1.0,
extra: Default::default(),
}
}
}
static DEFAULT_ENGINE_CONFIG_REF: LazyLock<EngineConfig> = LazyLock::new(EngineConfig::default);
impl EngineConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_weight(self, weight: f64) -> Self {
Self { weight, ..self }
}
pub fn disabled(self) -> Self {
Self {
enabled: false,
..self
}
}
pub fn with_extra(self, extra: toml::Table) -> Self {
Self { extra, ..self }
}
}
//
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Config { pub struct Config {
pub bind: SocketAddr, pub bind: SocketAddr,
@ -214,133 +342,3 @@ impl Config {
Ok(config) Ok(config)
} }
} }
//
// DEFAULTS
//
impl Default for Config {
fn default() -> Self {
Config {
bind: "0.0.0.0:28019".parse().unwrap(),
api: false,
ui: UiConfig {
show_engine_list_separator: false,
show_version_info: false,
site_name: "metasearch".to_string(),
show_settings_link: true,
stylesheet_url: "".to_string(),
stylesheet_str: "".to_string(),
},
image_search: ImageSearchConfig {
enabled: false,
show_engines: true,
proxy: ImageProxyConfig {
enabled: true,
max_download_size: 10_000_000,
},
},
engines: Arc::new(EnginesConfig::default()),
}
}
}
impl Default for EngineConfig {
fn default() -> Self {
Self {
enabled: true,
weight: 1.0,
extra: Default::default(),
}
}
}
static DEFAULT_ENGINE_CONFIG_REF: LazyLock<EngineConfig> = LazyLock::new(EngineConfig::default);
impl EngineConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_weight(self, weight: f64) -> Self {
Self { weight, ..self }
}
pub fn disabled(self) -> Self {
Self {
enabled: false,
..self
}
}
pub fn with_extra(self, extra: toml::Table) -> Self {
Self { extra, ..self }
}
}
impl Default for EnginesConfig {
fn default() -> Self {
use toml::value::Value;
let mut map = HashMap::new();
// engines are enabled by default, so engines that aren't listed here are
// enabled
// main search engines
map.insert(Engine::Google, EngineConfig::new().with_weight(1.05));
map.insert(Engine::Bing, EngineConfig::new().with_weight(1.0));
map.insert(Engine::Brave, EngineConfig::new().with_weight(1.25));
map.insert(
Engine::Marginalia,
EngineConfig::new().with_weight(0.15).with_extra(
vec![(
"args".to_string(),
Value::Table(
vec![
("profile".to_string(), Value::String("corpo".to_string())),
("js".to_string(), Value::String("default".to_string())),
("adtech".to_string(), Value::String("default".to_string())),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect(),
),
);
// additional search engines
map.insert(
Engine::GoogleScholar,
EngineConfig::new().with_weight(0.50).disabled(),
);
map.insert(
Engine::RightDao,
EngineConfig::new().with_weight(0.10).disabled(),
);
map.insert(
Engine::Stract,
EngineConfig::new().with_weight(0.15).disabled(),
);
map.insert(
Engine::Yep,
EngineConfig::new().with_weight(0.10).disabled(),
);
// calculators (give them a high weight so they're always the first thing in
// autocomplete)
map.insert(Engine::Numbat, EngineConfig::new().with_weight(10.0));
map.insert(
Engine::Fend,
EngineConfig::new().with_weight(10.0).disabled(),
);
// other engines
map.insert(
Engine::Mdn,
EngineConfig::new().with_extra(
vec![("max_sections".to_string(), Value::Integer(1))]
.into_iter()
.collect(),
),
);
Self { map }
}
}

View File

@ -1,4 +1,7 @@
use std::path::Path; use std::{
env,
path::{Path, PathBuf},
};
use config::Config; use config::Config;
use tracing::error; use tracing::error;
@ -13,10 +16,13 @@ pub mod web;
async fn main() { async fn main() {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let config_path = std::env::args().nth(1).unwrap_or("config.toml".into()); if env::args().any(|arg| arg == "--help" || arg == "-h" || arg == "help" || arg == "h") {
let config_path = Path::new(&config_path); println!("Usage: metasearch [config_path]");
return;
}
let config = match Config::read_or_create(config_path) { let config_path = config_path();
let config = match Config::read_or_create(&config_path) {
Ok(config) => config, Ok(config) => config,
Err(err) => { Err(err) => {
error!("Couldn't parse config:\n{err}"); error!("Couldn't parse config:\n{err}");
@ -25,3 +31,47 @@ async fn main() {
}; };
web::run(config).await; web::run(config).await;
} }
fn config_path() -> PathBuf {
if let Some(config_path) = env::args().nth(1) {
return PathBuf::from(config_path);
}
let app_name = env!("CARGO_PKG_NAME");
let mut default_config_dir = None;
// $XDG_CONFIG_HOME/metasearch/config.toml
if let Ok(xdg_config_home) = env::var("XDG_CONFIG_HOME") {
let path = PathBuf::from(xdg_config_home)
.join(app_name)
.join("config.toml");
if path.is_file() {
return path;
}
if default_config_dir.is_none() {
default_config_dir = Some(path);
}
}
// $HOME/.config/metasearch/config.toml
if let Ok(home) = env::var("HOME") {
let path = PathBuf::from(home)
.join(".config")
.join(app_name)
.join("config.toml");
if path.is_file() {
return path;
}
if default_config_dir.is_none() {
default_config_dir = Some(path);
}
}
// ./config.toml
let path = Path::new("config.toml");
if path.exists() {
return path.to_path_buf();
}
default_config_dir.unwrap_or(PathBuf::from("config.toml"))
}

View File

@ -448,6 +448,7 @@ h3.answer-thesaurus-category-title {
.image-result { .image-result {
min-width: 12rem; min-width: 12rem;
position: relative; position: relative;
flex-grow: 1;
} }
.image-result-img-container { .image-result-img-container {
margin: 0 auto; margin: 0 auto;
@ -459,7 +460,7 @@ h3.answer-thesaurus-category-title {
} }
.image-result-page-anchor { .image-result-page-anchor {
display: block; display: block;
height: 2em; height: 2.25em;
} }
.image-result-page-url { .image-result-page-url {
overflow: hidden; overflow: hidden;