add optional api
This commit is contained in:
parent
cb0edfd4fc
commit
24b8bb749c
@ -1,8 +1,7 @@
|
|||||||
# See https://github.com/mat-1/metasearch2/blob/master/config-base.toml and
|
# See src/config.rs for all of the possible options
|
||||||
# https://github.com/mat-1/metasearch2/blob/master/src/config.rs for some of
|
|
||||||
# the possible options
|
|
||||||
|
|
||||||
bind = "0.0.0.0:28019"
|
bind = "0.0.0.0:28019"
|
||||||
|
api = false
|
||||||
|
|
||||||
[ui]
|
[ui]
|
||||||
# engine_list_separator = true
|
# engine_list_separator = true
|
||||||
|
@ -8,6 +8,8 @@ use crate::engines::Engine;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub bind: SocketAddr,
|
pub bind: SocketAddr,
|
||||||
|
/// Whether the JSON API should be accessible.
|
||||||
|
pub api: bool,
|
||||||
pub ui: UiConfig,
|
pub ui: UiConfig,
|
||||||
pub image_search: ImageSearchConfig,
|
pub image_search: ImageSearchConfig,
|
||||||
pub engines: EnginesConfig,
|
pub engines: EnginesConfig,
|
||||||
@ -16,6 +18,7 @@ pub struct Config {
|
|||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct PartialConfig {
|
pub struct PartialConfig {
|
||||||
pub bind: Option<SocketAddr>,
|
pub bind: Option<SocketAddr>,
|
||||||
|
pub api: Option<bool>,
|
||||||
pub ui: Option<PartialUiConfig>,
|
pub ui: Option<PartialUiConfig>,
|
||||||
pub image_search: Option<PartialImageSearchConfig>,
|
pub image_search: Option<PartialImageSearchConfig>,
|
||||||
pub engines: Option<PartialEnginesConfig>,
|
pub engines: Option<PartialEnginesConfig>,
|
||||||
@ -24,6 +27,7 @@ pub struct PartialConfig {
|
|||||||
impl Config {
|
impl Config {
|
||||||
pub fn overlay(&mut self, partial: PartialConfig) {
|
pub fn overlay(&mut self, partial: PartialConfig) {
|
||||||
self.bind = partial.bind.unwrap_or(self.bind);
|
self.bind = partial.bind.unwrap_or(self.bind);
|
||||||
|
self.api = partial.api.unwrap_or(self.api);
|
||||||
self.ui.overlay(partial.ui.unwrap_or_default());
|
self.ui.overlay(partial.ui.unwrap_or_default());
|
||||||
self.image_search
|
self.image_search
|
||||||
.overlay(partial.image_search.unwrap_or_default());
|
.overlay(partial.image_search.unwrap_or_default());
|
||||||
@ -79,16 +83,16 @@ impl ImageSearchConfig {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ImageProxyConfig {
|
pub struct ImageProxyConfig {
|
||||||
|
/// Whether we should proxy remote images through our server. This is mostly
|
||||||
|
/// a privacy feature.
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
/// The maximum size of an image that can be proxied. This is in bytes.
|
||||||
pub max_download_size: u64,
|
pub max_download_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Default)]
|
#[derive(Deserialize, Debug, Default)]
|
||||||
pub struct PartialImageProxyConfig {
|
pub struct PartialImageProxyConfig {
|
||||||
/// Whether we should proxy remote images through our server. This is mostly
|
|
||||||
/// a privacy feature.
|
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
/// The maximum size of an image that can be proxied. This is in bytes.
|
|
||||||
pub max_download_size: Option<u64>,
|
pub max_download_size: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +149,9 @@ impl EnginesConfig {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EngineConfig {
|
pub struct EngineConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
/// The priority of this engine relative to the other engines.
|
||||||
pub weight: f64,
|
pub weight: f64,
|
||||||
|
/// Per-engine configs. These are parsed at request time.
|
||||||
pub extra: toml::Table,
|
pub extra: toml::Table,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,10 +160,8 @@ pub struct PartialEngineConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
|
|
||||||
/// The priority of this engine relative to the other engines.
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub weight: Option<f64>,
|
pub weight: Option<f64>,
|
||||||
/// Per-engine configs. These are parsed at request time.
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub extra: toml::Table,
|
pub extra: toml::Table,
|
||||||
}
|
}
|
||||||
@ -194,6 +198,7 @@ impl Default for Config {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Config {
|
Config {
|
||||||
bind: "0.0.0.0:28019".parse().unwrap(),
|
bind: "0.0.0.0:28019".parse().unwrap(),
|
||||||
|
api: false,
|
||||||
ui: UiConfig {
|
ui: UiConfig {
|
||||||
show_engine_list_separator: false,
|
show_engine_list_separator: false,
|
||||||
show_version_info: false,
|
show_version_info: false,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! engines {
|
macro_rules! engines {
|
||||||
($($engine:ident = $id:expr),* $(,)?) => {
|
($($engine:ident = $id:expr),* $(,)?) => {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||||
pub enum Engine {
|
pub enum Engine {
|
||||||
$($engine,)*
|
$($engine,)*
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ use eyre::bail;
|
|||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use maud::PreEscaped;
|
use maud::PreEscaped;
|
||||||
use reqwest::{header::HeaderMap, RequestBuilder};
|
use reqwest::{header::HeaderMap, RequestBuilder};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
@ -204,7 +204,7 @@ impl From<HttpResponse> for reqwest::Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct EngineSearchResult {
|
pub struct EngineSearchResult {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
@ -261,7 +261,7 @@ impl EngineImagesResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct EngineImageResult {
|
pub struct EngineImageResult {
|
||||||
pub image_url: String,
|
pub image_url: String,
|
||||||
pub page_url: String,
|
pub page_url: String,
|
||||||
@ -602,35 +602,38 @@ pub static CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
pub search_results: Vec<SearchResult<EngineSearchResult>>,
|
pub search_results: Vec<SearchResult<EngineSearchResult>>,
|
||||||
pub featured_snippet: Option<FeaturedSnippet>,
|
pub featured_snippet: Option<FeaturedSnippet>,
|
||||||
pub answer: Option<Answer>,
|
pub answer: Option<Answer>,
|
||||||
pub infobox: Option<Infobox>,
|
pub infobox: Option<Infobox>,
|
||||||
|
#[serde(skip)]
|
||||||
pub config: Arc<Config>,
|
pub config: Arc<Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct ImagesResponse {
|
pub struct ImagesResponse {
|
||||||
pub image_results: Vec<SearchResult<EngineImageResult>>,
|
pub image_results: Vec<SearchResult<EngineImageResult>>,
|
||||||
|
#[serde(skip)]
|
||||||
pub config: Arc<Config>,
|
pub config: Arc<Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
pub enum ResponseForTab {
|
pub enum ResponseForTab {
|
||||||
All(Response),
|
All(Response),
|
||||||
Images(ImagesResponse),
|
Images(ImagesResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct SearchResult<R> {
|
pub struct SearchResult<R: Serialize> {
|
||||||
pub result: R,
|
pub result: R,
|
||||||
pub engines: BTreeSet<Engine>,
|
pub engines: BTreeSet<Engine>,
|
||||||
pub score: f64,
|
pub score: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct FeaturedSnippet {
|
pub struct FeaturedSnippet {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
@ -638,14 +641,16 @@ pub struct FeaturedSnippet {
|
|||||||
pub engine: Engine,
|
pub engine: Engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct Answer {
|
pub struct Answer {
|
||||||
|
#[serde(serialize_with = "serialize_markup")]
|
||||||
pub html: PreEscaped<String>,
|
pub html: PreEscaped<String>,
|
||||||
pub engine: Engine,
|
pub engine: Engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct Infobox {
|
pub struct Infobox {
|
||||||
|
#[serde(serialize_with = "serialize_markup")]
|
||||||
pub html: PreEscaped<String>,
|
pub html: PreEscaped<String>,
|
||||||
pub engine: Engine,
|
pub engine: Engine,
|
||||||
}
|
}
|
||||||
@ -654,3 +659,10 @@ pub struct AutocompleteResult {
|
|||||||
pub query: String,
|
pub query: String,
|
||||||
pub score: f64,
|
pub score: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialize_markup<S>(markup: &PreEscaped<String>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&markup.0)
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ use axum::{
|
|||||||
extract::{ConnectInfo, Query, State},
|
extract::{ConnectInfo, Query, State},
|
||||||
http::{header, HeaderMap, StatusCode},
|
http::{header, HeaderMap, StatusCode},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
|
Json,
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use maud::{html, PreEscaped, DOCTYPE};
|
use maud::{html, PreEscaped, DOCTYPE};
|
||||||
@ -128,7 +129,7 @@ pub async fn route(
|
|||||||
State(config): State<Arc<Config>>,
|
State(config): State<Arc<Config>>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||||
) -> impl IntoResponse {
|
) -> axum::response::Response {
|
||||||
let query = params
|
let query = params
|
||||||
.get("q")
|
.get("q")
|
||||||
.cloned()
|
.cloned()
|
||||||
@ -144,7 +145,8 @@ pub async fn route(
|
|||||||
(header::CONTENT_TYPE, "text/html; charset=utf-8"),
|
(header::CONTENT_TYPE, "text/html; charset=utf-8"),
|
||||||
],
|
],
|
||||||
Body::from("<a href=\"/\">No query provided, click here to go back to index</a>"),
|
Body::from("<a href=\"/\">No query provided, click here to go back to index</a>"),
|
||||||
);
|
)
|
||||||
|
.into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
let search_tab = params
|
let search_tab = params
|
||||||
@ -176,6 +178,29 @@ pub async fn route(
|
|||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let trying_to_use_api =
|
||||||
|
query.request_headers.get("accept") == Some(&"application/json".to_string());
|
||||||
|
if trying_to_use_api {
|
||||||
|
if !config.api {
|
||||||
|
return (StatusCode::FORBIDDEN, "API access is disabled").into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (progress_tx, mut progress_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
let search_future = tokio::spawn(async move { engines::search(&query, progress_tx).await });
|
||||||
|
if let Err(e) = search_future.await {
|
||||||
|
return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
while let Some(progress_update) = progress_rx.recv().await {
|
||||||
|
if let ProgressUpdateData::Response(r) = progress_update.data {
|
||||||
|
results.push(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json(results).into_response();
|
||||||
|
}
|
||||||
|
|
||||||
let s = stream! {
|
let s = stream! {
|
||||||
type R = Result<Bytes, eyre::Error>;
|
type R = Result<Bytes, eyre::Error>;
|
||||||
|
|
||||||
@ -238,11 +263,11 @@ pub async fn route(
|
|||||||
let stream = Body::from_stream(s);
|
let stream = Body::from_stream(s);
|
||||||
|
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
|
||||||
[
|
[
|
||||||
(header::CONTENT_TYPE, "text/html; charset=utf-8"),
|
(header::CONTENT_TYPE, "text/html; charset=utf-8"),
|
||||||
(header::TRANSFER_ENCODING, "chunked"),
|
(header::TRANSFER_ENCODING, "chunked"),
|
||||||
],
|
],
|
||||||
stream,
|
stream,
|
||||||
)
|
)
|
||||||
|
.into_response()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user