2021-01-01 12:54:13 +13:00
|
|
|
// CRATES
|
2023-01-03 22:39:45 +13:00
|
|
|
use crate::utils::{self, catch_random, error, filter_posts, format_num, format_url, get_filters, param, redirect, setting, template, val, Post, Preferences};
|
2021-05-17 03:53:39 +12:00
|
|
|
use crate::{
|
|
|
|
client::json,
|
|
|
|
subreddit::{can_access_quarantine, quarantine},
|
|
|
|
RequestExt,
|
|
|
|
};
|
2021-01-01 12:54:13 +13:00
|
|
|
use askama::Template;
|
2021-03-18 11:30:33 +13:00
|
|
|
use hyper::{Body, Request, Response};
|
2023-01-01 16:57:42 +13:00
|
|
|
use once_cell::sync::Lazy;
|
|
|
|
use regex::Regex;
|
2021-01-01 12:54:13 +13:00
|
|
|
|
|
|
|
// STRUCTS
|
2021-01-03 19:37:54 +13:00
|
|
|
struct SearchParams {
|
|
|
|
q: String,
|
|
|
|
sort: String,
|
|
|
|
t: String,
|
|
|
|
before: String,
|
|
|
|
after: String,
|
|
|
|
restrict_sr: String,
|
2021-11-24 19:24:23 +13:00
|
|
|
typed: String,
|
2021-01-03 19:37:54 +13:00
|
|
|
}
|
|
|
|
|
2021-01-15 07:22:50 +13:00
|
|
|
// STRUCTS
|
|
|
|
struct Subreddit {
|
|
|
|
name: String,
|
|
|
|
url: String,
|
2021-03-19 17:32:54 +13:00
|
|
|
icon: String,
|
2021-01-15 07:22:50 +13:00
|
|
|
description: String,
|
2021-03-21 11:42:47 +13:00
|
|
|
subscribers: (String, String),
|
2021-01-15 07:22:50 +13:00
|
|
|
}
|
|
|
|
|
2021-01-01 12:54:13 +13:00
|
|
|
#[derive(Template)]
|
2022-05-21 17:28:31 +12:00
|
|
|
#[template(path = "search.html")]
|
2021-01-01 12:54:13 +13:00
|
|
|
struct SearchTemplate {
|
|
|
|
posts: Vec<Post>,
|
2021-01-15 07:22:50 +13:00
|
|
|
subreddits: Vec<Subreddit>,
|
2021-01-01 12:54:13 +13:00
|
|
|
sub: String,
|
2021-01-03 19:37:54 +13:00
|
|
|
params: SearchParams,
|
2021-01-09 14:35:04 +13:00
|
|
|
prefs: Preferences,
|
2021-05-10 13:25:52 +12:00
|
|
|
url: String,
|
2021-11-26 17:02:04 +13:00
|
|
|
/// Whether the subreddit itself is filtered.
|
|
|
|
is_filtered: bool,
|
|
|
|
/// Whether all fetched posts are filtered (to differentiate between no posts fetched in the first place,
|
|
|
|
/// and all fetched posts being filtered).
|
|
|
|
all_posts_filtered: bool,
|
2022-11-08 16:54:49 +13:00
|
|
|
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
|
|
|
|
all_posts_hidden_nsfw: bool,
|
2023-01-01 15:11:59 +13:00
|
|
|
no_posts: bool,
|
2021-01-01 12:54:13 +13:00
|
|
|
}
|
|
|
|
|
2023-01-01 16:57:42 +13:00
|
|
|
// Regex matched against search queries to determine if they are reddit urls.
|
|
|
|
static REDDIT_URL_MATCH: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://([^\./]+\.)*reddit.com/").unwrap());
|
|
|
|
|
2021-01-01 12:54:13 +13:00
|
|
|
// SERVICES
|
2021-03-18 11:30:33 +13:00
|
|
|
pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
2023-01-03 22:39:45 +13:00
|
|
|
// This ensures that during a search, no NSFW posts are fetched at all
|
|
|
|
let nsfw_results = if setting(&req, "show_nsfw") == "on" && !utils::sfw_only() {
|
|
|
|
"&include_over_18=on"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
2021-11-30 19:29:41 +13:00
|
|
|
let path = format!("{}.json?{}{}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default(), nsfw_results);
|
2023-01-01 16:57:42 +13:00
|
|
|
let mut query = param(&path, "q").unwrap_or_default();
|
|
|
|
query = REDDIT_URL_MATCH.replace(&query, "").to_string();
|
2021-09-10 12:28:55 +12:00
|
|
|
|
|
|
|
if query.is_empty() {
|
|
|
|
return Ok(redirect("/".to_string()));
|
|
|
|
}
|
|
|
|
|
2022-01-06 11:06:41 +13:00
|
|
|
if query.starts_with("r/") {
|
|
|
|
return Ok(redirect(format!("/{}", query)));
|
|
|
|
}
|
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
let sub = req.param("sub").unwrap_or_default();
|
2021-05-17 03:53:39 +12:00
|
|
|
let quarantined = can_access_quarantine(&req, &sub);
|
2021-05-10 03:40:49 +12:00
|
|
|
// Handle random subreddits
|
|
|
|
if let Ok(random) = catch_random(&sub, "/find").await {
|
|
|
|
return Ok(random);
|
|
|
|
}
|
2021-01-15 08:45:04 +13:00
|
|
|
|
2021-11-24 19:24:23 +13:00
|
|
|
let typed = param(&path, "type").unwrap_or_default();
|
|
|
|
|
2021-05-17 04:11:38 +12:00
|
|
|
let sort = param(&path, "sort").unwrap_or_else(|| "relevance".to_string());
|
2021-11-26 17:02:04 +13:00
|
|
|
let filters = get_filters(&req);
|
2021-01-15 07:22:50 +13:00
|
|
|
|
2021-05-21 07:24:06 +12:00
|
|
|
// If search is not restricted to this subreddit, show other subreddits in search results
|
2021-11-26 17:02:04 +13:00
|
|
|
let subreddits = if param(&path, "restrict_sr").is_none() {
|
|
|
|
let mut subreddits = search_subreddits(&query, &typed).await;
|
|
|
|
subreddits.retain(|s| !filters.contains(s.name.as_str()));
|
|
|
|
subreddits
|
|
|
|
} else {
|
|
|
|
Vec::new()
|
|
|
|
};
|
2021-01-01 12:54:13 +13:00
|
|
|
|
2021-05-10 13:25:52 +12:00
|
|
|
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
|
|
|
|
|
2021-11-26 17:02:04 +13:00
|
|
|
// If all requested subs are filtered, we don't need to fetch posts.
|
2021-12-27 18:18:20 +13:00
|
|
|
if sub.split('+').all(|s| filters.contains(s)) {
|
2021-11-26 17:02:04 +13:00
|
|
|
template(SearchTemplate {
|
|
|
|
posts: Vec::new(),
|
2021-02-10 06:38:52 +13:00
|
|
|
subreddits,
|
|
|
|
sub,
|
|
|
|
params: SearchParams {
|
2021-02-21 10:59:16 +13:00
|
|
|
q: query.replace('"', """),
|
2021-02-10 06:38:52 +13:00
|
|
|
sort,
|
2021-05-17 03:53:39 +12:00
|
|
|
t: param(&path, "t").unwrap_or_default(),
|
|
|
|
before: param(&path, "after").unwrap_or_default(),
|
2021-11-26 17:02:04 +13:00
|
|
|
after: "".to_string(),
|
2021-05-17 03:53:39 +12:00
|
|
|
restrict_sr: param(&path, "restrict_sr").unwrap_or_default(),
|
2021-11-24 19:24:23 +13:00
|
|
|
typed,
|
2021-02-10 06:38:52 +13:00
|
|
|
},
|
2023-01-02 15:39:38 +13:00
|
|
|
prefs: Preferences::new(&req),
|
2021-05-10 13:25:52 +12:00
|
|
|
url,
|
2021-11-26 17:02:04 +13:00
|
|
|
is_filtered: true,
|
|
|
|
all_posts_filtered: false,
|
2022-11-08 16:54:49 +13:00
|
|
|
all_posts_hidden_nsfw: false,
|
2023-01-01 15:11:59 +13:00
|
|
|
no_posts: false,
|
2021-11-26 17:02:04 +13:00
|
|
|
})
|
|
|
|
} else {
|
|
|
|
match Post::fetch(&path, quarantined).await {
|
|
|
|
Ok((mut posts, after)) => {
|
2022-11-10 05:16:51 +13:00
|
|
|
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
|
2023-01-01 15:11:59 +13:00
|
|
|
let no_posts = posts.is_empty();
|
|
|
|
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
|
2021-11-26 17:02:04 +13:00
|
|
|
template(SearchTemplate {
|
|
|
|
posts,
|
|
|
|
subreddits,
|
|
|
|
sub,
|
|
|
|
params: SearchParams {
|
|
|
|
q: query.replace('"', """),
|
|
|
|
sort,
|
|
|
|
t: param(&path, "t").unwrap_or_default(),
|
|
|
|
before: param(&path, "after").unwrap_or_default(),
|
|
|
|
after,
|
|
|
|
restrict_sr: param(&path, "restrict_sr").unwrap_or_default(),
|
|
|
|
typed,
|
|
|
|
},
|
2023-01-02 15:39:38 +13:00
|
|
|
prefs: Preferences::new(&req),
|
2021-11-26 17:02:04 +13:00
|
|
|
url,
|
|
|
|
is_filtered: false,
|
|
|
|
all_posts_filtered,
|
2022-11-08 16:54:49 +13:00
|
|
|
all_posts_hidden_nsfw,
|
2023-01-01 15:11:59 +13:00
|
|
|
no_posts,
|
2021-11-26 17:02:04 +13:00
|
|
|
})
|
|
|
|
}
|
|
|
|
Err(msg) => {
|
2023-02-26 20:40:32 +13:00
|
|
|
if msg == "quarantined" || msg == "gated" {
|
2021-11-26 17:02:04 +13:00
|
|
|
let sub = req.param("sub").unwrap_or_default();
|
2023-02-26 20:40:32 +13:00
|
|
|
quarantine(req, sub, msg)
|
2021-11-26 17:02:04 +13:00
|
|
|
} else {
|
|
|
|
error(req, msg).await
|
|
|
|
}
|
2021-05-17 03:53:39 +12:00
|
|
|
}
|
|
|
|
}
|
2021-01-01 12:54:13 +13:00
|
|
|
}
|
|
|
|
}
|
2021-01-15 08:45:04 +13:00
|
|
|
|
2021-11-24 19:24:23 +13:00
|
|
|
async fn search_subreddits(q: &str, typed: &str) -> Vec<Subreddit> {
|
|
|
|
let limit = if typed == "sr_user" { "50" } else { "3" };
|
|
|
|
let subreddit_search_path = format!("/subreddits/search.json?q={}&limit={}", q.replace(' ', "+"), limit);
|
2021-01-15 08:45:04 +13:00
|
|
|
|
|
|
|
// Send a request to the url
|
2021-05-21 07:24:06 +12:00
|
|
|
json(subreddit_search_path, false).await.unwrap_or_default()["data"]["children"]
|
|
|
|
.as_array()
|
|
|
|
.map(ToOwned::to_owned)
|
|
|
|
.unwrap_or_default()
|
|
|
|
.iter()
|
|
|
|
.map(|subreddit| {
|
|
|
|
// For each subreddit from subreddit list
|
|
|
|
// Fetch subreddit icon either from the community_icon or icon_img value
|
2021-11-24 19:24:23 +13:00
|
|
|
let icon = subreddit["data"]["community_icon"].as_str().map_or_else(|| val(subreddit, "icon_img"), ToString::to_string);
|
2021-03-19 17:32:54 +13:00
|
|
|
|
2021-05-21 07:24:06 +12:00
|
|
|
Subreddit {
|
2021-11-26 17:02:04 +13:00
|
|
|
name: val(subreddit, "display_name"),
|
2021-05-21 07:24:06 +12:00
|
|
|
url: val(subreddit, "url"),
|
|
|
|
icon: format_url(&icon),
|
|
|
|
description: val(subreddit, "public_description"),
|
|
|
|
subscribers: format_num(subreddit["data"]["subscribers"].as_f64().unwrap_or_default() as i64),
|
2021-01-15 08:45:04 +13:00
|
|
|
}
|
2021-05-21 07:24:06 +12:00
|
|
|
})
|
|
|
|
.collect::<Vec<Subreddit>>()
|
2021-01-15 08:45:04 +13:00
|
|
|
}
|