]> git.mxchange.org Git - friendica.git/blobdiff - src/Network/Probe.php
IHTTPResult::getContentType is now a string again
[friendica.git] / src / Network / Probe.php
index bd8b717779f61e2923e889cdad6b526939b20293..cfd03684397c9afb023dfdd55c139696c65310a9 100644 (file)
@@ -23,7 +23,7 @@ namespace Friendica\Network;
 
 use DOMDocument;
 use DomXPath;
 
 use DOMDocument;
 use DomXPath;
-use Friendica\Core\Cache\Duration;
+use Friendica\Core\Hook;
 use Friendica\Core\Logger;
 use Friendica\Core\Protocol;
 use Friendica\Core\System;
 use Friendica\Core\Logger;
 use Friendica\Core\Protocol;
 use Friendica\Core\System;
@@ -32,11 +32,13 @@ use Friendica\DI;
 use Friendica\Model\Contact;
 use Friendica\Model\GServer;
 use Friendica\Model\Profile;
 use Friendica\Model\Contact;
 use Friendica\Model\GServer;
 use Friendica\Model\Profile;
+use Friendica\Model\User;
 use Friendica\Protocol\ActivityNamespace;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Protocol\Email;
 use Friendica\Protocol\Feed;
 use Friendica\Util\Crypto;
 use Friendica\Protocol\ActivityNamespace;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Protocol\Email;
 use Friendica\Protocol\Feed;
 use Friendica\Util\Crypto;
+use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Network;
 use Friendica\Util\Strings;
 use Friendica\Util\XML;
 use Friendica\Util\Network;
 use Friendica\Util\Strings;
 use Friendica\Util\XML;
