4 receive_create_comment,
5 receive_delete_comment,
6 receive_dislike_comment,
8 receive_remove_comment,
9 receive_update_comment,
12 receive_undo_delete_comment,
13 receive_undo_dislike_comment,
14 receive_undo_like_comment,
15 receive_undo_remove_comment,
26 receive_undo_delete_post,
27 receive_undo_dislike_post,
28 receive_undo_like_post,
29 receive_undo_remove_post,
31 receive_unhandled_activity,
32 verify_activity_domains_valid,
35 objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
36 user::get_or_fetch_and_upsert_user,
38 find_post_or_comment_by_id,
39 inbox::is_addressed_to_public,
42 use activitystreams::{
58 use anyhow::{anyhow, Context};
59 use diesel::result::Error::NotFound;
60 use lemmy_api_structs::blocking;
61 use lemmy_db_queries::{ApubObject, Crud, Joinable};
62 use lemmy_db_schema::{
64 community::{Community, CommunityModerator, CommunityModeratorForm},
70 use lemmy_db_views_actor::community_view::CommunityView;
71 use lemmy_utils::{location_info, LemmyError};
72 use lemmy_websocket::LemmyContext;
73 use strum_macros::EnumString;
82 /// This file is for post/comment activities received by the community, and for post/comment
83 /// activities announced by the community and received by the user.
85 /// A post or comment being created
86 pub(in crate::inbox) async fn receive_create_for_community(
87 context: &LemmyContext,
89 expected_domain: &Url,
90 request_counter: &mut i32,
91 ) -> Result<(), LemmyError> {
92 let create = Create::from_any_base(activity)?.context(location_info!())?;
93 verify_activity_domains_valid(&create, &expected_domain, true)?;
94 is_addressed_to_public(&create)?;
99 .and_then(|s| s.parse().ok());
101 Some(PageOrNote::Page) => receive_create_post(create, context, request_counter).await,
102 Some(PageOrNote::Note) => receive_create_comment(create, context, request_counter).await,
103 _ => receive_unhandled_activity(create),
107 /// A post or comment being edited
108 pub(in crate::inbox) async fn receive_update_for_community(
109 context: &LemmyContext,
111 expected_domain: &Url,
112 request_counter: &mut i32,
113 ) -> Result<(), LemmyError> {
114 let update = Update::from_any_base(activity)?.context(location_info!())?;
115 verify_activity_domains_valid(&update, &expected_domain, true)?;
116 is_addressed_to_public(&update)?;
120 .as_single_kind_str()
121 .and_then(|s| s.parse().ok());
123 Some(PageOrNote::Page) => receive_update_post(update, context, request_counter).await,
124 Some(PageOrNote::Note) => receive_update_comment(update, context, request_counter).await,
125 _ => receive_unhandled_activity(update),
129 /// A post or comment being upvoted
130 pub(in crate::inbox) async fn receive_like_for_community(
131 context: &LemmyContext,
133 expected_domain: &Url,
134 request_counter: &mut i32,
135 ) -> Result<(), LemmyError> {
136 let like = Like::from_any_base(activity)?.context(location_info!())?;
137 verify_activity_domains_valid(&like, &expected_domain, false)?;
138 is_addressed_to_public(&like)?;
142 .as_single_xsd_any_uri()
143 .context(location_info!())?;
144 match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
145 PostOrComment::Post(post) => receive_like_post(like, *post, context, request_counter).await,
146 PostOrComment::Comment(comment) => {
147 receive_like_comment(like, *comment, context, request_counter).await
152 /// A post or comment being downvoted
153 pub(in crate::inbox) async fn receive_dislike_for_community(
154 context: &LemmyContext,
156 expected_domain: &Url,
157 request_counter: &mut i32,
158 ) -> Result<(), LemmyError> {
159 let enable_downvotes = blocking(context.pool(), move |conn| {
160 Site::read(conn, 1).map(|s| s.enable_downvotes)
163 if !enable_downvotes {
167 let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
168 verify_activity_domains_valid(&dislike, &expected_domain, false)?;
169 is_addressed_to_public(&dislike)?;
171 let object_id = dislike
173 .as_single_xsd_any_uri()
174 .context(location_info!())?;
175 match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
176 PostOrComment::Post(post) => {
177 receive_dislike_post(dislike, *post, context, request_counter).await
179 PostOrComment::Comment(comment) => {
180 receive_dislike_comment(dislike, *comment, context, request_counter).await
185 /// A post or comment being deleted by its creator
186 pub(in crate::inbox) async fn receive_delete_for_community(
187 context: &LemmyContext,
189 expected_domain: &Url,
190 ) -> Result<(), LemmyError> {
191 let delete = Delete::from_any_base(activity)?.context(location_info!())?;
192 verify_activity_domains_valid(&delete, &expected_domain, true)?;
193 is_addressed_to_public(&delete)?;
198 .single_xsd_any_uri()
199 .context(location_info!())?;
201 match find_post_or_comment_by_id(context, object).await {
202 Ok(PostOrComment::Post(p)) => receive_delete_post(context, *p).await,
203 Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, *c).await,
204 // if we dont have the object, no need to do anything
209 /// A post or comment being removed by a mod/admin
210 pub(in crate::inbox) async fn receive_remove_for_community(
211 context: &LemmyContext,
213 expected_domain: &Url,
214 request_counter: &mut i32,
215 ) -> Result<(), LemmyError> {
216 let remove = Remove::from_any_base(activity)?.context(location_info!())?;
217 verify_activity_domains_valid(&remove, &expected_domain, false)?;
218 is_addressed_to_public(&remove)?;
220 // Remove a moderator from community
221 if remove.target().is_some() {
222 let community = verify_actor_is_community_mod(&remove, context).await?;
224 let remove_mod = remove
226 .as_single_xsd_any_uri()
227 .context(location_info!())?;
228 let remove_mod = get_or_fetch_and_upsert_user(&remove_mod, context, request_counter).await?;
229 let form = CommunityModeratorForm {
230 community_id: community.id,
231 user_id: remove_mod.id,
233 blocking(context.pool(), move |conn| {
234 CommunityModerator::leave(conn, &form)
239 // Remove a post or comment
243 .map(|c| c.as_many())
245 .context(location_info!())?;
246 let community_id = cc
248 .map(|c| c.as_xsd_any_uri())
250 .context(location_info!())?;
255 .single_xsd_any_uri()
256 .context(location_info!())?;
258 // Ensure that remove activity comes from the same domain as the community
259 remove.id(community_id.domain().context(location_info!())?)?;
261 match find_post_or_comment_by_id(context, object).await {
262 Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, *p).await,
263 Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, *c).await,
264 // if we dont have the object, no need to do anything
270 #[derive(EnumString)]
271 enum UndoableActivities {
278 /// A post/comment action being reverted (either a delete, remove, upvote or downvote)
279 pub(in crate::inbox) async fn receive_undo_for_community(
280 context: &LemmyContext,
282 expected_domain: &Url,
283 request_counter: &mut i32,
284 ) -> Result<(), LemmyError> {
285 let undo = Undo::from_any_base(activity)?.context(location_info!())?;
286 verify_activity_domains_valid(&undo, &expected_domain.to_owned(), true)?;
287 is_addressed_to_public(&undo)?;
289 use UndoableActivities::*;
292 .as_single_kind_str()
293 .and_then(|s| s.parse().ok())
295 Some(Delete) => receive_undo_delete_for_community(context, undo, expected_domain).await,
296 Some(Remove) => receive_undo_remove_for_community(context, undo, expected_domain).await,
298 receive_undo_like_for_community(context, undo, expected_domain, request_counter).await
301 receive_undo_dislike_for_community(context, undo, expected_domain, request_counter).await
303 _ => receive_unhandled_activity(undo),
307 /// A post or comment deletion being reverted
308 pub(in crate::inbox) async fn receive_undo_delete_for_community(
309 context: &LemmyContext,
311 expected_domain: &Url,
312 ) -> Result<(), LemmyError> {
313 let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
314 .context(location_info!())?;
315 verify_activity_domains_valid(&delete, &expected_domain, true)?;
316 is_addressed_to_public(&delete)?;
321 .single_xsd_any_uri()
322 .context(location_info!())?;
323 match find_post_or_comment_by_id(context, object).await {
324 Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, *p).await,
325 Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, *c).await,
326 // if we dont have the object, no need to do anything
331 /// A post or comment removal being reverted
332 pub(in crate::inbox) async fn receive_undo_remove_for_community(
333 context: &LemmyContext,
335 expected_domain: &Url,
336 ) -> Result<(), LemmyError> {
337 let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
338 .context(location_info!())?;
339 verify_activity_domains_valid(&remove, &expected_domain, false)?;
340 is_addressed_to_public(&remove)?;
345 .single_xsd_any_uri()
346 .context(location_info!())?;
347 match find_post_or_comment_by_id(context, object).await {
348 Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, *p).await,
349 Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, *c).await,
350 // if we dont have the object, no need to do anything
355 /// A post or comment upvote being reverted
356 pub(in crate::inbox) async fn receive_undo_like_for_community(
357 context: &LemmyContext,
359 expected_domain: &Url,
360 request_counter: &mut i32,
361 ) -> Result<(), LemmyError> {
362 let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
363 .context(location_info!())?;
364 verify_activity_domains_valid(&like, &expected_domain, false)?;
365 is_addressed_to_public(&like)?;
369 .as_single_xsd_any_uri()
370 .context(location_info!())?;
371 match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
372 PostOrComment::Post(post) => {
373 receive_undo_like_post(&like, *post, context, request_counter).await
375 PostOrComment::Comment(comment) => {
376 receive_undo_like_comment(&like, *comment, context, request_counter).await
381 /// Add a new mod to the community (can only be done by an existing mod).
382 pub(in crate::inbox) async fn receive_add_for_community(
383 context: &LemmyContext,
385 expected_domain: &Url,
386 request_counter: &mut i32,
387 ) -> Result<(), LemmyError> {
388 let add = Add::from_any_base(activity)?.context(location_info!())?;
389 verify_activity_domains_valid(&add, &expected_domain, false)?;
390 is_addressed_to_public(&add)?;
391 let community = verify_actor_is_community_mod(&add, context).await?;
395 .as_single_xsd_any_uri()
396 .context(location_info!())?;
397 let new_mod = get_or_fetch_and_upsert_user(&new_mod, context, request_counter).await?;
398 let form = CommunityModeratorForm {
399 community_id: community.id,
402 blocking(context.pool(), move |conn| {
403 CommunityModerator::join(conn, &form)
409 /// A post or comment downvote being reverted
410 pub(in crate::inbox) async fn receive_undo_dislike_for_community(
411 context: &LemmyContext,
413 expected_domain: &Url,
414 request_counter: &mut i32,
415 ) -> Result<(), LemmyError> {
416 let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
417 .context(location_info!())?;
418 verify_activity_domains_valid(&dislike, &expected_domain, false)?;
419 is_addressed_to_public(&dislike)?;
421 let object_id = dislike
423 .as_single_xsd_any_uri()
424 .context(location_info!())?;
425 match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
426 PostOrComment::Post(post) => {
427 receive_undo_dislike_post(&dislike, *post, context, request_counter).await
429 PostOrComment::Comment(comment) => {
430 receive_undo_dislike_comment(&dislike, *comment, context, request_counter).await
435 async fn fetch_post_or_comment_by_id(
437 context: &LemmyContext,
438 request_counter: &mut i32,
439 ) -> Result<PostOrComment, LemmyError> {
440 if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
441 return Ok(PostOrComment::Post(Box::new(post)));
444 if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
445 return Ok(PostOrComment::Comment(Box::new(comment)));
451 async fn verify_actor_is_community_mod<T, Kind>(
453 context: &LemmyContext,
454 ) -> Result<Community, LemmyError>
456 T: ActorAndObjectRef + BaseExt<Kind> + OptTargetRef,
458 // should be the moderators collection of a local community
459 let target = activity
461 .map(|t| t.as_single_xsd_string())
463 .context(location_info!())?;
464 // TODO: very hacky, we should probably store the moderators url in db
465 let community_id: DbUrl = Url::parse(&target.to_string().replace("/moderators", ""))?.into();
466 let community = blocking(&context.pool(), move |conn| {
467 Community::read_from_apub_id(&conn, &community_id)
473 .as_single_xsd_any_uri()
474 .context(location_info!())?
476 let actor = blocking(&context.pool(), move |conn| {
477 User_::read_from_apub_id(&conn, &actor.into())
481 // Note: this will also return true for admins in addition to mods, but as we dont know about
482 // remote admins, it doesnt make any difference.
483 let community_id = community.id;
484 let actor_id = actor.id;
485 let is_mod_or_admin = blocking(context.pool(), move |conn| {
486 CommunityView::is_mod_or_admin(conn, actor_id, community_id)
489 if !is_mod_or_admin {
490 return Err(anyhow!("Not a mod").into());
493 // TODO: the function name doesnt make sense if we return the community