]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/community/announce.rs
2886c2bf0ff2d81e0c266914b98d2ee8216a8ebe
[lemmy.git] / crates / apub / src / activities / community / announce.rs
1 use crate::{
2   activities::{generate_activity_id, send_lemmy_activity, verify_is_public},
3   activity_lists::AnnouncableActivities,
4   check_apub_id_valid,
5   fetch_local_site_data,
6   insert_activity,
7   objects::community::ApubCommunity,
8   protocol::{
9     activities::{community::announce::AnnounceActivity, CreateOrUpdateType},
10     IdOrNestedObject,
11   },
12   ActorType,
13 };
14 use activitypub_federation::{core::object_id::ObjectId, data::Data, traits::ActivityHandler};
15 use activitystreams_kinds::{activity::AnnounceType, public};
16 use lemmy_api_common::utils::blocking;
17 use lemmy_utils::error::LemmyError;
18 use lemmy_websocket::LemmyContext;
19 use tracing::debug;
20 use url::Url;
21
22 #[async_trait::async_trait(?Send)]
23 pub(crate) trait GetCommunity {
24   async fn get_community(
25     &self,
26     context: &LemmyContext,
27     request_counter: &mut i32,
28   ) -> Result<ApubCommunity, LemmyError>;
29 }
30
31 impl AnnounceActivity {
32   pub(crate) fn new(
33     object: AnnouncableActivities,
34     community: &ApubCommunity,
35     context: &LemmyContext,
36   ) -> Result<AnnounceActivity, LemmyError> {
37     Ok(AnnounceActivity {
38       actor: ObjectId::new(community.actor_id()),
39       to: vec![public()],
40       object: IdOrNestedObject::NestedObject(object),
41       cc: vec![community.followers_url.clone().into()],
42       kind: AnnounceType::Announce,
43       id: generate_activity_id(
44         &AnnounceType::Announce,
45         &context.settings().get_protocol_and_hostname(),
46       )?,
47       unparsed: Default::default(),
48     })
49   }
50
51   #[tracing::instrument(skip_all)]
52   pub async fn send(
53     object: AnnouncableActivities,
54     community: &ApubCommunity,
55     context: &LemmyContext,
56   ) -> Result<(), LemmyError> {
57     let announce = AnnounceActivity::new(object.clone(), community, context)?;
58     let inboxes = community.get_follower_inboxes(context).await?;
59     send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?;
60
61     // Pleroma and Mastodon can't handle activities like Announce/Create/Page. So for
62     // compatibility, we also send Announce/Page so that they can follow Lemmy communities.
63     use AnnouncableActivities::*;
64     let object = match object {
65       CreateOrUpdatePost(c) if c.kind == CreateOrUpdateType::Create => Page(c.object),
66       _ => return Ok(()),
67     };
68     let announce_compat = AnnounceActivity::new(object, community, context)?;
69     send_lemmy_activity(context, announce_compat, community, inboxes, false).await?;
70     Ok(())
71   }
72 }
73
74 #[async_trait::async_trait(?Send)]
75 impl ActivityHandler for AnnounceActivity {
76   type DataType = LemmyContext;
77   type Error = LemmyError;
78
79   fn id(&self) -> &Url {
80     &self.id
81   }
82
83   fn actor(&self) -> &Url {
84     self.actor.inner()
85   }
86
87   #[tracing::instrument(skip_all)]
88   async fn verify(
89     &self,
90     context: &Data<LemmyContext>,
91     _request_counter: &mut i32,
92   ) -> Result<(), LemmyError> {
93     let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
94     check_apub_id_valid(self.id(), &local_site_data, context.settings())
95       .map_err(LemmyError::from_message)?;
96
97     verify_is_public(&self.to, &self.cc)?;
98     Ok(())
99   }
100
101   #[tracing::instrument(skip_all)]
102   async fn receive(
103     self,
104     context: &Data<LemmyContext>,
105     request_counter: &mut i32,
106   ) -> Result<(), LemmyError> {
107     let object = self.object.object(context, request_counter).await?;
108     // we have to verify this here in order to avoid fetching the object twice over http
109     object.verify(context, request_counter).await?;
110
111     // TODO: this can probably be implemented in a cleaner way
112     match object {
113       // Dont insert these into activities table, as they are not activities.
114       AnnouncableActivities::Page(_) => {}
115       _ => {
116         let object_value = serde_json::to_value(&object)?;
117         let insert =
118           insert_activity(object.id(), object_value, false, true, context.pool()).await?;
119         if !insert {
120           debug!(
121             "Received duplicate activity in announce {}",
122             object.id().to_string()
123           );
124           return Ok(());
125         }
126       }
127     }
128     object.receive(context, request_counter).await
129   }
130 }