2 activities::receive::verify_activity_domains_valid,
4 assert_activity_not_local,
6 inbox_verify_http_signature,
7 is_activity_already_known,
8 receive_for_community::{
9 receive_add_for_community,
10 receive_block_user_for_community,
11 receive_create_for_community,
12 receive_delete_for_community,
13 receive_dislike_for_community,
14 receive_like_for_community,
15 receive_remove_for_community,
16 receive_undo_for_community,
17 receive_update_for_community,
19 verify_is_addressed_to_public,
22 use activitystreams::{
23 activity::{kind::FollowType, ActorAndObject, Follow, Undo},
27 use actix_web::{web, HttpRequest, HttpResponse};
28 use anyhow::{anyhow, Context};
29 use lemmy_api_common::blocking;
31 check_community_or_site_ban,
32 get_activity_to_and_cc,
37 use lemmy_db_queries::{source::community::Community_, ApubObject, Followable};
38 use lemmy_db_schema::source::{
39 community::{Community, CommunityFollower, CommunityFollowerForm},
42 use lemmy_utils::{location_info, LemmyError};
43 use lemmy_websocket::LemmyContext;
45 use serde::{Deserialize, Serialize};
49 /// Allowed activities for community inbox.
50 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
51 #[serde(rename_all = "PascalCase")]
52 pub enum CommunityValidTypes {
53 Follow, // follow request from a person
54 Undo, // unfollow from a person
55 Create, // create post or comment
56 Update, // update post or comment
57 Like, // upvote post or comment
58 Dislike, // downvote post or comment
59 Delete, // post or comment deleted by creator
60 Remove, // post or comment removed by mod or admin, or mod removed from community
61 Add, // mod added to community
62 Block, // user blocked by community
65 pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
67 /// Handler for all incoming receive to community inboxes.
68 pub async fn community_inbox(
70 input: web::Json<CommunityAcceptedActivities>,
71 path: web::Path<String>,
72 context: web::Data<LemmyContext>,
73 ) -> Result<HttpResponse, LemmyError> {
74 let activity = input.into_inner();
75 // First of all check the http signature
76 let request_counter = &mut 0;
77 let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
79 // Do nothing if we received the same activity before
80 let activity_id = get_activity_id(&activity, &actor.actor_id())?;
81 if is_activity_already_known(context.pool(), &activity_id).await? {
82 return Ok(HttpResponse::Ok().finish());
85 // Check if the activity is actually meant for us
86 let path = path.into_inner();
87 let community = blocking(&context.pool(), move |conn| {
88 Community::read_from_name(&conn, &path)
91 let to_and_cc = get_activity_to_and_cc(&activity);
92 if !to_and_cc.contains(&&community.actor_id()) {
93 return Err(anyhow!("Activity delivered to wrong community").into());
96 assert_activity_not_local(&activity)?;
97 insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
99 community_receive_message(
109 /// Receives Follow, Undo/Follow, post actions, comment actions (including votes)
110 pub(crate) async fn community_receive_message(
111 activity: CommunityAcceptedActivities,
112 to_community: Community,
113 actor: &dyn ActorType,
114 context: &LemmyContext,
115 request_counter: &mut i32,
116 ) -> Result<HttpResponse, LemmyError> {
117 // Only persons can send activities to the community, so we can get the actor as person
119 let actor_id = actor.actor_id();
120 let person = blocking(&context.pool(), move |conn| {
121 Person::read_from_apub_id(&conn, &actor_id.into())
124 check_community_or_site_ban(&person, to_community.id, context.pool()).await?;
127 "Community {} received activity {} from {}",
131 .context(location_info!())?
133 &person.actor_id().to_string()
136 let any_base = activity.clone().into_any_base()?;
137 let actor_url = actor.actor_id();
138 let activity_kind = activity.kind().context(location_info!())?;
139 let do_announce = match activity_kind {
140 CommunityValidTypes::Follow => {
141 Box::pin(handle_follow(
150 CommunityValidTypes::Undo => {
151 Box::pin(handle_undo(
160 CommunityValidTypes::Create => {
161 Box::pin(receive_create_for_community(
170 CommunityValidTypes::Update => {
171 Box::pin(receive_update_for_community(
181 CommunityValidTypes::Like => {
182 Box::pin(receive_like_for_community(
191 CommunityValidTypes::Dislike => {
192 Box::pin(receive_dislike_for_community(
201 CommunityValidTypes::Delete => {
202 Box::pin(receive_delete_for_community(
212 CommunityValidTypes::Add => {
213 Box::pin(receive_add_for_community(
222 CommunityValidTypes::Remove => {
223 Box::pin(receive_remove_for_community(
232 CommunityValidTypes::Block => {
233 Box::pin(receive_block_user_for_community(
245 // Check again that the activity is public, just to be sure
246 verify_is_addressed_to_public(&activity)?;
247 let mut object_actor = activity.object().clone().single_xsd_any_uri();
248 // If activity is something like Undo/Block, we need to access activity.object.object
249 if object_actor.is_none() {
250 object_actor = activity
253 .map(|a| ActorAndObject::from_any_base(a.to_owned()).ok())
256 .map(|a: ActorAndObject<CommunityValidTypes>| a.object().to_owned().single_xsd_any_uri())
260 .send_announce(activity.into_any_base()?, object_actor, context)
264 Ok(HttpResponse::Ok().finish())
267 /// Handle a follow request from a remote person, adding the person as follower and returning an
269 async fn handle_follow(
272 community: &Community,
273 context: &LemmyContext,
274 ) -> Result<HttpResponse, LemmyError> {
275 let follow = Follow::from_any_base(activity)?.context(location_info!())?;
276 verify_activity_domains_valid(&follow, &person.actor_id(), false)?;
278 let community_follower_form = CommunityFollowerForm {
279 community_id: community.id,
280 person_id: person.id,
284 // This will fail if they're already a follower, but ignore the error.
285 blocking(&context.pool(), move |conn| {
286 CommunityFollower::follow(&conn, &community_follower_form).ok()
290 community.send_accept_follow(follow, context).await?;
292 Ok(HttpResponse::Ok().finish())
295 async fn handle_undo(
296 context: &LemmyContext,
297 activity: CommunityAcceptedActivities,
299 to_community: &Community,
300 request_counter: &mut i32,
301 ) -> Result<bool, LemmyError> {
302 let inner_kind = activity
304 .is_single_kind(&FollowType::Follow.to_string());
305 let any_base = activity.into_any_base()?;
307 handle_undo_follow(any_base, actor_url, to_community, &context).await?;
310 receive_undo_for_community(context, any_base, None, &actor_url, request_counter).await?;
315 /// Handle `Undo/Follow` from a person, removing the person from followers list.
316 async fn handle_undo_follow(
319 community: &Community,
320 context: &LemmyContext,
321 ) -> Result<(), LemmyError> {
322 let undo = Undo::from_any_base(activity)?.context(location_info!())?;
323 verify_activity_domains_valid(&undo, &person_url, true)?;
325 let object = undo.object().to_owned().one().context(location_info!())?;
326 let follow = Follow::from_any_base(object)?.context(location_info!())?;
327 verify_activity_domains_valid(&follow, &person_url, false)?;
329 let person = blocking(&context.pool(), move |conn| {
330 Person::read_from_apub_id(&conn, &person_url.into())
333 let community_follower_form = CommunityFollowerForm {
334 community_id: community.id,
335 person_id: person.id,
339 // This will fail if they aren't a follower, but ignore the error.
340 blocking(&context.pool(), move |conn| {
341 CommunityFollower::unfollow(&conn, &community_follower_form).ok()