X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;ds=sidebyside;f=src%2FProtocol%2FActivityPub%2FReceiver.php;h=0a0d37a7cdb331f3c87ce20291a51c7501cd57c8;hb=265137ef44032e30f968b6bdb1a6d21990d1dfdb;hp=ea5251438defa50a5e3cccb1b1c578a386d81f99;hpb=3e9aa606abcb1e7edcb4dcb04a3890029da4bf17;p=friendica.git diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index ea5251438d..0a0d37a7cd 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -1,6 +1,6 @@ $actor]); return; + } elseif ($apcontact['type'] == 'Application' && $apcontact['nick'] == 'relay') { + self::processRelayPost($ldactivity, $actor); + return; + } else { + APContact::unmarkForArchival($apcontact); } $http_signer = HTTPSignature::getSigner($body, $header); - if (empty($http_signer)) { + if ($http_signer === false) { Logger::warning('Invalid HTTP signature, message will be discarded.'); return; + } elseif (empty($http_signer)) { + Logger::info('Signer is a tombstone. The message will be discarded, the signer account is deleted.'); + return; } else { Logger::info('Valid HTTP signature', ['signer' => $http_signer]); } @@ -150,10 +159,11 @@ class Receiver /** * Process incoming posts from relays * - * @param array $activity + * @param array $activity + * @param string $actor * @return void */ - private static function processRelayPost(array $activity) + private static function processRelayPost(array $activity, string $actor) { $type = JsonLD::fetchElement($activity, '@type'); if (!$type) { @@ -172,6 +182,17 @@ class Receiver return; } + $contact = Contact::getByURL($actor); + if (empty($contact)) { + Logger::info('Relay contact not found', ['actor' => $actor]); + return; + } + + if (!in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND])) { + Logger::notice('Relay is no sharer', ['actor' => $actor]); + return; + } + Logger::info('Got relayed message id', ['id' => $object_id]); $item_id = Item::searchByLink($object_id); @@ -180,7 +201,11 @@ class Receiver return; } - Processor::fetchMissingActivity($object_id); + $id = Processor::fetchMissingActivity($object_id, [], $actor); + if (empty($id)) { + Logger::notice('Relayed message had not been fetched', ['id' => $object_id]); + return; + } $item_id = Item::searchByLink($object_id); if ($item_id) { @@ -210,13 +235,14 @@ class Receiver } } - if (Item::exists(['uri' => $object_id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]])) { + if (Post::exists(['uri' => $object_id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]])) { // We just assume "note" since it doesn't make a difference for the further processing return 'as:Note'; } $profile = APContact::getByURL($object_id); if (!empty($profile['type'])) { + APContact::unmarkForArchival($profile); return 'as:' . $profile['type']; } @@ -277,14 +303,14 @@ class Receiver $receivers = $reception_types = []; foreach ($receiverdata as $key => $data) { $receivers[$key] = $data['uid']; - $reception_types[$data['uid']] = $data['type'] ?? 0; + $reception_types[$data['uid']] = $data['type'] ?? self::TARGET_UNKNOWN; } // When it is a delivery to a personal inbox we add that user to the receivers if (!empty($uid)) { - $additional = ['uid:' . $uid => $uid]; - $receivers = array_merge($receivers, $additional); - if (empty($reception_types[$uid]) || in_array($reception_types[$uid], [self::TARGET_UNKNOWN, self::TARGET_FOLLOWER, self::TARGET_ANSWER, self::TARGET_GLOBAL])) { + $additional = [$uid => $uid]; + $receivers = array_replace($receivers, $additional); + if (empty($activity['thread-completion']) && (empty($reception_types[$uid]) || in_array($reception_types[$uid], [self::TARGET_UNKNOWN, self::TARGET_FOLLOWER, self::TARGET_ANSWER, self::TARGET_GLOBAL]))) { $reception_types[$uid] = self::TARGET_BCC; } } else { @@ -337,6 +363,7 @@ class Receiver $object_data['author'] = JsonLD::fetchElement($activity, 'as:actor', '@id'); $object_data['object_id'] = $object_id; $object_data['object_type'] = ''; // Since we don't fetch the object, we don't know the type + $object_data['push'] = $push; } elseif (in_array($type, ['as:Add'])) { $object_data = []; $object_data['id'] = JsonLD::fetchElement($activity, '@id'); @@ -344,6 +371,7 @@ class Receiver $object_data['object_id'] = JsonLD::fetchElement($activity, 'as:object', '@id'); $object_data['object_type'] = JsonLD::fetchElement($activity['as:object'], '@type'); $object_data['object_content'] = JsonLD::fetchElement($activity['as:object'], 'as:content', '@type'); + $object_data['push'] = $push; } else { $object_data = []; $object_data['id'] = JsonLD::fetchElement($activity, '@id'); @@ -351,6 +379,7 @@ class Receiver $object_data['object_actor'] = JsonLD::fetchElement($activity['as:object'], 'as:actor', '@id'); $object_data['object_object'] = JsonLD::fetchElement($activity['as:object'], 'as:object'); $object_data['object_type'] = JsonLD::fetchElement($activity['as:object'], '@type'); + $object_data['push'] = $push; // An Undo is done on the object of an object, so we need that type as well if (($type == 'as:Undo') && !empty($object_data['object_object'])) { @@ -367,8 +396,8 @@ class Receiver $object_data['type'] = $type; $object_data['actor'] = $actor; $object_data['item_receiver'] = $receivers; - $object_data['receiver'] = array_merge($object_data['receiver'] ?? [], $receivers); - $object_data['reception_type'] = array_merge($object_data['reception_type'] ?? [], $reception_types); + $object_data['receiver'] = array_replace($object_data['receiver'] ?? [], $receivers); + $object_data['reception_type'] = array_replace($object_data['reception_type'] ?? [], $reception_types); $author = $object_data['author'] ?? $actor; if (!empty($author) && !empty($object_data['id'])) { @@ -468,6 +497,11 @@ class Receiver $object_data['thread-completion'] = $activity['thread-completion']; } + // Internal flag for posts that arrived via relay + if (!empty($activity['from-relay'])) { + $object_data['from-relay'] = $activity['from-relay']; + } + switch ($type) { case 'as:Create': if (in_array($object_data['object_type'], self::CONTENT_TYPES)) { @@ -484,10 +518,14 @@ class Receiver case 'as:Announce': if (in_array($object_data['object_type'], self::CONTENT_TYPES)) { - $object_data['thread-completion'] = true; + $object_data['thread-completion'] = Contact::getIdForURL($actor); $item = ActivityPub\Processor::createItem($object_data); - $item['post-type'] = Item::PT_ANNOUNCEMENT; + if (empty($item)) { + return; + } + + $item['post-reason'] = Item::PR_ANNOUNCEMENT; ActivityPub\Processor::postItem($object_data, $item); $announce_object_data = self::processObject($activity); @@ -617,22 +655,27 @@ class Receiver } if (!empty($reply)) { - $parents = Item::select(['uid'], ['uri' => $reply]); - while ($parent = Item::fetch($parents)) { - $receivers['uid:' . $parent['uid']] = ['uid' => $parent['uid'], 'type' => self::TARGET_ANSWER]; + $parents = Post::select(['uid'], ['uri' => $reply]); + while ($parent = Post::fetch($parents)) { + $receivers[$parent['uid']] = ['uid' => $parent['uid'], 'type' => self::TARGET_ANSWER]; } + DBA::close($parents); } if (!empty($actor)) { - $profile = APContact::getByURL($actor); + $profile = APContact::getByURL($actor); $followers = $profile['followers'] ?? ''; - - Logger::log('Actor: ' . $actor . ' - Followers: ' . $followers, Logger::DEBUG); + $is_forum = ($actor['type'] ?? '') == 'Group'; + Logger::info('Got actor and followers', ['actor' => $actor, 'followers' => $followers]); } else { Logger::info('Empty actor', ['activity' => $activity]); $followers = ''; + $is_forum = false; } + // We have to prevent false follower assumptions upon thread completions + $follower_target = empty($activity['thread-completion']) ? self::TARGET_FOLLOWER : self::TARGET_UNKNOWN; + foreach (['as:to', 'as:cc', 'as:bto', 'as:bcc'] as $element) { $receiver_list = JsonLD::fetchElementArray($activity, $element, '@id'); if (empty($receiver_list)) { @@ -641,17 +684,17 @@ class Receiver foreach ($receiver_list as $receiver) { if ($receiver == self::PUBLIC_COLLECTION) { - $receivers['uid:0'] = ['uid' => 0, 'type' => self::TARGET_GLOBAL]; + $receivers[0] = ['uid' => 0, 'type' => self::TARGET_GLOBAL]; } // Add receiver "-1" for unlisted posts if ($fetch_unlisted && ($receiver == self::PUBLIC_COLLECTION) && ($element == 'as:cc')) { - $receivers['uid:-1'] = ['uid' => -1, 'type' => self::TARGET_GLOBAL]; + $receivers[-1] = ['uid' => -1, 'type' => self::TARGET_GLOBAL]; } // Fetch the receivers for the public and the followers collection - if (in_array($receiver, [$followers, self::PUBLIC_COLLECTION]) && !empty($actor)) { - $receivers = self::getReceiverForActor($actor, $tags, $receivers); + if ((($receiver == $followers) || (($receiver == self::PUBLIC_COLLECTION) && !$is_forum)) && !empty($actor)) { + $receivers = self::getReceiverForActor($actor, $tags, $receivers, $follower_target); continue; } @@ -679,7 +722,7 @@ class Receiver } } - $type = $receivers['uid:' . $contact['uid']]['type'] ?? self::TARGET_UNKNOWN; + $type = $receivers[$contact['uid']]['type'] ?? self::TARGET_UNKNOWN; if (in_array($type, [self::TARGET_UNKNOWN, self::TARGET_FOLLOWER, self::TARGET_ANSWER, self::TARGET_GLOBAL])) { switch ($element) { case 'as:to': @@ -696,7 +739,7 @@ class Receiver break; } - $receivers['uid:' . $contact['uid']] = ['uid' => $contact['uid'], 'type' => $type]; + $receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $type]; } } } @@ -709,32 +752,34 @@ class Receiver /** * Fetch the receiver list of a given actor * - * @param string $actor - * @param array $tags + * @param string $actor + * @param array $tags + * @param array $receivers + * @param integer $target_type * * @return array with receivers (user id) * @throws \Exception */ - private static function getReceiverForActor($actor, $tags, $receivers) + private static function getReceiverForActor($actor, $tags, $receivers, $target_type) { $basecondition = ['rel' => [Contact::SHARING, Contact::FRIEND, Contact::FOLLOWER], 'network' => Protocol::FEDERATED, 'archive' => false, 'pending' => false]; - $condition = DBA::mergeConditions($basecondition, ['nurl' => Strings::normaliseLink($actor)]); + $condition = DBA::mergeConditions($basecondition, ["`nurl` = ? AND `uid` != ?", Strings::normaliseLink($actor), 0]); $contacts = DBA::select('contact', ['uid', 'rel'], $condition); while ($contact = DBA::fetch($contacts)) { - if (empty($receivers['uid:' . $contact['uid']]) && self::isValidReceiverForActor($contact, $actor, $tags)) { - $receivers['uid:' . $contact['uid']] = ['uid' => $contact['uid'], 'type' => self::TARGET_FOLLOWER]; + if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) { + $receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type]; } } DBA::close($contacts); // The queries are split because of performance issues - $condition = DBA::mergeConditions($basecondition, ["`alias` IN (?, ?)", Strings::normaliseLink($actor), $actor]); + $condition = DBA::mergeConditions($basecondition, ["`alias` IN (?, ?) AND `uid` != ?", Strings::normaliseLink($actor), $actor, 0]); $contacts = DBA::select('contact', ['uid', 'rel'], $condition); while ($contact = DBA::fetch($contacts)) { - if (empty($receivers['uid:' . $contact['uid']]) && self::isValidReceiverForActor($contact, $actor, $tags)) { - $receivers['uid:' . $contact['uid']] = ['uid' => $contact['uid'], 'type' => self::TARGET_FOLLOWER]; + if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) { + $receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type]; } } DBA::close($contacts); @@ -751,13 +796,8 @@ class Receiver * @return bool with receivers (user id) * @throws \Exception */ - private static function isValidReceiverForActor($contact, $actor, $tags) + private static function isValidReceiverForActor($contact, $tags) { - // Public contacts are no valid receiver - if ($contact['uid'] == 0) { - return false; - } - // Are we following the contact? Then this is a valid receiver if (in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND])) { return true; @@ -775,7 +815,7 @@ class Receiver continue; } - if ($tag['href'] == $owner['url']) { + if (Strings::compareLink($tag['href'], $owner['url'])) { return true; } } @@ -895,7 +935,7 @@ class Receiver } else { Logger::log('Empty content for ' . $object_id . ', check if content is available locally.', Logger::DEBUG); - $item = Item::selectFirst([], ['uri' => $object_id]); + $item = Post::selectFirst(Item::DELIVER_FIELDLIST, ['uri' => $object_id]); if (!DBA::isResult($item)) { Logger::log('Object with url ' . $object_id . ' was not found locally.', Logger::DEBUG); return false; @@ -947,6 +987,28 @@ class Receiver return false; } + /** + * Converts the language element (Used by Peertube) + * + * @param array $languages + * @return array Languages + */ + public static function processLanguages(array $languages) + { + if (empty($languages)) { + return []; + } + + $language_list = []; + + foreach ($languages as $language) { + if (!empty($language['_:identifier']) && !empty($language['as:name'])) { + $language_list[$language['_:identifier']] = $language['as:name']; + } + } + return $language_list; + } + /** * Convert tags from JSON-LD format into a simplified format * @@ -954,7 +1016,7 @@ class Receiver * * @return array with tags in a simplified format */ - private static function processTags(array $tags) + public static function processTags(array $tags) { $taglist = []; @@ -1206,24 +1268,28 @@ class Receiver $filetype = strtolower(substr($mediatype, 0, strpos($mediatype, '/'))); if ($filetype == 'audio') { - $attachments[$filetype] = ['type' => $mediatype, 'url' => $href]; + $attachments[$filetype] = ['type' => $mediatype, 'url' => $href, 'height' => null, 'size' => null]; } elseif ($filetype == 'video') { $height = (int)JsonLD::fetchElement($url, 'as:height', '@value'); + $size = (int)JsonLD::fetchElement($url, 'pt:size', '@value'); + $attachments[$filetype] = ['type' => $mediatype, 'url' => $href, 'height' => $height, 'size' => $size]; + } elseif (in_array($mediatype, ['application/x-bittorrent', 'application/x-bittorrent;x-scheme-handler/magnet'])) { + $height = (int)JsonLD::fetchElement($url, 'as:height', '@value'); - // We save bandwidth by using a moderate height - // Peertube normally uses these heights: 240, 360, 480, 720, 1080 - if (!empty($attachments[$filetype]['height']) && - (($height > 480) || $height < $attachments[$filetype]['height'])) { + // For Torrent links we always store the highest resolution + if (!empty($attachments[$mediatype]['height']) && ($height < $attachments[$mediatype]['height'])) { continue; } - $attachments[$filetype] = ['type' => $mediatype, 'url' => $href, 'height' => $height]; + $attachments[$mediatype] = ['type' => $mediatype, 'url' => $href, 'height' => $height, 'size' => null]; } } foreach ($attachments as $type => $attachment) { $object_data['attachments'][] = ['type' => $type, 'mediaType' => $attachment['type'], + 'height' => $attachment['height'], + 'size' => $attachment['size'], 'name' => '', 'url' => $attachment['url']]; } @@ -1298,9 +1364,11 @@ class Receiver $object_data['name'] = JsonLD::fetchElement($object, 'as:name', '@value'); $object_data['summary'] = JsonLD::fetchElement($object, 'as:summary', '@value'); $object_data['content'] = JsonLD::fetchElement($object, 'as:content', '@value'); + $object_data['mediatype'] = JsonLD::fetchElement($object, 'as:mediaType', '@value'); $object_data = self::getSource($object, $object_data); $object_data['start-time'] = JsonLD::fetchElement($object, 'as:startTime', '@value'); $object_data['end-time'] = JsonLD::fetchElement($object, 'as:endTime', '@value'); + $object_data['adjust'] = JsonLD::fetchElement($object, 'dfrn:adjust', '@value'); $object_data['location'] = $location; $object_data['latitude'] = JsonLD::fetchElement($object, 'as:location', 'as:latitude', '@type', 'as:Place'); $object_data['latitude'] = JsonLD::fetchElement($object_data, 'latitude', '@value'); @@ -1308,7 +1376,8 @@ class Receiver $object_data['longitude'] = JsonLD::fetchElement($object_data, 'longitude', '@value'); $object_data['attachments'] = self::processAttachments(JsonLD::fetchElementArray($object, 'as:attachment') ?? []); $object_data['tags'] = self::processTags(JsonLD::fetchElementArray($object, 'as:tag') ?? []); - $object_data['emojis'] = self::processEmojis(JsonLD::fetchElementArray($object, 'as:tag', 'toot:Emoji') ?? []); + $object_data['emojis'] = self::processEmojis(JsonLD::fetchElementArray($object, 'as:tag', null, '@type', 'toot:Emoji') ?? []); + $object_data['languages'] = self::processLanguages(JsonLD::fetchElementArray($object, 'sc:inLanguage') ?? []); $object_data['generator'] = JsonLD::fetchElement($object, 'as:generator', 'as:name', '@type', 'as:Application'); $object_data['generator'] = JsonLD::fetchElement($object_data, 'generator', '@value'); $object_data['alternate-url'] = JsonLD::fetchElement($object, 'as:url', '@id'); @@ -1337,7 +1406,8 @@ class Receiver $object_data['reception_type'] = $reception_types; $object_data['unlisted'] = in_array(-1, $object_data['receiver']); - unset($object_data['receiver']['uid:-1']); + unset($object_data['receiver'][-1]); + unset($object_data['reception_type'][-1]); // Common object data: