| @ -0,0 +1,179 @@ | |||
| <?php | |||
| namespace p3k\XRay\Formats; | |||
| use DateTime; | |||
| use \p3k\XRay\PostType; | |||
| class ActivityStreams extends Format { | |||
| public static function is_as2_json($document) { | |||
| if(is_array($document) && isset($document['@context'])) { | |||
| if(is_string($document['@context']) && $document['@context'] == 'https://www.w3.org/ns/activitystreams') | |||
| return true; | |||
| if(is_array($document['@context']) && in_array('https://www.w3.org/ns/activitystreams', $document['@context'])) | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| public static function matches_host($url) { | |||
| return true; | |||
| } | |||
| public static function matches($url) { | |||
| return true; | |||
| } | |||
| public static function parse($as2, $url, $http, $opts=[]) { | |||
| if(!isset($as2['type'])) | |||
| return false; | |||
| switch($as2['type']) { | |||
| case 'Person': | |||
| return self::parseAsHCard($as2, $url, $http, $opts); | |||
| case 'Note': | |||
| return self::parseAsHEntry($as2, $url, $http, $opts); | |||
| } | |||
| $result = [ | |||
| 'data' => [ | |||
| 'type' => 'unknown', | |||
| ], | |||
| 'url' => $url, | |||
| ]; | |||
| return $result; | |||
| } | |||
| private static function parseAsHEntry($as2, $url, $http, $opts) { | |||
| $data = [ | |||
| 'type' => 'entry' | |||
| ]; | |||
| $refs = []; | |||
| if(isset($as2['url'])) | |||
| $data['url'] = $as2['url']; | |||
| elseif(isset($as2['id'])) | |||
| $data['url'] = $as2['id']; | |||
| if(isset($as2['published'])) { | |||
| try { | |||
| $date = new DateTime($as2['published']); | |||
| $data['published'] = $date->format('c'); | |||
| } catch(\Exception $e){} | |||
| } | |||
| if(isset($as2['content'])) { | |||
| $html = trim(self::sanitizeHTML($as2['content'])); | |||
| $text = trim(self::stripHTML($html)); | |||
| $data['content'] = [ | |||
| 'text' => $text | |||
| ]; | |||
| if($html && $text && $text != $html) { | |||
| $data['content']['html'] = $html; | |||
| } | |||
| } | |||
| if(isset($as2['tag']) && is_array($as2['tag'])) { | |||
| $emoji = []; | |||
| $category = []; | |||
| foreach($as2['tag'] as $tag) { | |||
| if(is_array($tag) && isset($tag['name']) && isset($tag['type']) && $tag['type'] == 'Hashtag') | |||
| $category[] = trim($tag['name'], '#'); | |||
| if(is_array($tag) && isset($tag['type']) && $tag['type'] == 'Emoji' && isset($tag['icon']['url'])) { | |||
| $emoji[$tag['name']] = $tag['icon']['url']; | |||
| } | |||
| } | |||
| if(count($category)) | |||
| $data['category'] = $category; | |||
| if(count($emoji) && isset($data['content']['html'])) { | |||
| foreach($emoji as $code=>$img) { | |||
| $data['content']['html'] = str_replace($code, '<img src="'.$img.'" alt="'.$code.'" title="'.$code.'" height="24" class="xray-custom-emoji">', $data['content']['html']); | |||
| } | |||
| } | |||
| } | |||
| if(isset($as2['inReplyTo'])) { | |||
| $data['in-reply-to'] = [$as2['inReplyTo']]; | |||
| } | |||
| // Photos and Videos | |||
| if(isset($as2['attachment'])) { | |||
| $photos = []; | |||
| $videos = []; | |||
| foreach($as2['attachment'] as $attachment) { | |||
| if(strpos($attachment['mediaType'], 'image/') !== false) { | |||
| $photos[] = $attachment['url']; | |||
| } | |||
| if(strpos($attachment['mediaType'], 'video/') !== false) { | |||
| $videos[] = $attachment['url']; | |||
| } | |||
| } | |||
| if(count($photos)) | |||
| $data['photo'] = $photos; | |||
| if(count($videos)) | |||
| $data['video'] = $videos; | |||
| } | |||
| // Fetch the author info, which requires an HTTP request | |||
| if(isset($as2['attributedTo']) && is_string($as2['attributedTo'])) { | |||
| $authorResponse = $http->get($as2['attributedTo'], ['Accept: application/activity+json,application/json']); | |||
| if($authorResponse && !empty($authorResponse['body'])) { | |||
| $authorProfile = json_decode($authorResponse['body'], true); | |||
| $author = self::parseAsHCard($authorProfile, $as2['attributedTo'], $http, $opts); | |||
| if($author && !empty($author['data'])) | |||
| $data['author'] = $author['data']; | |||
| } | |||
| } | |||
| $data['post-type'] = PostType::discover($data); | |||
| $response = [ | |||
| 'data' => $data, | |||
| ]; | |||
| if(count($refs)) { | |||
| $response['data']['refs'] = $refs; | |||
| } | |||
| return $response; | |||
| } | |||
| private static function parseAsHCard($as2, $url, $http, $opts) { | |||
| $data = [ | |||
| 'type' => 'card', | |||
| 'name' => null, | |||
| 'url' => null, | |||
| 'photo' => null | |||
| ]; | |||
| if(!empty($as2['name'])) | |||
| $data['name'] = $as2['name']; | |||
| elseif(isset($as2['preferredUsername'])) | |||
| $data['name'] = $as2['preferredUsername']; | |||
| if(isset($as2['preferredUsername'])) | |||
| $data['nickname'] = $as2['preferredUsername']; | |||
| if(isset($as2['url'])) | |||
| $data['url'] = $as2['url']; | |||
| if(isset($as2['icon']) && isset($as2['icon']['url'])) | |||
| $data['photo'] = $as2['icon']['url']; | |||
| // TODO: featured image for h-cards? | |||
| // if(isset($as2['image']) && isset($as2['image']['url'])) | |||
| // $data['featured'] = $as2['image']['url']; | |||
| $response = [ | |||
| 'data' => $data | |||
| ]; | |||
| return $response; | |||
| } | |||
| } | |||
| @ -0,0 +1,142 @@ | |||
| <?php | |||
| use Symfony\Component\HttpFoundation\Request; | |||
| use Symfony\Component\HttpFoundation\Response; | |||
| class ActivityStreamsTest extends PHPUnit_Framework_TestCase { | |||
| private $http; | |||
| public function setUp() { | |||
| $this->client = new Parse(); | |||
| $this->client->http = new p3k\HTTP\Test(dirname(__FILE__).'/data/'); | |||
| $this->client->mc = null; | |||
| } | |||
| private function parse($params) { | |||
| $request = new Request($params); | |||
| $response = new Response(); | |||
| return $this->client->parse($request, $response); | |||
| } | |||
| public function testAuthorProfile() { | |||
| $url = 'http://activitystreams.example/aaronpk'; | |||
| $response = $this->parse(['url' => $url]); | |||
| $body = $response->getContent(); | |||
| $this->assertEquals(200, $response->getStatusCode()); | |||
| $data = json_decode($body, true); | |||
| $this->assertEquals('activity+json', $data['source-format']); | |||
| $this->assertEquals('card', $data['data']['type']); | |||
| $this->assertEquals('aaronpk', $data['data']['name']); | |||
| $this->assertEquals('https://aaronparecki.com/images/profile.jpg', $data['data']['photo']); | |||
| $this->assertEquals('https://aaronparecki.com/', $data['data']['url']); | |||
| } | |||
| public function testNoteWithTags() { | |||
| $url = 'http://activitystreams.example/note.json'; | |||
| $response = $this->parse(['url' => $url]); | |||
| $body = $response->getContent(); | |||
| $this->assertEquals(200, $response->getStatusCode()); | |||
| $data = json_decode($body, true); | |||
| $this->assertEquals('activity+json', $data['source-format']); | |||
| $this->assertEquals('note', $data['data']['post-type']); | |||
| $this->assertEquals($url, $data['data']['url']); | |||
| $this->assertEquals('2018-07-12T13:02:04-07:00', $data['data']['published']); | |||
| $this->assertEquals('This is the text content of an ActivityStreams note', $data['data']['content']['text']); | |||
| $this->assertArrayNotHasKey('html', $data['data']['content']); | |||
| $this->assertSame(['activitystreams'], $data['data']['category']); | |||
| $this->assertEquals('aaronpk', $data['data']['author']['name']); | |||
| $this->assertEquals('https://aaronparecki.com/images/profile.jpg', $data['data']['author']['photo']); | |||
| $this->assertEquals('https://aaronparecki.com/', $data['data']['author']['url']); | |||
| } | |||
| public function testPhoto() { | |||
| $url = 'http://activitystreams.example/photo.json'; | |||
| $response = $this->parse(['url' => $url]); | |||
| $body = $response->getContent(); | |||
| $this->assertEquals(200, $response->getStatusCode()); | |||
| $data = json_decode($body, true); | |||
| $this->assertEquals('activity+json', $data['source-format']); | |||
| $this->assertEquals($url, $data['data']['url']); | |||
| $this->assertEquals('photo', $data['data']['post-type']); | |||
| $this->assertEquals('2018-07-12T13:02:04-07:00', $data['data']['published']); | |||
| $this->assertEquals('This is the text content of an ActivityStreams photo', $data['data']['content']['text']); | |||
| $this->assertArrayNotHasKey('html', $data['data']['content']); | |||
| $this->assertSame(['activitystreams'], $data['data']['category']); | |||
| $this->assertSame(['https://aaronparecki.com/2018/06/28/26/photo.jpg'], $data['data']['photo']); | |||
| } | |||
| public function testVideo() { | |||
| $url = 'http://activitystreams.example/video.json'; | |||
| $response = $this->parse(['url' => $url]); | |||
| $body = $response->getContent(); | |||
| $this->assertEquals(200, $response->getStatusCode()); | |||
| $data = json_decode($body, true); | |||
| $this->assertEquals('activity+json', $data['source-format']); | |||
| $this->assertEquals('video', $data['data']['post-type']); | |||
| $this->assertEquals('2018-07-12T13:02:04-07:00', $data['data']['published']); | |||
| $this->assertSame(['https://aaronparecki.com/2018/07/21/19/video.mp4'], $data['data']['video']); | |||
| } | |||
| public function testReply() { | |||
| $url = 'http://activitystreams.example/reply.json'; | |||
| $response = $this->parse(['url' => $url]); | |||
| $body = $response->getContent(); | |||
| $this->assertEquals(200, $response->getStatusCode()); | |||
| $data = json_decode($body, true); | |||
| $this->assertEquals('activity+json', $data['source-format']); | |||
| $this->assertEquals('reply', $data['data']['post-type']); | |||
| $this->assertEquals('2018-07-12T13:02:04-07:00', $data['data']['published']); | |||
| $this->assertArrayNotHasKey('category', $data['data']); // should not include the person-tag | |||
| // For now, don't fetch the reply context | |||
| $this->assertEquals(['http://activitystreams.example/note.json'], $data['data']['in-reply-to']); | |||
| } | |||
| public function testCustomEmoji() { | |||
| $url = 'http://activitystreams.example/custom-emoji.json'; | |||
| $response = $this->parse(['url' => $url]); | |||
| $body = $response->getContent(); | |||
| $this->assertEquals(200, $response->getStatusCode()); | |||
| $data = json_decode($body, true); | |||
| $this->assertEquals('activity+json', $data['source-format']); | |||
| $this->assertEquals('note', $data['data']['post-type']); | |||
| $this->assertEquals("https://mastodon.social/@Gargron/100465999501820229", $data['data']['url']); | |||
| $this->assertEquals('2018-07-30T22:24:54+00:00', $data['data']['published']); | |||
| $this->assertEquals(':yikes:', $data['data']['content']['text']); | |||
| $this->assertEquals('<p><img src="https://files.mastodon.social/custom_emojis/images/000/031/275/original/yikes.png" alt=":yikes:" title=":yikes:" height="24" class="xray-custom-emoji"></p>', $data['data']['content']['html']); | |||
| $this->assertEquals('Eugen', $data['data']['author']['name']); | |||
| $this->assertEquals('Gargron', $data['data']['author']['nickname']); | |||
| $this->assertEquals('https://files.mastodon.social/accounts/avatars/000/000/001/original/eb9e00274b135808.png', $data['data']['author']['photo']); | |||
| $this->assertEquals('https://mastodon.social/@Gargron', $data['data']['author']['url']); | |||
| } | |||
| public function testRelAlternatePriority() { | |||
| $url = 'http://source.example.com/rel-alternate-as2'; | |||
| $response = $this->parse(['url' => $url]); | |||
| $body = $response->getContent(); | |||
| $this->assertEquals(200, $response->getStatusCode()); | |||
| $data = json_decode($body, true); | |||
| $this->assertEquals('activity+json', $data['source-format']); | |||
| $this->assertEquals('http://activitystreams.example/note.json', $data['parsed-url']); | |||
| $this->assertEquals('http://source.example.com/rel-alternate-as2', $data['url']); | |||
| $this->assertEquals('note', $data['data']['post-type']); | |||
| $this->assertEquals('2018-07-12T13:02:04-07:00', $data['data']['published']); | |||
| $this->assertEquals('This is the text content of an ActivityStreams note', $data['data']['content']['text']); | |||
| $this->assertArrayNotHasKey('html', $data['data']['content']); | |||
| $this->assertSame(['activitystreams'], $data['data']['category']); | |||
| } | |||
| } | |||
| @ -0,0 +1,7 @@ | |||
| HTTP/1.1 200 OK | |||
| Server: Apache | |||
| Date: Wed, 30 Jul 2018 03:29:14 GMT | |||
| Content-Type: application/activity+json | |||
| Connection: keep-alive | |||
| {"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":{"@id":"as:movedTo","@type":"@id"},"Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji","focalPoint":{"@container":"@list","@id":"toot:focalPoint"},"featured":{"@id":"toot:featured","@type":"@id"},"schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value"}],"id":"https://mastodon.social/users/Gargron","type":"Person","following":"https://mastodon.social/users/Gargron/following","followers":"https://mastodon.social/users/Gargron/followers","inbox":"https://mastodon.social/users/Gargron/inbox","outbox":"https://mastodon.social/users/Gargron/outbox","featured":"https://mastodon.social/users/Gargron/collections/featured","preferredUsername":"Gargron","name":"Eugen","summary":"\u003cp\u003eDeveloper of Mastodon. 25\u003c/p\u003e","url":"https://mastodon.social/@Gargron","manuallyApprovesFollowers":false,"publicKey":{"id":"https://mastodon.social/users/Gargron#main-key","owner":"https://mastodon.social/users/Gargron","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXc4vkECU2/CeuSo1wtn\nFoim94Ne1jBMYxTZ9wm2YTdJq1oiZKif06I2fOqDzY/4q/S9uccrE9Bkajv1dnkO\nVm31QjWlhVpSKynVxEWjVBO5Ienue8gND0xvHIuXf87o61poqjEoepvsQFElA5ym\novljWGSA/jpj7ozygUZhCXtaS2W5AD5tnBQUpcO0lhItYPYTjnmzcc4y2NbJV8hz\n2s2G8qKv8fyimE23gY1XrPJg+cRF+g4PqFXujjlJ7MihD9oqtLGxbu7o1cifTn3x\nBfIdPythWu5b4cujNsB3m3awJjVmx+MHQ9SugkSIYXV0Ina77cTNS0M2PYiH1PFR\nTwIDAQAB\n-----END PUBLIC KEY-----\n"},"tag":[],"attachment":[{"type":"PropertyValue","name":"Patreon","value":"\u003ca href=\"https://www.patreon.com/mastodon\" rel=\"me nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"},{"type":"PropertyValue","name":"E-mail","value":"eugen@zeonfederated.com"}],"endpoints":{"sharedInbox":"https://mastodon.social/inbox"},"icon":{"type":"Image","mediaType":"image/png","url":"https://files.mastodon.social/accounts/avatars/000/000/001/original/eb9e00274b135808.png"},"image":{"type":"Image","mediaType":"image/jpeg","url":"https://files.mastodon.social/accounts/headers/000/000/001/original/998815725e9554b0.jpg"}} | |||
| @ -0,0 +1,33 @@ | |||
| HTTP/1.1 200 OK | |||
| Server: Apache | |||
| Date: Wed, 30 Jul 2018 03:29:14 GMT | |||
| Content-Type: application/activity+json | |||
| Connection: keep-alive | |||
| { | |||
| "@context": [ | |||
| "https://www.w3.org/ns/activitystreams", | |||
| "https://w3id.org/security/v1" | |||
| ], | |||
| "id": "https://aaronparecki.com/aaronpk", | |||
| "type": "Person", | |||
| "preferredUsername": "aaronpk", | |||
| "url": "https://aaronparecki.com/", | |||
| "icon": { | |||
| "type": "Image", | |||
| "mediaType": "image/jpeg", | |||
| "url": "https://aaronparecki.com/images/profile.jpg" | |||
| }, | |||
| "image": { | |||
| "type": "Image", | |||
| "mediaType": "image/jpeg", | |||
| "url": "https://aaronparecki.com/images/cover-photo.jpg" | |||
| }, | |||
| "inbox": "https://aaronparecki.com/activitypub/inbox", | |||
| "outbox": "https://aaronparecki.com/activitypub/outbox", | |||
| "publicKey": { | |||
| "id": "https://aaronparecki.com/aaronpk#key", | |||
| "owner": "https://aaronparecki.com/aaronpk", | |||
| "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzlRsHgaSwaoB/AOLxjsU\nBDGDZJileQyg2ZmEhWBMLaqNMJH0EqnraQRHfnHz7Yp32zjYCj2NObaX7ySd3uUJ\nzoEyhmfx122WQupOPclGYBC7mbXXhiheDb3ItPn9TxCOveC8d5d2Bm6vyCq4m31x\nBH0jOImL3AscLNwhEdYIHvweXuIqaat50O6yrgJUadJBvw0hyPVFvwiak1dKA2Su\nHCxsgLpxasEoByJMNy1COG8AvR+SuSvwJXZ2DeDS98Ji9EbeaKl6F2mJGJC/Fe2q\nz0t3mVll8Zs4bdnraw6pcnmFNzOv0SziunJoKjQ5IgZDmWHQY4EgEybxa9SEOZ/s\ntQIDAQAB\n-----END PUBLIC KEY-----\n" | |||
| } | |||
| } | |||
| @ -0,0 +1,7 @@ | |||
| HTTP/1.1 200 OK | |||
| Server: Apache | |||
| Date: Wed, 30 Jul 2018 03:29:14 GMT | |||
| Content-Type: application/activity+json | |||
| Connection: keep-alive | |||
| {"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":{"@id":"as:movedTo","@type":"@id"},"Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji","focalPoint":{"@container":"@list","@id":"toot:focalPoint"},"featured":{"@id":"toot:featured","@type":"@id"},"schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value"}],"id":"https://mastodon.social/users/Gargron/statuses/100465999501820229","type":"Note","summary":null,"inReplyTo":null,"published":"2018-07-30T22:24:54Z","url":"https://mastodon.social/@Gargron/100465999501820229","attributedTo":"https://activitystreams.example/Gargron","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.social/users/Gargron/followers"],"sensitive":false,"atomUri":"https://mastodon.social/users/Gargron/statuses/100465999501820229","inReplyToAtomUri":null,"conversation":"tag:mastodon.social,2018-07-30:objectId=44299858:objectType=Conversation","content":"\u003cp\u003e:yikes:\u003c/p\u003e","contentMap":{"en":"\u003cp\u003e:yikes:\u003c/p\u003e"},"attachment":[],"tag":[{"id":"https://mastodon.social/emojis/31275","type":"Emoji","name":":yikes:","updated":"2018-07-15T17:28:20Z","icon":{"type":"Image","mediaType":"image/png","url":"https://files.mastodon.social/custom_emojis/images/000/031/275/original/yikes.png"}}]} | |||
| @ -0,0 +1,24 @@ | |||
| HTTP/1.1 200 OK | |||
| Server: Apache | |||
| Date: Wed, 30 Jul 2018 03:29:14 GMT | |||
| Content-Type: application/activity+json | |||
| Connection: keep-alive | |||
| { | |||
| "@context": "https://www.w3.org/ns/activitystreams", | |||
| "id": "http://activitystreams.example/note.json", | |||
| "type": "Note", | |||
| "published": "2018-07-12T13:02:04-07:00", | |||
| "attributedTo": "https://activitystreams.example/aaronpk", | |||
| "content": "This is the text content of an ActivityStreams note", | |||
| "to": [ | |||
| "https://www.w3.org/ns/activitystreams#Public" | |||
| ], | |||
| "tag": [ | |||
| { | |||
| "id": "https://aaronparecki.com/tag/activitystreams", | |||
| "name": "#activitystreams", | |||
| "type": "Hashtag" | |||
| } | |||
| ] | |||
| } | |||
| @ -0,0 +1,32 @@ | |||
| HTTP/1.1 200 OK | |||
| Server: Apache | |||
| Date: Wed, 30 Jul 2018 03:29:14 GMT | |||
| Content-Type: application/activity+json | |||
| Connection: keep-alive | |||
| { | |||
| "@context": "https://www.w3.org/ns/activitystreams", | |||
| "id": "http://activitystreams.example/photo.json", | |||
| "type": "Note", | |||
| "published": "2018-07-12T13:02:04-07:00", | |||
| "attributedTo": "https://activitystreams.example/aaronpk", | |||
| "content": "This is the text content of an ActivityStreams photo", | |||
| "to": [ | |||
| "https://www.w3.org/ns/activitystreams#Public" | |||
| ], | |||
| "tag": [ | |||
| { | |||
| "type": "Hashtag", | |||
| "id": "https://aaronparecki.com/tag/activitystreams", | |||
| "name": "#activitystreams" | |||
| } | |||
| ], | |||
| "attachment": [ | |||
| { | |||
| "type": "Image", | |||
| "mediaType": "image/jpeg", | |||
| "url": "https://aaronparecki.com/2018/06/28/26/photo.jpg", | |||
| "name": null | |||
| } | |||
| ] | |||
| } | |||
| @ -0,0 +1,25 @@ | |||
| HTTP/1.1 200 OK | |||
| Server: Apache | |||
| Date: Wed, 30 Jul 2018 03:29:14 GMT | |||
| Content-Type: application/activity+json | |||
| Connection: keep-alive | |||
| { | |||
| "@context": "https://www.w3.org/ns/activitystreams", | |||
| "id": "http://activitystreams.example/reply.json", | |||
| "type": "Note", | |||
| "published": "2018-07-12T13:02:04-07:00", | |||
| "attributedTo": "https://activitystreams.example/aaronpk", | |||
| "content": "<a href=\"http://activitystreams.example/aaronpk\">@aaronpk</a> This is a reply", | |||
| "inReplyTo": "http://activitystreams.example/note.json", | |||
| "to": [ | |||
| "https://www.w3.org/ns/activitystreams#Public" | |||
| ], | |||
| "tag": [ | |||
| { | |||
| "type": "Mention", | |||
| "href": "http://activitystreams.example/aaronpk", | |||
| "name": "@aaronpk@activitystreams.example" | |||
| } | |||
| ] | |||
| } | |||
| @ -0,0 +1,31 @@ | |||
| HTTP/1.1 200 OK | |||
| Server: Apache | |||
| Date: Wed, 30 Jul 2018 03:29:14 GMT | |||
| Content-Type: application/activity+json | |||
| Connection: keep-alive | |||
| { | |||
| "@context": "https://www.w3.org/ns/activitystreams", | |||
| "id": "http://activitystreams.example/video.json", | |||
| "type": "Note", | |||
| "published": "2018-07-12T13:02:04-07:00", | |||
| "attributedTo": "https://activitystreams.example/aaronpk", | |||
| "content": "This is the text content of an ActivityStreams photo", | |||
| "to": [ | |||
| "https://www.w3.org/ns/activitystreams#Public" | |||
| ], | |||
| "tag": [ | |||
| { | |||
| "id": "https://aaronparecki.com/tag/activitystreams", | |||
| "name": "#activitystreams" | |||
| } | |||
| ], | |||
| "attachment": [ | |||
| { | |||
| "type": "Document", | |||
| "mediaType": "video/mp4", | |||
| "url": "https://aaronparecki.com/2018/07/21/19/video.mp4", | |||
| "name": null | |||
| } | |||
| ] | |||
| } | |||
| @ -0,0 +1,19 @@ | |||
| HTTP/1.1 200 OK | |||
| Server: Apache | |||
| Date: Wed, 09 Dec 2015 03:29:14 GMT | |||
| Content-Type: text/html | |||
| Connection: keep-alive | |||
| <!doctype html> | |||
| <html> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <title>Test</title> | |||
| <link rel="alternate" type="application/activity+json" href="http://activitystreams.example/note.json" /> | |||
| </head> | |||
| <body> | |||
| <div class="h-entry"> | |||
| <p class="p-content">This should not be the content from XRay</p> | |||
| </div> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,20 @@ | |||
| HTTP/1.1 200 OK | |||
| Server: Apache | |||
| Date: Wed, 09 Dec 2015 03:29:14 GMT | |||
| Content-Type: text/html | |||
| Connection: keep-alive | |||
| <!doctype html> | |||
| <html> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <title>Test</title> | |||
| <link rel="alternate" type="application/activity+json" href="http://activitystreams.example/note.json" /> | |||
| <link rel="alternate" type="application/mf2+json" href="http://source.example.com/rel-alternate-priority.json" /> | |||
| </head> | |||
| <body> | |||
| <div class="h-entry"> | |||
| <p class="p-content">This should not be the content from XRay</p> | |||
| </div> | |||
| </body> | |||
| </html> | |||