From 4d36fc3c61319100a4ec2607113fb2f9cbb82264 Mon Sep 17 00:00:00 2001 From: Yannis Vogel Date: Tue, 22 Apr 2025 14:30:26 +0200 Subject: [PATCH] fix remaining phan errors - also cleaned up dummy - added new rewrite for apache for sharedInbox (your.fqdn/inbox) - fixed json-formatting of publicKey when requesting user via api --- README.md | 1 + php/federator/api/fedusers.php | 16 +- php/federator/api/fedusers/inbox.php | 3 +- php/federator/api/v1/dummy.php | 495 ---------------------- php/federator/api/wellknown/webfinger.php | 6 + plugins/federator/mastodon.php | 53 +-- 6 files changed, 50 insertions(+), 524 deletions(-) diff --git a/README.md b/README.md index 5d98edb..3999cf4 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ To configure an apache server, add the following rewrite rules: RewriteCond expr "%{HTTP:content-type} -strcmatch '*application/activity+json*'" RewriteRule ^@(.*)$ /federator.php?_call=fedusers/$1 [L,END] RewriteRule ^users/(.*)$ /federator.php?_call=fedusers/$1 [L,END] + RewriteRule ^inbox[/]?$ /federator.php?_call=fedusers/$1 [L,END] RewriteRule ^api/federator/(.+)$ federator.php?_call=$1 [L,END] RewriteRule ^(\.well-known/.*)$ /federator.php?_call=$1 [L,END] RewriteRule ^(nodeinfo/2\.[01])$ /federator.php?_call=$1 [L,END] diff --git a/php/federator/api/fedusers.php b/php/federator/api/fedusers.php index 4c85117..8fa68be 100644 --- a/php/federator/api/fedusers.php +++ b/php/federator/api/fedusers.php @@ -148,20 +148,32 @@ class FedUsers implements APIInterface if ($user === false || $user->id === null) { throw new \Federator\Exceptions\FileNotFound(); } + $config = $this->main->getConfig(); + $domain = $config['generic']['externaldomain']; + $jsonKey = json_encode($user->publicKey); + if (!is_string($jsonKey)) { + throw new \Federator\Exceptions\FileNotFound(); + } $data = [ 'iconMediaType' => $user->iconMediaType, 'iconURL' => $user->iconURL, 'imageMediaType' => $user->imageMediaType, 'imageURL' => $user->imageURL, - 'fqdn' => $_SERVER['SERVER_NAME'], + 'fqdn' => $domain, 'name' => $user->name, 'username' => $user->id, - 'publickey' => $user->publicKey, + 'publickey' => trim($jsonKey, '"'), 'registered' => gmdate('Y-m-d\TH:i:s\Z', $user->registered), // 2021-03-25T00:00:00Z 'summary' => $user->summary, 'type' => $user->type ]; $this->response = $this->main->renderTemplate('user.json', $data); + // todo remove HTTP again + $this->response = str_replace( + 'https://192.168.178.143/', + 'http://192.168.178.143/', + $this->response + ); return true; } /** diff --git a/php/federator/api/fedusers/inbox.php b/php/federator/api/fedusers/inbox.php index 94abe43..3f36fc0 100644 --- a/php/federator/api/fedusers/inbox.php +++ b/php/federator/api/fedusers/inbox.php @@ -177,7 +177,8 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface $announce->setAActor((string) $activity['actor']) ->setPublished($published !== false ? $published : time()) ->setID((string) $activity['id']) - ->setURL((string) $activity['id']); + ->setURL((string) $activity['id']) + ->addTo("https://www.w3.org/ns/activitystreams#Public"); if (array_key_exists('cc', $activity)) { foreach ($activity['cc'] as $cc) { diff --git a/php/federator/api/v1/dummy.php b/php/federator/api/v1/dummy.php index 68a8dc3..934de18 100644 --- a/php/federator/api/v1/dummy.php +++ b/php/federator/api/v1/dummy.php @@ -58,43 +58,10 @@ class Dummy implements \Federator\Api\APIInterface switch ($paths[2]) { case 'moo': return $this->getDummy(); - case 'sharedInbox': - return $this->getSharedInbox(); default: break; } break; - case 4: - case 5: - switch ($paths[2]) { - case 'inbox': - return $this->getInbox($paths[3]); - case 'follow': - return $this->followAdmin($paths[3]); - case 'users': - switch (sizeof($paths)) { - case 4: - return $this->getUser($paths[3]); - case 5: - switch ($paths[4]) { - case 'inbox': - return $this->getInbox($paths[3]); - case 'outbox': - return $this->getOutbox($paths[3]); - case 'following': - return $this->getFollowing($paths[3]); - case 'followers': - return $this->getFollowing($paths[3]); - default: - break; - } - break; - default: - break; - } - default: - break; - } } break; case 'POST': @@ -103,41 +70,10 @@ class Dummy implements \Federator\Api\APIInterface switch ($paths[2]) { case 'moo': return $this->postDummy(); - case 'sharedInbox': - return $this->postSharedInbox(); default: break; } break; - case 4: - case 5: - switch ($paths[2]) { - case 'inbox': - return $this->postInbox($paths[3]); - case 'follow': - return $this->followAdmin($paths[3]); - case 'users': - switch (sizeof($paths)) { - case 5: - switch ($paths[4]) { - case 'inbox': - return $this->postInbox($paths[3]); - case 'outbox': - return $this->postOutbox($paths[3]); - case 'following': - return $this->postFollowing($paths[3]); - case 'followers': - return $this->postFollowing($paths[3]); - default: - break; - } - break; - default: - break; - } - default: - break; - } } } $this->main->setResponseCode(404); @@ -164,187 +100,6 @@ class Dummy implements \Federator\Api\APIInterface return true; } - - /** - * get function for "/v1/dummy/moo" - * - * @param string $_name user-id which we try to retrieve - * @return bool - */ - public function getUser($_name) - { - error_log("Someone tried to get user: " . $_name); - $user = \Federator\DIO\User::getUserByName( - $this->main->getDatabase(), - $_name, - $this->main->getConnector(), - $this->main->getCache() - ); - if ($user === false || $user->id === null) { - throw new \Federator\Exceptions\FileNotFound(); - } - $publicKeyPem = << $user->iconMediaType, - 'iconURL' => $user->iconURL, - 'imageMediaType' => $user->imageMediaType, - 'imageURL' => $user->imageURL, - 'fqdn' => '192.168.178.143', - 'name' => $user->name, - 'username' => $user->id, - 'publickey' => "", - 'registered' => gmdate('Y-m-d\TH:i:s\Z', $user->registered), // 2021-03-25T00:00:00Z - 'summary' => $user->summary, - 'type' => "Person" - ]; - $this->message = $this->main->renderTemplate('user.json', $data); - $fixedJson = str_replace( - 'https://192.168.178.143/users/yannis_test', - 'https://192.168.178.143/api/federator/v1/dummy/users/yannis_test', - $this->message - ); - $fixedJson = preg_replace( - '/"id"\s*:\s*"[^"]+"/', - '"id": "http://192.168.178.143/api/federator/v1/dummy/users/yannis_test"', - $fixedJson - ); - $fixedJson = preg_replace( - '/"inbox"\s*:\s*"[^"]+"/', - '"inbox": "http://192.168.178.143/users/yannis_test/inbox"', - $fixedJson - ); - $fixedJson = str_replace( - 'https://192.168.178.143', - 'http://192.168.178.143', - $fixedJson - ); - $fixedJson = str_replace( - '""', - $publicKeyPemJsonSafe, - $fixedJson - ); - $fixedJson = str_replace( - 'http://192.168.178.143/inbox', - 'http://192.168.178.143/api/federator/fedusers/inbox', - $fixedJson - ); - // $fixedJson = str_replace( - // 'http://192.168.178.143/api/federator/v1/dummy/users/yannis_test@192.168.178.143#main-key', - // 'http://192.168.178.143/api/federator/v1/dummy/users/yannis_test@192.168.178.143/key#main-key', - // $fixedJson - // ); - - $this->message = $fixedJson; - return true; - } - - public function followAdmin($_name) - { - $user = \Federator\DIO\User::getUserByName( - $this->main->getDatabase(), - $_name, - $this->main->getConnector(), - $this->main->getCache() - ); - if ($user === false || $user->id === null) { - throw new \Federator\Exceptions\FileNotFound(); - } - - - // Step 2: Prepare the Follow activity - $activityData = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'type' => 'Follow', - 'actor' => 'http://192.168.178.143/api/federator/v1/dummy/users/' . $_name, // Your user URL - 'object' => 'http://mastodon.local/users/admin' // Mastodon user to follow (e.g., http://mastodon.local/users/admin) - ]; - - // Step 3: Send the Follow activity to Mastodon - $inboxUrl = 'http://mastodon.local/users/admin/inbox'; // The inbox URL for the Mastodon user - $this->sendFollowActivityToMastodon($inboxUrl, $activityData); - - $this->message = "\n"; - return true; - } - - private function sendFollowActivityToMastodon($url, $data) - { - $json = json_encode($data, JSON_UNESCAPED_SLASHES); - $digest = 'SHA-256=' . base64_encode(hash('sha256', $json, true)); - $date = gmdate('D, d M Y H:i:s') . ' GMT'; - $parsed = parse_url($url); - $host = $parsed['host']; - $path = $parsed['path']; - - // Build the signature string - $signatureString = "(request-target): post {$path}\n" . - "host: {$host}\n" . - "date: {$date}\n" . - "digest: {$digest}"; - - // Load your private key here (replace with how you store keys) - $privateKey = "REDACTED"; // OR from DB - $pkeyId = openssl_pkey_get_private($privateKey); - - if (!$pkeyId) { - throw new \Exception('Invalid private key'); - } - - openssl_sign($signatureString, $signature, $pkeyId, OPENSSL_ALGO_SHA256); - $signature_b64 = base64_encode($signature); - - // Build keyId (public key ID from your actor object) - $keyId = 'http://192.168.178.143/api/federator/v1/dummy/users/yannis_test#main-key'; - - $signatureHeader = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"'; - - $headers = [ - 'Host: ' . $host, - 'Date: ' . $date, - 'Digest: ' . $digest, - 'Content-Type: application/activity+json', - 'Signature: ' . $signatureHeader, - 'Accept: application/activity+json', - ]; - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $json); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - - $response = curl_exec($ch); - $err = curl_error($ch); - curl_close($ch); - - $response = curl_exec($ch); - curl_close($ch); - - // Log the response for debugging if needed - if ($response === false) { - error_log("Failed to send Follow activity to Mastodon: " . curl_error($ch)); - echo "Failed to send Follow activity to Mastodon: " . curl_error($ch); - } else { - $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - if ($httpcode !== 200 && $httpcode !== 202) { - throw new \Exception("Unexpected HTTP code $httpcode: $response"); - } - error_log("Follow activity response from Mastodon: " . $response); - echo "Follow activity response from Mastodon: " . $response; - } - } - /** * post function for /v1/dummy/moo" * @@ -355,256 +110,6 @@ PEM; return $this->getDummy(); } - public function getInbox($_name) - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto GET Inbox Raw ===\n" . $_rawInput); - error_log("=== Masto GET Inbox JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/inbox_log.txt', - time() . ": ==== Masto GET Inbox RAW ====\n" . $_rawInput . "\n\n==== Masto GET Inbox JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - - public function getOutbox($_name) - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto GET Outbox Raw ===\n" . $_rawInput); - error_log("=== Masto GET Outbox JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/outbox_log.txt', - time() . ": ==== Masto GET Outbox RAW ====\n" . $_rawInput . "\n\n==== Masto GET Outbox JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - - public function getFollowing($_name) - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto GET Following Raw ===\n" . $_rawInput); - error_log("=== Masto GET Following JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/following_log.txt', - time() . ": ==== Masto GET Following RAW ====\n" . $_rawInput . "\n\n==== Masto GET Following JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - - public function getFollowers($_name) - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto GET Followers Raw ===\n" . $_rawInput); - error_log("=== Masto GET Followers JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/followers_log.txt', - time() . ": ==== Masto GET Followers RAW ====\n" . $_rawInput . "\n\n==== Masto GET Followers JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - - public function getSharedInbox() - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto GET SharedInbox Raw ===\n" . $_rawInput); - error_log("=== Masto GET SharedInbox JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/sharedInbox_log.txt', - time() . ": ==== Masto GET SharedInbox RAW ====\n" . $_rawInput . "\n\n==== Masto GET SharedInbox JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - - public function postInbox($_name) - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto POST Inbox Raw ===\n" . $_rawInput); - error_log("=== Masto POST Inbox JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/inbox_log.txt', - time() . ": ==== Masto POST Inbox RAW ====\n" . $_rawInput . "\n\n==== Masto POST Inbox JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - - public function postOutbox($_name) - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto POST Outbox Raw ===\n" . $_rawInput); - error_log("=== Masto POST Outbox JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/outbox_log.txt', - time() . ": ==== Masto POST Outbox RAW ====\n" . $_rawInput . "\n\n==== Masto POST Outbox JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - - public function postFollowing($_name) - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto POST Following Raw ===\n" . $_rawInput); - error_log("=== Masto POST Following JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/following_log.txt', - time() . ": ==== Masto POST Following RAW ====\n" . $_rawInput . "\n\n==== Masto POST Following JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - - public function postFollowers($_name) - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto POST Followers Raw ===\n" . $_rawInput); - error_log("=== Masto POST Followers JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/followers_log.txt', - time() . ": ==== Masto POST Followers RAW ====\n" . $_rawInput . "\n\n==== Masto POST Followers JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - - public function postSharedInbox() - { - $_rawInput = file_get_contents('php://input'); - - // Decode if it's JSON (as Mastodon usually sends JSON) - $jsonData = json_decode($_rawInput, true); - error_log("=== Masto POST SharedInbox Raw ===\n" . $_rawInput); - error_log("=== Masto POST SharedInbox JSON ===\n" . print_r($jsonData, true)); - // Save the raw input and parsed JSON to a file for inspection - file_put_contents( - __DIR__ . '/sharedInbox_log.txt', - time() . ": ==== Masto POST SharedInbox RAW ====\n" . $_rawInput . "\n\n==== Masto POST SharedInbox JSON ====\n" . print_r($jsonData, true) . "\n\n", - FILE_APPEND - ); - - $successMsg = json_encode([ - 'status' => 'received', - ]); - - if ($successMsg !== false) { - $this->message = $successMsg; - } - return true; - } - /** * get internal represenation as json string * diff --git a/php/federator/api/wellknown/webfinger.php b/php/federator/api/wellknown/webfinger.php index 4eaef6a..f2c9a40 100644 --- a/php/federator/api/wellknown/webfinger.php +++ b/php/federator/api/wellknown/webfinger.php @@ -67,6 +67,12 @@ class WebFinger 'domain' => $domain, ]; $response = $this->main->renderTemplate('webfinger_acct.json', $data); + // todo remove HTTP again + $response = str_replace( + 'https://192.168.178.143', + 'http://192.168.178.143', + $response + ); $this->wellKnown->setResponse($response); return true; } diff --git a/plugins/federator/mastodon.php b/plugins/federator/mastodon.php index 689abbf..64495de 100644 --- a/plugins/federator/mastodon.php +++ b/plugins/federator/mastodon.php @@ -60,7 +60,7 @@ class Mastodon implements Connector */ public function getRemotePostsByUser($userId, $min = null, $max = null) { - if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) === 1) { + if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) == 1) { $name = $matches[1]; } else { $name = $userId; @@ -78,7 +78,7 @@ class Mastodon implements Connector [$outboxResponse, $outboxInfo] = \Federator\Main::getFromRemote($remoteURL, ['Accept: application/activity+json']); - if ($outboxInfo['http_code'] !== 200) { + if ($outboxInfo['http_code'] != 200) { echo "MastodonConnector::getRemotePostsByUser HTTP call failed for remoteURL $remoteURL\n"; return false; } @@ -128,7 +128,7 @@ class Mastodon implements Connector } elseif (isset($outbox['first'])) { $firstURL = is_array($outbox['first']) ? $outbox['first']['id'] : $outbox['first']; [$pageResponse, $pageInfo] = \Federator\Main::getFromRemote($firstURL, ['Accept: application/activity+json']); - if ($pageInfo['http_code'] !== 200) { + if ($pageInfo['http_code'] != 200) { return false; } $page = json_decode($pageResponse, true); @@ -146,11 +146,12 @@ class Mastodon implements Connector } $obj = $activity['object']; + $published = strtotime($activity['published'] ?? $obj['published'] ?? 'now'); $create = new \Federator\Data\ActivityPub\Common\Create(); - $create->setID($activity['id']) + $create->setAActor($activity['actor']) + ->setID($activity['id']) ->setURL($activity['id']) - ->setPublished(strtotime($activity['published'] ?? $obj['published'] ?? 'now')) - ->setAActor($activity['actor']) + ->setPublished($published !== false ? $published : time()) ->addCC($activity['cc']); if (array_key_exists('to', $activity)) { @@ -161,9 +162,10 @@ class Mastodon implements Connector switch ($obj['type']) { case 'Note': + $published = strtotime($obj['published'] ?? 'now'); $apNote = new \Federator\Data\ActivityPub\Common\Note(); $apNote->setID($obj['id']) - ->setPublished(strtotime($obj['published'] ?? 'now')) + ->setPublished($published !== false ? $published : time()) ->setContent($obj['content'] ?? '') ->setSummary($obj['summary']) ->setURL($obj['url']) @@ -220,6 +222,11 @@ class Mastodon implements Connector $objectURL = is_array($activity['object']) ? $activity['object']['id'] : $activity['object']; + if (!is_string($objectURL)) { + // Handle error or cast/normalize + throw new \InvalidArgumentException('objectURL must be a string, got ' . gettype($objectURL)); + } + // Fetch the original object (e.g. Note) [$response, $info] = \Federator\Main::getFromRemote($objectURL, ['Accept: application/activity+json']); if ($info['http_code'] != 200) { @@ -232,11 +239,15 @@ class Mastodon implements Connector break; } + assert(is_string($activity['id'])); + assert(is_string($activity['actor'])); + + $published = strtotime((string) $activity['published']); $announce = new \Federator\Data\ActivityPub\Common\Announce(); - $announce->setID($activity['id']) + $announce->setAActor($activity['actor']) + ->setPublished($published !== false ? $published : time()) + ->setID($activity['id']) ->setURL($activity['id']) - ->setPublished(strtotime($activity['published'] ?? 'now')) - ->setAActor($activity['actor']) ->addTo("https://www.w3.org/ns/activitystreams#Public"); if (array_key_exists('to', $activity)) { @@ -248,10 +259,11 @@ class Mastodon implements Connector // Optionally parse the shared object as a Note or something else switch ($objData['type']) { case 'Note': + $published = strtotime($objData['published'] ?? 'now'); $note = new \Federator\Data\ActivityPub\Common\Note(); $note->setID($objData['id']) ->setContent($objData['content'] ?? '') - ->setPublished(strtotime($objData['published'] ?? 'now')) + ->setPublished($published !== false ? $published : time()) ->setURL($objData['url'] ?? $objData['id']) ->setAttributedTo($objData['attributedTo'] ?? null) ->addTo("https://www.w3.org/ns/activitystreams#Public"); @@ -277,18 +289,6 @@ class Mastodon implements Connector return $posts; } - /** - * Get the MIME type of a remote file by its URL. - * - * @param string $_url The URL of the remote file. - * @return string|false The MIME type if found, or false on failure. - */ - public function getRemoteMimeType($url) - { - $headers = get_headers($url, 1); - return $headers['Content-Type'] ?? 'unknown'; - } - /** * get statistics from remote system * @@ -321,7 +321,7 @@ class Mastodon implements Connector public function getRemoteUserByName(string $_name) { // Validate username (Mastodon usernames can include @ and domain parts) - if (preg_match("/^[a-zA-Z0-9_\-@.]+$/", $_name) !== 1) { + if (preg_match("/^[a-zA-Z0-9_\-@.]+$/", $_name) != 1) { return false; } @@ -334,7 +334,7 @@ class Mastodon implements Connector [$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers); // Handle HTTP errors - if ($info['http_code'] !== 200) { + if ($info['http_code'] != 200) { return false; } @@ -344,6 +344,7 @@ class Mastodon implements Connector return false; } + $createdAt = strtotime($r['created_at']); // Map response to User object $user = new \Federator\Data\User(); $user->externalid = (string) $r['id']; // Mastodon uses numeric IDs @@ -354,7 +355,7 @@ class Mastodon implements Connector $user->name = $r['display_name'] ?: $r['username']; $user->summary = $r['note']; $user->type = 'Person'; // Mastodon profiles are ActivityPub "Person" objects - $user->registered = strtotime($r['created_at']); + $user->registered = $createdAt !== false ? $createdAt : time(); return $user; }