From 2889b1ba7ed4323142c6923265a347b697b86d84 Mon Sep 17 00:00:00 2001 From: Diogo Cordeiro Date: Sun, 29 Apr 2018 13:43:18 +0100 Subject: [PATCH 1/4] Updated plugin as per the doc --- ActivityPubPlugin.php | 9 +-- README.md | 3 +- ...ctivitypubactor.php => apactorprofile.php} | 38 ++++++----- classes/Activitypub_notice.php | 64 +++++++++++++++++++ 4 files changed, 93 insertions(+), 21 deletions(-) rename actions/{activitypubactor.php => apactorprofile.php} (62%) create mode 100644 classes/Activitypub_notice.php diff --git a/ActivityPubPlugin.php b/ActivityPubPlugin.php index 313d017..dc7dd05 100644 --- a/ActivityPubPlugin.php +++ b/ActivityPubPlugin.php @@ -22,6 +22,7 @@ * @category Plugin * @package GNUsocial * @author Daniel Supernault + * @author Diogo Cordeiro * @copyright 2014 Free Software Foundation http://fsf.org * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link https://www.gnu.org/software/social/ @@ -34,16 +35,16 @@ class ActivityPubPlugin extends Plugin public function onRouterInitialized(URLMapper $m) { - $m->connect('api/statuses/user_timeline/:id.ap', - ['action' => 'activitypubactor'], - ['id' => '[0-9]+']); + $m->connect(':nickname/profile.json', + ['action' => 'apActorProfile'], + ['nickname' => Nickname::DISPLAY_FMT]); } public function onPluginVersion(array &$versions) { $versions[] = [ 'name' => 'ActivityPub', 'version' => GNUSOCIAL_VERSION, - 'author' => 'Daniel Supernault', + 'author' => 'Daniel Supernault, Diogo Cordeiro', 'homepage' => 'https://www.gnu.org/software/social/', 'rawdescription' => // Todo: Translation diff --git a/README.md b/README.md index d62738b..aa9e657 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,2 @@ # ActivityPub GNU/Social Plugin - -Warning: This is still a work in progress, not every ActivityPub property is fully implemented yet. \ No newline at end of file +Warning: This is still a work in progress, not every ActivityPub property is fully implemented yet. diff --git a/actions/activitypubactor.php b/actions/apactorprofile.php similarity index 62% rename from actions/activitypubactor.php rename to actions/apactorprofile.php index e6b6467..28abcd6 100644 --- a/actions/activitypubactor.php +++ b/actions/apactorprofile.php @@ -22,6 +22,7 @@ * @category Plugin * @package GNUsocial * @author Daniel Supernault + * @author Diogo Cordeiro * @copyright 2015 Free Software Foundaction, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link https://gnu.io/social @@ -29,18 +30,23 @@ if (!defined('GNUSOCIAL')) { exit(1); } -class ActivityPubActorAction extends ManagedAction +class apActorProfileAction extends ManagedAction { protected $needLogin = false; protected $canPost = true; protected function handle() { - $user = User::getByID($this->trimmed('id')); - $profile = $user->getProfile(); - $url = $profile->profileurl; + $nickname = $this->trimmed('nickname'); + try { + $user = User::getByNickname($nickname); + $profile = $user->getProfile(); + $url = $profile->profileurl; + } catch (Exception $e) { + throw new \Exception('Invalid username'); + } - $avatar = $profile->avatarUrl(AVATAR_PROFILE_SIZE); + $avatar_url = $profile->avatarUrl(AVATAR_PROFILE_SIZE); $res = [ '@context' => [ @@ -49,27 +55,29 @@ class ActivityPubActorAction extends ManagedAction "@language" => "en" ] ], - 'id' => $url, + 'id' => $user->getID(), 'type' => 'Person', - 'following' => "{$url}/subscriptions", + 'username' => $user->nickname, + 'inbox' => "{$url}/inbox.json", + 'outbox' => "{$url}/outbox.json", + 'acct' => null, // TODO: Equals `username` for local users, includes `@domain` for remote ones + 'display_name' => $profile->fullname, 'followers' => "{$url}/subscribers", - 'inbox' => null, - 'outbox' => null, - 'liked' => "{$url}/favorites", - 'preferredUsername' => $user->nickname, - 'name' => $user->nickname, + 'following' => "{$url}/subscriptions", + 'liked' => "{$url}/liked.json", + 'liked_count' => Fave::countByProfile ($profile), 'summary' => $profile->bio, 'url' => $url, - 'icon' => [ + 'avatar' => [ 'type' => 'Image', 'width' => 96, 'height' => 96, - 'url' => $avatar + 'url' => $avatar_url ] ]; header('Content-Type: application/json'); - echo json_encode($res, JSON_PRETTY_PRINT); + echo json_encode($res, isset($_GET["pretty"]) ? JSON_PRETTY_PRINT : null); } } diff --git a/classes/Activitypub_notice.php b/classes/Activitypub_notice.php new file mode 100644 index 0000000..eb25b7d --- /dev/null +++ b/classes/Activitypub_notice.php @@ -0,0 +1,64 @@ +. + * + * @category Plugin + * @package GNUsocial + * @author Daniel Supernault + * @author Diogo Cordeiro + * @copyright 2015 Free Software Foundaction, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link https://gnu.io/social + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +class Activitypub_notice extends Managed_DataObject +{ + + public static function noticeToObject($notice) + { + // todo: fix timestamp formats + $item = [ + 'id' => $notice->getUrl(), + // TODO: handle other types + 'type' => 'Notice', + 'actor' => $notice->getProfile()->getUrl(), + 'published' => $notice->created, + 'to' => [ + // TODO: handle proper scope + 'https://www.w3.org/ns/activitystreams#Public' + ], + 'cc' => [ + // TODO: add cc's + "{$notice->getProfile()->getUrl()}/subscribers", + ], + 'content' => $notice->getContent(), + 'rendered' => $notice->getRendered(), + 'url' => $notice->getUrl(), + 'reply_to' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl(), + 'is_local' => $notice->is_local, + 'conversation' => $notice->conversation, + 'attachment' => [], + 'tag' => [] + ]; + return $item; + } +} \ No newline at end of file From 9c4dc1a233ce8ec6f741f0341af8f691fdc5d173 Mon Sep 17 00:00:00 2001 From: Diogo Cordeiro Date: Sat, 5 May 2018 00:54:41 +0100 Subject: [PATCH 2/4] New Entities added Moved "hard coded" profile from action to an Entity class Added the following entities: - Profile - Attachment - Notice - Tag --- actions/apactorprofile.php | 35 ++-------------- classes/Activitypub_attachment.php | 65 +++++++++++++++++++++++++++++ classes/Activitypub_notice.php | 66 +++++++++++++++++------------ classes/Activitypub_profile.php | 67 ++++++++++++++++++++++++++++++ classes/Activitypub_tag.php | 50 ++++++++++++++++++++++ 5 files changed, 224 insertions(+), 59 deletions(-) create mode 100644 classes/Activitypub_attachment.php create mode 100644 classes/Activitypub_profile.php create mode 100644 classes/Activitypub_tag.php diff --git a/actions/apactorprofile.php b/actions/apactorprofile.php index 28abcd6..b89de7c 100644 --- a/actions/apactorprofile.php +++ b/actions/apactorprofile.php @@ -41,43 +41,14 @@ class apActorProfileAction extends ManagedAction try { $user = User::getByNickname($nickname); $profile = $user->getProfile(); - $url = $profile->profileurl; } catch (Exception $e) { throw new \Exception('Invalid username'); } - $avatar_url = $profile->avatarUrl(AVATAR_PROFILE_SIZE); - - $res = [ - '@context' => [ - "https://www.w3.org/ns/activitystreams", - [ - "@language" => "en" - ] - ], - 'id' => $user->getID(), - 'type' => 'Person', - 'username' => $user->nickname, - 'inbox' => "{$url}/inbox.json", - 'outbox' => "{$url}/outbox.json", - 'acct' => null, // TODO: Equals `username` for local users, includes `@domain` for remote ones - 'display_name' => $profile->fullname, - 'followers' => "{$url}/subscribers", - 'following' => "{$url}/subscriptions", - 'liked' => "{$url}/liked.json", - 'liked_count' => Fave::countByProfile ($profile), - 'summary' => $profile->bio, - 'url' => $url, - 'avatar' => [ - 'type' => 'Image', - 'width' => 96, - 'height' => 96, - 'url' => $avatar_url - ] - ]; - header('Content-Type: application/json'); - echo json_encode($res, isset($_GET["pretty"]) ? JSON_PRETTY_PRINT : null); + $res = Activitypub_profile::profileToObject($profile); + + echo json_encode($res, JSON_UNESCAPED_SLASHES | (isset($_GET["pretty"]) ? JSON_PRETTY_PRINT : null)); } } diff --git a/classes/Activitypub_attachment.php b/classes/Activitypub_attachment.php new file mode 100644 index 0000000..1473ff1 --- /dev/null +++ b/classes/Activitypub_attachment.php @@ -0,0 +1,65 @@ +. + * + * @category Plugin + * @package GNUsocial + * @author Diogo Cordeiro + * @author Daniel Supernault + * @copyright 2015 Free Software Foundaction, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link https://gnu.io/social + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +class Activitypub_attachment extends Managed_DataObject +{ + + public static function attachmentToObject($attachment) + { + $res = [ + '@context' => [ + "https://www.w3.org/ns/activitystreams", + [ + "@language" => "en" + ] + ], + 'id' => $attachment->getID (), + 'mimetype' => $attachment->mimetype, + 'url' => $attachment->getUrl (), + 'text_url' => $attachment->isLocal () ? common_shorten_links($attachment->getUrl (), true): null, + 'size' => $attachment->getSize (), + 'title' => $attachment->getTitle (), + 'meta' => null + ]; + + // Image + if (substr($res["mimetype"], 0, 5) == "image") + { + $res["meta"]= [ + 'width' => $attachment->width, + 'height' => $attachment->height + ]; + } + + return $res; + } +} diff --git a/classes/Activitypub_notice.php b/classes/Activitypub_notice.php index eb25b7d..7e75108 100644 --- a/classes/Activitypub_notice.php +++ b/classes/Activitypub_notice.php @@ -33,32 +33,44 @@ if (!defined('GNUSOCIAL')) { exit(1); } class Activitypub_notice extends Managed_DataObject { - public static function noticeToObject($notice) + public static function noticeToObject($notice) + { + $attachments = []; + foreach ($notice->attachments () as $attachment) { - // todo: fix timestamp formats - $item = [ - 'id' => $notice->getUrl(), - // TODO: handle other types - 'type' => 'Notice', - 'actor' => $notice->getProfile()->getUrl(), - 'published' => $notice->created, - 'to' => [ - // TODO: handle proper scope - 'https://www.w3.org/ns/activitystreams#Public' - ], - 'cc' => [ - // TODO: add cc's - "{$notice->getProfile()->getUrl()}/subscribers", - ], - 'content' => $notice->getContent(), - 'rendered' => $notice->getRendered(), - 'url' => $notice->getUrl(), - 'reply_to' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl(), - 'is_local' => $notice->is_local, - 'conversation' => $notice->conversation, - 'attachment' => [], - 'tag' => [] - ]; - return $item; + $attachments[] = Activitypub_attachment::attachmentToObject ($attachment); } -} \ No newline at end of file + + $tags = []; + foreach ($notice->getTags () as $tag) + { + $tags[] = Activitypub_tag::tagNameToObject ($tag); + } + + // todo: fix timestamp formats + $item = [ + 'id' => $notice->getUrl(), + 'type' => 'Notice', // TODO: handle other types + 'actor' => $notice->getProfile()->getUrl(), + 'published' => $notice->getCreated (), + 'to' => [ + // TODO: handle proper scope + 'https://www.w3.org/ns/activitystreams#Public' + ], + 'cc' => [ + // TODO: add cc's + "{$notice->getProfile()->getUrl()}/subscribers", + ], + 'content' => $notice->getContent(), + 'rendered' => $notice->getRendered(), + 'url' => $notice->getUrl(), + 'reply_to' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl(), + 'is_local' => $notice->isLocal(), + 'conversation' => $notice->conversation, + 'attachment' => $attachments, + 'tag' => $tags + ]; + + return $item; + } +} diff --git a/classes/Activitypub_profile.php b/classes/Activitypub_profile.php new file mode 100644 index 0000000..bbd560d --- /dev/null +++ b/classes/Activitypub_profile.php @@ -0,0 +1,67 @@ +. + * + * @category Plugin + * @package GNUsocial + * @author Diogo Cordeiro + * @author Daniel Supernault + * @copyright 2015 Free Software Foundaction, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link https://gnu.io/social + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +class Activitypub_profile extends Managed_DataObject +{ + + public static function profileToObject($profile) + { + $res = [ + '@context' => [ + "https://www.w3.org/ns/activitystreams", + [ + "@language" => "en" + ] + ], + 'id' => $profile->getID(), + 'type' => 'Person', + 'nickname' => $profile->getNickname (), + 'is_local' => $profile->isLocal(), + 'inbox' => "{$url}/inbox.json", + 'outbox' => "{$url}/outbox.json", + 'display_name' => $profile->getFullname(), + 'followers' => "{$url}/subscribers", + 'following' => "{$url}/subscriptions", + 'liked' => "{$url}/liked.json", + 'liked_count' => Fave::countByProfile ($profile), + 'summary' => $profile->getDescription(), + 'url' => $profile->getURL(), + 'avatar' => [ + 'type' => 'Image', + 'width' => 96, + 'height' => 96, + 'url' => $profile->avatarUrl(AVATAR_PROFILE_SIZE) + ] + ]; + return $res; + } +} diff --git a/classes/Activitypub_tag.php b/classes/Activitypub_tag.php new file mode 100644 index 0000000..24255c0 --- /dev/null +++ b/classes/Activitypub_tag.php @@ -0,0 +1,50 @@ +. + * + * @category Plugin + * @package GNUsocial + * @author Diogo Cordeiro + * @author Daniel Supernault + * @copyright 2015 Free Software Foundaction, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link https://gnu.io/social + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +class Activitypub_tag extends Managed_DataObject +{ + public static function tagNameToObject($tag) + { + $res = [ + '@context' => [ + "https://www.w3.org/ns/activitystreams", + [ + "@language" => "en" + ] + ], + 'name' => $tag, + 'url' => common_local_url('tag', array('tag' => $tag)) + ]; + + return $res; + } +} From be73d6bdf6b520c3015dc3bdb5794c83f53c9568 Mon Sep 17 00:00:00 2001 From: Diogo Cordeiro Date: Sat, 5 May 2018 01:06:43 +0100 Subject: [PATCH 3/4] Remove text_url which is no longer in the doc spec --- classes/Activitypub_attachment.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/Activitypub_attachment.php b/classes/Activitypub_attachment.php index 1473ff1..5508e61 100644 --- a/classes/Activitypub_attachment.php +++ b/classes/Activitypub_attachment.php @@ -45,7 +45,6 @@ class Activitypub_attachment extends Managed_DataObject 'id' => $attachment->getID (), 'mimetype' => $attachment->mimetype, 'url' => $attachment->getUrl (), - 'text_url' => $attachment->isLocal () ? common_shorten_links($attachment->getUrl (), true): null, 'size' => $attachment->getSize (), 'title' => $attachment->getTitle (), 'meta' => null From 95fd0a75c45e2dff96c4abfc9885b727a30eca54 Mon Sep 17 00:00:00 2001 From: Diogo Cordeiro Date: Sat, 5 May 2018 01:32:27 +0100 Subject: [PATCH 4/4] Update endpoint URLs and lack of variable declaration fix --- classes/Activitypub_profile.php | 53 +++++++++++++++++---------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/classes/Activitypub_profile.php b/classes/Activitypub_profile.php index bbd560d..59978bf 100644 --- a/classes/Activitypub_profile.php +++ b/classes/Activitypub_profile.php @@ -35,33 +35,34 @@ class Activitypub_profile extends Managed_DataObject public static function profileToObject($profile) { - $res = [ - '@context' => [ - "https://www.w3.org/ns/activitystreams", - [ - "@language" => "en" - ] - ], - 'id' => $profile->getID(), - 'type' => 'Person', - 'nickname' => $profile->getNickname (), - 'is_local' => $profile->isLocal(), - 'inbox' => "{$url}/inbox.json", - 'outbox' => "{$url}/outbox.json", - 'display_name' => $profile->getFullname(), - 'followers' => "{$url}/subscribers", - 'following' => "{$url}/subscriptions", - 'liked' => "{$url}/liked.json", - 'liked_count' => Fave::countByProfile ($profile), - 'summary' => $profile->getDescription(), - 'url' => $profile->getURL(), - 'avatar' => [ - 'type' => 'Image', - 'width' => 96, - 'height' => 96, - 'url' => $profile->avatarUrl(AVATAR_PROFILE_SIZE) + $url = $profile->getURL (); + $res = [ + '@context' => [ + "https://www.w3.org/ns/activitystreams", + [ + "@language" => "en" ] - ]; + ], + 'id' => $profile->getID(), + 'type' => 'Person', + 'nickname' => $profile->getNickname (), + 'is_local' => $profile->isLocal(), + 'inbox' => "{$url}/inbox.json", + 'outbox' => "{$url}/outbox.json", + 'display_name' => $profile->getFullname(), + 'followers' => "{$url}/followers.json", + 'following' => "{$url}/following.json", + 'liked' => "{$url}/liked.json", + 'liked_count' => Fave::countByProfile ($profile), + 'summary' => $profile->getDescription(), + 'url' => $profile->getURL(), + 'avatar' => [ + 'type' => 'Image', + 'width' => 96, + 'height' => 96, + 'url' => $profile->avatarUrl(AVATAR_PROFILE_SIZE) + ] + ]; return $res; } }