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
// 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
} else {
$host = 'dummy';

View file

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

View file

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

View file

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