]> git.mxchange.org Git - friendica.git/blobdiff - src/Protocol/Diaspora.php
Fix missing attached links in posts
[friendica.git] / src / Protocol / Diaspora.php
index 9e80c130282428401353223353ec1cac6fd4c6d9..91eb1cc2c412717878f48a9827a2fd5e2a84781e 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @copyright Copyright (C) 2010-2022, the Friendica project
+ * @copyright Copyright (C) 2010-2023, the Friendica project
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -33,7 +33,6 @@ use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Model\Contact;
 use Friendica\Model\Conversation;
-use Friendica\Model\FContact;
 use Friendica\Model\GServer;
 use Friendica\Model\Item;
 use Friendica\Model\ItemURI;
@@ -42,19 +41,21 @@ use Friendica\Model\Post;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
 use Friendica\Network\HTTPClient\Client\HttpClientAccept;
+use Friendica\Network\HTTPException;
 use Friendica\Network\Probe;
+use Friendica\Protocol\Delivery;
 use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Map;
 use Friendica\Util\Network;
 use Friendica\Util\Strings;
 use Friendica\Util\XML;
-use Friendica\Worker\Delivery;
 use GuzzleHttp\Psr7\Uri;
 use SimpleXMLElement;
 
 /**
- * This class contain functions to create and send Diaspora XML files
+ * This class contains functions to communicate via the Diaspora protocol
+ * @see https://diaspora.github.io/diaspora_federation/
  */
 class Diaspora
 {
@@ -83,7 +84,7 @@ class Diaspora
                }
 
                $items = Post::select(['author-id', 'author-link', 'parent-author-link', 'parent-guid', 'guid'],
-                       ['parent' => $item['parent'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]);
+                       ['parent' => $item['parent'], 'gravity' => [Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY]]);
                while ($item = Post::fetch($items)) {
                        $contact = DBA::selectFirst('contact', ['id', 'url', 'name', 'protocol', 'batch', 'network'],
                                ['id' => $item['author-id']]);
@@ -160,8 +161,12 @@ class Diaspora
                        return false;
                }
 
-               $key = self::key($handle);
-               if ($key == '') {
+               try {
+                       $key = self::key(WebFingerUri::fromString($handle));
+                       if ($key == '') {
+                               throw new \InvalidArgumentException();
+                       }
+               } catch (\InvalidArgumentException $e) {
                        Logger::notice("Couldn't get a key for handle " . $handle . ". Discarding.");
                        return false;
                }
@@ -223,14 +228,34 @@ class Diaspora
 
                // Is it a private post? Then decrypt the outer Salmon
                if (is_object($data)) {
-                       $encrypted_aes_key_bundle = base64_decode($data->aes_key);
-                       $ciphertext = base64_decode($data->encrypted_magic_envelope);
+                       try {
+                               if (!isset($data->aes_key) || !isset($data->encrypted_magic_envelope)) {
+                                       Logger::info('Missing keys "aes_key" and/or "encrypted_magic_envelope"', ['data' => $data]);
+                                       throw new \RuntimeException('Missing keys "aes_key" and/or "encrypted_magic_envelope"');
+                               }
 
-                       $outer_key_bundle = '';
-                       @openssl_private_decrypt($encrypted_aes_key_bundle, $outer_key_bundle, $privKey);
-                       $j_outer_key_bundle = json_decode($outer_key_bundle);
+                               $encrypted_aes_key_bundle = base64_decode($data->aes_key);
+                               $ciphertext = base64_decode($data->encrypted_magic_envelope);
+
+                               $outer_key_bundle = '';
+                               @openssl_private_decrypt($encrypted_aes_key_bundle, $outer_key_bundle, $privKey);
+                               $j_outer_key_bundle = json_decode($outer_key_bundle);
+
+                               if (!is_object($j_outer_key_bundle)) {
+                                       Logger::info('Unable to decode outer key bundle', ['outer_key_bundle' => $outer_key_bundle]);
+                                       throw new \RuntimeException('Unable to decode outer key bundle');
+                               }
+
+                               if (!isset($j_outer_key_bundle->iv) || !isset($j_outer_key_bundle->key)) {
+                                       Logger::info('Missing keys "iv" and/or "key" from outer Salmon', ['j_outer_key_bundle' => $j_outer_key_bundle]);
+                                       throw new \RuntimeException('Missing keys "iv" and/or "key" from outer Salmon');
+                               }
+
+                               $outer_iv = base64_decode($j_outer_key_bundle->iv);
+                               $outer_key = base64_decode($j_outer_key_bundle->key);
 
-                       if (!is_object($j_outer_key_bundle)) {
+                               $xml = self::aesDecrypt($outer_key, $outer_iv, $ciphertext);
+                       } catch (\Throwable $e) {
                                Logger::notice('Outer Salmon did not verify. Discarding.');
                                if ($no_exit) {
                                        return false;
@@ -238,11 +263,6 @@ class Diaspora
                                        throw new \Friendica\Network\HTTPException\BadRequestException();
                                }
                        }
-
-                       $outer_iv = base64_decode($j_outer_key_bundle->iv);
-                       $outer_key = base64_decode($j_outer_key_bundle->key);
-
-                       $xml = self::aesDecrypt($outer_key, $outer_iv, $ciphertext);
                } else {
                        $xml = $raw;
                }
@@ -284,8 +304,13 @@ class Diaspora
                        }
                }
 
