Added support for quarantined subreddits (#219)

* Added support for quarantined subreddits

* Added confirmation wall for quarantined subreddits

* Added quarantine walls to other routes and fixed case issue

* Correct obsolete use of cookie()

* Refactor param() and quarantine()

Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
This commit is contained in:
curlpipe 2021-05-16 16:53:39 +01:00 committed by GitHub
parent ed05f5a092
commit 8bb247af3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 174 additions and 70 deletions

View File

@ -60,7 +60,7 @@ async fn stream(url: &str, req: &Request<Body>) -> Result<Response<Body>, String
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }
fn request(url: String) -> Boxed<Result<Response<Body>, String>> { fn request(url: String, quarantine: bool) -> Boxed<Result<Response<Body>, String>> {
// Prepare the HTTPS connector. // Prepare the HTTPS connector.
let https = hyper_rustls::HttpsConnector::with_native_roots(); let https = hyper_rustls::HttpsConnector::with_native_roots();
@ -75,6 +75,7 @@ fn request(url: String) -> Boxed<Result<Response<Body>, String>> {
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
.header("Accept-Language", "en-US,en;q=0.5") .header("Accept-Language", "en-US,en;q=0.5")
.header("Connection", "keep-alive") .header("Connection", "keep-alive")
.header("Cookie", if quarantine { "_options=%7B%22pref_quarantine_optin%22%3A%20true%7D" } else { "" })
.body(Body::empty()); .body(Body::empty());
async move { async move {
@ -89,6 +90,7 @@ fn request(url: String) -> Boxed<Result<Response<Body>, String>> {
.map(|val| val.to_str().unwrap_or_default()) .map(|val| val.to_str().unwrap_or_default())
.unwrap_or_default() .unwrap_or_default()
.to_string(), .to_string(),
quarantine,
) )
.await .await
} else { } else {
@ -105,7 +107,7 @@ fn request(url: String) -> Boxed<Result<Response<Body>, String>> {
// Make a request to a Reddit API and parse the JSON response // Make a request to a Reddit API and parse the JSON response
#[cached(size = 100, time = 30, result = true)] #[cached(size = 100, time = 30, result = true)]
pub async fn json(path: String) -> Result<Value, String> { pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
// Build Reddit url from path // Build Reddit url from path
let url = format!("https://www.reddit.com{}", path); let url = format!("https://www.reddit.com{}", path);
@ -116,7 +118,7 @@ pub async fn json(path: String) -> Result<Value, String> {
}; };
// Fetch the url... // Fetch the url...
match request(url.clone()).await { match request(url.clone(), quarantine).await {
Ok(response) => { Ok(response) => {
// asynchronously aggregate the chunks of the body // asynchronously aggregate the chunks of the body
match hyper::body::aggregate(response).await { match hyper::body::aggregate(response).await {

View File

@ -194,7 +194,10 @@ async fn main() {
app.at("/settings/update").get(|r| settings::update(r).boxed()); app.at("/settings/update").get(|r| settings::update(r).boxed());
// Subreddit services // Subreddit services
app.at("/r/:sub").get(|r| subreddit::community(r).boxed()); app
.at("/r/:sub")
.get(|r| subreddit::community(r).boxed())
.post(|r| subreddit::add_quarantine_exception(r).boxed());
app app
.at("/r/u_:name") .at("/r/u_:name")

View File

@ -2,7 +2,9 @@
use crate::client::json; use crate::client::json;
use crate::esc; use crate::esc;
use crate::server::RequestExt; use crate::server::RequestExt;
use crate::subreddit::{can_access_quarantine, quarantine};
use crate::utils::{error, format_num, format_url, param, rewrite_urls, setting, template, time, val, Author, Comment, Flags, Flair, FlairPart, Media, Post, Preferences}; use crate::utils::{error, format_num, format_url, param, rewrite_urls, setting, template, time, val, Author, Comment, Flags, Flair, FlairPart, Media, Post, Preferences};
use hyper::{Body, Request, Response}; use hyper::{Body, Request, Response};
use async_recursion::async_recursion; use async_recursion::async_recursion;
@ -23,18 +25,22 @@ struct PostTemplate {
pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> { pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
// Build Reddit API path // Build Reddit API path
let mut path: String = format!("{}.json?{}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default()); let mut path: String = format!("{}.json?{}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default());
let sub = req.param("sub").unwrap_or_default();
let quarantined = can_access_quarantine(&req, &sub);
// Set sort to sort query parameter // Set sort to sort query parameter
let mut sort: String = param(&path, "sort"); let sort = param(&path, "sort").unwrap_or_else(|| {
// Grab default comment sort method from Cookies
let default_sort = setting(&req, "comment_sort");
// Grab default comment sort method from Cookies // If there's no sort query but there's a default sort, set sort to default_sort
let default_sort = setting(&req, "comment_sort"); if !default_sort.is_empty() {
path = format!("{}.json?{}&sort={}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default(), default_sort);
// If there's no sort query but there's a default sort, set sort to default_sort default_sort
if sort.is_empty() && !default_sort.is_empty() { } else {
sort = default_sort; String::new()
path = format!("{}.json?{}&sort={}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default(), sort); }
} });
// Log the post ID being fetched in debug mode // Log the post ID being fetched in debug mode
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -44,7 +50,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
let highlighted_comment = &req.param("comment_id").unwrap_or_default(); let highlighted_comment = &req.param("comment_id").unwrap_or_default();
// Send a request to the url, receive JSON in response // Send a request to the url, receive JSON in response
match json(path).await { match json(path, quarantined).await {
// Otherwise, grab the JSON output from the request // Otherwise, grab the JSON output from the request
Ok(res) => { Ok(res) => {
// Parse the JSON into Post and Comment structs // Parse the JSON into Post and Comment structs
@ -61,7 +67,14 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
}) })
} }
// If the Reddit API returns an error, exit and send error page to user // If the Reddit API returns an error, exit and send error page to user
Err(msg) => error(req, msg).await, Err(msg) => {
if msg == "quarantined" {
let sub = req.param("sub").unwrap_or_default();
quarantine(req, sub)
} else {
error(req, msg).await
}
}
} }
} }

View File

@ -1,6 +1,10 @@
// CRATES // CRATES
use crate::utils::{catch_random, error, format_num, format_url, param, setting, template, val, Post, Preferences}; use crate::utils::{catch_random, error, format_num, format_url, param, setting, template, val, Post, Preferences};
use crate::{client::json, RequestExt}; use crate::{
client::json,
subreddit::{can_access_quarantine, quarantine},
RequestExt,
};
use askama::Template; use askama::Template;
use hyper::{Body, Request, Response}; use hyper::{Body, Request, Response};
@ -39,27 +43,23 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
let nsfw_results = if setting(&req, "show_nsfw") == "on" { "&include_over_18=on" } else { "" }; let nsfw_results = if setting(&req, "show_nsfw") == "on" { "&include_over_18=on" } else { "" };
let path = format!("{}.json?{}{}", req.uri().path(), req.uri().query().unwrap_or_default(), nsfw_results); let path = format!("{}.json?{}{}", req.uri().path(), req.uri().query().unwrap_or_default(), nsfw_results);
let sub = req.param("sub").unwrap_or_default(); let sub = req.param("sub").unwrap_or_default();
let quarantined = can_access_quarantine(&req, &sub);
// Handle random subreddits // Handle random subreddits
if let Ok(random) = catch_random(&sub, "/find").await { if let Ok(random) = catch_random(&sub, "/find").await {
return Ok(random); return Ok(random);
} }
let query = param(&path, "q"); let query = param(&path, "q").unwrap_or_default();
let sort = if param(&path, "sort").is_empty() { let sort = param(&path, "sort").unwrap_or("relevance".to_string());
"relevance".to_string()
} else {
param(&path, "sort")
};
let subreddits = if param(&path, "restrict_sr").is_empty() { let subreddits = match param(&path, "restrict_sr") {
search_subreddits(&query).await None => search_subreddits(&query).await,
} else { Some(_) => Vec::new()
Vec::new()
}; };
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str())); let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
match Post::fetch(&path, String::new()).await { match Post::fetch(&path, String::new(), quarantined).await {
Ok((posts, after)) => template(SearchTemplate { Ok((posts, after)) => template(SearchTemplate {
posts, posts,
subreddits, subreddits,
@ -67,15 +67,22 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
params: SearchParams { params: SearchParams {
q: query.replace('"', "&quot;"), q: query.replace('"', "&quot;"),
sort, sort,
t: param(&path, "t"), t: param(&path, "t").unwrap_or_default(),
before: param(&path, "after"), before: param(&path, "after").unwrap_or_default(),
after, after,
restrict_sr: param(&path, "restrict_sr"), restrict_sr: param(&path, "restrict_sr").unwrap_or_default(),
}, },
prefs: Preferences::new(req), prefs: Preferences::new(req),
url, url,
}), }),
Err(msg) => error(req, msg).await, Err(msg) => {
if msg == "quarantined" {
let sub = req.param("sub").unwrap_or_default();
quarantine(req, sub)
} else {
error(req, msg).await
}
}
} }
} }
@ -83,7 +90,7 @@ async fn search_subreddits(q: &str) -> Vec<Subreddit> {
let subreddit_search_path = format!("/subreddits/search.json?q={}&limit=3", q.replace(' ', "+")); let subreddit_search_path = format!("/subreddits/search.json?q={}&limit=3", q.replace(' ', "+"));
// Send a request to the url // Send a request to the url
match json(subreddit_search_path).await { match json(subreddit_search_path, false).await {
// If success, receive JSON in response // If success, receive JSON in response
Ok(response) => { Ok(response) => {
match response["data"]["children"].as_array() { match response["data"]["children"].as_array() {

View File

@ -28,9 +28,20 @@ struct WikiTemplate {
prefs: Preferences, prefs: Preferences,
} }
#[derive(Template)]
#[template(path = "wall.html", escape = "none")]
struct WallTemplate {
title: String,
sub: String,
msg: String,
prefs: Preferences,
url: String,
}
// SERVICES // SERVICES
pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> { pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
// Build Reddit API path // Build Reddit API path
let root = req.uri().path() == "/";
let subscribed = setting(&req, "subscriptions"); let subscribed = setting(&req, "subscriptions");
let front_page = setting(&req, "front_page"); let front_page = setting(&req, "front_page");
let post_sort = req.cookie("post_sort").map_or_else(|| "hot".to_string(), |c| c.value().to_string()); let post_sort = req.cookie("post_sort").map_or_else(|| "hot".to_string(), |c| c.value().to_string());
@ -45,6 +56,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
} else { } else {
front_page.to_owned() front_page.to_owned()
}); });
let quarantined = can_access_quarantine(&req, &sub) || root;
// Handle random subreddits // Handle random subreddits
if let Ok(random) = catch_random(&sub, "").await { if let Ok(random) = catch_random(&sub, "").await {
@ -57,16 +69,16 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
let path = format!("/r/{}/{}.json?{}&raw_json=1", sub, sort, req.uri().query().unwrap_or_default()); let path = format!("/r/{}/{}.json?{}&raw_json=1", sub, sort, req.uri().query().unwrap_or_default());
match Post::fetch(&path, String::new()).await { match Post::fetch(&path, String::new(), quarantined).await {
Ok((posts, after)) => { Ok((posts, after)) => {
// If you can get subreddit posts, also request subreddit metadata // If you can get subreddit posts, also request subreddit metadata
let sub = if !sub.contains('+') && sub != subscribed && sub != "popular" && sub != "all" { let sub = if !sub.contains('+') && sub != subscribed && sub != "popular" && sub != "all" {
// Regular subreddit // Regular subreddit
subreddit(&sub).await.unwrap_or_default() subreddit(&sub, quarantined).await.unwrap_or_default()
} else if sub == subscribed { } else if sub == subscribed {
// Subscription feed // Subscription feed
if req.uri().path().starts_with("/r/") { if req.uri().path().starts_with("/r/") {
subreddit(&sub).await.unwrap_or_default() subreddit(&sub, quarantined).await.unwrap_or_default()
} else { } else {
Subreddit::default() Subreddit::default()
} }
@ -85,14 +97,14 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
template(SubredditTemplate { template(SubredditTemplate {
sub, sub,
posts, posts,
sort: (sort, param(&path, "t")), sort: (sort, param(&path, "t").unwrap_or_default()),
ends: (param(&path, "after"), after), ends: (param(&path, "after").unwrap_or_default(), after),
prefs: Preferences::new(req), prefs: Preferences::new(req),
url, url,
}) })
} }
Err(msg) => match msg.as_str() { Err(msg) => match msg.as_str() {
"quarantined" => error(req, format!("r/{} has been quarantined by Reddit", sub)).await, "quarantined" => quarantine(req, sub),
"private" => error(req, format!("r/{} is a private community", sub)).await, "private" => error(req, format!("r/{} is a private community", sub)).await,
"banned" => error(req, format!("r/{} has been banned from Reddit", sub)).await, "banned" => error(req, format!("r/{} has been banned from Reddit", sub)).await,
_ => error(req, msg).await, _ => error(req, msg).await,
@ -100,6 +112,43 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
} }
} }
pub fn quarantine(req: Request<Body>, sub: String) -> Result<Response<Body>, String> {
let wall = WallTemplate {
title: format!("r/{} is quarantined", sub),
msg: "Please click the button below to continue to this subreddit.".to_string(),
url: req.uri().to_string(),
sub,
prefs: Preferences::new(req),
};
Ok(
Response::builder()
.status(403)
.header("content-type", "text/html")
.body(wall.render().unwrap_or_default().into())
.unwrap_or_default(),
)
}
pub async fn add_quarantine_exception(req: Request<Body>) -> Result<Response<Body>, String> {
let subreddit = req.param("sub").ok_or("Invalid URL")?;
let redir = param(&format!("?{}", req.uri().query().unwrap_or_default()), "redir").ok_or("Invalid URL")?;
let mut res = redirect(redir.to_owned());
res.insert_cookie(
Cookie::build(&format!("allow_quaran_{}", subreddit.to_lowercase()), "true")
.path("/")
.http_only(true)
.expires(cookie::Expiration::Session)
.finish(),
);
Ok(res)
}
pub fn can_access_quarantine(req: &Request<Body>, sub: &str) -> bool {
// Determine if the subreddit can be accessed
setting(&req, &format!("allow_quaran_{}", sub.to_lowercase())).parse().unwrap_or_default()
}
// Sub or unsub by setting subscription cookie using response "Set-Cookie" header // Sub or unsub by setting subscription cookie using response "Set-Cookie" header
pub async fn subscriptions(req: Request<Body>) -> Result<Response<Body>, String> { pub async fn subscriptions(req: Request<Body>) -> Result<Response<Body>, String> {
let sub = req.param("sub").unwrap_or_default(); let sub = req.param("sub").unwrap_or_default();
@ -114,7 +163,7 @@ pub async fn subscriptions(req: Request<Body>) -> Result<Response<Body>, String>
let mut sub_list = Preferences::new(req).subscriptions; let mut sub_list = Preferences::new(req).subscriptions;
// Retrieve list of posts for these subreddits to extract display names // Retrieve list of posts for these subreddits to extract display names
let posts = json(format!("/r/{}/hot.json?raw_json=1", sub)).await?; let posts = json(format!("/r/{}/hot.json?raw_json=1", sub), true).await?;
let display_lookup: Vec<(String, &str)> = posts["data"]["children"] let display_lookup: Vec<(String, &str)> = posts["data"]["children"]
.as_array() .as_array()
.map(|list| { .map(|list| {
@ -138,7 +187,7 @@ pub async fn subscriptions(req: Request<Body>) -> Result<Response<Body>, String>
} else { } else {
// This subreddit display name isn't known, retrieve it // This subreddit display name isn't known, retrieve it
let path: String = format!("/r/{}/about.json?raw_json=1", part); let path: String = format!("/r/{}/about.json?raw_json=1", part);
display = json(path).await?; display = json(path, true).await?;
display["data"]["display_name"].as_str().ok_or_else(|| "Failed to query subreddit name".to_string())? display["data"]["display_name"].as_str().ok_or_else(|| "Failed to query subreddit name".to_string())?
}; };
@ -156,11 +205,9 @@ pub async fn subscriptions(req: Request<Body>) -> Result<Response<Body>, String>
// Redirect back to subreddit // Redirect back to subreddit
// check for redirect parameter if unsubscribing from outside sidebar // check for redirect parameter if unsubscribing from outside sidebar
let redirect_path = param(&format!("/?{}", query), "redirect"); let path = match param(&format!("?{}", query), "redirect") {
let path = if redirect_path.is_empty() { Some(redirect_path) => format!("/{}/", redirect_path),
format!("/r/{}", sub) None => format!("/r/{}", sub)
} else {
format!("/{}/", redirect_path)
}; };
let mut res = redirect(path); let mut res = redirect(path);
@ -183,6 +230,7 @@ pub async fn subscriptions(req: Request<Body>) -> Result<Response<Body>, String>
pub async fn wiki(req: Request<Body>) -> Result<Response<Body>, String> { pub async fn wiki(req: Request<Body>) -> Result<Response<Body>, String> {
let sub = req.param("sub").unwrap_or_else(|| "reddit.com".to_string()); let sub = req.param("sub").unwrap_or_else(|| "reddit.com".to_string());
let quarantined = can_access_quarantine(&req, &sub);
// Handle random subreddits // Handle random subreddits
if let Ok(random) = catch_random(&sub, "/wiki").await { if let Ok(random) = catch_random(&sub, "/wiki").await {
return Ok(random); return Ok(random);
@ -191,19 +239,26 @@ pub async fn wiki(req: Request<Body>) -> Result<Response<Body>, String> {
let page = req.param("page").unwrap_or_else(|| "index".to_string()); let page = req.param("page").unwrap_or_else(|| "index".to_string());
let path: String = format!("/r/{}/wiki/{}.json?raw_json=1", sub, page); let path: String = format!("/r/{}/wiki/{}.json?raw_json=1", sub, page);
match json(path).await { match json(path, quarantined).await {
Ok(response) => template(WikiTemplate { Ok(response) => template(WikiTemplate {
sub, sub,
wiki: rewrite_urls(response["data"]["content_html"].as_str().unwrap_or("<h3>Wiki not found</h3>")), wiki: rewrite_urls(response["data"]["content_html"].as_str().unwrap_or("<h3>Wiki not found</h3>")),
page, page,
prefs: Preferences::new(req), prefs: Preferences::new(req),
}), }),
Err(msg) => error(req, msg).await, Err(msg) => {
if msg == "quarantined" {
quarantine(req, sub)
} else {
error(req, msg).await
}
}
} }
} }
pub async fn sidebar(req: Request<Body>) -> Result<Response<Body>, String> { pub async fn sidebar(req: Request<Body>) -> Result<Response<Body>, String> {
let sub = req.param("sub").unwrap_or_else(|| "reddit.com".to_string()); let sub = req.param("sub").unwrap_or_else(|| "reddit.com".to_string());
let quarantined = can_access_quarantine(&req, &sub);
// Handle random subreddits // Handle random subreddits
if let Ok(random) = catch_random(&sub, "/about/sidebar").await { if let Ok(random) = catch_random(&sub, "/about/sidebar").await {
return Ok(random); return Ok(random);
@ -213,26 +268,32 @@ pub async fn sidebar(req: Request<Body>) -> Result<Response<Body>, String> {
let path: String = format!("/r/{}/about.json?raw_json=1", sub); let path: String = format!("/r/{}/about.json?raw_json=1", sub);
// Send a request to the url // Send a request to the url
match json(path).await { match json(path, quarantined).await {
// If success, receive JSON in response // If success, receive JSON in response
Ok(response) => template(WikiTemplate { Ok(response) => template(WikiTemplate {
wiki: format!( wiki: format!(
"{}<hr><h1>Moderators</h1><br><ul>{}</ul>", "{}<hr><h1>Moderators</h1><br><ul>{}</ul>",
rewrite_urls(&val(&response, "description_html").replace("\\", "")), rewrite_urls(&val(&response, "description_html").replace("\\", "")),
moderators(&sub).await?.join(""), moderators(&sub, quarantined).await?.join(""),
), ),
sub, sub,
page: "Sidebar".to_string(), page: "Sidebar".to_string(),
prefs: Preferences::new(req), prefs: Preferences::new(req),
}), }),
Err(msg) => error(req, msg).await, Err(msg) => {
if msg == "quarantined" {
quarantine(req, sub)
} else {
error(req, msg).await
}
}
} }
} }
pub async fn moderators(sub: &str) -> Result<Vec<String>, String> { pub async fn moderators(sub: &str, quarantined: bool) -> Result<Vec<String>, String> {
// Retrieve and format the html for the moderators list // Retrieve and format the html for the moderators list
Ok( Ok(
moderators_list(sub) moderators_list(sub, quarantined)
.await? .await?
.iter() .iter()
.map(|m| format!("<li><a style=\"color: var(--accent)\" href=\"/u/{name}\">{name}</a></li>", name = m)) .map(|m| format!("<li><a style=\"color: var(--accent)\" href=\"/u/{name}\">{name}</a></li>", name = m))
@ -240,12 +301,12 @@ pub async fn moderators(sub: &str) -> Result<Vec<String>, String> {
) )
} }
async fn moderators_list(sub: &str) -> Result<Vec<String>, String> { async fn moderators_list(sub: &str, quarantined: bool) -> Result<Vec<String>, String> {
// Build the moderator list URL // Build the moderator list URL
let path: String = format!("/r/{}/about/moderators.json?raw_json=1", sub); let path: String = format!("/r/{}/about/moderators.json?raw_json=1", sub);
// Retrieve response // Retrieve response
let response = json(path).await?["data"]["children"].clone(); let response = json(path, quarantined).await?["data"]["children"].clone();
Ok( Ok(
// Traverse json tree and format into list of strings // Traverse json tree and format into list of strings
response response
@ -265,12 +326,12 @@ async fn moderators_list(sub: &str) -> Result<Vec<String>, String> {
} }
// SUBREDDIT // SUBREDDIT
async fn subreddit(sub: &str) -> Result<Subreddit, String> { async fn subreddit(sub: &str, quarantined: bool) -> Result<Subreddit, String> {
// Build the Reddit JSON API url // Build the Reddit JSON API url
let path: String = format!("/r/{}/about.json?raw_json=1", sub); let path: String = format!("/r/{}/about.json?raw_json=1", sub);
// Send a request to the url // Send a request to the url
match json(path).await { match json(path, quarantined).await {
// If success, receive JSON in response // If success, receive JSON in response
Ok(res) => { Ok(res) => {
// Metadata regarding the subreddit // Metadata regarding the subreddit
@ -286,7 +347,7 @@ async fn subreddit(sub: &str) -> Result<Subreddit, String> {
title: esc!(&res, "title"), title: esc!(&res, "title"),
description: esc!(&res, "public_description"), description: esc!(&res, "public_description"),
info: rewrite_urls(&val(&res, "description_html").replace("\\", "")), info: rewrite_urls(&val(&res, "description_html").replace("\\", "")),
moderators: moderators_list(sub).await?, moderators: moderators_list(sub, quarantined).await?,
icon: format_url(&icon), icon: format_url(&icon),
members: format_num(members), members: format_num(members),
active: format_num(active), active: format_num(active),

View File

@ -29,11 +29,11 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
); );
// Retrieve other variables from Libreddit request // Retrieve other variables from Libreddit request
let sort = param(&path, "sort"); let sort = param(&path, "sort").unwrap_or_default();
let username = req.param("name").unwrap_or_default(); let username = req.param("name").unwrap_or_default();
// Request user posts/comments from Reddit // Request user posts/comments from Reddit
let posts = Post::fetch(&path, "Comment".to_string()).await; let posts = Post::fetch(&path, "Comment".to_string(), false).await;
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str())); let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
match posts { match posts {
@ -44,8 +44,8 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
template(UserTemplate { template(UserTemplate {
user, user,
posts, posts,
sort: (sort, param(&path, "t")), sort: (sort, param(&path, "t").unwrap_or_default()),
ends: (param(&path, "after"), after), ends: (param(&path, "after").unwrap_or_default(), after),
prefs: Preferences::new(req), prefs: Preferences::new(req),
url, url,
}) })
@ -61,7 +61,7 @@ async fn user(name: &str) -> Result<User, String> {
let path: String = format!("/user/{}/about.json?raw_json=1", name); let path: String = format!("/user/{}/about.json?raw_json=1", name);
// Send a request to the url // Send a request to the url
match json(path).await { match json(path, false).await {
// If success, receive JSON in response // If success, receive JSON in response
Ok(res) => { Ok(res) => {
// Grab creation date as unix timestamp // Grab creation date as unix timestamp

View File

@ -217,12 +217,12 @@ pub struct Post {
impl Post { impl Post {
// Fetch posts of a user or subreddit and return a vector of posts and the "after" value // Fetch posts of a user or subreddit and return a vector of posts and the "after" value
pub async fn fetch(path: &str, fallback_title: String) -> Result<(Vec<Self>, String), String> { pub async fn fetch(path: &str, fallback_title: String, quarantine: bool) -> Result<(Vec<Self>, String), String> {
let res; let res;
let post_list; let post_list;
// Send a request to the url // Send a request to the url
match json(path.to_string()).await { match json(path.to_string(), quarantine).await {
// If success, receive JSON in response // If success, receive JSON in response
Ok(response) => { Ok(response) => {
res = response; res = response;
@ -416,11 +416,16 @@ impl Preferences {
// //
// Grab a query parameter from a url // Grab a query parameter from a url
pub fn param(path: &str, value: &str) -> String { pub fn param(path: &str, value: &str) -> Option<String> {
match Url::parse(format!("https://libredd.it/{}", path).as_str()) { Some(
Ok(url) => url.query_pairs().into_owned().collect::<HashMap<_, _>>().get(value).unwrap_or(&String::new()).to_owned(), Url::parse(format!("https://libredd.it/{}", path).as_str())
_ => String::new(), .ok()?
} .query_pairs()
.into_owned()
.collect::<HashMap<_, _>>()
.get(value)?
.to_owned(),
)
} }
// Retrieve the value of a setting by name // Retrieve the value of a setting by name
@ -443,7 +448,7 @@ pub fn setting(req: &Request<Body>, name: &str) -> String {
// Detect and redirect in the event of a random subreddit // Detect and redirect in the event of a random subreddit
pub async fn catch_random(sub: &str, additional: &str) -> Result<Response<Body>, String> { pub async fn catch_random(sub: &str, additional: &str) -> Result<Response<Body>, String> {
if (sub == "random" || sub == "randnsfw") && !sub.contains('+') { if (sub == "random" || sub == "randnsfw") && !sub.contains('+') {
let new_sub = json(format!("/r/{}/about.json?raw_json=1", sub)).await?["data"]["display_name"] let new_sub = json(format!("/r/{}/about.json?raw_json=1", sub), false).await?["data"]["display_name"]
.as_str() .as_str()
.unwrap_or_default() .unwrap_or_default()
.to_string(); .to_string();

13
templates/wall.html Normal file
View File

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block title %}{{ msg }}{% endblock %}
{% block sortstyle %}{% endblock %}
{% block content %}
<div id="wall">
<h1>{{ title }}</h1>
<br>
<p>{{ msg }}</p>
<form action="/r/{{ sub }}?redir={{ url }}" method="POST">
<input id="save" type="submit" value="Continue">
</form>
</div>
{% endblock %}