From 7b86441bab1a60f48d7bd4ff1ac37d6788d3cbf9 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Fri, 6 May 2022 23:53:33 +0000 Subject: [PATCH] Federate with Peertube (#2244) --- .../peertube/activities/announce_video.json | 107 ++++ .../apub/assets/peertube/objects/group.json | 131 +++++ crates/apub/assets/peertube/objects/note.json | 119 +++++ .../apub/assets/peertube/objects/person.json | 124 +++++ .../apub/assets/peertube/objects/video.json | 493 ++++++++++++++++++ .../apub/src/activities/community/announce.rs | 20 +- .../src/activities/create_or_update/post.rs | 4 +- crates/apub/src/activities/deletion/delete.rs | 8 +- crates/apub/src/activity_lists.rs | 22 + .../src/collections/community_moderators.rs | 5 +- crates/apub/src/fetcher/user_or_community.rs | 10 +- crates/apub/src/objects/comment.rs | 6 +- crates/apub/src/objects/instance.rs | 6 +- crates/apub/src/objects/mod.rs | 26 +- crates/apub/src/objects/person.rs | 3 +- crates/apub/src/objects/post.rs | 26 +- crates/apub/src/objects/private_message.rs | 2 +- .../protocol/activities/community/announce.rs | 4 +- .../protocol/activities/deletion/delete.rs | 35 +- .../activities/deletion/delete_user.rs | 3 +- .../activities/deletion/undo_delete.rs | 3 +- crates/apub/src/protocol/activities/mod.rs | 6 + crates/apub/src/protocol/mod.rs | 38 +- .../apub/src/protocol/objects/chat_message.rs | 3 +- crates/apub/src/protocol/objects/group.rs | 7 +- crates/apub/src/protocol/objects/instance.rs | 3 +- crates/apub/src/protocol/objects/mod.rs | 8 + crates/apub/src/protocol/objects/note.rs | 10 +- crates/apub/src/protocol/objects/page.rs | 80 ++- crates/apub/src/protocol/objects/person.rs | 3 +- crates/apub/src/protocol/objects/tombstone.rs | 9 +- crates/apub_lib/src/lib.rs | 1 + crates/apub_lib/src/object_id.rs | 41 +- crates/apub_lib/src/utils.rs | 38 ++ crates/apub_lib/src/values.rs | 10 +- 35 files changed, 1270 insertions(+), 144 deletions(-) create mode 100644 crates/apub/assets/peertube/activities/announce_video.json create mode 100644 crates/apub/assets/peertube/objects/group.json create mode 100644 crates/apub/assets/peertube/objects/note.json create mode 100644 crates/apub/assets/peertube/objects/person.json create mode 100644 crates/apub/assets/peertube/objects/video.json create mode 100644 crates/apub_lib/src/utils.rs diff --git a/crates/apub/assets/peertube/activities/announce_video.json b/crates/apub/assets/peertube/activities/announce_video.json new file mode 100644 index 000000000..28f06e8c1 --- /dev/null +++ b/crates/apub/assets/peertube/activities/announce_video.json @@ -0,0 +1,107 @@ +{ + "type": "Announce", + "id": "https://framatube.org/videos/watch/60c4bea4-6bb2-4fce-8d9f-8a522575419d/announces/395533", + "actor": "https://framatube.org/video-channels/joinpeertube", + "object": "https://framatube.org/videos/watch/60c4bea4-6bb2-4fce-8d9f-8a522575419d", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://framatube.org/accounts/framasoft/followers" + ], + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "RsaSignature2017": "https://w3id.org/security#RsaSignature2017" + }, + { + "pt": "https://joinpeertube.org/ns#", + "sc": "http://schema.org#", + "Hashtag": "as:Hashtag", + "uuid": "sc:identifier", + "category": "sc:category", + "licence": "sc:license", + "subtitleLanguage": "sc:subtitleLanguage", + "sensitive": "as:sensitive", + "language": "sc:inLanguage", + "isLiveBroadcast": "sc:isLiveBroadcast", + "liveSaveReplay": { + "@type": "sc:Boolean", + "@id": "pt:liveSaveReplay" + }, + "permanentLive": { + "@type": "sc:Boolean", + "@id": "pt:permanentLive" + }, + "Infohash": "pt:Infohash", + "Playlist": "pt:Playlist", + "PlaylistElement": "pt:PlaylistElement", + "originallyPublishedAt": "sc:datePublished", + "views": { + "@type": "sc:Number", + "@id": "pt:views" + }, + "state": { + "@type": "sc:Number", + "@id": "pt:state" + }, + "size": { + "@type": "sc:Number", + "@id": "pt:size" + }, + "fps": { + "@type": "sc:Number", + "@id": "pt:fps" + }, + "startTimestamp": { + "@type": "sc:Number", + "@id": "pt:startTimestamp" + }, + "stopTimestamp": { + "@type": "sc:Number", + "@id": "pt:stopTimestamp" + }, + "position": { + "@type": "sc:Number", + "@id": "pt:position" + }, + "commentsEnabled": { + "@type": "sc:Boolean", + "@id": "pt:commentsEnabled" + }, + "downloadEnabled": { + "@type": "sc:Boolean", + "@id": "pt:downloadEnabled" + }, + "waitTranscoding": { + "@type": "sc:Boolean", + "@id": "pt:waitTranscoding" + }, + "support": { + "@type": "sc:Text", + "@id": "pt:support" + }, + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "dislikes": { + "@id": "as:dislikes", + "@type": "@id" + }, + "playlists": { + "@id": "pt:playlists", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + }, + "comments": { + "@id": "as:comments", + "@type": "@id" + } + } + ] +} \ No newline at end of file diff --git a/crates/apub/assets/peertube/objects/group.json b/crates/apub/assets/peertube/objects/group.json new file mode 100644 index 000000000..cf4e216c4 --- /dev/null +++ b/crates/apub/assets/peertube/objects/group.json @@ -0,0 +1,131 @@ +{ + "type": "Group", + "id": "https://framatube.org/video-channels/joinpeertube", + "following": "https://framatube.org/video-channels/joinpeertube/following", + "followers": "https://framatube.org/video-channels/joinpeertube/followers", + "playlists": "https://framatube.org/video-channels/joinpeertube/playlists", + "inbox": "https://framatube.org/video-channels/joinpeertube/inbox", + "outbox": "https://framatube.org/video-channels/joinpeertube/outbox", + "preferredUsername": "joinpeertube", + "url": "https://framatube.org/video-channels/joinpeertube", + "name": "A propos de PeerTube", + "endpoints": { + "sharedInbox": "https://framatube.org/inbox" + }, + "publicKey": { + "id": "https://framatube.org/video-channels/joinpeertube#main-key", + "owner": "https://framatube.org/video-channels/joinpeertube", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsJCIZJga+4Kumb9Wrmpy\ntyV7kWdINImoXBiFkGG+6OHreHN2C3UPwTu9IkX/e20NaX6Ly6c0busieW7yh//q\nomHl2U8zz2Z5xQHUN/2ljQjUNO+89OV6cFIGyEvcwc6QhuqGvrcxonjrEkux7xSv\nxQM4kZ3YW1Sii4piFpGGIm1pcUkOxFab8PWVB5Hzpg/df2/XOmH8UECT5vaMRPE6\ns6hNiQNE34z9QmPiG6nUlaWb/WDcMYbma3sUVWW3DI008ukLlwLaLIm30ax8CEYt\nHEv2jOQb1E1sXtBPe1FI+dXRgTIk40KF50KLqcgwJH1y5ck7c8IEeooj+tYGVqPr\npQIDAQAB\n-----END PUBLIC KEY-----" + }, + "published": "2021-08-09T14:26:09.514Z", + "icon": { + "type": "Image", + "mediaType": "image/png", + "height": 120, + "width": 120, + "url": "https://framatube.org/lazy-static/avatars/a2c2ff10-9da6-4c6c-9b25-2e557fa74b66.png" + }, + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "RsaSignature2017": "https://w3id.org/security#RsaSignature2017" + }, + { + "pt": "https://joinpeertube.org/ns#", + "sc": "http://schema.org#", + "Hashtag": "as:Hashtag", + "uuid": "sc:identifier", + "category": "sc:category", + "licence": "sc:license", + "subtitleLanguage": "sc:subtitleLanguage", + "sensitive": "as:sensitive", + "language": "sc:inLanguage", + "isLiveBroadcast": "sc:isLiveBroadcast", + "liveSaveReplay": { + "@type": "sc:Boolean", + "@id": "pt:liveSaveReplay" + }, + "permanentLive": { + "@type": "sc:Boolean", + "@id": "pt:permanentLive" + }, + "Infohash": "pt:Infohash", + "Playlist": "pt:Playlist", + "PlaylistElement": "pt:PlaylistElement", + "originallyPublishedAt": "sc:datePublished", + "views": { + "@type": "sc:Number", + "@id": "pt:views" + }, + "state": { + "@type": "sc:Number", + "@id": "pt:state" + }, + "size": { + "@type": "sc:Number", + "@id": "pt:size" + }, + "fps": { + "@type": "sc:Number", + "@id": "pt:fps" + }, + "startTimestamp": { + "@type": "sc:Number", + "@id": "pt:startTimestamp" + }, + "stopTimestamp": { + "@type": "sc:Number", + "@id": "pt:stopTimestamp" + }, + "position": { + "@type": "sc:Number", + "@id": "pt:position" + }, + "commentsEnabled": { + "@type": "sc:Boolean", + "@id": "pt:commentsEnabled" + }, + "downloadEnabled": { + "@type": "sc:Boolean", + "@id": "pt:downloadEnabled" + }, + "waitTranscoding": { + "@type": "sc:Boolean", + "@id": "pt:waitTranscoding" + }, + "support": { + "@type": "sc:Text", + "@id": "pt:support" + }, + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "dislikes": { + "@id": "as:dislikes", + "@type": "@id" + }, + "playlists": { + "@id": "pt:playlists", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + }, + "comments": { + "@id": "as:comments", + "@type": "@id" + } + } + ], + "summary": "Un logiciel libre pour reprendre le contrôle de vos vidéos", + "support": null, + "attributedTo": [ + { + "type": "Person", + "id": "https://framatube.org/accounts/framasoft" + } + ] +} diff --git a/crates/apub/assets/peertube/objects/note.json b/crates/apub/assets/peertube/objects/note.json new file mode 100644 index 000000000..8df8d5861 --- /dev/null +++ b/crates/apub/assets/peertube/objects/note.json @@ -0,0 +1,119 @@ +{ + "type": "Note", + "id": "https://video.antopie.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/comments/200873", + "content": "@af2@bae.st idk", + "mediaType": "text/markdown", + "inReplyTo": "https://bae.st/objects/87c1cbf5-542a-491d-af57-0414c8648381", + "updated": "2022-04-29T07:52:32.555Z", + "published": "2022-04-29T07:52:32.548Z", + "url": "https://video.antopie.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/comments/200873", + "attributedTo": "https://video.antopie.org/accounts/yoge6785555", + "tag": [ + { + "type": "Mention", + "href": "https://bae.st/users/af2", + "name": "@af2@bae.st" + } + ], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://video.antopie.org/accounts/yoge6785555/followers" + ], + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "RsaSignature2017": "https://w3id.org/security#RsaSignature2017" + }, + { + "pt": "https://joinpeertube.org/ns#", + "sc": "http://schema.org#", + "Hashtag": "as:Hashtag", + "uuid": "sc:identifier", + "category": "sc:category", + "licence": "sc:license", + "subtitleLanguage": "sc:subtitleLanguage", + "sensitive": "as:sensitive", + "language": "sc:inLanguage", + "isLiveBroadcast": "sc:isLiveBroadcast", + "liveSaveReplay": { + "@type": "sc:Boolean", + "@id": "pt:liveSaveReplay" + }, + "permanentLive": { + "@type": "sc:Boolean", + "@id": "pt:permanentLive" + }, + "Infohash": "pt:Infohash", + "Playlist": "pt:Playlist", + "PlaylistElement": "pt:PlaylistElement", + "originallyPublishedAt": "sc:datePublished", + "views": { + "@type": "sc:Number", + "@id": "pt:views" + }, + "state": { + "@type": "sc:Number", + "@id": "pt:state" + }, + "size": { + "@type": "sc:Number", + "@id": "pt:size" + }, + "fps": { + "@type": "sc:Number", + "@id": "pt:fps" + }, + "startTimestamp": { + "@type": "sc:Number", + "@id": "pt:startTimestamp" + }, + "stopTimestamp": { + "@type": "sc:Number", + "@id": "pt:stopTimestamp" + }, + "position": { + "@type": "sc:Number", + "@id": "pt:position" + }, + "commentsEnabled": { + "@type": "sc:Boolean", + "@id": "pt:commentsEnabled" + }, + "downloadEnabled": { + "@type": "sc:Boolean", + "@id": "pt:downloadEnabled" + }, + "waitTranscoding": { + "@type": "sc:Boolean", + "@id": "pt:waitTranscoding" + }, + "support": { + "@type": "sc:Text", + "@id": "pt:support" + }, + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "dislikes": { + "@id": "as:dislikes", + "@type": "@id" + }, + "playlists": { + "@id": "pt:playlists", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + }, + "comments": { + "@id": "as:comments", + "@type": "@id" + } + } + ] +} diff --git a/crates/apub/assets/peertube/objects/person.json b/crates/apub/assets/peertube/objects/person.json new file mode 100644 index 000000000..871e88e13 --- /dev/null +++ b/crates/apub/assets/peertube/objects/person.json @@ -0,0 +1,124 @@ +{ + "type": "Person", + "id": "https://framatube.org/accounts/framasoft", + "following": "https://framatube.org/accounts/framasoft/following", + "followers": "https://framatube.org/accounts/framasoft/followers", + "playlists": "https://framatube.org/accounts/framasoft/playlists", + "inbox": "https://framatube.org/accounts/framasoft/inbox", + "outbox": "https://framatube.org/accounts/framasoft/outbox", + "preferredUsername": "framasoft", + "url": "https://framatube.org/accounts/framasoft", + "name": "Framasoft", + "endpoints": { + "sharedInbox": "https://framatube.org/inbox" + }, + "publicKey": { + "id": "https://framatube.org/accounts/framasoft#main-key", + "owner": "https://framatube.org/accounts/framasoft", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuRh3frgIg866D0y0FThp\nSUkJImMcHGkUvpYQYv2iUgarZZtEbwT8PfQf0bJazy+cP8KqQmMDf5PBhT7dfdny\nf/GKGMw9Olc+QISeKDj3sqZ3Csrm4KV4avMGCfth6eSU7LozojeSGCXdUFz/8UgE\nfhV4mJjEX/FbwRYoKlagv5rY9mkX5XomzZU+z9j6ZVXyofwOwJvmI1hq0SYDv2bc\neB/RgIh/H0nyMtF8o+0CT42FNEET9j9m1BKOBtPzwZHmitKRkEmui5cK256s1laB\nT61KHpcD9gQKkQ+I3sFEzCBUJYfVo6fUe+GehBZuAfq4qDhd15SfE4K9veDscDFI\nTwIDAQAB\n-----END PUBLIC KEY-----" + }, + "published": "2018-03-01T15:16:17.118Z", + "icon": { + "type": "Image", + "mediaType": "image/png", + "height": null, + "width": null, + "url": "https://framatube.org/lazy-static/avatars/f73876f5-1d45-4f8a-942a-d3d5d5ac5dc1.png" + }, + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "RsaSignature2017": "https://w3id.org/security#RsaSignature2017" + }, + { + "pt": "https://joinpeertube.org/ns#", + "sc": "http://schema.org#", + "Hashtag": "as:Hashtag", + "uuid": "sc:identifier", + "category": "sc:category", + "licence": "sc:license", + "subtitleLanguage": "sc:subtitleLanguage", + "sensitive": "as:sensitive", + "language": "sc:inLanguage", + "isLiveBroadcast": "sc:isLiveBroadcast", + "liveSaveReplay": { + "@type": "sc:Boolean", + "@id": "pt:liveSaveReplay" + }, + "permanentLive": { + "@type": "sc:Boolean", + "@id": "pt:permanentLive" + }, + "Infohash": "pt:Infohash", + "Playlist": "pt:Playlist", + "PlaylistElement": "pt:PlaylistElement", + "originallyPublishedAt": "sc:datePublished", + "views": { + "@type": "sc:Number", + "@id": "pt:views" + }, + "state": { + "@type": "sc:Number", + "@id": "pt:state" + }, + "size": { + "@type": "sc:Number", + "@id": "pt:size" + }, + "fps": { + "@type": "sc:Number", + "@id": "pt:fps" + }, + "startTimestamp": { + "@type": "sc:Number", + "@id": "pt:startTimestamp" + }, + "stopTimestamp": { + "@type": "sc:Number", + "@id": "pt:stopTimestamp" + }, + "position": { + "@type": "sc:Number", + "@id": "pt:position" + }, + "commentsEnabled": { + "@type": "sc:Boolean", + "@id": "pt:commentsEnabled" + }, + "downloadEnabled": { + "@type": "sc:Boolean", + "@id": "pt:downloadEnabled" + }, + "waitTranscoding": { + "@type": "sc:Boolean", + "@id": "pt:waitTranscoding" + }, + "support": { + "@type": "sc:Text", + "@id": "pt:support" + }, + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "dislikes": { + "@id": "as:dislikes", + "@type": "@id" + }, + "playlists": { + "@id": "pt:playlists", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + }, + "comments": { + "@id": "as:comments", + "@type": "@id" + } + } + ], + "summary": null +} diff --git a/crates/apub/assets/peertube/objects/video.json b/crates/apub/assets/peertube/objects/video.json new file mode 100644 index 000000000..4325c8fa6 --- /dev/null +++ b/crates/apub/assets/peertube/objects/video.json @@ -0,0 +1,493 @@ +{ + "type": "Video", + "id": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277", + "name": "What is the Fediverse?", + "duration": "PT98S", + "uuid": "4294a720-f263-4ea4-9392-cf9cea4d5277", + "tag": [ + { + "type": "Hashtag", + "name": "fediverse" + }, + { + "type": "Hashtag", + "name": "framasoft" + }, + { + "type": "Hashtag", + "name": "Mastodon" + }, + { + "type": "Hashtag", + "name": "PeerTube " + } + ], + "category": { + "identifier": "15", + "name": "Science & Technology" + }, + "licence": { + "identifier": "2", + "name": "Attribution - Share Alike" + }, + "language": { + "identifier": "en", + "name": "English" + }, + "views": 4805, + "sensitive": false, + "waitTranscoding": true, + "isLiveBroadcast": false, + "liveSaveReplay": null, + "permanentLive": null, + "state": 1, + "commentsEnabled": true, + "downloadEnabled": true, + "published": "2022-04-28T11:51:16.293Z", + "originallyPublishedAt": null, + "updated": "2022-05-03T11:39:02.489Z", + "mediaType": "text/markdown", + "content": "Help us translate the subtitles [on our translation tool](https://weblate.framasoft.org/projects/what-is-the-fediverse-video/subtitles/).\r\n\r\n**Animation Produced by** [LILA](https://libreart.info/) - [ZeMarmot Team](https://film.zemarmot.net/)\r\n**Direction & Animation** by Aryeom\r\n**Script & Technology** by Jehan\r\n**Voice by** Paul Peterson\r\n**Licence**: [CC-By-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)\r\n\r\n**Sponsored by** [Framasoft](https://framasoft.org/)\r\n\r\n**Sound by** ORL - [AMMD](https://ammd.net/)\r\n\r\n**Music**: \"Dolling\" by CyberSDF - [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/)", + "support": null, + "subtitleLanguage": [ + { + "identifier": "ca", + "name": "Catalan", + "url": "https://framatube.org/lazy-static/video-captions/6f8aedd2-c61b-47f6-a2c9-75b15af24d14-ca.vtt" + }, + { + "identifier": "en", + "name": "English", + "url": "https://framatube.org/lazy-static/video-captions/2f199e59-5cf8-4529-a033-9d6dd4a858ca-en.vtt" + }, + { + "identifier": "es", + "name": "Spanish", + "url": "https://framatube.org/lazy-static/video-captions/3f74c16b-925f-45e1-8388-e358428c2436-es.vtt" + }, + { + "identifier": "eu", + "name": "Basque", + "url": "https://framatube.org/lazy-static/video-captions/c4c88e7e-b9d4-4192-bcf2-caf025ddc9fd-eu.vtt" + }, + { + "identifier": "fr", + "name": "French", + "url": "https://framatube.org/lazy-static/video-captions/c18906e3-6257-43e7-90e4-fa2c8ded258b-fr.vtt" + }, + { + "identifier": "hu", + "name": "Hungarian", + "url": "https://framatube.org/lazy-static/video-captions/0a8a295d-a288-404b-b7b3-a2272bc2a6fb-hu.vtt" + }, + { + "identifier": "it", + "name": "Italian", + "url": "https://framatube.org/lazy-static/video-captions/cf857bd9-8b04-4018-af9a-23fa1ff7662d-it.vtt" + }, + { + "identifier": "nb", + "name": "Norwegian Bokmål", + "url": "https://framatube.org/lazy-static/video-captions/12e3a0e9-a29e-4b06-8538-91bed2a11242-nb.vtt" + }, + { + "identifier": "oc", + "name": "Occitan", + "url": "https://framatube.org/lazy-static/video-captions/d841af30-97bf-4a0c-b1f9-e163ba77f23f-oc.vtt" + }, + { + "identifier": "sh", + "name": "Serbo-Croatian", + "url": "https://framatube.org/lazy-static/video-captions/7afe4dae-745f-4769-9f17-9c3a079235cf-sh.vtt" + }, + { + "identifier": "tr", + "name": "Turkish", + "url": "https://framatube.org/lazy-static/video-captions/1b2ea189-760c-4a3e-98d3-16f596c151f0-tr.vtt" + }, + { + "identifier": "vi", + "name": "Vietnamese", + "url": "https://framatube.org/lazy-static/video-captions/552b4086-54ab-4eb3-a8b3-7611a2175e77-vi.vtt" + } + ], + "icon": [ + { + "type": "Image", + "url": "https://framatube.org/static/thumbnails/1f9eb76e-c089-4bdd-af14-602935a6db72.jpg", + "mediaType": "image/jpeg", + "width": 280, + "height": 157 + }, + { + "type": "Image", + "url": "https://framatube.org/lazy-static/previews/8f89d4d8-696f-4512-9a1a-72f1d12caede.jpg", + "mediaType": "image/jpeg", + "width": 850, + "height": 480 + } + ], + "url": [ + { + "type": "Link", + "mediaType": "text/html", + "href": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277" + }, + { + "type": "Link", + "mediaType": "application/x-mpegURL", + "href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/adc259cb-06f7-496c-8a50-599e58358b29-master.m3u8", + "tag": [ + { + "type": "Infohash", + "name": "caf7178ddd2013e28c9fbcbb7be28df25d03a023" + }, + { + "type": "Infohash", + "name": "cc18bb140f51f64090ba41c951fba85705cafa38" + }, + { + "type": "Infohash", + "name": "595513d823a1aecc18abacac94a1ebb0c31ec009" + }, + { + "type": "Infohash", + "name": "6ae0ce749a57d0f8ff70286878ea7661f85eebf7" + }, + { + "type": "Infohash", + "name": "4eb799f42d461929ed8dd4befae274c9a4404b99" + }, + { + "type": "Infohash", + "name": "b48d1ea795657668783544fd1c9baf637198a323" + }, + { + "type": "Link", + "name": "sha256", + "mediaType": "application/json", + "href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/b414eda3-c8af-4271-8dde-253db28aacd1-segments-sha256.json" + }, + { + "type": "Link", + "mediaType": "video/mp4", + "href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/64147344-1957-480d-9106-59dd7bbf5661-1080-fragmented.mp4", + "height": 1080, + "size": 14653991, + "fps": 24 + }, + { + "type": "Link", + "rel": [ + "metadata", + "video/mp4" + ], + "mediaType": "application/json", + "href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421492", + "height": 1080, + "fps": 24 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent", + "href": "https://framatube.org/lazy-static/torrents/83fa27e3-aba7-4e01-9e66-931086374176-1080-hls.torrent", + "height": 1080 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2F83fa27e3-aba7-4e01-9e66-931086374176-1080-hls.torrent&xt=urn:btih:5651916e4301c812412f51381c5af0c1f627bfcb&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2F64147344-1957-480d-9106-59dd7bbf5661-1080-fragmented.mp4", + "height": 1080 + }, + { + "type": "Link", + "mediaType": "video/mp4", + "href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/0efaeae5-7468-4c45-ade5-d3b6c732621f-720-fragmented.mp4", + "height": 720, + "size": 9939723, + "fps": 24 + }, + { + "type": "Link", + "rel": [ + "metadata", + "video/mp4" + ], + "mediaType": "application/json", + "href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421496", + "height": 720, + "fps": 24 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent", + "href": "https://framatube.org/lazy-static/torrents/b325c824-c052-46e2-9b46-887595055521-720-hls.torrent", + "height": 720 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2Fb325c824-c052-46e2-9b46-887595055521-720-hls.torrent&xt=urn:btih:b5a1db245fe156edab7f1981693178dcd47075d2&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2F0efaeae5-7468-4c45-ade5-d3b6c732621f-720-fragmented.mp4", + "height": 720 + }, + { + "type": "Link", + "mediaType": "video/mp4", + "href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/201f9772-4971-4bc3-8356-9b85b405ae5d-480-fragmented.mp4", + "height": 480, + "size": 7398758, + "fps": 24 + }, + { + "type": "Link", + "rel": [ + "metadata", + "video/mp4" + ], + "mediaType": "application/json", + "href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421494", + "height": 480, + "fps": 24 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent", + "href": "https://framatube.org/lazy-static/torrents/bd99f84e-e9bc-4d36-bea6-6f06000f87c5-480-hls.torrent", + "height": 480 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2Fbd99f84e-e9bc-4d36-bea6-6f06000f87c5-480-hls.torrent&xt=urn:btih:6cbe09b50cf7788923a2ec4852a3b2bfd1cd1907&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2F201f9772-4971-4bc3-8356-9b85b405ae5d-480-fragmented.mp4", + "height": 480 + }, + { + "type": "Link", + "mediaType": "video/mp4", + "href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/b2313ae6-da36-4fe3-bec5-aa352824a38a-360-fragmented.mp4", + "height": 360, + "size": 6133890, + "fps": 24 + }, + { + "type": "Link", + "rel": [ + "metadata", + "video/mp4" + ], + "mediaType": "application/json", + "href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421495", + "height": 360, + "fps": 24 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent", + "href": "https://framatube.org/lazy-static/torrents/b939430a-fdfd-4da7-a030-759ecafa6ac7-360-hls.torrent", + "height": 360 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2Fb939430a-fdfd-4da7-a030-759ecafa6ac7-360-hls.torrent&xt=urn:btih:16693f14ad9e53fc41d335e3fa409c2f943d7b68&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2Fb2313ae6-da36-4fe3-bec5-aa352824a38a-360-fragmented.mp4", + "height": 360 + }, + { + "type": "Link", + "mediaType": "video/mp4", + "href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/06a866f2-0527-4d68-93b7-c656d7374e86-240-fragmented.mp4", + "height": 240, + "size": 4861464, + "fps": 24 + }, + { + "type": "Link", + "rel": [ + "metadata", + "video/mp4" + ], + "mediaType": "application/json", + "href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421497", + "height": 240, + "fps": 24 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent", + "href": "https://framatube.org/lazy-static/torrents/072001ee-18ad-4859-af10-9d7bf12d640c-240-hls.torrent", + "height": 240 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2F072001ee-18ad-4859-af10-9d7bf12d640c-240-hls.torrent&xt=urn:btih:b823f54d8cd73f9d7a55266ce683f43bf772d26a&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2F06a866f2-0527-4d68-93b7-c656d7374e86-240-fragmented.mp4", + "height": 240 + }, + { + "type": "Link", + "mediaType": "video/mp4", + "href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/f8a1caed-057f-4700-a28e-004efc158b15-0-fragmented.mp4", + "height": 0, + "size": 3141179, + "fps": 0 + }, + { + "type": "Link", + "rel": [ + "metadata", + "video/mp4" + ], + "mediaType": "application/json", + "href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421493", + "height": 0, + "fps": 0 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent", + "href": "https://framatube.org/lazy-static/torrents/77cb6940-7e90-48d1-a391-bfa463b9600c-0-hls.torrent", + "height": 0 + }, + { + "type": "Link", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2F77cb6940-7e90-48d1-a391-bfa463b9600c-0-hls.torrent&xt=urn:btih:9bc7717ed01869507041e31a7e65baffa78ba651&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2Ff8a1caed-057f-4700-a28e-004efc158b15-0-fragmented.mp4", + "height": 0 + } + ] + }, + { + "type": "Link", + "name": "tracker-http", + "rel": [ + "tracker", + "http" + ], + "href": "https://framatube.org/tracker/announce" + }, + { + "type": "Link", + "name": "tracker-websocket", + "rel": [ + "tracker", + "websocket" + ], + "href": "wss://framatube.org:443/tracker/socket" + } + ], + "likes": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/likes", + "dislikes": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/dislikes", + "shares": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/announces", + "comments": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/comments", + "attributedTo": [ + { + "type": "Person", + "id": "https://framatube.org/accounts/framasoft" + }, + { + "type": "Group", + "id": "https://framatube.org/video-channels/joinpeertube" + } + ], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://framatube.org/accounts/framasoft/followers" + ], + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "RsaSignature2017": "https://w3id.org/security#RsaSignature2017" + }, + { + "pt": "https://joinpeertube.org/ns#", + "sc": "http://schema.org#", + "Hashtag": "as:Hashtag", + "uuid": "sc:identifier", + "category": "sc:category", + "licence": "sc:license", + "subtitleLanguage": "sc:subtitleLanguage", + "sensitive": "as:sensitive", + "language": "sc:inLanguage", + "isLiveBroadcast": "sc:isLiveBroadcast", + "liveSaveReplay": { + "@type": "sc:Boolean", + "@id": "pt:liveSaveReplay" + }, + "permanentLive": { + "@type": "sc:Boolean", + "@id": "pt:permanentLive" + }, + "Infohash": "pt:Infohash", + "Playlist": "pt:Playlist", + "PlaylistElement": "pt:PlaylistElement", + "originallyPublishedAt": "sc:datePublished", + "views": { + "@type": "sc:Number", + "@id": "pt:views" + }, + "state": { + "@type": "sc:Number", + "@id": "pt:state" + }, + "size": { + "@type": "sc:Number", + "@id": "pt:size" + }, + "fps": { + "@type": "sc:Number", + "@id": "pt:fps" + }, + "startTimestamp": { + "@type": "sc:Number", + "@id": "pt:startTimestamp" + }, + "stopTimestamp": { + "@type": "sc:Number", + "@id": "pt:stopTimestamp" + }, + "position": { + "@type": "sc:Number", + "@id": "pt:position" + }, + "commentsEnabled": { + "@type": "sc:Boolean", + "@id": "pt:commentsEnabled" + }, + "downloadEnabled": { + "@type": "sc:Boolean", + "@id": "pt:downloadEnabled" + }, + "waitTranscoding": { + "@type": "sc:Boolean", + "@id": "pt:waitTranscoding" + }, + "support": { + "@type": "sc:Text", + "@id": "pt:support" + }, + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "dislikes": { + "@id": "as:dislikes", + "@type": "@id" + }, + "playlists": { + "@id": "pt:playlists", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + }, + "comments": { + "@id": "as:comments", + "@type": "@id" + } + } + ] +} diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 5bfcb9317..a6612ca1c 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -4,7 +4,10 @@ use crate::{ http::ActivityCommonFields, insert_activity, objects::community::ApubCommunity, - protocol::activities::{community::announce::AnnounceActivity, CreateOrUpdateType}, + protocol::{ + activities::{community::announce::AnnounceActivity, CreateOrUpdateType}, + IdOrNestedObject, + }, }; use activitystreams_kinds::{activity::AnnounceType, public}; use lemmy_apub_lib::{ @@ -34,7 +37,7 @@ impl AnnounceActivity { Ok(AnnounceActivity { actor: ObjectId::new(community.actor_id()), to: vec![public()], - object, + object: IdOrNestedObject::NestedObject(object), cc: vec![community.followers_url.clone().into()], kind: AnnounceType::Announce, id: generate_activity_id( @@ -92,11 +95,10 @@ impl ActivityHandler for AnnounceActivity { async fn verify( &self, context: &Data, - request_counter: &mut i32, + _request_counter: &mut i32, ) -> Result<(), LemmyError> { verify_is_public(&self.to, &self.cc)?; verify_activity(&self.id, self.actor.inner(), &context.settings())?; - self.object.verify(context, request_counter).await?; Ok(()) } @@ -106,12 +108,16 @@ impl ActivityHandler for AnnounceActivity { context: &Data, request_counter: &mut i32, ) -> Result<(), LemmyError> { + let object = self.object.object(context, request_counter).await?; + // we have to verify this here in order to avoid fetching the object twice over http + object.verify(context, request_counter).await?; + // TODO: this can probably be implemented in a cleaner way - match self.object { + match object { // Dont insert these into activities table, as they are not activities. AnnouncableActivities::Page(_) => {} _ => { - let object_value = serde_json::to_value(&self.object)?; + let object_value = serde_json::to_value(&object)?; let object_data: ActivityCommonFields = serde_json::from_value(object_value.to_owned())?; let insert = @@ -125,6 +131,6 @@ impl ActivityHandler for AnnounceActivity { } } } - self.object.receive(context, request_counter).await + object.receive(context, request_counter).await } } diff --git a/crates/apub/src/activities/create_or_update/post.rs b/crates/apub/src/activities/create_or_update/post.rs index 70c59c35c..286001828 100644 --- a/crates/apub/src/activities/create_or_update/post.rs +++ b/crates/apub/src/activities/create_or_update/post.rs @@ -93,7 +93,7 @@ impl ActivityHandler for CreateOrUpdatePost { match self.kind { CreateOrUpdateType::Create => { verify_domains_match(self.actor.inner(), self.object.id.inner())?; - verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?; + verify_urls_match(self.actor.inner(), self.object.creator()?.inner())?; // Check that the post isnt locked or stickied, as that isnt possible for newly created posts. // However, when fetching a remote post we generate a new create activity with the current // locked/stickied value, so this check may fail. So only check if its a local community, @@ -119,7 +119,7 @@ impl ActivityHandler for CreateOrUpdatePost { .await?; } else { verify_domains_match(self.actor.inner(), self.object.id.inner())?; - verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?; + verify_urls_match(self.actor.inner(), self.object.creator()?.inner())?; } } } diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index 3f0215875..f442494ad 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -6,7 +6,11 @@ use crate::{ verify_activity, }, objects::{community::ApubCommunity, person::ApubPerson}, - protocol::activities::deletion::delete::{Delete, IdOrNestedObject, NestedObject}, + protocol::{ + activities::deletion::delete::Delete, + objects::tombstone::Tombstone, + IdOrNestedObject, + }, }; use activitystreams_kinds::activity::DeleteType; use anyhow::anyhow; @@ -106,7 +110,7 @@ impl Delete { Ok(Delete { actor: ObjectId::new(actor.actor_id.clone()), to: vec![to], - object: IdOrNestedObject::NestedObject(NestedObject { + object: IdOrNestedObject::NestedObject(Tombstone { id: object.id(), kind: Default::default(), }), diff --git a/crates/apub/src/activity_lists.rs b/crates/apub/src/activity_lists.rs index 80d37fc63..c9debefaa 100644 --- a/crates/apub/src/activity_lists.rs +++ b/crates/apub/src/activity_lists.rs @@ -25,12 +25,14 @@ use crate::{ voting::{undo_vote::UndoVote, vote::Vote}, }, objects::page::Page, + Id, }, }; use lemmy_apub_lib::traits::ActivityHandler; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; +use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] #[serde(untagged)] @@ -120,3 +122,23 @@ impl GetCommunity for AnnouncableActivities { Ok(community) } } + +impl Id for AnnouncableActivities { + fn id(&self) -> &Url { + use AnnouncableActivities::*; + match self { + CreateOrUpdateComment(c) => &c.id, + CreateOrUpdatePost(c) => &c.id, + Vote(v) => &v.id, + UndoVote(u) => &u.id, + Delete(d) => &d.id, + UndoDelete(u) => &u.id, + UpdateCommunity(u) => &u.id, + BlockUser(b) => &b.id, + UndoBlockUser(u) => &u.id, + AddMod(a) => &a.id, + RemoveMod(r) => &r.id, + Page(p) => p.id.inner(), + } + } +} diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index 4e084a165..028174ae9 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -184,10 +184,7 @@ mod tests { file_to_json_object("assets/lemmy/collections/group_moderators.json").unwrap(); let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap(); let mut request_counter = 0; - let community_context = CommunityContext { - 0: community, - 1: context, - }; + let community_context = CommunityContext(community, context); ApubCommunityModerators::verify(&json, &url, &community_context, &mut request_counter) .await .unwrap(); diff --git a/crates/apub/src/fetcher/user_or_community.rs b/crates/apub/src/fetcher/user_or_community.rs index ee86adcec..7ec5e9dd9 100644 --- a/crates/apub/src/fetcher/user_or_community.rs +++ b/crates/apub/src/fetcher/user_or_community.rs @@ -6,7 +6,7 @@ use chrono::NaiveDateTime; use lemmy_apub_lib::traits::{ActorType, ApubObject}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use url::Url; #[derive(Clone, Debug)] @@ -15,13 +15,19 @@ pub enum UserOrCommunity { Community(ApubCommunity), } -#[derive(Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(untagged)] pub enum PersonOrGroup { Person(Person), Group(Group), } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub enum PersonOrGroupType { + Person, + Group, +} + #[async_trait::async_trait(?Send)] impl ApubObject for UserOrCommunity { type DataType = LemmyContext; diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index c987ff8bd..96c9f05a9 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -15,7 +15,7 @@ use lemmy_api_common::utils::blocking; use lemmy_apub_lib::{ object_id::ObjectId, traits::ApubObject, - values::MediaTypeHtml, + values::MediaTypeMarkdownOrHtml, verify::verify_domains_match, }; use lemmy_db_schema::{ @@ -117,7 +117,7 @@ impl ApubObject for ApubComment { to: vec![public()], cc: maa.ccs, content: markdown_to_html(&self.content), - media_type: Some(MediaTypeHtml::Html), + media_type: Some(MediaTypeMarkdownOrHtml::Html), source: Some(Source::new(self.content.clone())), in_reply_to, published: Some(convert_datetime(self.published)), @@ -178,7 +178,7 @@ impl ApubObject for ApubComment { .await?; let (post, parent_comment_id) = note.get_parents(context, request_counter).await?; - let content = read_from_string_or_source(¬e.content, ¬e.source); + let content = read_from_string_or_source(¬e.content, ¬e.media_type, ¬e.source); let content_slurs_removed = remove_slurs(&content, &context.settings().slur_regex()); let form = CommentForm { diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index 225a10912..57e115f61 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -124,7 +124,11 @@ impl ApubObject for ApubSite { ) -> Result { let site_form = SiteForm { name: apub.name.clone(), - sidebar: Some(read_from_string_or_source_opt(&apub.content, &apub.source)), + sidebar: Some(read_from_string_or_source_opt( + &apub.content, + &None, + &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 0e55c49da..7d5fd26ed 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -1,7 +1,7 @@ use crate::protocol::{ImageObject, Source}; use anyhow::anyhow; use html2md::parse_html; -use lemmy_apub_lib::verify::verify_domains_match; +use lemmy_apub_lib::{values::MediaTypeMarkdownOrHtml, verify::verify_domains_match}; use lemmy_utils::{settings::structs::Settings, LemmyError}; use url::Url; @@ -12,23 +12,31 @@ pub mod person; pub mod post; pub mod private_message; -pub(crate) fn read_from_string_or_source(raw: &str, source: &Option) -> String { +pub(crate) fn read_from_string_or_source( + content: &str, + media_type: &Option, + source: &Option, +) -> String { if let Some(s) = source { + // markdown sent by lemmy in source field s.content.clone() + } else if media_type == &Some(MediaTypeMarkdownOrHtml::Markdown) { + // markdown sent by peertube in content field + content.to_string() } else { - parse_html(raw) + // otherwise, convert content html to markdown + parse_html(content) } } pub(crate) fn read_from_string_or_source_opt( - raw: &Option, + content: &Option, + media_type: &Option, source: &Option, ) -> Option { - if let Some(s2) = source { - Some(s2.content.clone()) - } else { - raw.as_ref().map(|s| parse_html(s)) - } + content + .as_ref() + .map(|content| read_from_string_or_source(content, media_type, source)) } pub(crate) fn verify_image_domain_matches( diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index e1938ed3d..8304322c6 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -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 = read_from_string_or_source_opt(&person.summary, &person.source); + let bio = read_from_string_or_source_opt(&person.summary, &None, &person.source); check_slurs_opt(&bio, slur_regex)?; Ok(()) } @@ -158,6 +158,7 @@ impl ApubObject for ApubPerson { actor_id: Some(person.id.into()), bio: Some(read_from_string_or_source_opt( &person.summary, + &None, &person.source, )), local: Some(false), diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index f6938dd6e..497970364 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -4,7 +4,7 @@ use crate::{ objects::{read_from_string_or_source_opt, verify_is_remote_object}, protocol::{ objects::{ - page::{Attachment, Page, PageType}, + page::{Attachment, AttributedTo, Page, PageType}, tombstone::Tombstone, }, ImageObject, @@ -17,7 +17,7 @@ use lemmy_api_common::{request::fetch_site_data, utils::blocking}; use lemmy_apub_lib::{ object_id::ObjectId, traits::ApubObject, - values::MediaTypeHtml, + values::MediaTypeMarkdownOrHtml, verify::verify_domains_match, }; use lemmy_db_schema::{ @@ -102,14 +102,14 @@ impl ApubObject for ApubPost { .await??; let page = Page { - r#type: PageType::Page, + kind: PageType::Page, id: ObjectId::new(self.ap_id.clone()), - attributed_to: ObjectId::new(creator.actor_id), + attributed_to: AttributedTo::Lemmy(ObjectId::new(creator.actor_id)), to: vec![community.actor_id.into(), public()], cc: vec![], name: self.name.clone(), content: self.body.as_ref().map(|b| markdown_to_html(b)), - media_type: Some(MediaTypeHtml::Html), + media_type: Some(MediaTypeMarkdownOrHtml::Html), source: self.body.clone().map(Source::new), url: self.url.clone().map(|u| u.into()), attachment: self.url.clone().map(Attachment::new).into_iter().collect(), @@ -143,9 +143,9 @@ impl ApubObject for ApubPost { let community = page.extract_community(context, request_counter).await?; check_is_apub_id_valid(page.id.inner(), community.local, &context.settings())?; - verify_person_in_community(&page.attributed_to, &community, context, request_counter).await?; + verify_person_in_community(&page.creator()?, &community, context, request_counter).await?; check_slurs(&page.name, &context.settings().slur_regex())?; - verify_domains_match(page.attributed_to.inner(), page.id.inner())?; + verify_domains_match(page.creator()?.inner(), page.id.inner())?; verify_is_public(&page.to, &page.cc)?; Ok(()) } @@ -157,15 +157,20 @@ impl ApubObject for ApubPost { request_counter: &mut i32, ) -> Result { let creator = page - .attributed_to + .creator()? .dereference(context, context.client(), request_counter) .await?; 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() { + // url as sent by Lemmy (new) Some(attachment.href.clone()) + } else if page.kind == PageType::Video { + // we cant display videos directly, so insert a link to external video page + Some(page.id.inner().clone()) } else { + // url sent by lemmy (old) page.url }; let thumbnail_url: Option = page.image.map(|i| i.url); @@ -177,8 +182,9 @@ impl ApubObject for ApubPost { let (embed_title, embed_description, embed_html) = metadata_res .map(|u| (u.title, u.description, u.html)) .unwrap_or((None, None, None)); - let body_slurs_removed = read_from_string_or_source_opt(&page.content, &page.source) - .map(|s| remove_slurs(&s, &context.settings().slur_regex())); + let body_slurs_removed = + read_from_string_or_source_opt(&page.content, &page.media_type, &page.source) + .map(|s| remove_slurs(&s, &context.settings().slur_regex())); PostForm { name: page.name.clone(), diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 7a638af32..a984180db 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -137,7 +137,7 @@ impl ApubObject for ApubPrivateMessage { let form = PrivateMessageForm { creator_id: creator.id, recipient_id: recipient.id, - content: read_from_string_or_source(¬e.content, ¬e.source), + content: read_from_string_or_source(¬e.content, &None, ¬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/community/announce.rs b/crates/apub/src/protocol/activities/community/announce.rs index d693ed6f7..75a7d9af4 100644 --- a/crates/apub/src/protocol/activities/community/announce.rs +++ b/crates/apub/src/protocol/activities/community/announce.rs @@ -1,7 +1,7 @@ use crate::{ activity_lists::AnnouncableActivities, objects::community::ApubCommunity, - protocol::Unparsed, + protocol::{IdOrNestedObject, Unparsed}, }; use activitystreams_kinds::activity::AnnounceType; use lemmy_apub_lib::object_id::ObjectId; @@ -14,7 +14,7 @@ pub struct AnnounceActivity { pub(crate) actor: ObjectId, #[serde(deserialize_with = "crate::deserialize_one_or_many")] pub(crate) to: Vec, - pub(crate) object: AnnouncableActivities, + pub(crate) object: IdOrNestedObject, #[serde(deserialize_with = "crate::deserialize_one_or_many")] pub(crate) cc: Vec, #[serde(rename = "type")] diff --git a/crates/apub/src/protocol/activities/deletion/delete.rs b/crates/apub/src/protocol/activities/deletion/delete.rs index d3ec53be5..3fb5984d6 100644 --- a/crates/apub/src/protocol/activities/deletion/delete.rs +++ b/crates/apub/src/protocol/activities/deletion/delete.rs @@ -1,5 +1,8 @@ -use crate::{objects::person::ApubPerson, protocol::Unparsed}; -use activitystreams_kinds::{activity::DeleteType, object::TombstoneType}; +use crate::{ + objects::person::ApubPerson, + protocol::{objects::tombstone::Tombstone, IdOrNestedObject, Unparsed}, +}; +use activitystreams_kinds::activity::DeleteType; use lemmy_apub_lib::object_id::ObjectId; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -12,7 +15,7 @@ pub struct Delete { pub(crate) actor: ObjectId, #[serde(deserialize_with = "crate::deserialize_one_or_many")] pub(crate) to: Vec, - pub(crate) object: IdOrNestedObject, + pub(crate) object: IdOrNestedObject, #[serde(rename = "type")] pub(crate) kind: DeleteType, pub(crate) id: Url, @@ -27,29 +30,3 @@ pub struct Delete { #[serde(flatten)] pub(crate) unparsed: Unparsed, } - -/// Instead of a simple ID string as object, Mastodon sends a nested tombstone for some reason, -/// so we need to handle that as well. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(untagged)] -pub(crate) enum IdOrNestedObject { - Id(Url), - NestedObject(NestedObject), -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub(crate) struct NestedObject { - pub(crate) id: Url, - // Backwards compatibility with Lemmy 0.15 - #[serde(rename = "type")] - pub(crate) kind: TombstoneType, -} - -impl IdOrNestedObject { - pub(crate) fn id(&self) -> &Url { - match self { - IdOrNestedObject::Id(i) => i, - IdOrNestedObject::NestedObject(n) => &n.id, - } - } -} diff --git a/crates/apub/src/protocol/activities/deletion/delete_user.rs b/crates/apub/src/protocol/activities/deletion/delete_user.rs index 22d215eb4..a45bfbdb6 100644 --- a/crates/apub/src/protocol/activities/deletion/delete_user.rs +++ b/crates/apub/src/protocol/activities/deletion/delete_user.rs @@ -17,8 +17,7 @@ pub struct DeleteUser { pub(crate) kind: DeleteType, pub(crate) id: Url, - #[serde(deserialize_with = "crate::deserialize_one_or_many")] - #[serde(default)] + #[serde(deserialize_with = "crate::deserialize_one_or_many", default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub(crate) cc: Vec, } diff --git a/crates/apub/src/protocol/activities/deletion/undo_delete.rs b/crates/apub/src/protocol/activities/deletion/undo_delete.rs index bc5b942ff..e676ab00b 100644 --- a/crates/apub/src/protocol/activities/deletion/undo_delete.rs +++ b/crates/apub/src/protocol/activities/deletion/undo_delete.rs @@ -18,8 +18,7 @@ pub struct UndoDelete { pub(crate) kind: UndoType, pub(crate) id: Url, - #[serde(deserialize_with = "crate::deserialize_one_or_many")] - #[serde(default)] + #[serde(deserialize_with = "crate::deserialize_one_or_many", default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub(crate) cc: Vec, #[serde(flatten)] diff --git a/crates/apub/src/protocol/activities/mod.rs b/crates/apub/src/protocol/activities/mod.rs index 4301ed596..35d14f8f2 100644 --- a/crates/apub/src/protocol/activities/mod.rs +++ b/crates/apub/src/protocol/activities/mod.rs @@ -18,6 +18,7 @@ pub enum CreateOrUpdateType { mod tests { use crate::protocol::{ activities::{ + community::announce::AnnounceActivity, create_or_update::{comment::CreateOrUpdateComment, post::CreateOrUpdatePost}, deletion::delete::Delete, following::{follow::FollowCommunity, undo_follow::UndoFollowCommunity}, @@ -70,4 +71,9 @@ mod tests { test_json::("assets/gnusocial/activities/create_note.json").unwrap(); test_json::("assets/gnusocial/activities/like_note.json").unwrap(); } + + #[test] + fn test_parse_peertube_activities() { + test_json::("assets/peertube/activities/announce_video.json").unwrap(); + } } diff --git a/crates/apub/src/protocol/mod.rs b/crates/apub/src/protocol/mod.rs index ea56cda1f..3206ac23e 100644 --- a/crates/apub/src/protocol/mod.rs +++ b/crates/apub/src/protocol/mod.rs @@ -1,7 +1,9 @@ use activitystreams_kinds::object::ImageType; -use lemmy_apub_lib::values::MediaTypeMarkdown; +use lemmy_apub_lib::{utils::fetch_object_http, values::MediaTypeMarkdown}; use lemmy_db_schema::newtypes::DbUrl; -use serde::{Deserialize, Serialize}; +use lemmy_utils::LemmyError; +use lemmy_websocket::LemmyContext; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::HashMap; use url::Url; @@ -42,10 +44,40 @@ impl ImageObject { } } -#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(transparent)] pub struct Unparsed(HashMap); +pub(crate) trait Id { + fn id(&self) -> &Url; +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub(crate) enum IdOrNestedObject { + Id(Url), + NestedObject(Kind), +} + +impl IdOrNestedObject { + pub(crate) fn id(&self) -> &Url { + match self { + IdOrNestedObject::Id(i) => i, + IdOrNestedObject::NestedObject(n) => n.id(), + } + } + pub(crate) async fn object( + self, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result { + match self { + IdOrNestedObject::Id(i) => fetch_object_http(&i, context.client(), request_counter).await, + IdOrNestedObject::NestedObject(o) => Ok(o), + } + } +} + #[cfg(test)] pub(crate) mod tests { use crate::context::WithContext; diff --git a/crates/apub/src/protocol/objects/chat_message.rs b/crates/apub/src/protocol/objects/chat_message.rs index 163bff3a4..e0ac8db98 100644 --- a/crates/apub/src/protocol/objects/chat_message.rs +++ b/crates/apub/src/protocol/objects/chat_message.rs @@ -19,8 +19,7 @@ pub struct ChatMessage { pub(crate) content: String, pub(crate) media_type: Option, - #[serde(default)] - #[serde(deserialize_with = "crate::deserialize_skip_error")] + #[serde(deserialize_with = "crate::deserialize_skip_error", default)] 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 c0b544f37..5f03814a9 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -40,8 +40,7 @@ pub struct Group { /// title pub(crate) name: Option, pub(crate) summary: Option, - #[serde(default)] - #[serde(deserialize_with = "crate::deserialize_skip_error")] + #[serde(deserialize_with = "crate::deserialize_skip_error", default)] pub(crate) source: Option, pub(crate) icon: Option, /// banner @@ -72,7 +71,7 @@ impl Group { let slur_regex = &context.settings().slur_regex(); check_slurs(&self.preferred_username, slur_regex)?; check_slurs_opt(&self.name, slur_regex)?; - let description = read_from_string_or_source_opt(&self.summary, &self.source); + let description = read_from_string_or_source_opt(&self.summary, &None, &self.source); check_slurs_opt(&description, slur_regex)?; Ok(()) } @@ -81,7 +80,7 @@ impl Group { CommunityForm { name: self.preferred_username.clone(), title: self.name.unwrap_or(self.preferred_username), - description: read_from_string_or_source_opt(&self.summary, &self.source), + description: read_from_string_or_source_opt(&self.summary, &None, &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 353d6500e..a18b72795 100644 --- a/crates/apub/src/protocol/objects/instance.rs +++ b/crates/apub/src/protocol/objects/instance.rs @@ -30,8 +30,7 @@ pub struct Instance { // sidebar pub(crate) content: Option, - #[serde(default)] - #[serde(deserialize_with = "crate::deserialize_skip_error")] + #[serde(deserialize_with = "crate::deserialize_skip_error", default)] pub(crate) source: Option, // short instance description pub(crate) summary: Option, diff --git a/crates/apub/src/protocol/objects/mod.rs b/crates/apub/src/protocol/objects/mod.rs index 864c13d9e..8bacae950 100644 --- a/crates/apub/src/protocol/objects/mod.rs +++ b/crates/apub/src/protocol/objects/mod.rs @@ -86,4 +86,12 @@ mod tests { test_json::("assets/gnusocial/objects/page.json").unwrap(); test_json::("assets/gnusocial/objects/note.json").unwrap(); } + + #[test] + fn test_parse_object_peertube() { + test_json::("assets/peertube/objects/person.json").unwrap(); + test_json::("assets/peertube/objects/group.json").unwrap(); + test_json::("assets/peertube/objects/video.json").unwrap(); + test_json::("assets/peertube/objects/note.json").unwrap(); + } } diff --git a/crates/apub/src/protocol/objects/note.rs b/crates/apub/src/protocol/objects/note.rs index 779675cc1..ac3e722c7 100644 --- a/crates/apub/src/protocol/objects/note.rs +++ b/crates/apub/src/protocol/objects/note.rs @@ -7,7 +7,7 @@ use crate::{ use activitystreams_kinds::object::NoteType; use chrono::{DateTime, FixedOffset}; use lemmy_api_common::utils::blocking; -use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml}; +use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeMarkdownOrHtml}; use lemmy_db_schema::{newtypes::CommentId, source::post::Post, traits::Crud}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; @@ -25,15 +25,13 @@ pub struct Note { pub(crate) attributed_to: ObjectId, #[serde(deserialize_with = "crate::deserialize_one_or_many")] pub(crate) to: Vec, - #[serde(default)] - #[serde(deserialize_with = "crate::deserialize_one_or_many")] + #[serde(deserialize_with = "crate::deserialize_one_or_many", default)] pub(crate) cc: Vec, pub(crate) content: String, pub(crate) in_reply_to: ObjectId, - pub(crate) media_type: Option, - #[serde(default)] - #[serde(deserialize_with = "crate::deserialize_skip_error")] + pub(crate) media_type: Option, + #[serde(deserialize_with = "crate::deserialize_skip_error", default)] pub(crate) source: Option, pub(crate) published: Option>, pub(crate) updated: Option>, diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index c799c5209..426de96f4 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -1,4 +1,5 @@ use crate::{ + fetcher::user_or_community::{PersonOrGroupType, UserOrCommunity}, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, protocol::{ImageObject, Source}, }; @@ -9,7 +10,7 @@ use lemmy_apub_lib::{ data::Data, object_id::ObjectId, traits::{ActivityHandler, ApubObject}, - values::MediaTypeHtml, + values::MediaTypeMarkdownOrHtml, }; use lemmy_db_schema::newtypes::DbUrl; use lemmy_utils::LemmyError; @@ -18,33 +19,34 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub enum PageType { Page, Article, Note, + Video, } #[skip_serializing_none] #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Page { - pub(crate) r#type: PageType, + #[serde(rename = "type")] + pub(crate) kind: PageType, pub(crate) id: ObjectId, - pub(crate) attributed_to: ObjectId, + pub(crate) attributed_to: AttributedTo, #[serde(deserialize_with = "crate::deserialize_one_or_many")] pub(crate) to: Vec, pub(crate) name: String, - #[serde(default)] - #[serde(deserialize_with = "crate::deserialize_one_or_many")] + #[serde(deserialize_with = "crate::deserialize_one_or_many", default)] pub(crate) cc: Vec, pub(crate) content: Option, - pub(crate) media_type: Option, - #[serde(default)] - #[serde(deserialize_with = "crate::deserialize_skip_error")] + pub(crate) media_type: Option, + #[serde(deserialize_with = "crate::deserialize_skip_error", default)] pub(crate) source: Option, /// deprecated, use attachment field + #[serde(deserialize_with = "crate::deserialize_skip_error", default)] pub(crate) url: Option, /// most software uses array type for attachment field, so we do the same. nevertheless, we only /// use the first item @@ -60,11 +62,26 @@ pub struct Page { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -pub struct Attachment { +pub(crate) struct Attachment { pub(crate) href: Url, pub(crate) r#type: LinkType, } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub(crate) enum AttributedTo { + Lemmy(ObjectId), + Peertube([AttributedToPeertube; 2]), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct AttributedToPeertube { + #[serde(rename = "type")] + pub kind: PersonOrGroupType, + pub id: ObjectId, +} + impl Page { /// Only mods can change the post's stickied/locked status. So if either of these is changed from /// the current value, it is a mod action and needs to be verified as such. @@ -111,21 +128,44 @@ impl Page { context: &LemmyContext, request_counter: &mut i32, ) -> Result { - let mut iter = self.to.iter().merge(self.cc.iter()); - loop { - if let Some(cid) = iter.next() { - let cid = ObjectId::new(cid.clone()); - if let Ok(c) = cid + match &self.attributed_to { + AttributedTo::Lemmy(_) => { + let mut iter = self.to.iter().merge(self.cc.iter()); + loop { + if let Some(cid) = iter.next() { + let cid = ObjectId::new(cid.clone()); + if let Ok(c) = cid + .dereference(context, context.client(), request_counter) + .await + { + break Ok(c); + } + } else { + return Err(LemmyError::from_message("No community found in cc")); + } + } + } + AttributedTo::Peertube(p) => { + p.iter() + .find(|a| a.kind == PersonOrGroupType::Group) + .map(|a| ObjectId::::new(a.id.clone().into_inner())) + .ok_or_else(|| LemmyError::from_message("page does not specify group"))? .dereference(context, context.client(), request_counter) .await - { - break Ok(c); - } - } else { - return Err(LemmyError::from_message("No community found in cc")); } } } + + pub(crate) fn creator(&self) -> Result, LemmyError> { + match &self.attributed_to { + AttributedTo::Lemmy(l) => Ok(l.clone()), + AttributedTo::Peertube(p) => p + .iter() + .find(|a| a.kind == PersonOrGroupType::Person) + .map(|a| ObjectId::::new(a.id.clone().into_inner())) + .ok_or_else(|| LemmyError::from_message("page does not specify creator person")), + } + } } impl Attachment { diff --git a/crates/apub/src/protocol/objects/person.rs b/crates/apub/src/protocol/objects/person.rs index 1fe072a3d..75d20cf6d 100644 --- a/crates/apub/src/protocol/objects/person.rs +++ b/crates/apub/src/protocol/objects/person.rs @@ -32,8 +32,7 @@ pub struct Person { /// displayname pub(crate) name: Option, pub(crate) summary: Option, - #[serde(default)] - #[serde(deserialize_with = "crate::deserialize_skip_error")] + #[serde(deserialize_with = "crate::deserialize_skip_error", default)] pub(crate) source: Option, /// user avatar pub(crate) icon: Option, diff --git a/crates/apub/src/protocol/objects/tombstone.rs b/crates/apub/src/protocol/objects/tombstone.rs index 6eb812179..0e60d6247 100644 --- a/crates/apub/src/protocol/objects/tombstone.rs +++ b/crates/apub/src/protocol/objects/tombstone.rs @@ -1,3 +1,4 @@ +use crate::protocol::Id; use activitystreams_kinds::object::TombstoneType; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -9,7 +10,7 @@ use url::Url; pub struct Tombstone { pub(crate) id: Url, #[serde(rename = "type")] - kind: TombstoneType, + pub(crate) kind: TombstoneType, } impl Tombstone { @@ -20,3 +21,9 @@ impl Tombstone { } } } + +impl Id for Tombstone { + fn id(&self) -> &Url { + &self.id + } +} diff --git a/crates/apub_lib/src/lib.rs b/crates/apub_lib/src/lib.rs index 3c11fcea1..7c110169d 100644 --- a/crates/apub_lib/src/lib.rs +++ b/crates/apub_lib/src/lib.rs @@ -3,6 +3,7 @@ pub mod data; pub mod object_id; pub mod signatures; pub mod traits; +pub mod utils; pub mod values; pub mod verify; diff --git a/crates/apub_lib/src/object_id.rs b/crates/apub_lib/src/object_id.rs index c68b5b37a..da3896160 100644 --- a/crates/apub_lib/src/object_id.rs +++ b/crates/apub_lib/src/object_id.rs @@ -1,16 +1,14 @@ -use crate::{traits::ApubObject, APUB_JSON_CONTENT_TYPE}; +use crate::{traits::ApubObject, utils::fetch_object_http}; use anyhow::anyhow; use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc}; use diesel::NotFound; -use lemmy_utils::{request::retry, settings::structs::Settings, LemmyError, REQWEST_TIMEOUT}; -use reqwest::StatusCode; +use lemmy_utils::{settings::structs::Settings, LemmyError}; use reqwest_middleware::ClientWithMiddleware; use serde::{Deserialize, Serialize}; use std::{ fmt::{Debug, Display, Formatter}, marker::PhantomData, }; -use tracing::info; use url::Url; /// We store Url on the heap because it is quite large (88 bytes). @@ -37,6 +35,10 @@ where &self.0 } + pub fn into_inner(self) -> Url { + *self.0 + } + /// Fetches an activitypub object, either from local database (if possible), or over http. pub async fn dereference( &self, @@ -100,32 +102,19 @@ where request_counter: &mut i32, db_object: Option, ) -> Result { - // dont fetch local objects this way - debug_assert!(self.0.domain() != Some(&Settings::get().hostname)); - info!("Fetching remote object {}", self.to_string()); + let res = fetch_object_http(&self.0, client, request_counter).await; - *request_counter += 1; - if *request_counter > Settings::get().http_fetch_retry_limit { - return Err(LemmyError::from(anyhow!("Request retry limit reached"))); - } - - let res = retry(|| { - client - .get(self.0.as_str()) - .header("Accept", APUB_JSON_CONTENT_TYPE) - .timeout(REQWEST_TIMEOUT) - .send() - }) - .await?; - - if res.status() == StatusCode::GONE { - if let Some(db_object) = db_object { - db_object.delete(data).await?; + if let Err(e) = &res { + // TODO: very ugly + if e.message == Some("410".to_string()) { + if let Some(db_object) = db_object { + db_object.delete(data).await?; + } + return Err(anyhow!("Fetched remote object {} which was deleted", self).into()); } - return Err(anyhow!("Fetched remote object {} which was deleted", self).into()); } - let res2: Kind::ApubType = res.json().await?; + let res2 = res?; Kind::verify(&res2, self.inner(), data, request_counter).await?; Kind::from_apub(res2, data, request_counter).await diff --git a/crates/apub_lib/src/utils.rs b/crates/apub_lib/src/utils.rs new file mode 100644 index 000000000..391e01dfb --- /dev/null +++ b/crates/apub_lib/src/utils.rs @@ -0,0 +1,38 @@ +use crate::APUB_JSON_CONTENT_TYPE; +use anyhow::anyhow; +use http::StatusCode; +use lemmy_utils::{request::retry, settings::structs::Settings, LemmyError, REQWEST_TIMEOUT}; +use reqwest_middleware::ClientWithMiddleware; +use serde::de::DeserializeOwned; +use tracing::log::info; +use url::Url; + +pub async fn fetch_object_http( + url: &Url, + client: &ClientWithMiddleware, + request_counter: &mut i32, +) -> Result { + // dont fetch local objects this way + debug_assert!(url.domain() != Some(&Settings::get().hostname)); + info!("Fetching remote object {}", url.to_string()); + + *request_counter += 1; + if *request_counter > Settings::get().http_fetch_retry_limit { + return Err(LemmyError::from(anyhow!("Request retry limit reached"))); + } + + let res = retry(|| { + client + .get(url.as_str()) + .header("Accept", APUB_JSON_CONTENT_TYPE) + .timeout(REQWEST_TIMEOUT) + .send() + }) + .await?; + + if res.status() == StatusCode::GONE { + return Err(LemmyError::from_message("410")); + } + + Ok(res.json().await?) +} diff --git a/crates/apub_lib/src/values.rs b/crates/apub_lib/src/values.rs index bd014c12b..bf1780a81 100644 --- a/crates/apub_lib/src/values.rs +++ b/crates/apub_lib/src/values.rs @@ -42,7 +42,7 @@ pub enum MediaTypeMarkdown { Markdown, } -/// Media type for HTML text/ +/// Media type for HTML text. /// /// #[derive(Clone, Debug, Deserialize, Serialize)] @@ -50,3 +50,11 @@ pub enum MediaTypeHtml { #[serde(rename = "text/html")] Html, } +/// Media type which allows both markdown and HTML. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub enum MediaTypeMarkdownOrHtml { + #[serde(rename = "text/markdown")] + Markdown, + #[serde(rename = "text/html")] + Html, +}