X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FProtocol%2FActivityPub%2FTransmitter.php;h=7f5f51c38aedcde640271d4f8bfba59250606102;hb=32f70abf9a7913df1ddb28b9745eb37941d09d80;hp=2abc883078097147619676359c7b2063e4290078;hpb=78158624984e7d86d4a28e50ff891a1cfed1a920;p=friendica.git diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 2abc883078..7f5f51c38a 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -37,13 +37,14 @@ use Friendica\Model\Item; use Friendica\Model\ItemURI; use Friendica\Model\Profile; use Friendica\Model\Photo; +use Friendica\Model\Post; use Friendica\Model\Tag; use Friendica\Model\User; use Friendica\Protocol\Activity; use Friendica\Protocol\ActivityPub; +use Friendica\Protocol\Relay; use Friendica\Util\DateTimeFormat; use Friendica\Util\HTTPSignature; -use Friendica\Util\Images; use Friendica\Util\JsonLD; use Friendica\Util\LDSignature; use Friendica\Util\Map; @@ -61,6 +62,92 @@ require_once 'mod/share.php'; */ class Transmitter { + /** + * Add relay servers to the list of inboxes + * + * @param array $inboxes + * @return array inboxes with added relay servers + */ + public static function addRelayServerInboxes(array $inboxes = []) + { + $contacts = DBA::select('apcontact', ['inbox'], + ["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))", + 'Application', 0, Contact::FOLLOWER, Contact::FRIEND]); + while ($contact = DBA::fetch($contacts)) { + $inboxes[] = $contact['inbox']; + } + DBA::close($contacts); + + return $inboxes; + } + + /** + * Add relay servers to the list of inboxes + * + * @param array $inboxes + * @return array inboxes with added relay servers + */ + public static function addRelayServerInboxesForItem(int $item_id, array $inboxes = []) + { + $relays = Relay::getList($item_id, [], [Protocol::ACTIVITYPUB]); + if (empty($relays)) { + return $inboxes; + } + + foreach ($relays as $relay) { + if (!in_array($relay['batch'], $inboxes)) { + $inboxes[] = $relay['batch']; + } + } + return $inboxes; + } + + /** + * Subscribe to a relay + * + * @param string $url Subscribe actor url + * @return bool success + */ + public static function sendRelayFollow(string $url) + { + $contact = Contact::getByURL($url); + if (empty($contact)) { + return false; + } + + $activity_id = ActivityPub\Transmitter::activityIDFromContact($contact['id']); + $success = ActivityPub\Transmitter::sendActivity('Follow', $url, 0, $activity_id); + if ($success) { + $rel = $contact['rel'] == Contact::SHARING ? Contact::FRIEND : Contact::FOLLOWER; + DBA::update('contact', ['rel' => $rel], ['id' => $contact['id']]); + } + + return $success; + } + + /** + * Unsubscribe from a relay + * + * @param string $url Subscribe actor url + * @param bool $force Set the relay status as non follower even if unsubscribe hadn't worked + * @return bool success + */ + public static function sendRelayUndoFollow(string $url, bool $force = false) + { + $contact = Contact::getByURL($url); + if (empty($contact)) { + return false; + } + + $success = self::sendContactUndo($url, $contact['id'], 0); + if ($success || $force) { + $rel = $contact['rel'] == Contact::FRIEND ? Contact::SHARING : Contact::NOTHING; + DBA::update('contact', ['rel' => $rel], ['id' => $contact['id']]); + } + + return $success; + } + /** * Collects a list of contacts of the given owner * @@ -81,7 +168,8 @@ class Transmitter 'deleted' => false, 'hidden' => false, 'archive' => false, - 'pending' => false + 'pending' => false, + 'blocked' => false, ]; $condition = DBA::buildCondition($parameters); @@ -141,20 +229,37 @@ class Transmitter /** * Public posts for the given owner * - * @param array $owner Owner array - * @param integer $page Page numbe + * @param array $owner Owner array + * @param integer $page Page number + * @param string $requester URL of requesting account * * @return array of posts * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function getOutbox($owner, $page = null) + public static function getOutbox($owner, $page = null, $requester = '') { $public_contact = Contact::getIdForURL($owner['url']); + $condition = ['uid' => 0, 'contact-id' => $public_contact, + 'private' => [Item::PUBLIC, Item::UNLISTED]]; + + if (!empty($requester)) { + $requester_id = Contact::getIdForURL($requester, $owner['uid']); + if (!empty($requester_id)) { + $permissionSets = DI::permissionSet()->selectByContactId($requester_id, $owner['uid']); + if (!empty($permissionSets)) { + $condition = ['uid' => $owner['uid'], 'origin' => true, + 'psid' => array_merge($permissionSets->column('id'), + [DI::permissionSet()->getIdFromACL($owner['uid'], '', '', '', '')])]; + } + } + } + + $condition = array_merge($condition, + ['author-id' => $public_contact, + 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], + 'deleted' => false, 'visible' => true, 'moderated' => false]); - $condition = ['uid' => 0, 'contact-id' => $public_contact, 'author-id' => $public_contact, - 'private' => [Item::PUBLIC, Item::UNLISTED], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], - 'deleted' => false, 'visible' => true, 'moderated' => false]; $count = DBA::count('item', $condition); $data = ['@context' => ActivityPub::CONTEXT]; @@ -327,7 +432,12 @@ class Transmitter $activity = json_decode($conversation['source'], true); $actor = JsonLD::fetchElement($activity, 'actor', 'id'); - $profile = APContact::getByURL($actor); + if (!empty($actor)) { + $permissions['to'][] = $actor; + $profile = APContact::getByURL($actor); + } else { + $profile = []; + } $item_profile = APContact::getByURL($item['author-link']); $exclude[] = $item['author-link']; @@ -336,8 +446,6 @@ class Transmitter $exclude[] = $item['owner-link']; } - $permissions['to'][] = $actor; - foreach (['to', 'cc', 'bto', 'bcc'] as $element) { if (empty($activity[$element])) { continue; @@ -380,14 +488,12 @@ class Transmitter } $always_bcc = false; - $isforum = false; // Check if we should always deliver our stuff via BCC if (!empty($item['uid'])) { $profile = User::getOwnerDataById($item['uid']); if (!empty($profile)) { $always_bcc = $profile['hide-friends']; - $isforum = $profile['account-type'] == User::ACCOUNT_TYPE_COMMUNITY; } } @@ -395,7 +501,7 @@ class Transmitter $always_bcc = true; } - if ((self::isAnnounce($item) && !$isforum) || DI::config()->get('debug', 'total_ap_delivery')) { + if (self::isAnnounce($item) || DI::config()->get('debug', 'total_ap_delivery')) { // Will be activated in a later step $networks = Protocol::FEDERATED; } else { @@ -449,10 +555,6 @@ class Transmitter continue; } - if ($isforum && DBA::isResult($contact) && ($contact['dfrn'] == Protocol::DFRN)) { - continue; - } - if (!empty($profile = APContact::getByURL($contact['url'], false))) { $data['to'][] = $profile['url']; } @@ -465,10 +567,6 @@ class Transmitter continue; } - if ($isforum && DBA::isResult($contact) && ($contact['dfrn'] == Protocol::DFRN)) { - continue; - } - if (!empty($profile = APContact::getByURL($contact['url'], false))) { if ($contact['hidden'] || $always_bcc) { $data['bcc'][] = $profile['url']; @@ -572,7 +670,7 @@ class Transmitter * * @return boolean "true" if inbox is archived */ - private static function archivedInbox($url) + public static function archivedInbox($url) { return DBA::exists('inbox-status', ['url' => $url, 'archive' => true]); } @@ -608,7 +706,7 @@ class Transmitter $networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS]; } - $condition = ['uid' => $uid, 'archive' => false, 'pending' => false]; + $condition = ['uid' => $uid, 'archive' => false, 'pending' => false, 'blocked' => false]; if (!empty($uid)) { $condition['rel'] = [Contact::FOLLOWER, Contact::FRIEND]; @@ -624,7 +722,7 @@ class Transmitter continue; } - if ($isforum && ($contact['dfrn'] == Protocol::DFRN)) { + if ($isforum && ($contact['network'] == Protocol::DFRN)) { continue; } @@ -757,7 +855,6 @@ class Transmitter $mail['gravity'] = ($mail['reply'] ? GRAVITY_COMMENT: GRAVITY_PARENT); $mail['event-type'] = ''; - $mail['attach'] = ''; $mail['parent'] = 0; @@ -776,6 +873,9 @@ class Transmitter public static function createActivityFromMail($mail_id, $object_mode = false) { $mail = self::ItemArrayFromMail($mail_id); + if (empty($mail)) { + return []; + } $object = self::createNote($mail); if (!$object_mode) { @@ -920,11 +1020,19 @@ class Transmitter if (!empty($author['nurl'])) { $self = Contact::selectFirst(['uid'], ['nurl' => $author['nurl'], 'self' => true]); if (!empty($self['uid'])) { - $item = Item::selectFirst([], ['uri-id' => $item['uri-id'], 'uid' => $self['uid']]); + $forum_item = Item::selectFirst([], ['uri-id' => $item['uri-id'], 'uid' => $self['uid']]); + if (DBA::isResult($item)) { + $item = $forum_item; + } } } } + if (empty($item['uri-id'])) { + Logger::warning('Item without uri-id', ['item' => $item]); + return false; + } + if (empty($type)) { $condition = ['item-uri' => $item['uri'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB]; $conversation = DBA::selectFirst('conversation', ['source'], $condition); @@ -964,7 +1072,12 @@ class Transmitter $data = []; } - $data['id'] = $item['uri'] . '/' . $type; + if (($item['gravity'] == GRAVITY_ACTIVITY) && ($type != 'Undo')) { + $data['id'] = $item['uri']; + } else { + $data['id'] = $item['uri'] . '/' . $type; + } + $data['type'] = $type; if (($type != 'Announce') || ($item['gravity'] != GRAVITY_PARENT)) { @@ -1140,57 +1253,22 @@ class Transmitter $attachments[] = $attachment; } */ - $arr = explode('[/attach],', $item['attach']); - if (count($arr)) { - foreach ($arr as $r) { - $matches = false; - $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|', $r, $matches); - if ($cnt) { - $attributes = ['type' => 'Document', - 'mediaType' => $matches[3], - 'url' => $matches[1], - 'name' => null]; - - if (trim($matches[4]) != '') { - $attributes['name'] = trim($matches[4]); - } - - $attachments[] = $attributes; - } - } + foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT, Post\Media::UNKNOWN]) as $attachment) { + $attachments[] = ['type' => 'Document', + 'mediaType' => $attachment['mimetype'], + 'url' => $attachment['url'], + 'name' => $attachment['description']]; } if ($type != 'Note') { return $attachments; } - // Simplify image codes - $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $item['body']); - - // Grab all pictures without alternative descriptions and create attachments out of them - if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures)) { - foreach ($pictures[1] as $picture) { - $imgdata = Images::getInfoFromURLCached($picture); - if ($imgdata) { - $attachments[] = ['type' => 'Document', - 'mediaType' => $imgdata['mime'], - 'url' => $picture, - 'name' => null]; - } - } - } - - // Grab all pictures with alternative description and create attachments out of them - if (preg_match_all("/\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) { - foreach ($pictures as $picture) { - $imgdata = Images::getInfoFromURLCached($picture[1]); - if ($imgdata) { - $attachments[] = ['type' => 'Document', - 'mediaType' => $imgdata['mime'], - 'url' => $picture[1], - 'name' => $picture[2]]; - } - } + foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]) as $attachment) { + $attachments[] = ['type' => 'Document', + 'mediaType' => $attachment['mimetype'], + 'url' => $attachment['url'], + 'name' => $attachment['description']]; } return $attachments; @@ -1209,12 +1287,12 @@ class Transmitter return ''; } - $data = Contact::getByURL($match[1], false, ['url', 'nick']); + $data = Contact::getByURL($match[1], false, ['url', 'alias', 'nick']); if (empty($data['nick'])) { return $match[0]; } - return '@[url=' . $data['url'] . ']' . $data['nick'] . '[/url]'; + return '[url=' . ($data['alias'] ?: $data['url']) . ']@' . $data['nick'] . '[/url]'; } /** @@ -1374,16 +1452,16 @@ class Transmitter $body = $item['body']; - if (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) { - $body = self::prependMentions($body, $item['uri-id']); - } - if ($type == 'Note') { - $body = self::removePictures($body); + $body = $item['raw-body'] ?? self::removePictures($body); } elseif (($type == 'Article') && empty($data['summary'])) { $data['summary'] = BBCode::toPlaintext(Plaintext::shorten(self::removePictures($body), 1000)); } + if (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) { + $body = self::prependMentions($body, $item['uri-id'], $item['author-link']); + } + if ($type == 'Event') { $data = array_merge($data, self::createEvent($item)); } else { @@ -1895,18 +1973,19 @@ class Transmitter * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException * @throws \Exception + * @return bool success */ public static function sendContactUndo($target, $cid, $uid) { $profile = APContact::getByURL($target); if (empty($profile['inbox'])) { Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]); - return; + return false; } $object_id = self::activityIDFromContact($cid); if (empty($object_id)) { - return; + return false; } $id = DI::baseUrl() . '/activity/' . System::createGUID(); @@ -1925,10 +2004,10 @@ class Transmitter Logger::log('Sending undo to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG); $signed = LDSignature::sign($data, $owner); - HTTPSignature::transmit($signed, $profile['inbox'], $uid); + return HTTPSignature::transmit($signed, $profile['inbox'], $uid); } - private static function prependMentions($body, int $uriid) + private static function prependMentions($body, int $uriid, string $authorLink) { $mentions = []; @@ -1938,6 +2017,7 @@ class Transmitter && $profile['contact-type'] != Contact::TYPE_COMMUNITY && !strstr($body, $profile['addr']) && !strstr($body, $tag['url']) + && $tag['url'] !== $authorLink ) { $mentions[] = '@[url=' . $tag['url'] . ']' . $profile['nick'] . '[/url]'; }