]> Untitled Git - lemmy.git/blob - server/src/apub/comment.rs
routes.api: fix get_captcha endpoint (#1135)
[lemmy.git] / server / src / apub / comment.rs
1 use crate::{
2   apub::{
3     activities::{generate_activity_id, send_activity_to_community},
4     check_actor_domain,
5     create_apub_response,
6     create_apub_tombstone_response,
7     create_tombstone,
8     fetch_webfinger_url,
9     fetcher::{
10       get_or_fetch_and_insert_comment,
11       get_or_fetch_and_insert_post,
12       get_or_fetch_and_upsert_user,
13     },
14     ActorType,
15     ApubLikeableType,
16     ApubObjectType,
17     FromApub,
18     ToApub,
19   },
20   DbPool,
21   LemmyContext,
22 };
23 use activitystreams::{
24   activity::{
25     kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
26     Create,
27     Delete,
28     Dislike,
29     Like,
30     Remove,
31     Undo,
32     Update,
33   },
34   base::AnyBase,
35   link::Mention,
36   object::{kind::NoteType, Note, Tombstone},
37   prelude::*,
38   public,
39 };
40 use actix_web::{body::Body, web, web::Path, HttpResponse};
41 use anyhow::Context;
42 use itertools::Itertools;
43 use lemmy_api_structs::blocking;
44 use lemmy_db::{
45   comment::{Comment, CommentForm},
46   community::Community,
47   post::Post,
48   user::User_,
49   Crud,
50 };
51 use lemmy_utils::{
52   location_info,
53   utils::{convert_datetime, remove_slurs, scrape_text_for_mentions, MentionData},
54   LemmyError,
55 };
56 use log::debug;
57 use serde::Deserialize;
58 use serde_json::Error;
59 use url::Url;
60
61 #[derive(Deserialize)]
62 pub struct CommentQuery {
63   comment_id: String,
64 }
65
66 /// Return the post json over HTTP.
67 pub async fn get_apub_comment(
68   info: Path<CommentQuery>,
69   context: web::Data<LemmyContext>,
70 ) -> Result<HttpResponse<Body>, LemmyError> {
71   let id = info.comment_id.parse::<i32>()?;
72   let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??;
73
74   if !comment.deleted {
75     Ok(create_apub_response(
76       &comment.to_apub(context.pool()).await?,
77     ))
78   } else {
79     Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
80   }
81 }
82
83 #[async_trait::async_trait(?Send)]
84 impl ToApub for Comment {
85   type Response = Note;
86
87   async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
88     let mut comment = Note::new();
89
90     let creator_id = self.creator_id;
91     let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
92
93     let post_id = self.post_id;
94     let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
95
96     let community_id = post.community_id;
97     let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
98
99     // Add a vector containing some important info to the "in_reply_to" field
100     // [post_ap_id, Option(parent_comment_ap_id)]
101     let mut in_reply_to_vec = vec![post.ap_id];
102
103     if let Some(parent_id) = self.parent_id {
104       let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
105
106       in_reply_to_vec.push(parent_comment.ap_id);
107     }
108
109     comment
110       // Not needed when the Post is embedded in a collection (like for community outbox)
111       .set_context(activitystreams::context())
112       .set_id(Url::parse(&self.ap_id)?)
113       .set_published(convert_datetime(self.published))
114       .set_to(community.actor_id)
115       .set_many_in_reply_tos(in_reply_to_vec)
116       .set_content(self.content.to_owned())
117       .set_attributed_to(creator.actor_id);
118
119     if let Some(u) = self.updated {
120       comment.set_updated(convert_datetime(u));
121     }
122
123     Ok(comment)
124   }
125
126   fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
127     create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
128   }
129 }
130
131 #[async_trait::async_trait(?Send)]
132 impl FromApub for CommentForm {
133   type ApubType = Note;
134
135   /// Parse an ActivityPub note received from another instance into a Lemmy comment
136   async fn from_apub(
137     note: &Note,
138     context: &LemmyContext,
139     expected_domain: Option<Url>,
140   ) -> Result<CommentForm, LemmyError> {
141     let creator_actor_id = &note
142       .attributed_to()
143       .context(location_info!())?
144       .as_single_xsd_any_uri()
145       .context(location_info!())?;
146
147     let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
148
149     let mut in_reply_tos = note
150       .in_reply_to()
151       .as_ref()
152       .context(location_info!())?
153       .as_many()
154       .context(location_info!())?
155       .iter()
156       .map(|i| i.as_xsd_any_uri().context(""));
157     let post_ap_id = in_reply_tos.next().context(location_info!())??;
158
159     // This post, or the parent comment might not yet exist on this server yet, fetch them.
160     let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
161
162     // The 2nd item, if it exists, is the parent comment apub_id
163     // For deeply nested comments, FromApub automatically gets called recursively
164     let parent_id: Option<i32> = match in_reply_tos.next() {
165       Some(parent_comment_uri) => {
166         let parent_comment_ap_id = &parent_comment_uri?;
167         let parent_comment =
168           get_or_fetch_and_insert_comment(&parent_comment_ap_id, context).await?;
169
170         Some(parent_comment.id)
171       }
172       None => None,
173     };
174     let content = note
175       .content()
176       .context(location_info!())?
177       .as_single_xsd_string()
178       .context(location_info!())?
179       .to_string();
180     let content_slurs_removed = remove_slurs(&content);
181
182     Ok(CommentForm {
183       creator_id: creator.id,
184       post_id: post.id,
185       parent_id,
186       content: content_slurs_removed,
187       removed: None,
188       read: None,
189       published: note.published().map(|u| u.to_owned().naive_local()),
190       updated: note.updated().map(|u| u.to_owned().naive_local()),
191       deleted: None,
192       ap_id: Some(check_actor_domain(note, expected_domain)?),
193       local: false,
194     })
195   }
196 }
197
198 #[async_trait::async_trait(?Send)]
199 impl ApubObjectType for Comment {
200   /// Send out information about a newly created comment, to the followers of the community.
201   async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
202     let note = self.to_apub(context.pool()).await?;
203
204     let post_id = self.post_id;
205     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
206
207     let community_id = post.community_id;
208     let community = blocking(context.pool(), move |conn| {
209       Community::read(conn, community_id)
210     })
211     .await??;
212
213     let maa = collect_non_local_mentions_and_addresses(&self.content, &community, context).await?;
214
215     let mut create = Create::new(creator.actor_id.to_owned(), note.into_any_base()?);
216     create
217       .set_context(activitystreams::context())
218       .set_id(generate_activity_id(CreateType::Create)?)
219       .set_to(public())
220       .set_many_ccs(maa.addressed_ccs.to_owned())
221       // Set the mention tags
222       .set_many_tags(maa.get_tags()?);
223
224     send_activity_to_community(&creator, &community, maa.inboxes, create, context).await?;
225     Ok(())
226   }
227
228   /// Send out information about an edited post, to the followers of the community.
229   async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
230     let note = self.to_apub(context.pool()).await?;
231
232     let post_id = self.post_id;
233     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
234
235     let community_id = post.community_id;
236     let community = blocking(context.pool(), move |conn| {
237       Community::read(conn, community_id)
238     })
239     .await??;
240
241     let maa = collect_non_local_mentions_and_addresses(&self.content, &community, context).await?;
242
243     let mut update = Update::new(creator.actor_id.to_owned(), note.into_any_base()?);
244     update
245       .set_context(activitystreams::context())
246       .set_id(generate_activity_id(UpdateType::Update)?)
247       .set_to(public())
248       .set_many_ccs(maa.addressed_ccs.to_owned())
249       // Set the mention tags
250       .set_many_tags(maa.get_tags()?);
251
252     send_activity_to_community(&creator, &community, maa.inboxes, update, context).await?;
253     Ok(())
254   }
255
256   async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
257     let note = self.to_apub(context.pool()).await?;
258
259     let post_id = self.post_id;
260     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
261
262     let community_id = post.community_id;
263     let community = blocking(context.pool(), move |conn| {
264       Community::read(conn, community_id)
265     })
266     .await??;
267
268     let mut delete = Delete::new(creator.actor_id.to_owned(), note.into_any_base()?);
269     delete
270       .set_context(activitystreams::context())
271       .set_id(generate_activity_id(DeleteType::Delete)?)
272       .set_to(public())
273       .set_many_ccs(vec![community.get_followers_url()?]);
274
275     send_activity_to_community(
276       &creator,
277       &community,
278       vec![community.get_shared_inbox_url()?],
279       delete,
280       context,
281     )
282     .await?;
283     Ok(())
284   }
285
286   async fn send_undo_delete(
287     &self,
288     creator: &User_,
289     context: &LemmyContext,
290   ) -> Result<(), LemmyError> {
291     let note = self.to_apub(context.pool()).await?;
292
293     let post_id = self.post_id;
294     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
295
296     let community_id = post.community_id;
297     let community = blocking(context.pool(), move |conn| {
298       Community::read(conn, community_id)
299     })
300     .await??;
301
302     // Generate a fake delete activity, with the correct object
303     let mut delete = Delete::new(creator.actor_id.to_owned(), note.into_any_base()?);
304     delete
305       .set_context(activitystreams::context())
306       .set_id(generate_activity_id(DeleteType::Delete)?)
307       .set_to(public())
308       .set_many_ccs(vec![community.get_followers_url()?]);
309
310     // Undo that fake activity
311     let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
312     undo
313       .set_context(activitystreams::context())
314       .set_id(generate_activity_id(UndoType::Undo)?)
315       .set_to(public())
316       .set_many_ccs(vec![community.get_followers_url()?]);
317
318     send_activity_to_community(
319       &creator,
320       &community,
321       vec![community.get_shared_inbox_url()?],
322       undo,
323       context,
324     )
325     .await?;
326     Ok(())
327   }
328
329   async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
330     let note = self.to_apub(context.pool()).await?;
331
332     let post_id = self.post_id;
333     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
334
335     let community_id = post.community_id;
336     let community = blocking(context.pool(), move |conn| {
337       Community::read(conn, community_id)
338     })
339     .await??;
340
341     let mut remove = Remove::new(mod_.actor_id.to_owned(), note.into_any_base()?);
342     remove
343       .set_context(activitystreams::context())
344       .set_id(generate_activity_id(RemoveType::Remove)?)
345       .set_to(public())
346       .set_many_ccs(vec![community.get_followers_url()?]);
347
348     send_activity_to_community(
349       &mod_,
350       &community,
351       vec![community.get_shared_inbox_url()?],
352       remove,
353       context,
354     )
355     .await?;
356     Ok(())
357   }
358
359   async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
360     let note = self.to_apub(context.pool()).await?;
361
362     let post_id = self.post_id;
363     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
364
365     let community_id = post.community_id;
366     let community = blocking(context.pool(), move |conn| {
367       Community::read(conn, community_id)
368     })
369     .await??;
370
371     // Generate a fake delete activity, with the correct object
372     let mut remove = Remove::new(mod_.actor_id.to_owned(), note.into_any_base()?);
373     remove
374       .set_context(activitystreams::context())
375       .set_id(generate_activity_id(RemoveType::Remove)?)
376       .set_to(public())
377       .set_many_ccs(vec![community.get_followers_url()?]);
378
379     // Undo that fake activity
380     let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
381     undo
382       .set_context(activitystreams::context())
383       .set_id(generate_activity_id(UndoType::Undo)?)
384       .set_to(public())
385       .set_many_ccs(vec![community.get_followers_url()?]);
386
387     send_activity_to_community(
388       &mod_,
389       &community,
390       vec![community.get_shared_inbox_url()?],
391       undo,
392       context,
393     )
394     .await?;
395     Ok(())
396   }
397 }
398
399 #[async_trait::async_trait(?Send)]
400 impl ApubLikeableType for Comment {
401   async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
402     let note = self.to_apub(context.pool()).await?;
403
404     let post_id = self.post_id;
405     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
406
407     let community_id = post.community_id;
408     let community = blocking(context.pool(), move |conn| {
409       Community::read(conn, community_id)
410     })
411     .await??;
412
413     let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?);
414     like
415       .set_context(activitystreams::context())
416       .set_id(generate_activity_id(LikeType::Like)?)
417       .set_to(public())
418       .set_many_ccs(vec![community.get_followers_url()?]);
419
420     send_activity_to_community(
421       &creator,
422       &community,
423       vec![community.get_shared_inbox_url()?],
424       like,
425       context,
426     )
427     .await?;
428     Ok(())
429   }
430
431   async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
432     let note = self.to_apub(context.pool()).await?;
433
434     let post_id = self.post_id;
435     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
436
437     let community_id = post.community_id;
438     let community = blocking(context.pool(), move |conn| {
439       Community::read(conn, community_id)
440     })
441     .await??;
442
443     let mut dislike = Dislike::new(creator.actor_id.to_owned(), note.into_any_base()?);
444     dislike
445       .set_context(activitystreams::context())
446       .set_id(generate_activity_id(DislikeType::Dislike)?)
447       .set_to(public())
448       .set_many_ccs(vec![community.get_followers_url()?]);
449
450     send_activity_to_community(
451       &creator,
452       &community,
453       vec![community.get_shared_inbox_url()?],
454       dislike,
455       context,
456     )
457     .await?;
458     Ok(())
459   }
460
461   async fn send_undo_like(
462     &self,
463     creator: &User_,
464     context: &LemmyContext,
465   ) -> Result<(), LemmyError> {
466     let note = self.to_apub(context.pool()).await?;
467
468     let post_id = self.post_id;
469     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
470
471     let community_id = post.community_id;
472     let community = blocking(context.pool(), move |conn| {
473       Community::read(conn, community_id)
474     })
475     .await??;
476
477     let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?);
478     like
479       .set_context(activitystreams::context())
480       .set_id(generate_activity_id(DislikeType::Dislike)?)
481       .set_to(public())
482       .set_many_ccs(vec![community.get_followers_url()?]);
483
484     // Undo that fake activity
485     let mut undo = Undo::new(creator.actor_id.to_owned(), like.into_any_base()?);
486     undo
487       .set_context(activitystreams::context())
488       .set_id(generate_activity_id(UndoType::Undo)?)
489       .set_to(public())
490       .set_many_ccs(vec![community.get_followers_url()?]);
491
492     send_activity_to_community(
493       &creator,
494       &community,
495       vec![community.get_shared_inbox_url()?],
496       undo,
497       context,
498     )
499     .await?;
500     Ok(())
501   }
502 }
503
504 struct MentionsAndAddresses {
505   addressed_ccs: Vec<Url>,
506   inboxes: Vec<Url>,
507   tags: Vec<Mention>,
508 }
509
510 impl MentionsAndAddresses {
511   fn get_tags(&self) -> Result<Vec<AnyBase>, Error> {
512     self
513       .tags
514       .iter()
515       .map(|t| t.to_owned().into_any_base())
516       .collect::<Result<Vec<AnyBase>, Error>>()
517   }
518 }
519
520 /// This takes a comment, and builds a list of to_addresses, inboxes,
521 /// and mention tags, so they know where to be sent to.
522 /// Addresses are the users / addresses that go in the cc field.
523 async fn collect_non_local_mentions_and_addresses(
524   content: &str,
525   community: &Community,
526   context: &LemmyContext,
527 ) -> Result<MentionsAndAddresses, LemmyError> {
528   let mut addressed_ccs = vec![community.get_followers_url()?];
529
530   // Add the mention tag
531   let mut tags = Vec::new();
532
533   // Get the inboxes for any mentions
534   let mentions = scrape_text_for_mentions(&content)
535     .into_iter()
536     // Filter only the non-local ones
537     .filter(|m| !m.is_local())
538     .collect::<Vec<MentionData>>();
539
540   let mut mention_inboxes: Vec<Url> = Vec::new();
541   for mention in &mentions {
542     // TODO should it be fetching it every time?
543     if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
544       debug!("mention actor_id: {}", actor_id);
545       addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
546
547       let mention_user = get_or_fetch_and_upsert_user(&actor_id, context).await?;
548       let shared_inbox = mention_user.get_shared_inbox_url()?;
549
550       mention_inboxes.push(shared_inbox);
551       let mut mention_tag = Mention::new();
552       mention_tag.set_href(actor_id).set_name(mention.full_name());
553       tags.push(mention_tag);
554     }
555   }
556
557   let mut inboxes = vec![community.get_shared_inbox_url()?];
558   inboxes.extend(mention_inboxes);
559   inboxes = inboxes.into_iter().unique().collect();
560
561   Ok(MentionsAndAddresses {
562     addressed_ccs,
563     inboxes,
564     tags,
565   })
566 }