]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/send/comment.rs
Rename `lemmy_structs` to `lemmy_api_structs`
[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::user::get_or_fetch_and_upsert_user,
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_structs::{blocking, WebFingerResponse};
30 use lemmy_db_queries::{Crud, DbPool};
31 use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post, user::User_};
32 use lemmy_utils::{
33   request::{retry, RecvError},
34   settings::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 users.
48   async fn send_create(&self, creator: &User_, 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, 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   /// users.
81   async fn send_update(&self, creator: &User_, 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, context).await?;
108     send_comment_mentions(&creator, maa.inboxes, update, context).await?;
109     Ok(())
110   }
111
112   async fn send_delete(&self, creator: &User_, 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, context).await?;
133     Ok(())
134   }
135
136   async fn send_undo_delete(
137     &self,
138     creator: &User_,
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, context).await?;
173     Ok(())
174   }
175
176   async fn send_remove(&self, mod_: &User_, 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, context).await?;
197     Ok(())
198   }
199
200   async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
201     let post_id = self.post_id;
202     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
203
204     let community_id = post.community_id;
205     let community = blocking(context.pool(), move |conn| {
206       Community::read(conn, community_id)
207     })
208     .await??;
209
210     // Generate a fake delete activity, with the correct object
211     let mut remove = Remove::new(
212       mod_.actor_id.to_owned().into_inner(),
213       self.ap_id.to_owned().into_inner(),
214     );
215     remove
216       .set_many_contexts(lemmy_context()?)
217       .set_id(generate_activity_id(RemoveType::Remove)?)
218       .set_to(public())
219       .set_many_ccs(vec![community.actor_id()]);
220
221     // Undo that fake activity
222     let mut undo = Undo::new(
223       mod_.actor_id.to_owned().into_inner(),
224       remove.into_any_base()?,
225     );
226     undo
227       .set_many_contexts(lemmy_context()?)
228       .set_id(generate_activity_id(UndoType::Undo)?)
229       .set_to(public())
230       .set_many_ccs(vec![community.actor_id()]);
231
232     send_to_community(undo, &mod_, &community, context).await?;
233     Ok(())
234   }
235 }
236
237 #[async_trait::async_trait(?Send)]
238 impl ApubLikeableType for Comment {
239   async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
240     let post_id = self.post_id;
241     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
242
243     let community_id = post.community_id;
244     let community = blocking(context.pool(), move |conn| {
245       Community::read(conn, community_id)
246     })
247     .await??;
248
249     let mut like = Like::new(
250       creator.actor_id.to_owned().into_inner(),
251       self.ap_id.to_owned().into_inner(),
252     );
253     like
254       .set_many_contexts(lemmy_context()?)
255       .set_id(generate_activity_id(LikeType::Like)?)
256       .set_to(public())
257       .set_many_ccs(vec![community.actor_id()]);
258
259     send_to_community(like, &creator, &community, context).await?;
260     Ok(())
261   }
262
263   async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
264     let post_id = self.post_id;
265     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
266
267     let community_id = post.community_id;
268     let community = blocking(context.pool(), move |conn| {
269       Community::read(conn, community_id)
270     })
271     .await??;
272
273     let mut dislike = Dislike::new(
274       creator.actor_id.to_owned().into_inner(),
275       self.ap_id.to_owned().into_inner(),
276     );
277     dislike
278       .set_many_contexts(lemmy_context()?)
279       .set_id(generate_activity_id(DislikeType::Dislike)?)
280       .set_to(public())
281       .set_many_ccs(vec![community.actor_id()]);
282
283     send_to_community(dislike, &creator, &community, context).await?;
284     Ok(())
285   }
286
287   async fn send_undo_like(
288     &self,
289     creator: &User_,
290     context: &LemmyContext,
291   ) -> Result<(), LemmyError> {
292     let post_id = self.post_id;
293     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
294
295     let community_id = post.community_id;
296     let community = blocking(context.pool(), move |conn| {
297       Community::read(conn, community_id)
298     })
299     .await??;
300
301     let mut like = Like::new(
302       creator.actor_id.to_owned().into_inner(),
303       self.ap_id.to_owned().into_inner(),
304     );
305     like
306       .set_many_contexts(lemmy_context()?)
307       .set_id(generate_activity_id(DislikeType::Dislike)?)
308       .set_to(public())
309       .set_many_ccs(vec![community.actor_id()]);
310
311     // Undo that fake activity
312     let mut undo = Undo::new(
313       creator.actor_id.to_owned().into_inner(),
314       like.into_any_base()?,
315     );
316     undo
317       .set_many_contexts(lemmy_context()?)
318       .set_id(generate_activity_id(UndoType::Undo)?)
319       .set_to(public())
320       .set_many_ccs(vec![community.actor_id()]);
321
322     send_to_community(undo, &creator, &community, context).await?;
323     Ok(())
324   }
325 }
326
327 struct MentionsAndAddresses {
328   ccs: Vec<Url>,
329   inboxes: Vec<Url>,
330   tags: Vec<Mention>,
331 }
332
333 impl MentionsAndAddresses {
334   fn get_tags(&self) -> Result<Vec<AnyBase>, Error> {
335     self
336       .tags
337       .iter()
338       .map(|t| t.to_owned().into_any_base())
339       .collect::<Result<Vec<AnyBase>, Error>>()
340   }
341 }
342
343 /// This takes a comment, and builds a list of to_addresses, inboxes,
344 /// and mention tags, so they know where to be sent to.
345 /// Addresses are the users / addresses that go in the cc field.
346 async fn collect_non_local_mentions(
347   comment: &Comment,
348   community: &Community,
349   context: &LemmyContext,
350 ) -> Result<MentionsAndAddresses, LemmyError> {
351   let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
352   let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
353   // Note: dont include community inbox here, as we send to it separately with `send_to_community()`
354   let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
355
356   // Add the mention tag
357   let mut tags = Vec::new();
358
359   // Get the user IDs for any mentions
360   let mentions = scrape_text_for_mentions(&comment.content)
361     .into_iter()
362     // Filter only the non-local ones
363     .filter(|m| !m.is_local())
364     .collect::<Vec<MentionData>>();
365
366   for mention in &mentions {
367     // TODO should it be fetching it every time?
368     if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
369       debug!("mention actor_id: {}", actor_id);
370       addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
371
372       let mention_user = get_or_fetch_and_upsert_user(&actor_id, context, &mut 0).await?;
373       inboxes.push(mention_user.get_shared_inbox_or_inbox_url());
374
375       let mut mention_tag = Mention::new();
376       mention_tag.set_href(actor_id).set_name(mention.full_name());
377       tags.push(mention_tag);
378     }
379   }
380
381   let inboxes = inboxes.into_iter().unique().collect();
382
383   Ok(MentionsAndAddresses {
384     ccs: addressed_ccs,
385     inboxes,
386     tags,
387   })
388 }
389
390 /// Returns the apub ID of the user this comment is responding to. Meaning, in case this is a
391 /// top-level comment, the creator of the post, otherwise the creator of the parent comment.
392 async fn get_comment_parent_creator(pool: &DbPool, comment: &Comment) -> Result<User_, LemmyError> {
393   let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
394     let parent_comment =
395       blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
396     parent_comment.creator_id
397   } else {
398     let parent_post_id = comment.post_id;
399     let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
400     parent_post.creator_id
401   };
402   Ok(blocking(pool, move |conn| User_::read(conn, parent_creator_id)).await??)
403 }
404
405 /// Turns a user id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
406 /// using webfinger.
407 async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
408   let fetch_url = format!(
409     "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
410     Settings::get().get_protocol_string(),
411     mention.domain,
412     mention.name,
413     mention.domain
414   );
415   debug!("Fetching webfinger url: {}", &fetch_url);
416
417   let response = retry(|| client.get(&fetch_url).send()).await?;
418
419   let res: WebFingerResponse = response
420     .json()
421     .await
422     .map_err(|e| RecvError(e.to_string()))?;
423
424   let link = res
425     .links
426     .iter()
427     .find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
428     .ok_or_else(|| anyhow!("No application/activity+json link found."))?;
429   link
430     .href
431     .to_owned()
432     .ok_or_else(|| anyhow!("No href found.").into())
433 }