@@ -46,6 +48,8 @@ use Friendica\Util\XML;
  */
 class Probe
 {
  */
 class Probe
 {
+       const WEBFINGER = '/.well-known/webfinger?resource={uri}';
+
        private static $baseurl;
        private static $istimeout;
 
        private static $baseurl;
        private static $istimeout;
 
@@ -87,13 +91,19 @@ class Probe
                                "community", "keywords", "location", "about", "hide",
                                "batch", "notify", "poll", "request", "confirm", "subscribe", "poco",
                                "following", "followers", "inbox", "outbox", "sharedinbox",
                                "community", "keywords", "location", "about", "hide",
                                "batch", "notify", "poll", "request", "confirm", "subscribe", "poco",
                                "following", "followers", "inbox", "outbox", "sharedinbox",
-                               "priority", "network", "pubkey", "baseurl", "gsid"];
+                               "priority", "network", "pubkey", "manually-approve", "baseurl", "gsid"];
+
+               $numeric_fields = ["gsid", "hide", "account-type", "manually-approve"];
 
                $newdata = [];
                foreach ($fields as $field) {
                        if (isset($data[$field])) {
 
                $newdata = [];
                foreach ($fields as $field) {
                        if (isset($data[$field])) {
-                               $newdata[$field] = $data[$field];
-                       } elseif ($field != "gsid") {
+                               if (in_array($field, $numeric_fields)) {
+                                       $newdata[$field] = (int)$data[$field];
+                               } else {        
+                                       $newdata[$field] = $data[$field];
+                               }
+                       } elseif (!in_array($field, $numeric_fields)) {
                                $newdata[$field] = "";
                        } else {
                                $newdata[$field] = null;
                                $newdata[$field] = "";
                        } else {
                                $newdata[$field] = null;
@@ -159,7 +169,7 @@ class Probe
                Logger::info('Probing', ['host' => $host, 'ssl_url' => $ssl_url, 'url' => $url, 'callstack' => System::callstack(20)]);
                $xrd = null;
 
                Logger::info('Probing', ['host' => $host, 'ssl_url' => $ssl_url, 'url' => $url, 'callstack' => System::callstack(20)]);
                $xrd = null;
 
-               $curlResult = Network::curl($ssl_url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
+               $curlResult = DI::httpRequest()->get($ssl_url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
                $ssl_connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
                if ($curlResult->isSuccess()) {
                        $xml = $curlResult->getBody();
                $ssl_connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
                if ($curlResult->isSuccess()) {
                        $xml = $curlResult->getBody();
@@ -170,16 +180,16 @@ class Probe
                                $host_url = $host;
                        }
                } elseif ($curlResult->isTimeout()) {
                                $host_url = $host;
                        }
                } elseif ($curlResult->isTimeout()) {
-                       Logger::info('Probing timeout', ['url' => $ssl_url], Logger::DEBUG);
+                       Logger::info('Probing timeout', ['url' => $ssl_url]);
                        self::$istimeout = true;
                        return [];
                }
 
                if (!is_object($xrd) && !empty($url)) {
                        self::$istimeout = true;
                        return [];
                }
 
                if (!is_object($xrd) && !empty($url)) {
-                       $curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
+                       $curlResult = DI::httpRequest()->get($url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
                        $connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
                        if ($curlResult->isTimeout()) {
                        $connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
                        if ($curlResult->isTimeout()) {
-                               Logger::info('Probing timeout', ['url' => $url], Logger::DEBUG);
+                               Logger::info('Probing timeout', ['url' => $url]);
                                self::$istimeout = true;
                                return [];
                        } elseif ($connection_error && $ssl_connection_error) {
                                self::$istimeout = true;
                                return [];
                        } elseif ($connection_error && $ssl_connection_error) {
@@ -192,17 +202,17 @@ class Probe
                        $host_url = 'http://'.$host;
                }
                if (!is_object($xrd)) {
                        $host_url = 'http://'.$host;
                }
                if (!is_object($xrd)) {
-                       Logger::log("No xrd object found for ".$host, Logger::DEBUG);
+                       Logger::info('No xrd object found', ['host' => $host]);
                        return [];
                }
 
                $links = XML::elementToArray($xrd);
                if (!isset($links["xrd"]["link"])) {
                        return [];
                }
 
                $links = XML::elementToArray($xrd);
                if (!isset($links["xrd"]["link"])) {
-                       Logger::log("No xrd data found for ".$host, Logger::DEBUG);
+                       Logger::info('No xrd data found', ['host' => $host]);
                        return [];
                }
 
                        return [];
                }
 
-               $lrdd = ['application/jrd+json' => $host_url . '/.well-known/webfinger?resource={uri}'];
+               $lrdd = [];
 
                foreach ($links["xrd"]["link"] as $value => $link) {
                        if (!empty($link["@attributes"])) {
 
                foreach ($links["xrd"]["link"] as $value => $link) {
                        if (!empty($link["@attributes"])) {
@@ -222,7 +232,7 @@ class Probe
 
                self::$baseurl = $host_url;
 
 
                self::$baseurl = $host_url;
 
-               Logger::log("Probing successful for ".$host, Logger::DEBUG);
+               Logger::info('Probing successful', ['host' => $host]);
 
                return $lrdd;
        }
 
                return $lrdd;
        }
@@ -253,7 +263,7 @@ class Probe
                $profile_link = '';
 
                $links = self::lrdd($webbie);
                $profile_link = '';
 
                $links = self::lrdd($webbie);
-               Logger::log('webfingerDfrn: '.$webbie.':'.print_r($links, true), Logger::DATA);
+               Logger::debug('Result', ['url' => $webbie, 'links' => $links]);
                if (!empty($links) && is_array($links)) {
                        foreach ($links as $link) {
                                if ($link['@attributes']['rel'] === ActivityNamespace::DFRN) {
                if (!empty($links) && is_array($links)) {
                        foreach ($links as $link) {
                                if ($link['@attributes']['rel'] === ActivityNamespace::DFRN) {
@@ -280,68 +290,14 @@ class Probe
         */
        public static function lrdd(string $uri)
        {
         */
        public static function lrdd(string $uri)
        {
-               $lrdd = self::hostMeta($uri);
-               $webfinger = null;
-
-               if (is_bool($lrdd)) {
-                       return [];
-               }
-
-               if (!$lrdd) {
-                       $parts = @parse_url($uri);
-                       if (!$parts || empty($parts["host"]) || empty($parts["path"])) {
-                               return [];
-                       }
-
-                       $host = $parts['scheme'] . '://' . $parts["host"];
-                       if (!empty($parts["port"])) {
-                               $host .= ':'.$parts["port"];
-                       }
-
-                       $path_parts = explode("/", trim($parts["path"], "/"));
-
-                       $nick = array_pop($path_parts);
-
-                       do {
-                               $lrdd = self::hostMeta($host);
-                               $host .= "/".array_shift($path_parts);
-                       } while (!$lrdd && (sizeof($path_parts) > 0));
-               }
-
-               if (!$lrdd) {
-                       Logger::log("No lrdd data found for ".$uri, Logger::DEBUG);
+               $data = self::getWebfingerArray($uri);
+               if (empty($data)) {
                        return [];
                }
                        return [];
                }
-
-               foreach ($lrdd as $type => $template) {
-                       if ($webfinger) {
-                               continue;
-                       }
-
-                       $path = str_replace('{uri}', urlencode($uri), $template);
-                       $webfinger = self::webfinger($path, $type);
-
-                       if (!$webfinger && (strstr($uri, "@"))) {
-                               $path = str_replace('{uri}', urlencode("acct:".$uri), $template);
-                               $webfinger = self::webfinger($path, $type);
-                       }
-
-                       // Special treatment for Mastodon
-                       // Problem is that Mastodon uses an URL format like http://domain.tld/@nick
-                       // But the webfinger for this format fails.
-                       if (!$webfinger && !empty($nick)) {
-                               // Mastodon uses a "@" as prefix for usernames in their url format
-                               $nick = ltrim($nick, '@');
-
-                               $addr = $nick."@".$host;
-
-                               $path = str_replace('{uri}', urlencode("acct:".$addr), $template);
-                               $webfinger = self::webfinger($path, $type);
-                       }
-               }
+               $webfinger = $data['webfinger'];
 
                if (empty($webfinger["links"])) {
 
                if (empty($webfinger["links"])) {
-                       Logger::log("No webfinger links found for ".$uri, Logger::DEBUG);
+                       Logger::info('No webfinger links found', ['uri' => $uri]);
                        return [];
                }
 
                        return [];
                }
 
@@ -374,12 +330,13 @@ class Probe
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function uri($uri, $network = '', $uid = -1, $cache = true)
+       public static function uri($uri, $network = '', $uid = -1)
        {
        {
-               if ($cache) {
-                       $result = DI::cache()->get('Probe::uri:' . $network . ':' . $uri);
-                       if (!is_null($result)) {
-                               return $result;
+               // Local profiles aren't probed via network
+               if (empty($network) && strpos($uri, DI::baseUrl()->getHostname())) {
+                       $data = self::localProbe($uri);
+                       if (!empty($data)) {
+                               return $data;
                        }
                }
 
                        }
                }
 
@@ -387,25 +344,27 @@ class Probe
                        $uid = local_user();
                }
 
                        $uid = local_user();
                }
 
-               self::$istimeout = false;
-
-               $data = self::detect($uri, $network, $uid);
-
-               // When the previous detection process had got a time out
-               // we could falsely detect a Friendica profile as AP profile.
-               if (!self::$istimeout) {
+               if (empty($network) || ($network == Protocol::ACTIVITYPUB)) {
                        $ap_profile = ActivityPub::probeProfile($uri);
                        $ap_profile = ActivityPub::probeProfile($uri);
+               } else {
+                       $ap_profile = [];
+               }
+
+               self::$istimeout = false;
 
 
+               if ($network != Protocol::ACTIVITYPUB) {
+                       $data = self::detect($uri, $network, $uid, $ap_profile);
+                       if (!is_array($data)) {
+                               $data = [];
+                       }
                        if (empty($data) || (!empty($ap_profile) && empty($network) && (($data['network'] ?? '') != Protocol::DFRN))) {
                        if (empty($data) || (!empty($ap_profile) && empty($network) && (($data['network'] ?? '') != Protocol::DFRN))) {
-                               $subscribe = $data['subscribe'] ?? '';
                                $data = $ap_profile;
                                $data = $ap_profile;
-                               $data['subscribe'] = $subscribe;
                        } elseif (!empty($ap_profile)) {
                                $ap_profile['batch'] = '';
                                $data = array_merge($ap_profile, $data);
                        }
                } else {
                        } elseif (!empty($ap_profile)) {
                                $ap_profile['batch'] = '';
                                $data = array_merge($ap_profile, $data);
                        }
                } else {
-                       Logger::notice('Time out detected. AP will not be probed.', ['uri' => $uri]);
+                       $data = $ap_profile;
                }
 
                if (!isset($data['url'])) {
                }
 
                if (!isset($data['url'])) {
@@ -413,7 +372,7 @@ class Probe
                }
 
                if (empty($data['photo'])) {
                }
 
                if (empty($data['photo'])) {
-                       $data['photo'] = DI::baseUrl() . '/images/person-300.jpg';
+                       $data['photo'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO;
                }
 
                if (empty($data['name'])) {
                }
 
                if (empty($data['name'])) {
@@ -434,10 +393,6 @@ class Probe
                        }
                }
 
                        }
                }
 
-               if (!empty(self::$baseurl)) {
-                       $data['baseurl'] = self::$baseurl;
-               }
-
                if (!empty($data['baseurl']) && empty($data['gsid'])) {
                        $data['gsid'] = GServer::getID($data['baseurl']);
                }
                if (!empty($data['baseurl']) && empty($data['gsid'])) {
                        $data['gsid'] = GServer::getID($data['baseurl']);
                }
@@ -455,14 +410,7 @@ class Probe
                        $data['hide'] = self::getHideStatus($data['url']);
                }
 
                        $data['hide'] = self::getHideStatus($data['url']);
                }
 
-               $data = self::rearrangeData($data);
-
-               // Only store into the cache if the value seems to be valid
-               if (!in_array($data['network'], [Protocol::PHANTOM, Protocol::MAIL])) {
-                       DI::cache()->set('Probe::uri:' . $network . ':' . $uri, $data, Duration::DAY);
-               }
-
-               return $data;
+               return self::rearrangeData($data);
        }
 
 
        }
 
 
@@ -475,16 +423,11 @@ class Probe
         */
        private static function getHideStatus($url)
        {
         */
        private static function getHideStatus($url)
        {
-               $curlResult = Network::curl($url);
+               $curlResult = DI::httpRequest()->get($url, false, ['content_length' => 1000000]);
                if (!$curlResult->isSuccess()) {
                        return false;
                }
 
                if (!$curlResult->isSuccess()) {
                        return false;
                }
 
-               // If the file is too large then exit
-               if (($curlResult->getInfo()['download_content_length'] ?? 0) > 1000000) {
-                       return false;
-               }
-
                // If it isn't a HTML file then exit
                if (($curlResult->getContentType() != '') && !strstr(strtolower($curlResult->getContentType()), 'html')) {
                        return false;
                // If it isn't a HTML file then exit
                if (($curlResult->getContentType() != '') && !strstr(strtolower($curlResult->getContentType()), 'html')) {
                        return false;
@@ -529,50 +472,6 @@ class Probe
                return false;
        }
 
                return false;
        }
 
-       /**
-        * Checks if a profile url should be OStatus but only provides partial information
-        *
-        * @param array  $webfinger Webfinger data
-        * @param string $lrdd      Path template for webfinger request
-        * @param string $type      type
-        *
-        * @return array fixed webfinger data
-        * @throws HTTPException\InternalServerErrorException
-        */
-       private static function fixOStatus($webfinger, $lrdd, $type)
-       {
-               if (empty($webfinger['links']) || empty($webfinger['subject'])) {
-                       return $webfinger;
-               }
-
-               $is_ostatus = false;
-               $has_key = false;
-
-               foreach ($webfinger['links'] as $link) {
-                       if ($link['rel'] == ActivityNamespace::OSTATUSSUB) {
-                               $is_ostatus = true;
-                       }
-                       if ($link['rel'] == 'magic-public-key') {
-                               $has_key = true;
-                       }
-               }
-
-               if (!$is_ostatus || $has_key) {
-                       return $webfinger;
-               }
-
-               $url = Network::switchScheme($webfinger['subject']);
-               $path = str_replace('{uri}', urlencode($url), $lrdd);
-               $webfinger2 = self::webfinger($path, $type);
-
-               // Is the new webfinger detectable as OStatus?
-               if (self::ostatus($webfinger2, true)) {
-                       $webfinger = $webfinger2;
-               }
-
-               return $webfinger;
-       }
-
        /**
         * Fetch the "subscribe" and add it to the result
         *
        /**
         * Fetch the "subscribe" and add it to the result
         *
@@ -587,7 +486,7 @@ class Probe
                }
 
                foreach ($webfinger['links'] as $link) {
                }
 
                foreach ($webfinger['links'] as $link) {
-                       if ($link['rel'] === ActivityNamespace::OSTATUSSUB) {
+                       if (!empty($link['template']) && ($link['rel'] === ActivityNamespace::OSTATUSSUB)) {
                                $result['subscribe'] = $link['template'];
                        }
                }
                                $result['subscribe'] = $link['template'];
                        }
                }
@@ -596,129 +495,244 @@ class Probe
        }
 
        /**
        }
 
        /**
-        * Fetch information (protocol endpoints and user information) about a given uri
+        * Get webfinger data from a given URI
         *
         *
-        * This function is only called by the "uri" function that adds caching and rearranging of data.
-        *
-        * @param string  $uri     Address that should be probed
-        * @param string  $network Test for this specific network
-        * @param integer $uid     User ID for the probe (only used for mails)
-        *
-        * @return array uri data
-        * @throws HTTPException\InternalServerErrorException
+        * @param string $uri
+        * @return array Webfinger array
         */
         */
-       private static function detect($uri, $network, $uid)
+       private static function getWebfingerArray(string $uri)
        {
                $parts = parse_url($uri);
 
        {
                $parts = parse_url($uri);
 
-               if (!empty($parts["scheme"]) && !empty($parts["host"])) {
-                       $host = $parts["host"];
-                       if (!empty($parts["port"])) {
-                               $host .= ':'.$parts["port"];
+               if (!empty($parts['scheme']) && !empty($parts['host'])) {
+                       $host = $parts['host'];
+                       if (!empty($parts['port'])) {
+                               $host .= ':'.$parts['port'];
                        }
 
                        }
 
-                       if ($host == 'twitter.com') {
-                               return self::twitter($uri);
-                       }
-                       $lrdd = self::hostMeta($host);
+                       $baseurl = $parts['scheme'] . '://' . $host;
 
 
-                       if (is_bool($lrdd)) {
-                               return [];
-                       }
+                       $nick = '';
+                       $addr = '';
 
                        $path_parts = explode("/", trim($parts['path'] ?? '', "/"));
 
                        $path_parts = explode("/", trim($parts['path'] ?? '', "/"));
+                       if (!empty($path_parts)) {
+                               $nick = ltrim(end($path_parts), '@');
+                               // When the last part of the URI is numeric then it is most likely an ID and not a nick name
+                               if (!is_numeric($nick)) {
+                                       $addr = $nick."@".$host;
+                               } else {
+                                       $nick = '';
+                               }
+                       }
 
 
-                       while (!$lrdd && (sizeof($path_parts) > 1)) {
-                               $host .= "/".array_shift($path_parts);
+                       $webfinger = self::getWebfinger($parts['scheme'] . '://' . $host . self::WEBFINGER, 'application/jrd+json', $uri, $addr);
+                       if (empty($webfinger)) {
                                $lrdd = self::hostMeta($host);
                        }
                                $lrdd = self::hostMeta($host);
                        }
-                       if (!$lrdd) {
-                               Logger::log('No XRD data was found for '.$uri, Logger::DEBUG);
-                               return self::feed($uri);
-                       }
-                       $nick = array_pop($path_parts);
 
 
-                       // Mastodon uses a "@" as prefix for usernames in their url format
-                       $nick = ltrim($nick, '@');
+                       if (empty($webfinger) && empty($lrdd)) {
+                               while (empty($lrdd) && empty($webfinger) && (sizeof($path_parts) > 1)) {
+                                       $host .= "/".array_shift($path_parts);
+                                       $baseurl = $parts['scheme'] . '://' . $host;
 
 
-                       $addr = $nick."@".$host;
-               } elseif (strstr($uri, '@')) {
-                       // If the URI starts with "mailto:" then jump directly to the mail detection
-                       if (strpos($uri, 'mailto:') !== false) {
-                               $uri = str_replace('mailto:', '', $uri);
-                               return self::mail($uri, $uid);
-                       }
+                                       if (!empty($nick)) {
+                                               $addr = $nick."@".$host;
+                                       }
 
 
-                       if ($network == Protocol::MAIL) {
-                               return self::mail($uri, $uid);
+                                       $webfinger = self::getWebfinger($parts['scheme'] . '://' . $host . self::WEBFINGER, 'application/jrd+json', $uri, $addr);
+                                       if (empty($webfinger)) {
+                                               $lrdd = self::hostMeta($host);
+                                       }
+                               }
+
+                               if (empty($lrdd) && empty($webfinger)) {
+                                       return [];
+                               }
                        }
                        }
+               } elseif (strstr($uri, '@')) {
                        // Remove "acct:" from the URI
                        $uri = str_replace('acct:', '', $uri);
 
                        $host = substr($uri, strpos($uri, '@') + 1);
                        $nick = substr($uri, 0, strpos($uri, '@'));
                        // Remove "acct:" from the URI
                        $uri = str_replace('acct:', '', $uri);
 
                        $host = substr($uri, strpos($uri, '@') + 1);
                        $nick = substr($uri, 0, strpos($uri, '@'));
+                       $addr = $uri;
 
 
-                       if (strpos($uri, '@twitter.com')) {
-                               return self::twitter($uri);
+                       $webfinger = self::getWebfinger('https://' . $host . self::WEBFINGER, 'application/jrd+json', $uri, $addr);
+                       if (self::$istimeout) {
+                               return [];
                        }
                        }
-                       $lrdd = self::hostMeta($host);
 
 
-                       if (is_bool($lrdd)) {
-                               return [];
+                       if (empty($webfinger)) {
+                               $webfinger = self::getWebfinger('http://' . $host . self::WEBFINGER, 'application/jrd+json', $uri, $addr);
+                               if (self::$istimeout) {
+                                       return [];
+                               }
+                       } else {
+                               $baseurl = 'https://' . $host;
                        }
 
                        }
 
-                       if (!$lrdd) {
-                               Logger::log('No XRD data was found for '.$uri, Logger::DEBUG);
-                               return self::mail($uri, $uid);
+                       if (empty($webfinger)) {
+                               $lrdd = self::hostMeta($host);
+                               if (self::$istimeout) {
+                                       return [];
+                               }
+                               $baseurl = self::$baseurl;
+                       } else {
+                               $baseurl = 'http://' . $host;
                        }
                        }
-                       $addr = $uri;
                } else {
                } else {
-                       Logger::log("Uri ".$uri." was not detectable", Logger::DEBUG);
+                       Logger::info('URI was not detectable', ['uri' => $uri]);
+                       return [];
+               }
+
+               if (empty($webfinger)) {
+                       foreach ($lrdd as $type => $template) {
+                               if ($webfinger) {
+                                       continue;
+                               }
+
+                               $webfinger = self::getWebfinger($template, $type, $uri, $addr);
+                       }
+               }
+
+               if (empty($webfinger)) {
                        return [];
                }
 
                        return [];
                }
 
-               $webfinger = false;
+               if ($webfinger['detected'] == $addr) {
+                       $webfinger['nick'] = $nick;
+                       $webfinger['addr'] = $addr;
+               }
 
 
-               /// @todo Do we need the prefix "acct:" or "acct://"?
+               $webfinger['baseurl'] = $baseurl;
 
 
-               foreach ($lrdd as $type => $template) {
-                       if ($webfinger) {
-                               continue;
+               return $webfinger;
+       }
+
+       /**
+        * Perform network request for webfinger data
+        *
+        * @param string $template
+        * @param string $type
+        * @param string $uri
+        * @param string $addr
+        * @return array webfinger results
+        */
+       private static function getWebfinger(string $template, string $type, string $uri, string $addr)
+       {
+               // First try the address because this is the primary purpose of webfinger
+               if (!empty($addr)) {
+                       $detected = $addr;
+                       $path = str_replace('{uri}', urlencode("acct:" . $addr), $template);
+                       $webfinger = self::webfinger($path, $type);
+                       if (self::$istimeout) {
+                               return [];
                        }
                        }
+               }
 
 
-                       // At first try it with the given uri
+               // Then try the URI
+               if (empty($webfinger) && $uri != $addr) {
+                       $detected = $uri;
                        $path = str_replace('{uri}', urlencode($uri), $template);
                        $webfinger = self::webfinger($path, $type);
                        $path = str_replace('{uri}', urlencode($uri), $template);
                        $webfinger = self::webfinger($path, $type);
+                       if (self::$istimeout) {
+                               return [];
+                       }
+               }
+
+               if (empty($webfinger)) {
+                       return [];
+               }
+
+               return ['webfinger' => $webfinger, 'detected' => $detected];
+       }
+
+       /**
+        * Fetch information (protocol endpoints and user information) about a given uri
+        *
+        * This function is only called by the "uri" function that adds caching and rearranging of data.
+        *
+        * @param string  $uri        Address that should be probed
+        * @param string  $network    Test for this specific network
+        * @param integer $uid        User ID for the probe (only used for mails)
+        * @param array   $ap_profile Previously probed AP profile
+        *
+        * @return array uri data
+        * @throws HTTPException\InternalServerErrorException
+        */
+       private static function detect(string $uri, string $network, int $uid, array $ap_profile)
+       {
+               $hookData = [
+                       'uri'     => $uri,
+                       'network' => $network,
+                       'uid'     => $uid,
+                       'result'  => [],
+               ];
+
+               Hook::callAll('probe_detect', $hookData);
+
+               if ($hookData['result']) {
+                       if (!is_array($hookData['result'])) {
+                               return [];
+                       } else {
+                               return $hookData['result'];
+                       }
+               }
 
 
-                       // Fix possible problems with GNU Social probing to wrong scheme
-                       $webfinger = self::fixOStatus($webfinger, $template, $type);
+               $parts = parse_url($uri);
 
 
-                       // We cannot be sure that the detected address was correct, so we don't use the values
-                       if ($webfinger && ($uri != $addr)) {
-                               $nick = "";
-                               $addr = "";
+               if (!empty($parts['scheme']) && !empty($parts['host'])) {
+                       if (in_array($parts['host'], ['twitter.com', 'mobile.twitter.com'])) {
+                               return self::twitter($uri);
+                       }
+               } elseif (strstr($uri, '@')) {
+                       // If the URI starts with "mailto:" then jump directly to the mail detection
+                       if (strpos($uri, 'mailto:') !== false) {
+                               $uri = str_replace('mailto:', '', $uri);
+                               return self::mail($uri, $uid);
                        }
 
                        }
 
-                       // Try webfinger with the address (user@domain.tld)
-                       if (!$webfinger) {
-                               $path = str_replace('{uri}', urlencode($addr), $template);
-                               $webfinger = self::webfinger($path, $type);
+                       if ($network == Protocol::MAIL) {
+                               return self::mail($uri, $uid);
                        }
 
                        }
 
-                       // Mastodon needs to have it with "acct:"
-                       if (!$webfinger) {
-                               $path = str_replace('{uri}', urlencode("acct:".$addr), $template);
-                               $webfinger = self::webfinger($path, $type);
+                       if (Strings::endsWith($uri, '@twitter.com')
+                               || Strings::endsWith($uri, '@mobile.twitter.com')
+                       ) {
+                               return self::twitter($uri);
                        }
                        }
+               } else {
+                       Logger::info('URI was not detectable', ['uri' => $uri]);
+                       return [];
                }
 
                }
 
-               if (!$webfinger) {
-                       return self::feed($uri);
+               Logger::info('Probing start', ['uri' => $uri]);
+
+               if (!empty($ap_profile['addr']) && ($ap_profile['addr'] != $uri)) {
+                       $data = self::getWebfingerArray($ap_profile['addr']);
                }
 
                }
 
-               $result = [];
+               if (empty($data)) {
+                       $data = self::getWebfingerArray($uri);
+               }
+
+               if (empty($data)) {
+                       if (!empty($parts['scheme'])) {
+                               return self::feed($uri);
+                       } elseif (!empty($uid)) {
+                               return self::mail($uri, $uid);
+                       } else {
+                               return [];
+                       }
+               }
+
+               $webfinger = $data['webfinger'];
+               $nick = $data['nick'] ?? '';
+               $addr = $data['addr'] ?? '';
+               $baseurl = $data['baseurl'] ?? '';
 
 
-               Logger::info("Probing", ['uri' => $uri]);
+               $result = [];
 
                if (in_array($network, ["", Protocol::DFRN])) {
                        $result = self::dfrn($webfinger);
 
                if (in_array($network, ["", Protocol::DFRN])) {
                        $result = self::dfrn($webfinger);
@@ -730,12 +744,12 @@ class Probe
                        $result = self::ostatus($webfinger);
                }
                if (in_array($network, ['', Protocol::ZOT])) {
                        $result = self::ostatus($webfinger);
                }
                if (in_array($network, ['', Protocol::ZOT])) {
-                       $result = self::zot($webfinger, $result);
+                       $result = self::zot($webfinger, $result, $baseurl);
                }
                if ((!$result && ($network == "")) || ($network == Protocol::PUMPIO)) {
                        $result = self::pumpio($webfinger, $addr);
                }
                }
                if ((!$result && ($network == "")) || ($network == Protocol::PUMPIO)) {
                        $result = self::pumpio($webfinger, $addr);
                }
-               if ((!$result && ($network == "")) || ($network == Protocol::FEED)) {
+               if (empty($result['network']) && empty($ap_profile['network']) || ($network == Protocol::FEED)) {
                        $result = self::feed($uri);
                } else {
                        // We overwrite the detected nick with our try if the previois routines hadn't detected it.
                        $result = self::feed($uri);
                } else {
                        // We overwrite the detected nick with our try if the previois routines hadn't detected it.
@@ -755,18 +769,15 @@ class Probe
                        $result["network"] = Protocol::PHANTOM;
                }
 
                        $result["network"] = Protocol::PHANTOM;
                }
 
+               if (empty($result['baseurl']) && !empty($baseurl)) {
+                       $result['baseurl'] = $baseurl;
+               }
+
                if (empty($result["url"])) {
                        $result["url"] = $uri;
                }
 
                if (empty($result["url"])) {
                        $result["url"] = $uri;
                }
 
-               Logger::log($uri." is ".$result["network"], Logger::DEBUG);
-
-               if (empty($result["baseurl"]) && ($result["network"] != Protocol::PHANTOM)) {
-                       $pos = strpos($result["url"], $host);
-                       if ($pos) {
-                               $result["baseurl"] = substr($result["url"], 0, $pos).$host;
-                       }
-               }
+               Logger::info('Probing done', ['uri' => $uri, 'network' => $result["network"]]);
 
                return $result;
        }
 
                return $result;
        }
@@ -780,7 +791,7 @@ class Probe
         * @return array Zot data
         * @throws HTTPException\InternalServerErrorException
         */
         * @return array Zot data
         * @throws HTTPException\InternalServerErrorException
         */
-       private static function zot($webfinger, $data)
+       private static function zot($webfinger, $data, $baseurl)
        {
                if (!empty($webfinger["aliases"]) && is_array($webfinger["aliases"])) {
                        foreach ($webfinger["aliases"] as $alias) {
        {
                if (!empty($webfinger["aliases"]) && is_array($webfinger["aliases"])) {
                        foreach ($webfinger["aliases"] as $alias) {
@@ -801,12 +812,12 @@ class Probe
                        }
                }
 
                        }
                }
 
-               if (empty($zot_url) && !empty($data['addr']) && !empty(self::$baseurl)) {
-                       $condition = ['nurl' => Strings::normaliseLink(self::$baseurl), 'platform' => ['hubzilla']];
+               if (empty($zot_url) && !empty($data['addr']) && !empty($baseurl)) {
+                       $condition = ['nurl' => Strings::normaliseLink($baseurl), 'platform' => ['hubzilla']];
                        if (!DBA::exists('gserver', $condition)) {
                                return $data;
                        }
                        if (!DBA::exists('gserver', $condition)) {
                                return $data;
                        }
-                       $zot_url = self::$baseurl . '/.well-known/zot-info?address=' . $data['addr'];
+                       $zot_url = $baseurl . '/.well-known/zot-info?address=' . $data['addr'];
                }
 
                if (empty($zot_url)) {
                }
 
                if (empty($zot_url)) {
@@ -828,7 +839,7 @@ class Probe
 
        public static function pollZot($url, $data)
        {
 
        public static function pollZot($url, $data)
        {
-               $curlResult = Network::curl($url);
+               $curlResult = DI::httpRequest()->get($url);
                if ($curlResult->isTimeout()) {
                        return $data;
                }
                if ($curlResult->isTimeout()) {
                        return $data;
                }
@@ -879,7 +890,7 @@ class Probe
                }
                if (!empty($json['public_forum'])) {
                        $data['community'] = $json['public_forum'];
                }
                if (!empty($json['public_forum'])) {
                        $data['community'] = $json['public_forum'];
-                       $data['account-type'] = Contact::PAGE_COMMUNITY;
+                       $data['account-type'] = User::PAGE_FLAGS_COMMUNITY;
                }
 
                if (!empty($json['profile'])) {
                }
 
                if (!empty($json['profile'])) {
@@ -921,11 +932,11 @@ class Probe
         * @return array webfinger data
         * @throws HTTPException\InternalServerErrorException
         */
         * @return array webfinger data
         * @throws HTTPException\InternalServerErrorException
         */
-       private static function webfinger($url, $type)
+       public static function webfinger($url, $type)
        {
                $xrd_timeout = DI::config()->get('system', 'xrd_timeout', 20);
 
        {
                $xrd_timeout = DI::config()->get('system', 'xrd_timeout', 20);
 
-               $curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout, 'accept_content' => $type]);
+               $curlResult = DI::httpRequest()->get($url, false, ['timeout' => $xrd_timeout, 'accept_content' => $type]);
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
@@ -935,7 +946,7 @@ class Probe
                $webfinger = json_decode($data, true);
                if (!empty($webfinger)) {
                        if (!isset($webfinger["links"])) {
                $webfinger = json_decode($data, true);
                if (!empty($webfinger)) {
                        if (!isset($webfinger["links"])) {
-                               Logger::log("No json webfinger links for ".$url, Logger::DEBUG);
+                               Logger::info('No json webfinger links', ['url' => $url]);
                                return [];
                        }
                        return $webfinger;
                                return [];
                        }
                        return $webfinger;
@@ -944,13 +955,13 @@ class Probe
                // If it is not JSON, maybe it is XML
                $xrd = XML::parseString($data, true);
                if (!is_object($xrd)) {
                // If it is not JSON, maybe it is XML
                $xrd = XML::parseString($data, true);
                if (!is_object($xrd)) {
-                       Logger::log("No webfinger data retrievable for ".$url, Logger::DEBUG);
+                       Logger::info('No webfinger data retrievable', ['url' => $url]);
                        return [];
                }
 
                $xrd_arr = XML::elementToArray($xrd);
                if (!isset($xrd_arr["xrd"]["link"])) {
                        return [];
                }
 
                $xrd_arr = XML::elementToArray($xrd);
                if (!isset($xrd_arr["xrd"]["link"])) {
-                       Logger::log("No XML webfinger links for ".$url, Logger::DEBUG);
+                       Logger::info('No XML webfinger links', ['url' => $url]);
                        return [];
                }
 
                        return [];
                }
 
@@ -994,20 +1005,20 @@ class Probe
         */
        private static function pollNoscrape($noscrape_url, $data)
        {
         */
        private static function pollNoscrape($noscrape_url, $data)
        {
-               $curlResult = Network::curl($noscrape_url);
+               $curlResult = DI::httpRequest()->get($noscrape_url);
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
                }
                $content = $curlResult->getBody();
                if (!$content) {
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
                }
                $content = $curlResult->getBody();
                if (!$content) {
-                       Logger::log("Empty body for ".$noscrape_url, Logger::DEBUG);
+                       Logger::info('Empty body', ['url' => $noscrape_url]);
                        return [];
                }
 
                $json = json_decode($content, true);
                if (!is_array($json)) {
                        return [];
                }
 
                $json = json_decode($content, true);
                if (!is_array($json)) {
-                       Logger::log("No json data for ".$noscrape_url, Logger::DEBUG);
+                       Logger::info('No json data', ['url' => $noscrape_url]);
                        return [];
                }
 
                        return [];
                }
 
@@ -1121,7 +1132,7 @@ class Probe
        {
                $data = [];
 
        {
                $data = [];
 
-               Logger::log("Check profile ".$profile_link, Logger::DEBUG);
+               Logger::info('Check profile', ['link' => $profile_link]);
 
                // Fetch data via noscrape - this is faster
                $noscrape_url = str_replace(["/hcard/", "/profile/"], "/noscrape/", $profile_link);
 
                // Fetch data via noscrape - this is faster
                $noscrape_url = str_replace(["/hcard/", "/profile/"], "/noscrape/", $profile_link);
@@ -1155,7 +1166,7 @@ class Probe
                $prof_data["fn"]           = $data['name']    ?? null;
                $prof_data["key"]          = $data['pubkey']  ?? null;
 
                $prof_data["fn"]           = $data['name']    ?? null;
                $prof_data["key"]          = $data['pubkey']  ?? null;
 
-               Logger::log("Result for profile ".$profile_link.": ".print_r($prof_data, true), Logger::DEBUG);
+               Logger::debug('Result', ['link' => $profile_link, 'data' => $prof_data]);
 
                return $prof_data;
        }
 
                return $prof_data;
        }
@@ -1252,7 +1263,7 @@ class Probe
         */
        private static function pollHcard($hcard_url, $data, $dfrn = false)
        {
         */
        private static function pollHcard($hcard_url, $data, $dfrn = false)
        {
-               $curlResult = Network::curl($hcard_url);
+               $curlResult = DI::httpRequest()->get($hcard_url);
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
@@ -1440,6 +1451,7 @@ class Probe
                        && !empty($hcard_url)
                ) {
                        $data["network"] = Protocol::DIASPORA;
                        && !empty($hcard_url)
                ) {
                        $data["network"] = Protocol::DIASPORA;
+                       $data["manually-approve"] = false;
 
                        // The Diaspora handle must always be lowercase
                        if (!empty($data["addr"])) {
 
                        // The Diaspora handle must always be lowercase
                        if (!empty($data["addr"])) {
@@ -1491,7 +1503,7 @@ class Probe
                                        && (($link["type"] ?? "") == "text/html")
                                        && ($link["href"] != "")
                                ) {
                                        && (($link["type"] ?? "") == "text/html")
                                        && ($link["href"] != "")
                                ) {
-                                       $data["url"] = $link["href"];
+                                       $data["url"] = $data["alias"] = $link["href"];
                                } elseif (($link["rel"] == "salmon") && !empty($link["href"])) {
                                        $data["notify"] = $link["href"];
                                } elseif (($link["rel"] == ActivityNamespace::FEED) && !empty($link["href"])) {
                                } elseif (($link["rel"] == "salmon") && !empty($link["href"])) {
                                        $data["notify"] = $link["href"];
                                } elseif (($link["rel"] == ActivityNamespace::FEED) && !empty($link["href"])) {
@@ -1506,7 +1518,7 @@ class Probe
                                                        $pubkey = substr($pubkey, 5);
                                                }
                                        } elseif (Strings::normaliseLink($pubkey) == 'http://') {
                                                        $pubkey = substr($pubkey, 5);
                                                }
                                        } elseif (Strings::normaliseLink($pubkey) == 'http://') {
-                                               $curlResult = Network::curl($pubkey);
+                                               $curlResult = DI::httpRequest()->get($pubkey);
                                                if ($curlResult->isTimeout()) {
                                                        self::$istimeout = true;
                                                        return $short ? false : [];
                                                if ($curlResult->isTimeout()) {
                                                        self::$istimeout = true;
                                                        return $short ? false : [];
@@ -1530,6 +1542,7 @@ class Probe
                        && isset($data["url"])
                ) {
                        $data["network"] = Protocol::OSTATUS;
                        && isset($data["url"])
                ) {
                        $data["network"] = Protocol::OSTATUS;
+                       $data["manually-approve"] = false;
                } else {
                        return $short ? false : [];
                }
                } else {
                        return $short ? false : [];
                }
@@ -1539,7 +1552,7 @@ class Probe
                }
 
                // Fetch all additional data from the feed
                }
 
                // Fetch all additional data from the feed
-               $curlResult = Network::curl($data["poll"]);
+               $curlResult = DI::httpRequest()->get($data["poll"]);
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
@@ -1574,8 +1587,7 @@ class Probe
                        $data["url"] = $feed_data["header"]["author-link"];
                }
 
                        $data["url"] = $feed_data["header"]["author-link"];
                }
 
-               if (($data['poll'] == $data['url']) && ($data["alias"] != '')) {
-                       $data['url'] = $data["alias"];
+               if ($data["url"] == $data["alias"]) {
                        $data["alias"] = '';
                }
 
                        $data["alias"] = '';
                }
 
@@ -1592,7 +1604,7 @@ class Probe
         */
        private static function pumpioProfileData($profile_link)
        {
         */
        private static function pumpioProfileData($profile_link)
        {
-               $curlResult = Network::curl($profile_link);
+               $curlResult = DI::httpRequest()->get($profile_link);
                if (!$curlResult->isSuccess()) {
                        return [];
                }
                if (!$curlResult->isSuccess()) {
                        return [];
                }
@@ -1710,9 +1722,9 @@ class Probe
         */
        private static function twitter($uri)
        {
         */
        private static function twitter($uri)
        {
-               if (preg_match('=(.*)@twitter.com=i', $uri, $matches)) {
+               if (preg_match('=([^@]+)@(?:mobile\.)?twitter\.com$=i', $uri, $matches)) {
                        $nick = $matches[1];
                        $nick = $matches[1];
-               } elseif (preg_match('=https?://twitter.com/(.*)=i', $uri, $matches)) {
+               } elseif (preg_match('=^https?://(?:mobile\.)?twitter\.com/(.+)=i', $uri, $matches)) {
                        $nick = $matches[1];
                } else {
                        return [];
                        $nick = $matches[1];
                } else {
                        return [];
@@ -1725,39 +1737,6 @@ class Probe
                $data['network'] = Protocol::TWITTER;
                $data['baseurl'] = 'https://twitter.com';
 
                $data['network'] = Protocol::TWITTER;
                $data['baseurl'] = 'https://twitter.com';
 
-               $curlResult = Network::curl($data['url'], false);
-               if (!$curlResult->isSuccess()) {
-                       return [];
-               }
-
-               $body = $curlResult->getBody();
-               $doc = new DOMDocument();
-               @$doc->loadHTML($body);
-               $xpath = new DOMXPath($doc);
-
-               $list = $xpath->query('//img[@class]');
-               foreach ($list as $node) {
-                       $img_attr = [];
-                       if ($node->attributes->length) {
-                               foreach ($node->attributes as $attribute) {
-                                       $img_attr[$attribute->name] = $attribute->value;
-                               }
-                       }
-
-                       if (empty($img_attr['class'])) {
-                               continue;
-                       }
-
-                       if (strpos($img_attr['class'], 'ProfileAvatar-image') !== false) {
-                               if (!empty($img_attr['src'])) {
-                                       $data['photo'] = $img_attr['src'];
-                               }
-                               if (!empty($img_attr['alt'])) {
-                                       $data['name'] = $img_attr['alt'];
-                               }
-                       }
-               }
-
                return $data;
        }
 
                return $data;
        }
 
@@ -1805,6 +1784,9 @@ class Probe
                $base = $xpath->evaluate('string(/html/head/base/@href)') ?: $base;
 
                $baseParts = parse_url($base);
                $base = $xpath->evaluate('string(/html/head/base/@href)') ?: $base;
 
                $baseParts = parse_url($base);
+               if (empty($baseParts['host'])) {
+                       return $href;
+               }
 
                // Naked domain case (scheme://basehost)
                $path = $baseParts['path'] ?? '/';
 
                // Naked domain case (scheme://basehost)
                $path = $baseParts['path'] ?? '/';
@@ -1856,7 +1838,7 @@ class Probe
         */
        private static function feed($url, $probe = true)
        {
         */
        private static function feed($url, $probe = true)
        {
-               $curlResult = Network::curl($url);
+               $curlResult = DI::httpRequest()->get($url);
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
                if ($curlResult->isTimeout()) {
                        self::$istimeout = true;
                        return [];
@@ -1897,12 +1879,6 @@ class Probe
                $data["url"] = $url;
                $data["poll"] = $url;
 
                $data["url"] = $url;
                $data["poll"] = $url;
 
-               if (!empty($feed_data["header"]["author-link"])) {
-                       $data["baseurl"] = $feed_data["header"]["author-link"];
-               } else {
-                       $data["baseurl"] = $data["url"];
-               }
-
                $data["network"] = Protocol::FEED;
 
                return $data;
                $data["network"] = Protocol::FEED;
 
                return $data;
@@ -1946,7 +1922,7 @@ class Probe
                }
 
                $msgs = Email::poll($mbox, $uri);
                }
 
                $msgs = Email::poll($mbox, $uri);
-               Logger::log('searching '.$uri.', '.count($msgs).' messages found.', Logger::DEBUG);
+               Logger::info('Messages found', ['uri' => $uri, 'count' => count($msgs)]);
 
                if (!count($msgs)) {
                        return [];
 
                if (!count($msgs)) {
                        return [];
@@ -2029,8 +2005,220 @@ class Probe
 
                $fixed = $scheme.$host.$port.$path.$query.$fragment;
 
 
                $fixed = $scheme.$host.$port.$path.$query.$fragment;
 
-               Logger::log('Base: '.$base.' - Avatar: '.$avatar.' - Fixed: '.$fixed, Logger::DATA);
+               Logger::debug('Avatar fixed', ['base' => $base, 'avatar' => $avatar, 'fixed' => $fixed]);
 
                return $fixed;
        }
 
                return $fixed;
        }
+
+       /**
+        * Fetch the last date that the contact had posted something (publically)
+        *
+        * @param string $data  probing result
+        * @return string last activity
+        */
+       public static function getLastUpdate(array $data)
+       {
+               $uid = User::getIdForURL($data['url']);
+               if (!empty($uid)) {
+                       $contact = Contact::selectFirst(['url', 'last-item'], ['self' => true, 'uid' => $uid]);
+                       if (!empty($contact['last-item'])) {
+                               return $contact['last-item'];
+                       }
+               }
+
+               if ($lastUpdate = self::updateFromNoScrape($data)) {
+                       return $lastUpdate;
+               }
+
+               if (!empty($data['outbox'])) {
+                       return self::updateFromOutbox($data['outbox'], $data);
+               } elseif (!empty($data['poll']) && ($data['network'] == Protocol::ACTIVITYPUB)) {
+                       return self::updateFromOutbox($data['poll'], $data);
+               } elseif (!empty($data['poll'])) {
+                       return self::updateFromFeed($data);
+               }
+
+               return '';
+       }
+
+       /**
+        * Fetch the last activity date from the "noscrape" endpoint
+        *
+        * @param array $data Probing result
+        * @return string last activity
+        *
+        * @return bool 'true' if update was successful or the server was unreachable
+        */
+       private static function updateFromNoScrape(array $data)
+       {
+               if (empty($data['baseurl'])) {
+                       return '';
+               }
+
+               // Check the 'noscrape' endpoint when it is a Friendica server
+               $gserver = DBA::selectFirst('gserver', ['noscrape'], ["`nurl` = ? AND `noscrape` != ''",
+                       Strings::normaliseLink($data['baseurl'])]);
+               if (!DBA::isResult($gserver)) {
+                       return '';
+               }
+
+               $curlResult = DI::httpRequest()->get($gserver['noscrape'] . '/' . $data['nick']);
+
+               if ($curlResult->isSuccess() && !empty($curlResult->getBody())) {
+                       $noscrape = json_decode($curlResult->getBody(), true);
+                       if (!empty($noscrape) && !empty($noscrape['updated'])) {
+                               return DateTimeFormat::utc($noscrape['updated'], DateTimeFormat::MYSQL);
+                       }
+               }
+
+               return '';
+       }
+
+       /**
+        * Fetch the last activity date from an ActivityPub Outbox
+        *
+        * @param string $feed
+        * @param array  $data Probing result
+        * @return string last activity
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        */
+       private static function updateFromOutbox(string $feed, array $data)
+       {
+               $outbox = ActivityPub::fetchContent($feed);
+               if (empty($outbox)) {
+                       return '';
+               }
+
+               if (!empty($outbox['orderedItems'])) {
+                       $items = $outbox['orderedItems'];
+               } elseif (!empty($outbox['first']['orderedItems'])) {
+                       $items = $outbox['first']['orderedItems'];
+               } elseif (!empty($outbox['first']['href']) && ($outbox['first']['href'] != $feed)) {
+                       return self::updateFromOutbox($outbox['first']['href'], $data);
+               } elseif (!empty($outbox['first'])) {
+                       if (is_string($outbox['first']) && ($outbox['first'] != $feed)) {
+                               return self::updateFromOutbox($outbox['first'], $data);
+                       } else {
+                               Logger::warning('Unexpected data', ['outbox' => $outbox]);
+                       }
+                       return '';
+               } else {
+                       $items = [];
+               }
+
+               $last_updated = '';
+               foreach ($items as $activity) {
+                       if (!empty($activity['published'])) {
+                               $published =  DateTimeFormat::utc($activity['published']);
+                       } elseif (!empty($activity['object']['published'])) {
+                               $published =  DateTimeFormat::utc($activity['object']['published']);
+                       } else {
+                               continue;
+                       }
+
+                       if ($last_updated < $published) {
+                               $last_updated = $published;
+                       }
+               }
+
+               if (!empty($last_updated)) {
+                       return $last_updated;
+               }
+
+               return '';
+       }
+
+       /**
+        * Fetch the last activity date from an XML feed
+        *
+        * @param array $data Probing result
+        * @return string last activity
+        */
+       private static function updateFromFeed(array $data)
+       {
+               // Search for the newest entry in the feed
+               $curlResult = DI::httpRequest()->get($data['poll']);
+               if (!$curlResult->isSuccess()) {
+                       return '';
+               }
+
+               $doc = new DOMDocument();
+               @$doc->loadXML($curlResult->getBody());
+
+               $xpath = new DOMXPath($doc);
+               $xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
+
+               $entries = $xpath->query('/atom:feed/atom:entry');
+
+               $last_updated = '';
+
+               foreach ($entries as $entry) {
+                       $published_item = $xpath->query('atom:published/text()', $entry)->item(0);
+                       $updated_item   = $xpath->query('atom:updated/text()'  , $entry)->item(0);
+                       $published      = !empty($published_item->nodeValue) ? DateTimeFormat::utc($published_item->nodeValue) : null;
+                       $updated        = !empty($updated_item->nodeValue) ? DateTimeFormat::utc($updated_item->nodeValue) : null;
+
+                       if (empty($published) || empty($updated)) {
+                               Logger::notice('Invalid entry for XPath.', ['entry' => $entry, 'url' => $data['url']]);
+                               continue;
+                       }
+
+                       if ($last_updated < $published) {
+                               $last_updated = $published;
+                       }
+
+                       if ($last_updated < $updated) {
+                               $last_updated = $updated;
+                       }
+               }
+
+               if (!empty($last_updated)) {
+                       return $last_updated;
+               }
+
+               return '';
+       }
+
+       /**
+        * Probe data from local profiles without network traffic
+        *
+        * @param string $url
+        * @return array probed data
+        */
+       private static function localProbe(string $url)
+       {
+               $uid = User::getIdForURL($url);
+               if (empty($uid)) {
+                       return [];
+               }
+
+               $profile = User::getOwnerDataById($uid);
+               if (empty($profile)) {
+                       return [];
+               }
+
+               $approfile = ActivityPub\Transmitter::getProfile($uid);
+               if (empty($approfile)) {
+                       return [];
+               }
+
+               if (empty($profile['gsid'])) {
+                       $profile['gsid'] = GServer::getID($approfile['generator']['url']);
+               }
+
+               $data = ['name' => $profile['name'], 'nick' => $profile['nick'], 'guid' => $approfile['diaspora:guid'] ?? '',
+                       'url' => $profile['url'], 'addr' => $profile['addr'], 'alias' => $profile['alias'],
+                       'photo' => $profile['photo'], 'account-type' => $profile['contact-type'],
+                       'community' => ($profile['contact-type'] == User::ACCOUNT_TYPE_COMMUNITY),
+                       'keywords' => $profile['keywords'], 'location' => $profile['location'], 'about' => $profile['about'], 
+                       'hide' => !$profile['net-publish'], 'batch' => '', 'notify' => $profile['notify'],
+                       'poll' => $profile['poll'], 'request' => $profile['request'], 'confirm' => $profile['confirm'],
+                       'subscribe' => $approfile['generator']['url'] . '/follow?url={uri}', 'poco' => $profile['poco'], 
+                       'following' => $approfile['following'], 'followers' => $approfile['followers'],
+                       'inbox' => $approfile['inbox'], 'outbox' => $approfile['outbox'],
+                       'sharedinbox' => $approfile['endpoints']['sharedInbox'], 'network' => Protocol::DFRN, 
+                       'pubkey' => $profile['upubkey'], 'baseurl' => $approfile['generator']['url'], 'gsid' => $profile['gsid'],
+                       'manually-approve' => in_array($profile['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP])];
+               return self::rearrangeData($data);              
+       }
 }
 }