X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;ds=sidebyside;f=src%2FModel%2FAPContact.php;h=fd748f1cd4e92419b77513f5cad205a3cec60fcb;hb=60551e6277a4e3a0901481e2a23b3963db6a1c2c;hp=deed6299de8613f8c3c6fe78a15e0768651a81c0;hpb=79526564ca38c306c9fc78c5063dd1a0c7fbf6f0;p=friendica.git diff --git a/src/Model/APContact.php b/src/Model/APContact.php index deed6299de..fd748f1cd4 100644 --- a/src/Model/APContact.php +++ b/src/Model/APContact.php @@ -24,11 +24,12 @@ namespace Friendica\Model; use Friendica\Content\Text\HTML; use Friendica\Core\Cache\Enum\Duration; use Friendica\Core\Logger; +use Friendica\Core\Protocol; use Friendica\Core\System; use Friendica\Database\DBA; -use Friendica\Database\DBStructure; use Friendica\DI; -use Friendica\Network\HTTPClient\Client\HttpClient; +use Friendica\Model\Item; +use Friendica\Network\HTTPClient\Client\HttpClientAccept; use Friendica\Network\HTTPException; use Friendica\Network\Probe; use Friendica\Protocol\ActivityNamespace; @@ -39,7 +40,7 @@ use Friendica\Util\DateTimeFormat; use Friendica\Util\HTTPSignature; use Friendica\Util\JsonLD; use Friendica\Util\Network; -use Friendica\Util\Strings; +use GuzzleHttp\Psr7\Uri; class APContact { @@ -49,7 +50,7 @@ class APContact * @param string $addr Address * @return array webfinger data */ - private static function fetchWebfingerData(string $addr) + private static function fetchWebfingerData(string $addr): array { $addr_parts = explode('@', $addr); if (count($addr_parts) != 2) { @@ -61,7 +62,7 @@ class APContact 'addr' => $local_owner['addr'], 'baseurl' => $local_owner['baseurl'], 'url' => $local_owner['url'], - 'subscribe' => $local_owner['baseurl'] . '/follow?url={uri}']; + 'subscribe' => $local_owner['baseurl'] . '/contact/follow?url={uri}']; if (!empty($local_owner['alias']) && ($local_owner['url'] != $local_owner['alias'])) { $data['alias'] = $local_owner['alias']; @@ -72,10 +73,10 @@ class APContact $data = ['addr' => $addr]; $template = 'https://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr); - $webfinger = Probe::webfinger(str_replace('{uri}', urlencode($addr), $template), HttpClient::ACCEPT_JRD_JSON); + $webfinger = Probe::webfinger(str_replace('{uri}', urlencode($addr), $template), HttpClientAccept::JRD_JSON); if (empty($webfinger['links'])) { $template = 'http://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr); - $webfinger = Probe::webfinger(str_replace('{uri}', urlencode($addr), $template), HttpClient::ACCEPT_JRD_JSON); + $webfinger = Probe::webfinger(str_replace('{uri}', urlencode($addr), $template), HttpClientAccept::JRD_JSON); if (empty($webfinger['links'])) { return []; } @@ -117,15 +118,16 @@ class APContact * @return array profile array * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException + * @todo Rewrite parameter $update to avoid true|false|null (boolean is binary, null adds a third case) */ - public static function getByURL($url, $update = null) + public static function getByURL(string $url, $update = null): array { if (empty($url) || Network::isUrlBlocked($url)) { Logger::info('Domain is blocked', ['url' => $url]); return []; } - $fetched_contact = false; + $fetched_contact = []; if (empty($update)) { if (is_null($update)) { @@ -165,6 +167,8 @@ class APContact return $fetched_contact; } $url = $apcontact['url']; + } elseif (empty(parse_url($url, PHP_URL_PATH))) { + $apcontact['baseurl'] = $url; } // Detect multiple fast repeating request to the same address @@ -221,14 +225,14 @@ class APContact $apcontact['type'] = str_replace('as:', '', JsonLD::fetchElement($compacted, '@type')); $apcontact['following'] = JsonLD::fetchElement($compacted, 'as:following', '@id'); $apcontact['followers'] = JsonLD::fetchElement($compacted, 'as:followers', '@id'); - $apcontact['inbox'] = JsonLD::fetchElement($compacted, 'ldp:inbox', '@id'); + $apcontact['inbox'] = (JsonLD::fetchElement($compacted, 'ldp:inbox', '@id') ?? ''); self::unarchiveInbox($apcontact['inbox'], false); $apcontact['outbox'] = JsonLD::fetchElement($compacted, 'as:outbox', '@id'); $apcontact['sharedinbox'] = ''; if (!empty($compacted['as:endpoints'])) { - $apcontact['sharedinbox'] = JsonLD::fetchElement($compacted['as:endpoints'], 'as:sharedInbox', '@id'); + $apcontact['sharedinbox'] = (JsonLD::fetchElement($compacted['as:endpoints'], 'as:sharedInbox', '@id') ?? ''); self::unarchiveInbox($apcontact['sharedinbox'], true); } @@ -242,9 +246,10 @@ class APContact $apcontact['name'] = $apcontact['nick']; } - $apcontact['about'] = HTML::toBBCode(JsonLD::fetchElement($compacted, 'as:summary', '@value')); + $apcontact['about'] = HTML::toBBCode(JsonLD::fetchElement($compacted, 'as:summary', '@value') ?? ''); $ims = JsonLD::fetchElementArray($compacted, 'vcard:hasInstantMessage'); + if (!empty($ims)) { foreach ($ims as $link) { if (substr($link, 0, 5) == 'xmpp:') { @@ -286,28 +291,27 @@ class APContact return $fetched_contact; } - $parts = parse_url($apcontact['url']); - unset($parts['scheme']); - unset($parts['path']); - if (empty($apcontact['addr'])) { - if (!empty($apcontact['nick']) && is_array($parts)) { - $apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts)); - } else { + try { + $apcontact['addr'] = $apcontact['nick'] . '@' . (new Uri($apcontact['url']))->getAuthority(); + } catch (\Throwable $e) { + Logger::warning('Unable to coerce APContact URL into a UriInterface object', ['url' => $apcontact['url'], 'error' => $e->getMessage()]); $apcontact['addr'] = ''; } } $apcontact['pubkey'] = null; if (!empty($compacted['w3id:publicKey'])) { - $apcontact['pubkey'] = trim(JsonLD::fetchElement($compacted['w3id:publicKey'], 'w3id:publicKeyPem', '@value')); - if (strstr($apcontact['pubkey'], 'RSA ')) { + $apcontact['pubkey'] = trim(JsonLD::fetchElement($compacted['w3id:publicKey'], 'w3id:publicKeyPem', '@value') ?? ''); + if (strpos($apcontact['pubkey'], 'RSA ') !== false) { $apcontact['pubkey'] = Crypto::rsaToPem($apcontact['pubkey']); } } $apcontact['manually-approve'] = (int)JsonLD::fetchElement($compacted, 'as:manuallyApprovesFollowers'); + $apcontact['suspended'] = (int)JsonLD::fetchElement($compacted, 'toot:suspended'); + if (!empty($compacted['as:generator'])) { $apcontact['baseurl'] = JsonLD::fetchElement($compacted['as:generator'], 'as:url', '@id'); $apcontact['generator'] = JsonLD::fetchElement($compacted['as:generator'], 'as:name', '@value'); @@ -347,17 +351,18 @@ class APContact if (!empty($apcontact['outbox'])) { if (!empty($local_owner)) { - $outbox = ActivityPub\Transmitter::getOutbox($local_owner); + $statuses_count = self::getStatusesCount($local_owner); } else { $outbox = ActivityPub::fetchContent($apcontact['outbox']); + $statuses_count = $outbox['totalItems'] ?? 0; } - if (!empty($outbox['totalItems'])) { + if (!empty($statuses_count)) { // Mastodon seriously allows for this condition? // Jul 20 2021 - See https://chaos.social/@m11 for a negative posts count - if ($outbox['totalItems'] < 0) { - $outbox['totalItems'] = 0; + if ($statuses_count < 0) { + $statuses_count = 0; } - $apcontact['statuses_count'] = $outbox['totalItems']; + $apcontact['statuses_count'] = $statuses_count; } } @@ -366,7 +371,7 @@ class APContact // To-Do // Unhandled - // tag, attachment, image, nomadicLocations, signature, featured, movedTo, liked + // tag, attachment, image, nomadicLocations, signature, movedTo, liked // Unhandled from Misskey // sharedInbox, isCat @@ -375,14 +380,14 @@ class APContact // kroeg:blocks, updated // When the photo is too large, try to shorten it by removing parts - if (strlen($apcontact['photo']) > 255) { + if (strlen($apcontact['photo'] ?? '') > 255) { $parts = parse_url($apcontact['photo']); unset($parts['fragment']); - $apcontact['photo'] = Network::unparseURL($parts); + $apcontact['photo'] = (string)Uri::fromParts($parts); if (strlen($apcontact['photo']) > 255) { unset($parts['query']); - $apcontact['photo'] = Network::unparseURL($parts); + $apcontact['photo'] = (string)Uri::fromParts($parts); } if (strlen($apcontact['photo']) > 255) { @@ -432,6 +437,30 @@ class APContact $apcontact['uri-id'] = ItemURI::insert(['uri' => $apcontact['url'], 'guid' => $apcontact['uuid']]); } + foreach (APContact\Endpoint::ENDPOINT_NAMES as $type => $name) { + $value = JsonLD::fetchElement($compacted, $name, '@id'); + if (empty($value)) { + continue; + } + APContact\Endpoint::update($apcontact['uri-id'], $type, $value); + } + + if (!empty($compacted['as:endpoints'])) { + foreach ($compacted['as:endpoints'] as $name => $endpoint) { + if (empty($endpoint['@id']) || !is_string($endpoint['@id'])) { + continue; + } + + if (in_array($name, APContact\Endpoint::ENDPOINT_NAMES)) { + $key = array_search($name, APContact\Endpoint::ENDPOINT_NAMES); + APContact\Endpoint::update($apcontact['uri-id'], $key, $endpoint['@id']); + Logger::debug('Store endpoint', ['key' => $key, 'name' => $name, 'endpoint' => $endpoint['@id']]); + } elseif (!in_array($name, ['as:sharedInbox', 'as:uploadMedia', 'as:oauthTokenEndpoint', 'as:oauthAuthorizationEndpoint', 'litepub:oauthRegistrationEndpoint'])) { + Logger::debug('Unknown endpoint', ['name' => $name, 'endpoint' => $endpoint['@id']]); + } + } + } + $apcontact['updated'] = DateTimeFormat::utcNow(); // We delete the old entry when the URL is changed @@ -441,7 +470,7 @@ class APContact } // Limit the length on incoming fields - $apcontact = DBStructure::getFieldsForTable('apcontact', $apcontact); + $apcontact = DI::dbaDefinition()->truncateFieldsForTable('apcontact', $apcontact); if (DBA::exists('apcontact', ['url' => $apcontact['url']])) { DBA::update('apcontact', $apcontact, ['url' => $apcontact['url']]); @@ -454,6 +483,30 @@ class APContact return DBA::selectFirst('apcontact', [], ['url' => $apcontact['url']]) ?: []; } + /** + * Fetch the number of statuses for the given owner + * + * @param array $owner + * + * @return integer + */ + private static function getStatusesCount(array $owner): int + { + $condition = [ + 'private' => [Item::PUBLIC, Item::UNLISTED], + 'author-id' => Contact::getIdForURL($owner['url'], 0, false), + 'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], + 'network' => Protocol::DFRN, + 'parent-network' => Protocol::FEDERATED, + 'deleted' => false, + 'visible' => true, + ]; + + $count = Post::countPosts($condition); + + return $count; + } + /** * Mark the given AP Contact as "to archive" * @@ -502,8 +555,9 @@ class APContact * * @param string $url inbox url * @param boolean $shared Shared Inbox + * @return void */ - private static function unarchiveInbox($url, $shared) + private static function unarchiveInbox(string $url, bool $shared) { if (empty($url)) { return; @@ -511,4 +565,28 @@ class APContact HTTPSignature::setInboxStatus($url, true, $shared); } + + /** + * Check if the apcontact is a relay account + * + * @param array $apcontact + * + * @return bool + */ + public static function isRelay(array $apcontact): bool + { + if (empty($apcontact['nick']) || $apcontact['nick'] != 'relay') { + return false; + } + + if (!empty($apcontact['type']) && $apcontact['type'] == 'Application') { + return true; + } + + if (!empty($apcontact['type']) && in_array($apcontact['type'], ['Group', 'Service']) && is_null($apcontact['outbox'])) { + return true; + } + + return false; + } }