From e3bb43542c23577613429e1634c72151bd179715 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Sat, 12 Nov 2022 13:51:08 +0000 Subject: [PATCH] Mobilizon federation (#2544) * Mobilizon federation * Also accept document attachments from mobilizon --- .../apub/assets/mobilizon/objects/event.json | 186 ++++++++++++++++++ .../apub/assets/mobilizon/objects/group.json | 168 ++++++++++++++++ .../apub/assets/mobilizon/objects/person.json | 144 ++++++++++++++ crates/apub/src/objects/post.rs | 9 +- crates/apub/src/protocol/objects/mod.rs | 7 + crates/apub/src/protocol/objects/page.rs | 28 ++- 6 files changed, 535 insertions(+), 7 deletions(-) create mode 100644 crates/apub/assets/mobilizon/objects/event.json create mode 100644 crates/apub/assets/mobilizon/objects/group.json create mode 100644 crates/apub/assets/mobilizon/objects/person.json diff --git a/crates/apub/assets/mobilizon/objects/event.json b/crates/apub/assets/mobilizon/objects/event.json new file mode 100644 index 000000000..414ffc825 --- /dev/null +++ b/crates/apub/assets/mobilizon/objects/event.json @@ -0,0 +1,186 @@ +{ + "timezone": "Europe/London", + "isOnline": false, + "contacts": [ + "https://rendezvous.nomagic.uk/@emorrp1" + ], + "cc": [ + "https://rendezvous.nomagic.uk/@emorrp1/followers" + ], + "id": "https://rendezvous.nomagic.uk/events/b81c0531-a57c-497d-93ba-af0f8b255498", + "inLanguage": "en", + "endTime": "2022-12-11T21:00:00+00:00", + "repliesModerationOption": "allow_all", + "content": "

£6 each.

The dance style is like a posh ceilidh, with some exciting new ways to turn your partner, set patterns and learn some reels by heart. Looking forward to sharing the Hamilton House and the Inverness with those of you who can make it along to this one.

For anyone unfamiliar with ceilidhs, they're very social and energetic dances that are very accessible because everyone gets told exactly what to do by a caller and when to do it. Here's a sample video from when I learned CaledonianDancing at uni. The cover photo is by Dave Conner CC-BY-2.0.

", + "category": "SPORTS", + "actor": "https://rendezvous.nomagic.uk/@emorrp1", + "type": "Event", + "url": "https://rendezvous.nomagic.uk/events/b81c0531-a57c-497d-93ba-af0f8b255498", + "remainingAttendeeCapacity": null, + "anonymousParticipationEnabled": true, + "ical:status": "CONFIRMED", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "joinMode": "free", + "location": { + "address": { + "addressCountry": "United Kingdom", + "addressLocality": "Teignbridge", + "addressRegion": "England", + "postalCode": "EX6 7TW", + "streetAddress": "Devon Expressway", + "type": "PostalAddress" + }, + "id": "https://rendezvous.nomagic.uk/address/e4c95383-15ac-4cc7-adf6-723d74ee2ccc", + "latitude": 50.66881615, + "longitude": -3.537739788359949, + "name": "The Kenn Centre", + "type": "Place" + }, + "startTime": "2022-12-11T19:00:00+00:00", + "published": "2022-09-27T14:33:14Z", + "draft": false, + "participantCount": 0, + "uuid": "b81c0531-a57c-497d-93ba-af0f8b255498", + "maximumAttendeeCapacity": 0, + "tag": [ + { + "href": "https://rendezvous.nomagic.uk/tags/dance", + "name": "#Dance", + "type": "Hashtag" + }, + { + "href": "https://rendezvous.nomagic.uk/tags/caledonian", + "name": "#Caledonian", + "type": "Hashtag" + }, + { + "href": "https://rendezvous.nomagic.uk/tags/lesson", + "name": "#Lesson", + "type": "Hashtag" + } + ], + "updated": "2022-09-27T14:39:18Z", + "attributedTo": "https://rendezvous.nomagic.uk/@devon_caledonian_society", + "commentsEnabled": true, + "attachment": [ + { + "mediaType": "image/jpeg", + "name": "Banner", + "type": "Document", + "url": "https://rendezvous.nomagic.uk/media/cd75bf2f61b66004fe20af4797f5aa847ae1f9ea1c118f53093d6fc4e51a6045.jpg?name=devon_caledonian_society%27s%20banner.jpg" + } + ], + "name": "Caledonian scottish dance class", + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "addressRegion": "sc:addressRegion", + "timezone": { + "@id": "mz:timezone", + "@type": "sc:Text" + }, + "isOnline": { + "@id": "mz:isOnline", + "@type": "sc:Boolean" + }, + "pt": "https://joinpeertube.org/ns#", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "inLanguage": "sc:inLanguage", + "address": { + "@id": "sc:address", + "@type": "sc:PostalAddress" + }, + "discoverable": "toot:discoverable", + "repliesModerationOption": { + "@id": "mz:repliesModerationOption", + "@type": "mz:repliesModerationOptionType" + }, + "sc": "http://schema.org#", + "mz": "https://joinmobilizon.org/ns#", + "category": "sc:category", + "joinModeType": { + "@id": "mz:joinModeType", + "@type": "rdfs:Class" + }, + "Hashtag": "as:Hashtag", + "propertyID": "sc:propertyID", + "PostalAddress": "sc:PostalAddress", + "discussions": { + "@id": "mz:discussions", + "@type": "@id" + }, + "remainingAttendeeCapacity": "sc:remainingAttendeeCapacity", + "streetAddress": "sc:streetAddress", + "anonymousParticipationEnabled": { + "@id": "mz:anonymousParticipationEnabled", + "@type": "sc:Boolean" + }, + "addressLocality": "sc:addressLocality", + "joinMode": { + "@id": "mz:joinMode", + "@type": "mz:joinModeType" + }, + "location": { + "@id": "sc:location", + "@type": "sc:Place" + }, + "toot": "http://joinmastodon.org/ns#", + "participantCount": { + "@id": "mz:participantCount", + "@type": "sc:Integer" + }, + "uuid": "sc:identifier", + "maximumAttendeeCapacity": "sc:maximumAttendeeCapacity", + "participationMessage": { + "@id": "mz:participationMessage", + "@type": "sc:Text" + }, + "openness": { + "@id": "mz:openness", + "@type": "@id" + }, + "members": { + "@id": "mz:members", + "@type": "@id" + }, + "events": { + "@id": "mz:events", + "@type": "@id" + }, + "resources": { + "@id": "mz:resources", + "@type": "@id" + }, + "addressCountry": "sc:addressCountry", + "posts": { + "@id": "mz:posts", + "@type": "@id" + }, + "commentsEnabled": { + "@id": "pt:commentsEnabled", + "@type": "sc:Boolean" + }, + "value": "sc:value", + "PropertyValue": "sc:PropertyValue", + "repliesModerationOptionType": { + "@id": "mz:repliesModerationOptionType", + "@type": "rdfs:Class" + }, + "todos": { + "@id": "mz:todos", + "@type": "@id" + }, + "ical": "http://www.w3.org/2002/12/cal/ical#", + "postalCode": "sc:postalCode", + "memberCount": { + "@id": "mz:memberCount", + "@type": "sc:Integer" + }, + "@language": "und" + } + ], + "mediaType": "text/html" +} diff --git a/crates/apub/assets/mobilizon/objects/group.json b/crates/apub/assets/mobilizon/objects/group.json new file mode 100644 index 000000000..42beb42d4 --- /dev/null +++ b/crates/apub/assets/mobilizon/objects/group.json @@ -0,0 +1,168 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "addressRegion": "sc:addressRegion", + "timezone": { + "@id": "mz:timezone", + "@type": "sc:Text" + }, + "isOnline": { + "@id": "mz:isOnline", + "@type": "sc:Boolean" + }, + "pt": "https://joinpeertube.org/ns#", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "inLanguage": "sc:inLanguage", + "address": { + "@id": "sc:address", + "@type": "sc:PostalAddress" + }, + "discoverable": "toot:discoverable", + "repliesModerationOption": { + "@id": "mz:repliesModerationOption", + "@type": "mz:repliesModerationOptionType" + }, + "sc": "http://schema.org#", + "mz": "https://joinmobilizon.org/ns#", + "category": "sc:category", + "joinModeType": { + "@id": "mz:joinModeType", + "@type": "rdfs:Class" + }, + "Hashtag": "as:Hashtag", + "propertyID": "sc:propertyID", + "PostalAddress": "sc:PostalAddress", + "discussions": { + "@id": "mz:discussions", + "@type": "@id" + }, + "remainingAttendeeCapacity": "sc:remainingAttendeeCapacity", + "streetAddress": "sc:streetAddress", + "anonymousParticipationEnabled": { + "@id": "mz:anonymousParticipationEnabled", + "@type": "sc:Boolean" + }, + "addressLocality": "sc:addressLocality", + "joinMode": { + "@id": "mz:joinMode", + "@type": "mz:joinModeType" + }, + "location": { + "@id": "sc:location", + "@type": "sc:Place" + }, + "toot": "http://joinmastodon.org/ns#", + "participantCount": { + "@id": "mz:participantCount", + "@type": "sc:Integer" + }, + "uuid": "sc:identifier", + "maximumAttendeeCapacity": "sc:maximumAttendeeCapacity", + "participationMessage": { + "@id": "mz:participationMessage", + "@type": "sc:Text" + }, + "openness": { + "@id": "mz:openness", + "@type": "@id" + }, + "members": { + "@id": "mz:members", + "@type": "@id" + }, + "events": { + "@id": "mz:events", + "@type": "@id" + }, + "resources": { + "@id": "mz:resources", + "@type": "@id" + }, + "addressCountry": "sc:addressCountry", + "posts": { + "@id": "mz:posts", + "@type": "@id" + }, + "commentsEnabled": { + "@id": "pt:commentsEnabled", + "@type": "sc:Boolean" + }, + "value": "sc:value", + "PropertyValue": "sc:PropertyValue", + "repliesModerationOptionType": { + "@id": "mz:repliesModerationOptionType", + "@type": "rdfs:Class" + }, + "todos": { + "@id": "mz:todos", + "@type": "@id" + }, + "ical": "http://www.w3.org/2002/12/cal/ical#", + "postalCode": "sc:postalCode", + "memberCount": { + "@id": "mz:memberCount", + "@type": "sc:Integer" + }, + "@language": "und" + } + ], + "discoverable": true, + "discussions": "https://mobilizon.fr/@contribateliers/discussions", + "endpoints": { + "discussions": "https://mobilizon.fr/@contribateliers/discussions", + "events": "https://mobilizon.fr/@contribateliers/events", + "members": "https://mobilizon.fr/@contribateliers/members", + "posts": "https://mobilizon.fr/@contribateliers/posts", + "resources": "https://mobilizon.fr/@contribateliers/resources", + "sharedInbox": "https://mobilizon.fr/inbox", + "todos": "https://mobilizon.fr/@contribateliers/todos" + }, + "events": "https://mobilizon.fr/@contribateliers/events", + "followers": "https://mobilizon.fr/@contribateliers/followers", + "following": "https://mobilizon.fr/@contribateliers/following", + "icon": { + "mediaType": null, + "type": "Image", + "url": "https://mobilizon.fr/media/a94f7f8da4b39f6b375f55bd8664abff4ae61d33496df7ee23ad6bf473c3632f.png?name=contribateliers%27s%20avatar.png" + }, + "id": "https://mobilizon.fr/@contribateliers", + "image": { + "mediaType": null, + "type": "Image", + "url": "https://mobilizon.fr/media/7fe251dd5f8b5abcea10c31b655c09afee457efdd49f3087ba78b054b3f0dbeb.jpg?name=contribateliers%27s%20banner.jpg" + }, + "inbox": "https://mobilizon.fr/@contribateliers/inbox", + "location": { + "address": { + "addressCountry": null, + "addressLocality": null, + "addressRegion": null, + "postalCode": null, + "streetAddress": null, + "type": "PostalAddress" + }, + "id": "https://mobilizon.fr/address/935f207e-4c0f-4818-8762-51d6ab2ed27e", + "name": null, + "type": "Place" + }, + "manuallyApprovesFollowers": false, + "memberCount": 13, + "members": "https://mobilizon.fr/@contribateliers/members", + "name": "Contribateliers", + "openness": "open", + "outbox": "https://mobilizon.fr/@contribateliers/outbox", + "posts": "https://mobilizon.fr/@contribateliers/posts", + "preferredUsername": "contribateliers", + "publicKey": { + "id": "https://mobilizon.fr/@contribateliers#main-key", + "owner": "https://mobilizon.fr/@contribateliers", + "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEA1laog+0zKOkGdUHfWQ+lIJq5LOwWzGKLeqXzSdvaUzfk2X5Q5gTf\nbjh7pWJaWo2uxrIeNKRJSpmxeBn/lNR3+OrG05/MiYW6Y42q+ZL18coUDht46u23\nHH9+fFblmvY905cNslJ4/NouxpN0ai5JytZOzlNnJCan241rS4gkeLAy+LDW6UOd\nTvDPMJQlrAl8gr+OamRUxd4RL/8ws7/FbqNiAetXmN/5knjkQe5rFi0D/3fQtWEv\n/kSTG6CmnBhpeKE8eqp1sD0+CMROfOb7ceVIpJvUKAPHsENRE6DQFF9j3wl8AXjd\ndtGxTyOYYaMXCPyAUBjH/Rt6uV5Bc5x2CQIDAQAB\n-----END RSA PUBLIC KEY-----\n\n" + }, + "resources": "https://mobilizon.fr/@contribateliers/resources", + "summary": "

Des ateliers pour contribuer au libre sans rien y connaître.

", + "todos": "https://mobilizon.fr/@contribateliers/todos", + "type": "Group", + "url": "https://mobilizon.fr/@contribateliers" +} diff --git a/crates/apub/assets/mobilizon/objects/person.json b/crates/apub/assets/mobilizon/objects/person.json new file mode 100644 index 000000000..d0e0fbe86 --- /dev/null +++ b/crates/apub/assets/mobilizon/objects/person.json @@ -0,0 +1,144 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "addressRegion": "sc:addressRegion", + "timezone": { + "@id": "mz:timezone", + "@type": "sc:Text" + }, + "isOnline": { + "@id": "mz:isOnline", + "@type": "sc:Boolean" + }, + "pt": "https://joinpeertube.org/ns#", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "inLanguage": "sc:inLanguage", + "address": { + "@id": "sc:address", + "@type": "sc:PostalAddress" + }, + "discoverable": "toot:discoverable", + "repliesModerationOption": { + "@id": "mz:repliesModerationOption", + "@type": "mz:repliesModerationOptionType" + }, + "sc": "http://schema.org#", + "mz": "https://joinmobilizon.org/ns#", + "category": "sc:category", + "joinModeType": { + "@id": "mz:joinModeType", + "@type": "rdfs:Class" + }, + "Hashtag": "as:Hashtag", + "propertyID": "sc:propertyID", + "PostalAddress": "sc:PostalAddress", + "discussions": { + "@id": "mz:discussions", + "@type": "@id" + }, + "remainingAttendeeCapacity": "sc:remainingAttendeeCapacity", + "streetAddress": "sc:streetAddress", + "anonymousParticipationEnabled": { + "@id": "mz:anonymousParticipationEnabled", + "@type": "sc:Boolean" + }, + "addressLocality": "sc:addressLocality", + "joinMode": { + "@id": "mz:joinMode", + "@type": "mz:joinModeType" + }, + "location": { + "@id": "sc:location", + "@type": "sc:Place" + }, + "toot": "http://joinmastodon.org/ns#", + "participantCount": { + "@id": "mz:participantCount", + "@type": "sc:Integer" + }, + "uuid": "sc:identifier", + "maximumAttendeeCapacity": "sc:maximumAttendeeCapacity", + "participationMessage": { + "@id": "mz:participationMessage", + "@type": "sc:Text" + }, + "openness": { + "@id": "mz:openness", + "@type": "@id" + }, + "members": { + "@id": "mz:members", + "@type": "@id" + }, + "events": { + "@id": "mz:events", + "@type": "@id" + }, + "resources": { + "@id": "mz:resources", + "@type": "@id" + }, + "addressCountry": "sc:addressCountry", + "posts": { + "@id": "mz:posts", + "@type": "@id" + }, + "commentsEnabled": { + "@id": "pt:commentsEnabled", + "@type": "sc:Boolean" + }, + "value": "sc:value", + "PropertyValue": "sc:PropertyValue", + "repliesModerationOptionType": { + "@id": "mz:repliesModerationOptionType", + "@type": "rdfs:Class" + }, + "todos": { + "@id": "mz:todos", + "@type": "@id" + }, + "ical": "http://www.w3.org/2002/12/cal/ical#", + "postalCode": "sc:postalCode", + "memberCount": { + "@id": "mz:memberCount", + "@type": "sc:Integer" + }, + "@language": "und" + } + ], + "discoverable": false, + "discussions": null, + "endpoints": { + "discussions": null, + "events": null, + "members": null, + "posts": null, + "resources": null, + "sharedInbox": "https://mobilizon.fr/inbox", + "todos": null + }, + "events": null, + "followers": "https://mobilizon.fr/@sanof44/followers", + "following": "https://mobilizon.fr/@sanof44/following", + "id": "https://mobilizon.fr/@sanof44", + "inbox": "https://mobilizon.fr/@sanof44/inbox", + "manuallyApprovesFollowers": false, + "members": null, + "name": "Sanof44", + "openness": "moderated", + "outbox": "https://mobilizon.fr/@sanof44/outbox", + "posts": null, + "preferredUsername": "sanof44", + "publicKey": { + "id": "https://mobilizon.fr/@sanof44#main-key", + "owner": "https://mobilizon.fr/@sanof44", + "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAneK5zzdQQ/6ElSpPv1mj34IMoIIHcTK+iEjZYd85yPfG4krK5bqI\nkUw0TUXFekpntLfDSsGohayrvD2WhN2b499y/A9wdl77RVLIAcBfE3UXr/TfDnjh\nsQEEzV4ghcYaKmXZa/ct2sSt6poT/WhahVweEugfyA75UHgW5VA7nS5URhd7uZUw\nS2CI8fXigDbJlB9AqcxvR7Uncgsn0JCCt5boP8X1jDrh5PEsqsqePm9ZpxvvX4WD\n1yib/ZPBsTo50hJgHoA9bLXO14KvAOeIrzgOlJkyjWTQ+rk+5ewXIZuM0ECPEzAC\nRcpopBjqk07lMxPu1OMG4D+oI0n0K+PgNwIDAQAB\n-----END RSA PUBLIC KEY-----\n\n" + }, + "resources": null, + "summary": "", + "todos": null, + "type": "Person", + "url": "https://mobilizon.fr/@sanof44" +} diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index d6719926d..7d6c9e102 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -167,12 +167,9 @@ impl ApubObject for ApubPost { let community = page.extract_community(context, request_counter).await?; let form = if !page.is_mod_action(context).await? { - let url = if let Some(attachment) = page.attachment.first() { - Some(match attachment { - // url as sent by Lemmy (new) - Attachment::Link(link) => link.href.clone(), - Attachment::Image(image) => image.url.clone(), - }) + let first_attachment = page.attachment.into_iter().map(|a| a.url()).next(); + let url = if first_attachment.is_some() { + first_attachment } else if page.kind == PageType::Video { // we cant display videos directly, so insert a link to external video page Some(page.id.inner().clone()) diff --git a/crates/apub/src/protocol/objects/mod.rs b/crates/apub/src/protocol/objects/mod.rs index 0fcbc08e6..5a3b90bf6 100644 --- a/crates/apub/src/protocol/objects/mod.rs +++ b/crates/apub/src/protocol/objects/mod.rs @@ -167,4 +167,11 @@ mod tests { test_json::("assets/peertube/objects/video.json").unwrap(); test_json::("assets/peertube/objects/note.json").unwrap(); } + + #[test] + fn test_parse_object_mobilizon() { + test_json::("assets/mobilizon/objects/group.json").unwrap(); + test_json::("assets/mobilizon/objects/event.json").unwrap(); + test_json::("assets/mobilizon/objects/person.json").unwrap(); + } } diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index 5ffb4869b..ba76f62fb 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -13,7 +13,10 @@ use activitypub_federation::{ }, traits::{ActivityHandler, ApubObject}, }; -use activitystreams_kinds::{link::LinkType, object::ImageType}; +use activitystreams_kinds::{ + link::LinkType, + object::{DocumentType, ImageType}, +}; use chrono::{DateTime, FixedOffset}; use itertools::Itertools; use lemmy_db_schema::newtypes::DbUrl; @@ -29,6 +32,7 @@ pub enum PageType { Article, Note, Video, + Event, } #[skip_serializing_none] @@ -80,11 +84,33 @@ pub(crate) struct Image { pub(crate) url: Url, } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct Document { + #[serde(rename = "type")] + pub(crate) kind: DocumentType, + pub(crate) url: Url, +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(untagged)] pub(crate) enum Attachment { Link(Link), Image(Image), + Document(Document), +} + +impl Attachment { + pub(crate) fn url(self) -> Url { + match self { + // url as sent by Lemmy (new) + Attachment::Link(l) => l.href, + // image sent by lotide + Attachment::Image(i) => i.url, + // sent by mobilizon + Attachment::Document(d) => d.url, + } + } } #[derive(Clone, Debug, Deserialize, Serialize)]