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<sourcecompat>

* update gnu social tests, marked vote as "unlisted"
This commit is contained in:
Nutomic 2022-03-24 16:33:42 +00:00 committed by GitHub
parent 1e9f609cdb
commit dfb0938738
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 409 additions and 116 deletions

View file

@ -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": "<p>yay ^^</p>",
"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"
]
}

View file

@ -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": "<p>This is an interesting page.</p>",
"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"
]
}
}

View file

@ -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"
}

View file

@ -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"
}
}

View file

@ -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": "<p>yay ^^</p>",
"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"
]
}

View file

@ -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": "<p>This is an interesting page.</p>",
"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"
]
}

View file

@ -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"
}
}

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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(),

View file

@ -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<Vote, LemmyError> {
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(),

View file

@ -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) = &note.source {
source.content.clone()
} else {
parse_html(&note.content)
};
let content = read_from_string_or_source(&note.content, &note.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;

View file

@ -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),

View file

@ -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<Self, LemmyError> {
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())),

View file

@ -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<SourceCompat>) -> 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<String>,
source: &Option<Source>,
source: &Option<SourceCompat>,
) -> Option<String> {
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))
}

View file

@ -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,
)),

View file

@ -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()),

View file

@ -1,9 +1,11 @@
use crate::protocol::{
use crate::{
objects::read_from_string_or_source,
protocol::{
objects::chat_message::{ChatMessage, ChatMessageType},
Source,
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) = &note.source {
source.content.clone()
} else {
parse_html(&note.content)
};
let form = PrivateMessageForm {
creator_id: creator.id,
recipient_id: recipient.id,
content,
content: read_from_string_or_source(&note.content, &note.source),
published: note.published.map(|u| u.naive_local()),
updated: note.updated.map(|u| u.naive_local()),
deleted: None,

View file

@ -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::<CreateOrUpdateComment>("assets/friendica/activities/create_note.json").unwrap();
}
#[test]
fn test_parse_gnusocial_activities() {
test_json::<CreateOrUpdatePost>("assets/gnusocial/activities/create_page.json").unwrap();
test_json::<CreateOrUpdateComment>("assets/gnusocial/activities/create_note.json").unwrap();
test_json::<Vote>("assets/gnusocial/activities/like_note.json").unwrap();
}
}

View file

@ -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,
}
})
}
}

View file

@ -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<MediaTypeHtml>,
pub(crate) source: Option<Source>,
pub(crate) source: Option<SourceCompat>,
pub(crate) published: Option<DateTime<FixedOffset>>,
pub(crate) updated: Option<DateTime<FixedOffset>>,
}

View file

@ -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<ApubCommunity>,
/// 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<String>,
pub(crate) summary: Option<String>,
pub(crate) source: Option<Source>,
pub(crate) source: Option<SourceCompat>,
pub(crate) icon: Option<ImageObject>,
/// banner
pub(crate) image: Option<ImageObject>,
@ -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()),

View file

@ -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<String>,
pub(crate) source: Option<Source>,
pub(crate) source: Option<SourceCompat>,
// short instance description
pub(crate) summary: Option<String>,
pub(crate) media_type: Option<MediaTypeHtml>,

View file

@ -74,4 +74,12 @@ mod tests {
test_json::<Person>("assets/friendica/objects/person.json").unwrap();
test_json::<Note>("assets/friendica/objects/note.json").unwrap();
}
#[test]
fn test_parse_object_gnusocial() {
test_json::<Person>("assets/gnusocial/objects/person.json").unwrap();
test_json::<Group>("assets/gnusocial/objects/group.json").unwrap();
test_json::<Page>("assets/gnusocial/objects/page.json").unwrap();
test_json::<Note>("assets/gnusocial/objects/note.json").unwrap();
}
}

View file

@ -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<PostOrComment>,
pub(crate) media_type: Option<MediaTypeHtml>,
#[serde(default)]
pub(crate) source: SourceCompat,
pub(crate) source: Option<SourceCompat>,
pub(crate) published: Option<DateTime<FixedOffset>>,
pub(crate) updated: Option<DateTime<FixedOffset>>,
#[serde(default)]
pub(crate) tag: Vec<Mention>,
}
/// 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,

View file

@ -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<Url>,
pub(crate) content: Option<String>,
pub(crate) media_type: Option<MediaTypeHtml>,
pub(crate) source: Option<Source>,
pub(crate) source: Option<SourceCompat>,
pub(crate) url: Option<Url>,
pub(crate) image: Option<ImageObject>,
pub(crate) comments_enabled: Option<bool>,
@ -70,9 +71,9 @@ impl Page {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
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)

View file

@ -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<String>,
pub(crate) summary: Option<String>,
pub(crate) source: Option<Source>,
pub(crate) source: Option<SourceCompat>,
/// user avatar
pub(crate) icon: Option<ImageObject>,
/// user banner