]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/following/accept.rs
Implement federated user following (fixes #752) (#2577)
[lemmy.git] / crates / apub / src / activities / following / accept.rs
1 use crate::{
2   activities::{generate_activity_id, send_lemmy_activity},
3   local_instance,
4   protocol::activities::following::{accept::AcceptFollow, follow::Follow},
5   ActorType,
6 };
7 use activitypub_federation::{
8   core::object_id::ObjectId,
9   data::Data,
10   traits::{ActivityHandler, Actor},
11   utils::verify_urls_match,
12 };
13 use activitystreams_kinds::activity::AcceptType;
14 use lemmy_api_common::community::CommunityResponse;
15 use lemmy_db_schema::{source::community::CommunityFollower, traits::Followable};
16 use lemmy_db_views::structs::LocalUserView;
17 use lemmy_db_views_actor::structs::CommunityView;
18 use lemmy_utils::error::LemmyError;
19 use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
20 use url::Url;
21
22 impl AcceptFollow {
23   #[tracing::instrument(skip_all)]
24   pub async fn send(
25     follow: Follow,
26     context: &LemmyContext,
27     request_counter: &mut i32,
28   ) -> Result<(), LemmyError> {
29     let user_or_community = follow.object.dereference_local(context).await?;
30     let person = follow
31       .actor
32       .clone()
33       .dereference(context, local_instance(context).await, request_counter)
34       .await?;
35     let accept = AcceptFollow {
36       actor: ObjectId::new(user_or_community.actor_id()),
37       object: follow,
38       kind: AcceptType::Accept,
39       id: generate_activity_id(
40         AcceptType::Accept,
41         &context.settings().get_protocol_and_hostname(),
42       )?,
43     };
44     let inbox = vec![person.shared_inbox_or_inbox()];
45     send_lemmy_activity(context, accept, &user_or_community, inbox, true).await
46   }
47 }
48
49 /// Handle accepted follows
50 #[async_trait::async_trait(?Send)]
51 impl ActivityHandler for AcceptFollow {
52   type DataType = LemmyContext;
53   type Error = LemmyError;
54
55   fn id(&self) -> &Url {
56     &self.id
57   }
58
59   fn actor(&self) -> &Url {
60     self.actor.inner()
61   }
62
63   #[tracing::instrument(skip_all)]
64   async fn verify(
65     &self,
66     context: &Data<LemmyContext>,
67     request_counter: &mut i32,
68   ) -> Result<(), LemmyError> {
69     verify_urls_match(self.actor.inner(), self.object.object.inner())?;
70     self.object.verify(context, request_counter).await?;
71     Ok(())
72   }
73
74   #[tracing::instrument(skip_all)]
75   async fn receive(
76     self,
77     context: &Data<LemmyContext>,
78     request_counter: &mut i32,
79   ) -> Result<(), LemmyError> {
80     let community = self
81       .actor
82       .dereference(context, local_instance(context).await, request_counter)
83       .await?;
84     let person = self
85       .object
86       .actor
87       .dereference(context, local_instance(context).await, request_counter)
88       .await?;
89     // This will throw an error if no follow was requested
90     let community_id = community.id;
91     let person_id = person.id;
92     CommunityFollower::follow_accepted(context.pool(), community_id, person_id).await?;
93
94     // Send the Subscribed message over websocket
95     // Re-read the community_view to get the new SubscribedType
96     let community_view = CommunityView::read(context.pool(), community_id, Some(person_id)).await?;
97
98     // Get the local_user_id
99     let local_recipient_id = LocalUserView::read_person(context.pool(), person_id)
100       .await?
101       .local_user
102       .id;
103
104     let response = CommunityResponse { community_view };
105
106     context.chat_server().do_send(SendUserRoomMessage {
107       op: UserOperation::FollowCommunity,
108       response,
109       local_recipient_id,
110       websocket_id: None,
111     });
112
113     Ok(())
114   }
115 }