@@ -12,8 +12,8 @@ class MastodonBridge extends BridgeAbstract
12
12
const NAME = 'ActivityPub Bridge ' ;
13
13
const CACHE_TIMEOUT = 900 ; // 15mn
14
14
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>. ' ;
17
17
const URI = 'https://mastodon.social ' ;
18
18
19
19
// Some Mastodon instances use Secure Mode which requires all requests to be signed.
@@ -38,34 +38,38 @@ class MastodonBridge extends BridgeAbstract
38
38
'norep ' => [
39
39
'name ' => 'Without replies ' ,
40
40
'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). '
42
42
],
43
43
'noboost ' => [
44
44
'name ' => 'Without boosts ' ,
45
- 'required ' => false ,
46
45
'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. '
48
47
],
49
48
'signaturetype ' => [
50
49
'type ' => 'list ' ,
51
50
'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. ' ,
53
53
'values ' => [
54
54
'Without Query (Mastodon) ' => 'noquery ' ,
55
55
'With Query (GoToSocial) ' => 'query ' ,
56
+ 'Don \'t sign ' => 'nosig ' ,
56
57
],
57
58
'defaultValue ' => 'noquery '
58
59
],
59
60
]];
60
61
61
62
public function collectData ()
62
63
{
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 ' ]);
67
70
}
68
- foreach ($ content ['orderedItems ' ] as $ status ) {
71
+ $ items = $ content ['orderedItems ' ] ?? $ content ['items ' ];
72
+ foreach ($ items as $ status ) {
69
73
$ item = $ this ->parseItem ($ status );
70
74
if ($ item ) {
71
75
$ this ->items [] = $ item ;
@@ -104,15 +108,26 @@ protected function parseItem($content)
104
108
$ item ['uri ' ] = $ content ['object ' ];
105
109
}
106
110
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 ;
107
119
case 'Create ' : // posts
108
120
if ($ this ->getInput ('norep ' ) && isset ($ content ['object ' ]['inReplyTo ' ])) {
109
121
return null ;
110
122
}
111
- $ item ['author ' ] = $ this ->getInput ('canusername ' );
112
123
$ item ['title ' ] = '' ;
124
+ $ item ['author ' ] = $ this ->getInput ('canusername ' );
113
125
$ item = $ this ->parseObject ($ content ['object ' ], $ item );
126
+ break ;
127
+ default :
128
+ return null ;
114
129
}
115
- $ item ['timestamp ' ] = $ content ['published ' ];
130
+ $ item ['timestamp ' ] = $ content ['published ' ] ?? $ item [ ' timestamp ' ] ;
116
131
$ item ['uid ' ] = $ content ['id ' ];
117
132
return $ item ;
118
133
}
@@ -127,13 +142,20 @@ protected function parseObject($object, $item)
127
142
$ item ['content ' ] = $ object ['content ' ];
128
143
$ strippedContent = strip_tags (str_replace ('<br> ' , ' ' , $ object ['content ' ]));
129
144
130
- if (mb_strlen ($ strippedContent ) > 75 ) {
145
+ if (isset ($ object ['name ' ])) {
146
+ $ item ['title ' ] = $ object ['name ' ];
147
+ } else if (mb_strlen ($ strippedContent ) > 75 ) {
131
148
$ contentSubstring = mb_substr ($ strippedContent , 0 , mb_strpos (wordwrap ($ strippedContent , 75 ), "\n" ));
132
149
$ item ['title ' ] .= $ contentSubstring . '... ' ;
133
150
} else {
134
151
$ item ['title ' ] .= $ strippedContent ;
135
152
}
136
153
$ item ['uri ' ] = $ object ['id ' ];
154
+ $ item ['timestamp ' ] = $ object ['published ' ];
155
+
156
+ if (!isset ($ object ['attachment ' ])) {
157
+ return $ item ;
158
+ }
137
159
138
160
if (isset ($ object ['attachment ' ]['url ' ])) {
139
161
// Normalize attachment (turn single attachment into array)
@@ -214,6 +236,7 @@ protected function fetchAP($url)
214
236
215
237
// Exclude query string when parsing URL
216
238
'noquery ' => '/https?:\/\/([a-z0-9-\.]{0,})(\/[^#?]+)/ ' ,
239
+ 'nosig ' => '/https?:\/\/([a-z0-9-\.]{0,})(\/[^#?]+)/ ' ,
217
240
];
218
241
219
242
preg_match ($ regex [$ this ->getInput ('signaturetype ' )], $ url , $ matches );
@@ -224,7 +247,7 @@ protected function fetchAP($url)
224
247
];
225
248
$ privateKey = $ this ->getOption ('private_key ' );
226
249
$ keyId = $ this ->getOption ('key_id ' );
227
- if ($ privateKey && $ keyId ) {
250
+ if ($ privateKey && $ keyId && $ this -> getInput ( ' signaturetype ' ) !== ' nosig ' ) {
228
251
$ pkey = openssl_pkey_get_private ('file:// ' . $ privateKey );
229
252
$ toSign = '(request-target): get ' . $ matches [2 ] . "\nhost: " . $ matches [1 ] . "\ndate: " . $ date ;
230
253
$ result = openssl_sign ($ toSign , $ signature , $ pkey , 'RSA-SHA256 ' );
0 commit comments