]> git.mxchange.org Git - friendica.git/blobdiff - src/Protocol/Diaspora.php
Use the owner, not the author
[friendica.git] / src / Protocol / Diaspora.php
index 7fd899f8dd8cdc059dd24d6ea95f1fea2028caf3..dd6c2115b5e32714a868bae0ee589465ca76bf31 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,15 +41,15 @@ use Friendica\Model\Post;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
 use Friendica\Network\HTTPClient\Client\HttpClientAccept;
-use Friendica\Network\HTTPException\InternalServerErrorException;
+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;
 
@@ -84,14 +83,21 @@ class Diaspora
                        return $contacts;
                }
 
-               $items = Post::select(['author-id', 'author-link', 'parent-author-link', 'parent-guid', 'guid'],
-                       ['parent' => $item['parent'], 'gravity' => [Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY]]);
+               $items = Post::select(
+                       ['author-id', 'author-link', 'parent-author-link', 'parent-guid', 'guid'],
+                       ['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']]);
-                       if (!DBA::isResult($contact) || empty($contact['batch']) ||
+                       $contact = DBA::selectFirst(
+                               'contact',
+                               ['id', 'url', 'name', 'protocol', 'batch', 'network'],
+                               ['id' => $item['author-id']]
+                       );
+                       if (
+                               !DBA::isResult($contact) || empty($contact['batch']) ||
                                ($contact['network'] != Protocol::DIASPORA) ||
-                               Strings::compareLink($item['parent-author-link'], $item['author-link'])) {
+                               Strings::compareLink($item['parent-author-link'], $item['author-link'])
+                       ) {
                                continue;
                        }
 
@@ -261,7 +267,7 @@ class Diaspora
                                if ($no_exit) {
                                        return false;
                                } else {
-                                       throw new \Friendica\Network\HTTPException\BadRequestException();
+                                       throw new HTTPException\BadRequestException();
                                }
                        }
                } else {
@@ -271,11 +277,11 @@ class Diaspora
                $basedom = XML::parseString($xml, true);
 
                if (!is_object($basedom)) {
-                       Logger::notice('Received data does not seem to be an XML. Discarding. '.$xml);
+                       Logger::notice('Received data does not seem to be an XML. Discarding. ' . $xml);
                        if ($no_exit) {
                                return false;
                        } else {
-                               throw new \Friendica\Network\HTTPException\BadRequestException();
+                               throw new HTTPException\BadRequestException();
                        }
                }
 
@@ -301,7 +307,7 @@ class Diaspora
                        if ($no_exit) {
                                return false;
                        } else {
-                               throw new \Friendica\Network\HTTPException\BadRequestException();
+                               throw new HTTPException\BadRequestException();
                        }
                }
 
@@ -316,7 +322,7 @@ class Diaspora
                        if ($no_exit) {
                                return false;
                        } else {
-                               throw new \Friendica\Network\HTTPException\BadRequestException();
+                               throw new HTTPException\BadRequestException();
                        }
                }
 
@@ -326,7 +332,7 @@ class Diaspora
                        if ($no_exit) {
                                return false;
                        } else {
-                               throw new \Friendica\Network\HTTPException\BadRequestException();
+                               throw new HTTPException\BadRequestException();
                        }
                }
 
@@ -418,7 +424,7 @@ class Diaspora
 
                if (!$base) {
                        Logger::notice('unable to locate salmon data in xml');
-                       throw new \Friendica\Network\HTTPException\BadRequestException();
+                       throw new HTTPException\BadRequestException();
                }
 
 
@@ -438,14 +444,11 @@ class Diaspora
                $encoding = $base->encoding;
                $alg = $base->alg;
 
-
-               $signed_data = $data.'.'.Strings::base64UrlEncode($type).'.'.Strings::base64UrlEncode($encoding).'.'.Strings::base64UrlEncode($alg);
-
+               $signed_data = $data . '.' . Strings::base64UrlEncode($type) . '.' . Strings::base64UrlEncode($encoding) . '.' . Strings::base64UrlEncode($alg);
 
                // decode the data
                $data = Strings::base64UrlDecode($data);
 
