]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/community/announce.rs
e33e9fbf482ed1a02d978101574f04669187ccc4
[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_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     let community = activity.community(data).await?;
54     let actor_id = activity.actor().clone().into();
55
56     // verify and receive activity
57     activity.verify(data).await?;
58     activity.receive(data).await?;
59
60     // send to community followers
61     if community.local {
62       verify_person_in_community(&actor_id, &community, data).await?;
63       AnnounceActivity::send(self, &community, data).await?;
64     }
65     Ok(())
66   }
67 }
68
69 impl AnnounceActivity {
70   pub(crate) fn new(
71     object: RawAnnouncableActivities,
72     community: &ApubCommunity,
73     context: &Data<LemmyContext>,
74   ) -> Result<AnnounceActivity, LemmyError> {
75     Ok(AnnounceActivity {
76       actor: community.id().into(),
77       to: vec![public()],
78       object: IdOrNestedObject::NestedObject(object),
79       cc: vec![community.followers_url.clone().into()],
80       kind: AnnounceType::Announce,
81       id: generate_activity_id(
82         &AnnounceType::Announce,
83         &context.settings().get_protocol_and_hostname(),
84       )?,
85     })
86   }
87
88   #[tracing::instrument(skip_all)]
89   pub async fn send(
90     object: RawAnnouncableActivities,
91     community: &ApubCommunity,
92     context: &Data<LemmyContext>,
93   ) -> Result<(), LemmyError> {
94     let announce = AnnounceActivity::new(object.clone(), community, context)?;
95     let inboxes = community.get_follower_inboxes(context).await?;
96     send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?;
97
98     // Pleroma and Mastodon can't handle activities like Announce/Create/Page. So for
99     // compatibility, we also send Announce/Page so that they can follow Lemmy communities.
100     let object_parsed = object.try_into()?;
101     if let AnnouncableActivities::CreateOrUpdatePost(c) = object_parsed {
102       // Hack: need to convert Page into a format which can be sent as activity, which requires
103       //       adding actor field.
104       let announcable_page = RawAnnouncableActivities {
105         id: generate_activity_id(
106           AnnounceType::Announce,
107           &context.settings().get_protocol_and_hostname(),
108         )?,
109         actor: c.actor.clone().into_inner(),
110         other: serde_json::to_value(c.object)?
111           .as_object()
112           .expect("is object")
113           .clone(),
114       };
115       let announce_compat = AnnounceActivity::new(announcable_page, community, context)?;
116       send_lemmy_activity(context, announce_compat, community, inboxes, false).await?;
117     }
118     Ok(())
119   }
120 }
121
122 #[async_trait::async_trait]
123 impl ActivityHandler for AnnounceActivity {
124   type DataType = LemmyContext;
125   type Error = LemmyError;
126
127   fn id(&self) -> &Url {
128     &self.id
129   }
130
131   fn actor(&self) -> &Url {
132     self.actor.inner()
133   }
134
135   #[tracing::instrument(skip_all)]
136   async fn verify(&self, _context: &Data<Self::DataType>) -> Result<(), LemmyError> {
137     verify_is_public(&self.to, &self.cc)?;
138     Ok(())
139   }
140
141   #[tracing::instrument(skip_all)]
142   async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
143     insert_activity(&self.id, &self, false, false, context).await?;
144     let object: AnnouncableActivities = self.object.object(context).await?.try_into()?;
145     // This is only for sending, not receiving so we reject it.
146     if let AnnouncableActivities::Page(_) = object {
147       return Err(LemmyErrorType::CannotReceivePage)?;
148     }
149
150     // verify here in order to avoid fetching the object twice over http
151     object.verify(context).await?;
152     object.receive(context).await
153   }
154 }
155
156 impl Id for RawAnnouncableActivities {
157   fn object_id(&self) -> &Url {
158     ActivityHandler::id(self)
159   }
160 }
161
162 impl TryFrom<RawAnnouncableActivities> for AnnouncableActivities {
163   type Error = serde_json::error::Error;
164
165   fn try_from(value: RawAnnouncableActivities) -> Result<Self, Self::Error> {
166     let mut map = value.other.clone();
167     map.insert("id".to_string(), Value::String(value.id.to_string()));
168     map.insert("actor".to_string(), Value::String(value.actor.to_string()));
169     serde_json::from_value(Value::Object(map))
170   }
171 }
172
173 impl TryFrom<AnnouncableActivities> for RawAnnouncableActivities {
174   type Error = serde_json::error::Error;
175
176   fn try_from(value: AnnouncableActivities) -> Result<Self, Self::Error> {
177     serde_json::from_value(serde_json::to_value(value)?)
178   }
179 }