From ca9c8c02eff738d0ff18186bfdd86b352aeff58c Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Tue, 31 Jul 2018 14:13:47 -0700 Subject: [PATCH] AS: parse likes and reposts --- lib/XRay/Formats/ActivityStreams.php | 48 ++++++++++++++++- tests/ActivityStreamsTest.php | 37 ++++++++++++++ tests/data/activitystreams.example/like.json | 48 +++++++++++++++++ .../data/activitystreams.example/repost.json | 51 +++++++++++++++++++ 4 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 tests/data/activitystreams.example/like.json create mode 100644 tests/data/activitystreams.example/repost.json diff --git a/lib/XRay/Formats/ActivityStreams.php b/lib/XRay/Formats/ActivityStreams.php index 910d7d9..0687a05 100644 --- a/lib/XRay/Formats/ActivityStreams.php +++ b/lib/XRay/Formats/ActivityStreams.php @@ -34,6 +34,8 @@ class ActivityStreams extends Format { return self::parseAsHCard($as2, $url, $http, $opts); case 'Article': case 'Note': + case 'Announce': // repost + case 'Like': // like return self::parseAsHEntry($as2, $url, $http, $opts); } @@ -62,6 +64,12 @@ class ActivityStreams extends Format { $date = new DateTime($as2['published']); $data['published'] = $date->format('c'); } catch(\Exception $e){} + } elseif(isset($as2['signature']['created'])) { + // Pull date from the signature if there isn't one in the activity + try { + $date = new DateTime($as2['signature']['created']); + $data['published'] = $date->format('c'); + } catch(\Exception $e){} } if(isset($as2['name'])) { @@ -129,16 +137,52 @@ class ActivityStreams extends Format { } // Fetch the author info, which requires an HTTP request + $authorURL = false; if(isset($as2['attributedTo']) && is_string($as2['attributedTo'])) { - $authorResponse = $http->get($as2['attributedTo'], ['Accept: application/activity+json,application/json']); + $authorURL = $as2['attributedTo']; + } elseif(isset($as2['actor']) && is_string($as2['actor'])) { + $authorURL = $as2['actor']; + } + if($authorURL) { + $authorResponse = $http->get($authorURL, ['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); + $author = self::parseAsHCard($authorProfile, $authorURL, $http, $opts); if($author && !empty($author['data'])) $data['author'] = $author['data']; } } + // If this is a repost, fetch the reposted content + if($as2['type'] == 'Announce' && isset($as2['object']) && is_string($as2['object'])) { + $data['repost-of'] = [$as2['object']]; + $reposted = $http->get($as2['object'], ['Accept: application/activity+json,application/json']); + if($reposted && !empty($reposted['body'])) { + $repostedData = json_decode($reposted['body'], true); + if($repostedData) { + $repost = self::parse($repostedData, $as2['object'], $http, $opts); + if($repost) { + $refs[$as2['object']] = $repost; + } + } + } + } + + // If this is a like, fetch the liked post + if($as2['type'] == 'Like' && isset($as2['object']) && is_string($as2['object'])) { + $data['like-of'] = [$as2['object']]; + $liked = $http->get($as2['object'], ['Accept: application/activity+json,application/json']); + if($liked && !empty($liked['body'])) { + $likedData = json_decode($liked['body'], true); + if($likedData) { + $like = self::parse($likedData, $as2['object'], $http, $opts); + if($like) { + $refs[$as2['object']] = $like; + } + } + } + } + $data['post-type'] = PostType::discover($data); $response = [ diff --git a/tests/ActivityStreamsTest.php b/tests/ActivityStreamsTest.php index 4d3f95d..8872276 100644 --- a/tests/ActivityStreamsTest.php +++ b/tests/ActivityStreamsTest.php @@ -173,4 +173,41 @@ class ActivityStreamsTest extends PHPUnit_Framework_TestCase { $this->assertArrayNotHasKey('name', $data['data']); } + public function testRepost() { + $url = 'http://activitystreams.example/repost.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('repost', $data['data']['post-type']); + $this->assertArrayNotHasKey('content', $data['data']); + $this->assertArrayNotHasKey('name', $data['data']); + $this->assertEquals('Gargron', $data['data']['author']['nickname']); + $this->assertEquals(['http://activitystreams.example/note.json'], $data['data']['repost-of']); + $this->assertArrayHasKey('http://activitystreams.example/note.json', $data['data']['refs']); + $this->assertEquals('This is the text content of an ActivityStreams note', $data['data']['refs']['http://activitystreams.example/note.json']['data']['content']['text']); + } + + public function testLike() { + $url = 'http://activitystreams.example/like.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('like', $data['data']['post-type']); + $this->assertArrayNotHasKey('content', $data['data']); + $this->assertArrayNotHasKey('name', $data['data']); + $this->assertEquals('Gargron', $data['data']['author']['nickname']); + $this->assertEquals(['http://activitystreams.example/note.json'], $data['data']['like-of']); + $this->assertArrayHasKey('http://activitystreams.example/note.json', $data['data']['refs']); + $this->assertEquals('This is the text content of an ActivityStreams note', $data['data']['refs']['http://activitystreams.example/note.json']['data']['content']['text']); + } + + } diff --git a/tests/data/activitystreams.example/like.json b/tests/data/activitystreams.example/like.json new file mode 100644 index 0000000..6bbafbf --- /dev/null +++ b/tests/data/activitystreams.example/like.json @@ -0,0 +1,48 @@ +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": "http://activitystreams.example/Gargron#likes/9165926", + "type": "Like", + "actor": "http://activitystreams.example/Gargron", + "object": "http://activitystreams.example/note.json", + "signature": { + "type": "RsaSignature2017", + "creator": "http://activitystreams.example/Gargron#main-key", + "created": "2018-07-31T02:35:52Z", + "signatureValue": "BPNSmAPsULjeyoA67QmlYOIJiOZJcoAcczl9326O2mqd4P40yu+IL7B6J2EbWGkXoJiP/Pd+cVV/8+Anpzmo994Xc39R8STE/QRzHuLPOf0ZNB/1xf2wqPqf5H74D8sehzOexcEGIy9vfdLmSI5bpBoU1S9ljhfC/18YwaC30eKD3gsqu4qnlNw6EgOfutKYrCBwOT2+pVvbH5D8eMFMZ//RGyWJw/DMRoW7m/8yOsGOmc+lOw6ufQpDfhEKp+MET2RbAfubMbsec6puPhXGgx9Lzl9T9s9j+Ile37juhX/miN990t7mx2RmjjYSSdXw2anRd7CaSnnXnwnpaGxEiQ==" + } +} diff --git a/tests/data/activitystreams.example/repost.json b/tests/data/activitystreams.example/repost.json new file mode 100644 index 0000000..8a4f4c9 --- /dev/null +++ b/tests/data/activitystreams.example/repost.json @@ -0,0 +1,51 @@ +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/100470639549326850/activity", + "type": "Announce", + "actor": "http://activitystreams.example/Gargron", + "published": "2018-07-31T18:04:55Z", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://mastodon.social/users/tobypinder", + "https://mastodon.social/users/Gargron/followers" + ], + "object": "http://activitystreams.example/note.json", + "atomUri": "https://mastodon.social/users/Gargron/statuses/100470639549326850/activity" +}