-
                if ($public) {
                        $inner_decrypted = $data;
                } else {
@@ -455,24 +458,23 @@ class Diaspora
                }
 
                // 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);
+               Logger::info('Fetching key for ' . $author);
                $key = self::key($author);
                if (!$key) {
                        Logger::notice('Could not retrieve author key.');
-                       throw new \Friendica\Network\HTTPException\BadRequestException();
+                       throw new HTTPException\BadRequestException();
                }
 
                $verify = Crypto::rsaVerify($signed_data, $signature, $key);
 
                if (!$verify) {
                        Logger::notice('Message did not verify. Discarding.');
-                       throw new \Friendica\Network\HTTPException\BadRequestException();
+                       throw new HTTPException\BadRequestException();
                }
 
-               Logger::notice('Message verified.');
+               Logger::info('Message verified.');
 
                return [
                        'message' => $inner_decrypted,
@@ -494,14 +496,13 @@ class Diaspora
         */
        public static function dispatchPublic(array $msg, int $direction)
        {
-               $enabled = intval(DI::config()->get('system', 'diaspora_enabled'));
-               if (!$enabled) {
+               if (!DI::config()->get('system', 'diaspora_enabled')) {
                        Logger::notice('Diaspora is disabled');
                        return false;
                }
 
                if (!($fields = self::validPosting($msg))) {
-                       Logger::warning('Invalid posting');
+                       Logger::notice('Invalid posting', ['msg' => $msg]);
                        return false;
                }
 
@@ -536,7 +537,7 @@ class Diaspora
                if (is_null($fields)) {
                        $private = true;
                        if (!($fields = self::validPosting($msg))) {
-                               Logger::warning('Invalid posting');
+                               Logger::notice('Invalid posting', ['msg' => $msg]);
                                return false;
                        }
                } else {
@@ -721,7 +722,8 @@ class Diaspora
 
                                $signed_data .= $entry;
                        }
-                       if (!in_array($fieldname, ['parent_author_signature', 'target_author_signature'])
+                       if (
+                               !in_array($fieldname, ['parent_author_signature', 'target_author_signature'])
                                || ($orig_type == 'relayable_retraction')
                        ) {
                                XML::copy($entry, $fields, $fieldname);
@@ -794,14 +796,12 @@ class Diaspora
         */
        private static function key(WebFingerUri $uri): string
        {
-               Logger::notice('Fetching diaspora key', ['handle' => $uri->getAddr(), 'callstack' => System::callstack(20)]);
-
-               $fcontact = FContact::getByURL($uri);
-               if (!empty($fcontact['pubkey'])) {
-                       return $fcontact['pubkey'];
+               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 '';
        }
 
        /**
@@ -816,21 +816,30 @@ class Diaspora
         */
        private static function contactByHandle(int $uid, WebFingerUri $uri): array
        {
+               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;
        }
 
        /**
@@ -927,7 +936,7 @@ class Diaspora
        {
                $item = Post::selectFirst(['id'], ['uid' => $uid, 'guid' => $guid]);
                if (DBA::isResult($item)) {
-                       Logger::notice('Message ' . $guid . ' already exists for user ' . $uid);
+                       Logger::notice('Message already exists.', ['uid' => $uid, 'guid' => $guid, 'id' => $item['id']]);
                        return $item['id'];
                }
 
@@ -938,6 +947,7 @@ class Diaspora
         * Checks for links to posts in a message
         *
         * @param array $item The item array
+        *
         * @return void
         */
        private static function fetchGuid(array $item)
@@ -977,7 +987,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]';
@@ -1141,7 +1151,7 @@ class Diaspora
        {
                // Check for Diaspora (and Friendica) typical paths
                if (!preg_match('=(https?://.+)/(?:posts|display|objects)/([a-zA-Z0-9-_@.:%]+[a-zA-Z0-9])=i', $url, $matches)) {
-                       Logger::info('Invalid url', ['url' => $url]);
+                       Logger::notice('Invalid url', ['url' => $url]);
                        return false;
                }
 
@@ -1162,7 +1172,7 @@ class Diaspora
                        Logger::info('Found', ['id' => $item['id']]);
                        return $item['id'];
                } else {
-                       Logger::info('Not found', ['guid' => $guid, 'uid' => $uid]);
+                       Logger::notice('Not found', ['guid' => $guid, 'uid' => $uid]);
                        return false;
                }
        }
@@ -1180,26 +1190,31 @@ class Diaspora
         */
        private static function parentItem(int $uid, string $guid, WebFingerUri $author, array $contact)
        {
-               $fields = ['id', 'parent', 'body', 'wall', 'uri', 'guid', 'private', 'origin',
+               $fields = [
+                       'id', 'parent', 'body', 'wall', 'uri', 'guid', 'private', 'origin',
                        'author-name', 'author-link', 'author-avatar', 'gravity',
-                       'owner-name', 'owner-link', 'owner-avatar'];
+                       'owner-name', 'owner-link', 'owner-avatar'
+               ];
 
                $condition = ['uid' => $uid, 'guid' => $guid];
                $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()]);
                        }
                }
 
@@ -1207,7 +1222,7 @@ class Diaspora
                        Logger::notice('Parent item not found: parent: ' . $guid . ' - user: ' . $uid);
                        return false;
                } else {
-                       Logger::notice('Parent item found: parent: ' . $guid . ' - user: ' . $uid);
+                       Logger::info('Parent item found: parent: ' . $guid . ' - user: ' . $uid);
                        return $item;
                }
        }
