2 extensions::{context::lemmy_context, page_extension::PageExtension},
3 fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
7 get_source_markdown_value,
8 set_content_and_source,
14 use activitystreams::{
15 object::{kind::PageType, ApObject, Image, Page, Tombstone},
18 use activitystreams_ext::Ext1;
22 post::{Post, PostForm},
27 use lemmy_structs::blocking;
30 request::fetch_iframely_and_pictrs_data,
31 utils::{check_slurs, convert_datetime, remove_slurs},
34 use lemmy_websocket::LemmyContext;
37 #[async_trait::async_trait(?Send)]
38 impl ToApub for Post {
39 type ApubType = PageExt;
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());
45 let creator_id = self.creator_id;
46 let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
48 let community_id = self.community_id;
49 let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
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);
64 if let Some(body) = &self.body {
65 set_content_and_source(&mut page, &body)?;
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)?);
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()?);
81 if let Some(u) = self.updated {
82 page.set_updated(convert_datetime(u));
85 let ext = PageExtension {
86 comments_enabled: !self.locked,
88 stickied: self.stickied,
90 Ok(Ext1::new(page, ext))
93 fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
94 create_tombstone(self.deleted, &self.ap_id, self.updated, PageType::Page)
98 #[async_trait::async_trait(?Send)]
99 impl FromApub for PostForm {
100 type ApubType = PageExt;
102 /// Converts a `PageExt` to `PostForm`.
104 /// If the post's community or creator are not known locally, these are also fetched.
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
116 .context(location_info!())?
117 .as_single_xsd_any_uri()
118 .context(location_info!())?;
120 let creator = get_or_fetch_and_upsert_user(creator_actor_id, context, request_counter).await?;
122 let community_actor_id = page
126 .context(location_info!())?
127 .as_single_xsd_any_uri()
128 .context(location_info!())?;
131 get_or_fetch_and_upsert_community(community_actor_id, context, request_counter).await?;
133 let thumbnail_url = match &page.inner.image() {
134 Some(any_image) => Image::from_any_base(
138 .context(location_info!())?
141 .context(location_info!())?
143 .context(location_info!())?
144 .as_single_xsd_any_uri()
145 .map(|u| u.to_string()),
151 .map(|u| u.as_single_xsd_any_uri())
153 .map(|s| s.to_string());
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
159 (None, None, None, thumbnail_url)
166 .context(location_info!())?
167 .as_single_xsd_string()
168 .context(location_info!())?
170 let body = get_source_markdown_value(page)?;
173 let body_slurs_removed = body.map(|b| remove_slurs(&b));
177 body: body_slurs_removed,
178 creator_id: creator.id,
179 community_id: community.id,
181 locked: Some(!ext.comments_enabled),
186 .map(|u| u.to_owned().naive_local()),
191 .map(|u| u.to_owned().naive_local()),
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)?),