Skip to content

Commit e788e14

Browse files
[MastodonBridge] Add support for various platforms (#3133)
* [MastodonBridge] Add support for various platforms * [MastodonBridge] satisfy the lint
1 parent a5779d3 commit e788e14

File tree

1 file changed

+38
-15
lines changed

1 file changed

+38
-15
lines changed

bridges/MastodonBridge.php

+38-15
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ class MastodonBridge extends BridgeAbstract
1212
const NAME = 'ActivityPub Bridge';
1313
const CACHE_TIMEOUT = 900; // 15mn
1414
const DESCRIPTION = 'Returns recent statuses. Supports Mastodon, Pleroma and Misskey, among others. Access to
15-
instances that have Authorized Fetch enabled requires
16-
<a href="https://rss-bridge.github.io/rss-bridge/Bridge_Specific/ActivityPub_(Mastodon).html">configuration</a>.';
15+
instances that have Authorized Fetch enabled requires
16+
<a href="https://rss-bridge.github.io/rss-bridge/Bridge_Specific/ActivityPub_(Mastodon).html">configuration</a>.';
1717
const URI = 'https://mastodon.social';
1818

1919
// Some Mastodon instances use Secure Mode which requires all requests to be signed.
@@ -38,34 +38,38 @@ class MastodonBridge extends BridgeAbstract
3838
'norep' => [
3939
'name' => 'Without replies',
4040
'type' => 'checkbox',
41-
'title' => 'Only return statuses that are not replies, as determined by relations (not mentions).'
41+
'title' => 'Hide replies, as determined by relations (not mentions).'
4242
],
4343
'noboost' => [
4444
'name' => 'Without boosts',
45-
'required' => false,
4645
'type' => 'checkbox',
47-
'title' => 'Hide boosts. Note that RSS-Bridge will fetch the original status from other federated instances.'
46+
'title' => 'Hide boosts. This will reduce loading time as RSS-Bridge fetches the boosted status from other federated instances.'
4847
],
4948
'signaturetype' => [
5049
'type' => 'list',
5150
'name' => 'Signature Type',
52-
'title' => 'How to sign requests when fetching from Authorized Fetch enabled instances',
51+
'title' => 'How to sign requests when fetching from instances.
52+
Defaults to "nosig" for RSS-Bridge instances that did not set up signatures.',
5353
'values' => [
5454
'Without Query (Mastodon)' => 'noquery',
5555
'With Query (GoToSocial)' => 'query',
56+
'Don\'t sign' => 'nosig',
5657
],
5758
'defaultValue' => 'noquery'
5859
],
5960
]];
6061

6162
public function collectData()
6263
{
63-
$url = $this->getURI() . '/outbox?page=true';
64-
$content = $this->fetchAP($url);
65-
if ($content['id'] !== $url) {
66-
throw new \Exception('Unexpected response from server.');
64+
$user = $this->fetchAP($this->getURI());
65+
$content = $this->fetchAP($user['outbox']);
66+
if (is_array($content['first'])) { // mobilizon
67+
$content = $content['first'];
68+
} else {
69+
$content = $this->fetchAP($content['first']);
6770
}
68-
foreach ($content['orderedItems'] as $status) {
71+
$items = $content['orderedItems'] ?? $content['items'];
72+
foreach ($items as $status) {
6973
$item = $this->parseItem($status);
7074
if ($item) {
7175
$this->items[] = $item;
@@ -104,15 +108,26 @@ protected function parseItem($content)
104108
$item['uri'] = $content['object'];
105109
}
106110
break;
111+
case 'Note': // frendica posts
112+
if ($this->getInput('norep') && isset($content['inReplyTo'])) {
113+
return null;
114+
}
115+
$item['title'] = '';
116+
$item['author'] = $this->getInput('canusername');
117+
$item = $this->parseObject($content, $item);
118+
break;
107119
case 'Create': // posts
108120
if ($this->getInput('norep') && isset($content['object']['inReplyTo'])) {
109121
return null;
110122
}
111-
$item['author'] = $this->getInput('canusername');
112123
$item['title'] = '';
124+
$item['author'] = $this->getInput('canusername');
113125
$item = $this->parseObject($content['object'], $item);
126+
break;
127+
default:
128+
return null;
114129
}
115-
$item['timestamp'] = $content['published'];
130+
$item['timestamp'] = $content['published'] ?? $item['timestamp'];
116131
$item['uid'] = $content['id'];
117132
return $item;
118133
}
@@ -127,13 +142,20 @@ protected function parseObject($object, $item)
127142
$item['content'] = $object['content'];
128143
$strippedContent = strip_tags(str_replace('<br>', ' ', $object['content']));
129144

130-
if (mb_strlen($strippedContent) > 75) {
145+
if (isset($object['name'])) {
146+
$item['title'] = $object['name'];
147+
} else if (mb_strlen($strippedContent) > 75) {
131148
$contentSubstring = mb_substr($strippedContent, 0, mb_strpos(wordwrap($strippedContent, 75), "\n"));
132149
$item['title'] .= $contentSubstring . '...';
133150
} else {
134151
$item['title'] .= $strippedContent;
135152
}
136153
$item['uri'] = $object['id'];
154+
$item['timestamp'] = $object['published'];
155+
156+
if (!isset($object['attachment'])) {
157+
return $item;
158+
}
137159

138160
if (isset($object['attachment']['url'])) {
139161
// Normalize attachment (turn single attachment into array)
@@ -214,6 +236,7 @@ protected function fetchAP($url)
214236

215237
// Exclude query string when parsing URL
216238
'noquery' => '/https?:\/\/([a-z0-9-\.]{0,})(\/[^#?]+)/',
239+
'nosig' => '/https?:\/\/([a-z0-9-\.]{0,})(\/[^#?]+)/',
217240
];
218241

219242
preg_match($regex[$this->getInput('signaturetype')], $url, $matches);
@@ -224,7 +247,7 @@ protected function fetchAP($url)
224247
];
225248
$privateKey = $this->getOption('private_key');
226249
$keyId = $this->getOption('key_id');
227-
if ($privateKey && $keyId) {
250+
if ($privateKey && $keyId && $this->getInput('signaturetype') !== 'nosig') {
228251
$pkey = openssl_pkey_get_private('file://' . $privateKey);
229252
$toSign = '(request-target): get ' . $matches[2] . "\nhost: " . $matches[1] . "\ndate: " . $date;
230253
$result = openssl_sign($toSign, $signature, $pkey, 'RSA-SHA256');

0 commit comments

Comments
 (0)