fixes for outbox

- mastodon now properly sets available type-attributes
- database should now save user with his platform (as that's the actual unique identifier, plain username isn't)
- properly retrieve items from mastodon and print them on api-call
(example call for mastodon: federator/fedusers/gutenberg_org@mastodon.social/outbox?page=0)
This commit is contained in:
Yannis Vogel 2025-04-09 12:52:27 +02:00
parent 530caa7ea6
commit 1a7b8264a1
No known key found for this signature in database
4 changed files with 120 additions and 58 deletions

View file

@ -78,7 +78,7 @@ class Api extends Main
$host = 'dummy'; // fallback $host = 'dummy'; // fallback
// Check if path matches something like fedusers/username@domain.tld // Check if path matches something like fedusers/username@domain.tld
if (preg_match("#^fedusers/([^@]+)@([^/]+)$#", $this->path, $matches) === 1) { if (preg_match("#^fedusers/([^@]+)@([^/]+)/.*$#", $this->path, $matches) === 1) {
$host = strtolower($matches[2]); // extract domain $host = strtolower($matches[2]); // extract domain
} else { } else {
$host = 'dummy'; $host = 'dummy';

View file

@ -40,6 +40,7 @@ class Outbox implements \Federator\Api\FedUsers\FedUsersInterface
{ {
$dbh = $this->main->getDatabase(); $dbh = $this->main->getDatabase();
$cache = $this->main->getCache(); $cache = $this->main->getCache();
// get user // get user
$user = \Federator\DIO\User::getUserByName( $user = \Federator\DIO\User::getUserByName(
$dbh, $dbh,
@ -69,10 +70,6 @@ class Outbox implements \Federator\Api\FedUsers\FedUsersInterface
if ($page !== '') { if ($page !== '') {
$id .= '?page=' . urlencode($page); $id .= '?page=' . urlencode($page);
} }
if ($page === '' || $outbox->count() == 0) {
$outbox->setFirst($id);
$outbox->setLast($id . '&min=0');
}
if (sizeof($items)>0) { if (sizeof($items)>0) {
$newestId = $items[0]->getPublished(); $newestId = $items[0]->getPublished();
$oldestId = $items[sizeof($items)-1]->getPublished(); $oldestId = $items[sizeof($items)-1]->getPublished();

View file

@ -244,7 +244,7 @@ class ContentNation implements Connector
*/ */
public function getRemoteUserByName(string $_name) public function getRemoteUserByName(string $_name)
{ {
if (preg_match("#^([^@]+)@([^/]+)$#", $_name, $matches) === 1) { if (preg_match("#^([^@]+)@([^/]+)#", $_name, $matches) === 1) {
$_name = $matches[1]; $_name = $matches[1];
} }
// validate name // validate name

View file

@ -54,7 +54,8 @@ class Mastodon implements Connector
* *
* @return string * @return string
*/ */
public function getHost() { public function getHost()
{
return "mastodon.social"; return "mastodon.social";
} }
@ -68,22 +69,35 @@ class Mastodon implements Connector
*/ */
public function getRemotePostsByUser($userId, $min = null, $max = null) public function getRemotePostsByUser($userId, $min = null, $max = null)
{ {
$remoteURL = $this->service . '/users/' . $userId . '/outbox'; if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) === 1) {
$name = $matches[1];
$items = []; }
$remoteURL = $this->service . '/users/' . $name . '/outbox';
do {
if ($min !== '') { if ($min !== '') {
$remoteURL .= '&minTS=' . urlencode($min); $remoteURL .= '&minTS=' . urlencode($min);
} }
if ($max !== '') { if ($max !== '') {
$remoteURL .= '&maxTS=' . urlencode($max); $remoteURL .= '&maxTS=' . urlencode($max);
} }
$items = [];
[$outboxResponse, $outboxInfo] = \Federator\Main::getFromRemote($remoteURL, ['Accept: application/activity+json']);
if ($outboxInfo['http_code'] !== 200) {
echo "MastodonConnector::getRemotePostsByUser HTTP call failed for remoteURL $remoteURL\n";
return false;
}
$outbox = json_decode($outboxResponse, true);
// retrieve ALL outbox items - disabled for now
/* do {
// Fetch the current page of items (first or subsequent pages) // Fetch the current page of items (first or subsequent pages)
[$outboxResponse, $outboxInfo] = \Federator\Main::getFromRemote($remoteURL, ['Accept: application/activity+json']); [$outboxResponse, $outboxInfo] = \Federator\Main::getFromRemote($remoteURL, ['Accept: application/activity+json']);
if ($outboxInfo['http_code'] !== 200) { if ($outboxInfo['http_code'] !== 200) {
echo "aborting"; echo "MastodonConnector::getRemotePostsByUser HTTP call failed for remoteURL $remoteURL\n";
return false; return false;
} }
@ -94,18 +108,25 @@ class Mastodon implements Connector
$items = array_merge($items, $outbox['orderedItems']); $items = array_merge($items, $outbox['orderedItems']);
} }
// Step 4: Use 'last' URL to determine pagination // Use 'next' or 'last' URL to determine pagination
if (isset($outbox['last'])) { if (isset($outbox['next'])) {
// The 'last' URL will usually have a query string that includes min_id for the next set of results $remoteURL = $outbox['next']; // Update target URL for the next page of items
$remoteURL = $outbox['last']; // Update to the last URL for the next page of items } else if (isset($outbox['last'])) {
$remoteURL = $outbox['last']; // Update target URL for the next page of items
} else { } else {
$remoteURL = "";
break; // No more pages, exit pagination break; // No more pages, exit pagination
} }
if ($remoteURL !== "") {
if ($min !== '') {
$remoteURL .= '&minTS=' . urlencode($min);
}
if ($max !== '') {
$remoteURL .= '&maxTS=' . urlencode($max);
}
}
} while (!empty($outbox['last'])); // Continue fetching until no 'last' URL } while ($remoteURL !== ""); // Continue fetching until no 'last' URL */
$items = [];
// Follow `first` page (or get orderedItems directly) // Follow `first` page (or get orderedItems directly)
if (isset($outbox['orderedItems'])) { if (isset($outbox['orderedItems'])) {
@ -122,27 +143,49 @@ class Mastodon implements Connector
// Convert to internal representation // Convert to internal representation
$posts = []; $posts = [];
$host = $_SERVER['SERVER_NAME'];
foreach ($items as $activity) { foreach ($items as $activity) {
if (!isset($activity['type']) || $activity['type'] !== 'Create' || !isset($activity['object'])) { switch ($activity['type']) {
continue; // Skip non-Create activities case 'Create':
if (!isset($activity['object'])) {
break;
} }
$obj = $activity['object']; $obj = $activity['object'];
$create = new \Federator\Data\ActivityPub\Common\Create(); $create = new \Federator\Data\ActivityPub\Common\Create();
$create->setID($activity['id']) $create->setID($activity['id'])
->setURL($activity['id'])
->setPublished(strtotime($activity['published'] ?? $obj['published'] ?? 'now')) ->setPublished(strtotime($activity['published'] ?? $obj['published'] ?? 'now'))
->setAActor($activity['actor']) ->setAActor($activity['actor'])
->addTo("https://www.w3.org/ns/activitystreams#Public"); ->addCC($activity['cc']);
// Handle main Note content if (array_key_exists('to', $activity)) {
if ($obj['type'] === 'Note') { foreach ($activity['to'] as $to) {
$create->addTo($to);
}
}
switch ($obj['type']) {
case 'Note':
$apNote = new \Federator\Data\ActivityPub\Common\Note(); $apNote = new \Federator\Data\ActivityPub\Common\Note();
$apNote->setID($obj['id']) $apNote->setID($obj['id'])
->setPublished(strtotime($obj['published'] ?? 'now')) ->setPublished(strtotime($obj['published'] ?? 'now'))
->setContent($obj['content'] ?? '') ->setContent($obj['content'] ?? '')
->setSummary($obj['summary'])
->setURL($obj['url'])
->setAttributedTo($obj['attributedTo'] ?? $activity['actor']) ->setAttributedTo($obj['attributedTo'] ?? $activity['actor'])
->addTo("https://www.w3.org/ns/activitystreams#Public"); ->addTo("https://www.w3.org/ns/activitystreams#Public");
if (!empty($obj['sensitive'])) {
$apNote->setSensitive($obj['sensitive']);
}
if (!empty($obj['conversation'])) {
$apNote->setConversation($obj['conversation']);
}
if (!empty($obj['inReplyTo'])) {
$apNote->setInReplyTo($obj['inReplyTo']);
}
// Handle attachments // Handle attachments
if (!empty($obj['attachment']) && is_array($obj['attachment'])) { if (!empty($obj['attachment']) && is_array($obj['attachment'])) {
foreach ($obj['attachment'] as $media) { foreach ($obj['attachment'] as $media) {
@ -154,10 +197,32 @@ class Mastodon implements Connector
} }
} }
if (array_key_exists('tag', $obj)) {
foreach ($obj['tag'] as $tag) {
$tagName = is_array($tag) && isset($tag['name']) ? $tag['name'] : (string) $tag;
$cleanName = preg_replace('/\s+/', '', ltrim($tagName, '#')); // Remove space and leading #
$tagObj = new \Federator\Data\ActivityPub\Common\Tag();
$tagObj->setName('#' . $cleanName)
->setHref("https://$host/tags/" . urlencode($cleanName))
->setType('Hashtag');
$apNote->addTag($tagObj);
}
}
$create->setObject($apNote); $create->setObject($apNote);
break;
default:
echo "MastodonConnector::getRemotePostsByUser we currently don't support the obj type " . $obj['type'] . "\n";
break;
} }
$posts[] = $create; $posts[] = $create;
break;
default:
echo "MastodonConnector::getRemotePostsByUser we currently don't support the activity type " . $activity['type'] . "\n";
break;
}
} }
return $posts; return $posts;