]> Untitled Git - lemmy.git/blob - lemmy_apub/src/objects/post.rs
Better account deletion (fixes #730) (#143)
[lemmy.git] / lemmy_apub / src / objects / post.rs
1 use crate::{
2   extensions::{context::lemmy_context, page_extension::PageExtension},
3   fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
4   objects::{
5     check_object_domain,
6     create_tombstone,
7     get_source_markdown_value,
8     set_content_and_source,
9   },
10   FromApub,
11   PageExt,
12   ToApub,
13 };
14 use activitystreams::{
15   object::{kind::PageType, ApObject, Image, Page, Tombstone},
16   prelude::*,
17 };
18 use activitystreams_ext::Ext1;
19 use anyhow::Context;
20 use lemmy_db::{
21   community::Community,
22   post::{Post, PostForm},
23   user::User_,
24   Crud,
25   DbPool,
26 };
27 use lemmy_structs::blocking;
28 use lemmy_utils::{
29   location_info,
30   request::fetch_iframely_and_pictrs_data,
31   utils::{check_slurs, convert_datetime, remove_slurs},
32   LemmyError,
33 };
34 use lemmy_websocket::LemmyContext;
35 use url::Url;
36
37 #[async_trait::async_trait(?Send)]
38 impl ToApub for Post {
39   type ApubType = PageExt;
40
41   // Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
42   async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
43     let mut page = ApObject::new(Page::new());
44
45     let creator_id = self.creator_id;
46     let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
47
48     let community_id = self.community_id;
49     let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
50
51     page
52       // Not needed when the Post is embedded in a collection (like for community outbox)
53       // TODO: need to set proper context defining sensitive/commentsEnabled fields
54       // https://git.asonix.dog/Aardwolf/activitystreams/issues/5
55       .set_many_contexts(lemmy_context()?)
56       .set_id(self.ap_id.parse::<Url>()?)
57       // Use summary field to be consistent with mastodon content warning.
58       // https://mastodon.xyz/@Louisa/103987265222901387.json
59       .set_summary(self.name.to_owned())
60       .set_published(convert_datetime(self.published))
61       .set_to(community.actor_id)
62       .set_attributed_to(creator.actor_id);
63
64     if let Some(body) = &self.body {
65       set_content_and_source(&mut page, &body)?;
66     }
67
68     // TODO: hacky code because we get self.url == Some("")
69     // https://github.com/LemmyNet/lemmy/issues/602
70     let url = self.url.as_ref().filter(|u| !u.is_empty());
71     if let Some(u) = url {
72       page.set_url(Url::parse(u)?);
73     }
74
75     if let Some(thumbnail_url) = &self.thumbnail_url {
76       let mut image = Image::new();
77       image.set_url(Url::parse(thumbnail_url)?);
78       page.set_image(image.into_any_base()?);
79     }
80
81     if let Some(u) = self.updated {
82       page.set_updated(convert_datetime(u));
83     }
84
85     let ext = PageExtension {
86       comments_enabled: !self.locked,
87       sensitive: self.nsfw,
88       stickied: self.stickied,
89     };
90     Ok(Ext1::new(page, ext))
91   }
92
93   fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
94     create_tombstone(self.deleted, &self.ap_id, self.updated, PageType::Page)
95   }
96 }
97
98 #[async_trait::async_trait(?Send)]
99 impl FromApub for PostForm {
100   type ApubType = PageExt;
101
102   /// Converts a `PageExt` to `PostForm`.
103   ///
104   /// If the post's community or creator are not known locally, these are also fetched.
105   async fn from_apub(
106     page: &PageExt,
107     context: &LemmyContext,
108     expected_domain: Option<Url>,
109     request_counter: &mut i32,
110   ) -> Result<PostForm, LemmyError> {
111     let ext = &page.ext_one;
112     let creator_actor_id = page
113       .inner
114       .attributed_to()
115       .as_ref()
116       .context(location_info!())?
117       .as_single_xsd_any_uri()
118       .context(location_info!())?;
119
120     let creator = get_or_fetch_and_upsert_user(creator_actor_id, context, request_counter).await?;
121
122     let community_actor_id = page
123       .inner
124       .to()
125       .as_ref()
126       .context(location_info!())?
127       .as_single_xsd_any_uri()
128       .context(location_info!())?;
129
130     let community =
131       get_or_fetch_and_upsert_community(community_actor_id, context, request_counter).await?;
132
133     let thumbnail_url = match &page.inner.image() {
134       Some(any_image) => Image::from_any_base(
135         any_image
136           .to_owned()
137           .as_one()
138           .context(location_info!())?
139           .to_owned(),
140       )?
141       .context(location_info!())?
142       .url()
143       .context(location_info!())?
144       .as_single_xsd_any_uri()
145       .map(|u| u.to_string()),
146       None => None,
147     };
148     let url = page
149       .inner
150       .url()
151       .map(|u| u.as_single_xsd_any_uri())
152       .flatten()
153       .map(|s| s.to_string());
154
155     let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
156       if let Some(url) = &url {
157         fetch_iframely_and_pictrs_data(context.client(), Some(url.to_owned())).await
158       } else {
159         (None, None, None, thumbnail_url)
160       };
161
162     let name = page
163       .inner
164       .summary()
165       .as_ref()
166       .context(location_info!())?
167       .as_single_xsd_string()
168       .context(location_info!())?
169       .to_string();
170     let body = get_source_markdown_value(page)?;
171
172     check_slurs(&name)?;
173     let body_slurs_removed = body.map(|b| remove_slurs(&b));
174     Ok(PostForm {
175       name,
176       url,
177       body: body_slurs_removed,
178       creator_id: creator.id,
179       community_id: community.id,
180       removed: None,
181       locked: Some(!ext.comments_enabled),
182       published: page
183         .inner
184         .published()
185         .as_ref()
186         .map(|u| u.to_owned().naive_local()),
187       updated: page
188         .inner
189         .updated()
190         .as_ref()
191         .map(|u| u.to_owned().naive_local()),
192       deleted: None,
193       nsfw: ext.sensitive,
194       stickied: Some(ext.stickied),
195       embed_title: iframely_title,
196       embed_description: iframely_description,
197       embed_html: iframely_html,
198       thumbnail_url: pictrs_thumbnail,
199       ap_id: Some(check_object_domain(page, expected_domain)?),
200       local: false,
201     })
202   }
203 }