X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FProtocol%2FActivityPub%2FProcessor.php;h=04d05def8e93444cb0615cb549c9805f65e45f51;hb=ddb2d6f5b3f5d9eb0b716ddbd4309c0b527fdb8c;hp=171cf0ea4059981c2c74f242ffa59c547dd0adfe;hpb=e9280635298ef675eea4d222d8f5740de645dc3b;p=friendica.git diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 171cf0ea40..04d05def8e 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -21,12 +21,12 @@ namespace Friendica\Protocol\ActivityPub; -use Friendica\Content\PageInfo; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; use Friendica\Content\Text\Markdown; use Friendica\Core\Logger; use Friendica\Core\Protocol; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\APContact; @@ -53,18 +53,14 @@ use Friendica\Util\Strings; class Processor { /** - * Converts mentions from Pleroma into the Friendica format + * Extracts the tag character (#, @, !) from mention links * * @param string $body - * - * @return string converted body + * @return string */ - private static function convertMentions($body) + protected static function normalizeMentionLinks(string $body): string { - $URLSearchString = "^\[\]"; - $body = preg_replace("/\[url\=([$URLSearchString]*)\]([#@!])(.*?)\[\/url\]/ism", '$2[url=$1]$3[/url]', $body); - - return $body; + return preg_replace('%\[url=([^\[\]]*)]([#@!])(.*?)\[/url]%ism', '$2[url=$1]$3[/url]', $body); } /** @@ -95,17 +91,21 @@ class Processor * * @return string with replaced emojis */ - private static function replaceEmojis($body, array $emojis) + private static function replaceEmojis(int $uri_id, $body, array $emojis) { $body = strtr($body, array_combine( array_column($emojis, 'name'), array_map(function ($emoji) { - return '[class=emoji mastodon][img=' . $emoji['href'] . ']' . $emoji['name'] . '[/img][/class]'; + return '[emoji=' . $emoji['href'] . ']' . $emoji['name'] . '[/emoji]'; }, $emojis) ) ); + // We store the emoji here to be able to avoid storing it in the media + foreach ($emojis as $emoji) { + Post\Link::getByLink($uri_id, $emoji['href']); + } return $body; } @@ -123,24 +123,11 @@ class Processor } $data = ['uri-id' => $uriid]; - - $filetype = strtolower(substr($attachment['mediaType'], 0, strpos($attachment['mediaType'], '/'))); - if ($filetype == 'image') { - $data['type'] = Post\Media::IMAGE; - } elseif ($filetype == 'video') { - $data['type'] = Post\Media::VIDEO; - } elseif ($filetype == 'audio') { - $data['type'] = Post\Media::AUDIO; - } elseif (in_array($attachment['mediaType'], ['application/x-bittorrent', 'application/x-bittorrent;x-scheme-handler/magnet'])) { - $data['type'] = Post\Media::TORRENT; - } else { - Logger::info('Unknown type', ['attachment' => $attachment]); - return; - } - + $data['type'] = Post\Media::UNKNOWN; $data['url'] = $attachment['url']; $data['mimetype'] = $attachment['mediaType']; $data['height'] = $attachment['height'] ?? null; + $data['width'] = $attachment['width'] ?? null; $data['size'] = $attachment['size'] ?? null; $data['preview'] = $attachment['image'] ?? null; $data['description'] = $attachment['name'] ?? null; @@ -149,97 +136,20 @@ class Processor } /** - * Add attachment data to the item array + * Stire attachment data * * @param array $activity * @param array $item - * - * @return array array */ - private static function constructAttachList($activity, $item) + private static function storeAttachments($activity, $item) { if (empty($activity['attachments'])) { - return $item; + return; } - $leading = ''; - $trailing = ''; - foreach ($activity['attachments'] as $attach) { - switch ($attach['type']) { - case 'link': - $data = [ - 'url' => $attach['url'], - 'type' => $attach['type'], - 'title' => $attach['title'] ?? '', - 'text' => $attach['desc'] ?? '', - 'image' => $attach['image'] ?? '', - 'images' => [], - 'keywords' => [], - ]; - $item['body'] = PageInfo::appendDataToBody($item['body'], $data); - break; - default: - self::storeAttachmentAsMedia($item['uri-id'], $attach); - - $filetype = strtolower(substr($attach['mediaType'], 0, strpos($attach['mediaType'], '/'))); - if ($filetype == 'image') { - if (!empty($activity['source'])) { - foreach ([0, 1, 2] as $size) { - if (preg_match('#/photo/.*-' . $size . '\.#ism', $attach['url']) && - strpos(preg_replace('#(/photo/.*)-[012]\.#ism', '$1-' . $size . '.', $activity['source']), $attach['url'])) { - continue 3; - } - } - if (strpos($activity['source'], $attach['url'])) { - continue 2; - } - } - - // image is the preview/thumbnail URL - if (!empty($attach['image'])) { - $trailing .= '[url=' . $attach['url'] . ']'; - $attach['url'] = $attach['image']; - } - - if (empty($attach['name'])) { - $trailing .= '[img]' . $attach['url'] . '[/img]'; - } else { - $trailing .= '[img=' . $attach['url'] . ']' . $attach['name'] . '[/img]'; - } - - if (!empty($attach['image'])) { - $trailing .= '[/url]'; - } - } elseif ($filetype == 'audio') { - if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) { - continue 2; - } - - $leading .= '[audio]' . $attach['url'] . "[/audio]\n"; - } elseif ($filetype == 'video') { - if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) { - continue 2; - } - - $leading .= '[video]' . $attach['url'] . "[/video]\n"; - } - } - } - - if (!empty($leading) && !empty(trim($item['body']))) { - $item['body'] = $leading . "[hr]\n" . $item['body']; - } elseif (!empty($leading)) { - $item['body'] = $leading; - } - - if (!empty($trailing) && !empty(trim($item['body']))) { - $item['body'] = $item['body'] . "\n[hr]" . $trailing; - } elseif (!empty($trailing)) { - $item['body'] = $trailing; + self::storeAttachmentAsMedia($item['uri-id'], $attach); } - - return $item; } /** @@ -250,7 +160,7 @@ class Processor */ public static function updateItem($activity) { - $item = Post::selectFirst(['uri', 'uri-id', 'thr-parent', 'gravity'], ['uri' => $activity['id']]); + $item = Post::selectFirst(['uri', 'uri-id', 'thr-parent', 'gravity', 'post-type'], ['uri' => $activity['id']]); if (!DBA::isResult($item)) { Logger::warning('No existing item, item will be created', ['uri' => $activity['id']]); $item = self::createItem($activity); @@ -263,7 +173,7 @@ class Processor $item = self::processContent($activity, $item); - $item = self::constructAttachList($activity, $item); + self::storeAttachments($activity, $item); if (empty($item)) { return; @@ -336,6 +246,24 @@ class Processor $item['direction'] = Conversation::RELAY; } + if ($activity['object_type'] == 'as:Article') { + $item['post-type'] = Item::PT_ARTICLE; + } elseif ($activity['object_type'] == 'as:Audio') { + $item['post-type'] = Item::PT_AUDIO; + } elseif ($activity['object_type'] == 'as:Document') { + $item['post-type'] = Item::PT_DOCUMENT; + } elseif ($activity['object_type'] == 'as:Event') { + $item['post-type'] = Item::PT_EVENT; + } elseif ($activity['object_type'] == 'as:Image') { + $item['post-type'] = Item::PT_IMAGE; + } elseif ($activity['object_type'] == 'as:Page') { + $item['post-type'] = Item::PT_PAGE; + } elseif ($activity['object_type'] == 'as:Video') { + $item['post-type'] = Item::PT_VIDEO; + } else { + $item['post-type'] = Item::PT_NOTE; + } + $item['isForum'] = false; if (!empty($activity['thread-completion'])) { @@ -360,8 +288,12 @@ class Processor $item['uri'] = $activity['id']; - $item['created'] = DateTimeFormat::utc($activity['published']); - $item['edited'] = DateTimeFormat::utc($activity['updated']); + if (empty($activity['published']) || empty($activity['updated'])) { + DI::logger()->notice('published or updated keys are empty for activity', ['activity' => $activity, 'callstack' => System::callstack(10)]); + } + + $item['created'] = DateTimeFormat::utc($activity['published'] ?? 'now'); + $item['edited'] = DateTimeFormat::utc($activity['updated'] ?? 'now'); $guid = $activity['sc:identifier'] ?: self::getGUIDByURL($item['uri']); $item['guid'] = $activity['diaspora:guid'] ?: $guid; @@ -379,7 +311,7 @@ class Processor $item['plink'] = $activity['alternate-url'] ?? $item['uri']; - $item = self::constructAttachList($activity, $item); + self::storeAttachments($activity, $item); // We received the post via AP, so we set the protocol of the server to AP $contact = Contact::getById($item['author-id'], ['gsid']); @@ -456,6 +388,7 @@ class Processor $item['verb'] = $verb; $item['thr-parent'] = $activity['object_id']; $item['gravity'] = GRAVITY_ACTIVITY; + unset($item['post-type']); $item['object-type'] = Activity\ObjectType::NOTE; $item['diaspora_signed_text'] = $activity['diaspora:like'] ?? ''; @@ -468,6 +401,8 @@ class Processor * * @param array $activity Activity array * @param array $item + * + * @return int event id * @throws \Exception */ public static function createEvent($activity, $item) @@ -478,7 +413,6 @@ class Processor $event['finish'] = $activity['end-time']; $event['nofinish'] = empty($event['finish']); $event['location'] = $activity['location']; - $event['adjust'] = true; $event['cid'] = $item['contact-id']; $event['uid'] = $item['uid']; $event['uri'] = $item['uri']; @@ -491,14 +425,16 @@ class Processor $event['direction'] = $item['direction']; $event['source'] = $item['source']; - $condition = ['uri' => $item['uri'], 'uid' => $item['uid']]; - $ev = DBA::selectFirst('event', ['id'], $condition); + $ev = DBA::selectFirst('event', ['id'], ['uri' => $item['uri'], 'uid' => $item['uid']]); if (DBA::isResult($ev)) { $event['id'] = $ev['id']; } $event_id = Event::store($event); + Logger::info('Event was stored', ['id' => $event_id]); + + return $event_id; } /** @@ -528,14 +464,15 @@ class Processor } if (!empty($activity['emojis'])) { - $content = self::replaceEmojis($content, $activity['emojis']); + $content = self::replaceEmojis($item['uri-id'], $content, $activity['emojis']); } - $content = self::convertMentions($content); + $content = self::addMentionLinks($content, $activity['tags']); if (!empty($activity['source'])) { $item['body'] = $activity['source']; $item['raw-body'] = $content; + $item['body'] = Item::improveSharedDataInBody($item); } else { if (empty($activity['directmessage']) && ($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) { $item_private = !in_array(0, $activity['item_receiver']); @@ -629,34 +566,34 @@ class Processor $type = $activity['reception_type'][$receiver] ?? Receiver::TARGET_UNKNOWN; switch($type) { case Receiver::TARGET_TO: - $item['post-type'] = Item::PT_TO; + $item['post-reason'] = Item::PR_TO; break; case Receiver::TARGET_CC: - $item['post-type'] = Item::PT_CC; + $item['post-reason'] = Item::PR_CC; break; case Receiver::TARGET_BTO: - $item['post-type'] = Item::PT_BTO; + $item['post-reason'] = Item::PR_BTO; break; case Receiver::TARGET_BCC: - $item['post-type'] = Item::PT_BCC; + $item['post-reason'] = Item::PR_BCC; break; case Receiver::TARGET_FOLLOWER: - $item['post-type'] = Item::PT_FOLLOWER; + $item['post-reason'] = Item::PR_FOLLOWER; break; case Receiver::TARGET_ANSWER: - $item['post-type'] = Item::PT_COMMENT; + $item['post-reason'] = Item::PR_COMMENT; break; case Receiver::TARGET_GLOBAL: - $item['post-type'] = Item::PT_GLOBAL; + $item['post-reason'] = Item::PR_GLOBAL; break; default: - $item['post-type'] = Item::PT_ARTICLE; + $item['post-reason'] = Item::PR_NONE; } if (!empty($activity['from-relay'])) { - $item['post-type'] = Item::PT_RELAY; + $item['post-reason'] = Item::PR_RELAY; } elseif (!empty($activity['thread-completion'])) { - $item['post-type'] = Item::PT_FETCHED; + $item['post-reason'] = Item::PR_FETCHED; } if ($item['isForum'] ?? false) { @@ -674,6 +611,12 @@ class Processor continue; } + if (!($item['isForum'] ?? false) && ($receiver != 0) && ($item['gravity'] == GRAVITY_PARENT) && + ($item['post-reason'] == Item::PR_BCC) && !Contact::isSharingByURL($activity['author'], $receiver)) { + Logger::info('Top level post via BCC from a non sharer, ignoring', ['uid' => $receiver, 'contact' => $item['contact-id']]); + continue; + } + if (DI::pConfig()->get($receiver, 'system', 'accept_only_sharer', false) && ($receiver != 0) && ($item['gravity'] == GRAVITY_PARENT)) { $skip = !Contact::isSharingByURL($activity['author'], $receiver); @@ -690,7 +633,9 @@ class Processor } if (($item['gravity'] != GRAVITY_ACTIVITY) && ($activity['object_type'] == 'as:Event')) { - self::createEvent($activity, $item); + $event_id = self::createEvent($activity, $item); + + $item = Event::getItemArrayForImportedId($event_id, $item); } $item_id = Item::insert($item); @@ -809,7 +754,7 @@ class Processor $title = $matches[3]; } - $title = trim(HTML::toPlaintext(BBCode::convert($title, false, BBCode::API, true), 0)); + $title = trim(BBCode::toPlaintext($title)); if (strlen($title) > 20) { $title = substr($title, 0, 20) . '...'; @@ -842,12 +787,12 @@ class Processor $object = ActivityPub::fetchContent($url, $uid); if (empty($object)) { - Logger::log('Activity ' . $url . ' was not fetchable, aborting.'); + Logger::notice('Activity was not fetchable, aborting.', ['url' => $url]); return ''; } if (empty($object['id'])) { - Logger::log('Activity ' . $url . ' has got not id, aborting. ' . json_encode($object)); + Logger::notice('Activity has got not id, aborting. ', ['url' => $url, 'object' => $object]); return ''; } @@ -975,7 +920,7 @@ class Processor $cid = Contact::getIdForURL($activity['actor'], $uid); if (!empty($cid)) { self::switchContact($cid); - DBA::update('contact', ['hub-verify' => $activity['id'], 'protocol' => Protocol::ACTIVITYPUB], ['id' => $cid]); + Contact::update(['hub-verify' => $activity['id'], 'protocol' => Protocol::ACTIVITYPUB], ['id' => $cid]); } $item = ['author-id' => Contact::getIdForURL($activity['actor']), @@ -995,10 +940,10 @@ class Processor } if (empty($contact)) { - DBA::update('contact', ['hub-verify' => $activity['id'], 'protocol' => Protocol::ACTIVITYPUB], ['id' => $cid]); + Contact::update(['hub-verify' => $activity['id'], 'protocol' => Protocol::ACTIVITYPUB], ['id' => $cid]); } - Logger::log('Follow user ' . $uid . ' from contact ' . $cid . ' with id ' . $activity['id']); + Logger::notice('Follow user ' . $uid . ' from contact ' . $cid . ' with id ' . $activity['id']); } /** @@ -1074,7 +1019,7 @@ class Processor } $condition = ['id' => $cid]; - DBA::update('contact', $fields, $condition); + Contact::update($fields, $condition); Logger::info('Accept contact request', ['contact' => $cid, 'user' => $uid]); } @@ -1100,9 +1045,12 @@ class Processor self::switchContact($cid); - if (DBA::exists('contact', ['id' => $cid, 'rel' => Contact::SHARING])) { + $contact = Contact::getById($cid, ['rel']); + if ($contact['rel'] == Contact::SHARING) { Contact::remove($cid); Logger::info('Rejected contact request - contact removed', ['contact' => $cid, 'user' => $uid]); + } elseif ($contact['rel'] == Contact::FRIEND) { + Contact::update(['rel' => Contact::FOLLOWER], ['id' => $cid]); } else { Logger::info('Rejected contact request', ['contact' => $cid, 'user' => $uid]); } @@ -1165,7 +1113,7 @@ class Processor return; } - Contact::removeFollower($owner, $contact); + Contact::removeFollower($contact); Logger::info('Undo following request', ['contact' => $cid, 'user' => $uid]); } @@ -1257,4 +1205,38 @@ class Processor return implode('', $kept_mentions); } + + /** + * Adds links to string mentions + * + * @param string $body + * @param array $tags + * @return string + */ + protected static function addMentionLinks(string $body, array $tags): string + { + // This prevents links to be added again to Pleroma-style mention links + $body = self::normalizeMentionLinks($body); + + $body = BBCode::performWithEscapedTags($body, ['url'], function ($body) use ($tags) { + foreach ($tags as $tag) { + if (empty($tag['name']) || empty($tag['type']) || empty($tag['href']) || !in_array($tag['type'], ['Mention', 'Hashtag'])) { + continue; + } + + $hash = substr($tag['name'], 0, 1); + $name = substr($tag['name'], 1); + if (!in_array($hash, Tag::TAG_CHARACTER)) { + $hash = ''; + $name = $tag['name']; + } + + $body = str_replace($tag['name'], $hash . '[url=' . $tag['href'] . ']' . $name . '[/url]', $body); + } + + return $body; + }); + + return $body; + } }