diff --git a/Cargo.lock b/Cargo.lock index 4b1f9423a..e2efe8d68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "activitystreams-kinds" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a762e3441050b51cd0695c1413735cd04195950f50dba8f5da5d7201628fcfc" +checksum = "6d014a4fb8828870b7b46bee6257b9a89d06188ae8d435381ba94f14c8c697d8" dependencies = [ "serde", "url", diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml index 0d08612da..5521b3a40 100644 --- a/crates/apub/Cargo.toml +++ b/crates/apub/Cargo.toml @@ -21,7 +21,7 @@ lemmy_db_views_actor = { version = "=0.16.2", path = "../db_views_actor" } lemmy_api_common = { version = "=0.16.2", path = "../api_common" } lemmy_websocket = { version = "=0.16.2", path = "../websocket" } diesel = "1.4.8" -activitystreams-kinds = "0.2.0" +activitystreams-kinds = "0.2.1" bcrypt = "0.12.1" chrono = { version = "0.4.19", features = ["serde"] } serde_json = { version = "1.0.79", features = ["preserve_order"] } diff --git a/crates/apub/assets/lemmy/activities/create_or_update/create_page.json b/crates/apub/assets/lemmy/activities/create_or_update/create_page.json index b223120b0..37718b234 100644 --- a/crates/apub/assets/lemmy/activities/create_or_update/create_page.json +++ b/crates/apub/assets/lemmy/activities/create_or_update/create_page.json @@ -19,6 +19,12 @@ "mediaType": "text/markdown" }, "url": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg", + "attachment": [ + { + "type": "Link", + "href": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg" + } + ], "commentsEnabled": true, "sensitive": false, "stickied": false, diff --git a/crates/apub/assets/lemmy/activities/create_or_update/update_page.json b/crates/apub/assets/lemmy/activities/create_or_update/update_page.json index beadfa0d1..7cde6cdd9 100644 --- a/crates/apub/assets/lemmy/activities/create_or_update/update_page.json +++ b/crates/apub/assets/lemmy/activities/create_or_update/update_page.json @@ -19,6 +19,12 @@ "mediaType": "text/markdown" }, "url": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg", + "attachment": [ + { + "type": "Link", + "href": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg" + } + ], "commentsEnabled": true, "sensitive": false, "stickied": false, diff --git a/crates/apub/assets/lemmy/objects/page.json b/crates/apub/assets/lemmy/objects/page.json index 36cac596f..b90ee549a 100644 --- a/crates/apub/assets/lemmy/objects/page.json +++ b/crates/apub/assets/lemmy/objects/page.json @@ -14,6 +14,12 @@ "mediaType": "text/markdown" }, "url": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png", + "attachment": [ + { + "type": "Link", + "href": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png" + } + ], "image": { "type": "Image", "url": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png" diff --git a/crates/apub/src/activities/create_or_update/post.rs b/crates/apub/src/activities/create_or_update/post.rs index 242637768..9fd12b37e 100644 --- a/crates/apub/src/activities/create_or_update/post.rs +++ b/crates/apub/src/activities/create_or_update/post.rs @@ -63,7 +63,7 @@ impl CreateOrUpdatePost { let create_or_update = CreateOrUpdatePost::new(post, actor, &community, kind, context).await?; let id = create_or_update.id.clone(); - let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update); + let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update)); send_activity_in_community(activity, &id, actor, &community, vec![], context).await } } diff --git a/crates/apub/src/activity_lists.rs b/crates/apub/src/activity_lists.rs index a24ac8e48..362d29afb 100644 --- a/crates/apub/src/activity_lists.rs +++ b/crates/apub/src/activity_lists.rs @@ -70,7 +70,7 @@ pub enum PersonInboxActivities { #[activity_handler(LemmyContext)] pub enum AnnouncableActivities { CreateOrUpdateComment(CreateOrUpdateComment), - CreateOrUpdatePost(CreateOrUpdatePost), + CreateOrUpdatePost(Box), Vote(Vote), UndoVote(UndoVote), Delete(Delete), diff --git a/crates/apub/src/fetcher/post_or_comment.rs b/crates/apub/src/fetcher/post_or_comment.rs index f03a113ec..eaebeca6b 100644 --- a/crates/apub/src/fetcher/post_or_comment.rs +++ b/crates/apub/src/fetcher/post_or_comment.rs @@ -18,7 +18,7 @@ pub enum PostOrComment { #[derive(Deserialize)] #[serde(untagged)] pub enum PageOrNote { - Page(Page), + Page(Box), Note(Note), } @@ -85,7 +85,7 @@ impl ApubObject for PostOrComment { ) -> Result { Ok(match apub { PageOrNote::Page(p) => PostOrComment::Post(Box::new( - ApubPost::from_apub(p, context, request_counter).await?, + ApubPost::from_apub(*p, context, request_counter).await?, )), PageOrNote::Note(n) => PostOrComment::Comment(Box::new( ApubComment::from_apub(n, context, request_counter).await?, diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 7a66e8aa2..80116a8f8 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -130,6 +130,18 @@ where }) } +pub(crate) fn deserialize_skip_error<'de, T, D>(deserializer: D) -> Result +where + T: Deserialize<'de> + Default, + D: Deserializer<'de>, +{ + let result = Deserialize::deserialize(deserializer); + Ok(match result { + Ok(o) => o, + Err(_) => Default::default(), + }) +} + pub enum EndpointType { Community, Person, diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index c622c14dd..23a645f71 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -5,7 +5,7 @@ use crate::{ objects::read_from_string_or_source, protocol::{ objects::{note::Note, tombstone::Tombstone}, - SourceCompat, + Source, }, PostOrComment, }; @@ -118,7 +118,7 @@ impl ApubObject for ApubComment { cc: maa.ccs, content: markdown_to_html(&self.content), media_type: Some(MediaTypeHtml::Html), - source: Some(SourceCompat::new(self.content.clone())), + source: Some(Source::new(self.content.clone())), in_reply_to, published: Some(convert_datetime(self.published)), updated: self.updated.map(convert_datetime), diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index bbb239cb5..342a4080e 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -7,7 +7,7 @@ use crate::{ protocol::{ objects::{group::Group, tombstone::Tombstone, Endpoints}, ImageObject, - SourceCompat, + Source, }, }; use activitystreams_kinds::actor::GroupType; @@ -87,7 +87,7 @@ impl ApubObject for ApubCommunity { preferred_username: self.name.clone(), name: Some(self.title.clone()), summary: self.description.as_ref().map(|b| markdown_to_html(b)), - source: self.description.clone().map(SourceCompat::new), + source: self.description.clone().map(Source::new), icon: self.icon.clone().map(ImageObject::new), image: self.banner.clone().map(ImageObject::new), sensitive: Some(self.nsfw), diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index af75812da..fbd0bed96 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -1,7 +1,7 @@ use crate::{ check_is_apub_id_valid, objects::{read_from_string_or_source_opt, verify_image_domain_matches}, - protocol::{objects::instance::Instance, ImageObject, SourceCompat}, + protocol::{objects::instance::Instance, ImageObject, Source}, }; use activitystreams_kinds::actor::ServiceType; use chrono::NaiveDateTime; @@ -77,7 +77,7 @@ impl ApubObject for ApubSite { id: ObjectId::new(self.actor_id()), name: self.name.clone(), content: self.sidebar.as_ref().map(|d| markdown_to_html(d)), - source: self.sidebar.clone().map(SourceCompat::new), + source: self.sidebar.clone().map(Source::new), summary: self.description.clone(), media_type: self.sidebar.as_ref().map(|_| MediaTypeHtml::Html), icon: self.icon.clone().map(ImageObject::new), diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index b387564a7..78013b0cc 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -1,4 +1,4 @@ -use crate::protocol::{ImageObject, SourceCompat}; +use crate::protocol::{ImageObject, Source}; use html2md::parse_html; use lemmy_apub_lib::verify::verify_domains_match; use lemmy_utils::LemmyError; @@ -11,8 +11,8 @@ pub mod person; pub mod post; pub mod private_message; -pub(crate) fn read_from_string_or_source(raw: &str, source: &Option) -> String { - if let Some(SourceCompat::Lemmy(s)) = source { +pub(crate) fn read_from_string_or_source(raw: &str, source: &Option) -> String { + if let Some(s) = source { s.content.clone() } else { parse_html(raw) @@ -21,9 +21,9 @@ pub(crate) fn read_from_string_or_source(raw: &str, source: &Option, - source: &Option, + source: &Option, ) -> Option { - if let Some(SourceCompat::Lemmy(s2)) = source { + if let Some(s2) = source { Some(s2.content.clone()) } else { raw.as_ref().map(|s| parse_html(s)) diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index a6133ed47..039dbbb13 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -12,7 +12,7 @@ use crate::{ Endpoints, }, ImageObject, - SourceCompat, + Source, }, }; use chrono::NaiveDateTime; @@ -99,7 +99,7 @@ impl ApubObject for ApubPerson { preferred_username: self.name.clone(), name: self.display_name.clone(), summary: self.bio.as_ref().map(|b| markdown_to_html(b)), - source: self.bio.clone().map(SourceCompat::new), + source: self.bio.clone().map(Source::new), icon: self.avatar.clone().map(ImageObject::new), image: self.banner.clone().map(ImageObject::new), matrix_user_id: self.matrix_user_id.clone(), diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index dbea0349c..b5fbe9424 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -4,11 +4,11 @@ use crate::{ objects::read_from_string_or_source_opt, protocol::{ objects::{ - page::{Page, PageType}, + page::{Attachment, Page, PageType}, tombstone::Tombstone, }, ImageObject, - SourceCompat, + Source, }, }; use activitystreams_kinds::public; @@ -110,8 +110,9 @@ impl ApubObject for ApubPost { name: self.name.clone(), content: self.body.as_ref().map(|b| markdown_to_html(b)), media_type: Some(MediaTypeHtml::Html), - source: self.body.clone().map(SourceCompat::new), + source: self.body.clone().map(Source::new), url: self.url.clone().map(|u| u.into()), + attachment: self.url.clone().map(Attachment::new).into_iter().collect(), image: self.thumbnail_url.clone().map(ImageObject::new), comments_enabled: Some(!self.locked), sensitive: Some(self.nsfw), @@ -160,8 +161,13 @@ impl ApubObject for ApubPost { .await?; let community = page.extract_community(context, request_counter).await?; + let url = if let Some(attachment) = page.attachment.first() { + Some(attachment.href.clone()) + } else { + page.url + }; let thumbnail_url: Option = page.image.map(|i| i.url); - let (metadata_res, pictrs_thumbnail) = if let Some(url) = &page.url { + let (metadata_res, pictrs_thumbnail) = if let Some(url) = &url { fetch_site_data(context.client(), &context.settings(), Some(url)).await } else { (None, thumbnail_url) @@ -173,8 +179,8 @@ impl ApubObject for ApubPost { let body_slurs_removed = read_from_string_or_source_opt(&page.content, &page.source) .map(|s| remove_slurs(&s, &context.settings().slur_regex())); let form = PostForm { - name: page.name, - url: page.url.map(|u| u.into()), + name: page.name.clone(), + url: url.map(Into::into), body: body_slurs_removed, creator_id: creator.id, community_id: community.id, diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 04974b802..560059865 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -2,7 +2,7 @@ use crate::{ objects::read_from_string_or_source, protocol::{ objects::chat_message::{ChatMessage, ChatMessageType}, - SourceCompat, + Source, }, }; use chrono::NaiveDateTime; @@ -90,7 +90,7 @@ impl ApubObject for ApubPrivateMessage { to: [ObjectId::new(recipient.actor_id)], content: markdown_to_html(&self.content), media_type: Some(MediaTypeHtml::Html), - source: Some(SourceCompat::new(self.content.clone())), + source: Some(Source::new(self.content.clone())), published: Some(convert_datetime(self.published)), updated: self.updated.map(convert_datetime), }; diff --git a/crates/apub/src/protocol/mod.rs b/crates/apub/src/protocol/mod.rs index 5f60e2b91..ea56cda1f 100644 --- a/crates/apub/src/protocol/mod.rs +++ b/crates/apub/src/protocol/mod.rs @@ -1,11 +1,9 @@ use activitystreams_kinds::object::ImageType; -use serde::{Deserialize, Serialize}; -use url::Url; - use lemmy_apub_lib::values::MediaTypeMarkdown; use lemmy_db_schema::newtypes::DbUrl; -use serde_json::Value; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use url::Url; pub mod activities; pub(crate) mod collections; @@ -18,20 +16,12 @@ pub struct Source { pub(crate) media_type: MediaTypeMarkdown, } -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[serde(untagged)] -pub(crate) enum SourceCompat { - Lemmy(Source), - Other(Value), -} - -impl SourceCompat { +impl Source { pub(crate) fn new(content: String) -> Self { - SourceCompat::Lemmy(Source { + Source { content, media_type: MediaTypeMarkdown::Markdown, - }) + } } } diff --git a/crates/apub/src/protocol/objects/chat_message.rs b/crates/apub/src/protocol/objects/chat_message.rs index 8cd37b59b..163bff3a4 100644 --- a/crates/apub/src/protocol/objects/chat_message.rs +++ b/crates/apub/src/protocol/objects/chat_message.rs @@ -1,6 +1,6 @@ use crate::{ objects::{person::ApubPerson, private_message::ApubPrivateMessage}, - protocol::SourceCompat, + protocol::Source, }; use chrono::{DateTime, FixedOffset}; use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml}; @@ -19,7 +19,9 @@ pub struct ChatMessage { pub(crate) content: String, pub(crate) media_type: Option, - pub(crate) source: Option, + #[serde(default)] + #[serde(deserialize_with = "crate::deserialize_skip_error")] + pub(crate) source: Option, pub(crate) published: Option>, pub(crate) updated: Option>, } diff --git a/crates/apub/src/protocol/objects/group.rs b/crates/apub/src/protocol/objects/group.rs index 4114d4cfe..410c9e87f 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -9,7 +9,7 @@ use crate::{ read_from_string_or_source_opt, verify_image_domain_matches, }, - protocol::{objects::Endpoints, ImageObject, SourceCompat}, + protocol::{objects::Endpoints, ImageObject, Source}, }; use activitystreams_kinds::actor::GroupType; use chrono::{DateTime, FixedOffset}; @@ -40,7 +40,9 @@ pub struct Group { /// title pub(crate) name: Option, pub(crate) summary: Option, - pub(crate) source: Option, + #[serde(default)] + #[serde(deserialize_with = "crate::deserialize_skip_error")] + pub(crate) source: Option, pub(crate) icon: Option, /// banner pub(crate) image: Option, diff --git a/crates/apub/src/protocol/objects/instance.rs b/crates/apub/src/protocol/objects/instance.rs index b507d2991..ee3a0a96e 100644 --- a/crates/apub/src/protocol/objects/instance.rs +++ b/crates/apub/src/protocol/objects/instance.rs @@ -1,6 +1,6 @@ use crate::{ objects::instance::ApubSite, - protocol::{ImageObject, SourceCompat}, + protocol::{ImageObject, Source}, }; use activitystreams_kinds::actor::ServiceType; use chrono::{DateTime, FixedOffset}; @@ -25,7 +25,9 @@ pub struct Instance { // sidebar pub(crate) content: Option, - pub(crate) source: Option, + #[serde(default)] + #[serde(deserialize_with = "crate::deserialize_skip_error")] + pub(crate) source: Option, // short instance description pub(crate) summary: Option, pub(crate) media_type: Option, diff --git a/crates/apub/src/protocol/objects/note.rs b/crates/apub/src/protocol/objects/note.rs index 0aac5b854..dc7b99b86 100644 --- a/crates/apub/src/protocol/objects/note.rs +++ b/crates/apub/src/protocol/objects/note.rs @@ -2,7 +2,7 @@ use crate::{ fetcher::post_or_comment::PostOrComment, mentions::Mention, objects::{comment::ApubComment, person::ApubPerson, post::ApubPost}, - protocol::SourceCompat, + protocol::Source, }; use activitystreams_kinds::object::NoteType; use chrono::{DateTime, FixedOffset}; @@ -32,7 +32,9 @@ pub struct Note { pub(crate) in_reply_to: ObjectId, pub(crate) media_type: Option, - pub(crate) source: Option, + #[serde(default)] + #[serde(deserialize_with = "crate::deserialize_skip_error")] + pub(crate) source: Option, pub(crate) published: Option>, pub(crate) updated: Option>, #[serde(default)] diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index 58b39226d..987a7d750 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -1,7 +1,8 @@ use crate::{ objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, - protocol::{ImageObject, SourceCompat}, + protocol::{ImageObject, Source}, }; +use activitystreams_kinds::link::LinkType; use chrono::{DateTime, FixedOffset}; use itertools::Itertools; use lemmy_apub_lib::{ @@ -10,6 +11,7 @@ use lemmy_apub_lib::{ traits::{ActivityHandler, ApubObject}, values::MediaTypeHtml, }; +use lemmy_db_schema::newtypes::DbUrl; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; @@ -39,8 +41,15 @@ pub struct Page { pub(crate) cc: Vec, pub(crate) content: Option, pub(crate) media_type: Option, - pub(crate) source: Option, + #[serde(default)] + #[serde(deserialize_with = "crate::deserialize_skip_error")] + pub(crate) source: Option, + /// deprecated, use attachment field pub(crate) url: Option, + /// most software uses array type for attachment field, so we do the same. nevertheless, we only + /// use the first item + #[serde(default)] + pub(crate) attachment: Vec, pub(crate) image: Option, pub(crate) comments_enabled: Option, pub(crate) sensitive: Option, @@ -49,6 +58,13 @@ pub struct Page { pub(crate) updated: Option>, } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Attachment { + pub(crate) href: Url, + pub(crate) r#type: LinkType, +} + impl Page { /// Only mods can change the post's stickied/locked status. So if either of these is changed from /// the current value, it is a mod action and needs to be verified as such. @@ -89,6 +105,15 @@ impl Page { } } +impl Attachment { + pub(crate) fn new(url: DbUrl) -> Attachment { + Attachment { + href: url.into(), + r#type: Default::default(), + } + } +} + // Used for community outbox, so that it can be compatible with Pleroma/Mastodon. #[async_trait::async_trait(?Send)] impl ActivityHandler for Page { diff --git a/crates/apub/src/protocol/objects/person.rs b/crates/apub/src/protocol/objects/person.rs index bc45d9c30..f69b1ad64 100644 --- a/crates/apub/src/protocol/objects/person.rs +++ b/crates/apub/src/protocol/objects/person.rs @@ -1,6 +1,6 @@ use crate::{ objects::person::ApubPerson, - protocol::{objects::Endpoints, ImageObject, SourceCompat}, + protocol::{objects::Endpoints, ImageObject, Source}, }; use chrono::{DateTime, FixedOffset}; use lemmy_apub_lib::{object_id::ObjectId, signatures::PublicKey}; @@ -31,7 +31,9 @@ pub struct Person { /// displayname pub(crate) name: Option, pub(crate) summary: Option, - pub(crate) source: Option, + #[serde(default)] + #[serde(deserialize_with = "crate::deserialize_skip_error")] + pub(crate) source: Option, /// user avatar pub(crate) icon: Option, /// user banner diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index c1f0fcbf2..9a8bbcece 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -50,6 +50,8 @@ impl Post { use crate::schema::post::dsl::*; post .filter(community_id.eq(the_community_id)) + .filter(deleted.eq(false)) + .filter(removed.eq(false)) .then_order_by(published.desc()) .then_order_by(stickied.desc()) .limit(20)