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