diff --git a/bridges/MastodonBridge.php b/bridges/MastodonBridge.php index ef0555912fc..b02e5601b62 100644 --- a/bridges/MastodonBridge.php +++ b/bridges/MastodonBridge.php @@ -12,8 +12,8 @@ class MastodonBridge extends BridgeAbstract const NAME = 'ActivityPub Bridge'; const CACHE_TIMEOUT = 900; // 15mn const DESCRIPTION = 'Returns recent statuses. Supports Mastodon, Pleroma and Misskey, among others. Access to - instances that have Authorized Fetch enabled requires - configuration.'; + instances that have Authorized Fetch enabled requires + configuration.'; const URI = 'https://mastodon.social'; // Some Mastodon instances use Secure Mode which requires all requests to be signed. @@ -38,21 +38,22 @@ class MastodonBridge extends BridgeAbstract 'norep' => [ 'name' => 'Without replies', 'type' => 'checkbox', - 'title' => 'Only return statuses that are not replies, as determined by relations (not mentions).' + 'title' => 'Hide replies, as determined by relations (not mentions).' ], 'noboost' => [ 'name' => 'Without boosts', - 'required' => false, 'type' => 'checkbox', - 'title' => 'Hide boosts. Note that RSS-Bridge will fetch the original status from other federated instances.' + 'title' => 'Hide boosts. This will reduce loading time as RSS-Bridge fetches the boosted status from other federated instances.' ], 'signaturetype' => [ 'type' => 'list', 'name' => 'Signature Type', - 'title' => 'How to sign requests when fetching from Authorized Fetch enabled instances', + 'title' => 'How to sign requests when fetching from instances. + Defaults to "nosig" for RSS-Bridge instances that did not set up signatures.', 'values' => [ 'Without Query (Mastodon)' => 'noquery', 'With Query (GoToSocial)' => 'query', + 'Don\'t sign' => 'nosig', ], 'defaultValue' => 'noquery' ], @@ -60,12 +61,15 @@ class MastodonBridge extends BridgeAbstract public function collectData() { - $url = $this->getURI() . '/outbox?page=true'; - $content = $this->fetchAP($url); - if ($content['id'] !== $url) { - throw new \Exception('Unexpected response from server.'); + $user = $this->fetchAP($this->getURI()); + $content = $this->fetchAP($user['outbox']); + if (is_array($content['first'])) { // mobilizon + $content = $content['first']; + } else { + $content = $this->fetchAP($content['first']); } - foreach ($content['orderedItems'] as $status) { + $items = $content['orderedItems'] ?? $content['items']; + foreach ($items as $status) { $item = $this->parseItem($status); if ($item) { $this->items[] = $item; @@ -104,15 +108,26 @@ protected function parseItem($content) $item['uri'] = $content['object']; } break; + case 'Note': // frendica posts + if ($this->getInput('norep') && isset($content['inReplyTo'])) { + return null; + } + $item['title'] = ''; + $item['author'] = $this->getInput('canusername'); + $item = $this->parseObject($content, $item); + break; case 'Create': // posts if ($this->getInput('norep') && isset($content['object']['inReplyTo'])) { return null; } - $item['author'] = $this->getInput('canusername'); $item['title'] = ''; + $item['author'] = $this->getInput('canusername'); $item = $this->parseObject($content['object'], $item); + break; + default: + return null; } - $item['timestamp'] = $content['published']; + $item['timestamp'] = $content['published'] ?? $item['timestamp']; $item['uid'] = $content['id']; return $item; } @@ -127,13 +142,20 @@ protected function parseObject($object, $item) $item['content'] = $object['content']; $strippedContent = strip_tags(str_replace('
', ' ', $object['content'])); - if (mb_strlen($strippedContent) > 75) { + if (isset($object['name'])) { + $item['title'] = $object['name']; + } else if (mb_strlen($strippedContent) > 75) { $contentSubstring = mb_substr($strippedContent, 0, mb_strpos(wordwrap($strippedContent, 75), "\n")); $item['title'] .= $contentSubstring . '...'; } else { $item['title'] .= $strippedContent; } $item['uri'] = $object['id']; + $item['timestamp'] = $object['published']; + + if (!isset($object['attachment'])) { + return $item; + } if (isset($object['attachment']['url'])) { // Normalize attachment (turn single attachment into array) @@ -214,6 +236,7 @@ protected function fetchAP($url) // Exclude query string when parsing URL 'noquery' => '/https?:\/\/([a-z0-9-\.]{0,})(\/[^#?]+)/', + 'nosig' => '/https?:\/\/([a-z0-9-\.]{0,})(\/[^#?]+)/', ]; preg_match($regex[$this->getInput('signaturetype')], $url, $matches); @@ -224,7 +247,7 @@ protected function fetchAP($url) ]; $privateKey = $this->getOption('private_key'); $keyId = $this->getOption('key_id'); - if ($privateKey && $keyId) { + if ($privateKey && $keyId && $this->getInput('signaturetype') !== 'nosig') { $pkey = openssl_pkey_get_private('file://' . $privateKey); $toSign = '(request-target): get ' . $matches[2] . "\nhost: " . $matches[1] . "\ndate: " . $date; $result = openssl_sign($toSign, $signature, $pkey, 'RSA-SHA256');