]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/community/announce.rs
Federation tests replication round1 - demonstrate absent replication of comment delet...
[lemmy.git] / crates / apub / src / activities / community / announce.rs
1 use crate::{
2   activities::{
3     generate_activity_id,
4     send_lemmy_activity,
5     verify_is_public,
6     verify_person_in_community,
7   },
8   activity_lists::AnnouncableActivities,
9   insert_received_activity,
10   objects::community::ApubCommunity,
11   protocol::{
12     activities::community::announce::{AnnounceActivity, RawAnnouncableActivities},
13     Id,
14     IdOrNestedObject,
15     InCommunity,
16   },
17 };
18 use activitypub_federation::{
19   config::Data,
20   kinds::{activity::AnnounceType, public},
21   traits::{ActivityHandler, Actor},
22 };
23 use lemmy_api_common::context::LemmyContext;
24 use lemmy_utils::error::{LemmyError, LemmyErrorType};
25 use serde_json::Value;
26 use url::Url;
27
28 #[async_trait::async_trait]
29 impl ActivityHandler for RawAnnouncableActivities {
30   type DataType = LemmyContext;
31   type Error = LemmyError;
32
33   fn id(&self) -> &Url {
34     &self.id
35   }
36
37   fn actor(&self) -> &Url {
38     &self.actor
39   }
40
41   #[tracing::instrument(skip_all)]
42   async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
43     Ok(())
44   }
45
46   #[tracing::instrument(skip_all)]
47   async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
48     let activity: AnnouncableActivities = self.clone().try_into()?;
49     // This is only for sending, not receiving so we reject it.
50     if let AnnouncableActivities::Page(_) = activity {
51       return Err(LemmyErrorType::CannotReceivePage)?;
52     }
53
54     // verify and receive activity
55     activity.verify(data).await?;
56     activity.clone().receive(data).await?;
57
58     // if activity is in a community, send to followers
59     let community = activity.community(data).await;
60     if let Ok(community) = community {
61       if community.local {
62         let actor_id = activity.actor().clone().into();
63         verify_person_in_community(&actor_id, &community, data).await?;
64         AnnounceActivity::send(self, &community, data).await?;
65       }
66     }
67     Ok(())
68   }
69 }
70
71 impl AnnounceActivity {
72   pub(crate) fn new(
73     object: RawAnnouncableActivities,
74     community: &ApubCommunity,
75     context: &Data<LemmyContext>,
76   ) -> Result<AnnounceActivity, LemmyError> {
77     Ok(AnnounceActivity {
78       actor: community.id().into(),
79       to: vec![public()],
80       object: IdOrNestedObject::NestedObject(object),
81       cc: vec![community.followers_url.clone().into()],
82       kind: AnnounceType::Announce,
83       id: generate_activity_id(
84         &AnnounceType::Announce,
85         &context.settings().get_protocol_and_hostname(),
86       )?,
87     })
88   }
89
90   #[tracing::instrument(skip_all)]
91   pub async fn send(
92     object: RawAnnouncableActivities,
93     community: &ApubCommunity,
94     context: &Data<LemmyContext>,
95   ) -> Result<(), LemmyError> {
96     let announce = AnnounceActivity::new(object.clone(), community, context)?;
97     let inboxes = community.get_follower_inboxes(context).await?;
98     send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?;
99
100     // Pleroma and Mastodon can't handle activities like Announce/Create/Page. So for
101     // compatibility, we also send Announce/Page so that they can follow Lemmy communities.
102     let object_parsed = object.try_into()?;
103     if let AnnouncableActivities::CreateOrUpdatePost(c) = object_parsed {
104       // Hack: need to convert Page into a format which can be sent as activity, which requires
105       //       adding actor field.
106       let announcable_page = RawAnnouncableActivities {
107         id: generate_activity_id(
108           AnnounceType::Announce,
109           &context.settings().get_protocol_and_hostname(),
110         )?,
111         actor: c.actor.clone().into_inner(),
112         other: serde_json::to_value(c.object)?
113           .as_object()
114           .expect("is object")
115           .clone(),
116       };
117       let announce_compat = AnnounceActivity::new(announcable_page, community, context)?;
118       send_lemmy_activity(context, announce_compat, community, inboxes, false).await?;
119     }
120     Ok(())
121   }
122 }
123
124 #[async_trait::async_trait]
125 impl ActivityHandler for AnnounceActivity {
126   type DataType = LemmyContext;
127   type Error = LemmyError;
128
129   fn id(&self) -> &Url {
130     &self.id
131   }
132
133   fn actor(&self) -> &Url {
134     self.actor.inner()
135   }
136
137   #[tracing::instrument(skip_all)]
138   async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
139     insert_received_activity(&self.id, context).await?;
140     verify_is_public(&self.to, &self.cc)?;
141     Ok(())
142   }
143
144   #[tracing::instrument(skip_all)]
145   async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
146     let object: AnnouncableActivities = self.object.object(context).await?.try_into()?;
147     // This is only for sending, not receiving so we reject it.
148     if let AnnouncableActivities::Page(_) = object {
149       return Err(LemmyErrorType::CannotReceivePage)?;
150     }
151
152     // verify here in order to avoid fetching the object twice over http
153     object.verify(context).await?;
154     object.receive(context).await
155   }
156 }
157
158 impl Id for RawAnnouncableActivities {
159   fn object_id(&self) -> &Url {
160     ActivityHandler::id(self)
161   }
162 }
163
164 impl TryFrom<RawAnnouncableActivities> for AnnouncableActivities {
165   type Error = serde_json::error::Error;
166
167   fn try_from(value: RawAnnouncableActivities) -> Result<Self, Self::Error> {
168     let mut map = value.other.clone();
169     map.insert("id".to_string(), Value::String(value.id.to_string()));
170     map.insert("actor".to_string(), Value::String(value.actor.to_string()));
171     serde_json::from_value(Value::Object(map))
172   }
173 }
174
175 impl TryFrom<AnnouncableActivities> for RawAnnouncableActivities {
176   type Error = serde_json::error::Error;
177
178   fn try_from(value: AnnouncableActivities) -> Result<Self, Self::Error> {
179     serde_json::from_value(serde_json::to_value(value)?)
180   }
181 }