@@ -1347,7 +1362,7 @@ class Diaspora
                        return false;
                }
 
-               Logger::notice('Got migration for ' . $old_author . ', to ' . $new_author . ' with user ' . $importer['uid']);
+               Logger::info('Got migration for ' . $old_author . ', to ' . $new_author . ' with user ' . $importer['uid']);
 
                // Check signature
                $signed_text = 'AccountMigration:' . $old_author . ':' . $new_author;
@@ -1381,7 +1396,7 @@ class Diaspora
 
                Contact::update($fields, ['addr' => $old_author->getAddr()]);
 
-               Logger::notice('Contacts are updated.');
+               Logger::info('Contacts are updated.');
 
                return true;
        }
@@ -1404,7 +1419,7 @@ class Diaspora
                }
                DBA::close($contacts);
 
-               Logger::notice('Removed contacts for ' . $author_handle);
+               Logger::info('Removed contacts for ' . $author_handle);
 
                return true;
        }
@@ -1422,16 +1437,14 @@ class Diaspora
        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 ($person_uri) {
-                       $person = FContact::getByURL($person_uri);
-
-                       $parts = parse_url($person['url']);
-                       unset($parts['path']);
-                       $host_url = (string)Uri::fromParts($parts);
-
-                       return $host_url . '/objects/' . $guid;
+                       try {
+                               return DI::dsprContact()->selectOneByAddr($person_uri)->baseurl . '/objects/' . $guid;
+                       } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) {
+                               return '';
+                       }
                }
 
                return '';
@@ -1462,12 +1475,11 @@ 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']);
                }
        }
 
@@ -1523,14 +1535,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['url'], $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                $datarray = [];
 
@@ -1538,11 +1551,11 @@ 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);
@@ -1569,7 +1582,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']);
@@ -1633,6 +1646,12 @@ class Diaspora
                        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);
@@ -1643,20 +1662,18 @@ class Diaspora
                        return false;
                }
 
-               $body = Markdown::toBBCode($msg_text);
-
-               $person = FContact::getByURL($msg_author_handle);
+               $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,
+                       'body'       => Markdown::toBBCode($msg_text),
                        'uri'        => $msg_author_handle . ':' . $msg_guid,
                        'parent-uri' => $author_handle . ':' . $guid,
                        'created'    => $msg_created_at
