mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-03-28 14:15:28 +00:00
Add new setting to block NSFW content (#5436)
* Block NSFW content on instances with it disabled * Make disallow_nsfw_content a local_site setting * Clippy * Add comma * SQL fmt * Newline * Use func in apub + update js-client * Remove extra db queries, add purge_post_images * Add back local_site to funcs that need it * Fix tests * Add delay to api test * Address comments * Cleanup * Return results from db func * fmt * Remove unneeded result * Sync translations
This commit is contained in:
parent
be91a2f39c
commit
e7ab5256f7
21 changed files with 165 additions and 30 deletions
|
@ -29,7 +29,7 @@
|
||||||
"eslint": "^9.20.0",
|
"eslint": "^9.20.0",
|
||||||
"eslint-plugin-prettier": "^5.2.3",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"lemmy-js-client": "0.20.0-api-no-optional-vec.1",
|
"lemmy-js-client": "1.0.0-block-nsfw.1",
|
||||||
"prettier": "^3.5.0",
|
"prettier": "^3.5.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"tsoa": "^6.6.0",
|
"tsoa": "^6.6.0",
|
||||||
|
|
|
@ -33,8 +33,8 @@ importers:
|
||||||
specifier: ^29.5.0
|
specifier: ^29.5.0
|
||||||
version: 29.7.0(@types/node@22.13.1)
|
version: 29.7.0(@types/node@22.13.1)
|
||||||
lemmy-js-client:
|
lemmy-js-client:
|
||||||
specifier: 0.20.0-api-no-optional-vec.1
|
specifier: 1.0.0-block-nsfw.1
|
||||||
version: 0.20.0-api-no-optional-vec.1
|
version: 1.0.0-block-nsfw.1
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.5.0
|
specifier: ^3.5.0
|
||||||
version: 3.5.0
|
version: 3.5.0
|
||||||
|
@ -1528,8 +1528,8 @@ packages:
|
||||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
lemmy-js-client@0.20.0-api-no-optional-vec.1:
|
lemmy-js-client@1.0.0-block-nsfw.1:
|
||||||
resolution: {integrity: sha512-oIlTCiriuZVzTMScix4ubJyIOf3x0FPpnxCfm12EYbiix3Z9D44XMWs3JTV+ipJgmiAqgAiGhI0fF35RNu3FjQ==}
|
resolution: {integrity: sha512-7dIGSflkfl6JZ57tNNwoI4xwHc3uMkj9mp3lMMUh+DkSnvUNEc1BCk4sBGLYJTXGr4XreeJT99bM67RO8fGkmA==}
|
||||||
|
|
||||||
leven@3.1.0:
|
leven@3.1.0:
|
||||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||||
|
@ -4169,7 +4169,7 @@ snapshots:
|
||||||
|
|
||||||
kleur@3.0.3: {}
|
kleur@3.0.3: {}
|
||||||
|
|
||||||
lemmy-js-client@0.20.0-api-no-optional-vec.1: {}
|
lemmy-js-client@1.0.0-block-nsfw.1: {}
|
||||||
|
|
||||||
leven@3.1.0: {}
|
leven@3.1.0: {}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ import { AdminBlockInstanceParams } from "lemmy-js-client/dist/types/AdminBlockI
|
||||||
import {
|
import {
|
||||||
AddModToCommunity,
|
AddModToCommunity,
|
||||||
EditSite,
|
EditSite,
|
||||||
|
EditPost,
|
||||||
PersonPostMentionView,
|
PersonPostMentionView,
|
||||||
PostReport,
|
PostReport,
|
||||||
PostReportView,
|
PostReportView,
|
||||||
|
@ -927,6 +928,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) {
|
function checkPostReportName(rcv: ReportCombinedView, report: PostReport) {
|
||||||
switch (rcv.type_) {
|
switch (rcv.type_) {
|
||||||
case "Post":
|
case "Post":
|
||||||
|
|
|
@ -2,10 +2,9 @@ use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
request::purge_image_from_pictrs,
|
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
site::PurgePost,
|
site::PurgePost,
|
||||||
utils::is_admin,
|
utils::{is_admin, purge_post_images},
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
@ -38,14 +37,7 @@ pub async fn purge_post(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Purge image
|
purge_post_images(post.url.clone(), post.thumbnail_url.clone(), &context).await;
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
Post::delete(&mut context.pool(), data.post_id).await?;
|
Post::delete(&mut context.pool(), data.post_id).await?;
|
||||||
|
|
||||||
|
|
|
@ -254,6 +254,8 @@ pub struct CreateSite {
|
||||||
pub comment_downvotes: Option<FederationMode>,
|
pub comment_downvotes: Option<FederationMode>,
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub disable_donation_dialog: Option<bool>,
|
pub disable_donation_dialog: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub disallow_nsfw_content: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
|
@ -388,6 +390,9 @@ pub struct EditSite {
|
||||||
/// donations.
|
/// donations.
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub disable_donation_dialog: Option<bool>,
|
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)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
|
|
@ -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.
|
/// Read the site for an ap_id.
|
||||||
///
|
///
|
||||||
/// Used for GetCommunityResponse and GetPersonDetails
|
/// Used for GetCommunityResponse and GetPersonDetails
|
||||||
|
@ -671,6 +682,19 @@ pub async fn read_site_for_actor(
|
||||||
Ok(site)
|
Ok(site)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn purge_post_images(
|
||||||
|
url: Option<DbUrl>,
|
||||||
|
thumbnail_url: Option<DbUrl>,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn purge_image_posts_for_person(
|
pub async fn purge_image_posts_for_person(
|
||||||
banned_person_id: PersonId,
|
banned_person_id: PersonId,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
|
@ -678,12 +702,7 @@ pub async fn purge_image_posts_for_person(
|
||||||
let pool = &mut context.pool();
|
let pool = &mut context.pool();
|
||||||
let posts = Post::fetch_pictrs_posts_for_creator(pool, banned_person_id).await?;
|
let posts = Post::fetch_pictrs_posts_for_creator(pool, banned_person_id).await?;
|
||||||
for post in posts {
|
for post in posts {
|
||||||
if let Some(url) = post.url {
|
purge_post_images(post.url, post.thumbnail_url, context).await;
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Post::remove_pictrs_post_images_and_thumbnails_for_creator(pool, banned_person_id).await?;
|
Post::remove_pictrs_post_images_and_thumbnails_for_creator(pool, banned_person_id).await?;
|
||||||
|
@ -715,12 +734,7 @@ pub async fn purge_image_posts_for_community(
|
||||||
let pool = &mut context.pool();
|
let pool = &mut context.pool();
|
||||||
let posts = Post::fetch_pictrs_posts_for_community(pool, banned_community_id).await?;
|
let posts = Post::fetch_pictrs_posts_for_community(pool, banned_community_id).await?;
|
||||||
for post in posts {
|
for post in posts {
|
||||||
if let Some(url) = post.url {
|
purge_post_images(post.url, post.thumbnail_url, context).await;
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Post::remove_pictrs_post_images_and_thumbnails_for_community(pool, banned_community_id).await?;
|
Post::remove_pictrs_post_images_and_thumbnails_for_community(pool, banned_community_id).await?;
|
||||||
|
|
|
@ -6,6 +6,7 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, CreateCommunity},
|
community::{CommunityResponse, CreateCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{
|
utils::{
|
||||||
|
check_nsfw_allowed,
|
||||||
generate_followers_url,
|
generate_followers_url,
|
||||||
generate_inbox_url,
|
generate_inbox_url,
|
||||||
get_url_blocklist,
|
get_url_blocklist,
|
||||||
|
@ -54,6 +55,7 @@ pub async fn create_community(
|
||||||
Err(LemmyErrorType::OnlyAdminsCanCreateCommunities)?
|
Err(LemmyErrorType::OnlyAdminsCanCreateCommunities)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_nsfw_allowed(data.nsfw, Some(&local_site))?;
|
||||||
let slur_regex = slur_regex(&context).await?;
|
let slur_regex = slur_regex(&context).await?;
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
check_slurs(&data.name, &slur_regex)?;
|
check_slurs(&data.name, &slur_regex)?;
|
||||||
|
|
|
@ -7,12 +7,19 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, EditCommunity},
|
community::{CommunityResponse, EditCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
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::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
actor_language::{CommunityLanguage, SiteLanguage},
|
actor_language::{CommunityLanguage, SiteLanguage},
|
||||||
community::{Community, CommunityUpdateForm},
|
community::{Community, CommunityUpdateForm},
|
||||||
|
local_site::LocalSite,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::diesel_string_update,
|
utils::diesel_string_update,
|
||||||
|
@ -28,9 +35,12 @@ pub async fn update_community(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<CommunityResponse>> {
|
) -> LemmyResult<Json<CommunityResponse>> {
|
||||||
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let slur_regex = slur_regex(&context).await?;
|
let slur_regex = slur_regex(&context).await?;
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
check_slurs_opt(&data.title, &slur_regex)?;
|
check_slurs_opt(&data.title, &slur_regex)?;
|
||||||
|
check_nsfw_allowed(data.nsfw, Some(&local_site))?;
|
||||||
|
|
||||||
let sidebar = diesel_string_update(
|
let sidebar = diesel_string_update(
|
||||||
process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context)
|
process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context)
|
||||||
|
|
|
@ -9,6 +9,7 @@ use lemmy_api_common::{
|
||||||
send_activity::SendActivityData,
|
send_activity::SendActivityData,
|
||||||
utils::{
|
utils::{
|
||||||
check_community_user_action,
|
check_community_user_action,
|
||||||
|
check_nsfw_allowed,
|
||||||
get_url_blocklist,
|
get_url_blocklist,
|
||||||
honeypot_check,
|
honeypot_check,
|
||||||
process_markdown_opt,
|
process_markdown_opt,
|
||||||
|
@ -21,6 +22,7 @@ use lemmy_db_schema::{
|
||||||
newtypes::PostOrCommentId,
|
newtypes::PostOrCommentId,
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
|
local_site::LocalSite,
|
||||||
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostRead, PostReadForm},
|
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostRead, PostReadForm},
|
||||||
},
|
},
|
||||||
traits::{Crud, Likeable},
|
traits::{Crud, Likeable},
|
||||||
|
@ -48,6 +50,7 @@ pub async fn create_post(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<PostResponse>> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
honeypot_check(&data.honeypot)?;
|
honeypot_check(&data.honeypot)?;
|
||||||
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let slur_regex = slur_regex(&context).await?;
|
let slur_regex = slur_regex(&context).await?;
|
||||||
check_slurs(&data.name, &slur_regex)?;
|
check_slurs(&data.name, &slur_regex)?;
|
||||||
|
@ -56,6 +59,7 @@ pub async fn create_post(
|
||||||
let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?;
|
let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?;
|
||||||
let url = diesel_url_create(data.url.as_deref())?;
|
let url = diesel_url_create(data.url.as_deref())?;
|
||||||
let custom_thumbnail = diesel_url_create(data.custom_thumbnail.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)?;
|
is_valid_post_title(&data.name)?;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ use lemmy_api_common::{
|
||||||
send_activity::SendActivityData,
|
send_activity::SendActivityData,
|
||||||
utils::{
|
utils::{
|
||||||
check_community_user_action,
|
check_community_user_action,
|
||||||
|
check_nsfw_allowed,
|
||||||
get_url_blocklist,
|
get_url_blocklist,
|
||||||
process_markdown_opt,
|
process_markdown_opt,
|
||||||
send_webmention,
|
send_webmention,
|
||||||
|
@ -21,6 +22,7 @@ use lemmy_db_schema::{
|
||||||
newtypes::PostOrCommentId,
|
newtypes::PostOrCommentId,
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
|
local_site::LocalSite,
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
|
@ -48,6 +50,7 @@ pub async fn update_post(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<PostResponse>> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
let url = diesel_url_update(data.url.as_deref())?;
|
let url = diesel_url_update(data.url.as_deref())?;
|
||||||
|
|
||||||
let custom_thumbnail = diesel_url_update(data.custom_thumbnail.as_deref())?;
|
let custom_thumbnail = diesel_url_update(data.custom_thumbnail.as_deref())?;
|
||||||
|
@ -62,6 +65,8 @@ pub async fn update_post(
|
||||||
.as_deref(),
|
.as_deref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
check_nsfw_allowed(data.nsfw, Some(&local_site))?;
|
||||||
|
|
||||||
let alt_text = diesel_string_update(data.alt_text.as_deref());
|
let alt_text = diesel_string_update(data.alt_text.as_deref());
|
||||||
|
|
||||||
if let Some(name) = &data.name {
|
if let Some(name) = &data.name {
|
||||||
|
|
|
@ -105,6 +105,7 @@ pub async fn create_site(
|
||||||
comment_upvotes: data.comment_upvotes,
|
comment_upvotes: data.comment_upvotes,
|
||||||
comment_downvotes: data.comment_downvotes,
|
comment_downvotes: data.comment_downvotes,
|
||||||
disable_donation_dialog: data.disable_donation_dialog,
|
disable_donation_dialog: data.disable_donation_dialog,
|
||||||
|
disallow_nsfw_content: data.disallow_nsfw_content,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,7 @@ pub async fn update_site(
|
||||||
comment_upvotes: data.comment_upvotes,
|
comment_upvotes: data.comment_upvotes,
|
||||||
comment_downvotes: data.comment_downvotes,
|
comment_downvotes: data.comment_downvotes,
|
||||||
disable_donation_dialog: data.disable_donation_dialog,
|
disable_donation_dialog: data.disable_donation_dialog,
|
||||||
|
disallow_nsfw_content: data.disallow_nsfw_content,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{
|
utils::{
|
||||||
|
check_nsfw_allowed,
|
||||||
generate_featured_url,
|
generate_featured_url,
|
||||||
generate_moderators_url,
|
generate_moderators_url,
|
||||||
generate_outbox_url,
|
generate_outbox_url,
|
||||||
|
@ -33,6 +34,7 @@ use lemmy_db_schema::{
|
||||||
activity::ActorType,
|
activity::ActorType,
|
||||||
actor_language::CommunityLanguage,
|
actor_language::CommunityLanguage,
|
||||||
community::{Community, CommunityInsertForm, CommunityUpdateForm},
|
community::{Community, CommunityInsertForm, CommunityUpdateForm},
|
||||||
|
local_site::LocalSite,
|
||||||
},
|
},
|
||||||
traits::{ApubActor, Crud},
|
traits::{ApubActor, Crud},
|
||||||
CommunityVisibility,
|
CommunityVisibility,
|
||||||
|
@ -134,6 +136,7 @@ impl Object for ApubCommunity {
|
||||||
|
|
||||||
/// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
|
/// 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> {
|
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 instance_id = fetch_instance_actor_for_object(&group.id, context).await?;
|
||||||
|
|
||||||
let slur_regex = slur_regex(context).await?;
|
let slur_regex = slur_regex(context).await?;
|
||||||
|
@ -148,6 +151,12 @@ impl Object for ApubCommunity {
|
||||||
} else {
|
} else {
|
||||||
CommunityVisibility::Public
|
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 {
|
let form = CommunityInsertForm {
|
||||||
published: group.published,
|
published: group.published,
|
||||||
updated: group.updated,
|
updated: group.updated,
|
||||||
|
@ -159,6 +168,7 @@ impl Object for ApubCommunity {
|
||||||
icon,
|
icon,
|
||||||
banner,
|
banner,
|
||||||
sidebar,
|
sidebar,
|
||||||
|
removed,
|
||||||
description: group.summary,
|
description: group.summary,
|
||||||
followers_url: group.followers.clone().map(Into::into),
|
followers_url: group.followers.clone().map(Into::into),
|
||||||
inbox_url: Some(
|
inbox_url: Some(
|
||||||
|
|
|
@ -27,11 +27,18 @@ use html2text::{from_read_with_decorator, render::TrivialDecorator};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
request::generate_post_link_metadata,
|
request::generate_post_link_metadata,
|
||||||
utils::{get_url_blocklist, process_markdown_opt, slur_regex},
|
utils::{
|
||||||
|
check_nsfw_allowed,
|
||||||
|
get_url_blocklist,
|
||||||
|
process_markdown_opt,
|
||||||
|
purge_post_images,
|
||||||
|
slur_regex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
|
local_site::LocalSite,
|
||||||
person::Person,
|
person::Person,
|
||||||
post::{Post, PostInsertForm, PostUpdateForm},
|
post::{Post, PostInsertForm, PostUpdateForm},
|
||||||
},
|
},
|
||||||
|
@ -171,6 +178,7 @@ impl Object for ApubPost {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn from_json(page: Page, context: &Data<Self::DataType>) -> LemmyResult<ApubPost> {
|
async fn from_json(page: Page, context: &Data<Self::DataType>) -> LemmyResult<ApubPost> {
|
||||||
|
let local_site = LocalSite::read(&mut context.pool()).await.ok();
|
||||||
let creator = page.creator()?.dereference(context).await?;
|
let creator = page.creator()?.dereference(context).await?;
|
||||||
let community = page.community(context).await?;
|
let community = page.community(context).await?;
|
||||||
|
|
||||||
|
@ -220,6 +228,17 @@ impl Object for ApubPost {
|
||||||
None
|
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 let Err(e) = block_for_nsfw {
|
||||||
|
let url = url.clone().map(std::convert::Into::into);
|
||||||
|
let thumbnail_url = page.image.map(|i| i.url.into());
|
||||||
|
purge_post_images(url, thumbnail_url, context).await;
|
||||||
|
Post::delete_from_apub_id(&mut context.pool(), page.id.inner().clone()).await?;
|
||||||
|
Err(e)?
|
||||||
|
}
|
||||||
|
|
||||||
let url_blocklist = get_url_blocklist(context).await?;
|
let url_blocklist = get_url_blocklist(context).await?;
|
||||||
|
|
||||||
let url = if let Some(url) = url {
|
let url = if let Some(url) = url {
|
||||||
|
|
|
@ -55,6 +55,7 @@ diesel = { workspace = true, features = [
|
||||||
"postgres",
|
"postgres",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"64-column-tables",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
diesel-derive-newtype = { workspace = true, optional = true }
|
diesel-derive-newtype = { workspace = true, optional = true }
|
||||||
diesel-derive-enum = { workspace = true, optional = true }
|
diesel-derive-enum = { workspace = true, optional = true }
|
||||||
|
|
|
@ -182,6 +182,19 @@ impl Post {
|
||||||
.optional()
|
.optional()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn delete_from_apub_id(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
object_id: Url,
|
||||||
|
) -> Result<Vec<Self>, Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
let object_id: DbUrl = object_id.into();
|
||||||
|
|
||||||
|
diesel::update(post::table.filter(post::ap_id.eq(object_id)))
|
||||||
|
.set(post::deleted.eq(true))
|
||||||
|
.get_results::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch_pictrs_posts_for_creator(
|
pub async fn fetch_pictrs_posts_for_creator(
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
for_creator_id: PersonId,
|
for_creator_id: PersonId,
|
||||||
|
|
|
@ -449,6 +449,7 @@ diesel::table! {
|
||||||
comment_downvotes -> FederationModeEnum,
|
comment_downvotes -> FederationModeEnum,
|
||||||
disable_donation_dialog -> Bool,
|
disable_donation_dialog -> Bool,
|
||||||
default_post_time_range_seconds -> Nullable<Int4>,
|
default_post_time_range_seconds -> Nullable<Int4>,
|
||||||
|
disallow_nsfw_content -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,8 @@ pub struct LocalSite {
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
/// A default time range limit to apply to post sorts, in seconds.
|
/// A default time range limit to apply to post sorts, in seconds.
|
||||||
pub default_post_time_range_seconds: Option<i32>,
|
pub default_post_time_range_seconds: Option<i32>,
|
||||||
|
/// Block NSFW content being created
|
||||||
|
pub disallow_nsfw_content: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, derive_new::new)]
|
#[derive(Clone, derive_new::new)]
|
||||||
|
@ -152,6 +154,8 @@ pub struct LocalSiteInsertForm {
|
||||||
pub disable_donation_dialog: Option<bool>,
|
pub disable_donation_dialog: Option<bool>,
|
||||||
#[new(default)]
|
#[new(default)]
|
||||||
pub default_post_time_range_seconds: Option<Option<i32>>,
|
pub default_post_time_range_seconds: Option<Option<i32>>,
|
||||||
|
#[new(default)]
|
||||||
|
pub disallow_nsfw_content: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
|
@ -187,4 +191,5 @@ pub struct LocalSiteUpdateForm {
|
||||||
pub comment_downvotes: Option<FederationMode>,
|
pub comment_downvotes: Option<FederationMode>,
|
||||||
pub disable_donation_dialog: Option<bool>,
|
pub disable_donation_dialog: Option<bool>,
|
||||||
pub default_post_time_range_seconds: Option<Option<i32>>,
|
pub default_post_time_range_seconds: Option<Option<i32>>,
|
||||||
|
pub disallow_nsfw_content: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ pub enum LemmyErrorType {
|
||||||
LanguageNotAllowed,
|
LanguageNotAllowed,
|
||||||
CouldntUpdatePost,
|
CouldntUpdatePost,
|
||||||
NoPostEditAllowed,
|
NoPostEditAllowed,
|
||||||
|
NsfwNotAllowed,
|
||||||
EditPrivateMessageNotAllowed,
|
EditPrivateMessageNotAllowed,
|
||||||
SiteAlreadyExists,
|
SiteAlreadyExists,
|
||||||
ApplicationQuestionRequired,
|
ApplicationQuestionRequired,
|
||||||
|
|
3
migrations/2025-02-18-143408_block_nsfw/down.sql
Normal file
3
migrations/2025-02-18-143408_block_nsfw/down.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE local_site
|
||||||
|
DROP COLUMN disallow_nsfw_content;
|
||||||
|
|
3
migrations/2025-02-18-143408_block_nsfw/up.sql
Normal file
3
migrations/2025-02-18-143408_block_nsfw/up.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE local_site
|
||||||
|
ADD COLUMN disallow_nsfw_content boolean DEFAULT FALSE NOT NULL;
|
||||||
|
|
Loading…
Reference in a new issue