Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new setting to block NSFW content #5436

Merged
merged 21 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0d12a72
Block NSFW content on instances with it disabled
flamingo-cant-draw Feb 17, 2025
c567a8e
Make disallow_nsfw_content a local_site setting
flamingo-cant-draw Feb 19, 2025
5b0e162
Clippy
flamingo-cant-draw Feb 19, 2025
67bd86f
Add comma
flamingo-cant-draw Feb 19, 2025
e9a6445
SQL fmt
flamingo-cant-draw Feb 19, 2025
b09464f
Newline
flamingo-cant-draw Feb 19, 2025
1a5ed6b
Use func in apub + update js-client
flamingo-cant-draw Feb 20, 2025
f63c9c3
Merge branch 'main' of https://github.com/LemmyNet/lemmy into dont-al…
flamingo-cant-draw Feb 20, 2025
e594f48
Remove extra db queries, add purge_post_images
flamingo-cant-draw Feb 21, 2025
33e4414
Merge branch 'main' of https://github.com/LemmyNet/lemmy into dont-al…
flamingo-cant-draw Feb 21, 2025
df50d77
Add back local_site to funcs that need it
flamingo-cant-draw Feb 23, 2025
519690b
Fix tests
flamingo-cant-draw Feb 23, 2025
144008a
Add delay to api test
flamingo-cant-draw Feb 24, 2025
fb8bdc2
Address comments
flamingo-cant-draw Feb 24, 2025
6ad6d74
Cleanup
flamingo-cant-draw Feb 25, 2025
0cda2df
Merge branch 'main' of https://github.com/LemmyNet/lemmy into dont-al…
flamingo-cant-draw Feb 25, 2025
41aeccc
Return results from db func
flamingo-cant-draw Feb 25, 2025
91f8ae5
fmt
flamingo-cant-draw Feb 26, 2025
fc0b116
Remove unneeded result
flamingo-cant-draw Mar 1, 2025
78363b1
Merge branch 'main' of https://github.com/LemmyNet/lemmy into dont-al…
flamingo-cant-draw Mar 1, 2025
db44c16
Sync translations
flamingo-cant-draw Mar 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api_tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"eslint": "^9.20.0",
"eslint-plugin-prettier": "^5.2.3",
"jest": "^29.5.0",
"lemmy-js-client": "0.20.0-show-mod-reports.2",
"lemmy-js-client": "1.0.0-block-nsfw.1",
"prettier": "^3.5.0",
"ts-jest": "^29.1.0",
"tsoa": "^6.6.0",
Expand Down
10 changes: 5 additions & 5 deletions api_tests/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions api_tests/src/post.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { AdminBlockInstanceParams } from "lemmy-js-client/dist/types/AdminBlockI
import {
AddModToCommunity,
EditSite,
EditPost,
PersonPostMentionView,
PostReport,
PostReportView,
Expand Down Expand Up @@ -921,6 +922,47 @@ test("Rewrite markdown links", async () => {
);
});

test("Don't allow NSFW posts on instances that disable it", async () => {
// Disallow NSFW on gamma
let editSiteForm: EditSite = {
disallow_nsfw_content: true,
};
await gamma.editSite(editSiteForm);

if (!betaCommunity) {
throw "Missing beta community";
}

// Make a NSFW post
let postRes = await createPost(beta, betaCommunity.community.id);
let form: EditPost = {
nsfw: true,
post_id: postRes.post_view.post.id,
};
let updatePost = await beta.editPost(form);

// Gamma reject resolving the post
await expect(
resolvePost(gamma, updatePost.post_view.post),
).rejects.toStrictEqual(Error("not_found"));

// Local users can't create NSFW post on Gamma
let gammaCommunity = (
await resolveCommunity(gamma, betaCommunity.community.ap_id)
).community?.community;
if (!gammaCommunity) {
throw "Missing gamma community";
}
let gammaPost = await createPost(gamma, gammaCommunity.id);
let form2: EditPost = {
nsfw: true,
post_id: gammaPost.post_view.post.id,
};
await expect(gamma.editPost(form2)).rejects.toStrictEqual(
Error("nsfw_not_allowed"),
);
});