-               $key = self::key($author_addr);
-               if ($key == '') {
+               try {
+                       $author = WebFingerUri::fromString($author_addr);
+                       $key = self::key($author);
+                       if ($key == '') {
+                               throw new \InvalidArgumentException();
+                       }
+               } catch (\InvalidArgumentException $e) {
                        Logger::notice("Couldn't get a key for handle " . $author_addr . ". Discarding.");
                        if ($no_exit) {
                                return false;
@@ -306,8 +331,8 @@ class Diaspora
 
                return [
                        'message' => (string)Strings::base64UrlDecode($base->data),
-                       'author' => XML::unescape($author_addr),
-                       'key' => (string)$key
+                       'author'  => $author->getAddr(),
+                       'key'     => (string)$key
                ];
        }
 
@@ -340,7 +365,7 @@ class Diaspora
 
                if ($children->header) {
                        $public = true;
-                       $author_link = str_replace('acct:', '', $children->header->author_id);
+                       $idom = $children->header;
                } else {
                        // This happens with posts from a relais
                        if (empty($privKey)) {
@@ -368,8 +393,13 @@ class Diaspora
 
                        $inner_iv = base64_decode($idom->iv);
                        $inner_aes_key = base64_decode($idom->aes_key);
+               }
 
-                       $author_link = str_replace('acct:', '', $idom->author_id);
+               try {
+                       $author = WebFingerUri::fromString($idom->author_id);
+               } catch (\Throwable $e) {
+                       Logger::notice('Could not retrieve author URI.', ['idom' => $idom]);
+                       throw new \Friendica\Network\HTTPException\BadRequestException();
                }
 
                $dom = $basedom->children(ActivityNamespace::SALMON_ME);
@@ -423,17 +453,11 @@ class Diaspora
                        $inner_decrypted = self::aesDecrypt($inner_aes_key, $inner_iv, $inner_encrypted);
                }
 
-               if (!$author_link) {
-                       Logger::notice('Could not retrieve author URI.');
-                       throw new \Friendica\Network\HTTPException\BadRequestException();
-               }
                // Once we have the author URI, go to the web and try to find their public key
-               // (first this will look it up locally if it is in the fcontact cache)
+               // (first this will look it up locally if it is in the diaspora-contact cache)
                // This will also convert diaspora public key from pkcs#1 to pkcs#8
-
-               Logger::notice('Fetching key for '.$author_link);
-               $key = self::key($author_link);
-
+               Logger::notice('Fetching key for ' . $author);
+               $key = self::key($author);
                if (!$key) {
                        Logger::notice('Could not retrieve author key.');
                        throw new \Friendica\Network\HTTPException\BadRequestException();
@@ -449,9 +473,9 @@ class Diaspora
                Logger::notice('Message verified.');
 
                return [
-                       'message' => (string)$inner_decrypted,
-                       'author' => XML::unescape($author_link),
-                       'key' => (string)$key
+                       'message' => $inner_decrypted,
+                       'author'  => $author->getAddr(),
+                       'key'     => $key
                ];
        }
 
@@ -475,7 +499,7 @@ class Diaspora
                }
 
                if (!($fields = self::validPosting($msg))) {
-                       Logger::warning('Invalid posting');
+                       Logger::notice('Invalid posting', ['msg' => $msg]);
                        return false;
                }
 
@@ -504,13 +528,13 @@ class Diaspora
        {
                // The sender is the handle of the contact that sent the message.
                // This will often be different with relayed messages (for example "like" and "comment")
-               $sender = $msg['author'];
+               $sender = WebFingerUri::fromString($msg['author']);
 
                // This is only needed for private postings since this is already done for public ones before
                if (is_null($fields)) {
                        $private = true;
                        if (!($fields = self::validPosting($msg))) {
-                               Logger::warning('Invalid posting');
+                               Logger::notice('Invalid posting', ['msg' => $msg]);
                                return false;
                        }
                } else {
@@ -519,7 +543,7 @@ class Diaspora
 
                $type = $fields->getName();
 
-               Logger::info('Received message', ['type' => $type, 'sender' => $sender, 'user' => $importer['uid']]);
+               Logger::info('Received message', ['type' => $type, 'sender' => $sender->getAddr(), 'user' => $importer['uid']]);
 
                switch ($type) {
                        case 'account_migration':
@@ -727,7 +751,7 @@ class Diaspora
                }
 
                if (isset($parent_author_signature)) {
-                       $key = self::key($msg['author']);
+                       $key = self::key(WebFingerUri::fromString($msg['author']));
                        if (empty($key)) {
                                Logger::info('No key found for parent', ['author' => $msg['author']]);
                                return false;
@@ -739,8 +763,12 @@ class Diaspora
                        }
                }
 
-               $key = self::key($fields->author);
-               if (empty($key)) {
+               try {
+                       $key = self::key(WebFingerUri::fromString($fields->author));
+                       if (empty($key)) {
+                               throw new \InvalidArgumentException();
+                       }
+               } catch (\Throwable $e) {
                        Logger::info('No key found', ['author' => $fields->author]);
                        return false;
                }
@@ -756,85 +784,58 @@ class Diaspora
        /**
         * Fetches the public key for a given handle
         *
-        * @param string $handle The handle
+        * @param WebFingerUri $uri The handle
         *
         * @return string The public key
-        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        * @throws InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function key(string $handle = null): string
-       {
-               $handle = strval($handle);
-
-               Logger::notice('Fetching diaspora key', ['handle' => $handle, 'callstack' => System::callstack(20)]);
-
-               $fcontact = FContact::getByURL($handle);
-               if (!empty($fcontact['pubkey'])) {
-                       return $fcontact['pubkey'];
-               }
-
-               return '';
-       }
-
-       /**
-        * get a handle (user@domain.tld) from a given contact id
-        *
-        * @param int $contact_id  The id in the contact table
-        * @param int $pcontact_id The id in the contact table (Used for the public contact)
-        *
-        * @return string the handle
-        * @throws \Exception
-        */
-       private static function handleFromContact(int $contact_id, int $pcontact_id = 0): string
+       private static function key(WebFingerUri $uri): string
        {
-               $handle = '';
-
-               if ($pcontact_id != 0) {
-                       $contact = Contact::getById($pcontact_id, ['addr']);
-                       if (DBA::isResult($contact)) {
-                               $handle = $contact['addr'];
-                       }
-               }
-
-               if (empty($handle)) {
-                       $contact = Contact::getById($contact_id, ['addr']);
-                       if (DBA::isResult($contact)) {
-                               $handle = $contact['addr'];
-                       }
+               Logger::info('Fetching diaspora key', ['handle' => $uri->getAddr(), 'callstack' => System::callstack(20)]);
+               try {
+                       return DI::dsprContact()->getByAddr($uri)->pubKey;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       return '';
                }
-
-               return strtolower($handle);
        }
 
        /**
         * Get a contact id for a given handle
         *
-        * @todo  Move to Friendica\Model\Contact
-        *
-        * @param int    $uid    The user id
-        * @param string $handle The handle in the format user@domain.tld
+        * @param int          $uid The user id
+        * @param WebFingerUri $uri
         *
         * @return array Contact data
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function contactByHandle(int $uid, string $handle): array
+       private static function contactByHandle(int $uid, WebFingerUri $uri): array
        {
-               return Contact::getByURL($handle, null, [], $uid);
+               Contact::updateByUrlIfNeeded($uri->getAddr());
+               return Contact::getByURL($uri->getAddr(), null, [], $uid);
        }
 
        /**
         * Checks if the given contact url does support ActivityPub
         *
-        * @param string  $url    profile url
-        * @param boolean $update true = always update, false = never update, null = update when not found or outdated
+        * @param string       $url    profile url or WebFinger address
+        * @param boolean|null $update true = always update, false = never update, null = update when not found or outdated
         * @return boolean
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function isSupportedByContactUrl(string $url, $update = null)
+       public static function isSupportedByContactUrl(string $url, ?bool $update = null): bool
        {
-               return !empty(FContact::getByURL($url, $update));
+               $contact = Contact::getByURL($url, $update, ['uri-id', 'network']);
+
+               $supported = DI::dsprContact()->existsByUriId($contact['uri-id'] ?? 0);
+
+               if (!$supported && is_null($update) && ($contact['network'] == Protocol::DFRN)) {
+                       $supported = self::isSupportedByContactUrl($url, true);
+               }
+
+               return $supported;
        }
 
        /**
@@ -888,21 +889,22 @@ class Diaspora
        /**
         * Fetches the contact id for a handle and checks if posting is allowed
         *
-        * @param array  $importer   Array of the importer user
-        * @param string $handle     The checked handle in the format user@domain.tld
-        * @param bool   $is_comment Is the check for a comment?
+        * @param array        $importer    Array of the importer user
+        * @param WebFingerUri $contact_uri The checked contact
+        * @param bool         $is_comment  Is the check for a comment?
         *
         * @return array|bool The contact data or false on error
-        * @throws \Exception
+        * @throws InternalServerErrorException
+        * @throws \ImagickException
         */
-       private static function allowedContactByHandle(array $importer, string $handle, bool $is_comment = false)
+       private static function allowedContactByHandle(array $importer, WebFingerUri $contact_uri, bool $is_comment = false)
        {
-               $contact = self::contactByHandle($importer['uid'], $handle);
+               $contact = self::contactByHandle($importer['uid'], $contact_uri);
                if (!$contact) {
-                       Logger::notice('A Contact for handle ' . $handle . ' and user ' . $importer['uid'] . ' was not found');
+                       Logger::notice('A Contact for handle ' . $contact_uri . ' and user ' . $importer['uid'] . ' was not found');
                        // If a contact isn't found, we accept it anyway if it is a comment
                        if ($is_comment && ($importer['uid'] != 0)) {
-                               return self::contactByHandle(0, $handle);
+                               return self::contactByHandle(0, $contact_uri);
                        } elseif ($is_comment) {
                                return $importer;
                        } else {
@@ -911,7 +913,7 @@ class Diaspora
                }
 
                if (!self::postAllow($importer, $contact, $is_comment)) {
-                       Logger::notice('The handle: ' . $handle . ' is not allowed to post to user ' . $importer['uid']);
+                       Logger::notice('The handle: ' . $contact_uri . ' is not allowed to post to user ' . $importer['uid']);
                        return false;
                }
                return $contact;
@@ -980,7 +982,7 @@ class Diaspora
                                // 0 => '[url=/people/0123456789abcdef]Foo Bar[/url]'
                                // 1 => '0123456789abcdef'
                                // 2 => 'Foo Bar'
-                               $handle = FContact::getUrlByGuid($match[1]);
+                               $handle = DI::dsprContact()->getUrlByGuid($match[1]);
 
                                if ($handle) {
                                        $return = '@[url=' . $handle . ']' . $match[2] . '[/url]';
@@ -1106,25 +1108,27 @@ class Diaspora
                        return self::message($source_xml->root_guid, $server, ++$level);
                }
 
-               $author = '';
+               $author_handle = '';
 
                // Fetch the author - for the old and the new Diaspora version
                if ($source_xml->post->status_message && $source_xml->post->status_message->diaspora_handle) {
-                       $author = (string)$source_xml->post->status_message->diaspora_handle;
+                       $author_handle = (string)$source_xml->post->status_message->diaspora_handle;
                } elseif ($source_xml->author && ($source_xml->getName() == 'status_message')) {
-                       $author = (string)$source_xml->author;
+                       $author_handle = (string)$source_xml->author;
                }
 
-               // If this isn't a "status_message" then quit
-               if (!$author) {
+               try {
+                       $author = WebFingerUri::fromString($author_handle);
+               } catch (\InvalidArgumentException $e) {
+                       // If this isn't a "status_message" then quit
                        Logger::info("Message doesn't seem to be a status message");
                        return false;
                }
 
                return [
                        'message' => $x,
-                       'author' => $author,
-                       'key' => self::key($author)
+                       'author'  => $author->getAddr(),
+                       'key'     => self::key($author)
                ];
        }
 
@@ -1171,15 +1175,15 @@ class Diaspora
        /**
         * Fetches the item record of a given guid
         *
-        * @param int    $uid     The user id
-        * @param string $guid    message guid
-        * @param string $author  The handle of the item
-        * @param array  $contact The contact of the item owner
+        * @param int          $uid     The user id
+        * @param string       $guid    message guid
+        * @param WebFingerUri $author
+        * @param array        $contact The contact of the item owner
         *
         * @return array|bool the item record or false on failure
         * @throws \Exception
         */
-       private static function parentItem(int $uid, string $guid, string $author, array $contact)
+       private static function parentItem(int $uid, string $guid, WebFingerUri $author, array $contact)
        {
                $fields = ['id', 'parent', 'body', 'wall', 'uri', 'guid', 'private', 'origin',
                        'author-name', 'author-link', 'author-avatar', 'gravity',
@@ -1189,18 +1193,21 @@ class Diaspora
                $item = Post::selectFirst($fields, $condition);
 
                if (!DBA::isResult($item)) {
-                       $person = FContact::getByURL($author);
-                       $result = self::storeByGuid($guid, $person['url'], false);
+                       try {
+                               $result = self::storeByGuid($guid, DI::dsprContact()->getByAddr($author)->url, false);
 
-                       // We don't have an url for items that arrived at the public dispatcher
-                       if (!$result && !empty($contact['url'])) {
-                               $result = self::storeByGuid($guid, $contact['url'], false);
-                       }
+                               // We don't have an url for items that arrived at the public dispatcher
+                               if (!$result && !empty($contact['url'])) {
+                                       $result = self::storeByGuid($guid, $contact['url'], false);
+                               }
 
-                       if ($result) {
-                               Logger::info('Fetched missing item ' . $guid . ' - result: ' . $result);
+                               if ($result) {
+                                       Logger::info('Fetched missing item ' . $guid . ' - result: ' . $result);
 
-                               $item = Post::selectFirst($fields, $condition);
+                                       $item = Post::selectFirst($fields, $condition);
+                               }
+                       } catch (HTTPException\NotFoundException $e) {
+                               Logger::notice('Unable to retrieve author details', ['author' => $author->getAddr()]);
                        }
                }
 
@@ -1214,20 +1221,20 @@ class Diaspora
        }
 
        /**
-        * returns contact details
+        * returns contact details for the given user
         *
-        * @param array $def_contact The default contact if the person isn't found
-        * @param array $person      The record of the person
-        * @param int   $uid         The user id
+        * @param array  $def_contact The default details if the contact isn't found
+        * @param string $contact_url The url of the contact
+        * @param int    $uid         The user id
         *
         * @return array
         *      'cid' => contact id
         *      'network' => network type
         * @throws \Exception
         */
-       private static function authorContactByUrl(array $def_contact, array $person, int $uid): array
+       private static function authorContactByUrl(array $def_contact, string $contact_url, int $uid): array
        {
-               $condition = ['nurl' => Strings::normaliseLink($person['url']), 'uid' => $uid];
+               $condition = ['nurl' => Strings::normaliseLink($contact_url), 'uid' => $uid];
                $contact = DBA::selectFirst('contact', ['id', 'network'], $condition);
                if (DBA::isResult($contact)) {
                        $cid = $contact['id'];
@@ -1332,21 +1339,27 @@ class Diaspora
         */
        private static function receiveAccountMigration(array $importer, SimpleXMLElement $data): bool
        {
-               $old_handle = XML::unescape($data->author);
-               $new_handle = XML::unescape($data->profile->author);
+               try {
+                       $old_author = WebFingerUri::fromString(XML::unescape($data->author));
+                       $new_author = WebFingerUri::fromString(XML::unescape($data->profile->author));
+               } catch (\Throwable $e) {
+                       Logger::notice('Cannot find handles for sender and user', ['data' => $data]);
+                       return false;
+               }
+
                $signature = XML::unescape($data->signature);
 
-               $contact = self::contactByHandle($importer['uid'], $old_handle);
+               $contact = self::contactByHandle($importer['uid'], $old_author);
                if (!$contact) {
-                       Logger::notice('Cannot find contact for sender: ' . $old_handle . ' and user ' . $importer['uid']);
+                       Logger::notice('Cannot find contact for sender: ' . $old_author . ' and user ' . $importer['uid']);
                        return false;
                }
 
-               Logger::notice('Got migration for ' . $old_handle . ', to ' . $new_handle . ' with user ' . $importer['uid']);
+               Logger::notice('Got migration for ' . $old_author . ', to ' . $new_author . ' with user ' . $importer['uid']);
 
                // Check signature
-               $signed_text = 'AccountMigration:' . $old_handle . ':' . $new_handle;
-               $key = self::key($old_handle);
+               $signed_text = 'AccountMigration:' . $old_author . ':' . $new_author;
+               $key = self::key($old_author);
                if (!Crypto::rsaVerify($signed_text, $signature, $key, 'sha256')) {
                        Logger::notice('No valid signature for migration.');
                        return false;
@@ -1356,9 +1369,9 @@ class Diaspora
                self::receiveProfile($importer, $data->profile);
 
                // change the technical stuff in contact
-               $data = Probe::uri($new_handle);
+               $data = Probe::uri($new_author);
                if ($data['network'] == Protocol::PHANTOM) {
-                       Logger::notice("Account for " . $new_handle . " couldn't be probed.");
+                       Logger::notice("Account for " . $new_author . " couldn't be probed.");
                        return false;
                }
 
@@ -1374,7 +1387,7 @@ class Diaspora
                        'network' => $data['network'],
                ];
 
-               Contact::update($fields, ['addr' => $old_handle]);
+               Contact::update($fields, ['addr' => $old_author->getAddr()]);
 
                Logger::notice('Contacts are updated.');
 
@@ -1391,15 +1404,15 @@ class Diaspora
         */
        private static function receiveAccountDeletion(SimpleXMLElement $data): bool
        {
-               $author = XML::unescape($data->author);
+               $author_handle = XML::unescape($data->author);
 
-               $contacts = DBA::select('contact', ['id'], ['addr' => $author]);
+               $contacts = DBA::select('contact', ['id'], ['addr' => $author_handle]);
                while ($contact = DBA::fetch($contacts)) {
                        Contact::remove($contact['id']);
                }
                DBA::close($contacts);
 
-               Logger::notice('Removed contacts for ' . $author);
+               Logger::notice('Removed contacts for ' . $author_handle);
 
                return true;
        }
@@ -1407,27 +1420,24 @@ class Diaspora
        /**
         * Fetch the uri from our database if we already have this item (maybe from ourselves)
         *
-        * @param string  $author    Author handle
-        * @param string  $guid      Message guid
-        * @param boolean $onlyfound Only return uri when found in the database
+        * @param string            $guid       Message guid
+        * @param WebFingerUri|null $person_uri Optional person to derive the base URL from
         *
-        * @return string The constructed uri or the one from our database or empty string on if $onlyfound is true
+        * @return string The constructed uri or the one from our database or empty string
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function getUriFromGuid(string $author, string $guid, bool $onlyfound = false): string
+       private static function getUriFromGuid(string $guid, WebFingerUri $person_uri = null): string
        {
                $item = Post::selectFirst(['uri'], ['guid' => $guid]);
-               if (DBA::isResult($item)) {
+               if ($item) {
                        return $item['uri'];
-               } elseif (!$onlyfound) {
-                       $person = FContact::getByURL($author);
-
-                       $parts = parse_url($person['url']);
-                       unset($parts['path']);
-                       $host_url = (string)Uri::fromParts($parts);
-
-                       return $host_url . '/objects/' . $guid;
+               } elseif ($person_uri) {
+                       try {
+                               return DI::dsprContact()->selectOneByAddr($person_uri)->baseurl . '/objects/' . $guid;
+                       } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                               return '';
+                       }
                }
 
                return '';
@@ -1458,31 +1468,31 @@ class Diaspora
                                continue;
                        }
 
-                       $person = FContact::getByURL($match[3]);
-                       if (empty($person)) {
-                               continue;
-                       }
+                       try {
+                               $contact = DI::dsprContact()->getByUrl(new Uri($match[3]));
+                               Tag::storeByHash($uriid, $match[1], $contact->name ?: $contact->nick, $contact->url);
+                       } catch (\Throwable $e) {
 
-                       Tag::storeByHash($uriid, $match[1], $person['name'] ?: $person['nick'], $person['url']);
+                       }
                }
        }
 
        /**
         * Processes an incoming comment
         *
-        * @param array  $importer  Array of the importer user
-        * @param string $sender    The sender of the message
+        * @param array            $importer  Array of the importer user
+        * @param WebFingerUri     $sender    The sender of the message
         * @param SimpleXMLElement $data      The message object
-        * @param string $xml       The original XML of the message
-        * @param int    $direction Indicates if the message had been fetched or pushed (self::PUSHED, self::FETCHED, self::FORCED_FETCH)
+        * @param string           $xml       The original XML of the message
+        * @param int              $direction Indicates if the message had been fetched or pushed (self::PUSHED, self::FETCHED, self::FORCED_FETCH)
         *
-        * @return int The message id of the generated comment or "false" if there was an error
+        * @return bool The message id of the generated comment or "false" if there was an error
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function receiveComment(array $importer, string $sender, SimpleXMLElement $data, string $xml, int $direction): bool
+       private static function receiveComment(array $importer, WebFingerUri $sender, SimpleXMLElement $data, string $xml, int $direction): bool
        {
-               $author = XML::unescape($data->author);
+               $author = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $parent_guid = XML::unescape($data->parent_guid);
                $text = XML::unescape($data->text);
@@ -1495,7 +1505,7 @@ class Diaspora
 
                if (isset($data->thread_parent_guid)) {
                        $thread_parent_guid = XML::unescape($data->thread_parent_guid);
-                       $thr_parent = self::getUriFromGuid('', $thread_parent_guid, true);
+                       $thr_parent = self::getUriFromGuid($thread_parent_guid);
                } else {
                        $thr_parent = '';
                }
@@ -1519,14 +1529,15 @@ class Diaspora
                        return false;
                }
 
-               $person = FContact::getByURL($author);
-               if (!is_array($person)) {
-                       Logger::notice('Unable to find author details');
+               try {
+                       $author_url = (string)DI::dsprContact()->getByAddr($author)->url;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Unable to find author details', ['author' => $author->getAddr()]);
                        return false;
                }
 
                // Fetch the contact id - if we know this contact
-               $author_contact = self::authorContactByUrl($contact, $person, $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                $datarray = [];
 
@@ -1534,21 +1545,21 @@ class Diaspora
                $datarray['contact-id'] = $author_contact['cid'];
                $datarray['network']  = $author_contact['network'];
 
-               $datarray['author-link'] = $person['url'];
-               $datarray['author-id'] = Contact::getIdForURL($person['url'], 0);
+               $datarray['author-link'] = $author_url;
+               $datarray['author-id'] = Contact::getIdForURL($author_url);
 
                $datarray['owner-link'] = $contact['url'];
-               $datarray['owner-id'] = Contact::getIdForURL($contact['url'], 0);
+               $datarray['owner-id'] = Contact::getIdForURL($contact['url']);
 
                // Will be overwritten for sharing accounts in Item::insert
                $datarray = self::setDirection($datarray, $direction);
 
                $datarray['guid'] = $guid;
-               $datarray['uri'] = self::getUriFromGuid($author, $guid);
+               $datarray['uri'] = self::getUriFromGuid($guid, $author);
                $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
 
                $datarray['verb'] = Activity::POST;
-               $datarray['gravity'] = GRAVITY_COMMENT;
+               $datarray['gravity'] = Item::GRAVITY_COMMENT;
 
                $datarray['thr-parent'] = $thr_parent ?: $toplevel_parent_item['uri'];
 
@@ -1565,7 +1576,7 @@ class Diaspora
                $datarray['plink'] = self::plink($author, $guid, $toplevel_parent_item['guid']);
                $body = Markdown::toBBCode($text);
 
-               $datarray['body'] = self::replacePeopleGuid($body, $person['url']);
+               $datarray['body'] = self::replacePeopleGuid($body, $author_url);
 
                self::storeMentions($datarray['uri-id'], $text);
                Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray['body']);
@@ -1615,20 +1626,26 @@ class Diaspora
         */
        private static function receiveConversationMessage(array $importer, array $contact, SimpleXMLElement $data, array $msg, $mesg, array $conversation): bool
        {
-               $author = XML::unescape($data->author);
+               $author_handle = XML::unescape($data->author);
                $guid = XML::unescape($data->guid);
                $subject = XML::unescape($data->subject);
 
                // "diaspora_handle" is the element name from the old version
                // "author" is the element name from the new version
                if ($mesg->author) {
-                       $msg_author = XML::unescape($mesg->author);
+                       $msg_author_handle = XML::unescape($mesg->author);
                } elseif ($mesg->diaspora_handle) {
-                       $msg_author = XML::unescape($mesg->diaspora_handle);
+                       $msg_author_handle = XML::unescape($mesg->diaspora_handle);
                } else {
                        return false;
                }
 
+               try {
+                       $msg_author_uri = WebFingerUri::fromString($msg_author_handle);
+               } catch (\InvalidArgumentException $e) {
+                       return false;
+               }
+
                $msg_guid = XML::unescape($mesg->guid);
                $msg_conversation_guid = XML::unescape($mesg->conversation_guid);
                $msg_text = XML::unescape($mesg->text);
@@ -1639,23 +1656,20 @@ class Diaspora
                        return false;
                }
 
-               $body = Markdown::toBBCode($msg_text);
-               $message_uri = $msg_author . ':' . $msg_guid;
-
-               $person = FContact::getByURL($msg_author);
+               $msg_author = DI::dsprContact()->getByAddr($msg_author_uri);
 
                return Mail::insert([
                        'uid'        => $importer['uid'],
                        'guid'       => $msg_guid,
                        'convid'     => $conversation['id'],
-                       'from-name'  => $person['name'],
-                       'from-photo' => $person['photo'],
-                       'from-url'   => $person['url'],
+                       'from-name'  => $msg_author->name,
+                       'from-photo' => (string)$msg_author->photo,
+                       'from-url'   => (string)$msg_author->url,
                        'contact-id' => $contact['id'],
                        'title'      => $subject,
-                       'body'       => $body,
-                       'uri'        => $message_uri,
-                       'parent-uri' => $author . ':' . $guid,
+                       'body'       => Markdown::toBBCode($msg_text),
+                       'uri'        => $msg_author_handle . ':' . $msg_guid,
+                       'parent-uri' => $author_handle . ':' . $guid,
                        'created'    => $msg_created_at
                ]);
        }
@@ -1672,7 +1686,7 @@ class Diaspora
         */
        private static function receiveConversation(array $importer, array $msg, SimpleXMLElement $data)
        {
-               $author = XML::unescape($data->author);
+               $author_handle = XML::unescape($data->author);
                $guid = XML::unescape($data->guid);
                $subject = XML::unescape($data->subject);
                $created_at = DateTimeFormat::utc(XML::unescape($data->created_at));
@@ -1685,7 +1699,7 @@ class Diaspora
                        return false;
                }
 
-               $contact = self::allowedContactByHandle($importer, $msg['author'], true);
+               $contact = self::allowedContactByHandle($importer, WebFingerUri::fromString($msg['author']), true);
                if (!$contact) {
                        return false;
                }
@@ -1699,7 +1713,7 @@ class Diaspora
                        $r = DBA::insert('conv', [
                                'uid'     => $importer['uid'],
                                'guid'    => $guid,
-                               'creator' => $author,
+                               'creator' => $author_handle,
                                'created' => $created_at,
                                'updated' => DateTimeFormat::utcNow(),
                                'subject' => $subject,
@@ -1725,18 +1739,18 @@ class Diaspora
        /**
         * Processes "like" messages
         *
-        * @param array  $importer  Array of the importer user
-        * @param string $sender    The sender of the message
+        * @param array            $importer  Array of the importer user
+        * @param WebFingerUri     $sender    The sender of the message
         * @param SimpleXMLElement $data      The message object
-        * @param int    $direction Indicates if the message had been fetched or pushed (self::PUSHED, self::FETCHED, self::FORCED_FETCH)
+        * @param int              $direction Indicates if the message had been fetched or pushed (self::PUSHED, self::FETCHED, self::FORCED_FETCH)
         *
         * @return bool Success or failure
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function receiveLike(array $importer, string $sender, SimpleXMLElement $data, int $direction): bool
+       private static function receiveLike(array $importer, WebFingerUri $sender, SimpleXMLElement $data, int $direction): bool
        {
-               $author = XML::unescape($data->author);
+               $author = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $parent_guid = XML::unescape($data->parent_guid);
                $parent_type = XML::unescape($data->parent_type);
@@ -1767,14 +1781,15 @@ class Diaspora
                        return false;
                }
 
-               $person = FContact::getByURL($author);
-               if (!is_array($person)) {
-                       Logger::notice('Unable to find author details');
+               try {
+                       $author_url = (string)DI::dsprContact()->getByAddr($author)->url;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Unable to find author details', ['author' => $author->getAddr()]);
                        return false;
                }
 
                // Fetch the contact id - if we know this contact
-               $author_contact = self::authorContactByUrl($contact, $person, $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora
                // We would accept this anyhow.
@@ -1794,14 +1809,14 @@ class Diaspora
 
                $datarray = self::setDirection($datarray, $direction);
 
-               $datarray['owner-link'] = $datarray['author-link'] = $person['url'];
-               $datarray['owner-id'] = $datarray['author-id'] = Contact::getIdForURL($person['url'], 0);
+               $datarray['owner-link'] = $datarray['author-link'] = $author_url;
+               $datarray['owner-id'] = $datarray['author-id'] = Contact::getIdForURL($author_url);
 
                $datarray['guid'] = $guid;
-               $datarray['uri'] = self::getUriFromGuid($author, $guid);
+               $datarray['uri'] = self::getUriFromGuid($guid, $author);
 
                $datarray['verb'] = $verb;
-               $datarray['gravity'] = GRAVITY_ACTIVITY;
+               $datarray['gravity'] = Item::GRAVITY_ACTIVITY;
                $datarray['thr-parent'] = $toplevel_parent_item['uri'];
 
                $datarray['object-type'] = Activity\ObjectType::NOTE;
@@ -1812,7 +1827,7 @@ class Diaspora
                $datarray['changed'] = $datarray['created'] = $datarray['edited'] = DateTimeFormat::utcNow();
 
                // like on comments have the comment as parent. So we need to fetch the toplevel parent
-               if ($toplevel_parent_item['gravity'] != GRAVITY_PARENT) {
+               if ($toplevel_parent_item['gravity'] != Item::GRAVITY_PARENT) {
                        $toplevel = Post::selectFirst(['origin'], ['id' => $toplevel_parent_item['parent']]);
                        $origin = $toplevel['origin'];
                } else {
@@ -1857,13 +1872,13 @@ class Diaspora
         */
        private static function receiveMessage(array $importer, SimpleXMLElement $data): bool
        {
-               $author = XML::unescape($data->author);
+               $author_uri = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $conversation_guid = XML::unescape($data->conversation_guid);
                $text = XML::unescape($data->text);
                $created_at = DateTimeFormat::utc(XML::unescape($data->created_at));
 
-               $contact = self::allowedContactByHandle($importer, $author, true);
+               $contact = self::allowedContactByHandle($importer, $author_uri, true);
                if (!$contact) {
                        return false;
                }
@@ -1872,41 +1887,37 @@ class Diaspora
                        GServer::setProtocol($contact['gsid'], Post\DeliveryData::DIASPORA);
                }
 
-               $conversation = null;
-
                $condition = ['uid' => $importer['uid'], 'guid' => $conversation_guid];
                $conversation = DBA::selectFirst('conv', [], $condition);
-
                if (!DBA::isResult($conversation)) {
                        Logger::notice('Conversation not available.');
                        return false;
                }
 
-               $message_uri = $author . ':' . $guid;
-
-               $person = FContact::getByURL($author);
-               if (!$person) {
-                       Logger::notice('Unable to find author details');
+               try {
+                       $author = DI::dsprContact()->getByAddr($author_uri);
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Unable to find author details', ['author' => $author_uri->getAddr()]);
                        return false;
                }
 
                $body = Markdown::toBBCode($text);
 
-               $body = self::replacePeopleGuid($body, $person['url']);
+               $body = self::replacePeopleGuid($body, $author->url);
 
                return Mail::insert([
                        'uid'        => $importer['uid'],
                        'guid'       => $guid,
                        'convid'     => $conversation['id'],
-                       'from-name'  => $person['name'],
-                       'from-photo' => $person['photo'],
-                       'from-url'   => $person['url'],
+                       'from-name'  => $author->name,
+                       'from-photo' => (string)$author->photo,
+                       'from-url'   => (string)$author->url,
                        'contact-id' => $contact['id'],
                        'title'      => $conversation['subject'],
                        'body'       => $body,
                        'reply'      => 1,
-                       'uri'        => $message_uri,
-                       'parent-uri' => $author . ':' . $conversation['guid'],
+                       'uri'        => $author_uri . ':' . $guid,
+                       'parent-uri' => $author_uri . ':' . $conversation['guid'],
                        'created'    => $created_at
                ]);
        }
@@ -1924,7 +1935,7 @@ class Diaspora
         */
        private static function receiveParticipation(array $importer, SimpleXMLElement $data, int $direction): bool
        {
-               $author = strtolower(XML::unescape($data->author));
+               $author = WebFingerUri::fromString(strtolower(XML::unescape($data->author)));
                $guid = XML::unescape($data->guid);
                $parent_guid = XML::unescape($data->parent_guid);
 
@@ -1955,13 +1966,14 @@ class Diaspora
                        return false;
                }
 
-               $person = FContact::getByURL($author);
-               if (!is_array($person)) {
-                       Logger::notice('Person not found: ' . $author);
+               try {
+                       $author_url = (string)DI::dsprContact()->getByAddr($author)->url;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('unable to find author details', ['author' => $author->getAddr()]);
                        return false;
                }
 
-               $author_contact = self::authorContactByUrl($contact, $person, $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                // Store participation
                $datarray = [];
@@ -1974,14 +1986,14 @@ class Diaspora
 
                $datarray = self::setDirection($datarray, $direction);
 
-               $datarray['owner-link'] = $datarray['author-link'] = $person['url'];
-               $datarray['owner-id'] = $datarray['author-id'] = Contact::getIdForURL($person['url'], 0);
+               $datarray['owner-link'] = $datarray['author-link'] = $author_url;
+               $datarray['owner-id'] = $datarray['author-id'] = Contact::getIdForURL($author_url);
 
                $datarray['guid'] = $guid;
-               $datarray['uri'] = self::getUriFromGuid($author, $guid);
+               $datarray['uri'] = self::getUriFromGuid($guid, $author);
 
                $datarray['verb'] = Activity::FOLLOW;
-               $datarray['gravity'] = GRAVITY_ACTIVITY;
+               $datarray['gravity'] = Item::GRAVITY_ACTIVITY;
                $datarray['thr-parent'] = $toplevel_parent_item['uri'];
 
                $datarray['object-type'] = Activity\ObjectType::NOTE;
@@ -2002,9 +2014,9 @@ class Diaspora
 
                // Send all existing comments and likes to the requesting server
                $comments = Post::select(['id', 'uri-id', 'parent-author-network', 'author-network', 'verb', 'gravity'],
-                       ['parent' => $toplevel_parent_item['id'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]);
+                       ['parent' => $toplevel_parent_item['id'], 'gravity' => [Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY]]);
                while ($comment = Post::fetch($comments)) {
-                       if (($comment['gravity'] == GRAVITY_ACTIVITY) && !in_array($comment['verb'], [Activity::LIKE, Activity::DISLIKE])) {
+                       if (($comment['gravity'] == Item::GRAVITY_ACTIVITY) && !in_array($comment['verb'], [Activity::LIKE, Activity::DISLIKE])) {
                                Logger::info('Unsupported activities are not relayed', ['item' => $comment['id'], 'verb' => $comment['verb']]);
                                continue;
                        }
@@ -2020,7 +2032,7 @@ class Diaspora
                        }
 
                        Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $author_contact['cid']]);
-                       if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['uri-id'], $author_contact['cid'], $datarray['uid'])) {
+                       if (Worker::add(Worker::PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['uri-id'], $author_contact['cid'], $datarray['uid'])) {
                                Post\DeliveryData::incrementQueueCount($comment['uri-id'], 1);
                        }
                }
@@ -2070,7 +2082,7 @@ class Diaspora
         */
        private static function receiveProfile(array $importer, SimpleXMLElement $data): bool
        {
-               $author = strtolower(XML::unescape($data->author));
+               $author = WebFingerUri::fromString(strtolower(XML::unescape($data->author)));
 
                $contact = self::contactByHandle($importer['uid'], $author);
                if (!$contact) {
@@ -2098,16 +2110,13 @@ class Diaspora
 
                $keywords = implode(', ', $keywords);
 
-               $handle_parts = explode('@', $author);
-               $nick = $handle_parts[0];
-
                if ($name === '') {
-                       $name = $handle_parts[0];
+                       $name = $author->getUser();
                }
 
                if (preg_match('|^https?://|', $image_url) === 0) {
                        // @TODO No HTTPS here?
-                       $image_url = 'http://' . $handle_parts[1] . $image_url;
+                       $image_url = 'http://' . $author->getFullHost() . $image_url;
                }
 
                Contact::updateAvatar($contact['id'], $image_url);
@@ -2129,7 +2138,7 @@ class Diaspora
 
                $fields = ['name' => $name, 'location' => $location,
                        'name-date' => DateTimeFormat::utcNow(), 'about' => $about,
-                       'addr' => $author, 'nick' => $nick, 'keywords' => $keywords,
+                       'addr' => $author->getAddr(), 'nick' => $author->getUser(), 'keywords' => $keywords,
                        'unsearchable' => !$searchable, 'sensitive' => $nsfw];
 
                if (!empty($birthday)) {
@@ -2172,13 +2181,15 @@ class Diaspora
         */
        private static function receiveContactRequest(array $importer, SimpleXMLElement $data): bool
        {
-               $author = XML::unescape($data->author);
+               $author_handle = XML::unescape($data->author);
                $recipient = XML::unescape($data->recipient);
 
-               if (!$author || !$recipient) {
+               if (!$author_handle || !$recipient) {
                        return false;
                }
 
+               $author = WebFingerUri::fromString($author_handle);
+
                // the current protocol version doesn't know these fields
                // That means that we will assume their existance
                if (isset($data->following)) {
@@ -2236,22 +2247,24 @@ class Diaspora
                        Logger::info("Author " . $author . " wants to listen to us.");
                }
 
-               $ret = FContact::getByURL($author);
-
-               if (!$ret || ($ret['network'] != Protocol::DIASPORA)) {
-                       Logger::notice("Cannot resolve diaspora handle " . $author . " for ".$recipient);
+               try {
+                       $author_url = (string)DI::dsprContact()->getByAddr($author)->url;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Cannot resolve diaspora handle for recipient', ['author' => $author->getAddr(), 'recipient' => $recipient]);
                        return false;
                }
 
-               $cid = Contact::getIdForURL($ret['url'], $importer['uid']);
+               $cid = Contact::getIdForURL($author_url, $importer['uid']);
                if (!empty($cid)) {
                        $contact = DBA::selectFirst('contact', [], ['id' => $cid, 'network' => Protocol::NATIVE_SUPPORT]);
                } else {
                        $contact = [];
                }
 
-               $item = ['author-id' => Contact::getIdForURL($ret['url']),
-                       'author-link' => $ret['url']];
+               $item = [
+                       'author-id'   => Contact::getIdForURL($author_url),
+                       'author-link' => $author_url
+               ];
 
                $result = Contact::addRelationship($importer, $contact, $item, false);
                if ($result === true) {
@@ -2273,139 +2286,6 @@ class Diaspora
                return true;
        }
 
-       /**
-        * Fetches a message with a given guid
-        *
-        * @param string $guid        message guid
-        * @param string $orig_author handle of the original post
-        * @return array|bool The fetched item or false on failure
-        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
-        * @throws \ImagickException
-        */
-       public static function originalItem(string $guid, string $orig_author)
-       {
-               if (empty($guid)) {
-                       Logger::notice('Empty guid. Quitting.');
-                       return false;
-               }
-
-               // Do we already have this item?
-               $fields = ['body', 'title', 'app', 'created', 'object-type', 'uri', 'guid',
-                       'author-name', 'author-link', 'author-avatar', 'plink', 'uri-id'];
-               $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
-               $item = Post::selectFirst($fields, $condition);
-
-               if (DBA::isResult($item)) {
-                       Logger::notice("reshared message " . $guid . " already exists on system.");
-
-                       // Maybe it is already a reshared item?
-                       // Then refetch the content, if it is a reshare from a reshare.
-                       // If it is a reshared post from another network then reformat to avoid display problems with two share elements
-                       if (self::isReshare($item['body'], true)) {
-                               $item = [];
-                       } elseif (self::isReshare($item['body'], false) || strstr($item['body'], '[share')) {
-                               $item['body'] = Markdown::toBBCode(BBCode::toMarkdown($item['body']));
-
-                               $item['body'] = self::replacePeopleGuid($item['body'], $item['author-link']);
-
-                               return $item;
-                       } else {
-                               return $item;
-                       }
-               }
-
-               if (!DBA::isResult($item)) {
-                       if (empty($orig_author)) {
-                               Logger::notice('Empty author for guid ' . $guid . '. Quitting.');
-                               return false;
-                       }
-
-                       $server = 'https://' . substr($orig_author, strpos($orig_author, '@') + 1);
-                       Logger::notice('1st try: reshared message ' . $guid . ' will be fetched via SSL from the server ' . $server);
-                       $stored = self::storeByGuid($guid, $server, true);
-
-                       if (!$stored) {
-                               $server = 'http://' . substr($orig_author, strpos($orig_author, '@') + 1);
-                               Logger::notice('2nd try: reshared message ' . $guid . ' will be fetched without SSL from the server ' . $server);
-                               $stored = self::storeByGuid($guid, $server, true);
-                       }
-
-                       if ($stored) {
-                               $fields = ['body', 'title', 'app', 'created', 'object-type', 'uri', 'guid',
-                                       'author-name', 'author-link', 'author-avatar', 'plink', 'uri-id'];
-                               $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
-                               $item = Post::selectFirst($fields, $condition);
-
-                               if (DBA::isResult($item)) {
-                                       // If it is a reshared post from another network then reformat to avoid display problems with two share elements
-                                       if (self::isReshare($item['body'], false)) {
-                                               $item['body'] = Markdown::toBBCode(BBCode::toMarkdown($item['body']));
-                                               $item['body'] = self::replacePeopleGuid($item['body'], $item['author-link']);
-                                       }
-
-                                       return $item;
-                               }
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Stores a reshare activity
-        *
-        * @param array   $item              Array of reshare post
-        * @param integer $parent_message_id Id of the parent post
-        * @param string  $guid              GUID string of reshare action
-        * @param string  $author            Author handle
-        */
-       private static function addReshareActivity(array $item, int $parent_message_id, string $guid, string $author)
-       {
-               $parent = Post::selectFirst(['uri', 'guid'], ['id' => $parent_message_id]);
-
-               $datarray = [];
-
-               $datarray['uid'] = $item['uid'];
-               $datarray['contact-id'] = $item['contact-id'];
-               $datarray['network'] = $item['network'];
-
-               $datarray['author-link'] = $item['author-link'];
-               $datarray['author-id'] = $item['author-id'];
-
-               $datarray['owner-link'] = $datarray['author-link'];
-               $datarray['owner-id'] = $datarray['author-id'];
-
-               $datarray['guid'] = $parent['guid'] . '-' . $guid;
-               $datarray['uri'] = self::getUriFromGuid($author, $datarray['guid']);
-               $datarray['thr-parent'] = $parent['uri'];
-
-               $datarray['verb'] = $datarray['body'] = Activity::ANNOUNCE;
-               $datarray['gravity'] = GRAVITY_ACTIVITY;
-               $datarray['object-type'] = Activity\ObjectType::NOTE;
-
-               $datarray['protocol'] = $item['protocol'];
-               $datarray['source'] = $item['source'];
-               $datarray['direction'] = $item['direction'];
-               $datarray['post-reason'] = $item['post-reason'];
-
-               $datarray['plink'] = self::plink($author, $datarray['guid']);
-               $datarray['private'] = $item['private'];
-               $datarray['changed'] = $datarray['created'] = $datarray['edited'] = $item['created'];
-
-               if (Item::isTooOld($datarray)) {
-                       Logger::info('Reshare activity is too old', ['created' => $datarray['created'], 'uid' => $datarray['uid'], 'guid' => $datarray['guid']]);
-                       return false;
-               }
-
-               $message_id = Item::insert($datarray);
-
-               if ($message_id) {
-                       Logger::info('Stored reshare activity.', ['guid' => $guid, 'id' => $message_id]);
-                       if ($datarray['uid'] == 0) {
-                               Item::distribute($message_id);
-                       }
-               }
-       }
-
        /**
         * Processes a reshare message
         *
@@ -2420,15 +2300,20 @@ class Diaspora
         */
        private static function receiveReshare(array $importer, SimpleXMLElement $data, string $xml, int $direction): bool
        {
-               $author = XML::unescape($data->author);
+               $author = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $created_at = DateTimeFormat::utc(XML::unescape($data->created_at));
-               $root_author = XML::unescape($data->root_author);
+               try {
+                       $root_author = WebFingerUri::fromString(XML::unescape($data->root_author));
+               } catch (\InvalidArgumentException $e) {
+                       return false;
+               }
+
                $root_guid = XML::unescape($data->root_guid);
                /// @todo handle unprocessed property "provider_display_name"
                $public = XML::unescape($data->public);
 
-               $contact = self::allowedContactByHandle($importer, $author, false);
+               $contact = self::allowedContactByHandle($importer, $author);
                if (!$contact) {
                        return false;
                }
@@ -2442,15 +2327,12 @@ class Diaspora
                        return true;
                }
 
-               $original_item = self::originalItem($root_guid, $root_author);
-               if (!$original_item) {
+               try {
+                       $original_person = DI::dsprContact()->getByAddr($root_author);
+               } catch (HTTPException\NotFoundException $e) {
                        return false;
                }
 
-               if (empty($original_item['plink'])) {
-                       $original_item['plink'] = self::plink($root_author, $root_guid);
-               }
-
                $datarray = [];
 
                $datarray['uid'] = $importer['uid'];
@@ -2464,44 +2346,27 @@ class Diaspora
                $datarray['owner-id'] = $datarray['author-id'];
 
                $datarray['guid'] = $guid;
-               $datarray['uri'] = $datarray['thr-parent'] = self::getUriFromGuid($author, $guid);
+               $datarray['uri'] = $datarray['thr-parent'] = self::getUriFromGuid($guid, $author);
                $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
 
                $datarray['verb'] = Activity::POST;
-               $datarray['gravity'] = GRAVITY_PARENT;
+               $datarray['gravity'] = Item::GRAVITY_PARENT;
 
                $datarray['protocol'] = Conversation::PARCEL_DIASPORA;
                $datarray['source'] = $xml;
 
                $datarray = self::setDirection($datarray, $direction);
 
-               /// @todo Copy tag data from original post
-
-               $prefix = BBCode::getShareOpeningTag(
-                       $original_item['author-name'],
-                       $original_item['author-link'],
-                       $original_item['author-avatar'],
-                       $original_item['plink'],
-                       $original_item['created'],
-                       $original_item['guid']
-               );
-
-               if (!empty($original_item['title'])) {
-                       $prefix .= '[h3]' . $original_item['title'] . "[/h3]\n";
+               $datarray['quote-uri-id'] = self::getQuoteUriId($root_guid, $importer['uid'], $original_person->url);
+               if (empty($datarray['quote-uri-id'])) {
+                       return false;
                }
 
-               $datarray['body'] = $prefix.$original_item['body'] . '[/share]';
-
-               Tag::storeFromBody($datarray['uri-id'], $datarray['body']);
-
-               $datarray['app']  = $original_item['app'];
-
-               $datarray['plink'] = self::plink($author, $guid);
+               $datarray['body']    = '';
+               $datarray['plink']   = self::plink($author, $guid);
                $datarray['private'] = (($public == 'false') ? Item::PRIVATE : Item::PUBLIC);
                $datarray['changed'] = $datarray['created'] = $datarray['edited'] = $created_at;
 
-               $datarray['object-type'] = $original_item['object-type'];
-
                self::fetchGuid($datarray);
 
                if (Item::isTooOld($datarray)) {
@@ -2513,11 +2378,6 @@ class Diaspora
 
                self::sendParticipation($contact, $datarray);
 
-               $root_message_id = self::messageExists($importer['uid'], $root_guid);
-               if ($root_message_id) {
-                       self::addReshareActivity($datarray, $root_message_id, $guid, $author);
-               }
-
                if ($message_id) {
                        Logger::info('Stored reshare ' . $datarray['guid'] . ' with message id ' . $message_id);
                        if ($datarray['uid'] == 0) {
@@ -2529,6 +2389,25 @@ class Diaspora
                }
        }
 
+       private static function getQuoteUriId(string $guid, int $uid, string $host): int
+       {
+               $shared_item = Post::selectFirst(['uri-id'], ['guid' => $guid, 'uid' => [$uid, 0], 'private' => [Item::PUBLIC, Item::UNLISTED]]);
+
+               if (!DBA::isResult($shared_item) && !empty($host) && Diaspora::storeByGuid($guid, $host, true)) {
+                       Logger::debug('Fetched post', ['guid' => $guid, 'host' => $host, 'uid' => $uid]);
+                       $shared_item = Post::selectFirst(['uri-id'], ['guid' => $guid, 'uid' => [$uid, 0], 'private' => [Item::PUBLIC, Item::UNLISTED]]);
+               } elseif (DBA::isResult($shared_item)) {
+                       Logger::debug('Found existing post', ['guid' => $guid, 'host' => $host, 'uid' => $uid]);
+               }
+
+               if (!DBA::isResult($shared_item)) {
+                       Logger::notice('Post does not exist.', ['guid' => $guid, 'host' => $host, 'uid' => $uid]);
+                       return 0;
+               }
+
+               return $shared_item['uri-id'];
+       }
+
        /**
         * Processes retractions
         *
@@ -2541,19 +2420,18 @@ class Diaspora
         */
        private static function itemRetraction(array $importer, array $contact, SimpleXMLElement $data): bool
        {
-               $author = XML::unescape($data->author);
+               $author_uri  = WebFingerUri::fromString(XML::unescape($data->author));
                $target_guid = XML::unescape($data->target_guid);
                $target_type = XML::unescape($data->target_type);
 
-               $person = FContact::getByURL($author);
-               if (!is_array($person)) {
-                       Logger::notice('Unable to find author detail for ' . $author);
+               try {
+                       $author = DI::dsprContact()->getByAddr($author_uri);
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Unable to find details for author', ['author' => $author_uri->getAddr()]);
                        return false;
                }
 
-               if (empty($contact['url'])) {
-                       $contact['url'] = $person['url'];
-               }
+               $contact_url = $contact['url'] ?? '' ?: (string)$author->url;
 
                // Fetch items that are about to be deleted
                $fields = ['uid', 'id', 'parent', 'author-link', 'uri-id'];
@@ -2581,8 +2459,8 @@ class Diaspora
                        $parent = Post::selectFirst(['author-link'], ['id' => $item['parent']]);
 
                        // Only delete it if the parent author really fits
-                       if (!Strings::compareLink($parent['author-link'], $contact['url']) && !Strings::compareLink($item['author-link'], $contact['url'])) {
-                               Logger::info("Thread author " . $parent['author-link'] . " and item author " . $item['author-link'] . " don't fit to expected contact " . $contact['url']);
+                       if (!Strings::compareLink($parent['author-link'], $contact_url) && !Strings::compareLink($item['author-link'], $contact_url)) {
+                               Logger::info("Thread author " . $parent['author-link'] . " and item author " . $item['author-link'] . " don't fit to expected contact " . $contact_url);
                                continue;
                        }
 
@@ -2598,14 +2476,14 @@ class Diaspora
        /**
         * Receives retraction messages
         *
-        * @param array  $importer Array of the importer user
-        * @param string $sender   The sender of the message
+        * @param array            $importer Array of the importer user
+        * @param WebFingerUri     $sender   The sender of the message
         * @param SimpleXMLElement $data     The message object
         *
         * @return bool Success
         * @throws \Exception
         */
-       private static function receiveRetraction(array $importer, string $sender, SimpleXMLElement $data)
+       private static function receiveRetraction(array $importer, WebFingerUri $sender, SimpleXMLElement $data)
        {
                $target_type = XML::unescape($data->target_type);
 
@@ -2732,14 +2610,14 @@ class Diaspora
         */
        private static function receiveStatusMessage(array $importer, SimpleXMLElement $data, string $xml, int $direction)
        {
-               $author = XML::unescape($data->author);
+               $author = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $created_at = DateTimeFormat::utc(XML::unescape($data->created_at));
                $public = XML::unescape($data->public);
                $text = XML::unescape($data->text);
                $provider_display_name = XML::unescape($data->provider_display_name);
 
-               $contact = self::allowedContactByHandle($importer, $author, false);
+               $contact = self::allowedContactByHandle($importer, $author);
                if (!$contact) {
                        return false;
                }
@@ -2765,7 +2643,7 @@ class Diaspora
                $datarray = [];
 
                $datarray['guid'] = $guid;
-               $datarray['uri'] = $datarray['thr-parent'] = self::getUriFromGuid($author, $guid);
+               $datarray['uri'] = $datarray['thr-parent'] = self::getUriFromGuid($guid, $author);
                $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
 
                // Attach embedded pictures to the body
@@ -2804,7 +2682,7 @@ class Diaspora
                $datarray['owner-id'] = $datarray['author-id'];
 
                $datarray['verb'] = Activity::POST;
-               $datarray['gravity'] = GRAVITY_PARENT;
+               $datarray['gravity'] = Item::GRAVITY_PARENT;
 
                $datarray['protocol'] = Conversation::PARCEL_DIASPORA;
                $datarray['source'] = $xml;
@@ -2977,7 +2855,7 @@ class Diaspora
 
                $namespaces = ['me' => ActivityNamespace::SALMON_ME];
 
-               return XML::fromArray($xmldata, $xml, false, $namespaces);
+               return XML::fromArray($xmldata, $dummy, false, $namespaces);
        }
 
        /**
@@ -3047,13 +2925,13 @@ class Diaspora
 
                $logid = Strings::getRandomHex(4);
 
-               // We always try to use the data from the fcontact table.
+               // We always try to use the data from the diaspora-contact table.
                // This is important for transmitting data to Friendica servers.
-               if (!empty($contact['addr'])) {
-                       $fcontact = FContact::getByURL($contact['addr']);
-                       if (!empty($fcontact)) {
-                               $dest_url = ($public_batch ? $fcontact['batch'] : $fcontact['notify']);
-                       }
+               try {
+                       $target = DI::dsprContact()->getByAddr(WebFingerUri::fromString($contact['addr']));
+                       $dest_url = $public_batch ? $target->batch : $target->notify;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+
                }
 
                if (empty($dest_url)) {
@@ -3077,6 +2955,12 @@ class Diaspora
                        return 200;
                }
 
+               if (!empty($contact['gsid']) && (empty($return_code) || $postResult->isTimeout())) {
+                       GServer::setFailureById($contact['gsid']);
+               } elseif (!empty($contact['gsid']) && ($return_code >= 200) && ($return_code <= 299)) {
+                       GServer::setReachableById($contact['gsid'], Protocol::DIASPORA);
+               }
+
                Logger::notice('transmit: ' . $logid . '-' . $guid . ' to ' . $dest_url . ' returns: ' . $return_code);
 
                return $return_code ? $return_code : -1;
@@ -3090,12 +2974,11 @@ class Diaspora
         * @param array  $message The message data
         *
         * @return string The post XML
+        * @throws \Exception
         */
        public static function buildPostXml(string $type, array $message): string
        {
-               $data = [$type => $message];
-
-               return XML::fromArray($data, $xml);
+               return XML::fromArray([$type => $message]);
        }
 
        /**
@@ -3122,18 +3005,19 @@ class Diaspora
                }
 
                // When sending content to Friendica contacts using the Diaspora protocol
-               // we have to fetch the public key from the fcontact.
+               // we have to fetch the public key from the diaspora-contact.
                // This is due to the fact that legacy DFRN had unique keys for every contact.
                $pubkey = $contact['pubkey'];
                if (!empty($contact['addr'])) {
-                       $fcontact = FContact::getByURL($contact['addr']);
-                       if (!empty($fcontact)) {
-                               $pubkey = $fcontact['pubkey'];
+                       try {
+                               $pubkey = DI::dsprContact()->getByAddr(WebFingerUri::fromString($contact['addr']))->pubKey;
+                       } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+
                        }
                } else {
                        // The "addr" field should always be filled.
                        // If this isn't the case, it will raise a notice some lines later.
-                       // And in the log we will see where it came from and we can handle it there.
+                       // And in the log we will see where it came from, and we can handle it there.
                        Logger::notice('Empty addr', ['contact' => $contact ?? [], 'callstack' => System::callstack(20)]);
                }
 
@@ -3182,16 +3066,16 @@ class Diaspora
                        $owner = User::getOwnerDataById($item['uid']);
                }
 
-               $author = self::myHandle($owner);
+               $author_handle = self::myHandle($owner);
 
                $message = [
-                       'author' => $author,
+                       'author' => $author_handle,
                        'guid' => System::createUUID(),
                        'parent_type' => 'Post',
                        'parent_guid' => $item['guid']
                ];
 
-               Logger::info('Send participation for ' . $item['guid'] . ' by ' . $author);
+               Logger::info('Send participation for ' . $item['guid'] . ' by ' . $author_handle);
 
                // It doesn't matter what we store, we only want to avoid sending repeated notifications for the same item
                DI::cache()->set($cachekey, $item['guid'], Duration::QUARTER_HOUR);
@@ -3299,61 +3183,30 @@ class Diaspora
        }
 
        /**
-        * Checks a message body if it is a reshare
+        * Fetch reshare details
         *
-        * @param string $body     The message body that is to be check
-        * @param bool   $complete Should it be a complete check or a simple check?
+        * @param array $item The message body that is to be check
         *
-        * @return array|bool Reshare details or "false" if no reshare
+        * @return array Reshare details (empty if the item is no reshare)
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function isReshare(string $body, bool $complete = true)
+       public static function getReshareDetails(array $item): array
        {
-               $body = trim($body);
-
-               $reshared = Item::getShareArray(['body' => $body]);
+               $reshared = DI::contentItem()->getSharedPost($item, ['guid', 'network', 'author-addr']);
                if (empty($reshared)) {
-                       return false;
-               }
-
-               // Skip if it isn't a pure repeated messages
-               // Does it start with a share?
-               if (!empty($reshared['comment']) && $complete) {
-                       return false;
-               }
-
-               if (!empty($reshared['guid']) && $complete) {
-                       $condition = ['guid' => $reshared['guid'], 'network' => [Protocol::DFRN, Protocol::DIASPORA]];
-                       $item = Post::selectFirst(['contact-id'], $condition);
-                       if (DBA::isResult($item)) {
-                               $ret = [];
-                               $ret['root_handle'] = self::handleFromContact($item['contact-id']);
-                               $ret['root_guid'] = $reshared['guid'];
-                               return $ret;
-                       } elseif ($complete) {
-                               // We are resharing something that isn't a DFRN or Diaspora post.
-                               // So we have to return "false" on "$complete" to not trigger a reshare.
-                               return false;
-                       }
-               } elseif (empty($reshared['guid']) && $complete) {
-                       return false;
-               }
-
-               $ret = [];
-
-               if (!empty($reshared['profile']) && ($cid = Contact::getIdForURL($reshared['profile']))) {
-                       $contact = DBA::selectFirst('contact', ['addr'], ['id' => $cid]);
-                       if (!empty($contact['addr'])) {
-                               $ret['root_handle'] = $contact['addr'];
-                       }
+                       return [];
                }
 
-               if (empty($ret) && !$complete) {
-                       return true;
+               // Skip if it isn't a pure repeated messages or not a real reshare
+               if (!empty($reshared['comment']) || !in_array($reshared['post']['network'], [Protocol::DFRN, Protocol::DIASPORA])) {
+                       return [];
                }
 
-               return $ret;
+               return [
+                       'root_handle' => strtolower($reshared['post']['author-addr']),
+                       'root_guid'   => $reshared['post']['guid'],
+               ];
        }
 
        /**
@@ -3450,7 +3303,7 @@ class Diaspora
                $edited = DateTimeFormat::utc($item['edited'] ?? $item['created'], DateTimeFormat::ATOM);
 
                // Detect a share element and do a reshare
-               if (($item['private'] != Item::PRIVATE) && ($ret = self::isReshare($item['body']))) {
+               if (($item['private'] != Item::PRIVATE) && ($ret = self::getReshareDetails($item))) {
                        $message = [
                                'author'                => $myaddr,
                                'guid'                  => $item['guid'],
@@ -3463,8 +3316,17 @@ class Diaspora
 
                        $type = 'reshare';
                } else {
+                       $native_photos = DI::config()->get('diaspora', 'native_photos');
+                       if ($native_photos) {
+                               $item['body'] = Post\Media::removeFromEndOfBody($item['body']);
+                               $attach_media = [Post\Media::AUDIO, Post\Media::VIDEO];
+                       } else {
+                               $attach_media = [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO];
+                       }
+
                        $title = $item['title'];
-                       $body = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
+                       $body  = Post\Media::addAttachmentsToBody($item['uri-id'], DI::contentItem()->addSharedPost($item), $attach_media);
+                       $body  = Post\Media::addHTMLLinkToBody($item['uri-id'], $body);
 
                        // Fetch the title from an attached link - if there is one
                        if (empty($item['title']) && DI::pConfig()->get($owner['uid'], 'system', 'attach_link_title')) {
@@ -3474,11 +3336,6 @@ class Diaspora
                                }
                        }
 
-                       if ($item['author-link'] != $item['owner-link']) {
-                               $body = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'],
-                                       $item['plink'], $item['created']) . $body . '[/share]';
-                       }
-
                        // convert to markdown
                        $body = html_entity_decode(BBCode::toMarkdown($body));
 
@@ -3487,7 +3344,7 @@ class Diaspora
                                $body = '### ' . html_entity_decode($title) . "\n\n" . $body;
                        }
 
-                       $attachments = Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT, Post\Media::UNKNOWN]);
+                       $attachments = Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT]);
                        if (!empty($attachments)) {
                                $body .= "\n[hr]\n";
                                foreach ($attachments as $attachment) {
@@ -3517,6 +3374,10 @@ class Diaspora
                                'location' => $location
                        ];
 
+                       if ($native_photos) {
+                               $message = self::addPhotos($item, $message);
+                       }
+
                        // Diaspora rejects messages when they contain a location without "lat" or "lng"
                        if (!isset($location['lat']) || !isset($location['lng'])) {
                                unset($message['location']);
@@ -3551,6 +3412,44 @@ class Diaspora
                return $msg;
        }
 
+       /**
+        * Add photo elements to the message array
+        *
+        * @param array $item
+        * @param array $message
+        * @return array
+        */
+       private static function addPhotos(array $item, array $message): array
+       {
+               $medias = Post\Media::getByURIId($item['uri-id'], [Post\Media::IMAGE]);
+               $public = ($item['private'] == Item::PRIVATE ? 'false' : 'true');
+
+               $counter = 0;
+               foreach ($medias as $media) {
+                       if (Item::containsLink($item['body'], $media['preview'] ?? $media['url'], $media['type'])) {
+                               continue;
+                       }
+
+                       $name = basename($media['url']);
+                       $path = str_replace($name, '', $media['url']);
+
+                       $message[++$counter . ':photo'] = [
+                               'guid'                => Item::guid(['uri' => $media['url']], false),
+                               'author'              => $item['author-addr'],
+                               'public'              => $public,
+                               'created_at'          => $item['created'],
+                               'remote_photo_path'   => $path,
+                               'remote_photo_name'   => $name,
+                               'status_message_guid' => $item['guid'],
+                               'height'              => $media['height'],
+                               'width'               => $media['width'],
+                               'text'                => $media['description'],
+                       ];
+               }
+
+               return $message;
+       }
+
        private static function prependParentAuthorMention(string $body, string $profile_url): string
        {
                $profile = Contact::getByURL($profile_url, false, ['addr', 'name']);
@@ -3686,7 +3585,8 @@ class Diaspora
                        $thread_parent_item = Post::selectFirst(['guid', 'author-id', 'author-link', 'gravity'], ['uri' => $item['thr-parent'], 'uid' => $item['uid']]);
                }
 
-               $body = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
+               $body = Post\Media::addAttachmentsToBody($item['uri-id'], DI::contentItem()->addSharedPost($item));
+               $body = Post\Media::addHTMLLinkToBody($item['uri-id'], $body);
 
                // The replied to autor mention is prepended for clarity if:
                // - Item replied isn't yours
@@ -3694,7 +3594,7 @@ class Diaspora
                // - Implicit mentions are enabled
                if (
                        $item['author-id'] != $thread_parent_item['author-id']
-                       && ($thread_parent_item['gravity'] != GRAVITY_PARENT)
+                       && ($thread_parent_item['gravity'] != Item::GRAVITY_PARENT)
                        && (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions'))
                        && !DI::config()->get('system', 'disable_implicit_mentions')
                ) {
@@ -3782,7 +3682,7 @@ class Diaspora
 
                Logger::info('Got relayable data ' . $type . ' for item ' . $item['guid'] . ' (' . $item['id'] . ')');
 
-               $msg = json_decode($item['signed_text'], true);
+               $msg = json_decode($item['signed_text'] ?? '', true);
 
                $message = [];
                if (is_array($msg)) {
@@ -3823,11 +3723,11 @@ class Diaspora
         */
        public static function sendRetraction(array $item, array $owner, array $contact, bool $public_batch = false, bool $relay = false): int
        {
-               $itemaddr = self::handleFromContact($item['contact-id'], $item['author-id']);
+               $itemaddr = strtolower($item['author-addr']);
 
                $msg_type = 'retraction';
 
-               if ($item['gravity'] == GRAVITY_PARENT) {
+               if ($item['gravity'] == Item::GRAVITY_PARENT) {
                        $target_type = 'Post';
                } elseif (in_array($item['verb'], [Activity::LIKE, Activity::DISLIKE])) {
                        $target_type = 'Like';
@@ -4133,6 +4033,11 @@ class Diaspora
                        return false;
                }
 
+               if (!self::parentSupportDiaspora($item['thr-parent-id'])) {
+                       Logger::info('One of the parents does not support Diaspora. A signature will not be created.', ['uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
+                       return false;
+               }
+
                $message = self::constructComment($item, $owner);
                if ($message === false) {
                        return false;
@@ -4143,42 +4048,56 @@ class Diaspora
                return $message;
        }
 
-       public static function performReshare(int $UriId, int $uid): int
+       /**
+        * Check if the parent and their parents support Diaspora
+        *
+        * @param integer $parent_id
+        * @return boolean
+        * @throws InternalServerErrorException
+        * @throws \ImagickException
+        */
+       private static function parentSupportDiaspora(int $parent_id): bool
        {
-               $fields = ['uri-id', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
-               $item = Post::selectFirst($fields, ['uri-id' => $UriId, 'uid' => [$uid, 0], 'private' => [Item::PUBLIC, Item::UNLISTED]]);
-               if (!DBA::isResult($item)) {
-                       return 0;
+               $parent_post = Post::selectFirstPost(['gravity', 'signed_text', 'author-link', 'thr-parent-id'], ['uri-id' => $parent_id]);
+               if (empty($parent_post['thr-parent-id'])) {
+                       Logger::warning('Parent post does not exist.', ['parent-id' => $parent_id]);
+                       return false;
                }
 
-               if (strpos($item['body'], '[/share]') !== false) {
-                       $pos = strpos($item['body'], '[share');
-                       $post = substr($item['body'], $pos);
-               } else {
-                       $post = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created'], $item['guid']);
+               if (!self::isSupportedByContactUrl($parent_post['author-link'])) {
+                       Logger::info('Parent author is no Diaspora contact.', ['parent-id' => $parent_id]);
+                       return false;
+               }
 
-                       if (!empty($item['title'])) {
-                               $post .= '[h3]' . $item['title'] . "[/h3]\n";
-                       }
+               if (($parent_post['gravity'] == Item::GRAVITY_COMMENT) && empty($parent_post['signed_text'])) {
+                       Logger::info('Parent comment has got no Diaspora signature.', ['parent-id' => $parent_id]);
+                       return false;
+               }
 
-                       $post .= $item['body'];
-                       $post .= '[/share]';
+               if ($parent_post['gravity'] == Item::GRAVITY_COMMENT) {
+                       return self::parentSupportDiaspora($parent_post['thr-parent-id']);
                }
 
+               return true;
+       }
+
+       public static function performReshare(int $UriId, int $uid): int
+       {
                $owner  = User::getOwnerDataById($uid);
                $author = Contact::getPublicIdByUserId($uid);
 
                $item = [
-                       'uid'        => $uid,
-                       'verb'       => Activity::POST,
-                       'contact-id' => $owner['id'],
-                       'author-id'  => $author,
-                       'owner-id'   => $author,
-                       'body'       => $post,
-                       'allow_cid'  => $owner['allow_cid'] ?? '',
-                       'allow_gid'  => $owner['allow_gid']?? '',
-                       'deny_cid'   => $owner['deny_cid'] ?? '',
-                       'deny_gid'   => $owner['deny_gid'] ?? '',
+                       'uid'          => $uid,
+                       'verb'         => Activity::POST,
+                       'contact-id'   => $owner['id'],
+                       'author-id'    => $author,
+                       'owner-id'     => $author,
+                       'body'         => '',
+                       'quote-uri-id' => $UriId,
+                       'allow_cid'    => $owner['allow_cid'] ?? '',
+                       'allow_gid'    => $owner['allow_gid']?? '',
+                       'deny_cid'     => $owner['deny_cid'] ?? '',
+                       'deny_gid'     => $owner['deny_gid'] ?? '',
                ];
 
                if (!empty($item['allow_cid'] . $item['allow_gid'] . $item['deny_cid'] . $item['deny_gid'])) {