]> Untitled Git - lemmy.git/blob - crates/apub/src/activity_lists.rs
Handle Like, Undo/Like activities from Mastodon, add tests (fixes #2378) (#2380)
[lemmy.git] / crates / apub / src / activity_lists.rs
1 use crate::{
2   activities::{community::announce::GetCommunity, verify_person_in_community},
3   objects::community::ApubCommunity,
4   protocol::{
5     activities::{
6       block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
7       community::{
8         add_mod::AddMod,
9         announce::AnnounceActivity,
10         remove_mod::RemoveMod,
11         report::Report,
12         update::UpdateCommunity,
13       },
14       create_or_update::{
15         comment::CreateOrUpdateComment,
16         post::CreateOrUpdatePost,
17         private_message::CreateOrUpdatePrivateMessage,
18       },
19       deletion::{delete::Delete, delete_user::DeleteUser, undo_delete::UndoDelete},
20       following::{
21         accept::AcceptFollowCommunity,
22         follow::FollowCommunity,
23         undo_follow::UndoFollowCommunity,
24       },
25       voting::{undo_vote::UndoVote, vote::Vote},
26     },
27     objects::page::Page,
28     Id,
29   },
30 };
31 use activitypub_federation::{
32   core::object_id::ObjectId,
33   data::Data,
34   deser::context::WithContext,
35   traits::{activity_handler, ActivityHandler},
36 };
37 use lemmy_utils::error::LemmyError;
38 use lemmy_websocket::LemmyContext;
39 use serde::{Deserialize, Serialize};
40 use url::Url;
41
42 #[derive(Debug, Deserialize, Serialize)]
43 #[serde(untagged)]
44 #[activity_handler(LemmyContext, LemmyError)]
45 pub enum SharedInboxActivities {
46   GroupInboxActivities(Box<WithContext<GroupInboxActivities>>),
47   // Note, pm activities need to be at the end, otherwise comments will end up here. We can probably
48   // avoid this problem by replacing createpm.object with our own struct, instead of NoteExt.
49   PersonInboxActivities(Box<WithContext<PersonInboxActivities>>),
50 }
51
52 #[derive(Debug, Deserialize, Serialize)]
53 #[serde(untagged)]
54 pub enum GroupInboxActivities {
55   FollowCommunity(FollowCommunity),
56   UndoFollowCommunity(UndoFollowCommunity),
57   AnnouncableActivities(Box<AnnouncableActivities>),
58   Report(Report),
59 }
60
61 #[derive(Clone, Debug, Deserialize, Serialize)]
62 #[serde(untagged)]
63 #[activity_handler(LemmyContext, LemmyError)]
64 pub enum PersonInboxActivities {
65   AcceptFollowCommunity(AcceptFollowCommunity),
66   /// Some activities can also be sent from user to user, eg a comment with mentions
67   AnnouncableActivities(AnnouncableActivities),
68   CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage),
69   Delete(Delete),
70   UndoDelete(UndoDelete),
71   AnnounceActivity(AnnounceActivity),
72 }
73
74 #[derive(Clone, Debug, Deserialize, Serialize)]
75 #[serde(untagged)]
76 #[activity_handler(LemmyContext, LemmyError)]
77 pub enum AnnouncableActivities {
78   CreateOrUpdateComment(CreateOrUpdateComment),
79   CreateOrUpdatePost(Box<CreateOrUpdatePost>),
80   Vote(Vote),
81   UndoVote(UndoVote),
82   Delete(Delete),
83   UndoDelete(UndoDelete),
84   UpdateCommunity(UpdateCommunity),
85   BlockUser(BlockUser),
86   UndoBlockUser(UndoBlockUser),
87   AddMod(AddMod),
88   RemoveMod(RemoveMod),
89   // For compatibility with Pleroma/Mastodon (send only)
90   Page(Page),
91 }
92
93 #[derive(Clone, Debug, Deserialize, Serialize)]
94 #[serde(untagged)]
95 #[activity_handler(LemmyContext, LemmyError)]
96 #[allow(clippy::enum_variant_names)]
97 pub enum SiteInboxActivities {
98   BlockUser(BlockUser),
99   UndoBlockUser(UndoBlockUser),
100   DeleteUser(DeleteUser),
101 }
102
103 #[async_trait::async_trait(?Send)]
104 impl GetCommunity for AnnouncableActivities {
105   #[tracing::instrument(skip(self, context))]
106   async fn get_community(
107     &self,
108     context: &LemmyContext,
109     request_counter: &mut i32,
110   ) -> Result<ApubCommunity, LemmyError> {
111     use AnnouncableActivities::*;
112     let community = match self {
113       CreateOrUpdateComment(a) => a.get_community(context, request_counter).await?,
114       CreateOrUpdatePost(a) => a.get_community(context, request_counter).await?,
115       Vote(a) => a.get_community(context, request_counter).await?,
116       UndoVote(a) => a.get_community(context, request_counter).await?,
117       Delete(a) => a.get_community(context, request_counter).await?,
118       UndoDelete(a) => a.get_community(context, request_counter).await?,
119       UpdateCommunity(a) => a.get_community(context, request_counter).await?,
120       BlockUser(a) => a.get_community(context, request_counter).await?,
121       UndoBlockUser(a) => a.get_community(context, request_counter).await?,
122       AddMod(a) => a.get_community(context, request_counter).await?,
123       RemoveMod(a) => a.get_community(context, request_counter).await?,
124       Page(_) => unimplemented!(),
125     };
126     Ok(community)
127   }
128 }
129
130 impl Id for AnnouncableActivities {
131   fn object_id(&self) -> &Url {
132     ActivityHandler::id(self)
133   }
134 }
135
136 // Need to implement this manually to announce matching activities
137 #[async_trait::async_trait(?Send)]
138 impl ActivityHandler for GroupInboxActivities {
139   type DataType = LemmyContext;
140   type Error = LemmyError;
141
142   fn id(&self) -> &Url {
143     match self {
144       GroupInboxActivities::FollowCommunity(a) => a.id(),
145       GroupInboxActivities::UndoFollowCommunity(a) => a.id(),
146       GroupInboxActivities::AnnouncableActivities(a) => a.object_id(),
147       GroupInboxActivities::Report(a) => a.id(),
148     }
149   }
150
151   fn actor(&self) -> &Url {
152     match self {
153       GroupInboxActivities::FollowCommunity(a) => a.actor(),
154       GroupInboxActivities::UndoFollowCommunity(a) => a.actor(),
155       GroupInboxActivities::AnnouncableActivities(a) => a.actor(),
156       GroupInboxActivities::Report(a) => a.actor(),
157     }
158   }
159
160   async fn verify(
161     &self,
162     data: &Data<Self::DataType>,
163     request_counter: &mut i32,
164   ) -> Result<(), LemmyError> {
165     match self {
166       GroupInboxActivities::FollowCommunity(a) => a.verify(data, request_counter).await,
167       GroupInboxActivities::UndoFollowCommunity(a) => a.verify(data, request_counter).await,
168       GroupInboxActivities::AnnouncableActivities(a) => a.verify(data, request_counter).await,
169       GroupInboxActivities::Report(a) => a.verify(data, request_counter).await,
170     }
171   }
172
173   async fn receive(
174     self,
175     data: &Data<Self::DataType>,
176     request_counter: &mut i32,
177   ) -> Result<(), LemmyError> {
178     match self {
179       GroupInboxActivities::FollowCommunity(a) => a.receive(data, request_counter).await,
180       GroupInboxActivities::UndoFollowCommunity(a) => a.receive(data, request_counter).await,
181       GroupInboxActivities::AnnouncableActivities(activity) => {
182         activity.clone().receive(data, request_counter).await?;
183
184         // Ignore failures in get_community(). those happen because Delete/PrivateMessage is not in a
185         // community, but looks identical to Delete/Post or Delete/Comment which are in a community.
186         let community = activity.get_community(data, &mut 0).await;
187         if let Ok(community) = community {
188           if community.local {
189             let actor_id = ObjectId::new(activity.actor().clone());
190             verify_person_in_community(&actor_id, &community, data, &mut 0).await?;
191             AnnounceActivity::send(*activity, &community, data).await?;
192           }
193         }
194         Ok(())
195       }
196       GroupInboxActivities::Report(a) => a.receive(data, request_counter).await,
197     }
198   }
199 }
200
201 #[cfg(test)]
202 mod tests {
203   use crate::{
204     activity_lists::{GroupInboxActivities, PersonInboxActivities, SiteInboxActivities},
205     protocol::tests::test_parse_lemmy_item,
206   };
207
208   #[test]
209   fn test_group_inbox() {
210     test_parse_lemmy_item::<GroupInboxActivities>("assets/lemmy/activities/following/follow.json")
211       .unwrap();
212     test_parse_lemmy_item::<GroupInboxActivities>(
213       "assets/lemmy/activities/create_or_update/create_note.json",
214     )
215     .unwrap();
216   }
217
218   #[test]
219   fn test_person_inbox() {
220     test_parse_lemmy_item::<PersonInboxActivities>("assets/lemmy/activities/following/accept.json")
221       .unwrap();
222     test_parse_lemmy_item::<PersonInboxActivities>(
223       "assets/lemmy/activities/create_or_update/create_note.json",
224     )
225     .unwrap();
226     test_parse_lemmy_item::<PersonInboxActivities>(
227       "assets/lemmy/activities/create_or_update/create_private_message.json",
228     )
229     .unwrap();
230   }
231
232   #[test]
233   fn test_site_inbox() {
234     test_parse_lemmy_item::<SiteInboxActivities>(
235       "assets/lemmy/activities/deletion/delete_user.json",
236     )
237     .unwrap();
238   }
239 }