]> Untitled Git - lemmy.git/blob - src/apub/post.rs
routes.api: fix get_captcha endpoint (#1135)
[lemmy.git] / src / apub / post.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     extensions::page_extension::PageExtension,
9     fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
10     ActorType,
11     ApubLikeableType,
12     ApubObjectType,
13     FromApub,
14     PageExt,
15     ToApub,
16   },
17   DbPool,
18   LemmyContext,
19 };
20 use activitystreams::{
21   activity::{
22     kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
23     Create,
24     Delete,
25     Dislike,
26     Like,
27     Remove,
28     Undo,
29     Update,
30   },
31   object::{kind::PageType, Image, Object, Page, Tombstone},
32   prelude::*,
33   public,
34 };
35 use activitystreams_ext::Ext1;
36 use actix_web::{body::Body, web, HttpResponse};
37 use anyhow::Context;
38 use lemmy_api_structs::blocking;
39 use lemmy_db::{
40   community::Community,
41   post::{Post, PostForm},
42   user::User_,
43   Crud,
44 };
45 use lemmy_utils::{
46   location_info,
47   utils::{check_slurs, convert_datetime, remove_slurs},
48   LemmyError,
49 };
50 use serde::Deserialize;
51 use url::Url;
52
53 #[derive(Deserialize)]
54 pub struct PostQuery {
55   post_id: String,
56 }
57
58 /// Return the post json over HTTP.
59 pub async fn get_apub_post(
60   info: web::Path<PostQuery>,
61   context: web::Data<LemmyContext>,
62 ) -> Result<HttpResponse<Body>, LemmyError> {
63   let id = info.post_id.parse::<i32>()?;
64   let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??;
65
66   if !post.deleted {
67     Ok(create_apub_response(&post.to_apub(context.pool()).await?))
68   } else {
69     Ok(create_apub_tombstone_response(&post.to_tombstone()?))
70   }
71 }
72
73 #[async_trait::async_trait(?Send)]
74 impl ToApub for Post {
75   type Response = PageExt;
76
77   // Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
78   async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
79     let mut page = Page::new();
80
81     let creator_id = self.creator_id;
82     let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
83
84     let community_id = self.community_id;
85     let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
86
87     page
88       // Not needed when the Post is embedded in a collection (like for community outbox)
89       // TODO: need to set proper context defining sensitive/commentsEnabled fields
90       // https://git.asonix.dog/Aardwolf/activitystreams/issues/5
91       .set_context(activitystreams::context())
92       .set_id(self.ap_id.parse::<Url>()?)
93       // Use summary field to be consistent with mastodon content warning.
94       // https://mastodon.xyz/@Louisa/103987265222901387.json
95       .set_summary(self.name.to_owned())
96       .set_published(convert_datetime(self.published))
97       .set_to(community.actor_id)
98       .set_attributed_to(creator.actor_id);
99
100     if let Some(body) = &self.body {
101       page.set_content(body.to_owned());
102     }
103
104     // TODO: hacky code because we get self.url == Some("")
105     // https://github.com/LemmyNet/lemmy/issues/602
106     let url = self.url.as_ref().filter(|u| !u.is_empty());
107     if let Some(u) = url {
108       page.set_url(u.to_owned());
109
110       // Embeds
111       let mut page_preview = Page::new();
112       page_preview.set_url(u.to_owned());
113
114       if let Some(embed_title) = &self.embed_title {
115         page_preview.set_name(embed_title.to_owned());
116       }
117
118       if let Some(embed_description) = &self.embed_description {
119         page_preview.set_summary(embed_description.to_owned());
120       }
121
122       if let Some(embed_html) = &self.embed_html {
123         page_preview.set_content(embed_html.to_owned());
124       }
125
126       page.set_preview(page_preview.into_any_base()?);
127     }
128
129     if let Some(thumbnail_url) = &self.thumbnail_url {
130       let mut image = Image::new();
131       image.set_url(thumbnail_url.to_string());
132       page.set_image(image.into_any_base()?);
133     }
134
135     if let Some(u) = self.updated {
136       page.set_updated(convert_datetime(u));
137     }
138
139     let ext = PageExtension {
140       comments_enabled: !self.locked,
141       sensitive: self.nsfw,
142       stickied: self.stickied,
143     };
144     Ok(Ext1::new(page, ext))
145   }
146
147   fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
148     create_tombstone(self.deleted, &self.ap_id, self.updated, PageType::Page)
149   }
150 }
151
152 struct EmbedType {
153   title: Option<String>,
154   description: Option<String>,
155   html: Option<String>,
156 }
157
158 fn extract_embed_from_apub(
159   page: &Ext1<Object<PageType>, PageExtension>,
160 ) -> Result<EmbedType, LemmyError> {
161   match page.inner.preview() {
162     Some(preview) => {
163       let preview_page = Page::from_any_base(preview.one().context(location_info!())?.to_owned())?
164         .context(location_info!())?;
165       let title = preview_page
166         .name()
167         .map(|n| n.one())
168         .flatten()
169         .map(|s| s.as_xsd_string())
170         .flatten()
171         .map(|s| s.to_string());
172       let description = preview_page
173         .summary()
174         .map(|s| s.as_single_xsd_string())
175         .flatten()
176         .map(|s| s.to_string());
177       let html = preview_page
178         .content()
179         .map(|c| c.as_single_xsd_string())
180         .flatten()
181         .map(|s| s.to_string());
182       Ok(EmbedType {
183         title,
184         description,
185         html,
186       })
187     }
188     None => Ok(EmbedType {
189       title: None,
190       description: None,
191       html: None,
192     }),
193   }
194 }
195
196 #[async_trait::async_trait(?Send)]
197 impl FromApub for PostForm {
198   type ApubType = PageExt;
199
200   /// Parse an ActivityPub page received from another instance into a Lemmy post.
201   async fn from_apub(
202     page: &PageExt,
203     context: &LemmyContext,
204     expected_domain: Option<Url>,
205   ) -> Result<PostForm, LemmyError> {
206     let ext = &page.ext_one;
207     let creator_actor_id = page
208       .inner
209       .attributed_to()
210       .as_ref()
211       .context(location_info!())?
212       .as_single_xsd_any_uri()
213       .context(location_info!())?;
214
215     let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
216
217     let community_actor_id = page
218       .inner
219       .to()
220       .as_ref()
221       .context(location_info!())?
222       .as_single_xsd_any_uri()
223       .context(location_info!())?;
224
225     let community = get_or_fetch_and_upsert_community(community_actor_id, context).await?;
226
227     let thumbnail_url = match &page.inner.image() {
228       Some(any_image) => Image::from_any_base(
229         any_image
230           .to_owned()
231           .as_one()
232           .context(location_info!())?
233           .to_owned(),
234       )?
235       .context(location_info!())?
236       .url()
237       .context(location_info!())?
238       .as_single_xsd_any_uri()
239       .map(|u| u.to_string()),
240       None => None,
241     };
242
243     let embed = extract_embed_from_apub(page)?;
244
245     let name = page
246       .inner
247       .summary()
248       .as_ref()
249       .context(location_info!())?
250       .as_single_xsd_string()
251       .context(location_info!())?
252       .to_string();
253     let url = page
254       .inner
255       .url()
256       .map(|u| u.as_single_xsd_any_uri())
257       .flatten()
258       .map(|s| s.to_string());
259     let body = page
260       .inner
261       .content()
262       .as_ref()
263       .map(|c| c.as_single_xsd_string())
264       .flatten()
265       .map(|s| s.to_string());
266     check_slurs(&name)?;
267     let body_slurs_removed = body.map(|b| remove_slurs(&b));
268     Ok(PostForm {
269       name,
270       url,
271       body: body_slurs_removed,
272       creator_id: creator.id,
273       community_id: community.id,
274       removed: None,
275       locked: Some(!ext.comments_enabled),
276       published: page
277         .inner
278         .published()
279         .as_ref()
280         .map(|u| u.to_owned().naive_local()),
281       updated: page
282         .inner
283         .updated()
284         .as_ref()
285         .map(|u| u.to_owned().naive_local()),
286       deleted: None,
287       nsfw: ext.sensitive,
288       stickied: Some(ext.stickied),
289       embed_title: embed.title,
290       embed_description: embed.description,
291       embed_html: embed.html,
292       thumbnail_url,
293       ap_id: Some(check_actor_domain(page, expected_domain)?),
294       local: false,
295     })
296   }
297 }
298
299 #[async_trait::async_trait(?Send)]
300 impl ApubObjectType for Post {
301   /// Send out information about a newly created post, to the followers of the community.
302   async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
303     let page = self.to_apub(context.pool()).await?;
304
305     let community_id = self.community_id;
306     let community = blocking(context.pool(), move |conn| {
307       Community::read(conn, community_id)
308     })
309     .await??;
310
311     let mut create = Create::new(creator.actor_id.to_owned(), page.into_any_base()?);
312     create
313       .set_context(activitystreams::context())
314       .set_id(generate_activity_id(CreateType::Create)?)
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       create,
323       context,
324     )
325     .await?;
326     Ok(())
327   }
328
329   /// Send out information about an edited post, to the followers of the community.
330   async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
331     let page = self.to_apub(context.pool()).await?;
332
333     let community_id = self.community_id;
334     let community = blocking(context.pool(), move |conn| {
335       Community::read(conn, community_id)
336     })
337     .await??;
338
339     let mut update = Update::new(creator.actor_id.to_owned(), page.into_any_base()?);
340     update
341       .set_context(activitystreams::context())
342       .set_id(generate_activity_id(UpdateType::Update)?)
343       .set_to(public())
344       .set_many_ccs(vec![community.get_followers_url()?]);
345
346     send_activity_to_community(
347       creator,
348       &community,
349       vec![community.get_shared_inbox_url()?],
350       update,
351       context,
352     )
353     .await?;
354     Ok(())
355   }
356
357   async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
358     let page = self.to_apub(context.pool()).await?;
359
360     let community_id = self.community_id;
361     let community = blocking(context.pool(), move |conn| {
362       Community::read(conn, community_id)
363     })
364     .await??;
365
366     let mut delete = Delete::new(creator.actor_id.to_owned(), page.into_any_base()?);
367     delete
368       .set_context(activitystreams::context())
369       .set_id(generate_activity_id(DeleteType::Delete)?)
370       .set_to(public())
371       .set_many_ccs(vec![community.get_followers_url()?]);
372
373     send_activity_to_community(
374       creator,
375       &community,
376       vec![community.get_shared_inbox_url()?],
377       delete,
378       context,
379     )
380     .await?;
381     Ok(())
382   }
383
384   async fn send_undo_delete(
385     &self,
386     creator: &User_,
387     context: &LemmyContext,
388   ) -> Result<(), LemmyError> {
389     let page = self.to_apub(context.pool()).await?;
390
391     let community_id = self.community_id;
392     let community = blocking(context.pool(), move |conn| {
393       Community::read(conn, community_id)
394     })
395     .await??;
396
397     let mut delete = Delete::new(creator.actor_id.to_owned(), page.into_any_base()?);
398     delete
399       .set_context(activitystreams::context())
400       .set_id(generate_activity_id(DeleteType::Delete)?)
401       .set_to(public())
402       .set_many_ccs(vec![community.get_followers_url()?]);
403
404     // Undo that fake activity
405     let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
406     undo
407       .set_context(activitystreams::context())
408       .set_id(generate_activity_id(UndoType::Undo)?)
409       .set_to(public())
410       .set_many_ccs(vec![community.get_followers_url()?]);
411
412     send_activity_to_community(
413       creator,
414       &community,
415       vec![community.get_shared_inbox_url()?],
416       undo,
417       context,
418     )
419     .await?;
420     Ok(())
421   }
422
423   async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
424     let page = self.to_apub(context.pool()).await?;
425
426     let community_id = self.community_id;
427     let community = blocking(context.pool(), move |conn| {
428       Community::read(conn, community_id)
429     })
430     .await??;
431
432     let mut remove = Remove::new(mod_.actor_id.to_owned(), page.into_any_base()?);
433     remove
434       .set_context(activitystreams::context())
435       .set_id(generate_activity_id(RemoveType::Remove)?)
436       .set_to(public())
437       .set_many_ccs(vec![community.get_followers_url()?]);
438
439     send_activity_to_community(
440       mod_,
441       &community,
442       vec![community.get_shared_inbox_url()?],
443       remove,
444       context,
445     )
446     .await?;
447     Ok(())
448   }
449
450   async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
451     let page = self.to_apub(context.pool()).await?;
452
453     let community_id = self.community_id;
454     let community = blocking(context.pool(), move |conn| {
455       Community::read(conn, community_id)
456     })
457     .await??;
458
459     let mut remove = Remove::new(mod_.actor_id.to_owned(), page.into_any_base()?);
460     remove
461       .set_context(activitystreams::context())
462       .set_id(generate_activity_id(RemoveType::Remove)?)
463       .set_to(public())
464       .set_many_ccs(vec![community.get_followers_url()?]);
465
466     // Undo that fake activity
467     let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
468     undo
469       .set_context(activitystreams::context())
470       .set_id(generate_activity_id(UndoType::Undo)?)
471       .set_to(public())
472       .set_many_ccs(vec![community.get_followers_url()?]);
473
474     send_activity_to_community(
475       mod_,
476       &community,
477       vec![community.get_shared_inbox_url()?],
478       undo,
479       context,
480     )
481     .await?;
482     Ok(())
483   }
484 }
485
486 #[async_trait::async_trait(?Send)]
487 impl ApubLikeableType for Post {
488   async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
489     let page = self.to_apub(context.pool()).await?;
490
491     let community_id = self.community_id;
492     let community = blocking(context.pool(), move |conn| {
493       Community::read(conn, community_id)
494     })
495     .await??;
496
497     let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?);
498     like
499       .set_context(activitystreams::context())
500       .set_id(generate_activity_id(LikeType::Like)?)
501       .set_to(public())
502       .set_many_ccs(vec![community.get_followers_url()?]);
503
504     send_activity_to_community(
505       &creator,
506       &community,
507       vec![community.get_shared_inbox_url()?],
508       like,
509       context,
510     )
511     .await?;
512     Ok(())
513   }
514
515   async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
516     let page = self.to_apub(context.pool()).await?;
517
518     let community_id = self.community_id;
519     let community = blocking(context.pool(), move |conn| {
520       Community::read(conn, community_id)
521     })
522     .await??;
523
524     let mut dislike = Dislike::new(creator.actor_id.to_owned(), page.into_any_base()?);
525     dislike
526       .set_context(activitystreams::context())
527       .set_id(generate_activity_id(DislikeType::Dislike)?)
528       .set_to(public())
529       .set_many_ccs(vec![community.get_followers_url()?]);
530
531     send_activity_to_community(
532       &creator,
533       &community,
534       vec![community.get_shared_inbox_url()?],
535       dislike,
536       context,
537     )
538     .await?;
539     Ok(())
540   }
541
542   async fn send_undo_like(
543     &self,
544     creator: &User_,
545     context: &LemmyContext,
546   ) -> Result<(), LemmyError> {
547     let page = self.to_apub(context.pool()).await?;
548
549     let community_id = self.community_id;
550     let community = blocking(context.pool(), move |conn| {
551       Community::read(conn, community_id)
552     })
553     .await??;
554
555     let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?);
556     like
557       .set_context(activitystreams::context())
558       .set_id(generate_activity_id(LikeType::Like)?)
559       .set_to(public())
560       .set_many_ccs(vec![community.get_followers_url()?]);
561
562     // Undo that fake activity
563     let mut undo = Undo::new(creator.actor_id.to_owned(), like.into_any_base()?);
564     undo
565       .set_context(activitystreams::context())
566       .set_id(generate_activity_id(UndoType::Undo)?)
567       .set_to(public())
568       .set_many_ccs(vec![community.get_followers_url()?]);
569
570     send_activity_to_community(
571       &creator,
572       &community,
573       vec![community.get_shared_inbox_url()?],
574       undo,
575       context,
576     )
577     .await?;
578     Ok(())
579   }
580 }