Federate with Peertube (#2244)
This commit is contained in:
parent
3053e14be7
commit
7b86441bab
35 changed files with 1270 additions and 144 deletions
107
crates/apub/assets/peertube/activities/announce_video.json
Normal file
107
crates/apub/assets/peertube/activities/announce_video.json
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
131
crates/apub/assets/peertube/objects/group.json
Normal file
131
crates/apub/assets/peertube/objects/group.json
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
119
crates/apub/assets/peertube/objects/note.json
Normal file
119
crates/apub/assets/peertube/objects/note.json
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
124
crates/apub/assets/peertube/objects/person.json
Normal file
124
crates/apub/assets/peertube/objects/person.json
Normal file
|
@ -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
|
||||||
|
}
|
493
crates/apub/assets/peertube/objects/video.json
Normal file
493
crates/apub/assets/peertube/objects/video.json
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -4,7 +4,10 @@ use crate::{
|
||||||
http::ActivityCommonFields,
|
http::ActivityCommonFields,
|
||||||
insert_activity,
|
insert_activity,
|
||||||
objects::community::ApubCommunity,
|
objects::community::ApubCommunity,
|
||||||
protocol::activities::{community::announce::AnnounceActivity, CreateOrUpdateType},
|
protocol::{
|
||||||
|
activities::{community::announce::AnnounceActivity, CreateOrUpdateType},
|
||||||
|
IdOrNestedObject,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use activitystreams_kinds::{activity::AnnounceType, public};
|
use activitystreams_kinds::{activity::AnnounceType, public};
|
||||||
use lemmy_apub_lib::{
|
use lemmy_apub_lib::{
|
||||||
|
@ -34,7 +37,7 @@ impl AnnounceActivity {
|
||||||
Ok(AnnounceActivity {
|
Ok(AnnounceActivity {
|
||||||
actor: ObjectId::new(community.actor_id()),
|
actor: ObjectId::new(community.actor_id()),
|
||||||
to: vec![public()],
|
to: vec![public()],
|
||||||
object,
|
object: IdOrNestedObject::NestedObject(object),
|
||||||
cc: vec![community.followers_url.clone().into()],
|
cc: vec![community.followers_url.clone().into()],
|
||||||
kind: AnnounceType::Announce,
|
kind: AnnounceType::Announce,
|
||||||
id: generate_activity_id(
|
id: generate_activity_id(
|
||||||
|
@ -92,11 +95,10 @@ impl ActivityHandler for AnnounceActivity {
|
||||||
async fn verify(
|
async fn verify(
|
||||||
&self,
|
&self,
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
verify_is_public(&self.to, &self.cc)?;
|
verify_is_public(&self.to, &self.cc)?;
|
||||||
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
|
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
|
||||||
self.object.verify(context, request_counter).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,12 +108,16 @@ impl ActivityHandler for AnnounceActivity {
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> 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
|
// 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.
|
// Dont insert these into activities table, as they are not activities.
|
||||||
AnnouncableActivities::Page(_) => {}
|
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 object_data: ActivityCommonFields = serde_json::from_value(object_value.to_owned())?;
|
||||||
|
|
||||||
let insert =
|
let insert =
|
||||||
|
@ -125,6 +131,6 @@ impl ActivityHandler for AnnounceActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.object.receive(context, request_counter).await
|
object.receive(context, request_counter).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ impl ActivityHandler for CreateOrUpdatePost {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
CreateOrUpdateType::Create => {
|
CreateOrUpdateType::Create => {
|
||||||
verify_domains_match(self.actor.inner(), self.object.id.inner())?;
|
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.
|
// 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
|
// 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,
|
// 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?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
verify_domains_match(self.actor.inner(), self.object.id.inner())?;
|
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())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,11 @@ use crate::{
|
||||||
verify_activity,
|
verify_activity,
|
||||||
},
|
},
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
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 activitystreams_kinds::activity::DeleteType;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
@ -106,7 +110,7 @@ impl Delete {
|
||||||
Ok(Delete {
|
Ok(Delete {
|
||||||
actor: ObjectId::new(actor.actor_id.clone()),
|
actor: ObjectId::new(actor.actor_id.clone()),
|
||||||
to: vec![to],
|
to: vec![to],
|
||||||
object: IdOrNestedObject::NestedObject(NestedObject {
|
object: IdOrNestedObject::NestedObject(Tombstone {
|
||||||
id: object.id(),
|
id: object.id(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -25,12 +25,14 @@ use crate::{
|
||||||
voting::{undo_vote::UndoVote, vote::Vote},
|
voting::{undo_vote::UndoVote, vote::Vote},
|
||||||
},
|
},
|
||||||
objects::page::Page,
|
objects::page::Page,
|
||||||
|
Id,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_apub_lib::traits::ActivityHandler;
|
use lemmy_apub_lib::traits::ActivityHandler;
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
|
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
@ -120,3 +122,23 @@ impl GetCommunity for AnnouncableActivities {
|
||||||
Ok(community)
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -184,10 +184,7 @@ mod tests {
|
||||||
file_to_json_object("assets/lemmy/collections/group_moderators.json").unwrap();
|
file_to_json_object("assets/lemmy/collections/group_moderators.json").unwrap();
|
||||||
let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap();
|
let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap();
|
||||||
let mut request_counter = 0;
|
let mut request_counter = 0;
|
||||||
let community_context = CommunityContext {
|
let community_context = CommunityContext(community, context);
|
||||||
0: community,
|
|
||||||
1: context,
|
|
||||||
};
|
|
||||||
ApubCommunityModerators::verify(&json, &url, &community_context, &mut request_counter)
|
ApubCommunityModerators::verify(&json, &url, &community_context, &mut request_counter)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -6,7 +6,7 @@ use chrono::NaiveDateTime;
|
||||||
use lemmy_apub_lib::traits::{ActorType, ApubObject};
|
use lemmy_apub_lib::traits::{ActorType, ApubObject};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -15,13 +15,19 @@ pub enum UserOrCommunity {
|
||||||
Community(ApubCommunity),
|
Community(ApubCommunity),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum PersonOrGroup {
|
pub enum PersonOrGroup {
|
||||||
Person(Person),
|
Person(Person),
|
||||||
Group(Group),
|
Group(Group),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
|
pub enum PersonOrGroupType {
|
||||||
|
Person,
|
||||||
|
Group,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ApubObject for UserOrCommunity {
|
impl ApubObject for UserOrCommunity {
|
||||||
type DataType = LemmyContext;
|
type DataType = LemmyContext;
|
||||||
|
|
|
@ -15,7 +15,7 @@ use lemmy_api_common::utils::blocking;
|
||||||
use lemmy_apub_lib::{
|
use lemmy_apub_lib::{
|
||||||
object_id::ObjectId,
|
object_id::ObjectId,
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
values::MediaTypeHtml,
|
values::MediaTypeMarkdownOrHtml,
|
||||||
verify::verify_domains_match,
|
verify::verify_domains_match,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
@ -117,7 +117,7 @@ impl ApubObject for ApubComment {
|
||||||
to: vec![public()],
|
to: vec![public()],
|
||||||
cc: maa.ccs,
|
cc: maa.ccs,
|
||||||
content: markdown_to_html(&self.content),
|
content: markdown_to_html(&self.content),
|
||||||
media_type: Some(MediaTypeHtml::Html),
|
media_type: Some(MediaTypeMarkdownOrHtml::Html),
|
||||||
source: Some(Source::new(self.content.clone())),
|
source: Some(Source::new(self.content.clone())),
|
||||||
in_reply_to,
|
in_reply_to,
|
||||||
published: Some(convert_datetime(self.published)),
|
published: Some(convert_datetime(self.published)),
|
||||||
|
@ -178,7 +178,7 @@ impl ApubObject for ApubComment {
|
||||||
.await?;
|
.await?;
|
||||||
let (post, parent_comment_id) = note.get_parents(context, request_counter).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 content_slurs_removed = remove_slurs(&content, &context.settings().slur_regex());
|
||||||
|
|
||||||
let form = CommentForm {
|
let form = CommentForm {
|
||||||
|
|
|
@ -124,7 +124,11 @@ impl ApubObject for ApubSite {
|
||||||
) -> Result<Self, LemmyError> {
|
) -> Result<Self, LemmyError> {
|
||||||
let site_form = SiteForm {
|
let site_form = SiteForm {
|
||||||
name: apub.name.clone(),
|
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()),
|
updated: apub.updated.map(|u| u.clone().naive_local()),
|
||||||
icon: Some(apub.icon.clone().map(|i| i.url.into())),
|
icon: Some(apub.icon.clone().map(|i| i.url.into())),
|
||||||
banner: Some(apub.image.clone().map(|i| i.url.into())),
|
banner: Some(apub.image.clone().map(|i| i.url.into())),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::protocol::{ImageObject, Source};
|
use crate::protocol::{ImageObject, Source};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use html2md::parse_html;
|
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 lemmy_utils::{settings::structs::Settings, LemmyError};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -12,23 +12,31 @@ pub mod person;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
|
||||||
pub(crate) fn read_from_string_or_source(raw: &str, source: &Option<Source>) -> String {
|
pub(crate) fn read_from_string_or_source(
|
||||||
|
content: &str,
|
||||||
|
media_type: &Option<MediaTypeMarkdownOrHtml>,
|
||||||
|
source: &Option<Source>,
|
||||||
|
) -> String {
|
||||||
if let Some(s) = source {
|
if let Some(s) = source {
|
||||||
|
// markdown sent by lemmy in source field
|
||||||
s.content.clone()
|
s.content.clone()
|
||||||
|
} else if media_type == &Some(MediaTypeMarkdownOrHtml::Markdown) {
|
||||||
|
// markdown sent by peertube in content field
|
||||||
|
content.to_string()
|
||||||
} else {
|
} else {
|
||||||
parse_html(raw)
|
// otherwise, convert content html to markdown
|
||||||
|
parse_html(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn read_from_string_or_source_opt(
|
pub(crate) fn read_from_string_or_source_opt(
|
||||||
raw: &Option<String>,
|
content: &Option<String>,
|
||||||
|
media_type: &Option<MediaTypeMarkdownOrHtml>,
|
||||||
source: &Option<Source>,
|
source: &Option<Source>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
if let Some(s2) = source {
|
content
|
||||||
Some(s2.content.clone())
|
.as_ref()
|
||||||
} else {
|
.map(|content| read_from_string_or_source(content, media_type, source))
|
||||||
raw.as_ref().map(|s| parse_html(s))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn verify_image_domain_matches(
|
pub(crate) fn verify_image_domain_matches(
|
||||||
|
|
|
@ -134,7 +134,7 @@ impl ApubObject for ApubPerson {
|
||||||
let slur_regex = &context.settings().slur_regex();
|
let slur_regex = &context.settings().slur_regex();
|
||||||
check_slurs(&person.preferred_username, slur_regex)?;
|
check_slurs(&person.preferred_username, slur_regex)?;
|
||||||
check_slurs_opt(&person.name, 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)?;
|
check_slurs_opt(&bio, slur_regex)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -158,6 +158,7 @@ impl ApubObject for ApubPerson {
|
||||||
actor_id: Some(person.id.into()),
|
actor_id: Some(person.id.into()),
|
||||||
bio: Some(read_from_string_or_source_opt(
|
bio: Some(read_from_string_or_source_opt(
|
||||||
&person.summary,
|
&person.summary,
|
||||||
|
&None,
|
||||||
&person.source,
|
&person.source,
|
||||||
)),
|
)),
|
||||||
local: Some(false),
|
local: Some(false),
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
objects::{read_from_string_or_source_opt, verify_is_remote_object},
|
objects::{read_from_string_or_source_opt, verify_is_remote_object},
|
||||||
protocol::{
|
protocol::{
|
||||||
objects::{
|
objects::{
|
||||||
page::{Attachment, Page, PageType},
|
page::{Attachment, AttributedTo, Page, PageType},
|
||||||
tombstone::Tombstone,
|
tombstone::Tombstone,
|
||||||
},
|
},
|
||||||
ImageObject,
|
ImageObject,
|
||||||
|
@ -17,7 +17,7 @@ use lemmy_api_common::{request::fetch_site_data, utils::blocking};
|
||||||
use lemmy_apub_lib::{
|
use lemmy_apub_lib::{
|
||||||
object_id::ObjectId,
|
object_id::ObjectId,
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
values::MediaTypeHtml,
|
values::MediaTypeMarkdownOrHtml,
|
||||||
verify::verify_domains_match,
|
verify::verify_domains_match,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
@ -102,14 +102,14 @@ impl ApubObject for ApubPost {
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let page = Page {
|
let page = Page {
|
||||||
r#type: PageType::Page,
|
kind: PageType::Page,
|
||||||
id: ObjectId::new(self.ap_id.clone()),
|
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()],
|
to: vec![community.actor_id.into(), public()],
|
||||||
cc: vec![],
|
cc: vec![],
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
content: self.body.as_ref().map(|b| markdown_to_html(b)),
|
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),
|
source: self.body.clone().map(Source::new),
|
||||||
url: self.url.clone().map(|u| u.into()),
|
url: self.url.clone().map(|u| u.into()),
|
||||||
attachment: self.url.clone().map(Attachment::new).into_iter().collect(),
|
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?;
|
let community = page.extract_community(context, request_counter).await?;
|
||||||
check_is_apub_id_valid(page.id.inner(), community.local, &context.settings())?;
|
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())?;
|
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)?;
|
verify_is_public(&page.to, &page.cc)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -157,15 +157,20 @@ impl ApubObject for ApubPost {
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubPost, LemmyError> {
|
) -> Result<ApubPost, LemmyError> {
|
||||||
let creator = page
|
let creator = page
|
||||||
.attributed_to
|
.creator()?
|
||||||
.dereference(context, context.client(), request_counter)
|
.dereference(context, context.client(), request_counter)
|
||||||
.await?;
|
.await?;
|
||||||
let community = page.extract_community(context, request_counter).await?;
|
let community = page.extract_community(context, request_counter).await?;
|
||||||
|
|
||||||
let form = if !page.is_mod_action(context).await? {
|
let form = if !page.is_mod_action(context).await? {
|
||||||
let url = if let Some(attachment) = page.attachment.first() {
|
let url = if let Some(attachment) = page.attachment.first() {
|
||||||
|
// url as sent by Lemmy (new)
|
||||||
Some(attachment.href.clone())
|
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 {
|
} else {
|
||||||
|
// url sent by lemmy (old)
|
||||||
page.url
|
page.url
|
||||||
};
|
};
|
||||||
let thumbnail_url: Option<Url> = page.image.map(|i| i.url);
|
let thumbnail_url: Option<Url> = page.image.map(|i| i.url);
|
||||||
|
@ -177,7 +182,8 @@ impl ApubObject for ApubPost {
|
||||||
let (embed_title, embed_description, embed_html) = metadata_res
|
let (embed_title, embed_description, embed_html) = metadata_res
|
||||||
.map(|u| (u.title, u.description, u.html))
|
.map(|u| (u.title, u.description, u.html))
|
||||||
.unwrap_or((None, None, None));
|
.unwrap_or((None, None, None));
|
||||||
let body_slurs_removed = read_from_string_or_source_opt(&page.content, &page.source)
|
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()));
|
.map(|s| remove_slurs(&s, &context.settings().slur_regex()));
|
||||||
|
|
||||||
PostForm {
|
PostForm {
|
||||||
|
|
|
@ -137,7 +137,7 @@ impl ApubObject for ApubPrivateMessage {
|
||||||
let form = PrivateMessageForm {
|
let form = PrivateMessageForm {
|
||||||
creator_id: creator.id,
|
creator_id: creator.id,
|
||||||
recipient_id: recipient.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()),
|
published: note.published.map(|u| u.naive_local()),
|
||||||
updated: note.updated.map(|u| u.naive_local()),
|
updated: note.updated.map(|u| u.naive_local()),
|
||||||
deleted: None,
|
deleted: None,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activity_lists::AnnouncableActivities,
|
activity_lists::AnnouncableActivities,
|
||||||
objects::community::ApubCommunity,
|
objects::community::ApubCommunity,
|
||||||
protocol::Unparsed,
|
protocol::{IdOrNestedObject, Unparsed},
|
||||||
};
|
};
|
||||||
use activitystreams_kinds::activity::AnnounceType;
|
use activitystreams_kinds::activity::AnnounceType;
|
||||||
use lemmy_apub_lib::object_id::ObjectId;
|
use lemmy_apub_lib::object_id::ObjectId;
|
||||||
|
@ -14,7 +14,7 @@ pub struct AnnounceActivity {
|
||||||
pub(crate) actor: ObjectId<ApubCommunity>,
|
pub(crate) actor: ObjectId<ApubCommunity>,
|
||||||
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
||||||
pub(crate) to: Vec<Url>,
|
pub(crate) to: Vec<Url>,
|
||||||
pub(crate) object: AnnouncableActivities,
|
pub(crate) object: IdOrNestedObject<AnnouncableActivities>,
|
||||||
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
||||||
pub(crate) cc: Vec<Url>,
|
pub(crate) cc: Vec<Url>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use crate::{objects::person::ApubPerson, protocol::Unparsed};
|
use crate::{
|
||||||
use activitystreams_kinds::{activity::DeleteType, object::TombstoneType};
|
objects::person::ApubPerson,
|
||||||
|
protocol::{objects::tombstone::Tombstone, IdOrNestedObject, Unparsed},
|
||||||
|
};
|
||||||
|
use activitystreams_kinds::activity::DeleteType;
|
||||||
use lemmy_apub_lib::object_id::ObjectId;
|
use lemmy_apub_lib::object_id::ObjectId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
|
@ -12,7 +15,7 @@ pub struct Delete {
|
||||||
pub(crate) actor: ObjectId<ApubPerson>,
|
pub(crate) actor: ObjectId<ApubPerson>,
|
||||||
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
||||||
pub(crate) to: Vec<Url>,
|
pub(crate) to: Vec<Url>,
|
||||||
pub(crate) object: IdOrNestedObject,
|
pub(crate) object: IdOrNestedObject<Tombstone>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub(crate) kind: DeleteType,
|
pub(crate) kind: DeleteType,
|
||||||
pub(crate) id: Url,
|
pub(crate) id: Url,
|
||||||
|
@ -27,29 +30,3 @@ pub struct Delete {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub(crate) unparsed: Unparsed,
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,8 +17,7 @@ pub struct DeleteUser {
|
||||||
pub(crate) kind: DeleteType,
|
pub(crate) kind: DeleteType,
|
||||||
pub(crate) id: Url,
|
pub(crate) id: Url,
|
||||||
|
|
||||||
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
#[serde(deserialize_with = "crate::deserialize_one_or_many", default)]
|
||||||
#[serde(default)]
|
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
pub(crate) cc: Vec<Url>,
|
pub(crate) cc: Vec<Url>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,7 @@ pub struct UndoDelete {
|
||||||
pub(crate) kind: UndoType,
|
pub(crate) kind: UndoType,
|
||||||
pub(crate) id: Url,
|
pub(crate) id: Url,
|
||||||
|
|
||||||
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
#[serde(deserialize_with = "crate::deserialize_one_or_many", default)]
|
||||||
#[serde(default)]
|
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
pub(crate) cc: Vec<Url>,
|
pub(crate) cc: Vec<Url>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub enum CreateOrUpdateType {
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
activities::{
|
activities::{
|
||||||
|
community::announce::AnnounceActivity,
|
||||||
create_or_update::{comment::CreateOrUpdateComment, post::CreateOrUpdatePost},
|
create_or_update::{comment::CreateOrUpdateComment, post::CreateOrUpdatePost},
|
||||||
deletion::delete::Delete,
|
deletion::delete::Delete,
|
||||||
following::{follow::FollowCommunity, undo_follow::UndoFollowCommunity},
|
following::{follow::FollowCommunity, undo_follow::UndoFollowCommunity},
|
||||||
|
@ -70,4 +71,9 @@ mod tests {
|
||||||
test_json::<CreateOrUpdateComment>("assets/gnusocial/activities/create_note.json").unwrap();
|
test_json::<CreateOrUpdateComment>("assets/gnusocial/activities/create_note.json").unwrap();
|
||||||
test_json::<Vote>("assets/gnusocial/activities/like_note.json").unwrap();
|
test_json::<Vote>("assets/gnusocial/activities/like_note.json").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_peertube_activities() {
|
||||||
|
test_json::<AnnounceActivity>("assets/peertube/activities/announce_video.json").unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use activitystreams_kinds::object::ImageType;
|
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 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 std::collections::HashMap;
|
||||||
use url::Url;
|
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)]
|
#[serde(transparent)]
|
||||||
pub struct Unparsed(HashMap<String, serde_json::Value>);
|
pub struct Unparsed(HashMap<String, serde_json::Value>);
|
||||||
|
|
||||||
|
pub(crate) trait Id {
|
||||||
|
fn id(&self) -> &Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub(crate) enum IdOrNestedObject<Kind: Id> {
|
||||||
|
Id(Url),
|
||||||
|
NestedObject(Kind),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Kind: Id + DeserializeOwned> IdOrNestedObject<Kind> {
|
||||||
|
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<Kind, LemmyError> {
|
||||||
|
match self {
|
||||||
|
IdOrNestedObject::Id(i) => fetch_object_http(&i, context.client(), request_counter).await,
|
||||||
|
IdOrNestedObject::NestedObject(o) => Ok(o),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use crate::context::WithContext;
|
use crate::context::WithContext;
|
||||||
|
|
|
@ -19,8 +19,7 @@ pub struct ChatMessage {
|
||||||
pub(crate) content: String,
|
pub(crate) content: String,
|
||||||
|
|
||||||
pub(crate) media_type: Option<MediaTypeHtml>,
|
pub(crate) media_type: Option<MediaTypeHtml>,
|
||||||
#[serde(default)]
|
#[serde(deserialize_with = "crate::deserialize_skip_error", default)]
|
||||||
#[serde(deserialize_with = "crate::deserialize_skip_error")]
|
|
||||||
pub(crate) source: Option<Source>,
|
pub(crate) source: Option<Source>,
|
||||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||||
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
||||||
|
|
|
@ -40,8 +40,7 @@ pub struct Group {
|
||||||
/// title
|
/// title
|
||||||
pub(crate) name: Option<String>,
|
pub(crate) name: Option<String>,
|
||||||
pub(crate) summary: Option<String>,
|
pub(crate) summary: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(deserialize_with = "crate::deserialize_skip_error", default)]
|
||||||
#[serde(deserialize_with = "crate::deserialize_skip_error")]
|
|
||||||
pub(crate) source: Option<Source>,
|
pub(crate) source: Option<Source>,
|
||||||
pub(crate) icon: Option<ImageObject>,
|
pub(crate) icon: Option<ImageObject>,
|
||||||
/// banner
|
/// banner
|
||||||
|
@ -72,7 +71,7 @@ impl Group {
|
||||||
let slur_regex = &context.settings().slur_regex();
|
let slur_regex = &context.settings().slur_regex();
|
||||||
check_slurs(&self.preferred_username, slur_regex)?;
|
check_slurs(&self.preferred_username, slur_regex)?;
|
||||||
check_slurs_opt(&self.name, 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)?;
|
check_slurs_opt(&description, slur_regex)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -81,7 +80,7 @@ impl Group {
|
||||||
CommunityForm {
|
CommunityForm {
|
||||||
name: self.preferred_username.clone(),
|
name: self.preferred_username.clone(),
|
||||||
title: self.name.unwrap_or(self.preferred_username),
|
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,
|
removed: None,
|
||||||
published: self.published.map(|u| u.naive_local()),
|
published: self.published.map(|u| u.naive_local()),
|
||||||
updated: self.updated.map(|u| u.naive_local()),
|
updated: self.updated.map(|u| u.naive_local()),
|
||||||
|
|
|
@ -30,8 +30,7 @@ pub struct Instance {
|
||||||
|
|
||||||
// sidebar
|
// sidebar
|
||||||
pub(crate) content: Option<String>,
|
pub(crate) content: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(deserialize_with = "crate::deserialize_skip_error", default)]
|
||||||
#[serde(deserialize_with = "crate::deserialize_skip_error")]
|
|
||||||
pub(crate) source: Option<Source>,
|
pub(crate) source: Option<Source>,
|
||||||
// short instance description
|
// short instance description
|
||||||
pub(crate) summary: Option<String>,
|
pub(crate) summary: Option<String>,
|
||||||
|
|
|
@ -86,4 +86,12 @@ mod tests {
|
||||||
test_json::<Page>("assets/gnusocial/objects/page.json").unwrap();
|
test_json::<Page>("assets/gnusocial/objects/page.json").unwrap();
|
||||||
test_json::<Note>("assets/gnusocial/objects/note.json").unwrap();
|
test_json::<Note>("assets/gnusocial/objects/note.json").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_object_peertube() {
|
||||||
|
test_json::<Person>("assets/peertube/objects/person.json").unwrap();
|
||||||
|
test_json::<Group>("assets/peertube/objects/group.json").unwrap();
|
||||||
|
test_json::<Page>("assets/peertube/objects/video.json").unwrap();
|
||||||
|
test_json::<Note>("assets/peertube/objects/note.json").unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
use activitystreams_kinds::object::NoteType;
|
use activitystreams_kinds::object::NoteType;
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use lemmy_api_common::utils::blocking;
|
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_db_schema::{newtypes::CommentId, source::post::Post, traits::Crud};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
@ -25,15 +25,13 @@ pub struct Note {
|
||||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
||||||
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
||||||
pub(crate) to: Vec<Url>,
|
pub(crate) to: Vec<Url>,
|
||||||
#[serde(default)]
|
#[serde(deserialize_with = "crate::deserialize_one_or_many", default)]
|
||||||
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
|
||||||
pub(crate) cc: Vec<Url>,
|
pub(crate) cc: Vec<Url>,
|
||||||
pub(crate) content: String,
|
pub(crate) content: String,
|
||||||
pub(crate) in_reply_to: ObjectId<PostOrComment>,
|
pub(crate) in_reply_to: ObjectId<PostOrComment>,
|
||||||
|
|
||||||
pub(crate) media_type: Option<MediaTypeHtml>,
|
pub(crate) media_type: Option<MediaTypeMarkdownOrHtml>,
|
||||||
#[serde(default)]
|
#[serde(deserialize_with = "crate::deserialize_skip_error", default)]
|
||||||
#[serde(deserialize_with = "crate::deserialize_skip_error")]
|
|
||||||
pub(crate) source: Option<Source>,
|
pub(crate) source: Option<Source>,
|
||||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||||
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
fetcher::user_or_community::{PersonOrGroupType, UserOrCommunity},
|
||||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||||
protocol::{ImageObject, Source},
|
protocol::{ImageObject, Source},
|
||||||
};
|
};
|
||||||
|
@ -9,7 +10,7 @@ use lemmy_apub_lib::{
|
||||||
data::Data,
|
data::Data,
|
||||||
object_id::ObjectId,
|
object_id::ObjectId,
|
||||||
traits::{ActivityHandler, ApubObject},
|
traits::{ActivityHandler, ApubObject},
|
||||||
values::MediaTypeHtml,
|
values::MediaTypeMarkdownOrHtml,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::newtypes::DbUrl;
|
use lemmy_db_schema::newtypes::DbUrl;
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
|
@ -18,33 +19,34 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub enum PageType {
|
pub enum PageType {
|
||||||
Page,
|
Page,
|
||||||
Article,
|
Article,
|
||||||
Note,
|
Note,
|
||||||
|
Video,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
pub(crate) r#type: PageType,
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) kind: PageType,
|
||||||
pub(crate) id: ObjectId<ApubPost>,
|
pub(crate) id: ObjectId<ApubPost>,
|
||||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
pub(crate) attributed_to: AttributedTo,
|
||||||
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
||||||
pub(crate) to: Vec<Url>,
|
pub(crate) to: Vec<Url>,
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(deserialize_with = "crate::deserialize_one_or_many", default)]
|
||||||
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
|
|
||||||
pub(crate) cc: Vec<Url>,
|
pub(crate) cc: Vec<Url>,
|
||||||
pub(crate) content: Option<String>,
|
pub(crate) content: Option<String>,
|
||||||
pub(crate) media_type: Option<MediaTypeHtml>,
|
pub(crate) media_type: Option<MediaTypeMarkdownOrHtml>,
|
||||||
#[serde(default)]
|
#[serde(deserialize_with = "crate::deserialize_skip_error", default)]
|
||||||
#[serde(deserialize_with = "crate::deserialize_skip_error")]
|
|
||||||
pub(crate) source: Option<Source>,
|
pub(crate) source: Option<Source>,
|
||||||
/// deprecated, use attachment field
|
/// deprecated, use attachment field
|
||||||
|
#[serde(deserialize_with = "crate::deserialize_skip_error", default)]
|
||||||
pub(crate) url: Option<Url>,
|
pub(crate) url: Option<Url>,
|
||||||
/// most software uses array type for attachment field, so we do the same. nevertheless, we only
|
/// most software uses array type for attachment field, so we do the same. nevertheless, we only
|
||||||
/// use the first item
|
/// use the first item
|
||||||
|
@ -60,11 +62,26 @@ pub struct Page {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Attachment {
|
pub(crate) struct Attachment {
|
||||||
pub(crate) href: Url,
|
pub(crate) href: Url,
|
||||||
pub(crate) r#type: LinkType,
|
pub(crate) r#type: LinkType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub(crate) enum AttributedTo {
|
||||||
|
Lemmy(ObjectId<ApubPerson>),
|
||||||
|
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<UserOrCommunity>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
/// Only mods can change the post's stickied/locked status. So if either of these is changed from
|
/// 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.
|
/// the current value, it is a mod action and needs to be verified as such.
|
||||||
|
@ -111,6 +128,8 @@ impl Page {
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> Result<ApubCommunity, LemmyError> {
|
||||||
|
match &self.attributed_to {
|
||||||
|
AttributedTo::Lemmy(_) => {
|
||||||
let mut iter = self.to.iter().merge(self.cc.iter());
|
let mut iter = self.to.iter().merge(self.cc.iter());
|
||||||
loop {
|
loop {
|
||||||
if let Some(cid) = iter.next() {
|
if let Some(cid) = iter.next() {
|
||||||
|
@ -126,6 +145,27 @@ impl Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AttributedTo::Peertube(p) => {
|
||||||
|
p.iter()
|
||||||
|
.find(|a| a.kind == PersonOrGroupType::Group)
|
||||||
|
.map(|a| ObjectId::<ApubCommunity>::new(a.id.clone().into_inner()))
|
||||||
|
.ok_or_else(|| LemmyError::from_message("page does not specify group"))?
|
||||||
|
.dereference(context, context.client(), request_counter)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn creator(&self) -> Result<ObjectId<ApubPerson>, 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::<ApubPerson>::new(a.id.clone().into_inner()))
|
||||||
|
.ok_or_else(|| LemmyError::from_message("page does not specify creator person")),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Attachment {
|
impl Attachment {
|
||||||
|
|
|
@ -32,8 +32,7 @@ pub struct Person {
|
||||||
/// displayname
|
/// displayname
|
||||||
pub(crate) name: Option<String>,
|
pub(crate) name: Option<String>,
|
||||||
pub(crate) summary: Option<String>,
|
pub(crate) summary: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(deserialize_with = "crate::deserialize_skip_error", default)]
|
||||||
#[serde(deserialize_with = "crate::deserialize_skip_error")]
|
|
||||||
pub(crate) source: Option<Source>,
|
pub(crate) source: Option<Source>,
|
||||||
/// user avatar
|
/// user avatar
|
||||||
pub(crate) icon: Option<ImageObject>,
|
pub(crate) icon: Option<ImageObject>,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::protocol::Id;
|
||||||
use activitystreams_kinds::object::TombstoneType;
|
use activitystreams_kinds::object::TombstoneType;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
|
@ -9,7 +10,7 @@ use url::Url;
|
||||||
pub struct Tombstone {
|
pub struct Tombstone {
|
||||||
pub(crate) id: Url,
|
pub(crate) id: Url,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
kind: TombstoneType,
|
pub(crate) kind: TombstoneType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tombstone {
|
impl Tombstone {
|
||||||
|
@ -20,3 +21,9 @@ impl Tombstone {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Id for Tombstone {
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ pub mod data;
|
||||||
pub mod object_id;
|
pub mod object_id;
|
||||||
pub mod signatures;
|
pub mod signatures;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
pub mod utils;
|
||||||
pub mod values;
|
pub mod values;
|
||||||
pub mod verify;
|
pub mod verify;
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
use crate::{traits::ApubObject, APUB_JSON_CONTENT_TYPE};
|
use crate::{traits::ApubObject, utils::fetch_object_http};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc};
|
use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc};
|
||||||
use diesel::NotFound;
|
use diesel::NotFound;
|
||||||
use lemmy_utils::{request::retry, settings::structs::Settings, LemmyError, REQWEST_TIMEOUT};
|
use lemmy_utils::{settings::structs::Settings, LemmyError};
|
||||||
use reqwest::StatusCode;
|
|
||||||
use reqwest_middleware::ClientWithMiddleware;
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display, Formatter},
|
fmt::{Debug, Display, Formatter},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
};
|
};
|
||||||
use tracing::info;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// We store Url on the heap because it is quite large (88 bytes).
|
/// We store Url on the heap because it is quite large (88 bytes).
|
||||||
|
@ -37,6 +35,10 @@ where
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> Url {
|
||||||
|
*self.0
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetches an activitypub object, either from local database (if possible), or over http.
|
/// Fetches an activitypub object, either from local database (if possible), or over http.
|
||||||
pub async fn dereference(
|
pub async fn dereference(
|
||||||
&self,
|
&self,
|
||||||
|
@ -100,32 +102,19 @@ where
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
db_object: Option<Kind>,
|
db_object: Option<Kind>,
|
||||||
) -> Result<Kind, LemmyError> {
|
) -> Result<Kind, LemmyError> {
|
||||||
// dont fetch local objects this way
|
let res = fetch_object_http(&self.0, client, request_counter).await;
|
||||||
debug_assert!(self.0.domain() != Some(&Settings::get().hostname));
|
|
||||||
info!("Fetching remote object {}", self.to_string());
|
|
||||||
|
|
||||||
*request_counter += 1;
|
if let Err(e) = &res {
|
||||||
if *request_counter > Settings::get().http_fetch_retry_limit {
|
// TODO: very ugly
|
||||||
return Err(LemmyError::from(anyhow!("Request retry limit reached")));
|
if e.message == Some("410".to_string()) {
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
if let Some(db_object) = db_object {
|
||||||
db_object.delete(data).await?;
|
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::verify(&res2, self.inner(), data, request_counter).await?;
|
||||||
Kind::from_apub(res2, data, request_counter).await
|
Kind::from_apub(res2, data, request_counter).await
|
||||||
|
|
38
crates/apub_lib/src/utils.rs
Normal file
38
crates/apub_lib/src/utils.rs
Normal file
|
@ -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<Kind: DeserializeOwned>(
|
||||||
|
url: &Url,
|
||||||
|
client: &ClientWithMiddleware,
|
||||||
|
request_counter: &mut i32,
|
||||||
|
) -> Result<Kind, LemmyError> {
|
||||||
|
// 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?)
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ pub enum MediaTypeMarkdown {
|
||||||
Markdown,
|
Markdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Media type for HTML text/
|
/// Media type for HTML text.
|
||||||
///
|
///
|
||||||
/// <https://www.iana.org/assignments/media-types/media-types.xhtml>
|
/// <https://www.iana.org/assignments/media-types/media-types.xhtml>
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
@ -50,3 +50,11 @@ pub enum MediaTypeHtml {
|
||||||
#[serde(rename = "text/html")]
|
#[serde(rename = "text/html")]
|
||||||
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,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue