diff --git a/src/duplicates.rs b/src/duplicates.rs index eb032e4..12bce84 100644 --- a/src/duplicates.rs +++ b/src/duplicates.rs @@ -3,7 +3,7 @@ use crate::client::json; use crate::server::RequestExt; use crate::subreddit::{can_access_quarantine, quarantine}; -use crate::utils::{error, filter_posts, get_filters, parse_post, template, Post, Preferences}; +use crate::utils::{error, filter_posts, get_filters, nsfw_landing, parse_post, setting, template, Post, Preferences}; use askama::Template; use hyper::{Body, Request, Response}; @@ -65,8 +65,15 @@ pub async fn item(req: Request) -> Result, String> { match json(path, quarantined).await { // Process response JSON. Ok(response) => { - let filters = get_filters(&req); let post = parse_post(&response[0]["data"]["children"][0]).await; + + // Return landing page if this post if this Reddit deems this post + // NSFW, but we have also disabled the display of NSFW content. + if setting(&req, "show_nsfw") != "on" && post.nsfw { + return Ok(nsfw_landing(req).await.unwrap_or_default()); + } + + let filters = get_filters(&req); let (duplicates, num_posts_filtered, all_posts_filtered) = parse_duplicates(&response[1], &filters).await; // These are the values for the "before=", "after=", and "sort=" diff --git a/src/post.rs b/src/post.rs index 7e1add2..a0da095 100644 --- a/src/post.rs +++ b/src/post.rs @@ -3,7 +3,7 @@ use crate::client::json; use crate::server::RequestExt; use crate::subreddit::{can_access_quarantine, quarantine}; use crate::utils::{ - error, format_num, get_filters, param, parse_post, rewrite_urls, setting, template, time, val, Author, Awards, Comment, Flair, FlairPart, Post, Preferences, + error, format_num, get_filters, nsfw_landing, param, parse_post, rewrite_urls, setting, template, time, val, Author, Awards, Comment, Flair, FlairPart, Post, Preferences, }; use hyper::{Body, Request, Response}; @@ -55,6 +55,13 @@ pub async fn item(req: Request) -> Result, String> { Ok(response) => { // Parse the JSON into Post and Comment structs let post = parse_post(&response[0]["data"]["children"][0]).await; + + // Return landing page if this post if this Reddit deems this post + // NSFW, but we have also disabled the display of NSFW content. + if setting(&req, "show_nsfw") != "on" && post.nsfw { + return Ok(nsfw_landing(req).await.unwrap_or_default()); + } + let comments = parse_comments(&response[1], &post.permalink, &post.author.name, highlighted_comment, &get_filters(&req)); let url = req.uri().to_string(); diff --git a/src/subreddit.rs b/src/subreddit.rs index 5d4600f..242a508 100644 --- a/src/subreddit.rs +++ b/src/subreddit.rs @@ -1,6 +1,6 @@ // CRATES use crate::utils::{ - catch_random, error, filter_posts, format_num, format_url, get_filters, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit, + catch_random, error, filter_posts, format_num, format_url, get_filters, nsfw_landing, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit, }; use crate::{client::json, server::ResponseExt, RequestExt}; use askama::Template; @@ -96,6 +96,12 @@ pub async fn community(req: Request) -> Result, String> { } }; + // Return landing page if this post if this is NSFW community but the user + // has disabled the display of NSFW content. + if setting(&req, "show_nsfw") != "on" && sub.nsfw { + return Ok(nsfw_landing(req).await.unwrap_or_default()); + } + let path = format!("/r/{}/{}.json?{}&raw_json=1", sub_name.clone(), sort, req.uri().query().unwrap_or_default()); let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str())); let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26").replace('+', "%2B"); @@ -416,5 +422,6 @@ async fn subreddit(sub: &str, quarantined: bool) -> Result { members: format_num(members), active: format_num(active), wiki: res["data"]["wiki_enabled"].as_bool().unwrap_or_default(), + nsfw: res["data"]["over18"].as_bool().unwrap_or_default(), }) } diff --git a/src/user.rs b/src/user.rs index 8d70e86..181c36a 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,7 +1,7 @@ // CRATES use crate::client::json; use crate::server::RequestExt; -use crate::utils::{error, filter_posts, format_url, get_filters, param, setting, template, Post, Preferences, User}; +use crate::utils::{error, filter_posts, format_url, get_filters, nsfw_landing, param, setting, template, Post, Preferences, User}; use askama::Template; use hyper::{Body, Request, Response}; use time::{macros::format_description, OffsetDateTime}; @@ -45,8 +45,16 @@ pub async fn profile(req: Request) -> Result, String> { // Retrieve other variables from libbacon request let sort = param(&path, "sort").unwrap_or_default(); let username = req.param("name").unwrap_or_default(); + + // Retrieve info from user about page. let user = user(&username).await.unwrap_or_default(); + // Return landing page if this post if this Reddit deems this user NSFW, + // but we have also disabled the display of NSFW content. + if setting(&req, "show_nsfw") != "on" && user.nsfw { + return Ok(nsfw_landing(req).await.unwrap_or_default()); + } + let filters = get_filters(&req); if filters.contains(&["u_", &username].concat()) { template(UserTemplate { @@ -111,6 +119,7 @@ async fn user(name: &str) -> Result { created: created.format(format_description!("[month repr:short] [day] '[year repr:last_two]")).unwrap_or_default(), banner: about("banner_img"), description: about("public_description"), + nsfw: res["data"]["subreddit"]["over_18"].as_bool().unwrap_or_default(), } }) } diff --git a/src/utils.rs b/src/utils.rs index c0fcfc3..2c7e010 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,6 +13,16 @@ use std::str::FromStr; use time::{macros::format_description, Duration, OffsetDateTime}; use url::Url; +/// Identifies whether or not the page is a subreddit, a user page, or a post. +/// This is used by the NSFW landing template to determine the mesage to convey +/// to the user. +#[derive(PartialEq, Eq)] +pub enum ResourceType { + Subreddit, + User, + Post, +} + // Post flair with content, background color and foreground color pub struct Flair { pub flair_parts: Vec, @@ -214,6 +224,7 @@ pub struct Post { pub comments: (String, String), pub gallery: Vec, pub awards: Awards, + pub nsfw: bool, } impl Post { @@ -314,6 +325,7 @@ impl Post { comments: format_num(data["num_comments"].as_i64().unwrap_or_default()), gallery, awards, + nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(), }); } @@ -405,6 +417,27 @@ pub struct ErrorTemplate { pub url: String, } +/// Template for NSFW landing page. The landing page is displayed when a page's +/// content is wholly NSFW, but a user has not enabled the option to view NSFW +/// posts. +#[derive(Template)] +#[template(path = "nsfwlanding.html")] +pub struct NSFWLandingTemplate { + /// Identifier for the resource. This is either a subreddit name or a + /// username. (In the case of the latter, set is_user to true.) + pub res: String, + + /// Identifies whether or not the resource is a subreddit, a user page, + /// or a post. + pub res_type: ResourceType, + + /// User preferences. + pub prefs: Preferences, + + /// Request URL. + pub url: String, +} + #[derive(Default)] // User struct containing metadata about user pub struct User { @@ -415,6 +448,7 @@ pub struct User { pub created: String, pub banner: String, pub description: String, + pub nsfw: bool, } #[derive(Default)] @@ -429,6 +463,7 @@ pub struct Subreddit { pub members: (String, String), pub active: (String, String), pub wiki: bool, + pub nsfw: bool, } // Parser for query params, used in sorting (eg. /r/rust/?sort=hot) @@ -602,6 +637,7 @@ pub async fn parse_post(post: &serde_json::Value) -> Post { comments: format_num(post["data"]["num_comments"].as_i64().unwrap_or_default()), gallery, awards, + nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(), } } @@ -813,6 +849,37 @@ pub async fn error(req: Request, msg: String) -> Result, St Ok(Response::builder().status(404).header("content-type", "text/html").body(body.into()).unwrap_or_default()) } +/// Render the landing page for NSFW content when the user has not enabled +/// "show NSFW posts" in settings. +pub async fn nsfw_landing(req: Request) -> Result, String> { + let res_type: ResourceType; + let url = req.uri().to_string(); + + // Determine from the request URL if the resource is a subreddit, a user + // page, or a post. + let res: String = if !req.param("name").unwrap_or_default().is_empty() { + res_type = ResourceType::User; + req.param("name").unwrap_or_default() + } else if !req.param("id").unwrap_or_default().is_empty() { + res_type = ResourceType::Post; + req.param("id").unwrap_or_default() + } else { + res_type = ResourceType::Subreddit; + req.param("sub").unwrap_or_default() + }; + + let body = NSFWLandingTemplate { + res, + res_type, + prefs: Preferences::new(req), + url, + } + .render() + .unwrap_or_default(); + + Ok(Response::builder().status(403).header("content-type", "text/html").body(body.into()).unwrap_or_default()) +} + #[cfg(test)] mod tests { use super::format_num; diff --git a/static/style.css b/static/style.css index de26bc9..0d26302 100644 --- a/static/style.css +++ b/static/style.css @@ -1286,6 +1286,31 @@ td, th { color: var(--accent); }; +/* NSFW Landing Page */ + +#nsfw_landing { + display: inline-block; + text-align: center; + width: 100%; +} + +#nsfw_landing h1 { + display: inline-block; + margin-bottom: 20px; + text-align: center; + width: 100%; +} + +#nsfw_landing p { + display: inline-block; + text-align: center; + width: 100%; +} + +#nsfw_landing a { + color: var(--accent); +} + /* Mobile */ @media screen and (max-width: 800px) { diff --git a/templates/nsfwlanding.html b/templates/nsfwlanding.html new file mode 100644 index 0000000..aff229d --- /dev/null +++ b/templates/nsfwlanding.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% block title %}NSFW content gated{% endblock %} +{% block sortstyle %}{% endblock %} +{% block content %} +
+ {% if res_type == crate::utils::ResourceType::Subreddit %} +

😱 r/{{ res }} is a NSFW community!

+ {% else if res_type == crate::utils::ResourceType::User %} +

😱 u/{{ res }}'s content is NSFW!

+ {% else if res_type == crate::utils::ResourceType::Post %} +

😱 This post is NSFW!

+ {% endif %} +
+

Enable "Show NSFW posts" in settings to view this {% if res_type == crate::utils::ResourceType::Subreddit %}subreddit{% else if res_type == crate::utils::ResourceType::User %}user's posts or comments{% else if res_type == crate::utils::ResourceType::Post %}post{% endif %}.

+
+{% endblock %}