diff --git a/ActivityPubPlugin.php b/ActivityPubPlugin.php index 11e8045..62fb0f1 100755 --- a/ActivityPubPlugin.php +++ b/ActivityPubPlugin.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -30,7 +29,7 @@ if (!defined('GNUSOCIAL')) { } // Ensure proper timezone -date_default_timezone_set('UTC'); +date_default_timezone_set('GMT'); // Import required files by the plugin require_once __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; @@ -106,8 +105,8 @@ class ActivityPubPlugin extends Plugin // Look for a local notice (unfortunately GNU Social doesn't // provide this functionality natively) try { - $candidate = Notice::getByID(intval(substr($url, strlen(common_local_url('shownotice', ['notice' => '']))))); - if ($candidate->getUrl() == $url) { // Sanity check + $candidate = Notice::getByID(intval(substr($url, (strlen(common_local_url('apNotice', ['id' => 0]))-1)))); + if (common_local_url('apNotice', ['id' => $candidate->getID()]) === $url) { // Sanity check return $candidate; } else { common_debug('ActivityPubPlugin Notice Grabber: '.$candidate->getUrl(). ' is different of '.$url); @@ -151,49 +150,49 @@ class ActivityPubPlugin extends Plugin ['nickname' => Nickname::DISPLAY_FMT], 'apActorProfile' ); - - ActivityPubURLMapperOverwrite::variable( - $m, - 'notice/:id', - ['id' => '[0-9]+'], - 'apNotice' - ); } + // No .json here for convenience purposes on Notice grabber + $m->connect( + 'note/:id', + ['action' => 'apNotice'], + ['id' => '[0-9]+'] + ); + $m->connect( 'user/:id/liked.json', - ['action' => 'apActorLiked'], - ['id' => '[0-9]+'] - ); + ['action' => 'apActorLiked'], + ['id' => '[0-9]+'] + ); $m->connect( 'user/:id/followers.json', - ['action' => 'apActorFollowers'], - ['id' => '[0-9]+'] - ); + ['action' => 'apActorFollowers'], + ['id' => '[0-9]+'] + ); $m->connect( 'user/:id/following.json', - ['action' => 'apActorFollowing'], - ['id' => '[0-9]+'] - ); + ['action' => 'apActorFollowing'], + ['id' => '[0-9]+'] + ); $m->connect( 'user/:id/inbox.json', - ['action' => 'apInbox'], - ['id' => '[0-9]+'] - ); + ['action' => 'apInbox'], + ['id' => '[0-9]+'] + ); $m->connect( 'user/:id/outbox.json', - ['action' => 'apActorOutbox'], - ['id' => '[0-9]+'] - ); + ['action' => 'apActorOutbox'], + ['id' => '[0-9]+'] + ); $m->connect( 'inbox.json', - ['action' => 'apInbox'] - ); + ['action' => 'apInbox'] + ); } /** @@ -206,7 +205,7 @@ class ActivityPubPlugin extends Plugin { $versions[] = [ 'name' => 'ActivityPub', 'version' => GNUSOCIAL_VERSION, - 'author' => 'Diogo Cordeiro, Daniel Supernault', + 'author' => 'Diogo Cordeiro', 'homepage' => 'https://www.gnu.org/software/social/', 'rawdescription' => 'Adds ActivityPub Support']; @@ -214,8 +213,7 @@ class ActivityPubPlugin extends Plugin } /** - * Dummy string on AccountProfileBlock stating that ActivityPub is active - * this is more of a placeholder for eventual useful stuff ._. + * Adds an indicator on Remote ActivityPub profiles. * * @author Diogo Cordeiro * @return boolean hook return value @@ -226,8 +224,8 @@ class ActivityPubPlugin extends Plugin return true; } try { - $aprofile = Activitypub_profile::getKV('profile_id', $profile->id); - } catch (NoResultException $e) { + $aprofile = Activitypub_profile::from_profile($profile); + } catch (Exception $e) { // Not a remote ActivityPub_profile! Maybe some other network // that has imported a non-local user (e.g.: OStatus)? return true; @@ -278,7 +276,7 @@ class ActivityPubPlugin extends Plugin } // Look for profile URLs, with or without scheme: - $urls = array(); + $urls = []; if (preg_match('!^https?://((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!', $arg)) { $urls[] = $arg; } @@ -336,7 +334,7 @@ class ActivityPubPlugin extends Plugin */ public static function extractUrlMentions($text, $preMention='@') { - $wmatches = array(); + $wmatches = []; // In the regexp below we need to match / _before_ URL_REGEX_VALID_PATH_CHARS because it otherwise gets merged // with the TLD before (but / is in URL_REGEX_VALID_PATH_CHARS anyway, it's just its positioning that is important) $result = preg_match_all( @@ -386,7 +384,7 @@ class ActivityPubPlugin extends Plugin */ public function onEndFindMentions(Profile $sender, $text, &$mentions) { - $matches = array(); + $matches = []; foreach (self::extractWebfingerIds($text, '@') as $wmatch) { list($target, $pos) = $wmatch; @@ -547,7 +545,7 @@ class ActivityPubPlugin extends Plugin try { $other = Activitypub_profile::from_profile($other); } catch (Exception $e) { - return true; + return true; // Let other plugin handle this instead } $postman = new Activitypub_postman($profile, array($other)); @@ -574,7 +572,7 @@ class ActivityPubPlugin extends Plugin try { $other = Activitypub_profile::from_profile($other); } catch (Exception $e) { - return true; + return true; // Let other plugin handle this instead } $postman = new Activitypub_postman($profile, array($other)); @@ -585,7 +583,7 @@ class ActivityPubPlugin extends Plugin } /** - * Notify remote users when their notices get favorited. + * Notify remote users when their notices get favourited. * * @author Diogo Cordeiro * @param Profile $profile of local user doing the faving @@ -600,7 +598,7 @@ class ActivityPubPlugin extends Plugin return true; } - $other = array(); + $other = []; try { $other[] = Activitypub_profile::from_profile($notice->getProfile()); } catch (Exception $e) { @@ -644,7 +642,7 @@ class ActivityPubPlugin extends Plugin } /** - * Notify remote users when their notices get de-favorited. + * Notify remote users when their notices get de-favourited. * * @author Diogo Cordeiro * @param Profile $profile of local user doing the de-faving @@ -659,7 +657,7 @@ class ActivityPubPlugin extends Plugin return true; } - $other = array(); + $other = []; try { $other[] = Activitypub_profile::from_profile($notice->getProfile()); } catch (Exception $e) { @@ -718,7 +716,7 @@ class ActivityPubPlugin extends Plugin return true; } - $other = array(); + $other = []; foreach ($notice->getAttentionProfiles() as $to_profile) { try { @@ -765,44 +763,12 @@ class ActivityPubPlugin extends Plugin { assert($notice->id > 0); // Ignore if not a valid notice - $profile = Profile::getKV($notice->profile_id); + $profile = $notice->getProfile(); if (!$profile->isLocal()) { return true; } - $other = array(); - try { - $other[] = Activitypub_profile::from_profile($notice->getProfile()); - } catch (Exception $e) { - // Local user can be ignored - } - foreach ($notice->getAttentionProfiles() as $to_profile) { - try { - $other[] = Activitypub_profile::from_profile($to_profile); - } catch (Exception $e) { - // Local user can be ignored - } - } - - // Is Announce - if ($notice->isRepeat()) { - $repeated_notice = Notice::getKV('id', $notice->repeat_of); - if ($repeated_notice instanceof Notice) { - try { - $other[] = Activitypub_profile::from_profile($repeated_notice->getProfile()); - } catch (Exception $e) { - // Local user can be ignored - } - - $postman = new Activitypub_postman($profile, $other); - - // That was it - $postman->announce($repeated_notice); - return true; - } - } - // Ignore for activity/non-post-verb notices if (method_exists('ActivityUtils', 'compareVerbs')) { $is_post_verb = ActivityUtils::compareVerbs( @@ -816,7 +782,16 @@ class ActivityPubPlugin extends Plugin return true; } - // Create + $other = []; + foreach ($notice->getAttentionProfiles() as $mention) { + try { + $other[] = Activitypub_profile::from_profile($mention); + } catch (Exception $e) { + // Local user can be ignored + } + } + + // Is a reply? if ($notice->reply_to) { try { $other[] = Activitypub_profile::from_profile($notice->getParent()->getProfile()); @@ -824,10 +799,9 @@ class ActivityPubPlugin extends Plugin // Local user can be ignored } try { - $mentions = $notice->getParent()->getAttentionProfiles(); - foreach ($mentions as $to_profile) { + foreach ($notice->getParent()->getAttentionProfiles() as $mention) { try { - $other[] = Activitypub_profile::from_profile($to_profile); + $other[] = Activitypub_profile::from_profile($mention); } catch (Exception $e) { // Local user can be ignored } @@ -839,10 +813,27 @@ class ActivityPubPlugin extends Plugin common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage()); } } - $postman = new Activitypub_postman($profile, $other); + + // Is an Announce? + if ($notice->isRepeat()) { + $repeated_notice = Notice::getKV('id', $notice->repeat_of); + if ($repeated_notice instanceof Notice) { + try { + $other[] = Activitypub_profile::from_profile($repeated_notice->getProfile()); + } catch (Exception $e) { + // Local user can be ignored + } + + // That was it + $postman = new Activitypub_postman($profile, $other); + $postman->announce($repeated_notice); + return true; + } + } // That was it - $postman->create($notice); + $postman = new Activitypub_postman($profile, $other); + $postman->create_note($notice); return true; } diff --git a/README.md b/README.md index f67010a..cd67435 100755 --- a/README.md +++ b/README.md @@ -40,10 +40,14 @@ We use [SemVer](http://semver.org/) for versioning. For the versions available, ## Credits * **[Diogo Cordeiro](https://www.diogo.site/)** -* **[Daniel Supernault](https://github.com/dansup)** See also the list of [contributors](https://git.gnu.io/gnu/GS-ActivityPub-Plugin/contributors) who participated in this project. +## Extra special thanks + +* **[Daniel Supernault](https://github.com/dansup)** +* **[Mikael Nordfeldth](https://mmn-o.se/)** + ## License This program is free software: you can redistribute it and/or modify diff --git a/actions/apactorfollowers.php b/actions/apactorfollowers.php index 007e8ff..aae5089 100755 --- a/actions/apactorfollowers.php +++ b/actions/apactorfollowers.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -108,7 +107,7 @@ class apActorFollowersAction extends ManagedAction } /** - * Generates a list of followers for a given profile. + * Generates a list of stalkers for a given profile. * * @author Diogo Cordeiro * @param Profile $profile @@ -116,7 +115,7 @@ class apActorFollowersAction extends ManagedAction * @param int32 $limit * @return Array of URIs */ - public function generate_followers($profile, $since, $limit) + public static function generate_followers($profile, $since, $limit) { /* Fetch Followers */ try { @@ -130,6 +129,7 @@ class apActorFollowersAction extends ManagedAction while ($sub->fetch()) { $subs[] = ActivityPubPlugin::actor_uri($sub); } + return $subs; } } diff --git a/actions/apactorfollowing.php b/actions/apactorfollowing.php index 24f4221..d2a53ff 100755 --- a/actions/apactorfollowing.php +++ b/actions/apactorfollowing.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -108,7 +107,7 @@ class apActorFollowingAction extends ManagedAction } /** - * Generates a list of people following given profile. + * Generates the list of those a given profile is stalking. * * @author Diogo Cordeiro * @param Profile $profile diff --git a/actions/apactorliked.php b/actions/apactorliked.php index e810c00..a86cf01 100755 --- a/actions/apactorliked.php +++ b/actions/apactorliked.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ diff --git a/actions/apactoroutbox.php b/actions/apactoroutbox.php index b357cd2..64997a6 100644 --- a/actions/apactoroutbox.php +++ b/actions/apactoroutbox.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -124,7 +123,7 @@ class apActorOutboxAction extends ManagedAction // TODO: Handle other types if ($note->object_type == 'http://activitystrea.ms/schema/1.0/note') { $notices[] = Activitypub_create::create_to_array( - ActivityPubPlugin::actor_uri($note->getProfile()), + ActivityPubPlugin::actor_uri($note->getProfile()), Activitypub_notice::notice_to_array($note) ); } diff --git a/actions/apactorprofile.php b/actions/apactorprofile.php index 96babfb..272779a 100755 --- a/actions/apactorprofile.php +++ b/actions/apactorprofile.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ diff --git a/actions/apinbox.php b/actions/apinbox.php index f8d818d..40a68aa 100755 --- a/actions/apinbox.php +++ b/actions/apinbox.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -55,16 +54,50 @@ class apInboxAction extends ManagedAction ActivityPubReturn::error('Only POST requests allowed.'); } - common_debug('ActivityPub Shared Inbox: Received a POST request.'); + common_debug('ActivityPub Inbox: Received a POST request.'); $data = file_get_contents('php://input'); - common_debug('ActivityPub Shared Inbox: Request contents: '.$data); + common_debug('ActivityPub Inbox: Request contents: '.$data); $data = json_decode(file_get_contents('php://input'), true); + if (!isset($data['actor'])) { + ActivityPubReturn::error('Actor not found in the request.'); + } + + $actor = ActivityPub_explorer::get_profile_from_url($data['actor']); + $actor_public_key = new Activitypub_rsa(); + $actor_public_key = $actor_public_key->ensure_public_key($actor); + + common_debug('ActivityPub Inbox: HTTP Signature: Validation will now start!'); + + $headers = $this->get_all_headers(); + common_debug('ActivityPub Inbox: Request Headers: '.print_r($headers, true)); + + // TODO: Validate HTTP Signature, if it fails, attempt once with profile update + + common_debug('ActivityPub Inbox: HTTP Signature: Authorized request. Will now start the inbox handler.'); + try { - new Activitypub_inbox_handler($data); + new Activitypub_inbox_handler($data, $actor); ActivityPubReturn::answer(); } catch (Exception $e) { ActivityPubReturn::error($e->getMessage()); } } + + /** + * Get all HTTP header key/values as an associative array for the current request. + * + * @author PHP Manual Contributed Notes + * @return string[string] The HTTP header key/value pairs. + */ + private function get_all_headers() + { + $headers = []; + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[strtolower(str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))))] = $value; + } + } + return $headers; + } } diff --git a/actions/apnotice.php b/actions/apnotice.php index 949dbf9..c64f667 100755 --- a/actions/apnotice.php +++ b/actions/apnotice.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ diff --git a/classes/Activitypub_accept.php b/classes/Activitypub_accept.php index 4af6ad5..65a2273 100755 --- a/classes/Activitypub_accept.php +++ b/classes/Activitypub_accept.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -50,10 +49,7 @@ class Activitypub_accept extends Managed_DataObject public static function accept_to_array($object) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', 'id' => common_root_url().'accept_follow_from_'.urlencode($object['actor']).'_to_'.urlencode($object['object']), 'type' => 'Accept', 'actor' => $object['object'], diff --git a/classes/Activitypub_announce.php b/classes/Activitypub_announce.php index 8a04152..e89b053 100755 --- a/classes/Activitypub_announce.php +++ b/classes/Activitypub_announce.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -50,10 +49,7 @@ class Activitypub_announce extends Managed_DataObject public static function announce_to_array($actor, $object) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', "type" => "Announce", "actor" => $actor, "object" => $object diff --git a/classes/Activitypub_attachment.php b/classes/Activitypub_attachment.php index 06e48a5..f228d52 100755 --- a/classes/Activitypub_attachment.php +++ b/classes/Activitypub_attachment.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -50,10 +49,7 @@ class Activitypub_attachment extends Managed_DataObject public static function attachment_to_array($attachment) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', 'type' => 'Document', 'mediaType' => $attachment->mimetype, 'url' => $attachment->getUrl(), diff --git a/classes/Activitypub_create.php b/classes/Activitypub_create.php index 9b872ae..0dc2b39 100755 --- a/classes/Activitypub_create.php +++ b/classes/Activitypub_create.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -51,10 +50,7 @@ class Activitypub_create extends Managed_DataObject public static function create_to_array($actor, $object) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', 'id' => $object['id'].'/create', 'type' => 'Create', 'to' => $object['to'], diff --git a/classes/Activitypub_delete.php b/classes/Activitypub_delete.php index 64f56a4..bae494c 100755 --- a/classes/Activitypub_delete.php +++ b/classes/Activitypub_delete.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -50,10 +49,7 @@ class Activitypub_delete extends Managed_DataObject public static function delete_to_array($actor, $object) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', 'id' => $object.'/delete', 'type' => 'Delete', 'actor' => $actor, diff --git a/classes/Activitypub_error.php b/classes/Activitypub_error.php index c7fabb8..8dcb3f6 100755 --- a/classes/Activitypub_error.php +++ b/classes/Activitypub_error.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ diff --git a/classes/Activitypub_follow.php b/classes/Activitypub_follow.php index f8f42f0..abfc69d 100755 --- a/classes/Activitypub_follow.php +++ b/classes/Activitypub_follow.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -51,10 +50,7 @@ class Activitypub_follow extends Managed_DataObject public static function follow_to_array($actor, $object) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', 'id' => common_root_url().'follow_from_'.urlencode($actor).'_to_'.urlencode($object), 'type' => 'Follow', 'actor' => $actor, diff --git a/classes/Activitypub_like.php b/classes/Activitypub_like.php index 5a392ea..69fafc7 100755 --- a/classes/Activitypub_like.php +++ b/classes/Activitypub_like.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -51,10 +50,7 @@ class Activitypub_like extends Managed_DataObject public static function like_to_array($actor, $object) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', 'id' => common_root_url().'like_from_'.urlencode($actor).'_to_'.urlencode($object), "type" => "Like", "actor" => $actor, diff --git a/classes/Activitypub_mention_tag.php b/classes/Activitypub_mention_tag.php index 717fb0c..16ce7f9 100755 --- a/classes/Activitypub_mention_tag.php +++ b/classes/Activitypub_mention_tag.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -51,10 +50,7 @@ class Activitypub_mention_tag extends Managed_DataObject public static function mention_tag_to_array_from_values($href, $name) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', "type" => "Mention", "href" => $href, "name" => $name diff --git a/classes/Activitypub_notice.php b/classes/Activitypub_notice.php index 68e6440..9f58cfa 100755 --- a/classes/Activitypub_notice.php +++ b/classes/Activitypub_notice.php @@ -19,7 +19,6 @@ * * @category Plugin * @package GNUsocial - * @author Daniel Supernault * @author Diogo Cordeiro * @copyright 2018 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 @@ -72,11 +71,8 @@ class Activitypub_notice extends Managed_DataObject $to[]= 'https://www.w3.org/ns/activitystreams#Public'; $item = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], - 'id' => $notice->getUrl(), + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => common_local_url('apNotice', ['id' => $notice->getID()]), 'type' => 'Note', 'published' => str_replace(' ', 'T', $notice->getCreated()).'Z', 'url' => $notice->getUrl(), @@ -93,7 +89,7 @@ class Activitypub_notice extends Managed_DataObject // Is this a reply? if (!empty($notice->reply_to)) { - $item['inReplyTo'] = Notice::getById($notice->reply_to)->getUrl(); + $item['inReplyTo'] = common_local_url('apNotice', ['id' => $notice->getID()]); $item['inReplyToAtomUri'] = Notice::getById($notice->reply_to)->getUrl(); } @@ -124,7 +120,6 @@ class Activitypub_notice extends Managed_DataObject $id = $object['id']; // int32 $url = $object['url']; // string $content = $object['content']; // string - $cc = $object['cc']; // array|string // possible keys: ['inReplyTo', 'latitude', 'longitude', 'attachment'] $settings = []; @@ -192,43 +187,41 @@ class Activitypub_notice extends Managed_DataObject if (isset($settings['inReplyTo'])) { try { $inReplyTo = ActivityPubPlugin::grab_notice_from_url($settings['inReplyTo']); + $act->context->replyToID = $inReplyTo->getUri(); + $act->context->replyToUrl = $inReplyTo->getUrl(); } catch (Exception $e) { - throw new Exception('Invalid Object inReplyTo value: '.$e->getMessage()); + // It failed to grab, maybe we got this note from another source + // (e.g.: OStatus) that handles this differently or we really + // failed to get it... + // Welp, nothing that we can do about, let's + // just fake we don't have such notice. } - $act->context->replyToID = $inReplyTo->getUri(); - $act->context->replyToUrl = $inReplyTo->getUrl(); } else { $inReplyTo = null; } - $discovery = new Activitypub_explorer; - - // Generate Cc objects - $cc_profiles = []; - if (is_array($cc)) { - // Remove duplicates from Cc actors set - array_unique($cc); - foreach ($cc as $cc_url) { - try { - $cc_profiles = array_merge($cc_profiles, $discovery->lookup($cc_url)); - } catch (Exception $e) { - // Invalid actor found, just let it go. // TODO: Fallback to OStatus + // Mentions + $mentions = []; + if (isset($object['tag']) && is_array($object['tag'])) { + foreach ($object['tag'] as $tag) { + if ($tag['type'] == 'Mention') { + $mentions[] = $tag['href']; } } - } elseif (empty($cc) || in_array($cc, ACTIVITYPUB_PUBLIC_TO)) { - // No need to do anything else at this point, let's just break out the if - } else { + } + $mentions_profiles = []; + $discovery = new Activitypub_explorer; + foreach ($mentions as $mention) { try { - $cc_profiles = $discovery->lookup($cc); + $mentions_profiles[] = $discovery->lookup($mention)[0]; } catch (Exception $e) { // Invalid actor found, just let it go. // TODO: Fallback to OStatus } } - unset($discovery); - foreach ($cc_profiles as $cp) { - $act->context->attention[ActivityPubPlugin::actor_uri($cp)] = 'http://activitystrea.ms/schema/1.0/person'; + foreach ($mentions_profiles as $mp) { + $act->context->attention[ActivityPubPlugin::actor_uri($mp)] = 'http://activitystrea.ms/schema/1.0/person'; } // Add location if that is set diff --git a/classes/Activitypub_pending_follow_requests.php b/classes/Activitypub_pending_follow_requests.php index 0728dee..d8092fb 100755 --- a/classes/Activitypub_pending_follow_requests.php +++ b/classes/Activitypub_pending_follow_requests.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ diff --git a/classes/Activitypub_profile.php b/classes/Activitypub_profile.php index 8f6af35..6cdcc32 100755 --- a/classes/Activitypub_profile.php +++ b/classes/Activitypub_profile.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -96,10 +95,7 @@ class Activitypub_profile extends Managed_DataObject $public_key = $rsa->ensure_public_key($profile); unset($rsa); $res = [ - '@context' => [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1" - ], + '@context' => 'https://www.w3.org/ns/activitystreams', 'id' => $uri, 'type' => 'Person', 'following' => common_local_url('apActorFollowing', ['id' => $id]), @@ -242,9 +238,13 @@ class Activitypub_profile extends Managed_DataObject */ private static function create_from_local_profile(Profile $profile) { - $url = $profile->getURL(); + $url = $profile->getUri(); $inboxes = Activitypub_explorer::get_actor_inboxes_uri($url); + if ($inboxes == null) { + throw new Exception('This is not an ActivityPub user thus AProfile is politely refusing to proceed.'); + } + $aprofile->created = $aprofile->modified = common_sql_now(); $aprofile = new Activitypub_profile; @@ -413,4 +413,57 @@ class Activitypub_profile extends Managed_DataObject // TRANS: Exception. %s is a webfinger address. throw new Exception(sprintf(_m('Could not find a valid profile for "%s".'), $addr)); } + + /** + * Update remote user profile in local instance + * Depends on do_update + * + * @author Diogo Cordeiro + * @param array $res remote response + * @return Profile remote Profile object + */ + public static function update_profile($aprofile, $res) + { + // ActivityPub Profile + $aprofile->uri = $res['id']; + $aprofile->nickname = $res['preferredUsername']; + $aprofile->fullname = isset($res['name']) ? $res['name'] : null; + $aprofile->bio = isset($res['summary']) ? substr(strip_tags($res['summary']), 0, 1000) : null; + $aprofile->inboxuri = $res['inbox']; + $aprofile->sharedInboxuri = isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox']; + + $profile = $aprofile->local_profile(); + + $profile->modified = $aprofile->modified = common_sql_now(); + + $fields = [ + 'uri' => 'profileurl', + 'nickname' => 'nickname', + 'fullname' => 'fullname', + 'bio' => 'bio' + ]; + + foreach ($fields as $af => $pf) { + $profile->$pf = $aprofile->$af; + } + + // Profile + $profile->update(); + $aprofile->update(); + + // Public Key + Activitypub_rsa::update_public_key($profile, $res['publicKey']['publicKeyPem']); + + // Avatar + if (isset($res['icon']['url'])) { + try { + Activitypub_explorer::update_avatar($profile, $res['icon']['url']); + } catch (Exception $e) { + // Let the exception go, it isn't a serious issue + common_debug('An error ocurred while grabbing remote avatar'.$e->getMessage()); + } + } + + return $profile; + } } diff --git a/classes/Activitypub_reject.php b/classes/Activitypub_reject.php index d0a0095..98e3581 100755 --- a/classes/Activitypub_reject.php +++ b/classes/Activitypub_reject.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -50,10 +49,7 @@ class Activitypub_reject extends Managed_DataObject public static function reject_to_array($object) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', "type" => "Reject", "object" => $object ]; diff --git a/classes/Activitypub_rsa.php b/classes/Activitypub_rsa.php index bd7c11b..817ace7 100755 --- a/classes/Activitypub_rsa.php +++ b/classes/Activitypub_rsa.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -84,16 +83,15 @@ class Activitypub_rsa extends Managed_DataObject return $apRSA->private_key; } - /** * Guarantees a Public Key for a given profile. * * @author Diogo Cordeiro * @param Profile $profile * @return string The public key - * @throws Exception It should never occur + * @throws ServerException It should never occur, but if so, we break everything! */ - public function ensure_public_key($profile) + public function ensure_public_key($profile, $fetch = true) { $this->profile_id = $profile->getID(); $apRSA = self::getKV('profile_id', $this->profile_id); @@ -103,7 +101,14 @@ class Activitypub_rsa extends Managed_DataObject self::generate_keys($this->private_key, $this->public_key); $this->store_keys(); } else { - throw new Exception('No Keys for this Profile. That\'s odd.'); + // This should never happen, but try to recover! + if ($fetch) { + $res = Activitypub_explorer::get_remote_user_activity(ActivityPubPlugin::actor_uri($profile)); + Activitypub_rsa::update_public_key($profile, $res['publicKey']['publicKeyPem']); + return ensure_public_key($profile, false); + } else { + throw new ServerException('Activitypub_rsa: Failed to find keys for given profile. That should have not happened!'); + } } } return $apRSA->public_key; @@ -121,7 +126,6 @@ class Activitypub_rsa extends Managed_DataObject $this->created = $this->modified = common_sql_now(); $ok = $this->insert(); if ($ok === false) { - $profile->query('ROLLBACK'); throw new ServerException('Cannot save ActivityPub RSA.'); } } @@ -152,4 +156,23 @@ class Activitypub_rsa extends Managed_DataObject $public_key = $pubKey["key"]; unset($pubKey); } + + /** + * Update public key. + * + * @author Diogo Cordeiro + * @param Profile $profile + * @param string $public_key + */ + public static function update_public_key($profile, $public_key) + { + // Public Key + $apRSA = new Activitypub_rsa(); + $apRSA->profile_id = $profile->getID(); + $apRSA->public_key = $public_key; + $apRSA->modified = common_sql_now(); + if(!$apRSA->update()) { + $apRSA->insert(); + } + } } diff --git a/classes/Activitypub_tag.php b/classes/Activitypub_tag.php index 1ca5823..252627d 100755 --- a/classes/Activitypub_tag.php +++ b/classes/Activitypub_tag.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -50,10 +49,7 @@ class Activitypub_tag extends Managed_DataObject public static function tag_to_array($tag) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', 'name' => $tag, 'url' => common_local_url('tag', ['tag' => $tag]) ]; diff --git a/classes/Activitypub_undo.php b/classes/Activitypub_undo.php index 685dad1..54d6de4 100755 --- a/classes/Activitypub_undo.php +++ b/classes/Activitypub_undo.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -50,10 +49,7 @@ class Activitypub_undo extends Managed_DataObject public static function undo_to_array($object) { $res = [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context' => 'https://www.w3.org/ns/activitystreams', 'id' => $object['id'].'/undo', 'type' => 'Undo', 'actor' => $object['actor'], diff --git a/daemons/update_activitypub_profiles.php b/daemons/update_activitypub_profiles.php new file mode 100755 index 0000000..c41c83b --- /dev/null +++ b/daemons/update_activitypub_profiles.php @@ -0,0 +1,104 @@ +#!/usr/bin/env php +. + * + * @category Plugin + * @package GNUsocial + * @author Diogo Cordeiro + * @copyright 2018 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/ + */ + +define('INSTALLDIR', realpath(__DIR__ . '/../../..')); + +$shortoptions = 'u:af'; +$longoptions = ['uri=', 'all', 'force']; + +$helptext = <<lookup($uri); + if (empty($discovery)) { + echo "Bad URI\n"; + exit(1); + } + $user = $discovery->lookup($uri)[0]; + try { + $res = Activitypub_explorer::get_remote_user_activity($uri); + } catch (Exception $e) { + echo $e->getMessage()."\n"; + exit(1); + } + if (!$quiet) { + echo "Updated ".Activitypub_profile::update_profile($user, $res)->getBestName()."\n"; + } +} else if (!have_option('a', 'all')) { + show_help(); + exit(1); +} + +$user = new Activitypub_profile(); +$cnt = $user->find(); +if (!empty($cnt)) { + if (!$quiet) { + echo "Found {$cnt} ActivityPub profiles:\n"; + } +} else { + if (have_option('u', 'uri')) { + if (!$quiet) { + echo "Couldn't find an existing ActivityPub profile with that URI.\n"; + } + } else { + if (!$quiet) { + echo "Couldn't find any existing ActivityPub profiles.\n"; + } + } + exit(0); +} +while ($user->fetch()) { + try { + $res = Activitypub_explorer::get_remote_user_activity($user->uri); + if (!$quiet) { + echo "Updated ".Activitypub_profile::update_profile($user, $res)->getBestName()."\n"; + } + } catch (Exception $e) { + // let it go + } +} diff --git a/utils/Activitypub_activityverb2.php b/utils/Activitypub_activityverb2.php index 9b16789..a78f496 100644 --- a/utils/Activitypub_activityverb2.php +++ b/utils/Activitypub_activityverb2.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ diff --git a/utils/explorer.php b/utils/explorer.php index 0043310..dc8d600 100755 --- a/utils/explorer.php +++ b/utils/explorer.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -204,7 +203,7 @@ class Activitypub_explorer common_debug('ActivityPub Explorer: Trying to grab a remote actor for '.$url); if (!isset($this->temp_res)) { $client = new HTTPClient(); - $headers = array(); + $headers = []; $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social'; $response = $client->get($url, $headers); @@ -259,10 +258,10 @@ class Activitypub_explorer // Avatar if (isset($res['icon']['url'])) { try { - $this->_store_avatar($profile, $res['icon']['url']); + $this->update_avatar($profile, $res['icon']['url']); } catch (Exception $e) { // Let the exception go, it isn't a serious issue - common_debug('An error ocurred while grabbing remote avatar'.$e->getMessage()); + common_debug('ActivityPub Explorer: An error ocurred while grabbing remote avatar: '.$e->getMessage()); } } @@ -278,7 +277,7 @@ class Activitypub_explorer * @return Avatar The Avatar we have on disk. * @throws Exception in various failure cases */ - private function _store_avatar(Profile $profile, $url) + public static function update_avatar(Profile $profile, $url) { common_debug('ActivityPub Explorer: Started grabbing remote avatar from: '.$url); if (!filter_var($url, FILTER_VALIDATE_URL)) { @@ -388,7 +387,7 @@ class Activitypub_explorer public static function get_actor_inboxes_uri($url) { $client = new HTTPClient(); - $headers = array(); + $headers = []; $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social'; $response = $client->get($url, $headers); @@ -416,7 +415,7 @@ class Activitypub_explorer private function travel_collection($url) { $client = new HTTPClient(); - $headers = array(); + $headers = []; $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social'; $response = $client->get($url, $headers); @@ -439,4 +438,27 @@ class Activitypub_explorer return true; } + + /** + * Get a remote user array from its URL (this function is only used for + * profile updating and shall not be used for anything else) + * + * @author Diogo Cordeiro + * @param string $url User's url + * @throws Exception + */ + public static function get_remote_user_activity($url) + { + $client = new HTTPClient(); + $headers = []; + $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; + $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social'; + $response = $client->get($url, $headers); + $res = json_decode($response->getBody(), true); + if (Activitypub_explorer::validate_remote_response($res)) { + common_debug('ActivityPub Explorer: Found a valid remote actor for '.$url); + return $res; + } + throw new Exception('ActivityPub Explorer: Failed to get activity.'); + } } diff --git a/utils/inbox_handler.php b/utils/inbox_handler.php index 3f78a77..856709f 100644 --- a/utils/inbox_handler.php +++ b/utils/inbox_handler.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -49,8 +48,9 @@ class Activitypub_inbox_handler * * @author Diogo Cordeiro * @param Array $activity Activity we are receiving + * @param Profile $actor_profile Actor originating the activity */ - public function __construct($activity) + public function __construct($activity, $actor_profile = null) { $this->activity = $activity; $this->object = $activity['object']; @@ -59,7 +59,11 @@ class Activitypub_inbox_handler $this->validate_activity(); // Get Actor's Profile - $this->actor = ActivityPub_explorer::get_profile_from_url($this->activity['actor']); + if (!is_null($actor_profile)) { + $this->actor = $actor_profile; + } else { + $this->actor = ActivityPub_explorer::get_profile_from_url($this->activity['actor']); + } // Handle the Activity $this->process(); @@ -98,7 +102,7 @@ class Activitypub_inbox_handler case 'Like': case 'Announce': if (!filter_var($this->object, FILTER_VALIDATE_URL)) { - throw new Exception("Object is not a valid Object URI for Activity."); + throw new Exception('Object is not a valid Object URI for Activity.'); } break; case 'Undo': diff --git a/utils/postman.php b/utils/postman.php index a647ea8..6a40677 100755 --- a/utils/postman.php +++ b/utils/postman.php @@ -20,7 +20,6 @@ * @category Plugin * @package GNUsocial * @author Diogo Cordeiro - * @author Daniel Supernault * @copyright 2018 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/ @@ -60,27 +59,37 @@ class Activitypub_postman * @param Profile $from Profile of sender * @param Array of Activitypub_profile $to destinataries */ - public function __construct($from, $to = []) + public function __construct($from, $to) { $this->actor = $from; + $discovery = new Activitypub_explorer(); $this->to = $to; + $followers = apActorFollowersAction::generate_followers($this->actor, 0, null); + foreach ($followers as $sub) { + try { + $to[]= Activitypub_profile::from_profile($discovery->lookup($sub)[0]); + } catch (Exception $e) { + // Not an ActivityPub Remote Follower, let it go + } + } + unset($discovery); + $this->actor_uri = ActivityPubPlugin::actor_uri($this->actor); $actor_private_key = new Activitypub_rsa(); $actor_private_key = $actor_private_key->get_private_key($this->actor); $context = new Context([ - 'keys' => [$this->actor_uri."#public-key" => $actor_private_key], + 'keys' => [$this->actor_uri.'#public-key' => $actor_private_key], 'algorithm' => 'rsa-sha256', 'headers' => ['(request-target)', 'date', 'content-type', 'accept', 'user-agent'], ]); - $this->to = $to; $this->headers = [ 'content-type' => 'application/activity+json', 'accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'user-agent' => 'GNUSocialBot v0.1 - https://gnu.io/social', - 'date' => date('D, d M Y h:i:s') . ' GMT' + 'date' => gmdate('D, d M Y H:i:s \G\M\T', time()) ]; $handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context); @@ -228,15 +237,12 @@ class Activitypub_postman * @author Diogo Cordeiro * @param Notice $notice */ - public function create($notice) + public function create_note($notice) { $data = Activitypub_create::create_to_array( $this->actor_uri, Activitypub_notice::notice_to_array($notice) ); - if (isset($notice->reply_to)) { - $data["object"]["reply_to"] = $notice->getParent()->getUrl(); - } $data = json_encode($data, JSON_UNESCAPED_SLASHES); foreach ($this->to_inbox() as $inbox) {