From: Nutomic Date: Thu, 24 Mar 2022 16:33:42 +0000 (+0000) Subject: GNU social compatibility (#2100) X-Git-Url: http://these/git/?a=commitdiff_plain;h=dfb0938738a5f85834654498d0b0969698eb1dae;p=lemmy.git GNU social compatibility (#2100) * Use SourceCompat everywhere (better compat with other software) * Name field should not be mandatory in Group * also check page.cc field for community id * add gnu social tests * better to use option * update gnu social tests, marked vote as "unlisted" --- diff --git a/crates/apub/assets/gnusocial/activities/create_note.json b/crates/apub/assets/gnusocial/activities/create_note.json new file mode 100644 index 00000000..9e4221bb --- /dev/null +++ b/crates/apub/assets/gnusocial/activities/create_note.json @@ -0,0 +1,53 @@ +{ + "type": "Create", + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "gs": "https://www.gnu.org/software/social/ns#" + }, + { + "litepub": "http://litepub.social/ns#" + }, + { + "chatMessage": "litepub:chatMessage" + }, + { + "inConversation": { + "@id": "gs:inConversation", + "@type": "@id" + } + } + ], + "id": "https://instance.gnusocial.test/activity/1339", + "published": "2022-03-01T20:58:48+00:00", + "actor": "https://instance.gnusocial.test/actor/42", + "object": { + "type": "Note", + "id": "https://instance.gnusocial.test/object/note/1339", + "published": "2022-03-01T21:00:16+00:00", + "attributedTo": "https://instance.gnusocial.test/actor/42", + "content": "

yay ^^

", + "mediaType": "text/html", + "source": { + "content": "yay ^^", + "mediaType": "text/plain" + }, + "attachment": [], + "tag": [], + "inReplyTo": "https://instance.gnusocial.test/object/note/1338", + "inConversation": "https://instance.gnusocial.test/conversation/1338", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://instance.gnusocial.test/actor/42/subscribers" + ] + }, + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://instance.gnusocial.test/actor/42/subscribers" + ] +} diff --git a/crates/apub/assets/gnusocial/activities/create_page.json b/crates/apub/assets/gnusocial/activities/create_page.json new file mode 100644 index 00000000..2ce600cf --- /dev/null +++ b/crates/apub/assets/gnusocial/activities/create_page.json @@ -0,0 +1,53 @@ +{ + "type": "Create", + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "gs": "https://www.gnu.org/software/social/ns#" + }, + { + "litepub": "http://litepub.social/ns#" + }, + { + "chatMessage": "litepub:chatMessage" + }, + { + "inConversation": { + "@id": "gs:inConversation", + "@type": "@id" + } + } + ], + "id": "https://instance.gnusocial.test/activity/1338", + "published": "2022-03-17T23:30:26+00:00", + "actor": "https://instance.gnusocial.test/actor/42", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://instance.gnusocial.test/actor/21" + ], + "object": { + "type": "Page", + "id": "https://instance.gnusocial.test/object/note/1338", + "published": "2022-03-17T23:30:26+00:00", + "attributedTo": "https://instance.gnusocial.test/actor/42", + "name": "hello, world.", + "content": "

This is an interesting page.

", + "mediaType": "text/html", + "source": { + "content": "This is an interesting page.", + "mediaType": "text/markdown" + }, + "attachment": [], + "tag": [], + "inConversation": "https://instance.gnusocial.test/conversation/1338", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://instance.gnusocial.test/actor/21" + ] + } +} \ No newline at end of file diff --git a/crates/apub/assets/gnusocial/activities/like_note.json b/crates/apub/assets/gnusocial/activities/like_note.json new file mode 100644 index 00000000..09ce40ef --- /dev/null +++ b/crates/apub/assets/gnusocial/activities/like_note.json @@ -0,0 +1,16 @@ +{ + "type": "Like", + "@context": [ + "https://www.w3.org/ns/activitystreams" + ], + "id": "https://another_instance.gnusocial.test/activity/41362", + "published": "2022-03-20T17:54:15+00:00", + "actor": "https://another_instance.gnusocial.test/actor/43", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://instance.gnusocial.test/actor/42" + ], + "object": "https://instance.gnusocial.test/object/note/1337" +} diff --git a/crates/apub/assets/gnusocial/objects/group.json b/crates/apub/assets/gnusocial/objects/group.json new file mode 100644 index 00000000..9be83c2d --- /dev/null +++ b/crates/apub/assets/gnusocial/objects/group.json @@ -0,0 +1,42 @@ +{ + "type": "Group", + "streams": [], + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "gs": "https://www.gnu.org/software/social/ns#" + }, + { + "litepub": "http://litepub.social/ns#" + }, + { + "chatMessage": "litepub:chatMessage" + }, + { + "inConversation": { + "@id": "gs:inConversation", + "@type": "@id" + } + } + ], + "id": "https://instance.gnusocial.test/actor/21", + "inbox": "https://instance.gnusocial.test/actor/21/inbox.json", + "outbox": "https://instance.gnusocial.test/actor/21/outbox.json", + "following": "https://instance.gnusocial.test/actor/21/subscriptions", + "followers": "https://instance.gnusocial.test/actor/21/subscribers", + "liked": "https://instance.gnusocial.test/actor/21/favourites", + "preferredUsername": "hackers", + "publicKey": { + "id": "https://instance.gnusocial.test/actor/2#public-key", + "owner": "https://instance.gnusocial.test/actor/2", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoZyKL+GyJbTV/ilVBlzz\n8OL/UwNi3KpfV5kQwXU0pPcBbw6y2JOfWnKUT1CfiHG3ntiOFnc+wQfHZk4hRSE8\n9Xe/G5Y215xW+gqx/kjt2GOENqzSzYXdEZ5Qsx6yumZD/yb6VZK9Og0HjX2mpRs9\nbactY76w4BQVntjZ17gSkMhYcyPFZTAIe7QDkeSPk5lkXfTwtaB3YcJSbQ3+s7La\npeEgukQDkrLUIP6cxayKrgUl4fhHdpx1Yk4Bzd/1XkZCjeBca94lP1p2M12amI+Z\nOLSTuLyEiCcku8aN+Ms9plwATmIDaGvKFVk0YVtBHdIJlYXV0yIscab3bqyhsLBK\njwIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "name": "Hackers!", + "published": "2022-02-23T21:54:52+00:00", + "updated": "2022-02-23T21:55:16+00:00", + "url": "https://instance.gnusocial.test/!hackers", + "endpoints": { + "sharedInbox": "https://instance.gnusocial.test/inbox.json" + } +} diff --git a/crates/apub/assets/gnusocial/objects/note.json b/crates/apub/assets/gnusocial/objects/note.json new file mode 100644 index 00000000..08f58a93 --- /dev/null +++ b/crates/apub/assets/gnusocial/objects/note.json @@ -0,0 +1,44 @@ +{ + "type": "Note", + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "gs": "https://www.gnu.org/software/social/ns#" + }, + { + "litepub": "http://litepub.social/ns#" + }, + { + "chatMessage": "litepub:chatMessage" + }, + { + "inConversation": { + "@id": "gs:inConversation", + "@type": "@id" + } + }, + { + "@language": "en" + } + ], + "id": "https://instance.gnusocial.test/object/note/1339", + "published": "2022-03-01T21:00:16+00:00", + "attributedTo": "https://instance.gnusocial.test/actor/42", + "content": "

yay ^^

", + "mediaType": "text/html", + "source": { + "content": "yay ^^", + "mediaType": "text/plain" + }, + "attachment": [], + "tag": [], + "inReplyTo": "https://instance.gnusocial.test/object/note/1338", + "inConversation": "https://instance.gnusocial.test/conversation/1338", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://instance.gnusocial.test/actor/42/subscribers" + ] +} diff --git a/crates/apub/assets/gnusocial/objects/page.json b/crates/apub/assets/gnusocial/objects/page.json new file mode 100644 index 00000000..f66d2885 --- /dev/null +++ b/crates/apub/assets/gnusocial/objects/page.json @@ -0,0 +1,41 @@ +{ + "type": "Page", + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "gs": "https://www.gnu.org/software/social/ns#" + }, + { + "litepub": "http://litepub.social/ns#" + }, + { + "chatMessage": "litepub:chatMessage" + }, + { + "inConversation": { + "@id": "gs:inConversation", + "@type": "@id" + } + } + ], + "id": "https://instance.gnusocial.test/object/note/1338", + "published": "2022-03-17T23:30:26+00:00", + "attributedTo": "https://instance.gnusocial.test/actor/42", + "name": "hello, world.", + "content": "

This is an interesting page.

", + "mediaType": "text/html", + "source": { + "content": "This is an interesting page.", + "mediaType": "text/markdown" + }, + "attachment": [], + "tag": [], + "inConversation": "https://instance.gnusocial.test/conversation/1338", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://instance.gnusocial.test/actor/21" + ] +} diff --git a/crates/apub/assets/gnusocial/objects/person.json b/crates/apub/assets/gnusocial/objects/person.json new file mode 100644 index 00000000..c6338437 --- /dev/null +++ b/crates/apub/assets/gnusocial/objects/person.json @@ -0,0 +1,42 @@ +{ + "type": "Person", + "streams": [], + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "gs": "https://www.gnu.org/software/social/ns#" + }, + { + "litepub": "http://litepub.social/ns#" + }, + { + "chatMessage": "litepub:chatMessage" + }, + { + "inConversation": { + "@id": "gs:inConversation", + "@type": "@id" + } + } + ], + "id": "https://instance.gnusocial.test/actor/42", + "inbox": "https://instance.gnusocial.test/actor/42/inbox.json", + "outbox": "https://instance.gnusocial.test/actor/42/outbox.json", + "following": "https://instance.gnusocial.test/actor/42/subscriptions", + "followers": "https://instance.gnusocial.test/actor/42/subscribers", + "liked": "https://instance.gnusocial.test/actor/42/favourites", + "preferredUsername": "diogo", + "publicKey": { + "id": "https://instance.gnusocial.test/actor/42#public-key", + "owner": "https://instance.gnusocial.test/actor/42", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArBB+3ldwA2qC1hQTtIho\n9KYhvvMlPdydn8dA6OlyIQ3Jy57ADt2e144jDSY5RQ3esmzWm2QqsI8rAsZsAraO\nl2+855y7Fw35WH4GBc7PJ6MLAEvMk1YWeS/rttXaDzh2i4n/AXkMuxDjS1IBqw2w\nn0qTz2sdGcBJ+mop6AB9Qt2lseBc5IW040jSnfLEDDIaYgoc5m2yRsjGKItOh3BG\njGHDb6JB9FySToSMGIt0/tE5k06wfvAxtkxX5dfGeKtciBpC2MGT169iyMIOM8DN\nFhSl8mowtV1NJQ7nN692USrmNvSJjqe9ugPCDPPvwQ5A6A61Qrgpz5pav/o5Sz69\nzQIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "name": "Diogo Peralta Cordeiro", + "published": "2022-02-23T17:20:30+00:00", + "updated": "2022-02-25T02:12:48+00:00", + "url": "https://instance.gnusocial.test/@diogo", + "endpoints": { + "sharedInbox": "https://instance.gnusocial.test/inbox.json" + } +} diff --git a/crates/apub/assets/lemmy/activities/voting/dislike_page.json b/crates/apub/assets/lemmy/activities/voting/dislike_page.json index 822a9d35..6683a666 100644 --- a/crates/apub/assets/lemmy/activities/voting/dislike_page.json +++ b/crates/apub/assets/lemmy/activities/voting/dislike_page.json @@ -1,11 +1,11 @@ { "actor": "http://enterprise.lemmy.ml/u/lemmy_beta", "to": [ - "https://www.w3.org/ns/activitystreams#Public" + "http://enterprise.lemmy.ml/c/main" ], "object": "http://ds9.lemmy.ml/post/1", "cc": [ - "http://enterprise.lemmy.ml/c/main" + "https://www.w3.org/ns/activitystreams#Public" ], "type": "Dislike", "id": "http://enterprise.lemmy.ml/activities/dislike/64d40d40-a829-43a5-8247-1fb595b3ca1c" diff --git a/crates/apub/assets/lemmy/activities/voting/like_note.json b/crates/apub/assets/lemmy/activities/voting/like_note.json index 35e96906..e9fc366a 100644 --- a/crates/apub/assets/lemmy/activities/voting/like_note.json +++ b/crates/apub/assets/lemmy/activities/voting/like_note.json @@ -1,11 +1,11 @@ { "actor": "http://ds9.lemmy.ml/u/lemmy_alpha", "to": [ - "https://www.w3.org/ns/activitystreams#Public" + "http://enterprise.lemmy.ml/c/main" ], "object": "http://ds9.lemmy.ml/comment/1", "cc": [ - "http://enterprise.lemmy.ml/c/main" + "https://www.w3.org/ns/activitystreams#Public" ], "type": "Like", "id": "http://ds9.lemmy.ml/activities/like/fd61d070-7382-46a9-b2b7-6bb253732877" diff --git a/crates/apub/assets/lemmy/activities/voting/undo_dislike_page.json b/crates/apub/assets/lemmy/activities/voting/undo_dislike_page.json index 4123ebab..2234e410 100644 --- a/crates/apub/assets/lemmy/activities/voting/undo_dislike_page.json +++ b/crates/apub/assets/lemmy/activities/voting/undo_dislike_page.json @@ -1,22 +1,22 @@ { "actor": "http://enterprise.lemmy.ml/u/lemmy_beta", "to": [ - "https://www.w3.org/ns/activitystreams#Public" + "http://enterprise.lemmy.ml/c/main" ], "object": { "actor": "http://enterprise.lemmy.ml/u/lemmy_beta", "to": [ - "https://www.w3.org/ns/activitystreams#Public" + "http://enterprise.lemmy.ml/c/main" ], "object": "http://ds9.lemmy.ml/post/1", "cc": [ - "http://enterprise.lemmy.ml/c/main" + "https://www.w3.org/ns/activitystreams#Public" ], "type": "Like", "id": "http://enterprise.lemmy.ml/activities/like/2227ab2c-79e2-4fca-a1d2-1d67dacf2457" }, "cc": [ - "http://enterprise.lemmy.ml/c/main" + "https://www.w3.org/ns/activitystreams#Public" ], "type": "Undo", "id": "http://enterprise.lemmy.ml/activities/undo/6cc6fb71-39fe-49ea-9506-f0423b101e98" diff --git a/crates/apub/assets/lemmy/activities/voting/undo_like_note.json b/crates/apub/assets/lemmy/activities/voting/undo_like_note.json index 84a6efe5..e22c8fd1 100644 --- a/crates/apub/assets/lemmy/activities/voting/undo_like_note.json +++ b/crates/apub/assets/lemmy/activities/voting/undo_like_note.json @@ -1,22 +1,22 @@ { "actor": "http://ds9.lemmy.ml/u/lemmy_alpha", "to": [ - "https://www.w3.org/ns/activitystreams#Public" + "http://enterprise.lemmy.ml/c/main" ], "object": { "actor": "http://ds9.lemmy.ml/u/lemmy_alpha", "to": [ - "https://www.w3.org/ns/activitystreams#Public" + "http://enterprise.lemmy.ml/c/main" ], "object": "http://ds9.lemmy.ml/comment/1", "cc": [ - "http://enterprise.lemmy.ml/c/main" + "https://www.w3.org/ns/activitystreams#Public" ], "type": "Like", "id": "http://ds9.lemmy.ml/activities/like/efcf7ae2-dfcc-4ff4-9ce4-6adf251ff004" }, "cc": [ - "http://enterprise.lemmy.ml/c/main" + "https://www.w3.org/ns/activitystreams#Public" ], "type": "Undo", "id": "http://ds9.lemmy.ml/activities/undo/3518565c-24a7-4d9e-8e0a-f7a2f45ac618" diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs index 3606756a..16ea64e0 100644 --- a/crates/apub/src/activities/voting/undo_vote.rs +++ b/crates/apub/src/activities/voting/undo_vote.rs @@ -28,6 +28,9 @@ use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; impl UndoVote { + /// UndoVote has as:Public value in cc field, unlike other activities. This indicates to other + /// software (like GNU social, or presumably Mastodon), that the like actor should not be + /// disclosed. #[tracing::instrument(skip_all)] pub async fn send( object: &PostOrComment, @@ -49,9 +52,9 @@ impl UndoVote { )?; let undo_vote = UndoVote { actor: ObjectId::new(actor.actor_id()), - to: vec![public()], + to: vec![community.actor_id()], object, - cc: vec![community.actor_id()], + cc: vec![public()], kind: UndoType::Undo, id: id.clone(), unparsed: Default::default(), diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 11ed9b36..ce6dee28 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -28,6 +28,8 @@ use lemmy_db_schema::{ use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; +/// Vote has as:Public value in cc field, unlike other activities. This indicates to other software +/// (like GNU social, or presumably Mastodon), that the like actor should not be disclosed. impl Vote { pub(in crate::activities::voting) fn new( object: &PostOrComment, @@ -38,9 +40,9 @@ impl Vote { ) -> Result { Ok(Vote { actor: ObjectId::new(actor.actor_id()), - to: vec![public()], + to: vec![community.actor_id()], object: ObjectId::new(object.ap_id()), - cc: vec![community.actor_id()], + cc: vec![public()], kind: kind.clone(), id: generate_activity_id(kind, &context.settings().get_protocol_and_hostname())?, unparsed: Default::default(), diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 2b706aac..bc8cacea 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -2,18 +2,15 @@ use crate::{ activities::{verify_is_public, verify_person_in_community}, check_is_apub_id_valid, mentions::collect_non_local_mentions, + objects::read_from_string_or_source, protocol::{ - objects::{ - note::{Note, SourceCompat}, - tombstone::Tombstone, - }, - Source, + objects::{note::Note, tombstone::Tombstone}, + SourceCompat, }, PostOrComment, }; use activitystreams_kinds::{object::NoteType, public}; use chrono::NaiveDateTime; -use html2md::parse_html; use lemmy_api_common::blocking; use lemmy_apub_lib::{ object_id::ObjectId, @@ -121,7 +118,7 @@ impl ApubObject for ApubComment { cc: maa.ccs, content: markdown_to_html(&self.content), media_type: Some(MediaTypeHtml::Html), - source: SourceCompat::Lemmy(Source::new(self.content.clone())), + source: Some(SourceCompat::new(self.content.clone())), in_reply_to, published: Some(convert_datetime(self.published)), updated: self.updated.map(convert_datetime), @@ -180,11 +177,7 @@ impl ApubObject for ApubComment { .await?; let (post, parent_comment_id) = note.get_parents(context, request_counter).await?; - let content = if let SourceCompat::Lemmy(source) = ¬e.source { - source.content.clone() - } else { - parse_html(¬e.content) - }; + let content = read_from_string_or_source(¬e.content, ¬e.source); let content_slurs_removed = remove_slurs(&content, &context.settings().slur_regex()); let form = CommentForm { @@ -219,6 +212,7 @@ pub(crate) mod tests { protocol::tests::file_to_json_object, }; use assert_json_diff::assert_json_include; + use html2md::parse_html; use lemmy_db_schema::source::site::Site; use serial_test::serial; diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 87646e91..eb9cb218 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, - Source, + SourceCompat, }, }; use activitystreams_kinds::actor::GroupType; @@ -85,9 +85,9 @@ impl ApubObject for ApubCommunity { kind: GroupType::Group, id: ObjectId::new(self.actor_id()), preferred_username: self.name.clone(), - name: self.title.clone(), + name: Some(self.title.clone()), summary: self.description.as_ref().map(|b| markdown_to_html(b)), - source: self.description.clone().map(Source::new), + source: self.description.clone().map(SourceCompat::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 9b5ef117..9968b81e 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::{get_summary_from_string_or_source, verify_image_domain_matches}, - protocol::{objects::instance::Instance, ImageObject, Source}, + objects::{read_from_string_or_source_opt, verify_image_domain_matches}, + protocol::{objects::instance::Instance, ImageObject, SourceCompat}, }; 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(Source::new), + source: self.sidebar.clone().map(SourceCompat::new), summary: self.description.clone(), media_type: self.sidebar.as_ref().map(|_| MediaTypeHtml::Html), icon: self.icon.clone().map(ImageObject::new), @@ -121,10 +121,7 @@ impl ApubObject for ApubSite { ) -> Result { let site_form = SiteForm { name: apub.name.clone(), - sidebar: Some(get_summary_from_string_or_source( - &apub.content, - &apub.source, - )), + sidebar: Some(read_from_string_or_source_opt(&apub.content, &apub.source)), updated: apub.updated.map(|u| u.clone().naive_local()), icon: Some(apub.icon.clone().map(|i| i.url.into())), banner: Some(apub.image.clone().map(|i| i.url.into())), diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index 6acbf9ad..9f939aaa 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -1,4 +1,4 @@ -use crate::protocol::{ImageObject, Source}; +use crate::protocol::{ImageObject, SourceCompat}; use html2md::parse_html; use lemmy_apub_lib::verify::verify_domains_match; use lemmy_utils::LemmyError; @@ -11,12 +11,20 @@ pub mod person; pub mod post; pub mod private_message; -pub(crate) fn get_summary_from_string_or_source( +pub(crate) fn read_from_string_or_source(raw: &str, source: &Option) -> String { + if let Some(SourceCompat::Lemmy(s)) = source { + s.content.clone() + } else { + parse_html(raw) + } +} + +pub(crate) fn read_from_string_or_source_opt( raw: &Option, - source: &Option, + source: &Option, ) -> Option { - if let Some(source) = &source { - Some(source.content.clone()) + if let Some(SourceCompat::Lemmy(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 26edc39b..a903d5a6 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -2,8 +2,8 @@ use crate::{ check_is_apub_id_valid, generate_outbox_url, objects::{ - get_summary_from_string_or_source, instance::fetch_instance_actor_for_object, + read_from_string_or_source_opt, verify_image_domain_matches, }, protocol::{ @@ -12,7 +12,7 @@ use crate::{ Endpoints, }, ImageObject, - Source, + SourceCompat, }, }; 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(Source::new), + source: self.bio.clone().map(SourceCompat::new), icon: self.avatar.clone().map(ImageObject::new), image: self.banner.clone().map(ImageObject::new), matrix_user_id: self.matrix_user_id.clone(), @@ -134,7 +134,7 @@ impl ApubObject for ApubPerson { let slur_regex = &context.settings().slur_regex(); check_slurs(&person.preferred_username, slur_regex)?; check_slurs_opt(&person.name, slur_regex)?; - let bio = get_summary_from_string_or_source(&person.summary, &person.source); + let bio = read_from_string_or_source_opt(&person.summary, &person.source); check_slurs_opt(&bio, slur_regex)?; Ok(()) } @@ -156,7 +156,7 @@ impl ApubObject for ApubPerson { published: person.published.map(|u| u.naive_local()), updated: person.updated.map(|u| u.naive_local()), actor_id: Some(person.id.into()), - bio: Some(get_summary_from_string_or_source( + bio: Some(read_from_string_or_source_opt( &person.summary, &person.source, )), diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index c7d48a68..d5309dc1 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -1,13 +1,14 @@ use crate::{ activities::{verify_is_public, verify_person_in_community}, check_is_apub_id_valid, + objects::read_from_string_or_source_opt, protocol::{ objects::{ page::{Page, PageType}, tombstone::Tombstone, }, ImageObject, - Source, + SourceCompat, }, }; use activitystreams_kinds::public; @@ -16,7 +17,7 @@ use lemmy_api_common::blocking; use lemmy_apub_lib::{ object_id::ObjectId, traits::ApubObject, - values::{MediaTypeHtml, MediaTypeMarkdown}, + values::MediaTypeHtml, verify::verify_domains_match, }; use lemmy_db_schema::{ @@ -100,12 +101,6 @@ impl ApubObject for ApubPost { }) .await??; - let source = self.body.clone().map(|body| Source { - content: body, - media_type: MediaTypeMarkdown::Markdown, - }); - let image = self.thumbnail_url.clone().map(ImageObject::new); - let page = Page { r#type: PageType::Page, id: ObjectId::new(self.ap_id.clone()), @@ -115,9 +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, + source: self.body.clone().map(SourceCompat::new), url: self.url.clone().map(|u| u.into()), - image, + image: self.thumbnail_url.clone().map(ImageObject::new), comments_enabled: Some(!self.locked), sensitive: Some(self.nsfw), stickied: Some(self.stickied), @@ -175,10 +170,8 @@ impl ApubObject for ApubPost { .map(|u| (u.title, u.description, u.html)) .unwrap_or((None, None, None)); - let body_slurs_removed = page - .source - .as_ref() - .map(|s| remove_slurs(&s.content, &context.settings().slur_regex())); + 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()), diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 3768577c..6f445184 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -1,9 +1,11 @@ -use crate::protocol::{ - objects::chat_message::{ChatMessage, ChatMessageType}, - Source, +use crate::{ + objects::read_from_string_or_source, + protocol::{ + objects::chat_message::{ChatMessage, ChatMessageType}, + SourceCompat, + }, }; use chrono::NaiveDateTime; -use html2md::parse_html; use lemmy_api_common::blocking; use lemmy_apub_lib::{ object_id::ObjectId, @@ -88,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(Source::new(self.content.clone())), + source: Some(SourceCompat::new(self.content.clone())), published: Some(convert_datetime(self.published)), updated: self.updated.map(convert_datetime), }; @@ -131,16 +133,11 @@ impl ApubObject for ApubPrivateMessage { let recipient = note.to[0] .dereference(context, context.client(), request_counter) .await?; - let content = if let Some(source) = ¬e.source { - source.content.clone() - } else { - parse_html(¬e.content) - }; let form = PrivateMessageForm { creator_id: creator.id, recipient_id: recipient.id, - content, + content: read_from_string_or_source(¬e.content, ¬e.source), published: note.published.map(|u| u.naive_local()), updated: note.updated.map(|u| u.naive_local()), deleted: None, diff --git a/crates/apub/src/protocol/activities/mod.rs b/crates/apub/src/protocol/activities/mod.rs index 0f950931..38c9e345 100644 --- a/crates/apub/src/protocol/activities/mod.rs +++ b/crates/apub/src/protocol/activities/mod.rs @@ -21,6 +21,7 @@ mod tests { create_or_update::{comment::CreateOrUpdateComment, post::CreateOrUpdatePost}, deletion::delete::Delete, following::{follow::FollowCommunity, undo_follow::UndoFollowCommunity}, + voting::vote::Vote, }, tests::test_json, }; @@ -55,4 +56,11 @@ mod tests { fn test_parse_friendica_activities() { test_json::("assets/friendica/activities/create_note.json").unwrap(); } + + #[test] + fn test_parse_gnusocial_activities() { + test_json::("assets/gnusocial/activities/create_page.json").unwrap(); + test_json::("assets/gnusocial/activities/create_note.json").unwrap(); + test_json::("assets/gnusocial/activities/like_note.json").unwrap(); + } } diff --git a/crates/apub/src/protocol/mod.rs b/crates/apub/src/protocol/mod.rs index bb384de6..5f60e2b9 100644 --- a/crates/apub/src/protocol/mod.rs +++ b/crates/apub/src/protocol/mod.rs @@ -4,6 +4,7 @@ use url::Url; use lemmy_apub_lib::values::MediaTypeMarkdown; use lemmy_db_schema::newtypes::DbUrl; +use serde_json::Value; use std::collections::HashMap; pub mod activities; @@ -17,12 +18,20 @@ pub struct Source { pub(crate) media_type: MediaTypeMarkdown, } -impl Source { +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub(crate) enum SourceCompat { + Lemmy(Source), + Other(Value), +} + +impl SourceCompat { pub(crate) fn new(content: String) -> Self { - Source { + SourceCompat::Lemmy(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 a39f7b25..8cd37b59 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::Source, + protocol::SourceCompat, }; use chrono::{DateTime, FixedOffset}; use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml}; @@ -19,7 +19,7 @@ pub struct ChatMessage { pub(crate) content: String, pub(crate) media_type: Option, - pub(crate) source: Option, + 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 7820624d..4114d4cf 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -6,10 +6,10 @@ use crate::{ }, objects::{ community::ApubCommunity, - get_summary_from_string_or_source, + read_from_string_or_source_opt, verify_image_domain_matches, }, - protocol::{objects::Endpoints, ImageObject, Source}, + protocol::{objects::Endpoints, ImageObject, SourceCompat}, }; use activitystreams_kinds::actor::GroupType; use chrono::{DateTime, FixedOffset}; @@ -33,14 +33,14 @@ pub struct Group { pub(crate) id: ObjectId, /// username, set at account creation and usually fixed after that pub(crate) preferred_username: String, - /// displayname - pub(crate) name: String, pub(crate) inbox: Url, pub(crate) followers: Url, pub(crate) public_key: PublicKey, + /// title + pub(crate) name: Option, pub(crate) summary: Option, - pub(crate) source: Option, + pub(crate) source: Option, pub(crate) icon: Option, /// banner pub(crate) image: Option, @@ -67,17 +67,17 @@ impl Group { let slur_regex = &context.settings().slur_regex(); check_slurs(&self.preferred_username, slur_regex)?; - check_slurs(&self.name, slur_regex)?; - let description = get_summary_from_string_or_source(&self.summary, &self.source); + check_slurs_opt(&self.name, slur_regex)?; + let description = read_from_string_or_source_opt(&self.summary, &self.source); check_slurs_opt(&description, slur_regex)?; Ok(()) } pub(crate) fn into_form(self) -> CommunityForm { CommunityForm { - name: self.preferred_username, - title: self.name, - description: get_summary_from_string_or_source(&self.summary, &self.source), + name: self.preferred_username.clone(), + title: self.name.unwrap_or(self.preferred_username), + description: read_from_string_or_source_opt(&self.summary, &self.source), removed: None, published: self.published.map(|u| u.naive_local()), updated: self.updated.map(|u| u.naive_local()), diff --git a/crates/apub/src/protocol/objects/instance.rs b/crates/apub/src/protocol/objects/instance.rs index c70a780f..b507d299 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, Source}, + protocol::{ImageObject, SourceCompat}, }; use activitystreams_kinds::actor::ServiceType; use chrono::{DateTime, FixedOffset}; @@ -25,7 +25,7 @@ pub struct Instance { // sidebar pub(crate) content: Option, - pub(crate) source: Option, + pub(crate) source: Option, // short instance description pub(crate) summary: Option, pub(crate) media_type: Option, diff --git a/crates/apub/src/protocol/objects/mod.rs b/crates/apub/src/protocol/objects/mod.rs index dfe502f3..2d5f9be3 100644 --- a/crates/apub/src/protocol/objects/mod.rs +++ b/crates/apub/src/protocol/objects/mod.rs @@ -74,4 +74,12 @@ mod tests { test_json::("assets/friendica/objects/person.json").unwrap(); test_json::("assets/friendica/objects/note.json").unwrap(); } + + #[test] + fn test_parse_object_gnusocial() { + test_json::("assets/gnusocial/objects/person.json").unwrap(); + test_json::("assets/gnusocial/objects/group.json").unwrap(); + test_json::("assets/gnusocial/objects/page.json").unwrap(); + test_json::("assets/gnusocial/objects/note.json").unwrap(); + } } diff --git a/crates/apub/src/protocol/objects/note.rs b/crates/apub/src/protocol/objects/note.rs index d5c61c1f..0aac5b85 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::Source, + protocol::SourceCompat, }; use activitystreams_kinds::object::NoteType; use chrono::{DateTime, FixedOffset}; @@ -12,7 +12,6 @@ use lemmy_db_schema::{newtypes::CommentId, source::post::Post, traits::Crud}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; -use serde_json::Value; use serde_with::skip_serializing_none; use std::ops::Deref; use url::Url; @@ -33,30 +32,13 @@ pub struct Note { pub(crate) in_reply_to: ObjectId, pub(crate) media_type: Option, - #[serde(default)] - pub(crate) source: SourceCompat, + pub(crate) source: Option, pub(crate) published: Option>, pub(crate) updated: Option>, #[serde(default)] pub(crate) tag: Vec, } -/// Pleroma puts a raw string in the source, so we have to handle it here for deserialization to work -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[serde(untagged)] -pub(crate) enum SourceCompat { - Lemmy(Source), - Other(Value), - None, -} - -impl Default for SourceCompat { - fn default() -> Self { - SourceCompat::None - } -} - impl Note { pub(crate) async fn get_parents( &self, diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index f6a28231..a8a8b772 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -1,8 +1,9 @@ use crate::{ objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, - protocol::{ImageObject, Source}, + protocol::{ImageObject, SourceCompat}, }; use chrono::{DateTime, FixedOffset}; +use itertools::Itertools; use lemmy_apub_lib::{ data::Data, object_id::ObjectId, @@ -37,7 +38,7 @@ pub struct Page { pub(crate) cc: Vec, pub(crate) content: Option, pub(crate) media_type: Option, - pub(crate) source: Option, + pub(crate) source: Option, pub(crate) url: Option, pub(crate) image: Option, pub(crate) comments_enabled: Option, @@ -70,9 +71,9 @@ impl Page { context: &LemmyContext, request_counter: &mut i32, ) -> Result { - let mut to_iter = self.to.iter(); + let mut iter = self.to.iter().merge(self.cc.iter()); loop { - if let Some(cid) = to_iter.next() { + if let Some(cid) = iter.next() { let cid = ObjectId::new(cid.clone()); if let Ok(c) = cid .dereference(context, context.client(), request_counter) diff --git a/crates/apub/src/protocol/objects/person.rs b/crates/apub/src/protocol/objects/person.rs index 0ab359df..bc45d9c3 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, Source}, + protocol::{objects::Endpoints, ImageObject, SourceCompat}, }; use chrono::{DateTime, FixedOffset}; use lemmy_apub_lib::{object_id::ObjectId, signatures::PublicKey}; @@ -31,7 +31,7 @@ pub struct Person { /// displayname pub(crate) name: Option, pub(crate) summary: Option, - pub(crate) source: Option, + pub(crate) source: Option, /// user avatar pub(crate) icon: Option, /// user banner