]> Untitled Git - lemmy.git/commitdiff
Mobilizon federation (#2544)
authorNutomic <me@nutomic.com>
Sat, 12 Nov 2022 13:51:08 +0000 (13:51 +0000)
committerGitHub <noreply@github.com>
Sat, 12 Nov 2022 13:51:08 +0000 (08:51 -0500)
* Mobilizon federation

* Also accept document attachments from mobilizon

crates/apub/assets/mobilizon/objects/event.json [new file with mode: 0644]
crates/apub/assets/mobilizon/objects/group.json [new file with mode: 0644]
crates/apub/assets/mobilizon/objects/person.json [new file with mode: 0644]
crates/apub/src/objects/post.rs
crates/apub/src/protocol/objects/mod.rs
crates/apub/src/protocol/objects/page.rs

diff --git a/crates/apub/assets/mobilizon/objects/event.json b/crates/apub/assets/mobilizon/objects/event.json
new file mode 100644 (file)
index 0000000..414ffc8
--- /dev/null
@@ -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": "<p>£6 each.</p><p></p><p>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.</p><p></p><p>For anyone unfamiliar with <a target=\"_blank\" rel=\"noopener noreferrer ugc\" href=\"https://www.exeterceilidhs.net/\">ceilidhs</a>, they&#39;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&#39;s a <a target=\"_blank\" rel=\"noopener noreferrer ugc\" href=\"https://www.facebook.com/emorrp1/videos/735558448180\">sample video</a> from when I learned CaledonianDancing at uni. The cover photo is by Dave Conner CC-BY-2.0.</p>",
+  "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 (file)
index 0000000..42beb42
--- /dev/null
@@ -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": "<p>Des ateliers pour contribuer au libre sans rien y connaître.</p>",
+  "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 (file)
index 0000000..d0e0fbe
--- /dev/null
@@ -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"
+}
index d6719926d0e40aea884c9e0fae88c6eb49fe48da..7d6c9e102dee41f50454ae16cf494a5b953519f4 100644 (file)
@@ -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())
index 0fcbc08e6cfde9d64d6f6eba7ce9c3a6b6d2916f..5a3b90bf61446d942912a3e2893b49eb94a984af 100644 (file)
@@ -167,4 +167,11 @@ mod tests {
     test_json::<Page>("assets/peertube/objects/video.json").unwrap();
     test_json::<Note>("assets/peertube/objects/note.json").unwrap();
   }
+
+  #[test]
+  fn test_parse_object_mobilizon() {
+    test_json::<Group>("assets/mobilizon/objects/group.json").unwrap();
+    test_json::<Page>("assets/mobilizon/objects/event.json").unwrap();
+    test_json::<Person>("assets/mobilizon/objects/person.json").unwrap();
+  }
 }
index 5ffb4869b5ab8900852c89fc0d25590699e89c72..ba76f62fbe041d74c5425d6f8fb47b9d5ccc97da 100644 (file)
@@ -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)]