@@ -1770,14 +1787,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['url'], $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.
@@ -1797,8 +1815,8 @@ 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($guid, $author);
@@ -1860,13 +1878,13 @@ class Diaspora
         */
        private static function receiveMessage(array $importer, SimpleXMLElement $data): bool
        {
-               $author = WebFingerUri::fromString(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;
                }
@@ -1882,29 +1900,30 @@ class Diaspora
                        return false;
                }
 
-               $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'        => $author . ':' . $guid,
-                       'parent-uri' => $author . ':' . $conversation['guid'],
+                       'uri'        => $author_uri . ':' . $guid,
+                       'parent-uri' => $author_uri . ':' . $conversation['guid'],
                        'created'    => $created_at
                ]);
        }
@@ -1953,13 +1972,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['url'], $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                // Store participation
                $datarray = [];
@@ -1972,8 +1992,8 @@ 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($guid, $author);
@@ -1999,8 +2019,10 @@ class Diaspora
                Logger::info('Participation stored', ['id' => $message_id, 'guid' => $guid, 'parent_guid' => $parent_guid, 'author' => $author]);
 
                // 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' => [Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY]]);
+               $comments = Post::select(
+                       ['id', 'uri-id', 'parent-author-network', 'author-network', 'verb', 'gravity'],
+                       ['parent' => $toplevel_parent_item['id'], 'gravity' => [Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY]]
+               );
                while ($comment = Post::fetch($comments)) {
                        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']]);
@@ -2043,7 +2065,7 @@ class Diaspora
        }
 
        /**
-        * Processes poll participations - unssupported
+        * Processes poll participations - unsupported
         *
         * @param array  $importer Array of the importer user
         * @param object $data     The message object
@@ -2075,7 +2097,7 @@ class Diaspora
                        return false;
                }
 
-               $name = XML::unescape($data->first_name).((strlen($data->last_name)) ? ' ' . XML::unescape($data->last_name) : '');
+               $name = XML::unescape($data->first_name) . ((strlen($data->last_name)) ? ' ' . XML::unescape($data->last_name) : '');
                $image_url = XML::unescape($data->image_url);
                $birthday = XML::unescape($data->birthday);
                $about = Markdown::toBBCode(XML::unescape($data->bio));
@@ -2122,10 +2144,12 @@ class Diaspora
                        $birthday = $contact['bd'];
                }
 
-               $fields = ['name' => $name, 'location' => $location,
+               $fields = [
+                       'name' => $name, 'location' => $location,
                        'name-date' => DateTimeFormat::utcNow(), 'about' => $about,
                        'addr' => $author->getAddr(), 'nick' => $author->getUser(), 'keywords' => $keywords,
-                       'unsearchable' => !$searchable, 'sensitive' => $nsfw];
+                       'unsearchable' => !$searchable, 'sensitive' => $nsfw
+               ];
 
                if (!empty($birthday)) {
                        $fields['bd'] = $birthday;
@@ -2177,7 +2201,7 @@ class Diaspora
                $author = WebFingerUri::fromString($author_handle);
 
                // the current protocol version doesn't know these fields
-               // That means that we will assume their existance
+               // That means that we will assume their existence
                if (isset($data->following)) {
                        $following = (XML::unescape($data->following) == 'true');
                } else {
@@ -2228,27 +2252,29 @@ class Diaspora
                } elseif (!$following && $sharing) {
                        Logger::info("Author " . $author . " wants to share with us.");
                } elseif ($following && $sharing) {
-                       Logger::info("Author " . $author . " wants to have a bidirectional conection.");
+                       Logger::info("Author " . $author . " wants to have a bidirectional connection.");
                } elseif ($following && !$sharing) {
                        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) {
@@ -2270,65 +2296,6 @@ class Diaspora
                return true;
        }
 
-       /**
-        * 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 WebFingerUri $author            Author handle
-        * @return false|void
-        * @throws InternalServerErrorException
-        * @throws \ImagickException
-        */
-       private static function addReshareActivity(array $item, int $parent_message_id, string $guid, WebFingerUri $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($datarray['guid'], $author);
-               $datarray['thr-parent'] = $parent['uri'];
-
-               $datarray['verb'] = $datarray['body'] = Activity::ANNOUNCE;
-               $datarray['gravity'] = Item::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
         *
@@ -2346,7 +2313,12 @@ class Diaspora
                $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);
