diff --git a/php/federator/api/fedusers/inbox.php b/php/federator/api/fedusers/inbox.php index 9ca8ca4..fc88d16 100644 --- a/php/federator/api/fedusers/inbox.php +++ b/php/federator/api/fedusers/inbox.php @@ -194,6 +194,8 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface } } + $users = array_unique($users); // remove duplicates + if (empty($users)) { // todo remove after proper implementation, debugging for now $rootDir = PROJECT_ROOT . '/'; // Save the raw input and parsed JSON to a file for inspection @@ -229,15 +231,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface } } - try { - $articleId = \Federator\DIO\Posts::getOriginalArticleId($dbh, $inboxActivity); - if ($articleId !== null) { - $connector->sendActivity($user, $inboxActivity); - } - } catch (\Throwable $e) { - error_log("Inbox::postForUser Error sending activity to connector. Exception: " . $e->getMessage()); - return false; - } + $connector->sendActivity($user, $inboxActivity); return "success"; } @@ -334,7 +328,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface $cache ); if ($recipient === null || $recipient->id === null) { - error_log("Inbox::postForUser couldn't find user: $_recipientId"); + error_log("Inbox::postForUser couldn't find recipient: $_recipientId"); return false; } diff --git a/php/federator/api/v1/newcontent.php b/php/federator/api/v1/newcontent.php index 816afe0..e261d02 100644 --- a/php/federator/api/v1/newcontent.php +++ b/php/federator/api/v1/newcontent.php @@ -194,6 +194,9 @@ class NewContent implements \Federator\Api\APIInterface $users[] = $user->id; } } + + $users = array_unique($users); // remove duplicates + if (empty($users)) { // todo remove after proper implementation, debugging for now $rootDir = PROJECT_ROOT . '/'; // Save the raw input and parsed JSON to a file for inspection diff --git a/plugins/federator/contentnation.php b/plugins/federator/contentnation.php index d639a90..3a542eb 100644 --- a/plugins/federator/contentnation.php +++ b/plugins/federator/contentnation.php @@ -314,6 +314,7 @@ class ContentNation implements Connector } $user = new \Federator\Data\User(); $user->externalid = $_name; + $user->id = $_name; $user->iconMediaType = $r['iconMediaType']; $user->iconURL = $r['iconURL']; $user->imageMediaType = $r['imageMediaType']; @@ -675,8 +676,9 @@ class ContentNation implements Connector public function sendActivity($sender, $activity) { $targetUrl = $this->service; + $targetRequestType = 'post'; // Default request type // Convert ActivityPub activity to ContentNation JSON format and retrieve target url - $jsonData = self::activityToJson($this->main->getDatabase(), $this->service, $activity, $targetUrl); + $jsonData = self::activityToJson($this->main->getDatabase(), $this->service, $activity, $targetUrl, $targetRequestType); if ($jsonData === false) { error_log("ContentNation::sendActivity failed to convert activity to JSON"); @@ -702,7 +704,7 @@ class ContentNation implements Connector $path = $parsed['path']; // Build the signature string - $signatureString = "(request-target): post {$path}\n" . + $signatureString = "(request-target): $targetRequestType {$path}\n" . "host: {$extHost}\n" . "date: {$date}\n" . "digest: {$digest}"; @@ -740,7 +742,16 @@ class ContentNation implements Connector ]; curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); + switch ($targetRequestType) { + case 'post': + curl_setopt($ch, CURLOPT_POST, true); + break; + case 'delete': + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + break; + default: + throw new \Exception("ContentNation::sendActivity Unsupported target request type: $targetRequestType"); + } curl_setopt($ch, CURLOPT_POSTFIELDS, $json); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); @@ -765,11 +776,13 @@ class ContentNation implements Connector * @param string $serviceUrl the service URL * @param \Federator\Data\ActivityPub\Common\Activity $activity the activity * @param string $targetUrl the target URL for the activity + * @param string $targetRequestType the target request type (e.g., 'post', 'delete', etc.) * @return array|false the json data or false on failure */ - private static function activityToJson($dbh, $serviceUrl, \Federator\Data\ActivityPub\Common\Activity $activity, string &$targetUrl) + private function activityToJson($dbh, $serviceUrl, \Federator\Data\ActivityPub\Common\Activity $activity, string &$targetUrl, string &$targetRequestType) { $type = strtolower($activity->getType()); + $targetRequestType = 'post'; // Default request type switch ($type) { case 'create': case 'update': @@ -820,6 +833,50 @@ class ContentNation implements Connector } break; + case 'follow': + $profileUrl = $activity->getObject(); + if (!is_string($profileUrl)) { + error_log("ContentNation::activityToJson Invalid profile URL: " . json_encode($profileUrl)); + return false; + } + $receiverName = basename((string) (parse_url($profileUrl, PHP_URL_PATH) ?? '')); + $ourDomain = parse_url($profileUrl, PHP_URL_HOST); + if ($receiverName === "" || $ourDomain === "") { + error_log("ContentNation::activityToJson no profileName or domain found for object url: " . $profileUrl); + return false; + } + $receiver = $receiverName; + try { + $localUser = \Federator\DIO\User::getUserByName( + $dbh, + $receiver, + $this, + null + ); + } catch (\Throwable $e) { + error_log("ContentNation::activityToJson get user by name: " . $receiver . ". Exception: " . $e->getMessage()); + return false; + } + if ($localUser === null || $localUser->id === null) { + error_log("ContentNation::activityToJson couldn't find user: $receiver"); + return false; + } + $targetUrl = $serviceUrl . '/api/profile/' . $localUser->id . '/fedfollow'; + $type = 'follow'; + $actor = $activity->getAActor(); + $fedUser = \Federator\DIO\FedUser::getUserByName( + $dbh, + $actor, + null + ); + $from = $fedUser->id; + return [ + 'type' => $type, + 'id' => $activity->getID(), + 'from' => $from, + 'to' => $localUser->id, + ]; + case 'like': case 'dislike': $articleId = \Federator\DIO\Posts::getOriginalArticleId($dbh, $activity); @@ -859,6 +916,56 @@ class ContentNation implements Connector if (is_object($object)) { $objType = strtolower($object->getType()); switch ($objType) { + case 'follow': + $profileUrl = $object->getObject(); + if (!is_string($profileUrl)) { + error_log("ContentNation::activityToJson Invalid profile URL: " . json_encode($profileUrl)); + return false; + } + $receiverName = basename((string) (parse_url($profileUrl, PHP_URL_PATH) ?? '')); + $ourDomain = parse_url($profileUrl, PHP_URL_HOST); + if ($receiverName === "" || $ourDomain === "") { + error_log("ContentNation::activityToJson no profileName or domain found for object url: " . $profileUrl); + return false; + } + $receiver = $receiverName; + try { + $localUser = \Federator\DIO\User::getUserByName( + $dbh, + $receiver, + $this, + null + ); + } catch (\Throwable $e) { + error_log("ContentNation::activityToJson get user by name: " . $receiver . ". Exception: " . $e->getMessage()); + return false; + } + if ($localUser === null || $localUser->id === null) { + error_log("ContentNation::activityToJson couldn't find user: $receiver"); + return false; + } + $targetUrl = $serviceUrl . '/api/profile/' . $localUser->id . '/fedfollow'; + $type = 'follow'; + if ($object instanceof \Federator\Data\ActivityPub\Common\Activity) { + $actor = $object->getAActor(); + if ($actor !== '') { + $fedUser = \Federator\DIO\FedUser::getUserByName( + $dbh, + $actor, + null + ); + $from = $fedUser->id; + $targetRequestType = 'delete'; + return [ + 'type' => $type, + 'id' => $object->getID(), + 'from' => $from, + 'to' => $localUser->id, + ]; + } + } + return false; + case 'like': case 'dislike': $articleId = \Federator\DIO\Posts::getOriginalArticleId($dbh, $activity);