function checkPostReportName(rcv: ReportCombinedView, report: PostReport) {
switch (rcv.type_) {
case "Post":
Expand Down
5 changes: 5 additions & 0 deletions crates/api_common/src/site.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ pub struct CreateSite {
pub comment_downvotes: Option<FederationMode>,
#[cfg_attr(feature = "full", ts(optional))]
pub disable_donation_dialog: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub disallow_nsfw_content: Option<bool>,
}

#[skip_serializing_none]
Expand Down Expand Up @@ -388,6 +390,9 @@ pub struct EditSite {
/// donations.
#[cfg_attr(feature = "full", ts(optional))]
pub disable_donation_dialog: Option<bool>,
/// Block NSFW content being created
#[cfg_attr(feature = "full", ts(optional))]
pub disallow_nsfw_content: Option<bool>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
Expand Down
11 changes: 11 additions & 0 deletions crates/api_common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,17 @@ pub fn check_private_instance_and_federation_enabled(local_site: &LocalSite) ->
}
}

pub fn check_nsfw_allowed(nsfw: Option<bool>, local_site: Option<&LocalSite>) -> LemmyResult<()> {
let is_nsfw = nsfw.is_some_and(|nsfw| nsfw);
let nsfw_disallowed = local_site.is_some_and(|s| s.disallow_nsfw_content);

if nsfw_disallowed && is_nsfw {
Err(LemmyErrorType::NsfwNotAllowed)?
}

Ok(())
}

/// Read the site for an ap_id.
///
/// Used for GetCommunityResponse and GetPersonDetails
Expand Down
2 changes: 2 additions & 0 deletions crates/api_crud/src/community/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use lemmy_api_common::{
community::{CommunityResponse, CreateCommunity},
context::LemmyContext,
utils::{
check_nsfw_allowed,
generate_followers_url,
generate_inbox_url,
get_url_blocklist,
Expand Down Expand Up @@ -54,6 +55,7 @@ pub async fn create_community(
Err(LemmyErrorType::OnlyAdminsCanCreateCommunities)?
}

check_nsfw_allowed(data.nsfw, Some(&local_site))?;
let slur_regex = local_site_to_slur_regex(&local_site);
let url_blocklist = get_url_blocklist(&context).await?;
check_slurs(&data.name, &slur_regex)?;
Expand Down
2 changes: 2 additions & 0 deletions crates/api_crud/src/community/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use lemmy_api_common::{
send_activity::{ActivityChannel, SendActivityData},
utils::{
check_community_mod_action,
check_nsfw_allowed,
get_url_blocklist,
local_site_to_slur_regex,
process_markdown_opt,
Expand Down Expand Up @@ -36,6 +37,7 @@ pub async fn update_community(
) -> LemmyResult<Json<CommunityResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;

check_nsfw_allowed(data.nsfw, Some(&local_site))?;
let slur_regex = local_site_to_slur_regex(&local_site);
let url_blocklist = get_url_blocklist(&context).await?;
check_slurs_opt(&data.title, &slur_regex)?;
Expand Down
2 changes: 2 additions & 0 deletions crates/api_crud/src/post/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use lemmy_api_common::{
send_activity::SendActivityData,
utils::{
check_community_user_action,
check_nsfw_allowed,
get_url_blocklist,
honeypot_check,
local_site_to_slur_regex,
Expand Down Expand Up @@ -52,6 +53,7 @@ pub async fn create_post(

honeypot_check(&data.honeypot)?;

check_nsfw_allowed(data.nsfw, Some(&local_site))?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.name, &slur_regex)?;
let url_blocklist = get_url_blocklist(&context).await?;
Expand Down
3 changes: 3 additions & 0 deletions crates/api_crud/src/post/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use lemmy_api_common::{
send_activity::SendActivityData,
utils::{
check_community_user_action,
check_nsfw_allowed,
get_url_blocklist,
local_site_to_slur_regex,
process_markdown_opt,
Expand Down Expand Up @@ -65,6 +66,8 @@ pub async fn update_post(
.as_deref(),
);

check_nsfw_allowed(data.nsfw, Some(&local_site))?;

let alt_text = diesel_string_update(data.alt_text.as_deref());

if let Some(name) = &data.name {
Expand Down
1 change: 1 addition & 0 deletions crates/api_crud/src/site/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ pub async fn create_site(
comment_upvotes: data.comment_upvotes,
comment_downvotes: data.comment_downvotes,
disable_donation_dialog: data.disable_donation_dialog,
disallow_nsfw_content: data.disallow_nsfw_content,
..Default::default()
};

Expand Down
1 change: 1 addition & 0 deletions crates/api_crud/src/site/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub async fn update_site(
comment_upvotes: data.comment_upvotes,
comment_downvotes: data.comment_downvotes,
disable_donation_dialog: data.disable_donation_dialog,
disallow_nsfw_content: data.disallow_nsfw_content,
..Default::default()
};

Expand Down
17 changes: 17 additions & 0 deletions crates/apub/src/objects/community.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use chrono::{DateTime, Utc};
use lemmy_api_common::{
context::LemmyContext,
utils::{
check_nsfw_allowed,
generate_featured_url,
generate_moderators_url,
generate_outbox_url,
Expand Down Expand Up @@ -150,6 +151,21 @@ impl Object for ApubCommunity {
} else {
CommunityVisibility::Public
});

// If NSFW is not allowed, reject new communities marked NSFW and
// remove communities that update to be NSFW
let block_for_nsfw = check_nsfw_allowed(group.sensitive, local_site.as_ref());
let removed = if let Err(e) = block_for_nsfw {
let c = ApubCommunity::read_from_id(group.id.inner().clone(), context).await?;
if c.is_some() {
Some(true)
} else {
Err(e)?
}
} else {
None
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt you need to read the community. Probably this is enough.

// Your previous comment
let removed = check_nsfw_allowed(group.sensitive, local_site.as_ref()).is_err();


let form = CommunityInsertForm {
published: group.published,
updated: group.updated,
Expand All @@ -161,6 +177,7 @@ impl Object for ApubCommunity {
icon,
banner,
sidebar,
removed,
description: group.summary,
followers_url: group.followers.clone().map(Into::into),
inbox_url: Some(
Expand Down
26 changes: 24 additions & 2 deletions crates/apub/src/objects/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ use chrono::{DateTime, Utc};
use html2text::{from_read_with_decorator, render::TrivialDecorator};
use lemmy_api_common::{
context::LemmyContext,
request::generate_post_link_metadata,
utils::{get_url_blocklist, local_site_opt_to_slur_regex, process_markdown_opt},
request::{generate_post_link_metadata, purge_image_from_pictrs},
utils::{
check_nsfw_allowed,
get_url_blocklist,
local_site_opt_to_slur_regex,
process_markdown_opt,
},
};
use lemmy_db_schema::{
source::{
Expand Down Expand Up @@ -225,6 +230,23 @@ impl Object for ApubPost {
None
};

// If NSFW is not allowed, reject NSFW posts and delete existing
// posts that get updated to be NSFW
let block_for_nsfw = check_nsfw_allowed(page.sensitive, local_site.as_ref());
if block_for_nsfw.is_err() {
let post = ApubPost::read_from_id(page.id.inner().clone(), context).await?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, I don't think you need to read the post, you already have the url above for purging.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I did this to see if the post already existed, but thinking about it I could just ignore NotFound errors from ApubPost::delete to avoid the extra db query.

if let Some(post) = post {
if let Some(url) = &post.url {
purge_image_from_pictrs(url, context).await.ok();
}
if let Some(thumbnail_url) = &post.thumbnail_url {
purge_image_from_pictrs(thumbnail_url, context).await.ok();
}
ApubPost::delete(post, context).await?;
}
block_for_nsfw?
}

let url_blocklist = get_url_blocklist(context).await?;

let url = if let Some(url) = url {
Expand Down
1 change: 1 addition & 0 deletions crates/db_schema/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ diesel = { workspace = true, features = [
"postgres",
"serde_json",
"uuid",
"64-column-tables",
Comment on lines 55 to +58
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figured this would be OK given it's already happening in #5407

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep that's fine.

], optional = true }
diesel-derive-newtype = { workspace = true, optional = true }
diesel-derive-enum = { workspace = true, optional = true }
Expand Down
1 change: 1 addition & 0 deletions crates/db_schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ diesel::table! {
comment_downvotes -> FederationModeEnum,
disable_donation_dialog -> Bool,
default_post_time_range_seconds -> Nullable<Int4>,
disallow_nsfw_content -> Bool,
}
}

Expand Down
5 changes: 5 additions & 0 deletions crates/db_schema/src/source/local_site.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ pub struct LocalSite {
#[cfg_attr(feature = "full", ts(optional))]
/// A default time range limit to apply to post sorts, in seconds.
pub default_post_time_range_seconds: Option<i32>,
/// Block NSFW content being created
pub disallow_nsfw_content: bool,
}

#[derive(Clone, derive_new::new)]
Expand Down Expand Up @@ -152,6 +154,8 @@ pub struct LocalSiteInsertForm {
pub disable_donation_dialog: Option<bool>,
#[new(default)]
pub default_post_time_range_seconds: Option<Option<i32>>,
#[new(default)]
pub disallow_nsfw_content: bool,
}

#[derive(Clone, Default)]
Expand Down Expand Up @@ -187,4 +191,5 @@ pub struct LocalSiteUpdateForm {
pub comment_downvotes: Option<FederationMode>,
pub disable_donation_dialog: Option<bool>,
pub default_post_time_range_seconds: Option<Option<i32>>,
pub disallow_nsfw_content: Option<bool>,
}
1 change: 1 addition & 0 deletions crates/utils/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub enum LemmyErrorType {
LanguageNotAllowed,
CouldntUpdatePost,
NoPostEditAllowed,
NsfwNotAllowed,
EditPrivateMessageNotAllowed,
SiteAlreadyExists,
ApplicationQuestionRequired,
Expand Down
3 changes: 3 additions & 0 deletions migrations/2025-02-18-143408_block_nsfw/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE local_site
DROP COLUMN disallow_nsfw_content;

3 changes: 3 additions & 0 deletions migrations/2025-02-18-143408_block_nsfw/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE local_site
ADD COLUMN disallow_nsfw_content boolean DEFAULT FALSE NOT NULL;