3 assert_activity_not_local,
4 community_inbox::{community_receive_message, CommunityAcceptedActivities},
6 get_activity_to_and_cc,
7 inbox_verify_http_signature,
8 is_activity_already_known,
9 is_addressed_to_community_followers,
10 is_addressed_to_local_person,
11 person_inbox::{person_receive_message, PersonAcceptedActivities},
15 use activitystreams::{activity::ActorAndObject, prelude::*};
16 use actix_web::{web, HttpRequest, HttpResponse};
18 use lemmy_api_structs::blocking;
19 use lemmy_db_queries::{ApubObject, DbPool};
20 use lemmy_db_schema::source::community::Community;
21 use lemmy_utils::{location_info, LemmyError};
22 use lemmy_websocket::LemmyContext;
23 use serde::{Deserialize, Serialize};
27 /// Allowed activity types for shared inbox.
28 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
29 #[serde(rename_all = "PascalCase")]
42 // TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
43 // but it still works due to the anybase conversion
44 pub type AcceptedActivities = ActorAndObject<ValidTypes>;
46 /// Handler for all incoming requests to shared inbox.
47 pub async fn shared_inbox(
49 input: web::Json<AcceptedActivities>,
50 context: web::Data<LemmyContext>,
51 ) -> Result<HttpResponse, LemmyError> {
52 let activity = input.into_inner();
53 // First of all check the http signature
54 let request_counter = &mut 0;
55 let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
57 // Do nothing if we received the same activity before
58 let actor_id = actor.actor_id();
59 let activity_id = get_activity_id(&activity, &actor_id)?;
60 if is_activity_already_known(context.pool(), &activity_id).await? {
61 return Ok(HttpResponse::Ok().finish());
64 assert_activity_not_local(&activity)?;
65 // Log the activity, so we avoid receiving and parsing it twice. Note that this could still happen
66 // if we receive the same activity twice in very quick succession.
67 insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
69 let activity_any_base = activity.clone().into_any_base()?;
70 let mut res: Option<HttpResponse> = None;
71 let to_and_cc = get_activity_to_and_cc(&activity);
72 // Handle community first, so in case the sender is banned by the community, it will error out.
73 // If we handled the person receive first, the activity would be inserted to the database before the
74 // community could check for bans.
75 // Note that an activity can be addressed to a community and to a person (or multiple persons) at the
76 // same time. In this case we still only handle it once, to avoid duplicate websocket
78 let community = extract_local_community_from_destinations(&to_and_cc, context.pool()).await?;
79 if let Some(community) = community {
80 let community_activity = CommunityAcceptedActivities::from_any_base(activity_any_base.clone())?
81 .context(location_info!())?;
83 Box::pin(community_receive_message(
92 } else if is_addressed_to_local_person(&to_and_cc, context.pool()).await? {
93 let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())?
94 .context(location_info!())?;
95 // `to_person` is only used for follow activities (which we dont receive here), so no need to pass
97 Box::pin(person_receive_message(
105 } else if is_addressed_to_community_followers(&to_and_cc, context.pool())
109 let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())?
110 .context(location_info!())?;
112 Box::pin(person_receive_message(
123 // If none of those, throw an error
124 if let Some(r) = res {
127 Ok(HttpResponse::NotImplemented().finish())
131 /// If `to_and_cc` contains the ID of a local community, return that community, otherwise return
134 /// This doesnt handle the case where an activity is addressed to multiple communities (because
135 /// Lemmy doesnt generate such activities).
136 async fn extract_local_community_from_destinations(
139 ) -> Result<Option<Community>, LemmyError> {
140 for url in to_and_cc {
141 let url = url.to_owned();
142 let community = blocking(&pool, move |conn| {
143 Community::read_from_apub_id(&conn, &url.into())
146 if let Ok(c) = community {