@@ -2365,8 +2337,9 @@ class Diaspora
                        return true;
                }
 
-               $original_person = FContact::getByURL($root_author);
-               if (!$original_person) {
+               try {
+                       $original_person = DI::dsprContact()->getByAddr($root_author);
+               } catch (HTTPException\NotFoundException $e) {
                        return false;
                }
 
@@ -2394,7 +2367,7 @@ class Diaspora
 
                $datarray = self::setDirection($datarray, $direction);
 
-               $datarray['quote-uri-id'] = self::getQuoteUriId($root_guid, $importer['uid'], $original_person['url']);
+               $datarray['quote-uri-id'] = self::getQuoteUriId($root_guid, $importer['uid'], $original_person->url);
                if (empty($datarray['quote-uri-id'])) {
                        return false;
                }
@@ -2415,11 +2388,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) {
@@ -2462,19 +2430,18 @@ class Diaspora
         */
        private static function itemRetraction(array $importer, array $contact, SimpleXMLElement $data): bool
        {
-               $author_handle = 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_handle);
-               if (!is_array($person)) {
-                       Logger::notice('Unable to find author detail for ' . $author_handle);
+               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'];
@@ -2502,8 +2469,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;
                        }
 
@@ -2599,19 +2566,21 @@ class Diaspora
         *
         * @param int $uriid
         * @param object $photo
+        *
         * @return void
         */
        private static function storePhotoAsMedia(int $uriid, $photo)
        {
                // @TODO Need to find object type, roland@f.haeder.net
-               Logger::debug('photo='.get_class($photo));
-               $data = [];
-               $data['uri-id'] = $uriid;
-               $data['type'] = Post\Media::IMAGE;
-               $data['url'] = XML::unescape($photo->remote_photo_path) . XML::unescape($photo->remote_photo_name);
-               $data['height'] = (int)XML::unescape($photo->height ?? 0);
-               $data['width'] = (int)XML::unescape($photo->width ?? 0);
-               $data['description'] = XML::unescape($photo->text ?? '');
+               Logger::debug('photo=' . get_class($photo));
+               $data = [
+                       'uri-id'      => $uriid,
+                       'type'        => Post\Media::IMAGE,
+                       'url'         => XML::unescape($photo->remote_photo_path) . XML::unescape($photo->remote_photo_name),
+                       'height'      => (int)XML::unescape($photo->height ?? 0),
+                       'width'       => (int)XML::unescape($photo->width ?? 0),
+                       'description' => XML::unescape($photo->text ?? ''),
+               ];
 
                Post\Media::insert($data);
        }
@@ -2683,11 +2652,32 @@ class Diaspora
 
                $raw_body = $body = Markdown::toBBCode($text);
 
-               $datarray = [];
+               $datarray = [
+                       'guid'        => $guid,
+                       'plink'       => self::plink($author, $guid),
+                       'uid'         => $importer['uid'],
+                       'contact-id'  => $contact['id'],
+                       'network'     => Protocol::DIASPORA,
+                       'author-link' => $contact['url'],
+                       'author-id'   => Contact::getIdForURL($contact['url'], 0),
+                       'verb'        => Activity::POST,
+                       'gravity'     => Item::GRAVITY_PARENT,
+                       'protocol'    => Conversation::PARCEL_DIASPORA,
+                       'source'      => $xml,
+                       'body'        => self::replacePeopleGuid($body, $contact['url']),
+                       'raw-body'    => self::replacePeopleGuid($raw_body, $contact['url']),
+                       'private'     => (($public == 'false') ? Item::PRIVATE : Item::PUBLIC),
+                       // Default is note (aka. comment), later below is being checked the real type
+                       'object-type' => Activity\ObjectType::NOTE,
+                       'post-type'   => Item::PT_NOTE,
+               ];
 
-               $datarray['guid'] = $guid;
-               $datarray['uri'] = $datarray['thr-parent'] = self::getUriFromGuid($guid, $author);
-               $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
+               $datarray['uri']        = $datarray['thr-parent'] = self::getUriFromGuid($guid, $author);
+               $datarray['uri-id']     = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
+               $datarray['owner-link'] = $datarray['author-link'];
+               $datarray['owner-id']   = $datarray['author-id'];
+
+               $datarray = self::setDirection($datarray, $direction);
 
                // Attach embedded pictures to the body
                if ($data->photo) {
@@ -2698,11 +2688,7 @@ class Diaspora
                        $datarray['object-type'] = Activity\ObjectType::IMAGE;
                        $datarray['post-type'] = Item::PT_IMAGE;
                } elseif ($data->poll) {
-                       $datarray['object-type'] = Activity\ObjectType::NOTE;
                        $datarray['post-type'] = Item::PT_POLL;
-               } else {
-                       $datarray['object-type'] = Activity\ObjectType::NOTE;
-                       $datarray['post-type'] = Item::PT_NOTE;
                }
 
                /// @todo enable support for polls
@@ -2714,27 +2700,6 @@ class Diaspora
 
                /// @todo enable support for events
 
-               $datarray['uid'] = $importer['uid'];
-               $datarray['contact-id'] = $contact['id'];
-               $datarray['network'] = Protocol::DIASPORA;
-
-               $datarray['author-link'] = $contact['url'];
-               $datarray['author-id'] = Contact::getIdForURL($contact['url'], 0);
-
-               $datarray['owner-link'] = $datarray['author-link'];
-               $datarray['owner-id'] = $datarray['author-id'];
-
-               $datarray['verb'] = Activity::POST;
-               $datarray['gravity'] = Item::GRAVITY_PARENT;
-
-               $datarray['protocol'] = Conversation::PARCEL_DIASPORA;
-               $datarray['source'] = $xml;
-
-               $datarray = self::setDirection($datarray, $direction);
-
-               $datarray['body'] = self::replacePeopleGuid($body, $contact['url']);
-               $datarray['raw-body'] = self::replacePeopleGuid($raw_body, $contact['url']);
-
                self::storeMentions($datarray['uri-id'], $text);
                Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray['body']);
 
