]> Untitled Git - lemmy.git/blob - crates/apub/src/inbox/receive_for_community.rs
Rename `lemmy_structs` to `lemmy_api_structs`
[lemmy.git] / crates / apub / src / inbox / receive_for_community.rs
1 use crate::{
2   activities::receive::{
3     comment::{
4       receive_create_comment,
5       receive_delete_comment,
6       receive_dislike_comment,
7       receive_like_comment,
8       receive_remove_comment,
9       receive_update_comment,
10     },
11     comment_undo::{
12       receive_undo_delete_comment,
13       receive_undo_dislike_comment,
14       receive_undo_like_comment,
15       receive_undo_remove_comment,
16     },
17     post::{
18       receive_create_post,
19       receive_delete_post,
20       receive_dislike_post,
21       receive_like_post,
22       receive_remove_post,
23       receive_update_post,
24     },
25     post_undo::{
26       receive_undo_delete_post,
27       receive_undo_dislike_post,
28       receive_undo_like_post,
29       receive_undo_remove_post,
30     },
31     receive_unhandled_activity,
32     verify_activity_domains_valid,
33   },
34   fetcher::objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
35   find_post_or_comment_by_id,
36   inbox::is_addressed_to_public,
37   PostOrComment,
38 };
39 use activitystreams::{
40   activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
41   base::AnyBase,
42   prelude::*,
43 };
44 use anyhow::Context;
45 use diesel::result::Error::NotFound;
46 use lemmy_api_structs::blocking;
47 use lemmy_db_queries::Crud;
48 use lemmy_db_schema::source::site::Site;
49 use lemmy_utils::{location_info, LemmyError};
50 use lemmy_websocket::LemmyContext;
51 use strum_macros::EnumString;
52 use url::Url;
53
54 #[derive(EnumString)]
55 enum PageOrNote {
56   Page,
57   Note,
58 }
59
60 /// This file is for post/comment activities received by the community, and for post/comment
61 ///       activities announced by the community and received by the user.
62
63 /// A post or comment being created
64 pub(in crate::inbox) async fn receive_create_for_community(
65   context: &LemmyContext,
66   activity: AnyBase,
67   expected_domain: &Url,
68   request_counter: &mut i32,
69 ) -> Result<(), LemmyError> {
70   let create = Create::from_any_base(activity)?.context(location_info!())?;
71   verify_activity_domains_valid(&create, &expected_domain, true)?;
72   is_addressed_to_public(&create)?;
73
74   let kind = create
75     .object()
76     .as_single_kind_str()
77     .and_then(|s| s.parse().ok());
78   match kind {
79     Some(PageOrNote::Page) => receive_create_post(create, context, request_counter).await,
80     Some(PageOrNote::Note) => receive_create_comment(create, context, request_counter).await,
81     _ => receive_unhandled_activity(create),
82   }
83 }
84
85 /// A post or comment being edited
86 pub(in crate::inbox) async fn receive_update_for_community(
87   context: &LemmyContext,
88   activity: AnyBase,
89   expected_domain: &Url,
90   request_counter: &mut i32,
91 ) -> Result<(), LemmyError> {
92   let update = Update::from_any_base(activity)?.context(location_info!())?;
93   verify_activity_domains_valid(&update, &expected_domain, true)?;
94   is_addressed_to_public(&update)?;
95
96   let kind = update
97     .object()
98     .as_single_kind_str()
99     .and_then(|s| s.parse().ok());
100   match kind {
101     Some(PageOrNote::Page) => receive_update_post(update, context, request_counter).await,
102     Some(PageOrNote::Note) => receive_update_comment(update, context, request_counter).await,
103     _ => receive_unhandled_activity(update),
104   }
105 }
106
107 /// A post or comment being upvoted
108 pub(in crate::inbox) async fn receive_like_for_community(
109   context: &LemmyContext,
110   activity: AnyBase,
111   expected_domain: &Url,
112   request_counter: &mut i32,
113 ) -> Result<(), LemmyError> {
114   let like = Like::from_any_base(activity)?.context(location_info!())?;
115   verify_activity_domains_valid(&like, &expected_domain, false)?;
116   is_addressed_to_public(&like)?;
117
118   let object_id = like
119     .object()
120     .as_single_xsd_any_uri()
121     .context(location_info!())?;
122   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
123     PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await,
124     PostOrComment::Comment(comment) => {
125       receive_like_comment(like, comment, context, request_counter).await
126     }
127   }
128 }
129
130 /// A post or comment being downvoted
131 pub(in crate::inbox) async fn receive_dislike_for_community(
132   context: &LemmyContext,
133   activity: AnyBase,
134   expected_domain: &Url,
135   request_counter: &mut i32,
136 ) -> Result<(), LemmyError> {
137   let enable_downvotes = blocking(context.pool(), move |conn| {
138     Site::read(conn, 1).map(|s| s.enable_downvotes)
139   })
140   .await??;
141   if !enable_downvotes {
142     return Ok(());
143   }
144
145   let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
146   verify_activity_domains_valid(&dislike, &expected_domain, false)?;
147   is_addressed_to_public(&dislike)?;
148
149   let object_id = dislike
150     .object()
151     .as_single_xsd_any_uri()
152     .context(location_info!())?;
153   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
154     PostOrComment::Post(post) => {
155       receive_dislike_post(dislike, post, context, request_counter).await
156     }
157     PostOrComment::Comment(comment) => {
158       receive_dislike_comment(dislike, comment, context, request_counter).await
159     }
160   }
161 }
162
163 /// A post or comment being deleted by its creator
164 pub(in crate::inbox) async fn receive_delete_for_community(
165   context: &LemmyContext,
166   activity: AnyBase,
167   expected_domain: &Url,
168 ) -> Result<(), LemmyError> {
169   let delete = Delete::from_any_base(activity)?.context(location_info!())?;
170   verify_activity_domains_valid(&delete, &expected_domain, true)?;
171   is_addressed_to_public(&delete)?;
172
173   let object = delete
174     .object()
175     .to_owned()
176     .single_xsd_any_uri()
177     .context(location_info!())?;
178
179   match find_post_or_comment_by_id(context, object).await {
180     Ok(PostOrComment::Post(p)) => receive_delete_post(context, p).await,
181     Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, c).await,
182     // if we dont have the object, no need to do anything
183     Err(_) => Ok(()),
184   }
185 }
186
187 /// A post or comment being removed by a mod/admin
188 pub(in crate::inbox) async fn receive_remove_for_community(
189   context: &LemmyContext,
190   activity: AnyBase,
191   expected_domain: &Url,
192 ) -> Result<(), LemmyError> {
193   let remove = Remove::from_any_base(activity)?.context(location_info!())?;
194   verify_activity_domains_valid(&remove, &expected_domain, false)?;
195   is_addressed_to_public(&remove)?;
196
197   let cc = remove
198     .cc()
199     .map(|c| c.as_many())
200     .flatten()
201     .context(location_info!())?;
202   let community_id = cc
203     .first()
204     .map(|c| c.as_xsd_any_uri())
205     .flatten()
206     .context(location_info!())?;
207
208   let object = remove
209     .object()
210     .to_owned()
211     .single_xsd_any_uri()
212     .context(location_info!())?;
213
214   // Ensure that remove activity comes from the same domain as the community
215   remove.id(community_id.domain().context(location_info!())?)?;
216
217   match find_post_or_comment_by_id(context, object).await {
218     Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, p).await,
219     Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, c).await,
220     // if we dont have the object, no need to do anything
221     Err(_) => Ok(()),
222   }
223 }
224
225 #[derive(EnumString)]
226 enum UndoableActivities {
227   Delete,
228   Remove,
229   Like,
230   Dislike,
231 }
232
233 /// A post/comment action being reverted (either a delete, remove, upvote or downvote)
234 pub(in crate::inbox) async fn receive_undo_for_community(
235   context: &LemmyContext,
236   activity: AnyBase,
237   expected_domain: &Url,
238   request_counter: &mut i32,
239 ) -> Result<(), LemmyError> {
240   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
241   verify_activity_domains_valid(&undo, &expected_domain.to_owned(), true)?;
242   is_addressed_to_public(&undo)?;
243
244   use UndoableActivities::*;
245   match undo
246     .object()
247     .as_single_kind_str()
248     .and_then(|s| s.parse().ok())
249   {
250     Some(Delete) => receive_undo_delete_for_community(context, undo, expected_domain).await,
251     Some(Remove) => receive_undo_remove_for_community(context, undo, expected_domain).await,
252     Some(Like) => {
253       receive_undo_like_for_community(context, undo, expected_domain, request_counter).await
254     }
255     Some(Dislike) => {
256       receive_undo_dislike_for_community(context, undo, expected_domain, request_counter).await
257     }
258     _ => receive_unhandled_activity(undo),
259   }
260 }
261
262 /// A post or comment deletion being reverted
263 pub(in crate::inbox) async fn receive_undo_delete_for_community(
264   context: &LemmyContext,
265   undo: Undo,
266   expected_domain: &Url,
267 ) -> Result<(), LemmyError> {
268   let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
269     .context(location_info!())?;
270   verify_activity_domains_valid(&delete, &expected_domain, true)?;
271   is_addressed_to_public(&delete)?;
272
273   let object = delete
274     .object()
275     .to_owned()
276     .single_xsd_any_uri()
277     .context(location_info!())?;
278   match find_post_or_comment_by_id(context, object).await {
279     Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, p).await,
280     Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, c).await,
281     // if we dont have the object, no need to do anything
282     Err(_) => Ok(()),
283   }
284 }
285
286 /// A post or comment removal being reverted
287 pub(in crate::inbox) async fn receive_undo_remove_for_community(
288   context: &LemmyContext,
289   undo: Undo,
290   expected_domain: &Url,
291 ) -> Result<(), LemmyError> {
292   let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
293     .context(location_info!())?;
294   verify_activity_domains_valid(&remove, &expected_domain, false)?;
295   is_addressed_to_public(&remove)?;
296
297   let object = remove
298     .object()
299     .to_owned()
300     .single_xsd_any_uri()
301     .context(location_info!())?;
302   match find_post_or_comment_by_id(context, object).await {
303     Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, p).await,
304     Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, c).await,
305     // if we dont have the object, no need to do anything
306     Err(_) => Ok(()),
307   }
308 }
309
310 /// A post or comment upvote being reverted
311 pub(in crate::inbox) async fn receive_undo_like_for_community(
312   context: &LemmyContext,
313   undo: Undo,
314   expected_domain: &Url,
315   request_counter: &mut i32,
316 ) -> Result<(), LemmyError> {
317   let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
318     .context(location_info!())?;
319   verify_activity_domains_valid(&like, &expected_domain, false)?;
320   is_addressed_to_public(&like)?;
321
322   let object_id = like
323     .object()
324     .as_single_xsd_any_uri()
325     .context(location_info!())?;
326   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
327     PostOrComment::Post(post) => {
328       receive_undo_like_post(&like, post, context, request_counter).await
329     }
330     PostOrComment::Comment(comment) => {
331       receive_undo_like_comment(&like, comment, context, request_counter).await
332     }
333   }
334 }
335
336 /// A post or comment downvote being reverted
337 pub(in crate::inbox) async fn receive_undo_dislike_for_community(
338   context: &LemmyContext,
339   undo: Undo,
340   expected_domain: &Url,
341   request_counter: &mut i32,
342 ) -> Result<(), LemmyError> {
343   let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
344     .context(location_info!())?;
345   verify_activity_domains_valid(&dislike, &expected_domain, false)?;
346   is_addressed_to_public(&dislike)?;
347
348   let object_id = dislike
349     .object()
350     .as_single_xsd_any_uri()
351     .context(location_info!())?;
352   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
353     PostOrComment::Post(post) => {
354       receive_undo_dislike_post(&dislike, post, context, request_counter).await
355     }
356     PostOrComment::Comment(comment) => {
357       receive_undo_dislike_comment(&dislike, comment, context, request_counter).await
358     }
359   }
360 }
361
362 async fn fetch_post_or_comment_by_id(
363   apub_id: &Url,
364   context: &LemmyContext,
365   request_counter: &mut i32,
366 ) -> Result<PostOrComment, LemmyError> {
367   if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
368     return Ok(PostOrComment::Post(post));
369   }
370
371   if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
372     return Ok(PostOrComment::Comment(comment));
373   }
374
375   Err(NotFound.into())
376 }