]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/create_or_update/post.rs
Remove chatserver (#2919)
[lemmy.git] / crates / apub / src / activities / create_or_update / post.rs
1 use crate::{
2   activities::{
3     check_community_deleted_or_removed,
4     community::send_activity_in_community,
5     generate_activity_id,
6     verify_is_public,
7     verify_mod_action,
8     verify_person_in_community,
9   },
10   activity_lists::AnnouncableActivities,
11   insert_activity,
12   objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
13   protocol::{
14     activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType},
15     InCommunity,
16   },
17   SendActivity,
18 };
19 use activitypub_federation::{
20   config::Data,
21   kinds::public,
22   protocol::verification::{verify_domains_match, verify_urls_match},
23   traits::{ActivityHandler, Actor, Object},
24 };
25 use lemmy_api_common::{
26   context::LemmyContext,
27   post::{CreatePost, EditPost, PostResponse},
28 };
29 use lemmy_db_schema::{
30   newtypes::PersonId,
31   source::{
32     community::Community,
33     person::Person,
34     post::{Post, PostLike, PostLikeForm},
35   },
36   traits::{Crud, Likeable},
37 };
38 use lemmy_utils::error::LemmyError;
39 use url::Url;
40
41 #[async_trait::async_trait]
42 impl SendActivity for CreatePost {
43   type Response = PostResponse;
44
45   async fn send_activity(
46     _request: &Self,
47     response: &Self::Response,
48     context: &Data<LemmyContext>,
49   ) -> Result<(), LemmyError> {
50     CreateOrUpdatePage::send(
51       &response.post_view.post,
52       response.post_view.creator.id,
53       CreateOrUpdateType::Create,
54       context,
55     )
56     .await
57   }
58 }
59
60 #[async_trait::async_trait]
61 impl SendActivity for EditPost {
62   type Response = PostResponse;
63
64   async fn send_activity(
65     _request: &Self,
66     response: &Self::Response,
67     context: &Data<LemmyContext>,
68   ) -> Result<(), LemmyError> {
69     CreateOrUpdatePage::send(
70       &response.post_view.post,
71       response.post_view.creator.id,
72       CreateOrUpdateType::Update,
73       context,
74     )
75     .await
76   }
77 }
78
79 impl CreateOrUpdatePage {
80   pub(crate) async fn new(
81     post: ApubPost,
82     actor: &ApubPerson,
83     community: &ApubCommunity,
84     kind: CreateOrUpdateType,
85     context: &Data<LemmyContext>,
86   ) -> Result<CreateOrUpdatePage, LemmyError> {
87     let id = generate_activity_id(
88       kind.clone(),
89       &context.settings().get_protocol_and_hostname(),
90     )?;
91     Ok(CreateOrUpdatePage {
92       actor: actor.id().into(),
93       to: vec![public()],
94       object: post.into_json(context).await?,
95       cc: vec![community.id()],
96       kind,
97       id: id.clone(),
98       audience: Some(community.id().into()),
99     })
100   }
101
102   #[tracing::instrument(skip_all)]
103   pub(crate) async fn send(
104     post: &Post,
105     person_id: PersonId,
106     kind: CreateOrUpdateType,
107     context: &Data<LemmyContext>,
108   ) -> Result<(), LemmyError> {
109     let post = ApubPost(post.clone());
110     let community_id = post.community_id;
111     let person: ApubPerson = Person::read(context.pool(), person_id).await?.into();
112     let community: ApubCommunity = Community::read(context.pool(), community_id).await?.into();
113
114     let create_or_update =
115       CreateOrUpdatePage::new(post, &person, &community, kind, context).await?;
116     let is_mod_action = create_or_update.object.is_mod_action(context).await?;
117     let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update);
118     send_activity_in_community(
119       activity,
120       &person,
121       &community,
122       vec![],
123       is_mod_action,
124       context,
125     )
126     .await?;
127     Ok(())
128   }
129 }
130
131 #[async_trait::async_trait]
132 impl ActivityHandler for CreateOrUpdatePage {
133   type DataType = LemmyContext;
134   type Error = LemmyError;
135
136   fn id(&self) -> &Url {
137     &self.id
138   }
139
140   fn actor(&self) -> &Url {
141     self.actor.inner()
142   }
143
144   #[tracing::instrument(skip_all)]
145   async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
146     verify_is_public(&self.to, &self.cc)?;
147     let community = self.community(context).await?;
148     verify_person_in_community(&self.actor, &community, context).await?;
149     check_community_deleted_or_removed(&community)?;
150
151     match self.kind {
152       CreateOrUpdateType::Create => {
153         verify_domains_match(self.actor.inner(), self.object.id.inner())?;
154         verify_urls_match(self.actor.inner(), self.object.creator()?.inner())?;
155         // Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
156         // However, when fetching a remote post we generate a new create activity with the current
157         // locked/stickied value, so this check may fail. So only check if its a local community,
158         // because then we will definitely receive all create and update activities separately.
159         let is_featured_or_locked =
160           self.object.stickied == Some(true) || self.object.comments_enabled == Some(false);
161         if community.local && is_featured_or_locked {
162           return Err(LemmyError::from_message(
163             "New post cannot be stickied or locked",
164           ));
165         }
166       }
167       CreateOrUpdateType::Update => {
168         let is_mod_action = self.object.is_mod_action(context).await?;
169         if is_mod_action {
170           verify_mod_action(&self.actor, self.object.id.inner(), community.id, context).await?;
171         } else {
172           verify_domains_match(self.actor.inner(), self.object.id.inner())?;
173           verify_urls_match(self.actor.inner(), self.object.creator()?.inner())?;
174         }
175       }
176     }
177     ApubPost::verify(&self.object, self.actor.inner(), context).await?;
178     Ok(())
179   }
180
181   #[tracing::instrument(skip_all)]
182   async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
183     insert_activity(&self.id, &self, false, false, context).await?;
184     let post = ApubPost::from_json(self.object, context).await?;
185
186     // author likes their own post by default
187     let like_form = PostLikeForm {
188       post_id: post.id,
189       person_id: post.creator_id,
190       score: 1,
191     };
192     PostLike::like(context.pool(), &like_form).await?;
193     Ok(())
194   }
195 }