@@ -2747,8 +2712,6 @@ class Diaspora
                        $datarray['app'] = $provider_display_name;
                }
 
-               $datarray['plink'] = self::plink($author, $guid);
-               $datarray['private'] = (($public == 'false') ? Item::PRIVATE : Item::PUBLIC);
                $datarray['changed'] = $datarray['created'] = $datarray['edited'] = $created_at;
 
                if (isset($address['address'])) {
@@ -2786,7 +2749,7 @@ class Diaspora
         * ************************************************************************************** */
 
        /**
-        * returnes the handle of a contact
+        * returns the handle of a contact
         *
         * @param array $contact contact array
         *
@@ -2800,7 +2763,7 @@ class Diaspora
                }
 
                // Normally we should have a filled "addr" field - but in the past this wasn't the case
-               // So - just in case - we build the the address here.
+               // So - just in case - we build the address here.
                if ($contact['nickname'] != '') {
                        $nick = $contact['nickname'];
                } else {
@@ -2869,7 +2832,7 @@ class Diaspora
        public static function buildMagicEnvelope(string $msg, array $user): string
        {
                $b64url_data = Strings::base64UrlEncode($msg);
-               $data = str_replace(["\n", "\r", " ", "\t"], ['', '', '', ''], $b64url_data);
+               $data = str_replace(["\n", "\r", ' ', "\t"], ['', '', '', ''], $b64url_data);
 
                $key_id = Strings::base64UrlEncode(self::myHandle($user));
                $type = 'application/xml';
@@ -2887,18 +2850,18 @@ class Diaspora
 
                $xmldata = [
                        'me:env' => [
-                               'me:data' => $data,
-                               '@attributes' => ['type' => $type],
-                               'me:encoding' => $encoding,
-                               'me:alg' => $alg,
-                               'me:sig' => $sig,
+                               'me:data'      => $data,
+                               '@attributes'  => ['type' => $type],
+                               'me:encoding'  => $encoding,
+                               'me:alg'       => $alg,
+                               'me:sig'       => $sig,
                                '@attributes2' => ['key_id' => $key_id]
                        ]
                ];
 
                $namespaces = ['me' => ActivityNamespace::SALMON_ME];
 
-               return XML::fromArray($xmldata, $xml, false, $namespaces);
+               return XML::fromArray($xmldata, $dummy, false, $namespaces);
        }
 
        /**
@@ -2968,13 +2931,12 @@ 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)) {
@@ -2986,7 +2948,7 @@ class Diaspora
                        return 0;
                }
 
-               Logger::notice('transmit: ' . $logid . '-' . $guid . ' ' . $dest_url);
+               Logger::info('transmit: ' . $logid . '-' . $guid . ' ' . $dest_url);
 
                if (!intval(DI::config()->get('system', 'diaspora_test'))) {
                        $content_type = (($public_batch) ? 'application/magic-envelope+xml' : 'application/json');
@@ -2998,7 +2960,13 @@ class Diaspora
                        return 200;
                }
 
-               Logger::notice('transmit: ' . $logid . '-' . $guid . ' to ' . $dest_url . ' returns: ' . $return_code);
+               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::info('transmit: ' . $logid . '-' . $guid . ' to ' . $dest_url . ' returns: ' . $return_code);
 
                return $return_code ? $return_code : -1;
        }
@@ -3011,12 +2979,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]);
        }
 
        /**
@@ -3043,18 +3010,18 @@ 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)]);
                }
 
@@ -3095,8 +3062,10 @@ class Diaspora
                // If the item belongs to a user, we take this user id.
                if ($item['uid'] == 0) {
                        // @todo Possibly use an administrator account?
-                       $condition = ['verified' => true, 'blocked' => false,
-                               'account_removed' => false, 'account_expired' => false, 'account-type' => User::ACCOUNT_TYPE_PERSON];
+                       $condition = [
+                               'verified' => true, 'blocked' => false,
+                               'account_removed' => false, 'account_expired' => false, 'account-type' => User::ACCOUNT_TYPE_PERSON
+                       ];
                        $first_user = DBA::selectFirst('user', ['uid'], $condition, ['order' => ['uid']]);
                        $owner = User::getOwnerDataById($first_user['uid']);
                } else {
@@ -3136,7 +3105,7 @@ class Diaspora
                $old_handle = DI::pConfig()->get($uid, 'system', 'previous_addr');
                $profile = self::createProfileData($uid);
 
-               $signed_text = 'AccountMigration:'.$old_handle.':'.$profile['author'];
+               $signed_text = 'AccountMigration:' . $old_handle . ':' . $profile['author'];
                $signature = base64_encode(Crypto::rsaSign($signed_text, $owner['uprvkey'], 'sha256'));
 
                $message = [
@@ -3353,14 +3322,23 @@ 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'], DI::contentItem()->addSharedPost($item));
+                       $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')) {
-                               $page_data = BBCode::getAttachmentData($item['body']);
-                               if (!empty($page_data['type']) && !empty($page_data['title']) && ($page_data['type'] == 'link')) {
-                                       $title = $page_data['title'];
+                               $media = Post\Media::getByURIId($item['uri-id'], [Post\Media::HTML]);
+                               if (!empty($media) && !empty($media[0]['name']) && ($media[0]['name'] != $media[0]['url'])) {
+                                       $title = $media[0]['name'];
                                }
                        }
 
@@ -3372,7 +3350,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) {
@@ -3402,6 +3380,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']);
@@ -3412,9 +3394,11 @@ class Diaspora
                                if (count($event)) {
                                        $message['event'] = $event;
 
-                                       if (!empty($event['location']['address']) &&
+                                       if (
+                                               !empty($event['location']['address']) &&
                                                !empty($event['location']['lat']) &&
-                                               !empty($event['location']['lng'])) {
+                                               !empty($event['location']['lng'])
+                                       ) {
                                                $message['location'] = $event['location'];
                                        }
 
@@ -3436,10 +3420,49 @@ 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']);
-               if (!empty($profile['addr'])
+               if (
+                       !empty($profile['addr'])
                        && !strstr($body, $profile['addr'])
                        && !strstr($body, $profile_url)
                ) {
@@ -3572,6 +3595,7 @@ class Diaspora
                }
 
                $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
@@ -3893,9 +3917,9 @@ class Diaspora
                                $kw = str_replace('  ', ' ', $kw);
                                $arr = explode(' ', $kw);
                                if (count($arr)) {
-                                       for ($x = 0; $x < 5; $x ++) {
+                                       for ($x = 0; $x < 5; $x++) {
                                                if (!empty($arr[$x])) {
-                                                       $data['tag_string'] .= '#'. trim($arr[$x]) .' ';
+                                                       $data['tag_string'] .= '#' . trim($arr[$x]) . ' ';
                                                }
                                        }
                                }
@@ -4018,7 +4042,7 @@ class Diaspora
                        return false;
                }
 
-               if (!self::parentSupportDiaspora($item['thr-parent-id'])) {
+               if (!self::parentSupportDiaspora($item['thr-parent-id'], $uid)) {
                        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;
                }
@@ -4037,28 +4061,31 @@ class Diaspora
         * Check if the parent and their parents support Diaspora
         *
         * @param integer $parent_id
+        * @param integer $uid
         * @return boolean
+        * @throws InternalServerErrorException
+        * @throws \ImagickException
         */
-       private static function parentSupportDiaspora(int $parent_id): bool
+       private static function parentSupportDiaspora(int $parent_id, int $uid): bool
        {
-               $parent_post = Post::selectFirstPost(['gravity', 'signed_text', 'author-link', 'thr-parent-id'], ['uri-id' => $parent_id]);
+               $parent_post = Post::selectFirst(['gravity', 'signed_text', 'author-link', 'thr-parent-id', 'protocol'], ['uri-id' => $parent_id, 'uid' => [0, $uid]]);
                if (empty($parent_post['thr-parent-id'])) {
                        Logger::warning('Parent post does not exist.', ['parent-id' => $parent_id]);
                        return false;
                }
 
-               if (empty(FContact::getByURL($parent_post['author-link'], false))) {
+               if (!self::isSupportedByContactUrl($parent_post['author-link'])) {
                        Logger::info('Parent author is no Diaspora contact.', ['parent-id' => $parent_id]);
                        return false;
                }
 
-               if (($parent_post['gravity'] == Item::GRAVITY_COMMENT) && empty($parent_post['signed_text'])) {
+               if (($parent_post['protocol'] != Conversation::PARCEL_DIASPORA) && ($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;
                }
 
                if ($parent_post['gravity'] == Item::GRAVITY_COMMENT) {
-                       return self::parentSupportDiaspora($parent_post['thr-parent-id']);
+                       return self::parentSupportDiaspora($parent_post['thr-parent-id'], $uid);
                }
 
                return true;
@@ -4078,7 +4105,7 @@ class Diaspora
                        'body'         => '',
                        'quote-uri-id' => $UriId,
                        'allow_cid'    => $owner['allow_cid'] ?? '',
-                       'allow_gid'    => $owner['allow_gid']?? '',
+                       'allow_gid'    => $owner['allow_gid'] ?? '',
                        'deny_cid'     => $owner['deny_cid'] ?? '',
                        'deny_gid'     => $owner['deny_gid'] ?? '',
                ];