1 use activitypub_federation::config::Data;
2 use actix_web::web::Json;
3 use lemmy_api_common::{
4 build_response::build_post_response,
6 post::{CreatePost, PostResponse},
7 request::fetch_site_data,
8 send_activity::{ActivityChannel, SendActivityData},
11 check_community_deleted_or_removed,
12 generate_local_apub_endpoint,
14 local_site_to_slur_regex,
15 local_user_view_from_jwt,
22 use lemmy_db_schema::{
23 impls::actor_language::default_post_language,
25 actor_language::CommunityLanguage,
27 local_site::LocalSite,
28 post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
30 traits::{Crud, Likeable},
32 use lemmy_db_views_actor::structs::CommunityView;
34 error::{LemmyError, LemmyErrorExt, LemmyErrorType},
37 slurs::{check_slurs, check_slurs_opt},
38 validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title},
40 SYNCHRONOUS_FEDERATION,
42 use tracing::Instrument;
44 use webmention::{Webmention, WebmentionError};
46 #[tracing::instrument(skip(context))]
47 pub async fn create_post(
48 data: Json<CreatePost>,
49 context: Data<LemmyContext>,
50 ) -> Result<Json<PostResponse>, LemmyError> {
51 let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
52 let local_site = LocalSite::read(&mut context.pool()).await?;
54 let slur_regex = local_site_to_slur_regex(&local_site);
55 check_slurs(&data.name, &slur_regex)?;
56 check_slurs_opt(&data.body, &slur_regex)?;
57 honeypot_check(&data.honeypot)?;
59 let data_url = data.url.as_ref();
60 let url = data_url.map(clean_url_params).map(Into::into); // TODO no good way to handle a "clear"
62 is_valid_post_title(&data.name)?;
63 is_valid_body_field(&data.body, true)?;
64 check_url_scheme(&data.url)?;
67 local_user_view.person.id,
72 check_community_deleted_or_removed(data.community_id, &mut context.pool()).await?;
74 let community_id = data.community_id;
75 let community = Community::read(&mut context.pool(), community_id).await?;
76 if community.posting_restricted_to_mods {
77 let community_id = data.community_id;
78 let is_mod = CommunityView::is_mod_or_admin(
80 local_user_view.local_user.person_id,
85 return Err(LemmyErrorType::OnlyModsCanPostInCommunity)?;
89 // Fetch post links and pictrs cached image
90 let (metadata_res, thumbnail_url) =
91 fetch_site_data(context.client(), context.settings(), data_url, true).await;
92 let (embed_title, embed_description, embed_video_url) = metadata_res
93 .map(|u| (u.title, u.description, u.embed_video_url))
96 let name = sanitize_html(data.name.trim());
97 let body = sanitize_html_opt(&data.body);
98 let embed_title = sanitize_html_opt(&embed_title);
99 let embed_description = sanitize_html_opt(&embed_description);
101 // Only need to check if language is allowed in case user set it explicitly. When using default
102 // language, it already only returns allowed languages.
103 CommunityLanguage::is_allowed_community_language(
110 // attempt to set default language if none was provided
111 let language_id = match data.language_id {
112 Some(lid) => Some(lid),
114 default_post_language(
117 local_user_view.local_user.id,
123 let post_form = PostInsertForm::builder()
127 .community_id(data.community_id)
128 .creator_id(local_user_view.person.id)
130 .embed_title(embed_title)
131 .embed_description(embed_description)
132 .embed_video_url(embed_video_url)
133 .language_id(language_id)
134 .thumbnail_url(thumbnail_url)
137 let inserted_post = Post::create(&mut context.pool(), &post_form)
139 .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
141 let inserted_post_id = inserted_post.id;
142 let protocol_and_hostname = context.settings().get_protocol_and_hostname();
143 let apub_id = generate_local_apub_endpoint(
145 &inserted_post_id.to_string(),
146 &protocol_and_hostname,
148 let updated_post = Post::update(
151 &PostUpdateForm::builder().ap_id(Some(apub_id)).build(),
154 .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
156 // They like their own post by default
157 let person_id = local_user_view.person.id;
158 let post_id = inserted_post.id;
159 let like_form = PostLikeForm {
165 PostLike::like(&mut context.pool(), &like_form)
167 .with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
169 ActivityChannel::submit_activity(SendActivityData::CreatePost(updated_post.clone()), &context)
172 // Mark the post as read
173 mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
175 if let Some(url) = updated_post.url.clone() {
176 let task = async move {
178 Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?;
179 webmention.set_checked(true);
182 .instrument(tracing::info_span!("Sending webmention"))
185 Err(WebmentionError::NoEndpointDiscovered(_)) => Ok(()),
187 Err(e) => Err(e).with_lemmy_type(LemmyErrorType::CouldntSendWebmention),
190 if *SYNCHRONOUS_FEDERATION {
193 spawn_try_task(task);
197 build_post_response(&context, community_id, person_id, post_id).await