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 18 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.

45 changes: 45 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,50 @@ 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);

// Wait for cache on Gamma's LocalSite
await delay(1_000);

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
12 changes: 2 additions & 10 deletions crates/api/src/site/purge/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
request::purge_image_from_pictrs,
send_activity::{ActivityChannel, SendActivityData},
site::PurgePost,
utils::is_admin,
utils::{is_admin, purge_post_images},
SuccessResponse,
};
use lemmy_db_schema::{
Expand Down Expand Up @@ -38,14 +37,7 @@ pub async fn purge_post(
)
.await?;

// Purge image
if let Some(url) = &post.url {
purge_image_from_pictrs(url, &context).await.ok();
}
// Purge thumbnail
if let Some(thumbnail_url) = &post.thumbnail_url {
purge_image_from_pictrs(thumbnail_url, &context).await.ok();
}
purge_post_images(post.url.clone(), post.thumbnail_url.clone(), &context).await?;

Post::delete(&mut context.pool(), data.post_id).await?;

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
40 changes: 28 additions & 12 deletions crates/api_common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,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.unwrap_or_default();
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 All @@ -671,19 +682,29 @@ pub async fn read_site_for_actor(
Ok(site)
}

pub async fn purge_post_images(
url: Option<DbUrl>,
thumbnail_url: Option<DbUrl>,
context: &LemmyContext,
) -> LemmyResult<()> {
if let Some(url) = url {
purge_image_from_pictrs(&url, context).await.ok();
}
if let Some(thumbnail_url) = thumbnail_url {
purge_image_from_pictrs(&thumbnail_url, context).await.ok();
}

Ok(())
}

pub async fn purge_image_posts_for_person(
banned_person_id: PersonId,
context: &LemmyContext,
) -> LemmyResult<()> {
let pool = &mut context.pool();
let posts = Post::fetch_pictrs_posts_for_creator(pool, banned_person_id).await?;
for post in posts {
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();
}
purge_post_images(post.url, post.thumbnail_url, context).await?;
}

Post::remove_pictrs_post_images_and_thumbnails_for_creator(pool, banned_person_id).await?;
Expand Down Expand Up @@ -715,12 +736,7 @@ pub async fn purge_image_posts_for_community(
let pool = &mut context.pool();
let posts = Post::fetch_pictrs_posts_for_community(pool, banned_community_id).await?;
for post in posts {
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();
}
purge_post_images(post.url, post.thumbnail_url, context).await?;
}

Post::remove_pictrs_post_images_and_thumbnails_for_community(pool, banned_community_id).await?;
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 = slur_regex(&context).await?;
let url_blocklist = get_url_blocklist(&context).await?;
check_slurs(&data.name, &slur_regex)?;
Expand Down
12 changes: 11 additions & 1 deletion crates/api_crud/src/community/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@ use lemmy_api_common::{
community::{CommunityResponse, EditCommunity},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_mod_action, get_url_blocklist, process_markdown_opt, slur_regex},
utils::{
check_community_mod_action,
check_nsfw_allowed,
get_url_blocklist,
process_markdown_opt,
slur_regex,
},
};
use lemmy_db_schema::{
source::{
actor_language::{CommunityLanguage, SiteLanguage},
community::{Community, CommunityUpdateForm},
local_site::LocalSite,
},
traits::Crud,
utils::diesel_string_update,
Expand All @@ -28,9 +35,12 @@ pub async fn update_community(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;

let slur_regex = slur_regex(&context).await?;
let url_blocklist = get_url_blocklist(&context).await?;
check_slurs_opt(&data.title, &slur_regex)?;
check_nsfw_allowed(data.nsfw, Some(&local_site))?;

let sidebar = diesel_string_update(
process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context)
Expand Down
4 changes: 4 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,
process_markdown_opt,
Expand All @@ -21,6 +22,7 @@ use lemmy_db_schema::{
newtypes::PostOrCommentId,
source::{
community::Community,
local_site::LocalSite,
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostRead, PostReadForm},
},
traits::{Crud, Likeable},
Expand Down Expand Up @@ -48,6 +50,7 @@ pub async fn create_post(
local_user_view: LocalUserView,
) -> LemmyResult<Json<PostResponse>> {
honeypot_check(&data.honeypot)?;
let local_site = LocalSite::read(&mut context.pool()).await?;

let slur_regex = slur_regex(&context).await?;
check_slurs(&data.name, &slur_regex)?;
Expand All @@ -56,6 +59,7 @@ pub async fn create_post(
let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?;
let url = diesel_url_create(data.url.as_deref())?;
let custom_thumbnail = diesel_url_create(data.custom_thumbnail.as_deref())?;
check_nsfw_allowed(data.nsfw, Some(&local_site))?;

is_valid_post_title(&data.name)?;

Expand Down
5 changes: 5 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,
process_markdown_opt,
send_webmention,
Expand All @@ -21,6 +22,7 @@ use lemmy_db_schema::{
newtypes::PostOrCommentId,
source::{
community::Community,
local_site::LocalSite,
post::{Post, PostUpdateForm},
},
traits::Crud,
Expand Down Expand Up @@ -48,6 +50,7 @@ pub async fn update_post(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<PostResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let url = diesel_url_update(data.url.as_deref())?;

let custom_thumbnail = diesel_url_update(data.custom_thumbnail.as_deref())?;
Expand All @@ -62,6 +65,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
10 changes: 10 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 All @@ -33,6 +34,7 @@ use lemmy_db_schema::{
activity::ActorType,
actor_language::CommunityLanguage,
community::{Community, CommunityInsertForm, CommunityUpdateForm},
local_site::LocalSite,
},
traits::{ApubActor, Crud},
CommunityVisibility,
Expand Down Expand Up @@ -134,6 +136,7 @@ impl Object for ApubCommunity {

/// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
async fn from_json(group: Group, context: &Data<Self::DataType>) -> LemmyResult<ApubCommunity> {
let local_site = LocalSite::read(&mut context.pool()).await.ok();
let instance_id = fetch_instance_actor_for_object(&group.id, context).await?;

let slur_regex = slur_regex(context).await?;
Expand All @@ -148,6 +151,12 @@ impl Object for ApubCommunity {
} else {
CommunityVisibility::Public
});

// If NSFW is not allowed, then remove NSFW communities
let removed = check_nsfw_allowed(group.sensitive, local_site.as_ref())
.err()
.map(|_| true);

let form = CommunityInsertForm {
published: group.published,
updated: group.updated,
Expand All @@ -159,6 +168,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
Loading