]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/send/comment.rs
Running clippy --fix (#1647)
[lemmy.git] / crates / apub / src / activities / send / comment.rs
1 use crate::{
2   activities::send::generate_activity_id,
3   activity_queue::{send_comment_mentions, send_to_community},
4   extensions::context::lemmy_context,
5   fetcher::person::get_or_fetch_and_upsert_person,
6   objects::ToApub,
7   ActorType,
8   ApubLikeableType,
9   ApubObjectType,
10 };
11 use activitystreams::{
12   activity::{
13     kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
14     Create,
15     Delete,
16     Dislike,
17     Like,
18     Remove,
19     Undo,
20     Update,
21   },
22   base::AnyBase,
23   link::Mention,
24   prelude::*,
25   public,
26 };
27 use anyhow::anyhow;
28 use itertools::Itertools;
29 use lemmy_api_common::{blocking, WebFingerResponse};
30 use lemmy_db_queries::{Crud, DbPool};
31 use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
32 use lemmy_utils::{
33   request::{retry, RecvError},
34   settings::structs::Settings,
35   utils::{scrape_text_for_mentions, MentionData},
36   LemmyError,
37 };
38 use lemmy_websocket::LemmyContext;
39 use log::debug;
40 use reqwest::Client;
41 use serde_json::Error;
42 use url::Url;
43
44 #[async_trait::async_trait(?Send)]
45 impl ApubObjectType for Comment {
46   /// Send out information about a newly created comment, to the followers of the community and
47   /// mentioned persons.
48   async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
49     let note = self.to_apub(context.pool()).await?;
50
51     let post_id = self.post_id;
52     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
53
54     let community_id = post.community_id;
55     let community = blocking(context.pool(), move |conn| {
56       Community::read(conn, community_id)
57     })
58     .await??;
59
60     let maa = collect_non_local_mentions(self, &community, context).await?;
61
62     let mut create = Create::new(
63       creator.actor_id.to_owned().into_inner(),
64       note.into_any_base()?,
65     );
66     create
67       .set_many_contexts(lemmy_context()?)
68       .set_id(generate_activity_id(CreateType::Create)?)
69       .set_to(public())
70       .set_many_ccs(maa.ccs.to_owned())
71       // Set the mention tags
72       .set_many_tags(maa.get_tags()?);
73
74     send_to_community(create.clone(), creator, &community, None, context).await?;
75     send_comment_mentions(creator, maa.inboxes, create, context).await?;
76     Ok(())
77   }
78
79   /// Send out information about an edited post, to the followers of the community and mentioned
80   /// persons.
81   async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
82     let note = self.to_apub(context.pool()).await?;
83
84     let post_id = self.post_id;
85     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
86
87     let community_id = post.community_id;
88     let community = blocking(context.pool(), move |conn| {
89       Community::read(conn, community_id)
90     })
91     .await??;
92
93     let maa = collect_non_local_mentions(self, &community, context).await?;
94
95     let mut update = Update::new(
96       creator.actor_id.to_owned().into_inner(),
97       note.into_any_base()?,
98     );
99     update
100       .set_many_contexts(lemmy_context()?)
101       .set_id(generate_activity_id(UpdateType::Update)?)
102       .set_to(public())
103       .set_many_ccs(maa.ccs.to_owned())
104       // Set the mention tags
105       .set_many_tags(maa.get_tags()?);
106
107     send_to_community(update.clone(), creator, &community, None, context).await?;
108     send_comment_mentions(creator, maa.inboxes, update, context).await?;
109     Ok(())
110   }
111
112   async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
113     let post_id = self.post_id;
114     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
115
116     let community_id = post.community_id;
117     let community = blocking(context.pool(), move |conn| {
118       Community::read(conn, community_id)
119     })
120     .await??;
121
122     let mut delete = Delete::new(
123       creator.actor_id.to_owned().into_inner(),
124       self.ap_id.to_owned().into_inner(),
125     );
126     delete
127       .set_many_contexts(lemmy_context()?)
128       .set_id(generate_activity_id(DeleteType::Delete)?)
129       .set_to(public())
130       .set_many_ccs(vec![community.actor_id()]);
131
132     send_to_community(delete, creator, &community, None, context).await?;
133     Ok(())
134   }
135
136   async fn send_undo_delete(
137     &self,
138     creator: &Person,
139     context: &LemmyContext,
140   ) -> Result<(), LemmyError> {
141     let post_id = self.post_id;
142     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
143
144     let community_id = post.community_id;
145     let community = blocking(context.pool(), move |conn| {
146       Community::read(conn, community_id)
147     })
148     .await??;
149
150     // Generate a fake delete activity, with the correct object
151     let mut delete = Delete::new(
152       creator.actor_id.to_owned().into_inner(),
153       self.ap_id.to_owned().into_inner(),
154     );
155     delete
156       .set_many_contexts(lemmy_context()?)
157       .set_id(generate_activity_id(DeleteType::Delete)?)
158       .set_to(public())
159       .set_many_ccs(vec![community.actor_id()]);
160
161     // Undo that fake activity
162     let mut undo = Undo::new(
163       creator.actor_id.to_owned().into_inner(),
164       delete.into_any_base()?,
165     );
166     undo
167       .set_many_contexts(lemmy_context()?)
168       .set_id(generate_activity_id(UndoType::Undo)?)
169       .set_to(public())
170       .set_many_ccs(vec![community.actor_id()]);
171
172     send_to_community(undo, creator, &community, None, context).await?;
173     Ok(())
174   }
175
176   async fn send_remove(&self, mod_: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
177     let post_id = self.post_id;
178     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
179
180     let community_id = post.community_id;
181     let community = blocking(context.pool(), move |conn| {
182       Community::read(conn, community_id)
183     })
184     .await??;
185
186     let mut remove = Remove::new(
187       mod_.actor_id.to_owned().into_inner(),
188       self.ap_id.to_owned().into_inner(),
189     );
190     remove
191       .set_many_contexts(lemmy_context()?)
192       .set_id(generate_activity_id(RemoveType::Remove)?)
193       .set_to(public())
194       .set_many_ccs(vec![community.actor_id()]);
195
196     send_to_community(remove, mod_, &community, None, context).await?;
197     Ok(())
198   }
199
200   async fn send_undo_remove(
201     &self,
202     mod_: &Person,
203     context: &LemmyContext,
204   ) -> Result<(), LemmyError> {
205     let post_id = self.post_id;
206     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
207
208     let community_id = post.community_id;
209     let community = blocking(context.pool(), move |conn| {
210       Community::read(conn, community_id)
211     })
212     .await??;
213
214     // Generate a fake delete activity, with the correct object
215     let mut remove = Remove::new(
216       mod_.actor_id.to_owned().into_inner(),
217       self.ap_id.to_owned().into_inner(),
218     );
219     remove
220       .set_many_contexts(lemmy_context()?)
221       .set_id(generate_activity_id(RemoveType::Remove)?)
222       .set_to(public())
223       .set_many_ccs(vec![community.actor_id()]);
224
225     // Undo that fake activity
226     let mut undo = Undo::new(
227       mod_.actor_id.to_owned().into_inner(),
228       remove.into_any_base()?,
229     );
230     undo
231       .set_many_contexts(lemmy_context()?)
232       .set_id(generate_activity_id(UndoType::Undo)?)
233       .set_to(public())
234       .set_many_ccs(vec![community.actor_id()]);
235
236     send_to_community(undo, mod_, &community, None, context).await?;
237     Ok(())
238   }
239 }
240
241 #[async_trait::async_trait(?Send)]
242 impl ApubLikeableType for Comment {
243   async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
244     let post_id = self.post_id;
245     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
246
247     let community_id = post.community_id;
248     let community = blocking(context.pool(), move |conn| {
249       Community::read(conn, community_id)
250     })
251     .await??;
252
253     let mut like = Like::new(
254       creator.actor_id.to_owned().into_inner(),
255       self.ap_id.to_owned().into_inner(),
256     );
257     like
258       .set_many_contexts(lemmy_context()?)
259       .set_id(generate_activity_id(LikeType::Like)?)
260       .set_to(public())
261       .set_many_ccs(vec![community.actor_id()]);
262
263     send_to_community(like, creator, &community, None, context).await?;
264     Ok(())
265   }
266
267   async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
268     let post_id = self.post_id;
269     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
270
271     let community_id = post.community_id;
272     let community = blocking(context.pool(), move |conn| {
273       Community::read(conn, community_id)
274     })
275     .await??;
276
277     let mut dislike = Dislike::new(
278       creator.actor_id.to_owned().into_inner(),
279       self.ap_id.to_owned().into_inner(),
280     );
281     dislike
282       .set_many_contexts(lemmy_context()?)
283       .set_id(generate_activity_id(DislikeType::Dislike)?)
284       .set_to(public())
285       .set_many_ccs(vec![community.actor_id()]);
286
287     send_to_community(dislike, creator, &community, None, context).await?;
288     Ok(())
289   }
290
291   async fn send_undo_like(
292     &self,
293     creator: &Person,
294     context: &LemmyContext,
295   ) -> Result<(), LemmyError> {
296     let post_id = self.post_id;
297     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
298
299     let community_id = post.community_id;
300     let community = blocking(context.pool(), move |conn| {
301       Community::read(conn, community_id)
302     })
303     .await??;
304
305     let mut like = Like::new(
306       creator.actor_id.to_owned().into_inner(),
307       self.ap_id.to_owned().into_inner(),
308     );
309     like
310       .set_many_contexts(lemmy_context()?)
311       .set_id(generate_activity_id(DislikeType::Dislike)?)
312       .set_to(public())
313       .set_many_ccs(vec![community.actor_id()]);
314
315     // Undo that fake activity
316     let mut undo = Undo::new(
317       creator.actor_id.to_owned().into_inner(),
318       like.into_any_base()?,
319     );
320     undo
321       .set_many_contexts(lemmy_context()?)
322       .set_id(generate_activity_id(UndoType::Undo)?)
323       .set_to(public())
324       .set_many_ccs(vec![community.actor_id()]);
325
326     send_to_community(undo, creator, &community, None, context).await?;
327     Ok(())
328   }
329 }
330
331 struct MentionsAndAddresses {
332   ccs: Vec<Url>,
333   inboxes: Vec<Url>,
334   tags: Vec<Mention>,
335 }
336
337 impl MentionsAndAddresses {
338   fn get_tags(&self) -> Result<Vec<AnyBase>, Error> {
339     self
340       .tags
341       .iter()
342       .map(|t| t.to_owned().into_any_base())
343       .collect::<Result<Vec<AnyBase>, Error>>()
344   }
345 }
346
347 /// This takes a comment, and builds a list of to_addresses, inboxes,
348 /// and mention tags, so they know where to be sent to.
349 /// Addresses are the persons / addresses that go in the cc field.
350 async fn collect_non_local_mentions(
351   comment: &Comment,
352   community: &Community,
353   context: &LemmyContext,
354 ) -> Result<MentionsAndAddresses, LemmyError> {
355   let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
356   let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
357   // Note: dont include community inbox here, as we send to it separately with `send_to_community()`
358   let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
359
360   // Add the mention tag
361   let mut tags = Vec::new();
362
363   // Get the person IDs for any mentions
364   let mentions = scrape_text_for_mentions(&comment.content)
365     .into_iter()
366     // Filter only the non-local ones
367     .filter(|m| !m.is_local())
368     .collect::<Vec<MentionData>>();
369
370   for mention in &mentions {
371     // TODO should it be fetching it every time?
372     if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
373       debug!("mention actor_id: {}", actor_id);
374       addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
375
376       let mention_person = get_or_fetch_and_upsert_person(&actor_id, context, &mut 0).await?;
377       inboxes.push(mention_person.get_shared_inbox_or_inbox_url());
378
379       let mut mention_tag = Mention::new();
380       mention_tag.set_href(actor_id).set_name(mention.full_name());
381       tags.push(mention_tag);
382     }
383   }
384
385   let inboxes = inboxes.into_iter().unique().collect();
386
387   Ok(MentionsAndAddresses {
388     ccs: addressed_ccs,
389     inboxes,
390     tags,
391   })
392 }
393
394 /// Returns the apub ID of the person this comment is responding to. Meaning, in case this is a
395 /// top-level comment, the creator of the post, otherwise the creator of the parent comment.
396 async fn get_comment_parent_creator(
397   pool: &DbPool,
398   comment: &Comment,
399 ) -> Result<Person, LemmyError> {
400   let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
401     let parent_comment =
402       blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
403     parent_comment.creator_id
404   } else {
405     let parent_post_id = comment.post_id;
406     let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
407     parent_post.creator_id
408   };
409   Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??)
410 }
411
412 /// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
413 /// using webfinger.
414 async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
415   let fetch_url = format!(
416     "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
417     Settings::get().get_protocol_string(),
418     mention.domain,
419     mention.name,
420     mention.domain
421   );
422   debug!("Fetching webfinger url: {}", &fetch_url);
423
424   let response = retry(|| client.get(&fetch_url).send()).await?;
425
426   let res: WebFingerResponse = response
427     .json()
428     .await
429     .map_err(|e| RecvError(e.to_string()))?;
430
431   let link = res
432     .links
433     .iter()
434     .find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
435     .ok_or_else(|| anyhow!("No application/activity+json link found."))?;
436   link
437     .href
438     .to_owned()
439     .ok_or_else(|| anyhow!("No href found.").into())
440 }